Compare commits

..

173 Commits

Author SHA1 Message Date
Haley Wang
7d7d17d192 add changelog 2025-02-10 14:08:32 +08:00
Evgeny Kuzin
0a8b4281e5 fix race using the same list from 2 goroutines 2025-02-07 11:55:45 -05: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
Aliaksandr Valialkin
cf7ea78588 lib/logstorage: format pipe: add frequently used formatters
- url encoding / decoding with <urlencode:field> and <urldecode:field>
- base64 encoding / decoding with <base64encode:field> and <base64decode:field>
- hex encoding / decoding with <hexencode:field> and <hexdecode:field>
- hex encoding for integers with <hexnumencode:field> and <hexnumdecode:field>
2025-01-13 07:08:43 +01:00
Aliaksandr Valialkin
186aa3bb0e lib/logstorage: explicitly pass statsFunc to statsProcessor methods
This allows reducing the state of every statsProcessor by removing pointer to the corresponding statsFunc.
For example, this reduces statsCountProcessor size by 2x.
2025-01-13 04:49:39 +01:00
Aliaksandr Valialkin
e368f687a7 lib/logstorage: stats pipe: stop finalizeStats() as soon as the query is canceled
Previoysly finalizeStats() for some functions such as count_uniq() could run for long periods
of time after the query is canceled, since stopCh wan't propagated to finalizeStats().
2025-01-13 03:38:09 +01:00
Aliaksandr Valialkin
0214aa328e lib/logstorage: stats pipe: use integer group keys if stats by(...) contains a single field with integer values
This reduces memory usage and improves performance, since access to a map with integer keys
is faster than access to a map with string keys.
2025-01-13 03:22:00 +01:00
Aliaksandr Valialkin
dd919eeee6 lib/logstorage: count_uniq and count_uniq_hash stats functions: avoid converting integer values to strings
Prevsiously integer values were converted to strings before being passed to `updateState()` function at `count_uniq`
and `count_uniq_hash`. Later such values are converted back to integers in order to track them via integer map of unique values.

This commit avoids the int -> string -> int conversion. Instead, it passes integers directly to the integer map of unique values.
This improves performance of `count_uniq` and `count_uniq_hash` functions even further.
2025-01-13 02:45:28 +01:00
Aliaksandr Valialkin
3f22d06b0c lib/logstorage: add value_type filter to LogsQL
This filter can be used when debugging and exploring logs in order to understand better
which value types are used for storing the particular log fields.

The `value_type` filter complements `block_stats` pipe.
2025-01-12 22:21:39 +01:00
Aliaksandr Valialkin
b812de236b lib/logstorage: run make fmt after e610edf045 2025-01-12 03:17:57 +01:00
Aliaksandr Valialkin
40f56fa93b vendor: run make vendor-update
Add exclude google.golang.org/grpc/stats/opentelemetry v0.0.0-20240907200651-3ffb98b2c93a to go.mod
according to https://github.com/googleapis/google-cloud-go/issues/11283#issuecomment-2558515586 .

This fixes the following strange issue on `make vendor-update`:

cloud.google.com/go/storage imports
        google.golang.org/grpc/stats/opentelemetry: ambiguous import: found package google.golang.org/grpc/stats/opentelemetry in multiple modules:
        google.golang.org/grpc v1.69.0 (/go/pkg/mod/google.golang.org/grpc@v1.69.0/stats/opentelemetry)
        google.golang.org/grpc/stats/opentelemetry v0.0.0-20240907200651-3ffb98b2c93a (/go/pkg/mod/google.golang.org/grpc/stats/opentelemetry@v0.0.0-20240907200651-3ffb98b2c93a)
2025-01-12 03:14:52 +01:00
Aliaksandr Valialkin
e610edf045 lib/logstorage: improve performance for math pipe
- Pass the calculated results to the next pipe in float64 columns.
  Previously the results were converted to string columns. This could slow down further calculations.

- Use custom optimized logic for processing numeric columns, which are passed to math pipe.
  Previously all the input columns were converted to string and then converted to float64
  before math pipe calculations.

- Initialize the newly added columns at blockResult as soon as they are added.
  This improves performance when big number of columns are calculated by math pipe.
2025-01-12 03:01:47 +01:00
Aliaksandr Valialkin
764955b61c lib/logstorage: track integer values in integer maps when counting the number of unique values at count_uniq stats function
Previously integer values were tracked in string maps. Now every input value is parsed as integer.
On success the parsed integer is tracked via specialized maps, which hold only integers.
This reduces CPU usage and memory usage in general case.
2025-01-12 03:01:46 +01:00
Aliaksandr Valialkin
e3d31a371a lib/logstorage: avoid copying column name inside blockSearch.getColumnHeader() and blockSearch.getConstColumnValue()
Use the column name attached to the corresponding part. The lifetime of this column name exceed the blockSearch lifetime,
so it is safe using it here.

This is a follow-up for 8d968acd0a
2025-01-12 03:01:46 +01:00
Aliaksandr Valialkin
df723a4870 lib/logstorage: automatically detect columns with int64 values and store them as packed 8-byte int64 values
Previously columns with negative int64 values were stored either as float64 or string
depending on whether the negative int64 values are bigger or smaller than -2^53.
If the integer values are smaller than -2^53, then they are stored as string, since float64 cannot
hold such values without precision loss. Now such values are stored as int64.
This should improve compression ratio and query performance over columns with negative int64 values.
2025-01-12 03:01:46 +01:00
Aliaksandr Valialkin
bd00e3a735 lib/logstorage: make sure that the automatic conversion of field values to float64 is lossless
Previously field values could be automatically converted to float64 with precision loss.
This could lead to unexpected results when querying such field values.
For example, "10007199254740992" was incorrectly represented as 10007199254740993.
This commit prevents from such lossy conversions when storing field values.

While at it, prevent from int64 overflow at tryParseBytes and tryParseDuration functions,
which are used for parsing constants in queries for byte sizes and durations.
Now these functions return 1<<63-1 (the maximum int64 value) for constants exceeding
this value. Previously they could return arbitrary garbage for such constants.
2025-01-12 03:01:45 +01:00
Aliaksandr Valialkin
e794582f31 app/vlinsert/insertutils: avoid excess copying of lines at LineReader.buf
1. Do not copy every line from LineReader.buf to LineReader.Line - just refer the line at LineReader.buf.
2. Do not copy the next found line to the beginning of LineReader.buf - just track the next line start index with LineReader.bufOffset.

