mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2025-03-11 15:34:56 +00:00
Merge branch 'public-single-node' into pmm-6401-read-prometheus-data-files
This commit is contained in:
commit
3a15bc761b
39 changed files with 1851 additions and 1216 deletions
|
@ -3,7 +3,6 @@ package remotewrite
|
|||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
|
@ -118,9 +117,9 @@ func (rctx *relabelCtx) applyRelabeling(tss []prompbmarshal.TimeSeries, extraLab
|
|||
for j := range tmpLabels {
|
||||
label := &tmpLabels[j]
|
||||
if label.Name == "__name__" {
|
||||
label.Value = unsupportedPromChars.ReplaceAllString(label.Value, "_")
|
||||
label.Value = promrelabel.SanitizeName(label.Value)
|
||||
} else {
|
||||
label.Name = unsupportedPromChars.ReplaceAllString(label.Name, "_")
|
||||
label.Name = promrelabel.SanitizeName(label.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -138,9 +137,6 @@ func (rctx *relabelCtx) applyRelabeling(tss []prompbmarshal.TimeSeries, extraLab
|
|||
return tssDst
|
||||
}
|
||||
|
||||
// See https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels
|
||||
var unsupportedPromChars = regexp.MustCompile(`[^a-zA-Z0-9_:]`)
|
||||
|
||||
type relabelCtx struct {
|
||||
// pool for labels, which are used during the relabeling.
|
||||
labels []prompbmarshal.Label
|
||||
|
|
|
@ -120,7 +120,7 @@ name: <string>
|
|||
# params:
|
||||
# nocache: ["1"] # disable caching for vmselect
|
||||
# denyPartialResponse: ["true"] # fail if one or more vmstorage nodes returned an error
|
||||
# extra_label: ["env=dev"] # apply additional label filter "env=dev" for all requests
|
||||
# extra_label: ["env=dev"] # apply additional label filter "env=dev" for all requests
|
||||
# see more details at https://docs.victoriametrics.com#prometheus-querying-api-enhancements
|
||||
params:
|
||||
[ <string>: [<string>, ...]]
|
||||
|
@ -131,7 +131,7 @@ params:
|
|||
# headers:
|
||||
# - "CustomHeader: foo"
|
||||
# - "CustomHeader2: bar"
|
||||
# Headers set via this param have priority over headers set via `-datasource.headers` flag.
|
||||
# Headers set via this param have priority over headers set via `-datasource.headers` flag.
|
||||
headers:
|
||||
[ <string>, ...]
|
||||
|
||||
|
@ -185,8 +185,8 @@ expr: <string>
|
|||
[ for: <duration> | default = 0s ]
|
||||
|
||||
# Whether to print debug information into logs.
|
||||
# Information includes alerts state changes and requests sent to the datasource.
|
||||
# Please note, that if rule's query params contain sensitive
|
||||
# Information includes alerts state changes and requests sent to the datasource.
|
||||
# Please note, that if rule's query params contain sensitive
|
||||
# information - it will be printed to logs.
|
||||
# Is applicable to alerting rules only.
|
||||
[ debug: <bool> | default = false ]
|
||||
|
@ -200,9 +200,9 @@ annotations:
|
|||
[ <labelname>: <tmpl_string> ]
|
||||
```
|
||||
|
||||
#### Templating
|
||||
#### Templating
|
||||
|
||||
It is allowed to use [Go templating](https://golang.org/pkg/text/template/) in annotations to format data, iterate over
|
||||
It is allowed to use [Go templating](https://golang.org/pkg/text/template/) in annotations to format data, iterate over
|
||||
or execute expressions.
|
||||
The following variables are available in templating:
|
||||
|
||||
|
@ -224,7 +224,7 @@ and [reusable templates](#reusable-templates).
|
|||
#### Reusable templates
|
||||
|
||||
Like in Alertmanager you can define [reusable templates](https://prometheus.io/docs/prometheus/latest/configuration/template_examples/#defining-reusable-templates)
|
||||
to share same templates across annotations. Just define the templates in a file and
|
||||
to share same templates across annotations. Just define the templates in a file and
|
||||
set the path via `-rule.templates` flag.
|
||||
|
||||
For example, template `grafana.filter` can be defined as following:
|
||||
|
@ -418,8 +418,8 @@ To avoid recording rules results and alerts state duplication in VictoriaMetrics
|
|||
don't forget to configure [deduplication](https://docs.victoriametrics.com/Single-server-VictoriaMetrics.html#deduplication).
|
||||
The recommended value for `-dedup.minScrapeInterval` must be greater or equal to vmalert's `evaluation_interval`.
|
||||
If you observe inconsistent or "jumping" values in series produced by vmalert, try disabling `-datasource.queryTimeAlignment`
|
||||
command line flag. Because of alignment, two or more vmalert HA pairs will produce results with the same timestamps.
|
||||
But due of backfilling (data delivered to the datasource with some delay) values of such results may differ,
|
||||
command line flag. Because of alignment, two or more vmalert HA pairs will produce results with the same timestamps.
|
||||
But due of backfilling (data delivered to the datasource with some delay) values of such results may differ,
|
||||
which would affect deduplication logic and result into "jumping" datapoints.
|
||||
|
||||
Alertmanager will automatically deduplicate alerts with identical labels, so ensure that
|
||||
|
@ -439,8 +439,8 @@ This ability allows to aggregate data. For example, the following rule will calc
|
|||
metric `http_requests` on the `5m` interval:
|
||||
|
||||
```yaml
|
||||
- record: http_requests:avg5m
|
||||
expr: avg_over_time(http_requests[5m])
|
||||
- record: http_requests:avg5m
|
||||
expr: avg_over_time(http_requests[5m])
|
||||
```
|
||||
|
||||
Every time this rule will be evaluated, `vmalert` will backfill its results as a new time series `http_requests:avg5m`
|
||||
|
@ -454,9 +454,9 @@ This ability allows to downsample data. For example, the following config will e
|
|||
groups:
|
||||
- name: my_group
|
||||
interval: 5m
|
||||
rules:
|
||||
rules:
|
||||
- record: http_requests:avg5m
|
||||
expr: avg_over_time(http_requests[5m])
|
||||
expr: avg_over_time(http_requests[5m])
|
||||
```
|
||||
|
||||
Ability of `vmalert` to be configured with different `datasource.url` and `remoteWrite.url` allows
|
||||
|
@ -474,7 +474,7 @@ or reducing resolution) and push results to "cold" cluster.
|
|||
|
||||
```
|
||||
./bin/vmalert -rule=downsampling-rules.yml \ # Path to the file with rules configuration. Supports wildcard
|
||||
-datasource.url=http://raw-cluster-vmselect:8481/select/0/prometheus # vmselect addr for executing recordi ng rules expressions
|
||||
-datasource.url=http://raw-cluster-vmselect:8481/select/0/prometheus # vmselect addr for executing recording rules expressions
|
||||
-remoteWrite.url=http://aggregated-cluster-vminsert:8480/insert/0/prometheus # vminsert addr to persist recording rules results
|
||||
```
|
||||
|
||||
|
@ -496,7 +496,7 @@ we recommend using [vmagent](https://docs.victoriametrics.com/vmagent.html) as f
|
|||
|
||||
In this topology, `vmalert` is configured to persist rule results to `vmagent`. And `vmagent`
|
||||
is configured to fan-out received data to two or more destinations.
|
||||
Using `vmagent` as a proxy provides additional benefits such as
|
||||
Using `vmagent` as a proxy provides additional benefits such as
|
||||
[data persisting when storage is unreachable](https://docs.victoriametrics.com/vmagent.html#replication-and-high-availability),
|
||||
or time series modification via [relabeling](https://docs.victoriametrics.com/vmagent.html#relabeling).
|
||||
|
||||
|
@ -657,7 +657,7 @@ Note that too small value passed to `-search.latencyOffset` may lead to incomple
|
|||
|
||||
Try the following recommendations in such cases:
|
||||
|
||||
* Always configure group's `evaluationInterval` to be bigger or equal to `scrape_interval` at which metrics
|
||||
* Always configure group's `evaluationInterval` to be bigger or equal to `scrape_interval` at which metrics
|
||||
are delivered to the datasource;
|
||||
* If you know in advance, that data in datasource is delayed - try changing vmalert's `-datasource.lookback`
|
||||
command-line flag to add a time shift for evaluations;
|
||||
|
@ -671,7 +671,7 @@ after multiple consecutive evaluations, and at each evaluation their expression
|
|||
becomes false, then alert's state resets to the initial state.
|
||||
|
||||
If `-remoteWrite.url` command-line flag is configured, vmalert will persist alert's state in form of time series
|
||||
`ALERTS` and `ALERTS_FOR_STATE` to the specified destination. Such time series can be then queried via
|
||||
`ALERTS` and `ALERTS_FOR_STATE` to the specified destination. Such time series can be then queried via
|
||||
[vmui](https://docs.victoriametrics.com/Single-server-VictoriaMetrics.html#vmui) or Grafana to track how alerts state
|
||||
changed in time.
|
||||
|
||||
|
@ -680,9 +680,9 @@ on `/vmalert/groups` page and check the `Last updates` section:
|
|||
|
||||
<img alt="vmalert state" src="vmalert_state.png">
|
||||
|
||||
Rows in the section represent ordered rule evaluations and their results. The column `curl` contains an example of
|
||||
Rows in the section represent ordered rule evaluations and their results. The column `curl` contains an example of
|
||||
HTTP request sent by vmalert to the `-datasource.url` during evaluation. If specific state shows that there were
|
||||
no samples returned and curl command returns data - then it is very likely there was no data in datasource on the
|
||||
no samples returned and curl command returns data - then it is very likely there was no data in datasource on the
|
||||
moment when rule was evaluated.
|
||||
|
||||
vmalert also alows configuring more detailed logging for specific rule. Just set `debug: true` in rule's configuration
|
||||
|
@ -762,11 +762,11 @@ The shortlist of configuration flags is the following:
|
|||
-datasource.maxIdleConnections int
|
||||
Defines the number of idle (keep-alive connections) to each configured datasource. Consider setting this value equal to the value: groups_total * group.concurrency. Too low a value may result in a high number of sockets in TIME_WAIT state. (default 100)
|
||||
-datasource.oauth2.clientID string
|
||||
Optional OAuth2 clientID to use for -datasource.url.
|
||||
Optional OAuth2 clientID to use for -datasource.url.
|
||||
-datasource.oauth2.clientSecret string
|
||||
Optional OAuth2 clientSecret to use for -datasource.url.
|
||||
-datasource.oauth2.clientSecretFile string
|
||||
Optional OAuth2 clientSecretFile to use for -datasource.url.
|
||||
Optional OAuth2 clientSecretFile to use for -datasource.url.
|
||||
-datasource.oauth2.scopes string
|
||||
Optional OAuth2 scopes to use for -datasource.url. Scopes must be delimited by ';'
|
||||
-datasource.oauth2.tokenUrl string
|
||||
|
@ -960,7 +960,7 @@ The shortlist of configuration flags is the following:
|
|||
-remoteRead.oauth2.scopes string
|
||||
Optional OAuth2 scopes to use for -remoteRead.url. Scopes must be delimited by ';'.
|
||||
-remoteRead.oauth2.tokenUrl string
|
||||
Optional OAuth2 tokenURL to use for -remoteRead.url.
|
||||
Optional OAuth2 tokenURL to use for -remoteRead.url.
|
||||
-remoteRead.showURL
|
||||
Whether to show -remoteRead.url in the exported metrics. It is hidden by default, since it can contain sensitive info such as auth key
|
||||
-remoteRead.tlsCAFile string
|
||||
|
@ -1121,7 +1121,7 @@ and [DNS](https://prometheus.io/docs/prometheus/latest/configuration/configurati
|
|||
For example:
|
||||
|
||||
```
|
||||
static_configs:
|
||||
static_configs:
|
||||
- targets:
|
||||
- localhost:9093
|
||||
- localhost:9095
|
||||
|
@ -1130,7 +1130,7 @@ consul_sd_configs:
|
|||
- server: localhost:8500
|
||||
services:
|
||||
- alertmanager
|
||||
|
||||
|
||||
dns_sd_configs:
|
||||
- names:
|
||||
- my.domain.com
|
||||
|
|
|
@ -165,11 +165,15 @@ func (ar *AlertingRule) logDebugf(at time.Time, a *notifier.Alert, format string
|
|||
}
|
||||
|
||||
type labelSet struct {
|
||||
// origin labels from series
|
||||
// used for templating
|
||||
// origin labels extracted from received time series
|
||||
// plus extra labels (group labels, service labels like alertNameLabel).
|
||||
// in case of conflicts, origin labels from time series preferred.
|
||||
// used for templating annotations
|
||||
origin map[string]string
|
||||
// processed labels with additional data
|
||||
// used as Alert labels
|
||||
// processed labels includes origin labels
|
||||
// plus extra labels (group labels, service labels like alertNameLabel).
|
||||
// in case of conflicts, extra labels are preferred.
|
||||
// used as labels attached to notifier.Alert and ALERTS series written to remote storage.
|
||||
processed map[string]string
|
||||
}
|
||||
|
||||
|
@ -177,7 +181,7 @@ type labelSet struct {
|
|||
// to labelSet which contains original and processed labels.
|
||||
func (ar *AlertingRule) toLabels(m datasource.Metric, qFn templates.QueryFn) (*labelSet, error) {
|
||||
ls := &labelSet{
|
||||
origin: make(map[string]string, len(m.Labels)),
|
||||
origin: make(map[string]string),
|
||||
processed: make(map[string]string),
|
||||
}
|
||||
for _, l := range m.Labels {
|
||||
|
@ -199,14 +203,23 @@ func (ar *AlertingRule) toLabels(m datasource.Metric, qFn templates.QueryFn) (*l
|
|||
}
|
||||
for k, v := range extraLabels {
|
||||
ls.processed[k] = v
|
||||
if _, ok := ls.origin[k]; !ok {
|
||||
ls.origin[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// set additional labels to identify group and rule name
|
||||
if ar.Name != "" {
|
||||
ls.processed[alertNameLabel] = ar.Name
|
||||
if _, ok := ls.origin[alertNameLabel]; !ok {
|
||||
ls.origin[alertNameLabel] = ar.Name
|
||||
}
|
||||
}
|
||||
if !*disableAlertGroupLabel && ar.GroupName != "" {
|
||||
ls.processed[alertGroupNameLabel] = ar.GroupName
|
||||
if _, ok := ls.origin[alertGroupNameLabel]; !ok {
|
||||
ls.origin[alertGroupNameLabel] = ar.GroupName
|
||||
}
|
||||
}
|
||||
return ls, nil
|
||||
}
|
||||
|
|
|
@ -700,14 +700,26 @@ func TestAlertingRule_Template(t *testing.T) {
|
|||
expAlerts map[uint64]*notifier.Alert
|
||||
}{
|
||||
{
|
||||
newTestRuleWithLabels("common", "region", "east"),
|
||||
&AlertingRule{
|
||||
Name: "common",
|
||||
Labels: map[string]string{
|
||||
"region": "east",
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
"summary": `{{ $labels.alertname }}: Too high connection number for "{{ $labels.instance }}"`,
|
||||
},
|
||||
alerts: make(map[uint64]*notifier.Alert),
|
||||
state: newRuleState(),
|
||||
},
|
||||
[]datasource.Metric{
|
||||
metricWithValueAndLabels(t, 1, "instance", "foo"),
|
||||
metricWithValueAndLabels(t, 1, "instance", "bar"),
|
||||
},
|
||||
map[uint64]*notifier.Alert{
|
||||
hash(map[string]string{alertNameLabel: "common", "region": "east", "instance": "foo"}): {
|
||||
Annotations: map[string]string{},
|
||||
Annotations: map[string]string{
|
||||
"summary": `common: Too high connection number for "foo"`,
|
||||
},
|
||||
Labels: map[string]string{
|
||||
alertNameLabel: "common",
|
||||
"region": "east",
|
||||
|
@ -715,7 +727,9 @@ func TestAlertingRule_Template(t *testing.T) {
|
|||
},
|
||||
},
|
||||
hash(map[string]string{alertNameLabel: "common", "region": "east", "instance": "bar"}): {
|
||||
Annotations: map[string]string{},
|
||||
Annotations: map[string]string{
|
||||
"summary": `common: Too high connection number for "bar"`,
|
||||
},
|
||||
Labels: map[string]string{
|
||||
alertNameLabel: "common",
|
||||
"region": "east",
|
||||
|
|
|
@ -9,11 +9,12 @@ groups:
|
|||
denyPartialResponse: ["true"]
|
||||
rules:
|
||||
- alert: Conns
|
||||
expr: sum(vm_tcplistener_conns) by(instance) > 1
|
||||
expr: vm_tcplistener_conns > 0
|
||||
for: 3m
|
||||
debug: true
|
||||
annotations:
|
||||
summary: Too high connection number for {{$labels.instance}}
|
||||
labels: "Available labels: {{ $labels }}"
|
||||
summary: Too high connection number for {{ $labels.instance }}
|
||||
{{ with printf "sum(vm_tcplistener_conns{instance=%q})" .Labels.instance | query }}
|
||||
{{ . | first | value }}
|
||||
{{ end }}
|
||||
|
|
|
@ -6,13 +6,12 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promrelabel"
|
||||
)
|
||||
|
||||
var (
|
||||
allowedNames = regexp.MustCompile("^[a-zA-Z][a-zA-Z0-9_:]*$")
|
||||
allowedFirstChar = regexp.MustCompile("^[a-zA-Z]")
|
||||
replaceChars = regexp.MustCompile("[^a-zA-Z0-9_:]")
|
||||
allowedTagKeys = regexp.MustCompile("^[a-zA-Z][a-zA-Z0-9_]*$")
|
||||
)
|
||||
|
||||
func convertDuration(duration string) (time.Duration, error) {
|
||||
|
@ -180,13 +179,8 @@ func modifyData(msg Metric, normalize bool) (Metric, error) {
|
|||
}
|
||||
/*
|
||||
replace bad characters in metric name with _ per the data model
|
||||
only replace if needed to reduce string processing time
|
||||
*/
|
||||
if !allowedNames.MatchString(name) {
|
||||
finalMsg.Metric = replaceChars.ReplaceAllString(name, "_")
|
||||
} else {
|
||||
finalMsg.Metric = name
|
||||
}
|
||||
finalMsg.Metric = promrelabel.SanitizeName(name)
|
||||
// replace bad characters in tag keys with _ per the data model
|
||||
for key, value := range msg.Tags {
|
||||
// if normalization requested, lowercase the key and value
|
||||
|
@ -196,11 +190,8 @@ func modifyData(msg Metric, normalize bool) (Metric, error) {
|
|||
}
|
||||
/*
|
||||
replace all explicitly bad characters with _
|
||||
only replace if needed to reduce string processing time
|
||||
*/
|
||||
if !allowedTagKeys.MatchString(key) {
|
||||
key = replaceChars.ReplaceAllString(key, "_")
|
||||
}
|
||||
key = promrelabel.SanitizeName(key)
|
||||
// tags that start with __ are considered custom stats for internal prometheus stuff, we should drop them
|
||||
if !strings.HasPrefix(key, "__") {
|
||||
finalMsg.Tags[key] = value
|
||||
|
|
|
@ -176,6 +176,8 @@ curl 'http://localhost:8431/api/v1/labels' -H 'Authorization: Bearer eyJhbGciOiJ
|
|||
The shortlist of configuration flags include the following:
|
||||
|
||||
```console
|
||||
-auth.httpHeader
|
||||
HTTP header name to look for JWT authorization token
|
||||
-clusterMode
|
||||
enable this for the cluster version
|
||||
-datasource.appendTypePrefix
|
||||
|
|
|
@ -3,7 +3,6 @@ package relabel
|
|||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
||||
|
@ -115,9 +114,9 @@ func (ctx *Ctx) ApplyRelabeling(labels []prompb.Label) []prompb.Label {
|
|||
for i := range tmpLabels {
|
||||
label := &tmpLabels[i]
|
||||
if label.Name == "__name__" {
|
||||
label.Value = unsupportedPromChars.ReplaceAllString(label.Value, "_")
|
||||
label.Value = promrelabel.SanitizeName(label.Value)
|
||||
} else {
|
||||
label.Name = unsupportedPromChars.ReplaceAllString(label.Name, "_")
|
||||
label.Name = promrelabel.SanitizeName(label.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -149,6 +148,3 @@ func (ctx *Ctx) ApplyRelabeling(labels []prompb.Label) []prompb.Label {
|
|||
}
|
||||
|
||||
var metricsDropped = metrics.NewCounter(`vm_relabel_metrics_dropped_total`)
|
||||
|
||||
// See https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels
|
||||
var unsupportedPromChars = regexp.MustCompile(`[^a-zA-Z0-9_:]`)
|
||||
|
|
|
@ -104,6 +104,9 @@ func removeGroupTags(metricName *storage.MetricName, modifier *metricsql.Modifie
|
|||
|
||||
func aggrFuncExt(afe func(tss []*timeseries, modifier *metricsql.ModifierExpr) []*timeseries, argOrig []*timeseries,
|
||||
modifier *metricsql.ModifierExpr, maxSeries int, keepOriginal bool) ([]*timeseries, error) {
|
||||
// Remove empty time series, e.g. series with all NaN samples,
|
||||
// since such series are ignored by aggregate functions.
|
||||
argOrig = removeEmptySeries(argOrig)
|
||||
arg := copyTimeseriesMetricNames(argOrig, keepOriginal)
|
||||
|
||||
// Perform grouping.
|
||||
|
|
|
@ -2281,6 +2281,27 @@ func TestExecSuccess(t *testing.T) {
|
|||
resultExpected := []netstorage.Result{r}
|
||||
f(q, resultExpected)
|
||||
})
|
||||
t.Run(`limit_offset NaN`, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// q returns 3 time series, where foo=3 contains only NaN values
|
||||
// limit_offset suppose to apply offset for non-NaN series only
|
||||
q := `limit_offset(1, 1, sort_by_label_desc((
|
||||
label_set(time()*1, "foo", "1"),
|
||||
label_set(time()*2, "foo", "2"),
|
||||
label_set(time()*3, "foo", "3"),
|
||||
) < 3000, "foo"))`
|
||||
r := netstorage.Result{
|
||||
MetricName: metricNameExpected,
|
||||
Values: []float64{1000, 1200, 1400, 1600, 1800, 2000},
|
||||
Timestamps: timestampsExpected,
|
||||
}
|
||||
r.MetricName.Tags = []storage.Tag{{
|
||||
Key: []byte("foo"),
|
||||
Value: []byte("1"),
|
||||
}}
|
||||
resultExpected := []netstorage.Result{r}
|
||||
f(q, resultExpected)
|
||||
})
|
||||
t.Run(`sum(label_graphite_group)`, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
q := `sort(sum by (__name__) (
|
||||
|
@ -5480,6 +5501,12 @@ func TestExecSuccess(t *testing.T) {
|
|||
resultExpected := []netstorage.Result{r}
|
||||
f(q, resultExpected)
|
||||
})
|
||||
t.Run(`any(empty-series)`, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
q := `any(label_set(time()<0, "foo", "bar"))`
|
||||
resultExpected := []netstorage.Result{}
|
||||
f(q, resultExpected)
|
||||
})
|
||||
t.Run(`group() by (test)`, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
q := `group((
|
||||
|
|
|
@ -1854,7 +1854,9 @@ func transformLimitOffset(tfa *transformFuncArg) ([]*timeseries, error) {
|
|||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot obtain offset arg: %w", err)
|
||||
}
|
||||
rvs := args[2]
|
||||
// removeEmptySeries so offset will be calculated after empty series
|
||||
// were filtered out.
|
||||
rvs := removeEmptySeries(args[2])
|
||||
if len(rvs) >= offset {
|
||||
rvs = rvs[offset:]
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ The following tip changes can be tested by building VictoriaMetrics components f
|
|||
|
||||
**Update note 2:** [vmalert](https://docs.victoriametrics.com/vmalert.html) changes default value for command-line flag `-datasource.queryStep` from `0s` to `5m`. The change supposed to improve reliability of the rules evaluation when evaluation interval is lower than scraping interval.
|
||||
|
||||
* FEATURE: improve [relabeling](https://docs.victoriametrics.com/vmagent.html#relabeling) performance by up to 3x if non-trivial `regex` values are used.
|
||||
* FEATURE: sanitize metric names for data ingested via [DataDog protocol](https://docs.victoriametrics.com/#how-to-send-data-from-datadog-agent) according to [DataDog metric naming](https://docs.datadoghq.com/metrics/custom_metrics/#naming-custom-metrics). The behaviour can be disabled by passing `-datadog.sanitizeMetricName=false` command-line flag. Thanks to @PerGon for [the pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/3105).
|
||||
* FEATURE: add `-usePromCompatibleNaming` command-line flag to [vmagent](https://docs.victoriametrics.com/vmagent.html), to single-node VictoriaMetrics and to `vminsert` component of VictoriaMetrics cluster. This flag can be used for normalizing the ingested metric names and label names to [Prometheus-compatible form](https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels). If this flag is set, then all the chars unsupported by Prometheus are replaced with `_` chars in metric names and labels of the ingested samples. See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3113).
|
||||
* FEATURE: accept whitespace in metric names and tags ingested via [Graphite plaintext protocol](https://docs.victoriametrics.com/#how-to-send-data-from-graphite-compatible-agents-such-as-statsd) according to [the specs](https://graphite.readthedocs.io/en/latest/tags.html). See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3102).
|
||||
|
@ -30,12 +31,17 @@ The following tip changes can be tested by building VictoriaMetrics components f
|
|||
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): add `top queries` tab, which shows various stats for recently executed queries. See [these docs](https://docs.victoriametrics.com/#top-queries) and [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2707).
|
||||
* FEATURE: [vmalert](https://docs.victoriametrics.com/vmalert.html): add `debug` mode to the alerting rule settings for printing additional information into logs during evaluation. See `debug` param in [alerting rule config](https://docs.victoriametrics.com/vmalert.html#alerting-rules).
|
||||
* FEATURE: [vmalert](https://docs.victoriametrics.com/vmalert.html): add experimental feature for displaying last 10 states of the rule (recording or alerting) evaluation. The state is available on the Rule page, which can be opened by clicking on `Details` link next to Rule's name on the `/groups` page.
|
||||
* FEATURE: [vmalert](https://docs.victoriametrics.com/vmalert.html): allow using extra labels in annotiations. See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3013).
|
||||
* FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): minimize the time needed for reading large responses from scrape targets in [stream parsing mode](https://docs.victoriametrics.com/vmagent.html#stream-parsing-mode). This should reduce scrape durations for such targets as [kube-state-metrics](https://github.com/kubernetes/kube-state-metrics) running in a big Kubernetes cluster.
|
||||
* FEATURE: [MetricsQL](https://docs.victoriametrics.com/MetricsQL.html): add [sort_by_label_numeric](https://docs.victoriametrics.com/MetricsQL.html#sort_by_label_numeric) and [sort_by_label_numeric_desc](https://docs.victoriametrics.com/MetricsQL.html#sort_by_label_numeric_desc) functions for [numeric sort](https://www.gnu.org/software/coreutils/manual/html_node/Version-sort-is-not-the-same-as-numeric-sort.html) of input time series by the specified labels. See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2938).
|
||||
* FEATURE: [vmbackup](https://docs.victoriametrics.com/vmbackup.html) and [vmrestore](https://docs.victoriametrics.com/vmrestore.html): retry GCS operations for up to 3 minutes on temporary failures. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3147).
|
||||
* FEATURE: [vmgateway](https://docs.victoriametrics.com/vmgateway.html): add ability to extract JWT authorization token from non-standard HTTP header by passing it via `-auth.httpHeader` command-line flag. See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3054).
|
||||
|
||||
* BUGFIX: [vmagent](https://docs.victoriametrics.com/vmagent.html): properly encode query params for aws signed requests, use `%20` instead of `+` as api requires. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3171).
|
||||
* BUGFIX: [vmagent](https://docs.victoriametrics.com/vmagent.html): properly parse relabel config when regex ending with escaped `$`. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3131).
|
||||
* BUGFIX: [MetricsQL](https://docs.victoriametrics.com/MetricsQL.html): properly calculate `rate_over_sum(m[d])` as `sum_over_time(m[d])/d`. Previously the `sum_over_time(m[d])` could be improperly divided by smaller than `d` time range. See [rate_over_sum() docs](https://docs.victoriametrics.com/MetricsQL.html#rate_over_sum) and [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3045).
|
||||
* BUGFIX: [MetricsQL](https://docs.victoriametrics.com/MetricsQL.html): properly calculate `increase(m[d])` over slow-changing counters with values smaller than 100. Previously [increase](https://docs.victoriametrics.com/MetricsQL.html#increase) could return unexpectedly big results in this case. See [the related issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/962) and [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/3163).
|
||||
* BUGFIX: [MetricsQL](https://docs.victoriametrics.com/MetricsQL.html): ignore empty series when applying [limit_offset](https://docs.victoriametrics.com/MetricsQL.html#limit_offset). It should improve queries with additional filters by value in expressions like `limit_offset(1,1, foo > 1)`.
|
||||
* BUGFIX: [VictoriaMetrics cluster](https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html): properly calculate query results at `vmselect`. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3067). The issue has been introduced in [v1.81.0](https://docs.victoriametrics.com/CHANGELOG.html#v1810).
|
||||
* BUGFIX: [VictoriaMetrics cluster](https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html): log clear error when multiple identical `-storageNode` command-line flags are passed to `vmselect` or to `vminsert`. Previously these components were crashed with cryptic panic `metric ... is already registered` in this case. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3076).
|
||||
* BUGFIX: [vmui](https://docs.victoriametrics.com/#vmui): fix `RangeError: Maximum call stack size exceeded` error when the query returns too many data points at `Table` view. See [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/3092/files).
|
||||
|
|
|
@ -208,7 +208,7 @@ See more details about cardinality limiter in [these docs](https://docs.victoria
|
|||
|
||||
## Troubleshooting
|
||||
|
||||
See [trobuleshooting docs](https://docs.victoriametrics.com/Troubleshooting.html).
|
||||
See [troubleshooting docs](https://docs.victoriametrics.com/Troubleshooting.html).
|
||||
|
||||
## Readonly mode
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
Binary file not shown.
Before Width: | Height: | Size: 265 KiB After Width: | Height: | Size: 145 KiB |
|
@ -124,7 +124,7 @@ name: <string>
|
|||
# params:
|
||||
# nocache: ["1"] # disable caching for vmselect
|
||||
# denyPartialResponse: ["true"] # fail if one or more vmstorage nodes returned an error
|
||||
# extra_label: ["env=dev"] # apply additional label filter "env=dev" for all requests
|
||||
# extra_label: ["env=dev"] # apply additional label filter "env=dev" for all requests
|
||||
# see more details at https://docs.victoriametrics.com#prometheus-querying-api-enhancements
|
||||
params:
|
||||
[ <string>: [<string>, ...]]
|
||||
|
@ -135,7 +135,7 @@ params:
|
|||
# headers:
|
||||
# - "CustomHeader: foo"
|
||||
# - "CustomHeader2: bar"
|
||||
# Headers set via this param have priority over headers set via `-datasource.headers` flag.
|
||||
# Headers set via this param have priority over headers set via `-datasource.headers` flag.
|
||||
headers:
|
||||
[ <string>, ...]
|
||||
|
||||
|
@ -189,8 +189,8 @@ expr: <string>
|
|||
[ for: <duration> | default = 0s ]
|
||||
|
||||
# Whether to print debug information into logs.
|
||||
# Information includes alerts state changes and requests sent to the datasource.
|
||||
# Please note, that if rule's query params contain sensitive
|
||||
# Information includes alerts state changes and requests sent to the datasource.
|
||||
# Please note, that if rule's query params contain sensitive
|
||||
# information - it will be printed to logs.
|
||||
# Is applicable to alerting rules only.
|
||||
[ debug: <bool> | default = false ]
|
||||
|
@ -204,9 +204,9 @@ annotations:
|
|||
[ <labelname>: <tmpl_string> ]
|
||||
```
|
||||
|
||||
#### Templating
|
||||
#### Templating
|
||||
|
||||
It is allowed to use [Go templating](https://golang.org/pkg/text/template/) in annotations to format data, iterate over
|
||||
It is allowed to use [Go templating](https://golang.org/pkg/text/template/) in annotations to format data, iterate over
|
||||
or execute expressions.
|
||||
The following variables are available in templating:
|
||||
|
||||
|
@ -228,7 +228,7 @@ and [reusable templates](#reusable-templates).
|
|||
#### Reusable templates
|
||||
|
||||
Like in Alertmanager you can define [reusable templates](https://prometheus.io/docs/prometheus/latest/configuration/template_examples/#defining-reusable-templates)
|
||||
to share same templates across annotations. Just define the templates in a file and
|
||||
to share same templates across annotations. Just define the templates in a file and
|
||||
set the path via `-rule.templates` flag.
|
||||
|
||||
For example, template `grafana.filter` can be defined as following:
|
||||
|
@ -422,8 +422,8 @@ To avoid recording rules results and alerts state duplication in VictoriaMetrics
|
|||
don't forget to configure [deduplication](https://docs.victoriametrics.com/Single-server-VictoriaMetrics.html#deduplication).
|
||||
The recommended value for `-dedup.minScrapeInterval` must be greater or equal to vmalert's `evaluation_interval`.
|
||||
If you observe inconsistent or "jumping" values in series produced by vmalert, try disabling `-datasource.queryTimeAlignment`
|
||||
command line flag. Because of alignment, two or more vmalert HA pairs will produce results with the same timestamps.
|
||||
But due of backfilling (data delivered to the datasource with some delay) values of such results may differ,
|
||||
command line flag. Because of alignment, two or more vmalert HA pairs will produce results with the same timestamps.
|
||||
But due of backfilling (data delivered to the datasource with some delay) values of such results may differ,
|
||||
which would affect deduplication logic and result into "jumping" datapoints.
|
||||
|
||||
Alertmanager will automatically deduplicate alerts with identical labels, so ensure that
|
||||
|
@ -443,8 +443,8 @@ This ability allows to aggregate data. For example, the following rule will calc
|
|||
metric `http_requests` on the `5m` interval:
|
||||
|
||||
```yaml
|
||||
- record: http_requests:avg5m
|
||||
expr: avg_over_time(http_requests[5m])
|
||||
- record: http_requests:avg5m
|
||||
expr: avg_over_time(http_requests[5m])
|
||||
```
|
||||
|
||||
Every time this rule will be evaluated, `vmalert` will backfill its results as a new time series `http_requests:avg5m`
|
||||
|
@ -458,9 +458,9 @@ This ability allows to downsample data. For example, the following config will e
|
|||
groups:
|
||||
- name: my_group
|
||||
interval: 5m
|
||||
rules:
|
||||
rules:
|
||||
- record: http_requests:avg5m
|
||||
expr: avg_over_time(http_requests[5m])
|
||||
expr: avg_over_time(http_requests[5m])
|
||||
```
|
||||
|
||||
Ability of `vmalert` to be configured with different `datasource.url` and `remoteWrite.url` allows
|
||||
|
@ -478,7 +478,7 @@ or reducing resolution) and push results to "cold" cluster.
|
|||
|
||||
```
|
||||
./bin/vmalert -rule=downsampling-rules.yml \ # Path to the file with rules configuration. Supports wildcard
|
||||
-datasource.url=http://raw-cluster-vmselect:8481/select/0/prometheus # vmselect addr for executing recordi ng rules expressions
|
||||
-datasource.url=http://raw-cluster-vmselect:8481/select/0/prometheus # vmselect addr for executing recording rules expressions
|
||||
-remoteWrite.url=http://aggregated-cluster-vminsert:8480/insert/0/prometheus # vminsert addr to persist recording rules results
|
||||
```
|
||||
|
||||
|
@ -500,7 +500,7 @@ we recommend using [vmagent](https://docs.victoriametrics.com/vmagent.html) as f
|
|||
|
||||
In this topology, `vmalert` is configured to persist rule results to `vmagent`. And `vmagent`
|
||||
is configured to fan-out received data to two or more destinations.
|
||||
Using `vmagent` as a proxy provides additional benefits such as
|
||||
Using `vmagent` as a proxy provides additional benefits such as
|
||||
[data persisting when storage is unreachable](https://docs.victoriametrics.com/vmagent.html#replication-and-high-availability),
|
||||
or time series modification via [relabeling](https://docs.victoriametrics.com/vmagent.html#relabeling).
|
||||
|
||||
|
@ -661,7 +661,7 @@ Note that too small value passed to `-search.latencyOffset` may lead to incomple
|
|||
|
||||
Try the following recommendations in such cases:
|
||||
|
||||
* Always configure group's `evaluationInterval` to be bigger or equal to `scrape_interval` at which metrics
|
||||
* Always configure group's `evaluationInterval` to be bigger or equal to `scrape_interval` at which metrics
|
||||
are delivered to the datasource;
|
||||
* If you know in advance, that data in datasource is delayed - try changing vmalert's `-datasource.lookback`
|
||||
command-line flag to add a time shift for evaluations;
|
||||
|
@ -675,7 +675,7 @@ after multiple consecutive evaluations, and at each evaluation their expression
|
|||
becomes false, then alert's state resets to the initial state.
|
||||
|
||||
If `-remoteWrite.url` command-line flag is configured, vmalert will persist alert's state in form of time series
|
||||
`ALERTS` and `ALERTS_FOR_STATE` to the specified destination. Such time series can be then queried via
|
||||
`ALERTS` and `ALERTS_FOR_STATE` to the specified destination. Such time series can be then queried via
|
||||
[vmui](https://docs.victoriametrics.com/Single-server-VictoriaMetrics.html#vmui) or Grafana to track how alerts state
|
||||
changed in time.
|
||||
|
||||
|
@ -684,9 +684,9 @@ on `/vmalert/groups` page and check the `Last updates` section:
|
|||
|
||||
<img alt="vmalert state" src="vmalert_state.png">
|
||||
|
||||
Rows in the section represent ordered rule evaluations and their results. The column `curl` contains an example of
|
||||
Rows in the section represent ordered rule evaluations and their results. The column `curl` contains an example of
|
||||
HTTP request sent by vmalert to the `-datasource.url` during evaluation. If specific state shows that there were
|
||||
no samples returned and curl command returns data - then it is very likely there was no data in datasource on the
|
||||
no samples returned and curl command returns data - then it is very likely there was no data in datasource on the
|
||||
moment when rule was evaluated.
|
||||
|
||||
vmalert also alows configuring more detailed logging for specific rule. Just set `debug: true` in rule's configuration
|
||||
|
@ -766,11 +766,11 @@ The shortlist of configuration flags is the following:
|
|||
-datasource.maxIdleConnections int
|
||||
Defines the number of idle (keep-alive connections) to each configured datasource. Consider setting this value equal to the value: groups_total * group.concurrency. Too low a value may result in a high number of sockets in TIME_WAIT state. (default 100)
|
||||
-datasource.oauth2.clientID string
|
||||
Optional OAuth2 clientID to use for -datasource.url.
|
||||
Optional OAuth2 clientID to use for -datasource.url.
|
||||
-datasource.oauth2.clientSecret string
|
||||
Optional OAuth2 clientSecret to use for -datasource.url.
|
||||
-datasource.oauth2.clientSecretFile string
|
||||
Optional OAuth2 clientSecretFile to use for -datasource.url.
|
||||
Optional OAuth2 clientSecretFile to use for -datasource.url.
|
||||
-datasource.oauth2.scopes string
|
||||
Optional OAuth2 scopes to use for -datasource.url. Scopes must be delimited by ';'
|
||||
-datasource.oauth2.tokenUrl string
|
||||
|
@ -964,7 +964,7 @@ The shortlist of configuration flags is the following:
|
|||
-remoteRead.oauth2.scopes string
|
||||
Optional OAuth2 scopes to use for -remoteRead.url. Scopes must be delimited by ';'.
|
||||
-remoteRead.oauth2.tokenUrl string
|
||||
Optional OAuth2 tokenURL to use for -remoteRead.url.
|
||||
Optional OAuth2 tokenURL to use for -remoteRead.url.
|
||||
-remoteRead.showURL
|
||||
Whether to show -remoteRead.url in the exported metrics. It is hidden by default, since it can contain sensitive info such as auth key
|
||||
-remoteRead.tlsCAFile string
|
||||
|
@ -1125,7 +1125,7 @@ and [DNS](https://prometheus.io/docs/prometheus/latest/configuration/configurati
|
|||
For example:
|
||||
|
||||
```
|
||||
static_configs:
|
||||
static_configs:
|
||||
- targets:
|
||||
- localhost:9093
|
||||
- localhost:9095
|
||||
|
@ -1134,7 +1134,7 @@ consul_sd_configs:
|
|||
- server: localhost:8500
|
||||
services:
|
||||
- alertmanager
|
||||
|
||||
|
||||
dns_sd_configs:
|
||||
- names:
|
||||
- my.domain.com
|
||||
|
|
|
@ -180,6 +180,8 @@ curl 'http://localhost:8431/api/v1/labels' -H 'Authorization: Bearer eyJhbGciOiJ
|
|||
The shortlist of configuration flags include the following:
|
||||
|
||||
```console
|
||||
-auth.httpHeader
|
||||
HTTP header name to look for JWT authorization token
|
||||
-clusterMode
|
||||
enable this for the cluster version
|
||||
-datasource.appendTypePrefix
|
||||
|
|
|
@ -41,6 +41,9 @@ func signRequestWithTime(req *http.Request, service, region, payloadHash string,
|
|||
datestamp := t.Format("20060102")
|
||||
canonicalURL := uri.Path
|
||||
canonicalQS := uri.Query().Encode()
|
||||
// Replace "%20" with "+" according to AWS requirements.
|
||||
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3171
|
||||
canonicalQS = strings.ReplaceAll(canonicalQS, "+", "%20")
|
||||
|
||||
canonicalHeaders := fmt.Sprintf("host:%s\nx-amz-date:%s\n", uri.Host, amzdate)
|
||||
signedHeaders := "host;x-amz-date"
|
||||
|
|
48
lib/bytesutil/fast_string_matcher.go
Normal file
48
lib/bytesutil/fast_string_matcher.go
Normal file
|
@ -0,0 +1,48 @@
|
|||
package bytesutil
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// FastStringMatcher implements fast matcher for strings.
|
||||
//
|
||||
// It caches string match results and returns them back on the next calls
|
||||
// without calling the matchFunc, which may be expensive.
|
||||
type FastStringMatcher struct {
|
||||
m atomic.Value
|
||||
mLen uint64
|
||||
|
||||
matchFunc func(s string) bool
|
||||
}
|
||||
|
||||
// NewFastStringMatcher creates new matcher, which applies matchFunc to strings passed to Match()
|
||||
//
|
||||
// matchFunc must return the same result for the same input.
|
||||
func NewFastStringMatcher(matchFunc func(s string) bool) *FastStringMatcher {
|
||||
var fsm FastStringMatcher
|
||||
fsm.m.Store(&sync.Map{})
|
||||
fsm.matchFunc = matchFunc
|
||||
return &fsm
|
||||
}
|
||||
|
||||
// Match applies matchFunc to s and returns the result.
|
||||
func (fsm *FastStringMatcher) Match(s string) bool {
|
||||
m := fsm.m.Load().(*sync.Map)
|
||||
v, ok := m.Load(s)
|
||||
if ok {
|
||||
// Fast path - s match result is found in the cache.
|
||||
bp := v.(*bool)
|
||||
return *bp
|
||||
}
|
||||
// Slow path - run matchFunc for s and store the result in the cache.
|
||||
b := fsm.matchFunc(s)
|
||||
bp := &b
|
||||
m.Store(s, bp)
|
||||
n := atomic.AddUint64(&fsm.mLen, 1)
|
||||
if n > 100e3 {
|
||||
atomic.StoreUint64(&fsm.mLen, 0)
|
||||
fsm.m.Store(&sync.Map{})
|
||||
}
|
||||
return b
|
||||
}
|
25
lib/bytesutil/fast_string_matcher_test.go
Normal file
25
lib/bytesutil/fast_string_matcher_test.go
Normal file
|
@ -0,0 +1,25 @@
|
|||
package bytesutil
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFastStringMatcher(t *testing.T) {
|
||||
fsm := NewFastStringMatcher(func(s string) bool {
|
||||
return strings.HasPrefix(s, "foo")
|
||||
})
|
||||
f := func(s string, resultExpected bool) {
|
||||
t.Helper()
|
||||
for i := 0; i < 10; i++ {
|
||||
result := fsm.Match(s)
|
||||
if result != resultExpected {
|
||||
t.Fatalf("unexpected result for Match(%q) at iteration %d; got %v; want %v", s, i, result, resultExpected)
|
||||
}
|
||||
}
|
||||
}
|
||||
f("", false)
|
||||
f("foo", true)
|
||||
f("a_b-C", false)
|
||||
f("foobar", true)
|
||||
}
|
33
lib/bytesutil/fast_string_matcher_timing_test.go
Normal file
33
lib/bytesutil/fast_string_matcher_timing_test.go
Normal file
|
@ -0,0 +1,33 @@
|
|||
package bytesutil
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func BenchmarkFastStringMatcher(b *testing.B) {
|
||||
for _, s := range []string{"", "foo", "foo-bar-baz", "http_requests_total"} {
|
||||
b.Run(s, func(b *testing.B) {
|
||||
benchmarkFastStringMatcher(b, s)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkFastStringMatcher(b *testing.B, s string) {
|
||||
fsm := NewFastStringMatcher(func(s string) bool {
|
||||
return strings.HasPrefix(s, "foo")
|
||||
})
|
||||
b.ReportAllocs()
|
||||
b.SetBytes(1)
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
n := uint64(0)
|
||||
for pb.Next() {
|
||||
v := fsm.Match(s)
|
||||
if v {
|
||||
n++
|
||||
}
|
||||
}
|
||||
atomic.AddUint64(&GlobalSink, n)
|
||||
})
|
||||
}
|
57
lib/bytesutil/fast_string_transformer.go
Normal file
57
lib/bytesutil/fast_string_transformer.go
Normal file
|
@ -0,0 +1,57 @@
|
|||
package bytesutil
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// FastStringTransformer implements fast transformer for strings.
|
||||
//
|
||||
// It caches transformed strings and returns them back on the next calls
|
||||
// without calling the transformFunc, which may be expensive.
|
||||
type FastStringTransformer struct {
|
||||
m atomic.Value
|
||||
mLen uint64
|
||||
|
||||
transformFunc func(s string) string
|
||||
}
|
||||
|
||||
// NewFastStringTransformer creates new transformer, which applies transformFunc to strings passed to Transform()
|
||||
//
|
||||
// transformFunc must return the same result for the same input.
|
||||
func NewFastStringTransformer(transformFunc func(s string) string) *FastStringTransformer {
|
||||
var fst FastStringTransformer
|
||||
fst.m.Store(&sync.Map{})
|
||||
fst.transformFunc = transformFunc
|
||||
return &fst
|
||||
}
|
||||
|
||||
// Transform applies transformFunc to s and returns the result.
|
||||
func (fst *FastStringTransformer) Transform(s string) string {
|
||||
m := fst.m.Load().(*sync.Map)
|
||||
v, ok := m.Load(s)
|
||||
if ok {
|
||||
// Fast path - the transformed s is found in the cache.
|
||||
sp := v.(*string)
|
||||
return *sp
|
||||
}
|
||||
// Slow path - transform s and store it in the cache.
|
||||
sTransformed := fst.transformFunc(s)
|
||||
// Make a copy of s in order to limit memory usage to the s length,
|
||||
// since the s may point to bigger string.
|
||||
s = strings.Clone(s)
|
||||
if sTransformed == s {
|
||||
// point sTransformed to just allocated s, since it may point to s,
|
||||
// which, in turn, can point to bigger string.
|
||||
sTransformed = s
|
||||
}
|
||||
sp := &sTransformed
|
||||
m.Store(s, sp)
|
||||
n := atomic.AddUint64(&fst.mLen, 1)
|
||||
if n > 100e3 {
|
||||
atomic.StoreUint64(&fst.mLen, 0)
|
||||
fst.m.Store(&sync.Map{})
|
||||
}
|
||||
return sTransformed
|
||||
}
|
22
lib/bytesutil/fast_string_transformer_test.go
Normal file
22
lib/bytesutil/fast_string_transformer_test.go
Normal file
|
@ -0,0 +1,22 @@
|
|||
package bytesutil
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFastStringTransformer(t *testing.T) {
|
||||
fst := NewFastStringTransformer(strings.ToUpper)
|
||||
f := func(s, resultExpected string) {
|
||||
t.Helper()
|
||||
for i := 0; i < 10; i++ {
|
||||
result := fst.Transform(s)
|
||||
if result != resultExpected {
|
||||
t.Fatalf("unexpected result for Transform(%q) at iteration %d; got %q; want %q", s, i, result, resultExpected)
|
||||
}
|
||||
}
|
||||
}
|
||||
f("", "")
|
||||
f("foo", "FOO")
|
||||
f("a_b-C", "A_B-C")
|
||||
}
|
31
lib/bytesutil/fast_string_transformer_timing_test.go
Normal file
31
lib/bytesutil/fast_string_transformer_timing_test.go
Normal file
|
@ -0,0 +1,31 @@
|
|||
package bytesutil
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func BenchmarkFastStringTransformer(b *testing.B) {
|
||||
for _, s := range []string{"", "foo", "foo-bar-baz", "http_requests_total"} {
|
||||
b.Run(s, func(b *testing.B) {
|
||||
benchmarkFastStringTransformer(b, s)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkFastStringTransformer(b *testing.B, s string) {
|
||||
fst := NewFastStringTransformer(strings.ToUpper)
|
||||
b.ReportAllocs()
|
||||
b.SetBytes(1)
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
n := uint64(0)
|
||||
for pb.Next() {
|
||||
sTransformed := fst.Transform(s)
|
||||
n += uint64(len(sTransformed))
|
||||
}
|
||||
atomic.AddUint64(&GlobalSink, n)
|
||||
})
|
||||
}
|
||||
|
||||
var GlobalSink uint64
|
|
@ -1,4 +1,4 @@
|
|||
package discoveryutils
|
||||
package bytesutil
|
||||
|
||||
import (
|
||||
"sync"
|
|
@ -1,4 +1,4 @@
|
|||
package discoveryutils
|
||||
package bytesutil
|
||||
|
||||
import (
|
||||
"fmt"
|
|
@ -1,4 +1,4 @@
|
|||
package discoveryutils
|
||||
package bytesutil
|
||||
|
||||
import (
|
||||
"fmt"
|
|
@ -6,6 +6,7 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/envtemplate"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
|
@ -346,7 +347,7 @@ func parseRelabelConfig(rc *RelabelConfig) (*parsedRelabelConfig, error) {
|
|||
return nil, fmt.Errorf("`labels` config cannot be applied to `action=%s`; it is applied only to `action=graphite`", action)
|
||||
}
|
||||
}
|
||||
return &parsedRelabelConfig{
|
||||
prc := &parsedRelabelConfig{
|
||||
SourceLabels: sourceLabels,
|
||||
Separator: separator,
|
||||
TargetLabel: targetLabel,
|
||||
|
@ -365,7 +366,10 @@ func parseRelabelConfig(rc *RelabelConfig) (*parsedRelabelConfig, error) {
|
|||
hasCaptureGroupInTargetLabel: strings.Contains(targetLabel, "$"),
|
||||
hasCaptureGroupInReplacement: strings.Contains(replacement, "$"),
|
||||
hasLabelReferenceInReplacement: strings.Contains(replacement, "{{"),
|
||||
}, nil
|
||||
}
|
||||
prc.stringReplacer = bytesutil.NewFastStringTransformer(prc.replaceFullStringSlow)
|
||||
prc.submatchReplacer = bytesutil.NewFastStringTransformer(prc.replaceStringSubmatchesSlow)
|
||||
return prc, nil
|
||||
}
|
||||
|
||||
func isDefaultRegex(expr string) bool {
|
||||
|
|
|
@ -161,6 +161,12 @@ func TestParseRelabelConfigsSuccess(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
if pcs != nil {
|
||||
for _, prc := range pcs.prcs {
|
||||
prc.stringReplacer = nil
|
||||
prc.submatchReplacer = nil
|
||||
}
|
||||
}
|
||||
if !reflect.DeepEqual(pcs, pcsExpected) {
|
||||
t.Fatalf("unexpected pcs; got\n%#v\nwant\n%#v", pcs, pcsExpected)
|
||||
}
|
||||
|
|
|
@ -35,6 +35,9 @@ type parsedRelabelConfig struct {
|
|||
hasCaptureGroupInTargetLabel bool
|
||||
hasCaptureGroupInReplacement bool
|
||||
hasLabelReferenceInReplacement bool
|
||||
|
||||
stringReplacer *bytesutil.FastStringTransformer
|
||||
submatchReplacer *bytesutil.FastStringTransformer
|
||||
}
|
||||
|
||||
// String returns human-readable representation for prc.
|
||||
|
@ -203,23 +206,29 @@ func (prc *parsedRelabelConfig) apply(labels []prompbmarshal.Label, labelsOffset
|
|||
return labels
|
||||
}
|
||||
}
|
||||
if re := prc.regex; re.HasPrefix() && !re.MatchString(bytesutil.ToUnsafeString(bb.B)) {
|
||||
sourceStr := bytesutil.ToUnsafeString(bb.B)
|
||||
if !prc.regex.MatchString(sourceStr) {
|
||||
// Fast path - regexp mismatch.
|
||||
relabelBufPool.Put(bb)
|
||||
return labels
|
||||
}
|
||||
match := prc.RegexAnchored.FindSubmatchIndex(bb.B)
|
||||
if match == nil {
|
||||
// Fast path - nothing to replace.
|
||||
relabelBufPool.Put(bb)
|
||||
return labels
|
||||
var valueStr string
|
||||
if replacement == prc.Replacement {
|
||||
// Fast path - the replacement wasn't modified, so it is safe calling stringReplacer.Transform.
|
||||
valueStr = prc.stringReplacer.Transform(sourceStr)
|
||||
} else {
|
||||
// Slow path - the replacement has been modified, so the valueStr must be calculated
|
||||
// from scratch based on the new replacement value.
|
||||
match := prc.RegexAnchored.FindSubmatchIndex(bb.B)
|
||||
valueStr = prc.expandCaptureGroups(replacement, sourceStr, match)
|
||||
}
|
||||
sourceStr := bytesutil.ToUnsafeString(bb.B)
|
||||
nameStr := prc.TargetLabel
|
||||
if prc.hasCaptureGroupInTargetLabel {
|
||||
// Slow path - target_label contains regex capture groups, so the target_label
|
||||
// must be calculated from the regex match.
|
||||
match := prc.RegexAnchored.FindSubmatchIndex(bb.B)
|
||||
nameStr = prc.expandCaptureGroups(nameStr, sourceStr, match)
|
||||
}
|
||||
valueStr := prc.expandCaptureGroups(replacement, sourceStr, match)
|
||||
relabelBufPool.Put(bb)
|
||||
return setLabelValue(labels, labelsOffset, nameStr, valueStr)
|
||||
case "replace_all":
|
||||
|
@ -229,8 +238,8 @@ func (prc *parsedRelabelConfig) apply(labels []prompbmarshal.Label, labelsOffset
|
|||
bb.B = concatLabelValues(bb.B[:0], src, prc.SourceLabels, prc.Separator)
|
||||
sourceStr := string(bb.B)
|
||||
relabelBufPool.Put(bb)
|
||||
valueStr, ok := prc.replaceStringSubmatches(sourceStr, prc.Replacement, prc.hasCaptureGroupInReplacement)
|
||||
if ok {
|
||||
valueStr := prc.replaceStringSubmatchesFast(sourceStr)
|
||||
if valueStr != sourceStr {
|
||||
labels = setLabelValue(labels, labelsOffset, prc.TargetLabel, valueStr)
|
||||
}
|
||||
return labels
|
||||
|
@ -305,8 +314,8 @@ func (prc *parsedRelabelConfig) apply(labels []prompbmarshal.Label, labelsOffset
|
|||
case "labelmap":
|
||||
// Replace label names with the `replacement` if they match `regex`
|
||||
for _, label := range src {
|
||||
labelName, ok := prc.replaceFullString(label.Name, prc.Replacement, prc.hasCaptureGroupInReplacement)
|
||||
if ok {
|
||||
labelName := prc.replaceFullStringFast(label.Name)
|
||||
if labelName != label.Name {
|
||||
labels = setLabelValue(labels, labelsOffset, labelName, label.Value)
|
||||
}
|
||||
}
|
||||
|
@ -315,7 +324,7 @@ func (prc *parsedRelabelConfig) apply(labels []prompbmarshal.Label, labelsOffset
|
|||
// Replace all the occurrences of `regex` at label names with `replacement`
|
||||
for i := range src {
|
||||
label := &src[i]
|
||||
label.Name, _ = prc.replaceStringSubmatches(label.Name, prc.Replacement, prc.hasCaptureGroupInReplacement)
|
||||
label.Name = prc.replaceStringSubmatchesFast(label.Name)
|
||||
}
|
||||
return labels
|
||||
case "labeldrop":
|
||||
|
@ -360,16 +369,23 @@ func (prc *parsedRelabelConfig) apply(labels []prompbmarshal.Label, labelsOffset
|
|||
}
|
||||
}
|
||||
|
||||
func (prc *parsedRelabelConfig) replaceFullString(s, replacement string, hasCaptureGroupInReplacement bool) (string, bool) {
|
||||
// replaceFullStringFast replaces s with the replacement if s matches '^regex$'.
|
||||
//
|
||||
// s is returned as is if it doesn't match '^regex$'.
|
||||
func (prc *parsedRelabelConfig) replaceFullStringFast(s string) string {
|
||||
prefix, complete := prc.regexOriginal.LiteralPrefix()
|
||||
if complete && !hasCaptureGroupInReplacement {
|
||||
replacement := prc.Replacement
|
||||
if complete && !prc.hasCaptureGroupInReplacement {
|
||||
if s == prefix {
|
||||
return replacement, true
|
||||
// Fast path - s matches literal regex
|
||||
return replacement
|
||||
}
|
||||
return s, false
|
||||
// Fast path - s doesn't match literal regex
|
||||
return s
|
||||
}
|
||||
if !strings.HasPrefix(s, prefix) {
|
||||
return s, false
|
||||
// Fast path - s doesn't match literl prefix from regex
|
||||
return s
|
||||
}
|
||||
if replacement == "$1" {
|
||||
// Fast path for commonly used rule for deleting label prefixes such as:
|
||||
|
@ -383,44 +399,49 @@ func (prc *parsedRelabelConfig) replaceFullString(s, replacement string, hasCapt
|
|||
reSuffix := reStr[len(prefix):]
|
||||
switch reSuffix {
|
||||
case "(.*)":
|
||||
return suffix, true
|
||||
return suffix
|
||||
case "(.+)":
|
||||
if len(suffix) > 0 {
|
||||
return suffix, true
|
||||
return suffix
|
||||
}
|
||||
return s, false
|
||||
return s
|
||||
}
|
||||
}
|
||||
}
|
||||
if re := prc.regex; re.HasPrefix() && !re.MatchString(s) {
|
||||
if !prc.regex.MatchString(s) {
|
||||
// Fast path - regex mismatch
|
||||
return s, false
|
||||
return s
|
||||
}
|
||||
// Slow path - handle the rest of cases.
|
||||
return prc.stringReplacer.Transform(s)
|
||||
}
|
||||
|
||||
// replaceFullStringSlow replaces s with the replacement if s matches '^regex$'.
|
||||
//
|
||||
// s is returned as is if it doesn't match '^regex$'.
|
||||
func (prc *parsedRelabelConfig) replaceFullStringSlow(s string) string {
|
||||
// Slow path - regexp processing
|
||||
match := prc.RegexAnchored.FindStringSubmatchIndex(s)
|
||||
if match == nil {
|
||||
return s, false
|
||||
return s
|
||||
}
|
||||
bb := relabelBufPool.Get()
|
||||
bb.B = prc.RegexAnchored.ExpandString(bb.B[:0], replacement, s, match)
|
||||
result := string(bb.B)
|
||||
relabelBufPool.Put(bb)
|
||||
return result, true
|
||||
return prc.expandCaptureGroups(prc.Replacement, s, match)
|
||||
}
|
||||
|
||||
func (prc *parsedRelabelConfig) replaceStringSubmatches(s, replacement string, hasCaptureGroupInReplacement bool) (string, bool) {
|
||||
re := prc.regexOriginal
|
||||
prefix, complete := re.LiteralPrefix()
|
||||
if complete && !hasCaptureGroupInReplacement {
|
||||
if !strings.Contains(s, prefix) {
|
||||
return s, false
|
||||
}
|
||||
return strings.ReplaceAll(s, prefix, replacement), true
|
||||
// replaceStringSubmatchesFast replaces all the regex matches with the replacement in s.
|
||||
func (prc *parsedRelabelConfig) replaceStringSubmatchesFast(s string) string {
|
||||
prefix, complete := prc.regexOriginal.LiteralPrefix()
|
||||
if complete && !prc.hasCaptureGroupInReplacement && !strings.Contains(s, prefix) {
|
||||
// Fast path - zero regex matches in s.
|
||||
return s
|
||||
}
|
||||
if !re.MatchString(s) {
|
||||
return s, false
|
||||
}
|
||||
return re.ReplaceAllString(s, replacement), true
|
||||
// Slow path - replace all the regex matches in s with the replacement.
|
||||
return prc.submatchReplacer.Transform(s)
|
||||
}
|
||||
|
||||
// replaceStringSubmatchesSlow replaces all the regex matches with the replacement in s.
|
||||
func (prc *parsedRelabelConfig) replaceStringSubmatchesSlow(s string) string {
|
||||
return prc.regexOriginal.ReplaceAllString(s, prc.Replacement)
|
||||
}
|
||||
|
||||
func (prc *parsedRelabelConfig) expandCaptureGroups(template, source string, match []int) string {
|
||||
|
@ -558,3 +579,16 @@ func fillLabelReferences(dst []byte, replacement string, labels []prompbmarshal.
|
|||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// SanitizeName replaces unsupported by Prometheus chars in metric names and label names with _.
|
||||
//
|
||||
// See https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels
|
||||
func SanitizeName(name string) string {
|
||||
return promSanitizer.Transform(name)
|
||||
}
|
||||
|
||||
var promSanitizer = bytesutil.NewFastStringTransformer(func(s string) string {
|
||||
return unsupportedPromChars.ReplaceAllString(s, "_")
|
||||
})
|
||||
|
||||
var unsupportedPromChars = regexp.MustCompile(`[^a-zA-Z0-9_:]`)
|
||||
|
|
|
@ -7,6 +7,22 @@ import (
|
|||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
|
||||
)
|
||||
|
||||
func TestSanitizeName(t *testing.T) {
|
||||
f := func(s, resultExpected string) {
|
||||
t.Helper()
|
||||
for i := 0; i < 5; i++ {
|
||||
result := SanitizeName(s)
|
||||
if result != resultExpected {
|
||||
t.Fatalf("unexpected result for SanitizeName(%q) at iteration %d; got %q; want %q", s, i, result, resultExpected)
|
||||
}
|
||||
}
|
||||
}
|
||||
f("", "")
|
||||
f("a", "a")
|
||||
f("foo.bar/baz:a", "foo_bar_baz:a")
|
||||
f("foo...bar", "foo___bar")
|
||||
}
|
||||
|
||||
func TestLabelsToString(t *testing.T) {
|
||||
f := func(labels []prompbmarshal.Label, sExpected string) {
|
||||
t.Helper()
|
||||
|
@ -666,6 +682,15 @@ func TestApplyRelabelConfigs(t *testing.T) {
|
|||
regex: "a(.+)"
|
||||
`, `qwe{foo="bar",baz="aaa"}`, true, `qwe{abc="qwe.bar.aa",baz="aaa",foo="bar"}`)
|
||||
})
|
||||
// Check $ at the end of regex - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3131
|
||||
t.Run("replacement-with-$-at-the-end-of-regex", func(t *testing.T) {
|
||||
f(`
|
||||
- target_label: xyz
|
||||
regex: "foo\\$$"
|
||||
replacement: bar
|
||||
source_labels: [xyz]
|
||||
`, `metric{xyz="foo$",a="b"}`, true, `metric{a="b",xyz="bar"}`)
|
||||
})
|
||||
}
|
||||
|
||||
func TestFinalizeLabels(t *testing.T) {
|
||||
|
|
|
@ -8,6 +8,27 @@ import (
|
|||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
|
||||
)
|
||||
|
||||
func BenchmarkSanitizeName(b *testing.B) {
|
||||
for _, name := range []string{"", "foo", "foo-bar-baz", "http_requests_total"} {
|
||||
b.Run(name, func(b *testing.B) {
|
||||
benchmarkSanitizeName(b, name)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkSanitizeName(b *testing.B, name string) {
|
||||
b.ReportAllocs()
|
||||
b.SetBytes(1)
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
sanitizedName := SanitizeName(name)
|
||||
GlobalSink += len(sanitizedName)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var GlobalSink int
|
||||
|
||||
func BenchmarkMatchRegexPrefixDotPlusMatchOptimized(b *testing.B) {
|
||||
const pattern = "^foo.+$"
|
||||
const s = "foobar"
|
||||
|
|
|
@ -34,7 +34,6 @@ import (
|
|||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discovery/kubernetes"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discovery/openstack"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discovery/yandexcloud"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discoveryutils"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutils"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/proxy"
|
||||
"github.com/VictoriaMetrics/metrics"
|
||||
|
@ -1330,8 +1329,8 @@ func (swc *scrapeWorkConfig) getScrapeWork(target string, extraLabels, metaLabel
|
|||
func internLabelStrings(labels []prompbmarshal.Label) {
|
||||
for i := range labels {
|
||||
label := &labels[i]
|
||||
label.Name = discoveryutils.InternString(label.Name)
|
||||
label.Value = discoveryutils.InternString(label.Value)
|
||||
label.Name = bytesutil.InternString(label.Name)
|
||||
label.Value = bytesutil.InternString(label.Value)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -674,8 +674,9 @@ scrape_config_files:
|
|||
}
|
||||
|
||||
func resetNonEssentialFields(sws []*ScrapeWork) {
|
||||
for i := range sws {
|
||||
sws[i].OriginalLabels = nil
|
||||
for _, sw := range sws {
|
||||
sw.OriginalLabels = nil
|
||||
sw.MetricRelabelConfigs = nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1446,10 +1447,6 @@ scrape_configs:
|
|||
},
|
||||
AuthConfig: &promauth.Config{},
|
||||
ProxyAuthConfig: &promauth.Config{},
|
||||
MetricRelabelConfigs: mustParseRelabelConfigs(`
|
||||
- source_labels: [foo]
|
||||
target_label: abc
|
||||
`),
|
||||
jobNameOriginal: "foo",
|
||||
},
|
||||
})
|
||||
|
@ -1847,8 +1844,10 @@ func TestScrapeConfigClone(t *testing.T) {
|
|||
f := func(sc *ScrapeConfig) {
|
||||
t.Helper()
|
||||
scCopy := sc.clone()
|
||||
if !reflect.DeepEqual(sc, scCopy) {
|
||||
t.Fatalf("unexpected result after unmarshalJSON() for JSON:\n%s", sc.marshalJSON())
|
||||
scJSON := sc.marshalJSON()
|
||||
scCopyJSON := scCopy.marshalJSON()
|
||||
if !reflect.DeepEqual(scJSON, scCopyJSON) {
|
||||
t.Fatalf("unexpected cloned result:\ngot\n%s\nwant\n%s", scCopyJSON, scJSON)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,9 +6,8 @@ import (
|
|||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
|
||||
)
|
||||
|
||||
|
@ -17,43 +16,14 @@ import (
|
|||
//
|
||||
// This has been copied from Prometheus sources at util/strutil/strconv.go
|
||||
func SanitizeLabelName(name string) string {
|
||||
m := sanitizedLabelNames.Load().(*sync.Map)
|
||||
v, ok := m.Load(name)
|
||||
if ok {
|
||||
// Fast path - the sanitized label name is found in the cache.
|
||||
sp := v.(*string)
|
||||
return *sp
|
||||
}
|
||||
// Slow path - sanitize name and store it in the cache.
|
||||
sanitizedName := invalidLabelCharRE.ReplaceAllString(name, "_")
|
||||
// Make a copy of name in order to limit memory usage to the name length,
|
||||
// since the name may point to bigger string.
|
||||
s := string(append([]byte{}, name...))
|
||||
if sanitizedName == name {
|
||||
// point sanitizedName to just allocated s, since it may point to name,
|
||||
// which, in turn, can point to bigger string.
|
||||
sanitizedName = s
|
||||
}
|
||||
sp := &sanitizedName
|
||||
m.Store(s, sp)
|
||||
n := atomic.AddUint64(&sanitizedLabelNamesLen, 1)
|
||||
if n > 100e3 {
|
||||
atomic.StoreUint64(&sanitizedLabelNamesLen, 0)
|
||||
sanitizedLabelNames.Store(&sync.Map{})
|
||||
}
|
||||
return sanitizedName
|
||||
return labelNamesSanitizer.Transform(name)
|
||||
}
|
||||
|
||||
var (
|
||||
sanitizedLabelNames atomic.Value
|
||||
sanitizedLabelNamesLen uint64
|
||||
var labelNamesSanitizer = bytesutil.NewFastStringTransformer(func(s string) string {
|
||||
return invalidLabelCharRE.ReplaceAllString(s, "_")
|
||||
})
|
||||
|
||||
invalidLabelCharRE = regexp.MustCompile(`[^a-zA-Z0-9_]`)
|
||||
)
|
||||
|
||||
func init() {
|
||||
sanitizedLabelNames.Store(&sync.Map{})
|
||||
}
|
||||
var invalidLabelCharRE = regexp.MustCompile(`[^a-zA-Z0-9_]`)
|
||||
|
||||
// JoinHostPort returns host:port.
|
||||
//
|
||||
|
|
|
@ -155,12 +155,16 @@ var requestPool sync.Pool
|
|||
// sanitizeName performs DataDog-compatible santizing for metric names
|
||||
//
|
||||
// See https://docs.datadoghq.com/metrics/custom_metrics/#naming-custom-metrics
|
||||
func sanitizeName(s string) string {
|
||||
func sanitizeName(name string) string {
|
||||
return namesSanitizer.Transform(name)
|
||||
}
|
||||
|
||||
var namesSanitizer = bytesutil.NewFastStringTransformer(func(s string) string {
|
||||
s = unsupportedDatadogChars.ReplaceAllString(s, "_")
|
||||
s = multiUnderscores.ReplaceAllString(s, "_")
|
||||
s = underscoresWithDots.ReplaceAllString(s, ".")
|
||||
return s
|
||||
}
|
||||
})
|
||||
|
||||
var (
|
||||
unsupportedDatadogChars = regexp.MustCompile(`[^0-9a-zA-Z_\.]+`)
|
||||
|
|
|
@ -3,6 +3,8 @@ package regexutil
|
|||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
||||
)
|
||||
|
||||
// PromRegex implements an optimized string matching for Prometheus-like regex.
|
||||
|
@ -13,6 +15,8 @@ import (
|
|||
// - alternate strings such as "foo|bar|baz"
|
||||
// - prefix match such as "foo.*" or "foo.+"
|
||||
// - substring match such as ".*foo.*" or ".+bar.+"
|
||||
//
|
||||
// The rest of regexps are also optimized by returning cached match results for the same input strings.
|
||||
type PromRegex struct {
|
||||
// prefix contains literal prefix for regex.
|
||||
// For example, prefix="foo" for regex="foo(a|b)"
|
||||
|
@ -32,9 +36,8 @@ type PromRegex struct {
|
|||
// For example, orValues contain ["foo","bar","baz"] for regex suffix="foo|bar|baz"
|
||||
orValues []string
|
||||
|
||||
// reSuffix contains an anchored regexp built from suffix:
|
||||
// "^(?:suffix)$"
|
||||
reSuffix *regexp.Regexp
|
||||
// reSuffixMatcher contains fast matcher for "^suffix$"
|
||||
reSuffixMatcher *bytesutil.FastStringMatcher
|
||||
}
|
||||
|
||||
// NewPromRegex returns PromRegex for the given expr.
|
||||
|
@ -50,25 +53,18 @@ func NewPromRegex(expr string) (*PromRegex, error) {
|
|||
// Anchor suffix to the beginning and the end of the matching string.
|
||||
suffixExpr := "^(?:" + suffix + ")$"
|
||||
reSuffix := regexp.MustCompile(suffixExpr)
|
||||
reSuffixMatcher := bytesutil.NewFastStringMatcher(reSuffix.MatchString)
|
||||
pr := &PromRegex{
|
||||
prefix: prefix,
|
||||
suffix: suffix,
|
||||
substrDotStar: substrDotStar,
|
||||
substrDotPlus: substrDotPlus,
|
||||
orValues: orValues,
|
||||
reSuffix: reSuffix,
|
||||
prefix: prefix,
|
||||
suffix: suffix,
|
||||
substrDotStar: substrDotStar,
|
||||
substrDotPlus: substrDotPlus,
|
||||
orValues: orValues,
|
||||
reSuffixMatcher: reSuffixMatcher,
|
||||
}
|
||||
return pr, nil
|
||||
}
|
||||
|
||||
// HasPrefix returns true if pr contains non-empty literal prefix.
|
||||
//
|
||||
// For example, if pr is "foo(bar|baz)", then the prefix is "foo",
|
||||
// so HasPrefix() returns true.
|
||||
func (pr *PromRegex) HasPrefix() bool {
|
||||
return len(pr.prefix) > 0
|
||||
}
|
||||
|
||||
// MatchString retruns true if s matches pr.
|
||||
//
|
||||
// The pr is automatically anchored to the beginning and to the end
|
||||
|
@ -106,7 +102,7 @@ func (pr *PromRegex) MatchString(s string) bool {
|
|||
return len(s) > 0
|
||||
}
|
||||
// Fall back to slow path by matching the original regexp.
|
||||
return pr.reSuffix.MatchString(s)
|
||||
return pr.reSuffixMatcher.Match(s)
|
||||
}
|
||||
|
||||
func getSubstringLiteral(expr, prefixSuffix string) string {
|
||||
|
|
|
@ -12,7 +12,7 @@ func RemoveStartEndAnchors(expr string) string {
|
|||
for strings.HasPrefix(expr, "^") {
|
||||
expr = expr[1:]
|
||||
}
|
||||
for strings.HasSuffix(expr, "$") {
|
||||
for strings.HasSuffix(expr, "$") && !strings.HasSuffix(expr, "\\$") {
|
||||
expr = expr[:len(expr)-1]
|
||||
}
|
||||
return expr
|
||||
|
|
|
@ -110,3 +110,22 @@ func TestSimplify(t *testing.T) {
|
|||
// The transformed regexp mustn't match barx
|
||||
f("(foo|bar$)x*", "", "(?:foo|bar$)x*")
|
||||
}
|
||||
|
||||
func TestRemoveStartEndAnchors(t *testing.T) {
|
||||
f := func(s, resultExpected string) {
|
||||
t.Helper()
|
||||
result := RemoveStartEndAnchors(s)
|
||||
if result != resultExpected {
|
||||
t.Fatalf("unexpected result for RemoveStartEndAnchors(%q); got %q; want %q", s, result, resultExpected)
|
||||
}
|
||||
}
|
||||
f("", "")
|
||||
f("a", "a")
|
||||
f("^^abc", "abc")
|
||||
f("a^b$c", "a^b$c")
|
||||
f("$$abc^", "$$abc^")
|
||||
f("^abc|de$", "abc|de")
|
||||
f("abc\\$", "abc\\$")
|
||||
f("^abc\\$$$", "abc\\$")
|
||||
f("^a\\$b\\$$", "a\\$b\\$")
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue