mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2026-06-18 08:13:57 +03:00
Compare commits
33 Commits
test-tmp
...
v1.6.0-vic
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ca653a515c | ||
|
|
e5b4cf33bf | ||
|
|
e24a8f2088 | ||
|
|
f27e120aeb | ||
|
|
ee1ce90501 | ||
|
|
47fe8cf3be | ||
|
|
5813aa6602 | ||
|
|
b4f4ece162 | ||
|
|
bb00f7529f | ||
|
|
ad3bd11334 | ||
|
|
875c6663ef | ||
|
|
b48b7c454a | ||
|
|
f523348b3f | ||
|
|
63bf1e008f | ||
|
|
419ac10c60 | ||
|
|
d631d2c100 | ||
|
|
89431458bf | ||
|
|
d8d0c0ac01 | ||
|
|
c0f5699bad | ||
|
|
277fdd1070 | ||
|
|
d290efb849 | ||
|
|
b26a68641c | ||
|
|
b88cda5c41 | ||
|
|
d2a791bef3 | ||
|
|
99516a5730 | ||
|
|
aecc86c390 | ||
|
|
500b54f5aa | ||
|
|
cc29692e27 | ||
|
|
f018aa33cb | ||
|
|
92b6475fa6 | ||
|
|
bda3546cfd | ||
|
|
2691cdefe3 | ||
|
|
93b8aa5c9d |
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
1291
docs/changelog/CHANGELOG_2024.md
Normal file
1291
docs/changelog/CHANGELOG_2024.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
|
||||
- TODO
|
||||
|
||||
## 0.8.13
|
||||
|
||||
**Release date:** 14 Jan 2025
|
||||
|
||||
 
|
||||
|
||||
- victorialogs version: v1.4.0 -> v1.5.0
|
||||
|
||||
## 0.8.12
|
||||
|
||||
**Release date:** 06 Jan 2025
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
|
||||
- TODO
|
||||
|
||||
## 0.15.4
|
||||
|
||||
**Release date:** 14 Jan 2025
|
||||
|
||||
 
|
||||
|
||||
- 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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
|
||||
- TODO
|
||||
|
||||
## 0.13.6
|
||||
|
||||
**Release date:** 14 Jan 2025
|
||||
|
||||
 
|
||||
|
||||
- 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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
|
||||
- TODO
|
||||
|
||||
## 0.8.4
|
||||
|
||||
**Release date:** 14 Jan 2025
|
||||
|
||||
 
|
||||
|
||||
- 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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
|
||||
- TODO
|
||||
|
||||
## 0.17.1
|
||||
|
||||
**Release date:** 14 Jan 2025
|
||||
|
||||
 
|
||||
|
||||
- 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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
@@ -1,6 +1,24 @@
|
||||
## Next release
|
||||
|
||||
- TODO
|
||||
|
||||
## 0.7.2
|
||||
|
||||
**Release date:** 14 Jan 2025
|
||||
|
||||
 
|
||||
|
||||
- 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
|
||||
|
||||
 
|
||||
|
||||
- updated common dependency 0.0.35 -> 0.0.37
|
||||
- fixed typo useMultitenantMode -> useMultiTenantMode in remotewrite settings
|
||||
- allow passing additional remotewrite setings
|
||||
|
||||
## 0.7.0
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
|
||||
- TODO
|
||||
|
||||
## 0.6.4
|
||||
|
||||
**Release date:** 14 Jan 2025
|
||||
|
||||
 
|
||||
|
||||
- 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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
@@ -2,6 +2,22 @@
|
||||
|
||||
- TODO
|
||||
|
||||
## 0.33.4
|
||||
|
||||
**Release date:** 14 Jan 2025
|
||||
|
||||
 
|
||||
|
||||
- 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
|
||||
|
||||
 
|
||||
|
||||
- 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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
|
||||
- TODO
|
||||
|
||||
## 0.40.4
|
||||
|
||||
**Release date:** 13 Jan 2025
|
||||
|
||||
 
|
||||
|
||||
- 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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
|
||||
- TODO
|
||||
|
||||
## 0.13.5
|
||||
|
||||
**Release date:** 14 Jan 2025
|
||||
|
||||
 
|
||||
|
||||
- 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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
@@ -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
7
docs/search/_index.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
page: search
|
||||
layout: search
|
||||
draft: false
|
||||
weight: 0
|
||||
search: true
|
||||
---
|
||||
@@ -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 |
@@ -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). |
|
||||
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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
239
lib/logstorage/hits_map.go
Normal 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()
|
||||
}
|
||||
@@ -396,6 +396,7 @@ func (q *Query) CanReturnLastNResults() bool {
|
||||
*pipeTop,
|
||||
*pipeSort,
|
||||
*pipeStats,
|
||||
*pipeUnion,
|
||||
*pipeUniq:
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -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`)
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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) {
|
||||
|
||||
103
lib/logstorage/pipe_union.go
Normal file
103
lib/logstorage/pipe_union.go
Normal 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
|
||||
}
|
||||
44
lib/logstorage/pipe_union_test.go
Normal file
44
lib/logstorage/pipe_union_test.go
Normal 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", "")
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
189
lib/logstorage/stats_histogram.go
Normal file
189
lib/logstorage/stats_histogram.go
Normal 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
|
||||
}
|
||||
52
lib/logstorage/stats_histogram_test.go
Normal file
52
lib/logstorage/stats_histogram_test.go
Normal 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}]`},
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user