This reduces memory copying when many lines are read into LineReader.buf by a single read() syscall.
2025-01-12 03:01:45 +01:00
Roman Khavronenko
7cab4fd30d app/vmselect/promql: account for staleness when populating realPrevValue (#8002)
When vmselect process a rollup function it fetches all the raw samples
on requested `start-end` interval of the query. It then loops through
the raw samples, picks the range of the samples based on provided `step`
interval and invokes a rollup function for each of the picked ranges of
samples.

During this processing, vmselect always populates the `realPrevValue`
field with the closest previous raw sample value before the picked range
of samples. This `realPrevValue` is used by rollup functions like
increase_pure or delta to decide whether the counter change happened or
not. For example, we get the counter value == 1. If we've seen this
counter before and its value was also 1 - then no change happened. If we
didn't see it before, then this counter should have started with value=0
and we need to account for `1-0=1` change. All this is required to deal
with situations when scrapes are missing or `step` is too small.

However, vmselect doesn't check how "old" is the `realPrevValue`. In
other words, it doesn't respect the staleness interval when picking it.
In result, depending on the `start` and `end` params, vmselect can use
`realPrevValue` which is a couple of hours old and is unlikely to be a
temporary scrape fail. In result, some increases can be incorrectly
ingnored by vmselect.

This change makes sure that vmselect doesn't populate `realPrevValue`
with samples that are older than staleness interval.

### 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**:

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


-------------------

To reproduce, create a dataset with one metric `foo` which has samples
with value=1 on interval of couple of hours and resolution 15s, and a
gap for an hour in the middle:
<img width="769" alt="image"
src="https://github.com/user-attachments/assets/a39b2740-b741-45f8-ad18-093b7c57c3b3"
/>

Then run `increase(foo[1m])` expression on this time range (disable
cache):
<img width="1472" alt="image"
src="https://github.com/user-attachments/assets/463cece1-f359-4c75-a96c-60092a31cab2"
/>

In result, there will be one increase on the beginning of the series.
And no increase after the gap. Then change the time range so it starts
in the middle of the gap:
<img width="1505" alt="image"
src="https://github.com/user-attachments/assets/f4a460c3-9fd1-4ec7-ab47-15e716ec1019"
/>

Now, there is an increase>0 because the `realPrevValue` wasn't
populated. This is wrong, because it hides the increase of the series.

With the fix, the original increase query on full time range should show
2 increases:
<img width="1492" alt="image"
src="https://github.com/user-attachments/assets/aa9d8a6b-7b22-41f6-9eb9-83b3113a6982"
/>

Signed-off-by: hagen1778 <roman@victoriametrics.com>
2025-01-10 16:45:44 +01:00
Zakhar Bessarab
3333135bc0 docs/CHANGELOG.md: cut v1.109.0
Signed-off-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>
2025-01-10 18:53:33 +04:00
Zakhar Bessarab
1db1841b20 app/{vmselect,vlselect}: run make vmui-update vmui-logs-update
Signed-off-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>
2025-01-10 18:53:33 +04:00
Aliaksandr Valialkin
f7ce191482 docs/VictoriaLogs/README.md: add "Profiling" chapter
This chapter is needed for referring from Github issues when CPU or memory profiles are needed to be collected
in order to investigate issues with high CPU and/or RAM usage at VictoriaLogs.
2025-01-10 14:22:08 +01:00
Aliaksandr Valialkin
96ea222780 LICENSE: update the current year from 2024 to 2025 2025-01-10 14:19:11 +01:00
Zakhar Bessarab
03c0d9a672 app/vmselect/promql: set tenant information for numbers
Since
44b071296d
`evalNumber` function no longer updating MetricName tenancy information.
This leads to mismatch in metric names between the query result and
evaluated number for all tenants other than 0:0.

For example, query `count(up) or 0` will return different results for
tenants 0:0 and 1:1 (assuming up is present for both tenants):
- tenant 0:0 - will only contain result of `count(up)`
- tenant 1:1 - will return both `count(up)` and `0` since metric names
will not be matched

This restores setting of tenancy information for metric name for
single-tenant queries.

Related issue:

 https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7987
---
Signed-off-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>
2025-01-10 13:04:38 +01:00
Nikolay
e9f86af7f5 lib/storage: add a hint for merge about type of parts in merge (#7998)
Hint allows to choose type of cache to be used for index search:
- in-memory parts are storing recently ingested samples and should use
main cache. This improves ingestion speed and cache hit ration for
queries accessing recently ingested samples.
- merges of file parts is performed in background, using a separate
cache allows avoiding pollution of the main cache with irrelevant
entries.

Related issue:
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7182

---------

Signed-off-by: f41gh7 <nik@victoriametrics.com>
2025-01-10 16:01:39 +04:00
Nikolay
9ada784983 lib/storage: make finalDedup schedule interval configurable
This commit makes configurable interval for checking if final dedup
process for the historical data should be started. It allows to spread
resource utilisation for multiple vmstorage/vmsingle instances in time.
Since final dedup may add additional preasure on disk, backup systems
and make cluster less stable. Storage unconditionally adds 25% jitter to
the provided value, it should simplify configuration management at
Kubernetes ecosystem. Because Kubernetes application pods must have the
same configuration.

Related issue:
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7880


---------

Signed-off-by: f41gh7 <nik@victoriametrics.com>
Co-authored-by: Roman Khavronenko <roman@victoriametrics.com>
2025-01-10 10:46:46 +01:00
f41gh7
a83ee2b3f1 github/workflows: set GOGC=10 for unit tests
It reduces memory usage during tests execution. It makes tests execution more reliable.
Since it sometimes crashes with OOM at small github runners.

Signed-off-by: f41gh7 <nik@victoriametrics.com>
2025-01-10 10:45:41 +01:00
Github Actions
2564f10d98 Automatic update helm docs from VictoriaMetrics/helm-charts@117506e (#7996)
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-10 05:16:54 +01:00
Andrii Chubatiuk
0871770634 victorialogs: ugraded datadog extension version in compose to one which supports custom endpoint configuration (#7989)
recently new datadog extension was released, where custom endpoint
configuration was added

### 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/).
2025-01-09 10:44:36 +04:00
Zakhar Bessarab
51b21dfd57 app/vmalert/notifier: fix rendering of Alertmanager notification body
commitL  c7fc0d0d2f  enabled skipping alerts
in case there is no labels present for an alert. This made clause which
was adding a comma for the JSON list incorrect as it is not possible to
determine if the next alert will be skipped or not.

This fix renders all alert labels in advance allowing properly format
JSON payload for Alertmanager notification.

Related issue:
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7985


Signed-off-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>
2025-01-08 19:02:20 +01:00
Zhu Jiekun
276989716f lib/promscrape: add Marathon service discovery
This commit adds support for [Marathon](https://mesosphere.github.io/marathon/)
service discovery to the scrape configuration. 

The following flag is introduced:
```
  -promscrape.marathonSDCheckInterval duration
          Interval for checking for changes in Marathon service discovery. This works only if marathon_sd_configs is configured in '-promscrape.config' file. See https://docs.victoriametrics.com/sd_configs.html#marathon_sd_configs for details  (default 30s)
```

The service discovery could be config like:
```yaml
scrape_configs:          
- job_name: marathon_job 
  marathon_sd_configs:   
      servers:
        - "..."
        - "..."
```
See:
[b555d94d1a/docs/sd_configs.md (marathon_sd_configs))

related issue:
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6642


---------

Co-authored-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>
2025-01-08 18:57:22 +01:00
Artem Fetishev
6cb3c0cac8 Update VictoriaLogs FAQ: add a section about max log record length (#7984)
### Describe Your Changes

There has been a question in our public Slack on whether the length
limit of a log record is going to be changed. See:
https://victoriametrics.slack.com/archives/C05UNTPAEDN/p1736156255119689

This PR documents the max length and explains why it has been chosen.
This FAQ section could serve as an answer to more questions like this.

### Checklist

The following checks are **mandatory**:

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

---------

Signed-off-by: Artem Fetishev <rtm@victoriametrics.com>
Co-authored-by: Aliaksandr Valialkin <valyala@victoriametrics.com>
2025-01-08 15:33:38 +01:00
cuiweiyuan
d064e14933 chore: fix function name in comment (#7926)
### Describe Your Changes

 fix function name in comment

### Checklist

The following checks are **mandatory**:

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

Signed-off-by: cuiweiyuan <cuiweiyuan@aliyun.com>
2025-01-08 13:58:22 +01:00
Afolabi Badmos
77b0fcfdd9 vmauth: fix bug in discovering ipv6 addresses (#7955)
### Describe Your Changes

Fixes error in `vmauth` when discovering ipv6 addresses.

`vmauth` attempts to [slice till
`:`](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/app/vmauth/auth_config.go#L397)
in the discovered addresses without accounting for ipv6. This causes it
to fail in ipv6 only environments.

```sh
$ nslookup vmselect.ns.svc.cluster.local

...
Name: vmselect.ns.svc.cluster.local
Address: 2600:dead:beef:dead:beef::8
```

```sh
$ kubectl logs -f vmauth

...
error: dial tcp: lookup 2600: no such host
```


### Checklist

The following checks are **mandatory**:

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

---------

Co-authored-by: f41gh7 <nik@victoriametrics.com>
2025-01-08 16:51:13 +04:00
Yury Molodov
ee7fe11fd2 vmui/logs: add autocomplete support for LogsQL (#6949)
### Describe Your Changes

This pull request adds support for autocomplete in LogsQL queries. The
new feature provides suggestions for field names, field values, and pipe
names as you type.


---------

Co-authored-by: Aliaksandr Valialkin <valyala@victoriametrics.com>
2025-01-08 11:36:37 +01:00
Andrii Chubatiuk
4c26fb6fe5 docs: make badges in docs clickable (#7960)
### Describe Your Changes

added links to badges and made them clickable at
docs.victoriametrics.com

### Checklist

The following checks are **mandatory**:

- [ ] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).
2025-01-08 08:58:43 +01:00
Github Actions
fc135094b3 Automatic update operator docs from VictoriaMetrics/operator@028ab8b (#7981)
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-08 07:45:54 +01:00
Roman Khavronenko
5d42f21abd docs: mention publicly available playgrounds (#7977)
The point of the new section is to highlight publicly available
playgrounds for users. All of them were mentioned in other parts of the
documentation, but we didn't have all of them in one place before.

### 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-07 22:16:50 +01:00
hagen1778
28eeabded1 docs: rm extra new lines as they bring no value
Signed-off-by: hagen1778 <roman@victoriametrics.com>
2025-01-07 13:52:35 +01:00
hagen1778
b6910cfff7 docs: make vmui related pages below the vmui parent page
This change only updates the hierarchy of pages within the readme.

Signed-off-by: hagen1778 <roman@victoriametrics.com>
2025-01-07 13:48:39 +01:00
Zhu Jiekun
8938ef398c docs: clarify extra resource is needed for downsampling/retention filter (#7974)
### Describe Your Changes

clarify extra resource is needed when downsampling with filter(s) or
retention filter(s) is applied

### Checklist

The following checks are **mandatory**:

- [x] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).
2025-01-07 12:30:19 +01:00
hagen1778
df2b75fa81 docs: fix markdown typos
Signed-off-by: hagen1778 <roman@victoriametrics.com>
2025-01-07 09:18:25 +01:00
hagen1778
857734c66c docs: fix markdown typo
Signed-off-by: hagen1778 <roman@victoriametrics.com>
2025-01-07 09:06:45 +01:00
Github Actions
bedc0c0f8f Automatic update helm docs from VictoriaMetrics/helm-charts@7004727 (#7958)
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-03 19:06:18 +04:00
YuDong Tang
5a41bdf329 app/select: add command-line flag -search.maxBinaryOpPushdownLabelValues
### Describe Your Changes

Binary operations like `exprFirst op exprSecond` in VictoriaMetrics are
performed in the following way:
1. Execute exprFirst.
2. Extract **common label filters** from the result of step 1.
3. Apply these common label filters to `exprSecond` and execute it, in
order to retrieve less time series from vmstorage nodes.

In step 2, only labels with less than `100` (hard-coded) value could be
used as **common label filter** (e.g. `{common_lb=~"v1|v2|...|v100"}`.

In our scenarios, a label, take `instance` label as an example, could
has thousands of candidate values. Regarding bring more pressure to
vmstorage node, it's still beneficial if labels with more than 100
values could be used as filter in `exprSecond`, with enough vmstorage
resources. After adjusting the value from `100` to `10000`, our query
round-trip time drops significantly from 5s to 2s.

This pull request change the hard-coded value into a configurable flag.
2025-01-03 13:20:50 +01:00
Github Actions
bf5d0dd245 Automatic update Grafana datasource docs from VictoriaMetrics/victorialogs-datasource@7f94969 (#7911) 2025-01-03 14:58:46 +04:00
Github Actions
1cec37b0f5 Automatic update operator docs from VictoriaMetrics/operator@5992757 (#7954)
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-02 18:46:11 +01:00
f41gh7
c40c25b03c docs/changelog: properly mention vminsert changes
storageNode sorting should be BUGFIX, since previously vminsert performed sort and this behaviour was changed.
Also this change only affects OSS version
2025-01-02 17:53:57 +01:00
kiriklo
82badc3dd5 app/vmselect/promql: improve performance of parseCache on systems with many CPU cores
### Describe Your Changes

Parse cache is a pretty simple implementation of cache. It's just a
standard map with mutex.
Map with mutex overall has poor performance, plus when the cache
overflow occurs, the whole cache locks until 1k elements have been
deleted (now it's 10% of 10000 max elements in the cache). To avoid this
bottleneck and improve performance of cache on systems with many CPU
cores but keep it rather simple, we can implement cache with per bucket
locks like it's done in fastcache. The logic and API remain the same. So
now each bucket will have a map with approximately 78 elements (with 128
buckets), and overflow will occur now for each bucket, and only 7
elements need to be deleted.
Because exec_test.go has about 10k lines of code, it's better to move
the cache into a separate file to add tests and benchmarks for it,
because now it does not have them.

```
goos: windows
goarch: amd64
pkg: github.com/VictoriaMetrics/VictoriaMetrics/app/vmselect/promql
cpu: 11th Gen Intel(R) Core(TM) i9-11900K @ 3.50GHz

Current cache implementation performance on 8 cores:
BenchmarkCachePutNoOverFlow-8               1932            618372 ns/op             253 B/op          0 allocs/op
BenchmarkCacheGetNoOverflow-8               6547            211527 ns/op               0 B/op          0 allocs/op
BenchmarkCachePutGetNoOverflow-8            1873            621718 ns/op             261 B/op          0 allocs/op
BenchmarkCachePutOverflow-8                 2262            464328 ns/op              32 B/op          0 allocs/op
BenchmarkCachePutGetOverflow-8              1764            655866 ns/op              38 B/op          0 allocs/op

New cache implementation performance on 8 cores:
BenchmarkCachePutNoOverFlow-8              10408            111412 ns/op               0 B/op          0 allocs/op
BenchmarkCacheGetNoOverflow-8              22407             52809 ns/op               0 B/op          0 allocs/op
BenchmarkCachePutGetNoOverflow-8            6583            168088 ns/op               0 B/op          0 allocs/op
BenchmarkCachePutOverflow-8                 9822            117212 ns/op               2 B/op          0 allocs/op
BenchmarkCachePutGetOverflow-8              6481            175952 ns/op               3 B/op          0 allocs/op

Current cache implementation performance on 16 cores:
BenchmarkCachePutNoOverFlow-16              2331            475307 ns/op             218 B/op          0 allocs/op
BenchmarkCacheGetNoOverflow-16              6069            196905 ns/op               0 B/op          0 allocs/op
BenchmarkCachePutGetNoOverflow-16           1870            644236 ns/op             262 B/op          0 allocs/op
BenchmarkCachePutOverflow-16                2296            509279 ns/op              34 B/op          0 allocs/op
BenchmarkCachePutGetOverflow-16             1726            671510 ns/op              45 B/op          0 allocs/op

New cache implementation performance on 16 cores:
BenchmarkCachePutNoOverFlow-16             13549             82413 ns/op               0 B/op          0 allocs/op
BenchmarkCacheGetNoOverflow-16             30274             38997 ns/op               0 B/op          0 allocs/op
BenchmarkCachePutGetNoOverflow-16           8512            126239 ns/op               0 B/op          0 allocs/op
BenchmarkCachePutOverflow-16               13884             88124 ns/op               1 B/op          0 allocs/op
BenchmarkCachePutGetOverflow-16             7903            131299 ns/op               3 B/op          0 allocs/op
```
From the benchmarks above, we can see that the new implementation is ~5
times faster than the old one.


---------
Co-authored-by: f41gh7 <nik@victoriametrics.com>
2025-01-02 17:43:23 +01:00
Alex Gustafsson
43ded688f7 Add open containers source label to Dockerfiles (#7893)
### Describe Your Changes

In order for third-party tooling to identify the source repository of
VictoriaMetrics, add the org.opencontainers.image label to the
Dockerfiles. This enables a whole suite of tools that scan container
images to further correlate data with the source code.

The lack of these annotations can be identified using docker:

```shell
docker pull victoriametrics/victoria-metrics
docker inspect victoriametrics/victoria-metrics
```

```jsonc
// ...
"Labels": null
// ...
```

If we try an image that has the annotations, we'll see more output.

```shell
docker pull traefik
docker image inspect traefik
```

```jsonc
// ...
"Labels": {
    "org.opencontainers.image.description": "A modern reverse-proxy",
    "org.opencontainers.image.documentation": "https://docs.traefik.io",
    "org.opencontainers.image.source": "https://github.com/traefik/traefik",
    "org.opencontainers.image.title": "Traefik",
    "org.opencontainers.image.url": "https://traefik.io",
    "org.opencontainers.image.vendor": "Traefik Labs",
    "org.opencontainers.image.version": "v3.2.3"
}
// ...
```

### Checklist

The following checks are **mandatory**:

- [x] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).
2025-01-02 20:43:17 +04:00
Hui Wang
661420fe85 dashboard/vmagent: change metric for Persistent Queue panel
consistently use `vmagent_remotewrite_pending_data_bytes`  on vmagent dashboard to represent persistent queue size.

`vmagent_remotewrite_pending_data_bytes =
vm_persistentqueue_bytes_pending + pendingInmemoryBytes`
According to panel description, `vmagent_remotewrite_pending_data_bytes`
is more accurate.
>Persistent queue size shows size of pending samples in bytes which
hasn't been flushed to remote storage yet.
 
And we already use `vmagent_remotewrite_pending_data_bytes` in other two
panels.

44d2205136/dashboards/vmagent.json (L7132)
2025-01-02 13:04:40 +01:00
Andrii Chubatiuk
7aab967447 Makefile: cspell makefile refactor
- removed absolute paths to run without docker
- set cspell to default entrypoint value
- set cspell config path instead of cspell.json copying and removal
2025-01-02 12:52:24 +01:00
Hui Wang
afb07034ed app/vmalert: fix the auto-generated metrics ALERTS and ALERTS_FOR_STATE
Previously, since labels slice is reused for both `ALERTS` and
`ALERTS_FOR_STATE`, metrics might have incorrect labels and affect the
restore process. Tested the fix under `TestAlertingRule_Exec:
"for-pending=>empty"`.

The bug is introduced in
282f13cf11.
Affected versions: v1.106.1, v1.107...v1.108.x

related issue:
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7796
2025-01-02 12:51:05 +01:00
f41gh7
44d2205136 app/vmalert: properly format datasource URL for enterprise group.tenant
At Enterprise version of the vmalert, `group` supports `tenant` field.

`tenant` field value must be added to the `datasource` as a part of the URL path prefix.

But VictoriaLogs can obtain tenant information only from `headers` and defined `tenant` breaks requests to the `VictoriaLogs` datasource.

 This commit properly checks `datasourceType` and skips adding path prefix if `datasourceType` is `vlogs`.

---------
Co-authored-by: Nikolay <nik@victoriametrics.com>
2024-12-30 15:42:29 +01:00
f41gh7
b226318f9e app/vmstorage: allow to override the default unique time series limit
previously vmstorage ignored limit values from vmselect component.

This behavior is prohibited starting from v1.105.0, with
85f60237e2.

This breaks the original intent of the -search.maxUniqueTimeseries command-line flag, which has been added at vmselect nodes in the commit b843f0e : to be able to override the default limit at vmstorage on the number of unique time series, at different subsets of vmselect nodes.

The behavior should be the following:

*    If -search.maxUniqueTimeseries command-line flag isn't set at both vmselect and vmstorage nodes, then the limit on  the number of unique time series must be automatically detected at vmstorage nodes according to

* vmstorage: automatically adjust -search.maxUniqueTimeseries max value   . This simplifies configuration of VictoriaMetrics cluster for the typical case.

* If -search.maxUniqueTimeseries command-line flag is explicitly set at vmstorage node, then it must be used as the limit on the number of unique time series, without automatic detection of the limit. Explicitly set limit at vmstorage node cannot be exceeded by the limit from vmselect nodes.
* If the -search.maxUniqueTimeseries command-line flag is explicitly set at vmselect node, then it must override the automatically detected limit at vmstorage node. For example, if vmselect node provides the limit, which exceeds the automatically detected limit at vmstorage node, then the limit from the vmselect node must be applied during query execution at vmstorage node. This will allow properly executing queries from the subset of vmselect nodes for reporting queries described above.

related issue:
 https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7852
2024-12-30 15:20:52 +01:00
Github Actions
30999204c9 Automatic update operator docs from VictoriaMetrics/operator@5e0854a (#7938)
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>
2024-12-30 14:06:27 +01:00
Zhu Jiekun
ffddfa1f94 app/vmctl: properly handle influx series without tags
### Describe Your Changes

Previously, vmctl expect that tag must exist for each measurement, but
it's actually not necessary.


f16a58f14c/app/vmctl/influx/influx.go (L183-L186)

This pull request fix it by removing the check. For influx series
`measurement1_value1{}`, it will be represented as:
```go
Series{
  Measurement: "measurement1",
  Field:       "value1",
  LabelPairs:  []LabelPair{},
  EmptyTags:   []string{},
}
```
and searched by the following query:
```sql
select "value1" from "measurement1"
``` 

 Related issue:
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7921
2024-12-26 20:39:06 +01:00
f41gh7
fc336bbf20 app/vminsert: properly ingest influx metrics
Commit 71bb9fc0d0 introduced a regression.
If labels are empty and relabeling is not configured, influx ingestion hanlder
performed an earlier exit due to TryPrepareLabels call.
 Due micro-optimisations for this procotol, this check was not valid.
Since it didn't take in account metircName, which added later and skip metrics line.

 This commit removes `TryPrepareLabel` function call from this path and inline it instead.
It properly track empty labels path.

 Adds initial tests implementation for data ingestion protocols.

 Related issue:
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7933

Signed-off-by: f41gh7 <nik@victoriametrics.com>
2024-12-26 12:14:42 +01:00
f41gh7
e0b2c1c4f5 docs/changelog: removes duplicate record for maxIngestionRate feature
Signed-off-by: f41gh7 <nik@victoriametrics.com>
2024-12-24 19:40:44 +01:00
Github Actions
5afbee5f6f Automatic update operator docs from VictoriaMetrics/operator@43f5554 (#7923)
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>
2024-12-24 19:31:27 +01:00
Github Actions
51459196f9 Automatic update helm docs from VictoriaMetrics/helm-charts@1f33c21 (#7929)
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>
2024-12-24 19:30:43 +01:00
Dima Shur
7941877233 docs: changed typo in label (enterpriSe instead of enterpriZe) (#7925)
### Describe Your Changes

Fixed typo in contributing.md (enterpriZe -> enterpriSe in the label
name)

### Checklist

The following checks are **mandatory**:

- [x] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).
2024-12-24 15:51:35 +04:00
Phuong Le
f303081304 vminsert: sort the storage nodes during initialization (#7899)
Fixes #7898
2024-12-23 19:41:17 +01:00
Ted Possible
a84628f701 app/vminsert: support for rate limiting number of samples/sec with -maxIngestionRate
This commit adds feature to limit sample ingestion rate globally for ingestion protocols. 

Related issue:
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7377
2024-12-23 17:37:30 +01:00
Github Actions
f823a225ac Automatic update operator docs from VictoriaMetrics/operator@471f183 (#7916)
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>
2024-12-23 16:48:49 +01:00
Andrii Chubatiuk
79f1a37ee6 vlinsert: take into account order of msgfields to have predictable _msg field selection in case of multiple matches (#7784)
### Describe Your Changes

Currently if multiple msgFields are present in a log row it's not
obvious which field is selected as a _msg field. With this PR and order
of msgfield values defined either via headers or query arg params
defines a priority of these values

### Checklist

The following checks are **mandatory**:

- [ ] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).
2024-12-23 10:10:02 +01:00
Andrii Chubatiuk
f9cd408ca9 datadog-serverless: fixed metrics and logs ingestion from Datadog serverless extensions for AWS and GCP (#7769)
fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7761

### Describe Your Changes

- datadog /api/v2/logs api supports message field in json format, which
is not documented and is used by serverless extension. This PR allows
message field to be both string and object type. Also added support of
not documented timestamp field
- added `-datadog.streamFields` and `-datadog.ignoreFields` flags to
configure default stream fields for datadog logs, where there's no
alternative option to pass extra headers and query args
- added ingest `max` and `min` values of data, which are ingested using
`datadogsketches` API, which is also actively used by serverless
extensions
- use default `.` separator instead of `_` for sketches metric names
until metrics are not sanitized
2024-12-23 09:57:48 +01:00
Aliaksandr Valialkin
c2811d8d11 docs/VictoriaLogs/LogsQL.md: fix a link to count_uniq_hash stats function docs
It must be consistent with the other stats functions

This is a follow-up for de0ae735aa
2024-12-22 14:39:27 +01:00
Aliaksandr Valialkin
8d981b15c9 deployment: update VictoriaLogs Docker image from v1.3.2-victorialogs to v1.4.0-victorialogs
See https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.4.0-victorialogs
2024-12-22 14:36:49 +01:00
Aliaksandr Valialkin
58f09fe3f8 docs/VictoriaLogs/CHANGELOG.md: cut v1.4.0-victorialogs release 2024-12-22 14:31:31 +01:00
Aliaksandr Valialkin
afd926a0b0 lib/logstorage: limit the maximum number of logs and/or log streams, which can be passed to stream_context pipe
This should prevent from excess usage of CPU, RAM and other resources when too many logs
are passed to 'stream_context' pipe.

It is expected that 'stream_context' pipe results are investigated by humans, who cannot inspect
surrounding logs for millions of initial logs. That's why it is OK to limit the number of logs
and/or log streams, which can be passed to 'stream_context' pipe.

Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7766
Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7903
2024-12-22 14:28:50 +01:00
Aliaksandr Valialkin
204c102342 app/vlselect/vmui: run make vmui-logs-update after the commit 1fbc2c0db1
Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7288
2024-12-22 13:53:45 +01:00
Aliaksandr Valialkin
c5949af9e8 lib/logstorage: reduce memory allocations when splitting in(...) values into tokens and calculating hashes for these tokens
While at it, reduce memory allocations at Storage.getFieldValuesNoHits and make it more scalable on multi-CPU systems.

This improves performance of in(<query>) filter when the <query> returns big number of values.
2024-12-22 13:13:44 +01:00
Aliaksandr Valialkin
5dc0413bc0 lib/logstorage: allow specifying hits column name in the top pipe via top ... hits as <column_name> syntax 2024-12-22 11:23:19 +01:00
Aliaksandr Valialkin
f919783de9 lib/logstorage: uncommend accidentally commented tests at 60f9f44150 2024-12-22 02:20:57 +01:00
Aliaksandr Valialkin
60f9f44150 lib/logstorage: reduce memory allocations at stats and top pipes
Use chunked allocator in order to reduce memory allocations. It allocates objects from slices of up to 64Kb size.
This improves performance for `stats` and `top` pipes by up to 2x when they are applied to big number of `by (...)` groups.

Also parallelize execution of `count_uniq`, `count_uniq_hash` and `uniq_values` stats functions,
so they are executed faster on hosts with many CPU cores when applied to fields with big number
of unique values.
2024-12-22 02:13:02 +01:00
Github Actions
0fcbe8fdae Automatic update Grafana datasource docs from VictoriaMetrics/victoriametrics-datasource@b18583c (#7910) 2024-12-21 09:42:48 -08:00
Github Actions
458b602938 Automatic update Grafana datasource docs from VictoriaMetrics/victoriametrics-datasource@cbff3fa (#7909) 2024-12-21 09:31:07 -08:00
Aliaksandr Valialkin
471f1d0a09 lib/logstorage: fixed a typo in blockResult.reset()
The commit 4599429f51 improperly set br.cs to nil,
while it should set br.bs to nil instead. This resulted in excess memory allocations
at br.csInit() and br.csInitFast().
2024-12-21 13:39:25 +01:00
hagen1778
7f80c1633f docs: mention filebeat version requirement for vlogs integration
Signed-off-by: hagen1778 <roman@victoriametrics.com>
2024-12-20 16:21:13 +01:00
Yury Molodov
186b00df6b vmui: add export button for raw query data (#7828)
### Describe Your Changes

1. Added the ability to export data from the `Raw Query` page and import
exported data to the `Query Analyzer` page (related issue #7628).
2. Added a `Title` input field; the `Title` is displayed when importing
data on the `Query Analyzer` page.
3. Implemented `Markdown` support for comments in exported data.  
4. Updated the styling of the `Query Analyzer` page.  
5. Fixed an issue where the `Upload JSON` button on the `Query Analyzer`
page was only clickable on the button text (now clickable on the entire
button area).
6. Added a tooltip with `Deduplication` information on the `Raw Query`
page (related to issue #7763).

<details>
  <summary>Screenshots</summary>
  
#### Data export and `Markdown` preview

<img width="400"
src="https://github.com/user-attachments/assets/bbab31bb-81d3-4335-98c3-d01c8786bde4"/>
<img width="400"
src="https://github.com/user-attachments/assets/3cfd9938-b518-45d6-8ded-e3e7e6ab9299"/>

#### `Query Analyzer` page displaying data from `Raw Query`

<img width="900"
src="https://github.com/user-attachments/assets/008e0e93-92f2-4c25-a20e-3cee90a03397"/>

#### Viewing stats and comments on the `Query Analyzer` page  
    
<img width="600"
src="https://github.com/user-attachments/assets/18bfbba1-a11c-420e-84f2-78229ac7bd25"/>

#### Viewing stats data from the `Query` page

<img width="900"
src="https://github.com/user-attachments/assets/0f7a3009-9fb5-4727-b0c4-257aa196a9c1"/>

#### Tooltip on the `Raw Query` page  

<img width="900"
src="https://github.com/user-attachments/assets/400f86e7-f362-4307-8b1d-24af3c67020e"/>
  
</details>

---------

Signed-off-by: hagen1778 <roman@victoriametrics.com>
Co-authored-by: hagen1778 <roman@victoriametrics.com>
2024-12-20 15:51:06 +01:00
Github Actions
4205ae3011 Automatic update helm docs from VictoriaMetrics/helm-charts@feb0675 (#7897)
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>
2024-12-20 15:27:51 +01:00
Daria Karavaieva
491028774a docs/vmanomaly: popup deprecated_from and available_from for all docs (#7905)
### Describe Your Changes
added deprecated form and available from popups in vmanomaly docs

### Checklist

The following checks are **mandatory**:

- [x] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).
2024-12-20 15:24:16 +01:00
Mathias Palmersheim
565b79c9ca docs: update vmalert+victorialogs doc with multitenant recording (#7779)
### Describe Your Changes
 
- Adds Headers to FAQ questions in vmalert for Victorialogs
- Adds FAQ for multitenant recording rules described in #7656

### Checklist

The following checks are **mandatory**:

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

---------

Co-authored-by: Haley Wang <haley@victoriametrics.com>
2024-12-20 15:02:00 +01:00
Aliaksandr Valialkin
5478cc61c2 lib/cgroup: add missing initialization of gogc variable inside SetGOGC
This is a follow-up for 79c08ecac4

Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7902
2024-12-20 14:56:59 +01:00
Aliaksandr Valialkin
79c08ecac4 lib/cgroup: use the default GOGC=100 for the most of VictoriaMetrics components
Historically some of VictoriaMetrics components were optimized for the low rate of memory allocations.
These are: vmagent, single-node VictoriaMetrics and vmstorage. These components benefit from the low
GOGC value, since this allow reducing their memory usage in steady state on typical workloads.

Other VictoriaMetrics components aren't optimized for the reduced rate of memory allocations.
This results in the increased CPU usage spent on garbage collection (GC) in these components,
since it must be triggered at higher rate. See https://tip.golang.org/doc/gc-guide#GOGC for details.

These components do not use too much memory, so it is OK increasing the GOGC for these components
from 30 to 100 - this won't affect the most users.

Keep GOGC to 30 only for vmagent, single-node VictoriaMetrics and vmstorage components.
See 077193d87c and 54b9e1d3cb .

Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7902
2024-12-20 14:48:28 +01:00
hagen1778
f47fd83e54 docs: add example with dots in label name to vlogs rules
This change adds an example of how to use labels with `.` dots
in rule annotations.

Signed-off-by: hagen1778 <roman@victoriametrics.com>
2024-12-20 14:07:48 +01:00
Aliaksandr Valialkin
9c39bac565 lib/logstorage: fix imroper sorting of numeric fields when they are stored as const values at sort pipe
Numeric fields can be stored as const values in the block of logs. In this case the `sort` pipe
was incorrectly comparing such values as strings instead of numbers. This results in incorrect
sort results. For example, 123 was smaller than 2. Fix this by removing the incorrect case
for comparing const fields.

While at it, replace lessString() with strings.LessNatural() in the sortBlockLess.
This improves sorting performance a bit, since the sortBlockLess function already tried
comparing numeric values, and it doesn't need to spend CPU time on such a comparison again inside lessString() call.
The commit 42c9183281 wasn't correct by replacing strings.LessNatural() with lessString()
inside the sortBlockLess() function.
2024-12-20 13:26:20 +01:00
Roman Khavronenko
1042f07498 docs: update OTEL guide (#7887)
* simplify wording
* update styles
* remove extra info about go application details. The details are likely
not needed and we didn't have details for rolling-dice app anyway. So
keep it simple for consstency and brevity.
* update navigation for simplicity sake
* fix typos

follow-up after
40b47601d1

Signed-off-by: hagen1778 <roman@victoriametrics.com>
2024-12-19 15:13:03 +01:00
Nikolay
79a595c6d0 app/vmauth: properly log host at debugInfo function (#7886)
vmauth started to use request.Host after commit
f4776fec1b for`src_hosts` routing rules.

This commit adds http.Request.Host to the debugInfo output in order to
be consistent with routing logic.

### 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: f41gh7 <nik@victoriametrics.com>
2024-12-19 15:04:37 +01:00
Andrii Chubatiuk
40b47601d1 docs/guides/otel: added logs integration, updated old otel dependencies
### Describe Your Changes

- added VictoriaLogs to OpenTelemetry guide
- updated deprecated dependencies
- added deltatocumulative processor to example and deltatemporality
selector to one of examples to use for counters by default
- added exponential histograms to example

---
Signed-off-by: Andrii Chubatiuk <andrew.chubatiuk@gmail.com>
2024-12-19 12:32:41 +01:00
Zakhar Bessarab
6bfcbe66f7 docs/release-guide: add a note about versioning in helm charts and ansible
### 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>
2024-12-19 12:28:32 +01:00
f41gh7
94118c63f6 docs: update VM apps version to v1.108.1
Signed-off-by: f41gh7 <nik@victoriametrics.com>
2024-12-19 12:25:37 +01:00
f41gh7
9605d73809 CHANGELOG.md: cut v1.108.1 release 2024-12-18 23:34:58 +01:00
f41gh7
3237c64ef3 make vmui-update 2024-12-18 23:08:22 +01:00
Yury Molodov
1fbc2c0db1 vmui: fix cursor reset in query input
Fix cursor reset in query input field. 

Related issue:
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7288.
2024-12-18 22:30:08 +01:00
Nikolay
71bb9fc0d0 app/vminsert: properly apply relabeling at ingestion
Regression was introduced at 564e6ea024
after implementing:
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6928

ctx.Labels array could be incorrectly updated and changes to it after
relabeling rules can be lost.
E.g. ctx.Labels passed to WriteDataPoint function as slice copy, but
results of relabeling only changed an actual slice at ctx.Labels.

This commit replaces implicit relabeling call with explicit
`TryPrepareLabels` function.
It also reduces code diffs with cluster version and adds integration tests

 related issue:
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7865

---------

Signed-off-by: f41gh7 <nik@victoriametrics.com>
Co-authored-by: Roman Khavronenko <roman@victoriametrics.com>
2024-12-18 22:27:51 +01:00
Github Actions
0210f4ebd2 Automatic update operator docs from VictoriaMetrics/operator@9b337c1 (#7879)
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>
2024-12-18 16:27:37 +01:00
Andrii Chubatiuk
891ad8f202 app/vlinsert: loki healthcheck endpoint (#7864)
### Describe Your Changes

fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7824

### Checklist

The following checks are **mandatory**:

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

---------

Co-authored-by: Roman Khavronenko <roman@victoriametrics.com>
2024-12-18 14:59:44 +01:00
Github Actions
e501640f44 Automatic update Grafana datasource docs from VictoriaMetrics/victoriametrics-datasource@d830b2a (#7855) 2024-12-18 12:27:15 +01:00
Daria Karavaieva
21082405ec docs/vmanomaly: add version popup (#7860)
### Describe Your Changes

Added `available_from` popup into documentation of vmanomaly

### Checklist

The following checks are **mandatory**:

- [x] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).
2024-12-18 12:26:42 +01:00
Github Actions
094a5ab58f Automatic update Grafana datasource docs from VictoriaMetrics/victorialogs-datasource@b8fd925 (#7862) 2024-12-18 12:26:11 +01:00
hagen1778
bbc84fa119 docs: add requirements to commit message to contributing
Signed-off-by: hagen1778 <roman@victoriametrics.com>
2024-12-18 12:22:20 +01:00
Github Actions
9d1a72aca8 Automatic update operator docs from VictoriaMetrics/operator@5c3657d (#7871)
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>
2024-12-18 12:13:58 +01:00
Dmytro Kozlov
05d3db248b deployment/docker: rename victorialogs-datasource to victoriametrics-logs-datasource (#7874)
### Describe Your Changes

Renamed victorialogs-datasource to victoriametrics-logs-datasource.

We prepared the victorialogs Grafana plugin for sign and updated the
plugin ID. This action require to update configs in our ops repository

Please check this
[release](https://github.com/VictoriaMetrics/victorialogs-datasource/releases/tag/v0.13.0)
and https://github.com/VictoriaMetrics/victorialogs-datasource/pull/161
with changes

### Checklist

The following checks are **mandatory**:

- [X] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).
2024-12-18 11:41:16 +01:00
Github Actions
59d739ff0b Automatic update helm docs from VictoriaMetrics/helm-charts@3b7bfbd (#7876)
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>
2024-12-18 11:40:39 +01:00
hagen1778
b54d10be63 docs: port LTS changelog
Signed-off-by: hagen1778 <roman@victoriametrics.com>
2024-12-17 20:20:19 +01:00
Aliaksandr Valialkin
524f0e8d8b lib/logstorage: eliminate memory allocations when finalizing per-group values calculated by stats pipe
This improves query performance a bit when `stats by (...)` returns millions of individual `by (...)` groups
2024-12-17 15:17:01 +01:00
Roman Khavronenko
72419834af docs: add missing resource usage limits (#7856)
### 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>
2024-12-17 15:02:54 +01:00
Aliaksandr Valialkin
e6b7d25ab4 app/vlselect: allow passing arbitrary LogsQL filters to extra_filters and extra_stream_filters query args
While at at, allow passing an array of string values per each JSON entry at extra_filters and extra_stream_filters.
For example, `extra_filters={"foo":["bar","baz"]}` is converted into `foo:in("bar", "baz")` extra filter,
while `extra_stream_fitlers={"foo":["bar","baz"]}` is converted into `{foo=~"bar|baz"}` extra filter.

This should simplify creating faceted search when multiple values per a single log field must be selected.
This is needed for https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7365#issuecomment-2447964259

Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5542
2024-12-17 13:02:13 +01:00
Daria Karavaieva
ac124cf5aa docs/vmanomaly: deprecate Overview page (#7812)
### Describe Your Changes

-Deprecate Overview page in Anomaly Detection docs. 
- Adding service description  to `README.md`
- Moving Licensing information to Quickstart page

### Checklist

The following checks are **mandatory**:

- [x] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).
2024-12-17 12:45:44 +01:00
Aliaksandr Valialkin
3d7f8377f7 lib/logstorage: do not return log fields with the same constant value across all the selected logs from facets pipe
Such log fields do not give any useful information during logs' exploration.
They just clutter the output of the `facets` pipe. So it is better to drop such fields by default.

If these fields are needed, then `keep_const_fields` option can be added to `facets` pipe.
2024-12-17 12:23:00 +01:00
Mathias Palmersheim
4992e083f0 fixed #7804 Added NoSelfMonitoringMetrics rule (#7805)
### Describe Your Changes

fixes #7804 by adding alert for missing uptime metric in vmanomaly

### Checklist

The following checks are **mandatory**:

- [x] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).
2024-12-16 10:00:29 -06:00
hagen1778
71a9fb16f7 deployment/docker: fix typo after d86788e9a2
Thanks to @Haleygo for pointing it out here https://github.com/VictoriaMetrics/VictoriaMetrics/pull/7843#issuecomment-2545949268

Signed-off-by: hagen1778 <roman@victoriametrics.com>
2024-12-16 16:37:21 +01:00
Artem Fetishev
7e7d029de1 docs: fix typo in keyConcepts.md (#7844)
Fix a typo and simplify the statement

### 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/).
2024-12-16 16:34:33 +01:00
Github Actions
983f30c326 Automatic update helm docs from VictoriaMetrics/helm-charts@c486483 (#7840)
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>
2024-12-16 16:17:06 +01:00
Artem Fetishev
efd8098b0b docs: update instant query description in key concepts (#7842)
### Describe Your Changes

Update docs to reflect the changes introduced in #7767 to fix #5796

### Checklist

The following checks are **mandatory**:

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

Signed-off-by: Artem Fetishev <rtm@victoriametrics.com>
2024-12-16 16:14:54 +01:00
Dima Shur
d86788e9a2 deployment/docker: set vmalert --remoteWrite.url to port 8429 (vmagent) (#7843)
### Describe Your Changes

Updated docker.compose.yml, set remotewrite.url to port 8429 so it would
correspond to documentation

### Checklist

The following checks are **mandatory**:

- [x] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).
2024-12-16 16:13:45 +01:00
Aliaksandr Valialkin
a87ad250d0 docs/VictoriaLogs/data-ingestion/README.md: add missing of 2024-12-16 15:01:01 +01:00
hagen1778
bf84de3c6b docs: move change from c6f6302ca4 to #tip
The change was mistakenly put to the released version of VM

Signed-off-by: hagen1778 <roman@victoriametrics.com>
2024-12-16 14:20:32 +01:00
Aliaksandr Valialkin
7ec8ea8301 docs/VictoriaLogs/data-ingestion/Vector.md: improve docs a bit
- Remove Loki sink, since it brings more troubles when users try using it in Vector.
  For example, it encodes all the log fields as a JSON string and puts it into "message" field.
  This results in storing the "message" field with the JSON string containing all the log fields
  in VictoriaLogs. This is not what expected - every log field must be stored as a separate field
  according to https://docs.victoriametrics.com/victorialogs/keyconcepts/

- Remove 'mode: bulk' option from Elasticsearch sink configuration, since this option is set by default to this value,
  so there is no need in explicit setting.

- Add 'compression: gzip' to all the config examples, since the compression reduces the used network bandwidth by 4-5 times,
  while it doesn't increase CPU usage too much at both Vector and VictoriaLogs sides. So it is better to enable the compression in config examples.

- Mention about HTTP parameters accepted by VictoriaLogs data ingestion APIs in both examples for Elasticsearch and JSON line protocols.
2024-12-16 13:52:35 +01:00
Artem Fetishev
c6f6302ca4 Fix inconsistent treatment of millisecond-precision time for instant queries (#7767)
### Describe Your Changes

This PR fixes #5796. See the points 6 and 7 in `Steps to reproduce`:

> Now let's set time to only 5ms past the timestamp of the first point,
since even 199ms worked for the second point. Surprise, the point isn't
returned 💥:
>
> ```curl -s $VMQURL -d 'query=series1' -d 'time=1707123456705' -d
'step=1ms' | grep 10 # nothing!```
>
> But, 4ms works: 🤨🤔
>
> ```curl -s $VMQURL -d 'query=series1' -d 'time=1707123456704' -d
'step=1ms' | grep 10 # found```

This happens so because the actual step becomes 5ms due to jitter being
applied. THe fix is to do not apply jitter if scrape interval was not
detected (the case when vmstorage returns only one result). In this case
the scrape interval is set to `5m+step`.

An integration test has been added to check the steps to reproduce and
then to confirm that fix works. Note that the cluster tests are
currently disabled because the fix is not in cluster branch yet.

### Checklist

The following checks are **mandatory**:

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

---------

Signed-off-by: Artem Fetishev <rtm@victoriametrics.com>
2024-12-16 13:24:52 +01:00
Github Actions
87100e55cc Automatic update helm docs from VictoriaMetrics/helm-charts@ec141b8 (#7836)
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>
2024-12-16 12:51:04 +01:00
Roman Khavronenko
c464d4484f lib/storage: update dedup tests
* update misleading comments about preferring NaNs on intervals. NaNs
are only preferred on timestamp conflicts
* add conflicting timestamps to the benchmark test. Previously,
benchmark wasn't checking the timestamp conflict code branch. The
updated results after
c0fcfd6b97
are the following:
```
benchstat old.txt new.txt

goos: darwin
goarch: arm64
pkg: github.com/VictoriaMetrics/VictoriaMetrics/lib/storage
cpu: Apple M4 Pro
                                                       │   old.txt    │               new.txt                │
                                                       │    sec/op    │    sec/op     vs base                │
DeduplicateSamples/minScrapeInterval=3s-14               889.7n ± ∞ ¹   904.3n ± ∞ ¹       ~ (p=1.000 n=1) ²
DeduplicateSamples/minScrapeInterval=4s-14               735.9n ± ∞ ¹   748.7n ± ∞ ¹       ~ (p=1.000 n=1) ²
DeduplicateSamples/minScrapeInterval=10s-14              637.7n ± ∞ ¹   659.3n ± ∞ ¹       ~ (p=1.000 n=1) ²
DeduplicateSamplesDuringMerge/minScrapeInterval=3s-14    838.8n ± ∞ ¹   810.4n ± ∞ ¹       ~ (p=1.000 n=1) ²
DeduplicateSamplesDuringMerge/minScrapeInterval=4s-14    765.2n ± ∞ ¹   735.1n ± ∞ ¹       ~ (p=1.000 n=1) ²
DeduplicateSamplesDuringMerge/minScrapeInterval=10s-14   673.1n ± ∞ ¹   622.4n ± ∞ ¹       ~ (p=1.000 n=1) ²
geomean                                                  751.7n         741.0n        -1.42%
```

### 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.

---
Signed-off-by: hagen1778 <roman@victoriametrics.com>
2024-12-16 12:50:41 +01:00
f41gh7
91f858ee1e docs: bump last VM versions
Signed-off-by: f41gh7 <nik@victoriametrics.com>
2024-12-16 12:19:51 +01:00
f41gh7
da0d57e4b6 CHANGELOG.md: cut v1.108.0 release 2024-12-16 12:12:02 +01:00
hagen1778
fa621b384e docs: mention deprecation of metric names in update notes
Signed-off-by: hagen1778 <roman@victoriametrics.com>
2024-12-16 11:20:32 +01:00
f41gh7
02fedb8585 docs/changelog: add missing PR links
Signed-off-by: f41gh7 <nik@victoriametrics.com>
2024-12-13 12:08:16 +01:00
f41gh7
04d19a2200 make vmui-update 2024-12-13 12:01:03 +01:00
f41gh7
e612877fe7 app/vmselect: respect -search.skipSlowReplicas when -globalReplicationFactor > 1
Previously cluster with the following vmselect configuration:

./bin/vmselect
  -storageNode=gr1/:8211,gr1/:8212
  -storageNode=gr2/:8213,gr2/:8214
  -search.skipSlowReplicas=true
  -globalReplicationFactor=2

Here we have two vmstorage groups and -globalReplicationFactor=2, which effectively means that "every ingested sample is replicated across multiple vmstorage groups". Hence, gr1 and gr2 contain identical data set. And when we set -search.skipSlowReplicas=true it is expected vmselect should return result as soon as at least one storage group returned the full result.
In current state, -search.skipSlowReplicas is ignored on the storage group level. It is only respected within the group (with -replicationFactor flag).

   This commit fixes global replication for skipSlowReplicas.

 To ensure that the fix works and does not break
anything replication tests have been added. For checking the fix for
skipping slow replicas see `testGroupSkipSlowReplicas()`.

To emulate storage groups, the integration test creates a cluster with
multilevel vminsert. The L1 inserts are group-level inserts, each writes
to its own group of vmstorages. The L2 vminsert is a global vminsert
that writes replicated to the L1 vminserts.

To enable multilevel inserts changes in apptest framework and
`lib/ingestserver/clusternative/server.go` were necessary.

related issue:
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6924

---------

Signed-off-by: Artem Fetishev <rtm@victoriametrics.com>
2024-12-13 11:59:03 +01:00
Zhu Jiekun
43181b67b1 discovery/dockerswarm: add missing service labels to tasks discovery role
Previously service labels won't be attached when `role: tasks` is set.
Because the `addServicesLabels` function is shared by `role: tasks` and
`role: services`, and it will return nothing when `vip.Addr` is invalid
or empty.

In Prometheus, even if `vip.Addr` is empty, it attach common service
labels with [a standalone
function](f10c3454e9/discovery/moby/services.go (L129)),
which offers:
- `__meta_dockerswarm_service_id`: the id of the service.
- `__meta_dockerswarm_service_name`: the name of the service.
- `__meta_dockerswarm_service_mode`: the mode of the service.
- `__meta_dockerswarm_service_label_<labelname>`: each label of the
service, with any unsupported characters converted to an underscore.

This PR add a `addServicesLabelsForTask`, to replace the usage of
`addServicesLabels` when `role: tasks` is set. This function offers
common service labels listed above.

related issue:
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7800
2024-12-13 11:28:04 +01:00
Hui Wang
b0ed5b6174 app/vmalert: fixes reload of external templates
Previously after configuration reload call `externalURL` templaing function defined at external templates could be lost. Since it was added only at initial `Load` call and never copied during template reload process.
External templates for vmalert could be defined via `-rule.templates` flag.

 This commit properly reload external templates. It's no longer copies mutated templates and instead fully reloads it each time if there is any changes.
2024-12-13 10:29:19 +01:00
Github Actions
4aeda4b267 Automatic update helm docs from VictoriaMetrics/helm-charts@4b32065 (#7817)
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>
2024-12-13 10:04:56 +01:00
Github Actions
20a2822c23 Automatic update Grafana datasource docs from VictoriaMetrics/victorialogs-datasource@21936b8 (#7814) 2024-12-13 10:04:42 +01:00
hagen1778
1891b74a0a docs: mention required version for multitenancy endpoint in vmalert
Signed-off-by: hagen1778 <roman@victoriametrics.com>
2024-12-12 13:01:26 +01:00
Andrei Baidarov
0dc576d3da lib/storage: prefer stale markers over other values on dedup interval
Previously, during de-duplication staleness markers could be removed due to incorrect logic at
values equality check.
 During the evaluation of read query vmselect deduplicates samples using dedupInterval option. It picks the highest value across all points with the same timestamp next to the border of dedupInterval. The issue is any comparison with NaN via <, > returns false. This means that the position of NaN in srcValues could affect the result.


 This commit changes this logic with additional step, that explicitly checks for staleness marker for the following cases:
 1. Deduplication on vmselect
2. Deduplication in vmstorage during merges
3. Deduplication in stream aggregation

check performed only for stale markers, because other NaNs are rejected on ingestion
by vmstorage or by stream aggregation.

Checking for stale markers in general slows down dedup speed by 3%:
```
 benchstat old.txt new.txt

goos: darwin
goarch: arm64
pkg: github.com/VictoriaMetrics/VictoriaMetrics/lib/storage
cpu: Apple M4 Pro
                                                       │   old.txt    │               new.txt                │
                                                       │    sec/op    │    sec/op     vs base                │
DeduplicateSamples/minScrapeInterval=1s-14               462.8n ± ∞ ¹   425.2n ± ∞ ¹       ~ (p=1.000 n=1) ²
DeduplicateSamples/minScrapeInterval=2s-14               905.6n ± ∞ ¹   903.3n ± ∞ ¹       ~ (p=1.000 n=1) ²
DeduplicateSamples/minScrapeInterval=5s-14               710.0n ± ∞ ¹   698.9n ± ∞ ¹       ~ (p=1.000 n=1) ²
DeduplicateSamples/minScrapeInterval=10s-14              632.7n ± ∞ ¹   638.5n ± ∞ ¹       ~ (p=1.000 n=1) ²
DeduplicateSamplesDuringMerge/minScrapeInterval=1s-14    439.7n ± ∞ ¹   409.9n ± ∞ ¹       ~ (p=1.000 n=1) ²
DeduplicateSamplesDuringMerge/minScrapeInterval=2s-14    908.9n ± ∞ ¹   882.2n ± ∞ ¹       ~ (p=1.000 n=1) ²
DeduplicateSamplesDuringMerge/minScrapeInterval=5s-14    721.2n ± ∞ ¹   684.7n ± ∞ ¹       ~ (p=1.000 n=1) ²
DeduplicateSamplesDuringMerge/minScrapeInterval=10s-14   659.1n ± ∞ ¹   630.6n ± ∞ ¹       ~ (p=1.000 n=1) ²
geomean                                                  659.5n         636.0n        -3.56%
```

Related issue:
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7674
---------
Co-authored-by: hagen1778 <roman@victoriametrics.com>
2024-12-12 12:34:17 +01:00
dependabot[bot]
88861c66fe build(deps): bump golang.org/x/crypto from 0.29.0 to 0.31.0 (#7807)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from
0.29.0 to 0.31.0.
<details>
<summary>Commits</summary>
<ul>
<li><a
href="b4f1988a35"><code>b4f1988</code></a>
ssh: make the public key cache a 1-entry FIFO cache</li>
<li><a
href="7042ebcbe0"><code>7042ebc</code></a>
openpgp/clearsign: just use rand.Reader in tests</li>
<li><a
href="3e90321ac7"><code>3e90321</code></a>
go.mod: update golang.org/x dependencies</li>
<li><a
href="8c4e668694"><code>8c4e668</code></a>
x509roots/fallback: update bundle</li>
<li>See full diff in <a
href="https://github.com/golang/crypto/compare/v0.29.0...v0.31.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=golang.org/x/crypto&package-manager=go_modules&previous-version=0.29.0&new-version=0.31.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/VictoriaMetrics/VictoriaMetrics/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-12 09:07:43 +01:00
hagen1778
1ee5ba8d55 docs: clarify meaning of deduplication for exported data
See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7763

Signed-off-by: hagen1778 <roman@victoriametrics.com>
2024-12-11 20:57:35 +01:00
Andrii Chubatiuk
e0ab3fccaf app/vlinsert/syslog: fixed structured data parsing (#7801)
### Describe Your Changes

rfc5424 doesn't allow structured data to be started from whitespace, but
it can be present in the end of this section
related issue
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7776

### Checklist

The following checks are **mandatory**:

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

---------

Co-authored-by: Roman Khavronenko <roman@victoriametrics.com>
2024-12-11 17:08:36 +01:00
Github Actions
2fe6640193 Automatic update helm docs from VictoriaMetrics/helm-charts@9524e91 (#7793)
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>
2024-12-11 17:03:58 +01:00
Yury Molodov
d1ccf205c4 vmui: add more details for "clipboard not supported" error (#7778)
### Describe Your Changes

Added a message for Clipboard API errors with common issues and a link
to the docs. Added a check for secure context, showing a clear error and
a doc link if the context is not secure.

Related issue: #7677

<img width="400"
src="https://github.com/user-attachments/assets/a448d82e-f484-43de-9004-fbd5a57f49a7">
<img width="400"
src="https://github.com/user-attachments/assets/8de97577-89a3-445d-a4bb-a091a4549f39">

### Checklist

The following checks are **mandatory**:

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

---------

Co-authored-by: Roman Khavronenko <roman@victoriametrics.com>
2024-12-11 17:03:35 +01:00
Aliaksandr Valialkin
b42ed019f5 docs/VictoriaLogs/LogsQL.md: collapse_nums pipe docs: clarify that <N> is a placeholder 2024-12-11 16:34:40 +01:00
Aliaksandr Valialkin
5a41c7f5a5 docs/VictoriaLogs/LogsQL.md: mention that collapse_nums can miss collapsing some numbers or can collapse unexpected numbers
Suggest a solution with replace_regexp() pipe for custom collapsing.
2024-12-11 16:32:36 +01:00
Aliaksandr Valialkin
ec193ef691 docs/VictoriaLogs/querying/vlogscli.md: document \wrap_long_lines option
This is a follow-up for f55791f20b
2024-12-11 15:54:35 +01:00
hagen1778
e669c87af4 docs/changelog: re-order LTS releases for better navigation
Signed-off-by: hagen1778 <roman@victoriametrics.com>
2024-12-11 15:27:38 +01:00
Roman Khavronenko
87c1b2de6f deployment/docker: update base Alpine docker image from 3.20.3 to 3.21.0 (#7798)
See https://alpinelinux.org/posts/Alpine-3.21.0-released.html

### 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>
2024-12-11 11:30:37 +01:00
hagen1778
bcd8d9d6c6 docs: re-order changes by priority
Signed-off-by: hagen1778 <roman@victoriametrics.com>
2024-12-11 11:06:38 +01:00
f41gh7
dbed0de650 lib/timeserieslimits: follow-up for 564e6ea024
Changed enabled limit condition to `or` instead of `and`. Since labels must checked if at least one of the limits is defined.

Signed-off-by: f41gh7 <nik@victoriametrics.com>
2024-12-11 11:00:27 +01:00
hagen1778
34a730ac65 docs: update wording after 564e6ea024
Mention all related limits and the way to troubleshoot them.

Signed-off-by: hagen1778 <roman@victoriametrics.com>
2024-12-11 10:55:07 +01:00
hagen1778
e21bdcdbc7 docs: make wording more transparent for readers
Signed-off-by: hagen1778 <roman@victoriametrics.com>
2024-12-11 10:46:52 +01:00
Hui Wang
9db8e071c4 vmalert-tool: support debug mode for alerting rule (#7788)
User can enable [debug
mode](https://docs.victoriametrics.com/vmalert/#debug-mode) in
vmalert-tool, to check alerting rule evaluation status and write
`alert_rule_test` cases.
2024-12-11 09:49:14 +01:00
hagen1778
1627bcc6cb dashboards: add missing filter by instance to Go scheduling latency panel
Signed-off-by: hagen1778 <roman@victoriametrics.com>
2024-12-11 09:03:09 +01:00
Github Actions
5033d05d55 Automatic update Grafana datasource docs from VictoriaMetrics/victorialogs-datasource@f31bdac (#7790) 2024-12-10 21:23:47 +01:00
hagen1778
5279faf02f deployment: bump victorialogs datasource version
Signed-off-by: hagen1778 <roman@victoriametrics.com>
2024-12-10 21:23:08 +01:00
Andrii Chubatiuk
564e6ea024 app/{vminsert,vmagent}: drop time series on exceeding labels limits.
Previously, time series with labels exceeding the configured limits were truncated and written to storage, potentially causing data inconsistency. This could lead to collisions between time series and make it difficult to identify the source due to truncated labels.

This commit changes the behavior:
*  Such time series are now rejected outright.
* Rejected time series are logged to stdout, and corresponding counters are incremented.  
* removes `vm_too_long_label_values_total`, `vm_too_long_label_names_total`, `vm_metrics_with_dropped_labels_total` metrics.  
* adds new values `[too_many_labels,too_long_label_name,too_long_label_value]`  to `reason` label of the `vm_rows_ignored_total` metric name

related issues:
- https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6928
- https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7661
2024-12-10 21:19:16 +01:00
Zhu Jiekun
6b48126603 discovery/docker: add match_first_network support for docker_sd_configs
This commit aligns behaviour of docker service discovery with Prometheus implementation.

It adds the following changes:
* introduce new config param `match_first_network` with default value of `true`. It uses the first network if the container has multiple networks
defined.  It should help to avoid collecting duplicate targets error with multi network setups.

* add `networks` for the containers with linked network to the other containers with `network_mode: container:id` setting. It resolve an issue with attached containers aka `pods` in Kubernetes.

Related issue:
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7398
2024-12-10 20:15:33 +01:00
Yury Molodov
4a2192431d vmui: prevent accordion collapse on text selection in headers
Prevent accordion from collapsing when selecting text in headers.

Related issue: 
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7742
2024-12-10 20:05:32 +01:00
Yury Molodov
86bc7d5cd1 vmui: fix incorrect message in Table tab
Updated the message in the “Table” tab of the VictoriaMetrics UI. It now
correctly displays the step value based on the actual configuration.

Related issue:
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7401
2024-12-10 20:00:23 +01:00
hagen1778
d05fadf988 docs: fix typo in facets example
Signed-off-by: hagen1778 <roman@victoriametrics.com>
2024-12-10 15:52:33 +01:00
Hui Wang
e439e40e79 app/vmalert: fix possible template overwritten between rule annotations
Previous commit b09272ccac added regression, which could lead to the template
global state overwrites. 
 
 The issue related to the mechanism how `vmalert` inherits templates. It has global templates, that could be changed via `rule.templates` flag. And local templates defined per labels/annotations for rules and groups.

 During labels/annotations templating state could be changed via `define` syntax. 

 This commit restores previous behavior with `Clone` call for templates before templating labels/annotations.

 Affected releases:
- 1.106.1
- v1.102.7
- v1.97.12

 Related issue:
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6894
2024-12-10 14:59:40 +01:00
Nikolay
d6f5ba2887 app/vmauth: allow to start with empty auth config file
This commit adds ability to launch vmauth without configuration file.
Which is possible use case for operator based installations.

  Operator provides global resource `VMAuth` and allows to create
`VMUser` objects for it. Eventually operator creates configuration for
`VMAuth` based on user defined selectors for `VMUser`.

  Since there is no direct relations between
those objects. And any object could be created in on-demand by
Kubernetes users. It's required to be able to start `vmauth` with empty
auth config file.

Related issue:
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6467
2024-12-10 14:51:11 +01:00
Github Actions
94e4c4e367 Automatic update helm docs from VictoriaMetrics/helm-charts@42d92a7 (#7783)
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>
2024-12-10 14:48:29 +01:00
Github Actions
aadd8d5f3a Automatic update Grafana datasource docs from VictoriaMetrics/victorialogs-datasource@649d972 (#7786) 2024-12-10 14:47:03 +01:00
Dmytro Kozlov
44d8e6a19d deployment/docker: update victorialogs datasource versions to the latest releases (#7787)
### Describe Your Changes
Updated victorialogs-datasource to the latest
[release](https://github.com/VictoriaMetrics/victorialogs-datasource/tree/v0.11.0)
### Checklist

The following checks are **mandatory**:

- [x] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).
2024-12-10 13:08:55 +01:00
hagen1778
6b0ae0b79f docs/vmalert: update debug description
* mention that `debug` messages require -loggerLevel=INFO
* rm version requirement, as mentioned version is pretty old
and it is liklely everyone is using a newer version

Signed-off-by: hagen1778 <roman@victoriametrics.com>
2024-12-10 10:22:39 +01:00
Nikolay
a51a18403c lib/storage: properly apply dedup.minScrapeInterval
Previously, if only `-dedup.minScrapeInterval` was set without
`downsampling.Period, function
getDownsamplingFilters returned empty result for
downsamplingPeriodFilters. Because it didn't take in
account globalDedup variable.

 This commit adds fast path for this case and returns a single
downsampling filter with global interval value.

In addition, it adds the following changes:

* Removes global state modification at ParseDownsamplingPeriods
  function. Which could lead to data races at vmselect
* simplifies logic of isDedupNeeded function. Since
  donwsamplingPeriodsWithout filters is subset of
dowsamplingPeriodByFilters. There is no need for len check
* Improves tests by proper reset global state of downsampling

Related issue:
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7764
2024-12-09 15:20:22 +01:00
Aliaksandr Valialkin
de0ae735aa lib/logstorage: add count_uniq_hash function to stats pipe
This function calculates the number of unique value hashes. This number is a good approximation
for the number of unique values. The `count_uniq_hash` function uses less memory and works faster
than `count_uniq` when applied to fields with big number of unique values.
2024-12-09 13:29:41 +01:00
Alexander Marshalov
acbe526307 vmbackupmanager: increase min sleep time between scheduling cycles from 0 to 1s to avoid spammed logs. (#807)
* vmbackupmanager: increase min sleep time between scheduling cycles from 0 to 1s to avoid spammed logs.

* Update docs/changelog/CHANGELOG.md

Co-authored-by: Roman Khavronenko <roman@victoriametrics.com>

---------

Co-authored-by: Roman Khavronenko <roman@victoriametrics.com>
Signed-off-by: hagen1778 <roman@victoriametrics.com>
2024-12-09 12:29:25 +01:00
Github Actions
9a6ddb48df Automatic update operator docs from VictoriaMetrics/operator@ee0406d (#7765)
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>
2024-12-09 12:21:30 +01:00
Github Actions
7d3e60f7f1 Automatic update helm docs from VictoriaMetrics/helm-charts@4b502fc (#7768)
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>
2024-12-09 12:21:01 +01:00
Aliaksandr Valialkin
f54f73033b docs/VictoriaLogs/LogsQL.md: typo fix: remove double with with 2024-12-09 00:37:01 +01:00
Aliaksandr Valialkin
75a2e23b7e deployment: update VictoriaLogs Docker image from v1.3.1-victorialogs to v1.3.2-victorialogs
See https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.3.2-victorialogs
2024-12-09 00:34:53 +01:00
2006 changed files with 57213 additions and 428823 deletions

View File

@@ -85,7 +85,7 @@ jobs:
restore-keys: go-artifacts-${{ runner.os }}-${{ matrix.scenario }}-
- name: Run tests
run: make ${{ matrix.scenario}}
run: GOGC=10 make ${{ matrix.scenario}}
- name: Publish coverage
uses: codecov/codecov-action@v5

View File

@@ -175,7 +175,7 @@
END OF TERMS AND CONDITIONS
Copyright 2019-2024 VictoriaMetrics, Inc.
Copyright 2019-2025 VictoriaMetrics, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@@ -513,19 +513,19 @@ check-all: fmt vet golangci-lint govulncheck
clean-checkers: remove-golangci-lint remove-govulncheck
test:
DISABLE_FSYNC_FOR_TESTING=1 go test ./lib/... ./app/...
go test ./lib/... ./app/...
test-race:
DISABLE_FSYNC_FOR_TESTING=1 go test -race ./lib/... ./app/...
go test -race ./lib/... ./app/...
test-pure:
DISABLE_FSYNC_FOR_TESTING=1 CGO_ENABLED=0 go test ./lib/... ./app/...
CGO_ENABLED=0 go test ./lib/... ./app/...
test-full:
DISABLE_FSYNC_FOR_TESTING=1 go test -coverprofile=coverage.txt -covermode=atomic ./lib/... ./app/...
go test -coverprofile=coverage.txt -covermode=atomic ./lib/... ./app/...
test-full-386:
DISABLE_FSYNC_FOR_TESTING=1 GOARCH=386 go test -coverprofile=coverage.txt -covermode=atomic ./lib/... ./app/...
GOARCH=386 go test -coverprofile=coverage.txt -covermode=atomic ./lib/... ./app/...
integration-test: victoria-metrics vmagent vmalert vmauth
go test ./apptest/... -skip="^TestCluster.*"
@@ -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

@@ -1,12 +1,14 @@
# VictoriaMetrics
[![Latest Release](https://img.shields.io/github/release/VictoriaMetrics/VictoriaMetrics.svg?style=flat-square)](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/latest)
[![Docker Pulls](https://img.shields.io/docker/pulls/victoriametrics/victoria-metrics.svg?maxAge=604800)](https://hub.docker.com/r/victoriametrics/victoria-metrics)
[![Slack](https://img.shields.io/badge/join%20slack-%23victoriametrics-brightgreen.svg)](https://slack.victoriametrics.com/)
[![GitHub license](https://img.shields.io/github/license/VictoriaMetrics/VictoriaMetrics.svg)](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/LICENSE)
[![Go Report](https://goreportcard.com/badge/github.com/VictoriaMetrics/VictoriaMetrics)](https://goreportcard.com/report/github.com/VictoriaMetrics/VictoriaMetrics)
[![Build Status](https://github.com/VictoriaMetrics/VictoriaMetrics/workflows/main/badge.svg)](https://github.com/VictoriaMetrics/VictoriaMetrics/actions)
[![codecov](https://codecov.io/gh/VictoriaMetrics/VictoriaMetrics/branch/master/graph/badge.svg)](https://codecov.io/gh/VictoriaMetrics/VictoriaMetrics)
![Latest Release](https://img.shields.io/github/v/release/VictoriaMetrics/VictoriaMetrics?sort=semver&label=&filter=!*-victorialogs&logo=github&labelColor=gray&color=gray&link=https%3A%2F%2Fgithub.com%2FVictoriaMetrics%2FVictoriaMetrics%2Freleases%2Flatest)
![Docker Pulls](https://img.shields.io/docker/pulls/victoriametrics/victoria-metrics?label=&logo=docker&logoColor=white&labelColor=2496ED&color=2496ED&link=https%3A%2F%2Fhub.docker.com%2Fr%2Fvictoriametrics%2Fvictoria-metrics)
![Go Report](https://goreportcard.com/badge/github.com/VictoriaMetrics/VictoriaMetrics?link=https%3A%2F%2Fgoreportcard.com%2Freport%2Fgithub.com%2FVictoriaMetrics%2FVictoriaMetrics)
![Build Status](https://github.com/VictoriaMetrics/VictoriaMetrics/workflows/main/badge.svg?link=https%3A%2F%2Fgithub.com%2FVictoriaMetrics%2FVictoriaMetrics%2Factions)
![codecov](https://codecov.io/gh/VictoriaMetrics/VictoriaMetrics/branch/master/graph/badge.svg?link=https%3A%2F%2Fcodecov.io%2Fgh%2FVictoriaMetrics%2FVictoriaMetrics)
![License](https://img.shields.io/github/license/VictoriaMetrics/VictoriaMetrics?labelColor=green&label=&link=https%3A%2F%2Fgithub.com%2FVictoriaMetrics%2FVictoriaMetrics%2Fblob%2Fmaster%2FLICENSE)
![Slack](https://img.shields.io/badge/Join-4A154B?logo=slack&link=https%3A%2F%2Fslack.victoriametrics.com)
![X](https://img.shields.io/twitter/follow/VictoriaMetrics?style=flat&label=Follow&color=black&logo=x&labelColor=black&link=https%3A%2F%2Fx.com%2FVictoriaMetrics)
![Reddit](https://img.shields.io/reddit/subreddit-subscribers/VictoriaMetrics?style=flat&label=Join&labelColor=red&logoColor=white&logo=reddit&link=https%3A%2F%2Fwww.reddit.com%2Fr%2FVictoriaMetrics)
<picture>
<source srcset="docs/logo_white.webp" media="(prefers-color-scheme: dark)">

View File

@@ -14,6 +14,7 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmselect/promql"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmstorage"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/buildinfo"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/cgroup"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/envflag"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/flagutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
@@ -39,9 +40,24 @@ var (
"The saved data survives unclean shutdowns such as OOM crash, hardware reset, SIGKILL, etc. "+
"Bigger intervals may help increase the lifetime of flash storage with limited write cycles (e.g. Raspberry PI). "+
"Smaller intervals increase disk IO load. Minimum supported value is 1s")
maxIngestionRate = flag.Int("maxIngestionRate", 0, "The maximum number of samples vmsingle can receive per second. Data ingestion is paused when the limit is exceeded. "+
"By default there are no limits on samples ingestion rate.")
finalDedupScheduleInterval = flag.Duration("storage.finalDedupScheduleCheckInterval", time.Hour, "The interval for checking when final deduplication process should be started."+
"Storage unconditionally adds 25% jitter to the interval value on each check evaluation."+
" Changing the interval to the bigger values may delay downsampling, deduplication for historical data."+
" See also https://docs.victoriametrics.com/#deduplication")
)
func main() {
// VictoriaMetrics is optimized for reduced memory allocations,
// so it can run with the reduced GOGC in order to reduce the used memory,
// while keeping CPU usage spent in GC at low levels.
//
// Some workloads may need increased GOGC values. Then such values can be set via GOGC environment variable.
// It is recommended increasing GOGC if go_memstats_gc_cpu_fraction metric exposed at /metrics page
// exceeds 0.05 for extended periods of time.
cgroup.SetGOGC(30)
// Write flags and help message to stdout, since it is easier to grep or pipe.
flag.CommandLine.SetOutput(os.Stdout)
flag.Usage = usage
@@ -74,8 +90,13 @@ func main() {
startTime := time.Now()
storage.SetDedupInterval(*minScrapeInterval)
storage.SetDataFlushInterval(*inmemoryDataFlushInterval)
if *finalDedupScheduleInterval < time.Hour {
logger.Fatalf("-dedup.finalDedupScheduleCheckInterval cannot be smaller than 1 hour; got %s", *finalDedupScheduleInterval)
}
storage.SetFinalDedupScheduleInterval(*finalDedupScheduleInterval)
vmstorage.Init(promql.ResetRollupResultCacheIfNeeded)
vmselect.Init()
vminsertcommon.StartIngestionRateLimiter(*maxIngestionRate)
vminsert.Init()
startSelfScraper()
@@ -97,6 +118,7 @@ func main() {
}
logger.Infof("successfully shut down the webservice in %.3f seconds", time.Since(startTime).Seconds())
vminsert.Stop()
vminsertcommon.StopIngestionRateLimiter()
vmstorage.Stop()
vmselect.Stop()

View File

@@ -13,6 +13,7 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/prometheus"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/timeserieslimits"
)
var (
@@ -68,6 +69,10 @@ func selfScraper(scrapeInterval time.Duration) {
t := &r.Tags[j]
labels = addLabel(labels, t.Key, t.Value)
}
if timeserieslimits.IsExceeding(labels) {
// Skip metric with exceeding labels.
continue
}
if len(mrs) < cap(mrs) {
mrs = mrs[:len(mrs)+1]
} else {

View File

@@ -14,6 +14,7 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/app/vlinsert/insertutils"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vlstorage"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/flagutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httpserver"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logstorage"
@@ -21,6 +22,11 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/lib/writeconcurrencylimiter"
)
var (
datadogStreamFields = flagutil.NewArrayString("datadog.streamFields", "Datadog tags to be used as stream fields.")
datadogIgnoreFields = flagutil.NewArrayString("datadog.ignoreFields", "Datadog tags to ignore.")
)
var parserPool fastjson.ParserPool
// RequestHandler processes Datadog insert requests
@@ -79,6 +85,13 @@ func datadogLogsIngestion(w http.ResponseWriter, r *http.Request) bool {
return true
}
if len(cp.StreamFields) == 0 {
cp.StreamFields = *datadogStreamFields
}
if len(cp.IgnoreFields) == 0 {
cp.IgnoreFields = *datadogIgnoreFields
}
if err := vlstorage.CanWriteData(); err != nil {
httpserver.Errorf(w, r, "%s", err)
return true
@@ -105,6 +118,70 @@ var (
v2LogsRequestDuration = metrics.NewHistogram(`vl_http_request_duration_seconds{path="/insert/datadog/api/v2/logs"}`)
)
// datadog message field has two formats:
// - regular log message with string text
// - nested json format for serverless plugins
// which has folowing format:
// {"message": {"message": "text","lamdba": {"arn": "string","requestID": "string"}, "timestamp": int64} }
//
// See https://github.com/DataDog/datadog-lambda-extension/blob/28b90c7e4e985b72d60b5f5a5147c69c7ac693c4/bottlecap/src/logs/lambda/mod.rs#L24
func appendMsgFields(fields []logstorage.Field, v *fastjson.Value) ([]logstorage.Field, error) {
switch v.Type() {
case fastjson.TypeString:
val := v.GetStringBytes()
fields = append(fields, logstorage.Field{
Name: "_msg",
Value: bytesutil.ToUnsafeString(val),
})
case fastjson.TypeObject:
var firstErr error
v.GetObject().Visit(func(k []byte, v *fastjson.Value) {
if firstErr != nil {
return
}
switch bytesutil.ToUnsafeString(k) {
case "message":
val := v.GetStringBytes()
fields = append(fields, logstorage.Field{
Name: "_msg",
Value: bytesutil.ToUnsafeString(val),
})
case "status":
val := v.GetStringBytes()
fields = append(fields, logstorage.Field{
Name: "status",
Value: bytesutil.ToUnsafeString(val),
})
case "lamdba":
obj, err := v.Object()
if err != nil {
firstErr = err
firstErr = fmt.Errorf("unexpected lambda value type for %q:%q; want object", k, v)
return
}
obj.Visit(func(k []byte, v *fastjson.Value) {
if firstErr != nil {
return
}
val, err := v.StringBytes()
if err != nil {
firstErr = fmt.Errorf("unexpected lambda label value type for %q:%q; want string", k, v)
return
}
fields = append(fields, logstorage.Field{
Name: bytesutil.ToUnsafeString(k),
Value: bytesutil.ToUnsafeString(val),
})
})
}
})
default:
return fields, fmt.Errorf("unsupported message type %q", v.Type().String())
}
return fields, nil
}
// readLogsRequest parses data according to DataDog logs format
// https://docs.datadoghq.com/api/latest/logs/#send-logs
func readLogsRequest(ts int64, data []byte, lmp insertutils.LogMessageProcessor) error {
@@ -129,19 +206,27 @@ func readLogsRequest(ts int64, data []byte, lmp insertutils.LogMessageProcessor)
if err != nil {
return
}
val, e := v.StringBytes()
if e != nil {
err = fmt.Errorf("unexpected label value type for %q:%q; want string", k, v)
return
}
switch string(k) {
switch bytesutil.ToUnsafeString(k) {
case "message":
fields = append(fields, logstorage.Field{
Name: "_msg",
Value: bytesutil.ToUnsafeString(val),
})
fields, err = appendMsgFields(fields, v)
if err != nil {
return
}
case "timestamp":
val, e := v.Int64()
if e != nil {
err = fmt.Errorf("failed to parse timestamp for %q:%q", k, v)
}
if val > 0 {
ts = val * 1e6
}
case "ddtags":
// https://docs.datadoghq.com/getting_started/tagging/
val, e := v.StringBytes()
if e != nil {
err = fmt.Errorf("unexpected label value type for %q:%q; want string", k, v)
return
}
var pair []byte
idx := 0
for idx >= 0 {
@@ -168,12 +253,20 @@ func readLogsRequest(ts int64, data []byte, lmp insertutils.LogMessageProcessor)
}
}
default:
val, e := v.StringBytes()
if e != nil {
err = fmt.Errorf("unexpected label value type for %q:%q; want string", k, v)
return
}
fields = append(fields, logstorage.Field{
Name: bytesutil.ToUnsafeString(k),
Value: bytesutil.ToUnsafeString(val),
})
}
})
if err != nil {
return err
}
lmp.AddRow(ts, fields, nil)
fields = fields[:0]
}

View File

@@ -54,6 +54,12 @@ func TestReadLogsRequestSuccess(t *testing.T) {
"hostname":"127.0.0.1",
"message":"bar",
"service":"test"
}, {
"ddsource":"nginx",
"ddtags":"tag1:value1,tag2:value2",
"hostname":"127.0.0.1",
"message":{"message": "nested"},
"service":"test"
}, {
"ddsource":"nginx",
"ddtags":"tag1:value1,tag2:value2",
@@ -86,8 +92,9 @@ func TestReadLogsRequestSuccess(t *testing.T) {
"service":"test"
}
]`
rowsExpected := 6
rowsExpected := 7
resultExpected := `{"ddsource":"nginx","tag1":"value1","tag2":"value2","hostname":"127.0.0.1","_msg":"bar","service":"test"}
{"ddsource":"nginx","tag1":"value1","tag2":"value2","hostname":"127.0.0.1","_msg":"nested","service":"test"}
{"ddsource":"nginx","tag1":"value1","tag2":"value2","hostname":"127.0.0.1","_msg":"foobar","service":"test"}
{"ddsource":"nginx","tag1":"value1","tag2":"value2","hostname":"127.0.0.1","_msg":"baz","service":"test"}
{"ddsource":"nginx","tag1":"value1","tag2":"value2","hostname":"127.0.0.1","_msg":"xyz","service":"test"}

View File

@@ -199,8 +199,8 @@ func (lmp *logMessageProcessor) AddRow(timestamp int64, fields, streamFields []l
lmp.bytesIngestedTotal.Add(n)
if len(fields) > *MaxFieldsPerLine {
rf := logstorage.RowFormatter(fields)
logger.Warnf("dropping log line with %d fields; it exceeds -insert.maxFieldsPerLine=%d; %s", len(fields), *MaxFieldsPerLine, rf)
line := logstorage.MarshalFieldsToJSON(nil, fields)
logger.Warnf("dropping log line with %d fields; it exceeds -insert.maxFieldsPerLine=%d; %s", len(fields), *MaxFieldsPerLine, line)
rowsDroppedTotalTooManyFields.Inc()
return
}

View File

@@ -8,8 +8,10 @@ import (
var (
// MaxLineSizeBytes is the maximum length of a single line for /insert/* handlers
MaxLineSizeBytes = flagutil.NewBytes("insert.maxLineSizeBytes", 256*1024, "The maximum size of a single line, which can be read by /insert/* handlers")
MaxLineSizeBytes = flagutil.NewBytes("insert.maxLineSizeBytes", 256*1024, "The maximum size of a single line, which can be read by /insert/* handlers; "+
"see https://docs.victoriametrics.com/victorialogs/faq/#what-length-a-log-record-is-expected-to-have")
// MaxFieldsPerLine is the maximum number of fields per line for /insert/* handlers
MaxFieldsPerLine = flag.Int("insert.maxFieldsPerLine", 1000, "The maximum number of log fields per line, which can be read by /insert/* handlers")
MaxFieldsPerLine = flag.Int("insert.maxFieldsPerLine", 1000, "The maximum number of log fields per line, which can be read by /insert/* handlers; "+
"see https://docs.victoriametrics.com/victorialogs/faq/#how-many-fields-a-single-log-entry-may-contain")
)

View File

@@ -15,6 +15,8 @@ import (
// LineReader reads newline-delimited lines from the underlying reader
type LineReader struct {
// Line contains the next line read after the call to NextLine
//
// The Line contents is valid until the next call to NextLine.
Line []byte
// name is the LineReader name
@@ -26,6 +28,9 @@ type LineReader struct {
// buf is a buffer for reading the next line
buf []byte
// bufOffset is the offset at buf to read the next line from
bufOffset int
// err is the last error when reading data from r
err error
@@ -51,26 +56,27 @@ func NewLineReader(name string, r io.Reader) *LineReader {
// Check for Err in this case.
func (lr *LineReader) NextLine() bool {
for {
if len(lr.buf) == 0 {
if lr.bufOffset >= len(lr.buf) {
if lr.err != nil || lr.eofReached {
return false
}
if !lr.readMoreData() {
return false
}
if len(lr.buf) == 0 && lr.eofReached {
if lr.bufOffset >= len(lr.buf) && lr.eofReached {
return false
}
}
if n := bytes.IndexByte(lr.buf, '\n'); n >= 0 {
lr.Line = append(lr.Line[:0], lr.buf[:n]...)
lr.buf = append(lr.buf[:0], lr.buf[n+1:]...)
buf := lr.buf[lr.bufOffset:]
if n := bytes.IndexByte(buf, '\n'); n >= 0 {
lr.Line = buf[:n]
lr.bufOffset += n + 1
return true
}
if lr.eofReached {
lr.Line = append(lr.Line[:0], lr.buf...)
lr.buf = lr.buf[:0]
lr.Line = buf
lr.bufOffset += len(buf)
return true
}
if !lr.readMoreData() {
@@ -88,6 +94,11 @@ func (lr *LineReader) Err() error {
}
func (lr *LineReader) readMoreData() bool {
if lr.bufOffset > 0 {
lr.buf = append(lr.buf[:0], lr.buf[lr.bufOffset:]...)
lr.bufOffset = 0
}
bufLen := len(lr.buf)
if bufLen >= MaxLineSizeBytes.IntN() {
logger.Warnf("%s: the line length exceeds -insert.maxLineSizeBytes=%d; skipping it; line contents=%q", lr.name, MaxLineSizeBytes.IntN(), lr.buf)

View File

@@ -2,20 +2,19 @@ package insertutils
import (
"fmt"
"math"
"strconv"
"time"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logstorage"
)
// ExtractTimestampRFC3339NanoFromFields extracts RFC3339 timestamp in nanoseconds from the field with the name timeField at fields.
// ExtractTimestampFromFields extracts timestamp in nanoseconds from the field with the name timeField at fields.
//
// The value for the timeField is set to empty string after returning from the function,
// so it could be ignored during data ingestion.
//
// The current timestamp is returned if fields do not contain a field with timeField name or if the timeField value is empty.
func ExtractTimestampRFC3339NanoFromFields(timeField string, fields []logstorage.Field) (int64, error) {
func ExtractTimestampFromFields(timeField string, fields []logstorage.Field) (int64, error) {
for i := range fields {
f := &fields[i]
if f.Name != timeField {
@@ -48,22 +47,24 @@ func parseTimestamp(s string) (int64, error) {
return nsecs, nil
}
// ParseUnixTimestamp parses s as unix timestamp in either seconds or milliseconds and returns the parsed timestamp in nanoseconds.
// ParseUnixTimestamp parses s as unix timestamp in seconds, milliseconds, microseconds or nanoseconds and returns the parsed timestamp in nanoseconds.
func ParseUnixTimestamp(s string) (int64, error) {
n, err := strconv.ParseInt(s, 10, 64)
if err != nil {
return 0, fmt.Errorf("cannot parse unix timestamp from %q: %w", s, err)
}
if n < (1<<31) && n >= (-1<<31) {
// The timestamp is in seconds. Convert it to milliseconds
n *= 1e3
// The timestamp is in seconds.
return n * 1e9, nil
}
if n > int64(math.MaxInt64)/1e6 {
return 0, fmt.Errorf("too big timestamp in milliseconds: %d; mustn't exceed %d", n, int64(math.MaxInt64)/1e6)
if n < 1e3*(1<<31) && n >= 1e3*(-1<<31) {
// The timestamp is in milliseconds.
return n * 1e6, nil
}
if n < int64(math.MinInt64)/1e6 {
return 0, fmt.Errorf("too small timestamp in milliseconds: %d; must be bigger than %d", n, int64(math.MinInt64)/1e6)
if n < 1e6*(1<<31) && n >= 1e6*(-1<<31) {
// The timestamp is in microseconds.
return n * 1e3, nil
}
n *= 1e6
// The timestamp is in nanoseconds
return n, nil
}

View File

@@ -6,11 +6,11 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logstorage"
)
func TestExtractTimestampRFC3339NanoFromFields_Success(t *testing.T) {
func TestExtractTimestampFromFields_Success(t *testing.T) {
f := func(timeField string, fields []logstorage.Field, nsecsExpected int64) {
t.Helper()
nsecs, err := ExtractTimestampRFC3339NanoFromFields(timeField, fields)
nsecs, err := ExtractTimestampFromFields(timeField, fields)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
@@ -51,6 +51,18 @@ func TestExtractTimestampRFC3339NanoFromFields_Success(t *testing.T) {
{Name: "foo", Value: "bar"},
}, 1718773640123456789)
// Unix timestamp in nanoseconds
f("time", []logstorage.Field{
{Name: "foo", Value: "bar"},
{Name: "time", Value: "1718773640123456789"},
}, 1718773640123456789)
// Unix timestamp in microseconds
f("time", []logstorage.Field{
{Name: "foo", Value: "bar"},
{Name: "time", Value: "1718773640123456"},
}, 1718773640123456000)
// Unix timestamp in milliseconds
f("time", []logstorage.Field{
{Name: "foo", Value: "bar"},
@@ -64,14 +76,14 @@ func TestExtractTimestampRFC3339NanoFromFields_Success(t *testing.T) {
}, 1718773640000000000)
}
func TestExtractTimestampRFC3339NanoFromFields_Error(t *testing.T) {
func TestExtractTimestampFromFields_Error(t *testing.T) {
f := func(s string) {
t.Helper()
fields := []logstorage.Field{
{Name: "time", Value: s},
}
nsecs, err := ExtractTimestampRFC3339NanoFromFields("time", fields)
nsecs, err := ExtractTimestampFromFields("time", fields)
if err == nil {
t.Fatalf("expecting non-nil error")
}
@@ -80,6 +92,7 @@ func TestExtractTimestampRFC3339NanoFromFields_Error(t *testing.T) {
}
}
// invalid time
f("foobar")
// incomplete time

View File

@@ -99,7 +99,7 @@ func readLine(lr *insertutils.LineReader, timeField string, msgFields []string,
if err := p.ParseLogMessage(line); err != nil {
return false, fmt.Errorf("cannot parse json-encoded log entry: %w", err)
}
ts, err := insertutils.ExtractTimestampRFC3339NanoFromFields(timeField, p.Fields)
ts, err := insertutils.ExtractTimestampFromFields(timeField, p.Fields)
if err != nil {
return false, fmt.Errorf("cannot get timestamp: %w", err)
}

View File

@@ -1,6 +1,7 @@
package vlinsert
import (
"fmt"
"net/http"
"strings"
@@ -34,9 +35,15 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
path = strings.TrimPrefix(path, "/insert")
path = strings.ReplaceAll(path, "//", "/")
if path == "/jsonline" {
switch path {
case "/jsonline":
jsonline.RequestHandler(w, r)
return true
case "/ready":
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(200)
fmt.Fprintf(w, `{"status":"ok"}`)
return true
}
switch {
case strings.HasPrefix(path, "/elasticsearch/"):

View File

@@ -560,7 +560,7 @@ func processLine(line []byte, currentYear int, timezone *time.Location, useLocal
if useLocalTimestamp {
ts = time.Now().UnixNano()
} else {
nsecs, err := insertutils.ExtractTimestampRFC3339NanoFromFields("timestamp", p.Fields)
nsecs, err := insertutils.ExtractTimestampFromFields("timestamp", p.Fields)
if err != nil {
return fmt.Errorf("cannot get timestamp from syslog line %q: %w", line, err)
}

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

@@ -270,7 +270,7 @@ func printCommandsHelp(w io.Writer) {
\h - show this help
\s - singleline json output mode
\m - multiline json output mode
\c - compact output
\c - compact output mode
\logfmt - logfmt output mode
\wrap_long_lines - toggles wrapping long lines
\tail <query> - live tail <query> results

View File

@@ -45,6 +45,8 @@ var (
"see https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model")
u64FieldsPerLog = flag.Int("u64FieldsPerLog", 1, "The number of fields with uint64 values to generate per each log entry; "+
"see https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model")
i64FieldsPerLog = flag.Int("i64FieldsPerLog", 1, "The number of fields with int64 values to generate per each log entry; "+
"see https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model")
floatFieldsPerLog = flag.Int("floatFieldsPerLog", 1, "The number of fields with float64 values to generate per each log entry; "+
"see https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model")
ipFieldsPerLog = flag.Int("ipFieldsPerLog", 1, "The number of fields with IPv4 values to generate per each log entry; "+
@@ -254,6 +256,9 @@ func generateLogsAtTimestamp(bw *bufio.Writer, workerID int, ts int64, firstStre
for j := 0; j < *u64FieldsPerLog; j++ {
fmt.Fprintf(bw, `,"u64_%d":"%d"`, j, rand.Uint64())
}
for j := 0; j < *i64FieldsPerLog; j++ {
fmt.Fprintf(bw, `,"i64_%d":"%d"`, j, int64(rand.Uint64()))
}
for j := 0; j < *floatFieldsPerLog; j++ {
fmt.Fprintf(bw, `,"float_%d":"%v"`, j, math.Round(10_000*rand.Float64())/1000)
}

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"math"
"net/http"
"regexp"
"slices"
"sort"
"strconv"
@@ -13,6 +14,7 @@ import (
"time"
"github.com/VictoriaMetrics/metrics"
"github.com/valyala/fastjson"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vlstorage"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
@@ -48,9 +50,10 @@ func ProcessFacetsRequest(ctx context.Context, w http.ResponseWriter, r *http.Re
httpserver.Errorf(w, r, "%s", err)
return
}
keepConstFields := httputils.GetBool(r, "keep_const_fields")
q.DropAllPipes()
q.AddFacetsPipe(limit, maxValuesPerField, maxValueLen)
q.AddFacetsPipe(limit, maxValuesPerField, maxValueLen, keepConstFields)
var mLock sync.Mutex
m := make(map[string][]facetEntry)
@@ -685,13 +688,13 @@ func ProcessStatsQueryRangeRequest(ctx context.Context, w http.ResponseWriter, r
m := make(map[string]*statsSeries)
var mLock sync.Mutex
timestamp := q.GetTimestamp()
writeBlock := func(_ uint, timestamps []int64, columns []logstorage.BlockColumn) {
clonedColumnNames := make([]string, len(columns))
for i, c := range columns {
clonedColumnNames[i] = strings.Clone(c.Name)
}
for i := range timestamps {
timestamp := q.GetTimestamp()
labels := make([]logstorage.Field, 0, len(byFields))
for j, c := range columns {
if c.Name == "_time" {
@@ -1092,18 +1095,20 @@ func parseCommonArgs(r *http.Request) (*logstorage.Query, []logstorage.TenantID,
}
// Parse optional extra_filters
extraFilters, err := getExtraFilters(r, "extra_filters")
extraFiltersStr := r.FormValue("extra_filters")
extraFilters, err := parseExtraFilters(extraFiltersStr)
if err != nil {
return nil, nil, err
}
q.AddExtraFilters(extraFilters)
// Parse optional extra_stream_filters
extraStreamFilters, err := getExtraFilters(r, "extra_stream_filters")
extraStreamFiltersStr := r.FormValue("extra_stream_filters")
extraStreamFilters, err := parseExtraStreamFilters(extraStreamFiltersStr)
if err != nil {
return nil, nil, err
}
q.AddExtraStreamFilters(extraStreamFilters)
q.AddExtraFilters(extraStreamFilters)
return q, tenantIDs, nil
}
@@ -1121,15 +1126,114 @@ func getTimeNsec(r *http.Request, argName string) (int64, bool, error) {
return nsecs, true, nil
}
func getExtraFilters(r *http.Request, argName string) ([]logstorage.Field, error) {
s := r.FormValue(argName)
func parseExtraFilters(s string) (*logstorage.Filter, error) {
if s == "" {
return nil, nil
}
var p logstorage.JSONParser
if err := p.ParseLogMessage([]byte(s)); err != nil {
return nil, fmt.Errorf("cannot parse %s: %w", argName, err)
if !strings.HasPrefix(s, `{"`) {
return logstorage.ParseFilter(s)
}
return p.Fields, nil
// Extra filters in the form {"field":"value",...}.
kvs, err := parseExtraFiltersJSON(s)
if err != nil {
return nil, err
}
filters := make([]string, len(kvs))
for i, kv := range kvs {
if len(kv.values) == 1 {
filters[i] = fmt.Sprintf("%q:=%q", kv.key, kv.values[0])
} else {
orValues := make([]string, len(kv.values))
for j, v := range kv.values {
orValues[j] = fmt.Sprintf("%q", v)
}
filters[i] = fmt.Sprintf("%q:in(%s)", kv.key, strings.Join(orValues, ","))
}
}
s = strings.Join(filters, " ")
return logstorage.ParseFilter(s)
}
func parseExtraStreamFilters(s string) (*logstorage.Filter, error) {
if s == "" {
return nil, nil
}
if !strings.HasPrefix(s, `{"`) {
return logstorage.ParseFilter(s)
}
// Extra stream filters in the form {"field":"value",...}.
kvs, err := parseExtraFiltersJSON(s)
if err != nil {
return nil, err
}
filters := make([]string, len(kvs))
for i, kv := range kvs {
if len(kv.values) == 1 {
filters[i] = fmt.Sprintf("%q=%q", kv.key, kv.values[0])
} else {
orValues := make([]string, len(kv.values))
for j, v := range kv.values {
orValues[j] = regexp.QuoteMeta(v)
}
filters[i] = fmt.Sprintf("%q=~%q", kv.key, strings.Join(orValues, "|"))
}
}
s = "{" + strings.Join(filters, ",") + "}"
return logstorage.ParseFilter(s)
}
type extraFilter struct {
key string
values []string
}
func parseExtraFiltersJSON(s string) ([]extraFilter, error) {
v, err := fastjson.Parse(s)
if err != nil {
return nil, err
}
o := v.GetObject()
var errOuter error
var filters []extraFilter
o.Visit(func(k []byte, v *fastjson.Value) {
if errOuter != nil {
return
}
switch v.Type() {
case fastjson.TypeString:
filters = append(filters, extraFilter{
key: string(k),
values: []string{string(v.GetStringBytes())},
})
case fastjson.TypeArray:
a := v.GetArray()
if len(a) == 0 {
return
}
orValues := make([]string, len(a))
for i, av := range a {
ov, err := av.StringBytes()
if err != nil {
errOuter = fmt.Errorf("cannot obtain string item at the array for key %q; item: %s", k, av)
return
}
orValues[i] = string(ov)
}
filters = append(filters, extraFilter{
key: string(k),
values: orValues,
})
default:
errOuter = fmt.Errorf("unexpected type of value for key %q: %s; value: %s", k, v.Type(), v)
}
})
if errOuter != nil {
return nil, errOuter
}
return filters, nil
}

View File

@@ -0,0 +1,103 @@
package logsql
import (
"testing"
)
func TestParseExtraFilters_Success(t *testing.T) {
f := func(s, resultExpected string) {
t.Helper()
f, err := parseExtraFilters(s)
if err != nil {
t.Fatalf("unexpected error in parseExtraFilters: %s", err)
}
result := f.String()
if result != resultExpected {
t.Fatalf("unexpected result\ngot\n%s\nwant\n%s", result, resultExpected)
}
}
f("", "")
// JSON string
f(`{"foo":"bar"}`, `foo:=bar`)
f(`{"foo":["bar","baz"]}`, `foo:in(bar,baz)`)
f(`{"z":"=b ","c":["d","e,"],"a":[],"_msg":"x"}`, `z:="=b " c:in(d,"e,") =x`)
// LogsQL filter
f(`foobar`, `foobar`)
f(`foo:bar`, `foo:bar`)
f(`foo:(bar or baz) error _time:5m {"foo"=bar,baz="z"}`, `{foo="bar",baz="z"} (foo:bar or foo:baz) error _time:5m`)
}
func TestParseExtraFilters_Failure(t *testing.T) {
f := func(s string) {
t.Helper()
_, err := parseExtraFilters(s)
if err == nil {
t.Fatalf("expecting non-nil error")
}
}
// Invalid JSON
f(`{"foo"}`)
f(`[1,2]`)
f(`{"foo":[1]}`)
// Invliad LogsQL filter
f(`foo:(bar`)
// excess pipe
f(`foo | count()`)
}
func TestParseExtraStreamFilters_Success(t *testing.T) {
f := func(s, resultExpected string) {
t.Helper()
f, err := parseExtraStreamFilters(s)
if err != nil {
t.Fatalf("unexpected error in parseExtraStreamFilters: %s", err)
}
result := f.String()
if result != resultExpected {
t.Fatalf("unexpected result;\ngot\n%s\nwant\n%s", result, resultExpected)
}
}
f("", "")
// JSON string
f(`{"foo":"bar"}`, `{foo="bar"}`)
f(`{"foo":["bar","baz"]}`, `{foo=~"bar|baz"}`)
f(`{"z":"b","c":["d","e|\""],"a":[],"_msg":"x"}`, `{z="b",c=~"d|e\\|\"",_msg="x"}`)
// LogsQL filter
f(`foobar`, `foobar`)
f(`foo:bar`, `foo:bar`)
f(`foo:(bar or baz) error _time:5m {"foo"=bar,baz="z"}`, `{foo="bar",baz="z"} (foo:bar or foo:baz) error _time:5m`)
}
func TestParseExtraStreamFilters_Failure(t *testing.T) {
f := func(s string) {
t.Helper()
_, err := parseExtraStreamFilters(s)
if err == nil {
t.Fatalf("expecting non-nil error")
}
}
// Invalid JSON
f(`{"foo"}`)
f(`[1,2]`)
f(`{"foo":[1]}`)
// Invliad LogsQL filter
f(`foo:(bar`)
// excess pipe
f(`foo | count()`)
}

View File

@@ -1,13 +1,12 @@
{
"files": {
"main.css": "./static/css/main.d05122da.css",
"main.js": "./static/js/main.6082e5a5.js",
"main.css": "./static/css/main.02a1c6cb.css",
"main.js": "./static/js/main.55c8060b.js",
"static/js/685.f772060c.chunk.js": "./static/js/685.f772060c.chunk.js",
"static/media/MetricsQL.md": "./static/media/MetricsQL.a00044c91d9781cf8557.md",
"index.html": "./index.html"
},
"entrypoints": [
"static/css/main.d05122da.css",
"static/js/main.6082e5a5.js"
"static/css/main.02a1c6cb.css",
"static/js/main.55c8060b.js"
]
}

View File

@@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="./favicon.svg"/><link rel="apple-touch-icon" href="./favicon.svg"/><link rel="mask-icon" href="./favicon.svg" color="#000000"><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=5"/><meta name="theme-color" content="#000000"/><meta name="description" content="Explore your log data with VictoriaLogs UI"/><link rel="manifest" href="./manifest.json"/><title>UI for VictoriaLogs</title><meta name="twitter:card" content="summary"><meta name="twitter:title" content="UI for VictoriaLogs"><meta name="twitter:site" content="@https://victoriametrics.com/products/victorialogs/"><meta name="twitter:description" content="Explore your log data with VictoriaLogs UI"><meta name="twitter:image" content="./preview.jpg"><meta property="og:type" content="website"><meta property="og:title" content="UI for VictoriaLogs"><meta property="og:url" content="https://victoriametrics.com/products/victorialogs/"><meta property="og:description" content="Explore your log data with VictoriaLogs UI"><script defer="defer" src="./static/js/main.6082e5a5.js"></script><link href="./static/css/main.d05122da.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="./favicon.svg"/><link rel="apple-touch-icon" href="./favicon.svg"/><link rel="mask-icon" href="./favicon.svg" color="#000000"><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=5"/><meta name="theme-color" content="#000000"/><meta name="description" content="Explore your log data with VictoriaLogs UI"/><link rel="manifest" href="./manifest.json"/><title>UI for VictoriaLogs</title><meta name="twitter:card" content="summary"><meta name="twitter:title" content="UI for VictoriaLogs"><meta name="twitter:site" content="@https://victoriametrics.com/products/victorialogs/"><meta name="twitter:description" content="Explore your log data with VictoriaLogs UI"><meta name="twitter:image" content="./preview.jpg"><meta property="og:type" content="website"><meta property="og:title" content="UI for VictoriaLogs"><meta property="og:url" content="https://victoriametrics.com/products/victorialogs/"><meta property="og:description" content="Explore your log data with VictoriaLogs UI"><script defer="defer" src="./static/js/main.55c8060b.js"></script><link href="./static/css/main.02a1c6cb.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -30,6 +30,7 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/lib/auth"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/buildinfo"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/cgroup"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/envflag"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/flagutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httpserver"
@@ -45,6 +46,7 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/opentelemetry/firehose"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/pushmetrics"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/stringsutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/timeserieslimits"
)
var (
@@ -77,6 +79,9 @@ var (
dryRun = flag.Bool("dryRun", false, "Whether to check config files without running vmagent. The following files are checked: "+
"-promscrape.config, -remoteWrite.relabelConfig, -remoteWrite.urlRelabelConfig, -remoteWrite.streamAggr.config . "+
"Unknown config entries aren't allowed in -promscrape.config by default. This can be changed by passing -promscrape.config.strictParse=false command-line flag")
maxLabelsPerTimeseries = flag.Int("maxLabelsPerTimeseries", 0, "The maximum number of labels per time series to be accepted. Series with superfluous labels are ignored. In this case the vm_rows_ignored_total{reason=\"too_many_labels\"} metric at /metrics page is incremented")
maxLabelNameLen = flag.Int("maxLabelNameLen", 0, "The maximum length of label names in the accepted time series. Series with longer label name are ignored. In this case the vm_rows_ignored_total{reason=\"too_long_label_name\"} metric at /metrics page is incremented")
maxLabelValueLen = flag.Int("maxLabelValueLen", 0, "The maximum length of label values in the accepted time series. Series with longer label value are ignored. In this case the vm_rows_ignored_total{reason=\"too_long_label_value\"} metric at /metrics page is incremented")
)
var (
@@ -93,6 +98,15 @@ var (
)
func main() {
// vmagent is optimized for reduced memory allocations,
// so it can run with the reduced GOGC in order to reduce the used memory,
// while keeping CPU usage spent in GC at low levels.
//
// Some workloads may need increased GOGC values. Then such values can be set via GOGC environment variable.
// It is recommended increasing GOGC if go_memstats_gc_cpu_fraction metric exposed at /metrics page
// exceeds 0.05 for extended periods of time.
cgroup.SetGOGC(30)
// Write flags and help message to stdout, since it is easier to grep or pipe.
flag.CommandLine.SetOutput(os.Stdout)
flag.Usage = usage
@@ -100,6 +114,7 @@ func main() {
remotewrite.InitSecretFlags()
buildinfo.Init()
logger.Init()
timeserieslimits.Init(*maxLabelsPerTimeseries, *maxLabelNameLen, *maxLabelValueLen)
if promscrape.IsDryRun() {
if err := promscrape.CheckConfig(); err != nil {

View File

@@ -7,13 +7,10 @@ import (
"net/url"
"path/filepath"
"slices"
"strconv"
"sync"
"sync/atomic"
"time"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httpserver"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/auth"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bloomfilter"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
@@ -21,6 +18,7 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/flagutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httpserver"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/memory"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/persistentqueue"
@@ -30,6 +28,7 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutils"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/ratelimiter"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/streamaggr"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/timeserieslimits"
"github.com/VictoriaMetrics/metrics"
"github.com/cespare/xxhash/v2"
)
@@ -472,6 +471,15 @@ func tryPush(at *auth.Token, wr *prompbmarshal.WriteRequest, forceDropSamplesOnF
rowsCountAfterRelabel := getRowsCount(tssBlock)
rowsDroppedByGlobalRelabel.Add(rowsCountBeforeRelabel - rowsCountAfterRelabel)
}
if timeserieslimits.Enabled() {
tmpBlock := tssBlock[:0]
for _, ts := range tssBlock {
if !timeserieslimits.IsExceeding(ts.Labels) {
tmpBlock = append(tmpBlock, ts)
}
}
tssBlock = tmpBlock
}
sortLabelsIfNeeded(tssBlock)
tssBlock = limitSeriesCardinality(tssBlock)
if sas.IsEnabled() {
@@ -716,29 +724,14 @@ func logSkippedSeries(labels []prompbmarshal.Label, flagName string, flagValue i
select {
case <-logSkippedSeriesTicker.C:
// Do not use logger.WithThrottler() here, since this will increase CPU usage
// because every call to logSkippedSeries will result to a call to labelsToString.
logger.Warnf("skip series %s because %s=%d reached", labelsToString(labels), flagName, flagValue)
// because every call to logSkippedSeries will result to a call to prompbmarshal.LabelsToString.
logger.Warnf("skip series %s because %s=%d reached", prompbmarshal.LabelsToString(labels), flagName, flagValue)
default:
}
}
var logSkippedSeriesTicker = time.NewTicker(5 * time.Second)
func labelsToString(labels []prompbmarshal.Label) string {
var b []byte
b = append(b, '{')
for i, label := range labels {
b = append(b, label.Name...)
b = append(b, '=')
b = strconv.AppendQuote(b, label.Value)
if i+1 < len(labels) {
b = append(b, ',')
}
}
b = append(b, '}')
return string(b)
}
var (
globalRowsPushedBeforeRelabel = metrics.NewCounter("vmagent_remotewrite_global_rows_pushed_before_relabel_total")
rowsDroppedByGlobalRelabel = metrics.NewCounter("vmagent_remotewrite_global_relabel_metrics_dropped_total")

View File

@@ -51,9 +51,14 @@ Examples:
Usage: `Optional external URL to template in rule's labels or annotations.`,
Required: false,
},
&cli.StringFlag{
Name: "loggerLevel",
Usage: `Minimum level of errors to log. Possible values: INFO, WARN, ERROR, FATAL, PANIC (default "ERROR").`,
Required: false,
},
},
Action: func(c *cli.Context) error {
if failed := unittest.UnitTest(c.StringSlice("files"), c.Bool("disableAlertgroupLabel"), c.StringSlice("external.label"), c.String("external.url")); failed {
if failed := unittest.UnitTest(c.StringSlice("files"), c.Bool("disableAlertgroupLabel"), c.StringSlice("external.label"), c.String("external.url"), c.String("loggerLevel")); failed {
return fmt.Errorf("unittest failed")
}
return nil

View File

@@ -5,6 +5,7 @@ import (
"flag"
"fmt"
"net/http"
"net/url"
"os"
"path/filepath"
"reflect"
@@ -46,17 +47,24 @@ var (
testRemoteWritePath = "http://127.0.0.1" + httpListenAddr
testHealthHTTPPath = "http://127.0.0.1" + httpListenAddr + "/health"
testLogLevel = "ERROR"
disableAlertgroupLabel bool
)
const (
testStoragePath = "vmalert-unittest"
testLogLevel = "ERROR"
)
// UnitTest runs unittest for files
func UnitTest(files []string, disableGroupLabel bool, externalLabels []string, externalURL string) bool {
if err := templates.Load([]string{}, true); err != nil {
func UnitTest(files []string, disableGroupLabel bool, externalLabels []string, externalURL, logLevel string) bool {
if logLevel != "" {
testLogLevel = logLevel
}
eu, err := url.Parse(externalURL)
if err != nil {
logger.Fatalf("failed to parse external URL: %w", err)
}
if err := templates.Load([]string{}, *eu); err != nil {
logger.Fatalf("failed to load template: %v", err)
}
storagePath = filepath.Join(os.TempDir(), testStoragePath)

View File

@@ -1,24 +1,14 @@
package unittest
import (
"os"
"testing"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/templates"
)
func TestMain(m *testing.M) {
if err := templates.Load([]string{}, true); err != nil {
os.Exit(1)
}
os.Exit(m.Run())
}
func TestUnitTest_Failure(t *testing.T) {
f := func(files []string) {
t.Helper()
failed := UnitTest(files, false, nil, "")
failed := UnitTest(files, false, nil, "", "")
if !failed {
t.Fatalf("expecting failed test")
}
@@ -33,7 +23,7 @@ func TestUnitTest_Success(t *testing.T) {
f := func(disableGroupLabel bool, files []string, externalLabels []string, externalURL string) {
t.Helper()
failed := UnitTest(files, disableGroupLabel, externalLabels, externalURL)
failed := UnitTest(files, disableGroupLabel, externalLabels, externalURL, "")
if failed {
t.Fatalf("unexpected failed test")
}

View File

@@ -16,7 +16,7 @@ import (
)
func TestMain(m *testing.M) {
if err := templates.Load([]string{"testdata/templates/*good.tmpl"}, true); 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,7 @@ groups:
labels:
label: bar
annotations:
summary: "{{ $value }"
summary: "{{ }}"
description: "{{$labels}}"
- alert: UnkownAnnotationsFunction
for: 5m

View File

@@ -81,7 +81,10 @@ absolute path to all .tpl files in root.
dryRun = flag.Bool("dryRun", false, "Whether to check only config files without running vmalert. The rules file are validated. The -rule flag must be specified.")
)
var alertURLGeneratorFn notifier.AlertURLGenerator
var (
alertURLGeneratorFn notifier.AlertURLGenerator
extURL *url.URL
)
func main() {
// Write flags and help message to stdout, since it is easier to grep or pipe.
@@ -95,9 +98,15 @@ func main() {
buildinfo.Init()
logger.Init()
err := templates.Load(*ruleTemplatesPath, true)
var err error
extURL, err = getExternalURL(*externalURL)
if err != nil {
logger.Fatalf("failed to parse %q: %s", *ruleTemplatesPath, err)
logger.Fatalf("failed to init external.url %q: %s", *externalURL, err)
}
err = templates.Load(*ruleTemplatesPath, *extURL)
if err != nil {
logger.Fatalf("failed to load template %q: %s", *ruleTemplatesPath, err)
}
if *dryRun {
@@ -111,12 +120,7 @@ func main() {
return
}
eu, err := getExternalURL(*externalURL)
if err != nil {
logger.Fatalf("failed to init `-external.url`: %s", err)
}
alertURLGeneratorFn, err = getAlertURLGenerator(eu, *externalAlertSource, *validateTemplates)
alertURLGeneratorFn, err = getAlertURLGenerator(extURL, *externalAlertSource, *validateTemplates)
if err != nil {
logger.Fatalf("failed to init `external.alert.source`: %s", err)
}
@@ -304,7 +308,7 @@ func getAlertURLGenerator(externalURL *url.URL, externalAlertSource string, vali
}
templated, err := alert.ExecTemplate(qFn, alert.Labels, m)
if err != nil {
logger.Errorf("can not exec source template %s", err)
logger.Errorf("cannot template alert source: %s", err)
}
return fmt.Sprintf("%s/%s", externalURL, templated["tpl"])
}, nil
@@ -359,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.Load(*ruleTemplatesPath, false)
err := templates.Load(*ruleTemplatesPath, *extURL)
if err != nil {
setConfigError(err)
logger.Errorf("failed to load new templates: %s", err)

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

@@ -3,6 +3,7 @@ package main
import (
"context"
"math/rand"
"net/url"
"os"
"strings"
"sync"
@@ -18,7 +19,7 @@ import (
)
func TestMain(m *testing.M) {
if err := templates.Load([]string{"testdata/templates/*good.tmpl"}, true); err != nil {
if err := templates.Load([]string{"testdata/templates/*good.tmpl"}, url.URL{}); err != nil {
os.Exit(1)
}
os.Exit(m.Run())

View File

@@ -127,7 +127,7 @@ func ExecTemplate(q templates.QueryFn, annotations map[string]string, tplData Al
// ValidateTemplates validate annotations for possible template error, uses empty data for template population
func ValidateTemplates(annotations map[string]string) error {
tmpl, err := templates.Get()
tmpl, err := templates.GetWithFuncs(nil)
if err != nil {
return err
}
@@ -146,12 +146,21 @@ func templateAnnotations(annotations map[string]string, data AlertTplData, tmpl
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)
if err := templateAnnotation(&buf, builder.String(), tData, tmpl, execute); err != nil {
// 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

View File

@@ -75,7 +75,13 @@ func TestAlertExecTemplate(t *testing.T) {
Labels: map[string]string{
"instance": "localhost",
},
}, map[string]string{}, map[string]string{})
}, 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{
@@ -93,6 +99,19 @@ func TestAlertExecTemplate(t *testing.T) {
"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`,

View File

@@ -13,6 +13,7 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/utils"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httputils"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promauth"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promrelabel"
)
@@ -69,7 +70,17 @@ func (am *AlertManager) Send(ctx context.Context, alerts []Alert, headers map[st
func (am *AlertManager) send(ctx context.Context, alerts []Alert, headers map[string]string) error {
b := &bytes.Buffer{}
writeamRequest(b, alerts, am.argFunc, am.relabelConfigs)
alertsToSend := alerts[:0]
lblss := make([][]prompbmarshal.Label, 0, len(alerts))
for _, a := range alerts {
lbls := a.applyRelabelingIfNeeded(am.relabelConfigs)
if len(lbls) == 0 {
continue
}
alertsToSend = append(alertsToSend, a)
lblss = append(lblss, lbls)
}
writeamRequest(b, alertsToSend, am.argFunc, lblss)
req, err := http.NewRequest(http.MethodPost, am.addr.String(), b)
if err != nil {

View File

@@ -1,15 +1,14 @@
{% import (
"time"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promrelabel"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
) %}
{% stripspace %}
{% func amRequest(alerts []Alert, generatorURL func(Alert) string, relabelCfg *promrelabel.ParsedConfigs) %}
{% func amRequest(alerts []Alert, generatorURL func(Alert) string, lblss [][]prompbmarshal.Label) %}
[
{% for i, alert := range alerts %}
{% code lbls := alert.applyRelabelingIfNeeded(relabelCfg) %}
{% if len(lbls) == 0 %} {% continue %} {% endif %}
{% code lbls := lblss[i] %}
{
"startsAt":{%q= alert.Start.Format(time.RFC3339Nano) %},
"generatorURL": {%q= generatorURL(alert) %},

View File

@@ -8,7 +8,7 @@ package notifier
import (
"time"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promrelabel"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
)
//line app/vmalert/notifier/alertmanager_request.qtpl:8
@@ -25,122 +25,116 @@ var (
)
//line app/vmalert/notifier/alertmanager_request.qtpl:8
func streamamRequest(qw422016 *qt422016.Writer, alerts []Alert, generatorURL func(Alert) string, relabelCfg *promrelabel.ParsedConfigs) {
func streamamRequest(qw422016 *qt422016.Writer, alerts []Alert, generatorURL func(Alert) string, lblss [][]prompbmarshal.Label) {
//line app/vmalert/notifier/alertmanager_request.qtpl:8
qw422016.N().S(`[`)
//line app/vmalert/notifier/alertmanager_request.qtpl:10
for i, alert := range alerts {
//line app/vmalert/notifier/alertmanager_request.qtpl:11
lbls := alert.applyRelabelingIfNeeded(relabelCfg)
lbls := lblss[i]
//line app/vmalert/notifier/alertmanager_request.qtpl:12
if len(lbls) == 0 {
//line app/vmalert/notifier/alertmanager_request.qtpl:12
continue
//line app/vmalert/notifier/alertmanager_request.qtpl:12
}
//line app/vmalert/notifier/alertmanager_request.qtpl:12
//line app/vmalert/notifier/alertmanager_request.qtpl:11
qw422016.N().S(`{"startsAt":`)
//line app/vmalert/notifier/alertmanager_request.qtpl:14
//line app/vmalert/notifier/alertmanager_request.qtpl:13
qw422016.N().Q(alert.Start.Format(time.RFC3339Nano))
//line app/vmalert/notifier/alertmanager_request.qtpl:14
//line app/vmalert/notifier/alertmanager_request.qtpl:13
qw422016.N().S(`,"generatorURL":`)
//line app/vmalert/notifier/alertmanager_request.qtpl:15
//line app/vmalert/notifier/alertmanager_request.qtpl:14
qw422016.N().Q(generatorURL(alert))
//line app/vmalert/notifier/alertmanager_request.qtpl:15
//line app/vmalert/notifier/alertmanager_request.qtpl:14
qw422016.N().S(`,`)
//line app/vmalert/notifier/alertmanager_request.qtpl:16
//line app/vmalert/notifier/alertmanager_request.qtpl:15
if !alert.End.IsZero() {
//line app/vmalert/notifier/alertmanager_request.qtpl:16
//line app/vmalert/notifier/alertmanager_request.qtpl:15
qw422016.N().S(`"endsAt":`)
//line app/vmalert/notifier/alertmanager_request.qtpl:17
//line app/vmalert/notifier/alertmanager_request.qtpl:16
qw422016.N().Q(alert.End.Format(time.RFC3339Nano))
//line app/vmalert/notifier/alertmanager_request.qtpl:17
//line app/vmalert/notifier/alertmanager_request.qtpl:16
qw422016.N().S(`,`)
//line app/vmalert/notifier/alertmanager_request.qtpl:18
//line app/vmalert/notifier/alertmanager_request.qtpl:17
}
//line app/vmalert/notifier/alertmanager_request.qtpl:18
//line app/vmalert/notifier/alertmanager_request.qtpl:17
qw422016.N().S(`"labels": {`)
//line app/vmalert/notifier/alertmanager_request.qtpl:20
//line app/vmalert/notifier/alertmanager_request.qtpl:19
ll := len(lbls)
//line app/vmalert/notifier/alertmanager_request.qtpl:21
//line app/vmalert/notifier/alertmanager_request.qtpl:20
for idx, l := range lbls {
//line app/vmalert/notifier/alertmanager_request.qtpl:22
//line app/vmalert/notifier/alertmanager_request.qtpl:21
qw422016.N().Q(l.Name)
//line app/vmalert/notifier/alertmanager_request.qtpl:22
//line app/vmalert/notifier/alertmanager_request.qtpl:21
qw422016.N().S(`:`)
//line app/vmalert/notifier/alertmanager_request.qtpl:22
//line app/vmalert/notifier/alertmanager_request.qtpl:21
qw422016.N().Q(l.Value)
//line app/vmalert/notifier/alertmanager_request.qtpl:22
//line app/vmalert/notifier/alertmanager_request.qtpl:21
if idx != ll-1 {
//line app/vmalert/notifier/alertmanager_request.qtpl:22
//line app/vmalert/notifier/alertmanager_request.qtpl:21
qw422016.N().S(`,`)
//line app/vmalert/notifier/alertmanager_request.qtpl:22
//line app/vmalert/notifier/alertmanager_request.qtpl:21
}
//line app/vmalert/notifier/alertmanager_request.qtpl:23
//line app/vmalert/notifier/alertmanager_request.qtpl:22
}
//line app/vmalert/notifier/alertmanager_request.qtpl:23
//line app/vmalert/notifier/alertmanager_request.qtpl:22
qw422016.N().S(`},"annotations": {`)
//line app/vmalert/notifier/alertmanager_request.qtpl:26
//line app/vmalert/notifier/alertmanager_request.qtpl:25
c := len(alert.Annotations)
//line app/vmalert/notifier/alertmanager_request.qtpl:27
//line app/vmalert/notifier/alertmanager_request.qtpl:26
for k, v := range alert.Annotations {
//line app/vmalert/notifier/alertmanager_request.qtpl:28
//line app/vmalert/notifier/alertmanager_request.qtpl:27
c = c - 1
//line app/vmalert/notifier/alertmanager_request.qtpl:29
//line app/vmalert/notifier/alertmanager_request.qtpl:28
qw422016.N().Q(k)
//line app/vmalert/notifier/alertmanager_request.qtpl:29
//line app/vmalert/notifier/alertmanager_request.qtpl:28
qw422016.N().S(`:`)
//line app/vmalert/notifier/alertmanager_request.qtpl:29
//line app/vmalert/notifier/alertmanager_request.qtpl:28
qw422016.N().Q(v)
//line app/vmalert/notifier/alertmanager_request.qtpl:29
//line app/vmalert/notifier/alertmanager_request.qtpl:28
if c > 0 {
//line app/vmalert/notifier/alertmanager_request.qtpl:29
//line app/vmalert/notifier/alertmanager_request.qtpl:28
qw422016.N().S(`,`)
//line app/vmalert/notifier/alertmanager_request.qtpl:29
//line app/vmalert/notifier/alertmanager_request.qtpl:28
}
//line app/vmalert/notifier/alertmanager_request.qtpl:30
//line app/vmalert/notifier/alertmanager_request.qtpl:29
}
//line app/vmalert/notifier/alertmanager_request.qtpl:30
//line app/vmalert/notifier/alertmanager_request.qtpl:29
qw422016.N().S(`}}`)
//line app/vmalert/notifier/alertmanager_request.qtpl:33
//line app/vmalert/notifier/alertmanager_request.qtpl:32
if i != len(alerts)-1 {
//line app/vmalert/notifier/alertmanager_request.qtpl:33
//line app/vmalert/notifier/alertmanager_request.qtpl:32
qw422016.N().S(`,`)
//line app/vmalert/notifier/alertmanager_request.qtpl:33
//line app/vmalert/notifier/alertmanager_request.qtpl:32
}
//line app/vmalert/notifier/alertmanager_request.qtpl:34
//line app/vmalert/notifier/alertmanager_request.qtpl:33
}
//line app/vmalert/notifier/alertmanager_request.qtpl:34
//line app/vmalert/notifier/alertmanager_request.qtpl:33
qw422016.N().S(`]`)
//line app/vmalert/notifier/alertmanager_request.qtpl:36
//line app/vmalert/notifier/alertmanager_request.qtpl:35
}
//line app/vmalert/notifier/alertmanager_request.qtpl:36
func writeamRequest(qq422016 qtio422016.Writer, alerts []Alert, generatorURL func(Alert) string, relabelCfg *promrelabel.ParsedConfigs) {
//line app/vmalert/notifier/alertmanager_request.qtpl:36
//line app/vmalert/notifier/alertmanager_request.qtpl:35
func writeamRequest(qq422016 qtio422016.Writer, alerts []Alert, generatorURL func(Alert) string, lblss [][]prompbmarshal.Label) {
//line app/vmalert/notifier/alertmanager_request.qtpl:35
qw422016 := qt422016.AcquireWriter(qq422016)
//line app/vmalert/notifier/alertmanager_request.qtpl:36
streamamRequest(qw422016, alerts, generatorURL, relabelCfg)
//line app/vmalert/notifier/alertmanager_request.qtpl:36
//line app/vmalert/notifier/alertmanager_request.qtpl:35
streamamRequest(qw422016, alerts, generatorURL, lblss)
//line app/vmalert/notifier/alertmanager_request.qtpl:35
qt422016.ReleaseWriter(qw422016)
//line app/vmalert/notifier/alertmanager_request.qtpl:36
//line app/vmalert/notifier/alertmanager_request.qtpl:35
}
//line app/vmalert/notifier/alertmanager_request.qtpl:36
func amRequest(alerts []Alert, generatorURL func(Alert) string, relabelCfg *promrelabel.ParsedConfigs) string {
//line app/vmalert/notifier/alertmanager_request.qtpl:36
//line app/vmalert/notifier/alertmanager_request.qtpl:35
func amRequest(alerts []Alert, generatorURL func(Alert) string, lblss [][]prompbmarshal.Label) string {
//line app/vmalert/notifier/alertmanager_request.qtpl:35
qb422016 := qt422016.AcquireByteBuffer()
//line app/vmalert/notifier/alertmanager_request.qtpl:36
writeamRequest(qb422016, alerts, generatorURL, relabelCfg)
//line app/vmalert/notifier/alertmanager_request.qtpl:36
//line app/vmalert/notifier/alertmanager_request.qtpl:35
writeamRequest(qb422016, alerts, generatorURL, lblss)
//line app/vmalert/notifier/alertmanager_request.qtpl:35
qs422016 := string(qb422016.B)
//line app/vmalert/notifier/alertmanager_request.qtpl:36
//line app/vmalert/notifier/alertmanager_request.qtpl:35
qt422016.ReleaseByteBuffer(qb422016)
//line app/vmalert/notifier/alertmanager_request.qtpl:36
//line app/vmalert/notifier/alertmanager_request.qtpl:35
return qs422016
//line app/vmalert/notifier/alertmanager_request.qtpl:36
//line app/vmalert/notifier/alertmanager_request.qtpl:35
}

View File

@@ -105,6 +105,16 @@ func TestAlertManager_Send(t *testing.T) {
if r.Header.Get(headerKey) != "bar" {
t.Fatalf("expected header %q to be set to %q; got %q instead", headerKey, "bar", r.Header.Get(headerKey))
}
case 4:
var a []struct {
Labels map[string]string `json:"labels"`
}
if err := json.NewDecoder(r.Body).Decode(&a); err != nil {
t.Fatalf("can not unmarshal data into alert %s", err)
}
if len(a) != 1 {
t.Fatalf("expected 1 alert in array got %d", len(a))
}
}
})
srv := httptest.NewServer(mux)
@@ -168,7 +178,20 @@ func TestAlertManager_Send(t *testing.T) {
t.Fatalf("unexpected error %s", err)
}
if c != 3 {
t.Fatalf("expected 3 calls(count from zero) to server got %d", c)
if err := am.Send(context.Background(), []Alert{
{
Name: "alert1",
Labels: map[string]string{"rule": "test"},
},
{
Name: "alert2",
Labels: map[string]string{},
},
}, map[string]string{}); err != nil {
t.Fatalf("unexpected error %s", err)
}
if c != 4 {
t.Fatalf("expected 4 calls(count from zero) to server got %d", c)
}
}

View File

@@ -7,7 +7,6 @@ import (
"strings"
"time"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/templates"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/flagutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promauth"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutils"
@@ -93,13 +92,11 @@ var (
func Init(gen AlertURLGenerator, extLabels map[string]string, extURL string) (func() []Notifier, error) {
externalURL = extURL
externalLabels = extLabels
eu, err := url.Parse(externalURL)
_, err := url.Parse(externalURL)
if err != nil {
return nil, fmt.Errorf("failed to parse external URL: %w", err)
}
templates.UpdateWithFuncs(templates.FuncsWithExternalURL(eu))
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")

View File

@@ -1,13 +1,15 @@
package notifier
import (
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/templates"
"net/url"
"os"
"testing"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/templates"
)
func TestMain(m *testing.M) {
if err := templates.Load([]string{"testdata/templates/*good.tmpl"}, true); err != nil {
if err := templates.Load([]string{"testdata/templates/*good.tmpl"}, url.URL{}); err != nil {
os.Exit(1)
}
os.Exit(m.Run())

View File

@@ -36,7 +36,7 @@ var (
maxQueueSize = flag.Int("remoteWrite.maxQueueSize", defaultMaxQueueSize, "Defines the max number of pending datapoints to remote write endpoint")
maxBatchSize = flag.Int("remoteWrite.maxBatchSize", defaultMaxBatchSize, "Defines max number of timeseries to be flushed at once")
concurrency = flag.Int("remoteWrite.concurrency", defaultConcurrency, "Defines number of writers for concurrent writing into remote write endpoint")
concurrency = flag.Int("remoteWrite.concurrency", defaultConcurrency, "Defines number of writers for concurrent writing into remote write endpoint. Default value depends on the number of available CPU cores.")
flushInterval = flag.Duration("remoteWrite.flushInterval", defaultFlushInterval, "Defines interval of flushes to remote write endpoint")
tlsInsecureSkipVerify = flag.Bool("remoteWrite.tlsInsecureSkipVerify", false, "Whether to skip tls verification when connecting to -remoteWrite.url")

View File

@@ -614,7 +614,7 @@ func (ar *AlertingRule) alertToTimeSeries(a *notifier.Alert, timestamp int64) []
}
func alertToTimeSeries(a *notifier.Alert, timestamp int64) prompbmarshal.TimeSeries {
var labels []prompbmarshal.Label
labels := make([]prompbmarshal.Label, 0, len(a.Labels)+2)
for k, v := range a.Labels {
labels = append(labels, prompbmarshal.Label{
Name: k,
@@ -634,7 +634,7 @@ func alertToTimeSeries(a *notifier.Alert, timestamp int64) prompbmarshal.TimeSer
// alertForToTimeSeries returns a time series that represents
// state of active alerts, where value is time when alert become active
func alertForToTimeSeries(a *notifier.Alert, timestamp int64) prompbmarshal.TimeSeries {
var labels []prompbmarshal.Label
labels := make([]prompbmarshal.Label, 0, len(a.Labels)+1)
for k, v := range a.Labels {
labels = append(labels, prompbmarshal.Label{
Name: k,
@@ -650,21 +650,24 @@ func alertForToTimeSeries(a *notifier.Alert, timestamp int64) prompbmarshal.Time
// for alerts which changed their state from Pending to Inactive or Firing.
func pendingAlertStaleTimeSeries(ls map[string]string, timestamp int64, includeAlertForState bool) []prompbmarshal.TimeSeries {
var result []prompbmarshal.TimeSeries
var baseLabels []prompbmarshal.Label
baseLabels := make([]prompbmarshal.Label, 0, len(ls)+1)
for k, v := range ls {
baseLabels = append(baseLabels, prompbmarshal.Label{
Name: k,
Value: v,
})
}
alertsLabels := make([]prompbmarshal.Label, 0, len(ls)+2)
alertsLabels = append(alertsLabels, baseLabels...)
// __name__ already been dropped, no need to check duplication
alertsLabels := append(baseLabels, prompbmarshal.Label{Name: "__name__", Value: alertMetricName})
alertsLabels = append(alertsLabels, prompbmarshal.Label{Name: "__name__", Value: alertMetricName})
alertsLabels = append(alertsLabels, prompbmarshal.Label{Name: alertStateLabel, Value: notifier.StatePending.String()})
result = append(result, newTimeSeries([]float64{decimal.StaleNaN}, []int64{timestamp}, alertsLabels))
if includeAlertForState {
alertsForStateLabels := append(baseLabels, prompbmarshal.Label{Name: "__name__", Value: alertForStateMetricName})
result = append(result, newTimeSeries([]float64{decimal.StaleNaN}, []int64{timestamp}, alertsForStateLabels))
baseLabels = append(baseLabels, prompbmarshal.Label{Name: "__name__", Value: alertForStateMetricName})
result = append(result, newTimeSeries([]float64{decimal.StaleNaN}, []int64{timestamp}, baseLabels))
}
return result
}
@@ -672,22 +675,25 @@ func pendingAlertStaleTimeSeries(ls map[string]string, timestamp int64, includeA
// firingAlertStaleTimeSeries returns stale `ALERTS` and `ALERTS_FOR_STATE` time series
// for alerts which changed their state from Firing to Inactive.
func firingAlertStaleTimeSeries(ls map[string]string, timestamp int64) []prompbmarshal.TimeSeries {
var baseLabels []prompbmarshal.Label
baseLabels := make([]prompbmarshal.Label, 0, len(ls)+1)
for k, v := range ls {
baseLabels = append(baseLabels, prompbmarshal.Label{
Name: k,
Value: v,
})
}
alertsLabels := make([]prompbmarshal.Label, 0, len(ls)+2)
alertsLabels = append(alertsLabels, baseLabels...)
// __name__ already been dropped, no need to check duplication
alertsLabels := append(baseLabels, prompbmarshal.Label{Name: "__name__", Value: alertMetricName})
alertsLabels = append(alertsLabels, prompbmarshal.Label{Name: "__name__", Value: alertMetricName})
alertsLabels = append(alertsLabels, prompbmarshal.Label{Name: alertStateLabel, Value: notifier.StateFiring.String()})
alertsForStateLabels := append(baseLabels, prompbmarshal.Label{Name: "__name__", Value: alertForStateMetricName})
baseLabels = append(baseLabels, prompbmarshal.Label{Name: "__name__", Value: alertForStateMetricName})
return []prompbmarshal.TimeSeries{
newTimeSeries([]float64{decimal.StaleNaN}, []int64{timestamp}, alertsLabels),
newTimeSeries([]float64{decimal.StaleNaN}, []int64{timestamp}, alertsForStateLabels),
newTimeSeries([]float64{decimal.StaleNaN}, []int64{timestamp}, baseLabels),
}
}

View File

@@ -252,10 +252,14 @@ func TestAlertingRule_Exec(t *testing.T) {
},
map[int][]prompbmarshal.TimeSeries{
0: {
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "empty_labels"}, {Name: "alertstate", Value: "firing"}},
Samples: []prompbmarshal.Sample{{Value: 1, Timestamp: ts.UnixNano() / 1e6}}},
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "empty_labels"}},
Samples: []prompbmarshal.Sample{{Value: float64(ts.Unix()), Timestamp: ts.UnixNano() / 1e6}}},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "empty_labels"}, {Name: "alertstate", Value: "firing"}},
Samples: []prompbmarshal.Sample{{Value: 1, Timestamp: ts.UnixNano() / 1e6}},
},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "empty_labels"}},
Samples: []prompbmarshal.Sample{{Value: float64(ts.Unix()), Timestamp: ts.UnixNano() / 1e6}},
},
},
})
@@ -273,22 +277,34 @@ func TestAlertingRule_Exec(t *testing.T) {
4: {{labels: []string{"name", "foo"}, alert: &notifier.Alert{State: notifier.StateInactive}}},
}, map[int][]prompbmarshal.TimeSeries{
0: {
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "single-firing=>inactive=>firing=>inactive=>inactive"}, {Name: "alertstate", Value: "firing"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: 1, Timestamp: ts.UnixNano() / 1e6}}},
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "single-firing=>inactive=>firing=>inactive=>inactive"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: float64(ts.Unix()), Timestamp: ts.UnixNano() / 1e6}}},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "single-firing=>inactive=>firing=>inactive=>inactive"}, {Name: "alertstate", Value: "firing"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: 1, Timestamp: ts.UnixNano() / 1e6}},
},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "single-firing=>inactive=>firing=>inactive=>inactive"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: float64(ts.Unix()), Timestamp: ts.UnixNano() / 1e6}},
},
},
1: {
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "single-firing=>inactive=>firing=>inactive=>inactive"}, {Name: "alertstate", Value: "firing"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: decimal.StaleNaN, Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}}},
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "single-firing=>inactive=>firing=>inactive=>inactive"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: decimal.StaleNaN, Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}}},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "single-firing=>inactive=>firing=>inactive=>inactive"}, {Name: "alertstate", Value: "firing"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: decimal.StaleNaN, Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}},
},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "single-firing=>inactive=>firing=>inactive=>inactive"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: decimal.StaleNaN, Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}},
},
},
2: {
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "single-firing=>inactive=>firing=>inactive=>inactive"}, {Name: "alertstate", Value: "firing"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: 1, Timestamp: ts.Add(2*defaultStep).UnixNano() / 1e6}}},
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "single-firing=>inactive=>firing=>inactive=>inactive"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: float64(ts.Add(2 * defaultStep).Unix()), Timestamp: ts.Add(2*defaultStep).UnixNano() / 1e6}}},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "single-firing=>inactive=>firing=>inactive=>inactive"}, {Name: "alertstate", Value: "firing"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: 1, Timestamp: ts.Add(2*defaultStep).UnixNano() / 1e6}},
},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "single-firing=>inactive=>firing=>inactive=>inactive"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: float64(ts.Add(2 * defaultStep).Unix()), Timestamp: ts.Add(2*defaultStep).UnixNano() / 1e6}},
},
},
})
@@ -344,34 +360,54 @@ func TestAlertingRule_Exec(t *testing.T) {
},
}, map[int][]prompbmarshal.TimeSeries{
0: {
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "multiple-steps-firing"}, {Name: "alertstate", Value: "firing"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: 1, Timestamp: ts.UnixNano() / 1e6}}},
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "multiple-steps-firing"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: float64(ts.Unix()), Timestamp: ts.UnixNano() / 1e6}}},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "multiple-steps-firing"}, {Name: "alertstate", Value: "firing"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: 1, Timestamp: ts.UnixNano() / 1e6}},
},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "multiple-steps-firing"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: float64(ts.Unix()), Timestamp: ts.UnixNano() / 1e6}},
},
},
1: {
// stale time series for foo, `firing -> inactive`
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "multiple-steps-firing"}, {Name: "alertstate", Value: "firing"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: decimal.StaleNaN, Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}}},
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "multiple-steps-firing"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: decimal.StaleNaN, Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}}},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "multiple-steps-firing"}, {Name: "alertstate", Value: "firing"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: decimal.StaleNaN, Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}},
},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "multiple-steps-firing"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: decimal.StaleNaN, Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}},
},
// new time series for foo1
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "multiple-steps-firing"}, {Name: "alertstate", Value: "firing"}, {Name: "name", Value: "foo1"}},
Samples: []prompbmarshal.Sample{{Value: 1, Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}}},
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "multiple-steps-firing"}, {Name: "name", Value: "foo1"}},
Samples: []prompbmarshal.Sample{{Value: float64(ts.Add(defaultStep).Unix()), Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}}},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "multiple-steps-firing"}, {Name: "alertstate", Value: "firing"}, {Name: "name", Value: "foo1"}},
Samples: []prompbmarshal.Sample{{Value: 1, Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}},
},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "multiple-steps-firing"}, {Name: "name", Value: "foo1"}},
Samples: []prompbmarshal.Sample{{Value: float64(ts.Add(defaultStep).Unix()), Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}},
},
},
2: {
// stale time series for foo1
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "multiple-steps-firing"}, {Name: "alertstate", Value: "firing"}, {Name: "name", Value: "foo1"}},
Samples: []prompbmarshal.Sample{{Value: decimal.StaleNaN, Timestamp: ts.Add(2*defaultStep).UnixNano() / 1e6}}},
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "multiple-steps-firing"}, {Name: "name", Value: "foo1"}},
Samples: []prompbmarshal.Sample{{Value: decimal.StaleNaN, Timestamp: ts.Add(2*defaultStep).UnixNano() / 1e6}}},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "multiple-steps-firing"}, {Name: "alertstate", Value: "firing"}, {Name: "name", Value: "foo1"}},
Samples: []prompbmarshal.Sample{{Value: decimal.StaleNaN, Timestamp: ts.Add(2*defaultStep).UnixNano() / 1e6}},
},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "multiple-steps-firing"}, {Name: "name", Value: "foo1"}},
Samples: []prompbmarshal.Sample{{Value: decimal.StaleNaN, Timestamp: ts.Add(2*defaultStep).UnixNano() / 1e6}},
},
// new time series for foo2
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "multiple-steps-firing"}, {Name: "alertstate", Value: "firing"}, {Name: "name", Value: "foo2"}},
Samples: []prompbmarshal.Sample{{Value: 1, Timestamp: ts.Add(2*defaultStep).UnixNano() / 1e6}}},
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "multiple-steps-firing"}, {Name: "name", Value: "foo2"}},
Samples: []prompbmarshal.Sample{{Value: float64(ts.Add(2 * defaultStep).Unix()), Timestamp: ts.Add(2*defaultStep).UnixNano() / 1e6}}},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "multiple-steps-firing"}, {Name: "alertstate", Value: "firing"}, {Name: "name", Value: "foo2"}},
Samples: []prompbmarshal.Sample{{Value: 1, Timestamp: ts.Add(2*defaultStep).UnixNano() / 1e6}},
},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "multiple-steps-firing"}, {Name: "name", Value: "foo2"}},
Samples: []prompbmarshal.Sample{{Value: float64(ts.Add(2 * defaultStep).Unix()), Timestamp: ts.Add(2*defaultStep).UnixNano() / 1e6}},
},
},
})
@@ -389,50 +425,72 @@ func TestAlertingRule_Exec(t *testing.T) {
1: {{labels: []string{"name", "foo"}, alert: &notifier.Alert{State: notifier.StateFiring}}},
}, map[int][]prompbmarshal.TimeSeries{
0: {
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "for-fired"}, {Name: "alertstate", Value: "pending"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: 1, Timestamp: ts.UnixNano() / 1e6}}},
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "for-fired"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: float64(ts.Unix()), Timestamp: ts.UnixNano() / 1e6}}},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "for-fired"}, {Name: "alertstate", Value: "pending"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: 1, Timestamp: ts.UnixNano() / 1e6}},
},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "for-fired"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: float64(ts.Unix()), Timestamp: ts.UnixNano() / 1e6}},
},
},
1: {
// stale time series for `pending -> firing`
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "for-fired"}, {Name: "alertstate", Value: "pending"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: decimal.StaleNaN, Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}}},
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "for-fired"}, {Name: "alertstate", Value: "firing"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: 1, Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}}},
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "for-fired"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: float64(ts.Add(defaultStep).Unix()), Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}}},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "for-fired"}, {Name: "alertstate", Value: "pending"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: decimal.StaleNaN, Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}},
},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "for-fired"}, {Name: "alertstate", Value: "firing"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: 1, Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}},
},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "for-fired"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: float64(ts.Add(defaultStep).Unix()), Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}},
},
},
})
f(newTestAlertingRule("for-pending=>empty", time.Second), [][]datasource.Metric{
{metricWithLabels(t, "name", "foo")},
{metricWithLabels(t, "name", "foo")},
{metricWithLabels(t, "name", "foo", "a1", "b1", "a2", "b2", "a3", "b3")},
{metricWithLabels(t, "name", "foo", "a1", "b1", "a2", "b2", "a3", "b3")},
// empty step to delete pending alerts
{},
}, map[int][]testAlert{
0: {{labels: []string{"name", "foo"}, alert: &notifier.Alert{State: notifier.StatePending}}},
1: {{labels: []string{"name", "foo"}, alert: &notifier.Alert{State: notifier.StatePending}}},
0: {{labels: []string{"name", "foo", "a1", "b1", "a2", "b2", "a3", "b3"}, alert: &notifier.Alert{State: notifier.StatePending}}},
1: {{labels: []string{"name", "foo", "a1", "b1", "a2", "b2", "a3", "b3"}, alert: &notifier.Alert{State: notifier.StatePending}}},
2: {},
}, map[int][]prompbmarshal.TimeSeries{
0: {
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "for-pending=>empty"}, {Name: "alertstate", Value: "pending"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: 1, Timestamp: ts.UnixNano() / 1e6}}},
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "for-pending=>empty"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: float64(ts.Unix()), Timestamp: ts.UnixNano() / 1e6}}},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "a1", Value: "b1"}, {Name: "a2", Value: "b2"}, {Name: "a3", Value: "b3"}, {Name: "alertname", Value: "for-pending=>empty"}, {Name: "alertstate", Value: "pending"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: 1, Timestamp: ts.UnixNano() / 1e6}},
},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "a1", Value: "b1"}, {Name: "a2", Value: "b2"}, {Name: "a3", Value: "b3"}, {Name: "alertname", Value: "for-pending=>empty"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: float64(ts.Unix()), Timestamp: ts.UnixNano() / 1e6}},
},
},
1: {
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "for-pending=>empty"}, {Name: "alertstate", Value: "pending"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: 1, Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}}},
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "for-pending=>empty"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: float64(ts.Unix()), Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}}},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "a1", Value: "b1"}, {Name: "a2", Value: "b2"}, {Name: "a3", Value: "b3"}, {Name: "alertname", Value: "for-pending=>empty"}, {Name: "alertstate", Value: "pending"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: 1, Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}},
},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "a1", Value: "b1"}, {Name: "a2", Value: "b2"}, {Name: "a3", Value: "b3"}, {Name: "alertname", Value: "for-pending=>empty"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: float64(ts.Unix()), Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}},
},
},
// stale time series for `pending -> inactive`
2: {
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "for-pending=>empty"}, {Name: "alertstate", Value: "pending"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: decimal.StaleNaN, Timestamp: ts.Add(2*defaultStep).UnixNano() / 1e6}}},
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "for-pending=>empty"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: decimal.StaleNaN, Timestamp: ts.Add(2*defaultStep).UnixNano() / 1e6}}},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "a1", Value: "b1"}, {Name: "a2", Value: "b2"}, {Name: "a3", Value: "b3"}, {Name: "alertname", Value: "for-pending=>empty"}, {Name: "alertstate", Value: "pending"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: decimal.StaleNaN, Timestamp: ts.Add(2*defaultStep).UnixNano() / 1e6}},
},
{
Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "a1", Value: "b1"}, {Name: "a2", Value: "b2"}, {Name: "a3", Value: "b3"}, {Name: "alertname", Value: "for-pending=>empty"}, {Name: "name", Value: "foo"}},
Samples: []prompbmarshal.Sample{{Value: decimal.StaleNaN, Timestamp: ts.Add(2*defaultStep).UnixNano() / 1e6}},
},
},
})

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

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

