Compare commits

..

44 Commits

Author SHA1 Message Date
Haley Wang
e237e35c9c test alert annotation 2025-01-16 15:52:33 +08:00
Aliaksandr Valialkin
1645542a8a docs/VictoriaLogs/LogsQL.md: use top pipe in examples instead of stats by (...) count() | sort (...) limit N
`top` pipe is shorter and easier to understand
2025-01-16 04:22:18 +01:00
Aliaksandr Valialkin
151eb1e4b6 docs/VictoriaLogs/logsql-examples.md: replace last pipe with first pipe, since it is easier to understand 2025-01-16 04:21:09 +01:00
Aliaksandr Valialkin
5e4de8e860 docs/VictoriaLogs/LogsQL.md: use proper backticks around hexnumencode: 2025-01-16 04:12:37 +01:00
Aliaksandr Valialkin
6312d3bbba docs/VictoriaLogs: typo fixes for block_stats pipe docs 2025-01-16 03:53:50 +01:00
Aliaksandr Valialkin
d2bede6b51 docs/VictoriaLogs/LogsQL.md: add missing they word 2025-01-16 03:40:42 +01:00
Aliaksandr Valialkin
5ca5069fc4 docs/VictoriaLogs/LogsQL.md: fix incorrect url to VictoriaMetrics histogram buckets
This is a follow-up for d2a791bef3
2025-01-16 00:02:51 +01:00
Aliaksandr Valialkin
8a3c460f63 deployment/docker: update VictoriaLogs Docker images from v1.5.0-victorialogs to v1.6.0-victorialogs
See https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.6.0-victorialogs
2025-01-15 22:37:24 +01:00
Aliaksandr Valialkin
ca653a515c docs/VictoriaLogs/CHANGELOG.md: cut v1.6.0-victorialogs release 2025-01-15 22:31:13 +01:00
Aliaksandr Valialkin
e5b4cf33bf lib/logstorage: make golangci-lint happy after f27e120aeb 2025-01-15 22:28:13 +01:00
Aliaksandr Valialkin
e24a8f2088 deployment/docker: update Alpine Docker image from 3.21.0 to 3.21.2
See https://alpinelinux.org/posts/Alpine-3.21.1-released.html and https://alpinelinux.org/posts/Alpine-3.18.11-3.19.6-3.20.5-3.21.2-released.html
2025-01-15 22:26:26 +01:00
Aliaksandr Valialkin
f27e120aeb lib/logstorage: add union pipe, which allows uniting results from multiple queries 2025-01-15 22:22:07 +01:00
Aliaksandr Valialkin
ee1ce90501 lib/logstorage: properly drop temporary directories created by filter* tests 2025-01-15 22:22:07 +01:00
Aliaksandr Valialkin
47fe8cf3be lib/logstorage: math pipe: add rand() function 2025-01-15 22:22:06 +01:00
Daria Karavaieva
5813aa6602 docs/vmanomaly: fix parameters field width (#8025)
### Describe Your Changes

- fix parameters width in Components section - reader, wtiter,
monitoring, scheduler

### Checklist

The following checks are **mandatory**:

- [x] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).
2025-01-15 20:17:53 +01:00
Aliaksandr Valialkin
b4f4ece162 lib/logstorage: improve performance of unique pipe for integer columns with big number of unique values 2025-01-15 19:53:10 +01:00
Aliaksandr Valialkin
bb00f7529f lib/logstorage: improve performance when applying math calculations for _time, const and dict values 2025-01-15 19:53:10 +01:00
Roman Khavronenko
ad3bd11334 docs: mention regression in v1.109.0 (#8046)
See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8045

### Checklist

The following checks are **mandatory**:

- [ ] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).

---------

