Compare commits

...

33 Commits

Author SHA1 Message Date
Aliaksandr Valialkin
ca653a515c docs/VictoriaLogs/CHANGELOG.md: cut v1.6.0-victorialogs release 2025-01-15 22:31:13 +01:00
Aliaksandr Valialkin
e5b4cf33bf lib/logstorage: make golangci-lint happy after f27e120aeb 2025-01-15 22:28:13 +01:00
Aliaksandr Valialkin
e24a8f2088 deployment/docker: update Alpine Docker image from 3.21.0 to 3.21.2
See https://alpinelinux.org/posts/Alpine-3.21.1-released.html and https://alpinelinux.org/posts/Alpine-3.18.11-3.19.6-3.20.5-3.21.2-released.html
2025-01-15 22:26:26 +01:00
Aliaksandr Valialkin
f27e120aeb lib/logstorage: add union pipe, which allows uniting results from multiple queries 2025-01-15 22:22:07 +01:00
Aliaksandr Valialkin
ee1ce90501 lib/logstorage: properly drop temporary directories created by filter* tests 2025-01-15 22:22:07 +01:00
Aliaksandr Valialkin
47fe8cf3be lib/logstorage: math pipe: add rand() function 2025-01-15 22:22:06 +01:00
Daria Karavaieva
5813aa6602 docs/vmanomaly: fix parameters field width (#8025)
### Describe Your Changes

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

### Checklist

The following checks are **mandatory**:

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

### Checklist

The following checks are **mandatory**:

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

---------

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

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

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

### Checklist

The following checks are **mandatory**:

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

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

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

### Checklist

The following checks are **mandatory**:

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

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

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

### Checklist

The following checks are **mandatory**:

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

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

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

### Checklist

The following checks are **mandatory**:

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

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

Updated email images with new support email

### Checklist

The following checks are **mandatory**:

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

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

### Checklist

The following checks are **mandatory**:

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

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

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

### Checklist

The following checks are **mandatory**:

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

---------

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

### Describe Your Changes

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

### Checklist

The following checks are **mandatory**:

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

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

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

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

Follow-up after
b26a68641c

### Describe Your Changes

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

### Checklist

The following checks are **mandatory**:

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

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

- fix table rendering on writer and scheduler pages

### Checklist

The following checks are **mandatory**:

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

added search page required for docs site

### Checklist

The following checks are **mandatory**:

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

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

### Checklist

The following checks are **mandatory**:

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

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

Signed-off-by: Github Actions <133988544+victoriametrics-bot@users.noreply.github.com>
Co-authored-by: f41gh7 <18450869+f41gh7@users.noreply.github.com>
2025-01-13 12:12:48 +01:00
90 changed files with 3301 additions and 2181 deletions

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

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -19,7 +19,7 @@ services:
retries: 10
dd-proxy:
image: docker.io/victoriametrics/vmauth:v1.108.1
image: docker.io/victoriametrics/vmauth:v1.109.0
restart: on-failure
volumes:
- ./:/etc/vmauth

View File

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

View File

@@ -46,7 +46,7 @@ services:
- "--config=/config.yml"
vmsingle:
image: victoriametrics/victoria-metrics:v1.108.1
image: victoriametrics/victoria-metrics:v1.109.0
ports:
- "8428:8428"
command:

View File

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

View File

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

View File

@@ -16,6 +16,14 @@ according to [these docs](https://docs.victoriametrics.com/victorialogs/quicksta
## tip
## [v1.6.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.6.0-victorialogs)
Released at 2025-01-15
* FEATURE: add [`union` pipe](https://docs.victoriametrics.com/victorialogs/logsql/#union-pipe), which can be used for returning results from multiple independent LogsQL queries.
* FEATURE: add [`histogram` stats function](https://docs.victoriametrics.com/victorialogs/logsql/#histogram-stats) for calculating [VictoriaMetrics histogram buckets](https://valyala.medium.com/improving-histogram-usability-for-prometheus-and-grafana-bc7e5df0e350) over the given [log field](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model). They will be used for building heatmaps at the [built-in Web UI](https://docs.victoriametrics.com/victorialogs/querying/#web-ui) and [VictoriaLogs plugin for Grafana](https://docs.victoriametrics.com/victorialogs/victorialogs-datasource/).
* FEATURE: [`math` pipe](https://docs.victoriametrics.com/victorialogs/logsql/#math-pipe): add `rand()` function, which can be used for generating random numbers in the range `[0 ... 1)`.
## [v1.5.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.5.0-victorialogs)
Released at 2025-01-13

View File

@@ -1344,6 +1344,7 @@ LogsQL supports the following pipes:
- [`stream_context`](#stream_context-pipe) allows selecting surrounding logs in front and after the matching logs
per each [log stream](https://docs.victoriametrics.com/victorialogs/keyconcepts/#stream-fields).
- [`top`](#top-pipe) returns top `N` field sets with the maximum number of matching logs.
- [`union`](#union-pipe) returns results from multiple LogsQL queries.
- [`uniq`](#uniq-pipe) returns unique log entires.
- [`unpack_json`](#unpack_json-pipe) unpacks JSON messages from [log fields](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model).
- [`unpack_logfmt`](#unpack_logfmt-pipe) unpacks [logfmt](https://brandur.org/logfmt) messages from [log fields](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model).
@@ -2028,6 +2029,7 @@ _time:1d {app="app1"} | stats by (user) count() app1_hits
See also:
- [`in` filter](#multi-exact-filter)
- [`stats` pipe](#stats-pipe)
- [conditional `stats`](https://docs.victoriametrics.com/victorialogs/logsql/#stats-with-additional-filters)
- [`filter` pipe](#filter-pipe)
@@ -2141,6 +2143,7 @@ The following mathematical operations are supported by `math` pipe:
- `ln(arg)` - returns [natural logarithm](https://en.wikipedia.org/wiki/Natural_logarithm) for the given `arg`
- `max(arg1, ..., argN)` - returns the maximum value among the given `arg1`, ..., `argN`
- `min(arg1, ..., argN)` - returns the minimum value among the given `arg1`, ..., `argN`
- `rand()` - returns pseudo-random number in the range `[0...1)`.
- `round(arg)` - returns rounded to integer value for the given `arg`. The `round()` accepts optional `nearest` arg, which allows rounding the number to the given `nearest` multiple.
For example, `round(temperature, 0.1)` rounds `temperature` field to one decimal digit after the point.
@@ -2762,6 +2765,22 @@ See also:
- [`uniq` pipe](#uniq-pipe)
- [`stats` pipe](#stats-pipe)
- [`sort` pipe](#sort-pipe)
- [`histogram` stats function](#histogram-stats)
### union pipe
`q1 | union (q2)` [pipe](#pipes) returns results of `q1` followed by results of `q2`. It works similar to `UNION ALL` in SQL.
`q1` and `q2` may contain arbitrary [LogsQL queries](#logsql-tutorial).
For example, the following query returns logs with `error` [word](#word) for the last 5 minutes, plus logs with `panic` word for the last hour:
```logsql
_time:5m error | union (_time:1h panic)
```
See also:
- [`join` pipe](#join-pipe)
- [`in` filter](#multi-exact-filter)
### uniq pipe
@@ -3106,6 +3125,7 @@ LogsQL supports the following functions for [`stats` pipe](#stats-pipe):
- [`count_empty`](#count_empty-stats) returns the number logs with empty [log fields](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model).
- [`count_uniq`](#count_uniq-stats) returns the number of unique non-empty values for the given [log fields](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model).
- [`count_uniq_hash`](#count_uniq_hash-stats) returns the number of unique hashes for non-empty values at the given [log fields](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model).
- [`histogram`](#histogram-stats) returns [VictoriaMetrics histogram](https://valyala.medium.com/improving-histogram-usability-for-prometheus-and-grafana-bc7e5df0e350) for the given [log field](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model).
- [`max`](#max-stats) returns the maximum value over the given [log fields](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model).
- [`median`](#median-stats) returns the [median](https://en.wikipedia.org/wiki/Median) value over the given [log fields](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model).
- [`min`](#min-stats) returns the minimum value over the given [log fields](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model).
@@ -3253,6 +3273,25 @@ See also:
- [`uniq_values`](#uniq_values-stats)
- [`count`](#count-stats)
### histogram stats
`histogram(field)` [stats pipe function](#stats-pipe-functions) returns [VictoriaMetrics histogram buckets](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model)
for the given [`field`](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model).
For example, the following query returns histogram buckets for the `response_size` field grouped by `host` field, across logs for the last 5 minutes:
```logsql
_time:5m | stats by (host) histogram(response_size)
```
If the field contains [duration value](#duration-values), then `histogram` normalizes it to nanoseconds. For example, `1.25ms` is normalized to `1_250_000`.
If the field contains [short numeric value](#short-numeric-values), then `histogram` normalizes it to numeric value without any suffixes. For example, `1KiB` is converted to `1024`.
See also:
- [`quantile`](#quantile-stats)
### max stats
`max(field1, ..., fieldN)` [stats pipe function](#stats-pipe-functions) returns the maximum value across
@@ -3330,6 +3369,7 @@ _time:5m | stats
See also:
- [`histogram`](#histogram-stats)
- [`min`](#min-stats)
- [`max`](#max-stats)
- [`median`](#median-stats)

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,6 +2,8 @@ package logstorage
import (
"testing"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
)
func TestMatchSequence(t *testing.T) {
@@ -33,8 +35,6 @@ func TestFilterSequence(t *testing.T) {
t.Parallel()
t.Run("single-row", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -108,8 +108,6 @@ func TestFilterSequence(t *testing.T) {
})
t.Run("const-column", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -161,8 +159,6 @@ func TestFilterSequence(t *testing.T) {
})
t.Run("dict", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -218,8 +214,6 @@ func TestFilterSequence(t *testing.T) {
})
t.Run("strings", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -290,8 +284,6 @@ func TestFilterSequence(t *testing.T) {
})
t.Run("uint8", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -363,8 +355,6 @@ func TestFilterSequence(t *testing.T) {
})
t.Run("uint16", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -436,8 +426,6 @@ func TestFilterSequence(t *testing.T) {
})
t.Run("uint32", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -509,8 +497,6 @@ func TestFilterSequence(t *testing.T) {
})
t.Run("uint64", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -582,8 +568,6 @@ func TestFilterSequence(t *testing.T) {
})
t.Run("int64", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -661,8 +645,6 @@ func TestFilterSequence(t *testing.T) {
})
t.Run("float64", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -744,8 +726,6 @@ func TestFilterSequence(t *testing.T) {
})
t.Run("ipv4", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -842,8 +822,6 @@ func TestFilterSequence(t *testing.T) {
})
t.Run("timestamp-iso8601", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "_msg",
@@ -923,4 +901,7 @@ func TestFilterSequence(t *testing.T) {
}
testFilterMatchForColumns(t, columns, fs, "_msg", nil)
})
// Remove the remaining data files for the test
fs.MustRemoveAll(t.Name())
}

View File

@@ -2,6 +2,8 @@ package logstorage
import (
"testing"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
)
func TestMatchStringRange(t *testing.T) {
@@ -27,8 +29,6 @@ func TestFilterStringRange(t *testing.T) {
t.Parallel()
t.Run("const-column", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -86,8 +86,6 @@ func TestFilterStringRange(t *testing.T) {
})
t.Run("dict", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -143,8 +141,6 @@ func TestFilterStringRange(t *testing.T) {
})
t.Run("strings", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -188,8 +184,6 @@ func TestFilterStringRange(t *testing.T) {
})
t.Run("uint8", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -241,8 +235,6 @@ func TestFilterStringRange(t *testing.T) {
})
t.Run("uint16", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -294,8 +286,6 @@ func TestFilterStringRange(t *testing.T) {
})
t.Run("uint32", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -347,8 +337,6 @@ func TestFilterStringRange(t *testing.T) {
})
t.Run("uint64", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -400,8 +388,6 @@ func TestFilterStringRange(t *testing.T) {
})
t.Run("int64", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -453,8 +439,6 @@ func TestFilterStringRange(t *testing.T) {
})
t.Run("float64", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -506,8 +490,6 @@ func TestFilterStringRange(t *testing.T) {
})
t.Run("ipv4", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -567,8 +549,6 @@ func TestFilterStringRange(t *testing.T) {
})
t.Run("timestamp-iso8601", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "_msg",
@@ -623,4 +603,7 @@ func TestFilterStringRange(t *testing.T) {
}
testFilterMatchForColumns(t, columns, fr, "_msg", nil)
})
// Remove the remaining data files for the test
fs.MustRemoveAll(t.Name())
}

View File

@@ -2,14 +2,14 @@ package logstorage
import (
"testing"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
)
func TestFilterValueType(t *testing.T) {
t.Parallel()
t.Run("single-row", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -59,8 +59,6 @@ func TestFilterValueType(t *testing.T) {
})
t.Run("const-column", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "other-column",
@@ -152,8 +150,6 @@ func TestFilterValueType(t *testing.T) {
})
t.Run("dict", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -185,8 +181,6 @@ func TestFilterValueType(t *testing.T) {
})
t.Run("strings", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -221,8 +215,6 @@ func TestFilterValueType(t *testing.T) {
})
t.Run("uint8", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -258,8 +250,6 @@ func TestFilterValueType(t *testing.T) {
})
t.Run("uint16", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -294,8 +284,6 @@ func TestFilterValueType(t *testing.T) {
})
t.Run("uint32", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -330,8 +318,6 @@ func TestFilterValueType(t *testing.T) {
})
t.Run("uint64", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -365,8 +351,6 @@ func TestFilterValueType(t *testing.T) {
})
t.Run("int64", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -400,8 +384,6 @@ func TestFilterValueType(t *testing.T) {
})
t.Run("float64", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -435,8 +417,6 @@ func TestFilterValueType(t *testing.T) {
})
t.Run("ipv4", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "foo",
@@ -473,8 +453,6 @@ func TestFilterValueType(t *testing.T) {
})
t.Run("timestamp-iso8601", func(t *testing.T) {
t.Parallel()
columns := []column{
{
name: "_msg",
@@ -506,4 +484,7 @@ func TestFilterValueType(t *testing.T) {
}
testFilterMatchForColumns(t, columns, pv, "_msg", nil)
})
// Remove the remaining data files for the test
fs.MustRemoveAll(t.Name())
}

239
lib/logstorage/hits_map.go Normal file
View File

@@ -0,0 +1,239 @@
package logstorage
import (
"sync"
"unsafe"
"github.com/cespare/xxhash/v2"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
)
type hitsMap struct {
stateSizeBudget *int
u64 map[uint64]*uint64
negative64 map[uint64]*uint64
strings map[string]*uint64
// a reduces memory allocations when counting the number of hits over big number of unique values.
a chunkedAllocator
}
func (hm *hitsMap) reset() {
hm.stateSizeBudget = nil
hm.u64 = nil
hm.negative64 = nil
hm.strings = nil
}
func (hm *hitsMap) clear() {
*hm.stateSizeBudget += hm.stateSize()
hm.init(hm.stateSizeBudget)
}
func (hm *hitsMap) init(stateSizeBudget *int) {
hm.stateSizeBudget = stateSizeBudget
hm.u64 = make(map[uint64]*uint64)
hm.negative64 = make(map[uint64]*uint64)
hm.strings = make(map[string]*uint64)
}
func (hm *hitsMap) entriesCount() uint64 {
n := len(hm.u64) + len(hm.negative64) + len(hm.strings)
return uint64(n)
}
func (hm *hitsMap) stateSize() int {
n := 24*(len(hm.u64)+len(hm.negative64)) + 40*len(hm.strings)
for k := range hm.strings {
n += len(k)
}
return n
}
func (hm *hitsMap) updateStateGeneric(key string, hits uint64) {
if n, ok := tryParseUint64(key); ok {
hm.updateStateUint64(n, hits)
return
}
if len(key) > 0 && key[0] == '-' {
if n, ok := tryParseInt64(key); ok {
hm.updateStateNegativeInt64(n, hits)
return
}
}
hm.updateStateString(bytesutil.ToUnsafeBytes(key), hits)
}
func (hm *hitsMap) updateStateInt64(n int64, hits uint64) {
if n >= 0 {
hm.updateStateUint64(uint64(n), hits)
} else {
hm.updateStateNegativeInt64(n, hits)
}
}
func (hm *hitsMap) updateStateUint64(n, hits uint64) {
pHits := hm.u64[n]
if pHits != nil {
*pHits += hits
return
}
pHits = hm.a.newUint64()
*pHits = hits
hm.u64[n] = pHits
*hm.stateSizeBudget -= 24
}
func (hm *hitsMap) updateStateNegativeInt64(n int64, hits uint64) {
pHits := hm.negative64[uint64(n)]
if pHits != nil {
*pHits += hits
return
}
pHits = hm.a.newUint64()
*pHits = hits
hm.negative64[uint64(n)] = pHits
*hm.stateSizeBudget -= 24
}
func (hm *hitsMap) updateStateString(key []byte, hits uint64) {
pHits := hm.strings[string(key)]
if pHits != nil {
*pHits += hits
return
}
keyCopy := hm.a.cloneBytesToString(key)
pHits = hm.a.newUint64()
*pHits = hits
hm.strings[keyCopy] = pHits
*hm.stateSizeBudget -= len(keyCopy) + 40
}
func (hm *hitsMap) mergeState(src *hitsMap, stopCh <-chan struct{}) {
for n, pHitsSrc := range src.u64 {
if needStop(stopCh) {
return
}
pHitsDst := hm.u64[n]
if pHitsDst == nil {
hm.u64[n] = pHitsSrc
} else {
*pHitsDst += *pHitsSrc
}
}
for n, pHitsSrc := range src.negative64 {
if needStop(stopCh) {
return
}
pHitsDst := hm.negative64[n]
if pHitsDst == nil {
hm.negative64[n] = pHitsSrc
} else {
*pHitsDst += *pHitsSrc
}
}
for k, pHitsSrc := range src.strings {
if needStop(stopCh) {
return
}
pHitsDst := hm.strings[k]
if pHitsDst == nil {
hm.strings[k] = pHitsSrc
} else {
*pHitsDst += *pHitsSrc
}
}
}
// hitsMapMergeParallel merges hms in parallel on the given cpusCount
//
// The mered disjoint parts of hms are passed to f.
// The function may be interrupted by closing stopCh.
// The caller must check for closed stopCh after returning from the function.
func hitsMapMergeParallel(hms []*hitsMap, cpusCount int, stopCh <-chan struct{}, f func(hm *hitsMap)) {
srcLen := len(hms)
if srcLen < 2 {
// Nothing to merge
if len(hms) == 1 {
f(hms[0])
}
return
}
var wg sync.WaitGroup
perShardMaps := make([][]hitsMap, srcLen)
for i := range hms {
wg.Add(1)
go func(idx int) {
defer wg.Done()
stateSizeBudget := 0
perCPU := make([]hitsMap, cpusCount)
for i := range perCPU {
perCPU[i].init(&stateSizeBudget)
}
hm := hms[idx]
for n, pHits := range hm.u64 {
if needStop(stopCh) {
return
}
k := unsafe.Slice((*byte)(unsafe.Pointer(&n)), 8)
h := xxhash.Sum64(k)
cpuIdx := h % uint64(len(perCPU))
perCPU[cpuIdx].u64[n] = pHits
}
for n, pHits := range hm.negative64 {
if needStop(stopCh) {
return
}
k := unsafe.Slice((*byte)(unsafe.Pointer(&n)), 8)
h := xxhash.Sum64(k)
cpuIdx := h % uint64(len(perCPU))
perCPU[cpuIdx].negative64[n] = pHits
}
for k, pHits := range hm.strings {
if needStop(stopCh) {
return
}
h := xxhash.Sum64(bytesutil.ToUnsafeBytes(k))
cpuIdx := h % uint64(len(perCPU))
perCPU[cpuIdx].strings[k] = pHits
}
perShardMaps[idx] = perCPU
hm.reset()
}(i)
}
wg.Wait()
if needStop(stopCh) {
return
}
// Merge per-shard entries into perShardMaps[0]
for i := 0; i < cpusCount; i++ {
wg.Add(1)
go func(cpuIdx int) {
defer wg.Done()
hm := &perShardMaps[0][cpuIdx]
for _, perCPU := range perShardMaps[1:] {
hm.mergeState(&perCPU[cpuIdx], stopCh)
perCPU[cpuIdx].reset()
}
f(hm)
}(i)
}
wg.Wait()
}

View File

@@ -396,6 +396,7 @@ func (q *Query) CanReturnLastNResults() bool {
*pipeTop,
*pipeSort,
*pipeStats,
*pipeUnion,
*pipeUniq:
return false
}

View File

@@ -1207,6 +1207,10 @@ func TestParseQuerySuccess(t *testing.T) {
f(`* | stats sum_len(*) x`, `* | stats sum_len(*) as x`)
f(`* | stats sum_len(foo,*,bar) x`, `* | stats sum_len(*) as x`)
// stats pipe histogram
f(`* | stats histogram(foo) bar`, `* | stats histogram(foo) as bar`)
f(`* | histogram(foo)`, `* | stats histogram(foo) as "histogram(foo)"`)
// stats pipe quantile
f(`* | stats quantile(0, foo) bar`, `* | stats quantile(0, foo) as bar`)
f(`* | stats quantile(1, foo) bar`, `* | stats quantile(1, foo) as bar`)
@@ -1318,6 +1322,10 @@ func TestParseQuerySuccess(t *testing.T) {
f("* | extract foo<bar>baz from x", `* | extract "foo<bar>baz" from x`)
f("* | extract if (a:b) foo<bar>baz from x", `* | extract if (a:b) "foo<bar>baz" from x`)
// union pipe
f(`* | union(foo)`, `* | union (foo)`)
f(`* | union(foo | union(bar baz | count() x))`, `* | union (foo | union (bar baz | stats count(*) as x))`)
// unpack_json pipe
f(`* | unpack_json`, `* | unpack_json`)
f(`* | unpack_json result_prefix y`, `* | unpack_json result_prefix y`)
@@ -1742,6 +1750,12 @@ func TestParseQueryFailure(t *testing.T) {
// invalid stats sum_len
f(`foo | stats sum_len`)
// invalid stats histogram
f(`foo | stats histogram`)
f(`foo | stats histogram()`)
f(`foo | stats histogram(a, b)`)
f(`foo | stats histogram(*)`)
// invalid stats quantile
f(`foo | stats quantile`)
f(`foo | stats quantile() foo`)
@@ -1828,6 +1842,12 @@ func TestParseQueryFailure(t *testing.T) {
f(`foo | extract from x "<abc`)
f(`foo | extract from x "<abc>" de`)
// invalid union pipe
f(`foo | union`)
f(`foo | union (`)
f(`foo | union ( bar`)
f(`foo | union (bar | count)`)
// invalid unpack_json pipe
f(`foo | unpack_json bar`)
f(`foo | unpack_json from`)
@@ -1977,6 +1997,7 @@ func TestQueryGetNeededColumns(t *testing.T) {
f(`* | stats max() q`, `*`, ``)
f(`* | stats max(*) q`, `*`, ``)
f(`* | stats max(x) q`, `x`, ``)
f(`* | stats histogram(foo)`, `foo`, ``)
f(`* | stats quantile(0.5) q`, `*`, ``)
f(`* | stats quantile(0.5, *) q`, `*`, ``)
f(`* | stats quantile(0.5, x) q`, `x`, ``)
@@ -2184,6 +2205,7 @@ func TestQueryGetNeededColumns(t *testing.T) {
f(`* | stats count_uniq(a, b) if (q:w p:a) as c | count() r1`, ``, ``)
f(`* | stats by (a1,a2) count_uniq(a, b) as c | count() r1`, `a1,a2`, ``)
f(`* | stats by (a1,a2) count_uniq(a, b) if (q:w p:a) as c | count() r1`, `a1,a2`, ``)
f(`* | union (foo) | count() r1`, ``, ``)
f(`* | uniq by (a, b) | count() r1`, `a,b`, ``)
f(`* | unpack_json from x | count() r1`, ``, ``)
f(`* | unpack_json from x fields (a,b) | count() r1`, ``, ``)
@@ -2266,6 +2288,7 @@ func TestQueryCanReturnLastNResults(t *testing.T) {
f("* | len(x)", true)
f("* | limit 10", false)
f("* | offset 10", false)
f("* | union (x)", false)
f("* | uniq (x)", false)
f("* | block_stats", false)
f("* | blocks_count", false)
@@ -2321,6 +2344,7 @@ func TestQueryCanLiveTail(t *testing.T) {
f("* | stats count() rows", false)
f("* | stream_context after 10", false)
f("* | top 10 by (x)", false)
f("* | union (foo)", false)
f("* | uniq by (a)", false)
f("* | unpack_json", true)
f("* | unpack_logfmt", true)
@@ -2527,6 +2551,7 @@ func TestQueryGetStatsByFields_Failure(t *testing.T) {
f(`foo | count() | replace_regexp ("foo.+bar", "baz")`)
f(`foo | count() | stream_context after 10`)
f(`foo | count() | top 5 by (x)`)
f(`foo | count() | union (foo)`)
f(`foo | count() | uniq by (x)`)
f(`foo | count() | unpack_json`)
f(`foo | count() | unpack_logfmt`)

View File

@@ -276,6 +276,12 @@ func parsePipe(lex *lexer) (pipe, error) {
return nil, fmt.Errorf("cannot parse 'top' pipe: %w", err)
}
return pt, nil
case lex.isKeyword("union"):
pu, err := parsePipeUnion(lex)
if err != nil {
return nil, fmt.Errorf("cannot parse 'union' pipe: %w", err)
}
return pu, nil
case lex.isKeyword("uniq"):
pu, err := parsePipeUniq(lex)
if err != nil {
@@ -359,6 +365,7 @@ var pipeNames = func() map[string]struct{} {
"stats", "by",
"stream_context",
"top",
"union",
"uniq",
"unpack_json",
"unpack_logfmt",

View File

@@ -136,14 +136,12 @@ type pipeFacetsProcessorShardNopad struct {
}
type pipeFacetsFieldHits struct {
m map[string]*uint64
m hitsMap
mustIgnore bool
}
func (fhs *pipeFacetsFieldHits) enableIgnoreField(shard *pipeFacetsProcessorShard) {
mLen := len(fhs.m)
fhs.m = nil
shard.stateSizeBudget += mLen * 8
func (fhs *pipeFacetsFieldHits) enableIgnoreField() {
fhs.m.clear()
fhs.mustIgnore = true
}
@@ -153,7 +151,6 @@ func (shard *pipeFacetsProcessorShard) writeBlock(br *blockResult) {
for _, c := range cs {
shard.updateFacetsForColumn(br, c)
}
shard.rowsTotal += uint64(br.rowsLen)
}
@@ -162,28 +159,124 @@ func (shard *pipeFacetsProcessorShard) updateFacetsForColumn(br *blockResult, c
if fhs.mustIgnore {
return
}
if c.isConst {
v := c.valuesEncoded[0]
shard.updateState(fhs, v, uint64(br.rowsLen))
return
}
if c.valueType == valueTypeDict {
c.forEachDictValueWithHits(br, func(v string, hits uint64) {
shard.updateState(fhs, v, hits)
})
if fhs.m.entriesCount() >= shard.pf.maxValuesPerField {
// Ignore fields with too many unique values
fhs.enableIgnoreField()
return
}
for i := 0; i < br.rowsLen; i++ {
v := c.getValueAtRow(br, i)
shard.updateState(fhs, v, 1)
if c.isConst {
v := c.valuesEncoded[0]
shard.updateStateGeneric(fhs, v, uint64(br.rowsLen))
return
}
switch c.valueType {
case valueTypeDict:
c.forEachDictValueWithHits(br, func(v string, hits uint64) {
shard.updateStateGeneric(fhs, v, hits)
})
case valueTypeUint8:
values := c.getValuesEncoded(br)
for _, v := range values {
n := unmarshalUint8(v)
shard.updateStateUint64(fhs, uint64(n))
}
case valueTypeUint16:
values := c.getValuesEncoded(br)
for _, v := range values {
n := unmarshalUint16(v)
shard.updateStateUint64(fhs, uint64(n))
}
case valueTypeUint32:
values := c.getValuesEncoded(br)
for _, v := range values {
n := unmarshalUint32(v)
shard.updateStateUint64(fhs, uint64(n))
}
case valueTypeUint64:
values := c.getValuesEncoded(br)
for _, v := range values {
n := unmarshalUint64(v)
shard.updateStateUint64(fhs, n)
}
case valueTypeInt64:
values := c.getValuesEncoded(br)
for _, v := range values {
n := unmarshalInt64(v)
shard.updateStateInt64(fhs, n)
}
default:
for i := 0; i < br.rowsLen; i++ {
v := c.getValueAtRow(br, i)
shard.updateStateGeneric(fhs, v, 1)
}
}
}
func (shard *pipeFacetsProcessorShard) updateState(fhs *pipeFacetsFieldHits, v string, hits uint64) {
if fhs.mustIgnore {
func (shard *pipeFacetsProcessorShard) updateStateInt64(fhs *pipeFacetsFieldHits, n int64) {
if maxValueLen := shard.pf.maxValueLen; maxValueLen <= 21 && uint64(int64StringLen(n)) > maxValueLen {
// Ignore fields with too long values, since they are hard to use in faceted search.
fhs.enableIgnoreField()
return
}
fhs.m.updateStateInt64(n, 1)
}
func (shard *pipeFacetsProcessorShard) updateStateUint64(fhs *pipeFacetsFieldHits, n uint64) {
if maxValueLen := shard.pf.maxValueLen; maxValueLen <= 20 && uint64(uint64StringLen(n)) > maxValueLen {
// Ignore fields with too long values, since they are hard to use in faceted search.
fhs.enableIgnoreField()
return
}
fhs.m.updateStateUint64(n, 1)
}
func int64StringLen(n int64) int {
if n >= 0 {
return uint64StringLen(uint64(n))
}
if n == -1<<63 {
return 21
}
return 1 + uint64StringLen(uint64(-n))
}
func uint64StringLen(n uint64) int {
if n < 10 {
return 1
}
if n < 100 {
return 2
}
if n < 1_000 {
return 3
}
if n < 10_000 {
return 4
}
if n < 100_000 {
return 5
}
if n < 1_000_000 {
return 6
}
if n < 10_000_000 {
return 7
}
if n < 100_000_000 {
return 8
}
if n < 1_000_000_000 {
return 9
}
if n < 10_000_000_000 {
return 10
}
return 20
}
func (shard *pipeFacetsProcessorShard) updateStateGeneric(fhs *pipeFacetsFieldHits, v string, hits uint64) {
if len(v) == 0 {
// It is impossible to calculate properly the number of hits
// for all empty per-field values - the final number will be misleading,
@@ -193,23 +286,10 @@ func (shard *pipeFacetsProcessorShard) updateState(fhs *pipeFacetsFieldHits, v s
}
if uint64(len(v)) > shard.pf.maxValueLen {
// Ignore fields with too long values, since they are hard to use in faceted search.
fhs.enableIgnoreField(shard)
fhs.enableIgnoreField()
return
}
pHits := fhs.m[v]
if pHits == nil {
if uint64(len(fhs.m)) >= shard.pf.maxValuesPerField {
// Ignore fields with too many unique values
fhs.enableIgnoreField(shard)
return
}
vCopy := shard.a.cloneString(v)
pHits = shard.a.newUint64()
fhs.m[vCopy] = pHits
shard.stateSizeBudget -= len(vCopy) + int(unsafe.Sizeof(vCopy)+unsafe.Sizeof(hits)+unsafe.Sizeof(pHits))
}
*pHits += hits
fhs.m.updateStateGeneric(v, hits)
}
func (shard *pipeFacetsProcessorShard) getFieldHits(fieldName string) *pipeFacetsFieldHits {
@@ -218,12 +298,11 @@ func (shard *pipeFacetsProcessorShard) getFieldHits(fieldName string) *pipeFacet
}
fhs, ok := shard.m[fieldName]
if !ok {
fhs = &pipeFacetsFieldHits{
m: make(map[string]*uint64),
}
fhs = &pipeFacetsFieldHits{}
fhs.m.init(&shard.stateSizeBudget)
fieldNameCopy := shard.a.cloneString(fieldName)
shard.m[fieldNameCopy] = fhs
shard.stateSizeBudget -= len(fieldNameCopy) + int(unsafe.Sizeof(*fhs))
shard.stateSizeBudget -= len(fieldNameCopy) + int(unsafe.Sizeof(fhs)+unsafe.Sizeof(*fhs))
}
return fhs
}
@@ -258,7 +337,7 @@ func (pfp *pipeFacetsProcessor) flush() error {
}
// merge state across shards
m := make(map[string]map[string]*uint64)
hms := make(map[string]*hitsMap)
rowsTotal := uint64(0)
for _, shard := range pfp.shards {
if needStop(pfp.stopCh) {
@@ -268,26 +347,19 @@ func (pfp *pipeFacetsProcessor) flush() error {
if fhs.mustIgnore {
continue
}
vs, ok := m[fieldName]
hm, ok := hms[fieldName]
if !ok {
m[fieldName] = fhs.m
hms[fieldName] = &fhs.m
continue
}
for v, pHits := range fhs.m {
ph, ok := vs[v]
if !ok {
vs[v] = pHits
} else {
*ph += *pHits
}
}
hm.mergeState(&fhs.m, pfp.stopCh)
}
rowsTotal += shard.rowsTotal
}
// sort fieldNames
fieldNames := make([]string, 0, len(m))
for fieldName := range m {
fieldNames := make([]string, 0, len(hms))
for fieldName := range hms {
fieldNames = append(fieldNames, fieldName)
}
sort.Strings(fieldNames)
@@ -301,13 +373,25 @@ func (pfp *pipeFacetsProcessor) flush() error {
if needStop(pfp.stopCh) {
return nil
}
values := m[fieldName]
if uint64(len(values)) > pfp.pf.maxValuesPerField {
hm := hms[fieldName]
if hm.entriesCount() > pfp.pf.maxValuesPerField {
continue
}
vs := make([]pipeTopEntry, 0, len(values))
for k, pHits := range values {
vs := make([]pipeTopEntry, 0, hm.entriesCount())
for n, pHits := range hm.u64 {
vs = append(vs, pipeTopEntry{
k: string(marshalUint64String(nil, n)),
hits: *pHits,
})
}
for n, pHits := range hm.negative64 {
vs = append(vs, pipeTopEntry{
k: string(marshalInt64String(nil, int64(n))),
hits: *pHits,
})
}
for k, pHits := range hm.strings {
vs = append(vs, pipeTopEntry{
k: k,
hits: *pHits,
@@ -365,7 +449,9 @@ func (wctx *pipeFacetsWriteContext) writeRow(fieldName, fieldValue string, hits
wctx.valuesLen += len(hitsStr)
wctx.rowsCount++
if wctx.valuesLen >= 1_000_000 {
// The 64_000 limit provides the best performance results.
if wctx.valuesLen >= 64_000 {
wctx.flush()
}
}

View File

@@ -13,6 +13,7 @@ func TestParsePipeJoinSuccess(t *testing.T) {
f(`join by (foo) (error)`)
f(`join by (foo, bar) (a:b | fields x, y)`)
f(`join by (foo) (a:b) prefix c`)
f(`join by (foo) (bar | join by (x, z) (y))`)
}
func TestParsePipeJoinFailure(t *testing.T) {

View File

@@ -6,6 +6,8 @@ import (
"strings"
"unsafe"
"github.com/valyala/fastrand"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/decimal"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/encoding"
@@ -275,20 +277,18 @@ func (shard *pipeMathProcessorShard) executeMathEntry(e *mathEntry, rc *resultCo
shard.executeExpr(e.expr, br)
r := shard.rs[0]
if len(r) == 0 {
return nan, nan
}
b := shard.a.b
minValue := nan
maxValue := nan
minValue := r[0]
maxValue := r[0]
for _, f := range r {
if math.IsNaN(minValue) {
if f < minValue {
minValue = f
} else if f > maxValue {
maxValue = f
} else if !math.IsNaN(f) {
if f < minValue {
minValue = f
} else if f > maxValue {
maxValue = f
}
}
n := math.Float64bits(f)
bLen := len(b)
@@ -318,41 +318,7 @@ func (shard *pipeMathProcessorShard) executeExpr(me *mathExpr, br *blockResult)
if me.fieldName != "" {
r := shard.rs[rIdx]
c := br.getColumnByName(me.fieldName)
switch c.valueType {
case valueTypeUint8:
for i, v := range c.getValuesEncoded(br) {
r[i] = float64(unmarshalUint8(v))
}
case valueTypeUint16:
for i, v := range c.getValuesEncoded(br) {
r[i] = float64(unmarshalUint16(v))
}
case valueTypeUint32:
for i, v := range c.getValuesEncoded(br) {
r[i] = float64(unmarshalUint32(v))
}
case valueTypeUint64:
for i, v := range c.getValuesEncoded(br) {
r[i] = float64(unmarshalUint64(v))
}
case valueTypeInt64:
for i, v := range c.getValuesEncoded(br) {
r[i] = float64(unmarshalInt64(v))
}
case valueTypeFloat64:
for i, v := range c.getValuesEncoded(br) {
r[i] = unmarshalFloat64(v)
}
default:
values := c.getValues(br)
var f float64
for i, v := range values {
if i == 0 || v != values[i-1] {
f = parseMathNumber(v)
}
r[i] = f
}
}
shard.loadArgValuesFromColumn(r, br, c)
return
}
@@ -369,6 +335,80 @@ func (shard *pipeMathProcessorShard) executeExpr(me *mathExpr, br *blockResult)
shard.rsBuf = shard.rsBuf[:rsBufLen]
}
func (shard *pipeMathProcessorShard) loadArgValuesFromColumn(dst []float64, br *blockResult, c *blockResultColumn) {
if c.isConst {
v := c.valuesEncoded[0]
f := parseMathNumber(v)
for i := range dst {
dst[i] = f
}
return
}
if c.isTime {
timestamps := br.getTimestamps()
for i, ts := range timestamps {
dst[i] = float64(ts)
}
return
}
switch c.valueType {
case valueTypeDict:
a := encoding.GetFloat64s(len(c.dictValues))
fs := a.A
for i, v := range c.dictValues {
fs[i] = parseMathNumber(v)
}
values := c.getValuesEncoded(br)
for i, v := range values {
idx := v[0]
dst[i] = fs[idx]
}
encoding.PutFloat64s(a)
case valueTypeUint8:
for i, v := range c.getValuesEncoded(br) {
dst[i] = float64(unmarshalUint8(v))
}
case valueTypeUint16:
for i, v := range c.getValuesEncoded(br) {
dst[i] = float64(unmarshalUint16(v))
}
case valueTypeUint32:
for i, v := range c.getValuesEncoded(br) {
dst[i] = float64(unmarshalUint32(v))
}
case valueTypeUint64:
for i, v := range c.getValuesEncoded(br) {
dst[i] = float64(unmarshalUint64(v))
}
case valueTypeInt64:
for i, v := range c.getValuesEncoded(br) {
dst[i] = float64(unmarshalInt64(v))
}
case valueTypeFloat64:
for i, v := range c.getValuesEncoded(br) {
dst[i] = unmarshalFloat64(v)
}
case valueTypeIPv4:
for i, v := range c.getValuesEncoded(br) {
dst[i] = float64(unmarshalIPv4(v))
}
case valueTypeTimestampISO8601:
for i, v := range c.getValuesEncoded(br) {
dst[i] = float64(unmarshalTimestampISO8601(v))
}
default:
values := c.getValues(br)
var f float64
for i, v := range values {
if i == 0 || v != values[i-1] {
f = parseMathNumber(v)
}
dst[i] = f
}
}
}
func (pmp *pipeMathProcessor) writeBlock(workerID uint, br *blockResult) {
if br.rowsLen == 0 {
return
@@ -538,6 +578,8 @@ func parseMathExprOperand(lex *lexer) (*mathExpr, error) {
return parseMathExprMax(lex)
case lex.isKeyword("min"):
return parseMathExprMin(lex)
case lex.isKeyword("rand"):
return parseMathExprRand(lex)
case lex.isKeyword("round"):
return parseMathExprRound(lex)
case lex.isKeyword("ceil"):
@@ -612,6 +654,26 @@ func parseMathExprMin(lex *lexer) (*mathExpr, error) {
return me, nil
}
func parseMathExprRand(lex *lexer) (*mathExpr, error) {
if !lex.isKeyword("rand") {
return nil, fmt.Errorf("missing 'rand' keyword")
}
lex.nextToken()
args, err := parseMathFuncArgs(lex)
if err != nil {
return nil, fmt.Errorf("cannot parse args for 'rand' function: %w", err)
}
if len(args) != 0 {
return nil, fmt.Errorf("'rand' function must have no args; got %d args", len(args))
}
me := &mathExpr{
op: "rand",
f: mathFuncRand,
}
return me, nil
}
func parseMathExprRound(lex *lexer) (*mathExpr, error) {
me, err := parseMathExprGenericFunc(lex, "round", mathFuncRound)
if err != nil {
@@ -927,6 +989,13 @@ func mathFuncFloor(result []float64, args [][]float64) {
}
}
func mathFuncRand(result []float64, _ [][]float64) {
for i := range result {
n := fastrand.Uint32()
result[i] = float64(n) / (1 << 32)
}
}
func mathFuncRound(result []float64, args [][]float64) {
arg := args[0]
if len(args) == 1 {

View File

@@ -21,6 +21,7 @@ func TestParsePipeMathSuccess(t *testing.T) {
f(`math (foo + bar / baz - abc) as a`)
f(`math min(3, foo, (1 + bar) / baz) as a, max(a, b) as b, (abs(c) + 5) as d`)
f(`math round(foo) as x`)
f(`math rand() as y`)
f(`math round(foo, 0.1) as y`)
f(`math (a / b default 10) as z`)
f(`math (ln(a) + exp(b)) as x`)
@@ -47,6 +48,7 @@ func TestParsePipeMathFailure(t *testing.T) {
f(`math max(a) as x`)
f(`math round() as x`)
f(`math round(a, b, c) as x`)
f(`math rand(123) as x`)
}
func TestPipeMath(t *testing.T) {

View File

@@ -10,6 +10,7 @@ import (
"unsafe"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/encoding"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/memory"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/stringsutil"
)
@@ -279,7 +280,7 @@ func (shard *pipeTopkProcessorShard) addRow(br *blockResult, byColumns []string,
b := shard.partitionKey[:0]
for _, c := range shard.partitionColumns {
v := c.getValueAtRow(br, rowIdx)
b = marshalJSONKeyValue(b, c.name, v)
b = encoding.MarshalBytes(b, bytesutil.ToUnsafeBytes(v))
}
shard.partitionKey = b
@@ -408,8 +409,8 @@ func (ptp *pipeTopkProcessor) flush() error {
// Obtain all the partition keys
partitionKeysMap := make(map[string]struct{})
var partitionKeys []string
for _, shard := range shards {
for k := range shard.rowsByPartition {
for i := range shards {
for k := range shards[i].rowsByPartition {
if _, ok := partitionKeysMap[k]; !ok {
partitionKeysMap[k] = struct{}{}
partitionKeys = append(partitionKeys, k)
@@ -420,6 +421,9 @@ func (ptp *pipeTopkProcessor) flush() error {
// Merge sorted results across shards per each partitionKey
for _, k := range partitionKeys {
if needStop(ptp.stopCh) {
return nil
}
var rss []*pipeTopkRows
for _, shard := range shards {
rs, ok := shard.rowsByPartition[k]
@@ -428,9 +432,6 @@ func (ptp *pipeTopkProcessor) flush() error {
}
}
ptp.mergeAndFlushRows(rss)
if needStop(ptp.stopCh) {
return nil
}
}
return nil

View File

@@ -203,7 +203,7 @@ func (ps *pipeStats) initRateFuncs(step int64) {
const stateSizeBudgetChunk = 1 << 20
func (ps *pipeStats) newPipeProcessor(workersCount int, stopCh <-chan struct{}, cancel func(), ppNext pipeProcessor) pipeProcessor {
maxStateSize := int64(float64(memory.Allowed()) * 0.3)
maxStateSize := int64(float64(memory.Allowed()) * 0.4)
shards := make([]pipeStatsProcessorShard, workersCount)
for i := range shards {
@@ -326,7 +326,7 @@ func (psm *pipeStatsGroupMap) getPipeStatsGroupUint64(n uint64) *pipeStatsGroup
psg = psm.newPipeStatsGroup()
psm.u64[n] = psg
psm.shard.stateSizeBudget -= 8
psm.shard.stateSizeBudget -= int(unsafe.Sizeof(n) + unsafe.Sizeof(psg))
return psg
}
@@ -339,7 +339,7 @@ func (psm *pipeStatsGroupMap) getPipeStatsGroupNegativeInt64(n int64) *pipeStats
psg = psm.newPipeStatsGroup()
psm.negative64[uint64(n)] = psg
psm.shard.stateSizeBudget -= 8
psm.shard.stateSizeBudget -= int(unsafe.Sizeof(n) + unsafe.Sizeof(psg))
return psg
}
@@ -370,7 +370,7 @@ func (psm *pipeStatsGroupMap) newPipeStatsGroup() *pipeStatsGroup {
psg := psm.a.newPipeStatsGroup()
psg.funcs = psm.shard.ps.funcs
psg.sfps = sfps
psm.shard.stateSizeBudget -= int(unsafe.Sizeof(psg) + unsafe.Sizeof(sfps[0])*uintptr(len(sfps)))
psm.shard.stateSizeBudget -= int(unsafe.Sizeof(*psg) + unsafe.Sizeof(sfps[0])*uintptr(len(sfps)))
return psg
}
@@ -874,8 +874,8 @@ func (psp *pipeStatsProcessor) mergeShardsParallel() ([]*pipeStatsGroupMap, erro
}
k := unsafe.Slice((*byte)(unsafe.Pointer(&n)), 8)
h := xxhash.Sum64(k)
shardIdx := h % uint64(len(perCPU))
perCPU[shardIdx].u64[n] = psg
cpuIdx := h % uint64(len(perCPU))
perCPU[cpuIdx].u64[n] = psg
}
for n, psg := range psm.negative64 {
if needStop(psp.stopCh) {
@@ -883,16 +883,16 @@ func (psp *pipeStatsProcessor) mergeShardsParallel() ([]*pipeStatsGroupMap, erro
}
k := unsafe.Slice((*byte)(unsafe.Pointer(&n)), 8)
h := xxhash.Sum64(k)
shardIdx := h % uint64(len(perCPU))
perCPU[shardIdx].negative64[n] = psg
cpuIdx := h % uint64(len(perCPU))
perCPU[cpuIdx].negative64[n] = psg
}
for k, psg := range psm.strings {
if needStop(psp.stopCh) {
return
}
h := xxhash.Sum64(bytesutil.ToUnsafeBytes(k))
shardIdx := h % uint64(len(perCPU))
perCPU[shardIdx].strings[k] = psg
cpuIdx := h % uint64(len(perCPU))
perCPU[cpuIdx].strings[k] = psg
}
perShardMaps[idx] = perCPU
@@ -903,9 +903,6 @@ func (psp *pipeStatsProcessor) mergeShardsParallel() ([]*pipeStatsGroupMap, erro
if needStop(psp.stopCh) {
return nil, nil
}
if n := psp.stateSizeBudget.Load(); n < 0 {
return nil, fmt.Errorf("cannot calculate [%s], since it requires more than %dMB of memory", psp.ps.String(), psp.maxStateSize/(1<<20))
}
// Merge per-shard entries into perShardMaps[0]
for i := 0; i < cpusCount; i++ {
@@ -924,9 +921,6 @@ func (psp *pipeStatsProcessor) mergeShardsParallel() ([]*pipeStatsGroupMap, erro
if needStop(psp.stopCh) {
return nil, nil
}
if n := psp.stateSizeBudget.Load(); n < 0 {
return nil, fmt.Errorf("cannot calculate [%s], since it requires more than %dMB of memory", psp.ps.String(), psp.maxStateSize/(1<<20))
}
// Filter out maps without entries
psms := perShardMaps[0]
@@ -1055,6 +1049,12 @@ func parseStatsFunc(lex *lexer) (statsFunc, error) {
return nil, fmt.Errorf("cannot parse 'count_uniq_hash' func: %w", err)
}
return sus, nil
case lex.isKeyword("histogram"):
shs, err := parseStatsHistogram(lex)
if err != nil {
return nil, fmt.Errorf("cannot parse 'histogram' func: %w", err)
}
return shs, nil
case lex.isKeyword("max"):
sms, err := parseStatsMax(lex)
if err != nil {
@@ -1144,6 +1144,7 @@ var statsNames = []string{
"count_empty",
"count_uniq",
"count_uniq_hash",
"histogram",
"max",
"median",
"min",

View File

@@ -6,13 +6,13 @@ import (
"slices"
"sort"
"strconv"
"strings"
"sync"
"sync/atomic"
"unsafe"
"github.com/cespare/xxhash/v2"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/cgroup"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/encoding"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/memory"
@@ -82,7 +82,7 @@ func (pt *pipeTop) initFilterInValues(_ *inValuesCache, _ getFieldValuesFunc) (p
}
func (pt *pipeTop) newPipeProcessor(workersCount int, stopCh <-chan struct{}, cancel func(), ppNext pipeProcessor) pipeProcessor {
maxStateSize := int64(float64(memory.Allowed()) * 0.2)
maxStateSize := int64(float64(memory.Allowed()) * 0.4)
shards := make([]pipeTopProcessorShard, workersCount)
for i := range shards {
@@ -91,6 +91,7 @@ func (pt *pipeTop) newPipeProcessor(workersCount int, stopCh <-chan struct{}, ca
pt: pt,
},
}
shards[i].m.init(&shards[i].stateSizeBudget)
}
ptp := &pipeTopProcessor{
@@ -131,11 +132,8 @@ type pipeTopProcessorShardNopad struct {
// pt points to the parent pipeTop.
pt *pipeTop
// a reduces memory allocations when counting the number of hits over big number of unique values.
a chunkedAllocator
// m holds per-row hits.
m map[string]*uint64
// m holds per-value hits.
m hitsMap
// keyBuf is a temporary buffer for building keys for m.
keyBuf []byte
@@ -155,35 +153,21 @@ func (shard *pipeTopProcessorShard) writeBlock(br *blockResult) {
// Take into account all the columns in br.
keyBuf := shard.keyBuf
cs := br.getColumns()
for i := 0; i < br.rowsLen; i++ {
for rowIdx := 0; rowIdx < br.rowsLen; rowIdx++ {
keyBuf = keyBuf[:0]
for _, c := range cs {
v := c.getValueAtRow(br, i)
v := c.getValueAtRow(br, rowIdx)
keyBuf = encoding.MarshalBytes(keyBuf, bytesutil.ToUnsafeBytes(c.name))
keyBuf = encoding.MarshalBytes(keyBuf, bytesutil.ToUnsafeBytes(v))
}
shard.updateState(bytesutil.ToUnsafeString(keyBuf), 1)
shard.m.updateStateString(keyBuf, 1)
}
shard.keyBuf = keyBuf
return
}
if len(byFields) == 1 {
// Fast path for a single field.
c := br.getColumnByName(byFields[0])
if c.isConst {
v := c.valuesEncoded[0]
shard.updateState(v, uint64(br.rowsLen))
return
}
if c.valueType == valueTypeDict {
c.forEachDictValueWithHits(br, shard.updateState)
return
}
values := c.getValues(br)
for _, v := range values {
shard.updateState(v, 1)
}
shard.updateStatsSingleColumn(br, byFields[0])
return
}
@@ -197,33 +181,62 @@ func (shard *pipeTopProcessorShard) writeBlock(br *blockResult) {
shard.columnValues = columnValues
keyBuf := shard.keyBuf
for i := 0; i < br.rowsLen; i++ {
for rowIdx := 0; rowIdx < br.rowsLen; rowIdx++ {
keyBuf = keyBuf[:0]
for _, values := range columnValues {
keyBuf = encoding.MarshalBytes(keyBuf, bytesutil.ToUnsafeBytes(values[i]))
keyBuf = encoding.MarshalBytes(keyBuf, bytesutil.ToUnsafeBytes(values[rowIdx]))
}
shard.updateState(bytesutil.ToUnsafeString(keyBuf), 1)
shard.m.updateStateString(keyBuf, 1)
}
shard.keyBuf = keyBuf
}
func (shard *pipeTopProcessorShard) updateState(v string, hits uint64) {
m := shard.getM()
pHits := m[v]
if pHits == nil {
vCopy := shard.a.cloneString(v)
pHits = shard.a.newUint64()
m[vCopy] = pHits
shard.stateSizeBudget -= len(vCopy) + int(unsafe.Sizeof(vCopy)+unsafe.Sizeof(hits)+unsafe.Sizeof(pHits))
func (shard *pipeTopProcessorShard) updateStatsSingleColumn(br *blockResult, fieldName string) {
c := br.getColumnByName(fieldName)
if c.isConst {
v := c.valuesEncoded[0]
shard.m.updateStateGeneric(v, uint64(br.rowsLen))
return
}
*pHits += hits
}
func (shard *pipeTopProcessorShard) getM() map[string]*uint64 {
if shard.m == nil {
shard.m = make(map[string]*uint64)
switch c.valueType {
case valueTypeDict:
c.forEachDictValueWithHits(br, shard.m.updateStateGeneric)
case valueTypeUint8:
values := c.getValuesEncoded(br)
for _, v := range values {
n := unmarshalUint8(v)
shard.m.updateStateUint64(uint64(n), 1)
}
case valueTypeUint16:
values := c.getValuesEncoded(br)
for _, v := range values {
n := unmarshalUint16(v)
shard.m.updateStateUint64(uint64(n), 1)
}
case valueTypeUint32:
values := c.getValuesEncoded(br)
for _, v := range values {
n := unmarshalUint32(v)
shard.m.updateStateUint64(uint64(n), 1)
}
case valueTypeUint64:
values := c.getValuesEncoded(br)
for _, v := range values {
n := unmarshalUint64(v)
shard.m.updateStateUint64(n, 1)
}
case valueTypeInt64:
values := c.getValuesEncoded(br)
for _, v := range values {
n := unmarshalInt64(v)
shard.m.updateStateInt64(n, 1)
}
default:
values := c.getValues(br)
for _, v := range values {
shard.m.updateStateGeneric(v, 1)
}
}
return shard.m
}
func (ptp *pipeTopProcessor) writeBlock(workerID uint, br *blockResult) {
@@ -256,10 +269,7 @@ func (ptp *pipeTopProcessor) flush() error {
}
// merge state across shards in parallel
entries, err := ptp.mergeShardsParallel()
if err != nil {
return err
}
entries := ptp.mergeShardsParallel()
if needStop(ptp.stopCh) {
return nil
}
@@ -370,149 +380,94 @@ func (ptp *pipeTopProcessor) flush() error {
return nil
}
func (ptp *pipeTopProcessor) mergeShardsParallel() ([]*pipeTopEntry, error) {
func (ptp *pipeTopProcessor) mergeShardsParallel() []*pipeTopEntry {
limit := ptp.pt.limit
if limit == 0 {
return nil, nil
return nil
}
shards := ptp.shards
shardsLen := len(shards)
if shardsLen == 1 {
entries := getTopEntries(shards[0].getM(), limit, ptp.stopCh)
return entries, nil
hms := make([]*hitsMap, 0, len(ptp.shards))
for i := range ptp.shards {
hm := &ptp.shards[i].m
if hm.entriesCount() > 0 {
hms = append(hms, hm)
}
}
var wg sync.WaitGroup
perShardMaps := make([][]map[string]*uint64, shardsLen)
for i := range shards {
wg.Add(1)
go func(idx int) {
defer wg.Done()
shardMaps := make([]map[string]*uint64, shardsLen)
for i := range shardMaps {
shardMaps[i] = make(map[string]*uint64)
}
n := int64(0)
nTotal := int64(0)
for k, pHits := range shards[idx].getM() {
if needStop(ptp.stopCh) {
return
}
h := xxhash.Sum64(bytesutil.ToUnsafeBytes(k))
m := shardMaps[h%uint64(len(shardMaps))]
n += updatePipeTopMap(m, k, pHits)
if n > stateSizeBudgetChunk {
if nRemaining := ptp.stateSizeBudget.Add(-n); nRemaining < 0 {
return
}
nTotal += n
n = 0
}
}
nTotal += n
ptp.stateSizeBudget.Add(-n)
perShardMaps[idx] = shardMaps
// Clean the original map and return its state size budget back.
shards[idx].m = nil
ptp.stateSizeBudget.Add(nTotal)
}(i)
}
wg.Wait()
if needStop(ptp.stopCh) {
return nil, nil
}
if n := ptp.stateSizeBudget.Load(); n < 0 {
return nil, fmt.Errorf("cannot calculate [%s], since it requires more than %dMB of memory", ptp.pt.String(), ptp.maxStateSize/(1<<20))
}
// Obtain topN entries per each shard
entriess := make([][]*pipeTopEntry, shardsLen)
for i := range entriess {
wg.Add(1)
go func(idx int) {
defer wg.Done()
m := perShardMaps[0][idx]
for i := 1; i < len(perShardMaps); i++ {
n := int64(0)
nTotal := int64(0)
for k, pHits := range perShardMaps[i][idx] {
if needStop(ptp.stopCh) {
return
}
n += updatePipeTopMap(m, k, pHits)
if n > stateSizeBudgetChunk {
if nRemaining := ptp.stateSizeBudget.Add(-n); nRemaining < 0 {
return
}
nTotal += n
n = 0
}
}
nTotal += n
ptp.stateSizeBudget.Add(-n)
// Clean the original map and return its state size budget back.
perShardMaps[i][idx] = nil
ptp.stateSizeBudget.Add(nTotal)
}
perShardMaps[0][idx] = nil
entriess[idx] = getTopEntries(m, limit, ptp.stopCh)
}(i)
}
wg.Wait()
if needStop(ptp.stopCh) {
return nil, nil
}
if n := ptp.stateSizeBudget.Load(); n < 0 {
return nil, fmt.Errorf("cannot calculate [%s], since it requires more than %dMB of memory", ptp.pt.String(), ptp.maxStateSize/(1<<20))
}
// merge entriess
entries := entriess[0]
for _, es := range entriess[1:] {
cpusCount := cgroup.AvailableCPUs()
var entries []*pipeTopEntry
var entriesLock sync.Mutex
hitsMapMergeParallel(hms, cpusCount, ptp.stopCh, func(hm *hitsMap) {
es := getTopEntries(hm, limit, ptp.stopCh)
entriesLock.Lock()
entries = append(entries, es...)
entriesLock.Unlock()
})
if needStop(ptp.stopCh) {
return nil
}
sort.Slice(entries, func(i, j int) bool {
return entries[j].less(entries[i])
})
if uint64(len(entries)) > limit {
entries = entries[:limit]
}
return entries, nil
return entries
}
func getTopEntries(m map[string]*uint64, limit uint64, stopCh <-chan struct{}) []*pipeTopEntry {
func getTopEntries(hm *hitsMap, limit uint64, stopCh <-chan struct{}) []*pipeTopEntry {
if limit == 0 {
return nil
}
var eh topEntriesHeap
for k, pHits := range m {
var e pipeTopEntry
pushEntry := func(k string, hits uint64, kCopy bool) {
e.k = k
e.hits = hits
if uint64(len(eh)) < limit {
eCopy := e
if kCopy {
eCopy.k = strings.Clone(eCopy.k)
}
heap.Push(&eh, &eCopy)
return
}
if !eh[0].less(&e) {
return
}
eCopy := e
if kCopy {
eCopy.k = strings.Clone(eCopy.k)
}
eh[0] = &eCopy
heap.Fix(&eh, 0)
}
var b []byte
for n, pHits := range hm.u64 {
if needStop(stopCh) {
return nil
}
e := pipeTopEntry{
k: k,
hits: *pHits,
b = marshalUint64String(b[:0], n)
pushEntry(bytesutil.ToUnsafeString(b), *pHits, true)
}
for n, pHits := range hm.negative64 {
if needStop(stopCh) {
return nil
}
if uint64(len(eh)) < limit {
eCopy := e
heap.Push(&eh, &eCopy)
continue
}
if eh[0].less(&e) {
eCopy := e
eh[0] = &eCopy
heap.Fix(&eh, 0)
b = marshalInt64String(b[:0], int64(n))
pushEntry(bytesutil.ToUnsafeString(b), *pHits, true)
}
for k, pHits := range hm.strings {
if needStop(stopCh) {
return nil
}
pushEntry(k, *pHits, false)
}
result := ([]*pipeTopEntry)(eh)
@@ -524,17 +479,6 @@ func getTopEntries(m map[string]*uint64, limit uint64, stopCh <-chan struct{}) [
return result
}
func updatePipeTopMap(m map[string]*uint64, k string, pHitsSrc *uint64) int64 {
pHitsDst := m[k]
if pHitsDst != nil {
*pHitsDst += *pHitsSrc
return 0
}
m[k] = pHitsSrc
return int64(unsafe.Sizeof(k) + unsafe.Sizeof(pHitsSrc))
}
type topEntriesHeap []*pipeTopEntry
func (h *topEntriesHeap) Less(i, j int) bool {
@@ -614,7 +558,9 @@ func (wctx *pipeTopWriteContext) writeRow(rowFields []Field) {
}
wctx.rowsCount++
if wctx.valuesLen >= 1_000_000 {
// The 64_000 limit provides the best performance results.
if wctx.valuesLen >= 64_000 {
wctx.flush()
}
}
@@ -670,37 +616,41 @@ func parsePipeTop(lex *lexer) (pipe, error) {
byFields = bfs
}
hitsFieldName := "hits"
if lex.isKeyword("hits") {
lex.nextToken()
if lex.isKeyword("as") {
lex.nextToken()
}
s, err := getCompoundToken(lex)
if err != nil {
return nil, fmt.Errorf("cannot parse 'hits' name: %w", err)
}
hitsFieldName = s
}
for slices.Contains(byFields, hitsFieldName) {
hitsFieldName += "s"
}
pt := &pipeTop{
byFields: byFields,
limit: limit,
limitStr: limitStr,
hitsFieldName: hitsFieldName,
hitsFieldName: "hits",
}
if lex.isKeyword("rank") {
rankFieldName, err := parseRankFieldName(lex)
if err != nil {
return nil, fmt.Errorf("cannot parse rank field name in [%s]: %w", pt, err)
for {
switch {
case lex.isKeyword("hits"):
lex.nextToken()
if lex.isKeyword("as") {
lex.nextToken()
}
s, err := getCompoundToken(lex)
if err != nil {
return nil, fmt.Errorf("cannot parse 'hits' name: %w", err)
}
pt.hitsFieldName = s
case lex.isKeyword("rank"):
rankFieldName, err := parseRankFieldName(lex)
if err != nil {
return nil, fmt.Errorf("cannot parse rank field name in [%s]: %w", pt, err)
}
pt.rankFieldName = rankFieldName
for slices.Contains(byFields, pt.rankFieldName) {
pt.rankFieldName += "s"
}
default:
for slices.Contains(byFields, pt.hitsFieldName) {
pt.hitsFieldName += "s"
}
return pt, nil
}
pt.rankFieldName = rankFieldName
}
return pt, nil
}
func parseRankFieldName(lex *lexer) (string, error) {

View File

@@ -0,0 +1,103 @@
package logstorage
import (
"context"
"fmt"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/contextutil"
)
// pipeUnion processes '| union ...' pipe.
//
// See https://docs.victoriametrics.com/victorialogs/logsql/#union-pipe
type pipeUnion struct {
// q is a query for obtaining results to add after all the input results
q *Query
// runUnionQuery must be initialized by the caller via initUnionQuery before query execution
runUnionQuery func(ctx context.Context, q *Query, writeBlock func(workerID uint, br *blockResult)) error
}
func (pu *pipeUnion) initUnionQuery(runUnionQuery func(ctx context.Context, q *Query, writeblock func(workerID uint, br *blockResult)) error) pipe {
puNew := *pu
puNew.runUnionQuery = runUnionQuery
return &puNew
}
func (pu *pipeUnion) String() string {
return fmt.Sprintf("union (%s)", pu.q.String())
}
func (pu *pipeUnion) canLiveTail() bool {
return false
}
func (pu *pipeUnion) hasFilterInWithQuery() bool {
// The pu.q query with possible in(...) filters is processed independently at pu.flush(),
// so return false here.
return false
}
func (pu *pipeUnion) initFilterInValues(_ *inValuesCache, _ getFieldValuesFunc) (pipe, error) {
return pu, nil
}
func (pu *pipeUnion) updateNeededFields(_, _ fieldsSet) {
// nothing to do
}
func (pu *pipeUnion) newPipeProcessor(_ int, stopCh <-chan struct{}, _ func(), ppNext pipeProcessor) pipeProcessor {
return &pipeUnionProcessor{
pu: pu,
stopCh: stopCh,
ppNext: ppNext,
}
}
type pipeUnionProcessor struct {
pu *pipeUnion
stopCh <-chan struct{}
ppNext pipeProcessor
}
func (pup *pipeUnionProcessor) writeBlock(workerID uint, br *blockResult) {
if br.rowsLen == 0 {
return
}
pup.ppNext.writeBlock(workerID, br)
}
func (pup *pipeUnionProcessor) flush() error {
// execute the query to union
ctxWithCancel, cancel := contextutil.NewStopChanContext(pup.stopCh)
defer cancel()
return pup.pu.runUnionQuery(ctxWithCancel, pup.pu.q, pup.ppNext.writeBlock)
}
func parsePipeUnion(lex *lexer) (pipe, error) {
if !lex.isKeyword("union") {
return nil, fmt.Errorf("unexpected token: %q; want %q", lex.token, "union")
}
lex.nextToken()
if !lex.isKeyword("(") {
return nil, fmt.Errorf("missing '(' before the union query")
}
lex.nextToken()
q, err := parseQuery(lex)
if err != nil {
return nil, fmt.Errorf("cannot parse query inside union(...): %w", err)
}
if !lex.isKeyword(")") {
return nil, fmt.Errorf("missing ')' after the union query")
}
lex.nextToken()
pu := &pipeUnion{
q: q,
}
return pu, nil
}

View File

@@ -0,0 +1,44 @@
package logstorage
import (
"testing"
)
func TestParsePipeUnionSuccess(t *testing.T) {
f := func(pipeStr string) {
t.Helper()
expectParsePipeSuccess(t, pipeStr)
}
f(`union (*)`)
f(`union (foo)`)
f(`union (foo | union (bar | stats count(*) as x))`)
}
func TestParsePipeUnionFailure(t *testing.T) {
f := func(pipeStr string) {
t.Helper()
expectParsePipeFailure(t, pipeStr)
}
f(`union`)
f(`union()`)
f(`union(foo | count)`)
f(`union (foo) bar`)
}
func TestPipeUnionUpdateNeededFields(t *testing.T) {
f := func(s string, neededFields, unneededFields, neededFieldsExpected, unneededFieldsExpected string) {
t.Helper()
expectPipeNeededFields(t, s, neededFields, unneededFields, neededFieldsExpected, unneededFieldsExpected)
}
// all the needed fields
f("union (abc)", "*", "", "*", "")
// all the needed fields, non-empty unneeded fields
f("union (abc)", "*", "f1,f2", "*", "f1,f2")
// non-empty needed fields
f("union (abc)", "f1,f2", "", "f1,f2", "")
}

View File

@@ -7,9 +7,8 @@ import (
"sync/atomic"
"unsafe"
"github.com/cespare/xxhash/v2"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/cgroup"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/encoding"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/memory"
@@ -66,7 +65,7 @@ func (pu *pipeUniq) initFilterInValues(_ *inValuesCache, _ getFieldValuesFunc) (
}
func (pu *pipeUniq) newPipeProcessor(workersCount int, stopCh <-chan struct{}, cancel func(), ppNext pipeProcessor) pipeProcessor {
maxStateSize := int64(float64(memory.Allowed()) * 0.2)
maxStateSize := int64(float64(memory.Allowed()) * 0.4)
shards := make([]pipeUniqProcessorShard, workersCount)
for i := range shards {
@@ -75,6 +74,7 @@ func (pu *pipeUniq) newPipeProcessor(workersCount int, stopCh <-chan struct{}, c
pu: pu,
},
}
shards[i].m.init(&shards[i].stateSizeBudget)
}
pup := &pipeUniqProcessor{
@@ -115,11 +115,8 @@ type pipeUniqProcessorShardNopad struct {
// pu points to the parent pipeUniq.
pu *pipeUniq
// a is used for reducing memory allocations when collecting unique values.
a chunkedAllocator
// m holds per-row hits.
m map[string]*uint64
m hitsMap
// keyBuf is a temporary buffer for building keys for m.
keyBuf []byte
@@ -136,7 +133,7 @@ type pipeUniqProcessorShardNopad struct {
//
// It returns false if the block cannot be written because of the exceeded limit.
func (shard *pipeUniqProcessorShard) writeBlock(br *blockResult) bool {
if limit := shard.pu.limit; limit > 0 && uint64(len(shard.m)) > limit {
if limit := shard.pu.limit; limit > 0 && shard.m.entriesCount() > limit {
return false
}
@@ -153,30 +150,14 @@ func (shard *pipeUniqProcessorShard) writeBlock(br *blockResult) bool {
keyBuf = encoding.MarshalBytes(keyBuf, bytesutil.ToUnsafeBytes(c.name))
keyBuf = encoding.MarshalBytes(keyBuf, bytesutil.ToUnsafeBytes(v))
}
shard.updateState(bytesutil.ToUnsafeString(keyBuf), 1)
shard.m.updateStateString(keyBuf, 1)
}
shard.keyBuf = keyBuf
return true
}
if len(byFields) == 1 {
// Fast path for a single field.
c := br.getColumnByName(byFields[0])
if c.isConst {
v := c.valuesEncoded[0]
shard.updateState(v, uint64(br.rowsLen))
return true
}
if c.valueType == valueTypeDict {
c.forEachDictValueWithHits(br, shard.updateState)
return true
}
values := c.getValues(br)
for i, v := range values {
if needHits || i == 0 || values[i-1] != values[i] {
shard.updateState(v, 1)
}
}
shard.updateStatsSingleColumn(br, byFields[0], needHits)
return true
}
@@ -206,30 +187,61 @@ func (shard *pipeUniqProcessorShard) writeBlock(br *blockResult) bool {
for _, values := range columnValues {
keyBuf = encoding.MarshalBytes(keyBuf, bytesutil.ToUnsafeBytes(values[i]))
}
shard.updateState(bytesutil.ToUnsafeString(keyBuf), 1)
shard.m.updateStateString(keyBuf, 1)
}
shard.keyBuf = keyBuf
return true
}
func (shard *pipeUniqProcessorShard) updateState(v string, hits uint64) {
m := shard.getM()
pHits := m[v]
if pHits == nil {
vCopy := shard.a.cloneString(v)
pHits = shard.a.newUint64()
m[vCopy] = pHits
shard.stateSizeBudget -= len(vCopy) + int(unsafe.Sizeof(vCopy)+unsafe.Sizeof(hits)+unsafe.Sizeof(pHits))
func (shard *pipeUniqProcessorShard) updateStatsSingleColumn(br *blockResult, columnName string, needHits bool) {
c := br.getColumnByName(columnName)
if c.isConst {
v := c.valuesEncoded[0]
shard.m.updateStateGeneric(v, uint64(br.rowsLen))
return
}
*pHits += hits
}
func (shard *pipeUniqProcessorShard) getM() map[string]*uint64 {
if shard.m == nil {
shard.m = make(map[string]*uint64)
switch c.valueType {
case valueTypeDict:
c.forEachDictValueWithHits(br, shard.m.updateStateGeneric)
case valueTypeUint8:
values := c.getValuesEncoded(br)
for _, v := range values {
n := unmarshalUint8(v)
shard.m.updateStateUint64(uint64(n), 1)
}
case valueTypeUint16:
values := c.getValuesEncoded(br)
for _, v := range values {
n := unmarshalUint16(v)
shard.m.updateStateUint64(uint64(n), 1)
}
case valueTypeUint32:
values := c.getValuesEncoded(br)
for _, v := range values {
n := unmarshalUint32(v)
shard.m.updateStateUint64(uint64(n), 1)
}
case valueTypeUint64:
values := c.getValuesEncoded(br)
for _, v := range values {
n := unmarshalUint64(v)
shard.m.updateStateUint64(n, 1)
}
case valueTypeInt64:
values := c.getValuesEncoded(br)
for _, v := range values {
n := unmarshalInt64(v)
shard.m.updateStateInt64(n, 1)
}
default:
values := c.getValues(br)
for i, v := range values {
if needHits || i == 0 || values[i-1] != v {
shard.m.updateStateGeneric(v, 1)
}
}
}
return shard.m
}
func (pup *pipeUniqProcessor) writeBlock(workerID uint, br *blockResult) {
@@ -264,10 +276,7 @@ func (pup *pipeUniqProcessor) flush() error {
}
// merge state across shards in parallel
ms, err := pup.mergeShardsParallel()
if err != nil {
return err
}
hms := pup.mergeShardsParallel()
if needStop(pup.stopCh) {
return nil
}
@@ -275,12 +284,12 @@ func (pup *pipeUniqProcessor) flush() error {
resetHits := false
if limit := pup.pu.limit; limit > 0 {
// Trim the number of entries according to the given limit
entriesLen := 0
result := ms[:0]
for _, m := range ms {
entriesLen += len(m)
if uint64(entriesLen) <= limit {
result = append(result, m)
entriesLen := uint64(0)
result := hms[:0]
for _, hm := range hms {
entriesLen += hm.entriesCount()
if entriesLen <= limit {
result = append(result, hm)
continue
}
@@ -288,28 +297,42 @@ func (pup *pipeUniqProcessor) flush() error {
// since arbitrary number of unique entries and hits for these entries could be skipped.
// It is better to return zero hits instead of misleading hits results.
resetHits = true
for k := range m {
delete(m, k)
entriesLen--
if uint64(entriesLen) <= limit {
for n := range hm.u64 {
if entriesLen <= limit {
break
}
delete(hm.u64, n)
entriesLen--
}
if len(m) > 0 {
result = append(result, m)
for n := range hm.negative64 {
if entriesLen <= limit {
break
}
delete(hm.negative64, n)
entriesLen--
}
for k := range hm.strings {
if entriesLen <= limit {
break
}
delete(hm.strings, k)
entriesLen--
}
if hm.entriesCount() > 0 {
result = append(result, hm)
}
break
}
ms = result
hms = result
}
// Write the calculated stats in parallel to the next pipe.
var wg sync.WaitGroup
for i, m := range ms {
for i := range hms {
wg.Add(1)
go func(workerID uint) {
defer wg.Done()
pup.writeShardData(workerID, m, resetHits)
pup.writeShardData(workerID, hms[workerID], resetHits)
}(uint(i))
}
wg.Wait()
@@ -317,31 +340,32 @@ func (pup *pipeUniqProcessor) flush() error {
return nil
}
func (pup *pipeUniqProcessor) writeShardData(workerID uint, m map[string]*uint64, resetHits bool) {
func (pup *pipeUniqProcessor) writeShardData(workerID uint, hm *hitsMap, resetHits bool) {
wctx := &pipeUniqWriteContext{
workerID: workerID,
pup: pup,
}
byFields := pup.pu.byFields
var rowFields []Field
addHitsFieldIfNeeded := func(dst []Field, hits uint64) []Field {
addHitsFieldIfNeeded := func(dst []Field, pHits *uint64) []Field {
if pup.pu.hitsFieldName == "" {
return dst
}
if resetHits {
hits = 0
hits := uint64(0)
if !resetHits {
hits = *pHits
}
hitsStr := string(marshalUint64String(nil, hits))
dst = append(dst, Field{
Name: pup.pu.hitsFieldName,
Value: hitsStr,
Value: wctx.getUint64String(hits),
})
return dst
}
if len(byFields) == 0 {
for k, pHits := range m {
for k, pHits := range hm.strings {
if needStop(pup.stopCh) {
return
}
@@ -366,25 +390,46 @@ func (pup *pipeUniqProcessor) writeShardData(workerID uint, m map[string]*uint64
Value: bytesutil.ToUnsafeString(value),
})
}
rowFields = addHitsFieldIfNeeded(rowFields, *pHits)
rowFields = addHitsFieldIfNeeded(rowFields, pHits)
wctx.writeRow(rowFields)
}
} else if len(byFields) == 1 {
fieldName := byFields[0]
for k, pHits := range m {
for n, pHits := range hm.u64 {
if needStop(pup.stopCh) {
return
}
rowFields = append(rowFields[:0], Field{
Name: fieldName,
Value: wctx.getUint64String(n),
})
rowFields = addHitsFieldIfNeeded(rowFields, pHits)
wctx.writeRow(rowFields)
}
for n, pHits := range hm.negative64 {
if needStop(pup.stopCh) {
return
}
rowFields = append(rowFields[:0], Field{
Name: fieldName,
Value: wctx.getInt64String(int64(n)),
})
rowFields = addHitsFieldIfNeeded(rowFields, pHits)
wctx.writeRow(rowFields)
}
for k, pHits := range hm.strings {
if needStop(pup.stopCh) {
return
}
rowFields = append(rowFields[:0], Field{
Name: fieldName,
Value: k,
})
rowFields = addHitsFieldIfNeeded(rowFields, *pHits)
rowFields = addHitsFieldIfNeeded(rowFields, pHits)
wctx.writeRow(rowFields)
}
} else {
for k, pHits := range m {
for k, pHits := range hm.strings {
if needStop(pup.stopCh) {
return
}
@@ -405,7 +450,7 @@ func (pup *pipeUniqProcessor) writeShardData(workerID uint, m map[string]*uint64
})
fieldIdx++
}
rowFields = addHitsFieldIfNeeded(rowFields, *pHits)
rowFields = addHitsFieldIfNeeded(rowFields, pHits)
wctx.writeRow(rowFields)
}
}
@@ -413,126 +458,30 @@ func (pup *pipeUniqProcessor) writeShardData(workerID uint, m map[string]*uint64
wctx.flush()
}
func (pup *pipeUniqProcessor) mergeShardsParallel() ([]map[string]*uint64, error) {
shards := pup.shards
shardsLen := len(shards)
if shardsLen == 1 {
m := shards[0].getM()
var ms []map[string]*uint64
if len(m) > 0 {
ms = append(ms, m)
}
return ms, nil
}
var wg sync.WaitGroup
perShardMaps := make([][]map[string]*uint64, shardsLen)
for i := range shards {
wg.Add(1)
go func(idx int) {
defer wg.Done()
shardMaps := make([]map[string]*uint64, shardsLen)
for i := range shardMaps {
shardMaps[i] = make(map[string]*uint64)
}
n := int64(0)
nTotal := int64(0)
for k, pHits := range shards[idx].getM() {
if needStop(pup.stopCh) {
return
}
h := xxhash.Sum64(bytesutil.ToUnsafeBytes(k))
m := shardMaps[h%uint64(len(shardMaps))]
n += updatePipeUniqMap(m, k, pHits)
if n > stateSizeBudgetChunk {
if nRemaining := pup.stateSizeBudget.Add(-n); nRemaining < 0 {
return
}
nTotal += n
n = 0
}
}
nTotal += n
pup.stateSizeBudget.Add(-n)
perShardMaps[idx] = shardMaps
// Clean the original map and return its state size budget back.
shards[idx].m = nil
pup.stateSizeBudget.Add(nTotal)
}(i)
}
wg.Wait()
if needStop(pup.stopCh) {
return nil, nil
}
if n := pup.stateSizeBudget.Load(); n < 0 {
return nil, fmt.Errorf("cannot calculate [%s], since it requires more than %dMB of memory", pup.pu.String(), pup.maxStateSize/(1<<20))
}
// Merge per-shard entries into perShardMaps[0]
for i := range perShardMaps {
wg.Add(1)
go func(idx int) {
defer wg.Done()
m := perShardMaps[0][idx]
for i := 1; i < len(perShardMaps); i++ {
n := int64(0)
nTotal := int64(0)
for k, psg := range perShardMaps[i][idx] {
if needStop(pup.stopCh) {
return
}
n += updatePipeUniqMap(m, k, psg)
if n > stateSizeBudgetChunk {
if nRemaining := pup.stateSizeBudget.Add(-n); nRemaining < 0 {
return
}
nTotal += n
n = 0
}
}
nTotal += n
pup.stateSizeBudget.Add(-n)
// Clean the original map and return its state size budget back.
perShardMaps[i][idx] = nil
pup.stateSizeBudget.Add(nTotal)
}
}(i)
}
wg.Wait()
if needStop(pup.stopCh) {
return nil, nil
}
if n := pup.stateSizeBudget.Load(); n < 0 {
return nil, fmt.Errorf("cannot calculate [%s], since it requires more than %dMB of memory", pup.pu.String(), pup.maxStateSize/(1<<20))
}
// Filter out maps without entries
ms := perShardMaps[0]
result := ms[:0]
for _, m := range ms {
if len(m) > 0 {
result = append(result, m)
func (pup *pipeUniqProcessor) mergeShardsParallel() []*hitsMap {
hms := make([]*hitsMap, 0, len(pup.shards))
for i := range pup.shards {
hm := &pup.shards[i].m
if hm.entriesCount() > 0 {
hms = append(hms, hm)
}
}
return result, nil
}
func updatePipeUniqMap(m map[string]*uint64, k string, pHitsSrc *uint64) int64 {
pHitsDst := m[k]
if pHitsDst != nil {
*pHitsDst += *pHitsSrc
return 0
cpusCount := cgroup.AvailableCPUs()
hmsResult := make([]*hitsMap, 0, cpusCount)
var hmsLock sync.Mutex
hitsMapMergeParallel(hms, cpusCount, pup.stopCh, func(hm *hitsMap) {
if hm.entriesCount() > 0 {
hmsLock.Lock()
hmsResult = append(hmsResult, hm)
hmsLock.Unlock()
}
})
if needStop(pup.stopCh) {
return nil
}
m[k] = pHitsSrc
return int64(unsafe.Sizeof(k) + unsafe.Sizeof(pHitsSrc))
return hmsResult
}
type pipeUniqWriteContext struct {
@@ -541,6 +490,8 @@ type pipeUniqWriteContext struct {
rcs []resultColumn
br blockResult
a arena
// rowsCount is the number of rows in the current block
rowsCount int
@@ -548,6 +499,18 @@ type pipeUniqWriteContext struct {
valuesLen int
}
func (wctx *pipeUniqWriteContext) getUint64String(n uint64) string {
bLen := len(wctx.a.b)
wctx.a.b = marshalUint64String(wctx.a.b, n)
return bytesutil.ToUnsafeString(wctx.a.b[bLen:])
}
func (wctx *pipeUniqWriteContext) getInt64String(n int64) string {
bLen := len(wctx.a.b)
wctx.a.b = marshalInt64String(wctx.a.b, n)
return bytesutil.ToUnsafeString(wctx.a.b[bLen:])
}
func (wctx *pipeUniqWriteContext) writeRow(rowFields []Field) {
rcs := wctx.rcs
@@ -578,25 +541,28 @@ func (wctx *pipeUniqWriteContext) writeRow(rowFields []Field) {
}
wctx.rowsCount++
if wctx.valuesLen >= 1_000_000 {
// The 64_000 limit provides the best performance results.
if wctx.valuesLen >= 64_000 {
wctx.flush()
}
}
func (wctx *pipeUniqWriteContext) flush() {
rcs := wctx.rcs
br := &wctx.br
wctx.valuesLen = 0
if wctx.rowsCount == 0 {
return
}
// Flush rcs to ppNext
br.setResultColumns(rcs, wctx.rowsCount)
wctx.br.setResultColumns(wctx.rcs, wctx.rowsCount)
wctx.valuesLen = 0
wctx.rowsCount = 0
wctx.pup.ppNext.writeBlock(wctx.workerID, br)
br.reset()
for i := range rcs {
rcs[i].resetValues()
wctx.pup.ppNext.writeBlock(wctx.workerID, &wctx.br)
wctx.br.reset()
for i := range wctx.rcs {
wctx.rcs[i].resetValues()
}
wctx.a.reset()
}
func parsePipeUniq(lex *lexer) (pipe, error) {

View File

@@ -33,6 +33,7 @@ func (su *statsCountUniq) updateNeededFields(neededFields fieldsSet) {
func (su *statsCountUniq) newStatsProcessor(a *chunkedAllocator) statsProcessor {
sup := a.newStatsCountUniqProcessor()
sup.a = a
sup.m.init()
return sup
}
@@ -74,9 +75,6 @@ func (sus *statsCountUniqSet) entriesCount() uint64 {
}
func (sus *statsCountUniqSet) updateStateTimestamp(ts int64) int {
if sus.timestamps == nil {
sus.timestamps = make(map[uint64]struct{})
}
_, ok := sus.timestamps[uint64(ts)]
if ok {
return 0
@@ -86,9 +84,6 @@ func (sus *statsCountUniqSet) updateStateTimestamp(ts int64) int {
}
func (sus *statsCountUniqSet) updateStateUint64(n uint64) int {
if sus.u64 == nil {
sus.u64 = make(map[uint64]struct{})
}
_, ok := sus.u64[n]
if ok {
return 0
@@ -105,9 +100,6 @@ func (sus *statsCountUniqSet) updateStateInt64(n int64) int {
}
func (sus *statsCountUniqSet) updateStateNegativeInt64(n int64) int {
if sus.negative64 == nil {
sus.negative64 = make(map[uint64]struct{})
}
_, ok := sus.negative64[uint64(n)]
if ok {
return 0
@@ -129,9 +121,6 @@ func (sus *statsCountUniqSet) updateStateGeneric(a *chunkedAllocator, v string)
}
func (sus *statsCountUniqSet) updateStateString(a *chunkedAllocator, v string) int {
if sus.strings == nil {
sus.strings = make(map[string]struct{})
}
_, ok := sus.strings[v]
if ok {
return 0
@@ -142,13 +131,10 @@ func (sus *statsCountUniqSet) updateStateString(a *chunkedAllocator, v string) i
}
func (sus *statsCountUniqSet) mergeState(src *statsCountUniqSet, stopCh <-chan struct{}) {
mergeUint64Set(&sus.timestamps, src.timestamps, stopCh)
mergeUint64Set(&sus.u64, src.u64, stopCh)
mergeUint64Set(&sus.negative64, src.negative64, stopCh)
mergeUint64Set(sus.timestamps, src.timestamps, stopCh)
mergeUint64Set(sus.u64, src.u64, stopCh)
mergeUint64Set(sus.negative64, src.negative64, stopCh)
if src.strings != nil && sus.strings == nil {
sus.strings = make(map[string]struct{})
}
for k := range src.strings {
if needStop(stopCh) {
return
@@ -159,11 +145,7 @@ func (sus *statsCountUniqSet) mergeState(src *statsCountUniqSet, stopCh <-chan s
}
}
func mergeUint64Set(pDst *map[uint64]struct{}, src map[uint64]struct{}, stopCh <-chan struct{}) {
if src != nil && *pDst == nil {
*pDst = make(map[uint64]struct{})
}
dst := *pDst
func mergeUint64Set(dst map[uint64]struct{}, src map[uint64]struct{}, stopCh <-chan struct{}) {
for n := range src {
if needStop(stopCh) {
return

View File

@@ -33,6 +33,7 @@ func (su *statsCountUniqHash) updateNeededFields(neededFields fieldsSet) {
func (su *statsCountUniqHash) newStatsProcessor(a *chunkedAllocator) statsProcessor {
sup := a.newStatsCountUniqHashProcessor()
sup.a = a
sup.m.init()
return sup
}
@@ -74,9 +75,6 @@ func (sus *statsCountUniqHashSet) entriesCount() uint64 {
}
func (sus *statsCountUniqHashSet) updateStateTimestamp(ts int64) int {
if sus.timestamps == nil {
sus.timestamps = make(map[uint64]struct{})
}
_, ok := sus.timestamps[uint64(ts)]
if ok {
return 0
@@ -86,9 +84,6 @@ func (sus *statsCountUniqHashSet) updateStateTimestamp(ts int64) int {
}
func (sus *statsCountUniqHashSet) updateStateUint64(n uint64) int {
if sus.u64 == nil {
sus.u64 = make(map[uint64]struct{})
}
_, ok := sus.u64[n]
if ok {
return 0
@@ -105,9 +100,6 @@ func (sus *statsCountUniqHashSet) updateStateInt64(n int64) int {
}
func (sus *statsCountUniqHashSet) updateStateNegativeInt64(n int64) int {
if sus.negative64 == nil {
sus.negative64 = make(map[uint64]struct{})
}
_, ok := sus.negative64[uint64(n)]
if ok {
return 0
@@ -130,9 +122,6 @@ func (sus *statsCountUniqHashSet) updateStateGeneric(v string) int {
func (sus *statsCountUniqHashSet) updateStateString(v []byte) int {
h := xxhash.Sum64(v)
if sus.strings == nil {
sus.strings = make(map[uint64]struct{})
}
_, ok := sus.strings[h]
if ok {
return 0
@@ -142,10 +131,10 @@ func (sus *statsCountUniqHashSet) updateStateString(v []byte) int {
}
func (sus *statsCountUniqHashSet) mergeState(src *statsCountUniqHashSet, stopCh <-chan struct{}) {
mergeUint64Set(&sus.timestamps, src.timestamps, stopCh)
mergeUint64Set(&sus.u64, src.u64, stopCh)
mergeUint64Set(&sus.negative64, src.negative64, stopCh)
mergeUint64Set(&sus.strings, src.strings, stopCh)
mergeUint64Set(sus.timestamps, src.timestamps, stopCh)
mergeUint64Set(sus.u64, src.u64, stopCh)
mergeUint64Set(sus.negative64, src.negative64, stopCh)
mergeUint64Set(sus.strings, src.strings, stopCh)
}
func (sup *statsCountUniqHashProcessor) updateStatsForAllRows(sf statsFunc, br *blockResult) int {

View File

@@ -0,0 +1,189 @@
package logstorage
import (
"fmt"
"github.com/VictoriaMetrics/metrics"
)
type statsHistogram struct {
fieldName string
}
func (sh *statsHistogram) String() string {
return "histogram(" + quoteTokenIfNeeded(sh.fieldName) + ")"
}
func (sh *statsHistogram) updateNeededFields(neededFields fieldsSet) {
updateNeededFieldsForStatsFunc(neededFields, []string{sh.fieldName})
}
func (sh *statsHistogram) newStatsProcessor(a *chunkedAllocator) statsProcessor {
return a.newStatsHistogramProcessor()
}
type statsHistogramProcessor struct {
h metrics.Histogram
}
func (shp *statsHistogramProcessor) updateStatsForAllRows(sf statsFunc, br *blockResult) int {
sh := sf.(*statsHistogram)
c := br.getColumnByName(sh.fieldName)
if c.isConst {
v := c.valuesEncoded[0]
f, ok := tryParseNumber(v)
if ok {
for rowIdx := 0; rowIdx < br.rowsLen; rowIdx++ {
shp.h.Update(f)
}
}
return 0
}
switch c.valueType {
case valueTypeUint8:
values := c.getValuesEncoded(br)
for _, v := range values {
n := unmarshalUint8(v)
shp.h.Update(float64(n))
}
case valueTypeUint16:
values := c.getValuesEncoded(br)
for _, v := range values {
n := unmarshalUint16(v)
shp.h.Update(float64(n))
}
case valueTypeUint32:
values := c.getValuesEncoded(br)
for _, v := range values {
n := unmarshalUint32(v)
shp.h.Update(float64(n))
}
case valueTypeUint64:
values := c.getValuesEncoded(br)
for _, v := range values {
n := unmarshalUint64(v)
shp.h.Update(float64(n))
}
case valueTypeInt64:
values := c.getValuesEncoded(br)
for _, v := range values {
n := unmarshalInt64(v)
shp.h.Update(float64(n))
}
case valueTypeFloat64:
values := c.getValuesEncoded(br)
for _, v := range values {
f := unmarshalFloat64(v)
shp.h.Update(f)
}
case valueTypeIPv4:
// skip ipv4 values, since they cannot be represented as numbers
case valueTypeTimestampISO8601:
// skip iso8601 values, since they cannot be represented as numbers
default:
values := c.getValues(br)
for _, v := range values {
f, ok := tryParseNumber(v)
if ok {
shp.h.Update(f)
}
}
}
return 0
}
func (shp *statsHistogramProcessor) updateStatsForRow(sf statsFunc, br *blockResult, rowIdx int) int {
sh := sf.(*statsHistogram)
c := br.getColumnByName(sh.fieldName)
if c.isConst {
v := c.valuesEncoded[0]
f, ok := tryParseNumber(v)
if ok {
shp.h.Update(f)
}
return 0
}
switch c.valueType {
case valueTypeUint8:
values := c.getValuesEncoded(br)
v := values[rowIdx]
n := unmarshalUint8(v)
shp.h.Update(float64(n))
case valueTypeUint16:
values := c.getValuesEncoded(br)
v := values[rowIdx]
n := unmarshalUint16(v)
shp.h.Update(float64(n))
case valueTypeUint32:
values := c.getValuesEncoded(br)
v := values[rowIdx]
n := unmarshalUint32(v)
shp.h.Update(float64(n))
case valueTypeUint64:
values := c.getValuesEncoded(br)
v := values[rowIdx]
n := unmarshalUint64(v)
shp.h.Update(float64(n))
case valueTypeInt64:
values := c.getValuesEncoded(br)
v := values[rowIdx]
n := unmarshalInt64(v)
shp.h.Update(float64(n))
case valueTypeFloat64:
values := c.getValuesEncoded(br)
v := values[rowIdx]
f := unmarshalFloat64(v)
shp.h.Update(f)
case valueTypeIPv4:
// skip ipv4 values, since they cannot be represented as numbers
case valueTypeTimestampISO8601:
// skip iso8601 values, since they cannot be represented as numbers
default:
v := c.getValueAtRow(br, rowIdx)
f, ok := tryParseNumber(v)
if ok {
shp.h.Update(f)
}
}
return 0
}
func (shp *statsHistogramProcessor) mergeState(_ statsFunc, sfp statsProcessor) {
src := sfp.(*statsHistogramProcessor)
shp.h.Merge(&src.h)
}
func (shp *statsHistogramProcessor) finalizeStats(_ statsFunc, dst []byte, _ <-chan struct{}) []byte {
dst = append(dst, '[')
shp.h.VisitNonZeroBuckets(func(vmrange string, count uint64) {
dst = append(dst, `{"vmrange":"`...)
dst = append(dst, vmrange...)
dst = append(dst, `","hits":`...)
dst = marshalUint64String(dst, count)
dst = append(dst, `},`...)
})
dst = dst[:len(dst)-1]
dst = append(dst, ']')
return dst
}
func parseStatsHistogram(lex *lexer) (*statsHistogram, error) {
fields, err := parseStatsFuncFields(lex, "histogram")
if err != nil {
return nil, fmt.Errorf("cannot parse field name: %w", err)
}
if len(fields) != 1 {
return nil, fmt.Errorf("unexpected number of fields; got %d; want 1", len(fields))
}
sh := &statsHistogram{
fieldName: fields[0],
}
return sh, nil
}

View File

@@ -0,0 +1,52 @@
package logstorage
import (
"testing"
)
func TestParseStatsHistogramSuccess(t *testing.T) {
f := func(pipeStr string) {
t.Helper()
expectParseStatsFuncSuccess(t, pipeStr)
}
f(`histogram(foo)`)
}
func TestParseStatsHistogramFailure(t *testing.T) {
f := func(pipeStr string) {
t.Helper()
expectParseStatsFuncFailure(t, pipeStr)
}
f(`histogram`)
f(`histogram(a, b)`)
f(`histogram(a) abc`)
}
func TestStatsHistogram(t *testing.T) {
f := func(pipeStr string, rows, rowsExpected [][]Field) {
t.Helper()
expectPipeResults(t, pipeStr, rows, rowsExpected)
}
f("stats histogram(a) as x", [][]Field{
{
{"_msg", `abc`},
{"a", `2`},
{"b", `3`},
},
{
{"_msg", `def`},
{"a", `1.9`},
},
{
{"a", `3.05`},
{"b", `54`},
},
}, [][]Field{
{
{"x", `[{"vmrange":"1.896e+00...2.154e+00","hits":2},{"vmrange":"2.783e+00...3.162e+00","hits":1}]`},
},
})
}

View File

@@ -118,6 +118,10 @@ func (s *Storage) runQuery(ctx context.Context, tenantIDs []TenantID, q *Query,
if err != nil {
return err
}
qNew, err = s.initUnionQueries(tenantIDs, qNew)
if err != nil {
return err
}
q = qNew
streamIDs := q.getStreamIDs()
@@ -548,6 +552,38 @@ type inValuesCache struct {
m map[string][]string
}
func (s *Storage) initUnionQueries(tenantIDs []TenantID, q *Query) (*Query, error) {
if !hasUnionPipes(q.pipes) {
return q, nil
}
runUnionQuery := func(ctx context.Context, q *Query, writeBlock func(workerID uint, br *blockResult)) error {
return s.runQuery(ctx, tenantIDs, q, writeBlock)
}
pipesNew := make([]pipe, len(q.pipes))
for i, p := range q.pipes {
if pu, ok := p.(*pipeUnion); ok {
p = pu.initUnionQuery(runUnionQuery)
}
pipesNew[i] = p
}
qNew := &Query{
f: q.f,
pipes: pipesNew,
}
return qNew, nil
}
func hasUnionPipes(pipes []pipe) bool {
for _, p := range pipes {
if _, ok := p.(*pipeUnion); ok {
return true
}
}
return false
}
type getJoinMapFunc func(q *Query, byFields []string, prefix string) (map[string][][]Field, error)
func (s *Storage) initJoinMaps(ctx context.Context, tenantIDs []TenantID, q *Query) (*Query, error) {
@@ -560,8 +596,7 @@ func (s *Storage) initJoinMaps(ctx context.Context, tenantIDs []TenantID, q *Que
}
pipesNew := make([]pipe, len(q.pipes))
for i := range q.pipes {
p := q.pipes[i]
for i, p := range q.pipes {
if pj, ok := p.(*pipeJoin); ok {
pNew, err := pj.initJoinMap(getJoinMap)
if err != nil {

View File

@@ -6,6 +6,7 @@ import (
"math"
"strconv"
"sync"
"time"
"github.com/VictoriaMetrics/metrics"
@@ -53,6 +54,8 @@ func ParseStream(r io.Reader, isGzipped bool, processBody func([]byte) ([]byte,
return nil
}
var skippedSampleLogger = logger.WithThrottler("otlp_skipped_sample", 5*time.Second)
func (wr *writeContext) appendSamplesFromScopeMetrics(sc *pb.ScopeMetrics) {
for _, m := range sc.Metrics {
if len(m.Name) == 0 {
@@ -68,6 +71,7 @@ func (wr *writeContext) appendSamplesFromScopeMetrics(sc *pb.ScopeMetrics) {
case m.Sum != nil:
if m.Sum.AggregationTemporality != pb.AggregationTemporalityCumulative {
rowsDroppedUnsupportedSum.Inc()
skippedSampleLogger.Warnf("unsupported delta temporality for %q ('sum'): skipping it", metricName)
continue
}
for _, p := range m.Sum.DataPoints {
@@ -80,6 +84,7 @@ func (wr *writeContext) appendSamplesFromScopeMetrics(sc *pb.ScopeMetrics) {
case m.Histogram != nil:
if m.Histogram.AggregationTemporality != pb.AggregationTemporalityCumulative {
rowsDroppedUnsupportedHistogram.Inc()
skippedSampleLogger.Warnf("unsupported delta temporality for %q ('histogram'): skipping it", metricName)
continue
}
for _, p := range m.Histogram.DataPoints {
@@ -88,6 +93,7 @@ func (wr *writeContext) appendSamplesFromScopeMetrics(sc *pb.ScopeMetrics) {
case m.ExponentialHistogram != nil:
if m.ExponentialHistogram.AggregationTemporality != pb.AggregationTemporalityCumulative {
rowsDroppedUnsupportedExponentialHistogram.Inc()
skippedSampleLogger.Warnf("unsupported delta temporality for %q ('exponential histogram'): skipping it", metricName)
continue
}
for _, p := range m.ExponentialHistogram.DataPoints {
@@ -95,7 +101,7 @@ func (wr *writeContext) appendSamplesFromScopeMetrics(sc *pb.ScopeMetrics) {
}
default:
rowsDroppedUnsupportedMetricType.Inc()
logger.Warnf("unsupported type for metric %q", metricName)
skippedSampleLogger.Warnf("unsupported type for metric %q", metricName)
}
}
}
@@ -139,7 +145,7 @@ func (wr *writeContext) appendSamplesFromHistogram(metricName string, p *pb.Hist
}
if len(p.BucketCounts) != len(p.ExplicitBounds)+1 {
// fast path, broken data format
logger.Warnf("opentelemetry bad histogram format: %q, size of buckets: %d, size of bounds: %d", metricName, len(p.BucketCounts), len(p.ExplicitBounds))
skippedSampleLogger.Warnf("opentelemetry bad histogram format: %q, size of buckets: %d, size of bounds: %d", metricName, len(p.BucketCounts), len(p.ExplicitBounds))
return
}

View File

@@ -1822,8 +1822,8 @@ func testStorageVariousDataPatternsConcurrently(t *testing.T, registerOnly bool,
func testStorageVariousDataPatterns(t *testing.T, registerOnly bool, op func(s *Storage, mrs []MetricRow), concurrency int, splitBatches bool) {
f := func(t *testing.T, sameBatchMetricNames, sameRowMetricNames, sameBatchDates, sameRowDates bool) {
batches, wantCounts := testGenerateMetricRowBatches(&batchOptions{
numBatches: 4,
numRowsPerBatch: 100,
numBatches: 3,
numRowsPerBatch: 30,
registerOnly: registerOnly,
sameBatchMetricNames: sameBatchMetricNames,
sameRowMetricNames: sameRowMetricNames,