Compare commits

..

10 Commits

Author SHA1 Message Date
dependabot[bot]
ac82971faa build(deps): bump github/codeql-action from 4.35.3 to 4.36.2
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.35.3 to 4.36.2.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](e46ed2cbd0...8aad20d150)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 4.36.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-25 04:12:30 +00:00
Fred Navruzov
e30e8be1f4 docs/vmanomaly: update to v1.29.7 (#11163)
Update anomaly detection docs to align with `v1.29.7` release

---------

Signed-off-by: Fred Navruzov <fred-navruzov@users.noreply.github.com>
2026-06-24 21:22:58 +03:00
f41gh7
24ac567a9f fixes tests after dce8193c16 2026-06-24 17:13:04 +02:00
Aliaksandr Valialkin
dce8193c16 vendor: update github.com/VictoriaMetrics/VictoriaLogs from v1.121.1-0.20260616132739-c901a1e31cb3 to v1.51.1-0.20260624061259-dc94972a8708 2026-06-24 09:40:23 +02:00
Immanuel Tikhonov
e196479fb2 apptest: fix invalid go test command in README (#1484)
apptest/README.md points to `go test ./app/apptest`, but that dir is not
in this repo. Fresh clone, instant fail, kinda rough.

This switches it to `go test ./apptest/...` and adds a short note that
`go test ./...` needs the test binaries built first.

How to repro:
1. Fresh clone the repo.
2. Run `go test ./app/apptest`.
3. See `stat .../app/apptest: directory not found`.

---------

Signed-off-by: immanuwell <pchpr.00@list.ru>
Signed-off-by: Immanuel Tikhonov <122638311+immanuwell@users.noreply.github.com>
Co-authored-by: Phuong Le <39565248+func25@users.noreply.github.com>
2026-06-24 08:09:16 +02:00
Hui Wang
1c774564a2 stream aggregation: improve rate_xx results with out of order samples (#11140)
Previously, there could be an unexpected increase in `rate` if an
out-of-order sample was ingested after the previous flush.
Before:
<img width="1503" height="835" alt="image"
src="https://github.com/user-attachments/assets/3fb05f3e-51af-4c5c-989a-ec3da089fc23"
/>
After:
<img width="2546" height="924" alt="image"
src="https://github.com/user-attachments/assets/db8d8b0e-bbc0-4927-947a-713fc1fb4c5f"
/>

---------

Signed-off-by: hagen1778 <roman@victoriametrics.com>
Co-authored-by: hagen1778 <roman@victoriametrics.com>
2026-06-23 12:46:50 +02:00
Max Kotliar
e1c554d4a6 docs/changelog: follow-up on 3419328f
Follow-up on
3419328f1c
2026-06-23 11:48:23 +03:00
Zhu Jiekun
3419328f1c app/vmselect: propagate cache reset operation to selectNode (#11118)
Fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11112
PR https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11118

Propagate cache reset operation to `-selectNode` when `/internal/resetRollupResultCache` is called. Previously, the propagation only happened when `delete_series` API was called.

To avoid an infinite loop, the propagation happens only when `propagate=1` query arg is set.

Note that if `-search.resetCacheAuthKey` is configured. It will be used as `authKey` query arg while propagating requests to other select instances.

---------

Signed-off-by: Zhu Jiekun <jiekun@victoriametrics.com>
Co-authored-by: Max Kotliar <mkotlyar@victoriametrics.com>
2026-06-23 11:17:02 +03:00
Max Kotliar
e841e45877 dashboards: fix typo "Sey major ..." -> "See major ..."
The credit goes to @marco-m who proposed the change in
https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11126.
2026-06-23 11:01:20 +03:00
Max Kotliar
d53d8849e7 lib/promrelabel: hide relabel debug cross-link when no target id is present (#11143)
The cross-link between `/metric-relabel-debug` and `/target-relabel-debug` pages has always been rendered, regardless of whether a target `id` query param was present or not. The target ID is responsible for preloading either metrics or the target relabeling config.

The commit hides the cross-link when no target ID is present. Without the ID, pages are essentially the same, so there is no need to link them. It should reduce user confusion.
2026-06-23 10:53:44 +03:00
81 changed files with 1056 additions and 846 deletions

View File

@@ -54,14 +54,14 @@ jobs:
restore-keys: go-artifacts-${{ runner.os }}-codeql-analyze-${{ steps.go.outputs.go-version }}-
- name: Initialize CodeQL
uses: github/codeql-action/init@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
with:
languages: go
- name: Autobuild
uses: github/codeql-action/autobuild@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
uses: github/codeql-action/autobuild@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
with:
category: 'language:go'

View File

@@ -457,12 +457,10 @@ func TestSetIntervalAsTimeFilter(t *testing.T) {
f(`* | count()`, "vlogs", true)
f(`error OR _time:5m | count()`, "vlogs", true)
f(`(_time: 5m AND error) OR (_time: 5m AND warn) | count()`, "vlogs", true)
f(`* | error OR _time:5m | count()`, "vlogs", true)
f(`_time:5m | count()`, "vlogs", false)
f(`_time:2023-04-25T22:45:59Z | count()`, "vlogs", false)
f(`error AND _time:5m | count()`, "vlogs", false)
f(`* | error AND _time:5m | count()`, "vlogs", false)
}
func TestRecordingRuleExec_Partial(t *testing.T) {

View File

@@ -4,7 +4,7 @@ The `apptest` package contains the integration tests for the VictoriaMetrics
applications (such as vmstorage, vminsert, and vmselect).
An integration test aims at verifying the behavior of an application as a whole,
as apposed to a unit test that verifies the behavior of a building block of an
as opposed to a unit test that verifies the behavior of a building block of an
application.
To achieve that an integration test starts an application in a separate process
@@ -19,10 +19,10 @@ work together as a system.
The package provides a collection of helpers to start applications and make
queries to them:
- `app.go` - contains the generic code for staring an application and should
- `app.go` - contains the generic code for starting an application and should
not be used by integration tests directly.
- `{vmstorage,vminsert,etc}.go` - build on top of `app.go` and provide the
code for staring a specific application.
code for starting a specific application.
- `client.go` - provides helper functions for sending HTTP requests to
applications.
@@ -36,7 +36,7 @@ the application binary files to be built and put into the `bin` directory. The
build rule used for running integration tests, `make apptest`,
accounts for that, it builds all application binaries before running the tests.
But if you want to run the tests without `make`, i.e. by executing
`go test ./app/apptest`, you will need to build the binaries first (for example,
`go test ./apptest/tests`, you will need to build the binaries first (for example,
by executing `make all`).
Not all binaries can be built from `master` branch, cluster binaries can be built

View File

@@ -2083,7 +2083,7 @@
"type": "prometheus",
"uid": "$ds"
},
"description": "Shows memory pressure based on [Pressure Stall Information](https://docs.kernel.org/accounting/psi.html).\n\n**Lower is better.**\n\nPressure is measured as amount of time within 1sec time window the process was:\n- waiting: at least one thread was blocked on memory.\n- stalled: every thread was blocked on memory (severe pressure).\n\nElevated memory pressure can slowdown the process performance by utilizing more disk IO. Consider increasing amount of available RAM limit or decreasing the load on the process.\n\nSeу major page faults rate panel in Troubleshooting section if this metric continued to be high.",
"description": "Shows memory pressure based on [Pressure Stall Information](https://docs.kernel.org/accounting/psi.html).\n\n**Lower is better.**\n\nPressure is measured as amount of time within 1sec time window the process was:\n- waiting: at least one thread was blocked on memory.\n- stalled: every thread was blocked on memory (severe pressure).\n\nElevated memory pressure can slowdown the process performance by utilizing more disk IO. Consider increasing amount of available RAM limit or decreasing the load on the process.\n\nSee major page faults rate panel in Troubleshooting section if this metric continued to be high.",
"fieldConfig": {
"defaults": {
"color": {

View File

@@ -2388,7 +2388,7 @@
"type": "prometheus",
"uid": "$ds"
},
"description": "Shows memory pressure based on [Pressure Stall Information](https://docs.kernel.org/accounting/psi.html).\n\n**Lower is better.**\n\nPressure is measured as amount of time within 1sec time window the process was:\n- waiting: at least one thread was blocked on memory.\n- stalled: every thread was blocked on memory (severe pressure).\n\nElevated memory pressure can slowdown the process performance by utilizing more disk IO. Consider increasing amount of available RAM limit or decreasing the load on the process.\n\nSeу major page faults rate panel in Troubleshooting section if this metric continued to be high.",
"description": "Shows memory pressure based on [Pressure Stall Information](https://docs.kernel.org/accounting/psi.html).\n\n**Lower is better.**\n\nPressure is measured as amount of time within 1sec time window the process was:\n- waiting: at least one thread was blocked on memory.\n- stalled: every thread was blocked on memory (severe pressure).\n\nElevated memory pressure can slowdown the process performance by utilizing more disk IO. Consider increasing amount of available RAM limit or decreasing the load on the process.\n\nSee major page faults rate panel in Troubleshooting section if this metric continued to be high.",
"fieldConfig": {
"defaults": {
"color": {

View File

@@ -2084,7 +2084,7 @@
"type": "victoriametrics-metrics-datasource",
"uid": "$ds"
},
"description": "Shows memory pressure based on [Pressure Stall Information](https://docs.kernel.org/accounting/psi.html).\n\n**Lower is better.**\n\nPressure is measured as amount of time within 1sec time window the process was:\n- waiting: at least one thread was blocked on memory.\n- stalled: every thread was blocked on memory (severe pressure).\n\nElevated memory pressure can slowdown the process performance by utilizing more disk IO. Consider increasing amount of available RAM limit or decreasing the load on the process.\n\nSeу major page faults rate panel in Troubleshooting section if this metric continued to be high.",
"description": "Shows memory pressure based on [Pressure Stall Information](https://docs.kernel.org/accounting/psi.html).\n\n**Lower is better.**\n\nPressure is measured as amount of time within 1sec time window the process was:\n- waiting: at least one thread was blocked on memory.\n- stalled: every thread was blocked on memory (severe pressure).\n\nElevated memory pressure can slowdown the process performance by utilizing more disk IO. Consider increasing amount of available RAM limit or decreasing the load on the process.\n\nSee major page faults rate panel in Troubleshooting section if this metric continued to be high.",
"fieldConfig": {
"defaults": {
"color": {

View File

@@ -2389,7 +2389,7 @@
"type": "victoriametrics-metrics-datasource",
"uid": "$ds"
},
"description": "Shows memory pressure based on [Pressure Stall Information](https://docs.kernel.org/accounting/psi.html).\n\n**Lower is better.**\n\nPressure is measured as amount of time within 1sec time window the process was:\n- waiting: at least one thread was blocked on memory.\n- stalled: every thread was blocked on memory (severe pressure).\n\nElevated memory pressure can slowdown the process performance by utilizing more disk IO. Consider increasing amount of available RAM limit or decreasing the load on the process.\n\nSeу major page faults rate panel in Troubleshooting section if this metric continued to be high.",
"description": "Shows memory pressure based on [Pressure Stall Information](https://docs.kernel.org/accounting/psi.html).\n\n**Lower is better.**\n\nPressure is measured as amount of time within 1sec time window the process was:\n- waiting: at least one thread was blocked on memory.\n- stalled: every thread was blocked on memory (severe pressure).\n\nElevated memory pressure can slowdown the process performance by utilizing more disk IO. Consider increasing amount of available RAM limit or decreasing the load on the process.\n\nSee major page faults rate panel in Troubleshooting section if this metric continued to be high.",
"fieldConfig": {
"defaults": {
"color": {

View File

@@ -2165,7 +2165,7 @@
"type": "victoriametrics-metrics-datasource",
"uid": "$ds"
},
"description": "Shows memory pressure based on [Pressure Stall Information](https://docs.kernel.org/accounting/psi.html).\n\n**Lower is better.**\n\nPressure is measured as amount of time within 1sec time window the process was:\n- waiting: at least one thread was blocked on memory.\n- stalled: every thread was blocked on memory (severe pressure).\n\nElevated memory pressure can slowdown the process performance by utilizing more disk IO. Consider increasing amount of available RAM limit or decreasing the load on the process.\n\nSeу major page faults rate panel in Troubleshooting section if this metric continued to be high.",
"description": "Shows memory pressure based on [Pressure Stall Information](https://docs.kernel.org/accounting/psi.html).\n\n**Lower is better.**\n\nPressure is measured as amount of time within 1sec time window the process was:\n- waiting: at least one thread was blocked on memory.\n- stalled: every thread was blocked on memory (severe pressure).\n\nElevated memory pressure can slowdown the process performance by utilizing more disk IO. Consider increasing amount of available RAM limit or decreasing the load on the process.\n\nSee major page faults rate panel in Troubleshooting section if this metric continued to be high.",
"fieldConfig": {
"defaults": {
"color": {

View File

@@ -1840,7 +1840,7 @@
"type": "victoriametrics-metrics-datasource",
"uid": "$ds"
},
"description": "Shows memory pressure based on [Pressure Stall Information](https://docs.kernel.org/accounting/psi.html).\n\n**Lower is better.**\n\nPressure is measured as amount of time within 1sec time window the process was:\n- waiting: at least one thread was blocked on memory.\n- stalled: every thread was blocked on memory (severe pressure).\n\nElevated memory pressure can slowdown the process performance by utilizing more disk IO. Consider increasing amount of available RAM limit or decreasing the load on the process.\n\nSeу major page faults rate panel in Troubleshooting section if this metric continued to be high.",
"description": "Shows memory pressure based on [Pressure Stall Information](https://docs.kernel.org/accounting/psi.html).\n\n**Lower is better.**\n\nPressure is measured as amount of time within 1sec time window the process was:\n- waiting: at least one thread was blocked on memory.\n- stalled: every thread was blocked on memory (severe pressure).\n\nElevated memory pressure can slowdown the process performance by utilizing more disk IO. Consider increasing amount of available RAM limit or decreasing the load on the process.\n\nSee major page faults rate panel in Troubleshooting section if this metric continued to be high.",
"fieldConfig": {
"defaults": {
"color": {

View File

@@ -2164,7 +2164,7 @@
"type": "prometheus",
"uid": "$ds"
},
"description": "Shows memory pressure based on [Pressure Stall Information](https://docs.kernel.org/accounting/psi.html).\n\n**Lower is better.**\n\nPressure is measured as amount of time within 1sec time window the process was:\n- waiting: at least one thread was blocked on memory.\n- stalled: every thread was blocked on memory (severe pressure).\n\nElevated memory pressure can slowdown the process performance by utilizing more disk IO. Consider increasing amount of available RAM limit or decreasing the load on the process.\n\nSeу major page faults rate panel in Troubleshooting section if this metric continued to be high.",
"description": "Shows memory pressure based on [Pressure Stall Information](https://docs.kernel.org/accounting/psi.html).\n\n**Lower is better.**\n\nPressure is measured as amount of time within 1sec time window the process was:\n- waiting: at least one thread was blocked on memory.\n- stalled: every thread was blocked on memory (severe pressure).\n\nElevated memory pressure can slowdown the process performance by utilizing more disk IO. Consider increasing amount of available RAM limit or decreasing the load on the process.\n\nSee major page faults rate panel in Troubleshooting section if this metric continued to be high.",
"fieldConfig": {
"defaults": {
"color": {

View File

@@ -1839,7 +1839,7 @@
"type": "prometheus",
"uid": "$ds"
},
"description": "Shows memory pressure based on [Pressure Stall Information](https://docs.kernel.org/accounting/psi.html).\n\n**Lower is better.**\n\nPressure is measured as amount of time within 1sec time window the process was:\n- waiting: at least one thread was blocked on memory.\n- stalled: every thread was blocked on memory (severe pressure).\n\nElevated memory pressure can slowdown the process performance by utilizing more disk IO. Consider increasing amount of available RAM limit or decreasing the load on the process.\n\nSeу major page faults rate panel in Troubleshooting section if this metric continued to be high.",
"description": "Shows memory pressure based on [Pressure Stall Information](https://docs.kernel.org/accounting/psi.html).\n\n**Lower is better.**\n\nPressure is measured as amount of time within 1sec time window the process was:\n- waiting: at least one thread was blocked on memory.\n- stalled: every thread was blocked on memory (severe pressure).\n\nElevated memory pressure can slowdown the process performance by utilizing more disk IO. Consider increasing amount of available RAM limit or decreasing the load on the process.\n\nSee major page faults rate panel in Troubleshooting section if this metric continued to be high.",
"fieldConfig": {
"defaults": {
"color": {

View File

@@ -59,7 +59,7 @@ services:
- '--external.alert.source=explore?orgId=1&left=["now-1h","now","VictoriaMetrics",{"expr": },{"mode":"Metrics"},{"ui":[true,true,true,"none"]}]'
restart: always
vmanomaly:
image: victoriametrics/vmanomaly:v1.29.6
image: victoriametrics/vmanomaly:v1.29.7
depends_on:
- "victoriametrics"
ports:

View File

@@ -14,6 +14,17 @@ aliases:
---
Please find the changelog for VictoriaMetrics Anomaly Detection below.
## v1.29.7
Released: 2026-06-25
- UI: updated [vmanomaly UI](https://docs.victoriametrics.com/anomaly-detection/ui/) from [v1.7.1](https://docs.victoriametrics.com/anomaly-detection/ui/#v171) to [v1.7.2](https://docs.victoriametrics.com/anomaly-detection/ui/#v172), see respective [release notes](https://docs.victoriametrics.com/anomaly-detection/ui/#v172) for details.
- IMPROVEMENT: Increased high-cardinality inference scaling by optionally scattering periodic infer jobs to reduce contention on shared resources (e.g. datasource, CPU, RAM) when `settings.n_workers > 1` and `scheduler.infer_every` is smaller than the total time to fetch and process all queries. This is controlled by new `scatter_infer_jobs` boolean argument of [Periodic Scheduler](https://docs.victoriametrics.com/anomaly-detection/components/scheduler/#parameters-1) (default: `false`).
- IMPROVEMENT: Optimized internal batching for reader post-fetch series processing, exposing reader processing queue depth, and clarifying inference skip logs after data fetch timeouts.
- IMPROVEMENT: Refined `VmReader` and `VLogsReader` logging after datasource request failures by suppressing the follow-up generic "No data" or "No unseen data" warning for failed fetches. Failed requests now keep the original datasource error while empty successful responses still emit the no-data warning.
## v1.29.6
Released: 2026-06-17

View File

@@ -423,7 +423,7 @@ services:
# ...
vmanomaly:
container_name: vmanomaly
image: victoriametrics/vmanomaly:v1.29.6
image: victoriametrics/vmanomaly:v1.29.7
# ...
restart: always
volumes:
@@ -641,7 +641,7 @@ options:
Heres an example of using the config splitter to divide configurations based on the `extra_filters` argument from the reader section:
```sh
docker pull victoriametrics/vmanomaly:v1.29.6 && docker image tag victoriametrics/vmanomaly:v1.29.6 vmanomaly
docker pull victoriametrics/vmanomaly:v1.29.7 && docker image tag victoriametrics/vmanomaly:v1.29.7 vmanomaly
```
```sh

View File

@@ -45,7 +45,7 @@ There are 2 types of compatibility to consider when migrating in stateful mode:
| Group start | Group end | Compatibility | Notes |
|---------|--------- |------------|-------|
| [v1.29.1](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1291) | [v1.29.6](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1296) | Fully Compatible | - |
| [v1.29.1](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1291) | [v1.29.7](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1297) | Fully Compatible | - |
| [v1.28.7](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1287) | [v1.29.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1290) | Partially compatible* | Dumped models of class [prophet](https://docs.victoriametrics.com/anomaly-detection/components/models/#prophet) and [seasonal quantile](https://docs.victoriametrics.com/anomaly-detection/components/models/#online-seasonal-quantile) have problems with loading to [v1.29.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1290) due to dropped `pytz` library. **Upgrading directly from v1.28.7 to [v1.29.1](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1291) with a fix is suggested** |
| [v1.26.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1262) | [v1.28.7](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1287) | Fully Compatible | [v1.28.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1280) introduced [rolling](https://docs.victoriametrics.com/anomaly-detection/components/models/#rolling-models) model class drop in favor of [online](https://docs.victoriametrics.com/anomaly-detection/components/models/#online-models) models (`rolling_quantile` and `std` models), however, it does not impact compatibility, as artifacts were not produced by default for rolling models. Also, offline `mad` and `zscore` models are redirecting to their respective online counterparts since [v1.28.4](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1284). |
| [v1.25.3](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1253) | [v1.26.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1270) | Partially Compatible* | [v1.25.3](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1253) introduced `forecast_at` argument for base [univariate](https://docs.victoriametrics.com/anomaly-detection/components/models/#univariate-models) and `Prophet` [models](https://docs.victoriametrics.com/anomaly-detection/components/models/#prophet), however, itself remains backward-reversible from newer states like [v1.26.2](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1262), [v1.27.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1270). (All models except `isolation_forest_multivariate` class will be dropped) |

View File

@@ -132,7 +132,7 @@ Below are the steps to get `vmanomaly` up and running inside a Docker container:
1. Pull Docker image:
```sh
docker pull victoriametrics/vmanomaly:v1.29.6
docker pull victoriametrics/vmanomaly:v1.29.7
```
2. Create the license file with your license key.
@@ -152,7 +152,7 @@ docker run -it \
-v ./license:/license \
-v ./config.yaml:/config.yaml \
-p 8490:8490 \
victoriametrics/vmanomaly:v1.29.6 \
victoriametrics/vmanomaly:v1.29.7 \
/config.yaml \
--licenseFile=/license \
--loggerLevel=INFO \
@@ -169,7 +169,7 @@ docker run -it \
-e VMANOMALY_DATA_DUMPS_DIR=/tmp/vmanomaly/data \
-e VMANOMALY_MODEL_DUMPS_DIR=/tmp/vmanomaly/models \
-p 8490:8490 \
victoriametrics/vmanomaly:v1.29.6 \
victoriametrics/vmanomaly:v1.29.7 \
/config.yaml \
--licenseFile=/license \
--loggerLevel=INFO \
@@ -182,7 +182,7 @@ services:
# ...
vmanomaly:
container_name: vmanomaly
image: victoriametrics/vmanomaly:v1.29.6
image: victoriametrics/vmanomaly:v1.29.7
# ...
restart: always
volumes:
@@ -267,6 +267,7 @@ schedulers:
# https://docs.victoriametrics.com/anomaly-detection/components/scheduler/#periodic-scheduler
class: 'periodic'
infer_every: '5m'
scatter_infer_jobs: true
fit_every: '1d'
fit_window: '4w'
@@ -298,6 +299,7 @@ reader:
datasource_url: "https://play.victoriametrics.com/" # [YOUR_DATASOURCE_URL]
tenant_id: '0:0'
sampling_period: "5m"
series_processing_batch_size: 8 # number of time series to process together while preparing data for fit or infer stages
queries:
# define your queries with MetricsQL - https://docs.victoriametrics.com/victoriametrics/metricsql/
cpu_user:
@@ -413,11 +415,13 @@ For optimal service behavior, consider the following tweaks when configuring `vm
- Configure the **inference frequency** in the [scheduler](https://docs.victoriametrics.com/anomaly-detection/components/scheduler/) section of the configuration file.
- Ensure that `infer_every` aligns with your **minimum required alerting frequency**.
- For example, if receiving **alerts every 15 minutes** is sufficient (when `anomaly_score > 1`), set `infer_every` to match `reader.sampling_period` or override it per query via `reader.queries.query_xxx.step` for an optimal setup.
- Set `scheduler.scatter_infer_jobs` {{% available_from "v1.29.7" anomaly %}} [arg](https://docs.victoriametrics.com/anomaly-detection/components/scheduler/#parameters-1) to `true` to allow for equal distribution of inference jobs across `infer_every` intervals, which can further enhance parallel processing efficiency and reduce resource contention when `reader.queries` contains a large number of queries.
**Reader**:
- Setup the datasource to read data from in the [reader](https://docs.victoriametrics.com/anomaly-detection/components/reader/) section. Include tenant ID if using a [cluster version of VictoriaMetrics](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/) (`multitenant` value {{% available_from "v1.16.2" anomaly %}} can be also used here).
- Define queries for input data using [MetricsQL](https://docs.victoriametrics.com/victoriametrics/metricsql/) under `reader.queries` section. Note, it's possible to override reader-level arguments at query level for increased flexibility, e.g. specifying per-query [timezone](https://docs.victoriametrics.com/anomaly-detection/faq/#handling-timezones) or [sampling period](https://docs.victoriametrics.com/anomaly-detection/components/reader/#config-parameters).
- For longer `fit_window` intervals in scheduler, consider splitting queries into smaller time ranges to avoid excessive memory usage, timeouts and hitting server-side constraints, so they can be queried separately and reconstructed on `vmanomaly` side. Please refer to this [example](https://docs.victoriametrics.com/anomaly-detection/faq/#handling-large-queries-in-vmanomaly) for more details.
- Set `reader.series_processing_batch_size` {{% available_from "v1.29.7" anomaly %}} [arg](https://docs.victoriametrics.com/anomaly-detection/components/reader/#config-parameters) to a reasonable value (4-16, default is 8) to balance between memory usage and processing speed when preparing data for fit or infer stages.
> If applicable - consider [`VLogsReader`](https://docs.victoriametrics.com/anomaly-detection/components/reader/#victorialogs-reader) {{% available_from "v1.26.0" anomaly %}} to perform anomaly detection on **log-derived metrics**. This is particularly useful for scenarios where log data needs to be analyzed for unusual patterns or behaviors, such as error rates or request latencies.

View File

@@ -315,7 +315,7 @@ docker run -it --rm \
-e VMANOMALY_MCP_SERVER_URL=http://mcp-vmanomaly:8081/mcp \
-p 8080:8080 \
-p 8490:8490 \
victoriametrics/vmanomaly:v1.29.6 \
victoriametrics/vmanomaly:v1.29.7 \
vmanomaly_config.yaml
```
@@ -640,6 +640,21 @@ If the **results** look good and the **model configuration should be deployed in
## Changelog
### v1.7.2
Released: 2026-06-25
vmanomaly version: [v1.29.7](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1297)
- FEATURE: Added controls for selecting server-configured scheduled models (drop-down inside [model wizard](#model-panel)) and browsing scheduled queries from the running vmanomaly instance ("Queries" button, "scheduled queries" tab).
- IMPROVEMENT: Surfaced datasource fetch failures from ad-hoc VMUI raw queries as query-level errors instead of returning a successful empty result that triggers a generic "No match" warning. Now the user can see the actual error message from the datasource (e.g. "unauthorized", "not found", etc.) and take appropriate action.
- BUGFIX: Fixed [UI/query-server](#settings-panel) handling of VictoriaMetrics datasource URLs that already include `/select/multitenant/prometheus`. Such URLs are now recognized as cluster datasource URLs, preserving the multitenant path when proxying VMUI requests and allowing `server.use_reader_connection_settings` to reuse [configured reader credentials for authenticated datasources](#authentication).
- BUGFIX: Fixed [settings](#settings-panel) inputs for server and datasource URLs so editing, deleting, or pasting text is no longer immediately reverted to the previous value before applying changes.
- BUGFIX: Fixed [model wizard](#model-panel) settings for [`IsolationForestModel`](https://docs.victoriametrics.com/anomaly-detection/components/models/#isolation-forest-multivariate) `contamination`, allowing decimal float values such as `0.1` or `0,1` to be typed or pasted without being collapsed to `0`, while preserving the `"auto"` value.
### v1.7.1
Released: 2026-06-11

View File

@@ -49,6 +49,7 @@ schedulers:
periodic_online: # alias
class: 'periodic' # scheduler class
infer_every: "30s" # how often to produce anomaly scores for new data
scatter_infer_jobs: true # distribute infer jobs evenly across the infer interval to reduce synchronized bursts
fit_every: "365d" # how often to re-fit the models, for online models used effectively once, then they are updated with new data and won't require re-fit
fit_window: "3d" # how much historical data to use for fit stage
start_from: "00:00" # start from specified time, i.e. 00:00 given timezone and do daily fits as `fit_every` is 1 day
@@ -56,6 +57,7 @@ schedulers:
periodic_offline_1w:
class: 'periodic'
infer_every: "15m"
scatter_infer_jobs: true
fit_every: "24h"
fit_window: "14d"
# if no start_from is specified, jobs will start immediately after service starts
@@ -135,6 +137,7 @@ server:
port: 8490
path_prefix: '/vmanomaly' # optional path prefix for all HTTP routes
max_concurrent_tasks: 4 # maximum number of concurrent anomaly detection tasks processed by backend
use_reader_connection_settings: True # if True, use reader's datasource_url and credentials for UI requests to datasource
uvicorn_config: # optional Uvicorn server configuration
log_level: 'warning'
```

View File

@@ -1265,7 +1265,7 @@ monitoring:
Let's pull the docker image for `vmanomaly`:
```sh
docker pull victoriametrics/vmanomaly:v1.29.6
docker pull victoriametrics/vmanomaly:v1.29.7
```
Now we can run the docker container putting as volumes both config and model file:
@@ -1279,7 +1279,7 @@ docker run -it \
-v $(PWD)/license:/license \
-v $(PWD)/custom_model.py:/vmanomaly/model/custom.py \
-v $(PWD)/custom.yaml:/config.yaml \
victoriametrics/vmanomaly:v1.29.6 /config.yaml \
victoriametrics/vmanomaly:v1.29.7 /config.yaml \
--licenseFile=/license
--watch
```

View File

@@ -458,6 +458,21 @@ Label names [description](#labelnames)
<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>
`url`, `query_key`, `scheduler_alias`, `preset`
</td>
</tr>
<tr>
<td>
<span style="white-space: nowrap;">`vmanomaly_reader_processing_tasks_queued`</span>
</td>
<td>
`Gauge`
</td>
<td>The total number of queued processing tasks {{% available_from "v1.29.7" anomaly %}} (timeseries batches of size `series_processing_batch_size`) for the `query_key` query within the specified scheduler `scheduler_alias`, in the `vmanomaly` service running in `preset` mode. If continuously >0, it may lead to skipped infer runs due to resource contention and timeouts.</td>
<td>
`url`, `query_key`, `scheduler_alias`, `preset`
</td>
</tr>

View File

@@ -421,7 +421,20 @@ Optional argument{{% available_from "v1.18.1" anomaly %}} allows defining **vali
`60s`
</td>
<td>
Optional argument{{% available_from "v1.25.3" anomaly %}} allows specifying a time offset for all queries in `queries`. Defaults to `0s` (0) if not set and can be overridden on a [per-query basis](#per-query-parameters).
Optional argument {{% available_from "v1.25.3" anomaly %}}, allows specifying a time offset for all queries in `queries`. Defaults to `0s` (0) if not set and can be overridden on a [per-query basis](#per-query-parameters).
</td>
</tr>
<tr>
<td>
<span style="white-space: nowrap;">`series_processing_batch_size`</span>
</td>
<td>
`8`
</td>
<td>
Optional argument {{% available_from "v1.29.7" anomaly %}}, allows specifying the number of time series to process together while preparing data for fit or infer stages. Defaults to `8`. Suggested values are 4-16 for high-cardinality queries.
</td>
</tr>
</tbody>
@@ -450,6 +463,7 @@ reader:
sampling_period: '1m'
query_from_last_seen_timestamp: True # false by default
latency_offset: '1ms'
series_processing_batch_size: 8
```
### MetricsQL Playground

View File

@@ -74,40 +74,7 @@ options={`"scheduler.periodic.PeriodicScheduler"`, `"scheduler.oneoff.OneoffSche
### Parameters
For periodic scheduler parameters are defined as differences in times, expressed in difference units, e.g. days, hours, minutes, seconds.
Examples: `"50s"`, `"4m"`, `"3h"`, `"2d"`, `"1w"`.
<table class="params">
<thead>
<tr>
<th></th>
<th>Time granularity</th>
</tr>
</thead>
<tbody>
<tr>
<td>s</td>
<td>seconds</td>
</tr>
<tr>
<td>m</td>
<td>minutes</td>
</tr>
<tr>
<td>h</td>
<td>hours</td>
</tr>
<tr>
<td>d</td>
<td>days</td>
</tr>
<tr>
<td>w</td>
<td>weeks</td>
</tr>
</tbody>
</table>
For periodic scheduler parameters are defined as differences in times, expressed in difference units, e.g. days, hours, minutes, seconds. Time granularity is defined by the last characters of a string. Examples: `"50s"` (seconds), `"4m"` (minutes), `"3h"` (hours), `"2d"` (days), `"1w"` (weeks).
<table class="params">
<thead>
@@ -188,6 +155,21 @@ Specifies when to initiate the first `fit_every` call. Accepts either an ISO 860
Defines the local timezone for the `start_from` parameter, if specified. Defaults to `UTC` if no timezone is provided.
</td>
</tr>
<tr>
<td>
<span style="white-space: nowrap;">`scatter_infer_jobs`{{% available_from "v1.29.7" anomaly %}}</span>
</td>
<td>bool, <span style="white-space: nowrap;">Optional</span></td>
<td>
`true` or `false`
</td>
<td>
If `true`, distribute infer jobs and their dependent data-fetch jobs evenly across the infer interval. This reduces synchronized read and inference bursts for high-scale configurations. Defaults to `false`. Useful when `settings.n_workers > 1`, `reader.queries` cardinality is high, and `scheduler.infer_every` is small.
</td>
</tr>
</tbody>
</table>
@@ -200,6 +182,7 @@ schedulers:
# (or class: "scheduler.periodic.PeriodicScheduler" for versions before v1.13.0, without class alias support)
fit_window: "14d"
infer_every: "1m"
scatter_infer_jobs: true # Distribute infer jobs evenly across the infer interval to reduce synchronized bursts.
fit_every: "1h"
start_from: "20:00" # If launched before 20:00 (local Kyiv time), the first run starts today at 20:00. Otherwise, it starts tomorrow at 20:00.
tz: "Europe/Kyiv" # Defaults to 'UTC' if not specified.

View File

@@ -395,7 +395,7 @@ services:
restart: always
vmanomaly:
container_name: vmanomaly
image: victoriametrics/vmanomaly:v1.29.6
image: victoriametrics/vmanomaly:v1.29.7
depends_on:
- "victoriametrics"
ports:

View File

@@ -16,39 +16,9 @@ aliases:
---
## Install Recommendation
It is recommended to run the latest available release of VictoriaMetrics from [this page](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/latest), as it includes all bug fixes and enhancements.
It is recommended to run the latest available release of VictoriaMetrics from [this page](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/latest), since it contains all the bugfixes and enhancements.
There is no need to tune VictoriaMetrics, as it uses reasonable defaults for its command-line flags. These flags are automatically adjusted for the available CPU and RAM resources. There is no need for operating system tuning because VictoriaMetrics is optimized for default OS settings. The only option is to increase the limit on the [number of open files in the OS](https://medium.com/@muhammadtriwibowo/set-permanently-ulimit-n-open-files-in-ubuntu-4d61064429a), so VictoriaMetrics could accept more incoming connections and could keep open more data files. VictoriaMetrics is tested and developed to run efficiently on these defaults, which fit the majority of workloads. Change a setting only when the docs explicitly instruct you to, including when and why.
## Memory
VictoriaMetrics components detect the available memory at startup as the smaller of the host RAM and the cgroup memory limit.
To keep them stable:
1. Do not set `GOMEMLIMIT`. VictoriaMetrics paces garbage collection with `GOGC` and does not use `GOMEMLIMIT` for sizing.
`GOMEMLIMIT` bounds only Go runtime memory, not the process's total RSS, off-heap caches, mmap-ed files,
or the OS page cache, so it is not a reliable way to size VictoriaMetrics containers. It can curb Go heap growth,
but setting it too low makes the garbage collector run more often than `GOGC` dictates,
spending in the worst case up to ~50% of CPU time on GC —
the ceiling enforced by Go's [GC CPU limiter](https://go.dev/doc/gc-guide).
Set the container memory limit, and VictoriaMetrics automatically sizes its caches and memory-aware limits.
1. Do not hand-tune cache sizes with `-storage.cacheSize*` flags; rely on the defaults.
If a component needs larger caches, move it to a host with more memory.
See [Cache tuning](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#cache-tuning).
1. Do not autoscale `vmstorage` with the Vertical Pod Autoscaler (VPA) or the Horizontal Pod Autoscaler (HPA).
VPA: cache sizes are derived from the memory limit, read only once at startup.
Modes that recreate the pod (`Recreate`, `Auto`) reset the caches and force a cold start,
causing slow inserts and query latency spikes. In-place resizing is not picked up at runtime,
so `vmstorage` keeps the budget and `vm_available_memory_bytes` initialized at startup, which also skews the dashboards.
Set fixed memory requests and limits for `vmstorage` rather than autoscaling.
HPA: `vmstorage` is stateful. Adding nodes sends new series to them while existing data stays where it is.
Removing nodes makes the data on them unavailable to queries and can cause data loss without replication.
Frequent scaling keeps changing the routing and can degrade the cluster.
1. Leave headroom for the OS page cache and workload spikes —
see [capacity planning](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#capacity-planning).
There is no need to tune VictoriaMetrics because it uses reasonable defaults for command-line flags. These flags are automatically adjusted for the available CPU and RAM resources. There is no need in Operating System tuning because VictoriaMetrics is optimized for default OS settings. The only option is to increase the limit on the [number of open files in the OS](https://medium.com/@muhammadtriwibowo/set-permanently-ulimit-n-open-files-in-ubuntu-4d61064429a), so VictoriaMetrics could accept more incoming connections and could keep open more data files.
## Swap

View File

@@ -188,7 +188,7 @@ If you see unexpected or unreliable query results from VictoriaMetrics, then try
These are the most common reasons for slow data ingestion in VictoriaMetrics:
1. [Memory shortage](#memory-shortage) for the given amounts of [active time series](https://docs.victoriametrics.com/victoriametrics/faq/#what-is-an-active-time-series).
1. Memory shortage for the given amounts of [active time series](https://docs.victoriametrics.com/victoriametrics/faq/#what-is-an-active-time-series).
VictoriaMetrics (or `vmstorage` in the cluster version of VictoriaMetrics) maintains an in-memory cache `storage/tsid`
for a quick search for internal series IDs for each incoming metric. VictoriaMetrics automatically determines the maximum
@@ -352,119 +352,6 @@ These are the solutions that exist for improving the performance of slow queries
See also [this article](https://valyala.medium.com/how-to-optimize-promql-and-metricsql-queries-85a1b75bf986),
which explains how to identify and optimize slow queries.
## Memory shortage
+High memory utilization alone does not indicate a shortage.
VictoriaMetrics components could operate normally under high memory utilization,
but it is recommended to have
[at least 50% of free memory for stability](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#capacity-planning).
Before reacting, it is important to identify memory shortage — not enough memory for the workload —
apart from high utilization and memory pressure.
Memory pressure is the kernel's reclaim activity as shown by [PSI](https://docs.kernel.org/accounting/psi.html).
It could be an indicator of a shortage. It is better to avoid memory pressure
because it puts a VictoriaMetrics component at risk of not getting enough memory, which can lead to OOMs.
### Memory resource accounting
VictoriaMetrics components detect the available memory at startup as the smaller of the host RAM and the cgroup memory limit,
and expose it as `vm_available_memory_bytes` metric. Its main observable parts are:
1. Go (anonymous) memory — `process_resident_memory_anon_bytes`. It includes:
- A portion of total memory for caches, capped by `-memory.allowedPercent` (default 60%) or `-memory.allowedBytes`.
The memory is used for in-process caches, for example, `storage/tsid`, `indexdb` blocks, and the rollup cache.
- The Go heap, goroutine stacks, anonymous off-heap allocations, and runtime overhead used for ingestion and queries.
1. OS page cache for mmap-ed data and index files — the OS uses the memory not taken by the cache budget or the Go heap,
and reclaims it as needed. `process_resident_memory_file_bytes` shows the part of these file-backed pages
currently resident for the process, so it is a lower bound on the useful page cache.
Before tuning and troubleshooting memory issues,
see [Best practices](https://docs.victoriametrics.com/victoriametrics/bestpractices/#memory)
for memory configuration guidance. Be sure that `GOMEMLIMIT` is not set,
and that the [VPA](https://github.com/kubernetes/autoscaler/tree/master/vertical-pod-autoscaler) is not used for `vmstorage` pods.
### Signals
These metrics describe how a component uses memory and are visible on the
[official Grafana dashboards for VictoriaMetrics](https://grafana.com/orgs/victoriametrics/dashboards).
None of them means a shortage on its own — the next section reads them together.
- `process_resident_memory_anon_bytes` / `vm_available_memory_bytes` — anonymous memory (caches plus Go heap)
as a share of the available memory. This memory can't be reclaimed back by OS.
- `process_resident_memory_file_bytes` — the process's resident file-backed memory for mmap-ed data and index files.
It is reclaimable and is a lower bound on the useful page cache.
- `process_pressure_memory_waiting_seconds_total`, `process_pressure_memory_stalled_seconds_total` —
[PSI](https://docs.kernel.org/accounting/psi.html): time tasks were stalled waiting for memory reclaim.
Populated only on Linux hosts with PSI support.
- `vm_cache_size_bytes` and `vm_cache_size_max_bytes` (per `type`, e.g., `storage/tsid`) —
current and maximum size of each in-process cache. Their ratio shows how full a cache is.
- `vm_slow_row_inserts_total` vs `vm_rows_added_to_storage_total` —
share of ingested rows that missed the `storage/tsid` cache ([slow inserts](#slow-data-ingestion)).
- `increase(vm_new_timeseries_created_total[24h])` vs the number of
[active time series](https://docs.victoriametrics.com/victoriametrics/faq/#what-is-an-active-time-series) —
the [churn rate](https://docs.victoriametrics.com/victoriametrics/faq/#what-is-high-churn-rate):
new series registered over a day relative to the active set.
- `process_major_pagefaults_total` — rate of pages read from disk (page-cache misses, refaults, or swap-in).
- `go_memstats_heap_inuse_bytes` and the `CPU spent on GC` panel — the Go heap working set and the CPU cost of garbage collection.
### Shortage patterns
There are three patterns of memory shortage:
1. **The cache cannot hold the active series (cache-bound shortage).** The `storage/tsid` cache is full:
`vm_cache_size_bytes{type="storage/tsid"}` is close to `vm_cache_size_max_bytes{type="storage/tsid"}`, and slow inserts stay high.
Most sustained slow inserts are caused by `storage/tsid` cache misses.
The main sources are new series and misses on already-known active series.
`indexdb`-rotation repopulation can also contribute to `vm_slow_row_inserts_total`,
but it usually appears as a time-bound spike around rotation, not as a sustained high slow-inserts rate.
If slow inserts stay above 5% of ingested rows during a stable window without restarts or rerouting
and cannot be roughly explained by `rate(vm_new_timeseries_created_total)` plus `rate(vm_timeseries_repopulated_total)`,
it points to misses on active series that no longer fit the cache.
See the detailed explanation in the [Slow data ingestion](#slow-data-ingestion) section.
1. **The Go heap exceeds its budget (heap-bound shortage).** `go_memstats_heap_inuse_bytes` climbs well above its stable baseline.
Check it together with the non-cache part of anonymous memory:
`process_resident_memory_anon_bytes` minus the component's `vm_cache_size_bytes`.
There is no fixed normal value, so compare against the component's stable historical data.
A `process_resident_memory_anon_bytes / vm_available_memory_bytes` ratio that keeps rising leaves little headroom
and may lead to an [OOM kill](#out-of-memory-errors).
If heap growth tracks query or ingestion load, it is workload-driven.
If it grows regardless of load, suspect a memory leak and collect a heap profile.
1. **The OS page cache is too small (I/O-bound shortage).** `process_major_pagefaults_total` rises above the component's baseline
and `process_resident_memory_file_bytes` shrinks or stays too small for the working set,
while disk reads and query latency grow.
The file working set (data and index parts) no longer fits within the available memory for the page cache.
Add memory according to
[capacity planning](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#capacity-planning).
PSI is an additional confirmation signal: a rising `process_pressure_memory_*` value indicates that the kernel is reclaiming memory for the cgroup.
Where PSI is unavailable, rely on the per-pattern signals above.
### How to fix
After distinguishing the shortage from normal high memory utilization, and if it is sustained,
you can use the approaches below to resolve it:
- Reduce the number of [active time series](https://docs.victoriametrics.com/victoriametrics/faq/#what-is-an-active-time-series)
or the [churn rate](https://docs.victoriametrics.com/victoriametrics/faq/#what-is-high-churn-rate) —
see [Slow data ingestion](#slow-data-ingestion).
- Increase available memory by scaling vertically or horizontally — see capacity planning for
[single-node VictoriaMetrics](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#capacity-planning)
and the [cluster version](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/#capacity-planning).
Scaling memory is preferred over tuning individual caches, as covered in [Out of memory errors](#out-of-memory-errors).
- Investigate Go heap growth or a suspected memory leak — collect a memory profile using the profiling guide for
[single-node VictoriaMetrics](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#profiling)
or [cluster components](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/#profiling).
## Out of memory errors
The following are the most common sources of out-of-memory (aka OOM) crashes in VictoriaMetrics:

View File

@@ -26,6 +26,9 @@ See also [LTS releases](https://docs.victoriametrics.com/victoriametrics/lts-rel
## tip
* BUGFIX: `vmselect` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/): propagate cache reset operation to `selectNode` when `/internal/resetRollupResultCache` is called. Previously, the propagation only happened when the `delete_series` API was called. See [#11112](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11112).
* BUGFIX: [stream aggregation](https://docs.victoriametrics.com/victoriametrics/stream-aggregation/): fix possible unexpected increases in `rate_avg` and `rate_sum` if an out-of-order sample is ingested after the previous flush. See [#11140](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11140).
## [v1.146.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.146.0)
Released at 2026-06-22
@@ -45,11 +48,11 @@ Released at 2026-06-22
* BUGFIX: [stream aggregation](https://docs.victoriametrics.com/victoriametrics/stream-aggregation/): fix issue with producing aggregated samples with identical timestamps between flushes. See [#10808](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/10808).
* BUGFIX: [vmagent](https://docs.victoriametrics.com/victoriametrics/vmagent/): fix potential corruption of remote-write metadata `Unit` values. See [#11120](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11120). Thanks for @fxrlv for the contribution.
* BUGFIX: [vmalert](https://docs.victoriametrics.com/victoriametrics/vmalert/),[vmauth](https://docs.victoriametrics.com/victoriametrics/vmauth/),[vmagent](https://docs.victoriametrics.com/victoriametrics/vmagent/) and [vmsingle](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/): fix rare unbounded shutdown delay when config reload takes longer than `-configCheckInterval`. See [#11107](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11107). Thanks to @PleasingFungus for contribution.
* BUGFIX: [vmbackup](https://docs.victoriametrics.com/vmbackup/), [vmbackupmanager](https://docs.victoriametrics.com/victoriametrics/vmbackupmanager/): do not fail backup list if directory is absent while using `fs://` destination to align with other protocols. See [6c3c548d](https://github.com/VictoriaMetrics/VictoriaMetrics/commit/6c3c548ddb0385b749e731f52276f130e2a4e4a8).
* BUGFIX: [vmctl](https://docs.victoriametrics.com/victoriametrics/vmctl/): push metrics to configured `-pushmetrics.url` on shutdown when migration fails. Previously, metrics were not pushed if vmctl exited with an error. See [#11081](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11081). Thanks to @zasdaym for contribution.
* BUGFIX: [vmrestore](https://docs.victoriametrics.com/victoriametrics/vmrestore/): disallow restoring parts outside the configured `-storageDataPath` directory. See [710c920d](https://github.com/VictoriaMetrics/VictoriaMetrics/commit/710c920d6083327042a309e449fae4383617d817).
* BUGFIX: `vmselect` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/): correctly apply long tenant filters. Previously, such filters could be truncated, causing tenants to be matched incorrectly. See [#11096](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11096). Thanks for @fxrlv for the contribution.
* BUGFIX: `vmselect` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/): fix corrupted metrics metadata when a response contains multiple rows. See [#11115](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11115). Thanks for @fxrlv for the contribution.
* BUGFIX: [vmbackup](https://docs.victoriametrics.com/vmbackup/), [vmbackupmanager](https://docs.victoriametrics.com/victoriametrics/vmbackupmanager/): do not fail backup list if directory is absent while using `fs://` destination to align with other protocols. See [6c3c548](https://github.com/VictoriaMetrics/VictoriaMetrics/commit/6c3c548ddb0385b749e731f52276f130e2a4e4a8)
* BUGFIX: `vmselect` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/): don't cache empty responses for tenant IDs discovery during [multitenant queries](https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html#multitenant-reads). This problem was visible during integration tests when multitenant queries were executed before the first ingestion happened. See [#10982](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/10982)
* BUGFIX: [vmsingle](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/) and `vmselect` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/): properly escape `metricFamilyName` at metrics metadata response. See [#11129](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11129). Thanks for @fxrlv for the contribution.
* BUGFIX: [vmsingle](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/) and `vmstorage` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/): prevent more cases of panic during directory deletion on `NFS`-based mounts. See [#11060](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11060).

View File

@@ -622,11 +622,13 @@ curl -Is http://localhost:8428/internal/resetRollupResultCache
Cluster version of VictoriaMetrics:
```sh
curl -Is http://<vmselect>:8481/internal/resetRollupResultCache
curl -Is http://<vmselect>:8481/internal/resetRollupResultCache?propagate=1
```
vmselect will propagate this call to the rest of the vmselects listed in its `-selectNode` cmd-line flag. If this
flag isn't set, then cache need to be purged from each vmselect individually.
vmselect will propagate this call to the rest of the vmselects listed in its `-selectNode` cmd-line flag when `propagate=1` argument is set.
If this flag or the `propagate` argument isn't set, then cache need to be purged from each vmselect individually.
If `-search.resetCacheAuthKey` is set, it will be attached to the propagation request as query argument.
### TCP and UDP

2
go.mod
View File

@@ -7,7 +7,7 @@ require (
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.22.0
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.0
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.8.0
github.com/VictoriaMetrics/VictoriaLogs v1.121.1-0.20260616132739-c901a1e31cb3
github.com/VictoriaMetrics/VictoriaLogs v1.51.1-0.20260624061259-dc94972a8708
github.com/VictoriaMetrics/easyproto v1.2.0
github.com/VictoriaMetrics/fastcache v1.13.3
github.com/VictoriaMetrics/metrics v1.44.0

4
go.sum
View File

@@ -52,8 +52,8 @@ github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapp
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.57.0/go.mod h1:YqwkQPrWSC7+byyc1VlKbWLBF5JsW5IoL6xUkemYSXk=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/VictoriaMetrics/VictoriaLogs v1.121.1-0.20260616132739-c901a1e31cb3 h1:3eP8RRZitbga5EYiQ3IANrMPxpBwMAX4VA6akDaXwpU=
github.com/VictoriaMetrics/VictoriaLogs v1.121.1-0.20260616132739-c901a1e31cb3/go.mod h1:H4sDxcvk6OmC6zOt++IlDyrwfbn4F1eSLwMpR+kpRt8=
github.com/VictoriaMetrics/VictoriaLogs v1.51.1-0.20260624061259-dc94972a8708 h1:D9/Jzlm3B8PBnrWxg4ft8KYZdG607dV3lpBfPCoiJD8=
github.com/VictoriaMetrics/VictoriaLogs v1.51.1-0.20260624061259-dc94972a8708/go.mod h1:H4sDxcvk6OmC6zOt++IlDyrwfbn4F1eSLwMpR+kpRt8=
github.com/VictoriaMetrics/easyproto v1.2.0 h1:FJT9uNXA2isppFuJErbLqD306KoFlehl7Wn2dg/6oIE=
github.com/VictoriaMetrics/easyproto v1.2.0/go.mod h1:QlGlzaJnDfFd8Lk6Ci/fuLxfTo3/GThPs2KH23mv710=
github.com/VictoriaMetrics/fastcache v1.13.3 h1:rBabE0iIxcqKEMCwUmwHZ9dgEqXerg8FRbRDUvC7OVc=

View File

@@ -36,10 +36,12 @@ function submitRelabelDebugForm(e) {
<div class="container-fluid">
<a href="https://docs.victoriametrics.com/victoriametrics/relabeling/" target="_blank">Relabeling docs</a>{% space %}
{% if targetURL != "" %}
<a href="metric-relabel-debug{% if targetID != "" %}?id={%s targetID %}{% endif %}">Metric relabel debug</a>
{% else %}
<a href="target-relabel-debug{% if targetID != "" %}?id={%s targetID %}{% endif %}">Target relabel debug</a>
{% if targetID != "" %}
{% if targetURL != "" %}
<a href="metric-relabel-debug?id={%s targetID %}">Metric relabel debug</a>
{% else %}
<a href="target-relabel-debug?id={%s targetID %}">Target relabel debug</a>
{% endif %}
{% endif %}
<br>

View File

@@ -80,425 +80,417 @@ func StreamRelabelDebugStepsHTML(qw422016 *qt422016.Writer, targetURL, targetID
//line lib/promrelabel/debug.qtpl:37
qw422016.N().S(` `)
//line lib/promrelabel/debug.qtpl:39
if targetURL != "" {
//line lib/promrelabel/debug.qtpl:39
qw422016.N().S(`<a href="metric-relabel-debug`)
if targetID != "" {
//line lib/promrelabel/debug.qtpl:40
if targetID != "" {
if targetURL != "" {
//line lib/promrelabel/debug.qtpl:40
qw422016.N().S(`?id=`)
//line lib/promrelabel/debug.qtpl:40
qw422016.E().S(targetID)
//line lib/promrelabel/debug.qtpl:40
}
//line lib/promrelabel/debug.qtpl:40
qw422016.N().S(`">Metric relabel debug</a>`)
qw422016.N().S(`<a href="metric-relabel-debug?id=`)
//line lib/promrelabel/debug.qtpl:41
} else {
//line lib/promrelabel/debug.qtpl:41
qw422016.N().S(`<a href="target-relabel-debug`)
//line lib/promrelabel/debug.qtpl:42
if targetID != "" {
//line lib/promrelabel/debug.qtpl:42
qw422016.N().S(`?id=`)
//line lib/promrelabel/debug.qtpl:42
qw422016.E().S(targetID)
//line lib/promrelabel/debug.qtpl:41
qw422016.N().S(`">Metric relabel debug</a>`)
//line lib/promrelabel/debug.qtpl:42
}
} else {
//line lib/promrelabel/debug.qtpl:42
qw422016.N().S(`">Target relabel debug</a>`)
qw422016.N().S(`<a href="target-relabel-debug?id=`)
//line lib/promrelabel/debug.qtpl:43
qw422016.E().S(targetID)
//line lib/promrelabel/debug.qtpl:43
qw422016.N().S(`">Target relabel debug</a>`)
//line lib/promrelabel/debug.qtpl:44
}
//line lib/promrelabel/debug.qtpl:45
}
//line lib/promrelabel/debug.qtpl:43
//line lib/promrelabel/debug.qtpl:45
qw422016.N().S(`<br>`)
//line lib/promrelabel/debug.qtpl:46
//line lib/promrelabel/debug.qtpl:48
if err != nil {
//line lib/promrelabel/debug.qtpl:47
//line lib/promrelabel/debug.qtpl:49
htmlcomponents.StreamErrorNotification(qw422016, err)
//line lib/promrelabel/debug.qtpl:48
//line lib/promrelabel/debug.qtpl:50
}
//line lib/promrelabel/debug.qtpl:48
//line lib/promrelabel/debug.qtpl:50
qw422016.N().S(`<div class="m-3"><form method="POST" onsubmit="submitRelabelDebugForm(event)">`)
//line lib/promrelabel/debug.qtpl:52
//line lib/promrelabel/debug.qtpl:54
streamrelabelDebugFormInputs(qw422016, metric, relabelConfigs)
//line lib/promrelabel/debug.qtpl:53
//line lib/promrelabel/debug.qtpl:55
if targetID != "" {
//line lib/promrelabel/debug.qtpl:53
//line lib/promrelabel/debug.qtpl:55
qw422016.N().S(`<input type="hidden" name="id" value="`)
//line lib/promrelabel/debug.qtpl:54
//line lib/promrelabel/debug.qtpl:56
qw422016.E().S(targetID)
//line lib/promrelabel/debug.qtpl:54
//line lib/promrelabel/debug.qtpl:56
qw422016.N().S(`" />`)
//line lib/promrelabel/debug.qtpl:55
//line lib/promrelabel/debug.qtpl:57
}
//line lib/promrelabel/debug.qtpl:55
//line lib/promrelabel/debug.qtpl:57
qw422016.N().S(`<input type="submit" value="Submit" class="btn btn-primary m-1" />`)
//line lib/promrelabel/debug.qtpl:57
//line lib/promrelabel/debug.qtpl:59
if targetID != "" {
//line lib/promrelabel/debug.qtpl:57
//line lib/promrelabel/debug.qtpl:59
qw422016.N().S(`<button type="button" onclick="location.href='?id=`)
//line lib/promrelabel/debug.qtpl:58
//line lib/promrelabel/debug.qtpl:60
qw422016.E().S(targetID)
//line lib/promrelabel/debug.qtpl:58
//line lib/promrelabel/debug.qtpl:60
qw422016.N().S(`'" class="btn btn-secondary m-1">Reset</button>`)
//line lib/promrelabel/debug.qtpl:59
//line lib/promrelabel/debug.qtpl:61
}
//line lib/promrelabel/debug.qtpl:59
//line lib/promrelabel/debug.qtpl:61
qw422016.N().S(`</form></div><div class="row"><main class="col-12">`)
//line lib/promrelabel/debug.qtpl:65
//line lib/promrelabel/debug.qtpl:67
streamrelabelDebugSteps(qw422016, dss, targetURL, targetID)
//line lib/promrelabel/debug.qtpl:65
//line lib/promrelabel/debug.qtpl:67
qw422016.N().S(`</main></div></div></body></html>`)
//line lib/promrelabel/debug.qtpl:71
//line lib/promrelabel/debug.qtpl:73
}
//line lib/promrelabel/debug.qtpl:71
//line lib/promrelabel/debug.qtpl:73
func WriteRelabelDebugStepsHTML(qq422016 qtio422016.Writer, targetURL, targetID string, dss []DebugStep, metric, relabelConfigs string, err error) {
//line lib/promrelabel/debug.qtpl:71
//line lib/promrelabel/debug.qtpl:73
qw422016 := qt422016.AcquireWriter(qq422016)
//line lib/promrelabel/debug.qtpl:71
//line lib/promrelabel/debug.qtpl:73
StreamRelabelDebugStepsHTML(qw422016, targetURL, targetID, dss, metric, relabelConfigs, err)
//line lib/promrelabel/debug.qtpl:71
//line lib/promrelabel/debug.qtpl:73
qt422016.ReleaseWriter(qw422016)
//line lib/promrelabel/debug.qtpl:71
//line lib/promrelabel/debug.qtpl:73
}
//line lib/promrelabel/debug.qtpl:71
//line lib/promrelabel/debug.qtpl:73
func RelabelDebugStepsHTML(targetURL, targetID string, dss []DebugStep, metric, relabelConfigs string, err error) string {
//line lib/promrelabel/debug.qtpl:71
//line lib/promrelabel/debug.qtpl:73
qb422016 := qt422016.AcquireByteBuffer()
//line lib/promrelabel/debug.qtpl:71
//line lib/promrelabel/debug.qtpl:73
WriteRelabelDebugStepsHTML(qb422016, targetURL, targetID, dss, metric, relabelConfigs, err)
//line lib/promrelabel/debug.qtpl:71
//line lib/promrelabel/debug.qtpl:73
qs422016 := string(qb422016.B)
//line lib/promrelabel/debug.qtpl:71
//line lib/promrelabel/debug.qtpl:73
qt422016.ReleaseByteBuffer(qb422016)
//line lib/promrelabel/debug.qtpl:71
//line lib/promrelabel/debug.qtpl:73
return qs422016
//line lib/promrelabel/debug.qtpl:71
//line lib/promrelabel/debug.qtpl:73
}
//line lib/promrelabel/debug.qtpl:73
//line lib/promrelabel/debug.qtpl:75
func streamrelabelDebugFormInputs(qw422016 *qt422016.Writer, metric, relabelConfigs string) {
//line lib/promrelabel/debug.qtpl:73
//line lib/promrelabel/debug.qtpl:75
qw422016.N().S(`<div>Relabel configs:<br/><textarea name="relabel_configs" style="width: 100%; height: 15em; font-family: monospace" class="m-1">`)
//line lib/promrelabel/debug.qtpl:76
//line lib/promrelabel/debug.qtpl:78
qw422016.E().S(relabelConfigs)
//line lib/promrelabel/debug.qtpl:76
//line lib/promrelabel/debug.qtpl:78
qw422016.N().S(`</textarea></div><div>Labels:<br/><textarea name="metric" style="width: 100%; height: 5em; font-family: monospace" class="m-1">`)
//line lib/promrelabel/debug.qtpl:81
//line lib/promrelabel/debug.qtpl:83
qw422016.E().S(metric)
//line lib/promrelabel/debug.qtpl:81
//line lib/promrelabel/debug.qtpl:83
qw422016.N().S(`</textarea></div>`)
//line lib/promrelabel/debug.qtpl:83
}
//line lib/promrelabel/debug.qtpl:83
func writerelabelDebugFormInputs(qq422016 qtio422016.Writer, metric, relabelConfigs string) {
//line lib/promrelabel/debug.qtpl:83
qw422016 := qt422016.AcquireWriter(qq422016)
//line lib/promrelabel/debug.qtpl:83
streamrelabelDebugFormInputs(qw422016, metric, relabelConfigs)
//line lib/promrelabel/debug.qtpl:83
qt422016.ReleaseWriter(qw422016)
//line lib/promrelabel/debug.qtpl:83
}
//line lib/promrelabel/debug.qtpl:83
func relabelDebugFormInputs(metric, relabelConfigs string) string {
//line lib/promrelabel/debug.qtpl:83
qb422016 := qt422016.AcquireByteBuffer()
//line lib/promrelabel/debug.qtpl:83
writerelabelDebugFormInputs(qb422016, metric, relabelConfigs)
//line lib/promrelabel/debug.qtpl:83
qs422016 := string(qb422016.B)
//line lib/promrelabel/debug.qtpl:83
qt422016.ReleaseByteBuffer(qb422016)
//line lib/promrelabel/debug.qtpl:83
return qs422016
//line lib/promrelabel/debug.qtpl:83
//line lib/promrelabel/debug.qtpl:85
}
//line lib/promrelabel/debug.qtpl:85
func writerelabelDebugFormInputs(qq422016 qtio422016.Writer, metric, relabelConfigs string) {
//line lib/promrelabel/debug.qtpl:85
qw422016 := qt422016.AcquireWriter(qq422016)
//line lib/promrelabel/debug.qtpl:85
streamrelabelDebugFormInputs(qw422016, metric, relabelConfigs)
//line lib/promrelabel/debug.qtpl:85
qt422016.ReleaseWriter(qw422016)
//line lib/promrelabel/debug.qtpl:85
}
//line lib/promrelabel/debug.qtpl:85
func relabelDebugFormInputs(metric, relabelConfigs string) string {
//line lib/promrelabel/debug.qtpl:85
qb422016 := qt422016.AcquireByteBuffer()
//line lib/promrelabel/debug.qtpl:85
writerelabelDebugFormInputs(qb422016, metric, relabelConfigs)
//line lib/promrelabel/debug.qtpl:85
qs422016 := string(qb422016.B)
//line lib/promrelabel/debug.qtpl:85
qt422016.ReleaseByteBuffer(qb422016)
//line lib/promrelabel/debug.qtpl:85
return qs422016
//line lib/promrelabel/debug.qtpl:85
}
//line lib/promrelabel/debug.qtpl:87
func streamrelabelDebugSteps(qw422016 *qt422016.Writer, dss []DebugStep, targetURL, targetID string) {
//line lib/promrelabel/debug.qtpl:86
//line lib/promrelabel/debug.qtpl:88
if len(dss) > 0 {
//line lib/promrelabel/debug.qtpl:86
//line lib/promrelabel/debug.qtpl:88
qw422016.N().S(`<div class="m-3"><b>Original labels:</b> <samp>`)
//line lib/promrelabel/debug.qtpl:88
//line lib/promrelabel/debug.qtpl:90
streammustFormatLabels(qw422016, dss[0].In)
//line lib/promrelabel/debug.qtpl:88
//line lib/promrelabel/debug.qtpl:90
qw422016.N().S(`</samp></div>`)
//line lib/promrelabel/debug.qtpl:90
//line lib/promrelabel/debug.qtpl:92
}
//line lib/promrelabel/debug.qtpl:90
//line lib/promrelabel/debug.qtpl:92
qw422016.N().S(`<table class="table table-striped table-hover table-bordered table-sm"><thead><tr><th scope="col" style="width: 5%">Step</th><th scope="col" style="width: 25%">Relabeling Rule</th><th scope="col" style="width: 35%">Input Labels</th><th scope="col" stile="width: 35%">Output labels</a></tr></thead><tbody>`)
//line lib/promrelabel/debug.qtpl:101
for i, ds := range dss {
//line lib/promrelabel/debug.qtpl:103
for i, ds := range dss {
//line lib/promrelabel/debug.qtpl:105
inLabels, inErr := promutil.NewLabelsFromString(ds.In)
outLabels, outErr := promutil.NewLabelsFromString(ds.Out)
changedLabels := getChangedLabelNames(inLabels, outLabels)
//line lib/promrelabel/debug.qtpl:106
//line lib/promrelabel/debug.qtpl:108
qw422016.N().S(`<tr><td>`)
//line lib/promrelabel/debug.qtpl:108
//line lib/promrelabel/debug.qtpl:110
qw422016.N().D(i)
//line lib/promrelabel/debug.qtpl:108
//line lib/promrelabel/debug.qtpl:110
qw422016.N().S(`</td><td><b><pre class="m-2">`)
//line lib/promrelabel/debug.qtpl:109
//line lib/promrelabel/debug.qtpl:111
qw422016.E().S(ds.Rule)
//line lib/promrelabel/debug.qtpl:109
//line lib/promrelabel/debug.qtpl:111
qw422016.N().S(`</pre></b></td><td>`)
//line lib/promrelabel/debug.qtpl:111
//line lib/promrelabel/debug.qtpl:113
if inErr == nil {
//line lib/promrelabel/debug.qtpl:111
//line lib/promrelabel/debug.qtpl:113
qw422016.N().S(`<div class="m-2" style="font-size: 0.9em" title="deleted and updated labels highlighted in red">`)
//line lib/promrelabel/debug.qtpl:113
//line lib/promrelabel/debug.qtpl:115
streamlabelsWithHighlight(qw422016, inLabels, changedLabels, "#D15757")
//line lib/promrelabel/debug.qtpl:113
//line lib/promrelabel/debug.qtpl:115
qw422016.N().S(`</div>`)
//line lib/promrelabel/debug.qtpl:115
//line lib/promrelabel/debug.qtpl:117
} else {
//line lib/promrelabel/debug.qtpl:115
//line lib/promrelabel/debug.qtpl:117
qw422016.N().S(`<div class="m-2" style="font-size: 0.9em; color: red" title="error parsing input labels"><pre>`)
//line lib/promrelabel/debug.qtpl:117
qw422016.E().S(inErr.Error())
//line lib/promrelabel/debug.qtpl:117
qw422016.N().S(`</pre></div>`)
//line lib/promrelabel/debug.qtpl:119
break
//line lib/promrelabel/debug.qtpl:120
}
//line lib/promrelabel/debug.qtpl:120
qw422016.N().S(`</td><td>`)
//line lib/promrelabel/debug.qtpl:123
if outErr == nil {
//line lib/promrelabel/debug.qtpl:123
qw422016.N().S(`<div class="m-2" style="font-size: 0.9em" title="added and updated labels highlighted in blue">`)
//line lib/promrelabel/debug.qtpl:125
streamlabelsWithHighlight(qw422016, outLabels, changedLabels, "#4495e0")
//line lib/promrelabel/debug.qtpl:125
qw422016.N().S(`</div>`)
//line lib/promrelabel/debug.qtpl:127
} else {
//line lib/promrelabel/debug.qtpl:127
qw422016.N().S(`<div class="m-2" style="font-size: 0.9em; color: red" title="error parsing output labels"><pre>`)
//line lib/promrelabel/debug.qtpl:129
qw422016.E().S(outErr.Error())
//line lib/promrelabel/debug.qtpl:129
qw422016.E().S(inErr.Error())
//line lib/promrelabel/debug.qtpl:119
qw422016.N().S(`</pre></div>`)
//line lib/promrelabel/debug.qtpl:131
//line lib/promrelabel/debug.qtpl:121
break
//line lib/promrelabel/debug.qtpl:132
//line lib/promrelabel/debug.qtpl:122
}
//line lib/promrelabel/debug.qtpl:132
qw422016.N().S(`</td></tr>`)
//line lib/promrelabel/debug.qtpl:135
}
//line lib/promrelabel/debug.qtpl:135
qw422016.N().S(`</tbody></table>`)
//line lib/promrelabel/debug.qtpl:138
if len(dss) > 0 {
//line lib/promrelabel/debug.qtpl:138
qw422016.N().S(`<div class="m-3"><b>Resulting labels:</b> <samp>`)
//line lib/promrelabel/debug.qtpl:140
streammustFormatLabels(qw422016, dss[len(dss)-1].Out)
//line lib/promrelabel/debug.qtpl:140
qw422016.N().S(`</samp>`)
//line lib/promrelabel/debug.qtpl:141
if targetURL != "" {
//line lib/promrelabel/debug.qtpl:141
qw422016.N().S(`<div><b>Target URL:</b>`)
//line lib/promrelabel/debug.qtpl:143
qw422016.N().S(` `)
//line lib/promrelabel/debug.qtpl:143
qw422016.N().S(`<a href="`)
//line lib/promrelabel/debug.qtpl:143
qw422016.E().S(targetURL)
//line lib/promrelabel/debug.qtpl:143
qw422016.N().S(`" target="_blank">`)
//line lib/promrelabel/debug.qtpl:143
qw422016.E().S(targetURL)
//line lib/promrelabel/debug.qtpl:143
qw422016.N().S(`</a>`)
//line lib/promrelabel/debug.qtpl:144
if targetID != "" {
//line lib/promrelabel/debug.qtpl:145
qw422016.N().S(` `)
//line lib/promrelabel/debug.qtpl:145
qw422016.N().S(`(<a href="target_response?id=`)
//line lib/promrelabel/debug.qtpl:146
qw422016.E().S(targetID)
//line lib/promrelabel/debug.qtpl:146
qw422016.N().S(`" target="_blank" title="click to fetch target response on behalf of the scraper">response</a>)`)
//line lib/promrelabel/debug.qtpl:147
}
//line lib/promrelabel/debug.qtpl:147
//line lib/promrelabel/debug.qtpl:122
qw422016.N().S(`</td><td>`)
//line lib/promrelabel/debug.qtpl:125
if outErr == nil {
//line lib/promrelabel/debug.qtpl:125
qw422016.N().S(`<div class="m-2" style="font-size: 0.9em" title="added and updated labels highlighted in blue">`)
//line lib/promrelabel/debug.qtpl:127
streamlabelsWithHighlight(qw422016, outLabels, changedLabels, "#4495e0")
//line lib/promrelabel/debug.qtpl:127
qw422016.N().S(`</div>`)
//line lib/promrelabel/debug.qtpl:149
//line lib/promrelabel/debug.qtpl:129
} else {
//line lib/promrelabel/debug.qtpl:129
qw422016.N().S(`<div class="m-2" style="font-size: 0.9em; color: red" title="error parsing output labels"><pre>`)
//line lib/promrelabel/debug.qtpl:131
qw422016.E().S(outErr.Error())
//line lib/promrelabel/debug.qtpl:131
qw422016.N().S(`</pre></div>`)
//line lib/promrelabel/debug.qtpl:133
break
//line lib/promrelabel/debug.qtpl:134
}
//line lib/promrelabel/debug.qtpl:149
qw422016.N().S(`</div>`)
//line lib/promrelabel/debug.qtpl:151
//line lib/promrelabel/debug.qtpl:134
qw422016.N().S(`</td></tr>`)
//line lib/promrelabel/debug.qtpl:137
}
//line lib/promrelabel/debug.qtpl:152
//line lib/promrelabel/debug.qtpl:137
qw422016.N().S(`</tbody></table>`)
//line lib/promrelabel/debug.qtpl:140
if len(dss) > 0 {
//line lib/promrelabel/debug.qtpl:140
qw422016.N().S(`<div class="m-3"><b>Resulting labels:</b> <samp>`)
//line lib/promrelabel/debug.qtpl:142
streammustFormatLabels(qw422016, dss[len(dss)-1].Out)
//line lib/promrelabel/debug.qtpl:142
qw422016.N().S(`</samp>`)
//line lib/promrelabel/debug.qtpl:143
if targetURL != "" {
//line lib/promrelabel/debug.qtpl:143
qw422016.N().S(`<div><b>Target URL:</b>`)
//line lib/promrelabel/debug.qtpl:145
qw422016.N().S(` `)
//line lib/promrelabel/debug.qtpl:145
qw422016.N().S(`<a href="`)
//line lib/promrelabel/debug.qtpl:145
qw422016.E().S(targetURL)
//line lib/promrelabel/debug.qtpl:145
qw422016.N().S(`" target="_blank">`)
//line lib/promrelabel/debug.qtpl:145
qw422016.E().S(targetURL)
//line lib/promrelabel/debug.qtpl:145
qw422016.N().S(`</a>`)
//line lib/promrelabel/debug.qtpl:146
if targetID != "" {
//line lib/promrelabel/debug.qtpl:147
qw422016.N().S(` `)
//line lib/promrelabel/debug.qtpl:147
qw422016.N().S(`(<a href="target_response?id=`)
//line lib/promrelabel/debug.qtpl:148
qw422016.E().S(targetID)
//line lib/promrelabel/debug.qtpl:148
qw422016.N().S(`" target="_blank" title="click to fetch target response on behalf of the scraper">response</a>)`)
//line lib/promrelabel/debug.qtpl:149
}
//line lib/promrelabel/debug.qtpl:149
qw422016.N().S(`</div>`)
//line lib/promrelabel/debug.qtpl:151
}
//line lib/promrelabel/debug.qtpl:151
qw422016.N().S(`</div>`)
//line lib/promrelabel/debug.qtpl:153
}
//line lib/promrelabel/debug.qtpl:154
}
//line lib/promrelabel/debug.qtpl:152
//line lib/promrelabel/debug.qtpl:154
func writerelabelDebugSteps(qq422016 qtio422016.Writer, dss []DebugStep, targetURL, targetID string) {
//line lib/promrelabel/debug.qtpl:152
//line lib/promrelabel/debug.qtpl:154
qw422016 := qt422016.AcquireWriter(qq422016)
//line lib/promrelabel/debug.qtpl:152
//line lib/promrelabel/debug.qtpl:154
streamrelabelDebugSteps(qw422016, dss, targetURL, targetID)
//line lib/promrelabel/debug.qtpl:152
//line lib/promrelabel/debug.qtpl:154
qt422016.ReleaseWriter(qw422016)
//line lib/promrelabel/debug.qtpl:152
//line lib/promrelabel/debug.qtpl:154
}
//line lib/promrelabel/debug.qtpl:152
//line lib/promrelabel/debug.qtpl:154
func relabelDebugSteps(dss []DebugStep, targetURL, targetID string) string {
//line lib/promrelabel/debug.qtpl:152
//line lib/promrelabel/debug.qtpl:154
qb422016 := qt422016.AcquireByteBuffer()
//line lib/promrelabel/debug.qtpl:152
//line lib/promrelabel/debug.qtpl:154
writerelabelDebugSteps(qb422016, dss, targetURL, targetID)
//line lib/promrelabel/debug.qtpl:152
//line lib/promrelabel/debug.qtpl:154
qs422016 := string(qb422016.B)
//line lib/promrelabel/debug.qtpl:152
//line lib/promrelabel/debug.qtpl:154
qt422016.ReleaseByteBuffer(qb422016)
//line lib/promrelabel/debug.qtpl:152
//line lib/promrelabel/debug.qtpl:154
return qs422016
//line lib/promrelabel/debug.qtpl:152
//line lib/promrelabel/debug.qtpl:154
}
//line lib/promrelabel/debug.qtpl:154
//line lib/promrelabel/debug.qtpl:156
func StreamRelabelDebugStepsJSON(qw422016 *qt422016.Writer, targetURL, targetID string, dss []DebugStep, metric, relabelConfigs string, err error) {
//line lib/promrelabel/debug.qtpl:154
//line lib/promrelabel/debug.qtpl:156
qw422016.N().S(`{`)
//line lib/promrelabel/debug.qtpl:156
if err != nil {
//line lib/promrelabel/debug.qtpl:156
qw422016.N().S(`"status": "error","error":`)
//line lib/promrelabel/debug.qtpl:158
qw422016.N().Q(fmt.Sprintf("Error: %s", err))
//line lib/promrelabel/debug.qtpl:159
} else {
if err != nil {
//line lib/promrelabel/debug.qtpl:158
qw422016.N().S(`"status": "error","error":`)
//line lib/promrelabel/debug.qtpl:160
qw422016.N().Q(fmt.Sprintf("Error: %s", err))
//line lib/promrelabel/debug.qtpl:161
} else {
//line lib/promrelabel/debug.qtpl:162
var hasError bool
//line lib/promrelabel/debug.qtpl:160
//line lib/promrelabel/debug.qtpl:162
qw422016.N().S(`"status": "success","steps": [`)
//line lib/promrelabel/debug.qtpl:163
for i, ds := range dss {
//line lib/promrelabel/debug.qtpl:165
for i, ds := range dss {
//line lib/promrelabel/debug.qtpl:167
inLabels, inErr := promutil.NewLabelsFromString(ds.In)
outLabels, outErr := promutil.NewLabelsFromString(ds.Out)
changedLabels := getChangedLabelNames(inLabels, outLabels)
//line lib/promrelabel/debug.qtpl:168
//line lib/promrelabel/debug.qtpl:170
qw422016.N().S(`{"inLabels":`)
//line lib/promrelabel/debug.qtpl:170
//line lib/promrelabel/debug.qtpl:172
qw422016.N().Q(labelsWithHighlight(inLabels, changedLabels, "#D15757"))
//line lib/promrelabel/debug.qtpl:170
//line lib/promrelabel/debug.qtpl:172
qw422016.N().S(`,"outLabels":`)
//line lib/promrelabel/debug.qtpl:171
//line lib/promrelabel/debug.qtpl:173
qw422016.N().Q(labelsWithHighlight(outLabels, changedLabels, "#4495e0"))
//line lib/promrelabel/debug.qtpl:171
//line lib/promrelabel/debug.qtpl:173
qw422016.N().S(`,"rule":`)
//line lib/promrelabel/debug.qtpl:172
//line lib/promrelabel/debug.qtpl:174
qw422016.N().Q(ds.Rule)
//line lib/promrelabel/debug.qtpl:172
//line lib/promrelabel/debug.qtpl:174
qw422016.N().S(`,"errors": {`)
//line lib/promrelabel/debug.qtpl:174
if inErr != nil {
//line lib/promrelabel/debug.qtpl:174
qw422016.N().S(`"inLabels":`)
//line lib/promrelabel/debug.qtpl:175
qw422016.N().Q(`<span style="color: #D15757">` + inErr.Error() + `</span>`)
//line lib/promrelabel/debug.qtpl:175
if outErr != nil {
//line lib/promrelabel/debug.qtpl:175
qw422016.N().S(`,`)
//line lib/promrelabel/debug.qtpl:175
}
//line lib/promrelabel/debug.qtpl:176
hasError = true
if inErr != nil {
//line lib/promrelabel/debug.qtpl:176
qw422016.N().S(`"inLabels":`)
//line lib/promrelabel/debug.qtpl:177
} else {
qw422016.N().Q(`<span style="color: #D15757">` + inErr.Error() + `</span>`)
//line lib/promrelabel/debug.qtpl:177
if outErr != nil {
//line lib/promrelabel/debug.qtpl:177
qw422016.N().S(`,`)
//line lib/promrelabel/debug.qtpl:177
}
//line lib/promrelabel/debug.qtpl:178
}
//line lib/promrelabel/debug.qtpl:179
if outErr != nil {
//line lib/promrelabel/debug.qtpl:179
qw422016.N().S(`"outLabels":`)
//line lib/promrelabel/debug.qtpl:180
qw422016.N().Q(`<span style="color: #D15757">` + outErr.Error() + `</span>`)
//line lib/promrelabel/debug.qtpl:181
hasError = true
//line lib/promrelabel/debug.qtpl:182
//line lib/promrelabel/debug.qtpl:179
} else {
//line lib/promrelabel/debug.qtpl:180
}
//line lib/promrelabel/debug.qtpl:181
if outErr != nil {
//line lib/promrelabel/debug.qtpl:181
qw422016.N().S(`"outLabels":`)
//line lib/promrelabel/debug.qtpl:182
qw422016.N().Q(`<span style="color: #D15757">` + outErr.Error() + `</span>`)
//line lib/promrelabel/debug.qtpl:183
hasError = true
//line lib/promrelabel/debug.qtpl:184
}
//line lib/promrelabel/debug.qtpl:184
qw422016.N().S(`}}`)
//line lib/promrelabel/debug.qtpl:185
//line lib/promrelabel/debug.qtpl:187
if i != len(dss)-1 {
//line lib/promrelabel/debug.qtpl:185
//line lib/promrelabel/debug.qtpl:187
qw422016.N().S(`,`)
//line lib/promrelabel/debug.qtpl:185
//line lib/promrelabel/debug.qtpl:187
}
//line lib/promrelabel/debug.qtpl:186
//line lib/promrelabel/debug.qtpl:188
}
//line lib/promrelabel/debug.qtpl:186
//line lib/promrelabel/debug.qtpl:188
qw422016.N().S(`]`)
//line lib/promrelabel/debug.qtpl:188
//line lib/promrelabel/debug.qtpl:190
if len(dss) > 0 && !hasError {
//line lib/promrelabel/debug.qtpl:188
//line lib/promrelabel/debug.qtpl:190
qw422016.N().S(`,"originalLabels":`)
//line lib/promrelabel/debug.qtpl:190
qw422016.N().Q(mustFormatLabels(dss[0].In))
//line lib/promrelabel/debug.qtpl:190
qw422016.N().S(`,"resultingLabels":`)
//line lib/promrelabel/debug.qtpl:191
qw422016.N().Q(mustFormatLabels(dss[len(dss)-1].Out))
//line lib/promrelabel/debug.qtpl:192
qw422016.N().Q(mustFormatLabels(dss[0].In))
//line lib/promrelabel/debug.qtpl:192
qw422016.N().S(`,"resultingLabels":`)
//line lib/promrelabel/debug.qtpl:193
qw422016.N().Q(mustFormatLabels(dss[len(dss)-1].Out))
//line lib/promrelabel/debug.qtpl:194
}
//line lib/promrelabel/debug.qtpl:193
//line lib/promrelabel/debug.qtpl:195
}
//line lib/promrelabel/debug.qtpl:193
//line lib/promrelabel/debug.qtpl:195
qw422016.N().S(`}`)
//line lib/promrelabel/debug.qtpl:195
}
//line lib/promrelabel/debug.qtpl:195
func WriteRelabelDebugStepsJSON(qq422016 qtio422016.Writer, targetURL, targetID string, dss []DebugStep, metric, relabelConfigs string, err error) {
//line lib/promrelabel/debug.qtpl:195
qw422016 := qt422016.AcquireWriter(qq422016)
//line lib/promrelabel/debug.qtpl:195
StreamRelabelDebugStepsJSON(qw422016, targetURL, targetID, dss, metric, relabelConfigs, err)
//line lib/promrelabel/debug.qtpl:195
qt422016.ReleaseWriter(qw422016)
//line lib/promrelabel/debug.qtpl:195
}
//line lib/promrelabel/debug.qtpl:195
func RelabelDebugStepsJSON(targetURL, targetID string, dss []DebugStep, metric, relabelConfigs string, err error) string {
//line lib/promrelabel/debug.qtpl:195
qb422016 := qt422016.AcquireByteBuffer()
//line lib/promrelabel/debug.qtpl:195
WriteRelabelDebugStepsJSON(qb422016, targetURL, targetID, dss, metric, relabelConfigs, err)
//line lib/promrelabel/debug.qtpl:195
qs422016 := string(qb422016.B)
//line lib/promrelabel/debug.qtpl:195
qt422016.ReleaseByteBuffer(qb422016)
//line lib/promrelabel/debug.qtpl:195
return qs422016
//line lib/promrelabel/debug.qtpl:195
//line lib/promrelabel/debug.qtpl:197
}
//line lib/promrelabel/debug.qtpl:197
func streamlabelsWithHighlight(qw422016 *qt422016.Writer, labels *promutil.Labels, highlight map[string]struct{}, color string) {
func WriteRelabelDebugStepsJSON(qq422016 qtio422016.Writer, targetURL, targetID string, dss []DebugStep, metric, relabelConfigs string, err error) {
//line lib/promrelabel/debug.qtpl:197
qw422016 := qt422016.AcquireWriter(qq422016)
//line lib/promrelabel/debug.qtpl:197
StreamRelabelDebugStepsJSON(qw422016, targetURL, targetID, dss, metric, relabelConfigs, err)
//line lib/promrelabel/debug.qtpl:197
qt422016.ReleaseWriter(qw422016)
//line lib/promrelabel/debug.qtpl:197
}
//line lib/promrelabel/debug.qtpl:197
func RelabelDebugStepsJSON(targetURL, targetID string, dss []DebugStep, metric, relabelConfigs string, err error) string {
//line lib/promrelabel/debug.qtpl:197
qb422016 := qt422016.AcquireByteBuffer()
//line lib/promrelabel/debug.qtpl:197
WriteRelabelDebugStepsJSON(qb422016, targetURL, targetID, dss, metric, relabelConfigs, err)
//line lib/promrelabel/debug.qtpl:197
qs422016 := string(qb422016.B)
//line lib/promrelabel/debug.qtpl:197
qt422016.ReleaseByteBuffer(qb422016)
//line lib/promrelabel/debug.qtpl:197
return qs422016
//line lib/promrelabel/debug.qtpl:197
}
//line lib/promrelabel/debug.qtpl:199
func streamlabelsWithHighlight(qw422016 *qt422016.Writer, labels *promutil.Labels, highlight map[string]struct{}, color string) {
//line lib/promrelabel/debug.qtpl:201
labelsList := labels.GetLabels()
metricName := ""
for i, label := range labelsList {
@@ -509,153 +501,153 @@ func streamlabelsWithHighlight(qw422016 *qt422016.Writer, labels *promutil.Label
}
}
//line lib/promrelabel/debug.qtpl:209
//line lib/promrelabel/debug.qtpl:211
if metricName != "" {
//line lib/promrelabel/debug.qtpl:210
if _, ok := highlight["__name__"]; ok {
//line lib/promrelabel/debug.qtpl:210
qw422016.N().S(`<span style="font-weight:bold;color:`)
//line lib/promrelabel/debug.qtpl:211
qw422016.E().S(color)
//line lib/promrelabel/debug.qtpl:211
qw422016.N().S(`">`)
//line lib/promrelabel/debug.qtpl:211
qw422016.E().S(metricName)
//line lib/promrelabel/debug.qtpl:211
qw422016.N().S(`</span>`)
//line lib/promrelabel/debug.qtpl:212
} else {
if _, ok := highlight["__name__"]; ok {
//line lib/promrelabel/debug.qtpl:212
qw422016.N().S(`<span style="font-weight:bold;color:`)
//line lib/promrelabel/debug.qtpl:213
qw422016.E().S(color)
//line lib/promrelabel/debug.qtpl:213
qw422016.N().S(`">`)
//line lib/promrelabel/debug.qtpl:213
qw422016.E().S(metricName)
//line lib/promrelabel/debug.qtpl:214
}
//line lib/promrelabel/debug.qtpl:215
if len(labelsList) == 0 {
//line lib/promrelabel/debug.qtpl:215
return
//line lib/promrelabel/debug.qtpl:215
}
//line lib/promrelabel/debug.qtpl:216
}
//line lib/promrelabel/debug.qtpl:216
qw422016.N().S(`{`)
//line lib/promrelabel/debug.qtpl:218
for i, label := range labelsList {
//line lib/promrelabel/debug.qtpl:219
if _, ok := highlight[label.Name]; ok {
//line lib/promrelabel/debug.qtpl:219
qw422016.N().S(`<span style="font-weight:bold;color:`)
//line lib/promrelabel/debug.qtpl:220
qw422016.E().S(color)
//line lib/promrelabel/debug.qtpl:220
qw422016.N().S(`">`)
//line lib/promrelabel/debug.qtpl:220
qw422016.E().S(label.Name)
//line lib/promrelabel/debug.qtpl:220
qw422016.N().S(`=`)
//line lib/promrelabel/debug.qtpl:220
qw422016.E().Q(label.Value)
//line lib/promrelabel/debug.qtpl:220
//line lib/promrelabel/debug.qtpl:213
qw422016.N().S(`</span>`)
//line lib/promrelabel/debug.qtpl:221
//line lib/promrelabel/debug.qtpl:214
} else {
//line lib/promrelabel/debug.qtpl:215
qw422016.E().S(metricName)
//line lib/promrelabel/debug.qtpl:216
}
//line lib/promrelabel/debug.qtpl:217
if len(labelsList) == 0 {
//line lib/promrelabel/debug.qtpl:217
return
//line lib/promrelabel/debug.qtpl:217
}
//line lib/promrelabel/debug.qtpl:218
}
//line lib/promrelabel/debug.qtpl:218
qw422016.N().S(`{`)
//line lib/promrelabel/debug.qtpl:220
for i, label := range labelsList {
//line lib/promrelabel/debug.qtpl:221
if _, ok := highlight[label.Name]; ok {
//line lib/promrelabel/debug.qtpl:221
qw422016.N().S(`<span style="font-weight:bold;color:`)
//line lib/promrelabel/debug.qtpl:222
qw422016.E().S(color)
//line lib/promrelabel/debug.qtpl:222
qw422016.N().S(`">`)
//line lib/promrelabel/debug.qtpl:222
qw422016.E().S(label.Name)
//line lib/promrelabel/debug.qtpl:222
qw422016.N().S(`=`)
//line lib/promrelabel/debug.qtpl:222
qw422016.E().Q(label.Value)
//line lib/promrelabel/debug.qtpl:222
qw422016.N().S(`</span>`)
//line lib/promrelabel/debug.qtpl:223
}
} else {
//line lib/promrelabel/debug.qtpl:224
qw422016.E().S(label.Name)
//line lib/promrelabel/debug.qtpl:224
qw422016.N().S(`=`)
//line lib/promrelabel/debug.qtpl:224
qw422016.E().Q(label.Value)
//line lib/promrelabel/debug.qtpl:225
}
//line lib/promrelabel/debug.qtpl:226
if i < len(labelsList)-1 {
//line lib/promrelabel/debug.qtpl:224
//line lib/promrelabel/debug.qtpl:226
qw422016.N().S(`,`)
//line lib/promrelabel/debug.qtpl:224
//line lib/promrelabel/debug.qtpl:226
qw422016.N().S(` `)
//line lib/promrelabel/debug.qtpl:224
//line lib/promrelabel/debug.qtpl:226
}
//line lib/promrelabel/debug.qtpl:225
//line lib/promrelabel/debug.qtpl:227
}
//line lib/promrelabel/debug.qtpl:225
//line lib/promrelabel/debug.qtpl:227
qw422016.N().S(`}`)
//line lib/promrelabel/debug.qtpl:227
}
//line lib/promrelabel/debug.qtpl:227
func writelabelsWithHighlight(qq422016 qtio422016.Writer, labels *promutil.Labels, highlight map[string]struct{}, color string) {
//line lib/promrelabel/debug.qtpl:227
qw422016 := qt422016.AcquireWriter(qq422016)
//line lib/promrelabel/debug.qtpl:227
streamlabelsWithHighlight(qw422016, labels, highlight, color)
//line lib/promrelabel/debug.qtpl:227
qt422016.ReleaseWriter(qw422016)
//line lib/promrelabel/debug.qtpl:227
}
//line lib/promrelabel/debug.qtpl:227
func labelsWithHighlight(labels *promutil.Labels, highlight map[string]struct{}, color string) string {
//line lib/promrelabel/debug.qtpl:227
qb422016 := qt422016.AcquireByteBuffer()
//line lib/promrelabel/debug.qtpl:227
writelabelsWithHighlight(qb422016, labels, highlight, color)
//line lib/promrelabel/debug.qtpl:227
qs422016 := string(qb422016.B)
//line lib/promrelabel/debug.qtpl:227
qt422016.ReleaseByteBuffer(qb422016)
//line lib/promrelabel/debug.qtpl:227
return qs422016
//line lib/promrelabel/debug.qtpl:227
//line lib/promrelabel/debug.qtpl:229
}
//line lib/promrelabel/debug.qtpl:229
func writelabelsWithHighlight(qq422016 qtio422016.Writer, labels *promutil.Labels, highlight map[string]struct{}, color string) {
//line lib/promrelabel/debug.qtpl:229
qw422016 := qt422016.AcquireWriter(qq422016)
//line lib/promrelabel/debug.qtpl:229
streamlabelsWithHighlight(qw422016, labels, highlight, color)
//line lib/promrelabel/debug.qtpl:229
qt422016.ReleaseWriter(qw422016)
//line lib/promrelabel/debug.qtpl:229
}
//line lib/promrelabel/debug.qtpl:229
func labelsWithHighlight(labels *promutil.Labels, highlight map[string]struct{}, color string) string {
//line lib/promrelabel/debug.qtpl:229
qb422016 := qt422016.AcquireByteBuffer()
//line lib/promrelabel/debug.qtpl:229
writelabelsWithHighlight(qb422016, labels, highlight, color)
//line lib/promrelabel/debug.qtpl:229
qs422016 := string(qb422016.B)
//line lib/promrelabel/debug.qtpl:229
qt422016.ReleaseByteBuffer(qb422016)
//line lib/promrelabel/debug.qtpl:229
return qs422016
//line lib/promrelabel/debug.qtpl:229
}
//line lib/promrelabel/debug.qtpl:231
func streammustFormatLabels(qw422016 *qt422016.Writer, s string) {
//line lib/promrelabel/debug.qtpl:230
//line lib/promrelabel/debug.qtpl:232
labels, err := promutil.NewLabelsFromString(s)
//line lib/promrelabel/debug.qtpl:231
if err != nil {
//line lib/promrelabel/debug.qtpl:231
qw422016.N().S(`<span style="color: red" title="error parsing labels:`)
//line lib/promrelabel/debug.qtpl:232
qw422016.E().S(err.Error())
//line lib/promrelabel/debug.qtpl:232
qw422016.N().S(`">`)
//line lib/promrelabel/debug.qtpl:232
qw422016.E().S("error parsing labels: " + err.Error())
//line lib/promrelabel/debug.qtpl:232
qw422016.N().S(`</span>`)
//line lib/promrelabel/debug.qtpl:233
} else {
if err != nil {
//line lib/promrelabel/debug.qtpl:233
qw422016.N().S(`<span style="color: red" title="error parsing labels:`)
//line lib/promrelabel/debug.qtpl:234
streamlabelsWithHighlight(qw422016, labels, nil, "")
qw422016.E().S(err.Error())
//line lib/promrelabel/debug.qtpl:234
qw422016.N().S(`">`)
//line lib/promrelabel/debug.qtpl:234
qw422016.E().S("error parsing labels: " + err.Error())
//line lib/promrelabel/debug.qtpl:234
qw422016.N().S(`</span>`)
//line lib/promrelabel/debug.qtpl:235
} else {
//line lib/promrelabel/debug.qtpl:236
streamlabelsWithHighlight(qw422016, labels, nil, "")
//line lib/promrelabel/debug.qtpl:237
}
//line lib/promrelabel/debug.qtpl:236
//line lib/promrelabel/debug.qtpl:238
}
//line lib/promrelabel/debug.qtpl:236
//line lib/promrelabel/debug.qtpl:238
func writemustFormatLabels(qq422016 qtio422016.Writer, s string) {
//line lib/promrelabel/debug.qtpl:236
//line lib/promrelabel/debug.qtpl:238
qw422016 := qt422016.AcquireWriter(qq422016)
//line lib/promrelabel/debug.qtpl:236
//line lib/promrelabel/debug.qtpl:238
streammustFormatLabels(qw422016, s)
//line lib/promrelabel/debug.qtpl:236
//line lib/promrelabel/debug.qtpl:238
qt422016.ReleaseWriter(qw422016)
//line lib/promrelabel/debug.qtpl:236
//line lib/promrelabel/debug.qtpl:238
}
//line lib/promrelabel/debug.qtpl:236
//line lib/promrelabel/debug.qtpl:238
func mustFormatLabels(s string) string {
//line lib/promrelabel/debug.qtpl:236
//line lib/promrelabel/debug.qtpl:238
qb422016 := qt422016.AcquireByteBuffer()
//line lib/promrelabel/debug.qtpl:236
//line lib/promrelabel/debug.qtpl:238
writemustFormatLabels(qb422016, s)
//line lib/promrelabel/debug.qtpl:236
//line lib/promrelabel/debug.qtpl:238
qs422016 := string(qb422016.B)
//line lib/promrelabel/debug.qtpl:236
//line lib/promrelabel/debug.qtpl:238
qt422016.ReleaseByteBuffer(qb422016)
//line lib/promrelabel/debug.qtpl:236
//line lib/promrelabel/debug.qtpl:238
return qs422016
//line lib/promrelabel/debug.qtpl:236
//line lib/promrelabel/debug.qtpl:238
}

View File

@@ -108,7 +108,7 @@ func (av *rateAggrValue) pushSample(c aggrConfig, sample *pushSample, key string
}
if ok {
state = sv.getState(av.isGreen)
if sample.timestamp < state.timestamp {
if sample.timestamp < state.timestamp || sample.timestamp < sv.prevTimestamp {
// Skip out of order sample
return
}
@@ -143,9 +143,6 @@ func (av *rateAggrValue) flush(c aggrConfig, ctx *flushCtx, key string, isLast b
putRateAggrSharedValue(sv)
continue
}
if sv.prevTimestamp == 0 {
continue
}
state = sv.getState(av.isGreen)
if state.timestamp > 0 {
d := float64(state.timestamp-sv.prevTimestamp) / 1000

View File

@@ -789,6 +789,24 @@ foo:1m_by_cde_rate_sum{cde="1"} 0.125
outputs: [rate_sum, rate_avg]
`, "11111")
// test rate_sum with out of order samples
f([]string{`
foo 1
`, `
foo 61
`, `
foo 31 -70
foo 91
`, `
foo 121
`}, time.Minute, `foo:1m_rate_sum 1
foo:1m_rate_sum 0.5
foo:1m_rate_sum 0.5
`, `
- interval: 1m
outputs: [rate_sum]
`, "11111")
// test rate_sum and rate_avg with different staleness intervals
f([]string{`
foo{abc="123", cde="1"} 1

View File

@@ -30,7 +30,7 @@ func parseIfFilter(lex *lexer) (*ifFilter, error) {
return newIfFilter(newFilterNoop()), nil
}
f, err := parseFilter(lex, true)
f, err := parseFilter(lex)
if err != nil {
return nil, fmt.Errorf("cannot parse 'if' filter: %w", err)
}

View File

@@ -301,38 +301,43 @@ func (lr *LogRows) NeedFlush() bool {
// MustAddInsertRow adds r to lr.
func (lr *LogRows) MustAddInsertRow(r *InsertRow) {
// verify r.StreamTagsCanonical
if err := verifyStreamTagsCanonical(r.StreamTagsCanonical, r.Fields); err != nil {
st := GetStreamTags()
streamTagsCanonical := r.StreamTagsCanonical
if err := parseStreamTagsCanonical(st, streamTagsCanonical); err != nil {
line := MarshalFieldsToJSON(nil, r.Fields)
invalidStreamTagsLogger.Warnf("cannot unmarshal streamTagsCanonical: %s; skipping the log entry; log entry: %s", err, line)
PutStreamTags(st)
return
}
if st.normalize(r.Fields) {
bLen := len(lr.a.b)
lr.a.b = st.MarshalCanonical(lr.a.b)
streamTagsCanonical = bytesutil.ToUnsafeString(lr.a.b[bLen:])
}
PutStreamTags(st)
// Calculate the id for the StreamTags
var sid streamID
sid.tenantID = r.TenantID
streamTagsCanonical := bytesutil.ToUnsafeBytes(r.StreamTagsCanonical)
sid.id = hash128(streamTagsCanonical)
sid.id = hash128(bytesutil.ToUnsafeBytes(streamTagsCanonical))
// Store the row
lr.mustAddInternal(sid, r.Timestamp, r.Fields, r.StreamTagsCanonical)
lr.mustAddInternal(sid, r.Timestamp, r.Fields, streamTagsCanonical)
}
var invalidStreamTagsLogger = logger.WithThrottler("invalid_stream_tags", 5*time.Second)
func verifyStreamTagsCanonical(streamTagsCanonical string, fields []Field) error {
st := GetStreamTags()
defer PutStreamTags(st)
func parseStreamTagsCanonical(dst *StreamTags, streamTagsCanonical string) error {
src := bytesutil.ToUnsafeBytes(streamTagsCanonical)
tail, err := st.UnmarshalCanonicalInplace(src)
tail, err := dst.UnmarshalCanonicalInplace(src)
if err != nil {
return fmt.Errorf("cannot unmarshal streamTagsCanonical: %w", err)
}
if len(tail) > 0 {
return fmt.Errorf("unexpected tail left after unmarshaling streamTagsCanonical; len(tail)=%d; streamTags: %s", len(tail), st)
return fmt.Errorf("unexpected tail left after unmarshaling streamTagsCanonical; len(tail)=%d; streamTags: %s", len(tail), dst)
}
return st.verifyCanonicalFieldValues(fields)
return nil
}
func (lr *LogRows) mustAdd(tenantID TenantID, timestamp int64, fields []Field) {
@@ -394,11 +399,7 @@ func (lr *LogRows) MustAdd(tenantID TenantID, timestamp int64, fields []Field, s
invalidStreamTagsLogger.Warnf("cannot parse _stream=%s: %s; skipping the log entry; log entry: %s", f.Value, err, line)
return
}
if err := st.verifyCanonicalFieldValues(fields); err != nil {
line := MarshalFieldsToJSON(nil, fields)
invalidStreamTagsLogger.Warnf("invalid _stream=%s: %s; skipping the log entry; log entry: %s", f.Value, err, line)
return
}
st.normalize(fields)
// Remove _stream field, since it is re-generated from st below.
f.Value = ""
case "_stream_id":

View File

@@ -1928,7 +1928,7 @@ func parseQuery(lex *lexer) (*Query, error) {
lex.pushQueryOptions(&q.opts)
defer lex.popQueryOptions()
f, err := parseFilter(lex, true)
f, err := parseFilter(lex)
if err != nil {
return nil, fmt.Errorf("%w; context: [%s]", err, lex.context())
}
@@ -2113,20 +2113,11 @@ func parseQueryOptions(dstOpts *queryOptions, lex *lexer) error {
}
}
func parseFilter(lex *lexer, allowPipeKeywords bool) (filter, error) {
func parseFilter(lex *lexer) (filter, error) {
if lex.isQueryPartTrailer() {
return nil, fmt.Errorf("missing query")
}
if !allowPipeKeywords {
// Verify the first token in the filter doesn't match pipe names.
firstToken := strings.ToLower(lex.rawToken)
if firstToken == "by" || isPipeName(firstToken) || isStatsFuncName(firstToken) {
return nil, fmt.Errorf("query filter cannot start with pipe keyword %q; see https://docs.victoriametrics.com/victorialogs/logsql/#query-syntax; "+
"please put the first word of the filter into quotes", firstToken)
}
}
fo, err := parseFilterOr(lex, "")
if err != nil {
return nil, err
@@ -3875,7 +3866,7 @@ func quoteFieldFilterIfNeeded(s string) string {
if wildcard == "" || !needQuoteToken(wildcard) {
return s
}
return strconv.Quote(s)
return strconv.Quote(wildcard) + "*"
}
func quoteTokenIfNeeded(s string) string {
@@ -3912,7 +3903,7 @@ func isNumberPrefix(s string) bool {
}
func needQuoteToken(s string) bool {
if s == "." {
if !isWord(s) {
return true
}
@@ -3920,14 +3911,10 @@ func needQuoteToken(s string) bool {
if _, ok := reservedKeywords[sLower]; ok {
return true
}
if isPipeName(sLower) || isStatsFuncName(sLower) {
if isPipeName(sLower) || isStatsFuncName(sLower) || isMathFuncName(sLower) {
return true
}
for _, r := range s {
if !isTokenRune(r) && r != '.' {
return true
}
}
return false
}
@@ -4008,6 +3995,9 @@ var reservedKeywords = func() map[string]struct{} {
// 'as' is used in various pipes such as `format ... as ...`
"as",
// 'from' is used in various pipes such as `split ... from ...` and `unpack_json from ...`
"from",
}
m := make(map[string]struct{}, len(kws))
for _, kw := range kws {
@@ -4057,12 +4047,12 @@ func toFieldsFilters(pf *prefixfilter.Filter) string {
denyFilters := pf.GetDenyFilters()
if len(denyFilters) > 0 {
qStr += " | delete " + fieldNamesString(denyFilters)
qStr += " | delete " + fieldFiltersString(denyFilters)
}
allowFilters := pf.GetAllowFilters()
if len(allowFilters) > 0 && !prefixfilter.MatchAll(allowFilters) {
qStr += " | fields " + fieldNamesString(allowFilters)
qStr += " | fields " + fieldFiltersString(allowFilters)
}
return qStr

View File

@@ -0,0 +1,123 @@
package logstorage
import (
"fmt"
"strings"
"github.com/VictoriaMetrics/VictoriaLogs/lib/prefixfilter"
)
func parseFieldNamesInParens(lex *lexer) ([]string, error) {
fieldNames, err := parseFieldFiltersInParens(lex)
if err != nil {
return nil, err
}
for _, fieldName := range fieldNames {
if prefixfilter.IsWildcardFilter(fieldName) {
return nil, fmt.Errorf("the field name %q cannot end with '*'", fieldName)
}
}
return fieldNames, nil
}
func parseFieldFiltersInParens(lex *lexer) ([]string, error) {
if !lex.isKeyword("(") {
return nil, fmt.Errorf("missing `(`")
}
var fields []string
for {
lex.nextToken()
if lex.isKeyword(")") {
lex.nextToken()
return fields, nil
}
if lex.isKeyword(",") {
return nil, fmt.Errorf("unexpected `,`")
}
field, err := parseFieldFilter(lex)
if err != nil {
return nil, err
}
fields = append(fields, field)
switch {
case lex.isKeyword(")"):
lex.nextToken()
return fields, nil
case lex.isKeyword(","):
default:
return nil, fmt.Errorf("unexpected token: %q; expecting ',' or ')'", lex.token)
}
}
}
func parseCommaSeparatedFieldNames(lex *lexer) ([]string, error) {
fieldNames, err := parseCommaSeparatedFieldFilters(lex)
if err != nil {
return nil, err
}
for _, fieldName := range fieldNames {
if prefixfilter.IsWildcardFilter(fieldName) {
return nil, fmt.Errorf("the field name %q cannot end with '*'", fieldName)
}
}
return fieldNames, nil
}
func parseCommaSeparatedFieldFilters(lex *lexer) ([]string, error) {
var fields []string
for {
field, err := parseFieldFilter(lex)
if err != nil {
return nil, fmt.Errorf("cannot parse field name: %w", err)
}
fields = append(fields, field)
if !lex.isKeyword(",") {
return fields, nil
}
lex.nextToken()
}
}
func parseFieldName(lex *lexer) (string, error) {
fieldName, err := lex.nextCompoundToken()
if err != nil {
return "", err
}
fieldName = getCanonicalColumnName(fieldName)
return fieldName, nil
}
func parseFieldFilter(lex *lexer) (string, error) {
if lex.isKeyword("*") {
lex.nextToken()
return "*", nil
}
fieldName, err := lex.nextCompoundToken()
if err != nil {
return "", err
}
fieldName = getCanonicalColumnName(fieldName)
if !lex.isSkippedSpace && lex.isKeyword("*") {
lex.nextToken()
fieldName += "*"
}
return fieldName, nil
}
func fieldNamesString(fieldNames []string) string {
a := make([]string, len(fieldNames))
for i, f := range fieldNames {
a[i] = quoteTokenIfNeeded(f)
}
return strings.Join(a, ", ")
}
func fieldFiltersString(fieldFilters []string) string {
a := make([]string, len(fieldFilters))
for i, f := range fieldFilters {
a[i] = quoteFieldFilterIfNeeded(f)
}
return strings.Join(a, ", ")
}

View File

@@ -146,23 +146,54 @@ func parsePipe(lex *lexer) (pipe, error) {
return p, nil
}
lexState := lex.backupState()
// Try parsing stats pipe without 'stats' keyword
ps, err := parsePipeStatsNoStatsKeyword(lex)
if err == nil {
if isLikelyStatsPipe(lex) {
// Try parsing stats pipe without 'stats' keyword
ps, err := parsePipeStatsNoStatsKeyword(lex)
if err != nil {
return nil, fmt.Errorf("cannot parse 'stats' pipe: %w", err)
}
return ps, nil
}
lex.restoreState(lexState)
// Try parsing filter pipe without 'filter' keyword
pf, err := parsePipeFilterNoFilterKeyword(lex)
if err == nil {
if isLikelyFilterPipe(lex) {
// Try parsing filter pipe without 'filter' keyword
pf, err := parsePipeFilterNoFilterKeyword(lex)
if err != nil {
return nil, fmt.Errorf("cannot parse 'filter' pipe: %w", err)
}
return pf, nil
}
lex.restoreState(lexState)
return nil, fmt.Errorf("unexpected pipe %q", lex.token)
return nil, fmt.Errorf("unexpected pipe name %q; probably, 'filter' is missing in front of %q; "+
"see https://docs.victoriametrics.com/victorialogs/logsql/#filter-pipe", lex.token, lex.token)
}
func isLikelyStatsPipe(lex *lexer) bool {
return isStatsFuncName(lex.rawToken) || lex.isKeyword("by", "(")
}
func isLikelyFilterPipe(lex *lexer) bool {
if lex.isQuotedToken() {
return true
}
if !isWord(lex.token) {
// Any token that isn't a word cannot clash with a pipe name,
// since all pipe names are words. So treat it as a filter.
return true
}
if lex.isKeyword("not") {
// 'not' is a logical filter operator rather than a pipe name.
return true
}
lexState := lex.backupState()
defer lex.restoreState(lexState)
stopTokens := []string{":"}
if _, err := lex.nextCompoundTokenExt(stopTokens); err != nil {
return false
}
return lex.isKeyword(":")
}
var pipeParsers map[string]pipeParseFunc
@@ -201,6 +232,7 @@ func initPipeParsers() {
"generate_sequence": parsePipeGenerateSequence,
"hash": parsePipeHash,
"join": parsePipeJoin,
"json_array_concat": parsePipeJSONArrayConcat,
"json_array_len": parsePipeJSONArrayLen,
"head": parsePipeLimit,
"keep": parsePipeFields,

View File

@@ -23,7 +23,7 @@ func (pc *pipeCoalesce) String() string {
logger.Panicf("BUG: pipeCoalesce must contain at least one srcField")
}
s := "coalesce(" + fieldNamesString(pc.srcFieldFilters) + ")"
s := "coalesce(" + fieldFiltersString(pc.srcFieldFilters) + ")"
if pc.defaultValue != "" {
s += " default " + quoteTokenIfNeeded(pc.defaultValue)
}

View File

@@ -21,7 +21,7 @@ func (pd *pipeDelete) String() string {
logger.Panicf("BUG: pipeDelete must contain at least a single field")
}
return "delete " + fieldNamesString(pd.fieldFilters)
return "delete " + fieldFiltersString(pd.fieldFilters)
}
func (pd *pipeDelete) splitToRemoteAndLocal(_ int64) (pipe, []pipe) {
@@ -87,7 +87,7 @@ func parsePipeDelete(lex *lexer) (pipe, error) {
}
lex.nextToken()
fieldFilters, err := parseCommaSeparatedFields(lex)
fieldFilters, err := parseCommaSeparatedFieldFilters(lex)
if err != nil {
return nil, err
}

View File

@@ -20,7 +20,7 @@ func (pf *pipeFields) String() string {
if len(pf.fieldFilters) == 0 {
logger.Panicf("BUG: pipeFields must contain at least a single field filter")
}
return "fields " + fieldNamesString(pf.fieldFilters)
return "fields " + fieldFiltersString(pf.fieldFilters)
}
func (pf *pipeFields) splitToRemoteAndLocal(_ int64) (pipe, []pipe) {
@@ -100,7 +100,7 @@ func parsePipeFields(lex *lexer) (pipe, error) {
}
lex.nextToken()
fieldFilters, err := parseCommaSeparatedFields(lex)
fieldFilters, err := parseCommaSeparatedFieldFilters(lex)
if err != nil {
return nil, err
}
@@ -109,18 +109,3 @@ func parsePipeFields(lex *lexer) (pipe, error) {
}
return pf, nil
}
func parseCommaSeparatedFields(lex *lexer) ([]string, error) {
var fields []string
for {
field, err := parseFieldFilter(lex)
if err != nil {
return nil, fmt.Errorf("cannot parse field name: %w", err)
}
fields = append(fields, field)
if !lex.isKeyword(",") {
return fields, nil
}
lex.nextToken()
}
}

View File

@@ -124,7 +124,7 @@ func parsePipeFilterExt(lex *lexer, needFilterKeyword bool) (pipe, error) {
lex.nextToken()
}
f, err := parseFilter(lex, needFilterKeyword)
f, err := parseFilter(lex)
if err != nil {
return nil, fmt.Errorf("cannot parse 'filter': %w", err)
}

View File

@@ -192,7 +192,6 @@ func (pjp *pipeJoinProcessor) writeBlock(workerID uint, br *blockResult) {
for i := range cs {
name := cs[i].name
byValuesIdxs[i] = slices.Index(pj.byFields, name)
}
for rowIdx := range br.rowsLen {

View File

@@ -0,0 +1,197 @@
package logstorage
import (
"fmt"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/atomicutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
"github.com/VictoriaMetrics/VictoriaLogs/lib/prefixfilter"
)
// pipeJSONArrayConcat processes '| json_array_concat ...' pipe.
//
// See https://docs.victoriametrics.com/victorialogs/logsql/#json_array_concat-pipe
type pipeJSONArrayConcat struct {
delimiter string
fromField string
resultField string
}
func (pc *pipeJSONArrayConcat) String() string {
s := "json_array_concat"
if pc.delimiter != "" {
s += " " + quoteTokenIfNeeded(pc.delimiter)
}
if !isMsgFieldName(pc.fromField) {
s += " from " + quoteTokenIfNeeded(pc.fromField)
}
if pc.resultField != pc.fromField {
s += " as " + quoteTokenIfNeeded(pc.resultField)
}
return s
}
func (pc *pipeJSONArrayConcat) splitToRemoteAndLocal(_ int64) (pipe, []pipe) {
return pc, nil
}
func (pc *pipeJSONArrayConcat) canLiveTail() bool {
return true
}
func (pc *pipeJSONArrayConcat) canReturnLastNResults() bool {
return pc.resultField != "_time"
}
func (pc *pipeJSONArrayConcat) isFixedOutputFieldsOrder() bool {
return false
}
func (pc *pipeJSONArrayConcat) updateNeededFields(pf *prefixfilter.Filter) {
if pf.MatchString(pc.resultField) {
pf.AddDenyFilter(pc.resultField)
pf.AddAllowFilter(pc.fromField)
}
}
func (pc *pipeJSONArrayConcat) hasFilterInWithQuery() bool {
return false
}
func (pc *pipeJSONArrayConcat) initFilterInValues(_ *inValuesCache, _ getFieldValuesFunc) (pipe, error) {
return pc, nil
}
func (pc *pipeJSONArrayConcat) visitSubqueries(_ func(q *Query)) {
// nothing to do
}
func (pc *pipeJSONArrayConcat) newPipeProcessor(_ int, _ <-chan struct{}, _ func(), ppNext pipeProcessor) pipeProcessor {
pcp := &pipeJSONArrayConcatProcessor{
pc: pc,
ppNext: ppNext,
}
pcp.shards.Init = func(shard *pipeJSONArrayConcatProcessorShard) {
shard.reset()
}
return pcp
}
type pipeJSONArrayConcatProcessor struct {
pc *pipeJSONArrayConcat
ppNext pipeProcessor
shards atomicutil.Slice[pipeJSONArrayConcatProcessorShard]
}
func (pcp *pipeJSONArrayConcatProcessor) writeBlock(workerID uint, br *blockResult) {
if br.rowsLen == 0 {
return
}
shard := pcp.shards.Get(workerID)
shard.rc.name = pcp.pc.resultField
c := br.getColumnByName(pcp.pc.fromField)
delimiter := pcp.pc.delimiter
if c.isConst {
// Fast path for const column
v := c.valuesEncoded[0]
out := shard.concat(v, delimiter)
shard.rc.addValue(out)
br.addResultColumnConst(shard.rc)
} else {
// Slow path for other columns
values := c.getValues(br)
prevOut := ""
for rowIdx := range values {
if rowIdx == 0 || values[rowIdx] != values[rowIdx-1] {
prevOut = shard.concat(values[rowIdx], delimiter)
}
shard.rc.addValue(prevOut)
}
br.addResultColumn(shard.rc)
}
pcp.ppNext.writeBlock(workerID, br)
shard.reset()
}
type pipeJSONArrayConcatProcessorShard struct {
a arena
rc resultColumn
tmpValues []string
}
func (shard *pipeJSONArrayConcatProcessorShard) reset() {
shard.a.reset()
shard.rc.reset()
shard.tmpValues = shard.tmpValues[:0]
}
func (shard *pipeJSONArrayConcatProcessorShard) concat(arrayStr, delimiter string) string {
shard.tmpValues = unpackJSONArray(shard.tmpValues[:0], &shard.a, arrayStr)
bLen := len(shard.a.b)
for i, v := range shard.tmpValues {
if i > 0 {
shard.a.b = append(shard.a.b, delimiter...)
}
shard.a.b = append(shard.a.b, v...)
}
return bytesutil.ToUnsafeString(shard.a.b[bLen:])
}
func (pcp *pipeJSONArrayConcatProcessor) flush() error {
return nil
}
func parsePipeJSONArrayConcat(lex *lexer) (pipe, error) {
if !lex.isKeyword("json_array_concat") {
return nil, fmt.Errorf("unexpected token: %q; want %q", lex.token, "json_array_concat")
}
lex.nextToken()
delimiter := ""
if !lex.isQueryPartTrailer() && !lex.isKeyword("from", "as") {
s, err := lex.nextCompoundToken()
if err != nil {
return nil, fmt.Errorf("cannot parse delimiter for 'json_array_concat': %w", err)
}
delimiter = s
}
fromField := "_msg"
if !lex.isQueryPartTrailer() && !lex.isKeyword("as") {
if lex.isKeyword("from") {
lex.nextToken()
}
f, err := parseFieldName(lex)
if err != nil {
return nil, fmt.Errorf("cannot parse 'from' field for 'json_array_concat': %w", err)
}
fromField = f
}
resultField := fromField
if !lex.isQueryPartTrailer() {
if lex.isKeyword("as") {
lex.nextToken()
}
f, err := parseFieldName(lex)
if err != nil {
return nil, fmt.Errorf("cannot parse result field for 'json_array_concat': %w", err)
}
resultField = f
}
return &pipeJSONArrayConcat{
delimiter: delimiter,
fromField: fromField,
resultField: resultField,
}, nil
}

View File

@@ -4,6 +4,7 @@ import (
"fmt"
"math"
"strings"
"sync"
"time"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/atomicutil"
@@ -567,32 +568,51 @@ func parseMathExprInParens(lex *lexer) (*mathExpr, error) {
return me, nil
}
type mathFuncParser func(lex *lexer) (*mathExpr, error)
var mathFuncParsers map[string]mathFuncParser
var mathFuncParsersOnce sync.Once
func getMathFuncParsers() map[string]mathFuncParser {
mathFuncParsersOnce.Do(initMathFuncParsers)
return mathFuncParsers
}
func initMathFuncParsers() {
mathFuncParsers = map[string]mathFuncParser{
"abs": parseMathExprAbs,
"ceil": parseMathExprCeil,
"exp": parseMathExprExp,
"floor": parseMathExprFloor,
"ln": parseMathExprLn,
"max": parseMathExprMax,
"min": parseMathExprMin,
"now": parseMathExprNow,
"rand": parseMathExprRand,
"round": parseMathExprRound,
}
}
func isMathFuncName(s string) bool {
mps := getMathFuncParsers()
sLower := strings.ToLower(s)
return mps[sLower] != nil
}
func parseMathExprOperand(lex *lexer) (*mathExpr, error) {
if lex.isKeyword("(") {
return parseMathExprInParens(lex)
}
// A quoted token (e.g. "abs") isn't a keyword, so isKeyword() returns false for it
// and it falls through to parseMathExprFieldName below as a field name.
for funcName, parseFunc := range getMathFuncParsers() {
if lex.isKeyword(funcName) {
return parseFunc(lex)
}
}
switch {
case lex.isKeyword("abs"):
return parseMathExprAbs(lex)
case lex.isKeyword("exp"):
return parseMathExprExp(lex)
case lex.isKeyword("ln"):
return parseMathExprLn(lex)
case lex.isKeyword("max"):
return parseMathExprMax(lex)
case lex.isKeyword("min"):
return parseMathExprMin(lex)
case lex.isKeyword("now"):
return parseMathExprNow(lex)
case lex.isKeyword("rand"):
return parseMathExprRand(lex)
case lex.isKeyword("round"):
return parseMathExprRound(lex)
case lex.isKeyword("ceil"):
return parseMathExprCeil(lex)
case lex.isKeyword("floor"):
return parseMathExprFloor(lex)
case lex.isKeyword("-"):
return parseMathExprUnaryMinus(lex)
case lex.isKeyword("+"):

View File

@@ -20,7 +20,7 @@ type pipePackJSON struct {
func (pp *pipePackJSON) String() string {
s := "pack_json"
if len(pp.fieldFilters) > 0 {
s += " fields (" + fieldNamesString(pp.fieldFilters) + ")"
s += " fields (" + fieldFiltersString(pp.fieldFilters) + ")"
}
if !isMsgFieldName(pp.resultField) {
s += " as " + quoteTokenIfNeeded(pp.resultField)

View File

@@ -20,7 +20,7 @@ type pipePackLogfmt struct {
func (pp *pipePackLogfmt) String() string {
s := "pack_logfmt"
if len(pp.fieldFilters) > 0 {
s += " fields (" + fieldNamesString(pp.fieldFilters) + ")"
s += " fields (" + fieldFiltersString(pp.fieldFilters) + ")"
}
if !isMsgFieldName(pp.resultField) {
s += " as " + quoteTokenIfNeeded(pp.resultField)

View File

@@ -92,7 +92,6 @@ func (pr *pipeReplaceRegexp) newPipeProcessor(_ int, _ <-chan struct{}, _ func()
}
return newPipeUpdateProcessor(updateFunc, ppNext, pr.field, pr.iff)
}
func parsePipeReplaceRegexp(lex *lexer) (pipe, error) {

View File

@@ -25,7 +25,7 @@ func (ps *pipeSetStreamFields) String() string {
if ps.iff != nil {
s += " " + ps.iff.String()
}
s += " " + fieldNamesString(ps.streamFieldFilters)
s += " " + fieldFiltersString(ps.streamFieldFilters)
return s
}
@@ -186,7 +186,7 @@ func parsePipeSetStreamFields(lex *lexer) (pipe, error) {
}
// Parse stream fields
streamFieldFilters, err := parseCommaSeparatedFields(lex)
streamFieldFilters, err := parseCommaSeparatedFieldFilters(lex)
if err != nil {
return nil, err
}

View File

@@ -1395,7 +1395,7 @@ func parsePipeStatsExt(lex *lexer, needStatsKeyword bool) (pipe, error) {
for {
e, err := parseStatsEntry(lex)
if err != nil {
return nil, fmt.Errorf("cannot parse 'stats' entry: %w", err)
return nil, err
}
ps.entries = append(ps.entries, e)
@@ -1779,85 +1779,6 @@ func tryParseBucketSize(s string) (float64, bool) {
return 0, false
}
func parseFieldNamesInParens(lex *lexer) ([]string, error) {
fieldNames, err := parseFieldFiltersInParens(lex)
if err != nil {
return nil, err
}
for _, fieldName := range fieldNames {
if prefixfilter.IsWildcardFilter(fieldName) {
return nil, fmt.Errorf("the field name %q cannot end with '*'", fieldName)
}
}
return fieldNames, nil
}
func parseFieldFiltersInParens(lex *lexer) ([]string, error) {
if !lex.isKeyword("(") {
return nil, fmt.Errorf("missing `(`")
}
var fields []string
for {
lex.nextToken()
if lex.isKeyword(")") {
lex.nextToken()
return fields, nil
}
if lex.isKeyword(",") {
return nil, fmt.Errorf("unexpected `,`")
}
field, err := parseFieldFilter(lex)
if err != nil {
return nil, err
}
fields = append(fields, field)
switch {
case lex.isKeyword(")"):
lex.nextToken()
return fields, nil
case lex.isKeyword(","):
default:
return nil, fmt.Errorf("unexpected token: %q; expecting ',' or ')'", lex.token)
}
}
}
func parseFieldName(lex *lexer) (string, error) {
fieldName, err := lex.nextCompoundToken()
if err != nil {
return "", err
}
fieldName = getCanonicalColumnName(fieldName)
return fieldName, nil
}
func parseFieldFilter(lex *lexer) (string, error) {
if lex.isKeyword("*") {
lex.nextToken()
return "*", nil
}
fieldName, err := lex.nextCompoundToken()
if err != nil {
return "", err
}
fieldName = getCanonicalColumnName(fieldName)
if !lex.isSkippedSpace && lex.isKeyword("*") {
lex.nextToken()
fieldName += "*"
}
return fieldName, nil
}
func fieldNamesString(fields []string) string {
a := make([]string, len(fields))
for i, f := range fields {
a[i] = quoteFieldFilterIfNeeded(f)
}
return strings.Join(a, ", ")
}
func areConstValues(values []string) bool {
if len(values) == 0 {
return false

View File

@@ -621,7 +621,7 @@ func parsePipeTop(lex *lexer) (pipe, error) {
}
byFields = bfs
} else if !lex.isKeyword("hits", "rank") && !lex.isQueryPartTrailer() {
bfs, err := parseCommaSeparatedFields(lex)
bfs, err := parseCommaSeparatedFieldNames(lex)
if err != nil {
return nil, fmt.Errorf("cannot parse 'by ...': %w", err)
}

View File

@@ -551,7 +551,7 @@ func parsePipeUniq(lex *lexer) (pipe, error) {
}
byFields = bfs
} else if !lex.isKeyword("filter", "with", "hits", "limit") && !lex.isQueryPartTrailer() {
bfs, err := parseCommaSeparatedFields(lex)
bfs, err := parseCommaSeparatedFieldNames(lex)
if err != nil {
return nil, fmt.Errorf("cannot parse 'by ...': %w", err)
}

View File

@@ -83,7 +83,6 @@ func (uctx *fieldsUnpackerContext) addField(name, value string) {
func newPipeUnpackProcessor(unpackFunc func(uctx *fieldsUnpackerContext, s string), ppNext pipeProcessor,
fromField string, fieldPrefix string, keepOriginalFields, skipEmptyResults bool, iff *ifFilter) *pipeUnpackProcessor {
return &pipeUnpackProcessor{
unpackFunc: unpackFunc,
ppNext: ppNext,

View File

@@ -41,7 +41,7 @@ func (pu *pipeUnpackJSON) String() string {
s += " from " + quoteTokenIfNeeded(pu.fromField)
}
if !prefixfilter.MatchAll(pu.fieldFilters) {
s += " fields (" + fieldNamesString(pu.fieldFilters) + ")"
s += " fields (" + fieldFiltersString(pu.fieldFilters) + ")"
}
if len(pu.preserveKeys) > 0 {
s += " preserve_keys (" + fieldNamesString(pu.preserveKeys) + ")"

View File

@@ -35,7 +35,7 @@ func (pu *pipeUnpackLogfmt) String() string {
s += " from " + quoteTokenIfNeeded(pu.fromField)
}
if !prefixfilter.MatchAll(pu.fieldFilters) {
s += " fields (" + fieldNamesString(pu.fieldFilters) + ")"
s += " fields (" + fieldFiltersString(pu.fieldFilters) + ")"
}
if pu.resultPrefix != "" {
s += " result_prefix " + quoteTokenIfNeeded(pu.resultPrefix)

View File

@@ -234,7 +234,7 @@ func parsePipeUnroll(lex *lexer) (pipe, error) {
}
fields = fs
} else {
fs, err := parseCommaSeparatedFields(lex)
fs, err := parseCommaSeparatedFieldNames(lex)
if err != nil {
return nil, fmt.Errorf("cannot parse 'by ...': %w", err)
}

View File

@@ -11,7 +11,7 @@ type runningStatsCount struct {
}
func (sc *runningStatsCount) String() string {
return "count(" + fieldNamesString(sc.fieldFilters) + ")"
return "count(" + fieldFiltersString(sc.fieldFilters) + ")"
}
func (sc *runningStatsCount) updateNeededFields(pf *prefixfilter.Filter) {

View File

@@ -9,7 +9,7 @@ type runningStatsMax struct {
}
func (sm *runningStatsMax) String() string {
return "max(" + fieldNamesString(sm.fieldFilters) + ")"
return "max(" + fieldFiltersString(sm.fieldFilters) + ")"
}
func (sm *runningStatsMax) updateNeededFields(pf *prefixfilter.Filter) {

View File

@@ -9,7 +9,7 @@ type runningStatsMin struct {
}
func (sm *runningStatsMin) String() string {
return "min(" + fieldNamesString(sm.fieldFilters) + ")"
return "min(" + fieldFiltersString(sm.fieldFilters) + ")"
}
func (sm *runningStatsMin) updateNeededFields(pf *prefixfilter.Filter) {

View File

@@ -12,7 +12,7 @@ type runningStatsSum struct {
}
func (ss *runningStatsSum) String() string {
return "sum(" + fieldNamesString(ss.fieldFilters) + ")"
return "sum(" + fieldFiltersString(ss.fieldFilters) + ")"
}
func (ss *runningStatsSum) updateNeededFields(pf *prefixfilter.Filter) {

View File

@@ -15,7 +15,7 @@ type statsAvg struct {
}
func (sa *statsAvg) String() string {
return "avg(" + fieldNamesString(sa.fieldFilters) + ")"
return "avg(" + fieldFiltersString(sa.fieldFilters) + ")"
}
func (sa *statsAvg) updateNeededFields(pf *prefixfilter.Filter) {

View File

@@ -16,7 +16,7 @@ type statsCount struct {
}
func (sc *statsCount) String() string {
return "count(" + fieldNamesString(sc.fieldFilters) + ")"
return "count(" + fieldFiltersString(sc.fieldFilters) + ")"
}
func (sc *statsCount) updateNeededFields(pf *prefixfilter.Filter) {

View File

@@ -16,7 +16,7 @@ type statsCountEmpty struct {
}
func (sc *statsCountEmpty) String() string {
return "count_empty(" + fieldNamesString(sc.fieldFilters) + ")"
return "count_empty(" + fieldFiltersString(sc.fieldFilters) + ")"
}
func (sc *statsCountEmpty) updateNeededFields(pf *prefixfilter.Filter) {

View File

@@ -28,7 +28,7 @@ type statsJSONValues struct {
}
func (sv *statsJSONValues) String() string {
s := "json_values(" + fieldNamesString(sv.fieldFilters) + ")"
s := "json_values(" + fieldFiltersString(sv.fieldFilters) + ")"
if len(sv.sortFields) > 0 {
a := make([]string, len(sv.sortFields))

View File

@@ -17,7 +17,7 @@ type statsMax struct {
}
func (sm *statsMax) String() string {
return "max(" + fieldNamesString(sm.fieldFilters) + ")"
return "max(" + fieldFiltersString(sm.fieldFilters) + ")"
}
func (sm *statsMax) updateNeededFields(pf *prefixfilter.Filter) {

View File

@@ -9,7 +9,7 @@ type statsMedian struct {
}
func (sm *statsMedian) String() string {
return "median(" + fieldNamesString(sm.sq.fieldFilters) + ")"
return "median(" + fieldFiltersString(sm.sq.fieldFilters) + ")"
}
func (sm *statsMedian) updateNeededFields(pf *prefixfilter.Filter) {

View File

@@ -17,7 +17,7 @@ type statsMin struct {
}
func (sm *statsMin) String() string {
return "min(" + fieldNamesString(sm.fieldFilters) + ")"
return "min(" + fieldFiltersString(sm.fieldFilters) + ")"
}
func (sm *statsMin) updateNeededFields(pf *prefixfilter.Filter) {

View File

@@ -24,7 +24,7 @@ type statsQuantile struct {
func (sq *statsQuantile) String() string {
s := "quantile(" + sq.phiStr
if !prefixfilter.MatchAll(sq.fieldFilters) {
s += ", " + fieldNamesString(sq.fieldFilters)
s += ", " + fieldFiltersString(sq.fieldFilters)
}
s += ")"
return s

View File

@@ -14,7 +14,7 @@ type statsRateSum struct {
}
func (sr *statsRateSum) String() string {
return "rate_sum(" + fieldNamesString(sr.ss.fieldFilters) + ")"
return "rate_sum(" + fieldFiltersString(sr.ss.fieldFilters) + ")"
}
func (sr *statsRateSum) updateNeededFields(pf *prefixfilter.Filter) {

View File

@@ -16,7 +16,7 @@ type statsRowAny struct {
}
func (sa *statsRowAny) String() string {
return "row_any(" + fieldNamesString(sa.fieldFilters) + ")"
return "row_any(" + fieldFiltersString(sa.fieldFilters) + ")"
}
func (sa *statsRowAny) updateNeededFields(pf *prefixfilter.Filter) {

View File

@@ -21,7 +21,7 @@ type statsRowMax struct {
func (sm *statsRowMax) String() string {
s := "row_max(" + quoteTokenIfNeeded(sm.srcField)
if !prefixfilter.MatchAll(sm.fieldFilters) {
s += ", " + fieldNamesString(sm.fieldFilters)
s += ", " + fieldFiltersString(sm.fieldFilters)
}
s += ")"
return s

View File

@@ -21,7 +21,7 @@ type statsRowMin struct {
func (sm *statsRowMin) String() string {
s := "row_min(" + quoteTokenIfNeeded(sm.srcField)
if !prefixfilter.MatchAll(sm.fieldFilters) {
s += ", " + fieldNamesString(sm.fieldFilters)
s += ", " + fieldFiltersString(sm.fieldFilters)
}
s += ")"
return s

View File

@@ -15,7 +15,7 @@ type statsStddev struct {
}
func (ss *statsStddev) String() string {
return "stddev(" + fieldNamesString(ss.fieldFilters) + ")"
return "stddev(" + fieldFiltersString(ss.fieldFilters) + ")"
}
func (ss *statsStddev) updateNeededFields(pf *prefixfilter.Filter) {

View File

@@ -15,7 +15,7 @@ type statsSum struct {
}
func (ss *statsSum) String() string {
return "sum(" + fieldNamesString(ss.fieldFilters) + ")"
return "sum(" + fieldFiltersString(ss.fieldFilters) + ")"
}
func (ss *statsSum) updateNeededFields(pf *prefixfilter.Filter) {

View File

@@ -14,7 +14,7 @@ type statsSumLen struct {
}
func (ss *statsSumLen) String() string {
return "sum_len(" + fieldNamesString(ss.fieldFilters) + ")"
return "sum_len(" + fieldFiltersString(ss.fieldFilters) + ")"
}
func (ss *statsSumLen) updateNeededFields(pf *prefixfilter.Filter) {

View File

@@ -21,7 +21,7 @@ type statsUniqValues struct {
}
func (su *statsUniqValues) String() string {
s := "uniq_values(" + fieldNamesString(su.fieldFilters) + ")"
s := "uniq_values(" + fieldFiltersString(su.fieldFilters) + ")"
if su.limit > 0 {
s += fmt.Sprintf(" limit %d", su.limit)
}

View File

@@ -17,7 +17,7 @@ type statsValues struct {
}
func (sv *statsValues) String() string {
s := "values(" + fieldNamesString(sv.fieldFilters) + ")"
s := "values(" + fieldFiltersString(sv.fieldFilters) + ")"
if sv.limit > 0 {
s += fmt.Sprintf(" limit %d", sv.limit)
}

View File

@@ -1347,7 +1347,6 @@ func (s *Storage) searchParallel(workersCount int, sso *storageSearchOptions, qs
putBlockSearch(bs)
putBitmap(bm)
qs.UpdateAtomic(qsLocal)
})
}
@@ -1601,7 +1600,6 @@ func (p *part) hasMatchingRows(pso *partitionSearchOptions, stopCh <-chan struct
putBlockSearch(bs)
putBitmap(bm)
})
}

View File

@@ -44,46 +44,54 @@ func (st *StreamTags) Reset() {
st.tags = st.tags[:0]
}
// CopyFrom copies src to dst.
func (st *StreamTags) CopyFrom(src *StreamTags) {
st.Reset()
st.tags = append(st.tags[:0], src.tags...)
}
// String returns string representation of st.
func (st *StreamTags) String() string {
b := st.marshalString(nil)
return string(b)
}
func (st *StreamTags) verifyCanonicalFieldValues(fields []Field) error {
// Verify that the unmarshaled stream tags match the corresponding fields' values.
// See https://github.com/VictoriaMetrics/VictoriaLogs/issues/38
// normalize synchronizes st with the provided fields and returns true if st was updated.
//
// This function updates or keeps tags that exist in fields and removes missing ones.
func (st *StreamTags) normalize(fields []Field) bool {
updated := false
prevTagName := ""
for _, tag := range st.tags {
tags := st.tags
dstTags := tags[:0]
for _, tag := range tags {
tagName := tag.Name
if err := CheckStreamFieldName(tagName); err != nil {
return fmt.Errorf("invalid stream tag name: %s; streamTags: %s", tagName, st)
var f *Field
for j := range fields {
if fields[j].Name == tagName {
f = &fields[j]
// break is skipped intentionally in order to get the last matching field
}
}
if f == nil {
updated = true
continue
}
if tagName <= prevTagName {
return fmt.Errorf("stream tag names must be sorted; got %q after %q; streamTags: %s", tagName, prevTagName, st)
}
tagValue := tag.Value
found := false
for _, f := range fields {
if f.Name != tagName {
continue
}
if f.Value != tagValue {
line := MarshalFieldsToJSON(nil, fields)
return fmt.Errorf("unexpected value for the stream tag %q; got %q; want %q; streamTags: %s; fields: %s", tagName, f.Value, tagValue, st, line)
}
found = true
}
if !found {
line := MarshalFieldsToJSON(nil, fields)
return fmt.Errorf("cannot find value for the stream tag %q in fields; want %q; streamTags: %s; fields: %s", tagName, tagValue, st, line)
if tag.Value != f.Value {
tag.Value = f.Value
updated = true
}
dstTags = append(dstTags, tag)
}
return nil
if updated {
clear(tags[len(dstTags):])
st.tags = dstTags
}
return updated
}
func (st *StreamTags) marshalString(dst []byte) []byte {
@@ -112,7 +120,13 @@ func (st *StreamTags) unmarshalStringInplace(s string) error {
var err error
st.tags, err = parseStreamFields(st.tags[:0], s)
return err
if err != nil {
return err
}
sort.Sort(st)
return nil
}
// Add adds (name:value) tag to st.
@@ -136,7 +150,10 @@ func (st *StreamTags) Add(name, value string) {
// MarshalCanonical marshal st in a canonical way
func (st *StreamTags) MarshalCanonical(dst []byte) []byte {
sort.Sort(st)
return st.marshalCanonicalInternal(dst)
}
func (st *StreamTags) marshalCanonicalInternal(dst []byte) []byte {
tags := st.tags
dst = encoding.MarshalVarUint64(dst, uint64(len(tags)))
for i := range tags {
@@ -160,6 +177,7 @@ func (st *StreamTags) UnmarshalCanonicalInplace(src []byte) ([]byte, error) {
return srcOrig, fmt.Errorf("cannot unmarshal tags len")
}
src = src[nSize:]
for range n {
name, nSize := encoding.UnmarshalBytes(src)
if nSize <= 0 {
@@ -178,13 +196,28 @@ func (st *StreamTags) UnmarshalCanonicalInplace(src []byte) ([]byte, error) {
st.Add(sName, sValue)
}
if !sort.IsSorted(st) {
return srcOrig, fmt.Errorf("stream tags must be sorted in alphabetical order; got unsorted: %s", st)
if err := st.checkCorrectness(); err != nil {
return srcOrig, err
}
return src, nil
}
func (st *StreamTags) checkCorrectness() error {
prevTagName := ""
for _, tag := range st.tags {
tagName := tag.Name
if err := CheckStreamFieldName(tagName); err != nil {
return fmt.Errorf("invalid stream tag name: %w", err)
}
if tagName <= prevTagName {
return fmt.Errorf("stream tags must be sorted in alphabetical order; got %q which is less or equal than %q; streamTags=%s", tagName, prevTagName, st)
}
prevTagName = tagName
}
return nil
}
func getStreamTagsString(streamTagsCanonical string) string {
st := GetStreamTags()
mustUnmarshalStreamTagsInplace(st, streamTagsCanonical)

View File

@@ -655,7 +655,6 @@ func (p *SyslogParser) parseCEFExtension(s string) bool {
p.AddField(keyName, unescapeCEFValue(s[:n]))
s = s[n+1:]
}
}
func (p *SyslogParser) tryParseTimestampRFC3164(s string) bool {

2
vendor/modules.txt vendored
View File

@@ -133,7 +133,7 @@ github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric
# github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.57.0
## explicit; go 1.25.0
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping
# github.com/VictoriaMetrics/VictoriaLogs v1.121.1-0.20260616132739-c901a1e31cb3
# github.com/VictoriaMetrics/VictoriaLogs v1.51.1-0.20260624061259-dc94972a8708
## explicit; go 1.26.4
github.com/VictoriaMetrics/VictoriaLogs/lib/logstorage
github.com/VictoriaMetrics/VictoriaLogs/lib/prefixfilter