View File

@@ -54,10 +54,9 @@ func newTemplate() *textTpl.Template {
}
// Load func loads templates from multiple globs specified in pathPatterns and either
// sets them directly to current template if it's undefined or with overwrite=true
// or sets replacement templates and adds templates with new names to a current
func Load(pathPatterns []string, overwrite bool) error {
var err error
// 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)
@@ -79,36 +78,12 @@ func Load(pathPatterns []string, overwrite bool) error {
}
tplMu.Lock()
defer tplMu.Unlock()
if masterTmpl.current == nil || overwrite {
masterTmpl.replacement = nil
masterTmpl.current = newTemplate()
} else {
masterTmpl.replacement = newTemplate()
if err = copyTemplates(tmpl, masterTmpl.replacement, overwrite); err != nil {
return err
}
}
return copyTemplates(tmpl, masterTmpl.current, overwrite)
}
tmpl = tmpl.Funcs(funcsWithExternalURL(externalURL))
func copyTemplates(from *textTpl.Template, to *textTpl.Template, overwrite bool) error {
if from == nil {
return nil
}
if to == nil {
to = newTemplate()
}
tmpl, err := from.Clone()
if err != nil {
return err
}
for _, t := range tmpl.Templates() {
if to.Lookup(t.Name()) == nil || overwrite {
to, err = to.AddParseTree(t.Name(), t.Tree)
if err != nil {
return fmt.Errorf("failed to add template %q: %w", t.Name(), err)
}
}
if masterTmpl.current == nil {
masterTmpl.current = tmpl
} else {
masterTmpl.replacement = tmpl
}
return nil
}
@@ -153,13 +128,6 @@ func datasourceMetricsToTemplateMetrics(ms []datasource.Metric) []metric {
// for templating functions.
type QueryFn func(query string) ([]datasource.Metric, error)
// UpdateWithFuncs updates existing or sets a new function map for a template
func UpdateWithFuncs(funcs textTpl.FuncMap) {
tplMu.Lock()
defer tplMu.Unlock()
masterTmpl.current = masterTmpl.current.Funcs(funcs)
}
// GetWithFuncs returns a copy of current template with additional FuncMap
// provided with funcs argument
func GetWithFuncs(funcs textTpl.FuncMap) (*textTpl.Template, error) {
@@ -174,13 +142,6 @@ func GetWithFuncs(funcs textTpl.FuncMap) (*textTpl.Template, error) {
return tmpl.Funcs(funcs), nil
}
// Get returns a copy of a template
func Get() (*textTpl.Template, error) {
tplMu.RLock()
defer tplMu.RUnlock()
return masterTmpl.current.Clone()
}
// FuncsWithQuery returns a function map that depends on metric data
func FuncsWithQuery(query QueryFn) textTpl.FuncMap {
return textTpl.FuncMap{
@@ -198,8 +159,8 @@ func FuncsWithQuery(query QueryFn) textTpl.FuncMap {
}
}
// FuncsWithExternalURL returns a function map that depends on externalURL value
func FuncsWithExternalURL(externalURL *url.URL) textTpl.FuncMap {
// 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()

View File

@@ -2,6 +2,7 @@ package templates
import (
"math"
"net/url"
"strings"
"testing"
textTpl "text/template"
@@ -152,7 +153,7 @@ func TestTemplatesLoad_Failure(t *testing.T) {
f := func(pathPatterns []string, expectedErrStr string) {
t.Helper()
err := Load(pathPatterns, false)
err := Load(pathPatterns, url.URL{})
if err == nil {
t.Fatalf("expecting non-nil error")
}
@@ -171,128 +172,17 @@ func TestTemplatesLoad_Failure(t *testing.T) {
}
func TestTemplatesLoad_Success(t *testing.T) {
f := func(initialTmpl textTemplate, pathPatterns []string, overwrite bool, expectedTmpl textTemplate) {
f := func(pathPatterns []string, expectedTmpl textTemplate) {
t.Helper()
masterTmplOrig := masterTmpl
masterTmpl = initialTmpl
defer func() {
masterTmpl = masterTmplOrig
}()
if err := Load(pathPatterns, overwrite); err != nil {
if err := Load(pathPatterns, url.URL{}); err != nil {
t.Fatalf("cannot load templates: %s", err)
}
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 undefined template override
initialTmpl := mkTemplate(nil, nil)
pathPatterns := []string{
"templates/non-existing/good-*.tpl",
"templates/absent/good-*.tpl",
}
overwrite := true
expectedTmpl := mkTemplate(``, nil)
f(initialTmpl, pathPatterns, overwrite, expectedTmpl)
// non existing path defined template override
initialTmpl = mkTemplate(`
{{- define "test.1" -}}
{{- printf "value" -}}
{{- end -}}
`, nil)
pathPatterns = []string{
"templates/non-existing/good-*.tpl",
"templates/absent/good-*.tpl",
}
overwrite = true
expectedTmpl = mkTemplate(``, nil)
f(initialTmpl, pathPatterns, overwrite, expectedTmpl)
// existing path undefined template override
initialTmpl = mkTemplate(nil, nil)
pathPatterns = []string{
"templates/other/nested/good0-*.tpl",
"templates/test/good0-*.tpl",
}
overwrite = false
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.2" -}}
{{ printf "Hello %s!" externalURL }}
{{- end -}}
{{- define "test.3" -}}
{{ printf "Hello %s!" externalURL }}
{{- end -}}
`, nil)
f(initialTmpl, pathPatterns, overwrite, expectedTmpl)
// existing path defined template override
initialTmpl = mkTemplate(`
{{- define "test.1" -}}
{{ printf "Hello %s!" "world" }}
{{- end -}}
`, nil)
pathPatterns = []string{
"templates/other/nested/good0-*.tpl",
"templates/test/good0-*.tpl",
}
overwrite = false
expectedTmpl = mkTemplate(`
{{- define "good0-test.tpl" -}}{{- end -}}
{{- define "test.0" -}}
{{ printf "Hello %s!" externalURL }}
{{- end -}}
{{- define "test.1" -}}
{{ printf "Hello %s!" "world" }}
{{- end -}}
{{- define "test.2" -}}
{{ printf "Hello %s!" externalURL }}
{{- end -}}
{{- define "test.3" -}}
{{ printf "Hello %s!" externalURL }}
{{- end -}}
`, `
{{- define "good0-test.tpl" -}}{{- end -}}
{{- define "test.0" -}}
{{ printf "Hello %s!" externalURL }}
{{- end -}}
{{- define "test.1" -}}
{{ printf "Hello %s!" externalURL }}
{{- end -}}
{{- define "test.2" -}}
{{ printf "Hello %s!" externalURL }}
{{- end -}}
{{- define "test.3" -}}
{{ printf "Hello %s!" externalURL }}
{{- end -}}
`)
f(initialTmpl, pathPatterns, overwrite, expectedTmpl)
}
func TestTemplatesReload(t *testing.T) {
f := func(initialTmpl, expectedTmpl textTemplate) {
t.Helper()
masterTmplOrig := masterTmpl
masterTmpl = initialTmpl
defer func() {
masterTmpl = masterTmplOrig
}()
Reload()
if !equalTemplates(masterTmpl.replacement, expectedTmpl.replacement) {
@@ -303,46 +193,47 @@ func TestTemplatesReload(t *testing.T) {
}
}
// empty current and replacement templates
f(mkTemplate(nil, nil), mkTemplate(nil, nil))
// non existing path
pathPatterns := []string{
"templates/non-existing/good-*.tpl",
"templates/absent/good-*.tpl",
}
expectedTmpl := mkTemplate(``, nil)
f(pathPatterns, expectedTmpl)
// empty current template only
f(mkTemplate(`
{{- define "test.1" -}}
{{- printf "value" -}}
{{- end -}}
`, nil), mkTemplate(`
{{- define "test.1" -}}
{{- printf "value" -}}
{{- end -}}
`, nil))
// empty replacement template only
f(mkTemplate(nil, `
{{- define "test.1" -}}
{{- printf "value" -}}
{{- end -}}
`), mkTemplate(`
{{- define "test.1" -}}
{{- printf "value" -}}
{{- end -}}
`, nil))
// defined both templates
f(mkTemplate(`
// existing path
pathPatterns = []string{
"templates/test/good0-*.tpl",
}
expectedTmpl = mkTemplate(`
{{- define "good0-test.tpl" -}}{{- end -}}
{{- define "test.0" -}}
{{- printf "value" -}}
{{ 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 "before" -}}
{{ printf "Hello %s!" externalURL }}
{{- end -}}
`, `
{{- define "test.1" -}}
{{- printf "after" -}}
{{- define "test.3" -}}
{{ printf "Hello %s!" externalURL }}
{{- end -}}
`), mkTemplate(`
{{- define "test.1" -}}
{{- printf "after" -}}
{{- end -}}
`, nil))
`, nil)
f(pathPatterns, expectedTmpl)
}

View File

@@ -2,11 +2,12 @@ package main
import (
"fmt"
"reflect"
"testing"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/config"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/datasource"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/rule"
"reflect"
"testing"
)
func TestRecordingToApi(t *testing.T) {

View File

@@ -7,11 +7,13 @@ import (
"flag"
"fmt"
"math"
"net"
"net/http"
"net/url"
"os"
"regexp"
"sort"
"strconv"
"strings"
"sync"
"sync/atomic"
@@ -348,6 +350,7 @@ func (up *URLPrefix) discoverBackendAddrsIfNeeded() {
hostToAddrs := make(map[string][]string)
for _, bu := range up.busOriginal {
host := bu.Hostname()
port := bu.Port()
if hostToAddrs[host] != nil {
// ips for the given host have been already discovered
continue
@@ -364,7 +367,11 @@ func (up *URLPrefix) discoverBackendAddrsIfNeeded() {
} else {
resolvedAddrs = make([]string, len(addrs))
for i, addr := range addrs {
resolvedAddrs[i] = fmt.Sprintf("%s:%d", addr.Target, addr.Port)
hostPort := port
if hostPort == "" && addr.Port > 0 {
hostPort = strconv.FormatUint(uint64(addr.Port), 10)
}
resolvedAddrs[i] = net.JoinHostPort(addr.Target, hostPort)
}
}
} else {
@@ -375,7 +382,7 @@ func (up *URLPrefix) discoverBackendAddrsIfNeeded() {
} else {
resolvedAddrs = make([]string, len(addrs))
for i, addr := range addrs {
resolvedAddrs[i] = addr.String()
resolvedAddrs[i] = net.JoinHostPort(addr.String(), port)
}
}
}
@@ -389,17 +396,9 @@ func (up *URLPrefix) discoverBackendAddrsIfNeeded() {
var busNew []*backendURL
for _, bu := range up.busOriginal {
host := bu.Hostname()
port := bu.Port()
for _, addr := range hostToAddrs[host] {
buCopy := *bu
buCopy.Host = addr
if port != "" {
if n := strings.IndexByte(buCopy.Host, ':'); n >= 0 {
// Drop the discovered port and substitute it the port specified in bu.
buCopy.Host = buCopy.Host[:n]
}
buCopy.Host += ":" + port
}
busNew = append(busNew, &backendURL{
url: &buCopy,
})
@@ -783,10 +782,11 @@ func parseAuthConfig(data []byte) (*AuthConfig, error) {
func parseAuthConfigUsers(ac *AuthConfig) (map[string]*UserInfo, error) {
uis := ac.Users
if len(uis) == 0 && ac.UnauthorizedUser == nil {
return nil, fmt.Errorf("Missing `users` or `unauthorized_user` sections")
}
byAuthToken := make(map[string]*UserInfo, len(uis))
if len(uis) == 0 && ac.UnauthorizedUser == nil {
// fast path for empty configuration
return byAuthToken, nil
}
for i := range uis {
ui := &uis[i]
ats, err := getAuthTokens(ui.AuthToken, ui.BearerToken, ui.Username, ui.Password)

View File

@@ -3,12 +3,14 @@ package main
import (
"bytes"
"fmt"
"net"
"net/url"
"testing"
"gopkg.in/yaml.v2"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/netutil"
)
func TestParseAuthConfigFailure(t *testing.T) {
@@ -24,16 +26,10 @@ func TestParseAuthConfigFailure(t *testing.T) {
}
}
// Empty config
f(``)
// Invalid entry
f(`foobar`)
f(`foobar: baz`)
// Empty users
f(`users: []`)
// Missing url_prefix
f(`
users:
@@ -302,6 +298,12 @@ func TestParseAuthConfigSuccess(t *testing.T) {
insecureSkipVerifyTrue := true
// Empty config
f(``, map[string]*UserInfo{})
// Empty users
f(`users: []`, map[string]*UserInfo{})
// Single user
f(`
users:
@@ -799,6 +801,75 @@ func TestBrokenBackend(t *testing.T) {
}
}
func TestDiscoverBackendIPsWithIPV6(t *testing.T) {
f := func(actualUrl, expectedUrl string) {
t.Helper()
up := mustParseURL(actualUrl)
up.discoverBackendIPs = true
up.loadBalancingPolicy = "least_loaded"
up.discoverBackendAddrsIfNeeded()
pbus := up.bus.Load()
bus := *pbus
if len(bus) != 1 {
t.Fatalf("expected url list to be of size 1; got %d instead", len(bus))
}
got := bus[0].url.Host
if got != expectedUrl {
t.Fatalf(`expected url to be %q; got %q instead`, expectedUrl, bus[0].url.Host)
}
}
// Discover backendURL with SRV hostnames
customResolver := &fakeResolver{
Resolver: &net.Resolver{},
// SRV records must return hostname
// not an IP address
lookupSRVResults: map[string][]*net.SRV{
"_vmselect._tcp.selectwithport.": {
{
Target: "vmselect.local",
Port: 8481,
},
},
"_vmselect._tcp.selectwoport.": {
{
Target: "vmselect.local",
},
},
},
lookupIPAddrResults: map[string][]net.IPAddr{
"vminsert.local": {
{
IP: net.ParseIP("10.0.10.13"),
},
},
"ipv6.vminsert.local": {
{
IP: net.ParseIP("2607:f8b0:400a:80b::200e"),
},
},
},
}
origResolver := netutil.Resolver
netutil.Resolver = customResolver
defer func() {
netutil.Resolver = origResolver
}()
f("http://srv+_vmselect._tcp.selectwithport.:8080", "vmselect.local:8080")
f("http://srv+_vmselect._tcp.selectwithport.:", "vmselect.local:8481")
f("http://srv+_vmselect._tcp.selectwoport.:8080", "vmselect.local:8080")
f("http://srv+_vmselect._tcp.selectwoport.", "vmselect.local:")
f("http://vminsert.local:8080", "10.0.10.13:8080")
f("http://vminsert.local", "10.0.10.13:")
f("http://ipv6.vminsert.local:8080", "[2607:f8b0:400a:80b::200e]:8080")
f("http://ipv6.vminsert.local", "[2607:f8b0:400a:80b::200e]:")
}
func getRegexs(paths []string) []*Regex {
var sps []*Regex
for _, path := range paths {

View File

@@ -31,7 +31,11 @@ import (
)
var (
httpListenAddrs = flagutil.NewArrayString("httpListenAddr", "TCP address to listen for incoming http requests. See also -tls and -httpListenAddr.useProxyProtocol")
httpListenAddrs = flagutil.NewArrayString("httpListenAddr", "TCP address to listen for incoming http requests. "+
"By default, serves internal API and proxy requests. "+
" See also -tls, -httpListenAddr.useProxyProtocol and -httpInternalListenAddr.")
httpInternalListenAddr = flagutil.NewArrayString("httpInternalListenAddr", "TCP address to listen for incoming internal API http requests. Such as /health, /-/reload, /debug/pprof, etc. "+
"If flag is set, vmauth no longer serves internal API at -httpListenAddr.")
useProxyProtocol = flagutil.NewArrayBool("httpListenAddr.useProxyProtocol", "Whether to use proxy protocol for connections accepted at the corresponding -httpListenAddr . "+
"See https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt . "+
"With enabled proxy protocol http server cannot serve regular /metrics endpoint. Use -pushmetrics.url for metrics pushing")
@@ -91,7 +95,21 @@ func main() {
logger.Infof("starting vmauth at %q...", listenAddrs)
startTime := time.Now()
initAuthConfig()
go httpserver.Serve(listenAddrs, useProxyProtocol, requestHandler)
disableInternalRoutes := len(*httpInternalListenAddr) > 0
rh := requestHandlerWithInternalRoutes
if disableInternalRoutes {
rh = requestHandler
}
serveOpts := httpserver.ServeOptions{
UseProxyProtocol: useProxyProtocol,
DisableBuiltinRoutes: disableInternalRoutes,
}
go httpserver.ServeWithOpts(listenAddrs, rh, serveOpts)
if len(*httpInternalListenAddr) > 0 {
go httpserver.Serve(*httpInternalListenAddr, nil, internalRequestHandler)
}
logger.Infof("started vmauth in %.3f seconds", time.Since(startTime).Seconds())
pushmetrics.Init()
@@ -109,7 +127,7 @@ func main() {
logger.Infof("successfully stopped vmauth in %.3f seconds", time.Since(startTime).Seconds())
}
func requestHandler(w http.ResponseWriter, r *http.Request) bool {
func internalRequestHandler(w http.ResponseWriter, r *http.Request) bool {
switch r.URL.Path {
case "/-/reload":
if !httpserver.CheckAuthFlag(w, r, reloadAuthKey) {
@@ -120,6 +138,17 @@ func requestHandler(w http.ResponseWriter, r *http.Request) bool {
w.WriteHeader(http.StatusOK)
return true
}
return false
}
func requestHandlerWithInternalRoutes(w http.ResponseWriter, r *http.Request) bool {
if internalRequestHandler(w, r) {
return true
}
return requestHandler(w, r)
}
func requestHandler(w http.ResponseWriter, r *http.Request) bool {
ats := getAuthTokensFromRequest(r)
if len(ats) == 0 {
@@ -213,7 +242,7 @@ func processRequest(w http.ResponseWriter, r *http.Request, ui *UserInfo) {
missingRouteRequests.Inc()
var di string
if ui.DumpRequestOnErrors {
di = debugInfo(u, r.Header)
di = debugInfo(u, r)
}
httpserver.Errorf(w, r, "missing route for %q%s", u.String(), di)
return
@@ -222,8 +251,7 @@ func processRequest(w http.ResponseWriter, r *http.Request, ui *UserInfo) {
isDefault = true
}
rtb := getReadTrackingBody(r.Body, maxRequestBodySizeToRetry.IntN())
defer putReadTrackingBody(rtb)
rtb := newReadTrackingBody(r.Body, maxRequestBodySizeToRetry.IntN())
r.Body = rtb
maxAttempts := up.getBackendsCount()
@@ -559,22 +587,11 @@ type readTrackingBody struct {
bufComplete bool
}
func (rtb *readTrackingBody) reset() {
rtb.maxBodySize = 0
rtb.r = nil
rtb.buf = rtb.buf[:0]
rtb.readBuf = nil
rtb.cannotRetry = false
rtb.bufComplete = false
}
func getReadTrackingBody(r io.ReadCloser, maxBodySize int) *readTrackingBody {
v := readTrackingBodyPool.Get()
if v == nil {
v = &readTrackingBody{}
}
rtb := v.(*readTrackingBody)
func newReadTrackingBody(r io.ReadCloser, maxBodySize int) *readTrackingBody {
// do not use sync.Pool there
// since http.RoundTrip may still use request body after return
// See this issue for details https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8051
rtb := &readTrackingBody{}
if maxBodySize < 0 {
maxBodySize = 0
}
@@ -597,13 +614,6 @@ func (r *zeroReader) Close() error {
return nil
}
func putReadTrackingBody(rtb *readTrackingBody) {
rtb.reset()
readTrackingBodyPool.Put(rtb)
}
var readTrackingBodyPool sync.Pool
// Read implements io.Reader interface.
func (rtb *readTrackingBody) Read(p []byte) (int, error) {
if len(rtb.readBuf) > 0 {
@@ -668,13 +678,13 @@ func (rtb *readTrackingBody) Close() error {
return nil
}
func debugInfo(u *url.URL, h http.Header) string {
func debugInfo(u *url.URL, r *http.Request) string {
s := &strings.Builder{}
fmt.Fprintf(s, " (host: %q; ", u.Host)
fmt.Fprintf(s, " (host: %q; ", r.Host)
fmt.Fprintf(s, "path: %q; ", u.Path)
fmt.Fprintf(s, "args: %q; ", u.Query().Encode())
fmt.Fprint(s, "headers:")
_ = h.WriteSubset(s, nil)
_ = r.Header.WriteSubset(s, nil)
fmt.Fprint(s, ")")
return s.String()
}

View File

@@ -52,7 +52,7 @@ func TestRequestHandler(t *testing.T) {
r.Header.Set("Pass-Header", "abc")
w := &fakeResponseWriter{}
if !requestHandler(w, r) {
if !requestHandlerWithInternalRoutes(w, r) {
t.Fatalf("unexpected false is returned from requestHandler")
}
@@ -195,7 +195,7 @@ unauthorized_user:
}
responseExpected = `
statusCode=401
The provided authKey doesn't match -reloadAuthKey`
Expected to receive non-empty authKey when -reloadAuthKey is set`
f(cfgStr, requestURL, backendHandler, responseExpected)
if err := reloadAuthKey.Set(origAuthKey); err != nil {
t.Fatalf("unexpected error: %s", err)
@@ -545,8 +545,7 @@ func TestReadTrackingBody_RetrySuccess(t *testing.T) {
f := func(s string, maxBodySize int) {
t.Helper()
rtb := getReadTrackingBody(io.NopCloser(bytes.NewBufferString(s)), maxBodySize)
defer putReadTrackingBody(rtb)
rtb := newReadTrackingBody(io.NopCloser(bytes.NewBufferString(s)), maxBodySize)
if !rtb.canRetry() {
t.Fatalf("canRetry() must return true before reading anything")
@@ -581,8 +580,7 @@ func TestReadTrackingBody_RetrySuccessPartialRead(t *testing.T) {
t.Helper()
// Check the case with partial read
rtb := getReadTrackingBody(io.NopCloser(bytes.NewBufferString(s)), maxBodySize)
defer putReadTrackingBody(rtb)
rtb := newReadTrackingBody(io.NopCloser(bytes.NewBufferString(s)), maxBodySize)
for i := 0; i < len(s); i++ {
buf := make([]byte, i)
@@ -631,8 +629,7 @@ func TestReadTrackingBody_RetryFailureTooBigBody(t *testing.T) {
f := func(s string, maxBodySize int) {
t.Helper()
rtb := getReadTrackingBody(io.NopCloser(bytes.NewBufferString(s)), maxBodySize)
defer putReadTrackingBody(rtb)
rtb := newReadTrackingBody(io.NopCloser(bytes.NewBufferString(s)), maxBodySize)
if !rtb.canRetry() {
t.Fatalf("canRetry() must return true before reading anything")
@@ -681,8 +678,7 @@ func TestReadTrackingBody_RetryFailureZeroOrNegativeMaxBodySize(t *testing.T) {
f := func(s string, maxBodySize int) {
t.Helper()
rtb := getReadTrackingBody(io.NopCloser(bytes.NewBufferString(s)), maxBodySize)
defer putReadTrackingBody(rtb)
rtb := newReadTrackingBody(io.NopCloser(bytes.NewBufferString(s)), maxBodySize)
if !rtb.canRetry() {
t.Fatalf("canRetry() must return true before reading anything")

View File

@@ -596,7 +596,8 @@ var (
&cli.Int64Flag{
Name: vmRateLimit,
Usage: "Optional data transfer rate limit in bytes per second.\n" +
"By default, the rate limit is disabled. It can be useful for limiting load on source or destination databases.",
"By default, the rate limit is disabled. It can be useful for limiting load on source or destination databases. \n" +
"Rate limit is applied per worker, see `--vm-concurrency`.",
},
&cli.BoolFlag{
Name: vmInterCluster,

View File

@@ -180,11 +180,7 @@ func (c *Client) Explore() ([]*Series, error) {
log.Printf("skip measurement %q since it has no fields", s.Measurement)
continue
}
tags, ok := measurementTags[s.Measurement]
if !ok {
return nil, fmt.Errorf("failed to find tags of measurement %s", s.Measurement)
}
emptyTags := getEmptyTags(tags, s.LabelPairs)
emptyTags := getEmptyTags(measurementTags[s.Measurement], s.LabelPairs)
for _, field := range fields {
is := &Series{
Measurement: s.Measurement,
@@ -201,11 +197,16 @@ func (c *Client) Explore() ([]*Series, error) {
// getEmptyTags returns tags of a measurement that are missing in a specific series.
// Tags represent all tags of a measurement. LabelPairs represent tags of a specific series.
func getEmptyTags(tags map[string]struct{}, LabelPairs []LabelPair) []string {
if len(tags) == 0 {
// fast path: the measurement does not contain any tag
return nil
}
labelMap := make(map[string]struct{})
for _, pair := range LabelPairs {
labelMap[pair.Name] = struct{}{}
}
result := make([]string, 0, len(labelMap)-len(LabelPairs))
var result []string
for tag := range tags {
if _, ok := labelMap[tag]; !ok {
result = append(result, tag)

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

@@ -4,13 +4,46 @@ import (
"fmt"
"net/http"
"github.com/VictoriaMetrics/metrics"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vminsert/relabel"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmstorage"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httpserver"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/ratelimiter"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/slicesutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/timeserieslimits"
)
// StartIngestionRateLimiter starts ingestion rate limiter.
//
// Ingestion rate limiter must be started before Init() call.
//
// StopIngestionRateLimiter must be called before Stop() call in order to unblock all the callers
// to ingestion rate limiter. Otherwise deadlock may occur at Stop() call.
func StartIngestionRateLimiter(maxIngestionRate int) {
if maxIngestionRate <= 0 {
return
}
ingestionRateLimitReached := metrics.NewCounter(`vm_max_ingestion_rate_limit_reached_total`)
ingestionRateLimiterStopCh = make(chan struct{})
ingestionRateLimiter = ratelimiter.New(int64(maxIngestionRate), ingestionRateLimitReached, ingestionRateLimiterStopCh)
}
// StopIngestionRateLimiter stops ingestion rate limiter.
func StopIngestionRateLimiter() {
if ingestionRateLimiterStopCh == nil {
return
}
close(ingestionRateLimiterStopCh)
ingestionRateLimiterStopCh = nil
}
var (
ingestionRateLimiter *ratelimiter.RateLimiter
ingestionRateLimiterStopCh chan struct{}
)
// InsertCtx contains common bits for data points insertion.
@@ -59,7 +92,27 @@ func (ctx *InsertCtx) marshalMetricNameRaw(prefix []byte, labels []prompbmarshal
return metricNameRaw[:len(metricNameRaw):len(metricNameRaw)]
}
// TryPrepareLabels prepares context labels to the ingestion
//
// It returns false if timeseries should be skipped
func (ctx *InsertCtx) TryPrepareLabels(hasRelabeling bool) bool {
if hasRelabeling {
ctx.ApplyRelabeling()
}
if len(ctx.Labels) == 0 {
return false
}
if timeserieslimits.Enabled() && timeserieslimits.IsExceeding(ctx.Labels) {
return false
}
ctx.SortLabelsIfNeeded()
return true
}
// WriteDataPoint writes (timestamp, value) with the given prefix and labels into ctx buffer.
//
// caller should invoke TryPrepareLabels before using this function if needed
func (ctx *InsertCtx) WriteDataPoint(prefix []byte, labels []prompbmarshal.Label, timestamp int64, value float64) error {
metricNameRaw := ctx.marshalMetricNameRaw(prefix, labels)
return ctx.addRow(metricNameRaw, timestamp, value)
@@ -67,6 +120,8 @@ func (ctx *InsertCtx) WriteDataPoint(prefix []byte, labels []prompbmarshal.Label
// WriteDataPointExt writes (timestamp, value) with the given metricNameRaw and labels into ctx buffer.
//
// caller must invoke TryPrepareLabels before using this function
//
// It returns metricNameRaw for the given labels if len(metricNameRaw) == 0.
func (ctx *InsertCtx) WriteDataPointExt(metricNameRaw []byte, labels []prompbmarshal.Label, timestamp int64, value float64) ([]byte, error) {
if len(metricNameRaw) == 0 {
@@ -149,9 +204,12 @@ func (ctx *InsertCtx) FlushBufs() error {
}
matchIdxsPool.Put(matchIdxs)
}
ingestionRateLimiter.Register(len(ctx.mrs))
// There is no need in limiting the number of concurrent calls to vmstorage.AddRows() here,
// since the number of concurrent FlushBufs() calls should be already limited via writeconcurrencylimiter
// used at every stream.Parse() call under lib/protoparser/*
err := vmstorage.AddRows(ctx.mrs)
ctx.Reset(0)
if err == nil {

View File

@@ -46,14 +46,9 @@ func insertRows(rows []parser.Row, extraLabels []prompbmarshal.Label) error {
label := &extraLabels[j]
ctx.AddLabel(label.Name, label.Value)
}
if hasRelabeling {
ctx.ApplyRelabeling()
}
if len(ctx.Labels) == 0 {
// Skip metric without labels.
if !ctx.TryPrepareLabels(hasRelabeling) {
continue
}
ctx.SortLabelsIfNeeded()
if err := ctx.WriteDataPoint(nil, ctx.Labels, r.Timestamp, r.Value); err != nil {
return err
}

View File

@@ -60,14 +60,9 @@ func insertRows(sketches []*datadogsketches.Sketch, extraLabels []prompbmarshal.
label := &extraLabels[j]
ctx.AddLabel(label.Name, label.Value)
}
if hasRelabeling {
ctx.ApplyRelabeling()
}
if len(ctx.Labels) == 0 {
// Skip metric without labels.
if !ctx.TryPrepareLabels(hasRelabeling) {
continue
}
ctx.SortLabelsIfNeeded()
var metricNameRaw []byte
var err error
for _, p := range m.Points {

View File

@@ -63,14 +63,9 @@ func insertRows(series []datadogv1.Series, extraLabels []prompbmarshal.Label) er
label := &extraLabels[j]
ctx.AddLabel(label.Name, label.Value)
}
if hasRelabeling {
ctx.ApplyRelabeling()
}
if len(ctx.Labels) == 0 {
// Skip metric without labels.
if !ctx.TryPrepareLabels(hasRelabeling) {
continue
}
ctx.SortLabelsIfNeeded()
var metricNameRaw []byte
var err error
for _, pt := range ss.Points {

View File

@@ -66,14 +66,9 @@ func insertRows(series []datadogv2.Series, extraLabels []prompbmarshal.Label) er
label := &extraLabels[j]
ctx.AddLabel(label.Name, label.Value)
}
if hasRelabeling {
ctx.ApplyRelabeling()
}
if len(ctx.Labels) == 0 {
// Skip metric without labels.
if !ctx.TryPrepareLabels(hasRelabeling) {
continue
}
ctx.SortLabelsIfNeeded()
var metricNameRaw []byte
var err error
for _, pt := range ss.Points {

View File

@@ -36,14 +36,9 @@ func insertRows(rows []parser.Row) error {
tag := &r.Tags[j]
ctx.AddLabel(tag.Key, tag.Value)
}
if hasRelabeling {
ctx.ApplyRelabeling()
}
if len(ctx.Labels) == 0 {
// Skip metric without labels.
if !ctx.TryPrepareLabels(hasRelabeling) {
continue
}
ctx.SortLabelsIfNeeded()
if err := ctx.WriteDataPoint(nil, ctx.Labels, r.Timestamp, r.Value); err != nil {
return err
}

View File

@@ -14,6 +14,7 @@ import (
parser "github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/influx"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/influx/stream"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/timeserieslimits"
"github.com/VictoriaMetrics/metrics"
)
@@ -69,6 +70,7 @@ func insertRows(db string, rows []parser.Row, extraLabels []prompbmarshal.Label)
ic.Reset(rowsLen)
rowsTotal := 0
hasRelabeling := relabel.HasRelabeling()
hasLimitsEnabled := timeserieslimits.Enabled()
for i := range rows {
r := &rows[i]
rowsTotal += len(r.Fields)
@@ -108,18 +110,23 @@ func insertRows(db string, rows []parser.Row, extraLabels []prompbmarshal.Label)
metricGroup := bytesutil.ToUnsafeString(ctx.metricGroupBuf)
ic.Labels = append(ic.Labels[:0], ctx.originLabels...)
ic.AddLabel("", metricGroup)
ic.ApplyRelabeling()
if len(ic.Labels) == 0 {
// Skip metric without labels.
if !ic.TryPrepareLabels(true) {
continue
}
ic.SortLabelsIfNeeded()
if err := ic.WriteDataPoint(nil, ic.Labels, r.Timestamp, f.Value); err != nil {
return err
}
}
} else {
// special case for optimisations below
// do not call TryPrepareLabels
// manually apply sort and limits on demand
ic.SortLabelsIfNeeded()
if hasLimitsEnabled {
if timeserieslimits.IsExceeding(ic.Labels) {
continue
}
}
ctx.metricNameBuf = storage.MarshalMetricNameRaw(ctx.metricNameBuf[:0], ic.Labels)
labelsLen := len(ic.Labels)
for j := range r.Fields {
@@ -130,9 +137,10 @@ func insertRows(db string, rows []parser.Row, extraLabels []prompbmarshal.Label)
metricGroup := bytesutil.ToUnsafeString(ctx.metricGroupBuf)
ic.Labels = ic.Labels[:labelsLen]
ic.AddLabel("", metricGroup)
if len(ic.Labels) == 0 {
// Skip metric without labels.
continue
if hasLimitsEnabled {
if timeserieslimits.IsExceeding(ic.Labels[len(ic.Labels)-1:]) {
continue
}
}
if err := ic.WriteDataPoint(ctx.metricNameBuf, ic.Labels[len(ic.Labels)-1:], r.Timestamp, f.Value); err != nil {
return err

View File

@@ -41,8 +41,8 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/common"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/opentelemetry/firehose"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/stringsutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/timeserieslimits"
)
var (
@@ -67,8 +67,9 @@ var (
"at -opentsdbHTTPListenAddr . See https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt")
configAuthKey = flagutil.NewPassword("configAuthKey", "Authorization key for accessing /config page. It must be passed via authKey query arg. It overrides -httpAuth.*")
reloadAuthKey = flagutil.NewPassword("reloadAuthKey", "Auth key for /-/reload http endpoint. It must be passed via authKey query arg. It overrides httpAuth.* settings.")
maxLabelsPerTimeseries = flag.Int("maxLabelsPerTimeseries", 30, "The maximum number of labels accepted per time series. Superfluous labels are dropped. In this case the vm_metrics_with_dropped_labels_total metric at /metrics page is incremented")
maxLabelValueLen = flag.Int("maxLabelValueLen", 4*1024, "The maximum length of label values in the accepted time series. Longer label values are truncated. In this case the vm_too_long_label_values_total metric at /metrics page is incremented")
maxLabelsPerTimeseries = flag.Int("maxLabelsPerTimeseries", 40, "The maximum number of labels per time series to be accepted. Series with superfluous labels are ignored. In this case the vm_rows_ignored_total{reason=\"too_many_labels\"} metric at /metrics page is incremented")
maxLabelNameLen = flag.Int("maxLabelNameLen", 256, "The maximum length of label name in the accepted time series. Series with longer label name are ignored. In this case the vm_rows_ignored_total{reason=\"too_long_label_name\"} metric at /metrics page is incremented")
maxLabelValueLen = flag.Int("maxLabelValueLen", 4*1024, "The maximum length of label values in the accepted time series. Series with longer label value are ignored. In this case the vm_rows_ignored_total{reason=\"too_long_label_value\"} metric at /metrics page is incremented")
)
var (
@@ -87,8 +88,6 @@ var staticServer = http.FileServer(http.FS(staticFiles))
func Init() {
relabel.Init()
vminsertCommon.InitStreamAggr()
storage.SetMaxLabelsPerTimeseries(*maxLabelsPerTimeseries)
storage.SetMaxLabelValueLen(*maxLabelValueLen)
common.StartUnmarshalWorkers()
if len(*graphiteListenAddr) > 0 {
graphiteServer = graphiteserver.MustStart(*graphiteListenAddr, *graphiteUseProxyProtocol, graphite.InsertHandler)
@@ -105,6 +104,7 @@ func Init() {
promscrape.Init(func(_ *auth.Token, wr *prompbmarshal.WriteRequest) {
prompush.Push(wr)
})
timeserieslimits.Init(*maxLabelsPerTimeseries, *maxLabelNameLen, *maxLabelValueLen)
}
// Stop stops vminsert.
@@ -439,14 +439,4 @@ var (
promscrapeStatusConfigRequests = metrics.NewCounter(`vm_http_requests_total{path="/api/v1/status/config"}`)
promscrapeConfigReloadRequests = metrics.NewCounter(`vm_http_requests_total{path="/-/reload"}`)
_ = metrics.NewGauge(`vm_metrics_with_dropped_labels_total`, func() float64 {
return float64(storage.MetricsWithDroppedLabels.Load())
})
_ = metrics.NewGauge(`vm_too_long_label_names_total`, func() float64 {
return float64(storage.TooLongLabelNames.Load())
})
_ = metrics.NewGauge(`vm_too_long_label_values_total`, func() float64 {
return float64(storage.TooLongLabelValues.Load())
})
)

View File

@@ -55,14 +55,9 @@ func insertRows(block *stream.Block, extraLabels []prompbmarshal.Label) error {
label := &extraLabels[j]
ic.AddLabel(label.Name, label.Value)
}
if hasRelabeling {
ic.ApplyRelabeling()
}
if len(ic.Labels) == 0 {
// Skip metric without labels.
if !ic.TryPrepareLabels(hasRelabeling) {
return nil
}
ic.SortLabelsIfNeeded()
ctx.metricNameBuf = storage.MarshalMetricNameRaw(ctx.metricNameBuf[:0], ic.Labels)
values := block.Values
timestamps := block.Timestamps
@@ -71,7 +66,9 @@ func insertRows(block *stream.Block, extraLabels []prompbmarshal.Label) error {
}
for j, value := range values {
timestamp := timestamps[j]
if err := ic.WriteDataPoint(ctx.metricNameBuf, nil, timestamp, value); err != nil {
// TODO: @f41gh7 looks like it's better to use WriteDataPointExt
// since metricName never changes inside insertRows call
if err := ic.WriteDataPoint(ctx.metricNameBuf, ic.Labels, timestamp, value); err != nil {
return err
}
}

View File

@@ -58,14 +58,9 @@ func insertRows(rows []newrelic.Row, extraLabels []prompbmarshal.Label) error {
label := &extraLabels[k]
ctx.AddLabel(label.Name, label.Value)
}
if hasRelabeling {
ctx.ApplyRelabeling()
}
if len(ctx.Labels) == 0 {
// Skip metric without labels.
if !ctx.TryPrepareLabels(hasRelabeling) {
continue
}
ctx.SortLabelsIfNeeded()
if err := ctx.WriteDataPoint(nil, ctx.Labels, r.Timestamp, s.Value); err != nil {
return err
}

View File

@@ -59,14 +59,9 @@ func insertRows(tss []prompbmarshal.TimeSeries, extraLabels []prompbmarshal.Labe
for _, label := range extraLabels {
ctx.AddLabel(label.Name, label.Value)
}
if hasRelabeling {
ctx.ApplyRelabeling()
}
if len(ctx.Labels) == 0 {
// Skip metric without labels.
if !ctx.TryPrepareLabels(hasRelabeling) {
continue
}
ctx.SortLabelsIfNeeded()
var metricNameRaw []byte
var err error
samples := ts.Samples

View File

@@ -36,14 +36,9 @@ func insertRows(rows []parser.Row) error {
tag := &r.Tags[j]
ctx.AddLabel(tag.Key, tag.Value)
}
if hasRelabeling {
ctx.ApplyRelabeling()
}
if len(ctx.Labels) == 0 {
// Skip metric without labels.
if !ctx.TryPrepareLabels(hasRelabeling) {
continue
}
ctx.SortLabelsIfNeeded()
if err := ctx.WriteDataPoint(nil, ctx.Labels, r.Timestamp, r.Value); err != nil {
return err
}

View File

@@ -54,14 +54,9 @@ func insertRows(rows []parser.Row, extraLabels []prompbmarshal.Label) error {
label := &extraLabels[j]
ctx.AddLabel(label.Name, label.Value)
}
if hasRelabeling {
ctx.ApplyRelabeling()
}
if len(ctx.Labels) == 0 {
// Skip metric without labels.
if !ctx.TryPrepareLabels(hasRelabeling) {
continue
}
ctx.SortLabelsIfNeeded()
if err := ctx.WriteDataPoint(nil, ctx.Labels, r.Timestamp, r.Value); err != nil {
return err
}

View File

@@ -54,14 +54,9 @@ func insertRows(rows []parser.Row, extraLabels []prompbmarshal.Label) error {
label := &extraLabels[j]
ctx.AddLabel(label.Name, label.Value)
}
if hasRelabeling {
ctx.ApplyRelabeling()
}
if len(ctx.Labels) == 0 {
// Skip metric without labels.
if !ctx.TryPrepareLabels(hasRelabeling) {
continue
}
ctx.SortLabelsIfNeeded()
if err := ctx.WriteDataPoint(nil, ctx.Labels, r.Timestamp, r.Value); err != nil {
return err
}

View File

@@ -57,12 +57,9 @@ func push(ctx *common.InsertCtx, tss []prompbmarshal.TimeSeries) {
label := &ts.Labels[j]
ctx.AddLabel(label.Name, label.Value)
}
ctx.ApplyRelabeling()
if len(ctx.Labels) == 0 {
// Skip metric without labels.
if !ctx.TryPrepareLabels(false) {
continue
}
ctx.SortLabelsIfNeeded()
var metricNameRaw []byte
var err error
for i := range ts.Samples {

View File

@@ -52,14 +52,10 @@ func insertRows(timeseries []prompb.TimeSeries, extraLabels []prompbmarshal.Labe
label := &extraLabels[j]
ctx.AddLabel(label.Name, label.Value)
}
if hasRelabeling {
ctx.ApplyRelabeling()
}
if len(ctx.Labels) == 0 {
// Skip metric without labels.
if !ctx.TryPrepareLabels(hasRelabeling) {
continue
}
ctx.SortLabelsIfNeeded()
var metricNameRaw []byte
var err error
samples := ts.Samples

View File

@@ -58,14 +58,9 @@ func insertRows(rows []parser.Row, extraLabels []prompbmarshal.Label) error {
label := &extraLabels[j]
ic.AddLabel(label.Name, label.Value)
}
if hasRelabeling {
ic.ApplyRelabeling()
}
if len(ic.Labels) == 0 {
// Skip metric without labels.
if !ic.TryPrepareLabels(hasRelabeling) {
continue
}
ic.SortLabelsIfNeeded()
ctx.metricNameBuf = storage.MarshalMetricNameRaw(ctx.metricNameBuf[:0], ic.Labels)
values := r.Values
timestamps := r.Timestamps

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

@@ -29,13 +29,13 @@ import (
)
var (
deleteAuthKey = flagutil.NewPassword("deleteAuthKey", "authKey for metrics' deletion via /api/v1/admin/tsdb/delete_series and /tags/delSeries. It overrides -httpAuth.*")
deleteAuthKey = flagutil.NewPassword("deleteAuthKey", "authKey for metrics' deletion via /api/v1/admin/tsdb/delete_series and /tags/delSeries. It could be passed via authKey query arg. It overrides -httpAuth.*")
maxConcurrentRequests = flag.Int("search.maxConcurrentRequests", getDefaultMaxConcurrentRequests(), "The maximum number of concurrent search requests. "+
"It shouldn't be high, since a single request can saturate all the CPU cores, while many concurrently executed requests may require high amounts of memory. "+
"See also -search.maxQueueDuration and -search.maxMemoryPerQuery")
maxQueueDuration = flag.Duration("search.maxQueueDuration", 10*time.Second, "The maximum time the request waits for execution when -search.maxConcurrentRequests "+
"limit is reached; see also -search.maxQueryDuration")
resetCacheAuthKey = flagutil.NewPassword("search.resetCacheAuthKey", "Optional authKey for resetting rollup cache via /internal/resetRollupResultCache call. It overrides -httpAuth.*")
resetCacheAuthKey = flagutil.NewPassword("search.resetCacheAuthKey", "Optional authKey for resetting rollup cache via /internal/resetRollupResultCache call. It could be passed via authKey query arg. It overrides -httpAuth.*")
logSlowQueryDuration = flag.Duration("search.logSlowQueryDuration", 5*time.Second, "Log queries with execution time exceeding this value. Zero disables slow query logging. "+
"See also -search.logQueryMemoryUsage")
vmalertProxyURL = flag.String("vmalert.proxyURL", "", "Optional URL for proxying requests to vmalert. For example, if -vmalert.proxyURL=http://vmalert:8880 , then alerting API requests such as /api/v1/rules from Grafana will be proxied to http://vmalert:8880/api/v1/rules")

View File

@@ -8,7 +8,6 @@ import (
"sort"
"sync"
"sync/atomic"
"time"
"unsafe"
"github.com/VictoriaMetrics/metrics"
@@ -1002,9 +1001,7 @@ func ExportBlocks(qt *querytracer.Tracer, sq *storage.SearchQuery, deadline sear
sr := getStorageSearch()
defer putStorageSearch(sr)
startTime := time.Now()
sr.Init(qt, vmstorage.Storage, tfss, tr, sq.MaxMetrics, deadline.Deadline())
indexSearchDuration.UpdateDuration(startTime)
// Start workers that call f in parallel on available CPU cores.
workCh := make(chan *exportWork, gomaxprocs*8)
@@ -1142,9 +1139,7 @@ func ProcessSearchQuery(qt *querytracer.Tracer, sq *storage.SearchQuery, deadlin
defer vmstorage.WG.Done()
sr := getStorageSearch()
startTime := time.Now()
maxSeriesCount := sr.Init(qt, vmstorage.Storage, tfss, tr, sq.MaxMetrics, deadline.Deadline())
indexSearchDuration.UpdateDuration(startTime)
type blockRefs struct {
brs []blockRef
}
@@ -1296,8 +1291,6 @@ func ProcessSearchQuery(qt *querytracer.Tracer, sq *storage.SearchQuery, deadlin
return &rss, nil
}
var indexSearchDuration = metrics.NewHistogram(`vm_index_search_duration_seconds`)
type blockRef struct {
partRef storage.PartRef
addr tmpBlockAddr

View File

@@ -29,6 +29,7 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httputils"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/memory"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/netutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/querytracer"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage"
)
@@ -142,10 +143,13 @@ func FederateHandler(startTime time.Time, w http.ResponseWriter, r *http.Request
WriteFederate(bb, rs)
return sw.maybeFlushBuffer(bb)
})
if err != nil {
if err == nil {
err = sw.flush()
}
if err != nil && !netutil.IsTrivialNetworkError(err) {
return fmt.Errorf("error during sending data to remote client: %w", err)
}
return sw.flush()
return nil
}
var federateDuration = metrics.NewSummary(`vm_request_duration_seconds{path="/federate"}`)
@@ -226,10 +230,13 @@ func ExportCSVHandler(startTime time.Time, w http.ResponseWriter, r *http.Reques
}()
}
err = <-doneCh
if err != nil {
if err == nil {
err = sw.flush()
}
if err != nil && !netutil.IsTrivialNetworkError(err) {
return fmt.Errorf("error during sending the exported csv data to remote client: %w", err)
}
return sw.flush()
return nil
}
var exportCSVDuration = metrics.NewSummary(`vm_request_duration_seconds{path="/api/v1/export/csv"}`)
@@ -281,10 +288,13 @@ func ExportNativeHandler(startTime time.Time, w http.ResponseWriter, r *http.Req
bb.B = dst
return sw.maybeFlushBuffer(bb)
})
if err != nil {
if err == nil {
err = sw.flush()
}
if err != nil && !netutil.IsTrivialNetworkError(err) {
return fmt.Errorf("error during sending native data to remote client: %w", err)
}
return sw.flush()
return nil
}
var exportNativeDuration = metrics.NewSummary(`vm_request_duration_seconds{path="/api/v1/export/native"}`)
@@ -441,16 +451,19 @@ func exportHandler(qt *querytracer.Tracer, w http.ResponseWriter, cp *commonPara
}()
}
err := <-doneCh
if err != nil {
if err == nil {
err = sw.flush()
}
if err == nil {
if format == "promapi" {
WriteExportPromAPIFooter(bw, qt)
}
err = bw.Flush()
}
if err != nil && !netutil.IsTrivialNetworkError(err) {
return fmt.Errorf("cannot send data to remote client: %w", err)
}
if err := sw.flush(); err != nil {
return fmt.Errorf("cannot send data to remote client: %w", err)
}
if format == "promapi" {
WriteExportPromAPIFooter(bw, qt)
}
return bw.Flush()
return nil
}
type exportBlock struct {
@@ -481,6 +494,8 @@ func DeleteHandler(startTime time.Time, r *http.Request) error {
if err != nil {
return err
}
cp.deadline = searchutils.GetDeadlineForDelete(r, startTime)
if !cp.IsDefaultTimeRange() {
return fmt.Errorf("start=%d and end=%d args aren't supported. Remove these args from the query in order to delete all the matching metrics", cp.start, cp.end)
}

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

@@ -483,8 +483,11 @@ func binaryOpOr(bfa *binaryOpFuncArg) ([]*timeseries, error) {
mLeft, mRight := createTimeseriesMapByTagSet(bfa.be, bfa.left, bfa.right)
var rvs []*timeseries
for _, tss := range mLeft {
rvs = append(rvs, tss...)
for k, tss := range mLeft {
tssLeft := removeEmptySeries(tss)
// re-assign modified slice to map, since it can be referred later
mLeft[k] = tssLeft
rvs = append(rvs, tssLeft...)
}
// Sort left-hand-side series by metric name as Prometheus does.
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5393
@@ -497,7 +500,10 @@ func binaryOpOr(bfa *binaryOpFuncArg) ([]*timeseries, error) {
rvs = append(rvs, tssRight...)
continue
}
fillLeftNaNsWithRightValues(tssLeft, tssRight)
fillLeftNaNsWithRightValuesOrMerge(tssLeft, tssRight)
// tssRight might be filled with NaNs after merge
tssRight = removeEmptySeries(tssRight)
rvs = append(rvs, tssRight...)
}
// Sort the added right-hand-side series by metric name as Prometheus does.
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5393
@@ -526,6 +532,35 @@ func fillLeftNaNsWithRightValues(tssLeft, tssRight []*timeseries) {
}
}
// fill gaps in tssLeft with values from tssRight when labels match
// Set NaNs to tssRight when tssLeft has corresponding values
// or if tssLeft and tssRight can be merged.
//
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7759
// https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7640
func fillLeftNaNsWithRightValuesOrMerge(tssLeft, tssRight []*timeseries) {
for _, tsLeft := range tssLeft {
valuesLeft := tsLeft.Values
nameLeft := tsLeft.MetricName.String()
for i, v := range valuesLeft {
leftIsNaN := math.IsNaN(v)
for _, tsRight := range tssRight {
canBeMerged := nameLeft == tsRight.MetricName.String()
valueRight := tsRight.Values[i]
if leftIsNaN && canBeMerged {
// fill NaNs with valueRight if labels match
valuesLeft[i] = valueRight
}
if !leftIsNaN || canBeMerged {
// set NaN to valueRight if valueLeft is not NaN
// or if left and right can be merged
tsRight.Values[i] = nan
}
}
}
}
}
func binaryOpIfnot(bfa *binaryOpFuncArg) ([]*timeseries, error) {
mLeft, mRight := createTimeseriesMapByTagSet(bfa.be, bfa.left, bfa.right)
var rvs []*timeseries

View File

@@ -46,6 +46,8 @@ var (
"so there is no need in spending additional CPU time on its handling. Staleness markers may exist only in data obtained from Prometheus scrape targets")
minWindowForInstantRollupOptimization = flag.Duration("search.minWindowForInstantRollupOptimization", time.Hour*3, "Enable cache-based optimization for repeated queries "+
"to /api/v1/query (aka instant queries), which contain rollup functions with lookbehind window exceeding the given value")
maxBinaryOpPushdownLabelValues = flag.Int("search.maxBinaryOpPushdownLabelValues", 100, "The maximum number of values for a label in the first expression that can be extracted as a common label filter and pushed down to the second expression in a binary operation. "+
"A larger value makes the pushed-down filter more complex but fewer time series will be returned. This flag is useful when selective label contains numerous values, for example `instance`, and storage resources are abundant.")
)
// The minimum number of points per timeseries for enabling time rounding.
@@ -582,7 +584,7 @@ func getCommonLabelFilters(tss []*timeseries) []metricsql.LabelFilter {
}
continue
}
if len(vc.values) > 100 {
if len(vc.values) > *maxBinaryOpPushdownLabelValues {
// Too many unique values found for the given tag.
// Do not make a filter on such values, since it may slow down
// search for matching time series.

View File

@@ -6,8 +6,6 @@ import (
"math"
"sort"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmselect/netstorage"
@@ -16,7 +14,6 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/querytracer"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage"
"github.com/VictoriaMetrics/metrics"
"github.com/VictoriaMetrics/metricsql"
)
@@ -270,7 +267,7 @@ func getReverseCmpOp(op string) string {
}
func parsePromQLWithCache(q string) (metricsql.Expr, error) {
pcv := parseCacheV.Get(q)
pcv := parseCacheV.get(q)
if pcv == nil {
e, err := metricsql.Parse(q)
if err == nil {
@@ -284,7 +281,7 @@ func parsePromQLWithCache(q string) (metricsql.Expr, error) {
e: e,
err: err,
}
parseCacheV.Put(q, pcv)
parseCacheV.put(q, pcv)
}
if pcv.err != nil {
return nil, pcv.err
@@ -328,80 +325,3 @@ func escapeDots(s string) string {
}
return string(result)
}
var parseCacheV = func() *parseCache {
pc := &parseCache{
m: make(map[string]*parseCacheValue),
}
metrics.NewGauge(`vm_cache_requests_total{type="promql/parse"}`, func() float64 {
return float64(pc.Requests())
})
metrics.NewGauge(`vm_cache_misses_total{type="promql/parse"}`, func() float64 {
return float64(pc.Misses())
})
metrics.NewGauge(`vm_cache_entries{type="promql/parse"}`, func() float64 {
return float64(pc.Len())
})
return pc
}()
const parseCacheMaxLen = 10e3
type parseCacheValue struct {
e metricsql.Expr
err error
}
type parseCache struct {
requests atomic.Uint64
misses atomic.Uint64
m map[string]*parseCacheValue
mu sync.RWMutex
}
func (pc *parseCache) Requests() uint64 {
return pc.requests.Load()
}
func (pc *parseCache) Misses() uint64 {
return pc.misses.Load()
}
func (pc *parseCache) Len() uint64 {
pc.mu.RLock()
n := len(pc.m)
pc.mu.RUnlock()
return uint64(n)
}
func (pc *parseCache) Get(q string) *parseCacheValue {
pc.requests.Add(1)
pc.mu.RLock()
pcv := pc.m[q]
pc.mu.RUnlock()
if pcv == nil {
pc.misses.Add(1)
}
return pcv
}
func (pc *parseCache) Put(q string, pcv *parseCacheValue) {
pc.mu.Lock()
overflow := len(pc.m) - parseCacheMaxLen
if overflow > 0 {
// Remove 10% of items from the cache.
overflow = int(float64(len(pc.m)) * 0.1)
for k := range pc.m {
delete(pc.m, k)
overflow--
if overflow <= 0 {
break
}
}
}
pc.m[q] = pcv
pc.mu.Unlock()
}

View File

@@ -4461,9 +4461,9 @@ func TestExecSuccess(t *testing.T) {
t.Run(`histogram_quantile(nan-bucket-count-some)`, func(t *testing.T) {
t.Parallel()
q := `round(histogram_quantile(0.6,
label_set(90, "foo", "bar", "le", "10")
or label_set(NaN, "foo", "bar", "le", "30")
or label_set(300, "foo", "bar", "le", "+Inf")
union(label_set(90, "foo", "bar", "le", "10"),
label_set(NaN, "foo", "bar", "le", "30"),
label_set(300, "foo", "bar", "le", "+Inf"))
),0.01)`
r := netstorage.Result{
MetricName: metricNameExpected,
@@ -9409,7 +9409,384 @@ func TestExecSuccess(t *testing.T) {
resultExpected := []netstorage.Result{r}
f(q, resultExpected)
})
t.Run(`nan or on() series`, func(t *testing.T) {
t.Parallel()
// left side returns NaNs only, so the right side should replace its values and labels
// https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7759
q := `(label_set(1, "a", "a", "b", "b1") == 0) or on(a) label_set(2, "a", "a", "b", "b2")`
r := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{2, 2, 2, 2, 2, 2},
Timestamps: timestampsExpected,
}
r.MetricName.Tags = []storage.Tag{{
Key: []byte("a"),
Value: []byte("a"),
}, {
Key: []byte("b"),
Value: []byte("b2"),
}}
resultExpected := []netstorage.Result{r}
f(q, resultExpected)
})
t.Run(`series with NaNs or scalar`, func(t *testing.T) {
t.Parallel()
q := `(label_set(time() >= 1600, "a", "a", "b", "b1")) or 1`
r1 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{nan, nan, nan, 1600, 1800, 2000},
Timestamps: timestampsExpected,
}
r1.MetricName.Tags = []storage.Tag{{
Key: []byte("a"),
Value: []byte("a"),
}, {
Key: []byte("b"),
Value: []byte("b1"),
}}
r2 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{1, 1, 1, 1, 1, 1},
Timestamps: timestampsExpected,
}
resultExpected := []netstorage.Result{r1, r2}
f(q, resultExpected)
})
t.Run(`series or on() scalar`, func(t *testing.T) {
t.Parallel()
// https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7640
q := `(label_set(time() > 1200, "a", "a", "b", "b1")) or on() vector(0)`
r1 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{nan, nan, 1400, 1600, 1800, 2000},
Timestamps: timestampsExpected,
}
r1.MetricName.Tags = []storage.Tag{{
Key: []byte("a"),
Value: []byte("a"),
}, {
Key: []byte("b"),
Value: []byte("b1"),
}}
r2 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{0, 0, nan, nan, nan, nan},
Timestamps: timestampsExpected,
}
resultExpected := []netstorage.Result{r1, r2}
f(q, resultExpected)
})
t.Run(`series or on() series`, func(t *testing.T) {
t.Parallel()
// left side + right side
q := `(label_set(time() <= 1200, "a", "a", "b", "b1")) or on(a) label_set(time() > 1200, "a", "a", "b", "b2")`
r1 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{1000, 1200, nan, nan, nan, nan},
Timestamps: timestampsExpected,
}
r1.MetricName.Tags = []storage.Tag{{
Key: []byte("a"),
Value: []byte("a"),
}, {
Key: []byte("b"),
Value: []byte("b1"),
}}
r2 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{nan, nan, 1400, 1600, 1800, 2000},
Timestamps: timestampsExpected,
}
r2.MetricName.Tags = []storage.Tag{{
Key: []byte("a"),
Value: []byte("a"),
}, {
Key: []byte("b"),
Value: []byte("b2"),
}}
resultExpected := []netstorage.Result{r1, r2}
f(q, resultExpected)
})
t.Run(`series with no NaNs or on() series`, func(t *testing.T) {
t.Parallel()
// left side contains all needed values, so the right side should be dropped
q := `(label_set(time() < 3000, "a", "a", "b", "b1")) or on(a) label_set(time() > 3000, "a", "a", "b", "b2")`
r := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{1000, 1200, 1400, 1600, 1800, 2000},
Timestamps: timestampsExpected,
}
r.MetricName.Tags = []storage.Tag{{
Key: []byte("a"),
Value: []byte("a"),
}, {
Key: []byte("b"),
Value: []byte("b1"),
}}
resultExpected := []netstorage.Result{r}
f(q, resultExpected)
})
t.Run(`series or on() series with overlap`, func(t *testing.T) {
t.Parallel()
// left overlap with right
q := `(label_set(time() <= 1500, "a", "a", "b", "b1")) or on(a) label_set(time() > 1100, "a", "a", "b", "b2")`
r1 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{1000, 1200, 1400, nan, nan, nan},
Timestamps: timestampsExpected,
}
r1.MetricName.Tags = []storage.Tag{{
Key: []byte("a"),
Value: []byte("a"),
}, {
Key: []byte("b"),
Value: []byte("b1"),
}}
r2 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{nan, nan, nan, 1600, 1800, 2000},
Timestamps: timestampsExpected,
}
r2.MetricName.Tags = []storage.Tag{{
Key: []byte("a"),
Value: []byte("a"),
}, {
Key: []byte("b"),
Value: []byte("b2"),
}}
resultExpected := []netstorage.Result{r1, r2}
f(q, resultExpected)
})
t.Run(`series or on() series merge`, func(t *testing.T) {
t.Parallel()
// left + right for same series
q := `(label_set(time() <= 1200, "a", "a", "b", "b1")) or on(a) label_set(time() > 1400, "a", "a", "b", "b1")`
r := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{1000, 1200, nan, 1600, 1800, 2000},
Timestamps: timestampsExpected,
}
r.MetricName.Tags = []storage.Tag{{
Key: []byte("a"),
Value: []byte("a"),
}, {
Key: []byte("b"),
Value: []byte("b1"),
}}
resultExpected := []netstorage.Result{r}
f(q, resultExpected)
})
t.Run(`scalar or timeseries`, func(t *testing.T) {
t.Parallel()
q := `time() > 1400 or label_set(123, "foo", "bar")`
r1 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{nan, nan, nan, 1600, 1800, 2000},
Timestamps: timestampsExpected,
}
r2 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{123, 123, 123, 123, 123, 123},
Timestamps: timestampsExpected,
}
r2.MetricName.Tags = []storage.Tag{{
Key: []byte("foo"),
Value: []byte("bar"),
}}
resultExpected := []netstorage.Result{r1, r2}
f(q, resultExpected)
})
t.Run(`series or many series`, func(t *testing.T) {
//load 1m
// foo{a="a", b="1"} 1 0 1 1 1
// bar{a="a", b="2"} 2 2 2 2 2
// bar{a="a", b="3"} 3 3 3 3 3
//
//eval range from 0 to 4m step 1m foo!=0 or on (a) bar
// foo{a="a", b="1"} 1 _ 1 1 1
// bar{a="a", b="2"} _ 2 _ _ _
// bar{a="a", b="3"} _ 3 _ _ _
// https://github.com/prometheus/prometheus/tree/main/promql/promqltest
t.Parallel()
q := `(
label_set(time()!=1200, "x", "foo"),
) or on(x) (
label_set(time()+1, "x", "foo", "y", "bar"),
label_set(time()+2, "y", "baz", "x", "foo"),
)`
r1 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{1000, nan, 1400, 1600, 1800, 2000},
Timestamps: timestampsExpected,
}
r1.MetricName.Tags = []storage.Tag{
{Key: []byte("x"), Value: []byte("foo")},
}
r2 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{nan, 1201, nan, nan, nan, nan},
Timestamps: timestampsExpected,
}
r2.MetricName.Tags = []storage.Tag{
{Key: []byte("x"), Value: []byte("foo")},
{Key: []byte("y"), Value: []byte("bar")},
}
r3 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{nan, 1202, nan, nan, nan, nan},
Timestamps: timestampsExpected,
}
r3.MetricName.Tags = []storage.Tag{
{Key: []byte("x"), Value: []byte("foo")},
{Key: []byte("y"), Value: []byte("baz")},
}
resultExpected := []netstorage.Result{r1, r2, r3}
f(q, resultExpected)
})
t.Run(`many series or series`, func(t *testing.T) {
//load 1m
// foo{a="a", b="1"} 1 0 1 1 1
// foo{a="a", b="2"} 2 2 2 2 2
// bar{a="a", b="3"} 3 3 3 3 3
//
//eval range from 0 to 4m step 1m foo!=0 or on (a) bar
// foo{a="a", b="1"} 1 _ 1 1 1
// foo{a="a", b="2"} 2 2 2 2 2
// https://github.com/prometheus/prometheus/tree/main/promql/promqltest
t.Parallel()
q := `(
label_set(time()!=1200, "x", "foo"),
label_set(time()+1, "x", "foo", "y","baz"),
) or on(x) (
label_set(time()+2, "x", "foo", "y", "bar"),
)`
r1 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{1000, nan, 1400, 1600, 1800, 2000},
Timestamps: timestampsExpected,
}
r1.MetricName.Tags = []storage.Tag{
{Key: []byte("x"), Value: []byte("foo")},
}
r2 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{1001, 1201, 1401, 1601, 1801, 2001},
Timestamps: timestampsExpected,
}
r2.MetricName.Tags = []storage.Tag{
{Key: []byte("x"), Value: []byte("foo")},
{Key: []byte("y"), Value: []byte("baz")},
}
resultExpected := []netstorage.Result{r1, r2}
f(q, resultExpected)
})
t.Run(`many series or series with no merge`, func(t *testing.T) {
// load 1m
// foo{job="a1", a="a"} 0 0 1 1 0
// foo{job="a2", a="a"} 1 1 0 0 0
// foo{job="a3", a="a"} 1 1 1 1 1
// foo{job="a4", a="a"} 1 1 1 1 1
//
//eval range from 0 to 4m step 1m (foo{job=~"a1|a2"} == 0) or on (a) (foo{job=~"a3|a4"} == 1)
// foo{job="a1", a="a"} 0 0 _ _ 0
// foo{job="a2", a="a"} _ _ 0 0 0
// https://github.com/prometheus/prometheus/tree/main/promql/promqltest
t.Parallel()
q := `(
label_set(time()!=1400, "job", "a1", "a", "a"),
label_set(time()>=1400, "job", "a2", "a", "a"),
) or on(a) (
label_set(time(), "job", "a3", "a", "a"),
label_set(time(), "job", "a4", "a", "a"),
)`
r1 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{1000, 1200, nan, 1600, 1800, 2000},
Timestamps: timestampsExpected,
}
r1.MetricName.Tags = []storage.Tag{
{Key: []byte("a"), Value: []byte("a")},
{Key: []byte("job"), Value: []byte("a1")},
}
r2 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{nan, nan, 1400, 1600, 1800, 2000},
Timestamps: timestampsExpected,
}
r2.MetricName.Tags = []storage.Tag{
{Key: []byte("a"), Value: []byte("a")},
{Key: []byte("job"), Value: []byte("a2")},
}
resultExpected := []netstorage.Result{r1, r2}
f(q, resultExpected)
})
t.Run(`many series or series with merge`, func(t *testing.T) {
// load 1m
// foo{job="a1", a="a"} 0 0 1 1 0
// foo{job="a2", a="a"} 1 1 1 0 0
// foo{job="a3", a="a"} 1 1 1 1 1
// foo{job="a4", a="a"} 1 1 1 1 1
//
//eval range from 0 to 4m step 1m (foo{job=~"a1|a2"} == 0) or on (a) (foo{job=~"a3|a4"} == 1)
// foo{job="a1", a="a"} 0 0 _ _ 0
// foo{job="a2", a="a"} _ _ _ 0 0
// foo{job="a3", a="a"} _ _ 1 _ _
// foo{job="a4", a="a"} _ _ 1 _ _
// https://github.com/prometheus/prometheus/tree/main/promql/promqltest
t.Parallel()
q := `(
label_set(time()!=1400, "job", "a1", "a", "a"),
label_set(time()>=1600, "job", "a2", "a", "a"),
) or on(a) (
label_set(time(), "job", "a3", "a", "a"),
label_set(time(), "job", "a4", "a", "a"),
)`
r1 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{1000, 1200, nan, 1600, 1800, 2000},
Timestamps: timestampsExpected,
}
r1.MetricName.Tags = []storage.Tag{
{Key: []byte("a"), Value: []byte("a")},
{Key: []byte("job"), Value: []byte("a1")},
}
r2 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{nan, nan, nan, 1600, 1800, 2000},
Timestamps: timestampsExpected,
}
r2.MetricName.Tags = []storage.Tag{
{Key: []byte("a"), Value: []byte("a")},
{Key: []byte("job"), Value: []byte("a2")},
}
r3 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{nan, nan, 1400, nan, nan, nan},
Timestamps: timestampsExpected,
}
r3.MetricName.Tags = []storage.Tag{
{Key: []byte("a"), Value: []byte("a")},
{Key: []byte("job"), Value: []byte("a3")},
}
r4 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{nan, nan, 1400, nan, nan, nan},
Timestamps: timestampsExpected,
}
r4.MetricName.Tags = []storage.Tag{
{Key: []byte("a"), Value: []byte("a")},
{Key: []byte("job"), Value: []byte("a4")},
}
resultExpected := []netstorage.Result{r1, r2, r3, r4}
f(q, resultExpected)
})
}
func TestExecError(t *testing.T) {

View File

@@ -0,0 +1,142 @@
// Cache for metricsql expressions
// Based on the fastcache idea of locking buckets in order to avoid whole cache locks.
// See: https://github.com/VictoriaMetrics/fastcache
package promql
import (
"sync"
"sync/atomic"
"github.com/VictoriaMetrics/metrics"
"github.com/VictoriaMetrics/metricsql"
xxhash "github.com/cespare/xxhash/v2"
)
var parseCacheV = func() *parseCache {
pc := newParseCache()
metrics.NewGauge(`vm_cache_requests_total{type="promql/parse"}`, func() float64 {
return float64(pc.requests())
})
metrics.NewGauge(`vm_cache_misses_total{type="promql/parse"}`, func() float64 {
return float64(pc.misses())
})
metrics.NewGauge(`vm_cache_entries{type="promql/parse"}`, func() float64 {
return float64(pc.len())
})
return pc
}()
const (
parseBucketCount = 128
parseCacheMaxLen int = 10e3
parseBucketMaxLen int = parseCacheMaxLen / parseBucketCount
parseBucketFreePercent float64 = 0.1
)
type parseCacheValue struct {
e metricsql.Expr
err error
}
type parseBucket struct {
m map[string]*parseCacheValue
mu sync.RWMutex
requests atomic.Uint64
misses atomic.Uint64
}
type parseCache struct {
buckets [parseBucketCount]parseBucket
}
func newParseCache() *parseCache {
pc := new(parseCache)
for i := 0; i < parseBucketCount; i++ {
pc.buckets[i] = newParseBucket()
}
return pc
}
func (pc *parseCache) put(q string, pcv *parseCacheValue) {
h := xxhash.Sum64String(q)
idx := h % parseBucketCount
pc.buckets[idx].put(q, pcv)
}
func (pc *parseCache) get(q string) *parseCacheValue {
h := xxhash.Sum64String(q)
idx := h % parseBucketCount
return pc.buckets[idx].get(q)
}
func (pc *parseCache) requests() uint64 {
var n uint64
for i := 0; i < parseBucketCount; i++ {
n += pc.buckets[i].requests.Load()
}
return n
}
func (pc *parseCache) misses() uint64 {
var n uint64
for i := 0; i < parseBucketCount; i++ {
n += pc.buckets[i].misses.Load()
}
return n
}
func (pc *parseCache) len() uint64 {
var n uint64
for i := 0; i < parseBucketCount; i++ {
n += pc.buckets[i].len()
}
return n
}
func newParseBucket() parseBucket {
return parseBucket{
m: make(map[string]*parseCacheValue, parseBucketMaxLen),
}
}
func (pb *parseBucket) len() uint64 {
pb.mu.RLock()
n := len(pb.m)
pb.mu.RUnlock()
return uint64(n)
}
func (pb *parseBucket) get(q string) *parseCacheValue {
pb.requests.Add(1)
pb.mu.RLock()
pcv := pb.m[q]
pb.mu.RUnlock()
if pcv == nil {
pb.misses.Add(1)
}
return pcv
}
func (pb *parseBucket) put(q string, pcv *parseCacheValue) {
pb.mu.Lock()
overflow := len(pb.m) - parseBucketMaxLen
if overflow > 0 {
// Remove parseBucketDeletePercent*100 % of items from the bucket.
overflow = int(float64(len(pb.m)) * parseBucketFreePercent)
for k := range pb.m {
delete(pb.m, k)
overflow--
if overflow <= 0 {
break
}
}
}
pb.m[q] = pcv
pb.mu.Unlock()
}

View File

@@ -0,0 +1,129 @@
package promql
import (
"fmt"
"testing"
"github.com/VictoriaMetrics/metricsql"
)
func testGetParseCacheValue(q string) *parseCacheValue {
e, err := metricsql.Parse(q)
return &parseCacheValue{
e: e,
err: err,
}
}
func testGenerateQueries(items int) []string {
queries := make([]string, items)
for i := 0; i < items; i++ {
queries[i] = fmt.Sprintf(`node_time_seconds{instance="node%d", job="job%d"}`, i, i)
}
return queries
}
func TestParseCache(t *testing.T) {
pc := newParseCache()
if pc.len() != 0 || pc.misses() != 0 || pc.requests() != 0 {
t.Errorf("unexpected pc.Len()=%d, pc.Misses()=%d, pc.Requests()=%d; expected all to be zero.", pc.len(), pc.misses(), pc.requests())
}
q1 := `foo{bar="baz"}`
v1 := testGetParseCacheValue(q1)
q2 := `foo1{bar1="baz1"}`
v2 := testGetParseCacheValue(q2)
pc.put(q1, v1)
if pc.len() != 1 {
t.Errorf("unexpected value obtained; got %d; want %d", pc.len(), 1)
}
if res := pc.get(q2); res != nil {
t.Errorf("unexpected non-empty value obtained from cache: %d ", res)
}
if pc.len() != 1 {
t.Errorf("unexpected value obtained; got %d; want %d", pc.len(), 1)
}
if miss := pc.misses(); miss != 1 {
t.Errorf("unexpected value obtained; got %d; want %d", miss, 1)
}
if req := pc.requests(); req != 1 {
t.Errorf("unexpected value obtained; got %d; want %d", req, 1)
}
pc.put(q2, v2)
if pc.len() != 2 {
t.Errorf("unexpected value obtained; got %d; want %d", pc.len(), 2)
}
if res := pc.get(q1); res != v1 {
t.Errorf("unexpected value obtained; got %v; want %v", res, v1)
}
if res := pc.get(q2); res != v2 {
t.Errorf("unexpected value obtained; got %v; want %v", res, v2)
}
pc.put(q2, v2)
if pc.len() != 2 {
t.Errorf("unexpected value obtained; got %d; want %d", pc.len(), 2)
}
if miss := pc.misses(); miss != 1 {
t.Errorf("unexpected value obtained; got %d; want %d", miss, 1)
}
if req := pc.requests(); req != 3 {
t.Errorf("unexpected value obtained; got %d; want %d", req, 3)
}
if res := pc.get(q2); res != v2 {
t.Errorf("unexpected value obtained; got %v; want %v", res, v2)
}
if pc.len() != 2 {
t.Errorf("unexpected value obtained; got %d; want %d", pc.len(), 2)
}
if miss := pc.misses(); miss != 1 {
t.Errorf("unexpected value obtained; got %d; want %d", miss, 1)
}
if req := pc.requests(); req != 4 {
t.Errorf("unexpected value obtained; got %d; want %d", req, 4)
}
}
func TestParseCacheBucketOverflow(t *testing.T) {
b := newParseBucket()
var expectedLen uint64
// +2 for overflow and clean up
queries := testGenerateQueries(parseBucketMaxLen + 2)
// Same value for all keys
v := testGetParseCacheValue(queries[0])
// Fill bucket
for i := 0; i < parseBucketMaxLen; i++ {
b.put(queries[i], v)
}
expectedLen = uint64(parseBucketMaxLen)
if b.len() != expectedLen {
t.Errorf("unexpected value obtained; got %v; want %v", b.len(), expectedLen)
}
// Overflow bucket
expectedLen = uint64(parseBucketMaxLen + 1)
b.put(queries[parseBucketMaxLen], v)
if b.len() != uint64(expectedLen) {
t.Errorf("unexpected value obtained; got %v; want %v", b.len(), expectedLen)
}
// Clean up;
oldLen := b.len()
overflow := int(float64(oldLen) * parseBucketFreePercent)
expectedLen = oldLen - uint64(overflow) + 1 // +1 for new entry
b.put(queries[parseBucketMaxLen+1], v)
if b.len() != expectedLen {
t.Errorf("unexpected value obtained; got %v; want %v", b.len(), expectedLen)
}
}

View File

@@ -0,0 +1,235 @@
package promql
import (
"testing"
)
func BenchmarkCachePutNoOverFlow(b *testing.B) {
const items int = (parseCacheMaxLen / 2)
pc := newParseCache()
queries := testGenerateQueries(items)
v := testGetParseCacheValue(queries[0])
b.ResetTimer()
b.ReportAllocs()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
for i := 0; i < items; i++ {
pc.put(queries[i], v)
}
}
})
if pc.len() != uint64(items) {
b.Errorf("unexpected value obtained; got %d; want %d", pc.len(), items)
}
}
func BenchmarkCacheGetNoOverflow(b *testing.B) {
const items int = parseCacheMaxLen / 2
pc := newParseCache()
queries := testGenerateQueries(items)
v := testGetParseCacheValue(queries[0])
for i := 0; i < len(queries); i++ {
pc.put(queries[i], v)
}
b.ResetTimer()
b.ReportAllocs()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
for i := 0; i < items; i++ {
if v := pc.get(queries[i]); v == nil {
b.Errorf("unexpected nil value obtained from cache for query: %s ", queries[i])
}
}
}
})
}
func BenchmarkCachePutGetNoOverflow(b *testing.B) {
const items int = parseCacheMaxLen / 2
pc := newParseCache()
queries := testGenerateQueries(items)
v := testGetParseCacheValue(queries[0])
b.ResetTimer()
b.ReportAllocs()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
for i := 0; i < items; i++ {
pc.put(queries[i], v)
if res := pc.get(queries[i]); res == nil {
b.Errorf("unexpected nil value obtained from cache for query: %s ", queries[i])
}
}
}
})
if pc.len() != uint64(items) {
b.Errorf("unexpected value obtained; got %d; want %d", pc.len(), items)
}
}
func BenchmarkCachePutOverflow(b *testing.B) {
const items int = parseCacheMaxLen + (parseCacheMaxLen / 2)
c := newParseCache()
queries := testGenerateQueries(items)
v := testGetParseCacheValue(queries[0])
for i := 0; i < parseCacheMaxLen; i++ {
c.put(queries[i], v)
}
b.ReportAllocs()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
for i := parseCacheMaxLen; i < items; i++ {
c.put(queries[i], v)
}
}
})
maxElemnts := uint64(parseCacheMaxLen + parseBucketCount)
if c.len() > maxElemnts {
b.Errorf("cache length is more than expected; got %d, expected %d", c.len(), maxElemnts)
}
}
func BenchmarkCachePutGetOverflow(b *testing.B) {
const items int = parseCacheMaxLen + (parseCacheMaxLen / 2)
c := newParseCache()
queries := testGenerateQueries(items)
v := testGetParseCacheValue(queries[0])
for i := 0; i < parseCacheMaxLen; i++ {
c.put(queries[i], v)
}
b.ReportAllocs()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
for i := parseCacheMaxLen; i < items; i++ {
c.put(queries[i], v)
c.get(queries[i])
}
}
})
maxElemnts := uint64(parseCacheMaxLen + parseBucketCount)
if c.len() > maxElemnts {
b.Errorf("cache length is more than expected; got %d, expected %d", c.len(), maxElemnts)
}
}
var testSimpleQueries = []string{
`m{a="b"}`,
`{a="b"}`,
`m{c="d",a="b"}`,
`{a="b",c="d"}`,
`m1{a="foo"}`,
`m2{a="bar"}`,
`m1{b="foo"}`,
`m2{b="bar"}`,
`m1{a="foo",b="bar"}`,
`m2{b="bar",c="x"}`,
`{b="bar"}`,
}
func BenchmarkParsePromQLWithCacheSimple(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for j := 0; j < len(testSimpleQueries); j++ {
_, err := parsePromQLWithCache(testSimpleQueries[j])
if err != nil {
b.Errorf("unexpected error: %s", err)
}
}
}
}
func BenchmarkParsePromQLWithCacheSimpleParallel(b *testing.B) {
b.ReportAllocs()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
for i := 0; i < len(testSimpleQueries); i++ {
_, err := parsePromQLWithCache(testSimpleQueries[i])
if err != nil {
b.Errorf("unexpected error: %s", err)
}
}
}
})
}
var testComplexQueries = []string{
`sort_desc(label_set(2, "foo", "bar") * ignoring(a) (label_set(time(), "foo", "bar") or label_set(10, "foo", "qwert")))`,
`sum(a.b{c="d.e",x=~"a.b.+[.a]",y!~"aaa.bb|cc.dd"}) + avg_over_time(1,sum({x=~"aa.bb"}))`,
`sort((label_set(time() offset 100s, "foo", "bar"), label_set(time()+10, "foo", "baz") offset 50s) offset 400s)`,
`sort(label_map((
label_set(time(), "label", "v1"),
label_set(time()+100, "label", "v2"),
label_set(time()+200, "label", "v3"),
label_set(time()+300, "x", "y"),
label_set(time()+400, "label", "v4"),
), "label", "v1", "foo", "v2", "bar", "", "qwe", "v4", ""))`,
`sort(labels_equal((
label_set(10, "instance", "qwe", "host", "rty"),
label_set(20, "instance", "qwe", "host", "qwe"),
label_set(30, "aaa", "bbb", "instance", "foo", "host", "foo"),
), "instance", "host"))`,
`with (
x = (
label_set(time() > 1500, "foo", "123.456", "__name__", "aaa"),
label_set(-time(), "foo", "bar", "__name__", "bbb"),
label_set(-time(), "__name__", "bxs"),
label_set(-time(), "foo", "45", "bar", "xs"),
)
)
sort(x + label_value(x, "foo"))`,
`label_replace(
label_replace(
label_replace(time(), "__name__", "x${1}y", "foo", ".*"),
"xxx", "foo${1}bar(${1})", "__name__", "(.+)"),
"xxx", "AA$1", "xxx", "foox(.+)"
)`,
`sort_desc(union(
label_set(time() > 1400, "__name__", "x", "foo", "bar"),
label_set(time() < 1700, "__name__", "y", "foo", "baz")) default 123)`,
`sort(histogram_quantile(0.6,
label_set(90, "foo", "bar", "le", "10")
or label_set(100, "foo", "bar", "le", "30")
or label_set(300, "foo", "bar", "le", "+Inf")
or label_set(200, "tag", "xx", "le", "10")
or label_set(300, "tag", "xx", "le", "30")
))`,
}
func BenchmarkParsePromQLWithCacheComplex(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for j := 0; j < len(testComplexQueries); j++ {
_, err := parsePromQLWithCache(testComplexQueries[j])
if err != nil {
b.Errorf("unexpected error: %s", err)
}
}
}
}
func BenchmarkParsePromQLWithCacheComplexParallel(b *testing.B) {
b.ReportAllocs()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
for i := 0; i < len(testComplexQueries); i++ {
_, err := parsePromQLWithCache(testComplexQueries[i])
if err != nil {
b.Errorf("unexpected error: %s", err)
}
}
}
})
}

View File

@@ -374,8 +374,8 @@ func getRollupConfigs(funcName string, rf rollupFunc, expr metricsql.Expr, start
preFunc := func(_ []float64, _ []int64) {}
funcName = strings.ToLower(funcName)
if rollupFuncsRemoveCounterResets[funcName] {
preFunc = func(values []float64, _ []int64) {
removeCounterResets(values)
preFunc = func(values []float64, timestamps []int64) {
removeCounterResets(values, timestamps, lookbackDelta)
}
}
samplesScannedPerCall := rollupFuncsSamplesScannedPerCall[funcName]
@@ -486,8 +486,8 @@ func getRollupConfigs(funcName string, rf rollupFunc, expr metricsql.Expr, start
for _, aggrFuncName := range aggrFuncNames {
if rollupFuncsRemoveCounterResets[aggrFuncName] {
// There is no need to save the previous preFunc, since it is either empty or the same.
preFunc = func(values []float64, _ []int64) {
removeCounterResets(values)
preFunc = func(values []float64, timestamps []int64) {
removeCounterResets(values, timestamps, lookbackDelta)
}
}
rf := rollupAggrFuncs[aggrFuncName]
@@ -520,7 +520,8 @@ type rollupFuncArg struct {
// Timestamps for values.
timestamps []int64
// Real value preceding values without restrictions on staleness interval.
// Real value preceding values.
// Is populated if preceding value is within the -search.maxStalenessInterval (rc.LookbackDelta).
realPrevValue float64
// Real value which goes after values.
@@ -699,8 +700,13 @@ func (rc *rollupConfig) doInternal(dstValues []float64, tsm *timeseriesMap, valu
// Extend dstValues in order to remove mallocs below.
dstValues = decimal.ExtendFloat64sCapacity(dstValues, len(rc.Timestamps))
scrapeInterval := getScrapeInterval(timestamps, rc.Step)
maxPrevInterval := getMaxPrevInterval(scrapeInterval)
// Use step as the scrape interval for instant queries (when start == end).
maxPrevInterval := rc.Step
if rc.Start < rc.End {
scrapeInterval := getScrapeInterval(timestamps, rc.Step)
maxPrevInterval = getMaxPrevInterval(scrapeInterval)
}
if rc.LookbackDelta > 0 && maxPrevInterval > rc.LookbackDelta {
maxPrevInterval = rc.LookbackDelta
}
@@ -759,10 +765,18 @@ func (rc *rollupConfig) doInternal(dstValues []float64, tsm *timeseriesMap, valu
}
rfa.values = values[i:j]
rfa.timestamps = timestamps[i:j]
rfa.realPrevValue = nan
if i > 0 {
rfa.realPrevValue = values[i-1]
} else {
rfa.realPrevValue = nan
prevValue, prevTimestamp := values[i-1], timestamps[i-1]
// set realPrevValue if rc.LookbackDelta == 0
// or if distance between datapoint in prev interval and beginning of this interval
// doesn't exceed LookbackDelta.
// https://github.com/VictoriaMetrics/VictoriaMetrics/pull/1381
// https://github.com/VictoriaMetrics/VictoriaMetrics/issues/894
// https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8045
if rc.LookbackDelta == 0 || (tStart-prevTimestamp) < rc.LookbackDelta {
rfa.realPrevValue = prevValue
}
}
if j < len(values) {
rfa.realNextValue = values[j]
@@ -886,7 +900,7 @@ func getMaxPrevInterval(scrapeInterval int64) int64 {
return scrapeInterval + scrapeInterval/8
}
func removeCounterResets(values []float64) {
func removeCounterResets(values []float64, timestamps []int64, maxStalenessInterval int64) {
// There is no need in handling NaNs here, since they are impossible
// on values from vmstorage.
if len(values) == 0 {
@@ -905,6 +919,16 @@ func removeCounterResets(values []float64) {
correction += prevValue
}
}
if i > 0 && maxStalenessInterval > 0 {
gap := timestamps[i] - timestamps[i-1]
if gap > maxStalenessInterval {
// reset correction if gap between samples exceeds staleness interval
// see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8072
correction = 0
prevValue = v
continue
}
}
prevValue = v
values[i] = v + correction
// Check again, there could be precision error in float operations,
@@ -1677,9 +1701,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 {
@@ -2187,38 +2211,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

@@ -5,6 +5,8 @@ import (
"testing"
"github.com/VictoriaMetrics/metricsql"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/decimal"
)
var (
@@ -115,31 +117,49 @@ func TestRollupIderivDuplicateTimestamps(t *testing.T) {
}
func TestRemoveCounterResets(t *testing.T) {
removeCounterResets(nil)
removeCounterResets(nil, nil, 0)
values := append([]float64{}, testValues...)
removeCounterResets(values)
timestamps := append([]int64{}, testTimestamps...)
removeCounterResets(values, timestamps, 0)
valuesExpected := []float64{123, 157, 167, 188, 221, 255, 320, 332, 364, 396, 398, 398}
testRowsEqual(t, values, testTimestamps, valuesExpected, testTimestamps)
// removeCounterResets doesn't expect negative values, so it doesn't work properly with them.
values = []float64{-100, -200, -300, -400}
removeCounterResets(values)
valuesExpected = []float64{-100, -100, -100, -100}
timestampsExpected := []int64{0, 1, 2, 3}
removeCounterResets(values, timestampsExpected, 0)
valuesExpected = []float64{-100, -100, -100, -100}
testRowsEqual(t, values, timestampsExpected, valuesExpected, timestampsExpected)
// verify how partial counter reset is handled.
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2787
values = []float64{100, 95, 120, 119, 139, 50}
removeCounterResets(values)
valuesExpected = []float64{100, 100, 125, 125, 145, 195}
timestampsExpected = []int64{0, 1, 2, 3, 4, 5}
removeCounterResets(values, timestampsExpected, 0)
valuesExpected = []float64{100, 100, 125, 125, 145, 195}
testRowsEqual(t, values, timestampsExpected, valuesExpected, timestampsExpected)
// verify that staleness interval is respected during resets
// see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8072
values = []float64{10, 12, 14, 4, 6, 8, 6, 8, 4, 6}
timestamps = []int64{10, 20, 30, 60, 70, 80, 90, 100, 120, 130}
valuesExpected = []float64{10, 12, 14, 4, 6, 8, 14, 16, 4, 6}
removeCounterResets(values, timestamps, 10)
testRowsEqual(t, values, timestamps, valuesExpected, timestamps)
// verify that staleness is respected if there was no counter reset
// but correction was made previously
values = []float64{10, 12, 2, 4}
timestamps = []int64{10, 20, 30, 60}
valuesExpected = []float64{10, 12, 14, 4}
removeCounterResets(values, timestamps, 10)
testRowsEqual(t, values, timestamps, valuesExpected, timestamps)
// verify results always increase monotonically with possible float operations precision error
values = []float64{34.094223, 2.7518, 2.140669, 0.044878, 1.887095, 2.546569, 2.490149, 0.045, 0.035684, 0.062454, 0.058296}
removeCounterResets(values)
timestampsExpected = []int64{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
removeCounterResets(values, timestampsExpected, 0)
var prev float64
for i, v := range values {
if v < prev {
@@ -164,7 +184,7 @@ func TestDeltaValues(t *testing.T) {
// remove counter resets
values = append([]float64{}, testValues...)
removeCounterResets(values)
removeCounterResets(values, testTimestamps, 0)
deltaValues(values)
valuesExpected = []float64{34, 10, 21, 33, 34, 65, 12, 32, 32, 2, 0, 0}
testRowsEqual(t, values, testTimestamps, valuesExpected, testTimestamps)
@@ -186,7 +206,7 @@ func TestDerivValues(t *testing.T) {
// remove counter resets
values = append([]float64{}, testValues...)
removeCounterResets(values)
removeCounterResets(values, testTimestamps, 0)
derivValues(values, testTimestamps)
valuesExpected = []float64{3400, 1111.111111111111, 1750, 2538.4615384615386, 3090.909090909091, 3611.1111111111113,
6000, 1882.3529411764705, 1777.7777777777778, 400, 0, 0}
@@ -217,7 +237,7 @@ func testRollupFunc(t *testing.T, funcName string, args []any, vExpected float64
rfa.timestamps = append(rfa.timestamps, testTimestamps...)
rfa.window = rfa.timestamps[len(rfa.timestamps)-1] - rfa.timestamps[0]
if rollupFuncsRemoveCounterResets[funcName] {
removeCounterResets(rfa.values)
removeCounterResets(rfa.values, rfa.timestamps, 0)
}
for i := 0; i < 5; i++ {
v := rf(&rfa)
@@ -1587,3 +1607,229 @@ func TestRollupDelta(t *testing.T) {
f(1, nan, nan, nil, 0)
f(100, nan, nan, nil, 0)
}
func TestRollupDeltaWithStaleness(t *testing.T) {
// there is a gap between samples in the dataset below
timestamps := []int64{0, 15000, 30000, 70000}
values := []float64{1, 1, 1, 1}
// if step > gap, then delta will always respect value before gap
t.Run("step>gap", func(t *testing.T) {
rc := rollupConfig{
Func: rollupDelta,
Start: 0,
End: 70000,
Step: 45000,
Window: 0,
MaxPointsPerSeries: 1e4,
}
rc.Timestamps = rc.getTimestamps()
gotValues, samplesScanned := rc.Do(nil, values, timestamps)
if samplesScanned != 7 {
t.Fatalf("expecting 8 samplesScanned from rollupConfig.Do; got %d", samplesScanned)
}
valuesExpected := []float64{1, 0}
timestampsExpected := []int64{0, 45e3}
testRowsEqual(t, gotValues, rc.Timestamps, valuesExpected, timestampsExpected)
})
// even if LookbackDelta < gap
t.Run("step>gap;LookbackDelta<gap", func(t *testing.T) {
rc := rollupConfig{
Func: rollupDelta,
Start: 0,
End: 70000,
Step: 45000,
LookbackDelta: 10e3,
Window: 0,
MaxPointsPerSeries: 1e4,
}
rc.Timestamps = rc.getTimestamps()
gotValues, samplesScanned := rc.Do(nil, values, timestamps)
if samplesScanned != 7 {
t.Fatalf("expecting 8 samplesScanned from rollupConfig.Do; got %d", samplesScanned)
}
valuesExpected := []float64{1, 0}
timestampsExpected := []int64{0, 45e3}
testRowsEqual(t, gotValues, rc.Timestamps, valuesExpected, timestampsExpected)
})
// if step < gap and LookbackDelta==0 then delta will always respect value before gap
// as LookbackDelta=0 ignores staleness
t.Run("step<gap;LookbackDelta=0", func(t *testing.T) {
rc := rollupConfig{
Func: rollupDelta,
Start: 0,
End: 70000,
Step: 10000,
LookbackDelta: 0,
Window: 0,
MaxPointsPerSeries: 1e4,
}
rc.Timestamps = rc.getTimestamps()
gotValues, samplesScanned := rc.Do(nil, values, timestamps)
if samplesScanned != 8 {
t.Fatalf("expecting 8 samplesScanned from rollupConfig.Do; got %d", samplesScanned)
}
valuesExpected := []float64{1, 0, 0, 0, 0, 0, 0, 0}
timestampsExpected := []int64{0, 10e3, 20e3, 30e3, 40e3, 50e3, 60e3, 70e3}
testRowsEqual(t, gotValues, rc.Timestamps, valuesExpected, timestampsExpected)
})
// if step < gap and LookbackDelta>0 then delta will respect value before gap
// only if it is not stale according to LookbackDelta
t.Run("step<gap;LookbackDelta>0", func(t *testing.T) {
rc := rollupConfig{
Func: rollupDelta,
Start: 0,
End: 70000,
Step: 10000,
Window: 0,
MaxPointsPerSeries: 1e4,
LookbackDelta: 30e3,
}
rc.Timestamps = rc.getTimestamps()
gotValues, samplesScanned := rc.Do(nil, values, timestamps)
if samplesScanned != 8 {
t.Fatalf("expecting 8 samplesScanned from rollupConfig.Do; got %d", samplesScanned)
}
valuesExpected := []float64{1, 0, 0, 0, 0, 0, 0, 1}
timestampsExpected := []int64{0, 10e3, 20e3, 30e3, 40e3, 50e3, 60e3, 70e3}
testRowsEqual(t, gotValues, rc.Timestamps, valuesExpected, timestampsExpected)
})
// there is a staleness marker between samples in the dataset below
timestamps = []int64{0, 10000, 20000, 30000, 40000}
values = []float64{1, 1, 1, decimal.StaleNaN, 1}
t.Run("staleness marker", func(t *testing.T) {
rc := rollupConfig{
Func: rollupDelta,
Start: 0,
End: 40000,
Step: 10000,
Window: 0,
MaxPointsPerSeries: 1e4,
}
rc.Timestamps = rc.getTimestamps()
gotValues, samplesScanned := rc.Do(nil, values, timestamps)
if samplesScanned != 10 {
t.Fatalf("expecting 10 samplesScanned from rollupConfig.Do; got %d", samplesScanned)
}
valuesExpected := []float64{1, 0, 0, nan, 1}
timestampsExpected := []int64{0, 10e3, 20e3, 30e3, 40e3}
testRowsEqual(t, gotValues, rc.Timestamps, valuesExpected, timestampsExpected)
})
}
func TestRollupIncreasePureWithStaleness(t *testing.T) {
// there is a gap between samples in the dataset below
timestamps := []int64{0, 15000, 30000, 70000}
values := []float64{1, 1, 1, 1}
// if step > gap, then delta will always respect value before gap
t.Run("step>gap", func(t *testing.T) {
rc := rollupConfig{
Func: rollupIncreasePure,
Start: 0,
End: 70000,
Step: 45000,
Window: 0,
MaxPointsPerSeries: 1e4,
}
rc.Timestamps = rc.getTimestamps()
gotValues, samplesScanned := rc.Do(nil, values, timestamps)
if samplesScanned != 7 {
t.Fatalf("expecting 8 samplesScanned from rollupConfig.Do; got %d", samplesScanned)
}
valuesExpected := []float64{1, 0}
timestampsExpected := []int64{0, 45e3}
testRowsEqual(t, gotValues, rc.Timestamps, valuesExpected, timestampsExpected)
})
// even if LookbackDelta < gap
t.Run("step>gap;LookbackDelta<gap", func(t *testing.T) {
rc := rollupConfig{
Func: rollupIncreasePure,
Start: 0,
End: 70000,
Step: 45000,
LookbackDelta: 10e3,
Window: 0,
MaxPointsPerSeries: 1e4,
}
rc.Timestamps = rc.getTimestamps()
gotValues, samplesScanned := rc.Do(nil, values, timestamps)
if samplesScanned != 7 {
t.Fatalf("expecting 8 samplesScanned from rollupConfig.Do; got %d", samplesScanned)
}
valuesExpected := []float64{1, 0}
timestampsExpected := []int64{0, 45e3}
testRowsEqual(t, gotValues, rc.Timestamps, valuesExpected, timestampsExpected)
})
// if step < gap and LookbackDelta==0 then delta will always respect value before gap
// as LookbackDelta=0 ignores staleness
t.Run("step<gap;LookbackDelta=0", func(t *testing.T) {
rc := rollupConfig{
Func: rollupIncreasePure,
Start: 0,
End: 70000,
Step: 10000,
LookbackDelta: 0,
Window: 0,
MaxPointsPerSeries: 1e4,
}
rc.Timestamps = rc.getTimestamps()
gotValues, samplesScanned := rc.Do(nil, values, timestamps)
if samplesScanned != 8 {
t.Fatalf("expecting 8 samplesScanned from rollupConfig.Do; got %d", samplesScanned)
}
valuesExpected := []float64{1, 0, 0, 0, 0, 0, 0, 0}
timestampsExpected := []int64{0, 10e3, 20e3, 30e3, 40e3, 50e3, 60e3, 70e3}
testRowsEqual(t, gotValues, rc.Timestamps, valuesExpected, timestampsExpected)
})
// if step < gap and LookbackDelta>0 then delta will respect value before gap
// only if it is not stale according to LookbackDelta
t.Run("step<gap;LookbackDelta>0", func(t *testing.T) {
rc := rollupConfig{
Func: rollupIncreasePure,
Start: 0,
End: 70000,
Step: 10000,
Window: 0,
MaxPointsPerSeries: 1e4,
LookbackDelta: 30e3,
}
rc.Timestamps = rc.getTimestamps()
gotValues, samplesScanned := rc.Do(nil, values, timestamps)
if samplesScanned != 8 {
t.Fatalf("expecting 8 samplesScanned from rollupConfig.Do; got %d", samplesScanned)
}
valuesExpected := []float64{1, 0, 0, 0, 0, 0, 0, 1}
timestampsExpected := []int64{0, 10e3, 20e3, 30e3, 40e3, 50e3, 60e3, 70e3}
testRowsEqual(t, gotValues, rc.Timestamps, valuesExpected, timestampsExpected)
})
// there is a staleness marker between samples in the dataset below
timestamps = []int64{0, 10000, 20000, 30000, 40000}
values = []float64{1, 1, 1, decimal.StaleNaN, 1}
t.Run("staleness marker", func(t *testing.T) {
rc := rollupConfig{
Func: rollupIncreasePure,
Start: 0,
End: 40000,
Step: 10000,
Window: 0,
MaxPointsPerSeries: 1e4,
}
rc.Timestamps = rc.getTimestamps()
gotValues, samplesScanned := rc.Do(nil, values, timestamps)
if samplesScanned != 10 {
t.Fatalf("expecting 10 samplesScanned from rollupConfig.Do; got %d", samplesScanned)
}
valuesExpected := []float64{1, 0, 0, nan, 1}
timestampsExpected := []int64{0, 10e3, 20e3, 30e3, 40e3}
testRowsEqual(t, gotValues, rc.Timestamps, valuesExpected, timestampsExpected)
})
}

View File

@@ -15,6 +15,7 @@ import (
var (
maxExportDuration = flag.Duration("search.maxExportDuration", time.Hour*24*30, "The maximum duration for /api/v1/export call")
maxDeleteDuration = flag.Duration("search.maxDeleteDuration", time.Minute*5, "The maximum duration for /api/v1/admin/tsdb/delete_series call")
maxQueryDuration = flag.Duration("search.maxQueryDuration", time.Second*30, "The maximum duration for query execution. It can be overridden to a smaller value on a per-query basis via 'timeout' query arg")
maxStatusRequestDuration = flag.Duration("search.maxStatusRequestDuration", time.Minute*5, "The maximum duration for /api/v1/status/* requests")
maxLabelsAPIDuration = flag.Duration("search.maxLabelsAPIDuration", time.Second*5, "The maximum duration for /api/v1/labels, /api/v1/label/.../values and /api/v1/series requests. "+
@@ -58,6 +59,12 @@ func GetDeadlineForLabelsAPI(r *http.Request, startTime time.Time) Deadline {
return getDeadlineWithMaxDuration(r, startTime, dMax, "-search.maxLabelsAPIDuration")
}
// GetDeadlineForDelete returns deadline for the given request to /api/v1/admin/tsdb/delete_series.
func GetDeadlineForDelete(r *http.Request, startTime time.Time) Deadline {
dMax := maxDeleteDuration.Milliseconds()
return getDeadlineWithMaxDuration(r, startTime, dMax, "-search.maxDeleteDuration")
}
func getDeadlineWithMaxDuration(r *http.Request, startTime time.Time, dMax int64, flagHint string) Deadline {
d, err := httputils.GetDuration(r, "timeout", 0)
if err != nil {

View File

@@ -99,7 +99,8 @@ func TestParseMetricSelectorSuccess(t *testing.T) {
f(`{foo="bar"}`)
f(`{:f:oo=~"bar.+"}`)
f(`foo {bar != "baz"}`)
f(` foo { bar !~ "^ddd(x+)$", a="ss", __name__="sffd"} `)
f(` { bar !~ "^ddd(x+)$", a="ss", __name__="sffd"} `)
f(` { bar !~ "^ddd(x+)$", a="ss", "foo"} `)
f(`(foo)`)
f(`\п\р\и\в\е{\ы="111"}`)
}

View File

@@ -1,13 +1,13 @@
{
"files": {
"main.css": "./static/css/main.b1929c64.css",
"main.js": "./static/js/main.a7d57628.js",
"main.css": "./static/css/main.7fa18e1b.css",
"main.js": "./static/js/main.ba08300f.js",
"static/js/685.f772060c.chunk.js": "./static/js/685.f772060c.chunk.js",
"static/media/MetricsQL.md": "./static/media/MetricsQL.a00044c91d9781cf8557.md",
"index.html": "./index.html"
},
"entrypoints": [
"static/css/main.b1929c64.css",
"static/js/main.a7d57628.js"
"static/css/main.7fa18e1b.css",
"static/js/main.ba08300f.js"
]
}

View File

@@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="./favicon.svg"/><link rel="apple-touch-icon" href="./favicon.svg"/><link rel="mask-icon" href="./favicon.svg" color="#000000"><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=5"/><meta name="theme-color" content="#000000"/><meta name="description" content="Explore and troubleshoot your VictoriaMetrics data"/><link rel="manifest" href="./manifest.json"/><title>vmui</title><script src="./dashboards/index.js" type="module"></script><meta name="twitter:card" content="summary"><meta name="twitter:title" content="UI for VictoriaMetrics"><meta name="twitter:site" content="@https://victoriametrics.com/"><meta name="twitter:description" content="Explore and troubleshoot your VictoriaMetrics data"><meta name="twitter:image" content="./preview.jpg"><meta property="og:type" content="website"><meta property="og:title" content="UI for VictoriaMetrics"><meta property="og:url" content="https://victoriametrics.com/"><meta property="og:description" content="Explore and troubleshoot your VictoriaMetrics data"><script defer="defer" src="./static/js/main.a7d57628.js"></script><link href="./static/css/main.b1929c64.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="./favicon.svg"/><link rel="apple-touch-icon" href="./favicon.svg"/><link rel="mask-icon" href="./favicon.svg" color="#000000"><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=5"/><meta name="theme-color" content="#000000"/><meta name="description" content="Explore and troubleshoot your VictoriaMetrics data"/><link rel="manifest" href="./manifest.json"/><title>vmui</title><script src="./dashboards/index.js" type="module"></script><meta name="twitter:card" content="summary"><meta name="twitter:title" content="UI for VictoriaMetrics"><meta name="twitter:site" content="@https://victoriametrics.com/"><meta name="twitter:description" content="Explore and troubleshoot your VictoriaMetrics data"><meta name="twitter:image" content="./preview.jpg"><meta property="og:type" content="website"><meta property="og:title" content="UI for VictoriaMetrics"><meta property="og:url" content="https://victoriametrics.com/"><meta property="og:description" content="Explore and troubleshoot your VictoriaMetrics data"><script defer="defer" src="./static/js/main.ba08300f.js"></script><link href="./static/css/main.7fa18e1b.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>

File diff suppressed because one or more lines are too long

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