Signed-off-by: hagen1778 <roman@victoriametrics.com>
2025-01-15 16:26:24 +01:00
Github Actions
875c6663ef Automatic update helm docs from VictoriaMetrics/helm-charts@d8e5f03 (#8035)
Automated changes by
[create-pull-request](https://github.com/peter-evans/create-pull-request)
GitHub action

Signed-off-by: Github Actions <133988544+victoriametrics-bot@users.noreply.github.com>
Co-authored-by: AndrewChubatiuk <3162380+AndrewChubatiuk@users.noreply.github.com>
2025-01-15 15:16:02 +08:00
Zakhar Bessarab
b48b7c454a docs: update references to v1.109.0 (#8034)
### Describe Your Changes

Please provide a brief description of the changes you made. Be as
specific as possible to help others understand the purpose and impact of
your modifications.

### Checklist

The following checks are **mandatory**:

- [ ] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).

Signed-off-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>
2025-01-14 16:42:11 +01:00
Zakhar Bessarab
f523348b3f deployment/docker: update to v1.109.0 (#8032)
### Describe Your Changes

Please provide a brief description of the changes you made. Be as
specific as possible to help others understand the purpose and impact of
your modifications.

### Checklist

The following checks are **mandatory**:

- [ ] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).

Signed-off-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>
2025-01-14 16:39:06 +01:00
Zakhar Bessarab
63bf1e008f docs/changelog: add missing entry for #7182 (#8030)
### Describe Your Changes

Please provide a brief description of the changes you made. Be as
specific as possible to help others understand the purpose and impact of
your modifications.

### Checklist

The following checks are **mandatory**:

- [ ] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).

Signed-off-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>
2025-01-14 16:30:26 +01:00
Zakhar Bessarab
419ac10c60 docs/lts-releases: update links to latest releases (#8028)
### Describe Your Changes

Please provide a brief description of the changes you made. Be as
specific as possible to help others understand the purpose and impact of
your modifications.

### Checklist

The following checks are **mandatory**:

- [ ] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).

Signed-off-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>
2025-01-14 16:22:29 +01:00
Dmytro Kozlov
d631d2c100 cloud: update email images for cloud (#8024)
### Describe Your Changes

Updated email images with new support email

### Checklist

The following checks are **mandatory**:

- [X] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).
2025-01-14 14:24:33 +01:00
Zakhar Bessarab
89431458bf docs/release-guide: move testing on sandbox step before pushing tags (#8026)
### Describe Your Changes

Please provide a brief description of the changes you made. Be as
specific as possible to help others understand the purpose and impact of
your modifications.

### Checklist

The following checks are **mandatory**:

- [ ] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).

Signed-off-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>
2025-01-14 14:23:32 +01:00
Zakhar Bessarab
d8d0c0ac01 docs/changelog: port LTS changes, update release date (#8027)
### Describe Your Changes

Please provide a brief description of the changes you made. Be as
specific as possible to help others understand the purpose and impact of
your modifications.

### Checklist

The following checks are **mandatory**:

- [ ] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).

---------

Signed-off-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>
Co-authored-by: Roman Khavronenko <roman@victoriametrics.com>
2025-01-14 14:23:01 +01:00
Roman Khavronenko
c0f5699bad docs: cut new changelog doc for 2024 (#8023)
Cutting new changelod doc reduces the size of the current's year
changelog and improves navigation for users.

### Describe Your Changes

Please provide a brief description of the changes you made. Be as
specific as possible to help others understand the purpose and impact of
your modifications.

### Checklist

The following checks are **mandatory**:

- [ ] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).

Signed-off-by: hagen1778 <roman@victoriametrics.com>
2025-01-14 11:31:16 +01:00
Nikolay
277fdd1070 lib/storage: reduce test suite batch size (#8022)
Commit eef6943084 added new test
functions. Which checks various cases for metricName registration at
data ingestion.
Initial dataset size had 4 batches with 100 rows each. It works fine at
machines with 5GB+ memory.
But i386 architecture supports only 4GB of memory per process.

Due to given limitations, batch size should be reduced to 3 batches and
30 rows. It keeps the same
test funtionality, but reduces overall memory usage to ~3GB.

Signed-off-by: f41gh7 <nik@victoriametrics.com>
2025-01-14 11:27:50 +01:00
Roman Khavronenko
d290efb849 lib/opentlemetry: throttle log messages during parsing (#8021)
Samples parsing is a hot path. Bad client could easily overwhelm
receiver with bad or unsupported data. So it is better to throttle such
messages.

Follow-up after
b26a68641c

### Describe Your Changes

Please provide a brief description of the changes you made. Be as
specific as possible to help others understand the purpose and impact of
your modifications.

### Checklist

The following checks are **mandatory**:

- [ ] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).

Signed-off-by: hagen1778 <roman@victoriametrics.com>
2025-01-14 11:03:11 +01:00
chenlujjj
b26a68641c lib/opentelemetry: log the metric name of unsupported metrics (#8018)
To resolve:
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8009
Log the name of unsupported metrics.
2025-01-14 10:49:30 +01:00
Aliaksandr Valialkin
b88cda5c41 lib/logstorage: make golangci-lint happy after the commit d2a791bef3 2025-01-13 22:31:33 +01:00
Aliaksandr Valialkin
d2a791bef3 lib/logstorage: add histogram stats function for calculating histogram buckets over numeric fields 2025-01-13 22:30:19 +01:00
Aliaksandr Valialkin
99516a5730 lib/logstorage: top pipe: allow mixing the order of hits and rank suffixes 2025-01-13 22:30:19 +01:00
Aliaksandr Valialkin
aecc86c390 lib/logstorage: do not copy pipeTopkProcessorShard when obtaining parition keys 2025-01-13 22:30:19 +01:00
Aliaksandr Valialkin
500b54f5aa app/vlogscli: typo fix, which could result in incomplete results in compact mode 2025-01-13 22:30:18 +01:00
Aliaksandr Valialkin
cc29692e27 lib/logstorage: track integer field values in integer map for top N (int_field)
This reduces memory usage by up to 2x for the map used for tracking hits.
This also reduces CPU usage for tracking integer fields.
2025-01-13 22:30:18 +01:00
Aliaksandr Valialkin
f018aa33cb lib/logstorage: avoid callback overhead at visitValuesReadonly
Process values in batches instead of passing every value in the callback.
This improves performance of reading the encoded values from storage by up to 50%.
2025-01-13 22:30:17 +01:00
Daria Karavaieva
92b6475fa6 docs/vmanomaly: fix table rendering (#8005)
### Describe Your Changes

- fix table rendering on writer and scheduler pages

### Checklist

The following checks are **mandatory**:

- [x] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).
2025-01-13 14:19:16 +01:00
Andrii Chubatiuk
bda3546cfd docs: added search resupts page for docs.victoriametrics.com (#8017)
### Describe Your Changes

added search page required for docs site

### Checklist

The following checks are **mandatory**:

- [ ] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).
2025-01-13 14:18:25 +01:00
Artem Navoiev
2691cdefe3 docs: cloud use separate support email for cloud (#8013)
### Describe Your Changes

Please provide a brief description of the changes you made. Be as
specific as possible to help others understand the purpose and impact of
your modifications.

### Checklist

The following checks are **mandatory**:

- [ ] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).

Signed-off-by: Artem Navoiev <tenmozes@gmail.com>
2025-01-13 14:17:38 +01:00
Github Actions
93b8aa5c9d Automatic update helm docs from VictoriaMetrics/helm-charts@457951a (#8014)
Automated changes by
[create-pull-request](https://github.com/peter-evans/create-pull-request)
GitHub action

Signed-off-by: Github Actions <133988544+victoriametrics-bot@users.noreply.github.com>
Co-authored-by: f41gh7 <18450869+f41gh7@users.noreply.github.com>
2025-01-13 12:12:48 +01:00
Aliaksandr Valialkin
7a7f188133 deployment/docker: update VictoriaLogs Docker image tag from v1.4.0-victorialogs to v1.5.0-victorialogs
See https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.5.0-victorialogs
2025-01-13 07:34:33 +01:00
Aliaksandr Valialkin
3e00fae3f4 docs/VictoriaLogs/CHANGELOG.md: cut v1.5.0-victorialogs release 2025-01-13 07:28:08 +01:00
Roman Khavronenko
ee3c0c6a87 make: bump golangci-lint to v1.63.4 (
New version has additional checks and reduced resource consumption, so
it doesn't timeout for our internal repos.

To make linter happy, I addressed "redefinition of the built-in
function" lint error.

----
Signed-off-by: hagen1778 <roman@victoriametrics.com>
2025-01-13 07:18:04 +01:00
128 changed files with 4578 additions and 3592 deletions

View File

@@ -567,7 +567,7 @@ golangci-lint: install-golangci-lint
golangci-lint run
install-golangci-lint:
which golangci-lint || curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(shell go env GOPATH)/bin v1.60.3
which golangci-lint || curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(shell go env GOPATH)/bin v1.63.4
remove-golangci-lint:
rm -rf `which golangci-lint`

View File

@@ -176,7 +176,7 @@ func writeCompactObject(w io.Writer, fields []logstorage.Field) error {
_, err := fmt.Fprintf(w, "%s\n", fields[0].Value)
return err
}
if len(fields) == 2 && fields[0].Name == "_time" || fields[1].Name == "_time" {
if len(fields) == 2 && (fields[0].Name == "_time" || fields[1].Name == "_time") {
// Write _time\tfieldValue as is
if fields[0].Name == "_time" {
_, err := fmt.Fprintf(w, "%s\t%s\n", fields[0].Value, fields[1].Value)

View File

@@ -8,8 +8,6 @@ tests:
input_series:
- series: 'up{job="vmagent2", instance="localhost:9090"}'
values: "0+0x1440"
- series: "test_query"
values: "1x200"
metricsql_expr_test:
- expr: suquery_interval_test
@@ -34,8 +32,7 @@ tests:
- eval_time: 0
alertname: AlwaysFiring
exp_alerts:
- exp_annotations:
queryValue: "1"
- {}
- eval_time: 0
alertname: InstanceDown

View File

@@ -12,8 +12,6 @@ groups:
dashboard: '{{ $externalURL }}/d/dashboard?orgId=1'
- alert: AlwaysFiring
expr: 1
annotations:
queryValue: "{{ query \"test_query\" | first | value }}"
- alert: SameAlertNameWithDifferentGroup
expr: absent(test)
for: 1m

View File

@@ -10,8 +10,6 @@ tests:
input_series:
- series: "test"
values: "_x5 1x5 _ stale"
- series: "test_query"
values: "1x100"
alert_rule_test:
- eval_time: 1m
@@ -52,8 +50,6 @@ tests:
values: "0+0x1440"
- series: "test"
values: "0+1x1440"
- series: "test_query"
values: "0+1x100"
metricsql_expr_test:
- expr: count(ALERTS) by (alertgroup, alertname, alertstate)
@@ -101,17 +97,6 @@ tests:
exp_alerts:
- exp_labels:
cluster: prod
exp_annotations:
queryValue: "0"
- eval_time: 5m
groupname: group1
alertname: AlwaysFiring
exp_alerts:
- exp_labels:
cluster: prod
exp_annotations:
queryValue: "5"
- eval_time: 0
groupname: alerts

View File

@@ -8,8 +8,6 @@ tests:
input_series:
- series: 'up{job="vmagent2", instance="localhost:9090"}'
values: "0+0x1440"
- series: "test_query"
values: "0+1x200"
metricsql_expr_test:
- expr: suquery_interval_test
@@ -39,8 +37,6 @@ tests:
exp_alerts:
- exp_labels:
cluster: prod
exp_annotations:
queryValue: "0"
- eval_time: 0
groupname: group1

View File

@@ -64,18 +64,7 @@ func UnitTest(files []string, disableGroupLabel bool, externalLabels []string, e
if err != nil {
logger.Fatalf("failed to parse external URL: %w", err)
}
labels := make(map[string]string)
for _, s := range externalLabels {
if len(s) == 0 {
continue
}
n := strings.IndexByte(s, '=')
if n < 0 {
logger.Fatalf("missing '=' in `-label`. It must contain label in the form `name=value`; got %q", s)
}
labels[s[:n]] = s[n+1:]
}
if err := templates.Init([]string{}, labels, *eu); err != nil {
if err := templates.Load([]string{}, *eu); err != nil {
logger.Fatalf("failed to load template: %v", err)
}
storagePath = filepath.Join(os.TempDir(), testStoragePath)
@@ -95,7 +84,19 @@ func UnitTest(files []string, disableGroupLabel bool, externalLabels []string, e
if len(testfiles) == 0 {
logger.Fatalf("no test file found")
}
_, err = notifier.Init(nil)
labels := make(map[string]string)
for _, s := range externalLabels {
if len(s) == 0 {
continue
}
n := strings.IndexByte(s, '=')
if n < 0 {
logger.Fatalf("missing '=' in `-label`. It must contain label in the form `name=value`; got %q", s)
}
labels[s[:n]] = s[n+1:]
}
_, err = notifier.Init(nil, labels, externalURL)
if err != nil {
logger.Fatalf("failed to init notifier: %v", err)
}

View File

@@ -9,13 +9,14 @@ import (
"testing"
"time"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/notifier"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/templates"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutils"
"gopkg.in/yaml.v2"
)
func TestMain(m *testing.M) {
if err := templates.Init([]string{"testdata/templates/*good.tmpl"}, nil, url.URL{}); err != nil {
if err := templates.Load([]string{"testdata/templates/*good.tmpl"}, url.URL{}); err != nil {
os.Exit(1)
}
os.Exit(m.Run())
@@ -78,7 +79,7 @@ groups:
for i, u := range urls {
urls[i] = srv.URL + u
}
_, err := Parse(urls, templates.ValidateTemplates, true)
_, err := Parse(urls, notifier.ValidateTemplates, true)
if err != nil && !expErr {
t.Fatalf("error parsing URLs %s", err)
}
@@ -94,7 +95,7 @@ groups:
}
func TestParse_Success(t *testing.T) {
_, err := Parse([]string{"testdata/rules/*good.rules", "testdata/dir/*good.*"}, templates.ValidateTemplates, true)
_, err := Parse([]string{"testdata/rules/*good.rules", "testdata/dir/*good.*"}, notifier.ValidateTemplates, true)
if err != nil {
t.Fatalf("error parsing files %s", err)
}
@@ -104,7 +105,7 @@ func TestParse_Failure(t *testing.T) {
f := func(paths []string, errStrExpected string) {
t.Helper()
_, err := Parse(paths, templates.ValidateTemplates, true)
_, err := Parse(paths, notifier.ValidateTemplates, true)
if err == nil {
t.Fatalf("expected to get error")
}
@@ -115,7 +116,7 @@ func TestParse_Failure(t *testing.T) {
f([]string{"testdata/rules/rules_interval_bad.rules"}, "eval_offset should be smaller than interval")
f([]string{"testdata/rules/rules0-bad.rules"}, "unexpected token")
f([]string{"testdata/dir/rules0-bad.rules"}, "failed to parse text")
f([]string{"testdata/dir/rules0-bad.rules"}, "error parsing annotation")
f([]string{"testdata/dir/rules1-bad.rules"}, "duplicate in file")
f([]string{"testdata/dir/rules2-bad.rules"}, "function \"unknown\" not defined")
f([]string{"testdata/dir/rules3-bad.rules"}, "either `record` or `alert` must be set")
@@ -329,7 +330,7 @@ func TestGroupValidate_Success(t *testing.T) {
var validateTplFn ValidateTplFn
if validateAnnotations {
validateTplFn = templates.ValidateTemplates
validateTplFn = notifier.ValidateTemplates
}
err := group.Validate(validateTplFn, validateExpressions)
if err != nil {

View File

@@ -83,6 +83,7 @@ absolute path to all .tpl files in root.
var (
alertURLGeneratorFn notifier.AlertURLGenerator
extURL *url.URL
)
func main() {
@@ -98,29 +99,18 @@ func main() {
logger.Init()
var err error
extURL, err := getExternalURL(*externalURL)
extURL, err = getExternalURL(*externalURL)
if err != nil {
logger.Fatalf("failed to init external.url %q: %s", *externalURL, err)
}
externalls := make(map[string]string)
for _, s := range *externalLabels {
if len(s) == 0 {
continue
}
n := strings.IndexByte(s, '=')
if n < 0 {
logger.Fatalf("wrong format in `-external.label`, it must contain label as `Name=value`; got %q", s)
}
externalls[s[:n]] = s[n+1:]
}
err = templates.Init(*ruleTemplatesPath, externalls, *extURL)
err = templates.Load(*ruleTemplatesPath, *extURL)
if err != nil {
logger.Fatalf("failed to load template %q: %s", *ruleTemplatesPath, err)
}
if *dryRun {
groups, err := config.Parse(*rulePath, templates.ValidateTemplates, true)
groups, err := config.Parse(*rulePath, notifier.ValidateTemplates, true)
if err != nil {
logger.Fatalf("failed to parse %q: %s", *rulePath, err)
}
@@ -137,7 +127,7 @@ func main() {
var validateTplFn config.ValidateTplFn
if *validateTemplates {
validateTplFn = templates.ValidateTemplates
validateTplFn = notifier.ValidateTemplates
}
if *replayFrom != "" {
@@ -166,7 +156,7 @@ func main() {
}
ctx, cancel := context.WithCancel(context.Background())
manager, err := newManager(ctx, externalls)
manager, err := newManager(ctx)
if err != nil {
logger.Fatalf("failed to init: %s", err)
}
@@ -213,13 +203,25 @@ var (
configTimestamp = metrics.NewCounter(`vmalert_config_last_reload_success_timestamp_seconds`)
)
func newManager(ctx context.Context, externalls map[string]string) (*manager, error) {
func newManager(ctx context.Context) (*manager, error) {
q, err := datasource.Init(nil)
if err != nil {
return nil, fmt.Errorf("failed to init datasource: %w", err)
}
nts, err := notifier.Init(alertURLGeneratorFn)
labels := make(map[string]string)
for _, s := range *externalLabels {
if len(s) == 0 {
continue
}
n := strings.IndexByte(s, '=')
if n < 0 {
return nil, fmt.Errorf("missing '=' in `-label`. It must contain label in the form `Name=value`; got %q", s)
}
labels[s[:n]] = s[n+1:]
}
nts, err := notifier.Init(alertURLGeneratorFn, labels, *externalURL)
if err != nil {
return nil, fmt.Errorf("failed to init notifier: %w", err)
}
@@ -227,7 +229,7 @@ func newManager(ctx context.Context, externalls map[string]string) (*manager, er
groups: make(map[uint64]*rule.Group),
querierBuilder: q,
notifiers: nts,
labels: externalls,
labels: labels,
}
rw, err := remotewrite.Init(ctx)
if err != nil {
@@ -291,36 +293,24 @@ func getAlertURLGenerator(externalURL *url.URL, externalAlertSource string, vali
}, nil
}
if validateTemplate {
if err := templates.ValidateTemplates(map[string]string{
if err := notifier.ValidateTemplates(map[string]string{
"tpl": externalAlertSource,
}); err != nil {
return nil, fmt.Errorf("cannot parse `external.alert.source` %q: %w", externalAlertSource, err)
return nil, fmt.Errorf("error validating source template %s: %w", externalAlertSource, err)
}
}
var err error
tmpl := templates.GetCurrentTmpl()
tmpl, err = templates.ParseWithFixedHeader(externalAlertSource, tmpl)
if err != nil {
return nil, err
m := map[string]string{
"tpl": externalAlertSource,
}
return func(alert notifier.Alert) string {
// recreate template if it was changed during config reload
cm := templates.GetCurrentTmpl()
if tmpl.Name() != cm.Name() {
tmpl = cm
tmpl, err = templates.ParseWithFixedHeader(externalAlertSource, tmpl)
if err != nil {
logger.Errorf("cannot parse `external.alert.source` %q: %w", externalAlertSource, err)
return fmt.Sprintf("%s/%s", externalURL, externalAlertSource)
}
qFn := func(_ string) ([]datasource.Metric, error) {
return nil, fmt.Errorf("`query` template isn't supported for alert source template")
}
tplData := alert.ToTplData()
rr, err := templates.ExecuteWithTemplate(tplData, tmpl)
templated, err := alert.ExecTemplate(qFn, alert.Labels, m)
if err != nil {
logger.Errorf("can not template alert source: %v", err)
return fmt.Sprintf("%s/%s", externalURL, externalAlertSource)
logger.Errorf("cannot template alert source: %s", err)
}
return fmt.Sprintf("%s/%s", externalURL, rr)
return fmt.Sprintf("%s/%s", externalURL, templated["tpl"])
}, nil
}
@@ -344,7 +334,7 @@ func configReload(ctx context.Context, m *manager, groupsCfg []config.Group, sig
var validateTplFn config.ValidateTplFn
if *validateTemplates {
validateTplFn = templates.ValidateTemplates
validateTplFn = notifier.ValidateTemplates
}
// init metrics for config state with positive values to improve alerting conditions
@@ -373,7 +363,7 @@ func configReload(ctx context.Context, m *manager, groupsCfg []config.Group, sig
logger.Errorf("failed to reload notifier config: %s", err)
continue
}
err := templates.LoadTemplateFile(*ruleTemplatesPath)
err := templates.Load(*ruleTemplatesPath, *extURL)
if err != nil {
setConfigError(err)
logger.Errorf("failed to load new templates: %s", err)
@@ -386,6 +376,7 @@ func configReload(ctx context.Context, m *manager, groupsCfg []config.Group, sig
continue
}
if configsEqual(newGroupsCfg, groupsCfg) {
templates.Reload()
// set success to 1 since previous reload could have been unsuccessful
// do not update configTimestamp as config version remains old.
configSuccess.Set(1)
@@ -399,6 +390,7 @@ func configReload(ctx context.Context, m *manager, groupsCfg []config.Group, sig
logger.Errorf("error while reloading rules: %s", err)
continue
}
templates.Reload()
groupsCfg = newGroupsCfg
setConfigSuccessAt(fasttime.UnixTimestamp())
logger.Infof("Rules reloaded successfully from %q", *rulePath)

View File

@@ -74,7 +74,10 @@ func TestGetAlertURLGenerator(t *testing.T) {
func TestConfigReload(t *testing.T) {
originalRulePath := *rulePath
originalExternalURL := extURL
extURL = &url.URL{}
defer func() {
extURL = originalExternalURL
*rulePath = originalRulePath
}()

View File

@@ -160,8 +160,8 @@ func (m *manager) update(ctx context.Context, groupsCfg []config.Group, restore
// it is important to call InterruptEval before the update, because cancel fn
// can be re-assigned during the update.
item.old.InterruptEval()
go func(old *rule.Group, new *rule.Group) {
old.UpdateWith(new)
go func(oldGroup *rule.Group, newGroup *rule.Group) {
oldGroup.UpdateWith(newGroup)
wg.Done()
}(item.old, item.new)
}

View File

@@ -19,7 +19,7 @@ import (
)
func TestMain(m *testing.M) {
if err := templates.Init([]string{"testdata/templates/*good.tmpl"}, nil, url.URL{}); err != nil {
if err := templates.Load([]string{"testdata/templates/*good.tmpl"}, url.URL{}); err != nil {
os.Exit(1)
}
os.Exit(m.Run())
@@ -72,7 +72,7 @@ func TestManagerUpdateConcurrent(t *testing.T) {
r := rand.New(rand.NewSource(int64(n)))
for i := 0; i < iterations; i++ {
rnd := r.Intn(len(paths))
cfg, err := config.Parse([]string{paths[rnd]}, templates.ValidateTemplates, true)
cfg, err := config.Parse([]string{paths[rnd]}, notifier.ValidateTemplates, true)
if err != nil { // update can fail and this is expected
continue
}
@@ -135,7 +135,7 @@ func TestManagerUpdate_Success(t *testing.T) {
t.Fatalf("failed to complete initial rules update: %s", err)
}
cfgUpdate, err := config.Parse([]string{updatePath}, templates.ValidateTemplates, true)
cfgUpdate, err := config.Parse([]string{updatePath}, notifier.ValidateTemplates, true)
if err == nil { // update can fail and that's expected
_ = m.update(ctx, cfgUpdate, false)
}
@@ -326,7 +326,7 @@ func loadCfg(t *testing.T, path []string, validateAnnotations, validateExpressio
t.Helper()
var validateTplFn config.ValidateTplFn
if validateAnnotations {
validateTplFn = templates.ValidateTemplates
validateTplFn = notifier.ValidateTemplates
}
cfg, err := config.Parse(path, validateTplFn, validateExpressions)
if err != nil {

View File

@@ -1,9 +1,15 @@
package notifier
import (
"bytes"
"fmt"
"io"
"strings"
textTpl "text/template"
"time"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/templates"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/utils"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promrelabel"
)
@@ -70,18 +76,118 @@ func (as AlertState) String() string {
return "inactive"
}
// ToTplData converts Alert to AlertTplData,
// which only exposes necessary fields for template.
func (a Alert) ToTplData() templates.AlertTplData {
return templates.AlertTplData{
// AlertTplData is used to execute templating
type AlertTplData struct {
Labels map[string]string
Value float64
Expr string
AlertID uint64
GroupID uint64
ActiveAt time.Time
For time.Duration
}
var tplHeaders = []string{
"{{ $value := .Value }}",
"{{ $labels := .Labels }}",
"{{ $expr := .Expr }}",
"{{ $externalLabels := .ExternalLabels }}",
"{{ $externalURL := .ExternalURL }}",
"{{ $alertID := .AlertID }}",
"{{ $groupID := .GroupID }}",
"{{ $activeAt := .ActiveAt }}",
"{{ $for := .For }}",
}
// ExecTemplate executes the Alert template for given
// map of annotations.
// Every alert could have a different datasource, so function
// requires a queryFunction as an argument.
func (a *Alert) ExecTemplate(q templates.QueryFn, labels, annotations map[string]string) (map[string]string, error) {
tplData := AlertTplData{
Value: a.Value,
Labels: a.Labels,
Labels: labels,
Expr: a.Expr,
AlertID: a.ID,
GroupID: a.GroupID,
ActiveAt: a.ActiveAt,
For: a.For,
}
return ExecTemplate(q, annotations, tplData)
}
// ExecTemplate executes the given template for given annotations map.
func ExecTemplate(q templates.QueryFn, annotations map[string]string, tplData AlertTplData) (map[string]string, error) {
tmpl, err := templates.GetWithFuncs(templates.FuncsWithQuery(q))
if err != nil {
return nil, fmt.Errorf("error cloning template: %w", err)
}
return templateAnnotations(annotations, tplData, tmpl, true)
}
// ValidateTemplates validate annotations for possible template error, uses empty data for template population
func ValidateTemplates(annotations map[string]string) error {
tmpl, err := templates.GetWithFuncs(nil)
if err != nil {
return err
}
_, err = templateAnnotations(annotations, AlertTplData{
Labels: map[string]string{},
Value: 0,
}, tmpl, false)
return err
}
func templateAnnotations(annotations map[string]string, data AlertTplData, tmpl *textTpl.Template, execute bool) (map[string]string, error) {
var builder strings.Builder
var buf bytes.Buffer
eg := new(utils.ErrGroup)
r := make(map[string]string, len(annotations))
tData := tplData{data, externalLabels, externalURL}
header := strings.Join(tplHeaders, "")
for key, text := range annotations {
// simple check to skip text without template
if !strings.Contains(text, "{{") || !strings.Contains(text, "}}") {
r[key] = text
continue
}
buf.Reset()
builder.Reset()
builder.Grow(len(header) + len(text))
builder.WriteString(header)
builder.WriteString(text)
// clone a new template for each parse to avoid collision
ctmpl, _ := tmpl.Clone()
ctmpl = ctmpl.Option("missingkey=zero")
if err := templateAnnotation(&buf, builder.String(), tData, ctmpl, execute); err != nil {
r[key] = text
eg.Add(fmt.Errorf("key %q, template %q: %w", key, text, err))
continue
}
r[key] = buf.String()
}
return r, eg.Err()
}
type tplData struct {
AlertTplData
ExternalLabels map[string]string
ExternalURL string
}
func templateAnnotation(dst io.Writer, text string, data tplData, tpl *textTpl.Template, execute bool) error {
tpl, err := tpl.Parse(text)
if err != nil {
return fmt.Errorf("error parsing annotation template: %w", err)
}
if !execute {
return nil
}
if err = tpl.Execute(dst, data); err != nil {
return fmt.Errorf("error evaluating annotation template: %w", err)
}
return nil
}
func (a Alert) applyRelabelingIfNeeded(relabelCfg *promrelabel.ParsedConfigs) []prompbmarshal.Label {

View File

@@ -1,13 +1,207 @@
package notifier
import (
"fmt"
"reflect"
"testing"
"time"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/datasource"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promrelabel"
)
func TestAlertExecTemplate(t *testing.T) {
extLabels := make(map[string]string)
const (
extCluster = "prod"
extDC = "east"
extURL = "https://foo.bar"
)
extLabels["cluster"] = extCluster
extLabels["dc"] = extDC
_, err := Init(nil, extLabels, extURL)
checkErr(t, err)
f := func(alert *Alert, annotations map[string]string, tplExpected map[string]string) {
t.Helper()
if err := ValidateTemplates(annotations); err != nil {
t.Fatalf("cannot validate annotations: %s", err)
}
qFn := func(_ string) ([]datasource.Metric, error) {
return []datasource.Metric{
{
Labels: []prompbmarshal.Label{
{Name: "foo", Value: "bar"},
{Name: "baz", Value: "qux"},
},
Values: []float64{1},
Timestamps: []int64{1},
},
{
Labels: []prompbmarshal.Label{
{Name: "foo", Value: "garply"},
{Name: "baz", Value: "fred"},
},
Values: []float64{2},
Timestamps: []int64{1},
},
}, nil
}
tpl, err := alert.ExecTemplate(qFn, alert.Labels, annotations)
if err != nil {
t.Fatalf("cannot execute template: %s", err)
}
if len(tpl) != len(tplExpected) {
t.Fatalf("unexpected number of elements; got %d; want %d", len(tpl), len(tplExpected))
}
for k := range tplExpected {
got, exp := tpl[k], tplExpected[k]
if got != exp {
t.Fatalf("unexpected template for key=%q; got %q; want %q", k, got, exp)
}
}
}
// empty-alert
f(&Alert{}, map[string]string{}, map[string]string{})
// no-template
f(&Alert{
Value: 1e4,
Labels: map[string]string{
"instance": "localhost",
},
}, map[string]string{
"summary": "it's a test summary",
"description": "it's a test description",
}, map[string]string{
"summary": "it's a test summary",
"description": "it's a test description",
})
// label-template
f(&Alert{
Value: 1e4,
Labels: map[string]string{
"job": "staging",
"instance": "localhost",
},
For: 5 * time.Minute,
}, map[string]string{
"summary": "Too high connection number for {{$labels.instance}} for job {{$labels.job}}",
"description": "It is {{ $value }} connections for {{$labels.instance}} for more than {{ .For }}",
}, map[string]string{
"summary": "Too high connection number for localhost for job staging",
"description": "It is 10000 connections for localhost for more than 5m0s",
})
// label template override
f(&Alert{
Value: 1e4,
}, map[string]string{
"summary": `{{- define "default.template" -}} {{ printf "summary" }} {{- end -}} {{ template "default.template" . }}`,
"description": `{{ template "default.template" . }}`,
"value": `{{$value }}`,
}, map[string]string{
"summary": "summary",
"description": "",
"value": "10000",
})
// expression-template
f(&Alert{
Expr: `vm_rows{"label"="bar"}<0`,
}, map[string]string{
"exprEscapedQuery": "{{ $expr|queryEscape }}",
"exprEscapedPath": "{{ $expr|pathEscape }}",
"exprEscapedJSON": "{{ $expr|jsonEscape }}",
"exprEscapedQuotes": "{{ $expr|quotesEscape }}",
"exprEscapedHTML": "{{ $expr|htmlEscape }}",
}, map[string]string{
"exprEscapedQuery": "vm_rows%7B%22label%22%3D%22bar%22%7D%3C0",
"exprEscapedPath": "vm_rows%7B%22label%22=%22bar%22%7D%3C0",
"exprEscapedJSON": `"vm_rows{\"label\"=\"bar\"}\u003c0"`,
"exprEscapedQuotes": `vm_rows{\"label\"=\"bar\"}\u003c0`,
"exprEscapedHTML": "vm_rows{&quot;label&quot;=&quot;bar&quot;}&lt;0",
})
// query
f(&Alert{
Expr: `vm_rows{"label"="bar"}>0`,
}, map[string]string{
"summary": `{{ query "foo" | first | value }}`,
"desc": `{{ range query "bar" }}{{ . | label "foo" }} {{ . | value }};{{ end }}`,
}, map[string]string{
"summary": "1",
"desc": "bar 1;garply 2;",
})
// external
f(&Alert{
Value: 1e4,
Labels: map[string]string{
"job": "staging",
"instance": "localhost",
},
}, map[string]string{
"url": "{{ $externalURL }}",
"summary": "Issues with {{$labels.instance}} (dc-{{$externalLabels.dc}}) for job {{$labels.job}}",
"description": "It is {{ $value }} connections for {{$labels.instance}} (cluster-{{$externalLabels.cluster}})",
}, map[string]string{
"url": extURL,
"summary": fmt.Sprintf("Issues with localhost (dc-%s) for job staging", extDC),
"description": fmt.Sprintf("It is 10000 connections for localhost (cluster-%s)", extCluster),
})
// alert and group IDs
f(&Alert{
ID: 42,
GroupID: 24,
}, map[string]string{
"url": "/api/v1/alert?alertID={{$alertID}}&groupID={{$groupID}}",
}, map[string]string{
"url": "/api/v1/alert?alertID=42&groupID=24",
})
// ActiveAt time
f(&Alert{
ActiveAt: time.Date(2022, 8, 19, 20, 34, 58, 651387237, time.UTC),
}, map[string]string{
"diagram": "![](http://example.com?render={{$activeAt.Unix}}",
}, map[string]string{
"diagram": "![](http://example.com?render=1660941298",
})
// ActiveAt time is nil
f(&Alert{}, map[string]string{
"default_time": "{{$activeAt}}",
}, map[string]string{
"default_time": "0001-01-01 00:00:00 +0000 UTC",
})
// ActiveAt custom format
f(&Alert{
ActiveAt: time.Date(2022, 8, 19, 20, 34, 58, 651387237, time.UTC),
}, map[string]string{
"fire_time": `{{$activeAt.Format "2006/01/02 15:04:05"}}`,
}, map[string]string{
"fire_time": "2022/08/19 20:34:58",
})
// ActiveAt query range
f(&Alert{
ActiveAt: time.Date(2022, 8, 19, 20, 34, 58, 651387237, time.UTC),
}, map[string]string{
"grafana_url": `vm-grafana.com?from={{($activeAt.Add (parseDurationTime "1h")).Unix}}&to={{($activeAt.Add (parseDurationTime "-1h")).Unix}}`,
}, map[string]string{
"grafana_url": "vm-grafana.com?from=1660944898&to=1660937698",
})
}
func TestAlert_toPromLabels(t *testing.T) {
fn := func(labels map[string]string, exp []prompbmarshal.Label, relabel *promrelabel.ParsedConfigs) {
t.Helper()

View File

@@ -3,6 +3,7 @@ package notifier
import (
"flag"
"fmt"
"net/url"
"strings"
"time"
@@ -72,6 +73,15 @@ func Reload() error {
var staticNotifiersFn func() []Notifier
var (
// externalLabels is a global variable for holding external labels configured via flags
// It is supposed to be inited via Init function only.
externalLabels map[string]string
// externalURL is a global variable for holding external URL value configured via flag
// It is supposed to be inited via Init function only.
externalURL string
)
// Init returns a function for retrieving actual list of Notifier objects.
// Init works in two mods:
// - configuration via flags (for backward compatibility). Is always static
@@ -79,7 +89,14 @@ var staticNotifiersFn func() []Notifier
// - configuration via file. Supports live reloads and service discovery.
//
// Init returns an error if both mods are used.
func Init(gen AlertURLGenerator) (func() []Notifier, error) {
func Init(gen AlertURLGenerator, extLabels map[string]string, extURL string) (func() []Notifier, error) {
externalURL = extURL
externalLabels = extLabels
_, err := url.Parse(externalURL)
if err != nil {
return nil, fmt.Errorf("failed to parse external URL: %w", err)
}
if *blackHole {
if len(*addrs) > 0 || *configPath != "" {
return nil, fmt.Errorf("only one of -notifier.blackhole, -notifier.url and -notifier.config flags must be specified")
@@ -109,7 +126,6 @@ func Init(gen AlertURLGenerator) (func() []Notifier, error) {
return staticNotifiersFn, nil
}
var err error
cw, err = newWatcher(*configPath, gen)
if err != nil {
return nil, fmt.Errorf("failed to init config watcher: %w", err)

View File

@@ -12,7 +12,7 @@ func TestInit(t *testing.T) {
*addrs = flagutil.ArrayString{"127.0.0.1", "127.0.0.2"}
fn, err := Init(nil)
fn, err := Init(nil, nil, "")
if err != nil {
t.Fatalf("%s", err)
}
@@ -52,7 +52,7 @@ func TestInitNegative(t *testing.T) {
*configPath = path
*addrs = flagutil.ArrayString{addr}
*blackHole = bh
if _, err := Init(nil); err == nil {
if _, err := Init(nil, nil, ""); err == nil {
t.Fatalf("expected to get error; got nil instead")
}
}
@@ -69,7 +69,7 @@ func TestBlackHole(t *testing.T) {
*blackHole = true
fn, err := Init(nil)
fn, err := Init(nil, nil, "")
if err != nil {
t.Fatalf("%s", err)
}

View File

@@ -9,7 +9,7 @@ import (
)
func TestMain(m *testing.M) {
if err := templates.Init([]string{"testdata/templates/*good.tmpl"}, nil, url.URL{}); err != nil {
if err := templates.Load([]string{"testdata/templates/*good.tmpl"}, url.URL{}); err != nil {
os.Exit(1)
}
os.Exit(m.Run())

View File

@@ -7,7 +7,6 @@ import (
"sort"
"strings"
"sync"
textTpl "text/template"
"time"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/config"
@@ -48,12 +47,6 @@ type AlertingRule struct {
state *ruleState
metrics *alertingRuleMetrics
// set rootTemplateName with loaded global template name,
// so we can check if the template has changed when evaluating.
rootTemplateName string
LabelTemplates map[string]*textTpl.Template
AnnotationsTemplates map[string]*textTpl.Template
}
type alertingRuleMetrics struct {
@@ -149,47 +142,9 @@ func NewAlertingRule(qb datasource.QuerierBuilder, group *Group, cfg config.Rule
}
return seriesFetched
})
ar.initTemplate()
return ar
}
// initTemplate pre-creates templates that can be reused in execution.
func (ar *AlertingRule) initTemplate() {
currentTmpl := templates.GetCurrentTmpl()
ar.rootTemplateName = currentTmpl.Name()
ar.LabelTemplates = make(map[string]*textTpl.Template, len(ar.Labels))
ar.AnnotationsTemplates = make(map[string]*textTpl.Template, len(ar.Annotations))
for k, v := range ar.Labels {
var err error
tmpl, _ := currentTmpl.Clone()
tmpl, err = templates.ParseWithFixedHeader(v, tmpl)
if err != nil {
// parse can fail in two cases:
// 1. the text contains `query` function, which is not supported during rule initialization.
// 2. the text itself is invalid.
// In both case, we skip the error here, and try it again during rule execution.
continue
}
ar.LabelTemplates[k] = tmpl
}
for k, v := range ar.Annotations {
var err error
tmpl, _ := currentTmpl.Clone()
tmpl, err = templates.ParseWithFixedHeader(v, tmpl)
if err != nil {
// parse can fail in two cases:
// 1. the text contains `query` function, which is not supported during rule initialization.
// 2. the text itself is invalid.
// In both case, we skip the error here, and try it again during rule execution.
continue
}
ar.AnnotationsTemplates[k] = tmpl
}
}
// close unregisters rule metrics
func (ar *AlertingRule) close() {
ar.metrics.active.Unregister()
@@ -270,8 +225,6 @@ func (ar *AlertingRule) updateWith(r Rule) error {
ar.KeepFiringFor = nr.KeepFiringFor
ar.Labels = nr.Labels
ar.Annotations = nr.Annotations
ar.initTemplate()
ar.EvalInterval = nr.EvalInterval
ar.Debug = nr.Debug
ar.q = nr.q
@@ -326,32 +279,15 @@ func (ar *AlertingRule) toLabels(m datasource.Metric, qFn templates.QueryFn) (*l
}
ls.processed[l.Name] = l.Value
}
extraLabels := make(map[string]string, len(ar.Labels))
// compare to annotation, label value can only use limited variables for now
labelTplData := templates.AlertTplData{
extraLabels, err := notifier.ExecTemplate(qFn, ar.Labels, notifier.AlertTplData{
Labels: ls.origin,
Value: m.Values[0],
Expr: ar.Expr,
})
if err != nil {
return nil, fmt.Errorf("failed to expand labels: %w", err)
}
for k := range ar.Labels {
if ar.LabelTemplates[k] == nil {
// this label may contain `query` function, which requires creating new template with query function in each evaluation.
v, err := templates.ExecuteWithoutTemplate(qFn, ar.Labels[k], labelTplData)
if err != nil {
logger.Errorf("error templating label %q for rule %q: %w", ar.Labels[k], ar.Name, err)
v = ar.Labels[k]
}
extraLabels[k] = v
continue
}
v, err := templates.ExecuteWithTemplate(labelTplData, ar.LabelTemplates[k])
if err != nil {
logger.Errorf("error templating label %q for rule %q: %w", ar.Labels[k], ar.Name, err)
v = ar.Labels[k]
}
extraLabels[k] = v
}
for k, v := range extraLabels {
ls.add(k, v)
}
@@ -577,18 +513,12 @@ func (ar *AlertingRule) exec(ctx context.Context, ts time.Time, limit int) ([]pr
}
func (ar *AlertingRule) expandTemplates(m datasource.Metric, qFn templates.QueryFn, ts time.Time) (*labelSet, map[string]string, error) {
// check if the rule template has changed during reload,
// if so, label&annotation templates must be re-created.
if ar.rootTemplateName != templates.GetCurrentTmpl().Name() {
ar.initTemplate()
}
ls, err := ar.toLabels(m, qFn)
if err != nil {
return nil, nil, fmt.Errorf("failed to expand labels: %w", err)
}
extraAnnotation := make(map[string]string, len(ar.Annotations))
annotationTplData := templates.AlertTplData{
tplData := notifier.AlertTplData{
Value: m.Values[0],
Labels: ls.origin,
Expr: ar.Expr,
@@ -597,26 +527,11 @@ func (ar *AlertingRule) expandTemplates(m datasource.Metric, qFn templates.Query
ActiveAt: ts,
For: ar.For,
}
for k := range ar.Annotations {
if ar.AnnotationsTemplates[k] == nil {
// this label may contain `query` function, which requires creating new template with query function in each evaluation.
v, err := templates.ExecuteWithoutTemplate(qFn, ar.Annotations[k], annotationTplData)
if err != nil {
logger.Errorf("error templating annotation %q for rule %q: %w", ar.Annotations[k], ar.Name, err)
v = ar.Annotations[k]
}
extraAnnotation[k] = v
continue
}
v, err := templates.ExecuteWithTemplate(annotationTplData, ar.AnnotationsTemplates[k])
if err != nil {
logger.Errorf("error templating annotation %q for rule %q: %w", ar.Annotations[k], ar.Name, err)
v = ar.Annotations[k]
}
extraAnnotation[k] = v
as, err := notifier.ExecTemplate(qFn, ar.Annotations, tplData)
if err != nil {
return nil, nil, fmt.Errorf("failed to template annotations: %w", err)
}
return ls, extraAnnotation, nil
return ls, as, nil
}
// toTimeSeries creates `ALERTS` and `ALERTS_FOR_STATE` for active alerts

View File

@@ -1046,7 +1046,6 @@ func TestAlertingRule_Template(t *testing.T) {
}
fq.Add(metrics...)
rule.initTemplate()
if _, err := rule.exec(context.TODO(), time.Now(), 0); err != nil {
t.Fatalf("unexpected error: %s", err)
}
@@ -1263,7 +1262,6 @@ func newTestAlertingRuleWithCustomFields(name string, waitFor, evalInterval, kee
}
rule.KeepFiringFor = keepFiringFor
rule.Annotations = annotation
rule.initTemplate()
return rule
}

View File

@@ -443,8 +443,8 @@ func (g *Group) Start(ctx context.Context, nts func() []notifier.Notifier, rw re
}
// UpdateWith inserts new group to updateCh
func (g *Group) UpdateWith(new *Group) {
g.updateCh <- new
func (g *Group) UpdateWith(newGroup *Group) {
g.updateCh <- newGroup
}
// DeepCopy returns a deep copy of group

View File

@@ -27,7 +27,7 @@ func init() {
}
func TestMain(m *testing.M) {
if err := templates.Init([]string{}, nil, url.URL{}); err != nil {
if err := templates.Load([]string{}, url.URL{}); err != nil {
fmt.Println("failed to load template for test")
os.Exit(1)
}

View File

@@ -1,553 +0,0 @@
package templates
import (
"errors"
"fmt"
htmlTpl "html/template"
"io"
"math"
"net"
"net/url"
"regexp"
"sort"
"strconv"
"strings"
"sync"
textTpl "text/template"
"time"
"github.com/bmatcuk/doublestar/v4"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/datasource"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/formatutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutils"
)
var (
tplMu sync.RWMutex
masterTmpl *textTpl.Template
// externalLabels is a global variable for holding external labels configured via flags
// It is supposed to be initiated via Init function only.
externalLabels map[string]string
// externalURL is a global variable for holding external URL value configured via flag
// It is supposed to be initiated via Init function only.
externalURL url.URL
)
// Init initializes global externalLabels and externalURL variables, and load templates from pathPatterns.
func Init(pathPatterns []string, extLabels map[string]string, extURL url.URL) error {
externalURL = extURL
externalLabels = extLabels
return LoadTemplateFile(pathPatterns)
}
// LoadTemplateFile loads templates from multiple globs specified in pathPatterns:
// 1. if it's the first load, sets them directly to current template;
// 2. if it's not the first load, only update masterTmpl when the contents change.
func LoadTemplateFile(pathPatterns []string) error {
templateName := fmt.Sprintf("rule-template-%d", time.Now().UnixMilli())
// using Load timestamp as template root name,
// so we can check if this global template has been reloaded when use it elsewhere, like during alerting rules execution.
tmpl := newTemplate(templateName)
tmpl = tmpl.Funcs(funcsWithExternalURL(externalURL))
for _, tp := range pathPatterns {
p, err := doublestar.FilepathGlob(tp)
if err != nil {
return fmt.Errorf("failed to retrieve a template glob %q: %w", tp, err)
}
if len(p) > 0 {
tmpl, err = tmpl.ParseFiles(p...)
if err != nil {
return fmt.Errorf("failed to parse template glob %q: %w", tp, err)
}
}
}
err := tmpl.Execute(io.Discard, nil)
if err != nil {
return fmt.Errorf("failed to test rule template: %w", err)
}
tplMu.Lock()
defer tplMu.Unlock()
if masterTmpl == nil {
masterTmpl = tmpl
} else {
// only update the masterTmpl when content has changed
if !isTemplatesTheSame(masterTmpl, tmpl) {
masterTmpl = tmpl
}
}
return nil
}
func newTemplate(name string) *textTpl.Template {
tmpl := textTpl.New(name).Funcs(templateFuncs())
return textTpl.Must(tmpl.Parse(""))
}
// isTemplatesTheSame returns true if the content of two templates are the same,
// the root template name difference is ignored.
func isTemplatesTheSame(t1, t2 *textTpl.Template) bool {
getRootString := func(t *textTpl.Template) string {
if t.Tree == nil || t.Tree.Root == nil {
return ""
}
return t.Tree.Root.String()
}
if getRootString(t1) != getRootString(t2) {
return false
}
t1Templates := make(map[string]string)
t2Templates := make(map[string]string)
for _, tmpl := range t1.Templates() {
// skip root template, since it's null and changes every time
if tmpl.Name() == t1.Name() {
continue
}
t1Templates[tmpl.Name()] = getRootString(tmpl)
}
for _, tmpl := range t2.Templates() {
if tmpl.Name() == t2.Name() {
continue
}
t2Templates[tmpl.Name()] = getRootString(tmpl)
}
if len(t1Templates) != len(t2Templates) {
return false
}
for k, v := range t1Templates {
if t2Templates[k] != v {
return false
}
delete(t2Templates, k)
}
return len(t2Templates) == 0
}
// funcsWithExternalURL returns a function map that depends on externalURL value
func funcsWithExternalURL(externalURL url.URL) textTpl.FuncMap {
return textTpl.FuncMap{
"externalURL": func() string {
return externalURL.String()
},
"pathPrefix": func() string {
return externalURL.Path
},
}
}
// templateFuncs initiates template helper functions
func templateFuncs() textTpl.FuncMap {
// See https://prometheus.io/docs/prometheus/latest/configuration/template_reference/
// and https://github.com/prometheus/prometheus/blob/fa6e05903fd3ce52e374a6e1bf4eb98c9f1f45a7/template/template.go#L150
return textTpl.FuncMap{
/* Strings */
// title returns a copy of the string s with all Unicode letters
// that begin words mapped to their Unicode title case.
// alias for https://golang.org/pkg/strings/#Title
"title": strings.Title,
// toUpper returns s with all Unicode letters mapped to their upper case.
// alias for https://golang.org/pkg/strings/#ToUpper
"toUpper": strings.ToUpper,
// toLower returns s with all Unicode letters mapped to their lower case.
// alias for https://golang.org/pkg/strings/#ToLower
"toLower": strings.ToLower,
// crlfEscape replaces '\n' and '\r' chars with `\\n` and `\\r`.
// This function is deprecated.
//
// It is better to use quotesEscape, jsonEscape, queryEscape or pathEscape instead -
// these functions properly escape `\n` and `\r` chars according to their purpose.
"crlfEscape": func(q string) string {
q = strings.Replace(q, "\n", `\n`, -1)
return strings.Replace(q, "\r", `\r`, -1)
},
// quotesEscape escapes the string, so it can be safely put inside JSON string.
//
// See also jsonEscape.
"quotesEscape": quotesEscape,
// jsonEscape converts the string to properly encoded JSON string.
//
// See also quotesEscape.
"jsonEscape": jsonEscape,
// htmlEscape applies html-escaping to q, so it can be safely embedded as plaintext into html.
//
// See also safeHtml.
"htmlEscape": htmlEscape,
// stripPort splits string into host and port, then returns only host.
"stripPort": func(hostPort string) string {
host, _, err := net.SplitHostPort(hostPort)
if err != nil {
return hostPort
}
return host
},
// stripDomain removes the domain part of a FQDN. Leaves port untouched.
"stripDomain": func(hostPort string) string {
host, port, err := net.SplitHostPort(hostPort)
if err != nil {
host = hostPort
}
ip := net.ParseIP(host)
if ip != nil {
return hostPort
}
host = strings.Split(host, ".")[0]
if port != "" {
return net.JoinHostPort(host, port)
}
return host
},
// match reports whether the string s
// contains any match of the regular expression pattern.
// alias for https://golang.org/pkg/regexp/#MatchString
"match": regexp.MatchString,
// reReplaceAll ReplaceAllString returns a copy of src, replacing matches of the Regexp with
// the replacement string repl. Inside repl, $ signs are interpreted as in Expand,
// so for instance $1 represents the text of the first submatch.
// alias for https://golang.org/pkg/regexp/#Regexp.ReplaceAllString
"reReplaceAll": func(pattern, repl, text string) string {
re := regexp.MustCompile(pattern)
return re.ReplaceAllString(text, repl)
},
// parseDuration parses a duration string such as "1h" into the number of seconds it represents
"parseDuration": func(s string) (float64, error) {
d, err := promutils.ParseDuration(s)
if err != nil {
return 0, err
}
return d.Seconds(), nil
},
// same with parseDuration but returns a time.Duration
"parseDurationTime": func(s string) (time.Duration, error) {
d, err := promutils.ParseDuration(s)
if err != nil {
return 0, err
}
return d, nil
},
/* Numbers */
// humanize converts given number to a human readable format
// by adding metric prefixes https://en.wikipedia.org/wiki/Metric_prefix
"humanize": func(i any) (string, error) {
v, err := toFloat64(i)
if err != nil {
return "", err
}
if v == 0 || math.IsNaN(v) || math.IsInf(v, 0) {
return fmt.Sprintf("%.4g", v), nil
}
if math.Abs(v) >= 1 {
prefix := ""
for _, p := range []string{"k", "M", "G", "T", "P", "E", "Z", "Y"} {
if math.Abs(v) < 1000 {
break
}
prefix = p
v /= 1000
}
return fmt.Sprintf("%.4g%s", v, prefix), nil
}
prefix := ""
for _, p := range []string{"m", "u", "n", "p", "f", "a", "z", "y"} {
if math.Abs(v) >= 1 {
break
}
prefix = p
v *= 1000
}
return fmt.Sprintf("%.4g%s", v, prefix), nil
},
// humanize1024 converts given number to a human readable format with 1024 as base
"humanize1024": func(i any) (string, error) {
v, err := toFloat64(i)
if err != nil {
return "", err
}
if math.Abs(v) <= 1 || math.IsNaN(v) || math.IsInf(v, 0) {
return fmt.Sprintf("%.4g", v), nil
}
return formatutil.HumanizeBytes(v), nil
},
// humanizeDuration converts given seconds to a human-readable duration
"humanizeDuration": func(i any) (string, error) {
v, err := toFloat64(i)
if err != nil {
return "", err
}
if math.IsNaN(v) || math.IsInf(v, 0) {
return fmt.Sprintf("%.4g", v), nil
}
if v == 0 {
return fmt.Sprintf("%.4gs", v), nil
}
if math.Abs(v) >= 1 {
sign := ""
if v < 0 {
sign = "-"
v = -v
}
seconds := int64(v) % 60
minutes := (int64(v) / 60) % 60
hours := (int64(v) / 60 / 60) % 24
days := int64(v) / 60 / 60 / 24
// For days to minutes, we display seconds as an integer.
if days != 0 {
return fmt.Sprintf("%s%dd %dh %dm %ds", sign, days, hours, minutes, seconds), nil
}
if hours != 0 {
return fmt.Sprintf("%s%dh %dm %ds", sign, hours, minutes, seconds), nil
}
if minutes != 0 {
return fmt.Sprintf("%s%dm %ds", sign, minutes, seconds), nil
}
// For seconds, we display 4 significant digits.
return fmt.Sprintf("%s%.4gs", sign, v), nil
}
prefix := ""
for _, p := range []string{"m", "u", "n", "p", "f", "a", "z", "y"} {
if math.Abs(v) >= 1 {
break
}
prefix = p
v *= 1000
}
return fmt.Sprintf("%.4g%ss", v, prefix), nil
},
// humanizePercentage converts given ratio value to a fraction of 100
"humanizePercentage": func(i any) (string, error) {
v, err := toFloat64(i)
if err != nil {
return "", err
}
return fmt.Sprintf("%.4g%%", v*100), nil
},
// humanizeTimestamp converts given timestamp to a human readable time equivalent
"humanizeTimestamp": func(i any) (string, error) {
v, err := toFloat64(i)
if err != nil {
return "", err
}
if math.IsNaN(v) || math.IsInf(v, 0) {
return fmt.Sprintf("%.4g", v), nil
}
t := timeFromUnixTimestamp(v).Time().UTC()
return fmt.Sprint(t), nil
},
// toTime converts given timestamp to a time.Time.
"toTime": func(i any) (time.Time, error) {
v, err := toFloat64(i)
if err != nil {
return time.Time{}, err
}
if math.IsNaN(v) || math.IsInf(v, 0) {
return time.Time{}, fmt.Errorf("cannot convert %v to time.Time", v)
}
t := timeFromUnixTimestamp(v).Time().UTC()
return t, nil
},
/* URLs */
// externalURL returns value of `external.url` flag
"externalURL": func() string {
// externalURL function supposed to be substituted at FuncsWithExteralURL().
// it is present here only for validation purposes, when there is no
// provided datasource.
//
// return non-empty slice to pass validation with chained functions in template
return ""
},
// pathPrefix returns a Path segment from the URL value in `external.url` flag
"pathPrefix": func() string {
// pathPrefix function supposed to be substituted at FuncsWithExteralURL().
// it is present here only for validation purposes, when there is no
// provided datasource.
//
// return non-empty slice to pass validation with chained functions in template
return ""
},
// pathEscape escapes the string so it can be safely placed inside a URL path segment.
//
// See also queryEscape.
"pathEscape": url.PathEscape,
// queryEscape escapes the string so it can be safely placed inside a query arg in URL.
//
// See also queryEscape.
"queryEscape": url.QueryEscape,
// first returns the first by order element from the given metrics list.
// usually used alongside with `query` template function.
"first": func(metrics []metric) (metric, error) {
if len(metrics) > 0 {
return metrics[0], nil
}
return metric{}, errors.New("first() called on vector with no elements")
},
// label returns the value of the given label name for the given metric.
// usually used alongside with `query` template function.
"label": func(label string, m metric) string {
return m.Labels[label]
},
// value returns the value of the given metric.
// usually used alongside with `query` template function.
"value": func(m metric) float64 {
return m.Value
},
// strvalue returns metric name.
"strvalue": func(m metric) string {
return m.Labels["__name__"]
},
// sortByLabel sorts the given metrics by provided label key
"sortByLabel": func(label string, metrics []metric) []metric {
sort.SliceStable(metrics, func(i, j int) bool {
return metrics[i].Labels[label] < metrics[j].Labels[label]
})
return metrics
},
/* Helpers */
// Converts a list of objects to a map with keys arg0, arg1 etc.
// This is intended to allow multiple arguments to be passed to templates.
"args": func(args ...any) map[string]any {
result := make(map[string]any)
for i, a := range args {
result[fmt.Sprintf("arg%d", i)] = a
}
return result
},
// safeHtml marks string as HTML not requiring auto-escaping.
//
// See also htmlEscape.
"safeHtml": func(text string) htmlTpl.HTML {
return htmlTpl.HTML(text)
},
}
}
// metric is private copy of datasource.Metric,
// it is used for templating annotations,
// Labels as map simplifies templates evaluation.
type metric struct {
Labels map[string]string
Timestamp int64
Value float64
}
// datasourceMetricsToTemplateMetrics converts Metrics from datasource package to private copy for templating.
func datasourceMetricsToTemplateMetrics(ms []datasource.Metric) []metric {
mss := make([]metric, 0, len(ms))
for _, m := range ms {
labelsMap := make(map[string]string, len(m.Labels))
for _, labelValue := range m.Labels {
labelsMap[labelValue.Name] = labelValue.Value
}
mss = append(mss, metric{
Labels: labelsMap,
Timestamp: m.Timestamps[0],
Value: m.Values[0]})
}
return mss
}
// QueryFn is used to wrap a call to datasource into simple-to-use function
// for templating functions.
type QueryFn func(query string) ([]datasource.Metric, error)
// FuncsWithQuery returns a function map that depends on metric data
func FuncsWithQuery(query QueryFn) textTpl.FuncMap {
return textTpl.FuncMap{
"query": func(q string) ([]metric, error) {
if query == nil {
return nil, fmt.Errorf("cannot execute query %q: query is not available in this context", q)
}
result, err := query(q)
if err != nil {
return nil, err
}
return datasourceMetricsToTemplateMetrics(result), nil
},
}
}
// Time is the number of milliseconds since the epoch
// (1970-01-01 00:00 UTC) excluding leap seconds.
type Time int64
// timeFromUnixTimestamp returns the Time equivalent to t in unix timestamp.
func timeFromUnixTimestamp(t float64) Time {
return Time(t * 1e3)
}
// The number of nanoseconds per minimum tick.
const nanosPerTick = int64(minimumTick / time.Nanosecond)
// MinimumTick is the minimum supported time resolution. This has to be
// at least time.Second in order for the code below to work.
const minimumTick = time.Millisecond
// second is the Time duration equivalent to one second.
const second = int64(time.Second / minimumTick)
// Time returns the time.Time representation of t.
func (t Time) Time() time.Time {
return time.Unix(int64(t)/second, (int64(t)%second)*nanosPerTick)
}
func toFloat64(v any) (float64, error) {
switch i := v.(type) {
case float64:
return i, nil
case float32:
return float64(i), nil
case int64:
return float64(i), nil
case int32:
return float64(i), nil
case int:
return float64(i), nil
case uint64:
return float64(i), nil
case uint32:
return float64(i), nil
case uint:
return float64(i), nil
case string:
return strconv.ParseFloat(i, 64)
default:
return 0, fmt.Errorf("unexpected value type %v", i)
}
}

View File

@@ -1,261 +0,0 @@
package templates
import (
"fmt"
"math"
"net/url"
"os"
"strings"
"testing"
textTpl "text/template"
)
func TestMain(m *testing.M) {
if err := Init([]string{}, nil, url.URL{}); err != nil {
fmt.Println("failed to load template for test")
os.Exit(1)
}
os.Exit(m.Run())
}
func TestTemplateFuncs_Match(t *testing.T) {
funcs := templateFuncs()
// check "match" func
matchFunc := funcs["match"].(func(pattern, s string) (bool, error))
if _, err := matchFunc("invalid[regexp", "abc"); err == nil {
t.Fatalf("expecting non-nil error on invalid regexp")
}
ok, err := matchFunc("abc", "def")
if err != nil {
t.Fatalf("unexpected error")
}
if ok {
t.Fatalf("unexpected match")
}
ok, err = matchFunc("a.+b", "acsdb")
if err != nil {
t.Fatalf("unexpected error")
}
if !ok {
t.Fatalf("unexpected mismatch")
}
}
func TestTemplateFuncs_Formatting(t *testing.T) {
f := func(funcName string, p any, resultExpected string) {
t.Helper()
funcs := templateFuncs()
v := funcs[funcName]
fLocal := v.(func(s any) (string, error))
result, err := fLocal(p)
if err != nil {
t.Fatalf("unexpected error for %s(%f): %s", funcName, p, err)
}
if result != resultExpected {
t.Fatalf("unexpected result for %s(%f); got\n%s\nwant\n%s", funcName, p, result, resultExpected)
}
}
f("humanize1024", float64(0), "0")
f("humanize1024", math.Inf(0), "+Inf")
f("humanize1024", math.NaN(), "NaN")
f("humanize1024", float64(127087), "124.1ki")
f("humanize1024", float64(130137088), "124.1Mi")
f("humanize1024", float64(133260378112), "124.1Gi")
f("humanize1024", float64(136458627186688), "124.1Ti")
f("humanize1024", float64(139733634239168512), "124.1Pi")
f("humanize1024", float64(143087241460908556288), "124.1Ei")
f("humanize1024", float64(146521335255970361638912), "124.1Zi")
f("humanize1024", float64(150037847302113650318245888), "124.1Yi")
f("humanize1024", float64(153638755637364377925883789312), "1.271e+05Yi")
f("humanize", float64(127087), "127.1k")
f("humanize", float64(136458627186688), "136.5T")
f("humanizeDuration", 1, "1s")
f("humanizeDuration", 0.2, "200ms")
f("humanizeDuration", 42000, "11h 40m 0s")
f("humanizeDuration", 16790555, "194d 8h 2m 35s")
f("humanizePercentage", 1, "100%")
f("humanizePercentage", 0.8, "80%")
f("humanizePercentage", 0.015, "1.5%")
f("humanizeTimestamp", 1679055557, "2023-03-17 12:19:17 +0000 UTC")
}
func TestTemplateFuncs_StringConversion(t *testing.T) {
f := func(funcName, s, resultExpected string) {
t.Helper()
funcs := templateFuncs()
v := funcs[funcName]
fLocal := v.(func(s string) string)
result := fLocal(s)
if result != resultExpected {
t.Fatalf("unexpected result for %s(%q); got\n%s\nwant\n%s", funcName, s, result, resultExpected)
}
}
f("title", "foo bar", "Foo Bar")
f("toUpper", "foo", "FOO")
f("toLower", "FOO", "foo")
f("pathEscape", "foo/bar\n+baz", "foo%2Fbar%0A+baz")
f("queryEscape", "foo+bar\n+baz", "foo%2Bbar%0A%2Bbaz")
f("jsonEscape", `foo{bar="baz"}`+"\n + 1", `"foo{bar=\"baz\"}\n + 1"`)
f("quotesEscape", `foo{bar="baz"}`+"\n + 1", `foo{bar=\"baz\"}\n + 1`)
f("htmlEscape", "foo < 10\nabc", "foo &lt; 10\nabc")
f("crlfEscape", "foo\nbar\rx", `foo\nbar\rx`)
f("stripPort", "foo", "foo")
f("stripPort", "foo:1234", "foo")
f("stripDomain", "foo.bar.baz", "foo")
f("stripDomain", "foo.bar:123", "foo:123")
}
func TestTemplatesLoad_Success(t *testing.T) {
f := func(pathPatterns []string, expectedTmpl *textTpl.Template) {
t.Helper()
masterTmplOrig := masterTmpl
defer func() {
masterTmpl = masterTmplOrig
}()
masterTmpl = nil
if err := LoadTemplateFile(pathPatterns); err != nil {
t.Fatalf("cannot load templates: %s", err)
}
if !isTemplatesTheSame(masterTmpl, expectedTmpl) {
t.Fatalf("unexpected template\ngot\n%+v\nwant\n%+v", masterTmpl, expectedTmpl)
}
}
// non existing path
pathPatterns := []string{
"templates/non-existing/good-*.tpl",
"templates/absent/good-*.tpl",
}
expectedTmpl := textTpl.Must(newTemplate("").Parse(""))
f(pathPatterns, expectedTmpl)
// existing path
pathPatterns = []string{
"templates/test/good0-*.tpl",
}
expectedTmpl = textTpl.Must(newTemplate("").Parse(`
{{- define "good0-test.tpl" -}}{{- end -}}
{{- define "test.0" -}}
{{ printf "Hello %s!" externalURL }}
{{- end -}}
{{- define "test.2" -}}
{{ printf "Hello %s!" externalURL }}
{{- end -}}
{{- define "test.3" -}}
{{ printf "Hello %s!" externalURL }}
{{- end -}}
`))
f(pathPatterns, expectedTmpl)
// template update
pathPatterns = []string{
"templates/other/nested/good0-*.tpl",
}
expectedTmpl = textTpl.Must(newTemplate("").Parse(`
{{- define "good0-test.tpl" -}}{{- end -}}
{{- define "test.0" -}}
{{ printf "Hello %s!" externalURL }}
{{- end -}}
{{- define "test.1" -}}
{{ printf "Hello %s!" externalURL }}
{{- end -}}
{{- define "test.3" -}}
{{ printf "Hello %s!" externalURL }}
{{- end -}}
`))
f(pathPatterns, expectedTmpl)
}
func TestTemplatesLoad_Failure(t *testing.T) {
f := func(pathPatterns []string, expectedErrStr string) {
t.Helper()
err := LoadTemplateFile(pathPatterns)
if err == nil {
t.Fatalf("expecting non-nil error")
}
errStr := err.Error()
if !strings.Contains(errStr, expectedErrStr) {
t.Fatalf("the returned error %q doesn't contain %q", errStr, expectedErrStr)
}
}
// load template with syntax error
f([]string{
"templates/other/nested/bad0-*.tpl",
"templates/test/good0-*.tpl",
}, "failed to parse template glob")
}
func TestTemplatesReload(t *testing.T) {
masterTmplOrig := masterTmpl
defer func() {
masterTmpl = masterTmplOrig
}()
masterTmpl = nil
// load with non existing path
pathPatterns := []string{
"templates/non-existing/good-*.tpl",
"templates/absent/good-*.tpl",
}
if err := LoadTemplateFile(pathPatterns); err != nil {
t.Fatalf("cannot load templates: %s", err)
}
tpl1 := GetCurrentTmpl()
// reload with existing path
pathPatterns = []string{
"templates/test/good0-*.tpl",
}
if err := LoadTemplateFile(pathPatterns); err != nil {
t.Fatalf("cannot load templates: %s", err)
}
tpl2 := GetCurrentTmpl()
if isTemplatesTheSame(tpl1, tpl2) {
t.Fatalf("tpl1 should be different from tpl2")
}
// reload the same path
pathPatterns = []string{
"templates/test/good0-*.tpl",
}
if err := LoadTemplateFile(pathPatterns); err != nil {
t.Fatalf("cannot load templates: %s", err)
}
tpl3 := GetCurrentTmpl()
if !isTemplatesTheSame(tpl2, tpl3) || tpl2.Name() != tpl3.Name() {
t.Fatalf("tpl3 should be the same as tpl2")
}
}
func TestIsTemplateTheSame(t *testing.T) {
f := func(tmpl1, tmpl2 *textTpl.Template, isTheSame bool) {
t.Helper()
if isTemplatesTheSame(tmpl1, tmpl2) != isTheSame {
t.Fatalf("unexpected result for isTemplatesTheSame")
}
}
tmpl1 := textTpl.Must(newTemplate("t1").Parse("{{- define \"test\" -}}{{- end -}}"))
tmpl2 := textTpl.Must(newTemplate("t2").Parse("{{- define \"test\" -}}{{- end -}}"))
f(tmpl1, tmpl2, true)
tmpl1, _ = tmpl1.Parse("{{- define \"test2\" -}}{{- end -}}")
f(tmpl1, tmpl2, false)
tmpl2, _ = tmpl2.Parse("{{- define \"test3\" -}}{{- end -}}")
f(tmpl1, tmpl2, false)
}

View File

@@ -1,95 +1,554 @@
// Copyright 2013 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package templates
import (
"bytes"
"errors"
"fmt"
htmlTpl "html/template"
"io"
"math"
"net"
"net/url"
"regexp"
"sort"
"strconv"
"strings"
"sync"
textTpl "text/template"
"time"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
"github.com/bmatcuk/doublestar/v4"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/datasource"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/formatutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutils"
)
// supported variables are list in https://docs.victoriametrics.com/vmalert/#templating.
const tplHeaders = `{{ $value := .Value }}{{ $labels := .Labels }}{{ $expr := .Expr }}{{ $externalLabels := .ExternalLabels }}{{ $externalURL := .ExternalURL }}{{ $alertID := .AlertID }}{{ $groupID := .GroupID }}{{ $activeAt := .ActiveAt }}{{ $for := .For }}`
// go template execution fails when it's tree is empty
const defaultTemplate = `{{- define "default.template" -}}{{- end -}}`
// AlertTplData is used to execute templating
type AlertTplData struct {
Labels map[string]string
Value float64
Expr string
AlertID uint64
GroupID uint64
ActiveAt time.Time
For time.Duration
var tplMu sync.RWMutex
type textTemplate struct {
current *textTpl.Template
replacement *textTpl.Template
}
// ValidateTemplates validates the given annotations,
// mock the `query` function during validation.
func ValidateTemplates(annotations map[string]string) error {
// it's ok to reuse one template for multiple text validations.
tmpl := GetCurrentTmpl()
tmpl = tmpl.Funcs(FuncsWithQuery(nil))
for _, v := range annotations {
_, err := tmpl.Parse(tplHeaders + v)
var masterTmpl textTemplate
func newTemplate() *textTpl.Template {
tmpl := textTpl.New("").Option("missingkey=zero").Funcs(templateFuncs())
return textTpl.Must(tmpl.Parse(defaultTemplate))
}
// Load func loads templates from multiple globs specified in pathPatterns and either
// sets them directly to current template if it's the first init;
// or sets replacement templates and wait for Reload() to replace current template with replacement.
func Load(pathPatterns []string, externalURL url.URL) error {
tmpl := newTemplate()
for _, tp := range pathPatterns {
p, err := doublestar.FilepathGlob(tp)
if err != nil {
return fmt.Errorf("failed to parse text %q into template: %w", v, err)
return fmt.Errorf("failed to retrieve a template glob %q: %w", tp, err)
}
if len(p) > 0 {
tmpl, err = tmpl.ParseFiles(p...)
if err != nil {
return fmt.Errorf("failed to parse template glob %q: %w", tp, err)
}
}
}
if len(tmpl.Templates()) > 0 {
err := tmpl.Execute(io.Discard, nil)
if err != nil {
return fmt.Errorf("failed to execute template: %w", err)
}
}
tplMu.Lock()
defer tplMu.Unlock()
tmpl = tmpl.Funcs(funcsWithExternalURL(externalURL))
if masterTmpl.current == nil {
masterTmpl.current = tmpl
} else {
masterTmpl.replacement = tmpl
}
return nil
}
// GetCurrentTmpl returns a copy of the current global template
func GetCurrentTmpl() *textTpl.Template {
// Reload func replaces current template with a replacement template
// which was set by Load with override=false
func Reload() {
tplMu.Lock()
defer tplMu.Unlock()
if masterTmpl.replacement != nil {
masterTmpl.current = masterTmpl.replacement
masterTmpl.replacement = nil
}
}
// metric is private copy of datasource.Metric,
// it is used for templating annotations,
// Labels as map simplifies templates evaluation.
type metric struct {
Labels map[string]string
Timestamp int64
Value float64
}
// datasourceMetricsToTemplateMetrics converts Metrics from datasource package to private copy for templating.
func datasourceMetricsToTemplateMetrics(ms []datasource.Metric) []metric {
mss := make([]metric, 0, len(ms))
for _, m := range ms {
labelsMap := make(map[string]string, len(m.Labels))
for _, labelValue := range m.Labels {
labelsMap[labelValue.Name] = labelValue.Value
}
mss = append(mss, metric{
Labels: labelsMap,
Timestamp: m.Timestamps[0],
Value: m.Values[0]})
}
return mss
}
// QueryFn is used to wrap a call to datasource into simple-to-use function
// for templating functions.
type QueryFn func(query string) ([]datasource.Metric, error)
// GetWithFuncs returns a copy of current template with additional FuncMap
// provided with funcs argument
func GetWithFuncs(funcs textTpl.FuncMap) (*textTpl.Template, error) {
tplMu.RLock()
defer tplMu.RUnlock()
tmpl, err := masterTmpl.Clone()
tmpl, err := masterTmpl.current.Clone()
if err != nil {
logger.Panicf("failed to clone current rule template: %w", err)
return nil, err
}
return tmpl
// Clone() doesn't copy tpl Options, so we set them manually
tmpl = tmpl.Option("missingkey=zero")
return tmpl.Funcs(funcs), nil
}
type tplData struct {
AlertTplData
ExternalLabels map[string]string
ExternalURL string
// FuncsWithQuery returns a function map that depends on metric data
func FuncsWithQuery(query QueryFn) textTpl.FuncMap {
return textTpl.FuncMap{
"query": func(q string) ([]metric, error) {
if query == nil {
return nil, fmt.Errorf("cannot execute query %q: query is not available in this context", q)
}
result, err := query(q)
if err != nil {
return nil, err
}
return datasourceMetricsToTemplateMetrics(result), nil
},
}
}
// ParseWithFixedHeader parses the text with the fixed tplHeaders into the given template
func ParseWithFixedHeader(text string, tpl *textTpl.Template) (*textTpl.Template, error) {
return tpl.Parse(tplHeaders + text)
// funcsWithExternalURL returns a function map that depends on externalURL value
func funcsWithExternalURL(externalURL url.URL) textTpl.FuncMap {
return textTpl.FuncMap{
"externalURL": func() string {
return externalURL.String()
},
"pathPrefix": func() string {
return externalURL.Path
},
}
}
// ExecuteWithoutTemplate retrieves the current global templates, parses the text and executes with the given data
func ExecuteWithoutTemplate(q QueryFn, text string, data AlertTplData) (string, error) {
if !strings.Contains(text, "{{") || !strings.Contains(text, "}}") {
return text, nil
}
// templateFuncs initiates template helper functions
func templateFuncs() textTpl.FuncMap {
// See https://prometheus.io/docs/prometheus/latest/configuration/template_reference/
// and https://github.com/prometheus/prometheus/blob/fa6e05903fd3ce52e374a6e1bf4eb98c9f1f45a7/template/template.go#L150
return textTpl.FuncMap{
/* Strings */
var err error
tmpl := GetCurrentTmpl()
tmpl = tmpl.Funcs(FuncsWithQuery(q))
tmpl, err = tmpl.Parse(tplHeaders + text)
if err != nil {
return "", fmt.Errorf("failed to parse text %q into template: %w", text, err)
// title returns a copy of the string s with all Unicode letters
// that begin words mapped to their Unicode title case.
// alias for https://golang.org/pkg/strings/#Title
"title": strings.Title,
// toUpper returns s with all Unicode letters mapped to their upper case.
// alias for https://golang.org/pkg/strings/#ToUpper
"toUpper": strings.ToUpper,
// toLower returns s with all Unicode letters mapped to their lower case.
// alias for https://golang.org/pkg/strings/#ToLower
"toLower": strings.ToLower,
// crlfEscape replaces '\n' and '\r' chars with `\\n` and `\\r`.
// This function is deprecated.
//
// It is better to use quotesEscape, jsonEscape, queryEscape or pathEscape instead -
// these functions properly escape `\n` and `\r` chars according to their purpose.
"crlfEscape": func(q string) string {
q = strings.Replace(q, "\n", `\n`, -1)
return strings.Replace(q, "\r", `\r`, -1)
},
// quotesEscape escapes the string, so it can be safely put inside JSON string.
//
// See also jsonEscape.
"quotesEscape": quotesEscape,
// jsonEscape converts the string to properly encoded JSON string.
//
// See also quotesEscape.
"jsonEscape": jsonEscape,
// htmlEscape applies html-escaping to q, so it can be safely embedded as plaintext into html.
//
// See also safeHtml.
"htmlEscape": htmlEscape,
// stripPort splits string into host and port, then returns only host.
"stripPort": func(hostPort string) string {
host, _, err := net.SplitHostPort(hostPort)
if err != nil {
return hostPort
}
return host
},
// stripDomain removes the domain part of a FQDN. Leaves port untouched.
"stripDomain": func(hostPort string) string {
host, port, err := net.SplitHostPort(hostPort)
if err != nil {
host = hostPort
}
ip := net.ParseIP(host)
if ip != nil {
return hostPort
}
host = strings.Split(host, ".")[0]
if port != "" {
return net.JoinHostPort(host, port)
}
return host
},
// match reports whether the string s
// contains any match of the regular expression pattern.
// alias for https://golang.org/pkg/regexp/#MatchString
"match": regexp.MatchString,
// reReplaceAll ReplaceAllString returns a copy of src, replacing matches of the Regexp with
// the replacement string repl. Inside repl, $ signs are interpreted as in Expand,
// so for instance $1 represents the text of the first submatch.
// alias for https://golang.org/pkg/regexp/#Regexp.ReplaceAllString
"reReplaceAll": func(pattern, repl, text string) string {
re := regexp.MustCompile(pattern)
return re.ReplaceAllString(text, repl)
},
// parseDuration parses a duration string such as "1h" into the number of seconds it represents
"parseDuration": func(s string) (float64, error) {
d, err := promutils.ParseDuration(s)
if err != nil {
return 0, err
}
return d.Seconds(), nil
},
// same with parseDuration but returns a time.Duration
"parseDurationTime": func(s string) (time.Duration, error) {
d, err := promutils.ParseDuration(s)
if err != nil {
return 0, err
}
return d, nil
},
/* Numbers */
// humanize converts given number to a human readable format
// by adding metric prefixes https://en.wikipedia.org/wiki/Metric_prefix
"humanize": func(i any) (string, error) {
v, err := toFloat64(i)
if err != nil {
return "", err
}
if v == 0 || math.IsNaN(v) || math.IsInf(v, 0) {
return fmt.Sprintf("%.4g", v), nil
}
if math.Abs(v) >= 1 {
prefix := ""
for _, p := range []string{"k", "M", "G", "T", "P", "E", "Z", "Y"} {
if math.Abs(v) < 1000 {
break
}
prefix = p
v /= 1000
}
return fmt.Sprintf("%.4g%s", v, prefix), nil
}
prefix := ""
for _, p := range []string{"m", "u", "n", "p", "f", "a", "z", "y"} {
if math.Abs(v) >= 1 {
break
}
prefix = p
v *= 1000
}
return fmt.Sprintf("%.4g%s", v, prefix), nil
},
// humanize1024 converts given number to a human readable format with 1024 as base
"humanize1024": func(i any) (string, error) {
v, err := toFloat64(i)
if err != nil {
return "", err
}
if math.Abs(v) <= 1 || math.IsNaN(v) || math.IsInf(v, 0) {
return fmt.Sprintf("%.4g", v), nil
}
return formatutil.HumanizeBytes(v), nil
},
// humanizeDuration converts given seconds to a human-readable duration
"humanizeDuration": func(i any) (string, error) {
v, err := toFloat64(i)
if err != nil {
return "", err
}
if math.IsNaN(v) || math.IsInf(v, 0) {
return fmt.Sprintf("%.4g", v), nil
}
if v == 0 {
return fmt.Sprintf("%.4gs", v), nil
}
if math.Abs(v) >= 1 {
sign := ""
if v < 0 {
sign = "-"
v = -v
}
seconds := int64(v) % 60
minutes := (int64(v) / 60) % 60
hours := (int64(v) / 60 / 60) % 24
days := int64(v) / 60 / 60 / 24
// For days to minutes, we display seconds as an integer.
if days != 0 {
return fmt.Sprintf("%s%dd %dh %dm %ds", sign, days, hours, minutes, seconds), nil
}
if hours != 0 {
return fmt.Sprintf("%s%dh %dm %ds", sign, hours, minutes, seconds), nil
}
if minutes != 0 {
return fmt.Sprintf("%s%dm %ds", sign, minutes, seconds), nil
}
// For seconds, we display 4 significant digits.
return fmt.Sprintf("%s%.4gs", sign, v), nil
}
prefix := ""
for _, p := range []string{"m", "u", "n", "p", "f", "a", "z", "y"} {
if math.Abs(v) >= 1 {
break
}
prefix = p
v *= 1000
}
return fmt.Sprintf("%.4g%ss", v, prefix), nil
},
// humanizePercentage converts given ratio value to a fraction of 100
"humanizePercentage": func(i any) (string, error) {
v, err := toFloat64(i)
if err != nil {
return "", err
}
return fmt.Sprintf("%.4g%%", v*100), nil
},
// humanizeTimestamp converts given timestamp to a human readable time equivalent
"humanizeTimestamp": func(i any) (string, error) {
v, err := toFloat64(i)
if err != nil {
return "", err
}
if math.IsNaN(v) || math.IsInf(v, 0) {
return fmt.Sprintf("%.4g", v), nil
}
t := timeFromUnixTimestamp(v).Time().UTC()
return fmt.Sprint(t), nil
},
// toTime converts given timestamp to a time.Time.
"toTime": func(i any) (time.Time, error) {
v, err := toFloat64(i)
if err != nil {
return time.Time{}, err
}
if math.IsNaN(v) || math.IsInf(v, 0) {
return time.Time{}, fmt.Errorf("cannot convert %v to time.Time", v)
}
t := timeFromUnixTimestamp(v).Time().UTC()
return t, nil
},
/* URLs */
// externalURL returns value of `external.url` flag
"externalURL": func() string {
// externalURL function supposed to be substituted at FuncsWithExteralURL().
// it is present here only for validation purposes, when there is no
// provided datasource.
//
// return non-empty slice to pass validation with chained functions in template
return ""
},
// pathPrefix returns a Path segment from the URL value in `external.url` flag
"pathPrefix": func() string {
// pathPrefix function supposed to be substituted at FuncsWithExteralURL().
// it is present here only for validation purposes, when there is no
// provided datasource.
//
// return non-empty slice to pass validation with chained functions in template
return ""
},
// pathEscape escapes the string so it can be safely placed inside a URL path segment.
//
// See also queryEscape.
"pathEscape": url.PathEscape,
// queryEscape escapes the string so it can be safely placed inside a query arg in URL.
//
// See also queryEscape.
"queryEscape": url.QueryEscape,
// query executes the MetricsQL/PromQL query against
// configured `datasource.url` address.
// For example, {{ query "foo" | first | value }} will
// execute "/api/v1/query?query=foo" request and will return
// the first value in response.
"query": func(_ string) ([]metric, error) {
// query function supposed to be substituted at FuncsWithQuery().
// it is present here only for validation purposes, when there is no
// provided datasource.
//
// return non-empty slice to pass validation with chained functions in template
// see issue #989 for details
return []metric{{}}, nil
},
// first returns the first by order element from the given metrics list.
// usually used alongside with `query` template function.
"first": func(metrics []metric) (metric, error) {
if len(metrics) > 0 {
return metrics[0], nil
}
return metric{}, errors.New("first() called on vector with no elements")
},
// label returns the value of the given label name for the given metric.
// usually used alongside with `query` template function.
"label": func(label string, m metric) string {
return m.Labels[label]
},
// value returns the value of the given metric.
// usually used alongside with `query` template function.
"value": func(m metric) float64 {
return m.Value
},
// strvalue returns metric name.
"strvalue": func(m metric) string {
return m.Labels["__name__"]
},
// sortByLabel sorts the given metrics by provided label key
"sortByLabel": func(label string, metrics []metric) []metric {
sort.SliceStable(metrics, func(i, j int) bool {
return metrics[i].Labels[label] < metrics[j].Labels[label]
})
return metrics
},
/* Helpers */
// Converts a list of objects to a map with keys arg0, arg1 etc.
// This is intended to allow multiple arguments to be passed to templates.
"args": func(args ...any) map[string]any {
result := make(map[string]any)
for i, a := range args {
result[fmt.Sprintf("arg%d", i)] = a
}
return result
},
// safeHtml marks string as HTML not requiring auto-escaping.
//
// See also htmlEscape.
"safeHtml": func(text string) htmlTpl.HTML {
return htmlTpl.HTML(text)
},
}
return ExecuteWithTemplate(data, tmpl)
}
// ExecuteWithTemplate executes with the given template and data
func ExecuteWithTemplate(data AlertTplData, tpl *textTpl.Template) (string, error) {
fullData := tplData{
data,
externalLabels,
externalURL.String(),
}
var buf bytes.Buffer
// returns the zero value for the map type's element
tpl.Option("missingkey=zero")
if err := tpl.Execute(&buf, fullData); err != nil {
return "", fmt.Errorf("failed to execute template: %w", err)
}
return buf.String(), nil
// Time is the number of milliseconds since the epoch
// (1970-01-01 00:00 UTC) excluding leap seconds.
type Time int64
// timeFromUnixTimestamp returns the Time equivalent to t in unix timestamp.
func timeFromUnixTimestamp(t float64) Time {
return Time(t * 1e3)
}
// The number of nanoseconds per minimum tick.
const nanosPerTick = int64(minimumTick / time.Nanosecond)
// MinimumTick is the minimum supported time resolution. This has to be
// at least time.Second in order for the code below to work.
const minimumTick = time.Millisecond
// second is the Time duration equivalent to one second.
const second = int64(time.Second / minimumTick)
// Time returns the time.Time representation of t.
func (t Time) Time() time.Time {
return time.Unix(int64(t)/second, (int64(t)%second)*nanosPerTick)
}
func toFloat64(v any) (float64, error) {
switch i := v.(type) {
case float64:
return i, nil
case float32:
return float64(i), nil
case int64:
return float64(i), nil
case int32:
return float64(i), nil
case int:
return float64(i), nil
case uint64:
return float64(i), nil
case uint32:
return float64(i), nil
case uint:
return float64(i), nil
case string:
return strconv.ParseFloat(i, 64)
default:
return 0, fmt.Errorf("unexpected value type %v", i)
}
}

View File

@@ -1,226 +1,239 @@
package templates
import (
"fmt"
"math"
"net/url"
"strings"
"testing"
"time"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/datasource"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
textTpl "text/template"
)
func TestValidateTemplates(t *testing.T) {
f := func(annotations map[string]string, isValid bool) {
func TestTemplateFuncs_StringConversion(t *testing.T) {
f := func(funcName, s, resultExpected string) {
t.Helper()
err := ValidateTemplates(annotations)
if (err == nil) != isValid {
t.Fatalf("failed to validate template, got %t; want %t", (err == nil), isValid)
funcs := templateFuncs()
v := funcs[funcName]
fLocal := v.(func(s string) string)
result := fLocal(s)
if result != resultExpected {
t.Fatalf("unexpected result for %s(%q); got\n%s\nwant\n%s", funcName, s, result, resultExpected)
}
}
// empty
f(map[string]string{}, true)
// wrong text
f(map[string]string{
"summary": "{{",
}, false)
// valid
f(map[string]string{
"value": "{{$value}}",
"summary": "it's a test summary",
}, true)
// invalid variable
f(map[string]string{
"value": "{{$invalidValue}}",
"summary": "it's a test summary",
}, false)
f("title", "foo bar", "Foo Bar")
f("toUpper", "foo", "FOO")
f("toLower", "FOO", "foo")
f("pathEscape", "foo/bar\n+baz", "foo%2Fbar%0A+baz")
f("queryEscape", "foo+bar\n+baz", "foo%2Bbar%0A%2Bbaz")
f("jsonEscape", `foo{bar="baz"}`+"\n + 1", `"foo{bar=\"baz\"}\n + 1"`)
f("quotesEscape", `foo{bar="baz"}`+"\n + 1", `foo{bar=\"baz\"}\n + 1`)
f("htmlEscape", "foo < 10\nabc", "foo &lt; 10\nabc")
f("crlfEscape", "foo\nbar\rx", `foo\nbar\rx`)
f("stripPort", "foo", "foo")
f("stripPort", "foo:1234", "foo")
f("stripDomain", "foo.bar.baz", "foo")
f("stripDomain", "foo.bar:123", "foo:123")
}
func TestExecuteWithoutTemplate(t *testing.T) {
extLabels := make(map[string]string)
const (
extCluster = "prod"
extDC = "east"
extURL = "https://foo.bar"
)
url, _ := url.Parse(extURL)
extLabels["cluster"] = extCluster
extLabels["dc"] = extDC
err := Init(nil, extLabels, *url)
func TestTemplateFuncs_Match(t *testing.T) {
funcs := templateFuncs()
// check "match" func
matchFunc := funcs["match"].(func(pattern, s string) (bool, error))
if _, err := matchFunc("invalid[regexp", "abc"); err == nil {
t.Fatalf("expecting non-nil error on invalid regexp")
}
ok, err := matchFunc("abc", "def")
if err != nil {
t.Fatalf("cannot init templates: %s", err)
t.Fatalf("unexpected error")
}
if ok {
t.Fatalf("unexpected match")
}
ok, err = matchFunc("a.+b", "acsdb")
if err != nil {
t.Fatalf("unexpected error")
}
if !ok {
t.Fatalf("unexpected mismatch")
}
}
f := func(data AlertTplData, annotations, expResults map[string]string) {
func TestTemplateFuncs_Formatting(t *testing.T) {
f := func(funcName string, p any, resultExpected string) {
t.Helper()
qFn := func(_ string) ([]datasource.Metric, error) {
return []datasource.Metric{
{
Labels: []prompbmarshal.Label{
{Name: "foo", Value: "bar"},
{Name: "baz", Value: "qux"},
},
Values: []float64{1},
Timestamps: []int64{1},
},
{
Labels: []prompbmarshal.Label{
{Name: "foo", Value: "garply"},
{Name: "baz", Value: "fred"},
},
Values: []float64{2},
Timestamps: []int64{1},
},
}, nil
funcs := templateFuncs()
v := funcs[funcName]
fLocal := v.(func(s any) (string, error))
result, err := fLocal(p)
if err != nil {
t.Fatalf("unexpected error for %s(%f): %s", funcName, p, err)
}
for k := range annotations {
v, err := ExecuteWithoutTemplate(qFn, annotations[k], data)
if err != nil {
t.Fatalf("cannot execute template: %s", err)
}
if v != expResults[k] {
t.Fatalf("unexpected result; got %s; want %s", v, expResults[k])
}
if result != resultExpected {
t.Fatalf("unexpected result for %s(%f); got\n%s\nwant\n%s", funcName, p, result, resultExpected)
}
}
// empty-alert
f(AlertTplData{}, map[string]string{}, map[string]string{})
f("humanize1024", float64(0), "0")
f("humanize1024", math.Inf(0), "+Inf")
f("humanize1024", math.NaN(), "NaN")
f("humanize1024", float64(127087), "124.1ki")
f("humanize1024", float64(130137088), "124.1Mi")
f("humanize1024", float64(133260378112), "124.1Gi")
f("humanize1024", float64(136458627186688), "124.1Ti")
f("humanize1024", float64(139733634239168512), "124.1Pi")
f("humanize1024", float64(143087241460908556288), "124.1Ei")
f("humanize1024", float64(146521335255970361638912), "124.1Zi")
f("humanize1024", float64(150037847302113650318245888), "124.1Yi")
f("humanize1024", float64(153638755637364377925883789312), "1.271e+05Yi")
// no-template
f(AlertTplData{
Value: 1e4,
Labels: map[string]string{
"instance": "localhost",
},
}, map[string]string{
"summary": "it's a test summary",
"description": "it's a test description",
}, map[string]string{
"summary": "it's a test summary",
"description": "it's a test description",
})
f("humanize", float64(127087), "127.1k")
f("humanize", float64(136458627186688), "136.5T")
// label-template
f(AlertTplData{
Value: 1e4,
Labels: map[string]string{
"job": "staging",
"instance": "localhost",
},
For: 5 * time.Minute,
}, map[string]string{
"summary": "Too high connection number for {{$labels.instance}} for job {{$labels.job}}",
"description": "It is {{ $value }} connections for {{$labels.instance}} for more than {{ .For }}",
"non-existing-label": "{{$labels.nonexisting}}",
}, map[string]string{
"summary": "Too high connection number for localhost for job staging",
"description": "It is 10000 connections for localhost for more than 5m0s",
"non-existing-label": "",
})
f("humanizeDuration", 1, "1s")
f("humanizeDuration", 0.2, "200ms")
f("humanizeDuration", 42000, "11h 40m 0s")
f("humanizeDuration", 16790555, "194d 8h 2m 35s")
// label template override
f(AlertTplData{
Value: 1e4,
}, map[string]string{
"summary": `{{- define "default.template" -}} {{ printf "summary" }} {{- end -}} {{ template "default.template" . }}`,
"description": `{{- define "default.template" -}} {{ printf "description" }} {{- end -}} {{ template "default.template" . }}`,
"value": `{{$value }}`,
}, map[string]string{
"summary": "summary",
"description": "description",
"value": "10000",
})
f("humanizePercentage", 1, "100%")
f("humanizePercentage", 0.8, "80%")
f("humanizePercentage", 0.015, "1.5%")
// expression-template
f(AlertTplData{
Expr: `vm_rows{"label"="bar"}<0`,
}, map[string]string{
"exprEscapedQuery": "{{ $expr|queryEscape }}",
"exprEscapedPath": "{{ $expr|pathEscape }}",
"exprEscapedJSON": "{{ $expr|jsonEscape }}",
"exprEscapedQuotes": "{{ $expr|quotesEscape }}",
"exprEscapedHTML": "{{ $expr|htmlEscape }}",
}, map[string]string{
"exprEscapedQuery": "vm_rows%7B%22label%22%3D%22bar%22%7D%3C0",
"exprEscapedPath": "vm_rows%7B%22label%22=%22bar%22%7D%3C0",
"exprEscapedJSON": `"vm_rows{\"label\"=\"bar\"}\u003c0"`,
"exprEscapedQuotes": `vm_rows{\"label\"=\"bar\"}\u003c0`,
"exprEscapedHTML": "vm_rows{&quot;label&quot;=&quot;bar&quot;}&lt;0",
})
// query function
f(AlertTplData{
Expr: `vm_rows{"label"="bar"}>0`,
}, map[string]string{
"summary": `{{ query "foo" | first | value }}`,
"desc": `{{ range query "bar" }}{{ . | label "foo" }} {{ . | value }};{{ end }}`,
}, map[string]string{
"summary": "1",
"desc": "bar 1;garply 2;",
})
// external
f(AlertTplData{
Value: 1e4,
Labels: map[string]string{
"job": "staging",
"instance": "localhost",
},
}, map[string]string{
"url": "{{ $externalURL }}",
"summary": "Issues with {{$labels.instance}} (dc-{{$externalLabels.dc}}) for job {{$labels.job}}",
"description": "It is {{ $value }} connections for {{$labels.instance}} (cluster-{{$externalLabels.cluster}})",
}, map[string]string{
"url": extURL,
"summary": fmt.Sprintf("Issues with localhost (dc-%s) for job staging", extDC),
"description": fmt.Sprintf("It is 10000 connections for localhost (cluster-%s)", extCluster),
})
// alert, group IDs & ActiveAt time
f(AlertTplData{
AlertID: 42,
GroupID: 24,
ActiveAt: time.Date(2022, 8, 19, 20, 34, 58, 651387237, time.UTC),
}, map[string]string{
"url": "/api/v1/alert?alertID={{$alertID}}&groupID={{$groupID}}",
"diagram": "![](http://example.com?render={{$activeAt.Unix}}",
}, map[string]string{
"url": "/api/v1/alert?alertID=42&groupID=24",
"diagram": "![](http://example.com?render=1660941298",
})
// ActiveAt time is nil
f(AlertTplData{}, map[string]string{
"default_time": "{{$activeAt}}",
}, map[string]string{
"default_time": "0001-01-01 00:00:00 +0000 UTC",
})
// ActiveAt custom format
f(AlertTplData{
ActiveAt: time.Date(2022, 8, 19, 20, 34, 58, 651387237, time.UTC),
}, map[string]string{
"fire_time": `{{$activeAt.Format "2006/01/02 15:04:05"}}`,
}, map[string]string{
"fire_time": "2022/08/19 20:34:58",
})
// ActiveAt query range
f(AlertTplData{
ActiveAt: time.Date(2022, 8, 19, 20, 34, 58, 651387237, time.UTC),
}, map[string]string{
"grafana_url": `vm-grafana.com?from={{($activeAt.Add (parseDurationTime "1h")).Unix}}&to={{($activeAt.Add (parseDurationTime "-1h")).Unix}}`,
}, map[string]string{
"grafana_url": "vm-grafana.com?from=1660944898&to=1660937698",
})
f("humanizeTimestamp", 1679055557, "2023-03-17 12:19:17 +0000 UTC")
}
func mkTemplate(current, replacement any) textTemplate {
tmpl := textTemplate{}
if current != nil {
switch val := current.(type) {
case string:
tmpl.current = textTpl.Must(newTemplate().Parse(val))
}
}
if replacement != nil {
switch val := replacement.(type) {
case string:
tmpl.replacement = textTpl.Must(newTemplate().Parse(val))
}
}
return tmpl
}
func equalTemplates(tmpls ...*textTpl.Template) bool {
var cmp *textTpl.Template
for i, tmpl := range tmpls {
if i == 0 {
cmp = tmpl
} else {
if cmp == nil || tmpl == nil {
if cmp != tmpl {
return false
}
continue
}
if len(tmpl.Templates()) != len(cmp.Templates()) {
return false
}
for _, t := range tmpl.Templates() {
tp := cmp.Lookup(t.Name())
if tp == nil {
return false
}
if tp.Root.String() != t.Root.String() {
return false
}
}
}
}
return true
}
func TestTemplatesLoad_Failure(t *testing.T) {
f := func(pathPatterns []string, expectedErrStr string) {
t.Helper()
err := Load(pathPatterns, url.URL{})
if err == nil {
t.Fatalf("expecting non-nil error")
}
errStr := err.Error()
if !strings.Contains(errStr, expectedErrStr) {
t.Fatalf("the returned error %q doesn't contain %q", errStr, expectedErrStr)
}
}
// load template with syntax error
f([]string{
"templates/other/nested/bad0-*.tpl",
"templates/test/good0-*.tpl",
}, "failed to parse template glob")
}
func TestTemplatesLoad_Success(t *testing.T) {
f := func(pathPatterns []string, expectedTmpl textTemplate) {
t.Helper()
masterTmplOrig := masterTmpl
defer func() {
masterTmpl = masterTmplOrig
}()
if err := Load(pathPatterns, url.URL{}); err != nil {
t.Fatalf("cannot load templates: %s", err)
}
Reload()
if !equalTemplates(masterTmpl.replacement, expectedTmpl.replacement) {
t.Fatalf("unexpected replacement template\ngot\n%+v\nwant\n%+v", masterTmpl.replacement, expectedTmpl.replacement)
}
if !equalTemplates(masterTmpl.current, expectedTmpl.current) {
t.Fatalf("unexpected current template\ngot\n%+v\nwant\n%+v", masterTmpl.current, expectedTmpl.current)
}
}
// non existing path
pathPatterns := []string{
"templates/non-existing/good-*.tpl",
"templates/absent/good-*.tpl",
}
expectedTmpl := mkTemplate(``, nil)
f(pathPatterns, expectedTmpl)
// existing path
pathPatterns = []string{
"templates/test/good0-*.tpl",
}
expectedTmpl = mkTemplate(`
{{- define "good0-test.tpl" -}}{{- end -}}
{{- define "test.0" -}}
{{ printf "Hello %s!" externalURL }}
{{- end -}}
{{- define "test.2" -}}
{{ printf "Hello %s!" externalURL }}
{{- end -}}
{{- define "test.3" -}}
{{ printf "Hello %s!" externalURL }}
{{- end -}}
`, nil)
f(pathPatterns, expectedTmpl)
// existing path defined template override
pathPatterns = []string{
"templates/other/nested/good0-*.tpl",
}
expectedTmpl = mkTemplate(`
{{- define "good0-test.tpl" -}}{{- end -}}
{{- define "test.0" -}}
{{ printf "Hello %s!" externalURL }}
{{- end -}}
{{- define "test.1" -}}
{{ printf "Hello %s!" externalURL }}
{{- end -}}
{{- define "test.3" -}}
{{ printf "Hello %s!" externalURL }}
{{- end -}}
`, nil)
f(pathPatterns, expectedTmpl)
}

View File

@@ -40,15 +40,15 @@ type filter struct {
labelValue string
}
func (f filter) inRange(min, max int64) bool {
func (f filter) inRange(minV, maxV int64) bool {
fmin, fmax := f.min, f.max
if min == 0 {
fmin = min
if minV == 0 {
fmin = minV
}
if fmax == 0 {
fmax = max
fmax = maxV
}
return min <= fmax && fmin <= max
return minV <= fmax && fmin <= maxV
}
// NewClient creates and validates new Client
@@ -59,13 +59,13 @@ func NewClient(cfg Config) (*Client, error) {
return nil, fmt.Errorf("failed to open snapshot %q: %s", cfg.Snapshot, err)
}
c := &Client{DBReadOnly: db}
min, max, err := parseTime(cfg.Filter.TimeMin, cfg.Filter.TimeMax)
minTime, maxTime, err := parseTime(cfg.Filter.TimeMin, cfg.Filter.TimeMax)
if err != nil {
return nil, fmt.Errorf("failed to parse time in filter: %s", err)
}
c.filter = filter{
min: min,
max: max,
min: minTime,
max: maxTime,
label: cfg.Filter.Label,
labelValue: cfg.Filter.LabelValue,
}

View File

@@ -98,13 +98,13 @@ func aggrMin(values []float64) float64 {
if pos < 0 {
return nan
}
min := values[pos]
minV := values[pos]
for _, v := range values[pos+1:] {
if !math.IsNaN(v) && v < min {
min = v
if !math.IsNaN(v) && v < minV {
minV = v
}
}
return min
return minV
}
func aggrMax(values []float64) float64 {
@@ -112,13 +112,13 @@ func aggrMax(values []float64) float64 {
if pos < 0 {
return nan
}
max := values[pos]
maxV := values[pos]
for _, v := range values[pos+1:] {
if !math.IsNaN(v) && v > max {
max = v
if !math.IsNaN(v) && v > maxV {
maxV = v
}
}
return max
return maxV
}
func aggrDiff(values []float64) float64 {
@@ -177,12 +177,12 @@ func aggrCount(values []float64) float64 {
}
func aggrRange(values []float64) float64 {
min := aggrMin(values)
if math.IsNaN(min) {
minV := aggrMin(values)
if math.IsNaN(minV) {
return nan
}
max := aggrMax(values)
return max - min
maxV := aggrMax(values)
return maxV - minV
}
func aggrMultiply(values []float64) float64 {

View File

@@ -2594,17 +2594,17 @@ func transformMinMax(ec *evalConfig, fe *graphiteql.FuncExpr) (nextSeriesFunc, e
}
f := nextSeriesConcurrentWrapper(nextSeries, func(s *series) (*series, error) {
values := s.Values
min := aggrMin(values)
if math.IsNaN(min) {
min = 0
minV := aggrMin(values)
if math.IsNaN(minV) {
minV = 0
}
max := aggrMax(values)
if math.IsNaN(max) {
max = 0
maxV := aggrMax(values)
if math.IsNaN(maxV) {
maxV = 0
}
vRange := max - min
vRange := maxV - minV
for i, v := range values {
v = (v - min) / vRange
v = (v - minV) / vRange
if math.IsInf(v, 0) {
v = 0
}
@@ -2975,9 +2975,9 @@ func transformRemoveAbovePercentile(ec *evalConfig, fe *graphiteql.FuncExpr) (ne
}
f := nextSeriesConcurrentWrapper(nextSeries, func(s *series) (*series, error) {
values := s.Values
max := aggrFunc(values)
maxV := aggrFunc(values)
for i, v := range values {
if v > max {
if v > maxV {
values[i] = nan
}
}
@@ -3035,9 +3035,9 @@ func transformRemoveBelowPercentile(ec *evalConfig, fe *graphiteql.FuncExpr) (ne
}
f := nextSeriesConcurrentWrapper(nextSeries, func(s *series) (*series, error) {
values := s.Values
min := aggrFunc(values)
minV := aggrFunc(values)
for i, v := range values {
if v < min {
if v < minV {
values[i] = nan
}
}
@@ -4514,11 +4514,11 @@ func transformOffsetToZero(ec *evalConfig, fe *graphiteql.FuncExpr) (nextSeriesF
}
f := nextSeriesConcurrentWrapper(nextSeries, func(s *series) (*series, error) {
values := s.Values
min := aggrMin(values)
minV := aggrMin(values)
for i, v := range values {
values[i] = v - min
values[i] = v - minV
}
s.Tags["offsetToZero"] = fmt.Sprintf("%g", min)
s.Tags["offsetToZero"] = fmt.Sprintf("%g", minV)
s.Name = fmt.Sprintf("offsetToZero(%s)", s.Name)
s.expr = fe
s.pathExpression = s.Name
@@ -4567,29 +4567,29 @@ func transformPerSecond(ec *evalConfig, fe *graphiteql.FuncExpr) (nextSeriesFunc
return f, nil
}
func nonNegativeDelta(curr, prev, max, min float64) (float64, float64) {
if !math.IsNaN(max) && curr > max {
func nonNegativeDelta(currV, prevV, maxV, minV float64) (float64, float64) {
if !math.IsNaN(maxV) && currV > maxV {
return nan, nan
}
if !math.IsNaN(min) && curr < min {
if !math.IsNaN(minV) && currV < minV {
return nan, nan
}
if math.IsNaN(curr) || math.IsNaN(prev) {
return nan, curr
if math.IsNaN(currV) || math.IsNaN(prevV) {
return nan, currV
}
if curr >= prev {
return curr - prev, curr
if currV >= prevV {
return currV - prevV, currV
}
if !math.IsNaN(max) {
if math.IsNaN(min) {
min = float64(0)
if !math.IsNaN(maxV) {
if math.IsNaN(minV) {
minV = float64(0)
}
return max + 1 + curr - prev - min, curr
return maxV + 1 + currV - prevV - minV, currV
}
if !math.IsNaN(min) {
return curr - min, curr
if !math.IsNaN(minV) {
return currV - minV, currV
}
return nan, curr
return nan, currV
}
// See https://graphite.readthedocs.io/en/stable/functions.html#graphite.render.functions.threshold
@@ -4941,8 +4941,8 @@ func transformSortByMinima(ec *evalConfig, fe *graphiteql.FuncExpr) (nextSeriesF
}
// Filter out series with all the values smaller than 0
f := nextSeriesConcurrentWrapper(nextSeries, func(s *series) (*series, error) {
max := aggrMax(s.Values)
if math.IsNaN(max) || max <= 0 {
maxV := aggrMax(s.Values)
if math.IsNaN(maxV) || maxV <= 0 {
return nil, nil
}
return s, nil

View File

@@ -295,13 +295,13 @@ func aggrFuncMin(tss []*timeseries) []*timeseries {
}
dst := tss[0]
for i := range dst.Values {
min := dst.Values[i]
minV := dst.Values[i]
for _, ts := range tss {
if math.IsNaN(min) || ts.Values[i] < min {
min = ts.Values[i]
if math.IsNaN(minV) || ts.Values[i] < minV {
minV = ts.Values[i]
}
}
dst.Values[i] = min
dst.Values[i] = minV
}
return tss[:1]
}
@@ -313,13 +313,13 @@ func aggrFuncMax(tss []*timeseries) []*timeseries {
}
dst := tss[0]
for i := range dst.Values {
max := dst.Values[i]
maxV := dst.Values[i]
for _, ts := range tss {
if math.IsNaN(max) || ts.Values[i] > max {
max = ts.Values[i]
if math.IsNaN(maxV) || ts.Values[i] > maxV {
maxV = ts.Values[i]
}
}
dst.Values[i] = max
dst.Values[i] = maxV
}
return tss[:1]
}
@@ -793,7 +793,7 @@ func fillNaNsAtIdx(idx int, k float64, tss []*timeseries) {
}
}
func getIntK(k float64, max int) int {
func getIntK(k float64, maxV int) int {
if math.IsNaN(k) {
return 0
}
@@ -801,38 +801,38 @@ func getIntK(k float64, max int) int {
if kn < 0 {
return 0
}
if kn > max {
return max
if kn > maxV {
return maxV
}
return kn
}
func minValue(values []float64) float64 {
min := nan
for len(values) > 0 && math.IsNaN(min) {
min = values[0]
minV := nan
for len(values) > 0 && math.IsNaN(minV) {
minV = values[0]
values = values[1:]
}
for _, v := range values {
if !math.IsNaN(v) && v < min {
min = v
if !math.IsNaN(v) && v < minV {
minV = v
}
}
return min
return minV
}
func maxValue(values []float64) float64 {
max := nan
for len(values) > 0 && math.IsNaN(max) {
max = values[0]
maxV := nan
for len(values) > 0 && math.IsNaN(maxV) {
maxV = values[0]
values = values[1:]
}
for _, v := range values {
if !math.IsNaN(v) && v > max {
max = v
if !math.IsNaN(v) && v > maxV {
maxV = v
}
}
return max
return maxV
}
func avgValue(values []float64) float64 {

View File

@@ -1685,9 +1685,9 @@ func rollupRateOverSum(rfa *rollupFuncArg) float64 {
}
func rollupRange(rfa *rollupFuncArg) float64 {
max := rollupMax(rfa)
min := rollupMin(rfa)
return max - min
maxV := rollupMax(rfa)
minV := rollupMin(rfa)
return maxV - minV
}
func rollupSum2(rfa *rollupFuncArg) float64 {
@@ -2195,38 +2195,38 @@ func rollupClose(rfa *rollupFuncArg) float64 {
func rollupHigh(rfa *rollupFuncArg) float64 {
values := getCandlestickValues(rfa)
max := getFirstValueForCandlestick(rfa)
if math.IsNaN(max) {
maxV := getFirstValueForCandlestick(rfa)
if math.IsNaN(maxV) {
if len(values) == 0 {
return nan
}
max = values[0]
maxV = values[0]
values = values[1:]
}
for _, v := range values {
if v > max {
max = v
if v > maxV {
maxV = v
}
}
return max
return maxV
}
func rollupLow(rfa *rollupFuncArg) float64 {
values := getCandlestickValues(rfa)
min := getFirstValueForCandlestick(rfa)
if math.IsNaN(min) {
minV := getFirstValueForCandlestick(rfa)
if math.IsNaN(minV) {
if len(values) == 0 {
return nan
}
min = values[0]
minV = values[0]
values = values[1:]
}
for _, v := range values {
if v < min {
min = v
if v < minV {
minV = v
}
}
return min
return minV
}
func rollupModeOverTime(rfa *rollupFuncArg) float64 {

View File

@@ -6,7 +6,7 @@ COPY web/ /build/
RUN GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o web-amd64 github.com/VictoriMetrics/vmui/ && \
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -o web-windows github.com/VictoriMetrics/vmui/
FROM alpine:3.21.0
FROM alpine:3.21.2
USER root
COPY --from=build-web-stage /build/web-amd64 /app/web

View File

@@ -2,9 +2,9 @@
DOCKER_NAMESPACE ?= victoriametrics
ROOT_IMAGE ?= alpine:3.21.0
ROOT_IMAGE ?= alpine:3.21.2
ROOT_IMAGE_SCRATCH ?= scratch
CERTS_IMAGE := alpine:3.21.0
CERTS_IMAGE := alpine:3.21.2
GO_BUILDER_IMAGE := golang:1.23.4-alpine
BUILDER_IMAGE := local/builder:2.0.0-$(shell echo $(GO_BUILDER_IMAGE) | tr :/ __)-1

View File

@@ -4,7 +4,7 @@ services:
# And forward them to --remoteWrite.url
vmagent:
container_name: vmagent
image: victoriametrics/vmagent:v1.108.1
image: victoriametrics/vmagent:v1.109.0
depends_on:
- "vminsert"
ports:
@@ -39,7 +39,7 @@ services:
# where N is number of vmstorages (2 in this case).
vmstorage-1:
container_name: vmstorage-1
image: victoriametrics/vmstorage:v1.108.1-cluster
image: victoriametrics/vmstorage:v1.109.0-cluster
ports:
- 8482
- 8400
@@ -51,7 +51,7 @@ services:
restart: always
vmstorage-2:
container_name: vmstorage-2
image: victoriametrics/vmstorage:v1.108.1-cluster
image: victoriametrics/vmstorage:v1.109.0-cluster
ports:
- 8482
- 8400
@@ -66,7 +66,7 @@ services:
# pre-process them and distributes across configured vmstorage shards.
vminsert:
container_name: vminsert
image: victoriametrics/vminsert:v1.108.1-cluster
image: victoriametrics/vminsert:v1.109.0-cluster
depends_on:
- "vmstorage-1"
- "vmstorage-2"
@@ -81,7 +81,7 @@ services:
# vmselect collects results from configured `--storageNode` shards.
vmselect-1:
container_name: vmselect-1
image: victoriametrics/vmselect:v1.108.1-cluster
image: victoriametrics/vmselect:v1.109.0-cluster
depends_on:
- "vmstorage-1"
- "vmstorage-2"
@@ -94,7 +94,7 @@ services:
restart: always
vmselect-2:
container_name: vmselect-2
image: victoriametrics/vmselect:v1.108.1-cluster
image: victoriametrics/vmselect:v1.109.0-cluster
depends_on:
- "vmstorage-1"
- "vmstorage-2"
@@ -112,7 +112,7 @@ services:
# It can be used as an authentication proxy.
vmauth:
container_name: vmauth
image: victoriametrics/vmauth:v1.108.1
image: victoriametrics/vmauth:v1.109.0
depends_on:
- "vmselect-1"
- "vmselect-2"
@@ -127,7 +127,7 @@ services:
# vmalert executes alerting and recording rules
vmalert:
container_name: vmalert
image: victoriametrics/vmalert:v1.108.1
image: victoriametrics/vmalert:v1.109.0
depends_on:
- "vmauth"
ports:

View File

@@ -45,7 +45,7 @@ services:
# storing logs and serving read queries.
victorialogs:
container_name: victorialogs
image: victoriametrics/victoria-logs:v1.4.0-victorialogs
image: victoriametrics/victoria-logs:v1.6.0-victorialogs
command:
- "--storageDataPath=/vlogs"
- "--httpListenAddr=:9428"
@@ -60,7 +60,7 @@ services:
# scraping, storing metrics and serve read requests.
victoriametrics:
container_name: victoriametrics
image: victoriametrics/victoria-metrics:v1.108.1
image: victoriametrics/victoria-metrics:v1.109.0
ports:
- 8428:8428
volumes:
@@ -79,7 +79,7 @@ services:
# depending on the requested path.
vmauth:
container_name: vmauth
image: victoriametrics/vmauth:v1.108.1
image: victoriametrics/vmauth:v1.109.0
depends_on:
- "victoriametrics"
- "victorialogs"
@@ -96,7 +96,7 @@ services:
# vmalert executes alerting and recording rules according to given rule type.
vmalert:
container_name: vmalert
image: victoriametrics/vmalert:v1.108.1
image: victoriametrics/vmalert:v1.109.0
depends_on:
- "vmauth"
- "alertmanager"

View File

@@ -4,7 +4,7 @@ services:
# And forward them to --remoteWrite.url
vmagent:
container_name: vmagent
image: victoriametrics/vmagent:v1.108.1
image: victoriametrics/vmagent:v1.109.0
depends_on:
- "victoriametrics"
ports:
@@ -22,7 +22,7 @@ services:
# storing metrics and serve read requests.
victoriametrics:
container_name: victoriametrics
image: victoriametrics/victoria-metrics:v1.108.1
image: victoriametrics/victoria-metrics:v1.109.0
ports:
- 8428:8428
- 8089:8089
@@ -65,7 +65,7 @@ services:
# vmalert executes alerting and recording rules
vmalert:
container_name: vmalert
image: victoriametrics/vmalert:v1.108.1
image: victoriametrics/vmalert:v1.109.0
depends_on:
- "victoriametrics"
- "alertmanager"

View File

@@ -3,6 +3,16 @@
# The alerts below are just recommendations and may require some updates
# and threshold calibration according to every specific setup.
groups:
- name: alwayFiring
rules:
- alert: AlwaysFiring
expr: up==1
labels:
severity: critical
annotations:
summary: "job {{ $labels.job }} has an instance {{ $labels.instance | stripPort }}"
instance: "{{ $labels.instance }}"
description: "This alert is fine"
- name: vm-health
# note the `job` filter and update accordingly to your setup
rules:

View File

@@ -1,7 +1,7 @@
services:
# meta service will be ignored by compose
.victorialogs:
image: docker.io/victoriametrics/victoria-logs:v1.4.0-victorialogs
image: docker.io/victoriametrics/victoria-logs:v1.6.0-victorialogs
command:
- -storageDataPath=/vlogs
- -loggerFormat=json
@@ -19,7 +19,7 @@ services:
retries: 10
dd-proxy:
image: docker.io/victoriametrics/vmauth:v1.108.1
image: docker.io/victoriametrics/vmauth:v1.109.0
restart: on-failure
volumes:
- ./:/etc/vmauth

View File

@@ -1,7 +1,7 @@
services:
vmagent:
container_name: vmagent
image: victoriametrics/vmagent:v1.108.1
image: victoriametrics/vmagent:v1.109.0
depends_on:
- "victoriametrics"
ports:
@@ -18,7 +18,7 @@ services:
victoriametrics:
container_name: victoriametrics
image: victoriametrics/victoria-metrics:v1.108.1
image: victoriametrics/victoria-metrics:v1.109.0
ports:
- 8428:8428
volumes:
@@ -50,7 +50,7 @@ services:
vmalert:
container_name: vmalert
image: victoriametrics/vmalert:v1.108.1
image: victoriametrics/vmalert:v1.109.0
depends_on:
- "victoriametrics"
ports:

View File

@@ -3,7 +3,7 @@ version: "3"
services:
# Run `make package-victoria-logs` to build victoria-logs image
vlogs:
image: docker.io/victoriametrics/victoria-logs:v1.4.0-victorialogs
image: docker.io/victoriametrics/victoria-logs:v1.6.0-victorialogs
volumes:
- vlogs:/vlogs
ports:
@@ -46,7 +46,7 @@ services:
- "--config=/config.yml"
vmsingle:
image: victoriametrics/victoria-metrics:v1.108.1
image: victoriametrics/victoria-metrics:v1.109.0
ports:
- "8428:8428"
command:

View File

@@ -22,5 +22,5 @@ to [the latest available releases](https://docs.victoriametrics.com/changelog/).
## Currently supported LTS release lines
- v1.102.x - the latest one is [v1.102.9 LTS release](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.102.9)
- v1.97.x - the latest one is [v1.97.14 LTS release](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.97.14)
- v1.102.x - the latest one is [v1.102.10 LTS release](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.102.10)
- v1.97.x - the latest one is [v1.97.15 LTS release](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.97.15)

View File

@@ -70,6 +70,7 @@ Bumping the limits may significantly improve build speed.
* linux/386
This step can be run manually with the command `make publish` from the needed git tag.
1. Verify that created images are stable and don't introduce regressions on [test environment](https://github.com/VictoriaMetrics/VictoriaMetrics-enterprise/blob/master/Release-Guide.md#testing-releases).
1. Test new images on [sandbox](https://github.com/VictoriaMetrics/VictoriaMetrics-enterprise/blob/master/Release-Guide.md#testing-releases).
1. Push the tags `v1.xx.y` and `v1.xx.y-cluster` created at previous steps to public GitHub repository at https://github.com/VictoriaMetrics/VictoriaMetrics.
Push the tags `v1.xx.y`, `v1.xx.y-cluster`, `v1.xx.y-enterprise` and `v1.xx.y-enterprise-cluster` to the corresponding
branches in private repository.
@@ -88,7 +89,6 @@ Bumping the limits may significantly improve build speed.
file created at the step `a`.
- To run the command `TAG=v1.xx.y make github-create-release github-upload-assets`, so new release is created
and all the needed assets are re-uploaded to it.
1. Test new images on [sandbox](https://github.com/VictoriaMetrics/VictoriaMetrics-enterprise/blob/master/Release-Guide.md#testing-releases).
1. Go to <https://github.com/VictoriaMetrics/VictoriaMetrics/releases> and verify that draft release with the name `TAG` has been created
and this release contains all the needed binaries and checksums.
1. Update the release description with the content of [CHANGELOG](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/docs/CHANGELOG.md) for this release.

View File

@@ -16,6 +16,18 @@ according to [these docs](https://docs.victoriametrics.com/victorialogs/quicksta
## tip
## [v1.6.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.6.0-victorialogs)
Released at 2025-01-15
* FEATURE: add [`union` pipe](https://docs.victoriametrics.com/victorialogs/logsql/#union-pipe), which can be used for returning results from multiple independent LogsQL queries.
* FEATURE: add [`histogram` stats function](https://docs.victoriametrics.com/victorialogs/logsql/#histogram-stats) for calculating [VictoriaMetrics histogram buckets](https://valyala.medium.com/improving-histogram-usability-for-prometheus-and-grafana-bc7e5df0e350) over the given [log field](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model). They will be used for building heatmaps at the [built-in Web UI](https://docs.victoriametrics.com/victorialogs/querying/#web-ui) and [VictoriaLogs plugin for Grafana](https://docs.victoriametrics.com/victorialogs/victorialogs-datasource/).
* FEATURE: [`math` pipe](https://docs.victoriametrics.com/victorialogs/logsql/#math-pipe): add `rand()` function, which can be used for generating random numbers in the range `[0 ... 1)`.
## [v1.5.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.5.0-victorialogs)
Released at 2025-01-13
* FEATURE: [`count_uniq` stats function](https://docs.victoriametrics.com/victorialogs/logsql/#count_uniq-stats): improve performance by up to 50% and reduce memory usage by up to 4x when this function is applied to fields with big number of unique integer values.
* FEATURE: [`stats` pipe](https://docs.victoriametrics.com/victorialogs/logsql/#stats-pipe): improve performance and reduce memory usage by up to 50% for `log_field` with big number of unique values at `stats by (log_field) ...`.
* FEATURE: [`math` pipe](https://docs.victoriametrics.com/victorialogs/logsql/#math-pipe): improve performance by up to 8x.

View File

@@ -139,7 +139,7 @@ _time:5m error -(buggy_app OR foobar)
The parentheses are **required** here, since otherwise the query won't return the expected results.
The query `error -buggy_app OR foobar` is interpreted as `(error AND NOT buggy_app) OR foobar` according to [priorities for AND, OR and NOT operator](#logical-filters).
This query returns logs with `foobar` [word](#word), even if do not contain `error` word or contain `buggy_app` word.
This query returns logs with `foobar` [word](#word), even if they do not contain `error` word or contain `buggy_app` word.
So it is recommended wrapping the needed query parts into explicit parentheses if you are unsure in priority rules.
As an additional bonus, explicit parentheses make queries easier to read and maintain.
@@ -1344,6 +1344,7 @@ LogsQL supports the following pipes:
- [`stream_context`](#stream_context-pipe) allows selecting surrounding logs in front and after the matching logs
per each [log stream](https://docs.victoriametrics.com/victorialogs/keyconcepts/#stream-fields).
- [`top`](#top-pipe) returns top `N` field sets with the maximum number of matching logs.
- [`union`](#union-pipe) returns results from multiple LogsQL queries.
- [`uniq`](#uniq-pipe) returns unique log entires.
- [`unpack_json`](#unpack_json-pipe) unpacks JSON messages from [log fields](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model).
- [`unpack_logfmt`](#unpack_logfmt-pipe) unpacks [logfmt](https://brandur.org/logfmt) messages from [log fields](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model).
@@ -1352,18 +1353,18 @@ LogsQL supports the following pipes:
### block_stats pipe
`<q> | block_stats` [pipe](#pipes) returns the following stats per each block processed by `<q>`. This pipe is needed mostly for debugging.
The returned per-block stats:
`<q> | block_stats` [pipe](#pipes) returns the following stats per each block processed by `<q>` [query](#query-syntax):
- `field` - [field](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model) name
- `rows` - the number of rows at the given `field.
- `rows` - the number of rows at the given `field`
- `type` - internal storage type for the given `field`
- `values_bytes` - on-disk size of the data for the given `field`
- `bloom_bytes` - on-disk size of bloom filter data for the given `field`
- `dict_bytes` - on-disk size of the dictionary data for the given `field`
- `dict_items` - the number of unique values in the dictionary for the given `field`
The `block_stats` pipe is needed mostly for debugging purposes.
See also:
- [`value_type` filter](#value_type-filter)
@@ -1507,10 +1508,10 @@ See also:
For example, the following query selects logs with the `error` [word](#word) for the last day,
extracts ip address from [`_msg` field](https://docs.victoriametrics.com/victorialogs/keyconcepts/#message-field) into `ip` field and then calculates top 10 ip addresses
with the biggest number of logs:
with the biggest number of logs using [`top` pipe](#top-pipe):
```logsql
_time:1d error | extract "ip=<ip> " from _msg | stats by (ip) count() logs | sort by (logs) desc limit 10
_time:1d error | extract "ip=<ip> " from _msg | top 10 (ip)
```
It is expected that `_msg` field contains `ip=...` substring ending with space. For example, `error ip=1.2.3.4 from user_id=42`.
@@ -1520,7 +1521,7 @@ If the `| extract ...` pipe is applied to [`_msg` field](https://docs.victoriame
For example, the following query is equivalent to the previous one:
```logsql
_time:1d error | extract "ip=<ip> " | stats by (ip) count() logs | sort by (logs) desc limit 10
_time:1d error | extract "ip=<ip> " | top 10 (ip)
```
If the `pattern` contains double quotes, then either put `\` in front of double quotes or put the `pattern` inside single quotes.
@@ -1940,7 +1941,7 @@ Numeric fields can be transformed into the following string representation at `f
- IPv4 - by adding `ipv4:` in front of the corresponding field name containing `uint32` representation of the IPv4 address.
For example, `format "ip=<ipv4:ip_num>"`.
- Zero-padded 64-bit hex number - by adding 'hexnumencode:' in front of the corresponding field name. For example, `format "hex_num=<hexnumencode:some_field>"`.
- Zero-padded 64-bit hex number - by adding `hexnumencode:` in front of the corresponding field name. For example, `format "hex_num=<hexnumencode:some_field>"`.
Add `keep_original_fields` to the end of `format ... as result_field` when the original non-empty value of the `result_field` must be preserved
instead of overwriting it with the `format` results. For example, the following query adds formatted result to `foo` field only if it was missing or empty:
@@ -2028,6 +2029,7 @@ _time:1d {app="app1"} | stats by (user) count() app1_hits
See also:
- [`in` filter](#multi-exact-filter)
- [`stats` pipe](#stats-pipe)
- [conditional `stats`](https://docs.victoriametrics.com/victorialogs/logsql/#stats-with-additional-filters)
- [`filter` pipe](#filter-pipe)
@@ -2063,7 +2065,7 @@ For example, the following query shows top 5 log entries with the maximum byte l
logs for the last 5 minutes:
```logsql
_time:5m | len(_msg) as msg_len | sort by (msg_len desc) | limit 1
_time:5m | len(_msg) as msg_len | sort by (msg_len desc) | limit 5
```
See also:
@@ -2141,6 +2143,7 @@ The following mathematical operations are supported by `math` pipe:
- `ln(arg)` - returns [natural logarithm](https://en.wikipedia.org/wiki/Natural_logarithm) for the given `arg`
- `max(arg1, ..., argN)` - returns the maximum value among the given `arg1`, ..., `argN`
- `min(arg1, ..., argN)` - returns the minimum value among the given `arg1`, ..., `argN`
- `rand()` - returns pseudo-random number in the range `[0...1)`.
- `round(arg)` - returns rounded to integer value for the given `arg`. The `round()` accepts optional `nearest` arg, which allows rounding the number to the given `nearest` multiple.
For example, `round(temperature, 0.1)` rounds `temperature` field to one decimal digit after the point.
@@ -2762,6 +2765,22 @@ See also:
- [`uniq` pipe](#uniq-pipe)
- [`stats` pipe](#stats-pipe)
- [`sort` pipe](#sort-pipe)
- [`histogram` stats function](#histogram-stats)
### union pipe
`q1 | union (q2)` [pipe](#pipes) returns results of `q1` followed by results of `q2`. It works similar to `UNION ALL` in SQL.
`q1` and `q2` may contain arbitrary [LogsQL queries](#logsql-tutorial).
For example, the following query returns logs with `error` [word](#word) for the last 5 minutes, plus logs with `panic` word for the last hour:
```logsql
_time:5m error | union (_time:1h panic)
```
See also:
- [`join` pipe](#join-pipe)
- [`in` filter](#multi-exact-filter)
### uniq pipe
@@ -3106,6 +3125,7 @@ LogsQL supports the following functions for [`stats` pipe](#stats-pipe):
- [`count_empty`](#count_empty-stats) returns the number logs with empty [log fields](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model).
- [`count_uniq`](#count_uniq-stats) returns the number of unique non-empty values for the given [log fields](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model).
- [`count_uniq_hash`](#count_uniq_hash-stats) returns the number of unique hashes for non-empty values at the given [log fields](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model).
- [`histogram`](#histogram-stats) returns [VictoriaMetrics histogram](https://valyala.medium.com/improving-histogram-usability-for-prometheus-and-grafana-bc7e5df0e350) for the given [log field](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model).
- [`max`](#max-stats) returns the maximum value over the given [log fields](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model).
- [`median`](#median-stats) returns the [median](https://en.wikipedia.org/wiki/Median) value over the given [log fields](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model).
- [`min`](#min-stats) returns the minimum value over the given [log fields](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model).
@@ -3253,6 +3273,25 @@ See also:
- [`uniq_values`](#uniq_values-stats)
- [`count`](#count-stats)
### histogram stats
`histogram(field)` [stats pipe function](#stats-pipe-functions) returns [VictoriaMetrics histogram buckets](https://valyala.medium.com/improving-histogram-usability-for-prometheus-and-grafana-bc7e5df0e350)
for the given [`field`](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model).
For example, the following query returns histogram buckets for the `response_size` field grouped by `host` field, across logs for the last 5 minutes:
```logsql
_time:5m | stats by (host) histogram(response_size)
```
If the field contains [duration value](#duration-values), then `histogram` normalizes it to nanoseconds. For example, `1.25ms` is normalized to `1_250_000`.
If the field contains [short numeric value](#short-numeric-values), then `histogram` normalizes it to numeric value without any suffixes. For example, `1KiB` is converted to `1024`.
See also:
- [`quantile`](#quantile-stats)
### max stats
`max(field1, ..., fieldN)` [stats pipe function](#stats-pipe-functions) returns the maximum value across
@@ -3330,6 +3369,7 @@ _time:5m | stats
See also:
- [`histogram`](#histogram-stats)
- [`min`](#min-stats)
- [`max`](#max-stats)
- [`median`](#median-stats)

View File

@@ -33,8 +33,8 @@ Just download archive for the needed Operating system and architecture, unpack i
For example, the following commands download VictoriaLogs archive for Linux/amd64, unpack and run it:
```sh
curl -L -O https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v1.4.0-victorialogs/victoria-logs-linux-amd64-v1.4.0-victorialogs.tar.gz
tar xzf victoria-logs-linux-amd64-v1.4.0-victorialogs.tar.gz
curl -L -O https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v1.6.0-victorialogs/victoria-logs-linux-amd64-v1.6.0-victorialogs.tar.gz
tar xzf victoria-logs-linux-amd64-v1.6.0-victorialogs.tar.gz
./victoria-logs-prod
```
@@ -58,7 +58,7 @@ Here is the command to run VictoriaLogs in a Docker container:
```sh
docker run --rm -it -p 9428:9428 -v ./victoria-logs-data:/victoria-logs-data \
docker.io/victoriametrics/victoria-logs:v1.4.0-victorialogs
docker.io/victoriametrics/victoria-logs:v1.6.0-victorialogs
```
See also:

View File

@@ -24,11 +24,11 @@ the returned logs by some field (usually [`_time` field](https://docs.victoriame
_time:5m | sort by (_time)
```
If the number of returned logs is too big, it may be limited with the [`last` pipe](https://docs.victoriametrics.com/victorialogs/logsql/#last-pipe).
If the number of returned logs is too big, it may be limited with the [`first` pipe](https://docs.victoriametrics.com/victorialogs/logsql/#first-pipe).
For example, the following query returns 10 most recent logs, which were ingested during the last 5 minutes:
```logsql
_time:5m | last 10 by (_time)
_time:5m | first 10 by (_time desc)
```
See also:
@@ -358,7 +358,7 @@ query returns top 10 `/24` subnetworks with the biggest number of logs for the l
_time:5m | stats by (ip:/24) count() rows | last 10 by (rows)
```
This query uses [`last` pipe](https://docs.victoriametrics.com/victorialogs/logsql/#last-pipe) in order to get up to 10 per-subnetwork stats
This query uses [`first` pipe](https://docs.victoriametrics.com/victorialogs/logsql/#first-pipe) in order to get up to 10 per-subnetwork stats
with the biggest number of rows.
The query assumes the original logs have `ip` [field](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model) with the IPv4 address.
@@ -369,7 +369,7 @@ extracts IPv4 address from [`_msg` field](https://docs.victoriametrics.com/victo
`/16` subnetworks with the biggest number of logs for the last 5 minutes:
```logsql
_time:5m | extract_regexp "(?P<ip>([0-9]+[.]){3}[0-9]+)" | stats by (ip:/16) count() rows | last 10 by (rows)
_time:5m | extract_regexp "(?P<ip>([0-9]+[.]){3}[0-9]+)" | stats by (ip:/16) count() rows | first 10 by (rows desc)
```
## How to calculate the number of logs per every value of the given field?
@@ -407,12 +407,12 @@ _time:5m | uniq by (host, path)
## How to return last N logs for the given query?
Use [`last` pipe](https://docs.victoriametrics.com/victorialogs/logsql/#last-pipe). For example, the following query returns the last 10 logs with the `error`
Use [`first` pipe](https://docs.victoriametrics.com/victorialogs/logsql/#first-pipe). For example, the following query returns the last 10 logs with the `error`
[word](https://docs.victoriametrics.com/victorialogs/logsql/#word) in the [`_msg` field](https://docs.victoriametrics.com/victorialogs/keyconcepts/#message-field)
over the logs for the last 5 minutes:
```logsql
_time:5m error | last 10 by (_time)
_time:5m error | first 10 by (_time desc)
```
It sorts the matching logs by [`_time` field](https://docs.victoriametrics.com/victorialogs/keyconcepts/#time-field) and then selects

View File

@@ -18,22 +18,16 @@ It has the following features:
- It supports live tailing - see [these docs](#live-tailing).
This tool can be obtained from the linked release pages at the [changelog](https://docs.victoriametrics.com/victorialogs/changelog/)
or from [docker images](https://hub.docker.com/r/victoriametrics/vlogscli/tags):
or from [docker images](https://hub.docker.com/r/victoriametrics/vlogscli/tags).
### Running `vlogscli` from release binary
```sh
curl -L -O https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v1.4.0-victorialogs/vlogscli-linux-amd64-v1.4.0-victorialogs.tar.gz
tar xzf vlogscli-linux-amd64-v1.4.0-victorialogs.tar.gz
curl -L -O https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v1.6.0-victorialogs/vlogscli-linux-amd64-v1.6.0-victorialogs.tar.gz
tar xzf vlogscli-linux-amd64-v1.6.0-victorialogs.tar.gz
./vlogscli-prod
```
### Running `vlogscli` from Docker image
```sh
docker run --rm -it docker.io/victoriametrics/vlogscli:v1.4.0-victorialogs
```
## Configuration
By default `vlogscli` sends queries to [`http://localhost:8429/select/logsql/query`](https://docs.victoriametrics.com/victorialogs/querying/#querying-logs).

View File

@@ -75,7 +75,7 @@ The `push_frequency` parameter{{% available_from "v1.18.7" anomaly %}} (default
<tr>
<td>
`url`
<span style="white-space: nowrap;">`url`</span>
</td>
<td></td>
<td>
@@ -86,7 +86,7 @@ Link where to push metrics to. Example: `"http://localhost:8480/"`
<tr>
<td>
`tenant_id`
<span style="white-space: nowrap;">`tenant_id`</span>
</td>
<td></td>
<td>
@@ -97,7 +97,7 @@ Tenant ID for cluster version. Example: `"0:0"`
<tr>
<td>
`health_path`
<span style="white-space: nowrap;">`health_path`</span>
</td>
<td>
@@ -111,7 +111,7 @@ Tenant ID for cluster version. Example: `"0:0"`
<tr>
<td>
`user`
<span style="white-space: nowrap;">`user`</span>
</td>
<td></td>
<td>BasicAuth username</td>
@@ -119,7 +119,7 @@ Tenant ID for cluster version. Example: `"0:0"`
<tr>
<td>
`password`
<span style="white-space: nowrap;">`password`</span>
</td>
<td></td>
<td>BasicAuth password</td>
@@ -127,7 +127,7 @@ Tenant ID for cluster version. Example: `"0:0"`
<tr>
<td>
`bearer_token`
<span style="white-space: nowrap;">`bearer_token`</span>
</td>
<td>
@@ -140,7 +140,7 @@ Token is passed in the standard format with header: `Authorization: bearer {toke
<tr>
<td>
`bearer_token_file`
<span style="white-space: nowrap;">`bearer_token_file`</span>
</td>
<td>
@@ -153,7 +153,7 @@ Path to a file, which contains token, that is passed in the standard format with
<tr>
<td>
`verify_tls`
<span style="white-space: nowrap;">`verify_tls`</span>
</td>
<td>
@@ -168,11 +168,11 @@ If a path to a CA bundle file (like `ca.crt`), it will verify the certificate us
<tr>
<td>
`tls_cert_file`
<span style="white-space: nowrap;">`tls_cert_file`</span>
</td>
<td>
`path/to/cert.crt`
<span style="white-space: nowrap;">`path/to/cert.crt`</span>
</td>
<td>
Path to a file with the client certificate, i.e. `client.crt`{{% available_from "v1.16.3" anomaly %}}.
@@ -181,7 +181,7 @@ Path to a file with the client certificate, i.e. `client.crt`{{% available_from
<tr>
<td>
`tls_key_file`
<span style="white-space: nowrap;">`tls_key_file`</span>
</td>
<td>
@@ -194,7 +194,7 @@ Path to a file with the client certificate key, i.e. `client.key`{{% available_f
<tr>
<td>
`timeout`
<span style="white-space: nowrap;">`timeout`</span>
</td>
<td>
@@ -205,7 +205,7 @@ Path to a file with the client certificate key, i.e. `client.key`{{% available_f
<tr>
<td>
`push_frequency`
<span style="white-space: nowrap;">`push_frequency`</span>
</td>
<td>
@@ -216,7 +216,7 @@ Path to a file with the client certificate key, i.e. `client.key`{{% available_f
<tr>
<td>
`extra_labels`
<span style="white-space: nowrap;">`extra_labels`</span>
</td>
<td></td>
<td>Section for custom labels specified by user.</td>
@@ -271,7 +271,7 @@ For detailed guidance on configuring mTLS parameters such as `verify_tls`, `tls_
<tr>
<td>
`vmanomaly_start_time_seconds`
<span style="white-space: nowrap;">`vmanomaly_start_time_seconds`</span>
</td>
<td>Gauge</td>
<td>vmanomaly start time in UNIX time</td>
@@ -279,7 +279,7 @@ For detailed guidance on configuring mTLS parameters such as `verify_tls`, `tls_
<tr>
<td>
`vmanomaly_version_info`
<span style="white-space: nowrap;">`vmanomaly_version_info`</span>
</td>
<td>Gauge</td>
<td>vmanomaly version information, contained in `version` label{{% available_from "v1.17.2" anomaly %}}.</td>
@@ -287,7 +287,7 @@ For detailed guidance on configuring mTLS parameters such as `verify_tls`, `tls_
<tr>
<td>
`vmanomaly_ui_version_info`
<span style="white-space: nowrap;">`vmanomaly_ui_version_info`</span>
</td>
<td>Gauge</td>
<td>vmanomaly UI version information, contained in `version` label{{% available_from "v1.17.2" anomaly %}}.</td>
@@ -295,7 +295,7 @@ For detailed guidance on configuring mTLS parameters such as `verify_tls`, `tls_
<tr>
<td>
`vmanomaly_available_memory_bytes`
<span style="white-space: nowrap;">`vmanomaly_available_memory_bytes`</span>
</td>
<td>Gauge</td>
<td>Virtual memory size in bytes, available to the process{{% available_from "v1.18.4" anomaly %}}.</td>
@@ -303,7 +303,7 @@ For detailed guidance on configuring mTLS parameters such as `verify_tls`, `tls_
<tr>
<td>
`vmanomaly_cpu_cores_available`
<span style="white-space: nowrap;">`vmanomaly_cpu_cores_available`</span>
</td>
<td>Gauge</td>
<td>Number of (logical) CPU cores available to the process{{% available_from "v1.18.4" anomaly %}}.</td>
@@ -331,9 +331,11 @@ Label names [description](#labelnames)
<tr>
<td>
`vmanomaly_reader_request_duration_seconds`
<span style="white-space: nowrap;">`vmanomaly_reader_request_duration_seconds`</span>
</td>
<td>`Histogram` (was `Summary`{{% deprecated_from "v1.17.0" anomaly %}})</td>
<td>
<span style="white-space: nowrap;">`Histogram`</span> (was `Summary`{{% deprecated_from "v1.17.0" anomaly %}})</td>
<td>The total time (in seconds) taken by queries to VictoriaMetrics `url` for the `query_key` query within the specified scheduler `scheduler_alias`, in the `vmanomaly` service running in `preset` mode.</td>
<td>
@@ -343,9 +345,12 @@ Label names [description](#labelnames)
<tr>
<td>
`vmanomaly_reader_responses` (named `vmanomaly_reader_response_count`{{% deprecated_from "v1.17.0" anomaly %}})
<span style="white-space: nowrap;">`vmanomaly_reader_responses`</span> (named `vmanomaly_reader_response_count`{{% deprecated_from "v1.17.0" anomaly %}})
</td>
<td>
`Counter`
</td>
<td>`Counter`</td>
<td>The count of responses received from VictoriaMetrics `url` for the `query_key` query, categorized by `code`, within the specified scheduler `scheduler_alias`, in the `vmanomaly` service running in `preset` mode.</td>
<td>
@@ -355,21 +360,27 @@ Label names [description](#labelnames)
<tr>
<td>
`vmanomaly_reader_received_bytes`
<span style="white-space: nowrap;">`vmanomaly_reader_received_bytes`</span>
</td>
<td>
`Counter`
</td>
<td>`Counter`</td>
<td>The total number of bytes received in responses for the `query_key` query within the specified scheduler `scheduler_alias`, in the `vmanomaly` service running in `preset` mode.</td>
<td>
`url`, `query_key`, `scheduler_alias`, `preset`
`url`, `query_key`, <span style="white-space: nowrap;">`scheduler_alias`</span>, `preset`
</td>
</tr>
<tr>
<td>
`vmanomaly_reader_response_parsing_seconds`
<span style="white-space: nowrap;">`vmanomaly_reader_response_parsing_seconds`</span>
</td>
<td>
`Histogram` (was `Summary`{{% deprecated_from "v1.17.0" anomaly %}})
</td>
<td>`Histogram` (was `Summary`{{% deprecated_from "v1.17.0" anomaly %}}</td>
<td>The total time (in seconds) taken for data parsing at each `step` (json, dataframe) for the `query_key` query within the specified scheduler `scheduler_alias`, in the `vmanomaly` service running in `preset` mode.</td>
<td>
@@ -379,9 +390,12 @@ Label names [description](#labelnames)
<tr>
<td>
`vmanomaly_reader_timeseries_received`
<span style="white-space: nowrap;">`vmanomaly_reader_timeseries_received`</span>
</td>
<td>
`Counter`
</td>
<td>`Counter`</td>
<td>The total number of timeseries received from VictoriaMetrics for the `query_key` query within the specified scheduler `scheduler_alias`, in the `vmanomaly` service running in `preset` mode.</td>
<td>
@@ -391,9 +405,12 @@ Label names [description](#labelnames)
<tr>
<td>
`vmanomaly_reader_datapoints_received`
<span style="white-space: nowrap;">`vmanomaly_reader_datapoints_received`</span>
</td>
<td>
`Counter`
</td>
<td>`Counter`</td>
<td>The total number of datapoints received from VictoriaMetrics for the `query_key` query within the specified scheduler `scheduler_alias`, in the `vmanomaly` service running in `preset` mode.</td>
<td>
@@ -425,9 +442,12 @@ Label names [description](#labelnames)
<tr>
<td>
`vmanomaly_model_runs`
<span style="white-space: nowrap;">`vmanomaly_model_runs`</span>
</td>
<td>
`Counter`
</td>
<td>`Counter`</td>
<td>How many successful `stage` (`fit`, `infer`, `fit_infer`) runs occurred for models of class `model_alias` based on results from the `query_key` query, within the specified scheduler `scheduler_alias`, in the `vmanomaly` service running in `preset` mode.</td>
<td>
@@ -437,9 +457,11 @@ Label names [description](#labelnames)
<tr>
<td>
`vmanomaly_model_run_duration_seconds`
<span style="white-space: nowrap;">`vmanomaly_model_run_duration_seconds`</span>
</td>
<td>`Histogram` (was `Summary`{{% deprecated_from "v1.17.0" anomaly %}}) </td>
<td>
<span style="white-space: nowrap;">`Histogram`</span> (was `Summary`{{% deprecated_from "v1.17.0" anomaly %}}) </td>
<td>The total time (in seconds) taken by model invocations during the `stage` (`fit`, `infer`, `fit_infer`), based on the results of the `query_key` query, for models of class `model_alias`, within the specified scheduler `scheduler_alias`, in the `vmanomaly` service running in `preset` mode.</td>
<td>
@@ -449,9 +471,12 @@ Label names [description](#labelnames)
<tr>
<td>
`vmanomaly_model_datapoints_accepted`
<span style="white-space: nowrap;">`vmanomaly_model_datapoints_accepted`</span>
</td>
<td>
`Counter`
</td>
<td>`Counter`</td>
<td>The number of datapoints accepted (excluding NaN or Inf values) by models of class `model_alias` from the results of the `query_key` query during the `stage` (`infer`, `fit_infer`), within the specified scheduler `scheduler_alias`, in the `vmanomaly` service running in `preset` mode.</td>
<td>
@@ -461,9 +486,12 @@ Label names [description](#labelnames)
<tr>
<td>
`vmanomaly_model_datapoints_produced`
<span style="white-space: nowrap;">`vmanomaly_model_datapoints_produced`</span>
</td>
<td>
`Counter`
</td>
<td>`Counter`</td>
<td>The number of datapoints generated by models of class `model_alias` during the `stage` (`infer`, `fit_infer`) based on results from the `query_key` query, within the specified scheduler `scheduler_alias`, in the `vmanomaly` service running in `preset` mode.</td>
<td>
@@ -473,9 +501,12 @@ Label names [description](#labelnames)
<tr>
<td>
`vmanomaly_models_active`
<span style="white-space: nowrap;">`vmanomaly_models_active`</span>
</td>
<td>
`Gauge`
</td>
<td>`Gauge`</td>
<td>The number of model instances of class `model_alias` currently available for inference for the `query_key` query, within the specified scheduler `scheduler_alias`, in the `vmanomaly` service running in `preset` mode.</td>
<td>
@@ -485,9 +516,12 @@ Label names [description](#labelnames)
<tr>
<td>
`vmanomaly_model_runs_skipped`
<span style="white-space: nowrap;">`vmanomaly_model_runs_skipped`</span>
</td>
<td>
`Counter`
</td>
<td>`Counter`</td>
<td>The number of times model runs (of class `model_alias`) were skipped in expected situations (e.g., no data for fitting/inference, or no new data to infer on) during the `stage` (`fit`, `infer`, `fit_infer`), based on results from the `query_key` query, within the specified scheduler `scheduler_alias`, in the `vmanomaly` service running in `preset` mode.</td>
<td>
@@ -497,12 +531,15 @@ Label names [description](#labelnames)
<tr>
<td>
`vmanomaly_model_run_errors`
<span style="white-space: nowrap;">`vmanomaly_model_run_errors`</span>
</td>
<td>
`Counter`
</td>
<td>`Counter`</td>
<td>The number of times model runs (of class `model_alias`) failed due to internal service errors during the `stage` (`fit`, `infer`, `fit_infer`), based on results from the `query_key` query, within the specified scheduler `scheduler_alias`, in the `vmanomaly` service running in `preset` mode.</td>
<td>
`stage`, `query_key`, `model_alias`, `scheduler_alias`, `preset`
`stage`, `query_key`, `model_alias`, <span style="white-space: nowrap;">`scheduler_alias`</span>, `preset`
</td>
</tr>
</tbody>
@@ -528,22 +565,28 @@ Label names [description](#labelnames)
<tr>
<td>
`vmanomaly_writer_request_duration_seconds`
<span style="white-space: nowrap;">`vmanomaly_writer_request_duration_seconds`</span>
</td>
<td>
`Histogram` (was `Summary`{{% deprecated_from "v1.17.0" anomaly %}})
</td>
<td>`Histogram` (was `Summary`{{% deprecated_from "v1.17.0" anomaly %}})</td>
<td>The total time (in seconds) taken by write requests to VictoriaMetrics `url` for the `query_key` query within the specified scheduler `scheduler_alias`, in the `vmanomaly` service running in `preset` mode.
</td>
<td>
`url`, `query_key`, `scheduler_alias`, `preset`
`url`, `query_key`, <span style="white-space: nowrap;">`scheduler_alias`</span>, `preset`
</td>
</tr>
<tr>
<td>
`vmanomaly_writer_responses` (named `vmanomaly_reader_response_count`{{% deprecated_from "v1.17.0" anomaly %}})
<span style="white-space: nowrap;">`vmanomaly_writer_responses`</span> (named `vmanomaly_reader_response_count`{{% deprecated_from "v1.17.0" anomaly %}})
</td>
<td>
`Counter`
</td>
<td>`Counter`</td>
<td>The count of response codes received from VictoriaMetrics `url` for the `query_key` query, categorized by `code`, within the specified scheduler `scheduler_alias`, in the `vmanomaly` service running in `preset` mode.
</td>
<td>
@@ -554,9 +597,12 @@ Label names [description](#labelnames)
<tr>
<td>
`vmanomaly_writer_sent_bytes`
<span style="white-space: nowrap;">`vmanomaly_writer_sent_bytes`</span>
</td>
<td>
`Counter`
</td>
<td>`Counter`</td>
<td>The total number of bytes sent to VictoriaMetrics `url` for the `query_key` query within the specified scheduler `scheduler_alias`, in the `vmanomaly` service running in `preset` mode.</td>
<td>
@@ -566,9 +612,11 @@ Label names [description](#labelnames)
<tr>
<td>
`vmanomaly_writer_request_serialize_seconds`
<span style="white-space: nowrap;">`vmanomaly_writer_request_serialize_seconds`</span>
</td>
<td>`Histogram` (was `Summary`{{% deprecated_from "v1.17.0" anomaly %}}</td>
<td>
<span style="white-space: nowrap;">`Histogram`</span> (was `Summary`{{% deprecated_from "v1.17.0" anomaly %}})</td>
<td>The total time (in seconds) taken for serializing data for the `query_key` query within the specified scheduler `scheduler_alias`, in the `vmanomaly` service running in `preset` mode.</td>
<td>
@@ -578,9 +626,12 @@ Label names [description](#labelnames)
<tr>
<td>
`vmanomaly_writer_datapoints_sent`
<span style="white-space: nowrap;">`vmanomaly_writer_datapoints_sent`</span>
</td>
<td>
`Counter`
</td>
<td>`Counter`</td>
<td>The total number of datapoints sent to VictoriaMetrics for the `query_key` query within the specified scheduler `scheduler_alias`, in the `vmanomaly` service running in `preset` mode.</td>
<td>
@@ -589,9 +640,13 @@ Label names [description](#labelnames)
</tr>
<tr>
<td>
`vmanomaly_writer_timeseries_sent`
<span style="white-space: nowrap;">`vmanomaly_writer_timeseries_sent`</span>
</td>
<td>
`Counter`
</td>
<td>`Counter`</td>
<td>The total number of timeseries sent to VictoriaMetrics for the `query_key` query within the specified scheduler `scheduler_alias`, in the `vmanomaly` service running in `preset` mode.</td>
<td>

View File

@@ -105,9 +105,10 @@ reader:
<tr>
<td>
`class`
<span style="white-space: nowrap;">`class`</span>
</td>
<td>
`reader.vm.VmReader` (or `vm`{{% available_from "v1.13.0" anomaly %}})
</td>
<td>
@@ -117,7 +118,7 @@ Name of the class needed to enable reading from VictoriaMetrics or Prometheus. V
<tr>
<td>
`queries`
<span style="white-space: nowrap;">`queries`</span>
</td>
<td>
See [per-query config example](#per-query-config-example) above
@@ -129,10 +130,11 @@ See [per-query config section](#per-query-parameters) above
<tr>
<td>
`datasource_url`
<span style="white-space: nowrap;">`datasource_url`</span>
</td>
<td>
`http://localhost:8481/`
<span style="white-space: nowrap;">`http://localhost:8481/`</span>
</td>
<td>
Datasource URL address
@@ -141,7 +143,7 @@ Datasource URL address
<tr>
<td>
`tenant_id`
<span style="white-space: nowrap;">`tenant_id`</span>
</td>
<td>
@@ -154,7 +156,7 @@ For VictoriaMetrics Cluster version only, tenants are identified by `accountID`
<tr>
<td>
`sampling_period`
<span style="white-space: nowrap;">`sampling_period`</span>
</td>
<td>
`1h`
@@ -166,10 +168,11 @@ Frequency of the points returned. Will be converted to `/query_range?step=%s` pa
<tr>
<td>
`query_range_path`
<span style="white-space: nowrap;">`query_range_path`</span>
</td>
<td>
`/api/v1/query_range`
<span style="white-space: nowrap;">`/api/v1/query_range`</span>
</td>
<td>
Performs PromQL/MetricsQL range query
@@ -178,7 +181,7 @@ Performs PromQL/MetricsQL range query
<tr>
<td>
`health_path`
<span style="white-space: nowrap;">`health_path`</span>
</td>
<td>
@@ -191,7 +194,7 @@ Absolute or relative URL address where to check availability of the datasource.
<tr>
<td>
`user`
<span style="white-space: nowrap;">`user`</span>
</td>
<td>
@@ -204,7 +207,7 @@ BasicAuth username
<tr>
<td>
`password`
<span style="white-space: nowrap;">`password`</span>
</td>
<td>
@@ -217,7 +220,7 @@ BasicAuth password
<tr>
<td>
`timeout`
<span style="white-space: nowrap;">`timeout`</span>
</td>
<td>
@@ -230,7 +233,7 @@ Timeout for the requests, passed as a string
<tr>
<td>
`verify_tls`
<span style="white-space: nowrap;">`verify_tls`</span>
</td>
<td>
@@ -245,7 +248,7 @@ If a path to a CA bundle file (like `ca.crt`), it will verify the certificate us
<tr>
<td>
`tls_cert_file`
<span style="white-space: nowrap;">`tls_cert_file`</span>
</td>
<td>
@@ -258,7 +261,7 @@ Path to a file with the client certificate, i.e. `client.crt`{{% available_from
<tr>
<td>
`tls_key_file`
<span style="white-space: nowrap;">`tls_key_file`</span>
</td>
<td>
@@ -271,7 +274,7 @@ Path to a file with the client certificate key, i.e. `client.key`{{% available_f
<tr>
<td>
`bearer_token`
<span style="white-space: nowrap;">`bearer_token`</span>
</td>
<td>
@@ -284,7 +287,7 @@ Token is passed in the standard format with header: `Authorization: bearer {toke
<tr>
<td>
`bearer_token_file`
<span style="white-space: nowrap;">`bearer_token_file`</span>
</td>
<td>
@@ -297,7 +300,7 @@ Path to a file, which contains token, that is passed in the standard format with
<tr>
<td>
`extra_filters`
<span style="white-space: nowrap;">`extra_filters`</span>
</td>
<td>
@@ -310,7 +313,7 @@ List of strings with series selector. See: [Prometheus querying API enhancements
<tr>
<td>
`query_from_last_seen_timestamp`
<span style="white-space: nowrap;">`query_from_last_seen_timestamp`</span>
</td>
<td>
@@ -323,7 +326,7 @@ If True, then query will be performed from the last seen timestamp for a given s
<tr>
<td>
`latency_offset`
<span style="white-space: nowrap;">`latency_offset`</span>
</td>
<td>
@@ -336,7 +339,7 @@ It allows overriding the default `-search.latencyOffset`{{% available_from "v1.1
<tr>
<td>
`max_points_per_query`
<span style="white-space: nowrap;">`max_points_per_query`</span>
</td>
<td>
@@ -349,7 +352,7 @@ Optional arg{{% available_from "v1.17.0" anomaly %}} overrides how `search.maxPo
<tr>
<td>
`tz`
<span style="white-space: nowrap;">`tz`</span>
</td>
<td>
@@ -362,7 +365,7 @@ Optional argument{{% available_from "v1.18.0" anomaly %}} specifies the [IANA](h
<tr>
<td>
`data_range`
<span style="white-space: nowrap;">`data_range`</span>
</td>
<td>

View File

@@ -116,7 +116,7 @@ Examples: `"50s"`, `"4m"`, `"3h"`, `"2d"`, `"1w"`.
<tr>
<td>
`fit_window`
<span style="white-space: nowrap;">`fit_window`</span>
</td>
<td>str</td>
<td>
@@ -128,7 +128,7 @@ Examples: `"50s"`, `"4m"`, `"3h"`, `"2d"`, `"1w"`.
<tr>
<td>
`infer_every`
<span style="white-space: nowrap;">`infer_every`</span>
</td>
<td>str</td>
<td>
@@ -140,7 +140,7 @@ Examples: `"50s"`, `"4m"`, `"3h"`, `"2d"`, `"1w"`.
<tr>
<td>
`fit_every`
<span style="white-space: nowrap;">`fit_every`</span>
</td>
<td>str, Optional</td>
<td>
@@ -155,12 +155,12 @@ How often to completely retrain the models. If not set, value of `infer_every` i
<tr>
<td>
`start_from`{{% available_from "v1.18.5" anomaly %}}
<span style="white-space: nowrap;">`start_from`{{% available_from "v1.18.5" anomaly %}}</span>
</td>
<td>str, Optional</td>
<td>str, <span style="white-space: nowrap;">Optional</span></td>
<td>
`2024-11-26T01:00:00Z`, `01:00`
<span style="white-space: nowrap;">`2024-11-26T01:00:00Z`</span>, `01:00`
</td>
<td>
@@ -170,9 +170,9 @@ Specifies when to initiate the first `fit_every` call. Accepts either an ISO 860
<tr>
<td>
`tz`{{% available_from "v1.18.5" anomaly %}}
<span style="white-space: nowrap;">`tz`{{% available_from "v1.18.5" anomaly %}}</span>
</td>
<td>str, Optional</td>
<td>str, <span style="white-space: nowrap;">Optional</span></td>
<td>
`America/New_York`
@@ -229,7 +229,7 @@ If a time zone is omitted, a timezone-naive datetime is used.
<td>ISO 8601</td>
<td>
`fit_start_iso`
<span style="white-space: nowrap;">`fit_start_iso`</span>
</td>
<td>str</td>
<td>
@@ -242,16 +242,19 @@ If a time zone is omitted, a timezone-naive datetime is used.
<td>UNIX time</td>
<td>
`fit_start_s`
<span style="white-space: nowrap;">`fit_start_s`</span>
</td>
<td>
<span style="white-space: nowrap;">float</span>
</td>
<td>float</td>
<td>1648771200</td>
</tr>
<tr>
<td>ISO 8601</td>
<td>
`fit_end_iso`
<span style="white-space: nowrap;">`fit_end_iso`</span>
</td>
<td>str</td>
<td>
@@ -267,7 +270,7 @@ If a time zone is omitted, a timezone-naive datetime is used.
<td>UNIX time</td>
<td>
`fit_end_s`
<span style="white-space: nowrap;">`fit_end_s`</span>
</td>
<td>float</td>
<td>1649548800</td>
@@ -291,7 +294,7 @@ If a time zone is omitted, a timezone-naive datetime is used.
<td>ISO 8601</td>
<td>
`infer_start_iso`
<span style="white-space: nowrap;">`infer_start_iso`</span>
</td>
<td>str</td>
<td>
@@ -304,16 +307,19 @@ If a time zone is omitted, a timezone-naive datetime is used.
<td>UNIX time</td>
<td>
`infer_start_s`
<span style="white-space: nowrap;">`infer_start_s`</span>
</td>
<td>
<span style="white-space: nowrap;">float</span>
</td>
<td>float</td>
<td>1649635200</td>
</tr>
<tr>
<td>ISO 8601</td>
<td>
`infer_end_iso`
<span style="white-space: nowrap;">`infer_end_iso`</span>
</td>
<td>str</td>
<td>
@@ -329,7 +335,7 @@ If a time zone is omitted, a timezone-naive datetime is used.
<td>UNIX time</td>
<td>
`infer_end_s`
<span style="white-space: nowrap;">`infer_end_s`</span>
</td>
<td>float</td>
<td>1649894400</td>
@@ -378,9 +384,18 @@ If a time zone is omitted, a timezone-naive datetime is used.
<table class="params">
<thead>
<tr>
<th>Parameter</th>
<th>Type</th>
<th>Example</th>
<th>
<span style="white-space: nowrap;">Parameter</span>
</th>
<th>
<span style="white-space: nowrap;">Type</span>
</th>
<th>
<span style="white-space: nowrap;">Example</span>
</th>
<th>Description</th>
</tr>
</thead>
@@ -388,7 +403,7 @@ If a time zone is omitted, a timezone-naive datetime is used.
<tr>
<td>
`n_jobs`
<span style="white-space: nowrap;">`n_jobs`</span>
</td>
<td>int</td>
<td>
@@ -409,10 +424,22 @@ This timeframe will be used for slicing on intervals `(fit_window, infer_window
<table class="params">
<thead>
<tr>
<th>Format</th>
<th>Parameter</th>
<th>Type</th>
<th>Example</th>
<th>
<span style="white-space: nowrap;">Format</span>
</th>
<th>
<span style="white-space: nowrap;">Parameter</span>
</th>
<th>
<span style="white-space: nowrap;">Type</span>
</th>
<th>
<span style="white-space: nowrap;">Example</span>
</th>
<th>Description</th>
</tr>
</thead>
@@ -421,7 +448,7 @@ This timeframe will be used for slicing on intervals `(fit_window, infer_window
<td>ISO 8601</td>
<td>
`from_iso`
<span style="white-space: nowrap;">`from_iso`</span>
</td>
<td>str</td>
<td>
@@ -434,7 +461,7 @@ This timeframe will be used for slicing on intervals `(fit_window, infer_window
<td>UNIX time</td>
<td>
`from_s`
<span style="white-space: nowrap;">`from_s`</span>
</td>
<td>float</td>
<td>1648771200</td>
@@ -443,7 +470,7 @@ This timeframe will be used for slicing on intervals `(fit_window, infer_window
<td>ISO 8601</td>
<td>
`to_iso`
<span style="white-space: nowrap;">`to_iso`</span>
</td>
<td>str</td>
<td>
@@ -459,7 +486,7 @@ This timeframe will be used for slicing on intervals `(fit_window, infer_window
<td>UNIX time</td>
<td>
`to_s`
<span style="white-space: nowrap;">`to_s`</span>
</td>
<td>float</td>
<td>1649548800</td>
@@ -484,7 +511,7 @@ The same *explicit* logic as in [Periodic scheduler](#periodic-scheduler)
<td>ISO 8601</td>
<td rowspan=2>
`fit_window`
<span style="white-space: nowrap;">`fit_window`</span>
</td>
<td rowspan=2>str</td>
<td>
@@ -520,7 +547,7 @@ In `BacktestingScheduler`, the inference window is *implicitly* defined as a per
<td>ISO 8601</td>
<td rowspan=2>
`fit_every`
<span style="white-space: nowrap;">`fit_every`</span>
</td>
<td rowspan=2>str</td>
<td>

View File

@@ -28,11 +28,12 @@ Future updates will introduce additional export methods, offering users more fle
<tr>
<td>
`class`
<span style="white-space: nowrap;">`class`</span>
</td>
<td>
`writer.vm.VmWriter` or `vm`{{% available_from "v1.13.0" anomaly %}}
<span style="white-space: nowrap;">`writer.vm.VmWriter` or `vm`{{% available_from "v1.13.0" anomaly %}}
</span>
</td>
<td>
@@ -42,11 +43,11 @@ Name of the class needed to enable writing to VictoriaMetrics or Prometheus. VmW
<tr>
<td>
`datasource_url`
<span style="white-space: nowrap;">`datasource_url`</span>
</td>
<td>
`http://localhost:8481/`
<span style="white-space: nowrap;">`http://localhost:8481/`</span>
</td>
<td>
@@ -56,11 +57,13 @@ Datasource URL address
<tr>
<td>
`tenant_id`
<span style="white-space: nowrap;">`tenant_id`</span>
</td>
<td>
<span>
`0:0`, `multitenant`{{% available_from "v1.16.2" anomaly %}}
</span>
</td>
<td>
@@ -71,11 +74,11 @@ For VictoriaMetrics Cluster version only, tenants are identified by `accountID`
<tr>
<td rowspan="4">
`metric_format`
<span style="white-space: nowrap;">`metric_format`</span>
</td>
<td>
`__name__: "vmanomaly_$VAR"`
<span style="white-space: nowrap;">`__name__: "vmanomaly_$VAR"`</span>
</td>
<td rowspan="4">
@@ -97,26 +100,26 @@ Metrics to save the output (in metric names or labels). Must have `__name__` key
<tr>
<td>
`for: "$QUERY_KEY"`
<span style="white-space: nowrap;">`for: "$QUERY_KEY"`</span>
</td>
</tr>
<tr>
<td>
`run: "test_metric_format"`
<span style="white-space: nowrap;">`run: "test_metric_format"`</span>
</td>
</tr>
<tr>
<td>
`config: "io_vm_single.yaml"`
<span style="white-space: nowrap;">`config: "io_vm_single.yaml"`</span>
</td>
</tr>
<!-- End of additional rows -->
<tr>
<td>
`import_json_path`
<span style="white-space: nowrap;">`import_json_path`</span>
</td>
<td>
@@ -130,7 +133,7 @@ Optional, to override the default import path
<tr>
<td>
`health_path`
<span style="white-space: nowrap;">`health_path`</span>
</td>
<td>
@@ -144,7 +147,7 @@ Absolute or relative URL address where to check the availability of the datasour
<tr>
<td>
`user`
<span style="white-space: nowrap;">`user`</span>
</td>
<td>
@@ -158,7 +161,7 @@ BasicAuth username
<tr>
<td>
`password`
<span style="white-space: nowrap;">`password`</span>
</td>
<td>
@@ -172,7 +175,7 @@ BasicAuth password
<tr>
<td>
`timeout`
<span style="white-space: nowrap;">`timeout`</span>
</td>
<td>
@@ -186,7 +189,7 @@ Timeout for the requests, passed as a string
<tr>
<td>
`verify_tls`
<span style="white-space: nowrap;">`verify_tls`</span>
</td>
<td>
@@ -201,7 +204,7 @@ If a path to a CA bundle file (like `ca.crt`), it will verify the certificate us
<tr>
<td>
`tls_cert_file`
<span style="white-space: nowrap;">`tls_cert_file`</span>
</td>
<td>
@@ -214,7 +217,7 @@ Path to a file with the client certificate, i.e. `client.crt`{{% available_from
<tr>
<td>
`tls_key_file`
<span style="white-space: nowrap;">`tls_key_file`</span>
</td>
<td>
@@ -227,7 +230,7 @@ Path to a file with the client certificate key, i.e. `client.key`{{% available_f
<tr>
<td>
`bearer_token`
<span style="white-space: nowrap;">`bearer_token`</span>
</td>
<td>
@@ -240,15 +243,16 @@ Token is passed in the standard format with header: `Authorization: bearer {toke
<tr>
<td>
`bearer_token_file`
<span style="white-space: nowrap;">`bearer_token_file`</span>
</td>
<td>
`path_to_file`
</td>
<td>
<span>
Path to a file, which contains token, that is passed in the standard format with header: `Authorization: bearer {token}`{{% available_from "v1.15.9" anomaly %}}
</td>
</span> </td>
</tr>
</tbody>
</table>

View File

@@ -2,9 +2,9 @@
- To use *vmanomaly*, part of the enterprise package, a license key is required. Obtain your key [here](https://victoriametrics.com/products/enterprise/trial/) for this tutorial or for enterprise use.
- In the tutorial, we'll be using the following VictoriaMetrics components:
- [VictoriaMetrics Single-Node](https://docs.victoriametrics.com/single-server-victoriametrics) (v1.108.1)
- [vmalert](https://docs.victoriametrics.com/vmalert/) (v1.108.1)
- [vmagent](https://docs.victoriametrics.com/vmagent/) (v1.108.1)
- [VictoriaMetrics Single-Node](https://docs.victoriametrics.com/single-server-victoriametrics) (v1.109.0)
- [vmalert](https://docs.victoriametrics.com/vmalert/) (v1.109.0)
- [vmagent](https://docs.victoriametrics.com/vmagent/) (v1.109.0)
- [Grafana](https://grafana.com/) (v.10.2.1)
- [Docker](https://docs.docker.com/get-docker/) and [Docker Compose](https://docs.docker.com/compose/)
- [Node exporter](https://github.com/prometheus/node_exporter#node-exporter) (v1.7.0) and [Alertmanager](https://prometheus.io/docs/alerting/latest/alertmanager/) (v0.27.0)
@@ -313,7 +313,7 @@ Let's wrap it all up together into the `docker-compose.yml` file.
services:
vmagent:
container_name: vmagent
image: victoriametrics/vmagent:v1.108.1
image: victoriametrics/vmagent:v1.109.0
depends_on:
- "victoriametrics"
ports:
@@ -330,7 +330,7 @@ services:
victoriametrics:
container_name: victoriametrics
image: victoriametrics/victoria-metrics:v1.108.1
image: victoriametrics/victoria-metrics:v1.109.0
ports:
- 8428:8428
volumes:
@@ -363,7 +363,7 @@ services:
vmalert:
container_name: vmalert
image: victoriametrics/vmalert:v1.108.1
image: victoriametrics/vmalert:v1.109.0
depends_on:
- "victoriametrics"
ports:

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,11 @@
---
weight: 6
weight: 7
title: Year 2020
menu:
docs:
identifier: vm-changelog-2020
parent: vm-changelog
weight: 6
weight: 7
aliases:
- /CHANGELOG_2020.html
- /changelog_2020

View File

@@ -1,11 +1,11 @@
---
weight: 5
weight: 6
title: Year 2021
menu:
docs:
identifier: vm-changelog-2021
parent: vm-changelog
weight: 5
weight: 6
aliases:
- /CHANGELOG_2021.html
- /changelog_2021

View File

@@ -1,11 +1,11 @@
---
weight: 4
weight: 5
title: Year 2022
menu:
docs:
identifier: vm-changelog-2022
parent: vm-changelog
weight: 4
weight: 5
aliases:
- /CHANGELOG_2022.html
- /changelog_2022

View File

@@ -1,11 +1,11 @@
---
weight: 3
weight: 4
title: Year 2023
menu:
docs:
identifier: vm-changelog-2023
parent: vm-changelog
weight: 3
weight: 4
aliases:
- /CHANGELOG_2023.html
- /changelog_2023

File diff suppressed because it is too large Load Diff

View File

@@ -82,7 +82,7 @@ VictoriaMetrics Enterprise components are available in the following forms:
It is allowed to run VictoriaMetrics Enterprise components in [cases listed here](#valid-cases-for-victoriametrics-enterprise).
Binary releases of VictoriaMetrics Enterprise are available [at the releases page](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/latest).
Enterprise binaries and packages have `enterprise` suffix in their names. For example, `victoria-metrics-linux-amd64-v1.108.1-enterprise.tar.gz`.
Enterprise binaries and packages have `enterprise` suffix in their names. For example, `victoria-metrics-linux-amd64-v1.109.0-enterprise.tar.gz`.
In order to run binary release of VictoriaMetrics Enterprise component, please download the `*-enterprise.tar.gz` archive for your OS and architecture
from the [releases page](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/latest) and unpack it. Then run the unpacked binary.
@@ -100,8 +100,8 @@ For example, the following command runs VictoriaMetrics Enterprise binary with t
obtained at [this page](https://victoriametrics.com/products/enterprise/trial/):
```sh
wget https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v1.108.1/victoria-metrics-linux-amd64-v1.108.1-enterprise.tar.gz
tar -xzf victoria-metrics-linux-amd64-v1.108.1-enterprise.tar.gz
wget https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v1.109.0/victoria-metrics-linux-amd64-v1.109.0-enterprise.tar.gz
tar -xzf victoria-metrics-linux-amd64-v1.109.0-enterprise.tar.gz
./victoria-metrics-prod -license=BASE64_ENCODED_LICENSE_KEY
```
@@ -116,7 +116,7 @@ Alternatively, VictoriaMetrics Enterprise license can be stored in the file and
It is allowed to run VictoriaMetrics Enterprise components in [cases listed here](#valid-cases-for-victoriametrics-enterprise).
Docker images for VictoriaMetrics Enterprise are available [at VictoriaMetrics DockerHub](https://hub.docker.com/u/victoriametrics).
Enterprise docker images have `enterprise` suffix in their names. For example, `victoriametrics/victoria-metrics:v1.108.1-enterprise`.
Enterprise docker images have `enterprise` suffix in their names. For example, `victoriametrics/victoria-metrics:v1.109.0-enterprise`.
In order to run Docker image of VictoriaMetrics Enterprise component, it is required to provide the license key via command-line
flag as described [here](#binary-releases).
@@ -126,13 +126,13 @@ Enterprise license key can be obtained at [this page](https://victoriametrics.co
For example, the following command runs VictoriaMetrics Enterprise Docker image with the specified license key:
```sh
docker run --name=victoria-metrics victoriametrics/victoria-metrics:v1.108.1-enterprise -license=BASE64_ENCODED_LICENSE_KEY
docker run --name=victoria-metrics victoriametrics/victoria-metrics:v1.109.0-enterprise -license=BASE64_ENCODED_LICENSE_KEY
```
Alternatively, the license code can be stored in the file and then referred via `-licenseFile` command-line flag:
```sh
docker run --name=victoria-metrics -v /vm-license:/vm-license victoriametrics/victoria-metrics:v1.108.1-enterprise -licenseFile=/path/to/vm-license
docker run --name=victoria-metrics -v /vm-license:/vm-license victoriametrics/victoria-metrics:v1.109.0-enterprise -licenseFile=/path/to/vm-license
```
Example docker-compose configuration:
@@ -141,7 +141,7 @@ version: "3.5"
services:
victoriametrics:
container_name: victoriametrics
image: victoriametrics/victoria-metrics:v1.108.1
image: victoriametrics/victoria-metrics:v1.109.0
ports:
- 8428:8428
volumes:
@@ -173,7 +173,7 @@ is used to provide key in plain-text:
```yaml
server:
image:
tag: v1.108.1-enterprise
tag: v1.109.0-enterprise
license:
key: {BASE64_ENCODED_LICENSE_KEY}
@@ -184,7 +184,7 @@ In order to provide key via existing secret, the following values file is used:
```yaml
server:
image:
tag: v1.108.1-enterprise
tag: v1.109.0-enterprise
license:
secret:
@@ -233,7 +233,7 @@ spec:
license:
key: {BASE64_ENCODED_LICENSE_KEY}
image:
tag: v1.108.1-enterprise
tag: v1.109.0-enterprise
```
In order to provide key via existing secret, the following custom resource is used:
@@ -250,7 +250,7 @@ spec:
name: vm-license
key: license
image:
tag: v1.108.1-enterprise
tag: v1.109.0-enterprise
```
Example secret with license key:

View File

@@ -236,27 +236,27 @@ services:
- grafana_data:/var/lib/grafana/
vmsingle:
image: victoriametrics/victoria-metrics:v1.108.1
image: victoriametrics/victoria-metrics:v1.109.0
command:
- -httpListenAddr=0.0.0.0:8429
vmstorage:
image: victoriametrics/vmstorage:v1.108.1-cluster
image: victoriametrics/vmstorage:v1.109.0-cluster
vminsert:
image: victoriametrics/vminsert:v1.108.1-cluster
image: victoriametrics/vminsert:v1.109.0-cluster
command:
- -storageNode=vmstorage:8400
- -httpListenAddr=0.0.0.0:8480
vmselect:
image: victoriametrics/vmselect:v1.108.1-cluster
image: victoriametrics/vmselect:v1.109.0-cluster
command:
- -storageNode=vmstorage:8401
- -httpListenAddr=0.0.0.0:8481
vmagent:
image: victoriametrics/vmagent:v1.108.1
image: victoriametrics/vmagent:v1.109.0
volumes:
- ./scrape.yaml:/etc/vmagent/config.yaml
command:
@@ -265,7 +265,7 @@ services:
- -remoteWrite.url=http://vmsingle:8429/api/v1/write
vmgateway-cluster:
image: victoriametrics/vmgateway:v1.108.1-enterprise
image: victoriametrics/vmgateway:v1.109.0-enterprise
ports:
- 8431:8431
volumes:
@@ -281,7 +281,7 @@ services:
- -auth.oidcDiscoveryEndpoints=http://keycloak:8080/realms/master/.well-known/openid-configuration
vmgateway-single:
image: victoriametrics/vmgateway:v1.108.1-enterprise
image: victoriametrics/vmgateway:v1.109.0-enterprise
ports:
- 8432:8431
volumes:
@@ -393,7 +393,7 @@ Once iDP configuration is done, vmagent configuration needs to be updated to use
```yaml
vmagent:
image: victoriametrics/vmagent:v1.108.1
image: victoriametrics/vmagent:v1.109.0
volumes:
- ./scrape.yaml:/etc/vmagent/config.yaml
- ./vmagent-client-secret:/etc/vmagent/oauth2-client-secret

View File

@@ -2,6 +2,14 @@
- TODO
## 0.8.13
**Release date:** 14 Jan 2025
![Helm: v3](https://img.shields.io/badge/Helm-v3.14%2B-informational?color=informational&logo=helm&link=https%3A%2F%2Fgithub.com%2Fhelm%2Fhelm%2Freleases%2Ftag%2Fv3.14.0) ![AppVersion: v1.5.0](https://img.shields.io/badge/v1.5.0-success?logo=VictoriaMetrics&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Fvictorialogs%2Fchangelog%23v150)
- victorialogs version: v1.4.0 -> v1.5.0
## 0.8.12
**Release date:** 06 Jan 2025

View File

@@ -1,6 +1,6 @@
![Version](https://img.shields.io/badge/0.8.12-gray?logo=Helm&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Fhelm%2Fvictoria-logs-single%2Fchangelog%2F%230812)
![Version](https://img.shields.io/badge/0.8.13-gray?logo=Helm&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Fhelm%2Fvictoria-logs-single%2Fchangelog%2F%230813)
![ArtifactHub](https://img.shields.io/badge/ArtifactHub-informational?logoColor=white&color=417598&logo=artifacthub&link=https%3A%2F%2Fartifacthub.io%2Fpackages%2Fhelm%2Fvictoriametrics%2Fvictoria-logs-single)
![License](https://img.shields.io/github/license/VictoriaMetrics/helm-charts?labelColor=green&label=&link=https%3A%2F%2Fgithub.com%2FVictoriaMetrics%2Fhelm-charts%2Fblob%2Fmaster%2FLICENSE)
![Slack](https://img.shields.io/badge/Join-4A154B?logo=slack&link=https%3A%2F%2Fslack.victoriametrics.com)

View File

@@ -2,6 +2,14 @@
- TODO
## 0.15.4
**Release date:** 14 Jan 2025
![Helm: v3](https://img.shields.io/badge/Helm-v3.14%2B-informational?color=informational&logo=helm&link=https%3A%2F%2Fgithub.com%2Fhelm%2Fhelm%2Freleases%2Ftag%2Fv3.14.0) ![AppVersion: v1.109.0](https://img.shields.io/badge/v1.109.0-success?logo=VictoriaMetrics&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Fchangelog%23v11090)
- bump version of VM components to [v1.109.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.109.0)
## 0.15.3
**Release date:** 06 Jan 2025

View File

@@ -1,6 +1,6 @@
![Version](https://img.shields.io/badge/0.15.3-gray?logo=Helm&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Fhelm%2Fvictoria-metrics-agent%2Fchangelog%2F%230153)
![Version](https://img.shields.io/badge/0.15.4-gray?logo=Helm&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Fhelm%2Fvictoria-metrics-agent%2Fchangelog%2F%230154)
![ArtifactHub](https://img.shields.io/badge/ArtifactHub-informational?logoColor=white&color=417598&logo=artifacthub&link=https%3A%2F%2Fartifacthub.io%2Fpackages%2Fhelm%2Fvictoriametrics%2Fvictoria-metrics-agent)
![License](https://img.shields.io/github/license/VictoriaMetrics/helm-charts?labelColor=green&label=&link=https%3A%2F%2Fgithub.com%2FVictoriaMetrics%2Fhelm-charts%2Fblob%2Fmaster%2FLICENSE)
![Slack](https://img.shields.io/badge/Join-4A154B?logo=slack&link=https%3A%2F%2Fslack.victoriametrics.com)

View File

@@ -2,6 +2,14 @@
- TODO
## 0.13.6
**Release date:** 14 Jan 2025
![Helm: v3](https://img.shields.io/badge/Helm-v3.14%2B-informational?color=informational&logo=helm&link=https%3A%2F%2Fgithub.com%2Fhelm%2Fhelm%2Freleases%2Ftag%2Fv3.14.0) ![AppVersion: v1.109.0](https://img.shields.io/badge/v1.109.0-success?logo=VictoriaMetrics&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Fchangelog%23v11090)
- bump version of VM components to [v1.109.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.109.0)
## 0.13.5
**Release date:** 06 Jan 2025

View File

@@ -1,6 +1,6 @@
![Version](https://img.shields.io/badge/0.13.5-gray?logo=Helm&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Fhelm%2Fvictoria-metrics-alert%2Fchangelog%2F%230135)
![Version](https://img.shields.io/badge/0.13.6-gray?logo=Helm&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Fhelm%2Fvictoria-metrics-alert%2Fchangelog%2F%230136)
![ArtifactHub](https://img.shields.io/badge/ArtifactHub-informational?logoColor=white&color=417598&logo=artifacthub&link=https%3A%2F%2Fartifacthub.io%2Fpackages%2Fhelm%2Fvictoriametrics%2Fvictoria-metrics-alert)
![License](https://img.shields.io/github/license/VictoriaMetrics/helm-charts?labelColor=green&label=&link=https%3A%2F%2Fgithub.com%2FVictoriaMetrics%2Fhelm-charts%2Fblob%2Fmaster%2FLICENSE)
![Slack](https://img.shields.io/badge/Join-4A154B?logo=slack&link=https%3A%2F%2Fslack.victoriametrics.com)

View File

@@ -2,6 +2,14 @@
- TODO
## 0.8.4
**Release date:** 14 Jan 2025
![Helm: v3](https://img.shields.io/badge/Helm-v3.14%2B-informational?color=informational&logo=helm&link=https%3A%2F%2Fgithub.com%2Fhelm%2Fhelm%2Freleases%2Ftag%2Fv3.14.0) ![AppVersion: v1.109.0](https://img.shields.io/badge/v1.109.0-success?logo=VictoriaMetrics&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Fchangelog%23v11090)
- bump version of VM components to [v1.109.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.109.0)
## 0.8.3
**Release date:** 06 Jan 2025

View File

@@ -1,6 +1,6 @@
![Version](https://img.shields.io/badge/0.8.3-gray?logo=Helm&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Fhelm%2Fvictoria-metrics-auth%2Fchangelog%2F%23083)
![Version](https://img.shields.io/badge/0.8.4-gray?logo=Helm&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Fhelm%2Fvictoria-metrics-auth%2Fchangelog%2F%23084)
![ArtifactHub](https://img.shields.io/badge/ArtifactHub-informational?logoColor=white&color=417598&logo=artifacthub&link=https%3A%2F%2Fartifacthub.io%2Fpackages%2Fhelm%2Fvictoriametrics%2Fvictoria-metrics-auth)
![License](https://img.shields.io/github/license/VictoriaMetrics/helm-charts?labelColor=green&label=&link=https%3A%2F%2Fgithub.com%2FVictoriaMetrics%2Fhelm-charts%2Fblob%2Fmaster%2FLICENSE)
![Slack](https://img.shields.io/badge/Join-4A154B?logo=slack&link=https%3A%2F%2Fslack.victoriametrics.com)

View File

@@ -2,6 +2,14 @@
- TODO
## 0.17.1
**Release date:** 14 Jan 2025
![Helm: v3](https://img.shields.io/badge/Helm-v3.14%2B-informational?color=informational&logo=helm&link=https%3A%2F%2Fgithub.com%2Fhelm%2Fhelm%2Freleases%2Ftag%2Fv3.14.0) ![AppVersion: v1.109.0](https://img.shields.io/badge/v1.109.0-success?logo=VictoriaMetrics&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Fchangelog%23v11090)
- bump version of VM components to [v1.109.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.109.0)
## 0.17.0
**Release date:** 09 Jan 2025

View File

@@ -1,6 +1,6 @@
![Version](https://img.shields.io/badge/0.17.0-gray?logo=Helm&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Fhelm%2Fvictoria-metrics-cluster%2Fchangelog%2F%230170)
![Version](https://img.shields.io/badge/0.17.1-gray?logo=Helm&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Fhelm%2Fvictoria-metrics-cluster%2Fchangelog%2F%230171)
![ArtifactHub](https://img.shields.io/badge/ArtifactHub-informational?logoColor=white&color=417598&logo=artifacthub&link=https%3A%2F%2Fartifacthub.io%2Fpackages%2Fhelm%2Fvictoriametrics%2Fvictoria-metrics-cluster)
![License](https://img.shields.io/github/license/VictoriaMetrics/helm-charts?labelColor=green&label=&link=https%3A%2F%2Fgithub.com%2FVictoriaMetrics%2Fhelm-charts%2Fblob%2Fmaster%2FLICENSE)
![Slack](https://img.shields.io/badge/Join-4A154B?logo=slack&link=https%3A%2F%2Fslack.victoriametrics.com)

View File

@@ -1,6 +1,24 @@
## Next release
- TODO
## 0.7.2
**Release date:** 14 Jan 2025
![Helm: v3](https://img.shields.io/badge/Helm-v3.14%2B-informational?color=informational&logo=helm&link=https%3A%2F%2Fgithub.com%2Fhelm%2Fhelm%2Freleases%2Ftag%2Fv3.14.0) ![AppVersion: v1.109.0](https://img.shields.io/badge/v1.109.0-success?logo=VictoriaMetrics&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Fchangelog%23v11090)
- bump version of VM components to [v1.109.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.109.0)
## 0.7.1
**Release date:** 10 Jan 2025
![Helm: v3](https://img.shields.io/badge/Helm-v3.14%2B-informational?color=informational&logo=helm&link=https%3A%2F%2Fgithub.com%2Fhelm%2Fhelm%2Freleases%2Ftag%2Fv3.14.0) ![AppVersion: v1.108.1](https://img.shields.io/badge/v1.108.1-success?logo=VictoriaMetrics&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Fchangelog%23v11081)
- updated common dependency 0.0.35 -> 0.0.37
- fixed typo useMultitenantMode -> useMultiTenantMode in remotewrite settings
- allow passing additional remotewrite setings
## 0.7.0

View File

@@ -1,6 +1,6 @@
![Version](https://img.shields.io/badge/0.7.0-gray?logo=Helm&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Fhelm%2Fvictoria-metrics-distributed%2Fchangelog%2F%23070)
![Version](https://img.shields.io/badge/0.7.2-gray?logo=Helm&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Fhelm%2Fvictoria-metrics-distributed%2Fchangelog%2F%23072)
![ArtifactHub](https://img.shields.io/badge/ArtifactHub-informational?logoColor=white&color=417598&logo=artifacthub&link=https%3A%2F%2Fartifacthub.io%2Fpackages%2Fhelm%2Fvictoriametrics%2Fvictoria-metrics-distributed)
![License](https://img.shields.io/github/license/VictoriaMetrics/helm-charts?labelColor=green&label=&link=https%3A%2F%2Fgithub.com%2FVictoriaMetrics%2Fhelm-charts%2Fblob%2Fmaster%2FLICENSE)
![Slack](https://img.shields.io/badge/Join-4A154B?logo=slack&link=https%3A%2F%2Fslack.victoriametrics.com)

View File

@@ -2,6 +2,14 @@
- TODO
## 0.6.4
**Release date:** 14 Jan 2025
![Helm: v3](https://img.shields.io/badge/Helm-v3.14%2B-informational?color=informational&logo=helm&link=https%3A%2F%2Fgithub.com%2Fhelm%2Fhelm%2Freleases%2Ftag%2Fv3.14.0) ![AppVersion: v1.109.0](https://img.shields.io/badge/v1.109.0-success?logo=VictoriaMetrics&labelColor=gray&link=)
- bump version of VM components to [v1.109.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.109.0)
## 0.6.3
**Release date:** 06 Jan 2025

View File

@@ -1,6 +1,6 @@
![Version](https://img.shields.io/badge/0.6.3-gray?logo=Helm&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Fhelm%2Fvictoria-metrics-gateway%2Fchangelog%2F%23063)
![Version](https://img.shields.io/badge/0.6.4-gray?logo=Helm&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Fhelm%2Fvictoria-metrics-gateway%2Fchangelog%2F%23064)
![ArtifactHub](https://img.shields.io/badge/ArtifactHub-informational?logoColor=white&color=417598&logo=artifacthub&link=https%3A%2F%2Fartifacthub.io%2Fpackages%2Fhelm%2Fvictoriametrics%2Fvictoria-metrics-gateway)
![License](https://img.shields.io/github/license/VictoriaMetrics/helm-charts?labelColor=green&label=&link=https%3A%2F%2Fgithub.com%2FVictoriaMetrics%2Fhelm-charts%2Fblob%2Fmaster%2FLICENSE)
![Slack](https://img.shields.io/badge/Join-4A154B?logo=slack&link=https%3A%2F%2Fslack.victoriametrics.com)

View File

@@ -2,6 +2,22 @@
- TODO
## 0.33.4
**Release date:** 14 Jan 2025
![Helm: v3](https://img.shields.io/badge/Helm-v3.14%2B-informational?color=informational&logo=helm&link=https%3A%2F%2Fgithub.com%2Fhelm%2Fhelm%2Freleases%2Ftag%2Fv3.14.0) ![AppVersion: v1.109.0](https://img.shields.io/badge/v1.109.0-success?logo=VictoriaMetrics&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Fchangelog%23v11090)
- bump version of VM components to [v1.109.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.109.0)
## 0.33.3
**Release date:** 13 Jan 2025
![Helm: v3](https://img.shields.io/badge/Helm-v3.14%2B-informational?color=informational&logo=helm&link=https%3A%2F%2Fgithub.com%2Fhelm%2Fhelm%2Freleases%2Ftag%2Fv3.14.0) ![AppVersion: v1.108.1](https://img.shields.io/badge/v1.108.1-success?logo=VictoriaMetrics&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Fchangelog%23v11081)
- updates operator to [v0.51.3](https://github.com/VictoriaMetrics/operator/releases/tag/v0.51.3) version
## 0.33.2
**Release date:** 06 Jan 2025

View File

@@ -1,6 +1,6 @@
![Version](https://img.shields.io/badge/0.33.2-gray?logo=Helm&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Fhelm%2Fvictoria-metrics-k8s-stack%2Fchangelog%2F%230332)
![Version](https://img.shields.io/badge/0.33.4-gray?logo=Helm&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Fhelm%2Fvictoria-metrics-k8s-stack%2Fchangelog%2F%230334)
![ArtifactHub](https://img.shields.io/badge/ArtifactHub-informational?logoColor=white&color=417598&logo=artifacthub&link=https%3A%2F%2Fartifacthub.io%2Fpackages%2Fhelm%2Fvictoriametrics%2Fvictoria-metrics-k8s-stack)
![License](https://img.shields.io/github/license/VictoriaMetrics/helm-charts?labelColor=green&label=&link=https%3A%2F%2Fgithub.com%2FVictoriaMetrics%2Fhelm-charts%2Fblob%2Fmaster%2FLICENSE)
![Slack](https://img.shields.io/badge/Join-4A154B?logo=slack&link=https%3A%2F%2Fslack.victoriametrics.com)

View File

@@ -2,6 +2,14 @@
- TODO
## 0.40.4
**Release date:** 13 Jan 2025
![Helm: v3](https://img.shields.io/badge/Helm-v3.14%2B-informational?color=informational&logo=helm&link=https%3A%2F%2Fgithub.com%2Fhelm%2Fhelm%2Freleases%2Ftag%2Fv3.14.0) ![AppVersion: v0.51.3](https://img.shields.io/badge/v0.51.3-success?logo=VictoriaMetrics&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Foperator%2Fchangelog%23v0513)
- updates operator to [v0.51.3](https://github.com/VictoriaMetrics/operator/releases/tag/v0.51.3) version
## 0.40.3
**Release date:** 06 Jan 2025

View File

@@ -1,6 +1,6 @@
![Version](https://img.shields.io/badge/0.40.3-gray?logo=Helm&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Fhelm%2Fvictoria-metrics-operator%2Fchangelog%2F%230403)
![Version](https://img.shields.io/badge/0.40.4-gray?logo=Helm&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Fhelm%2Fvictoria-metrics-operator%2Fchangelog%2F%230404)
![ArtifactHub](https://img.shields.io/badge/ArtifactHub-informational?logoColor=white&color=417598&logo=artifacthub&link=https%3A%2F%2Fartifacthub.io%2Fpackages%2Fhelm%2Fvictoriametrics%2Fvictoria-metrics-operator)
![License](https://img.shields.io/github/license/VictoriaMetrics/helm-charts?labelColor=green&label=&link=https%3A%2F%2Fgithub.com%2FVictoriaMetrics%2Fhelm-charts%2Fblob%2Fmaster%2FLICENSE)
![Slack](https://img.shields.io/badge/Join-4A154B?logo=slack&link=https%3A%2F%2Fslack.victoriametrics.com)

View File

@@ -2,6 +2,14 @@
- TODO
## 0.13.5
**Release date:** 14 Jan 2025
![Helm: v3](https://img.shields.io/badge/Helm-v3.14%2B-informational?color=informational&logo=helm&link=https%3A%2F%2Fgithub.com%2Fhelm%2Fhelm%2Freleases%2Ftag%2Fv3.14.0) ![AppVersion: v1.109.0](https://img.shields.io/badge/v1.109.0-success?logo=VictoriaMetrics&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Foperator%2Fchangelog%23v11090)
- bump version of VM components to [v1.109.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.109.0)
## 0.13.4
**Release date:** 06 Jan 2025

View File

@@ -1,6 +1,6 @@
![Version](https://img.shields.io/badge/0.13.4-gray?logo=Helm&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Fhelm%2Fvictoria-metrics-single%2Fchangelog%2F%230134)
![Version](https://img.shields.io/badge/0.13.5-gray?logo=Helm&labelColor=gray&link=https%3A%2F%2Fdocs.victoriametrics.com%2Fhelm%2Fvictoria-metrics-single%2Fchangelog%2F%230135)
![ArtifactHub](https://img.shields.io/badge/ArtifactHub-informational?logoColor=white&color=417598&logo=artifacthub&link=https%3A%2F%2Fartifacthub.io%2Fpackages%2Fhelm%2Fvictoriametrics%2Fvictoria-metrics-single)
![License](https://img.shields.io/github/license/VictoriaMetrics/helm-charts?labelColor=green&label=&link=https%3A%2F%2Fgithub.com%2FVictoriaMetrics%2Fhelm-charts%2Fblob%2Fmaster%2FLICENSE)
![Slack](https://img.shields.io/badge/Join-4A154B?logo=slack&link=https%3A%2F%2Fslack.victoriametrics.com)

View File

@@ -30,8 +30,8 @@ scrape_configs:
After you created the `scrape.yaml` file, download and unpack [single-node VictoriaMetrics](https://docs.victoriametrics.com/) to the same directory:
```
wget https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v1.108.1/victoria-metrics-linux-amd64-v1.108.1.tar.gz
tar xzf victoria-metrics-linux-amd64-v1.108.1.tar.gz
wget https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v1.109.0/victoria-metrics-linux-amd64-v1.109.0.tar.gz
tar xzf victoria-metrics-linux-amd64-v1.109.0.tar.gz
```
Then start VictoriaMetrics and instruct it to scrape targets defined in `scrape.yaml` and save scraped metrics
@@ -146,8 +146,8 @@ Then start [single-node VictoriaMetrics](https://docs.victoriametrics.com/) acco
```yaml
# Download and unpack single-node VictoriaMetrics
wget https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v1.108.1/victoria-metrics-linux-amd64-v1.108.1.tar.gz
tar xzf victoria-metrics-linux-amd64-v1.108.1.tar.gz
wget https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v1.109.0/victoria-metrics-linux-amd64-v1.109.0.tar.gz
tar xzf victoria-metrics-linux-amd64-v1.109.0.tar.gz
# Run single-node VictoriaMetrics with the given scrape.yaml
./victoria-metrics-prod -promscrape.config=scrape.yaml

7
docs/search/_index.md Normal file
View File

@@ -0,0 +1,7 @@
---
page: search
layout: search
draft: false
weight: 0
search: true
---

View File

@@ -149,7 +149,7 @@ or [Prometheus recording rules definition format](https://prometheus.io/docs/pro
There are limitations for the rules files:
1. All files may contain no more than 100 rules in total. If you need to upload more rules contact us via [support@victoriametrics.com](mailto:support@victoriametrics.com).
1. All files may contain no more than 100 rules in total. If you need to upload more rules contact us via [support-cloud@victoriametrics.com](mailto:support-cloud@victoriametrics.com).
2. The maximum file size is 20mb.
3. The names of the groups in the files should be unique.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

View File

@@ -18,7 +18,7 @@ The tier parameters are derived from testing in typical monitoring environments,
| **Active Time Series Count** | Per Tier Limits | Number of [active time series](https://docs.victoriametrics.com/faq/#what-is-an-active-time-series) that received at least one data point in the last hour. |
| **Read Rate** | Per Tier Limits | Number of datapoints retrieved from the database per second. |
| **New Series Over 24 Hours** (churn rate) | `<= Active Time Series Count` | Number of new series created in 24 hours. High [churn rate](https://docs.victoriametrics.com/faq/#what-is-high-churn-rate) leads to higher resource consumption. |
| **Concurrent Requests per Token** | `<= 600` | Maximum concurrent requests per access token. It is recommended to create separate tokens for different clients and environments. This can be adjusted via [support](mailto:support@victoriametrics.com). |
| **Concurrent Requests per Token** | `<= 600` | Maximum concurrent requests per access token. It is recommended to create separate tokens for different clients and environments. This can be adjusted via [support](mailto:support-cloud@victoriametrics.com). |
For a detailed explanation of each parameter, visit the guide on [Understanding Your Setup Size](https://docs.victoriametrics.com/guides/understand-your-setup-size.html).
@@ -26,7 +26,7 @@ For a detailed explanation of each parameter, visit the guide on [Understanding
| **Flag** | **Default Value** | **Description** |
|-----------------------------------|---------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **Max Label Value Length** | `<= 1kb` (Default: `4kb`) | Maximum length of label values. Longer values are truncated. Large label values can lead to high RAM consumption. This can be adjusted via [support](mailto:support@victoriametrics.com). |
| **Max Label Value Length** | `<= 1kb` (Default: `4kb`) | Maximum length of label values. Longer values are truncated. Large label values can lead to high RAM consumption. This can be adjusted via [support](mailto:support-cloud@victoriametrics.com). |
| **Max Labels per Time Series** | `<= 30` | Maximum number of labels per time series. Excess labels are dropped. Higher values can increase [cardinality](https://docs.victoriametrics.com/keyconcepts/#cardinality) and resource usage. This can be configured in [deployment settings](https://docs.victoriametrics.com/victoriametrics-cloud/quickstart/#modifying-an-existing-deployment). |

View File

@@ -273,7 +273,6 @@ expr: <string>
# Labels to add or overwrite for each alert.
# In case of conflicts, original labels are kept with prefix `exported_`.
# label value supports [templating functions](#template-functions), [reusable templates](#reusable-templates) and limited variables, such as {{$value}}, {{$expr}}.
labels:
[ <labelname>: <tmpl_string> ]

View File

@@ -5,6 +5,7 @@ import (
"slices"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
"unsafe"
@@ -227,6 +228,16 @@ func (br *blockResult) cloneValues(values []string) []string {
return br.valuesBuf[valuesBufLen:]
}
func (br *blockResult) addValues(values []string) {
valuesBufLen := len(br.valuesBuf)
br.valuesBuf = slicesutil.SetLength(br.valuesBuf, valuesBufLen+len(values))
valuesBuf := br.valuesBuf[valuesBufLen:]
_ = valuesBuf[len(values)-1]
for i, v := range values {
valuesBuf[i] = br.a.copyString(v)
}
}
func (br *blockResult) addValue(v string) {
valuesBuf := br.valuesBuf
if len(valuesBuf) > 0 && v == valuesBuf[len(valuesBuf)-1] {
@@ -281,11 +292,14 @@ func (br *blockResult) addResultColumn(rc *resultColumn) {
logger.Panicf("BUG: column %q must contain %d rows, but it contains %d rows", rc.name, br.rowsLen, len(rc.values))
}
if areConstValues(rc.values) {
// This optimization allows reducing memory usage after br cloning
// Clone the constant value into rc, so it doesn't hold the external memory.
// This optimization allows reducing memory usage after br cloning.
br.addValue(rc.values[0])
valuesEncoded := br.valuesBuf[len(br.valuesBuf)-1:]
br.csAdd(blockResultColumn{
name: rc.name,
isConst: true,
valuesEncoded: rc.values[:1],
valuesEncoded: valuesEncoded,
})
} else {
br.csAdd(blockResultColumn{
@@ -490,73 +504,57 @@ func (br *blockResult) newValuesEncodedFromColumnHeader(bs *blockSearch, bm *bit
switch ch.valueType {
case valueTypeString:
visitValuesReadonly(bs, ch, bm, br.addValue)
visitValuesReadonly(bs, ch, bm, br.addValues)
case valueTypeDict:
visitValuesReadonly(bs, ch, bm, func(v string) {
if len(v) != 1 {
logger.Panicf("FATAL: %s: unexpected dict value size for column %q; got %d bytes; want 1 byte", bs.partPath(), ch.name, len(v))
visitValuesReadonly(bs, ch, bm, func(values []string) {
checkValuesSize(bs, ch, values, 1, "dict")
for _, v := range values {
dictIdx := v[0]
if int(dictIdx) >= len(ch.valuesDict.values) {
logger.Panicf("FATAL: %s: too big dict index for column %q: %d; should be smaller than %d", bs.partPath(), ch.name, dictIdx, len(ch.valuesDict.values))
}
}
dictIdx := v[0]
if int(dictIdx) >= len(ch.valuesDict.values) {
logger.Panicf("FATAL: %s: too big dict index for column %q: %d; should be smaller than %d", bs.partPath(), ch.name, dictIdx, len(ch.valuesDict.values))
}
br.addValue(v)
br.addValues(values)
})
case valueTypeUint8:
visitValuesReadonly(bs, ch, bm, func(v string) {
if len(v) != 1 {
logger.Panicf("FATAL: %s: unexpected size for uint8 column %q; got %d bytes; want 1 byte", bs.partPath(), ch.name, len(v))
}
br.addValue(v)
visitValuesReadonly(bs, ch, bm, func(values []string) {
checkValuesSize(bs, ch, values, 1, "uint8")
br.addValues(values)
})
case valueTypeUint16:
visitValuesReadonly(bs, ch, bm, func(v string) {
if len(v) != 2 {
logger.Panicf("FATAL: %s: unexpected size for uint16 column %q; got %d bytes; want 2 bytes", bs.partPath(), ch.name, len(v))
}
br.addValue(v)
visitValuesReadonly(bs, ch, bm, func(values []string) {
checkValuesSize(bs, ch, values, 2, "uint16")
br.addValues(values)
})
case valueTypeUint32:
visitValuesReadonly(bs, ch, bm, func(v string) {
if len(v) != 4 {
logger.Panicf("FATAL: %s: unexpected size for uint32 column %q; got %d bytes; want 4 bytes", bs.partPath(), ch.name, len(v))
}
br.addValue(v)
visitValuesReadonly(bs, ch, bm, func(values []string) {
checkValuesSize(bs, ch, values, 4, "uint32")
br.addValues(values)
})
case valueTypeUint64:
visitValuesReadonly(bs, ch, bm, func(v string) {
if len(v) != 8 {
logger.Panicf("FATAL: %s: unexpected size for uint64 column %q; got %d bytes; want 8 bytes", bs.partPath(), ch.name, len(v))
}
br.addValue(v)
visitValuesReadonly(bs, ch, bm, func(values []string) {
checkValuesSize(bs, ch, values, 8, "uint64")
br.addValues(values)
})
case valueTypeInt64:
visitValuesReadonly(bs, ch, bm, func(v string) {
if len(v) != 8 {
logger.Panicf("FATAL: %s: unexpected size for int64 column %q; got %d bytes; want 8 bytes", bs.partPath(), ch.name, len(v))
}
br.addValue(v)
visitValuesReadonly(bs, ch, bm, func(values []string) {
checkValuesSize(bs, ch, values, 8, "int64")
br.addValues(values)
})
case valueTypeFloat64:
visitValuesReadonly(bs, ch, bm, func(v string) {
if len(v) != 8 {
logger.Panicf("FATAL: %s: unexpected size for float64 column %q; got %d bytes; want 8 bytes", bs.partPath(), ch.name, len(v))
}
br.addValue(v)
visitValuesReadonly(bs, ch, bm, func(values []string) {
checkValuesSize(bs, ch, values, 8, "float64")
br.addValues(values)
})
case valueTypeIPv4:
visitValuesReadonly(bs, ch, bm, func(v string) {
if len(v) != 4 {
logger.Panicf("FATAL: %s: unexpected size for ipv4 column %q; got %d bytes; want 4 bytes", bs.partPath(), ch.name, len(v))
}
br.addValue(v)
visitValuesReadonly(bs, ch, bm, func(values []string) {
checkValuesSize(bs, ch, values, 4, "ipv4")
br.addValues(values)
})
case valueTypeTimestampISO8601:
visitValuesReadonly(bs, ch, bm, func(v string) {
if len(v) != 8 {
logger.Panicf("FATAL: %s: unexpected size for timestmap column %q; got %d bytes; want 8 bytes", bs.partPath(), ch.name, len(v))
}
br.addValue(v)
visitValuesReadonly(bs, ch, bm, func(values []string) {
checkValuesSize(bs, ch, values, 8, "iso8601")
br.addValues(values)
})
default:
logger.Panicf("FATAL: %s: unknown valueType=%d for column %q", bs.partPath(), ch.valueType, ch.name)
@@ -565,6 +563,14 @@ func (br *blockResult) newValuesEncodedFromColumnHeader(bs *blockSearch, bm *bit
return br.valuesBuf[valuesBufLen:]
}
func checkValuesSize(bs *blockSearch, ch *columnHeader, values []string, sizeExpected int, typeStr string) {
for _, v := range values {
if len(v) != sizeExpected {
logger.Panicf("FATAL: %s: unexpected size for %s column %q; got %d bytes; want %d bytes", typeStr, bs.partPath(), ch.name, len(v), sizeExpected)
}
}
}
// addColumn adds column for the given ch to br.
//
// The added column is valid until ch is changed.
@@ -2138,17 +2144,46 @@ func getEmptyStrings(rowsLen int) []string {
var emptyStrings atomic.Pointer[[]string]
func visitValuesReadonly(bs *blockSearch, ch *columnHeader, bm *bitmap, f func(value string)) {
func visitValuesReadonly(bs *blockSearch, ch *columnHeader, bm *bitmap, f func(values []string)) {
if bm.isZero() {
// Fast path - nothing to visit
return
}
values := bs.getValuesForColumn(ch)
if bm.areAllBitsSet() {
// Faster path - visit all the values
f(values)
return
}
// Slower path - visit only the selected values
vb := getValuesBuf()
bm.forEachSetBitReadonly(func(idx int) {
f(values[idx])
vb.values = append(vb.values, values[idx])
})
f(vb.values)
putValuesBuf(vb)
}
type valuesBuf struct {
values []string
}
func getValuesBuf() *valuesBuf {
v := valuesBufPool.Get()
if v == nil {
return &valuesBuf{}
}
return v.(*valuesBuf)
}
func putValuesBuf(vb *valuesBuf) {
vb.values = vb.values[:0]
valuesBufPool.Put(vb)
}
var valuesBufPool sync.Pool
func getCanonicalColumnName(columnName string) string {
if columnName == "" {
return "_msg"

View File

@@ -12,6 +12,7 @@ type chunkedAllocator struct {
countEmptyProcessors []statsCountEmptyProcessor
countUniqProcessors []statsCountUniqProcessor
countUniqHashProcessors []statsCountUniqHashProcessor
histogramProcessors []statsHistogramProcessor
maxProcessors []statsMaxProcessor
medianProcessors []statsMedianProcessor
minProcessors []statsMinProcessor
@@ -60,6 +61,11 @@ func (a *chunkedAllocator) newStatsCountUniqHashProcessor() (p *statsCountUniqHa
return p
}
func (a *chunkedAllocator) newStatsHistogramProcessor() (p *statsHistogramProcessor) {
a.histogramProcessors, p = addNewItem(a.histogramProcessors, a)
return p
}
func (a *chunkedAllocator) newStatsMaxProcessor() (p *statsMaxProcessor) {
a.maxProcessors, p = addNewItem(a.maxProcessors, a)
return p

View File

@@ -2,6 +2,8 @@ package logstorage
import (
"testing"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
)
func TestMatchAnyCasePhrase(t *testing.T) {
@@ -44,8 +46,6 @@ func TestFilterAnyCasePhrase(t *testing.T) {
t.Parallel()
t.Run("single-row", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -119,8 +119,6 @@ func TestFilterAnyCasePhrase(t *testing.T) {
})
t.Run("const-column", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "other-column",
@@ -230,8 +228,6 @@ func TestFilterAnyCasePhrase(t *testing.T) {
})
t.Run("dict", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -287,8 +283,6 @@ func TestFilterAnyCasePhrase(t *testing.T) {
})
t.Run("strings", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -359,8 +353,6 @@ func TestFilterAnyCasePhrase(t *testing.T) {
})
t.Run("uint8", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -426,8 +418,6 @@ func TestFilterAnyCasePhrase(t *testing.T) {
})
t.Run("uint16", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -492,8 +482,6 @@ func TestFilterAnyCasePhrase(t *testing.T) {
})
t.Run("uint32", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -558,8 +546,6 @@ func TestFilterAnyCasePhrase(t *testing.T) {
})
t.Run("uint64", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -623,8 +609,6 @@ func TestFilterAnyCasePhrase(t *testing.T) {
})
t.Run("int64", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -688,8 +672,6 @@ func TestFilterAnyCasePhrase(t *testing.T) {
})
t.Run("float64", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -795,8 +777,6 @@ func TestFilterAnyCasePhrase(t *testing.T) {
})
t.Run("ipv4", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -893,8 +873,6 @@ func TestFilterAnyCasePhrase(t *testing.T) {
})
t.Run("timestamp-iso8601", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "_msg",
@@ -976,4 +954,7 @@ func TestFilterAnyCasePhrase(t *testing.T) {
}
testFilterMatchForColumns(t, columns, pf, "_msg", nil)
})
// Remove the remaining data files for the test
fs.MustRemoveAll(t.Name())
}

View File

@@ -2,6 +2,8 @@ package logstorage
import (
"testing"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
)
func TestMatchAnyCasePrefix(t *testing.T) {
@@ -44,8 +46,6 @@ func TestFilterAnyCasePrefix(t *testing.T) {
t.Parallel()
t.Run("single-row", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -137,8 +137,6 @@ func TestFilterAnyCasePrefix(t *testing.T) {
})
t.Run("const-column", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "other-column",
@@ -254,8 +252,6 @@ func TestFilterAnyCasePrefix(t *testing.T) {
})
t.Run("dict", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -311,8 +307,6 @@ func TestFilterAnyCasePrefix(t *testing.T) {
})
t.Run("strings", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -395,8 +389,6 @@ func TestFilterAnyCasePrefix(t *testing.T) {
})
t.Run("uint8", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -462,8 +454,6 @@ func TestFilterAnyCasePrefix(t *testing.T) {
})
t.Run("uint16", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -528,8 +518,6 @@ func TestFilterAnyCasePrefix(t *testing.T) {
})
t.Run("uint32", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -594,8 +582,6 @@ func TestFilterAnyCasePrefix(t *testing.T) {
})
t.Run("uint64", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -659,8 +645,6 @@ func TestFilterAnyCasePrefix(t *testing.T) {
})
t.Run("int64", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -724,8 +708,6 @@ func TestFilterAnyCasePrefix(t *testing.T) {
})
t.Run("float64", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -837,8 +819,6 @@ func TestFilterAnyCasePrefix(t *testing.T) {
})
t.Run("ipv4", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -935,8 +915,6 @@ func TestFilterAnyCasePrefix(t *testing.T) {
})
t.Run("timestamp-iso8601", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "_msg",
@@ -1018,4 +996,7 @@ func TestFilterAnyCasePrefix(t *testing.T) {
}
testFilterMatchForColumns(t, columns, fp, "_msg", nil)
})
// Remove the remaining data files for the test
fs.MustRemoveAll(t.Name())
}

View File

@@ -2,14 +2,14 @@ package logstorage
import (
"testing"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
)
func TestFilterExactPrefix(t *testing.T) {
t.Parallel()
t.Run("single-row", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -59,8 +59,6 @@ func TestFilterExactPrefix(t *testing.T) {
})
t.Run("const-column", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -112,8 +110,6 @@ func TestFilterExactPrefix(t *testing.T) {
})
t.Run("dict", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -157,8 +153,6 @@ func TestFilterExactPrefix(t *testing.T) {
})
t.Run("strings", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -211,8 +205,6 @@ func TestFilterExactPrefix(t *testing.T) {
})
t.Run("uint8", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -266,8 +258,6 @@ func TestFilterExactPrefix(t *testing.T) {
})
t.Run("uint16", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -321,8 +311,6 @@ func TestFilterExactPrefix(t *testing.T) {
})
t.Run("uint32", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -376,8 +364,6 @@ func TestFilterExactPrefix(t *testing.T) {
})
t.Run("uint64", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -431,8 +417,6 @@ func TestFilterExactPrefix(t *testing.T) {
})
t.Run("int64", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -498,8 +482,6 @@ func TestFilterExactPrefix(t *testing.T) {
})
t.Run("float64", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -557,8 +539,6 @@ func TestFilterExactPrefix(t *testing.T) {
})
t.Run("ipv4", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -607,8 +587,6 @@ func TestFilterExactPrefix(t *testing.T) {
})
t.Run("timestamp-iso8601", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "_msg",
@@ -652,4 +630,7 @@ func TestFilterExactPrefix(t *testing.T) {
}
testFilterMatchForColumns(t, columns, fep, "_msg", nil)
})
// Remove the remaining data files for the test
fs.MustRemoveAll(t.Name())
}

View File

@@ -2,14 +2,14 @@ package logstorage
import (
"testing"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
)
func TestFilterExact(t *testing.T) {
t.Parallel()
t.Run("single-row", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -47,8 +47,6 @@ func TestFilterExact(t *testing.T) {
})
t.Run("const-column", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -94,8 +92,6 @@ func TestFilterExact(t *testing.T) {
})
t.Run("dict", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -139,8 +135,6 @@ func TestFilterExact(t *testing.T) {
})
t.Run("strings", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -193,8 +187,6 @@ func TestFilterExact(t *testing.T) {
})
t.Run("uint8", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -248,8 +240,6 @@ func TestFilterExact(t *testing.T) {
})
t.Run("uint16", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -303,8 +293,6 @@ func TestFilterExact(t *testing.T) {
})
t.Run("uint32", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -358,8 +346,6 @@ func TestFilterExact(t *testing.T) {
})
t.Run("uint64", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -413,8 +399,6 @@ func TestFilterExact(t *testing.T) {
})
t.Run("int64", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -474,8 +458,6 @@ func TestFilterExact(t *testing.T) {
})
t.Run("float64", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -551,8 +533,6 @@ func TestFilterExact(t *testing.T) {
})
t.Run("ipv4", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -613,8 +593,6 @@ func TestFilterExact(t *testing.T) {
})
t.Run("timestamp-iso8601", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "_msg",
@@ -664,4 +642,7 @@ func TestFilterExact(t *testing.T) {
}
testFilterMatchForColumns(t, columns, fe, "_msg", nil)
})
// Remove the remaining data files for the test
fs.MustRemoveAll(t.Name())
}

View File

@@ -4,14 +4,14 @@ import (
"reflect"
"slices"
"testing"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
)
func TestFilterIn(t *testing.T) {
t.Parallel()
t.Run("single-row", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -79,8 +79,6 @@ func TestFilterIn(t *testing.T) {
})
t.Run("const-column", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -132,8 +130,6 @@ func TestFilterIn(t *testing.T) {
})
t.Run("dict", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -189,8 +185,6 @@ func TestFilterIn(t *testing.T) {
})
t.Run("strings", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -243,8 +237,6 @@ func TestFilterIn(t *testing.T) {
})
t.Run("uint8", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -310,8 +302,6 @@ func TestFilterIn(t *testing.T) {
})
t.Run("uint16", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -377,8 +367,6 @@ func TestFilterIn(t *testing.T) {
})
t.Run("uint32", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -444,8 +432,6 @@ func TestFilterIn(t *testing.T) {
})
t.Run("uint64", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -505,8 +491,6 @@ func TestFilterIn(t *testing.T) {
})
t.Run("int64", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -566,8 +550,6 @@ func TestFilterIn(t *testing.T) {
})
t.Run("float64", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -649,8 +631,6 @@ func TestFilterIn(t *testing.T) {
})
t.Run("ipv4", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -717,8 +697,6 @@ func TestFilterIn(t *testing.T) {
})
t.Run("timestamp-iso8601", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "_msg",
@@ -774,6 +752,9 @@ func TestFilterIn(t *testing.T) {
}
testFilterMatchForColumns(t, columns, fi, "_msg", nil)
})
// Remove the remaining data files for the test
fs.MustRemoveAll(t.Name())
}
func TestGetCommonTokensAndTokenSets(t *testing.T) {

View File

@@ -2,6 +2,8 @@ package logstorage
import (
"testing"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
)
func TestMatchIPv4Range(t *testing.T) {
@@ -33,8 +35,6 @@ func TestFilterIPv4Range(t *testing.T) {
t.Parallel()
t.Run("const-column", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -85,8 +85,6 @@ func TestFilterIPv4Range(t *testing.T) {
})
t.Run("dict", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -142,8 +140,6 @@ func TestFilterIPv4Range(t *testing.T) {
})
t.Run("strings", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -187,8 +183,6 @@ func TestFilterIPv4Range(t *testing.T) {
})
t.Run("uint8", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -218,8 +212,6 @@ func TestFilterIPv4Range(t *testing.T) {
})
t.Run("uint16", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -249,8 +241,6 @@ func TestFilterIPv4Range(t *testing.T) {
})
t.Run("uint32", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -280,8 +270,6 @@ func TestFilterIPv4Range(t *testing.T) {
})
t.Run("uint64", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -311,8 +299,6 @@ func TestFilterIPv4Range(t *testing.T) {
})
t.Run("int64", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -342,8 +328,6 @@ func TestFilterIPv4Range(t *testing.T) {
})
t.Run("float64", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -373,8 +357,6 @@ func TestFilterIPv4Range(t *testing.T) {
})
t.Run("ipv4", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -427,8 +409,6 @@ func TestFilterIPv4Range(t *testing.T) {
})
t.Run("timestamp-iso8601", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "_msg",
@@ -454,4 +434,7 @@ func TestFilterIPv4Range(t *testing.T) {
}
testFilterMatchForColumns(t, columns, fr, "_msg", nil)
})
// Remove the remaining data files for the test
fs.MustRemoveAll(t.Name())
}

View File

@@ -2,6 +2,8 @@ package logstorage
import (
"testing"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
)
func TestMatchLenRange(t *testing.T) {
@@ -36,8 +38,6 @@ func TestFilterLenRange(t *testing.T) {
t.Parallel()
t.Run("const-column", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -81,8 +81,6 @@ func TestFilterLenRange(t *testing.T) {
})
t.Run("dict", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -123,8 +121,6 @@ func TestFilterLenRange(t *testing.T) {
})
t.Run("strings", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -161,8 +157,6 @@ func TestFilterLenRange(t *testing.T) {
})
t.Run("uint8", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -207,8 +201,6 @@ func TestFilterLenRange(t *testing.T) {
})
t.Run("uint16", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -253,8 +245,6 @@ func TestFilterLenRange(t *testing.T) {
})
t.Run("uint32", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -299,8 +289,6 @@ func TestFilterLenRange(t *testing.T) {
})
t.Run("uint64", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -345,8 +333,6 @@ func TestFilterLenRange(t *testing.T) {
})
t.Run("int64", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -391,8 +377,6 @@ func TestFilterLenRange(t *testing.T) {
})
t.Run("float64", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -430,8 +414,6 @@ func TestFilterLenRange(t *testing.T) {
})
t.Run("ipv4", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -470,8 +452,6 @@ func TestFilterLenRange(t *testing.T) {
})
t.Run("timestamp-iso8601", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "_msg",
@@ -505,4 +485,7 @@ func TestFilterLenRange(t *testing.T) {
}
testFilterMatchForColumns(t, columns, fr, "_msg", nil)
})
// Remove the remaining data files for the test
fs.MustRemoveAll(t.Name())
}

View File

@@ -2,6 +2,8 @@ package logstorage
import (
"testing"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
)
func TestMatchPhrase(t *testing.T) {
@@ -49,8 +51,6 @@ func TestFilterPhrase(t *testing.T) {
t.Parallel()
t.Run("single-row", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -124,8 +124,6 @@ func TestFilterPhrase(t *testing.T) {
})
t.Run("const-column", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "other-column",
@@ -235,8 +233,6 @@ func TestFilterPhrase(t *testing.T) {
})
t.Run("dict", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -292,8 +288,6 @@ func TestFilterPhrase(t *testing.T) {
})
t.Run("strings", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -364,8 +358,6 @@ func TestFilterPhrase(t *testing.T) {
})
t.Run("uint8", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -431,8 +423,6 @@ func TestFilterPhrase(t *testing.T) {
})
t.Run("uint16", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -497,8 +487,6 @@ func TestFilterPhrase(t *testing.T) {
})
t.Run("uint32", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -563,8 +551,6 @@ func TestFilterPhrase(t *testing.T) {
})
t.Run("uint64", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -628,8 +614,6 @@ func TestFilterPhrase(t *testing.T) {
})
t.Run("int64", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -693,8 +677,6 @@ func TestFilterPhrase(t *testing.T) {
})
t.Run("float64", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -800,8 +782,6 @@ func TestFilterPhrase(t *testing.T) {
})
t.Run("ipv4", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -898,8 +878,6 @@ func TestFilterPhrase(t *testing.T) {
})
t.Run("timestamp-iso8601", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "_msg",
@@ -981,4 +959,7 @@ func TestFilterPhrase(t *testing.T) {
}
testFilterMatchForColumns(t, columns, pf, "_msg", nil)
})
// Remove the remaining data files for the test
fs.MustRemoveAll(t.Name())
}

View File

@@ -2,6 +2,8 @@ package logstorage
import (
"testing"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
)
func TestMatchPrefix(t *testing.T) {
@@ -49,8 +51,6 @@ func TestFilterPrefix(t *testing.T) {
t.Parallel()
t.Run("single-row", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -136,8 +136,6 @@ func TestFilterPrefix(t *testing.T) {
})
t.Run("const-column", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "other-column",
@@ -253,8 +251,6 @@ func TestFilterPrefix(t *testing.T) {
})
t.Run("dict", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -310,8 +306,6 @@ func TestFilterPrefix(t *testing.T) {
})
t.Run("strings", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -394,8 +388,6 @@ func TestFilterPrefix(t *testing.T) {
})
t.Run("uint8", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -461,8 +453,6 @@ func TestFilterPrefix(t *testing.T) {
})
t.Run("uint16", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -527,8 +517,6 @@ func TestFilterPrefix(t *testing.T) {
})
t.Run("uint32", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -593,8 +581,6 @@ func TestFilterPrefix(t *testing.T) {
})
t.Run("uint64", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -658,8 +644,6 @@ func TestFilterPrefix(t *testing.T) {
})
t.Run("float64", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -771,8 +755,6 @@ func TestFilterPrefix(t *testing.T) {
})
t.Run("ipv4", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -869,8 +851,6 @@ func TestFilterPrefix(t *testing.T) {
})
t.Run("timestamp-iso8601", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "_msg",
@@ -952,4 +932,7 @@ func TestFilterPrefix(t *testing.T) {
}
testFilterMatchForColumns(t, columns, fp, "_msg", nil)
})
// Remove the remaining data files for the test
fs.MustRemoveAll(t.Name())
}

View File

@@ -2,14 +2,14 @@ package logstorage
import (
"testing"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
)
func TestFilterRange(t *testing.T) {
t.Parallel()
t.Run("const-column", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -81,8 +81,6 @@ func TestFilterRange(t *testing.T) {
})
t.Run("dict", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -152,8 +150,6 @@ func TestFilterRange(t *testing.T) {
})
t.Run("strings", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -218,8 +214,6 @@ func TestFilterRange(t *testing.T) {
})
t.Run("uint8", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -286,8 +280,6 @@ func TestFilterRange(t *testing.T) {
})
t.Run("uint16", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -353,8 +345,6 @@ func TestFilterRange(t *testing.T) {
})
t.Run("uint32", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -420,8 +410,6 @@ func TestFilterRange(t *testing.T) {
})
t.Run("uint64", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -494,8 +482,6 @@ func TestFilterRange(t *testing.T) {
})
t.Run("int64", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -568,8 +554,6 @@ func TestFilterRange(t *testing.T) {
})
t.Run("float64", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -642,8 +626,6 @@ func TestFilterRange(t *testing.T) {
})
t.Run("ipv4", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -673,8 +655,6 @@ func TestFilterRange(t *testing.T) {
})
t.Run("timestamp-iso8601", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "_msg",
@@ -700,4 +680,7 @@ func TestFilterRange(t *testing.T) {
}
testFilterMatchForColumns(t, columns, fr, "_msg", nil)
})
// Remove the remaining data files for the test
fs.MustRemoveAll(t.Name())
}

View File

@@ -4,6 +4,7 @@ import (
"fmt"
"testing"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/regexutil"
)
@@ -11,8 +12,6 @@ func TestFilterRegexp(t *testing.T) {
t.Parallel()
t.Run("const-column", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -58,8 +57,6 @@ func TestFilterRegexp(t *testing.T) {
})
t.Run("dict", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -98,8 +95,6 @@ func TestFilterRegexp(t *testing.T) {
})
t.Run("strings", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -134,8 +129,6 @@ func TestFilterRegexp(t *testing.T) {
})
t.Run("uint8", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -171,8 +164,6 @@ func TestFilterRegexp(t *testing.T) {
})
t.Run("uint16", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -208,8 +199,6 @@ func TestFilterRegexp(t *testing.T) {
})
t.Run("uint32", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -245,8 +234,6 @@ func TestFilterRegexp(t *testing.T) {
})
t.Run("uint64", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -282,8 +269,6 @@ func TestFilterRegexp(t *testing.T) {
})
t.Run("float64", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -319,8 +304,6 @@ func TestFilterRegexp(t *testing.T) {
})
t.Run("ipv4", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -357,8 +340,6 @@ func TestFilterRegexp(t *testing.T) {
})
t.Run("timestamp-iso8601", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "_msg",
@@ -390,6 +371,9 @@ func TestFilterRegexp(t *testing.T) {
}
testFilterMatchForColumns(t, columns, fr, "_msg", nil)
})
// Remove the remaining data files for the test
fs.MustRemoveAll(t.Name())
}
func TestSkipFirstLastToken(t *testing.T) {

Some files were not shown because too many files have changed in this diff Show More