Compare commits

..

118 Commits

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

---
updated-dependencies:
- dependency-name: github/codeql-action/analyze
  dependency-version: 4.36.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-23 04:15:23 +00:00
Nikolay
344d3e1911 lib/httputil: add load-balancing http transport
This commit adds http client load-balancing with DNS discovery. For both A and SRV records.
It helps to route HTTP requests evenly for across discovered backends.

Discovered IP addresses are cached locally.

 The main benefit of this feature is to remove intermediate vmauth as a load-balancer between
vmagent (vmalert) and remote targets. Which simplifies components management and
reduces operational overhead.

 By default, it's disable and could be used with `dns+` or `srv+` hostname suffix at url.
For example, `-remoteWrite.url=http://dns+victoria-metrics:8428/api/v1/write`.

Fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2388
2026-07-22 18:34:23 +02:00
Andrei Baidarov
8b94ca0e30 lib/persistentqueue: expose fastQueue name in metrics
This commit adds `name` label to the `vm_persistentqueue_*` metrics. It provides more context for queues with human read-able format. Because `path` label contains only on-disk path, which is not really useful.

 For vmagent case it will contain remote.writeURL position at command-line args.

Fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7944
2026-07-22 18:28:58 +02:00
f41gh7
a9ae7e1da0 vendor: update golang.org/x/... packages 2026-07-22 18:00:29 +02:00
Max Kotliar
ba59d78f10 docs: update supported LTS versions 2026-07-22 12:56:17 +03:00
Max Kotliar
1c774f9216 app/vmstorage: enhance vmselectAddr flag description (#11270)
The change applies only to the master and must not be ported to the
cluster version

Follow-up on
https://github.com/VictoriaMetrics/VictoriaMetrics/pull/10926
2026-07-21 18:46:37 +03:00
Max Kotliar
7ea9bfa8d6 docs: bump version to v1.148.0
Signed-off-by: Max Kotliar <mkotlyar@victoriametrics.com>
2026-07-21 18:06:37 +03:00
Artem Fetishev
2b95c11095 lib/storage: rewrite TestIndexDB (#11283)
This makes the test suitable for testing cases when global or per-day
indexes are disabled.

Related to https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11196

Signed-off-by: Artem Fetishev <rtm@victoriametrics.com>
2026-07-21 15:43:00 +02:00
Rudransh Shrivastava
655ed9e8d1 .github/workflows/vmui.yml: remove eslint annotation action (#11259)
Remove the unmaintained third-party `ataylorme/eslint-annotate-action`
from the vmui workflow in order to reduce supply-chain risk.
Switch `npm run lint` back to human-readable ESLint output, since the
JSON report was used only by the removed action.

The `make vmui-lint` command still fails CI on lint errors. Printing
ESLint errors in the workflow logs should be enough for blocking pull
requests with lint errors.

See the alternatives considered and why this was removed:
https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11259#issuecomment-4981601165

cc: @arkid15r 

Ref:
[ataylorme/eslint-annotate-action](https://github.com/ataylorme/eslint-annotate-action)

Signed-off-by: Rudransh Shrivastava <rudransh@victoriametrics.com>
2026-07-21 15:13:47 +03:00
Pablo (Tomas) Fernandez
4f676b4012 docs: add "Connecting VictoriaMetrics components to cloud storage" guide (#11106)
This PR adds "Connecting VictoriaMetrics components to cloud storage" guide and references it from vmbackup and vmalert pages. 

Fixes: https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11073
PR https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11106
2026-07-21 15:09:35 +03:00
Artem Fetishev
dce9514c65 docs: update flags with actual v1.148.0 binaries
Signed-off-by: Artem Fetishev <rtm@victoriametrics.com>
2026-07-20 13:10:17 +02:00
Artem Fetishev
25598da441 docs: bump version to v1.148.0
Signed-off-by: Artem Fetishev <rtm@victoriametrics.com>
2026-07-20 13:06:43 +02:00
Artem Fetishev
6250255c4f deplyoment/docker: bump version to v1.148.0
Signed-off-by: Artem Fetishev <rtm@victoriametrics.com>
2026-07-20 13:04:19 +02:00
Artem Fetishev
be7eefcf00 docs: forward port LTS v1.122.27 changelog to upstream
Signed-off-by: Artem Fetishev <rtm@victoriametrics.com>
2026-07-20 13:01:35 +02:00
Artem Fetishev
79eba63498 docs: forward port LTS v1.136.14 changelog to upstream
Signed-off-by: Artem Fetishev <rtm@victoriametrics.com>
2026-07-20 13:00:55 +02:00
Artem Fetishev
d94a85a405 docs/changelog: cut release v1.148.0
Signed-off-by: Artem Fetishev <rtm@victoriametrics.com>
2026-07-17 12:46:27 +02:00
Artem Fetishev
d1097ea032 app/vmselect: run make vmui-update
Signed-off-by: Artem Fetishev <rtm@victoriametrics.com>
2026-07-17 12:44:20 +02:00
JAYICE
439a0cdc9d vmauth: return 408 instead of 400 when fail to read request body within deadline (#11273)
A slow vmagent can cause reading the request body to exceed the deadline configured by `-maxQueueDuration`. In this case, vmauth returned 400 Bad Request, causing vmagent to downgrade the remote write protocol from zstd to snappy. If subsequent requests also failed, vmagent could drop data blocks without retrying.

This change returns 408 Request Timeout instead of 400 Bad Request when reading the request body exceeds `-maxQueueDuration`, preventing the unnecessary protocol downgrade and subsequent data loss.

Fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11272
PR https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11273

---------

Signed-off-by: “Jayice” <jzhou@victoriametrics.com>
Co-authored-by: Max Kotliar <mkotlyar@victoriametrics.com>
2026-07-17 12:51:37 +03:00
f41gh7
72fcea8f04 vmimport: fix typo
Due to copy-paste vmimport had link for influx line protocol instead of json line format

Signed-off-by: f41gh7 <nik@victoriametrics.com>
2026-07-17 10:46:22 +02:00
Yury Moladau
4287d177b6 app/vmui: update dependencies
Updates npm dependencies and refreshes `package-lock.json`.

Related PR https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11265
2026-07-17 10:37:51 +02:00
Nikolay
2900ad8d60 lib/persistentqueue: atomically write metainfo
Previously queue metainfo was flushed with fsync. However it may
corrupt file if OS filesystem not properly flush data.

 This commit adds atomic file write with temporary file.
It must prevent possible file corruption issue.

Fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11192
2026-07-17 10:05:29 +02:00
Max Kotliar
663ef1ee45 docs/changelog: add update note to v1.147.0 2026-07-16 21:49:38 +03:00
Max Kotliar
c4e9d59b15 docs/changelog: chore issue refs 2026-07-16 21:39:35 +03:00
Zhu Jiekun
868ae63e9a app/vmagent: enhance metrics relabel debug UX (#10607)
Target metrics relabel debug UI now preloads relabeling rules configured in `-promscrape.config`, `-remoteWrite.relabelConfig` and `-remoteWrite.urlRelabelConfig` flags. 

Metrics relabel debug UI now preloads relabeling rules configured in `-remoteWrite.relabelConfig` and `-remoteWrite.urlRelabelConfig` flags. 

The configs are ordered in the same order they are executed by vmagent. They also contain hint comments that highlight from which flag they come.

The select at the top lets you change which remote write configuration to use. The first one is preloaded by default. 

Other UI improvements:
  - renamed "Labels" -> "A Time Series", "Reset" -> "Reset All",
    "Relabel Configs" -> "Configs"
  - pre-populate "A Time Series" field with an `up` metric placeholder
  - display YAML comments (lines starting with #) in gray
  - added links to "Relabeling Cookbook" and "Relabeling Stages" docs
  - added JSONPath hints for -promscrape.config relabel config fields
  - use POST when reloading config content larger than 1KB
  - improved error messages when multiple time series are provided

Fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/9918
PR https://github.com/VictoriaMetrics/VictoriaMetrics/pull/10607

---------

Signed-off-by: Zhu Jiekun <jiekun@victoriametrics.com>
Co-authored-by: Max Kotliar <mkotlyar@victoriametrics.com>
2026-07-16 21:14:16 +03:00
f41gh7
a3bda3c4e2 deployment/docker: update Go builder from Go1.26.4 to Go1.26.5
See https://github.com/golang/go/issues?q=milestone%3AGo1.26.5%20label%3ACherryPickApproved
2026-07-16 17:41:16 +02:00
vinyas-bharadwaj
14ce82b5ff lib/promscrape: support scraping metrics over Unix domain sockets
This commit introduces a new internal target label: `__unix_socket__`.

When this label is set in the scrape config, scraper uses specified Unix
socket file.

Fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11156
2026-07-16 17:28:19 +02:00
Artem Fetishev
21cc4ef693 lib/storage: Simplify TestStorageDeleteSeries
Remove empty db checks before and after test.
These checks are redundant and make it difficult to port them to the cluster
branch.

Follow-up for 52de485bc9.

Signed-off-by: Artem Fetishev <rtm@victoriametrics.com>
2026-07-16 15:50:58 +02:00
Artem Fetishev
52de485bc9 Rewrite delete series test (#11271)
Rewrite `TestStorageDeleteSeries`: add subtests that disable per-day
index. This prepares the test for adding subtests for disabling global
index in https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11196.

---------

Signed-off-by: Artem Fetishev <rtm@victoriametrics.com>
2026-07-16 15:11:41 +02:00
Artem Fetishev
aeaa0919c2 lib/storage: fix concurrent forced data flush (#11260)
Concurrent goroutines that ingest, flush, and then read their own data
may not see it. See `TestStorageAddFlushSearchDataConcurrently` in this
PR.

This is not the case for reading index. See
`TestStorageAddFlushSearchMetricNamesConcurrently`. This is because
index table handles concurrent flushes correctly while data table don't.
The solution is to use the same mechanism in data table
(`flushPendingItemsWG`).

The issue was originally discovered when rewriting the series deletion
test in https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11271.

---------

Signed-off-by: Artem Fetishev <rtm@victoriametrics.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
2026-07-16 12:41:24 +02:00
hagen1778
2b600e3646 docs: fix typo in the changelog line
Signed-off-by: hagen1778 <roman@victoriametrics.com>
(cherry picked from commit 363554dc56)
2026-07-15 16:23:55 +02:00
hagen1778
0ca68c7f79 docs: group components in changelog by name for recent releases
Grouping by name keeps user's focus on specific area of changes
while reading the changelog.

Signed-off-by: hagen1778 <roman@victoriametrics.com>
2026-07-15 16:19:03 +02:00
yui
a2a960b0ba vmui: hide total metric names for filtered cardinality view (#11240)
Fixes #11154 

  ## What
Hide the `Total metric names` widget in the cardinality page when
`match` or `focusLabel` is not empty.

  ## Why
`Total metric names` is relevant for the unfiltered overview, but
becomes misleading in filtered cardinality views.

  ## Changes
  - hide `Total metric names` when `match` is set
  - hide `Total metric names` when `focusLabel` is set
  - keep existing totals behavior otherwise

  ## Validation
  - `npm run test`
  - `npm run lint:local`
  - `npm run typecheck`

---------

Signed-off-by: hagen1778 <roman@victoriametrics.com>
Co-authored-by: hagen1778 <roman@victoriametrics.com>
2026-07-15 16:14:22 +02:00
Roman Khavronenko
6d80ee6ab8 docs: add "Graceful Shutdown" section to the quickstart (#11244)
Recommendation to gracefuly shutdown process is sometimes overlooked by
users. Mentioning it explicitly so it is present in docs as
recommendation and can be linkable when sharing with users.

---------

Signed-off-by: hagen1778 <roman@victoriametrics.com>
Signed-off-by: Roman Khavronenko <hagen1778@gmail.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
Co-authored-by: Kirill Yurkov <kirillyu@users.noreply.github.com>
2026-07-15 16:14:03 +02:00
Jesús Espino
25af4d28ff docs: add "Try it locally" section to the quick start guide (#11263)
The idea here is to give a very direct path to start trying
VictoriaMetrics with a very clear, simple and straight forward guidance.

We keep the full instalation process in the same document on purpose to
keep any external link working.

---------

Signed-off-by: Jesús Espino <jespinog@gmail.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
2026-07-15 16:13:38 +02:00
Aliaksandr Valialkin
769ad61ecb docs/victoriametrics/Articles.md: add https://jorijn.com/en/blog/victoriametrics-vs-prometheus/ 2026-07-15 01:19:33 +02:00
dependabot[bot]
f0cb99c44d build(deps): bump actions/checkout from 5 to 7 (#1058)
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Commits](https://github.com/actions/checkout/compare/v5...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.0.3
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-15 01:18:08 +02:00
dependabot[bot]
86cd44fe3e build(deps): bump github/codeql-action/autobuild from 4.35.3 to 4.36.2 (#11177)
[//]: # (dependabot-start)
⚠️  **Dependabot is rebasing this PR** ⚠️ 

Rebasing might not happen immediately, so don't worry if this takes some
time.

Note: if you make any changes to this PR yourself, they will take
precedence over the rebase.

---

[//]: # (dependabot-end)

Bumps
[github/codeql-action/autobuild](https://github.com/github/codeql-action)
from 4.35.3 to 4.36.2.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/releases">github/codeql-action/autobuild's
releases</a>.</em></p>
<blockquote>
<h2>v4.36.2</h2>
<ul>
<li>Cache CodeQL CLI version information across Actions steps. <a
href="https://redirect.github.com/github/codeql-action/pull/3943">#3943</a></li>
<li>Reduce requests while waiting for analysis processing by using
exponential backoff when polling SARIF processing status. <a
href="https://redirect.github.com/github/codeql-action/pull/3937">#3937</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.6">2.25.6</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3948">#3948</a></li>
</ul>
<h2>v4.36.1</h2>
<p>No user facing changes.</p>
<h2>v4.36.0</h2>
<ul>
<li><em>Breaking change</em>: Bump the minimum required CodeQL bundle
version to 2.19.4. <a
href="https://redirect.github.com/github/codeql-action/pull/3894">#3894</a></li>
<li>Add support for SHA-256 Git object IDs. <a
href="https://redirect.github.com/github/codeql-action/pull/3893">#3893</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.5">2.25.5</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3926">#3926</a></li>
</ul>
<h2>v4.35.5</h2>
<ul>
<li>We have improved how the JavaScript bundles for the CodeQL Action
are generated to avoid duplication across bundles and reduce the size of
the repository by around 70%. This should have no effect on the runtime
behaviour of the CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3899">#3899</a></li>
<li>For performance and accuracy reasons, <a
href="https://redirect.github.com/github/roadmap/issues/1158">improved
incremental analysis</a> will now only be enabled on a pull request when
diff-informed analysis is also enabled for that run. If diff-informed
analysis is unavailable (for example, because the PR diff ranges could
not be computed), the action will fall back to a full analysis. <a
href="https://redirect.github.com/github/codeql-action/pull/3791">#3791</a></li>
<li>If multiple inputs are provided for the GitHub-internal
<code>analysis-kinds</code> input, only <code>code-scanning</code> will
be enabled. The <code>analysis-kinds</code> input is experimental, for
GitHub-internal use only, and may change without notice at any time. <a
href="https://redirect.github.com/github/codeql-action/pull/3892">#3892</a></li>
<li>Added an experimental change which, when running a Code Scanning
analysis for a PR with <a
href="https://redirect.github.com/github/roadmap/issues/1158">improved
incremental analysis</a> enabled, prefers CodeQL CLI versions that have
a cached overlay-base database for the configured languages. This speeds
up analysis for a repository when there is not yet a cached overlay-base
database for the latest CLI version. We expect to roll this change out
to everyone in May. <a
href="https://redirect.github.com/github/codeql-action/pull/3880">#3880</a></li>
</ul>
<h2>v4.35.4</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.4">2.25.4</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3881">#3881</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/blob/main/CHANGELOG.md">github/codeql-action/autobuild's
changelog</a>.</em></p>
<blockquote>
<h1>CodeQL Action Changelog</h1>
<p>See the <a
href="https://github.com/github/codeql-action/releases">releases
page</a> for the relevant changes to the CodeQL CLI and language
packs.</p>
<h2>[UNRELEASED]</h2>
<p>No user facing changes.</p>
<h2>4.36.2 - 04 Jun 2026</h2>
<ul>
<li>Cache CodeQL CLI version information across Actions steps. <a
href="https://redirect.github.com/github/codeql-action/pull/3943">#3943</a></li>
<li>Reduce requests while waiting for analysis processing by using
exponential backoff when polling SARIF processing status. <a
href="https://redirect.github.com/github/codeql-action/pull/3937">#3937</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.6">2.25.6</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3948">#3948</a></li>
</ul>
<h2>4.36.1 - 02 Jun 2026</h2>
<p>No user facing changes.</p>
<h2>4.36.0 - 22 May 2026</h2>
<ul>
<li><em>Breaking change</em>: Bump the minimum required CodeQL bundle
version to 2.19.4. <a
href="https://redirect.github.com/github/codeql-action/pull/3894">#3894</a></li>
<li>Add support for SHA-256 Git object IDs. <a
href="https://redirect.github.com/github/codeql-action/pull/3893">#3893</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.5">2.25.5</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3926">#3926</a></li>
</ul>
<h2>4.35.5 - 15 May 2026</h2>
<ul>
<li>We have improved how the JavaScript bundles for the CodeQL Action
are generated to avoid duplication across bundles and reduce the size of
the repository by around 70%. This should have no effect on the runtime
behaviour of the CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3899">#3899</a></li>
<li>For performance and accuracy reasons, <a
href="https://redirect.github.com/github/roadmap/issues/1158">improved
incremental analysis</a> will now only be enabled on a pull request when
diff-informed analysis is also enabled for that run. If diff-informed
analysis is unavailable (for example, because the PR diff ranges could
not be computed), the action will fall back to a full analysis. <a
href="https://redirect.github.com/github/codeql-action/pull/3791">#3791</a></li>
<li>If multiple inputs are provided for the GitHub-internal
<code>analysis-kinds</code> input, only <code>code-scanning</code> will
be enabled. The <code>analysis-kinds</code> input is experimental, for
GitHub-internal use only, and may change without notice at any time. <a
href="https://redirect.github.com/github/codeql-action/pull/3892">#3892</a></li>
<li>Added an experimental change which, when running a Code Scanning
analysis for a PR with <a
href="https://redirect.github.com/github/roadmap/issues/1158">improved
incremental analysis</a> enabled, prefers CodeQL CLI versions that have
a cached overlay-base database for the configured languages. This speeds
up analysis for a repository when there is not yet a cached overlay-base
database for the latest CLI version. We expect to roll this change out
to everyone in May. <a
href="https://redirect.github.com/github/codeql-action/pull/3880">#3880</a></li>
</ul>
<h2>4.35.4 - 07 May 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.4">2.25.4</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3881">#3881</a></li>
</ul>
<h2>4.35.3 - 01 May 2026</h2>
<ul>
<li><em>Upcoming breaking change</em>: Add a deprecation warning for
customers using CodeQL version 2.19.3 and earlier. These versions of
CodeQL were discontinued on 9 April 2026 alongside GitHub Enterprise
Server 3.15, and will be unsupported by the next minor release of the
CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3837">#3837</a></li>
<li>Configurations for private registries that use Cloudsmith or GCP
OIDC are now accepted. <a
href="https://redirect.github.com/github/codeql-action/pull/3850">#3850</a></li>
<li>Best-effort connection tests for private registries now use
<code>GET</code> requests instead of <code>HEAD</code> for better
compatibility with various registry implementations. For NuGet feeds,
the test is now always performed against the service index. <a
href="https://redirect.github.com/github/codeql-action/pull/3853">#3853</a></li>
<li>Fixed a bug where two diagnostics produced within the same
millisecond could overwrite each other on disk, causing one of them to
be lost. <a
href="https://redirect.github.com/github/codeql-action/pull/3852">#3852</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.3">2.25.3</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3865">#3865</a></li>
</ul>
<h2>4.35.2 - 15 Apr 2026</h2>
<ul>
<li>The undocumented TRAP cache cleanup feature that could be enabled
using the <code>CODEQL_ACTION_CLEANUP_TRAP_CACHES</code> environment
variable is deprecated and will be removed in May 2026. If you are
affected by this, we recommend disabling TRAP caching by passing the
<code>trap-caching: false</code> input to the <code>init</code> Action.
<a
href="https://redirect.github.com/github/codeql-action/pull/3795">#3795</a></li>
<li>The Git version 2.36.0 requirement for improved incremental analysis
now only applies to repositories that contain submodules. <a
href="https://redirect.github.com/github/codeql-action/pull/3789">#3789</a></li>
<li>Python analysis on GHES no longer extracts the standard library,
relying instead on models of the standard library. This should result in
significantly faster extraction and analysis times, while the effect on
alerts should be minimal. <a
href="https://redirect.github.com/github/codeql-action/pull/3794">#3794</a></li>
<li>Fixed a bug in the validation of OIDC configurations for private
registries that was added in CodeQL Action 4.33.0 / 3.33.0. <a
href="https://redirect.github.com/github/codeql-action/pull/3807">#3807</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.2">2.25.2</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3823">#3823</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="8aad20d150"><code>8aad20d</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/3949">#3949</a>
from github/update-v4.36.2-dcb947ce1</li>
<li><a
href="f521b08cd8"><code>f521b08</code></a>
Add additional changelog notes</li>
<li><a
href="8aeff0ffb7"><code>8aeff0f</code></a>
Update changelog for v4.36.2</li>
<li><a
href="dcb947ce15"><code>dcb947c</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/3948">#3948</a>
from github/update-bundle/codeql-bundle-v2.25.6</li>
<li><a
href="c251bcefa1"><code>c251bce</code></a>
Add changelog note</li>
<li><a
href="62953c18b3"><code>62953c1</code></a>
Update default bundle to codeql-bundle-v2.25.6</li>
<li><a
href="423b570baf"><code>423b570</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/3946">#3946</a>
from github/dependabot/npm_and_yarn/npm-minor-5d507a...</li>
<li><a
href="c35d1b1644"><code>c35d1b1</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/3947">#3947</a>
from github/dependabot/github_actions/dot-github/wor...</li>
<li><a
href="cb1a588b02"><code>cb1a588</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/3937">#3937</a>
from github/robertbrignull/waitForProcessing_backoff</li>
<li><a
href="ba47406412"><code>ba47406</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/3943">#3943</a>
from github/henrymercer/cache-cli-version-info</li>
<li>Additional commits viewable in <a
href="e46ed2cbd0...8aad20d150">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github/codeql-action/autobuild&package-manager=github_actions&previous-version=4.35.3&new-version=4.36.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

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

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

---

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

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-14 18:44:36 +03:00
dependabot[bot]
e7d44fabba build(deps): bump github/codeql-action/init from 4.35.3 to 4.36.2 (#11176)
Bumps
[github/codeql-action/init](https://github.com/github/codeql-action)
from 4.35.3 to 4.36.2.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/releases">github/codeql-action/init's
releases</a>.</em></p>
<blockquote>
<h2>v4.36.2</h2>
<ul>
<li>Cache CodeQL CLI version information across Actions steps. <a
href="https://redirect.github.com/github/codeql-action/pull/3943">#3943</a></li>
<li>Reduce requests while waiting for analysis processing by using
exponential backoff when polling SARIF processing status. <a
href="https://redirect.github.com/github/codeql-action/pull/3937">#3937</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.6">2.25.6</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3948">#3948</a></li>
</ul>
<h2>v4.36.1</h2>
<p>No user facing changes.</p>
<h2>v4.36.0</h2>
<ul>
<li><em>Breaking change</em>: Bump the minimum required CodeQL bundle
version to 2.19.4. <a
href="https://redirect.github.com/github/codeql-action/pull/3894">#3894</a></li>
<li>Add support for SHA-256 Git object IDs. <a
href="https://redirect.github.com/github/codeql-action/pull/3893">#3893</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.5">2.25.5</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3926">#3926</a></li>
</ul>
<h2>v4.35.5</h2>
<ul>
<li>We have improved how the JavaScript bundles for the CodeQL Action
are generated to avoid duplication across bundles and reduce the size of
the repository by around 70%. This should have no effect on the runtime
behaviour of the CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3899">#3899</a></li>
<li>For performance and accuracy reasons, <a
href="https://redirect.github.com/github/roadmap/issues/1158">improved
incremental analysis</a> will now only be enabled on a pull request when
diff-informed analysis is also enabled for that run. If diff-informed
analysis is unavailable (for example, because the PR diff ranges could
not be computed), the action will fall back to a full analysis. <a
href="https://redirect.github.com/github/codeql-action/pull/3791">#3791</a></li>
<li>If multiple inputs are provided for the GitHub-internal
<code>analysis-kinds</code> input, only <code>code-scanning</code> will
be enabled. The <code>analysis-kinds</code> input is experimental, for
GitHub-internal use only, and may change without notice at any time. <a
href="https://redirect.github.com/github/codeql-action/pull/3892">#3892</a></li>
<li>Added an experimental change which, when running a Code Scanning
analysis for a PR with <a
href="https://redirect.github.com/github/roadmap/issues/1158">improved
incremental analysis</a> enabled, prefers CodeQL CLI versions that have
a cached overlay-base database for the configured languages. This speeds
up analysis for a repository when there is not yet a cached overlay-base
database for the latest CLI version. We expect to roll this change out
to everyone in May. <a
href="https://redirect.github.com/github/codeql-action/pull/3880">#3880</a></li>
</ul>
<h2>v4.35.4</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.4">2.25.4</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3881">#3881</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/blob/main/CHANGELOG.md">github/codeql-action/init's
changelog</a>.</em></p>
<blockquote>
<h1>CodeQL Action Changelog</h1>
<p>See the <a
href="https://github.com/github/codeql-action/releases">releases
page</a> for the relevant changes to the CodeQL CLI and language
packs.</p>
<h2>[UNRELEASED]</h2>
<p>No user facing changes.</p>
<h2>4.36.2 - 04 Jun 2026</h2>
<ul>
<li>Cache CodeQL CLI version information across Actions steps. <a
href="https://redirect.github.com/github/codeql-action/pull/3943">#3943</a></li>
<li>Reduce requests while waiting for analysis processing by using
exponential backoff when polling SARIF processing status. <a
href="https://redirect.github.com/github/codeql-action/pull/3937">#3937</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.6">2.25.6</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3948">#3948</a></li>
</ul>
<h2>4.36.1 - 02 Jun 2026</h2>
<p>No user facing changes.</p>
<h2>4.36.0 - 22 May 2026</h2>
<ul>
<li><em>Breaking change</em>: Bump the minimum required CodeQL bundle
version to 2.19.4. <a
href="https://redirect.github.com/github/codeql-action/pull/3894">#3894</a></li>
<li>Add support for SHA-256 Git object IDs. <a
href="https://redirect.github.com/github/codeql-action/pull/3893">#3893</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.5">2.25.5</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3926">#3926</a></li>
</ul>
<h2>4.35.5 - 15 May 2026</h2>
<ul>
<li>We have improved how the JavaScript bundles for the CodeQL Action
are generated to avoid duplication across bundles and reduce the size of
the repository by around 70%. This should have no effect on the runtime
behaviour of the CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3899">#3899</a></li>
<li>For performance and accuracy reasons, <a
href="https://redirect.github.com/github/roadmap/issues/1158">improved
incremental analysis</a> will now only be enabled on a pull request when
diff-informed analysis is also enabled for that run. If diff-informed
analysis is unavailable (for example, because the PR diff ranges could
not be computed), the action will fall back to a full analysis. <a
href="https://redirect.github.com/github/codeql-action/pull/3791">#3791</a></li>
<li>If multiple inputs are provided for the GitHub-internal
<code>analysis-kinds</code> input, only <code>code-scanning</code> will
be enabled. The <code>analysis-kinds</code> input is experimental, for
GitHub-internal use only, and may change without notice at any time. <a
href="https://redirect.github.com/github/codeql-action/pull/3892">#3892</a></li>
<li>Added an experimental change which, when running a Code Scanning
analysis for a PR with <a
href="https://redirect.github.com/github/roadmap/issues/1158">improved
incremental analysis</a> enabled, prefers CodeQL CLI versions that have
a cached overlay-base database for the configured languages. This speeds
up analysis for a repository when there is not yet a cached overlay-base
database for the latest CLI version. We expect to roll this change out
to everyone in May. <a
href="https://redirect.github.com/github/codeql-action/pull/3880">#3880</a></li>
</ul>
<h2>4.35.4 - 07 May 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.4">2.25.4</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3881">#3881</a></li>
</ul>
<h2>4.35.3 - 01 May 2026</h2>
<ul>
<li><em>Upcoming breaking change</em>: Add a deprecation warning for
customers using CodeQL version 2.19.3 and earlier. These versions of
CodeQL were discontinued on 9 April 2026 alongside GitHub Enterprise
Server 3.15, and will be unsupported by the next minor release of the
CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3837">#3837</a></li>
<li>Configurations for private registries that use Cloudsmith or GCP
OIDC are now accepted. <a
href="https://redirect.github.com/github/codeql-action/pull/3850">#3850</a></li>
<li>Best-effort connection tests for private registries now use
<code>GET</code> requests instead of <code>HEAD</code> for better
compatibility with various registry implementations. For NuGet feeds,
the test is now always performed against the service index. <a
href="https://redirect.github.com/github/codeql-action/pull/3853">#3853</a></li>
<li>Fixed a bug where two diagnostics produced within the same
millisecond could overwrite each other on disk, causing one of them to
be lost. <a
href="https://redirect.github.com/github/codeql-action/pull/3852">#3852</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.3">2.25.3</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3865">#3865</a></li>
</ul>
<h2>4.35.2 - 15 Apr 2026</h2>
<ul>
<li>The undocumented TRAP cache cleanup feature that could be enabled
using the <code>CODEQL_ACTION_CLEANUP_TRAP_CACHES</code> environment
variable is deprecated and will be removed in May 2026. If you are
affected by this, we recommend disabling TRAP caching by passing the
<code>trap-caching: false</code> input to the <code>init</code> Action.
<a
href="https://redirect.github.com/github/codeql-action/pull/3795">#3795</a></li>
<li>The Git version 2.36.0 requirement for improved incremental analysis
now only applies to repositories that contain submodules. <a
href="https://redirect.github.com/github/codeql-action/pull/3789">#3789</a></li>
<li>Python analysis on GHES no longer extracts the standard library,
relying instead on models of the standard library. This should result in
significantly faster extraction and analysis times, while the effect on
alerts should be minimal. <a
href="https://redirect.github.com/github/codeql-action/pull/3794">#3794</a></li>
<li>Fixed a bug in the validation of OIDC configurations for private
registries that was added in CodeQL Action 4.33.0 / 3.33.0. <a
href="https://redirect.github.com/github/codeql-action/pull/3807">#3807</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.2">2.25.2</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3823">#3823</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="8aad20d150"><code>8aad20d</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/3949">#3949</a>
from github/update-v4.36.2-dcb947ce1</li>
<li><a
href="f521b08cd8"><code>f521b08</code></a>
Add additional changelog notes</li>
<li><a
href="8aeff0ffb7"><code>8aeff0f</code></a>
Update changelog for v4.36.2</li>
<li><a
href="dcb947ce15"><code>dcb947c</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/3948">#3948</a>
from github/update-bundle/codeql-bundle-v2.25.6</li>
<li><a
href="c251bcefa1"><code>c251bce</code></a>
Add changelog note</li>
<li><a
href="62953c18b3"><code>62953c1</code></a>
Update default bundle to codeql-bundle-v2.25.6</li>
<li><a
href="423b570baf"><code>423b570</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/3946">#3946</a>
from github/dependabot/npm_and_yarn/npm-minor-5d507a...</li>
<li><a
href="c35d1b1644"><code>c35d1b1</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/3947">#3947</a>
from github/dependabot/github_actions/dot-github/wor...</li>
<li><a
href="cb1a588b02"><code>cb1a588</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/3937">#3937</a>
from github/robertbrignull/waitForProcessing_backoff</li>
<li><a
href="ba47406412"><code>ba47406</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/3943">#3943</a>
from github/henrymercer/cache-cli-version-info</li>
<li>Additional commits viewable in <a
href="e46ed2cbd0...8aad20d150">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github/codeql-action/init&package-manager=github_actions&previous-version=4.35.3&new-version=4.36.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

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

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

---

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

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-14 18:44:05 +03:00
dependabot[bot]
22232d94f8 build(deps): bump github/codeql-action/analyze from 4.35.3 to 4.36.2 (#11175)
Bumps
[github/codeql-action/analyze](https://github.com/github/codeql-action)
from 4.35.3 to 4.36.2.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/releases">github/codeql-action/analyze's
releases</a>.</em></p>
<blockquote>
<h2>v4.36.2</h2>
<ul>
<li>Cache CodeQL CLI version information across Actions steps. <a
href="https://redirect.github.com/github/codeql-action/pull/3943">#3943</a></li>
<li>Reduce requests while waiting for analysis processing by using
exponential backoff when polling SARIF processing status. <a
href="https://redirect.github.com/github/codeql-action/pull/3937">#3937</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.6">2.25.6</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3948">#3948</a></li>
</ul>
<h2>v4.36.1</h2>
<p>No user facing changes.</p>
<h2>v4.36.0</h2>
<ul>
<li><em>Breaking change</em>: Bump the minimum required CodeQL bundle
version to 2.19.4. <a
href="https://redirect.github.com/github/codeql-action/pull/3894">#3894</a></li>
<li>Add support for SHA-256 Git object IDs. <a
href="https://redirect.github.com/github/codeql-action/pull/3893">#3893</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.5">2.25.5</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3926">#3926</a></li>
</ul>
<h2>v4.35.5</h2>
<ul>
<li>We have improved how the JavaScript bundles for the CodeQL Action
are generated to avoid duplication across bundles and reduce the size of
the repository by around 70%. This should have no effect on the runtime
behaviour of the CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3899">#3899</a></li>
<li>For performance and accuracy reasons, <a
href="https://redirect.github.com/github/roadmap/issues/1158">improved
incremental analysis</a> will now only be enabled on a pull request when
diff-informed analysis is also enabled for that run. If diff-informed
analysis is unavailable (for example, because the PR diff ranges could
not be computed), the action will fall back to a full analysis. <a
href="https://redirect.github.com/github/codeql-action/pull/3791">#3791</a></li>
<li>If multiple inputs are provided for the GitHub-internal
<code>analysis-kinds</code> input, only <code>code-scanning</code> will
be enabled. The <code>analysis-kinds</code> input is experimental, for
GitHub-internal use only, and may change without notice at any time. <a
href="https://redirect.github.com/github/codeql-action/pull/3892">#3892</a></li>
<li>Added an experimental change which, when running a Code Scanning
analysis for a PR with <a
href="https://redirect.github.com/github/roadmap/issues/1158">improved
incremental analysis</a> enabled, prefers CodeQL CLI versions that have
a cached overlay-base database for the configured languages. This speeds
up analysis for a repository when there is not yet a cached overlay-base
database for the latest CLI version. We expect to roll this change out
to everyone in May. <a
href="https://redirect.github.com/github/codeql-action/pull/3880">#3880</a></li>
</ul>
<h2>v4.35.4</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.4">2.25.4</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3881">#3881</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/blob/main/CHANGELOG.md">github/codeql-action/analyze's
changelog</a>.</em></p>
<blockquote>
<h1>CodeQL Action Changelog</h1>
<p>See the <a
href="https://github.com/github/codeql-action/releases">releases
page</a> for the relevant changes to the CodeQL CLI and language
packs.</p>
<h2>[UNRELEASED]</h2>
<p>No user facing changes.</p>
<h2>4.36.2 - 04 Jun 2026</h2>
<ul>
<li>Cache CodeQL CLI version information across Actions steps. <a
href="https://redirect.github.com/github/codeql-action/pull/3943">#3943</a></li>
<li>Reduce requests while waiting for analysis processing by using
exponential backoff when polling SARIF processing status. <a
href="https://redirect.github.com/github/codeql-action/pull/3937">#3937</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.6">2.25.6</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3948">#3948</a></li>
</ul>
<h2>4.36.1 - 02 Jun 2026</h2>
<p>No user facing changes.</p>
<h2>4.36.0 - 22 May 2026</h2>
<ul>
<li><em>Breaking change</em>: Bump the minimum required CodeQL bundle
version to 2.19.4. <a
href="https://redirect.github.com/github/codeql-action/pull/3894">#3894</a></li>
<li>Add support for SHA-256 Git object IDs. <a
href="https://redirect.github.com/github/codeql-action/pull/3893">#3893</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.5">2.25.5</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3926">#3926</a></li>
</ul>
<h2>4.35.5 - 15 May 2026</h2>
<ul>
<li>We have improved how the JavaScript bundles for the CodeQL Action
are generated to avoid duplication across bundles and reduce the size of
the repository by around 70%. This should have no effect on the runtime
behaviour of the CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3899">#3899</a></li>
<li>For performance and accuracy reasons, <a
href="https://redirect.github.com/github/roadmap/issues/1158">improved
incremental analysis</a> will now only be enabled on a pull request when
diff-informed analysis is also enabled for that run. If diff-informed
analysis is unavailable (for example, because the PR diff ranges could
not be computed), the action will fall back to a full analysis. <a
href="https://redirect.github.com/github/codeql-action/pull/3791">#3791</a></li>
<li>If multiple inputs are provided for the GitHub-internal
<code>analysis-kinds</code> input, only <code>code-scanning</code> will
be enabled. The <code>analysis-kinds</code> input is experimental, for
GitHub-internal use only, and may change without notice at any time. <a
href="https://redirect.github.com/github/codeql-action/pull/3892">#3892</a></li>
<li>Added an experimental change which, when running a Code Scanning
analysis for a PR with <a
href="https://redirect.github.com/github/roadmap/issues/1158">improved
incremental analysis</a> enabled, prefers CodeQL CLI versions that have
a cached overlay-base database for the configured languages. This speeds
up analysis for a repository when there is not yet a cached overlay-base
database for the latest CLI version. We expect to roll this change out
to everyone in May. <a
href="https://redirect.github.com/github/codeql-action/pull/3880">#3880</a></li>
</ul>
<h2>4.35.4 - 07 May 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.4">2.25.4</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3881">#3881</a></li>
</ul>
<h2>4.35.3 - 01 May 2026</h2>
<ul>
<li><em>Upcoming breaking change</em>: Add a deprecation warning for
customers using CodeQL version 2.19.3 and earlier. These versions of
CodeQL were discontinued on 9 April 2026 alongside GitHub Enterprise
Server 3.15, and will be unsupported by the next minor release of the
CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3837">#3837</a></li>
<li>Configurations for private registries that use Cloudsmith or GCP
OIDC are now accepted. <a
href="https://redirect.github.com/github/codeql-action/pull/3850">#3850</a></li>
<li>Best-effort connection tests for private registries now use
<code>GET</code> requests instead of <code>HEAD</code> for better
compatibility with various registry implementations. For NuGet feeds,
the test is now always performed against the service index. <a
href="https://redirect.github.com/github/codeql-action/pull/3853">#3853</a></li>
<li>Fixed a bug where two diagnostics produced within the same
millisecond could overwrite each other on disk, causing one of them to
be lost. <a
href="https://redirect.github.com/github/codeql-action/pull/3852">#3852</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.3">2.25.3</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3865">#3865</a></li>
</ul>
<h2>4.35.2 - 15 Apr 2026</h2>
<ul>
<li>The undocumented TRAP cache cleanup feature that could be enabled
using the <code>CODEQL_ACTION_CLEANUP_TRAP_CACHES</code> environment
variable is deprecated and will be removed in May 2026. If you are
affected by this, we recommend disabling TRAP caching by passing the
<code>trap-caching: false</code> input to the <code>init</code> Action.
<a
href="https://redirect.github.com/github/codeql-action/pull/3795">#3795</a></li>
<li>The Git version 2.36.0 requirement for improved incremental analysis
now only applies to repositories that contain submodules. <a
href="https://redirect.github.com/github/codeql-action/pull/3789">#3789</a></li>
<li>Python analysis on GHES no longer extracts the standard library,
relying instead on models of the standard library. This should result in
significantly faster extraction and analysis times, while the effect on
alerts should be minimal. <a
href="https://redirect.github.com/github/codeql-action/pull/3794">#3794</a></li>
<li>Fixed a bug in the validation of OIDC configurations for private
registries that was added in CodeQL Action 4.33.0 / 3.33.0. <a
href="https://redirect.github.com/github/codeql-action/pull/3807">#3807</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.2">2.25.2</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3823">#3823</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="8aad20d150"><code>8aad20d</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/3949">#3949</a>
from github/update-v4.36.2-dcb947ce1</li>
<li><a
href="f521b08cd8"><code>f521b08</code></a>
Add additional changelog notes</li>
<li><a
href="8aeff0ffb7"><code>8aeff0f</code></a>
Update changelog for v4.36.2</li>
<li><a
href="dcb947ce15"><code>dcb947c</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/3948">#3948</a>
from github/update-bundle/codeql-bundle-v2.25.6</li>
<li><a
href="c251bcefa1"><code>c251bce</code></a>
Add changelog note</li>
<li><a
href="62953c18b3"><code>62953c1</code></a>
Update default bundle to codeql-bundle-v2.25.6</li>
<li><a
href="423b570baf"><code>423b570</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/3946">#3946</a>
from github/dependabot/npm_and_yarn/npm-minor-5d507a...</li>
<li><a
href="c35d1b1644"><code>c35d1b1</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/3947">#3947</a>
from github/dependabot/github_actions/dot-github/wor...</li>
<li><a
href="cb1a588b02"><code>cb1a588</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/3937">#3937</a>
from github/robertbrignull/waitForProcessing_backoff</li>
<li><a
href="ba47406412"><code>ba47406</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/3943">#3943</a>
from github/henrymercer/cache-cli-version-info</li>
<li>Additional commits viewable in <a
href="e46ed2cbd0...8aad20d150">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github/codeql-action/analyze&package-manager=github_actions&previous-version=4.35.3&new-version=4.36.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

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

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

---

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

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-14 18:43:36 +03:00
dependabot[bot]
6814a7ea2b build(deps): bump github/codeql-action from 4.35.3 to 4.36.2 (#11168)
Bumps [github/codeql-action](https://github.com/github/codeql-action)
from 4.35.3 to 4.36.2.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/releases">github/codeql-action's
releases</a>.</em></p>
<blockquote>
<h2>v4.36.2</h2>
<ul>
<li>Cache CodeQL CLI version information across Actions steps. <a
href="https://redirect.github.com/github/codeql-action/pull/3943">#3943</a></li>
<li>Reduce requests while waiting for analysis processing by using
exponential backoff when polling SARIF processing status. <a
href="https://redirect.github.com/github/codeql-action/pull/3937">#3937</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.6">2.25.6</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3948">#3948</a></li>
</ul>
<h2>v4.36.1</h2>
<p>No user facing changes.</p>
<h2>v4.36.0</h2>
<ul>
<li><em>Breaking change</em>: Bump the minimum required CodeQL bundle
version to 2.19.4. <a
href="https://redirect.github.com/github/codeql-action/pull/3894">#3894</a></li>
<li>Add support for SHA-256 Git object IDs. <a
href="https://redirect.github.com/github/codeql-action/pull/3893">#3893</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.5">2.25.5</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3926">#3926</a></li>
</ul>
<h2>v4.35.5</h2>
<ul>
<li>We have improved how the JavaScript bundles for the CodeQL Action
are generated to avoid duplication across bundles and reduce the size of
the repository by around 70%. This should have no effect on the runtime
behaviour of the CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3899">#3899</a></li>
<li>For performance and accuracy reasons, <a
href="https://redirect.github.com/github/roadmap/issues/1158">improved
incremental analysis</a> will now only be enabled on a pull request when
diff-informed analysis is also enabled for that run. If diff-informed
analysis is unavailable (for example, because the PR diff ranges could
not be computed), the action will fall back to a full analysis. <a
href="https://redirect.github.com/github/codeql-action/pull/3791">#3791</a></li>
<li>If multiple inputs are provided for the GitHub-internal
<code>analysis-kinds</code> input, only <code>code-scanning</code> will
be enabled. The <code>analysis-kinds</code> input is experimental, for
GitHub-internal use only, and may change without notice at any time. <a
href="https://redirect.github.com/github/codeql-action/pull/3892">#3892</a></li>
<li>Added an experimental change which, when running a Code Scanning
analysis for a PR with <a
href="https://redirect.github.com/github/roadmap/issues/1158">improved
incremental analysis</a> enabled, prefers CodeQL CLI versions that have
a cached overlay-base database for the configured languages. This speeds
up analysis for a repository when there is not yet a cached overlay-base
database for the latest CLI version. We expect to roll this change out
to everyone in May. <a
href="https://redirect.github.com/github/codeql-action/pull/3880">#3880</a></li>
</ul>
<h2>v4.35.4</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.4">2.25.4</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3881">#3881</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/blob/main/CHANGELOG.md">github/codeql-action's
changelog</a>.</em></p>
<blockquote>
<h1>CodeQL Action Changelog</h1>
<p>See the <a
href="https://github.com/github/codeql-action/releases">releases
page</a> for the relevant changes to the CodeQL CLI and language
packs.</p>
<h2>[UNRELEASED]</h2>
<p>No user facing changes.</p>
<h2>4.36.2 - 04 Jun 2026</h2>
<ul>
<li>Cache CodeQL CLI version information across Actions steps. <a
href="https://redirect.github.com/github/codeql-action/pull/3943">#3943</a></li>
<li>Reduce requests while waiting for analysis processing by using
exponential backoff when polling SARIF processing status. <a
href="https://redirect.github.com/github/codeql-action/pull/3937">#3937</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.6">2.25.6</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3948">#3948</a></li>
</ul>
<h2>4.36.1 - 02 Jun 2026</h2>
<p>No user facing changes.</p>
<h2>4.36.0 - 22 May 2026</h2>
<ul>
<li><em>Breaking change</em>: Bump the minimum required CodeQL bundle
version to 2.19.4. <a
href="https://redirect.github.com/github/codeql-action/pull/3894">#3894</a></li>
<li>Add support for SHA-256 Git object IDs. <a
href="https://redirect.github.com/github/codeql-action/pull/3893">#3893</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.5">2.25.5</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3926">#3926</a></li>
</ul>
<h2>4.35.5 - 15 May 2026</h2>
<ul>
<li>We have improved how the JavaScript bundles for the CodeQL Action
are generated to avoid duplication across bundles and reduce the size of
the repository by around 70%. This should have no effect on the runtime
behaviour of the CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3899">#3899</a></li>
<li>For performance and accuracy reasons, <a
href="https://redirect.github.com/github/roadmap/issues/1158">improved
incremental analysis</a> will now only be enabled on a pull request when
diff-informed analysis is also enabled for that run. If diff-informed
analysis is unavailable (for example, because the PR diff ranges could
not be computed), the action will fall back to a full analysis. <a
href="https://redirect.github.com/github/codeql-action/pull/3791">#3791</a></li>
<li>If multiple inputs are provided for the GitHub-internal
<code>analysis-kinds</code> input, only <code>code-scanning</code> will
be enabled. The <code>analysis-kinds</code> input is experimental, for
GitHub-internal use only, and may change without notice at any time. <a
href="https://redirect.github.com/github/codeql-action/pull/3892">#3892</a></li>
<li>Added an experimental change which, when running a Code Scanning
analysis for a PR with <a
href="https://redirect.github.com/github/roadmap/issues/1158">improved
incremental analysis</a> enabled, prefers CodeQL CLI versions that have
a cached overlay-base database for the configured languages. This speeds
up analysis for a repository when there is not yet a cached overlay-base
database for the latest CLI version. We expect to roll this change out
to everyone in May. <a
href="https://redirect.github.com/github/codeql-action/pull/3880">#3880</a></li>
</ul>
<h2>4.35.4 - 07 May 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.4">2.25.4</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3881">#3881</a></li>
</ul>
<h2>4.35.3 - 01 May 2026</h2>
<ul>
<li><em>Upcoming breaking change</em>: Add a deprecation warning for
customers using CodeQL version 2.19.3 and earlier. These versions of
CodeQL were discontinued on 9 April 2026 alongside GitHub Enterprise
Server 3.15, and will be unsupported by the next minor release of the
CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3837">#3837</a></li>
<li>Configurations for private registries that use Cloudsmith or GCP
OIDC are now accepted. <a
href="https://redirect.github.com/github/codeql-action/pull/3850">#3850</a></li>
<li>Best-effort connection tests for private registries now use
<code>GET</code> requests instead of <code>HEAD</code> for better
compatibility with various registry implementations. For NuGet feeds,
the test is now always performed against the service index. <a
href="https://redirect.github.com/github/codeql-action/pull/3853">#3853</a></li>
<li>Fixed a bug where two diagnostics produced within the same
millisecond could overwrite each other on disk, causing one of them to
be lost. <a
href="https://redirect.github.com/github/codeql-action/pull/3852">#3852</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.3">2.25.3</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3865">#3865</a></li>
</ul>
<h2>4.35.2 - 15 Apr 2026</h2>
<ul>
<li>The undocumented TRAP cache cleanup feature that could be enabled
using the <code>CODEQL_ACTION_CLEANUP_TRAP_CACHES</code> environment
variable is deprecated and will be removed in May 2026. If you are
affected by this, we recommend disabling TRAP caching by passing the
<code>trap-caching: false</code> input to the <code>init</code> Action.
<a
href="https://redirect.github.com/github/codeql-action/pull/3795">#3795</a></li>
<li>The Git version 2.36.0 requirement for improved incremental analysis
now only applies to repositories that contain submodules. <a
href="https://redirect.github.com/github/codeql-action/pull/3789">#3789</a></li>
<li>Python analysis on GHES no longer extracts the standard library,
relying instead on models of the standard library. This should result in
significantly faster extraction and analysis times, while the effect on
alerts should be minimal. <a
href="https://redirect.github.com/github/codeql-action/pull/3794">#3794</a></li>
<li>Fixed a bug in the validation of OIDC configurations for private
registries that was added in CodeQL Action 4.33.0 / 3.33.0. <a
href="https://redirect.github.com/github/codeql-action/pull/3807">#3807</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.2">2.25.2</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3823">#3823</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="8aad20d150"><code>8aad20d</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/3949">#3949</a>
from github/update-v4.36.2-dcb947ce1</li>
<li><a
href="f521b08cd8"><code>f521b08</code></a>
Add additional changelog notes</li>
<li><a
href="8aeff0ffb7"><code>8aeff0f</code></a>
Update changelog for v4.36.2</li>
<li><a
href="dcb947ce15"><code>dcb947c</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/3948">#3948</a>
from github/update-bundle/codeql-bundle-v2.25.6</li>
<li><a
href="c251bcefa1"><code>c251bce</code></a>
Add changelog note</li>
<li><a
href="62953c18b3"><code>62953c1</code></a>
Update default bundle to codeql-bundle-v2.25.6</li>
<li><a
href="423b570baf"><code>423b570</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/3946">#3946</a>
from github/dependabot/npm_and_yarn/npm-minor-5d507a...</li>
<li><a
href="c35d1b1644"><code>c35d1b1</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/3947">#3947</a>
from github/dependabot/github_actions/dot-github/wor...</li>
<li><a
href="cb1a588b02"><code>cb1a588</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/3937">#3937</a>
from github/robertbrignull/waitForProcessing_backoff</li>
<li><a
href="ba47406412"><code>ba47406</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/3943">#3943</a>
from github/henrymercer/cache-cli-version-info</li>
<li>Additional commits viewable in <a
href="e46ed2cbd0...8aad20d150">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github/codeql-action&package-manager=github_actions&previous-version=4.35.3&new-version=4.36.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

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

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

---

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

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-14 18:43:06 +03:00
dependabot[bot]
00582e5f35 build(deps): bump actions/checkout from 6.0.2 to 6.0.3 (#11151)
Bumps [actions/checkout](https://github.com/actions/checkout) from 6.0.2
to 6.0.3.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/checkout/releases">actions/checkout's
releases</a>.</em></p>
<blockquote>
<h2>v6.0.3</h2>
<h2>What's Changed</h2>
<ul>
<li>Update changelog by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2357">actions/checkout#2357</a></li>
<li>fix: expand merge commit SHA regex and add SHA-256 test cases by <a
href="https://github.com/yaananth"><code>@​yaananth</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2414">actions/checkout#2414</a></li>
<li>Fix checkout init for SHA-256 repositories by <a
href="https://github.com/yaananth"><code>@​yaananth</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2439">actions/checkout#2439</a></li>
<li>Update changelog for v6.0.3 by <a
href="https://github.com/yaananth"><code>@​yaananth</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2446">actions/checkout#2446</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/yaananth"><code>@​yaananth</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/checkout/pull/2414">actions/checkout#2414</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/checkout/compare/v6...v6.0.3">https://github.com/actions/checkout/compare/v6...v6.0.3</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/actions/checkout/blob/main/CHANGELOG.md">actions/checkout's
changelog</a>.</em></p>
<blockquote>
<h1>Changelog</h1>
<h2>v7.0.0</h2>
<ul>
<li>Block checking out fork PR for pull_request_target and workflow_run
by <a href="https://github.com/aiqiaoy"><code>@​aiqiaoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2454">actions/checkout#2454</a></li>
<li>Bump actions/publish-immutable-action from 0.0.3 to 0.0.4 in the
minor-actions-dependencies group across 1 directory by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/checkout/pull/2458">actions/checkout#2458</a></li>
<li>Bump flatted from 3.3.1 to 3.4.2 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/checkout/pull/2460">actions/checkout#2460</a></li>
<li>Bump js-yaml from 4.1.0 to 4.2.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/checkout/pull/2461">actions/checkout#2461</a></li>
<li>Bump <code>@​actions/core</code> and
<code>@​actions/tool-cache</code> and Remove uuid by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/checkout/pull/2459">actions/checkout#2459</a></li>
<li>upgrade module to esm and update dependencies by <a
href="https://github.com/aiqiaoy"><code>@​aiqiaoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2463">actions/checkout#2463</a></li>
<li>Bump the minor-npm-dependencies group across 1 directory with 3
updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/checkout/pull/2462">actions/checkout#2462</a></li>
</ul>
<h2>v6.0.3</h2>
<ul>
<li>Fix checkout init for SHA-256 repositories by <a
href="https://github.com/yaananth"><code>@​yaananth</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2439">actions/checkout#2439</a></li>
<li>fix: expand merge commit SHA regex and add SHA-256 test cases by <a
href="https://github.com/yaananth"><code>@​yaananth</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2414">actions/checkout#2414</a></li>
</ul>
<h2>v6.0.2</h2>
<ul>
<li>Fix tag handling: preserve annotations and explicit fetch-tags by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2356">actions/checkout#2356</a></li>
</ul>
<h2>v6.0.1</h2>
<ul>
<li>Add worktree support for persist-credentials includeIf by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2327">actions/checkout#2327</a></li>
</ul>
<h2>v6.0.0</h2>
<ul>
<li>Persist creds to a separate file by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2286">actions/checkout#2286</a></li>
<li>Update README to include Node.js 24 support details and requirements
by <a href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a>
in <a
href="https://redirect.github.com/actions/checkout/pull/2248">actions/checkout#2248</a></li>
</ul>
<h2>v5.0.1</h2>
<ul>
<li>Port v6 cleanup to v5 by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2301">actions/checkout#2301</a></li>
</ul>
<h2>v5.0.0</h2>
<ul>
<li>Update actions checkout to use node 24 by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2226">actions/checkout#2226</a></li>
</ul>
<h2>v4.3.1</h2>
<ul>
<li>Port v6 cleanup to v4 by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2305">actions/checkout#2305</a></li>
</ul>
<h2>v4.3.0</h2>
<ul>
<li>docs: update README.md by <a
href="https://github.com/motss"><code>@​motss</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1971">actions/checkout#1971</a></li>
<li>Add internal repos for checking out multiple repositories by <a
href="https://github.com/mouismail"><code>@​mouismail</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1977">actions/checkout#1977</a></li>
<li>Documentation update - add recommended permissions to Readme by <a
href="https://github.com/benwells"><code>@​benwells</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2043">actions/checkout#2043</a></li>
<li>Adjust positioning of user email note and permissions heading by <a
href="https://github.com/joshmgross"><code>@​joshmgross</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2044">actions/checkout#2044</a></li>
<li>Update README.md by <a
href="https://github.com/nebuk89"><code>@​nebuk89</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2194">actions/checkout#2194</a></li>
<li>Update CODEOWNERS for actions by <a
href="https://github.com/TingluoHuang"><code>@​TingluoHuang</code></a>
in <a
href="https://redirect.github.com/actions/checkout/pull/2224">actions/checkout#2224</a></li>
<li>Update package dependencies by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2236">actions/checkout#2236</a></li>
</ul>
<h2>v4.2.2</h2>
<ul>
<li><code>url-helper.ts</code> now leverages well-known environment
variables by <a href="https://github.com/jww3"><code>@​jww3</code></a>
in <a
href="https://redirect.github.com/actions/checkout/pull/1941">actions/checkout#1941</a></li>
<li>Expand unit test coverage for <code>isGhes</code> by <a
href="https://github.com/jww3"><code>@​jww3</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1946">actions/checkout#1946</a></li>
</ul>
<h2>v4.2.1</h2>
<ul>
<li>Check out other refs/* by commit if provided, fall back to ref by <a
href="https://github.com/orhantoy"><code>@​orhantoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1924">actions/checkout#1924</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="df4cb1c069"><code>df4cb1c</code></a>
Update changelog for v6.0.3 (<a
href="https://redirect.github.com/actions/checkout/issues/2446">#2446</a>)</li>
<li><a
href="1cce3390c2"><code>1cce339</code></a>
Fix checkout init for SHA-256 repositories (<a
href="https://redirect.github.com/actions/checkout/issues/2439">#2439</a>)</li>
<li><a
href="900f2210b1"><code>900f221</code></a>
fix: expand merge commit SHA regex and add SHA-256 test cases (<a
href="https://redirect.github.com/actions/checkout/issues/2414">#2414</a>)</li>
<li><a
href="0c366fd6a8"><code>0c366fd</code></a>
Update changelog (<a
href="https://redirect.github.com/actions/checkout/issues/2357">#2357</a>)</li>
<li>See full diff in <a
href="de0fac2e45...df4cb1c069">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=6.0.2&new-version=6.0.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

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

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

---

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

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-14 18:42:38 +03:00
Max Kotliar
fe3865a5fd docs/changelog: move bugfix to tip; follow-up on baf1dd5a 2026-07-14 18:31:47 +03:00
Yury Moladau
baf1dd5a3b app/vmui: preserve line breaks in alert annotations (#11212)
Preserve multiline formatting for alert and rule annotations on the Alerting page.

Fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11171
PR https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11212

Signed-off-by: Yury Molodov <yurymolodov@gmail.com>
2026-07-14 18:24:18 +03:00
Roman Khavronenko
be9a1055ed docs: add OpenTelemetry integration article (#11245)
See
https://itnext.io/opentelemetry-otel-collectors-in-kubernetes-and-victoriametrics-stack-integration-d907ed0a15a0

Signed-off-by: hagen1778 <roman@victoriametrics.com>
2026-07-14 18:21:38 +03:00
Pablo (Tomas) Fernandez
dd65bbe903 docs: fix broken anchor in stream-aggregation page (#11246)
Addresses part of: https://github.com/VictoriaMetrics/vmdocs/issues/221
2026-07-14 18:21:08 +03:00
JAYICE
1708995801 vmagent: fix the bug that cause ts array being not returned to object pool. (#11255)
Fix a regression that prevented time series slices from being returned
to the object pool after processing remote write data. As a result, the
slices couldn't be reused, increasing CPU and memory usage.

The issue affects `vmagent` instances configured with either
`-remoteWrite.urlRelabelConfig` or `-remoteWrite.streamAggr.config`.

The regression was introduced in
https://github.com/VictoriaMetrics/VictoriaMetrics/pull/10854 and first
released in v1.147.0.

Fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11250.

---------

Signed-off-by: “Jayice” <jzhou@victoriametrics.com>
Signed-off-by: JAYICE <1185430411@qq.com>
Co-authored-by: Max Kotliar <mkotlyar@victoriametrics.com>
2026-07-14 18:15:32 +03:00
JAYICE
14c8c8b4fe docs/changelog: move MDX changelog entry from v1.146.0 to v1.147.0 (#11251)
The MDX feature is actually in v1.147.0, but it was placed in v1.146.0 changelog somehow.

Signed-off-by: “Jayice” <jzhou@victoriametrics.com>
2026-07-14 17:51:57 +03:00
JAYICE
eee1bd0e27 lib/storage: fix flaky TestStorageAddRows_currHourMetricIDs (#11247)
The test becomes flaky when run at the moment when time advances from from current hour to next hour.

See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11241
2026-07-14 14:14:18 +02:00
Max Kotliar
8952c5b290 docs/changelog: order bugfix properly 2026-07-13 14:27:55 +03:00
Artem Fetishev
9c1ea5d5ad lib/storage: lockless creation of in-memory parts (#11236)
When converting `rawRows` to in-memory parts, it is possible to do it
without a mutex.

Signed-off-by: Artem Fetishev <rtm@victoriametrics.com>
2026-07-13 12:56:23 +02:00
Artem Fetishev
eed5299f84 lib/storage: Move rawRowsShards to raw_row.go (#11233)
Currently, `rawRowsShard` and `rawRowsShards` lives in
`lib/partition.go` file. But these types and the `partition` type are
only loosely coupled. While the `rawRows` logic is scattered across two
files `lib/raw_row.go` and `lib/partition.go`.

This PR moves `rawRowsShard` and `rawRowsShards` and related constants
to `raw_row.go`. This allows to keep related funtionality together and
view raw row types as functionality that is independent from the
`partition` type.

Additionally, the `rawRowsShards` and the `partition` types were
decoupled by using the `func([][]rawRow)` callback.

---------

Signed-off-by: Artem Fetishev <rtm@victoriametrics.com>
2026-07-13 12:17:26 +02:00
JAYICE
c1e39b2cef lib/persistentqueue: flush pending persistent queue data to chunk file before updating metadata
Previously, reader could advance writer offset at metadata without actual data write.
It may produce corrupted persistent queue, if vmagent crashed.

 This commit changes flush behavior to perform f-sync flush, only if there is some recently written data.

Related to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11192
2026-07-10 17:52:30 +02:00
Nikolay
0634599809 lib/promscrape/http: add background discovery for http_sd
Previously, slow http_sd target may slow discovery pipeline. And for
example, if configuration contains 100 targets which respond in 1
second. It will take 100 seconds to refresh targets.

  This commit adds background discovery for `http_sd` scrape config.
A new goroutine starts for each http_sd url. Which reduces discovery
time.

Fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8838
2026-07-10 16:54:47 +02:00
Samarth Bagga
fccd724cd7 lib/storage: add metric for memory occupied by metadataindex
This commit adds new metrics:
* vm_data_size_bytes{type="storage/metaindex"}
* vm_data_size_bytes{type="indexdb/metaindex"}

Which are useful to track memory used to store meta index.

Related to https://github.com/VictoriaMetrics/VictoriaMetrics/issues/10203
2026-07-10 16:01:35 +02:00
Evgeny Martyn
dedf4563d2 app/vmagent: fix opentelemetry metadata race (#11238)
Fixes a possible data race in `vmagent` when processing OpenTelemetry
metric metadata.

The OpenTelemetry stream parser passes metadata to `insertRows`:

[streamparser.go#L87](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/lib/protoparser/opentelemetry/stream/streamparser.go#L87)

Before this commit, `insertRows` assigned that slice directly to
`ctx.WriteRequest.Metadata`:

[request_handler.go#L85](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/app/vmagent/opentelemetry/request_handler.go#L85)

As a result, the write request could keep a reference to the
parser-owned metadata backing array. The parser later reuses and resets
its internal buffers, while the remote write path may still read the
queued write request:

- `PushCtx` / `WriteRequest` reset path:
[prompb.go#L21](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/lib/prompb/prompb.go#L21)
- OpenTelemetry `writeRequestContext` reset path:
[streamparser.go#L145](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/lib/protoparser/opentelemetry/stream/streamparser.go#L145)

This may put the same metadata backing array under two independent
pools:

- `PushCtx` pool:
[push_ctx.go#L52](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/app/vmagent/common/push_ctx.go#L52)
- OpenTelemetry parser context pool:
[streamparser.go#L253](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/lib/protoparser/opentelemetry/stream/streamparser.go#L253)

This commit fixes it by making a copy of Metadata entries into the `PushCtx`-owned
`WriteRequest.Metadata` buffer before calling `remotewrite.TryPush`.

The same approach is already used by the prom remote write handler:

[promremotewrite/request_handler.go#L78](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/app/vmagent/promremotewrite/request_handler.go#L78)
```
		for i := range mms {
			mm := &mms[i]
			mmsDst = append(mmsDst, prompb.MetricMetadata{
				MetricFamilyName: mm.MetricFamilyName,
				Help:             mm.Help,
				Type:             mm.Type,
				Unit:             mm.Unit,

				AccountID: mm.AccountID,
				ProjectID: mm.ProjectID,
			})
		}
		ctx.WriteRequest.Metadata = mmsDst
		metadataTotal = len(mms)
```

Related PR https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11238
2026-07-10 15:43:34 +02:00
Artem Fetishev
a0c60b4027 changelog: Thank contributor
Signed-off-by: Artem Fetishev <rtm@victoriametrics.com>
2026-07-10 13:39:21 +02:00
Ashwin Ramani
096bb8fdeb app/vmstorage: add -maxBackfillAge command-line flag (#11228)
Allows rejecting ingested samples with historical timestamps older than
now-maxBackfillAge, independently of -retentionPeriod. This is useful
for limiting ingestion of historical samples, for example when older
data has been moved between storage tiers (nvme/hdd, hot/cold).

Mirrors the -maxBackfillAge flag already available in VictoriaLogs.
Defaults to 0, in which case it is clamped to -retentionPeriod,
preserving previous ingestion behavior.

Fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11199

---------

Signed-off-by: Ashwin Ramani <ashwinramanipsg@gmail.com>
Co-authored-by: Artem Fetishev <rtm@victoriametrics.com>
2026-07-10 13:03:16 +02:00
Max Kotliar
eaa39f8194 docs: mention vmestimator in cardinality explorer section (#11237) 2026-07-10 12:33:00 +03:00
Nikolay
180a59119b app/vminsert: properly send rows to legacy vmstorages
Previously, in case when connection to the vmstorage was established in
legacy mode. VMinsert could still forward non metric rows to this
connection. It caused parsing errors at legacy vmstorage.

 This commit drops any non-metric rows designated to the legacy
vmstorages. Because it's not possible correctly re-route it, since most
likely other vmstorages also don't support RPC protocol. Instead
vminsert logs warning message with suggestion to perform an upgrade for
vmstorage.

Fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11146
2026-07-09 17:04:04 +02:00
JAYICE
414aa7b3ab lib/promscrape: support overriding max_scrape_size on a per-target basis via the __max_scrape_size__
This commit allows changing max scrape size with meta label - `__max_scrape_size__`.

Fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11188
2026-07-09 14:26:53 +02:00
ky505
6155e06ad4 app/vmui: made the changes to prevent overlapping of dropdown in the navbar
Previously, the animation is fast due to which the overlap is occurring. If we can add a delay to animation, or smooth the animation, the UI experience would be much better and smooth.

Fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11213
2026-07-08 20:38:52 +02:00
Hui Wang
fb26016beb app/vmselect/metricsql: add fill modifiers support (#11207)
This commit adds new modifiers:
* fill(<value>): Fill in missing series on both sides of the operation with the specified value.
* fill_left(<value>): Fill in missing series on the left side only.
* fill_right(<value>): Fill in missing series on the right side only.

The simplest form is to use fill() to provide a default value for missing series on either side:

`rate(successful_requests_total[5m]) + fill(0) rate(failed_requests_total[5m])`

Fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/10598
2026-07-08 20:31:39 +02:00
f41gh7
66ac4114ed vendor: update metricsql to v0.87.3 2026-07-08 20:24:57 +02:00
Gautam R
945dc5e42c app/vmbackup: retry S3 HTTP 429 and TooManyRequests errors
vmbackup fails immediately when S3 returns a 429. The SDK retries
TooManyRequestsException but not the short form TooManyRequests, and 429
isn't in the retryable status codes either (only 500/502/503/504 are).
Added both so a rate limit doesn't kill the whole backup.
Note: Followed the same pattern as ExpiredToken and IncompleteBody in
the same retryer config.

Fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11218
2026-07-08 16:25:13 +02:00
Max Kotliar
2c9af81fbb app/vmauth: follow-up on #11202
Clean up debug file. Follow-up on
https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11202
2026-07-08 16:37:12 +03:00
Artem Fetishev
431c315bff lib/storage: use different index entry type for counting series (#11090)
Using nsPrefixTagToMetricIDs will stop working once global index will be
disabled by default. See:
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/10641.

Instead, use nsPrefixMetricIDToTSID index entry type which must always
exist readless whether global or per-day index is disabled or not. Using
nsPrefixMetricIDToTSID may also be faster because the prefix to match is
shorter.

---------

Signed-off-by: Artem Fetishev <rtm@victoriametrics.com>
2026-07-08 13:00:00 +02:00
yui
3b68e7fe1a docs/victoriametrics: update and debulk url-examples page (#11109)
## Summary

This PR refreshes `docs/victoriametrics/url-examples.md` to make the
page more compact and more up-to-date.

It focuses on:
- trimming overly verbose request/response dumps down to a useful request
examples and short response expectations;
- adding missing API examples and support coverage for `vmagent`,
`vmalert`, `vmauth`, and administrative endpoints;
- clarifying which endpoints apply to single-node VictoriaMetrics,
VictoriaMetrics cluster, and `vmagent`.

Fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11103

## Why

The current `url-examples` page is useful and widely referenced, but it
is bulky and misses a number of APIs that are already supported in the
codebase. This change keeps the page easier to scan while improving
coverage.

## Scope

Included in this PR:
- debulking verbose examples on the existing page;
- documenting missing endpoints such as Prometheus remote write,
OpenTelemetry, DataDog sketches, snapshots, forced flush/forced merge,
and selected `vmagent`, `vmalert`, and `vmauth` endpoints;
  - updating the changelog entry under `tip`.

## Notes
I realize this is still a fairly large docs diff for a single page. If
maintainers would prefer to split it into smaller steps, I would be
happy to follow that direction and break the work into narrower
follow-up changes.

I think broader regrouping of APIs into categories such as read,
write/import, and administrative would also improve navigation in the
right-side table of contents, but that would create a more structural
and higher-churn diff.

Since administrative APIs were explicitly mentioned in the issue, I
included the missing administrative endpoints in this PR. If maintainers
think broader regrouping is desirable, I can follow up with a separate
issue or PR focused only on that structural reorganization.

---------

Co-authored-by: Pablo Fernandez <46322567+TomFern@users.noreply.github.com>
Co-authored-by: hagen1778 <roman@victoriametrics.com>
2026-07-08 13:40:10 +03:00
Artem Fetishev
404c86f6c5 lib/storage: unit tests for searching label names with just one tag filter (#11226)
Related to https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11196
since searching with one tag filter involves filtering which involves
using of `nsPrefixTagToMetricIDs` global index prefix (see
`filterLabelValues()` in `lib/storage/index_db.go`)

---------

Signed-off-by: Artem Fetishev <rtm@victoriametrics.com>
2026-07-07 16:24:06 +02:00
Alexander Frolov
e5943700a3 app/vmselect: apply metadata limit after deduplication
Moving `/api/v1/metadata` deduplication to vmselect `netstorage`, so
metadata rows are deduplicated before applying the response limit.

The same behavior now also applies to vmselect `clusternative` metadata
requests. Before the change `clusternative` might carry repeated values.

Note that it's still possible to observe truncated metadata from
vmstorage before vmselect receives it when a limit is passed downstream.

Related PR https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11139
2026-07-07 09:18:58 +02:00
Max Kotliar
5aee4758da docs: remote vmestimator docs
No need to sync it manually. Now the estimator docs will be synced from
vmestimator repository via docs Github Action.

Starting with commit
352cae4eff
2026-07-06 21:08:20 +03:00
Artem Fetishev
78015e5e4e docs: fix vmsingle multitenancy available_from
Signed-off-by: Artem Fetishev <rtm@victoriametrics.com>
2026-07-06 18:33:21 +02:00
Artem Fetishev
06148ea0ec docs/changelog: add a link to vmsingle multitenancy docs
Signed-off-by: Artem Fetishev <rtm@victoriametrics.com>
2026-07-06 18:25:05 +02:00
Artem Fetishev
f8a99a51bf docs: vmsingle multitenancy (#11211)
Document the limited multitenancy support in vmsingle and how this
enables easy migrations from vmsingle to vmcluster.

Follow-up for #4328, #10926.

---------

Signed-off-by: Artem Fetishev <rtm@victoriametrics.com>
Signed-off-by: Artem Fetishev <149964189+rtm0@users.noreply.github.com>
Co-authored-by: Pablo Fernandez <46322567+TomFern@users.noreply.github.com>
Co-authored-by: Max Kotliar <mkotlyar@victoriametrics.com>
2026-07-06 18:18:21 +02:00
Artem Fetishev
c110e70337 Makefile: fix apptest-mixed again (follow-up for 92d10148d0)
Unconditionally downloading both vmsingle and vmcluster does not break the build rule across both branches, master and cluster.

Signed-off-by: Artem Fetishev <rtm@victoriametrics.com>
2026-07-06 18:04:04 +02:00
Artem Fetishev
bf53f91dc1 vmalert-tool: do not use 1970-01-01 in unit tests (#11219)
1970-01-01 is a special date that forces vmstorage to use global index
for searching. The unit test will start failing once the global index is
disabled by default. See
https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11196.

Instead, use some other date, such as 2000-01-01.

Signed-off-by: Artem Fetishev <rtm@victoriametrics.com>
2026-07-06 17:42:04 +02:00
Artem Fetishev
92d10148d0 Makefile: fix apptest-mixed (#11220)
Change the external binary version to v1.147.0 since this is the first
version that supports vmselect RPC in vmsingle. This change is only
important for cluster branches. Single-node apptests pass just fine.

Follow-up for
https://github.com/VictoriaMetrics/VictoriaMetrics/pull/10926.

Signed-off-by: Artem Fetishev <rtm@victoriametrics.com>
2026-07-06 17:23:18 +02:00
Max Kotliar
604dfff299 docs: update flags with actual v1.147.0 binaries
Signed-off-by: Max Kotliar <mkotlyar@victoriametrics.com>
2026-07-06 17:13:51 +03:00
Max Kotliar
a183ec67c7 docs: bump version to v1.147.0
Signed-off-by: Max Kotliar <mkotlyar@victoriametrics.com>
2026-07-06 17:09:57 +03:00
Max Kotliar
fc0559a8ab deplyoment/docker: bump version to v1.147.0
Signed-off-by: Max Kotliar <mkotlyar@victoriametrics.com>
2026-07-06 17:09:04 +03:00
Max Kotliar
d17f79428e docs: forward port LTS v1.122.26 changelog to upstream
Signed-off-by: Max Kotliar <mkotlyar@victoriametrics.com>
2026-07-06 16:52:48 +03:00
Max Kotliar
f3a3df0143 docs: forward port LTS v1.136.13 changelog to upstream
Signed-off-by: Max Kotliar <mkotlyar@victoriametrics.com>
2026-07-06 16:52:25 +03:00
Nikolay
3ac6505d7f app/vmstorage: reduce memory allocations after 3548858843 (#11216)
Commit 3548858843 introduced additional
memory allocation because new marshalFunc. Compiler cannot inline
`bi.marshal` and have to allocate a copy of `bi.mb` on heap. It made
impossible to properly pool `bi.mb`.

 This commit fixes regression by using proper reference to `bi.mb`  instead of copy.

PR https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11216
2026-07-06 14:55:47 +03:00
Max Kotliar
dcf36b8a60 docs/changelog: cut release v1.147.0
Signed-off-by: Max Kotliar <mkotlyar@victoriametrics.com>
2026-07-03 17:38:18 +03:00
Max Kotliar
f6edd9d642 docs: update version to v1.147.0
Signed-off-by: Max Kotliar <mkotlyar@victoriametrics.com>
2026-07-03 17:37:38 +03:00
Max Kotliar
22b58c72a2 app/vmselect: run make vmui-update
Signed-off-by: Max Kotliar <mkotlyar@victoriametrics.com>
2026-07-03 17:34:09 +03:00
Max Kotliar
485f4c3524 .golangci: exclude vmui node modules as linter checks .go files in it
When I run make golangci-lint from VictoriaMetrics/release project it
complains on some files in vmui's node_modules directory. The paths
looks like this:

```
$ make golangci-lint
which golangci-lint && (golangci-lint --version | grep -q 2.12.2) ||
curl -sSfL https://golangci-lint.run/install.sh | sh -s -- -b
/Users/makasim/go/bin v2.12.2
/Users/makasim/go/bin/golangci-lint
golangci-lint run --build-tags 'synctest'
../../../VictoriaMetrics/app/vmui/packages/vmui/node_modules/flatted/golang/pkg/flatted/flatted.go:35:88:
inline: Constant reflect.Ptr should be inlined (govet)
                if kind == reflect.String || kind == reflect.Slice ||
kind == reflect.Map || kind == reflect.Ptr {

^
../../../VictoriaMetrics/app/vmui/packages/vmui/node_modules/flatted/golang/pkg/flatted/flatted.go:62:20:
inline: Constant reflect.Ptr should be inlined (govet)
                for rv.Kind() == reflect.Ptr && !rv.IsNil() {
```

I guess symlinks is so pnpm magic. But because of it the current
exclusion does not work.

So I added another focused on node_modules and without `^`.
2026-07-03 16:55:07 +03:00
vinyas-bharadwaj
83a9f7335c app/vmalert: expose group results limit as a metric (#11182)
This PR exposes a new metric `vmalert_rule_group_results_limit` to track the effective results limit applied to a given rule group.

Currently, `vmalert` allows bounding the number of series returned by
evaluating a rule either globally (`-rule.resultsLimit`) or per group
(`groups[].limit`). However, the effective limit applied isn't easily
observable as a metric, which makes it difficult to natively construct
alerts for rules that are dropping output due to exceeding this limit.

By exposing `vmalert_rule_group_results_limit{group="...", file="..."}`,
users can seamlessly compare actual evaluation outputs against the
configured bounds natively using Prometheus/VictoriaMetrics, without
needing out-of-band workarounds.

**Changes:**
- Registers the `vmalert_rule_group_results_limit` gauge during group
initialization.
- Adds tests verifying limits correctly resolve from both global
fallbacks and per-group overrides.

Fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11179
PR https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11182

---------

Signed-off-by: Vinyas Bharadwaj <vinyasbharadwaj101@gmail.com>
Signed-off-by: vinyas-bharadwaj <vinyasbharadwaj101@gmail.com>
Co-authored-by: Max Kotliar <mkotlyar@victoriametrics.com>
2026-07-03 15:59:57 +03:00
Hui Wang
669de296ac dashboards: sync uptime panel in vmsingle&vmagent dashboards with the others (#11189)
1. sync the `uptime` panel in vmsingle&vmagent dashboards with the
others;
2. improve comments in `fixBrokenBuckets`.
2026-07-03 15:55:37 +03:00
JAYICE
21119cdc9e app/vmselect: buckets_limit: improve selection algorithm to remove consecutive empty buckets from beginning and end (#10764)
Fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/10417

Co-authored-by: Max Kotliar <mkotlyar@victoriametrics.com>
2026-07-03 15:31:27 +03:00
Max Kotliar
ebb0b5cb87 app/vmauth: fall through to unauthorized_user when JWT token has no vm_access claim (#11210)
Previously, if a JWT token had no vm_access claim and no default
vm_access claim was configured, vmauth returned 401 immediately.

Other authentication paths fall through to the unauthorized_user section
in this case, so requests can be handled by it if configured.

This commit aligns JWT handling with the same pattern.

Also, move `defer putToken(tkn)` earlier so the token is actually
reused.

Related to:
- https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5740,
- https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7543

PR https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11210
2026-07-03 14:57:41 +03:00
Jesús Espino
f913a845b8 app/vmselect/promql: Improve code readability by using positive if clauses
This commit improves the code readability of the execBinaryOpArgs by
reorganizing the if statements to use positive clauses, making easier to
follow the logic there.

There code inside the if blocks is only rearanged, not changed. The main
change is where the if are checked and how.

This is a follow up from the PR https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11152 and commit 15a4c31e87
2026-07-03 12:40:07 +02:00
JAYICE
da73c0805d app/vmagent/kafka: ignore enable.auto.offset.store in kafka.consumer.topic.options
This commit ignores enable.auto.offset.store in kafka.consumer.topic.options
and emits warning log when ignoring the option.

Fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11208
2026-07-03 12:37:49 +02:00
Max Kotliar
5602298b62 app/vmauth: allow logging unauthorized requests to access log (#11202)
Allow `unauthorized_user` to be configured without `URLPrefix` or
`URLMaps`. Previously either was required; now both are optional. This
enables the following minimal config:

```yaml
unauthorized_user:
  access_log: {}
```

When set, vmauth logs all requests with missing or invalid auth tokens
to the access log, including `remote_addr`. This helps identify and
block `remote_addr` IPs performing brute-force attacks.

Fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11180

---------

Co-authored-by: f41gh7 <nik@victoriametrics.com>
2026-07-03 12:40:17 +03:00
Nikolay
64a27faac7 lib/promscape: reduce memory allocations for newCompressedLabels
Previously, creation of compressedLabels could require extra memory due
 to re-allocation of tmpBuf and clone of 3 extra fields. It could result
 into extra CPU usage for garbage-collection.

  This commit adds sync.Pool for labels escape with JSON marshal and
  allocates dedicated buffer for job, address and ID strings.

 Optimisations was made based on the following profiles from reported
 issue:

 1) CPU:
 ```
 Showing top 10 nodes out of 172
      flat  flat%   sum%        cum   cum%
    12.17s 17.19% 17.19%     12.25s 17.30%  runtime.cgocall
     5.87s  8.29% 25.48%      5.87s  8.29%  runtime.memmove
     3.45s  4.87% 30.35%      6.66s  9.41%  runtime.tryDeferToSpanScan
```

2) memory go tool pprof -alloc_objects heap_profile.txt
```
Showing top 10 nodes out of 94
      flat  flat%   sum%        cum   cum%
3673568660 26.09% 26.09% 4147984949 29.46%
github.com/valyala/quicktemplate.AppendJSONString
1657933055 11.77% 37.86% 1657933055 11.77% internal/stringslite.Clone
(inline)
1555166274 11.04% 48.91% 1555166274 11.04%
github.com/valyala/gozstd.compress
1254756359 8.91% 57.82% 9433313305 66.99%
github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape.newCompressedLabels
1067036870 7.58% 65.39% 1067036870 7.58%
github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape.appendExtraLabels
```

results of benchstat:
```
benchstat before after
goos: darwin
goarch: arm64
pkg: github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape
cpu: Apple M1 Pro
│ 134/before │ after │
│ sec/op │ sec/op vs base │
NewCompressedLabels-10 981.3n ± 2% 908.6n ± 2% -7.40% (p=0.000 n=10)

│ 134/before │ after │
│ B/op │ B/op vs base │
NewCompressedLabels-10   891.5 ± 0%   772.0 ± 0%  -13.40% (p=0.000 n=10)

│ 134/before │ after │
│ allocs/op │ allocs/op vs base │
NewCompressedLabels-10 10.000 ± 0% 3.000 ± 0% -70.00% (p=0.000 n=10)
```

Fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/10919
2026-07-02 16:43:18 +02:00
Roman Khavronenko
d54037b422 docs: mention useful ai skills in the docs
Listing https://github.com/VictoriaMetrics/skills in the places where
user may benefit from using them.
2026-07-02 16:41:57 +02:00
Nikolay
93508215d9 lib/timeserieslimits: properly check range for limit values
Limit cannot be 0 or greater then 65535, because it may corrupt
ingested data. Historically VictoriaMetrics supported only 65535 bytes
len for label name and value.

See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11128
2026-07-02 16:41:11 +02:00
Roman Khavronenko
3f933e9722 dashboards: update query-stats.json (#11187)
Dashboard stopped working in Grafana v12. The applied changes make it
operational again. Exported from Grafana via "Share Externally" option.

Signed-off-by: hagen1778 <roman@victoriametrics.com>
2026-07-02 17:11:19 +03:00
Immanuel Tikhonov
37e7485a98 app/vmctl: URL-encode extra_label query params (#11144)
Fixes a small but real gotcha in `vmctl`. If `--vm-extra-label` contains special chars in the value, for example
`team=a&b`, vmctl builds this kind of URL:

```
/api/v1/import?extra_label=team=a&b
```

so `&` splits the query, and the label gets mangled.

The commit URL-encodes each `extra_label` value before building the
import URL. Plain labels keep the same meaning, special chars round-trip fine now.

PR https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11144

---------

Co-authored-by: Max Kotliar <mkotlyar@victoriametrics.com>
2026-07-02 16:59:35 +03:00
Max Kotliar
da172fd4e7 deployment: add InvalidAuthTokenRequestErrors alert (#11197)
The new rule notifies when vmauth receives requests with invalid or missing auth tokens, which may indicate a client misconfiguration, expired token use, or brute-force attack.

Related to
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11180,
https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11195

---------

Signed-off-by: Max Kotliar <kotlyar.maksim@gmail.com>
Co-authored-by: Hui Wang <haley@victoriametrics.com>
2026-07-02 15:42:30 +03:00
Artem Fetishev
142d7a9505 lib/storage: reduce MetricName.marshalRaw() scope to test-only since it is only used in tests (#11194)
Signed-off-by: Artem Fetishev <rtm@victoriametrics.com>
2026-07-01 18:20:58 +02:00
Artem Fetishev
3548858843 vmsingle: add the support of vmselect RPC (#10926)
The PR adds the support of vmselect RPC to vmsingle.

- The RPC server was copied from cluster branch as is and is disabled by
default. To enable, users must provide the listen address explicitly via
`-vmselectAddr` flag.
- Users must also specify which accountID and projectID the vmsingle
data corresponds to. To do this, `-accountID` and `-projectID` flags. By
default, these flags are 0. So the default tenantID is `"0:0"`.

Copying RPC server from cluster branch also required copying its
dependencies:
- Code located in `lib/handshake` (copied as is)
- Some parts of `SearchQuery` type from `lib/storage/search.go`.

The entire API surface is tested with an app test.

Implements https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4328

Signed-off-by: Artem Fetishev <rtm@victoriametrics.com>
2026-07-01 17:36:30 +02:00
Phuong Le
1c05efb6ed docs: fix broken links (#11147)
- fix broken links
- fix broken image paths (not quite broken in the current page, but it's
currently broken in alias page
https://docs.victoriametrics.com/guides/vm-architectures.html)
- remove "VictoriaMetrics on Linode" link on Quick Start (I can't find
it anywhere so remove it)

---------

Co-authored-by: Max Kotliar <mkotlyar@victoriametrics.com>
2026-07-01 18:01:33 +03:00
f41gh7
2220f4e110 add assert to marshalBytesFast function
This commit properly checks input for marshalBytesFast function
in order to prevent possible silent data corruption and unexpected bugs.

Related to
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11128
2026-07-01 16:38:11 +02:00
Nikolay
658f0b8a06 lib/storage: introduce limit for metric metadata fields
This commit adds a size limit for metric metadata fields.
Limit is hard-capped by `56kb` ( max uint16), since historically metric
metadata marshalled with those limit at cluster version. And it seems to
be a sane limit, it's unlikely that any valid metadata will exceed it.

Fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11128
2026-07-01 14:33:56 +02:00
f41gh7
381468a9a2 Makefile: exclude govulncheck from check-all
Currently govulncheck fails linter at CI with CVE at vctl that doesn't have any
fix released. So it's better to suppress it for now. Release process
uses a different tools that properly detect CVEs.
2026-07-01 14:25:18 +02:00
f41gh7
632c42d8e7 docs: update changelog entry after 15a4c31e87 2026-07-01 11:53:30 +02:00
Max Kotliar
8e5ca7e9b0 docs/changelog: fix bugfixs order 2026-07-01 12:32:05 +03:00
Max Kotliar
119ba0fb5b app/vmauth: slow down unauthorized responses to mitigate brute-force attacks (#11195)
Without a delay, an attacker can attempt thousands of authentication requests per second at minimal cost. The only indication of such an attack is the `vmauth_http_request_errors_total{reason="invalid_auth_token"}` metric, which may go unnoticed if it isn't being monitored.

Introduce a random delay of 2–3 seconds before returning 401 Unauthorized responses. This significantly increases the cost of sustaining a brute-force attack while having a negligible impact on legitimate clients, which are not expected to generate unauthorized
requests frequently. 

OWASP Top10 also recommends adding delays on failed auth: https://owasp.org/Top10/2025/A07_2025-Authentication_Failures.

The added delay could itself be abused as part of a DoS attack by tying up request-processing resources. However, such an attack is easier to detect because it generates sustained unauthorized traffic from identifiable source IPs, allowing operators to block or rate-limit the offending clients using specialized tools.

Fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11180

---------

Signed-off-by: Nikolay <nik@victoriametrics.com>
Co-authored-by: f41gh7 <nik@victoriametrics.com>
2026-07-01 12:31:06 +03:00
Benjamin Nichols-Farquhar
c82127b6d4 app/vmagent: Optimize remotewrite hot paths
## Description

We have a simple vmagent setup using `remotewrite.shardByURL` to
horizontally scale stream aggregations, as suggested
[here](https://docs.victoriametrics.com/victoriametrics/vmagent/#sharding-among-remote-storages)
with minimal additional configuration and noticed there was some
optimization opportunities.

## Investigation 

We took a
[pprof](https://github.com/user-attachments/files/28968177/cpu.pprof.zip)
and found that our CPU time was in a few primary places.

1. payload protobuf marshaling 
2. zstd compresssion 
3. Map access in sharding
4. Hashing in sharding
5. Memory copies for timeseries 

## Changes


Zstd compression is pretty intractable(besides using
`remotewrite.vmProtoCompressLevel`, which we already do), but there were
some relatively easy gains for the other paths.

1. inline sov() when l < 128 on marshal. Skips calling a bunch of byte
math in a common case
2. Use slices instead of maps for shard keys, in the common case where
there are less than ~10 keys a slice iteration will be faster
3. use `xxhash.Digest.WriteString` rather than buffering values, avoids
some extra allocations and GC work
4. In the simple case where `insertRows` doesn't modify label names or
values construct the remotewrite request from the passed values instead
of copying them. Avoids a bunch of memory copy and GC work.

## Results

Modest but effective, we saw a ~10% reduction in total cpu cores used by
vmagent with these changes.
<img width="2358" height="709" alt="Screenshot 2026-06-15 at 2 24 43 PM"
src="https://github.com/user-attachments/assets/d2931242-c7ed-447b-9cfd-73b29cd0b893"
/>

Related PR https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11113
2026-06-30 18:52:03 +02:00
JAYICE
06bc808ddc app/vmagent: support MDX(monitor data exchange) for remote write
This commit adds MDX(Monitoring Data eXchange) feature into vmagent. 
It allows to detect VictoriaMetrics related components and forward metrics only for those instances.

In default state, mdx detects timeseries based on `vm_app_version` metric name and tracks instance based on "job:instance" key. It holds tracked instances in-memory for 1 hour, which must be enough for the most scrape intervals.

fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/10600
2026-06-30 18:31:17 +02:00
Nikolay
a6927c46be follow-up for e3121ad473 (#11162)
Fixes data-race at emwa.go package.
Write and read requests of the shared variable must be guarded by the
mutex lock.
2026-06-30 18:19:59 +02:00
xhe
15a4c31e87 app/vmselect: optimize repeated binary op rollup aggregates
This commit improves performance for sub query requests by properly cache binary operation results.

 For context:

When using binary operator in query, vmselect will executes the left and right expressions in parallel and independently. When the left and right expressions contain the same sub-expression, e.g.

```
count( count(vm_requests_total) by (action,addr,cluster,endpoint) ) by (action,addr,cluster) 
/
count( count(vm_requests_total) by (action,addr,cluster,endpoint) )
```

```count( count(vm_requests_total) by (action,addr,cluster,endpoint) is a sub expression in both left and right side.```

The sub expression will be executed twice (once in each of the left and right expressions). This introduces additional RPC and resource overhead.

 Currently, this feature is hidden behind query request param - `optimize_repeated_binary_op_subexprs`.
Later it could be transited into default behavior.

Fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/10575
2026-06-30 17:47:19 +02:00
Andrii Chubatiuk
54f9cd6edd chore(app/vmalert): update TestGroupValidate_Success and TestGroupValidate_Failure tests
This changes is needed to simplify tests logic and reduce diff between OSS and enterprise branches

Related https://github.com/VictoriaMetrics/VictoriaMetrics/pull/11157
2026-06-30 17:44:01 +02:00
Zakhar Bessarab
2e16874e95 app/vmauth: add new default_vm_access_claim field
This commit relaxes JWT token format. Previously  "vm_access" claim was required.
Which may not be a case for some users. For example, if there is no need at request templating.
  The new `jwt` section field `default_vm_access_claim` was added for this purpose.
It's used as fall-back for templating data.

See: https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11054

---------

Signed-off-by: Nikolay <nik@victoriametrics.com>
Co-authored-by: f41gh7 <nik@victoriametrics.com>
2026-06-30 17:37:20 +02:00
Phuong Le
81d330f297 lib/httpserver: cancel in-flight requests when graceful shutdown times out
`http.Server.Shutdown()` waits for in-flight requests but never cancels
them, so long-lived ones (e.g. VictoriaLogs live tailing) make graceful
shutdown timeout, and the resulting `logger.Fatalf` -> `os.Exit` skips
the storage flush and loses data. So adding a cancelable `BaseContext`
that is canceled once `-http.maxGracefulShutdownDuration` elapses.

Updates https://github.com/VictoriaMetrics/VictoriaLogs/issues/1502

Also notes that VictoriaMetrics query execution is deadline-driven and
ignores ctx, so it's a no-op there.
2026-06-30 17:30:30 +02:00
Raymond Chen
3278ddd170 docs/victoriametrics: fix broken Prometheus WAL remote write blog link
The Grafana blog post about Prometheus 2.8 WAL-based remote write has
moved. The old URL
(`https://grafana.com/blog/2019/03/25/whats-new-in-prometheus-2.8-wal-based-remote-write/`)
now 404s; this updates both occurrences in the docs to the current
location
(`https://grafana.com/blog/whats-new-in-prometheus-2-8-wal-based-remote-write/`),
which returns HTTP 200.

Fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/11184

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-06-30 17:29:41 +02:00
Max Kotliar
cc790c2ea1 deployment: bump Alpine version to v3.24.1 for security updates
See https://www.alpinelinux.org/posts/Alpine-3.24.1-released.html
2026-06-30 16:02:14 +03:00
Max Kotliar
950f38fd6a docs/vmestimator: sync docs 2026-06-30 12:11:27 +03:00
254 changed files with 12188 additions and 4188 deletions

View File

@@ -65,7 +65,7 @@ jobs:
arch: amd64
steps:
- name: Code checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false

View File

@@ -13,7 +13,7 @@ jobs:
contents: read
runs-on: 'ubuntu-latest'
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
# needed for proper diff
fetch-depth: 0

View File

@@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0 # we need full history for commit verification
persist-credentials: false

View File

@@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Code checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false

View File

@@ -31,7 +31,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
@@ -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@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
with:
category: 'language:go'

View File

@@ -18,13 +18,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Code checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
path: __vm
persist-credentials: false
- name: Checkout private code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
repository: VictoriaMetrics/vmdocs
token: ${{ secrets.VM_BOT_GH_TOKEN }}

View File

@@ -34,7 +34,7 @@ jobs:
runs-on: 'vm-runner'
steps:
- name: Code checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
@@ -79,7 +79,7 @@ jobs:
steps:
- name: Code checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
@@ -106,7 +106,7 @@ jobs:
steps:
- name: Code checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false

View File

@@ -26,13 +26,11 @@ jobs:
vmui-checks:
name: VMUI Checks (lint, test, typecheck)
permissions:
checks: write
contents: read
pull-requests: read
runs-on: ubuntu-latest
steps:
- name: Code checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
@@ -70,12 +68,6 @@ jobs:
env:
VMUI_SKIP_INSTALL: true
- name: Annotate Code Linting Results
uses: ataylorme/eslint-annotate-action@d57a1193d4c59cbfbf3f86c271f42612f9dbd9e9 # 3.0.0
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
report-json: app/vmui/packages/vmui/vmui-lint-report.json
- name: Check overall status
run: |
echo "Lint status: ${{ steps.lint.outcome }}"

View File

@@ -23,3 +23,4 @@ linters:
text: 'SA(4003|1019|5011):'
paths:
- ^app/vmui/
- app/vmui/packages/vmui/node_modules/

View File

@@ -447,7 +447,7 @@ vet:
go vet ./app/...
go vet ./apptest/...
check-all: fmt vet golangci-lint govulncheck
check-all: fmt vet golangci-lint
clean-checkers: remove-golangci-lint remove-govulncheck
@@ -471,8 +471,9 @@ test-full-386:
apptest:
$(MAKE) victoria-metrics-race vmagent-race vmalert-race vmauth-race vmctl-race vmbackup-race vmrestore-race
go test ./apptest/... -skip="^Test(Cluster|Legacy).*"
go test ./apptest/... -skip="^Test(Cluster|Mixed|Legacy).*"
# App tests for legacy indexDB
apptest-legacy: victoria-metrics-race vmbackup-race vmrestore-race
OS=$$(uname | tr '[:upper:]' '[:lower:]'); \
ARCH=$$(uname -m | tr '[:upper:]' '[:lower:]' | sed 's/x86_64/amd64/'); \
@@ -489,6 +490,22 @@ apptest-legacy: victoria-metrics-race vmbackup-race vmrestore-race
VMSTORAGE_V1_132_0_PATH=$${DIR}/vmstorage-prod \
go test ./apptest/tests -run="^TestLegacySingle.*"
# App tests for mixed setups where vmsingle and vmcluster coexist.
apptest-mixed: victoria-metrics-race
OS=$$(uname | tr '[:upper:]' '[:lower:]'); \
ARCH=$$(uname -m | tr '[:upper:]' '[:lower:]' | sed 's/x86_64/amd64/'); \
VERSION=v1.147.0; \
VMSINGLE=victoria-metrics-$${OS}-$${ARCH}-$${VERSION}.tar.gz; \
VMCLUSTER=victoria-metrics-$${OS}-$${ARCH}-$${VERSION}-cluster.tar.gz; \
URL=https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/$${VERSION}; \
DIR=/tmp/$${VERSION}; \
test -d $${DIR} || (mkdir $${DIR} && \
curl --output-dir /tmp -LO $${URL}/$${VMSINGLE} && tar xzf /tmp/$${VMSINGLE} -C $${DIR} && \
curl --output-dir /tmp -LO $${URL}/$${VMCLUSTER} && tar xzf /tmp/$${VMCLUSTER} -C $${DIR} \
); \
VMSELECT_PATH=$${DIR}/vmselect-prod \
go test ./apptest/tests -run="^TestMixed.*"
benchmark:
go test -run=NO_TESTS -bench=. ./lib/...
go test -run=NO_TESTS -bench=. ./app/...

View File

@@ -89,7 +89,7 @@ func main() {
}
logger.Infof("starting VictoriaMetrics at %q...", listenAddrs)
startTime := time.Now()
vmstorage.Init(*vmselectMaxConcurrentRequests, promql.ResetRollupResultCacheIfNeeded)
vmstorage.Init(*vmselectMaxConcurrentRequests, *vmselectMaxQueueDuration, promql.ResetRollupResultCacheIfNeeded)
vmselect.Init(*vmselectMaxConcurrentRequests, *vmselectMaxQueueDuration)
vminsertcommon.StartIngestionRateLimiter(*maxIngestionRate)
vminsert.Init()

View File

@@ -51,7 +51,7 @@ func InsertHandlerForHTTP(at *auth.Token, req *http.Request) error {
}
q := req.URL.Query()
precision := q.Get("precision")
// Read db tag from https://docs.influxdata.com/influxdb/v1.7/tools/api/#write-http-endpoint
// Read db tag from https://docs.influxdata.com/influxdb/v1/api/write/#operation/PostWrite
db := q.Get("db")
encoding := req.Header.Get("Content-Encoding")
isStreamMode := req.Header.Get("Stream-Mode") == "1"

View File

@@ -462,7 +462,9 @@ func requestHandler(w http.ResponseWriter, r *http.Request) bool {
return true
case "/prometheus/metric-relabel-debug", "/metric-relabel-debug":
promscrapeMetricRelabelDebugRequests.Inc()
promscrape.WriteMetricRelabelDebug(w, r)
rwGlobalRelabelConfigs := remotewrite.GetRemoteWriteRelabelConfigString()
rwURLRelabelConfigss := remotewrite.GetURLRelabelConfigString()
promscrape.WriteMetricRelabelDebug(w, r, rwGlobalRelabelConfigs, rwURLRelabelConfigss)
return true
case "/prometheus/target-relabel-debug", "/target-relabel-debug":
promscrapeTargetRelabelDebugRequests.Inc()

View File

@@ -63,6 +63,7 @@ func insertRows(at *auth.Token, tss []prompb.TimeSeries, mms []prompb.MetricMeta
rowsTotal := 0
tssDst := ctx.WriteRequest.Timeseries[:0]
mmsDst := ctx.WriteRequest.Metadata[:0]
labels := ctx.Labels[:0]
samples := ctx.Samples[:0]
for i := range tss {
@@ -82,7 +83,19 @@ func insertRows(at *auth.Token, tss []prompb.TimeSeries, mms []prompb.MetricMeta
var metadataTotal int
if prommetadata.IsEnabled() {
ctx.WriteRequest.Metadata = mms
for i := range mms {
mm := &mms[i]
mmsDst = append(mmsDst, prompb.MetricMetadata{
MetricFamilyName: mm.MetricFamilyName,
Help: mm.Help,
Type: mm.Type,
Unit: mm.Unit,
AccountID: mm.AccountID,
ProjectID: mm.ProjectID,
})
}
ctx.WriteRequest.Metadata = mmsDst
metadataTotal = len(mms)
}

View File

@@ -35,6 +35,9 @@ func InsertHandler(at *auth.Token, req *http.Request) error {
}
func insertRows(at *auth.Token, timeseries []prompb.TimeSeries, mms []prompb.MetricMetadata, extraLabels []prompb.Label) error {
if len(extraLabels) == 0 && !prommetadata.IsEnabled() && at == nil {
return insertRowsFast(at, timeseries)
}
ctx := common.GetPushCtx()
defer common.PutPushCtx(ctx)
@@ -102,3 +105,17 @@ func insertRows(at *auth.Token, timeseries []prompb.TimeSeries, mms []prompb.Met
rowsPerInsert.Update(float64(rowsTotal))
return nil
}
func insertRowsFast(at *auth.Token, timeseries []prompb.TimeSeries) error {
rowsTotal := 0
for i := range timeseries {
rowsTotal += len(timeseries[i].Samples)
}
wr := &prompb.WriteRequest{Timeseries: timeseries}
if !remotewrite.TryPush(at, wr) {
return remotewrite.ErrQueueFullHTTPRetry
}
rowsInserted.Add(rowsTotal)
rowsPerInsert.Update(float64(rowsTotal))
return nil
}

View File

@@ -151,17 +151,23 @@ func newHTTPClient(argIdx int, remoteWriteURL, sanitizedURL string, fq *persiste
}
tr.Proxy = http.ProxyURL(pu)
}
hc := &http.Client{
Transport: authCfg.NewRoundTripper(tr),
Timeout: sendTimeout.GetOptionalArg(argIdx),
}
rwURL, err := url.Parse(remoteWriteURL)
if err != nil {
logger.Fatalf("BUG: cannot parse already parsed -remoteWrite.url=%q: %s", remoteWriteURL, err)
}
hc.Transport, rwURL = httputil.NewLoadBalancerTransport(hc.Transport, rwURL)
retryMaxIntervalFlag := retryMaxTime
if retryMaxInterval.String() != "" {
retryMaxIntervalFlag = retryMaxInterval
}
c := &client{
sanitizedURL: sanitizedURL,
remoteWriteURL: remoteWriteURL,
remoteWriteURL: rwURL.String(),
authCfg: authCfg,
awsCfg: awsCfg,
fq: fq,

View File

@@ -12,6 +12,7 @@ import (
"github.com/VictoriaMetrics/metrics"
"gopkg.in/yaml.v2"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/flagutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
@@ -82,6 +83,16 @@ func WriteRelabelConfigData(w io.Writer) {
_, _ = w.Write(*p)
}
// GetRemoteWriteRelabelConfigString returns -remoteWrite.relabelConfig contents in string
func GetRemoteWriteRelabelConfigString() string {
var bb bytesutil.ByteBuffer
WriteRelabelConfigData(&bb)
if bb.Len() == 0 {
return ""
}
return string(bb.B)
}
// WriteURLRelabelConfigData writes -remoteWrite.urlRelabelConfig contents to w
func WriteURLRelabelConfigData(w io.Writer) {
p := remoteWriteURLRelabelConfigData.Load()
@@ -108,6 +119,24 @@ func WriteURLRelabelConfigData(w io.Writer) {
_, _ = w.Write(d)
}
// GetURLRelabelConfigString returns -remoteWrite.urlRelabelConfig contents in []string
func GetURLRelabelConfigString() []string {
p := remoteWriteURLRelabelConfigData.Load()
if p == nil {
return nil
}
var ss []string
for i := range *remoteWriteURLs {
cfgData := (*p)[i]
var cfgDataBytes []byte
if cfgData != nil {
cfgDataBytes, _ = yaml.Marshal(cfgData)
}
ss = append(ss, string(cfgDataBytes))
}
return ss
}
func reloadRelabelConfigs() {
rcs := allRelabelConfigs.Load()
if !rcs.isSet() {

View File

@@ -12,19 +12,18 @@ import (
"sync/atomic"
"time"
"github.com/cespare/xxhash/v2"
"github.com/VictoriaMetrics/metrics"
"github.com/cespare/xxhash/v2"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/auth"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bloomfilter"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/cgroup"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/consistenthash"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/flagutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httpserver"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/mdx"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/memory"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/persistentqueue"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/procutil"
@@ -106,6 +105,9 @@ var (
"cannot be pushed into the configured -remoteWrite.url systems in a timely manner. See https://docs.victoriametrics.com/victoriametrics/vmagent/#disabling-on-disk-persistence")
disableMetadataPerURL = flagutil.NewArrayBool("remoteWrite.disableMetadata", "Whether to disable sending metadata to the corresponding -remoteWrite.url. "+
"By default, metadata sending is controlled by the global -enableMetadata flag")
enableMdx = flagutil.NewArrayBool("remoteWrite.mdx.enable", "Whether to only retain metrics from VictoriaMetrics services before sending them to the corresponding -remoteWrite.url. "+
"Please see https://docs.victoriametrics.com/victoriametrics/vmagent/#monitoring-data-exchange")
)
var (
@@ -162,8 +164,8 @@ func InitSecretFlags() {
}
var (
shardByURLLabelsMap map[string]struct{}
shardByURLIgnoreLabelsMap map[string]struct{}
shardByURLLabelsFilter []string
shardByURLIgnoreLabelsFilter []string
)
// Init initializes remotewrite.
@@ -210,8 +212,8 @@ func Init() {
logger.Fatalf("-remoteWrite.shardByURL.labels and -remoteWrite.shardByURL.ignoreLabels cannot be set simultaneously; " +
"see https://docs.victoriametrics.com/victoriametrics/vmagent/#sharding-among-remote-storages")
}
shardByURLLabelsMap = newMapFromStrings(*shardByURLLabels)
shardByURLIgnoreLabelsMap = newMapFromStrings(*shardByURLIgnoreLabels)
shardByURLLabelsFilter = slices.Clone(*shardByURLLabels)
shardByURLIgnoreLabelsFilter = slices.Clone(*shardByURLIgnoreLabels)
initLabelsGlobal()
@@ -307,6 +309,10 @@ func initRemoteWriteCtxs(urls []string) {
}
fs.RegisterPathFsMetrics(*tmpDataPath)
if slices.Contains(*enableMdx, true) && *shardByURL {
logger.Fatalf("-remoteWrite.mdx.enable and -remoteWrite.shardByURL cannot be set to true simultaneously.")
}
if *shardByURL {
consistentHashNodes := make([]string, 0, len(urls))
for i, url := range urls {
@@ -565,6 +571,14 @@ func tryPushMetadataToRemoteStorages(at *auth.Token, rwctxs []*remoteWriteCtx, m
mm.ProjectID = at.ProjectID
}
}
tmp := mms[:0]
for _, mm := range mms {
if timeserieslimits.IsMetricMetadataExceeding(&mm) {
continue
}
tmp = append(tmp, mm)
}
mms = tmp
// Do not shard metadata even if -remoteWrite.shardByURL is set, just replicate it among rwctxs.
// Since metadata is usually small and there is no guarantee that metadata can be sent to
// the same remote storage with the corresponding metrics.
@@ -698,18 +712,18 @@ func shardAmountRemoteWriteCtx(tssBlock []prompb.TimeSeries, shards [][]prompb.T
for _, ts := range tssBlock {
hashLabels := ts.Labels
if len(shardByURLLabelsMap) > 0 {
if len(shardByURLLabelsFilter) > 0 {
hashLabels = tmpLabels.Labels[:0]
for _, label := range ts.Labels {
if _, ok := shardByURLLabelsMap[label.Name]; ok {
if slices.Contains(shardByURLLabelsFilter, label.Name) {
hashLabels = append(hashLabels, label)
}
}
tmpLabels.Labels = hashLabels
} else if len(shardByURLIgnoreLabelsMap) > 0 {
} else if len(shardByURLIgnoreLabelsFilter) > 0 {
hashLabels = tmpLabels.Labels[:0]
for _, label := range ts.Labels {
if _, ok := shardByURLIgnoreLabelsMap[label.Name]; !ok {
if !slices.Contains(shardByURLIgnoreLabelsFilter, label.Name) {
hashLabels = append(hashLabels, label)
}
}
@@ -810,34 +824,26 @@ var (
// it omits the '=' separator between label name and value for backward compatibility.
// Changing it would re-shard all series across remoteWrite targets.
func getLabelsHashForShard(labels []prompb.Label) uint64 {
bb := labelsHashBufPool.Get()
b := bb.B[:0]
var d xxhash.Digest
d.Reset()
for _, label := range labels {
b = append(b, label.Name...)
b = append(b, label.Value...)
_, _ = d.WriteString(label.Name)
_, _ = d.WriteString(label.Value)
}
h := xxhash.Sum64(b)
bb.B = b
labelsHashBufPool.Put(bb)
return h
return d.Sum64()
}
func getLabelsHash(labels []prompb.Label) uint64 {
bb := labelsHashBufPool.Get()
b := bb.B[:0]
var d xxhash.Digest
d.Reset()
for _, label := range labels {
b = append(b, label.Name...)
b = append(b, '=')
b = append(b, label.Value...)
_, _ = d.WriteString(label.Name)
_, _ = d.WriteString("=")
_, _ = d.WriteString(label.Value)
}
h := xxhash.Sum64(b)
bb.B = b
labelsHashBufPool.Put(bb)
return h
return d.Sum64()
}
var labelsHashBufPool bytesutil.ByteBufferPool
func logSkippedSeries(labels []prompb.Label, flagName string, flagValue int) {
select {
case <-logSkippedSeriesTicker.C:
@@ -862,6 +868,7 @@ type remoteWriteCtx struct {
sas atomic.Pointer[streamaggr.Aggregators]
deduplicator *streamaggr.Deduplicator
mdxFilter *mdx.Filter
streamAggrKeepInput bool
streamAggrDropInput bool
@@ -876,6 +883,7 @@ type remoteWriteCtx struct {
rowsPushedAfterRelabel *metrics.Counter
rowsDroppedByRelabel *metrics.Counter
mdxRowsPreserved *metrics.Counter
pushFailures *metrics.Counter
metadataDroppedOnPushFailure *metrics.Counter
@@ -972,7 +980,6 @@ func newRemoteWriteCtx(argIdx int, remoteWriteURL *url.URL, sanitizedURL string)
for i := range pss {
pss[i] = newPendingSeries(fq, &c.useVMProto, sf, rd)
}
rwctx := &remoteWriteCtx{
idx: argIdx,
fq: fq,
@@ -989,6 +996,16 @@ func newRemoteWriteCtx(argIdx int, remoteWriteURL *url.URL, sanitizedURL string)
}
rwctx.initStreamAggrConfig()
if enableMdx.GetOptionalArg(argIdx) {
mdxFilter := mdx.NewFilter()
rwctx.mdxFilter = mdxFilter
rwctx.mdxRowsPreserved = metrics.GetOrCreateCounter(fmt.Sprintf(`vmagent_remotewrite_mdx_rows_preserved_total{path=%q,url=%q}`, queuePath, sanitizedURL))
_ = metrics.NewGauge(fmt.Sprintf(`vmagent_remotewrite_mdx_tracked_instances{path=%q,url=%q}`, queuePath, sanitizedURL), func() float64 {
return float64(mdxFilter.VMInstancesCount())
})
}
return rwctx
}
@@ -1002,6 +1019,11 @@ func (rwctx *remoteWriteCtx) MustStop() {
rwctx.deduplicator.MustStop()
rwctx.deduplicator = nil
}
if rwctx.mdxFilter != nil {
rwctx.mdxFilter.MustStop()
rwctx.mdxFilter = nil
rwctx.mdxRowsPreserved = nil
}
for _, ps := range rwctx.pss {
ps.MustStop()
@@ -1017,6 +1039,7 @@ func (rwctx *remoteWriteCtx) MustStop() {
rwctx.rowsPushedAfterRelabel = nil
rwctx.rowsDroppedByRelabel = nil
}
// TryPushTimeSeries sends tss series to the configured remote write endpoint
@@ -1024,16 +1047,41 @@ func (rwctx *remoteWriteCtx) MustStop() {
// TryPushTimeSeries doesn't modify tss, so tss can be passed concurrently to TryPush across distinct rwctx instances.
func (rwctx *remoteWriteCtx) TryPushTimeSeries(tss []prompb.TimeSeries, forceDropSamplesOnFailure bool) bool {
var rctx *relabelCtx
var mctx *mdx.Ctx
var v *[]prompb.TimeSeries
defer func() {
if rctx == nil {
return
if v != nil {
*v = prompb.ResetTimeSeries(tss)
tssPool.Put(v)
}
if rctx != nil {
putRelabelCtx(rctx)
}
if mctx != nil {
mdx.PutContext(mctx)
}
*v = prompb.ResetTimeSeries(tss)
tssPool.Put(v)
putRelabelCtx(rctx)
}()
copyTimeSeriesIfNeeded := func() {
if v == nil {
v = tssPool.Get().(*[]prompb.TimeSeries)
tss = append(*v, tss...)
}
}
if rwctx.mdxFilter != nil {
mctx = mdx.GetContext()
// Make a copy of tss before applying relabeling in order to prevent
// from affecting time series for other remoteWrite.mdx configs.
copyTimeSeriesIfNeeded()
tss = rwctx.mdxFilter.Filter(mctx, tss)
if len(tss) == 0 {
return true
}
rowsCount := getRowsCount(tss)
rwctx.mdxRowsPreserved.Add(rowsCount)
}
// Apply relabeling
rcs := allRelabelConfigs.Load()
pcs := rcs.perURL[rwctx.idx]
@@ -1043,8 +1091,7 @@ func (rwctx *remoteWriteCtx) TryPushTimeSeries(tss []prompb.TimeSeries, forceDro
// from affecting time series for other remoteWrite.url configs.
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/467
// and https://github.com/VictoriaMetrics/VictoriaMetrics/issues/599
v = tssPool.Get().(*[]prompb.TimeSeries)
tss = append(*v, tss...)
copyTimeSeriesIfNeeded()
rowsCountBeforeRelabel := getRowsCount(tss)
tss = rctx.applyRelabeling(tss, pcs)
rowsCountAfterRelabel := getRowsCount(tss)
@@ -1062,8 +1109,7 @@ func (rwctx *remoteWriteCtx) TryPushTimeSeries(tss []prompb.TimeSeries, forceDro
if rctx == nil {
rctx = getRelabelCtx()
// Make a copy of tss before dropping aggregated series
v = tssPool.Get().(*[]prompb.TimeSeries)
tss = append(*v, tss...)
copyTimeSeriesIfNeeded()
}
tss = dropAggregatedSeries(tss, matchIdxs.B, rwctx.streamAggrDropInput)
} else if rwctx.streamAggrDropInput {
@@ -1071,8 +1117,7 @@ func (rwctx *remoteWriteCtx) TryPushTimeSeries(tss []prompb.TimeSeries, forceDro
if rctx == nil {
rctx = getRelabelCtx()
// Make a copy of tss before dropping aggregated series
v = tssPool.Get().(*[]prompb.TimeSeries)
tss = append(*v, tss...)
copyTimeSeriesIfNeeded()
}
tss = dropUnaggregatedSeries(tss, matchIdxs.B)
}
@@ -1191,15 +1236,6 @@ func getRowsCount(tss []prompb.TimeSeries) int {
}
return rowsCount
}
func newMapFromStrings(a []string) map[string]struct{} {
m := make(map[string]struct{}, len(a))
for _, s := range a {
m[s] = struct{}{}
}
return m
}
func getMaxHourlySeries() int {
limit := *maxHourlySeries
if limit == -1 || limit > math.MaxInt32 {

View File

@@ -6,7 +6,6 @@ import (
"net/url"
"reflect"
"sort"
"time"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/datasource"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompb"
@@ -94,10 +93,3 @@ Outer:
}
return
}
func durationToTime(pd *promutil.Duration) time.Time {
if pd == nil {
return time.Time{}
}
return time.UnixMilli(pd.Duration().Milliseconds())
}

View File

@@ -44,12 +44,19 @@ import (
var (
storagePath string
httpListenAddr string
// insert series from 1970-01-01T00:00:00
testStartTime = time.Unix(0, 0).UTC()
// Insert series from 2000-01-01T00:00:00.
testStartTime = time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)
testLogLevel = "ERROR"
disableAlertgroupLabel bool
)
func durationToTime(pd *promutil.Duration) time.Time {
if pd == nil {
return testStartTime
}
return testStartTime.Add(pd.Duration())
}
const (
testStoragePath = "vmalert-unittest"
)
@@ -282,7 +289,8 @@ func processFlags() {
func setUp() {
const maxConcurrentRequests = 4
vmstorage.Init(maxConcurrentRequests, promql.ResetRollupResultCacheIfNeeded)
maxQueueDuration := 5 * time.Second
vmstorage.Init(maxConcurrentRequests, maxQueueDuration, promql.ResetRollupResultCacheIfNeeded)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
readyCheckFunc := func() bool {

View File

@@ -145,10 +145,10 @@ func TestRuleValidate(t *testing.T) {
}
func TestGroupValidate_Failure(t *testing.T) {
f := func(group *Group, validateExpressions bool, errStrExpected string) {
f := func(data []byte, validateExpressions bool, errStrExpected string) {
t.Helper()
err := group.Validate(nil, validateExpressions)
_, err := parse(map[string][]byte{"test.yaml": data}, nil, validateExpressions)
if err == nil {
t.Fatalf("expecting non-nil error")
}
@@ -158,275 +158,238 @@ func TestGroupValidate_Failure(t *testing.T) {
}
}
f(&Group{}, false, "group name must be set")
f([]byte(`
groups:
- name: ""
`), false, "group name must be set")
f(&Group{
Name: "both record and alert are not set",
Rules: []Rule{
{
Expr: "sum(up == 0 ) by (host)",
For: promutil.NewDuration(10 * time.Millisecond),
},
{
Expr: "sumSeries(time('foo.bar',10))",
},
},
}, false, "invalid rule")
f([]byte(`
groups:
- name: both record and alert are not set
rules:
- expr: "sum(up == 0 ) by (host)"
for: 10ms
- expr: "sumSeries(time('foo.bar',10))"
`), false, "invalid rule")
f(&Group{
Name: "negative interval",
Interval: promutil.NewDuration(-1),
}, false, "interval shouldn't be lower than 0")
f([]byte(`
groups:
- name: negative interval
interval: -1ms
`), false, "interval shouldn't be lower than 0")
f(&Group{
Name: "too big eval_offset",
Interval: promutil.NewDuration(time.Minute),
EvalOffset: promutil.NewDuration(2 * time.Minute),
}, false, "eval_offset should be smaller than interval")
f([]byte(`
groups:
- name: too big eval_offset
interval: 1m
eval_offset: 2m
`), false, "eval_offset should be smaller than interval")
f(&Group{
Name: "too big negative eval_offset",
Interval: promutil.NewDuration(time.Minute),
EvalOffset: promutil.NewDuration(-2 * time.Minute),
}, false, "eval_offset should be smaller than interval")
f([]byte(`
groups:
- name: too big negative eval_offset
interval: 1m
eval_offset: -2m
`), false, "eval_offset should be smaller than interval")
limit := -1
f(&Group{
Name: "wrong limit",
Limit: &limit,
}, false, "invalid limit")
f([]byte(`
groups:
- name: wrong limit
limit: -1
`), false, "invalid limit")
f(&Group{
Name: "wrong concurrency",
Concurrency: -1,
}, false, "invalid concurrency")
f([]byte(`
groups:
- name: wrong concurrency
concurrency: -1
`), false, "invalid concurrency")
f(&Group{
Name: "test",
Rules: []Rule{
{
Alert: "alert",
Expr: "up == 1",
},
{
Alert: "alert",
Expr: "up == 1",
},
},
}, false, "duplicate")
f([]byte(`
groups:
- name: test
rules:
- alert: alert
expr: up == 1
- alert: alert
expr: up == 1
`), false, "duplicate")
f(&Group{
Name: "test",
Rules: []Rule{
{Alert: "alert", Expr: "up == 1", Labels: map[string]string{
"summary": "{{ value|query }}",
}},
{Alert: "alert", Expr: "up == 1", Labels: map[string]string{
"summary": "{{ value|query }}",
}},
},
}, false, "duplicate")
f([]byte(`
groups:
- name: test
rules:
- alert: alert
expr: up == 1
labels:
summary: "{{ value|query }}"
- alert: alert
expr: up == 1
labels:
summary: "{{ value|query }}"
`), false, "duplicate")
f(&Group{
Name: "test",
Rules: []Rule{
{Record: "record", Expr: "up == 1", Labels: map[string]string{
"summary": "{{ value|query }}",
}},
{Record: "record", Expr: "up == 1", Labels: map[string]string{
"summary": "{{ value|query }}",
}},
},
}, false, "duplicate")
f([]byte(`
groups:
- name: test
rules:
- record: record
expr: up == 1
labels:
summary: "{{ value|query }}"
- record: record
expr: up == 1
labels:
summary: "{{ value|query }}"
`), false, "duplicate")
f(&Group{
Name: "test",
Rules: []Rule{
{Alert: "alert", Expr: "up == 1", Labels: map[string]string{
"summary": "{{ value|query }}",
}},
{Alert: "alert", Expr: "up == 1", Labels: map[string]string{
"description": "{{ value|query }}",
}},
},
}, false, "duplicate")
f(&Group{
Name: "test",
Rules: []Rule{
{Record: "alert", Expr: "up == 1", Labels: map[string]string{
"summary": "{{ value|query }}",
}},
{Alert: "alert", Expr: "up == 1", Labels: map[string]string{
"summary": "{{ value|query }}",
}},
},
}, false, "duplicate")
f(&Group{
Name: "test thanos",
Type: NewRawType("thanos"),
Rules: []Rule{
{Alert: "alert", Expr: "up == 1", Labels: map[string]string{
"description": "{{ value|query }}",
}},
},
}, true, "unknown datasource type")
f([]byte(`
groups:
- name: test thanos
type: thanos
rules:
- alert: alert
expr: up == 1
labels:
description: "{{ value|query }}"
`), true, "unknown datasource type")
// validate expressions
f(&Group{
Name: "test prometheus expr",
Type: NewPrometheusType(),
Rules: []Rule{
{
Record: "record",
Expr: "up | 0",
},
},
}, true, "bad MetricsQL expr")
f([]byte(`
groups:
- name: test prometheus expr
type: prometheus
rules:
- record: record
expr: "up | 0"
`), true, "bad MetricsQL expr")
f(&Group{
Name: "test graphite expr",
Type: NewGraphiteType(),
Rules: []Rule{
{Alert: "alert", Expr: "up == 1", Labels: map[string]string{
"description": "some-description",
}},
},
}, true, "bad GraphiteQL expr")
f([]byte(`
groups:
- name: test graphite expr
type: graphite
rules:
- alert: alert
expr: up == 1
labels:
description: some-description
`), true, "bad GraphiteQL expr")
f(&Group{
Name: "test vlogs expr",
Type: NewVLogsType(),
Rules: []Rule{
{Alert: "alert", Expr: "stats count(*) as requests"},
},
}, true, "bad LogsQL expr")
f([]byte(`
groups:
- name: test vlogs expr
type: vlogs
rules:
- alert: alert
expr: "stats count(*) as requests"
`), true, "bad LogsQL expr")
f(&Group{
Name: "test vlogs expr",
Type: NewVLogsType(),
Rules: []Rule{
{Alert: "alert", Expr: "_time: 1m | stats by (path, _time: 1m) count(*) as requests"},
},
}, true, "bad LogsQL expr")
f([]byte(`
groups:
- name: test vlogs expr multipart
type: vlogs
rules:
- alert: alert
expr: "_time: 1m | stats by (path, _time: 1m) count(*) as requests"
`), true, "bad LogsQL expr")
f(&Group{
Name: "test graphite with prometheus expr",
Type: NewGraphiteType(),
Rules: []Rule{
{
Record: "r1",
ID: 1,
Expr: "sumSeries(time('foo.bar',10))",
For: promutil.NewDuration(10 * time.Millisecond),
},
{
Record: "r2",
ID: 2,
Expr: "sum(up == 0 ) by (host)",
},
},
}, true, "bad GraphiteQL expr")
f([]byte(`
groups:
- name: test graphite with prometheus expr
type: graphite
rules:
- record: r1
expr: "sumSeries(time('foo.bar',10))"
for: 10ms
- record: r2
expr: "sum(up == 0 ) by (host)"
`), true, "bad GraphiteQL expr")
f(&Group{
Name: "test vlogs with prometheus exp",
Type: NewVLogsType(),
Rules: []Rule{
{
Record: "r1",
Expr: "sum(up == 0 ) by (host)",
For: promutil.NewDuration(10 * time.Millisecond),
},
},
}, true, "bad LogsQL expr")
f([]byte(`
groups:
- name: test vlogs with prometheus expr
type: vlogs
rules:
- record: r1
expr: "sum(up == 0 ) by (host)"
for: 10ms
`), true, "bad LogsQL expr")
f(&Group{
Name: "test prometheus with vlogs exp",
Type: NewPrometheusType(),
Rules: []Rule{
{
Record: "r1",
Expr: "* | stats by (path) count()",
For: promutil.NewDuration(10 * time.Millisecond),
},
},
}, true, "bad MetricsQL expr")
f([]byte(`
groups:
- name: test prometheus with vlogs expr
type: prometheus
rules:
- record: r1
expr: "* | stats by (path) count()"
for: 10ms
`), true, "bad MetricsQL expr")
}
func TestGroupValidate_Success(t *testing.T) {
f := func(group *Group, validateAnnotations, validateExpressions bool) {
f := func(data []byte, validateAnnotations, validateExpressions bool) {
t.Helper()
var validateTplFn ValidateTplFn
if validateAnnotations {
validateTplFn = notifier.ValidateTemplates
}
err := group.Validate(validateTplFn, validateExpressions)
_, err := parse(map[string][]byte{"test.yaml": data}, validateTplFn, validateExpressions)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
}
f(&Group{
Name: "test",
Rules: []Rule{
{
Record: "record",
Expr: "up | 0",
},
},
}, false, false)
f([]byte(`
groups:
- name: test
rules:
- record: record
expr: "up | 0"
`), false, false)
f(&Group{
Name: "test",
Rules: []Rule{
{
Alert: "alert",
Expr: "up == 1",
Labels: map[string]string{
"summary": "{{ value|query }}",
},
},
},
}, false, false)
f([]byte(`
groups:
- name: test
rules:
- alert: alert
expr: up == 1
labels:
summary: "{{ value|query }}"
`), false, false)
// validate annotations
f(&Group{
Name: "test",
Rules: []Rule{
{
Alert: "alert",
Expr: "up == 1",
Labels: map[string]string{
"summary": `
{{ with printf "node_memory_MemTotal{job='node',instance='%s'}" "localhost" | query }}
{{ . | first | value | humanize1024 }}B
{{ end }}`,
},
},
},
}, true, false)
f([]byte(`
groups:
- name: test
rules:
- alert: alert
expr: up == 1
labels:
summary: "\n{{ with printf \"node_memory_MemTotal{job='node',instance='%s'}\" \"localhost\" | query }}\n {{ . | first | value | humanize1024 }}B\n{{ end }}"
`), true, false)
// validate expressions
f(&Group{
Name: "test prometheus",
Type: NewPrometheusType(),
Rules: []Rule{
{Alert: "alert", Expr: "up == 1", Labels: map[string]string{
"description": "{{ value|query }}",
}},
},
}, false, true)
f(&Group{
Name: "test victorialogs",
Type: NewVLogsType(),
Rules: []Rule{
{Alert: "alert", Expr: " _time: 1m | stats count(*) as requests", Labels: map[string]string{
"description": "{{ value|query }}",
}},
},
}, false, true)
f([]byte(`
groups:
- name: test prometheus
type: prometheus
rules:
- alert: alert
expr: up == 1
labels:
description: "{{ value|query }}"
`), false, true)
f([]byte(`
groups:
- name: test victorialogs
type: vlogs
rules:
- alert: alert
expr: " _time: 1m | stats count(*) as requests"
labels:
description: "{{ value|query }}"
`), false, true)
}
func TestHashRule_NotEqual(t *testing.T) {

View File

@@ -11,6 +11,7 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/vmalertutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/flagutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httputil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promauth"
)
@@ -94,6 +95,12 @@ func Init(extraParams url.Values) (QuerierBuilder, error) {
tr.MaxIdleConns = tr.MaxIdleConnsPerHost
}
tr.IdleConnTimeout = *idleConnectionTimeout
hc := &http.Client{Transport: tr}
datasourceURL, err := url.Parse(*addr)
if err != nil {
logger.Fatalf("BUG: cannot parse already parsed -datasource.url=%q: %s", *addr, err)
}
hc.Transport, datasourceURL = httputil.NewLoadBalancerTransport(tr, datasourceURL)
if extraParams == nil {
extraParams = url.Values{}
@@ -120,9 +127,9 @@ func Init(extraParams url.Values) (QuerierBuilder, error) {
}
return &Client{
c: &http.Client{Transport: tr},
c: hc,
authCfg: authCfg,
datasourceURL: strings.TrimSuffix(*addr, "/"),
datasourceURL: strings.TrimSuffix(datasourceURL.String(), "/"),
appendTypePrefix: *appendTypePrefix,
queryStep: *queryStep,
extraParams: extraParams,

View File

@@ -4,12 +4,14 @@ import (
"flag"
"fmt"
"net/http"
"net/url"
"time"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/datasource"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/vmalertutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/flagutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httputil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promauth"
)
@@ -76,7 +78,13 @@ func Init() (datasource.QuerierBuilder, error) {
return nil, fmt.Errorf("failed to create transport for -remoteRead.url=%q: %w", *addr, err)
}
tr.IdleConnTimeout = *idleConnectionTimeout
c := &http.Client{Transport: tr}
rrURL, err := url.Parse(*addr)
if err != nil {
logger.Fatalf("BUG: cannot parse already parsed -remoteRead.url=%q: %s", *addr, err)
}
c.Transport, rrURL = httputil.NewLoadBalancerTransport(tr, rrURL)
endpointParams, err := flagutil.ParseJSONMap(*oauth2EndpointParams)
if err != nil {
return nil, fmt.Errorf("cannot parse JSON for -remoteRead.oauth2.endpointParams=%s: %w", *oauth2EndpointParams, err)
@@ -89,6 +97,5 @@ func Init() (datasource.QuerierBuilder, error) {
if err != nil {
return nil, fmt.Errorf("failed to configure auth: %w", err)
}
c := &http.Client{Transport: tr}
return datasource.NewPrometheusClient(*addr, authCfg, false, c), nil
return datasource.NewPrometheusClient(rrURL.String(), authCfg, false, c), nil
}

View File

@@ -8,6 +8,7 @@ import (
"fmt"
"io"
"net/http"
"net/url"
"path"
"strings"
"sync"
@@ -111,12 +112,18 @@ func NewClient(ctx context.Context, cfg Config) (*Client, error) {
if cfg.Concurrency > 0 {
cc = cfg.Concurrency
}
hc := &http.Client{
Timeout: *sendTimeout,
Transport: cfg.Transport,
}
rwURL, err := url.Parse(cfg.Addr)
if err != nil {
logger.Fatalf("cannot parse already parsed -remoteWrite.url=%q: %s", cfg.Addr, err)
}
hc.Transport, rwURL = httputil.NewLoadBalancerTransport(hc.Transport, rwURL)
c := &Client{
c: &http.Client{
Timeout: *sendTimeout,
Transport: cfg.Transport,
},
addr: strings.TrimSuffix(cfg.Addr, "/"),
c: hc,
addr: strings.TrimSuffix(rwURL.String(), "/"),
authCfg: cfg.AuthCfg,
flushInterval: cfg.FlushInterval,
maxBatchSize: cfg.MaxBatchSize,

View File

@@ -97,6 +97,7 @@ type groupMetrics struct {
iterationMissed *metrics.Counter
iterationReset *metrics.Counter
iterationInterval *metrics.Gauge
iterationLimit *metrics.Gauge
}
// merges group rule labels into result map
@@ -336,6 +337,12 @@ func (g *Group) Init() {
i := g.Interval.Seconds()
return i
})
g.metrics.iterationLimit = g.metrics.set.NewGauge(fmt.Sprintf(`vmalert_rule_group_results_limit{%s}`, labels), func() float64 {
g.mu.RLock()
limit := g.Limit
g.mu.RUnlock()
return float64(limit)
})
for i := range g.Rules {
g.Rules[i].registerMetrics(g.metrics.set)
}

View File

@@ -543,10 +543,8 @@ func (rh *requestHandler) groups(rf *rulesFilter) *listGroupsResponse {
if !groupFound && !strings.Contains(strings.ToLower(rule.Name), rf.search) {
continue
}
ruleWithExtendedState := rule
ruleWithExtendedState.ExtendState()
if rf.extendedStates {
rule = ruleWithExtendedState
rule.ExtendState()
}
if !rf.matchesRule(&rule) {
continue
@@ -554,18 +552,7 @@ func (rh *requestHandler) groups(rf *rulesFilter) *listGroupsResponse {
if rf.excludeAlerts {
rule.Alerts = nil
}
ruleState := ruleWithExtendedState.State
if ruleState == "inactive" || ruleState == "pending" || ruleState == "firing" {
g.States["ok"]++
} else {
// 1. alerting rule with unhealthy or nomatch state
// 2. recording rule
g.States[ruleState]++
if ruleState == "nomatch" {
g.States["ok"]++
}
}
g.States[rule.State]++
filteredRules = append(filteredRules, rule)
}
if len(g.Rules) == 0 || len(filteredRules) > 0 {

View File

@@ -169,7 +169,7 @@
<thead>
<tr>
<th scope="col" class="w-60">Rule</th>
<th scope="col" class="w-20" class="text-center" title="How many series were produced by the rule">Series Returned</th>
<th scope="col" class="w-20" class="text-center" title="How many series were produced by the rule">Series</th>
<th scope="col" class="w-20" class="text-center" title="How many seconds ago rule was executed">Updated</th>
</tr>
</thead>

View File

@@ -535,7 +535,7 @@ func StreamListGroups(qw422016 *qt422016.Writer, r *http.Request, groups []*rule
<thead>
<tr>
<th scope="col" class="w-60">Rule</th>
<th scope="col" class="w-20" class="text-center" title="How many series were produced by the rule">Series Returned</th>
<th scope="col" class="w-20" class="text-center" title="How many series were produced by the rule">Series</th>
<th scope="col" class="w-20" class="text-center" title="How many seconds ago rule was executed">Updated</th>
</tr>
</thead>

View File

@@ -118,9 +118,10 @@ type AccessLogFilters struct {
}
func (ui *UserInfo) logRequest(r *http.Request, userName string, statusCode int, duration time.Duration) {
if ui.AccessLog == nil {
if ui == nil || ui.AccessLog == nil {
return
}
filters := ui.AccessLog.Filters
if filters != nil && len(filters.SkipStatusCodes) > 0 {
if slices.Contains(filters.SkipStatusCodes, statusCode) {
@@ -134,6 +135,17 @@ func (ui *UserInfo) logRequest(r *http.Request, userName string, statusCode int,
r.Host, requestURI, statusCode, remoteAddr, r.UserAgent(), r.Referer(), duration.Milliseconds(), userName)
}
// hasAnyURLs reports whether ui has at least one backend URL route configured.
// It is used only for unauthorized_user config section, since other users
// must always have either URLPrefix or URLMaps set.
func (ui *UserInfo) hasAnyURLs() bool {
if ui == nil {
return false
}
return ui.URLPrefix != nil || len(ui.URLMaps) > 0 || ui.DefaultURL != nil
}
// HeadersConf represents config for request and response headers.
type HeadersConf struct {
RequestHeaders []*Header `yaml:"headers,omitempty"`
@@ -983,8 +995,11 @@ func parseAuthConfig(data []byte) (*AuthConfig, error) {
if err := parseJWTPlaceholdersForUserInfo(ui, false); err != nil {
return nil, err
}
if err := ui.initURLs(); err != nil {
return nil, err
if ui.hasAnyURLs() {
if err := ui.initURLs(); err != nil {
return nil, err
}
}
metricLabels, err := ui.getMetricLabels()

View File

@@ -140,6 +140,18 @@ users:
- "ProjectID: {{.MetricsProjectID}}"
url_prefix: "http://vminsert:8480/insert/prometheus"
# JWT-based routing that relies solely on custom claims.
# The `vm_access` claim is missing, default value will be used.
# e.g. {"role": "admin"}.
- name: jwt-custom-claims
jwt:
skip_verify: true
vm_default_access_claim:
metrics_account_id: 1
match_claims:
role: admin
url_prefix: "http://vmselect-admin:8481/select/0/prometheus"
# Requests without Authorization header are proxied according to `unauthorized_user` section.
# Requests are proxied in round-robin fashion between `url_prefix` backends.
# The deny_partial_response query arg is added to all the proxied requests.

View File

@@ -65,6 +65,8 @@ type JWTConfig struct {
MatchClaims map[string]string `yaml:"match_claims,omitempty"`
parsedMatchClaims []*jwt.Claim
DefaultVMAccessClaim *jwt.VMAccessClaim `yaml:"default_vm_access_claim,omitempty"`
// verifierPool is used to verify JWT tokens.
// It is initialized from PublicKeys and/or PublicKeyFiles.
// In this case, it is initialized once at config reload and never updated until next reload
@@ -432,7 +434,6 @@ func validateJWTPlaceholdersForURL(up *URLPrefix, isAllowed bool) error {
}
if strings.Contains(p, placeholderPrefix) {
return fmt.Errorf("invalid placeholder found in URL request path: %q, supported values are: %s", bu.Path, strings.Join(allPlaceholders, ", "))
}
}
for param, values := range bu.Query() {
@@ -487,7 +488,6 @@ func hasAnyPlaceholders(u *url.URL) bool {
return true
}
}
}
return false
}

View File

@@ -6,6 +6,7 @@ import (
"flag"
"fmt"
"io"
"math/rand/v2"
"net"
"net/http"
"net/textproto"
@@ -31,6 +32,7 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/lib/procutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promauth"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/pushmetrics"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/timerpool"
)
var (
@@ -173,11 +175,12 @@ func requestHandler(w http.ResponseWriter, r *http.Request) bool {
if len(ats) == 0 {
// Process requests for unauthorized users
ui := authConfig.Load().UnauthorizedUser
if ui != nil {
if ui.hasAnyURLs() {
processUserRequest(w, r, ui, nil)
return true
}
ui.logRequest(r, `unauthorized`, http.StatusUnauthorized, 0)
handleMissingAuthorizationError(w)
return true
}
@@ -191,17 +194,23 @@ func requestHandler(w http.ResponseWriter, r *http.Request) bool {
logger.Panicf("BUG: unexpected nil jwt token for user %q", ui.name())
}
defer putToken(tkn)
processUserRequest(w, r, ui, tkn)
return true
// Call processUserRequest only if the token contains the vm_access claim
// or a default claim is configured; otherwise fall through to unauthorized_user.
if tkn.HasVMAccessClaim() || ui.JWT.DefaultVMAccessClaim != nil {
processUserRequest(w, r, ui, tkn)
return true
}
}
uu := authConfig.Load().UnauthorizedUser
if uu != nil {
if uu.hasAnyURLs() {
processUserRequest(w, r, uu, nil)
return true
}
invalidAuthTokenRequests.Inc()
slowdownUnauthorizedResponse(r)
uu.logRequest(r, `unauthorized`, http.StatusUnauthorized, 0)
if *logInvalidAuthTokens {
err := fmt.Errorf("cannot authorize request with auth tokens %q", ats)
err = &httpserver.ErrorWithStatusCode{
@@ -377,7 +386,7 @@ func bufferRequestBody(ctx context.Context, r io.ReadCloser, userName string) (i
return nil, &httpserver.ErrorWithStatusCode{
Err: fmt.Errorf("reject request from the user %s because the request body couldn't be read in -maxQueueDuration=%s; read %d bytes in %s",
userName, *maxQueueDuration, len(buf), d.Truncate(time.Second)),
StatusCode: http.StatusBadRequest,
StatusCode: http.StatusRequestTimeout,
}
}
@@ -424,8 +433,12 @@ func processRequest(w http.ResponseWriter, r *http.Request, ui *UserInfo, tkn *j
}
targetURL := bu.url
if tkn != nil {
vmac := tkn.VMAccess()
if !tkn.HasVMAccessClaim() {
vmac = ui.JWT.DefaultVMAccessClaim
}
// for security reasons allow templating only for configured url values and headers
targetURL, hc = replaceJWTPlaceholders(bu, hc, tkn.VMAccess())
targetURL, hc = replaceJWTPlaceholders(bu, hc, vmac)
}
if isDefault {
// Don't change path and add request_path query param for default route.
@@ -881,3 +894,20 @@ func debugInfo(u *url.URL, r *http.Request) string {
fmt.Fprint(s, ")")
return s.String()
}
// slowdownUnauthorizedResponse adds a random delay in the [2..3] seconds range before returning an unauthorized response.
// This reduces the effectiveness of brute-force.
//
// Recommended by OWASP Top10:
// https://owasp.org/Top10/2025/A07_2025-Authentication_Failures
func slowdownUnauthorizedResponse(r *http.Request) {
d := 2*time.Second + time.Duration(rand.IntN(1000))*time.Millisecond
t := timerpool.Get(d)
select {
case <-t.C:
case <-r.Context().Done():
}
timerpool.Put(t)
}

View File

@@ -0,0 +1,54 @@
//go:build synctest
package main
import (
"context"
"net/http"
"testing"
"testing/synctest"
"time"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httpserver"
)
func TestBufferRequestBody_Timeout(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
defaultMaxQueueDuration := *maxQueueDuration
defer func() {
*maxQueueDuration = defaultMaxQueueDuration
}()
*maxQueueDuration = 100 * time.Millisecond
ctx, cancel := context.WithTimeout(context.Background(), *maxQueueDuration)
defer cancel()
_, err := bufferRequestBody(ctx, &timeoutBody{delay: *maxQueueDuration + 100*time.Millisecond}, "foo")
if err == nil {
t.Fatalf("expecting non-nil error")
}
esc, ok := err.(*httpserver.ErrorWithStatusCode)
if !ok {
t.Fatalf("unexpected error type: %s", err)
}
if esc.StatusCode != http.StatusRequestTimeout {
t.Fatalf("read request body timeout meet unexpected status code; got %d; want %d", esc.StatusCode, http.StatusRequestTimeout)
}
})
}
type timeoutBody struct {
delay time.Duration
}
func (r *timeoutBody) Read(_ []byte) (int, error) {
time.Sleep(r.delay)
return 0, context.DeadlineExceeded
}
func (r *timeoutBody) Close() error {
return nil
}

View File

@@ -739,6 +739,12 @@ users:
"vm_access": map[string]any{},
}, false)
// token without vm_access claim, but with a custom claim usable for routing
roleToken := genToken(t, map[string]any{
"exp": time.Now().Add(10 * time.Minute).Unix(),
"role": "admin",
}, true)
fullToken := genToken(t, map[string]any{
"exp": time.Now().Add(10 * time.Minute).Unix(),
"vm_access": map[string]any{
@@ -779,6 +785,45 @@ statusCode=401
Unauthorized`
f(simpleCfgStr, request, responseExpected)
// token without vm_access claim should fall through to unauthorized_user
request = httptest.NewRequest(`GET`, "http://some-host.com/abc", nil)
request.Header.Set(`Authorization`, `Bearer `+noVMAccessClaimToken)
responseExpected = `
statusCode=200
path: /bar/abc
query:
headers:`
f(fmt.Sprintf(`
unauthorized_user:
url_prefix: {BACKEND}/bar
users:
- jwt:
public_keys:
- %q
match_claims:
role: admin
url_prefix: {BACKEND}/foo`, string(publicKeyPEM)), request, responseExpected)
// token without vm_access claim is accepted when default_vm_access_claim configured
request = httptest.NewRequest(`GET`, "http://some-host.com/abc", nil)
request.Header.Set(`Authorization`, `Bearer `+roleToken)
responseExpected = `
statusCode=200
path: /foo/abc
query:
headers:`
f(fmt.Sprintf(`
users:
- jwt:
public_keys:
- %q
default_vm_access_claim:
metrics_account_id: 10
metrics_project_id: 10
match_claims:
role: admin
url_prefix: {BACKEND}/foo`, string(publicKeyPEM)), request, responseExpected)
// expired token
request = httptest.NewRequest(`GET`, "http://some-host.com/abc", nil)
request.Header.Set(`Authorization`, `Bearer `+expiredToken)

View File

@@ -8,6 +8,7 @@ import (
"fmt"
"io"
"net/http"
"net/url"
"strings"
"sync"
"time"
@@ -111,7 +112,7 @@ func AddExtraLabelsToImportPath(path string, extraLabels []string) (string, erro
if strings.Contains(dst, "?") {
separator = "&"
}
dst += fmt.Sprintf("%sextra_label=%s", separator, extraLabel)
dst += fmt.Sprintf("%sextra_label=%s", separator, url.QueryEscape(extraLabel))
}
return dst, nil
}

View File

@@ -33,11 +33,14 @@ func TestAddExtraLabelsToImportPath_Success(t *testing.T) {
f("/api/v1/import", nil, "/api/v1/import")
// ok one extra label
f("/api/v1/import", []string{"instance=host-1"}, "/api/v1/import?extra_label=instance=host-1")
f("/api/v1/import", []string{"instance=host-1"}, "/api/v1/import?extra_label=instance%3Dhost-1")
// ok two extra labels
f("/api/v1/import", []string{"instance=host-2", "job=vmagent"}, "/api/v1/import?extra_label=instance=host-2&extra_label=job=vmagent")
f("/api/v1/import", []string{"instance=host-2", "job=vmagent"}, "/api/v1/import?extra_label=instance%3Dhost-2&extra_label=job%3Dvmagent")
// ok two extra with exist param
f("/api/v1/import?timeout=50", []string{"instance=host-2", "job=vmagent"}, "/api/v1/import?timeout=50&extra_label=instance=host-2&extra_label=job=vmagent")
f("/api/v1/import?timeout=50", []string{"instance=host-2", "job=vmagent"}, "/api/v1/import?timeout=50&extra_label=instance%3Dhost-2&extra_label=job%3Dvmagent")
// ok special chars in label value
f("/api/v1/import", []string{"team=a&b"}, "/api/v1/import?extra_label=team%3Da%26b")
}

View File

@@ -175,13 +175,19 @@ func (ctx *InsertCtx) WriteMetadata(mmpbs []prompb.MetricMetadata) error {
}
mms := ctx.mms
mms = slicesutil.SetLength(mms, len(mmpbs))
for idx, mmpb := range mmpbs {
mm := &mms[idx]
var cnt int
for _, mmpb := range mmpbs {
if timeserieslimits.IsMetricMetadataExceeding(&mmpb) {
continue
}
mm := &mms[cnt]
mm.MetricFamilyName = bytesutil.ToUnsafeBytes(mmpb.MetricFamilyName)
mm.Help = bytesutil.ToUnsafeBytes(mmpb.Help)
mm.Type = mmpb.Type
mm.Unit = bytesutil.ToUnsafeBytes(mmpb.Unit)
cnt++
}
mms = mms[:cnt]
ctx.mms = mms
err := vmstorage.VMInsertAPI.WriteMetadata(mms)
@@ -201,14 +207,19 @@ func (ctx *InsertCtx) WritePromMetadata(mmps []prometheus.Metadata) error {
}
mms := ctx.mms
mms = slicesutil.SetLength(mms, len(mmps))
for idx, mmpb := range mmps {
mm := &mms[idx]
var cnt int
for _, mmpb := range mmps {
mm := &mms[cnt]
if timeserieslimits.IsPrometheusMetadataExceeding(&mmpb) {
continue
}
mm.MetricFamilyName = bytesutil.ToUnsafeBytes(mmpb.Metric)
mm.Help = bytesutil.ToUnsafeBytes(mmpb.Help)
mm.Type = mmpb.Type
cnt++
}
mms = mms[:cnt]
ctx.mms = mms
err := vmstorage.VMInsertAPI.WriteMetadata(mms)
if err != nil {
return &httpserver.ErrorWithStatusCode{

View File

@@ -49,7 +49,7 @@ func InsertHandlerForHTTP(req *http.Request) error {
}
q := req.URL.Query()
precision := q.Get("precision")
// Read db tag from https://docs.influxdata.com/influxdb/v1.7/tools/api/#write-http-endpoint
// Read db tag from https://docs.influxdata.com/influxdb/v1/api/write/#operation/PostWrite
db := q.Get("db")
encoding := req.Header.Get("Content-Encoding")
isStreamMode := req.Header.Get("Stream-Mode") == "1"

View File

@@ -68,9 +68,11 @@ var (
"at -opentsdbHTTPListenAddr . See https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt")
configAuthKey = flagutil.NewPassword("configAuthKey", "Authorization key for accessing /config page. It must be passed via authKey query arg. It overrides -httpAuth.*")
reloadAuthKey = flagutil.NewPassword("reloadAuthKey", "Auth key for /-/reload http endpoint. It must be passed via authKey query arg. It overrides httpAuth.* settings.")
maxLabelsPerTimeseries = flag.Int("maxLabelsPerTimeseries", 40, "The maximum number of labels per time series to be accepted. Series with superfluous labels are ignored. In this case the vm_rows_ignored_total{reason=\"too_many_labels\"} metric at /metrics page is incremented")
maxLabelNameLen = flag.Int("maxLabelNameLen", 256, "The maximum length of label name in the accepted time series. Series with longer label name are ignored. In this case the vm_rows_ignored_total{reason=\"too_long_label_name\"} metric at /metrics page is incremented")
maxLabelValueLen = flag.Int("maxLabelValueLen", 4*1024, "The maximum length of label values in the accepted time series. Series with longer label value are ignored. In this case the vm_rows_ignored_total{reason=\"too_long_label_value\"} metric at /metrics page is incremented")
maxLabelsPerTimeseries = flag.Int("maxLabelsPerTimeseries", 40, "The maximum number of labels per time series to be accepted. Series with superfluous labels are ignored. In this case the vm_rows_ignored_total{reason=\"too_many_labels\"} metric at /metrics page is incremented.")
maxLabelNameLen = flag.Int("maxLabelNameLen", 256, "The maximum length of label name in the accepted time series. Series with longer label name are ignored. In this case the vm_rows_ignored_total{reason=\"too_long_label_name\"} metric at /metrics page is incremented. "+
"Value must be in range 1..65535.")
maxLabelValueLen = flag.Int("maxLabelValueLen", 4*1024, "The maximum length of label values in the accepted time series. Series with longer label value are ignored. In this case the vm_rows_ignored_total{reason=\"too_long_label_value\"} metric at /metrics page is incremented. "+
"Value must be in range 1..65535.")
)
var (
@@ -106,7 +108,7 @@ func Init() {
promscrape.Init(func(_ *auth.Token, wr *prompb.WriteRequest) {
prompush.Push(wr)
})
timeserieslimits.Init(*maxLabelsPerTimeseries, *maxLabelNameLen, *maxLabelValueLen)
timeserieslimits.MustInit(*maxLabelsPerTimeseries, *maxLabelNameLen, *maxLabelValueLen)
}
// Stop stops vminsert.

View File

@@ -541,7 +541,7 @@ func handleStaticAndSimpleRequests(w http.ResponseWriter, r *http.Request, path
return true
case "/metric-relabel-debug":
promscrapeMetricRelabelDebugRequests.Inc()
promscrape.WriteMetricRelabelDebug(w, r)
promscrape.WriteMetricRelabelDebug(w, r, "", nil)
return true
case "/target-relabel-debug":
promscrapeTargetRelabelDebugRequests.Inc()

View File

@@ -956,6 +956,7 @@ func queryRangeHandler(qt *querytracer.Tracer, startTime time.Time, w http.Respo
start, end, step int64, r *http.Request, ct int64, etfs [][]storage.TagFilter) error {
deadline := searchutil.GetDeadlineForQuery(r, startTime)
mayCache := !httputil.GetBool(r, "nocache")
optimizeRepeatedBinaryOpSubexprs := httputil.GetBool(r, "optimize_repeated_binary_op_subexprs")
lookbackDelta, err := getMaxLookback(r)
if err != nil {
return err
@@ -977,18 +978,19 @@ func queryRangeHandler(qt *querytracer.Tracer, startTime time.Time, w http.Respo
}
ec := &promql.EvalConfig{
Start: start,
End: end,
Step: step,
MaxPointsPerSeries: *maxPointsPerTimeseries,
MaxSeries: 0, // let vmstorage use maxUniqueTimeseries by default
QuotedRemoteAddr: httpserver.GetQuotedRemoteAddr(r),
Deadline: deadline,
MayCache: mayCache,
LookbackDelta: lookbackDelta,
RoundDigits: getRoundDigits(r),
EnforcedTagFilterss: etfs,
CacheTagFilters: etfs,
Start: start,
End: end,
Step: step,
MaxPointsPerSeries: *maxPointsPerTimeseries,
MaxSeries: 0, // let vmstorage use maxUniqueTimeseries by default
QuotedRemoteAddr: httpserver.GetQuotedRemoteAddr(r),
Deadline: deadline,
MayCache: mayCache,
OptimizeRepeatedBinaryOpSubexprs: optimizeRepeatedBinaryOpSubexprs,
LookbackDelta: lookbackDelta,
RoundDigits: getRoundDigits(r),
EnforcedTagFilterss: etfs,
CacheTagFilters: etfs,
GetRequestURI: func() string {
return httpserver.GetRequestURI(r)
},

View File

@@ -172,7 +172,13 @@ func newBinaryOpFunc(bf func(left, right float64, isBool bool) float64) binaryOp
left = removeEmptySeries(left)
right = removeEmptySeries(right)
}
if len(left) == 0 || len(right) == 0 {
if len(left) == 0 && len(right) == 0 {
return nil, nil
}
if len(left) == 0 && bfa.be.FillLeft == nil {
return nil, nil
}
if len(right) == 0 && bfa.be.FillRight == nil {
return nil, nil
}
left, right, dst, err := adjustBinaryOpTags(bfa.be, left, right)
@@ -183,6 +189,8 @@ func newBinaryOpFunc(bf func(left, right float64, isBool bool) float64) binaryOp
logger.Panicf("BUG: len(left) must match len(right) and len(dst); got %d vs %d vs %d", len(left), len(right), len(dst))
}
isBool := bfa.be.Bool
fillLeft := bfa.be.FillLeft
fillRight := bfa.be.FillRight
for i, tsLeft := range left {
leftValues := tsLeft.Values
rightValues := right[i].Values
@@ -193,6 +201,19 @@ func newBinaryOpFunc(bf func(left, right float64, isBool bool) float64) binaryOp
}
for j, a := range leftValues {
b := rightValues[j]
leftIsNaN := math.IsNaN(a)
rightIsNaN := math.IsNaN(b)
// apply the fill value when either the left or right side is NaN, but not both.
if leftIsNaN && rightIsNaN {
dstValues[j] = bf(a, b, isBool)
continue
}
if leftIsNaN && fillLeft != nil {
a = fillLeft.N
}
if rightIsNaN && fillRight != nil {
b = fillRight.N
}
dstValues[j] = bf(a, b, isBool)
}
}
@@ -226,7 +247,7 @@ func adjustBinaryOpTags(be *metricsql.BinaryOpExpr, left, right []*timeseries) (
}
}
// Slow path: `vector op vector` or `a op {on|ignoring} {group_left|group_right} b`
// Slow path: `vector op vector` or `a op {on|ignoring} {group_left|group_right} {fill|fill_left|fill_right} b`
var rvsLeft, rvsRight []*timeseries
mLeft, mRight := createTimeseriesMapByTagSet(be, left, right)
joinOp := strings.ToLower(be.JoinModifier.Op)
@@ -239,10 +260,27 @@ func adjustBinaryOpTags(be *metricsql.BinaryOpExpr, left, right []*timeseries) (
// Add __name__ to groupTags if metric name must be preserved.
groupTags = append(groupTags[:len(groupTags):len(groupTags)], "__name__")
}
// Add missing keys from mRight to mLeft when fill_left()/fill() modifier is used
if be.FillLeft != nil {
for k := range mRight {
if _, ok := mLeft[k]; !ok {
mLeft[k] = nil
}
}
}
for k, tssLeft := range mLeft {
tssRight := mRight[k]
if len(tssLeft) == 0 {
if be.FillLeft == nil {
logger.Panicf("BUG: unexpected empty tssLeft for key %q when FillLeft is nil", k)
}
tssLeft = []*timeseries{newFillTimeseries(be, tssRight[0])}
}
if len(tssRight) == 0 {
continue
if be.FillRight == nil {
continue
}
tssRight = []*timeseries{newFillTimeseries(be, tssLeft[0])}
}
switch joinOp {
case "group_left":
@@ -287,6 +325,28 @@ func adjustBinaryOpTags(be *metricsql.BinaryOpExpr, left, right []*timeseries) (
return rvsLeft, rvsRight, dst, nil
}
// newFillTimeseries returns a time series filled with NaN values for the fill_left()/fill_right()/fill() modifiers.
// These NaN values will be replaced later with the fill value if needed.
func newFillTimeseries(be *metricsql.BinaryOpExpr, src *timeseries) *timeseries {
var ts timeseries
ts.CopyFromShallowTimestamps(src)
if !be.KeepMetricNames {
ts.MetricName.ResetMetricGroup()
}
groupTags := be.GroupModifier.Args
switch strings.ToLower(be.GroupModifier.Op) {
case "on":
ts.MetricName.RemoveTagsOn(groupTags)
default:
ts.MetricName.RemoveTagsIgnoring(groupTags)
}
values := ts.Values
for i := range values {
values[i] = math.NaN()
}
return &ts
}
func ensureSingleTimeseries(side string, be *metricsql.BinaryOpExpr, tss []*timeseries) error {
if len(tss) == 0 {
logger.Panicf("BUG: tss must contain at least one value")

View File

@@ -132,6 +132,9 @@ type EvalConfig struct {
// Whether the response can be cached.
MayCache bool
// Whether repeated cacheable binary op subexpressions can be optimized.
OptimizeRepeatedBinaryOpSubexprs bool
// LookbackDelta is analog to `-query.lookback-delta` from Prometheus.
LookbackDelta int64
@@ -171,6 +174,7 @@ func copyEvalConfig(src *EvalConfig) *EvalConfig {
ec.MaxPointsPerSeries = src.MaxPointsPerSeries
ec.Deadline = src.Deadline
ec.MayCache = src.MayCache
ec.OptimizeRepeatedBinaryOpSubexprs = src.OptimizeRepeatedBinaryOpSubexprs
ec.LookbackDelta = src.LookbackDelta
ec.RoundDigits = src.RoundDigits
ec.EnforcedTagFilterss = src.EnforcedTagFilterss
@@ -420,18 +424,7 @@ func evalBinaryOp(qt *querytracer.Tracer, ec *EvalConfig, be *metricsql.BinaryOp
if bf == nil {
return nil, fmt.Errorf(`unknown binary op %q`, be.Op)
}
var err error
var tssLeft, tssRight []*timeseries
switch strings.ToLower(be.Op) {
case "and", "if":
// Fetch right-side series at first, since it usually contains
// lower number of time series for `and` and `if` operator.
// This should produce more specific label filters for the left side of the query.
// This, in turn, should reduce the time to select series for the left side of the query.
tssRight, tssLeft, err = execBinaryOpArgs(qt, ec, be.Right, be.Left, be)
default:
tssLeft, tssRight, err = execBinaryOpArgs(qt, ec, be.Left, be.Right, be)
}
tssLeft, tssRight, err := execBinaryOpArgs(qt, ec, be)
if err != nil {
return nil, fmt.Errorf("cannot execute %q: %w", be.AppendString(nil), err)
}
@@ -447,6 +440,29 @@ func evalBinaryOp(qt *querytracer.Tracer, ec *EvalConfig, be *metricsql.BinaryOp
return rv, nil
}
// binaryOpEvalOrder might change the order of evaluation of the left and right sides of a binary operation,
// when there is chance to push down common label filters from exprFirst to exprSecond in the following executions.
func binaryOpEvalOrder(be *metricsql.BinaryOpExpr) (exprFirst, exprSecond metricsql.Expr) {
exprFirst, exprSecond = be.Left, be.Right
switch strings.ToLower(be.Op) {
case "and", "if":
// For `and` and `if`, fetch the right-side series first, since it usually contains
// fewer time series and yields more specific filters for the left side.
exprFirst, exprSecond = be.Right, be.Left
}
if be.FillLeft != nil && be.FillRight == nil {
// For `fill_left(<value>)`, the unmatched series can only come from the right side, so evaluate it first.
exprFirst, exprSecond = be.Right, be.Left
}
return exprFirst, exprSecond
}
// canPushdownCommonFilters decides if common label filters can be pushed down from one side of a binary operation to the other.
//
// Common filters cannot be pushed down when:
// - the operator is `or` or `default`;
// - either side is an aggregation function without explicit grouping;
// - fill(<value>) modifier is used.
func canPushdownCommonFilters(be *metricsql.BinaryOpExpr) bool {
switch strings.ToLower(be.Op) {
case "or", "default":
@@ -455,6 +471,10 @@ func canPushdownCommonFilters(be *metricsql.BinaryOpExpr) bool {
if isAggrFuncWithoutGrouping(be.Left) || isAggrFuncWithoutGrouping(be.Right) {
return false
}
// Filters cannot be propagated when fill(<value>) modifier is used.
if be.FillLeft != nil && be.FillRight != nil {
return false
}
return true
}
@@ -466,82 +486,185 @@ func isAggrFuncWithoutGrouping(e metricsql.Expr) bool {
return len(afe.Modifier.Args) == 0
}
func execBinaryOpArgs(qt *querytracer.Tracer, ec *EvalConfig, exprFirst, exprSecond metricsql.Expr, be *metricsql.BinaryOpExpr) ([]*timeseries, []*timeseries, error) {
if !canPushdownCommonFilters(be) {
// Execute exprFirst and exprSecond in parallel, since it is impossible to pushdown common filters
// from exprFirst to exprSecond.
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2886
qt = qt.NewChild("execute left and right sides of %q in parallel", be.Op)
defer qt.Done()
var wg sync.WaitGroup
var tssFirst []*timeseries
var errFirst error
qtFirst := qt.NewChild("expr1")
wg.Go(func() {
tssFirst, errFirst = evalExpr(qtFirst, ec, exprFirst)
qtFirst.Done()
})
var tssSecond []*timeseries
var errSecond error
qtSecond := qt.NewChild("expr2")
wg.Go(func() {
tssSecond, errSecond = evalExpr(qtSecond, ec, exprSecond)
qtSecond.Done()
})
wg.Wait()
if errFirst != nil {
return nil, nil, errFirst
func execBinaryOpArgs(qt *querytracer.Tracer, ec *EvalConfig, be *metricsql.BinaryOpExpr) ([]*timeseries, []*timeseries, error) {
exprFirst, exprSecond := binaryOpEvalOrder(be)
firstIsLeft := exprFirst == be.Left
sortResult := func(tssFirst, tssSecond []*timeseries) ([]*timeseries, []*timeseries, error) {
if firstIsLeft {
return tssFirst, tssSecond, nil
}
if errSecond != nil {
return nil, nil, errSecond
return tssSecond, tssFirst, nil
}
if canPushdownCommonFilters(be) {
// Execute binary operation in the following way:
//
// 1) execute the exprFirst
// 2) get common label filters for series returned at step 1
// 3) push down the found common label filters to exprSecond. This filters out unneeded series
// during exprSecond execution instead of spending compute resources on extracting and processing these series
// before they are dropped later when matching time series according to https://prometheus.io/docs/prometheus/latest/querying/operators/#vector-matching
// 4) execute the exprSecond with possible additional filters found at step 3
//
// Typical use cases:
// - Kubernetes-related: show pod creation time with the node name:
//
// kube_pod_created{namespace="prod"} * on (uid) group_left(node) kube_pod_info
//
// Without the optimization `kube_pod_info` would select and spend compute resources
// for more time series than needed. The selected time series would be dropped later
// when matching time series on the right and left sides of binary operand.
//
// - Generic alerting queries, which rely on `info` metrics.
// See https://grafana.com/blog/2021/08/04/how-to-use-promql-joins-for-more-effective-queries-of-prometheus-metrics-at-scale/
//
// - Queries, which get additional labels from `info` metrics.
// See https://www.robustperception.io/exposing-the-software-version-to-prometheus
tssFirst, err := evalExpr(qt, ec, exprFirst)
if err != nil {
return nil, nil, err
}
if len(tssFirst) == 0 && !strings.EqualFold(be.Op, "or") {
// Fast path: there is no sense in executing the exprSecond when exprFirst returns an empty result,
// since the "exprFirst op exprSecond" would return an empty result in any case.
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3349
return nil, nil, nil
}
lfs := getCommonLabelFilters(tssFirst)
lfs = metricsql.TrimFiltersByGroupModifier(lfs, be)
exprSecond = metricsql.PushdownBinaryOpFilters(exprSecond, lfs)
tssSecond, err := evalExpr(qt, ec, exprSecond)
if err != nil {
return nil, nil, err
}
return sortResult(tssFirst, tssSecond)
}
// Execute exprFirst and exprSecond sequentially if there are cacheable repeated subexpressions
// in exprFirst and exprSecond.
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/10575
if shouldOptimizeRepeatedBinaryOpSubexprs(ec, exprFirst, exprSecond) {
qt = qt.NewChild("execute left and right sides of %q sequentially because repeated cacheable subexpression was found", be.Op)
defer qt.Done()
qtFirst := qt.NewChild("expr1")
tssFirst, err := evalExpr(qtFirst, ec, exprFirst)
qtFirst.Done()
if err != nil {
return nil, nil, err
}
qtSecond := qt.NewChild("expr2")
tssSecond, err := evalExpr(qtSecond, ec, exprSecond)
qtSecond.Done()
if err != nil {
return nil, nil, err
}
return tssFirst, tssSecond, nil
}
// Execute binary operation in the following way:
//
// 1) execute the exprFirst
// 2) get common label filters for series returned at step 1
// 3) push down the found common label filters to exprSecond. This filters out unneeded series
// during exprSecond execution instead of spending compute resources on extracting and processing these series
// before they are dropped later when matching time series according to https://prometheus.io/docs/prometheus/latest/querying/operators/#vector-matching
// 4) execute the exprSecond with possible additional filters found at step 3
//
// Typical use cases:
// - Kubernetes-related: show pod creation time with the node name:
//
// kube_pod_created{namespace="prod"} * on (uid) group_left(node) kube_pod_info
//
// Without the optimization `kube_pod_info` would select and spend compute resources
// for more time series than needed. The selected time series would be dropped later
// when matching time series on the right and left sides of binary operand.
//
// - Generic alerting queries, which rely on `info` metrics.
// See https://grafana.com/blog/2021/08/04/how-to-use-promql-joins-for-more-effective-queries-of-prometheus-metrics-at-scale/
//
// - Queries, which get additional labels from `info` metrics.
// See https://www.robustperception.io/exposing-the-software-version-to-prometheus
tssFirst, err := evalExpr(qt, ec, exprFirst)
if err != nil {
return nil, nil, err
// Execute exprFirst and exprSecond in parallel, since it is impossible to pushdown common filters
// from exprFirst to exprSecond.
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2886
qt = qt.NewChild("execute left and right sides of %q in parallel", be.Op)
defer qt.Done()
var wg sync.WaitGroup
var tssFirst []*timeseries
var errFirst error
qtFirst := qt.NewChild("expr1")
wg.Go(func() {
tssFirst, errFirst = evalExpr(qtFirst, ec, exprFirst)
qtFirst.Done()
})
var tssSecond []*timeseries
var errSecond error
qtSecond := qt.NewChild("expr2")
wg.Go(func() {
tssSecond, errSecond = evalExpr(qtSecond, ec, exprSecond)
qtSecond.Done()
})
wg.Wait()
if errFirst != nil {
return nil, nil, errFirst
}
if len(tssFirst) == 0 && !strings.EqualFold(be.Op, "or") {
// Fast path: there is no sense in executing the exprSecond when exprFirst returns an empty result,
// since the "exprFirst op exprSecond" would return an empty result in any case.
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3349
return nil, nil, nil
if errSecond != nil {
return nil, nil, errSecond
}
lfs := getCommonLabelFilters(tssFirst)
lfs = metricsql.TrimFiltersByGroupModifier(lfs, be)
exprSecond = metricsql.PushdownBinaryOpFilters(exprSecond, lfs)
tssSecond, err := evalExpr(qt, ec, exprSecond)
if err != nil {
return nil, nil, err
return sortResult(tssFirst, tssSecond)
}
func shouldOptimizeRepeatedBinaryOpSubexprs(ec *EvalConfig, exprFirst, exprSecond metricsql.Expr) bool {
if !ec.OptimizeRepeatedBinaryOpSubexprs {
return false
}
return tssFirst, tssSecond, nil
if ec.Start == ec.End {
return false
}
if !ec.mayCache() {
return false
}
candidatesFirst := make(map[string]struct{}, 1)
var b []byte
visitOptimizedAggrs(exprFirst, func(ae *metricsql.AggrFuncExpr) {
if hasUnseededVolatileFunc(ae) {
return
}
b = ae.AppendString(b[:0])
candidatesFirst[string(b)] = struct{}{}
})
if len(candidatesFirst) == 0 {
return false
}
repeated := false
visitOptimizedAggrs(exprSecond, func(ae *metricsql.AggrFuncExpr) {
if repeated {
return
}
b = ae.AppendString(b[:0])
_, repeated = candidatesFirst[string(b)]
})
return repeated
}
func visitOptimizedAggrs(e metricsql.Expr, f func(ae *metricsql.AggrFuncExpr)) {
metricsql.VisitAll(e, func(expr metricsql.Expr) {
ae, ok := expr.(*metricsql.AggrFuncExpr)
if !ok {
return
}
if getIncrementalAggrFuncCallbacks(ae.Name) == nil {
return
}
fe, _ := tryGetArgRollupFuncWithMetricExpr(ae)
if fe == nil {
return
}
f(ae)
})
}
func hasUnseededVolatileFunc(e metricsql.Expr) bool {
found := false
metricsql.VisitAll(e, func(expr metricsql.Expr) {
if found {
return
}
fe, ok := expr.(*metricsql.FuncExpr)
if !ok {
return
}
switch strings.ToLower(fe.Name) {
case "now":
found = true
case "rand", "rand_normal", "rand_exponential":
found = len(fe.Args) == 0
}
})
return found
}
func getCommonLabelFilters(tss []*timeseries) []metricsql.LabelFilter {

View File

@@ -170,3 +170,87 @@ func TestGetSumInstantValues(t *testing.T) {
[]*timeseries{ts("foo", 100, 1)},
)
}
func TestShouldOptimizeRepeatedBinaryOpSubexprsGate(t *testing.T) {
e, err := metricsql.Parse(`count(count(vm_requests_total) by (action,addr,cluster,endpoint)) by (action,addr,cluster) / count(count(vm_requests_total) by (action,addr,cluster,endpoint))`)
if err != nil {
t.Fatalf("unexpected error in metricsql.Parse(): %s", err)
}
be, ok := e.(*metricsql.BinaryOpExpr)
if !ok {
t.Fatalf("unexpected expr type; got %T; want *metricsql.BinaryOpExpr", e)
}
f := func(name string, ec *EvalConfig, resultExpected bool) {
t.Helper()
result := shouldOptimizeRepeatedBinaryOpSubexprs(ec, be.Left, be.Right)
if result != resultExpected {
t.Fatalf("unexpected result for %q; got %v; want %v", name, result, resultExpected)
}
}
f("disabled optimization", &EvalConfig{
Start: 1000,
End: 2000,
Step: 1000,
}, false)
f("disabled cache", &EvalConfig{
Start: 1000,
End: 2000,
Step: 1000,
OptimizeRepeatedBinaryOpSubexprs: true,
}, false)
f("instant query", &EvalConfig{
Start: 1000,
End: 1000,
Step: 1000,
MayCache: true,
OptimizeRepeatedBinaryOpSubexprs: true,
}, false)
f("repeated cacheable aggregate subexpression", &EvalConfig{
Start: 1000,
End: 2000,
Step: 1000,
MayCache: true,
OptimizeRepeatedBinaryOpSubexprs: true,
}, true)
f("unaligned range query", &EvalConfig{
Start: 1001,
End: 2000,
Step: 1000,
MayCache: true,
OptimizeRepeatedBinaryOpSubexprs: true,
}, false)
}
func TestShouldOptimizeRepeatedBinaryOpSubexprsExpressions(t *testing.T) {
f := func(name, q string, resultExpected bool) {
t.Helper()
e, err := metricsql.Parse(q)
if err != nil {
t.Fatalf("unexpected error in metricsql.Parse(%q) for %q: %s", q, name, err)
}
be, ok := e.(*metricsql.BinaryOpExpr)
if !ok {
t.Fatalf("unexpected expr type for %q; got %T; want *metricsql.BinaryOpExpr", name, e)
}
ec := &EvalConfig{Start: 1000, End: 2000, Step: 1000, MayCache: true, OptimizeRepeatedBinaryOpSubexprs: true}
result := shouldOptimizeRepeatedBinaryOpSubexprs(ec, be.Left, be.Right)
if result != resultExpected {
t.Fatalf("unexpected result for %q; got %v; want %v; query: %q", name, result, resultExpected, q)
}
}
f("original issue query", `count(count(vm_requests_total) by (action,addr,cluster,endpoint)) by (action,addr,cluster) / count(count(vm_requests_total) by (action,addr,cluster,endpoint))`, true)
f("right side contains repeated count aggregate", `count(foo) by (job) / (count(foo) by (job) + 1)`, true)
f("same sum aggregate", `sum(rate(foo[5m])) by (job) / sum(rate(foo[5m])) by (job)`, true)
f("same inner rollup but different aggregates", `sum(rate(foo[5m])) by (job) / count(rate(foo[5m])) by (job)`, false)
f("different count aggregates", `count(foo) by (job) / count(bar) by (job)`, false)
f("bare metric selector", `foo / foo`, false)
f("bare rollup function", `rate(a[5m]) / rate(a[5m])`, false)
f("now at modifier", `sum(rate(foo[5m] @ now())) by (job) / sum(rate(foo[5m] @ now())) by (job)`, false)
f("unseeded rand at modifier", `sum(rate(foo[5m] @ rand())) by (job) / sum(rate(foo[5m] @ rand())) by (job)`, false)
f("unseeded rand_normal at modifier", `sum(rate(foo[5m] @ rand_normal())) by (job) / sum(rate(foo[5m] @ rand_normal())) by (job)`, false)
f("unseeded rand_exponential at modifier", `sum(rate(foo[5m] @ rand_exponential())) by (job) / sum(rate(foo[5m] @ rand_exponential())) by (job)`, false)
f("seeded rand at modifier", `sum(rate(foo[5m] @ rand(1))) by (job) / sum(rate(foo[5m] @ rand(1))) by (job)`, true)
}

View File

@@ -4006,6 +4006,275 @@ func TestExecSuccess(t *testing.T) {
resultExpected := []netstorage.Result{r1, r2}
f(q, resultExpected)
})
t.Run(`vector + vector fill()`, func(t *testing.T) {
t.Parallel()
q := `sort_by_label((
label_set(1, "foo", "common")
or label_set(2, "foo", "left_only")
) + fill(0) (
label_set(3, "foo", "common")
or label_set(4, "foo", "right_only")
), "foo")`
r1 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{4, 4, 4, 4, 4, 4},
Timestamps: timestampsExpected,
}
r1.MetricName.Tags = []storage.Tag{{
Key: []byte("foo"),
Value: []byte("common"),
}}
r2 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{2, 2, 2, 2, 2, 2},
Timestamps: timestampsExpected,
}
r2.MetricName.Tags = []storage.Tag{{
Key: []byte("foo"),
Value: []byte("left_only"),
}}
r3 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{4, 4, 4, 4, 4, 4},
Timestamps: timestampsExpected,
}
r3.MetricName.Tags = []storage.Tag{{
Key: []byte("foo"),
Value: []byte("right_only"),
}}
resultExpected := []netstorage.Result{r1, r2, r3}
f(q, resultExpected)
})
t.Run(`vector + vector fill() both sides NaN case`, func(t *testing.T) {
t.Parallel()
q := `(
label_set(time() <= 1200, "foo", "common")
) + fill(10) (
label_set(time() >= 1600, "foo", "common")
)`
r := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{1010, 1210, nan, 1610, 1810, 2010},
Timestamps: timestampsExpected,
}
r.MetricName.Tags = []storage.Tag{{
Key: []byte("foo"),
Value: []byte("common"),
}}
resultExpected := []netstorage.Result{r}
f(q, resultExpected)
})
t.Run(`vector + vector fill_left() fill_right()`, func(t *testing.T) {
t.Parallel()
q := `sort_by_label((
label_set(1, "foo", "common")
or label_set(2, "foo", "left_only")
) + fill_left(10) fill_right(20) (
label_set(3, "foo", "common")
or label_set(4, "foo", "right_only")
), "foo")`
r1 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{4, 4, 4, 4, 4, 4},
Timestamps: timestampsExpected,
}
r1.MetricName.Tags = []storage.Tag{{
Key: []byte("foo"),
Value: []byte("common"),
}}
r2 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{22, 22, 22, 22, 22, 22},
Timestamps: timestampsExpected,
}
r2.MetricName.Tags = []storage.Tag{{
Key: []byte("foo"),
Value: []byte("left_only"),
}}
r3 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{14, 14, 14, 14, 14, 14},
Timestamps: timestampsExpected,
}
r3.MetricName.Tags = []storage.Tag{{
Key: []byte("foo"),
Value: []byte("right_only"),
}}
resultExpected := []netstorage.Result{r1, r2, r3}
f(q, resultExpected)
})
t.Run(`vector + vector fill_right() only`, func(t *testing.T) {
t.Parallel()
q := `sort_by_label((
label_set(1, "foo", "common")
or label_set(2, "foo", "left_only")
) + fill_right(20) (
label_set(3, "foo", "common")
or label_set(4, "foo", "right_only")
), "foo")`
r1 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{4, 4, 4, 4, 4, 4},
Timestamps: timestampsExpected,
}
r1.MetricName.Tags = []storage.Tag{{
Key: []byte("foo"),
Value: []byte("common"),
}}
r2 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{22, 22, 22, 22, 22, 22},
Timestamps: timestampsExpected,
}
r2.MetricName.Tags = []storage.Tag{{
Key: []byte("foo"),
Value: []byte("left_only"),
}}
resultExpected := []netstorage.Result{r1, r2}
f(q, resultExpected)
})
t.Run(`vector + vector on() fill()`, func(t *testing.T) {
t.Parallel()
q := `sort_by_label((
label_set(1, "foo", "common", "extra", "l")
or label_set(2, "foo", "left_only", "extra", "l")
) + on(foo) fill(0) (
label_set(3, "foo", "common", "extra", "r")
or label_set(4, "foo", "right_only", "extra", "r")
), "foo")`
r1 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{4, 4, 4, 4, 4, 4},
Timestamps: timestampsExpected,
}
r1.MetricName.Tags = []storage.Tag{{
Key: []byte("foo"),
Value: []byte("common"),
}}
r2 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{2, 2, 2, 2, 2, 2},
Timestamps: timestampsExpected,
}
r2.MetricName.Tags = []storage.Tag{{
Key: []byte("foo"),
Value: []byte("left_only"),
}}
r3 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{4, 4, 4, 4, 4, 4},
Timestamps: timestampsExpected,
}
r3.MetricName.Tags = []storage.Tag{{
Key: []byte("foo"),
Value: []byte("right_only"),
}}
resultExpected := []netstorage.Result{r1, r2, r3}
f(q, resultExpected)
})
t.Run(`vector + vector on() group_left() fill_right()`, func(t *testing.T) {
t.Parallel()
q := `sort_by_label((
label_set(1, "method", "get", "code", "500")
or label_set(2, "method", "get", "code", "404")
or label_set(3, "method", "put", "code", "501")
) + on(method) group_left() fill_right(0) (
label_set(10, "method", "get")
), "method", "code")`
r1 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{12, 12, 12, 12, 12, 12},
Timestamps: timestampsExpected,
}
r1.MetricName.Tags = []storage.Tag{
{
Key: []byte("code"),
Value: []byte("404"),
},
{
Key: []byte("method"),
Value: []byte("get"),
},
}
r2 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{11, 11, 11, 11, 11, 11},
Timestamps: timestampsExpected,
}
r2.MetricName.Tags = []storage.Tag{
{
Key: []byte("code"),
Value: []byte("500"),
},
{
Key: []byte("method"),
Value: []byte("get"),
},
}
r3 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{3, 3, 3, 3, 3, 3},
Timestamps: timestampsExpected,
}
r3.MetricName.Tags = []storage.Tag{
{
Key: []byte("code"),
Value: []byte("501"),
},
{
Key: []byte("method"),
Value: []byte("put"),
},
}
resultExpected := []netstorage.Result{r1, r2, r3}
f(q, resultExpected)
})
t.Run(`vector / vector ignoring() fill()`, func(t *testing.T) {
t.Parallel()
q := `sort_by_label((
label_set(6, "method", "get", "code", "500")
or label_set(1, "method", "put", "code", "500")
) / ignoring(code) fill(0) (
label_set(12, "method", "get")
or label_set(5, "method", "post")
or label_set(10, "method", "put")
), "method")`
r1 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{0.5, 0.5, 0.5, 0.5, 0.5, 0.5},
Timestamps: timestampsExpected,
}
r1.MetricName.Tags = []storage.Tag{
{
Key: []byte("method"),
Value: []byte("get"),
},
}
r2 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{0, 0, 0, 0, 0, 0},
Timestamps: timestampsExpected,
}
r2.MetricName.Tags = []storage.Tag{
{
Key: []byte("method"),
Value: []byte("post"),
},
}
r3 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{0.1, 0.1, 0.1, 0.1, 0.1, 0.1},
Timestamps: timestampsExpected,
}
r3.MetricName.Tags = []storage.Tag{
{
Key: []byte("method"),
Value: []byte("put"),
},
}
resultExpected := []netstorage.Result{r1, r2, r3}
f(q, resultExpected)
})
t.Run(`histogram_quantile(scalar)`, func(t *testing.T) {
t.Parallel()
q := `histogram_quantile(0.6, time())`
@@ -4833,13 +5102,137 @@ func TestExecSuccess(t *testing.T) {
resultExpected := []netstorage.Result{}
f(q, resultExpected)
})
t.Run(`buckets_limit(zero)`, func(t *testing.T) {
// buckets that are consecutively empty at left and right ends will not be preserved.
t.Run(`buckets_limit(trim_zero_preserve_empty_when_limit_not_reached)`, func(t *testing.T) {
t.Parallel()
q := `buckets_limit(0, (
alias(label_set(100, "le", "inf", "x", "y"), "metric"),
alias(label_set(50, "le", "120", "x", "y"), "metric"),
))`
resultExpected := []netstorage.Result{}
q := `sort(buckets_limit(3, (
alias(label_set(36, "le", "+Inf"), "metric"),
alias(label_set(36, "le", "25"), "metric"),
alias(label_set(36, "le", "21"), "metric"),
alias(label_set(36, "le", "19"), "metric"),
alias(label_set(36, "le", "18"), "metric"),
alias(label_set(36, "le", "17"), "metric"),
alias(label_set(36, "le", "16"), "metric"),
alias(label_set(27, "le", "12"), "metric"),
alias(label_set(14, "le", "9"), "metric"),
alias(label_set(0, "le", "6"), "metric"),
alias(label_set(0, "le", "1"), "metric"),
)))`
r1 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{14, 14, 14, 14, 14, 14},
Timestamps: timestampsExpected,
}
r1.MetricName.MetricGroup = []byte("metric")
r1.MetricName.Tags = []storage.Tag{
{
Key: []byte("le"),
Value: []byte("9"),
},
}
r2 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{27, 27, 27, 27, 27, 27},
Timestamps: timestampsExpected,
}
r2.MetricName.MetricGroup = []byte("metric")
r2.MetricName.Tags = []storage.Tag{
{
Key: []byte("le"),
Value: []byte("12"),
},
}
r3 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{36, 36, 36, 36, 36, 36},
Timestamps: timestampsExpected,
}
r3.MetricName.MetricGroup = []byte("metric")
r3.MetricName.Tags = []storage.Tag{
{
Key: []byte("le"),
Value: []byte("16"),
},
}
resultExpected := []netstorage.Result{r1, r2, r3}
f(q, resultExpected)
})
// the number of non-empty bucket doesn't reach the given "limit", so some empty buckets will be preserved, and left buckets are preferred to be kept.
t.Run(`buckets_limit(trim_zero)`, func(t *testing.T) {
t.Parallel()
q := `sort(buckets_limit(5, (
alias(label_set(36, "le", "18"), "metric"),
alias(label_set(36, "le", "17"), "metric"),
alias(label_set(36, "le", "16"), "metric"),
alias(label_set(27, "le", "12"), "metric"),
alias(label_set(14, "le", "9"), "metric"),
alias(label_set(0, "le", "6"), "metric"),
alias(label_set(0, "le", "1"), "metric"),
)))`
r1 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{0, 0, 0, 0, 0, 0},
Timestamps: timestampsExpected,
}
r1.MetricName.MetricGroup = []byte("metric")
r1.MetricName.Tags = []storage.Tag{
{
Key: []byte("le"),
Value: []byte("1"),
},
}
r2 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{0, 0, 0, 0, 0, 0},
Timestamps: timestampsExpected,
}
r2.MetricName.MetricGroup = []byte("metric")
r2.MetricName.Tags = []storage.Tag{
{
Key: []byte("le"),
Value: []byte("6"),
},
}
r3 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{14, 14, 14, 14, 14, 14},
Timestamps: timestampsExpected,
}
r3.MetricName.MetricGroup = []byte("metric")
r3.MetricName.Tags = []storage.Tag{
{
Key: []byte("le"),
Value: []byte("9"),
},
}
r4 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{27, 27, 27, 27, 27, 27},
Timestamps: timestampsExpected,
}
r4.MetricName.MetricGroup = []byte("metric")
r4.MetricName.Tags = []storage.Tag{
{
Key: []byte("le"),
Value: []byte("12"),
},
}
r5 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{36, 36, 36, 36, 36, 36},
Timestamps: timestampsExpected,
}
r5.MetricName.MetricGroup = []byte("metric")
r5.MetricName.Tags = []storage.Tag{
{
Key: []byte("le"),
Value: []byte("16"),
},
}
resultExpected := []netstorage.Result{r1, r2, r3, r4, r5}
f(q, resultExpected)
})
t.Run(`buckets_limit(unused)`, func(t *testing.T) {
@@ -6228,50 +6621,6 @@ func TestExecSuccess(t *testing.T) {
resultExpected := []netstorage.Result{r1, r2, r3, r4, r5, r6, r7}
f(q, resultExpected)
})
t.Run(`sum(histogram_over_time) by (vmrange)`, func(t *testing.T) {
t.Parallel()
q := `sort_by_label(
buckets_limit(
3,
sum(histogram_over_time(alias(label_set(rand(0)*1.3+1.1, "foo", "bar"), "xxx")[200s:5s])) by (vmrange)
), "le"
)`
r1 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{40, 40, 40, 40, 40, 40},
Timestamps: timestampsExpected,
}
r1.MetricName.Tags = []storage.Tag{
{
Key: []byte("le"),
Value: []byte("+Inf"),
},
}
r2 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{0, 0, 0, 0, 0, 0},
Timestamps: timestampsExpected,
}
r2.MetricName.Tags = []storage.Tag{
{
Key: []byte("le"),
Value: []byte("1.000e+00"),
},
}
r3 := netstorage.Result{
MetricName: metricNameExpected,
Values: []float64{40, 40, 40, 40, 40, 40},
Timestamps: timestampsExpected,
}
r3.MetricName.Tags = []storage.Tag{
{
Key: []byte("le"),
Value: []byte("2.448e+00"),
},
}
resultExpected := []netstorage.Result{r1, r2, r3}
f(q, resultExpected)
})
t.Run(`sum(histogram_over_time)`, func(t *testing.T) {
t.Parallel()
q := `sum(histogram_over_time(alias(label_set(rand(0)*1.3+1.1, "foo", "bar"), "xxx")[200s:5s]))`

View File

@@ -1055,7 +1055,7 @@ func newRollupHoltWinters(args []any) (rollupFunc, error) {
return nan
}
// See https://en.wikipedia.org/wiki/Exponential_smoothing#Double_exponential_smoothing .
// See https://en.wikipedia.org/wiki/Exponential_smoothing#Double_exponential_smoothing_%28Holt_linear%29 .
// TODO: determine whether this shit really works.
s0 := rfa.prevValue
if math.IsNaN(s0) {

View File

@@ -2,6 +2,7 @@ package promql
import (
"fmt"
"math"
"sort"
"strconv"
"sync"
@@ -288,6 +289,9 @@ func marshalMetricTagsSorted(dst []byte, mn *storage.MetricName) []byte {
}
func marshalBytesFast(dst []byte, s []byte) []byte {
if len(s) > math.MaxUint16 {
logger.Panicf("BUG: s len %d cannot exceed %d", len(s), math.MaxUint16)
}
dst = encoding.MarshalUint16(dst, uint16(len(s)))
dst = append(dst, s...)
return dst

View File

@@ -393,7 +393,7 @@ func transformBucketsLimit(tfa *transformFuncArg) ([]*timeseries, error) {
return nil, err
}
if limit <= 0 {
return nil, nil
return nil, fmt.Errorf("limit must be greater than 0; got %d", limit)
}
if limit < 3 {
// Preserve the first and the last bucket for better accuracy for min and max values.
@@ -461,6 +461,23 @@ func transformBucketsLimit(tfa *transformFuncArg) ([]*timeseries, error) {
prevValue = value
}
}
// Remove buckets that are consecutively empty at left and right ends to obtain more accurate max and min values.
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/10417.
epsilon := 1e-9
isEmptyBucket := func(hits float64) bool {
return !math.IsNaN(hits) && math.Abs(hits) < epsilon
}
l := 0
r := len(leGroup) - 1
for r-l+1 > limit && isEmptyBucket(leGroup[r].hits) {
r--
}
for r-l+1 > limit && isEmptyBucket(leGroup[l].hits) {
l++
}
leGroup = leGroup[l : r+1]
for len(leGroup) > limit {
// Preserve the first and the last bucket for better accuracy for min and max values
xxMinIdx := 1
@@ -1121,29 +1138,29 @@ func groupLeTimeseries(tss []*timeseries) map[string][]leTimeseries {
func fixBrokenBuckets(i int, xss []leTimeseries) {
// Buckets are already sorted by le, so their values must be in ascending order,
// since the next bucket includes all the previous buckets.
// If the next bucket has lower value than the current bucket,
// then the next bucket must be substituted with the current bucket value.
// since the upper bucket includes all the lower buckets.
// If the upper bucket has lower value than the current bucket,
// then the upper bucket must be substituted with the current bucket value.
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4580#issuecomment-2186659102
if len(xss) < 2 {
return
}
vNext := xss[0].ts.Values[i]
vPrev := xss[0].ts.Values[i]
// Set the lowest bucket to 0 if its value is NaN, so it can be properly
// compared with upper buckets in the loop below.
if math.IsNaN(vNext) {
vNext = 0
xss[0].ts.Values[i] = vNext
if math.IsNaN(vPrev) {
vPrev = 0
xss[0].ts.Values[i] = vPrev
}
// Substitute upper bucket values with lower bucket values if the upper values are NaN
// or are bigger than the lower bucket values.
// or are smaller than the lower bucket values.
for j := 1; j < len(xss); j++ {
v := xss[j].ts.Values[i]
if math.IsNaN(v) || vNext > v {
xss[j].ts.Values[i] = vNext
if math.IsNaN(v) || vPrev > v {
xss[j].ts.Values[i] = vPrev
} else {
vNext = v
vPrev = v
}
}
}

View File

@@ -91,9 +91,9 @@ The list of MetricsQL features on top of PromQL:
Labels from the `on()` list aren't copied.
* [Aggregate functions](#aggregate-functions) accept arbitrary number of args.
For example, `avg(q1, q2, q3)` would return the average values for every point across time series returned by `q1`, `q2` and `q3`.
* [@ modifier](https://prometheus.io/docs/prometheus/latest/querying/basics/#modifier) can be put anywhere in the query.
* [@ modifier](https://prometheus.io/docs/prometheus/latest/querying/basics/#-modifier) can be put anywhere in the query.
For example, `sum(foo) @ end()` calculates `sum(foo)` at the `end` timestamp of the selected time range `[start ... end]`.
* Arbitrary subexpression can be used as [@ modifier](https://prometheus.io/docs/prometheus/latest/querying/basics/#modifier).
* Arbitrary subexpression can be used as [@ modifier](https://prometheus.io/docs/prometheus/latest/querying/basics/#-modifier).
For example, `foo @ (end() - 1h)` calculates `foo` at the `end - 1 hour` timestamp on the selected time range `[start ... end]`.
* [offset](https://prometheus.io/docs/prometheus/latest/querying/basics/#offset-modifier), lookbehind window in square brackets
and `step` value for [subquery](#subqueries) may refer to the current step aka `$__interval` value from Grafana with `[Ni]` syntax.
@@ -482,7 +482,7 @@ See also [hoeffding_bound_lower](#hoeffding_bound_lower).
#### holt_winters
`holt_winters(series_selector[d], sf, tf)` is a [rollup function](#rollup-functions), which calculates Holt-Winters value
(aka [double exponential smoothing](https://en.wikipedia.org/wiki/Exponential_smoothing#Double_exponential_smoothing)) for [raw samples](https://docs.victoriametrics.com/victoriametrics/keyconcepts/#raw-samples)
(aka [double exponential smoothing](https://en.wikipedia.org/wiki/Exponential_smoothing#Double_exponential_smoothing_%28Holt_linear%29)) for [raw samples](https://docs.victoriametrics.com/victoriametrics/keyconcepts/#raw-samples)
over the given lookbehind window `d` using the given smoothing factor `sf` and the given trend factor `tf`.
Both `sf` and `tf` must be in the range `[0...1]`.
@@ -1154,7 +1154,7 @@ See also [asin](#asin) and [cos](#cos).
#### acosh
`acosh(q)` is a [transform function](#transform-functions), which returns
[inverse hyperbolic cosine](https://en.wikipedia.org/wiki/Inverse_hyperbolic_functions#Inverse_hyperbolic_cosine) for every point of every time series returned by `q`.
[inverse hyperbolic cosine](https://en.wikipedia.org/wiki/Inverse_hyperbolic_functions#Definitions_in_terms_of_logarithms) for every point of every time series returned by `q`.
Metric names are stripped from the resulting series. Add [keep_metric_names](#keep_metric_names) modifier in order to keep metric names.
@@ -1176,7 +1176,7 @@ See also [acos](#acos) and [sin](#sin).
#### asinh
`asinh(q)` is a [transform function](#transform-functions), which returns
[inverse hyperbolic sine](https://en.wikipedia.org/wiki/Inverse_hyperbolic_functions#Inverse_hyperbolic_sine) for every point of every time series returned by `q`.
[inverse hyperbolic sine](https://en.wikipedia.org/wiki/Inverse_hyperbolic_functions#Definitions_in_terms_of_logarithms) for every point of every time series returned by `q`.
Metric names are stripped from the resulting series. Add [keep_metric_names](#keep_metric_names) modifier in order to keep metric names.
@@ -1198,7 +1198,7 @@ See also [tan](#tan).
#### atanh
`atanh(q)` is a [transform function](#transform-functions), which returns
[inverse hyperbolic tangent](https://en.wikipedia.org/wiki/Inverse_hyperbolic_functions#Inverse_hyperbolic_tangent) for every point of every time series returned by `q`.
[inverse hyperbolic tangent](https://en.wikipedia.org/wiki/Inverse_hyperbolic_functions#Definitions_in_terms_of_logarithms) for every point of every time series returned by `q`.
Metric names are stripped from the resulting series. Add [keep_metric_names](#keep_metric_names) modifier in order to keep metric names.
@@ -1229,8 +1229,7 @@ Metric names are stripped from the resulting series. Add [keep_metric_names](#ke
`buckets_limit(limit, buckets)` is a [transform function](#transform-functions), which limits the number
of [histogram buckets](https://valyala.medium.com/improving-histogram-usability-for-prometheus-and-grafana-bc7e5df0e350) to the given `limit`.
The result will preserve the first and the last bucket to improve accuracy for min and max values.
So, if the `limit` is greater than 0 and less than 3, the function will still return 3 buckets: the first bucket, the last bucket, and a selected bucket.
The given `limit` should be greater than `0`. If it is less than `3`, it will be automatically raised to `3` to preserve the first and last buckets for better accuracy of min and max values.
See also [prometheus_buckets](#prometheus_buckets) and [histogram_quantile](#histogram_quantile).

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,t,n)=>()=>{if(n)throw n[0];try{return e&&(t=e(e=0)),t}catch(e){throw n=[e],e}},s=(e,t)=>()=>(t||(e((t={exports:{}}).exports,t),e=null),t.exports),c=(e,n)=>{let r={};for(var i in e)t(r,i,{get:e[i],enumerable:!0});return n||t(r,Symbol.toStringTag,{value:`Module`}),r},l=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},u=(n,r,a)=>(a=n==null?{}:e(i(n)),l(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n)),d=e=>a.call(e,`module.exports`)?e[`module.exports`]:l(t({},`__esModule`,{value:!0}),e);export{u as a,d as i,o as n,c as r,s as t};

View File

@@ -1 +0,0 @@
var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,t)=>()=>(e&&(t=e(e=0)),t),s=(e,t)=>()=>(t||(e((t={exports:{}}).exports,t),e=null),t.exports),c=(e,n)=>{let r={};for(var i in e)t(r,i,{get:e[i],enumerable:!0});return n||t(r,Symbol.toStringTag,{value:`Module`}),r},l=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},u=(n,r,a)=>(a=n==null?{}:e(i(n)),l(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n)),d=e=>a.call(e,`module.exports`)?e[`module.exports`]:l(t({},`__esModule`,{value:!0}),e);export{u as a,d as i,o as n,c as r,s as t};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -37,11 +37,11 @@
<meta property="og:title" content="UI for VictoriaMetrics">
<meta property="og:url" content="https://victoriametrics.com/">
<meta property="og:description" content="Explore and troubleshoot your VictoriaMetrics data">
<script type="module" crossorigin src="./assets/index-CusQvJzs.js"></script>
<link rel="modulepreload" crossorigin href="./assets/rolldown-runtime-Cyuzqnbw.js">
<link rel="modulepreload" crossorigin href="./assets/vendor-B83wxFqK.js">
<script type="module" crossorigin src="./assets/index-D5egN2id.js"></script>
<link rel="modulepreload" crossorigin href="./assets/rolldown-runtime-CNC7AqOf.js">
<link rel="modulepreload" crossorigin href="./assets/vendor-DwJYpOdw.js">
<link rel="stylesheet" crossorigin href="./assets/vendor-CnsZ1jie.css">
<link rel="stylesheet" crossorigin href="./assets/index-BBUnmLOr.css">
<link rel="stylesheet" crossorigin href="./assets/index-BJqoElx2.css">
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>

View File

@@ -30,6 +30,17 @@ var (
"See https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#retention. See also -retentionFilter")
futureRetention = flagutil.NewRetentionDuration("futureRetention", "2d", "Data with timestamps bigger than now+futureRetention is automatically deleted. "+
"The minimum futureRetention is 2 days. See https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#retention")
maxBackfillAge = flagutil.NewRetentionDuration("maxBackfillAge", "0", "The maximum allowed age for the ingested samples with historical timestamps. "+
"Samples with timestamps older than now-maxBackfillAge are rejected during data ingestion. "+
"By default, or when set to 0, -maxBackfillAge equals to -retentionPeriod, e.g. it is unlimited within the configured retention. "+
"This can be useful for limiting ingestion of historical samples, for example, when older data has been moved to another storage tier. "+
"See https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#retention")
vmselectAddr = flag.String("vmselectAddr", "", "TCP address to listen for incoming connections from vmselect. "+
"When set, the node will be able to accept cluster-native vmselect RPC requests as if it were vmstorage. "+
"The tenant ID assigned to this node's data is controlled by -accountID and -projectID flags. "+
"See https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#multi-tenancy")
vmselectDisableRPCCompression = flag.Bool("rpc.disableCompression", false, "Whether to disable compression of the data sent from vmstorage to vmselect. "+
"This reduces CPU usage at the cost of higher network bandwidth usage")
snapshotAuthKey = flagutil.NewPassword("snapshotAuthKey", "authKey, which must be passed in query string to /snapshot* pages. It overrides -httpAuth.*")
forceMergeAuthKey = flagutil.NewPassword("forceMergeAuthKey", "authKey, which must be passed in query string to /internal/force_merge pages. It overrides -httpAuth.*")
forceFlushAuthKey = flagutil.NewPassword("forceFlushAuthKey", "authKey, which must be passed in query string to /internal/force_flush pages. It overrides -httpAuth.*")
@@ -108,7 +119,7 @@ func DataPath() string {
}
// Init initializes vmstorage.
func Init(vmselectMaxConcurrentRequests int, resetCacheIfNeeded func(mrs []storage.MetricRow)) {
func Init(vmselectMaxConcurrentRequests int, vmselectMaxQueueDuration time.Duration, resetCacheIfNeeded func(mrs []storage.MetricRow)) {
storage.SetDedupInterval(*minScrapeInterval)
storage.SetDataFlushInterval(*inmemoryDataFlushInterval)
storage.LegacySetRetentionTimezoneOffset(*retentionTimezoneOffset)
@@ -143,6 +154,7 @@ func Init(vmselectMaxConcurrentRequests int, resetCacheIfNeeded func(mrs []stora
opts := storage.OpenOptions{
Retention: retentionPeriod.Duration(),
FutureRetention: futureRetention.Duration(),
MaxBackfillAge: maxBackfillAge.Duration(),
DenyQueriesOutsideRetention: *denyQueriesOutsideRetention,
MaxHourlySeries: getMaxHourlySeries(),
MaxDailySeries: getMaxDailySeries(),
@@ -169,6 +181,21 @@ func Init(vmselectMaxConcurrentRequests int, resetCacheIfNeeded func(mrs []stora
storageMetrics.RegisterMetricsWriter(vmStorage.writeStorageMetrics)
metrics.RegisterSet(storageMetrics)
if *vmselectAddr != "" {
var err error
limits := vmselectapi.Limits{
MaxConcurrentRequests: vmselectMaxConcurrentRequests,
MaxConcurrentRequestsFlagName: "search.maxConcurrentRequests",
MaxQueueDuration: vmselectMaxQueueDuration,
MaxQueueDurationFlagName: "search.maxQueueDuration",
}
api := newVMStorageWithTenantID(vmStorage)
vmselectSrv, err = vmselectapi.NewServer(*vmselectAddr, api, limits, *vmselectDisableRPCCompression)
if err != nil {
logger.Fatalf("cannot create a server with -vmselectAddr=%s: %s", *vmselectAddr, err)
}
}
VMInsertAPI = vmStorage
VMSelectAPI = vmStorage
GetSearch = vmStorage.GetSearch
@@ -191,6 +218,8 @@ var (
// TODO(@rtm0): Remove this dependency from vmalert-tool unit tests.
DebugFlush func()
vmselectSrv *vmselectapi.Server
)
// Stop stops the vmstorage
@@ -201,6 +230,10 @@ func Stop() {
logger.Infof("gracefully closing the storage at %s", *storageDataPath)
startTime := time.Now()
if vmselectSrv != nil {
vmselectSrv.MustStop()
}
vmStorage.Stop()
logger.Infof("successfully closed the storage in %.3f seconds", time.Since(startTime).Seconds())
@@ -443,8 +476,10 @@ func (vms *VMStorage) writeStorageMetrics(w io.Writer) {
metrics.WriteGaugeUint64(w, `vm_data_size_bytes{type="storage/inmemory"}`, tm.InmemorySizeBytes)
metrics.WriteGaugeUint64(w, `vm_data_size_bytes{type="storage/small"}`, tm.SmallSizeBytes)
metrics.WriteGaugeUint64(w, `vm_data_size_bytes{type="storage/big"}`, tm.BigSizeBytes)
metrics.WriteGaugeUint64(w, `vm_data_size_bytes{type="storage/metaindex"}`, tm.MetaindexSizeBytes)
metrics.WriteGaugeUint64(w, `vm_data_size_bytes{type="indexdb/inmemory"}`, idbm.InmemorySizeBytes)
metrics.WriteGaugeUint64(w, `vm_data_size_bytes{type="indexdb/file"}`, idbm.FileSizeBytes)
metrics.WriteGaugeUint64(w, `vm_data_size_bytes{type="indexdb/metaindex"}`, idbm.MetaindexSizeBytes)
metrics.WriteCounterUint64(w, `vm_rows_received_by_storage_total`, m.RowsReceivedTotal)
metrics.WriteCounterUint64(w, `vm_rows_added_to_storage_total`, m.RowsAddedTotal)

View File

@@ -164,6 +164,10 @@ func (vms *VMStorage) IsReadOnly() bool {
}
func (vms *VMStorage) InitSearch(qt *querytracer.Tracer, sq *storage.SearchQuery, deadline uint64) (vmselectapi.BlockIterator, error) {
return vms.initSearch(qt, sq, marshalDefault, deadline)
}
func (vms *VMStorage) initSearch(qt *querytracer.Tracer, sq *storage.SearchQuery, marshal marshalFunc, deadline uint64) (vmselectapi.BlockIterator, error) {
vms.wg.Add(1)
tr := sq.GetTimeRange()
@@ -178,6 +182,7 @@ func (vms *VMStorage) InitSearch(qt *querytracer.Tracer, sq *storage.SearchQuery
return nil, fmt.Errorf("missing tag filters")
}
bi := getBlockIterator()
bi.marshal = marshal
bi.wgDone = vms.wg.Done
bi.sr.Init(qt, vms.s, tfss, tr, maxMetrics, deadline)
if err := bi.sr.Error(); err != nil {
@@ -198,11 +203,19 @@ func (vms *VMStorage) getMaxMetrics(searchQueryLimit int) int {
return searchQueryLimit
}
type marshalFunc func(dst []byte, src *storage.MetricBlock) []byte
// marshalDefault is the default implementation of the MetricBlock marshaling.
func marshalDefault(dst []byte, src *storage.MetricBlock) []byte {
return src.Marshal(dst)
}
// blockIterator implements vmselectapi.BlockIterator
type blockIterator struct {
sr storage.Search
mb storage.MetricBlock
wgDone func()
sr storage.Search
mb storage.MetricBlock
marshal marshalFunc
wgDone func()
}
var blockIteratorsPool sync.Pool
@@ -228,10 +241,10 @@ func (bi *blockIterator) NextBlock(dst []byte) ([]byte, bool) {
if !bi.sr.NextMetricBlock() {
return dst, false
}
mb := bi.mb
mb := &bi.mb
mb.MetricName = bi.sr.MetricBlockRef.MetricName
bi.sr.MetricBlockRef.BlockRef.MustReadBlock(&mb.Block)
dst = mb.Marshal(dst[:0])
dst = bi.marshal(dst[:0], mb)
return dst, true
}

View File

@@ -0,0 +1,282 @@
package vmstorage
import (
"flag"
"fmt"
"math"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/encoding"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/querytracer"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage/metricnamestats"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage/metricsmetadata"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/vmselectapi"
)
var (
accountID = flag.Uint64("accountID", 0, "The accountID of the stored data")
projectID = flag.Uint64("projectID", 0, "The projectID of the stored data")
)
func newVMStorageWithTenantID(vms *VMStorage) *VMStorageWithTenantID {
if *accountID > math.MaxUint32 {
logger.Fatalf("-accountID must be in the range [0, %d], got %d", uint32(math.MaxUint32), *accountID)
}
if *projectID > math.MaxUint32 {
logger.Fatalf("-projectID must be in the range [0, %d], got %d", uint32(math.MaxUint32), *projectID)
}
return &VMStorageWithTenantID{
vms: vms,
accountID: uint32(*accountID),
projectID: uint32(*projectID),
}
}
// VMStorageWithTenantID is a thin wrapper around VMStorage type that overrides
// its methods to properly serve requests coming from a vmselect (require
// tenantID).
//
// A new instance of this type should be created using
// newVMStorageWithTenantID(). The created instance does not require closing.
// The instance also does not take ownership of vms and it is the responsibility
// of the caller to close vms.
type VMStorageWithTenantID struct {
vms *VMStorage
accountID uint32
projectID uint32
}
// InitSearch initializes a storage search for a request initiated by a
// vmselect.
//
// The search is initialized only if the search query is either multitenant or
// its accountID and projectID match -accountID and -projectID flag values.
// Otherwise, the method returns an interator that will return no data.
//
// The method also overrides the data format of the data returned by the
// iterator by prepending accountID and projectID bytes to the metric name and
// the data block (a format used in vmcluster).
func (vmst *VMStorageWithTenantID) InitSearch(qt *querytracer.Tracer, sq *storage.SearchQuery, deadline uint64) (vmselectapi.BlockIterator, error) {
if !vmst.hasValidTenantID(sq) {
return emptyBI, nil
}
return vmst.vms.initSearch(qt, sq, vmst.marshalMetricBlock, deadline)
}
var emptyBI = &emptyBlockIterator{}
// emptyBlockIterator is an implementation of vmselectapi.BlockIterator that
// always returns no data.
type emptyBlockIterator struct{}
func (*emptyBlockIterator) MustClose() {}
func (*emptyBlockIterator) NextBlock(dst []byte) ([]byte, bool) {
return dst, false
}
func (*emptyBlockIterator) Error() error {
return nil
}
// marshalMetricBlock serializes a metric block in the format expected by
// vmselect.
//
// vmselect expects metric names and data blocks to have the tenantID but
// vmsingle does not have it. Therefore the tenantID needs to be included to
// every metric name and block.
func (vmst *VMStorageWithTenantID) marshalMetricBlock(dst []byte, src *storage.MetricBlock) []byte {
// Marshal metric name:
// 1. Marshal metric name length + accountID length + projectID length (in
// bytes).
// 2. append accountID and projectID bytes
// 3. Finally append metric name bytes
dst = encoding.MarshalVarUint64(dst, uint64(len(src.MetricName))+8)
dst = encoding.MarshalUint32(dst, vmst.accountID)
dst = encoding.MarshalUint32(dst, vmst.projectID)
dst = append(dst, src.MetricName...)
// Marshal data block.
dst = encoding.MarshalUint32(dst, vmst.accountID)
dst = encoding.MarshalUint32(dst, vmst.projectID)
dst = storage.MarshalBlock(dst, &src.Block)
return dst
}
// SearchMetricNames searches the storage for metric names that match the query.
//
// If the query is not multitenant or the query accountID and projectID do not
// match the -accoutID and -projectID flag values, the method will return an
// empty result.
//
// Found metric names are prepended with accountID and projectID bytes (a format
// used in vmcluster).
func (vmst *VMStorageWithTenantID) SearchMetricNames(qt *querytracer.Tracer, sq *storage.SearchQuery, deadline uint64) ([]string, error) {
if !vmst.hasValidTenantID(sq) {
return nil, nil
}
metricNames, err := vmst.vms.SearchMetricNames(qt, sq, deadline)
if err != nil {
return nil, err
}
// vmselect expects metric names to have the tenantID but vmsingle does not
// have it. Therefore the tenantID needs to be prepended to every metric
// name.
dst := make([]byte, 0, 8)
dst = encoding.MarshalUint32(dst, vmst.accountID)
dst = encoding.MarshalUint32(dst, vmst.projectID)
tenantID := string(dst)
for i, metricName := range metricNames {
metricNames[i] = tenantID + metricName
}
return metricNames, nil
}
// LabelValues searches the storage for label values that match the query and
// correspond to a label whose name is `labelName`. The returned result
// will contain not more than `maxLabelValues`.
//
// If the query is not multitenant or the query accountID and projectID do not
// match the -accoutID and -projectID flag values, the method will return an
// empty result.
func (vmst *VMStorageWithTenantID) LabelValues(qt *querytracer.Tracer, sq *storage.SearchQuery, labelName string, maxLabelValues int, deadline uint64) ([]string, error) {
if !vmst.hasValidTenantID(sq) {
return nil, nil
}
return vmst.vms.LabelValues(qt, sq, labelName, maxLabelValues, deadline)
}
// TagValueSuffixes searches the storage for Graphite tag value suffixes. The
// returned result will contain not more than `maxSuffixes`.
//
// If the query is not multitenant or the query accountID and projectID do not
// match the -accoutID and -projectID flag values, the method will return an
// empty result.
func (vmst *VMStorageWithTenantID) TagValueSuffixes(qt *querytracer.Tracer, accountID, projectID uint32, tr storage.TimeRange, tagKey, tagValuePrefix string, delimiter byte, maxSuffixes int, deadline uint64) ([]string, error) {
if !vmst.isValidTenantID(accountID, projectID) {
return nil, nil
}
return vmst.vms.TagValueSuffixes(qt, accountID, projectID, tr, tagKey, tagValuePrefix, delimiter, maxSuffixes, deadline)
}
// LabelNames searches the storage for label names that match the query.
// The returned result will contain not more than `maxLabelNames`.
//
// If the query is not multitenant or the query accountID and projectID do not
// match the -accoutID and -projectID flag values, the method will return an
// empty result.
func (vmst *VMStorageWithTenantID) LabelNames(qt *querytracer.Tracer, sq *storage.SearchQuery, maxLabelNames int, deadline uint64) ([]string, error) {
if !vmst.hasValidTenantID(sq) {
return nil, nil
}
return vmst.vms.LabelNames(qt, sq, maxLabelNames, deadline)
}
// SeriesCount returns the total number of metrics stored in the database.
//
// The method may return inflated numbers. How inflated the count depends
// on the churn rate and the retention period. For example, if a metric lasts
// for 2 months, it will be counted twice.
//
// The method also counts the deleted metrics.
//
// If the query is not multitenant or the query accountID and projectID do not
// match the -accoutID and -projectID flag values, the method will return 0.
func (vmst *VMStorageWithTenantID) SeriesCount(qt *querytracer.Tracer, accountID, projectID uint32, deadline uint64) (uint64, error) {
if !vmst.isValidTenantID(accountID, projectID) {
return 0, nil
}
return vmst.vms.SeriesCount(qt, accountID, projectID, deadline)
}
// Tenants returns just one tenant consisting of the -accountID and -projectID
// flag values.
func (vmst *VMStorageWithTenantID) Tenants(qt *querytracer.Tracer, tr storage.TimeRange, deadline uint64) ([]string, error) {
tenantID := fmt.Sprintf("%d:%d", vmst.accountID, vmst.projectID)
return []string{tenantID}, nil
}
// TSDBStatus retrieves the status for metrics that match to the search query.
//
// If the query is not multitenant or the query accountID and projectID do not
// match the -accoutID and -projectID flag values, the method will return empty
// status.
func (vmst *VMStorageWithTenantID) TSDBStatus(qt *querytracer.Tracer, sq *storage.SearchQuery, focusLabel string, topN int, deadline uint64) (*storage.TSDBStatus, error) {
if !vmst.hasValidTenantID(sq) {
return &storage.TSDBStatus{}, nil
}
return vmst.vms.TSDBStatus(qt, sq, focusLabel, topN, deadline)
}
// DeleteSeries marks as deleted metrics that match the search query.
// The method returns the number of deleted metrics.
//
// If the query is not multitenant or the query accountID and projectID do not
// match the -accoutID and -projectID flag values, no metrics will be deleted
// and the method will return 0.
func (vmst *VMStorageWithTenantID) DeleteSeries(qt *querytracer.Tracer, sq *storage.SearchQuery, deadline uint64) (int, error) {
if !vmst.hasValidTenantID(sq) {
return 0, nil
}
return vmst.vms.DeleteSeries(qt, sq, deadline)
}
// RegisterMetricNames registers metric names in the index, the sample values
// and timestamps are ignored.
func (vmst *VMStorageWithTenantID) RegisterMetricNames(qt *querytracer.Tracer, mrs []storage.MetricRow, deadline uint64) error {
return vmst.vms.RegisterMetricNames(qt, mrs, deadline)
}
// GetMetricNamesUsageStats retrieves the usage stats for metrics whose name
// matches the pattern.
//
// If the request is not multitenant or the request accountID and projectID do
// not match the -accoutID and -projectID flag values, no metrics will be
// deleted and the method will return 0.
func (vmst *VMStorageWithTenantID) GetMetricNamesUsageStats(qt *querytracer.Tracer, tt *storage.TenantToken, limit, le int, matchPattern string, deadline uint64) (metricnamestats.StatsResult, error) {
if !vmst.isValidTenantToken(tt) {
return metricnamestats.StatsResult{}, nil
}
return vmst.vms.GetMetricNamesUsageStats(qt, tt, limit, le, matchPattern, deadline)
}
// ResetMetricNamesUsageStats resets the metric name usage stats.
func (vmst *VMStorageWithTenantID) ResetMetricNamesUsageStats(qt *querytracer.Tracer, deadline uint64) error {
return vmst.vms.ResetMetricNamesUsageStats(qt, deadline)
}
// GetMetadataRecords retrieves the metadata for the metricName.
//
// If the request is not multitenant or the request accountID and projectID do
// not match the -accoutID and -projectID flag values, no metrics will be
// deleted and the method will return 0.
func (vmst *VMStorageWithTenantID) GetMetadataRecords(qt *querytracer.Tracer, tt *storage.TenantToken, limit int, metricName string, deadline uint64) ([]*metricsmetadata.Row, error) {
if !vmst.isValidTenantToken(tt) {
return nil, nil
}
return vmst.vms.GetMetadataRecords(qt, tt, limit, metricName, deadline)
}
// hasValidTenantID returns true if the search query is either multitenant or
// its accountID and projectID match -accountID and -projectID flag values.
func (vmst *VMStorageWithTenantID) hasValidTenantID(sq *storage.SearchQuery) bool {
return sq.IsMultiTenant || vmst.isValidTenantID(sq.AccountID, sq.ProjectID)
}
// isValidTenantToken returns true if the TenantToken is either multitenant or
// its accountID and projectID match -accountID and -projectID flag values.
func (vmst *VMStorageWithTenantID) isValidTenantToken(tt *storage.TenantToken) bool {
return tt == nil || vmst.isValidTenantID(tt.AccountID, tt.ProjectID)
}
// isValidTenantID returns true if the accountID and projectID match -accountID
// and -projectID flag values.
func (vmst *VMStorageWithTenantID) isValidTenantID(accountID, projectID uint32) bool {
return accountID == vmst.accountID && projectID == vmst.projectID
}

View File

@@ -1,4 +1,4 @@
FROM golang:1.26.4 AS build-web-stage
FROM golang:1.26.5 AS build-web-stage
COPY build /build
WORKDIR /build
@@ -6,7 +6,7 @@ COPY web/ /build/
RUN GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o web-amd64 github.com/VictoriMetrics/vmui/ && \
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -o web-windows github.com/VictoriMetrics/vmui/
FROM alpine:3.23.4
FROM alpine:3.24.1
USER root
COPY --from=build-web-stage /build/web-amd64 /app/web

File diff suppressed because it is too large Load Diff

View File

@@ -9,7 +9,7 @@
"start": "vite",
"start:playground": "cross-env PLAYGROUND=true npm run start",
"build": "vite build",
"lint": "eslint --output-file vmui-lint-report.json --format json 'src/**/*.{ts,tsx}'",
"lint": "eslint --format stylish 'src/**/*.{ts,tsx}'",
"lint:local": "eslint --ext .ts,.tsx -f stylish src",
"lint:fix": "eslint 'src/**/*.{ts,tsx}' --fix",
"copy-metricsql-docs": "cp ../../../../docs/MetricsQL.md src/assets/MetricsQL.md || true",
@@ -23,40 +23,40 @@
"classnames": "^2.5.1",
"dayjs": "^1.11.21",
"lodash.debounce": "^4.0.8",
"marked": "^18.0.5",
"preact": "^10.29.2",
"qs": "^6.15.2",
"marked": "^18.0.6",
"preact": "^10.29.7",
"qs": "^6.15.3",
"react-input-mask": "^2.0.4",
"react-router-dom": "^7.17.0",
"react-router-dom": "^7.18.1",
"uplot": "^1.6.32",
"vite": "^8.0.16",
"vite": "^8.1.4",
"web-vitals": "^5.3.0"
},
"devDependencies": {
"@eslint/eslintrc": "^3.3.5",
"@eslint/eslintrc": "^3.3.6",
"@eslint/js": "^9.39.2",
"@preact/preset-vite": "^2.10.5",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/preact": "^3.2.4",
"@types/lodash.debounce": "^4.0.9",
"@types/node": "^25.9.2",
"@types/node": "^26.1.1",
"@types/qs": "^6.15.1",
"@types/react": "^19.2.17",
"@types/react-input-mask": "^3.0.6",
"@types/react-router-dom": "^5.3.3",
"@typescript-eslint/eslint-plugin": "^8.61.0",
"@typescript-eslint/parser": "^8.61.0",
"@typescript-eslint/eslint-plugin": "^8.64.0",
"@typescript-eslint/parser": "^8.64.0",
"cross-env": "^10.1.0",
"eslint": "^9.39.2",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-unused-imports": "^4.4.1",
"globals": "^17.6.0",
"http-proxy-middleware": "^4.1.0",
"globals": "^17.7.0",
"http-proxy-middleware": "^4.2.0",
"jsdom": "^29.1.1",
"postcss": "^8.5.15",
"postcss": "^8.5.19",
"sass-embedded": "^1.100.0",
"typescript": "^6.0.3",
"vitest": "^4.1.8"
"vitest": "^4.1.10"
},
"browserslist": {
"production": [

View File

@@ -91,9 +91,9 @@ The list of MetricsQL features on top of PromQL:
Labels from the `on()` list aren't copied.
* [Aggregate functions](#aggregate-functions) accept arbitrary number of args.
For example, `avg(q1, q2, q3)` would return the average values for every point across time series returned by `q1`, `q2` and `q3`.
* [@ modifier](https://prometheus.io/docs/prometheus/latest/querying/basics/#modifier) can be put anywhere in the query.
* [@ modifier](https://prometheus.io/docs/prometheus/latest/querying/basics/#-modifier) can be put anywhere in the query.
For example, `sum(foo) @ end()` calculates `sum(foo)` at the `end` timestamp of the selected time range `[start ... end]`.
* Arbitrary subexpression can be used as [@ modifier](https://prometheus.io/docs/prometheus/latest/querying/basics/#modifier).
* Arbitrary subexpression can be used as [@ modifier](https://prometheus.io/docs/prometheus/latest/querying/basics/#-modifier).
For example, `foo @ (end() - 1h)` calculates `foo` at the `end - 1 hour` timestamp on the selected time range `[start ... end]`.
* [offset](https://prometheus.io/docs/prometheus/latest/querying/basics/#offset-modifier), lookbehind window in square brackets
and `step` value for [subquery](#subqueries) may refer to the current step aka `$__interval` value from Grafana with `[Ni]` syntax.
@@ -482,7 +482,7 @@ See also [hoeffding_bound_lower](#hoeffding_bound_lower).
#### holt_winters
`holt_winters(series_selector[d], sf, tf)` is a [rollup function](#rollup-functions), which calculates Holt-Winters value
(aka [double exponential smoothing](https://en.wikipedia.org/wiki/Exponential_smoothing#Double_exponential_smoothing)) for [raw samples](https://docs.victoriametrics.com/victoriametrics/keyconcepts/#raw-samples)
(aka [double exponential smoothing](https://en.wikipedia.org/wiki/Exponential_smoothing#Double_exponential_smoothing_%28Holt_linear%29)) for [raw samples](https://docs.victoriametrics.com/victoriametrics/keyconcepts/#raw-samples)
over the given lookbehind window `d` using the given smoothing factor `sf` and the given trend factor `tf`.
Both `sf` and `tf` must be in the range `[0...1]`.
@@ -1154,7 +1154,7 @@ See also [asin](#asin) and [cos](#cos).
#### acosh
`acosh(q)` is a [transform function](#transform-functions), which returns
[inverse hyperbolic cosine](https://en.wikipedia.org/wiki/Inverse_hyperbolic_functions#Inverse_hyperbolic_cosine) for every point of every time series returned by `q`.
[inverse hyperbolic cosine](https://en.wikipedia.org/wiki/Inverse_hyperbolic_functions#Definitions_in_terms_of_logarithms) for every point of every time series returned by `q`.
Metric names are stripped from the resulting series. Add [keep_metric_names](#keep_metric_names) modifier in order to keep metric names.
@@ -1176,7 +1176,7 @@ See also [acos](#acos) and [sin](#sin).
#### asinh
`asinh(q)` is a [transform function](#transform-functions), which returns
[inverse hyperbolic sine](https://en.wikipedia.org/wiki/Inverse_hyperbolic_functions#Inverse_hyperbolic_sine) for every point of every time series returned by `q`.
[inverse hyperbolic sine](https://en.wikipedia.org/wiki/Inverse_hyperbolic_functions#Definitions_in_terms_of_logarithms) for every point of every time series returned by `q`.
Metric names are stripped from the resulting series. Add [keep_metric_names](#keep_metric_names) modifier in order to keep metric names.
@@ -1198,7 +1198,7 @@ See also [tan](#tan).
#### atanh
`atanh(q)` is a [transform function](#transform-functions), which returns
[inverse hyperbolic tangent](https://en.wikipedia.org/wiki/Inverse_hyperbolic_functions#Inverse_hyperbolic_tangent) for every point of every time series returned by `q`.
[inverse hyperbolic tangent](https://en.wikipedia.org/wiki/Inverse_hyperbolic_functions#Definitions_in_terms_of_logarithms) for every point of every time series returned by `q`.
Metric names are stripped from the resulting series. Add [keep_metric_names](#keep_metric_names) modifier in order to keep metric names.
@@ -1229,8 +1229,7 @@ Metric names are stripped from the resulting series. Add [keep_metric_names](#ke
`buckets_limit(limit, buckets)` is a [transform function](#transform-functions), which limits the number
of [histogram buckets](https://valyala.medium.com/improving-histogram-usability-for-prometheus-and-grafana-bc7e5df0e350) to the given `limit`.
The result will preserve the first and the last bucket to improve accuracy for min and max values.
So, if the `limit` is greater than 0 and less than 3, the function will still return 3 buckets: the first bucket, the last bucket, and a selected bucket.
The given `limit` should be greater than `0`. If it is less than `3`, it will be automatically raised to `3` to preserve the first and last buckets for better accuracy of min and max values.
See also [prometheus_buckets](#prometheus_buckets) and [histogram_quantile](#histogram_quantile).

View File

@@ -183,7 +183,7 @@ const StepConfigurator: FC = () => {
<div className="vm-step-control-popper-info">
<p>
<code>step</code> - the <Hyperlink
href="https://prometheus.io/docs/prometheus/latest/querying/basics/#time-durations"
href="https://prometheus.io/docs/prometheus/latest/querying/basics/#float-literals-and-time-durations"
text="interval"
/> between datapoints, which must be returned from the range query.
The <code>query</code> is executed

View File

@@ -98,7 +98,7 @@ const BaseAlert = ({ item, group }: BaseAlertProps) => {
{!!Object.keys(item.annotations || {}).length && (
<>
<span className="vm-alerts-title">Annotations</span>
<table>
<table className="vm-annotations-table">
<colgroup>
<col className="vm-col-md"/>
<col/>

View File

@@ -121,7 +121,7 @@ const BaseRule = ({ item, group }: BaseRuleProps) => {
{!!Object.keys(item?.annotations || {}).length && (
<>
<span className="vm-alerts-title">Annotations</span>
<table>
<table className="vm-annotations-table">
<colgroup>
<col className="vm-col-md"/>
<col/>

View File

@@ -17,10 +17,12 @@ interface HeaderNavProps {
const HeaderNav: FC<HeaderNavProps> = ({ color, background, direction }) => {
const { pathname } = useLocation();
const [activeMenu, setActiveMenu] = useState(pathname);
const [openMenu, setOpenMenu] = useState<string | null>(null);
const menu = useNavigationMenu();
useEffect(() => {
setActiveMenu(pathname);
setOpenMenu(null);
}, [pathname]);
return (
@@ -41,6 +43,8 @@ const HeaderNav: FC<HeaderNavProps> = ({ color, background, direction }) => {
color={color}
background={background}
direction={direction}
openMenu={openMenu}
setOpenMenu={setOpenMenu}
/>
)
: (

View File

@@ -1,11 +1,8 @@
import { FC, useRef, useState } from "preact/compat";
import { useLocation } from "react-router-dom";
import { FC, useRef, useState, Dispatch, SetStateAction } from "preact/compat";
import classNames from "classnames";
import { ArrowDropDownIcon } from "../../../components/Main/Icons";
import Popper from "../../../components/Main/Popper/Popper";
import NavItem from "./NavItem";
import { useEffect } from "react";
import useBoolean from "../../../hooks/useBoolean";
import { NavigationItem, NavigationItemType } from "../../../router/navigation";
interface NavItemProps {
@@ -15,6 +12,8 @@ interface NavItemProps {
color?: string
background?: string
direction?: "row" | "column"
openMenu: string | null,
setOpenMenu: Dispatch<SetStateAction<string | null>>,
}
const NavSubItem: FC<NavItemProps> = ({
@@ -23,21 +22,18 @@ const NavSubItem: FC<NavItemProps> = ({
color,
background,
submenu,
direction = "row"
direction = "row",
openMenu,
setOpenMenu,
}) => {
const { pathname } = useLocation();
const [menuTimeout, setMenuTimeout] = useState<NodeJS.Timeout | null>(null);
const buttonRef = useRef<HTMLDivElement>(null);
const {
value: openSubmenu,
setFalse: handleCloseSubmenu,
setTrue: setOpenSubmenu,
} = useBoolean(false);
const openSubmenu = openMenu === label;
const handleCloseSubmenu = () => setOpenMenu(prev => (prev === label ? null : prev));
const handleOpenSubmenu = () => {
if (direction === "row" || !openSubmenu) setOpenSubmenu();
if (direction === "row" || !openSubmenu) setOpenMenu(label);
if (direction === "column" && openSubmenu) handleCloseSubmenu();
if (direction === "row" && menuTimeout) clearTimeout(menuTimeout);
};
@@ -52,10 +48,6 @@ const NavSubItem: FC<NavItemProps> = ({
if (menuTimeout) clearTimeout(menuTimeout);
};
useEffect(() => {
handleCloseSubmenu();
}, [pathname]);
return (
<div
className={classNames({

View File

@@ -32,7 +32,7 @@ const CardinalityTotals: FC<CardinalityTotalsProps> = ({
const match = searchParams.get("match");
const focusLabel = searchParams.get("focusLabel");
const isMetric = /__name__/.test(match || "");
const showMetricNameStats = !(match || focusLabel);
const progress = totalSeries / totalSeriesAll * 100;
const diff = totalSeries - totalSeriesPrev;
const dynamic = Math.abs(diff) / totalSeriesPrev * 100;
@@ -56,7 +56,7 @@ const CardinalityTotals: FC<CardinalityTotalsProps> = ({
},
].filter(t => t.display);
if (!totals.length) {
if (!totals.length && !showMetricNameStats) {
return null;
}
@@ -75,7 +75,7 @@ const CardinalityTotals: FC<CardinalityTotalsProps> = ({
<h4 className="vm-cardinality-totals-card__title">
{info && (
<Tooltip title={<p className="vm-cardinality-totals-card__tooltip">{info}</p>}>
<div className="vm-cardinality-totals-card__info-icon"><InfoOutlinedIcon/></div>
<div className="vm-cardinality-totals-card__info-icon"><InfoOutlinedIcon /></div>
</Tooltip>
)}
{title}
@@ -99,7 +99,10 @@ const CardinalityTotals: FC<CardinalityTotalsProps> = ({
)}
</div>
))}
<CardinalityMetricNameStats metricNameStats={metricNameStats}/>
{
showMetricNameStats &&
<CardinalityMetricNameStats metricNameStats={metricNameStats} />
}
</div>
);
};

View File

@@ -62,6 +62,13 @@
padding-right: 40px;
}
.vm-annotations-table {
tbody > tr > td {
vertical-align: top;
white-space: pre-line;
}
}
display: flex;
flex-direction: column;
align-items: stretch;

View File

@@ -16,7 +16,7 @@ const supportedValuesOf = Intl.supportedValuesOf;
export const supportedTimezones = supportedValuesOf ? supportedValuesOf("timeZone") as string[] : timezones;
// The list of supported units could be the following -
// https://prometheus.io/docs/prometheus/latest/querying/basics/#time-durations
// https://prometheus.io/docs/prometheus/latest/querying/basics/#float-literals-and-time-durations
export const supportedDurations = [
{ long: "years", short: "y", possible: "year" },
{ long: "weeks", short: "w", possible: "week" },

358
apptest/testdata.go Normal file
View File

@@ -0,0 +1,358 @@
package apptest
import (
"fmt"
"slices"
)
type TestData struct {
Samples []string
Step int64
WantSeries []map[string]string
WantLabels []string
WantLabelValues []string
WantQueryResults []*QueryResult
WantMetadata map[string][]MetadataEntry
WantMetricNamesStats []MetricNamesStatsRecord
}
func GenerateTestData(prefix string, numMetrics, start, end int64) TestData {
d := TestData{
Samples: []string{},
Step: (end - start) / numMetrics,
WantSeries: make([]map[string]string, numMetrics),
WantLabels: make([]string, numMetrics),
WantLabelValues: make([]string, numMetrics),
WantQueryResults: make([]*QueryResult, numMetrics),
WantMetadata: make(map[string][]MetadataEntry),
WantMetricNamesStats: make([]MetricNamesStatsRecord, numMetrics),
}
for i := range numMetrics {
metricName := fmt.Sprintf("%s_%04d", prefix, i)
metricHelp := fmt.Sprintf("# HELP %s some help message", metricName)
metricType := fmt.Sprintf("# TYPE %s gauge", metricName)
labelName := fmt.Sprintf("label_%04d", i)
labelValue := fmt.Sprintf("value_%04d", i)
value := i
timestamp := start + i*d.Step
sample := fmt.Sprintf(`%s{%s="value", label="%s"} %d %d`, metricName, labelName, labelValue, value, timestamp)
d.Samples = append(d.Samples, metricHelp, metricType, sample)
d.WantSeries[i] = map[string]string{
"__name__": metricName,
labelName: "value",
"label": labelValue,
}
d.WantLabels[i] = labelName
d.WantLabelValues[i] = labelValue
d.WantQueryResults[i] = &QueryResult{
Metric: map[string]string{
"__name__": metricName,
labelName: "value",
"label": labelValue,
},
Samples: []*Sample{{Timestamp: timestamp, Value: float64(value)}},
}
d.WantMetadata[metricName] = []MetadataEntry{{Help: "some help message", Type: "gauge"}}
d.WantMetricNamesStats[i].MetricName = metricName
}
d.WantLabels = append(d.WantLabels, "__name__", "label")
slices.Sort(d.WantLabels)
return d
}
// AssertSeries retrieves metric names from the storage and compares the result
// with the expected one.
func AssertSeries(tc *TestCase, app PrometheusQuerier, metricNameRE, tenantID string, start, end int64, want []map[string]string) {
tc.T().Helper()
query := fmt.Sprintf(`{__name__=~"%s"}`, metricNameRE)
tc.Assert(&AssertOptions{
Msg: "unexpected /prometheus/api/v1/series response",
Got: func() any {
tc.T().Helper()
return app.PrometheusAPIV1Series(tc.T(), query, QueryOpts{
Tenant: tenantID,
Start: fmt.Sprintf("%d", start),
End: fmt.Sprintf("%d", end),
}).Sort()
},
Want: &PrometheusAPIV1SeriesResponse{
Status: "success",
Data: want,
},
Retries: 1000,
FailNow: true,
})
}
// AssertSeriesCount retrieves series count and compares it with expected one.
func AssertSeriesCount(tc *TestCase, app PrometheusQuerier, tenantID string, start, end int64, want uint64) {
tc.T().Helper()
tc.Assert(&AssertOptions{
Msg: "unexpected /prometheus/api/v1/series/count response",
Got: func() any {
tc.T().Helper()
return app.PrometheusAPIV1SeriesCount(tc.T(), QueryOpts{
Tenant: tenantID,
Start: fmt.Sprintf("%d", start),
End: fmt.Sprintf("%d", end),
})
},
Want: &PrometheusAPIV1SeriesCountResponse{
Status: "success",
Data: []uint64{want},
},
FailNow: true,
})
}
// AssertLabels retrieves label names from the storage and compares the result
// with the expected one.
func AssertLabels(tc *TestCase, app PrometheusQuerier, metricNameRE, tenantID string, start, end int64, want []string) {
tc.T().Helper()
query := fmt.Sprintf(`{__name__=~"%s"}`, metricNameRE)
tc.Assert(&AssertOptions{
Msg: "unexpected /prometheus/api/v1/labels response",
Got: func() any {
tc.T().Helper()
res := app.PrometheusAPIV1Labels(tc.T(), query, QueryOpts{
Tenant: tenantID,
Start: fmt.Sprintf("%d", start),
End: fmt.Sprintf("%d", end),
})
slices.Sort(res.Data)
return res
},
Want: &PrometheusAPIV1LabelsResponse{
Status: "success",
Data: want,
},
FailNow: true,
})
}
// AssertLabelValues retrieves values for the label whose name is labelName for
// the series whose name mathes metricNameRE, compares the result with the
// expected one.
func AssertLabelValues(tc *TestCase, app PrometheusQuerier, metricNameRE, labelName, tenantID string, start, end int64, want []string) {
tc.T().Helper()
query := fmt.Sprintf(`{__name__=~"%s"}`, metricNameRE)
tc.Assert(&AssertOptions{
Msg: "unexpected /prometheus/api/v1/labels/.../values response",
Got: func() any {
tc.T().Helper()
res := app.PrometheusAPIV1LabelValues(tc.T(), labelName, query, QueryOpts{
Tenant: tenantID,
Start: fmt.Sprintf("%d", start),
End: fmt.Sprintf("%d", end),
})
slices.Sort(res.Data)
return res
},
Want: &PrometheusAPIV1LabelValuesResponse{
Status: "success",
Data: want,
},
FailNow: true,
})
}
// AssertQueryResults sends a data query to storage and compares the query
// result with the expected one.
func AssertQueryResults(tc *TestCase, app PrometheusQuerier, metricNameRE, tenantID string, start, end, step int64, want []*QueryResult) {
tc.T().Helper()
query := fmt.Sprintf(`{__name__=~"%s"}`, metricNameRE)
tc.Assert(&AssertOptions{
Msg: "unexpected /prometheus/api/v1/query_range response",
Got: func() any {
tc.T().Helper()
return app.PrometheusAPIV1QueryRange(tc.T(), query, QueryOpts{
Tenant: tenantID,
Start: fmt.Sprintf("%d", start),
End: fmt.Sprintf("%d", end),
Step: fmt.Sprintf("%dms", step),
MaxLookback: fmt.Sprintf("%dms", step-1),
NoCache: "1",
})
},
Want: &PrometheusAPIV1QueryResponse{
Status: "success",
Data: &QueryData{
ResultType: "matrix",
Result: want,
},
},
FailNow: true,
})
}
func AssertMetadata(tc *TestCase, app PrometheusQuerier, metricName, tenantID string, want map[string][]MetadataEntry) {
tc.T().Helper()
tc.Assert(&AssertOptions{
Msg: "unexpected /prometheus/api/v1/metadata response",
Got: func() any {
tc.T().Helper()
return app.PrometheusAPIV1Metadata(tc.T(), metricName, 0, QueryOpts{
Tenant: tenantID,
})
},
Want: &PrometheusAPIV1Metadata{
Status: "success",
Data: want,
},
FailNow: true,
})
}
func AssertMetricNamesStats(tc *TestCase, app PrometheusQuerier, metricNameRE, tenantID string, want []MetricNamesStatsRecord) {
tc.T().Helper()
tc.Assert(&AssertOptions{
Msg: "unexpected /prometheus/api/v1/status/metric_names_stats response",
Got: func() any {
tc.T().Helper()
return app.PrometheusAPIV1StatusMetricNamesStats(tc.T(), "", "", metricNameRE, QueryOpts{
Tenant: tenantID,
})
},
Want: MetricNamesStatsResponse{
Records: want,
},
FailNow: true,
})
}
// GraphiteTestData holds the data samples in Graphite Pickle format, distance
// between samples in milliseconds and expected responses for various Graphite
// API endpoints.
type GraphiteTestData struct {
Samples []string
Step int64
WantMetricsIndex []string
WantMetricsFind []GraphiteMetric
WantMetricsExpand []string
WantRenderedTargets []GraphiteRenderedTarget
}
// GenerateGraphiteTestData generates Graphite test data.
func GenerateGraphiteTestData(prefix string, numMetrics, start, end int64) GraphiteTestData {
d := GraphiteTestData{
Samples: make([]string, numMetrics),
Step: (end - start) / numMetrics,
WantMetricsIndex: make([]string, numMetrics),
WantMetricsFind: make([]GraphiteMetric, numMetrics),
WantMetricsExpand: make([]string, numMetrics),
WantRenderedTargets: make([]GraphiteRenderedTarget, numMetrics),
}
datapoints := make([][2]float64, numMetrics)
for i := range numMetrics {
timestamp := (start + i*d.Step) / 1000
datapoints[i][1] = float64(timestamp)
}
for i := range numMetrics {
suffix := fmt.Sprintf("%04d", i)
metricName := fmt.Sprintf("%s.%s", prefix, suffix)
value := i
timestamp := (start + i*d.Step) / 1000
sample := fmt.Sprintf(`%s %d %d`, metricName, value, timestamp)
d.Samples[i] = sample
d.WantMetricsIndex[i] = metricName
d.WantMetricsFind[i].Id = metricName
d.WantMetricsFind[i].Text = suffix
d.WantMetricsFind[i].Leaf = 1
d.WantMetricsExpand[i] = metricName
d.WantRenderedTargets[i].Target = metricName
d.WantRenderedTargets[i].Datapoints = slices.Clone(datapoints)
d.WantRenderedTargets[i].Datapoints[i][0] = float64(value)
}
return d
}
// AssertGraphiteMetricsIndex retrieves all metrics by sending a request to
// /graphite/metrics/index.json and compares the result with the expected one.
func AssertGraphiteMetricsIndex(tc *TestCase, app PrometheusQuerier, tenantID string, want []string) {
tc.T().Helper()
tc.Assert(&AssertOptions{
Msg: "unexpected /graphite/metrics/index.json response",
Got: func() any {
tc.T().Helper()
return app.GraphiteMetricsIndex(tc.T(), QueryOpts{
Tenant: tenantID,
})
},
Want: want,
Retries: 30,
FailNow: true,
})
}
// AssertGraphiteMetricsFind finds metric names by sending a request to
// /graphite/metrics/find and compares the result with the expected one.
func AssertGraphiteMetricsFind(tc *TestCase, app PrometheusQuerier, query, tenantID string, want []GraphiteMetric) {
tc.T().Helper()
tc.Assert(&AssertOptions{
Msg: "unexpected /graphite/metrics/find response",
Got: func() any {
tc.T().Helper()
return app.GraphiteMetricsFind(tc.T(), query, QueryOpts{
Tenant: tenantID,
})
},
Want: want,
FailNow: true,
})
}
// AssertGraphiteMetricsFind expands metric names by sending a request to
// /graphite/metrics/expand and compares the result with the expected one.
func AssertGraphiteMetricsExpand(tc *TestCase, app PrometheusQuerier, query, tenantID string, want []string) {
tc.T().Helper()
tc.Assert(&AssertOptions{
Msg: "unexpected /graphite/metrics/expand response",
Got: func() any {
tc.T().Helper()
return app.GraphiteMetricsExpand(tc.T(), query, QueryOpts{
Tenant: tenantID,
})
},
Want: want,
FailNow: true,
})
}
// AssertGraphiteRender retieves metric raw data by sending a request to
// /graphite/render and compares the result with the expected one.
func AssertGraphiteRender(tc *TestCase, app PrometheusQuerier, target, tenantID string, from, until, step int64, want []GraphiteRenderedTarget) {
tc.T().Helper()
tc.Assert(&AssertOptions{
Msg: "unexpected /graphite/render response",
Got: func() any {
tc.T().Helper()
return app.GraphiteRender(tc.T(), target, QueryOpts{
Tenant: tenantID,
From: fmt.Sprintf("%d", from/1000),
Until: fmt.Sprintf("%d", until/1000),
StorageStep: fmt.Sprintf("%dms", step),
})
},
Want: want,
FailNow: true,
})
}

View File

@@ -0,0 +1,222 @@
package tests
import (
"fmt"
"path/filepath"
"testing"
"time"
"github.com/VictoriaMetrics/VictoriaMetrics/apptest"
)
func TestSingleMaxBackfillAge(t *testing.T) {
tc := apptest.NewTestCase(t)
defer tc.Stop()
opts := maxBackfillAgeOpts{
start: func(retentionPeriod, maxBackfillAge string) apptest.PrometheusWriteQuerier {
return tc.MustStartVmsingle("vmsingle", []string{
"-storageDataPath=" + filepath.Join(tc.Dir(), "vmsingle"),
"-retentionPeriod=" + retentionPeriod,
"-maxBackfillAge=" + maxBackfillAge,
})
},
stop: func() {
tc.StopApp("vmsingle")
},
}
testMaxBackfillAge(tc, opts)
}
func TestClusterMaxBackfillAge(t *testing.T) {
tc := apptest.NewTestCase(t)
defer tc.Stop()
opts := maxBackfillAgeOpts{
start: func(retentionPeriod, maxBackfillAge string) apptest.PrometheusWriteQuerier {
return tc.MustStartCluster(&apptest.ClusterOptions{
Vmstorage1Instance: "vmstorage1",
Vmstorage1Flags: []string{
"-storageDataPath=" + filepath.Join(tc.Dir(), "vmstorage1"),
"-retentionPeriod=" + retentionPeriod,
"-maxBackfillAge=" + maxBackfillAge,
},
Vmstorage2Instance: "vmstorage2",
Vmstorage2Flags: []string{
"-storageDataPath=" + filepath.Join(tc.Dir(), "vmstorage2"),
"-retentionPeriod=" + retentionPeriod,
"-maxBackfillAge=" + maxBackfillAge,
},
VminsertInstance: "vminsert",
VminsertFlags: []string{},
VmselectInstance: "vmselect",
VmselectFlags: []string{},
})
},
stop: func() {
tc.StopApp("vminsert")
tc.StopApp("vmselect")
tc.StopApp("vmstorage1")
tc.StopApp("vmstorage2")
},
}
testMaxBackfillAge(tc, opts)
}
type maxBackfillAgeOpts struct {
start func(retentionPeriod, maxBackfillAge string) apptest.PrometheusWriteQuerier
stop func()
}
func testMaxBackfillAge(tc *apptest.TestCase, opts maxBackfillAgeOpts) {
t := tc.T()
assertSeries := func(app apptest.PrometheusQuerier, prefix string, start, end int64, want []map[string]string) {
t.Helper()
query := fmt.Sprintf(`{__name__=~"metric_%s.*"}`, prefix)
tc.Assert(&apptest.AssertOptions{
Msg: "unexpected /api/v1/series response",
Got: func() any {
return app.PrometheusAPIV1Series(t, query, apptest.QueryOpts{
Start: fmt.Sprintf("%d", start),
End: fmt.Sprintf("%d", end),
}).Sort()
},
Want: &apptest.PrometheusAPIV1SeriesResponse{
Status: "success",
Data: want,
},
FailNow: true,
})
}
assertQueryResults := func(app apptest.PrometheusQuerier, prefix string, start, end, step int64, want []*apptest.QueryResult) {
t.Helper()
query := fmt.Sprintf(`{__name__=~"metric_%s.*"}`, prefix)
tc.Assert(&apptest.AssertOptions{
Msg: "unexpected /api/v1/query_range response",
Got: func() any {
return app.PrometheusAPIV1QueryRange(t, query, apptest.QueryOpts{
Start: fmt.Sprintf("%d", start),
End: fmt.Sprintf("%d", end),
Step: fmt.Sprintf("%dms", step),
MaxLookback: fmt.Sprintf("%dms", step-1),
NoCache: "1",
})
},
Want: &apptest.PrometheusAPIV1QueryResponse{
Status: "success",
Data: &apptest.QueryData{
ResultType: "matrix",
Result: want,
},
},
FailNow: true,
})
}
const numMetrics = 1000
now := time.Now().UTC()
var start, end, step int64
emptySeries := []map[string]string{}
emptyQueryResults := []*apptest.QueryResult{}
// Start sut with the same -retentionPeriod and -maxBackfillAge.
sut := opts.start("1y", "1y")
// Verify that samples older than the retention period are rejected.
start = now.Add(-365 * 24 * time.Hour).Add(-time.Hour).UnixMilli()
end = now.Add(-365 * 24 * time.Hour).UnixMilli()
step = (end - start) / numMetrics
outsideRetention := genMaxBackfillAgeData("outside_retention", numMetrics, start, step)
sut.PrometheusAPIV1ImportPrometheus(t, outsideRetention.samples, apptest.QueryOpts{})
sut.ForceFlush(t)
assertSeries(sut, "outside_retention", start, end, emptySeries)
assertQueryResults(sut, "outside_retention", start, end, step, emptyQueryResults)
// Verify that samples within the retention period are accepted and
// searcheable.
start = now.Add(-365 * 24 * time.Hour).Add(time.Hour).UnixMilli()
end = now.Add(-365 * 24 * time.Hour).Add(2 * time.Hour).UnixMilli()
step = (end - start) / numMetrics
insideRetention := genMaxBackfillAgeData("inside_retention", numMetrics, start, step)
sut.PrometheusAPIV1ImportPrometheus(t, insideRetention.samples, apptest.QueryOpts{})
sut.ForceFlush(t)
assertSeries(sut, "inside_retention", start, end, insideRetention.wantSeries)
assertQueryResults(sut, "inside_retention", start, end, step, insideRetention.wantQueryResults)
// Restart sut with -maxBackfillAge shorter than the -retentionPeriod.
opts.stop()
sut = opts.start("1y", "6M")
// Verify that new samples older than max backfill age but still within the
// retention period are rejected but existing samples are still searcheable.
start = now.Add(-365 * 24 * time.Hour).Add(time.Hour).UnixMilli()
end = now.Add(-365 * 24 * time.Hour).Add(2 * time.Hour).UnixMilli()
step = (end - start) / numMetrics
insideRetention2 := genMaxBackfillAgeData("inside_retention2", numMetrics, start, step)
sut.PrometheusAPIV1ImportPrometheus(t, insideRetention2.samples, apptest.QueryOpts{})
sut.ForceFlush(t)
assertSeries(sut, "inside_retention2", start, end, emptySeries)
assertQueryResults(sut, "inside_retention2", start, end, step, emptyQueryResults)
assertSeries(sut, "inside_retention", start, end, insideRetention.wantSeries)
assertQueryResults(sut, "inside_retention", start, end, step, insideRetention.wantQueryResults)
// Verify that the metrics that are outside the backfill window can still
// be deleted.
sut.PrometheusAPIV1AdminTSDBDeleteSeries(t, `{__name__=~".*inside_retention.*"}`, apptest.QueryOpts{})
sut.ForceFlush(t)
assertSeries(sut, "inside_retention", start, end, emptySeries)
assertQueryResults(sut, "inside_retention", start, end, step, emptyQueryResults)
// Verify that the samples that are within the backfill window are accepted
// and searchable.
start = now.Add(-180 * 24 * time.Hour).UnixMilli()
end = now.Add(-180 * 24 * time.Hour).Add(1 * time.Hour).UnixMilli()
step = (end - start) / numMetrics
insideMaxBackfillAge := genMaxBackfillAgeData("inside_max_backfill_age", numMetrics, start, step)
sut.PrometheusAPIV1ImportPrometheus(t, insideMaxBackfillAge.samples, apptest.QueryOpts{})
sut.ForceFlush(t)
assertSeries(sut, "inside_max_backfill_age", start, end, insideMaxBackfillAge.wantSeries)
assertQueryResults(sut, "inside_max_backfill_age", start, end, step, insideMaxBackfillAge.wantQueryResults)
opts.stop()
}
type maxBackfillAgeData struct {
samples []string
wantSeries []map[string]string
wantQueryResults []*apptest.QueryResult
}
func genMaxBackfillAgeData(prefix string, numMetrics, start, step int64) maxBackfillAgeData {
samples := make([]string, numMetrics)
wantSeries := make([]map[string]string, numMetrics)
wantQueryResults := make([]*apptest.QueryResult, numMetrics)
for i := range numMetrics {
metricName := fmt.Sprintf("metric_%s_%04d", prefix, i)
labelName := fmt.Sprintf("label_%s_%04d", prefix, i)
labelValue := fmt.Sprintf("value_%s_%04d", prefix, i)
value := i
timestamp := start + i*step
samples[i] = fmt.Sprintf(`%s{%s="value", label="%s"} %d %d`, metricName, labelName, labelValue, value, timestamp)
wantSeries[i] = map[string]string{
"__name__": metricName,
labelName: "value",
"label": labelValue,
}
wantQueryResults[i] = &apptest.QueryResult{
Metric: map[string]string{
"__name__": metricName,
labelName: "value",
"label": labelValue,
},
Samples: []*apptest.Sample{{Timestamp: timestamp, Value: float64(value)}},
}
}
return maxBackfillAgeData{samples, wantSeries, wantQueryResults}
}

View File

@@ -2,6 +2,7 @@ package tests
import (
"fmt"
"math"
"testing"
"github.com/google/go-cmp/cmp"
@@ -25,7 +26,11 @@ func TestSingleMetricsMetadata(t *testing.T) {
if len(resp.Data) != 0 {
t.Fatalf("unexpected resp Records: %d, want: %d", len(resp.Data), 0)
}
generateValueExceedLimit := func(prefix string) string {
buf := make([]byte, math.MaxUint16+len(prefix))
copy(buf, prefix)
return string(buf)
}
const ingestTimestamp = 1707123456700
prometheusTextDataSet := []string{
`# HELP metric_name_1 some help message`,
@@ -40,6 +45,12 @@ func TestSingleMetricsMetadata(t *testing.T) {
`# TYPE metric_name_3 gauge`,
`metric_name_3{label="baz"} 30`,
}
prometheusTextDataSet = append(prometheusTextDataSet,
`# HELP metric_name_4 `+generateValueExceedLimit("large help"),
`# TYPE metric_name_4 gauge`,
`metric_name_4{label="baz"} 30`,
)
prometheusRemoteWriteDataSet := prompb.WriteRequest{
Timeseries: []prompb.TimeSeries{
{Labels: []prompb.Label{{Name: "__name__", Value: "metric_name_4"}}, Samples: []prompb.Sample{{Value: 40, Timestamp: ingestTimestamp}}},
@@ -52,6 +63,9 @@ func TestSingleMetricsMetadata(t *testing.T) {
{MetricFamilyName: "metric_name_5", Help: "some help message", Type: prompb.MetricTypeSummary},
{MetricFamilyName: "metric_name_6", Help: "some help message", Type: prompb.MetricTypeStateset},
{MetricFamilyName: `metric_name_7_!@"_suffix`, Help: "some help message", Type: prompb.MetricTypeStateset},
{MetricFamilyName: "metric_name_8", Help: generateValueExceedLimit("large_help"), Type: prompb.MetricTypeStateset},
{MetricFamilyName: "metric_name_9", Help: "some help message", Type: prompb.MetricTypeStateset, Unit: generateValueExceedLimit("large_unit")},
{MetricFamilyName: generateValueExceedLimit("metric_name_10"), Help: "some help message", Type: prompb.MetricTypeStateset},
},
}
@@ -137,6 +151,11 @@ func TestClusterMetricsMetadata(t *testing.T) {
if len(resp.Data) != 0 {
t.Fatalf("unexpected resp Records: %d, want: %d", len(resp.Data), 0)
}
generateValueExceedLimit := func(prefix string) string {
buf := make([]byte, math.MaxUint16+len(prefix))
copy(buf, prefix)
return string(buf)
}
const ingestTimestamp = 1707123456700
prometheusTextDataSet := []string{
@@ -152,6 +171,11 @@ func TestClusterMetricsMetadata(t *testing.T) {
`# TYPE metric_name_3 gauge`,
`metric_name_3{label="baz"} 30`,
}
prometheusTextDataSet = append(prometheusTextDataSet,
`# HELP metric_name_4 `+generateValueExceedLimit("large help"),
`# TYPE metric_name_4 gauge`,
`metric_name_4{label="baz"} 30`,
)
prometheusRemoteWriteDataSet := prompb.WriteRequest{
Timeseries: []prompb.TimeSeries{
{Labels: []prompb.Label{{Name: "__name__", Value: "metric_name_4"}}, Samples: []prompb.Sample{{Value: 40, Timestamp: ingestTimestamp}}},
@@ -164,6 +188,9 @@ func TestClusterMetricsMetadata(t *testing.T) {
{MetricFamilyName: "metric_name_5", Help: "some help message", Type: prompb.MetricTypeSummary},
{MetricFamilyName: "metric_name_6", Help: "some help message", Type: prompb.MetricTypeStateset},
{MetricFamilyName: `metric_name_7_!@"_suffix`, Help: "some help message", Type: prompb.MetricTypeStateset},
{MetricFamilyName: "metric_name_8", Help: generateValueExceedLimit("large_help"), Type: prompb.MetricTypeStateset},
{MetricFamilyName: "metric_name_9", Help: "some help message", Type: prompb.MetricTypeStateset, Unit: generateValueExceedLimit("large_unit")},
{MetricFamilyName: generateValueExceedLimit("metric_name_10"), Help: "some help message", Type: prompb.MetricTypeStateset},
},
}

View File

@@ -517,10 +517,15 @@ func TestClusterVMAgentForwardMetricsMetadata(t *testing.T) {
"-remoteWrite.tmpDataPath=" + tc.Dir() + "/vmagent",
fmt.Sprintf(`-remoteWrite.url=http://%s/insert/multitenant/prometheus/api/v1/write`, sut.Vminsert.HTTPAddr()),
})
generateValueExceedLimit := func(prefix string) string {
buf := make([]byte, math.MaxUint16+len(prefix))
copy(buf, prefix)
return string(buf)
}
prometheusRemoteWriteDataSet := prompb.WriteRequest{
Metadata: []prompb.MetricMetadata{
{MetricFamilyName: "metric_name_4", Help: "some help message", Type: prompb.MetricTypeSummary, AccountID: 100},
{MetricFamilyName: "metric_name_8", Help: generateValueExceedLimit("large_help"), Type: prompb.MetricTypeStateset, AccountID: 100},
},
}
vmagent.PrometheusAPIV1Write(t, prometheusRemoteWriteDataSet, apptest.QueryOpts{Tenant: "multitenant"})

View File

@@ -0,0 +1,216 @@
package tests
import (
"fmt"
"path/filepath"
"slices"
"strconv"
"testing"
"time"
"github.com/VictoriaMetrics/VictoriaMetrics/apptest"
"github.com/google/go-cmp/cmp"
)
func TestMixedPrometheusQueries(t *testing.T) {
tc := apptest.NewTestCase(t)
defer tc.Stop()
const (
accountID1 = 12
projectID1 = 34
accountID2 = 56
projectID2 = 78
numMetrics = 10
)
tenantID1 := fmt.Sprintf("%d:%d", accountID1, projectID1)
tenantID2 := fmt.Sprintf("%d:%d", accountID2, projectID2)
start := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC).UnixMilli()
end := time.Date(2026, 1, 2, 0, 0, 0, 0, time.UTC).UnixMilli()
data := apptest.GenerateTestData("metric", numMetrics, start, end)
emptySeries := []map[string]string{}
emptyLabels := []string{}
emptyLabelValues := []string{}
emptyQueryResults := []*apptest.QueryResult{}
emptyMetadata := map[string][]apptest.MetadataEntry{}
emptyMetricNamesStats := []apptest.MetricNamesStatsRecord{}
vmsingle := tc.MustStartVmsingle("vmsingle", []string{
"-storageDataPath=" + filepath.Join(tc.Dir(), "vmsingle"),
"-retentionPeriod=100y",
fmt.Sprintf("-accountID=%d", accountID1),
fmt.Sprintf("-projectID=%d", projectID1),
})
vmselect := tc.MustStartVmselect("vmselect", []string{
"-storageNode=" + vmsingle.VmselectAddr(),
})
vmsingle.PrometheusAPIV1ImportPrometheus(tc.T(), data.Samples, apptest.QueryOpts{})
vmsingle.ForceFlush(t)
// Ensure vmsingle returns data.
apptest.AssertSeries(tc, vmsingle, "metric.*", "", start, end, data.WantSeries)
apptest.AssertSeriesCount(tc, vmsingle, "", start, end, numMetrics)
apptest.AssertLabels(tc, vmsingle, "metric.*", "", start, end, data.WantLabels)
apptest.AssertLabelValues(tc, vmsingle, "metric.*", "label", "", start, end, data.WantLabelValues)
apptest.AssertQueryResults(tc, vmsingle, "metric.*", "", start, end, data.Step, data.WantQueryResults)
apptest.AssertMetadata(tc, vmsingle, "", "", data.WantMetadata)
for i := range data.WantMetricNamesStats {
data.WantMetricNamesStats[i].QueryRequestsCount = 1
}
apptest.AssertMetricNamesStats(tc, vmsingle, "", "", data.WantMetricNamesStats)
// Check that current vmsingle tenant (configured via flags) is tenant1.
gotAdminTenantsResponse := vmselect.APIV1AdminTenants(t, apptest.QueryOpts{})
wantAdminTenantsResponse := &apptest.AdminTenantsResponse{
Status: "success",
Data: []string{tenantID1},
}
if diff := cmp.Diff(wantAdminTenantsResponse, gotAdminTenantsResponse); diff != "" {
t.Fatalf("unexpected tenants (-want, +got):\n%s", diff)
}
// Ensure vmselect returns data for tenant1.
apptest.AssertSeries(tc, vmselect, "metric.*", tenantID1, start, end, data.WantSeries)
apptest.AssertSeriesCount(tc, vmselect, tenantID1, start, end, numMetrics)
apptest.AssertLabels(tc, vmselect, "metric.*", tenantID1, start, end, data.WantLabels)
apptest.AssertLabelValues(tc, vmselect, "metric.*", "label", tenantID1, start, end, data.WantLabelValues)
apptest.AssertQueryResults(tc, vmselect, "metric.*", tenantID1, start, end, data.Step, data.WantQueryResults)
apptest.AssertMetadata(tc, vmselect, "", tenantID1, data.WantMetadata)
for i := range data.WantMetricNamesStats {
data.WantMetricNamesStats[i].QueryRequestsCount = 2
}
apptest.AssertMetricNamesStats(tc, vmselect, "", tenantID1, data.WantMetricNamesStats)
// Ensure vmselect does not return any data for tenant2.
apptest.AssertSeries(tc, vmselect, "metric.*", tenantID2, start, end, emptySeries)
apptest.AssertSeriesCount(tc, vmselect, tenantID2, start, end, 0)
apptest.AssertLabels(tc, vmselect, "metric.*", tenantID2, start, end, emptyLabels)
apptest.AssertLabelValues(tc, vmselect, "metric.*", "label", tenantID2, start, end, emptyLabelValues)
apptest.AssertQueryResults(tc, vmselect, "metric.*", tenantID2, start, end, data.Step, emptyQueryResults)
apptest.AssertMetadata(tc, vmselect, "", tenantID2, emptyMetadata)
apptest.AssertMetricNamesStats(tc, vmselect, "", tenantID2, emptyMetricNamesStats)
// Ensure vmselect returns data for multitenant.
for _, v := range data.WantSeries {
v["vm_account_id"] = strconv.Itoa(accountID1)
v["vm_project_id"] = strconv.Itoa(projectID1)
}
apptest.AssertSeries(tc, vmselect, "metric.*", "multitenant", start, end, data.WantSeries)
data.WantLabels = append(data.WantLabels, "vm_account_id", "vm_project_id")
apptest.AssertLabels(tc, vmselect, "metric.*", "multitenant", start, end, data.WantLabels)
apptest.AssertLabelValues(tc, vmselect, "metric.*", "label", "multitenant", start, end, data.WantLabelValues)
for _, v := range data.WantQueryResults {
v.Metric["vm_account_id"] = strconv.Itoa(accountID1)
v.Metric["vm_project_id"] = strconv.Itoa(projectID1)
}
apptest.AssertQueryResults(tc, vmselect, "metric.*", "multitenant", start, end, data.Step, data.WantQueryResults)
apptest.AssertMetadata(tc, vmselect, "", "multitenant", data.WantMetadata)
for i := range data.WantMetricNamesStats {
data.WantMetricNamesStats[i].QueryRequestsCount = 3
}
apptest.AssertMetricNamesStats(tc, vmselect, "", "multitenant", data.WantMetricNamesStats)
}
func TestMixedDeleteSeries(t *testing.T) {
tc := apptest.NewTestCase(t)
defer tc.Stop()
const (
accountID1 = 12
projectID1 = 34
accountID2 = 56
projectID2 = 78
numMetrics = 10
)
tenantID1 := fmt.Sprintf("%d:%d", accountID1, projectID1)
tenantID2 := fmt.Sprintf("%d:%d", accountID2, projectID2)
start := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC).UnixMilli()
end := time.Date(2026, 1, 2, 0, 0, 0, 0, time.UTC).UnixMilli()
data1 := apptest.GenerateTestData("metric1", numMetrics, start, end)
data2 := apptest.GenerateTestData("metric2", numMetrics, start, end)
emptySeries := []map[string]string{}
vmsingle := tc.MustStartVmsingle("vmsingle", []string{
"-storageDataPath=" + filepath.Join(tc.Dir(), "vmsingle"),
"-retentionPeriod=100y",
fmt.Sprintf("-accountID=%d", accountID1),
fmt.Sprintf("-projectID=%d", projectID1),
})
vmselect := tc.MustStartVmselect("vmselect", []string{
"-storageNode=" + vmsingle.VmselectAddr(),
})
vmsingle.PrometheusAPIV1ImportPrometheus(tc.T(), data1.Samples, apptest.QueryOpts{})
vmsingle.PrometheusAPIV1ImportPrometheus(tc.T(), data2.Samples, apptest.QueryOpts{})
vmsingle.ForceFlush(t)
wantSeries12 := slices.Concat(data1.WantSeries, data2.WantSeries)
apptest.AssertSeries(tc, vmsingle, "metric.*", "", start, end, wantSeries12)
vmselect.PrometheusAPIV1AdminTSDBDeleteSeries(tc.T(), `{__name__=~"metric1.*"}`, apptest.QueryOpts{
Tenant: tenantID1,
})
apptest.AssertSeries(tc, vmsingle, "metric.*", "", start, end, data2.WantSeries)
vmselect.PrometheusAPIV1AdminTSDBDeleteSeries(tc.T(), `{__name__=~"metric2.*"}`, apptest.QueryOpts{
Tenant: tenantID2,
})
apptest.AssertSeries(tc, vmsingle, "metric.*", "", start, end, data2.WantSeries)
vmselect.PrometheusAPIV1AdminTSDBDeleteSeries(tc.T(), `{__name__=~"metric2.*"}`, apptest.QueryOpts{
Tenant: "multitenant",
})
apptest.AssertSeries(tc, vmsingle, "metric.*", "", start, end, emptySeries)
}
func TestMixedGraphiteQueries(t *testing.T) {
tc := apptest.NewTestCase(t)
defer tc.Stop()
const (
accountID1 = 12
projectID1 = 34
accountID2 = 56
projectID2 = 78
numMetrics = 10
)
tenantID1 := fmt.Sprintf("%d:%d", accountID1, projectID1)
tenantID2 := fmt.Sprintf("%d:%d", accountID2, projectID2)
start := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC).UnixMilli()
end := time.Date(2026, 1, 2, 0, 0, 0, 0, time.UTC).UnixMilli()
data := apptest.GenerateGraphiteTestData("metric", numMetrics, start, end)
emptyMetricsIndex := []string{}
emptyMetricsFind := []apptest.GraphiteMetric{}
emptyMetricsExpand := []string{}
emptyRenderedTargets := []apptest.GraphiteRenderedTarget{}
vmsingle := tc.MustStartVmsingle("vmsingle", []string{
"-storageDataPath=" + filepath.Join(tc.Dir(), "vmsingle"),
"-retentionPeriod=100y",
fmt.Sprintf("-accountID=%d", accountID1),
fmt.Sprintf("-projectID=%d", projectID1),
})
vmselect := tc.MustStartVmselect("vmselect", []string{
"-storageNode=" + vmsingle.VmselectAddr(),
})
vmsingle.GraphiteWrite(tc.T(), data.Samples, apptest.QueryOpts{})
vmsingle.ForceFlush(t)
// Ensure vmsingle returns data.
apptest.AssertGraphiteMetricsIndex(tc, vmsingle, "", data.WantMetricsIndex)
apptest.AssertGraphiteMetricsFind(tc, vmsingle, "metric.*", "", data.WantMetricsFind)
apptest.AssertGraphiteMetricsExpand(tc, vmsingle, "metric.*", "", data.WantMetricsExpand)
apptest.AssertGraphiteRender(tc, vmsingle, "metric.*", "", start, end, data.Step, data.WantRenderedTargets)
// Ensure vmselect returns data for tenant1.
apptest.AssertGraphiteMetricsIndex(tc, vmselect, tenantID1, data.WantMetricsIndex)
apptest.AssertGraphiteMetricsFind(tc, vmselect, "metric.*", tenantID1, data.WantMetricsFind)
apptest.AssertGraphiteMetricsExpand(tc, vmselect, "metric.*", tenantID1, data.WantMetricsExpand)
apptest.AssertGraphiteRender(tc, vmselect, "metric.*", tenantID1, start, end, data.Step, data.WantRenderedTargets)
// Ensure vmselect does not return any data for tenant2.
apptest.AssertGraphiteMetricsIndex(tc, vmselect, tenantID2, emptyMetricsIndex)
apptest.AssertGraphiteMetricsFind(tc, vmselect, "metric.*", tenantID2, emptyMetricsFind)
apptest.AssertGraphiteMetricsExpand(tc, vmselect, "metric.*", tenantID2, emptyMetricsExpand)
apptest.AssertGraphiteRender(tc, vmselect, "metric.*", tenantID2, start, end, data.Step, emptyRenderedTargets)
}

View File

@@ -25,12 +25,14 @@ func StartVmsingle(instance string, flags []string, cli *Client, output io.Write
"-httpListenAddr": "127.0.0.1:0",
"-graphiteListenAddr": "127.0.0.1:0",
"-opentsdbListenAddr": "127.0.0.1:0",
"-vmselectAddr": "127.0.0.1:0",
},
extractREs: []*regexp.Regexp{
storageDataPathRE,
httpListenAddrRE,
graphiteListenAddrRE,
openTSDBListenAddrRE,
vmselectAddrRE,
},
output: output,
})
@@ -43,6 +45,7 @@ func StartVmsingle(instance string, flags []string, cli *Client, output io.Write
httpListenAddr: stderrExtracts[1],
graphiteListenAddr: stderrExtracts[2],
openTSDBListenAddr: stderrExtracts[3],
vmselectAddr: stderrExtracts[4],
}), nil
}
@@ -51,6 +54,7 @@ type vmsingleRuntimeValues struct {
httpListenAddr string
graphiteListenAddr string
openTSDBListenAddr string
vmselectAddr string
}
func newVmsingle(app *app, cli *Client, rt vmsingleRuntimeValues) *Vmsingle {
@@ -85,6 +89,7 @@ func newVmsingle(app *app, cli *Client, rt vmsingleRuntimeValues) *Vmsingle {
},
storageDataPath: rt.storageDataPath,
httpListenAddr: rt.httpListenAddr,
vmselectAddr: rt.vmselectAddr,
}
}
@@ -99,6 +104,7 @@ type Vmsingle struct {
storageDataPath string
httpListenAddr string
vmselectAddr string
}
// HTTPAddr returns the address at which the vminsert process is
@@ -107,6 +113,12 @@ func (app *Vmsingle) HTTPAddr() string {
return app.httpListenAddr
}
// VmselectAddr returns the address at which the vmsingle process is listening
// for vmselect connections.
func (app *Vmsingle) VmselectAddr() string {
return app.vmselectAddr
}
// String returns the string representation of the vmsingle app state.
func (app *Vmsingle) String() string {
return fmt.Sprintf("{app: %s storageDataPath: %q httpListenAddr: %q}", []any{

View File

@@ -1,4 +1,59 @@
{
"__inputs": [
{
"name": "DS_VICTORIALOGS",
"label": "VictoriaLogs",
"description": "",
"type": "datasource",
"pluginId": "victoriametrics-logs-datasource",
"pluginName": "VictoriaLogs"
}
],
"__elements": {},
"__requires": [
{
"type": "grafana",
"id": "grafana",
"name": "Grafana",
"version": "12.4.3"
},
{
"type": "panel",
"id": "logs",
"name": "Logs",
"version": ""
},
{
"type": "panel",
"id": "stat",
"name": "Stat",
"version": ""
},
{
"type": "panel",
"id": "table",
"name": "Table",
"version": ""
},
{
"type": "panel",
"id": "text",
"name": "Text",
"version": ""
},
{
"type": "panel",
"id": "timeseries",
"name": "Time series",
"version": ""
},
{
"type": "datasource",
"id": "victoriametrics-logs-datasource",
"name": "VictoriaLogs",
"version": "0.29.0"
}
],
"annotations": {
"list": [
{
@@ -18,7 +73,6 @@
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": 861,
"links": [
{
"icon": "doc",
@@ -78,7 +132,8 @@
"mode": "absolute",
"steps": [
{
"color": "green"
"color": "green",
"value": 0
}
]
}
@@ -109,7 +164,7 @@
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "11.6.0",
"pluginVersion": "12.4.3",
"targets": [
{
"datasource": {
@@ -117,7 +172,7 @@
"uid": "${ds}"
},
"editorMode": "code",
"expr": "\"\\tvm_slow_query_stats\" | extract 'vm_slow_query_stats <vm_slow_query_stats>' | unpack_logfmt from vm_slow_query_stats \n| tenant:in($tenant)\n| query_hash:in($query_hash)\n| type:=\"instant\"\n| count()",
"expr": "\"vm_slow_query_stats\" | extract 'vm_slow_query_stats <vm_slow_query_stats>' | unpack_logfmt from vm_slow_query_stats \n| tenant:in($tenant)\n| query_hash:$query_hash\n| type:=\"instant\"\n| count()",
"queryType": "stats",
"refId": "A"
}
@@ -140,7 +195,8 @@
"mode": "absolute",
"steps": [
{
"color": "green"
"color": "green",
"value": 0
}
]
}
@@ -171,7 +227,7 @@
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "11.6.0",
"pluginVersion": "12.4.3",
"targets": [
{
"datasource": {
@@ -179,7 +235,7 @@
"uid": "${ds}"
},
"editorMode": "code",
"expr": "\"\\tvm_slow_query_stats\" | extract 'vm_slow_query_stats <vm_slow_query_stats>' | unpack_logfmt from vm_slow_query_stats \n| tenant:in($tenant)\n| query_hash:in($query_hash) \n| type:=\"range\"\n| count()",
"expr": "\"vm_slow_query_stats\" | extract 'vm_slow_query_stats <vm_slow_query_stats>' | unpack_logfmt from vm_slow_query_stats \n| tenant:in($tenant)\n| query_hash:$query_hash \n| type:=\"range\"\n| count()",
"queryType": "stats",
"refId": "A"
}
@@ -202,7 +258,8 @@
"mode": "absolute",
"steps": [
{
"color": "green"
"color": "green",
"value": 0
}
]
}
@@ -233,7 +290,7 @@
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "11.6.0",
"pluginVersion": "12.4.3",
"targets": [
{
"datasource": {
@@ -241,7 +298,7 @@
"uid": "${ds}"
},
"editorMode": "code",
"expr": "\"\\tvm_slow_query_stats\" | extract 'vm_slow_query_stats <vm_slow_query_stats>' | unpack_logfmt from vm_slow_query_stats \n| tenant:in($tenant)\n| query_hash:in($query_hash) \n| series_fetched:=0\n| count()",
"expr": "\"vm_slow_query_stats\" | extract 'vm_slow_query_stats <vm_slow_query_stats>' | unpack_logfmt from vm_slow_query_stats \n| tenant:in($tenant)\n| query_hash:$query_hash \n| series_fetched:=0\n| count()",
"queryType": "stats",
"refId": "A"
}
@@ -265,7 +322,8 @@
"mode": "absolute",
"steps": [
{
"color": "green"
"color": "green",
"value": 0
}
]
},
@@ -297,7 +355,7 @@
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "11.6.0",
"pluginVersion": "12.4.3",
"targets": [
{
"datasource": {
@@ -305,7 +363,7 @@
"uid": "${ds}"
},
"editorMode": "code",
"expr": "\"\\tvm_slow_query_stats\" | extract 'vm_slow_query_stats <vm_slow_query_stats>' | unpack_logfmt from vm_slow_query_stats \n| tenant:in($tenant)\n| query_hash:in($query_hash) \n| stats min(start_ms)\n",
"expr": "\"vm_slow_query_stats\" | extract 'vm_slow_query_stats <vm_slow_query_stats>' | unpack_logfmt from vm_slow_query_stats \n| tenant:in($tenant)\n| query_hash:$query_hash \n| stats min(start_ms)\n",
"queryType": "stats",
"refId": "A"
}
@@ -327,10 +385,6 @@
"type": "row"
},
{
"fieldConfig": {
"defaults": {},
"overrides": []
},
"gridPos": {
"h": 2,
"w": 24,
@@ -347,8 +401,7 @@
"content": "To filter by specific query copy its hash from the table and put it into `query_hash` filter on the top. To disable filtering enter `*`.",
"mode": "markdown"
},
"pluginVersion": "11.6.0",
"title": "",
"pluginVersion": "12.4.3",
"transparent": true,
"type": "text"
},
@@ -367,6 +420,9 @@
"cellOptions": {
"type": "auto"
},
"footer": {
"reducers": []
},
"inspect": false
},
"mappings": [],
@@ -374,7 +430,8 @@
"mode": "absolute",
"steps": [
{
"color": "green"
"color": "green",
"value": 0
},
{
"color": "red",
@@ -423,18 +480,6 @@
"value": 204
}
]
},
{
"matcher": {
"id": "byName",
"options": "duration_max"
},
"properties": [
{
"id": "custom.width",
"value": 122
}
]
}
]
},
@@ -447,18 +492,10 @@
"id": 4,
"options": {
"cellHeight": "sm",
"footer": {
"countRows": false,
"fields": "",
"reducer": [
"sum"
],
"show": false
},
"showHeader": true,
"sortBy": []
},
"pluginVersion": "11.6.0",
"pluginVersion": "12.4.3",
"targets": [
{
"datasource": {
@@ -466,7 +503,7 @@
"uid": "${ds}"
},
"editorMode": "code",
"expr": "\"\\tvm_slow_query_stats\" | extract 'vm_slow_query_stats <vm_slow_query_stats>' | unpack_logfmt from vm_slow_query_stats \n| tenant:in($tenant)\n| query_hash:in($query_hash)\n| stats by(tenant,query,query_hash) max(execution_duration_ms) duration_max \n| sort by(duration_max) desc | limit $top",
"expr": "\"vm_slow_query_stats\" | extract 'vm_slow_query_stats <vm_slow_query_stats>' | unpack_logfmt from vm_slow_query_stats \n| tenant:in($tenant)\n| query_hash:$query_hash\n| stats by(tenant,query,query_hash) max(execution_duration_ms) duration_max \n| sort by(duration_max) desc | limit $top",
"queryType": "instant",
"refId": "A"
}
@@ -478,7 +515,7 @@
"options": {
"delimiter": ",",
"replace": true,
"source": "Line"
"source": "labels"
}
},
{
@@ -533,6 +570,7 @@
"type": "linear"
},
"showPoints": "auto",
"showValues": false,
"spanNulls": false,
"stacking": {
"group": "A",
@@ -547,7 +585,8 @@
"mode": "absolute",
"steps": [
{
"color": "green"
"color": "green",
"value": 0
},
{
"color": "red",
@@ -580,7 +619,7 @@
"sort": "none"
}
},
"pluginVersion": "11.6.0",
"pluginVersion": "12.4.3",
"targets": [
{
"datasource": {
@@ -588,7 +627,7 @@
"uid": "${ds}"
},
"editorMode": "code",
"expr": "\"\\tvm_slow_query_stats\" | extract 'vm_slow_query_stats <vm_slow_query_stats>' | unpack_logfmt from vm_slow_query_stats \n| tenant:in($tenant)\n| query_hash:in($query_hash)\n| stats max(execution_duration_ms) execution_duration_max",
"expr": "\"vm_slow_query_stats\" | extract 'vm_slow_query_stats <vm_slow_query_stats>' | unpack_logfmt from vm_slow_query_stats \n| tenant:in($tenant)\n| query_hash:$query_hash\n| stats max(execution_duration_ms) execution_duration_max",
"queryType": "statsRange",
"refId": "A"
}
@@ -611,6 +650,9 @@
"cellOptions": {
"type": "auto"
},
"footer": {
"reducers": []
},
"inspect": false
},
"mappings": [],
@@ -618,7 +660,8 @@
"mode": "absolute",
"steps": [
{
"color": "green"
"color": "green",
"value": 0
},
{
"color": "red",
@@ -680,18 +723,10 @@
"interval": "1m",
"options": {
"cellHeight": "sm",
"footer": {
"countRows": false,
"fields": "",
"reducer": [
"sum"
],
"show": false
},
"showHeader": true,
"sortBy": []
},
"pluginVersion": "11.6.0",
"pluginVersion": "12.4.3",
"targets": [
{
"datasource": {
@@ -699,7 +734,7 @@
"uid": "${ds}"
},
"editorMode": "code",
"expr": "\"\\tvm_slow_query_stats\" | extract 'vm_slow_query_stats <vm_slow_query_stats>' | unpack_logfmt from vm_slow_query_stats \n| tenant:in($tenant)\n| query_hash:in($query_hash)\n| stats by(tenant,query,query_hash) max(series_fetched) series_fetched_max\n| sort by(series_fetched_max) desc | limit $top",
"expr": "\"vm_slow_query_stats\" | extract 'vm_slow_query_stats <vm_slow_query_stats>' | unpack_logfmt from vm_slow_query_stats \n| tenant:in($tenant)\n| query_hash:$query_hash\n| stats by(tenant,query,query_hash) max(series_fetched) series_fetched_max\n| sort by(series_fetched_max) desc | limit $top",
"queryType": "instant",
"refId": "A"
}
@@ -711,7 +746,7 @@
"options": {
"delimiter": ",",
"replace": true,
"source": "Line"
"source": "labels"
}
},
{
@@ -766,6 +801,7 @@
"type": "linear"
},
"showPoints": "auto",
"showValues": false,
"spanNulls": false,
"stacking": {
"group": "A",
@@ -780,7 +816,8 @@
"mode": "absolute",
"steps": [
{
"color": "green"
"color": "green",
"value": 0
},
{
"color": "red",
@@ -813,7 +850,7 @@
"sort": "none"
}
},
"pluginVersion": "11.6.0",
"pluginVersion": "12.4.3",
"targets": [
{
"datasource": {
@@ -821,7 +858,7 @@
"uid": "${ds}"
},
"editorMode": "code",
"expr": "\"\\tvm_slow_query_stats\" | extract 'vm_slow_query_stats <vm_slow_query_stats>' | unpack_logfmt from vm_slow_query_stats \n| tenant:in($tenant)\n| query_hash:in($query_hash)\n| stats max(series_fetched) series_fetched_max",
"expr": "\"vm_slow_query_stats\" | extract 'vm_slow_query_stats <vm_slow_query_stats>' | unpack_logfmt from vm_slow_query_stats \n| tenant:in($tenant)\n| query_hash:$query_hash\n| stats max(series_fetched) series_fetched_max",
"queryType": "statsRange",
"refId": "A"
}
@@ -844,6 +881,9 @@
"cellOptions": {
"type": "auto"
},
"footer": {
"reducers": []
},
"inspect": false
},
"mappings": [],
@@ -851,7 +891,8 @@
"mode": "absolute",
"steps": [
{
"color": "green"
"color": "green",
"value": 0
},
{
"color": "red",
@@ -912,18 +953,10 @@
"id": 5,
"options": {
"cellHeight": "sm",
"footer": {
"countRows": false,
"fields": "",
"reducer": [
"sum"
],
"show": false
},
"showHeader": true,
"sortBy": []
},
"pluginVersion": "11.6.0",
"pluginVersion": "12.4.3",
"targets": [
{
"datasource": {
@@ -931,7 +964,7 @@
"uid": "${ds}"
},
"editorMode": "code",
"expr": "\"\\tvm_slow_query_stats\" | extract 'vm_slow_query_stats <vm_slow_query_stats>' | unpack_logfmt from vm_slow_query_stats \n| tenant:in($tenant)\n| query_hash:in($query_hash)\n| stats by(tenant,query,query_hash) max(samples_fetched) samples_fetched_max\n| sort by(samples_fetched_max) desc | limit $top",
"expr": "\"vm_slow_query_stats\" | extract 'vm_slow_query_stats <vm_slow_query_stats>' | unpack_logfmt from vm_slow_query_stats \n| tenant:in($tenant)\n| query_hash:$query_hash\n| stats by(tenant,query,query_hash) max(samples_fetched) samples_fetched_max\n| sort by(samples_fetched_max) desc | limit $top",
"queryType": "instant",
"refId": "A"
}
@@ -942,8 +975,10 @@
"id": "extractFields",
"options": {
"delimiter": ",",
"format": "json",
"keepTime": false,
"replace": true,
"source": "Line"
"source": "labels"
}
},
{
@@ -998,6 +1033,7 @@
"type": "linear"
},
"showPoints": "auto",
"showValues": false,
"spanNulls": false,
"stacking": {
"group": "A",
@@ -1012,7 +1048,8 @@
"mode": "absolute",
"steps": [
{
"color": "green"
"color": "green",
"value": 0
},
{
"color": "red",
@@ -1045,7 +1082,7 @@
"sort": "none"
}
},
"pluginVersion": "11.6.0",
"pluginVersion": "12.4.3",
"targets": [
{
"datasource": {
@@ -1053,7 +1090,7 @@
"uid": "${ds}"
},
"editorMode": "code",
"expr": "\"\\tvm_slow_query_stats\" | extract 'vm_slow_query_stats <vm_slow_query_stats>' | unpack_logfmt from vm_slow_query_stats \n| tenant:in($tenant)\n| query_hash:in($query_hash)\n| stats max(samples_fetched) samples_fetched_max",
"expr": "\"vm_slow_query_stats\" | extract 'vm_slow_query_stats <vm_slow_query_stats>' | unpack_logfmt from vm_slow_query_stats \n| tenant:in($tenant)\n| query_hash:$query_hash\n| stats max(samples_fetched) samples_fetched_max",
"queryType": "statsRange",
"refId": "A"
}
@@ -1076,6 +1113,9 @@
"cellOptions": {
"type": "auto"
},
"footer": {
"reducers": []
},
"inspect": false
},
"mappings": [],
@@ -1083,7 +1123,8 @@
"mode": "absolute",
"steps": [
{
"color": "green"
"color": "green",
"value": 0
},
{
"color": "red",
@@ -1144,18 +1185,10 @@
"id": 11,
"options": {
"cellHeight": "sm",
"footer": {
"countRows": false,
"fields": "",
"reducer": [
"sum"
],
"show": false
},
"showHeader": true,
"sortBy": []
},
"pluginVersion": "11.6.0",
"pluginVersion": "12.4.3",
"targets": [
{
"datasource": {
@@ -1163,7 +1196,7 @@
"uid": "${ds}"
},
"editorMode": "code",
"expr": "\"\\tvm_slow_query_stats\" | extract 'vm_slow_query_stats <vm_slow_query_stats>' | unpack_logfmt from vm_slow_query_stats \n| tenant:in($tenant)\n| query_hash:in($query_hash)\n| stats by(tenant,query,query_hash) max(bytes) bytes_fetched_max \n| sort by(bytes_fetched_max) desc | limit $top",
"expr": "\"vm_slow_query_stats\" | extract 'vm_slow_query_stats <vm_slow_query_stats>' | unpack_logfmt from vm_slow_query_stats \n| tenant:in($tenant)\n| query_hash:$query_hash\n| stats by(tenant,query,query_hash) max(bytes) bytes_fetched_max \n| sort by(bytes_fetched_max) desc | limit $top",
"queryType": "instant",
"refId": "A"
}
@@ -1175,7 +1208,7 @@
"options": {
"delimiter": ",",
"replace": true,
"source": "Line"
"source": "labels"
}
},
{
@@ -1230,6 +1263,7 @@
"type": "linear"
},
"showPoints": "auto",
"showValues": false,
"spanNulls": false,
"stacking": {
"group": "A",
@@ -1244,7 +1278,8 @@
"mode": "absolute",
"steps": [
{
"color": "green"
"color": "green",
"value": 0
},
{
"color": "red",
@@ -1277,7 +1312,7 @@
"sort": "none"
}
},
"pluginVersion": "11.6.0",
"pluginVersion": "12.4.3",
"targets": [
{
"datasource": {
@@ -1285,7 +1320,7 @@
"uid": "${ds}"
},
"editorMode": "code",
"expr": "\"\\tvm_slow_query_stats\" | extract 'vm_slow_query_stats <vm_slow_query_stats>' | unpack_logfmt from vm_slow_query_stats \n| tenant:in($tenant)\n| query_hash:in($query_hash)\n| stats max(bytes) bytes_fetched",
"expr": "\"vm_slow_query_stats\" | extract 'vm_slow_query_stats <vm_slow_query_stats>' | unpack_logfmt from vm_slow_query_stats \n| tenant:in($tenant)\n| query_hash:$query_hash\n| stats max(bytes) bytes_fetched",
"queryType": "statsRange",
"refId": "A"
}
@@ -1308,6 +1343,9 @@
"cellOptions": {
"type": "auto"
},
"footer": {
"reducers": []
},
"inspect": false
},
"mappings": [],
@@ -1315,7 +1353,8 @@
"mode": "absolute",
"steps": [
{
"color": "green"
"color": "green",
"value": 0
},
{
"color": "red",
@@ -1388,18 +1427,10 @@
"id": 13,
"options": {
"cellHeight": "sm",
"footer": {
"countRows": false,
"fields": "",
"reducer": [
"sum"
],
"show": false
},
"showHeader": true,
"sortBy": []
},
"pluginVersion": "11.6.0",
"pluginVersion": "12.4.3",
"targets": [
{
"datasource": {
@@ -1407,7 +1438,7 @@
"uid": "${ds}"
},
"editorMode": "code",
"expr": "\"\\tvm_slow_query_stats\" | extract 'vm_slow_query_stats <vm_slow_query_stats>' | unpack_logfmt from vm_slow_query_stats \n| tenant:in($tenant)\n| query_hash:in($query_hash)\n| stats by(tenant,query,query_hash) max(memory_estimated_bytes) memory_estimated_max\n| sort by(memory_estimated_max) desc | limit $top",
"expr": "\"vm_slow_query_stats\" | extract 'vm_slow_query_stats <vm_slow_query_stats>' | unpack_logfmt from vm_slow_query_stats \n| tenant:in($tenant)\n| query_hash:$query_hash\n| stats by(tenant,query,query_hash) max(memory_estimated_bytes) memory_estimated_max\n| sort by(memory_estimated_max) desc | limit $top",
"queryType": "instant",
"refId": "A"
}
@@ -1419,7 +1450,7 @@
"options": {
"delimiter": ",",
"replace": true,
"source": "Line"
"source": "labels"
}
},
{
@@ -1474,6 +1505,7 @@
"type": "linear"
},
"showPoints": "auto",
"showValues": false,
"spanNulls": false,
"stacking": {
"group": "A",
@@ -1488,7 +1520,8 @@
"mode": "absolute",
"steps": [
{
"color": "green"
"color": "green",
"value": 0
},
{
"color": "red",
@@ -1521,7 +1554,7 @@
"sort": "none"
}
},
"pluginVersion": "11.6.0",
"pluginVersion": "12.4.3",
"targets": [
{
"datasource": {
@@ -1529,7 +1562,7 @@
"uid": "${ds}"
},
"editorMode": "code",
"expr": "\"\\tvm_slow_query_stats\" | extract 'vm_slow_query_stats <vm_slow_query_stats>' | unpack_logfmt from vm_slow_query_stats \n| tenant:in($tenant)\n| query_hash:in($query_hash)\n| stats max(memory_estimated_bytes) memory_estimated_bytes",
"expr": "\"vm_slow_query_stats\" | extract 'vm_slow_query_stats <vm_slow_query_stats>' | unpack_logfmt from vm_slow_query_stats \n| tenant:in($tenant)\n| query_hash:$query_hash\n| stats max(memory_estimated_bytes) memory_estimated_bytes",
"queryType": "statsRange",
"refId": "A"
}
@@ -1552,6 +1585,9 @@
"cellOptions": {
"type": "auto"
},
"footer": {
"reducers": []
},
"inspect": false
},
"mappings": [],
@@ -1559,7 +1595,8 @@
"mode": "absolute",
"steps": [
{
"color": "green"
"color": "green",
"value": 0
},
{
"color": "red",
@@ -1620,18 +1657,10 @@
"id": 18,
"options": {
"cellHeight": "sm",
"footer": {
"countRows": false,
"fields": "",
"reducer": [
"sum"
],
"show": false
},
"showHeader": true,
"sortBy": []
},
"pluginVersion": "11.6.0",
"pluginVersion": "12.4.3",
"targets": [
{
"datasource": {
@@ -1639,7 +1668,7 @@
"uid": "${ds}"
},
"editorMode": "code",
"expr": "\"\\tvm_slow_query_stats\" | extract 'vm_slow_query_stats <vm_slow_query_stats>' | unpack_logfmt from vm_slow_query_stats \n| tenant:in($tenant)\n| query_hash:in($query_hash)\n| stats by(tenant,query,query_hash) max(range_ms) range_max \n| sort by(range_max) desc | limit $top",
"expr": "\"vm_slow_query_stats\" | extract 'vm_slow_query_stats <vm_slow_query_stats>' | unpack_logfmt from vm_slow_query_stats \n| tenant:in($tenant)\n| query_hash:$query_hash\n| stats by(tenant,query,query_hash) max(range_ms) range_max \n| sort by(range_max) desc | limit $top",
"queryType": "instant",
"refId": "A"
}
@@ -1651,7 +1680,7 @@
"options": {
"delimiter": ",",
"replace": true,
"source": "Line"
"source": "labels"
}
},
{
@@ -1706,6 +1735,7 @@
"type": "linear"
},
"showPoints": "auto",
"showValues": false,
"spanNulls": false,
"stacking": {
"group": "A",
@@ -1720,7 +1750,8 @@
"mode": "absolute",
"steps": [
{
"color": "green"
"color": "green",
"value": 0
},
{
"color": "red",
@@ -1753,7 +1784,7 @@
"sort": "none"
}
},
"pluginVersion": "11.6.0",
"pluginVersion": "12.4.3",
"targets": [
{
"datasource": {
@@ -1761,7 +1792,7 @@
"uid": "${ds}"
},
"editorMode": "code",
"expr": "\"\\tvm_slow_query_stats\" | extract 'vm_slow_query_stats <vm_slow_query_stats>' | unpack_logfmt from vm_slow_query_stats \n| query_hash:in($query_hash)\n| stats max(range_ms) range_max",
"expr": "\"vm_slow_query_stats\" | extract 'vm_slow_query_stats <vm_slow_query_stats>' | unpack_logfmt from vm_slow_query_stats \n| query_hash:$query_hash\n| stats max(range_ms) range_max",
"queryType": "statsRange",
"refId": "A"
}
@@ -1770,7 +1801,7 @@
"type": "timeseries"
},
{
"collapsed": false,
"collapsed": true,
"gridPos": {
"h": 1,
"w": 24,
@@ -1778,52 +1809,59 @@
"y": 56
},
"id": 12,
"panels": [],
"title": "Query log",
"type": "row"
},
{
"datasource": {
"type": "victoriametrics-logs-datasource",
"uid": "${ds}"
},
"fieldConfig": {
"defaults": {},
"overrides": []
},
"gridPos": {
"h": 14,
"w": 24,
"x": 0,
"y": 57
},
"id": 6,
"options": {
"dedupStrategy": "none",
"enableInfiniteScrolling": false,
"enableLogDetails": true,
"prettifyLogMessage": false,
"showCommonLabels": false,
"showLabels": false,
"showTime": false,
"sortOrder": "Descending",
"wrapLogMessage": false
},
"pluginVersion": "11.6.0",
"targets": [
"panels": [
{
"datasource": {
"type": "victoriametrics-logs-datasource",
"uid": "${ds}"
},
"editorMode": "code",
"expr": "\"\\tvm_slow_query_stats\" | extract 'vm_slow_query_stats <vm_slow_query_stats>' | unpack_logfmt from vm_slow_query_stats \n| query_hash:in($query_hash)\n| limit 200",
"queryType": "instant",
"refId": "A"
"fieldConfig": {
"defaults": {},
"overrides": []
},
"gridPos": {
"h": 14,
"w": 24,
"x": 0,
"y": 57
},
"id": 6,
"options": {
"dedupStrategy": "none",
"detailsMode": "sidebar",
"enableInfiniteScrolling": false,
"enableLogDetails": true,
"fontSize": "small",
"prettifyLogMessage": false,
"showCommonLabels": false,
"showControls": true,
"showLabels": false,
"showTime": false,
"sortOrder": "Descending",
"syntaxHighlighting": false,
"unwrappedColumns": false,
"wrapLogMessage": true
},
"pluginVersion": "12.4.3",
"targets": [
{
"datasource": {
"type": "victoriametrics-logs-datasource",
"uid": "${ds}"
},
"direction": "desc",
"editorMode": "code",
"expr": "\"vm_slow_query_stats\" | extract 'vm_slow_query_stats <vm_slow_query_stats>' | unpack_logfmt from vm_slow_query_stats \n| query_hash:$query_hash\n| limit 200",
"queryType": "instant",
"refId": "A"
}
],
"title": "Raw logs",
"type": "logs"
}
],
"title": "Raw logs",
"type": "logs"
"title": "Query log",
"type": "row"
},
{
"collapsed": true,
@@ -1831,7 +1869,7 @@
"h": 1,
"w": 24,
"x": 0,
"y": 71
"y": 57
},
"id": 17,
"panels": [
@@ -1848,7 +1886,7 @@
"h": 14,
"w": 24,
"x": 0,
"y": 70
"y": 58
},
"id": 15,
"options": {
@@ -1857,12 +1895,14 @@
"enableLogDetails": true,
"prettifyLogMessage": false,
"showCommonLabels": false,
"showControls": false,
"showLabels": false,
"showTime": false,
"sortOrder": "Descending",
"unwrappedColumns": false,
"wrapLogMessage": false
},
"pluginVersion": "11.6.0",
"pluginVersion": "12.4.3",
"targets": [
{
"datasource": {
@@ -1870,7 +1910,7 @@
"uid": "${ds}"
},
"editorMode": "code",
"expr": "\"\\tvm_slow_query_stats\" | extract 'vm_slow_query_stats <vm_slow_query_stats>' | unpack_logfmt from vm_slow_query_stats \n| series_fetched:=0\n| query_hash:in($query_hash)",
"expr": "\"vm_slow_query_stats\" | extract 'vm_slow_query_stats <vm_slow_query_stats>' | unpack_logfmt from vm_slow_query_stats \n| series_fetched:=0\n| query_hash:$query_hash",
"queryType": "instant",
"refId": "A"
}
@@ -1885,7 +1925,7 @@
],
"preload": false,
"refresh": "",
"schemaVersion": 41,
"schemaVersion": 42,
"tags": [
"victoriametrics",
"victorialogs"
@@ -1894,8 +1934,9 @@
"list": [
{
"current": {
"text": "VictoriaLogs",
"value": "PD775F2863313E6C7"
"text": "",
"value": "${ds}",
"selected": true
},
"name": "ds",
"options": [],
@@ -1933,21 +1974,17 @@
}
],
"query": "5,10,15,20",
"type": "custom"
"type": "custom",
"valuesFormat": "csv"
},
{
"allValue": "*",
"current": {
"text": "All",
"value": [
"$__all"
]
},
"current": {},
"datasource": {
"type": "victoriametrics-logs-datasource",
"uid": "${ds}"
},
"definition": "\"\\tvm_slow_query_stats\" | extract 'vm_slow_query_stats <vm_slow_query_stats>' | unpack_logfmt from vm_slow_query_stats | fields tenant",
"definition": "\"vm_slow_query_stats\" | extract 'vm_slow_query_stats <vm_slow_query_stats>' | unpack_logfmt from vm_slow_query_stats | fields tenant",
"includeAll": true,
"multi": true,
"name": "tenant",
@@ -1955,12 +1992,13 @@
"query": {
"field": "tenant",
"limit": 25,
"query": "\"\\tvm_slow_query_stats\" | extract 'vm_slow_query_stats <vm_slow_query_stats>' | unpack_logfmt from vm_slow_query_stats | fields tenant",
"query": "\"vm_slow_query_stats\" | extract 'vm_slow_query_stats <vm_slow_query_stats>' | unpack_logfmt from vm_slow_query_stats | fields tenant",
"refId": "VictoriaLogsVariableQueryEditor-VariableQuery",
"type": "fieldValue"
},
"refresh": 1,
"refresh": 2,
"regex": "",
"regexApplyTo": "value",
"type": "query"
},
{
@@ -2000,5 +2038,6 @@
"timezone": "browser",
"title": "Query Stats (cluster)",
"uid": "feg3od1zt1fy8e",
"version": 1
}
"version": 1,
"weekStart": ""
}

View File

@@ -896,7 +896,7 @@
"uid": "$ds"
},
"editorMode": "code",
"expr": "sum(min_over_time(vm_app_version{job=~\"$job\", instance=~\"$instance\"}[$__rate_interval])) by (job)",
"expr": "sum(min_over_time(up{job=~\"$job\", instance=~\"$instance\"}[$__rate_interval])) by (job)",
"format": "time_series",
"instant": false,
"legendFormat": "{{job}}",

View File

@@ -897,7 +897,7 @@
"uid": "$ds"
},
"editorMode": "code",
"expr": "sum(min_over_time(vm_app_version{job=~\"$job\", instance=~\"$instance\"}[$__rate_interval])) by (job)",
"expr": "sum(min_over_time(up{job=~\"$job\", instance=~\"$instance\"}[$__rate_interval])) by (job)",
"format": "time_series",
"instant": false,
"legendFormat": "{{job}}",

View File

@@ -892,7 +892,7 @@
"uid": "$ds"
},
"editorMode": "code",
"expr": "sum(up{job=~\"$job\", instance=~\"$instance\"}) by (job)",
"expr": "sum(min_over_time(up{job=~\"$job\", instance=~\"$instance\"}[$__rate_interval])) by (job)",
"format": "time_series",
"instant": false,
"interval": "",

View File

@@ -891,7 +891,7 @@
"uid": "$ds"
},
"editorMode": "code",
"expr": "sum(up{job=~\"$job\", instance=~\"$instance\"}) by (job)",
"expr": "sum(min_over_time(up{job=~\"$job\", instance=~\"$instance\"}[$__rate_interval])) by (job)",
"format": "time_series",
"instant": false,
"interval": "",

View File

@@ -3,11 +3,11 @@
DOCKER_REGISTRIES ?= docker.io quay.io
DOCKER_NAMESPACE ?= victoriametrics
ROOT_IMAGE ?= alpine:3.23.4
ROOT_IMAGE ?= alpine:3.24.1
ROOT_IMAGE_SCRATCH ?= scratch
CERTS_IMAGE := alpine:3.23.4
CERTS_IMAGE := alpine:3.24.1
GO_BUILDER_IMAGE := golang:1.26.4
GO_BUILDER_IMAGE := golang:1.26.5
BUILDER_IMAGE := local/builder:2.0.0-$(shell echo $(GO_BUILDER_IMAGE) | tr :/ __)-1
BASE_IMAGE := local/base:1.1.4-$(shell echo $(ROOT_IMAGE) | tr :/ __)-$(shell echo $(CERTS_IMAGE) | tr :/ __)

View File

@@ -3,7 +3,7 @@ services:
# It scrapes targets defined in --promscrape.config
# And forward them to --remoteWrite.url
vmagent:
image: victoriametrics/vmagent:v1.146.0
image: victoriametrics/vmagent:v1.148.0
depends_on:
- "vmauth"
ports:
@@ -42,14 +42,14 @@ services:
# vmstorage shards. Each shard receives 1/N of all metrics sent to vminserts,
# where N is number of vmstorages (2 in this case).
vmstorage-1:
image: victoriametrics/vmstorage:v1.146.0-cluster
image: victoriametrics/vmstorage:v1.148.0-cluster
volumes:
- strgdata-1:/storage
command:
- "--storageDataPath=/storage"
restart: always
vmstorage-2:
image: victoriametrics/vmstorage:v1.146.0-cluster
image: victoriametrics/vmstorage:v1.148.0-cluster
volumes:
- strgdata-2:/storage
command:
@@ -59,7 +59,7 @@ services:
# vminsert is ingestion frontend. It receives metrics pushed by vmagent,
# pre-process them and distributes across configured vmstorage shards.
vminsert-1:
image: victoriametrics/vminsert:v1.146.0-cluster
image: victoriametrics/vminsert:v1.148.0-cluster
depends_on:
- "vmstorage-1"
- "vmstorage-2"
@@ -68,7 +68,7 @@ services:
- "--storageNode=vmstorage-2:8400"
restart: always
vminsert-2:
image: victoriametrics/vminsert:v1.146.0-cluster
image: victoriametrics/vminsert:v1.148.0-cluster
depends_on:
- "vmstorage-1"
- "vmstorage-2"
@@ -80,7 +80,7 @@ services:
# vmselect is a query fronted. It serves read queries in MetricsQL or PromQL.
# vmselect collects results from configured `--storageNode` shards.
vmselect-1:
image: victoriametrics/vmselect:v1.146.0-cluster
image: victoriametrics/vmselect:v1.148.0-cluster
depends_on:
- "vmstorage-1"
- "vmstorage-2"
@@ -90,7 +90,7 @@ services:
- "--vmalert.proxyURL=http://vmalert:8880"
restart: always
vmselect-2:
image: victoriametrics/vmselect:v1.146.0-cluster
image: victoriametrics/vmselect:v1.148.0-cluster
depends_on:
- "vmstorage-1"
- "vmstorage-2"
@@ -105,7 +105,7 @@ services:
# read requests from Grafana, vmui, vmalert among vmselects.
# It can be used as an authentication proxy.
vmauth:
image: victoriametrics/vmauth:v1.146.0
image: victoriametrics/vmauth:v1.148.0
depends_on:
- "vmselect-1"
- "vmselect-2"
@@ -119,7 +119,7 @@ services:
# vmalert executes alerting and recording rules
vmalert:
image: victoriametrics/vmalert:v1.146.0
image: victoriametrics/vmalert:v1.148.0
depends_on:
- "vmauth"
ports:

View File

@@ -3,7 +3,7 @@ services:
# It scrapes targets defined in --promscrape.config
# And forward them to --remoteWrite.url
vmagent:
image: victoriametrics/vmagent:v1.146.0
image: victoriametrics/vmagent:v1.148.0
depends_on:
- "victoriametrics"
ports:
@@ -18,7 +18,7 @@ services:
# VictoriaMetrics instance, a single process responsible for
# storing metrics and serve read requests.
victoriametrics:
image: victoriametrics/victoria-metrics:v1.146.0
image: victoriametrics/victoria-metrics:v1.148.0
ports:
- 8428:8428
- 8089:8089
@@ -59,7 +59,7 @@ services:
# vmalert executes alerting and recording rules
vmalert:
image: victoriametrics/vmalert:v1.146.0
image: victoriametrics/vmalert:v1.148.0
depends_on:
- "victoriametrics"
- "alertmanager"

View File

@@ -10,7 +10,7 @@ groups:
concurrency: 2
rules:
- alert: PersistentQueueIsDroppingData
expr: sum(increase(vm_persistentqueue_bytes_dropped_total[5m])) without (path) > 0
expr: sum(increase(vm_persistentqueue_bytes_dropped_total[5m])) without (path,name) > 0
for: 10m
labels:
severity: critical
@@ -201,4 +201,4 @@ groups:
annotations:
summary: "Persistent Queue (url {{ $labels.url }}) of {{ $labels.instance }} (job:{{ $labels.job }}) will run out of space in 4 hours."
description: "RemoteWrite destination ({{ $labels.url }}) is unavailable or unable to receive data in a timely manner, so the persistent queue size is growing.
Once the available space is exhausted, some samples will be discarded and cause incident. Please check the health of remoteWrite destination ({{ $labels.url }})."
Once the available space is exhausted, some samples will be discarded and cause incident. Please check the health of remoteWrite destination ({{ $labels.url }})."

View File

@@ -120,3 +120,39 @@ groups:
summary: "vmalert instance {{ $labels.instance }} is failing to send notifications to Alertmanager"
description: "vmalert instance {{ $labels.instance }} is failing to send alert notifications to \"{{ $labels.addr }}\".
Check vmalert's logs for detailed error message."
- alert: AlertingRuleResultsApproachingLimit
expr: |
(
vmalert_alerting_rules_last_evaluation_samples
> on(group,file) group_left()
(vmalert_group_rule_results_limit * 0.9)
)
and on(group,file)
(vmalert_group_rule_results_limit > 0)
for: 5m
labels:
severity: warning
annotations:
summary: "Alerting rule {{ $labels.alertname }} in group {{ $labels.group }} is approaching the configured results limit"
description: "Alerting rule \"{{ $labels.alertname }}\" from group \"{{ $labels.group }}\" in file \"{{ $labels.file }}\" produced {{ $value }} samples in last evaluation, which approaches the configured results limit.
If the produced results exceed the limit, the rule will be marked with an error and all its results will be discarded.
Try increasing the results limit for the group or reducing the number of series produced by the rule. See https://docs.victoriametrics.com/victoriametrics/vmalert/#groups."
- alert: RecordingRuleResultsApproachingLimit
expr: |
(
vmalert_recording_rules_last_evaluation_samples
> on(group,file) group_left()
(vmalert_group_rule_results_limit * 0.9)
)
and on(group,file)
(vmalert_group_rule_results_limit > 0)
for: 5m
labels:
severity: warning
annotations:
summary: "Recording rule {{ $labels.recording }} in group {{ $labels.group }} is approaching the configured results limit"
description: "Recording rule \"{{ $labels.recording }}\" from group \"{{ $labels.group }}\" in file \"{{ $labels.file }}\" produced {{ $value }} samples in last evaluation, which approaches the configured results limit.
If the produced results exceed the limit, the rule will be marked with an error and all its results will be discarded.
Try increasing the results limit for the group or reducing the number of series produced by the rule. See https://docs.victoriametrics.com/victoriametrics/vmalert/#groups."

View File

@@ -56,3 +56,20 @@ groups:
summary: "Too many errors served for user {{ $labels.username }} (instance {{ $labels.instance }})"
description: "Requests from user {{ $labels.username }} are receiving errors.
Please check the vmauth logs to verify that the configuration is correct and clients are sending valid requests."
- alert: InvalidAuthTokenRequestErrors
expr: sum(increase(vmauth_http_request_errors_total{reason="invalid_auth_token"}[5m])) without (instance, reason) > 0
for: 15m
labels:
severity: warning
annotations:
dashboard: "{{ $externalURL }}/d/nbuo5Mr4k?viewPanel=16&var-job={{ $labels.job }}"
summary: "vmauth {{ $labels.job }} is receiving many requests with invalid auth tokens"
description: |
vmauth {{ $labels.job }} received {{ $value }} requests with invalid auth tokens in the last 5 minutes.
This may indicate:
- credentials have been updated on vmauth but not on clients
- client misconfiguration or use of an expired token
- a brute-force attack.
Check vmauth metrics for longevity and scale of the issue.
Check access log for detailed information: https://docs.victoriametrics.com/victoriametrics/vmauth/#access-log

View File

@@ -1,6 +1,6 @@
services:
vmagent:
image: victoriametrics/vmagent:v1.146.0
image: victoriametrics/vmagent:v1.148.0
depends_on:
- "victoriametrics"
ports:
@@ -14,7 +14,7 @@ services:
restart: always
victoriametrics:
image: victoriametrics/victoria-metrics:v1.146.0
image: victoriametrics/victoria-metrics:v1.148.0
ports:
- 8428:8428
volumes:
@@ -40,7 +40,7 @@ services:
restart: always
vmalert:
image: victoriametrics/vmalert:v1.146.0
image: victoriametrics/vmalert:v1.148.0
depends_on:
- "victoriametrics"
ports:

View File

@@ -283,7 +283,7 @@ Released: 2025-06-13
## v1.23.2
Released: 2025-06-09
- IMPROVEMENT: Increased convergence speed for [OnlineZScoreModel](https://docs.victoriametrics.com/anomaly-detection/components/models/#online-z-score), [ZScoreModel](https://docs.victoriametrics.com/anomaly-detection/components/models/#z-score), [MADModel](https://docs.victoriametrics.com/anomaly-detection/components/models/#mad), and [OnlineMADModel](https://docs.victoriametrics.com/anomaly-detection/components/models/#online-mad) models. Now it works better for tight optimization budgets (n_trials < 10, timeout < 1s)
- IMPROVEMENT: Increased convergence speed for [OnlineZScoreModel](https://docs.victoriametrics.com/anomaly-detection/components/models/#online-z-score), [ZScoreModel](https://docs.victoriametrics.com/anomaly-detection/components/models/#z-score), [MADModel](https://docs.victoriametrics.com/anomaly-detection/components/models/#mad-median-absolute-deviation), and [OnlineMADModel](https://docs.victoriametrics.com/anomaly-detection/components/models/#online-mad) models. Now it works better for tight optimization budgets (n_trials < 10, timeout < 1s)
- BUGFIX: Now mean and variance of [OnlineZScoreModel](https://docs.victoriametrics.com/anomaly-detection/components/models/#online-z-score) with exponential `decay` < 1 [arg](https://docs.victoriametrics.com/anomaly-detection/components/models/#decay) are properly calculated for unbiased predictions.
@@ -538,7 +538,7 @@ Released: 2024-10-01
> A bug was discovered in this release that causes the service to crash. Please use the patch [v1.16.1](#v1161) to resolve this issue.
- FEATURE: Introduced data dumps to a host filesystem for [VmReader](https://docs.victoriametrics.com/anomaly-detection/#vm-reader). Resource-intensive setups (multiple queries returning many metrics, bigger `fit_window` arg) will have RAM consumption reduced during fit calls.
- FEATURE: Introduced data dumps to a host filesystem for [VmReader](https://docs.victoriametrics.com/anomaly-detection/components/reader/#vm-reader). Resource-intensive setups (multiple queries returning many metrics, bigger `fit_window` arg) will have RAM consumption reduced during fit calls.
- IMPROVEMENT: Added a `groupby` argument for logical grouping in [multivariate models](https://docs.victoriametrics.com/anomaly-detection/components/models/#multivariate-models). When specified, a separate multivariate model is trained for each unique combination of label values in the `groupby` columns. For example, to perform multivariate anomaly detection on metrics at the machine level without cross-entity interference, you can use `groupby: [host]` or `groupby: [instance]`, ensuring one model per entity being trained (e.g., per host). Please find more details [here](https://docs.victoriametrics.com/anomaly-detection/components/models/#group-by).
- IMPROVEMENT: Improved performance of [VmReader](https://docs.victoriametrics.com/anomaly-detection/components/reader/#vm-reader) on multicore instances for reading and data processing.
- IMPROVEMENT: Introduced new CLI argument aliases to enhance compatibility with [Helm charts](https://github.com/VictoriaMetrics/helm-charts/blob/master/charts/victoria-metrics-anomaly/README.md) (i.e. using secrets) and better align with [VictoriaMetrics flags](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#list-of-command-line-flags):
@@ -685,7 +685,7 @@ Released: 2024-02-15
## v1.9.2
Released: 2024-01-29
- BUGFIX: now multivariate models (like [`IsolationForestMultivariateModel`](https://docs.victoriametrics.com/anomaly-detection/components/models/#isolation-foresthttpsenwikipediaorgwikiisolation_forest-multivariate)) are properly handled throughout fit/infer phases.
- BUGFIX: now multivariate models (like [`IsolationForestMultivariateModel`](https://docs.victoriametrics.com/anomaly-detection/components/models/#isolation-forest-multivariate)) are properly handled throughout fit/infer phases.
## v1.9.1

View File

@@ -553,7 +553,7 @@ preset: ui
# other optional server/settings parameters, e.g. port, max_concurrent_tasks, n_workers, logger_levels, etc.
```
using one of the [deployment methods](https://docs.victoriametrics.com/anomaly-detection/quickstart/#how-to-install-and-run-vmanomaly) in a [QuickStart guide](https://docs.victoriametrics.com/anomaly-detection/quickstart/#quickstart), e.g. via Docker.
using one of the [deployment methods](https://docs.victoriametrics.com/anomaly-detection/quickstart/#how-to-install-and-run-vmanomaly) in a [QuickStart guide](https://docs.victoriametrics.com/anomaly-detection/quickstart/), e.g. via Docker.
Retrieve the UI at `http://<vmanomaly-host>:<port>` (e.g. at `http://localhost:8490` if running locally with default port) and start exploring anomaly detection models and their configurations interactively.

View File

@@ -32,7 +32,7 @@ Server component of VictoriaMetrics Anomaly Detection (`vmanomaly`) is responsib
### Example Configuration
> [!TIP]
> If [hot-reloading](https://docs.victoriametrics.com/anomaly-detection/components/scheduler/#hot-reloading) is enabled in vmanomaly service, the server will automatically pick up changes made to the configuration file without requiring a restart.
> If [hot-reloading](https://docs.victoriametrics.com/anomaly-detection/components/#hot-reload) is enabled in vmanomaly service, the server will automatically pick up changes made to the configuration file without requiring a restart.
```yaml
server:
@@ -63,4 +63,4 @@ reader:
After starting the `vmanomaly` server with the above configuration, UI can be accessed at `<vmanomaly-host>:8490/vmanomaly/vmui/` (e.g. `http://localhost:8490/vmanomaly/vmui/`).
Rest API endpoints (e.g. `/metrics`) can be accessed at `<vmanomaly-host>:8490/vmanomaly/metrics` (e.g. `http://localhost:8490/vmanomaly/metrics`).
Rest API endpoints (e.g. `/metrics`) can be accessed at `<vmanomaly-host>:8490/vmanomaly/metrics` (e.g. `http://localhost:8490/vmanomaly/metrics`).

View File

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

View File

@@ -0,0 +1,426 @@
Several VictoriaMetrics components can connect to cloud storage to read or write object data.
The following table shows the supported types of storage for each component:
| Component | AWS S3 and S3-compatible | Google Cloud Storage | Azure Blob Storage |
|-----------|----|----------------------|--------------------|
| [vmbackup](https://docs.victoriametrics.com/victoriametrics/vmbackup/) | ✅ | ✅ | ✅ |
| [vmrestore](https://docs.victoriametrics.com/victoriametrics/vmrestore/) | ✅ | ✅ | ✅ |
| [vmbackupmanager](https://docs.victoriametrics.com/victoriametrics/vmbackupmanager/) | ✅ | ✅ | ✅ |
| [vmalert](https://docs.victoriametrics.com/victoriametrics/vmalert/) | ✅ | ✅ | ❌ |
All these components use the same underlying libraries, so the authentication setup is largely the same. The main difference is in the command-line flags:
- vmalert uses `-s3.*` prefixed flags (e.g., `-s3.credsFilePath`)
- backup and restore tools use unprefixed flags (e.g., `-credsFilePath`)
See the [component reference](https://docs.victoriametrics.com/guides/connecting-vm-components-to-cloud-storage/#per-component-flag-reference) for details.
## Obtaining credentials
You need to supply credentials so the component can connect to the cloud storage. The setup differs by provider; the sections below cover AWS S3, S3compatible systems, GCS, and Azure Blob Storage.
### AWS S3
1. In AWS, [create an IAM user](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_users_create.html) or role with the minimum permissions for the target bucket (see table below).
1. [Create an access key](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html) for that IAM identity.
1. Copy the **Access key ID** and **Secret access key** values. You will use them in the credentials file or environment variables.
| Component | S3 usage | Minimum S3 permissions |
|-----------------|-------------------------------------------------------------|------------------------|
| vmalert (Enterprise) | Reads alerting/recording rules from bucket. | `s3:GetObject`, `s3:ListBucket` on the rules bucket/prefix.|
| vmrestore | Restores backups from S3 buckets | `s3:GetObject`, `s3:ListBucket` on the backup bucket/prefix.|
| vmbackup | Uploads backups to S3 and may delete old backup objects during maintenance.| `s3:PutObject`, `s3:GetObject`, `s3:ListBucket`, `s3:DeleteObject` on the backup bucket/prefix.|
| vmbackupmanager (Enterprise) | Automates backups using vmbackup behavior. | Same as vmbackup. |
### S3-compatible storage (MinIO, Ceph)
Generate access keys using your storage system's admin interface or CLI. The credentials follow the same format as AWS S3.
### Google Cloud Storage
1. Open the Google Cloud Console and go to **IAM & Admin > Service Accounts**.
1. Click **Create service account**, enter a name, and assign a Storage role (for example, Storage Object Admin).
1. Open the service account, go to **Keys**, then click **Add key > Create new key**.
1. Choose **JSON** as the key type and click **Create**.
1. Store the JSON file on the machine running the VictoriaMetrics component.
### Azure Blob Storage
> Azure does not use credential files.
1. Log in to the Azure Portal.
1. Search for and select **Storage accounts**.
1. Click on your specific storage account name. (This is your `AZURE_STORAGE_ACCOUNT_NAME`).
1. In the left menu under **Security + networking**, click **Access keys**.
1. Copy the key value from either **key1** or **key2** (this is your `AZURE_STORAGE_ACCOUNT_KEY`).
1. Define the access keys as environment variables.
```sh
export AZURE_STORAGE_ACCOUNT_NAME=mystorageaccount
export AZURE_STORAGE_ACCOUNT_KEY=myaccountkey
```
## Authenticating with the cloud provider
Provide the credentials as a file or with environment variables, along with the path to the cloud storage bucket. The syntax for the bucket name depends on the cloud provider:
- `s3://`: for AWS S3 and self-hosted S3-compatible storage (MinIO, Ceph)
- `gs://`: Google Cloud Storage
- `azblob://`: Azure Blob Storage
### vmbackup and vmrestore
The following example backups to an AWS S3 bucket using a credentials file:
```sh
vmbackup \
-storageDataPath=/data \
-snapshot.createURL=http://localhost:8428/snapshot/create \
-dst=s3://victoriametrics-backup/backup01 \
-credsFilePath=/etc/credentials
```
In order to restore from the same backup from AWS S3:
```sh
vmrestore \
-src=s3://victoriametrics-backup/backup01 \
-storageDataPath=/data \
-credsFilePath=/etc/credentials
```
> To use non-AWS S3 buckets such as MinIO or Ceph, you must [supply the `-customS3Endpoint` argument](https://docs.victoriametrics.com/guides/connecting-vm-components-to-cloud-storage/#s3-compatible).
Alternatively, you can set the access keys as environment variables instead of using a credential file:
```sh
export AWS_ACCESS_KEY_ID=YOUR_AWS_ACCESS_KEY
export AWS_SECRET_ACCESS_KEY=YOUR_SECRET_AWS_ACCESS_KEY
vmbackup \
-storageDataPath=/data \
-snapshot.createURL=http://localhost:8428/snapshot/create \
-dst=s3://victoriametrics-backup/backup01
vmrestore \
-src=s3://victoriametrics-backup/backup01 \
-storageDataPath=/data
```
Backups on Google Cloud Storage use the `gs://` prefix in the destination:
```sh
vmbackup \
-storageDataPath=/data \
-snapshot.createURL=http://localhost:8428/snapshot/create \
-dst=gs://victoriametrics-backup/backup01 \
-credsFilePath=/etc/credentials
```
You can restore this backup with:
```sh
vmrestore \
-src=gs://victoriametrics-backup/backup01 \
-storageDataPath=/data \
-credsFilePath=/etc/credentials
```
On Google Cloud, you can define the path to the JSON credential file with `GOOGLE_APPLICATION_CREDENTIALS`. For example:
```sh
export GOOGLE_APPLICATION_CREDENTIALS=/etc/credentials
vmbackup \
-storageDataPath=/data \
-snapshot.createURL=http://localhost:8428/snapshot/create \
-dst=gs://victoriametrics-backup/backup01
vmrestore \
-src=gs://victoriametrics-backup/backup01 \
-storageDataPath=/data
```
For Azure Blob Storage, use the `azblob://` prefix and rely on environment variables instead of `-credsFilePath`.
```sh
export AZURE_STORAGE_ACCOUNT_NAME=myaccount
export AZURE_STORAGE_ACCOUNT_KEY=mykey
vmbackup \
-storageDataPath=/data \
-snapshot.createURL=http://localhost:8428/snapshot/create \
-dst=azblob://victoriametrics-backup/backup01
vmrestore \
-src=azblob://victoriametrics-backup/backup01 \
-storageDataPath=/data
```
### vmbackupmanager
> vmbackupmanager only works in the [Enterprise](https://docs.victoriametrics.com/victoriametrics/enterprise/) edition.
To manage backups with vmbackupmanager on AWS S3, add the credentials with the `-credsFilePath` flag:
```sh
vmbackupmanager \
-dst=s3://vmstorage-data/backups \
-credsFilePath=/etc/credentials \
-storageDataPath=/vmstorage-data \
-snapshot.createURL=http://vmstorage:8482/snapshot/create \
-licenseFile=/etc/vm-license
```
Or define the access keys using environment variables:
```sh
export AWS_ACCESS_KEY_ID=YOUR_AWS_ACCESS_KEY
export AWS_SECRET_ACCESS_KEY=YOUR_SECRET_AWS_ACCESS_KEY
vmbackupmanager \
-dst=s3://vmstorage-data/backups \
-storageDataPath=/vmstorage-data \
-snapshot.createURL=http://vmstorage:8482/snapshot/create \
-licenseFile=/etc/vm-license
```
> To use non-AWS S3 buckets, you must [supply the `-customS3Endpoint` argument](#s3-compatible).
Automated backups on Google Cloud Storage take the following form:
```sh
vmbackupmanager \
-dst=gs://vmstorage-data/backups \
-credsFilePath=/etc/credentials \
-storageDataPath=/vmstorage-data \
-snapshot.createURL=http://vmstorage:8482/snapshot/create \
-licenseFile=/etc/vm-license
```
As with vmbackup and vmrestore, you can also define the path to the JSON credential file with `GOOGLE_APPLICATION_CREDENTIALS`:
```sh
export GOOGLE_APPLICATION_CREDENTIALS=/etc/credentials
vmbackupmanager \
-dst=gs://vmstorage-data/backups \
-storageDataPath=/vmstorage-data \
-snapshot.createURL=http://vmstorage:8482/snapshot/create \
-licenseFile=/etc/vm-license
```
vmbackupmanager can also use Azure Blob Storage by defining environment variables:
```sh
export AZURE_STORAGE_ACCOUNT_NAME=mystorageaccount
export AZURE_STORAGE_ACCOUNT_KEY=myaccountkey
vmbackupmanager \
-dst=azblob://vmstorage-data/backups \
-storageDataPath=/vmstorage-data \
-snapshot.createURL=http://vmstorage:8482/snapshot/create \
-licenseFile=/etc/vm-license
```
### vmalert
> - vmalert cloud storage command line flags are prefixed with `-s3.` for S3 buckets *and* Google Cloud Storage.
> - Cloud storage only works in the [Enterprise](https://docs.victoriametrics.com/victoriametrics/enterprise/) edition.
Read alerting rules from an S3 bucket. The `-rule` flag accepts a prefix, so it matches all files starting with `alerts_` in the `rules` folder:
```sh
vmalert \
-rule=s3://my-alert-bucket/rules/alerts_ \
-s3.credsFilePath=/etc/vmalert/aws-credentials \
-datasource.url=http://vmselect:8481/select/0/prometheus \
-notifier.url=http://alertmanager:9093 \
-licenseFile=/etc/vm-license
```
Instead of a credential file, you can supply the access keys using environment variables:
```sh
export AWS_ACCESS_KEY_ID=YOUR_AWS_ACCESS_KEY
export AWS_SECRET_ACCESS_KEY=YOUR_SECRET_AWS_ACCESS_KEY
vmalert \
-rule=s3://my-alert-bucket/rules/alerts_ \
-datasource.url=http://vmselect:8481/select/0/prometheus \
-notifier.url=http://alertmanager:9093 \
-licenseFile=/etc/vm-license
```
> To use non-AWS S3 buckets, you must [supply the `-s3.customEndpoint` argument](#s3-compatible).
To read rules from Google Cloud Storage:
```sh
vmalert \
-rule=gs://my-alert-bucket/rules/alerts_ \
-s3.credsFilePath=/etc/vmalert/gcp-service-account.json \
-datasource.url=http://vmselect:8481/select/0/prometheus \
-notifier.url=http://alertmanager:9093 \
-licenseFile=/etc/vm-license
```
If you prefer, you can supply the path to the JSON credential file with the `GOOGLE_APPLICATION_CREDENTIALS` environment variable:
```sh
export GOOGLE_APPLICATION_CREDENTIALS=/etc/credentials
vmalert \
-rule=gs://my-alert-bucket/rules/alerts_ \
-datasource.url=http://vmselect:8481/select/0/prometheus \
-notifier.url=http://alertmanager:9093 \
-licenseFile=/etc/vm-license
```
## Credentials files format
The file format depends on the storage provider.
### S3 credentials
The file uses the standard AWS shared credentials format used by the [AWS CLI](https://docs.aws.amazon.com/cli/v1/userguide/cli-configure-files.html) and [AWS SDKs](https://docs.aws.amazon.com/sdkref/latest/guide/file-format.html):
```ini
[default]
aws_access_key_id=YOUR_AWS_ACCESS_KEY
aws_secret_access_key=YOUR_AWS_SECRET_ACCESS_KEY
```
You can define multiple profiles in a single file:
```ini
[default]
aws_access_key_id=DEFAULT_ACCESS_KEY
aws_secret_access_key=DEFAULT_SECRET_KEY
[monitoring]
aws_access_key_id=MONITORING_ACCESS_KEY
aws_secret_access_key=MONITORING_SECRET_KEY
[alerts]
aws_access_key_id=ALERTS_ACCESS_KEY
aws_secret_access_key=ALERTS_SECRET_KEY
```
Use the `-configProfile` flag (or `-s3.configProfile` in vmalert) to select a non-default profile:
```sh
-configProfile=alerts
```
You can separate credentials from other configuration settings. Put credentials in one file:
```ini
[default]
aws_access_key_id=DEFAULT_ACCESS_KEY
aws_secret_access_key=DEFAULT_SECRET_KEY
```
And non-sensitive settings in another:
```ini
[default]
region=us-east-1
```
Then pass both files:
```sh
-configFilePath=/etc/aws-config \
-credsFilePath=/etc/credentials
```
### GCS credentials file format
The file is the JSON key downloaded from Google Cloud Console. Its content looks like this:
```json
{
"type": "service_account",
"project_id": "project-id",
"private_key_id": "key-id",
"private_key": "-----BEGIN PRIVATE KEY-----\nprivate-key\n-----END PRIVATE KEY-----\n",
"client_email": "service-account-email",
"client_id": "client-id",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://accounts.google.com/o/oauth2/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/service-account-email"
}
```
This is the standard service account key format defined by [Google Cloud IAM](https://developers.google.com/workspace/guides/create-credentials).
### Azure Blob Storage
Azure does not support credentials via file. Use environment variables instead.
```sh
export AZURE_STORAGE_ACCOUNT_NAME=mystorageaccount
export AZURE_STORAGE_ACCOUNT_KEY=myaccountkey
```
## Self-hosted and S3-compatible endpoints
For S3-compatible storage such as MinIO or Ceph, set a custom endpoint with the `-customS3Endpoint` flag for vmbackup, vmrestore, and vmbackupmanager. For example:
```sh
vmbackup \
-storageDataPath=/data \
-snapshot.createURL=http://localhost:8428/snapshot/create \
-dst=s3://victoriametrics-backup/backup01 \
-customS3Endpoint=http://minio.example.local:9000
```
On vmalert, use the `-s3.customEndpoint` flag instead:
```sh
vmalert \
-rule=s3://my-alert-bucket/rules/alerts_ \
-s3.customEndpoint=http://minio.example.local:9000 \
-s3.credsFilePath=/etc/vmalert/aws-credentials \
-datasource.url=http://vmselect:8481/select/0/prometheus \
-notifier.url=http://alertmanager:9093 \
-licenseFile=/etc/vm-license
```
### Addressing S3-compatible buckets {#s3-compatible}
When connecting to non-AWS S3-compatible buckets, there is an additional flag you might need to configure:
- `-s3ForcePathStyle`: on vmbackupmanager, vmbackup, and vmrestore.
- `-s3.forcePathStyle`: on vmalert.
The flag changes the expected URL pattern for a bucket.
| Flag value | Address-style | Example | Use with |
|------------|---------------|---------|----------|
| `true` (default) | Path-style | `https://endpoint/bucket/key` | MinIO, Ceph, most S3-compatible storages |
| `false` | Virtual host-style | `https://bucket.endpoint/key` | [Aliyun OSS](https://www.aliyun.com/product/oss) and other endpoints that require it |
> The flag only takes effect when you use a custom endpoint (`-customS3Endpoint` or `-s3.customEndpoint` on vmalert). When connecting to real AWS S3, the SDK handles addressing automatically.
## Per-component flag reference
The table below shows how the same concept maps to different flag names across components.
| Concept | vmalert | vmbackup, vmrestore, and vmbackupmanager |
|---|---|---|---|---|
| Credentials file | `-s3.credsFilePath` | `-credsFilePath` |
| Config file | `-s3.configFilePath` | `-configFilePath` |
| Profile selection | `-s3.configProfile` | `-configProfile` |
| Custom endpoint | `-s3.customEndpoint` | `-customS3Endpoint` |
| Force path style | `-s3.forcePathStyle` | `-s3ForcePathStyle` |
| TLS insecure | N/A | `-s3TLSInsecureSkipVerify` |
| Storage class | N/A | `-s3StorageClass` |

View File

@@ -0,0 +1,12 @@
---
weight: 5
title: Connecting VictoriaMetrics components to cloud storage
menu:
docs:
parent: guides
weight: 5
tags:
- metrics
- guide
---
{{% content "README.md" %}}

View File

@@ -240,23 +240,23 @@ vmagent will write data into VictoriaMetrics single-node and cluster (with tenan
# compose.yaml
services:
vmsingle:
image: victoriametrics/victoria-metrics:v1.146.0
image: victoriametrics/victoria-metrics:v1.148.0
vmstorage:
image: victoriametrics/vmstorage:v1.146.0-cluster
image: victoriametrics/vmstorage:v1.148.0-cluster
vminsert:
image: victoriametrics/vminsert:v1.146.0-cluster
image: victoriametrics/vminsert:v1.148.0-cluster
command:
- -storageNode=vmstorage:8400
vmselect:
image: victoriametrics/vmselect:v1.146.0-cluster
image: victoriametrics/vmselect:v1.148.0-cluster
command:
- -storageNode=vmstorage:8401
vmagent:
image: victoriametrics/vmagent:v1.146.0
image: victoriametrics/vmagent:v1.148.0
volumes:
- ./scrape.yaml:/etc/vmagent/config.yaml
command:
@@ -308,7 +308,7 @@ Now add the vmauth service to `compose.yaml`:
# compose.yaml
services:
vmauth:
image: docker.io/victoriametrics/vmauth:v1.146.0
image: docker.io/victoriametrics/vmauth:v1.148.0
ports:
- 8427:8427
volumes:

View File

@@ -39,7 +39,7 @@ Each subsequent section of this guide presents an architecture designed to handl
### The decision tree
<p align="center">
<img src="decision-tree.webp" alt="Decision Tree" width="80%">
<img src="/guides/vm-architectures/decision-tree.webp" alt="Decision Tree" width="80%">
</p>
## Basic
@@ -62,7 +62,7 @@ Installation guide reference: [VictoriaMetrics Single](https://docs.victoriametr
**Schema:**
<p align="center">
<img src="basic-architecture.webp" alt="Basic Architecture" width="40%">
<img src="/guides/vm-architectures/basic-architecture.webp" alt="Basic Architecture" width="40%">
</p>
### Unavailability Scenarios
@@ -93,7 +93,7 @@ High availability implementation: [HA VictoriaMetrics Cluster](https://docs.vict
**Schema:**
<p align="center">
<img src="single-az-architecture.webp" alt="Single AZ Architecture" width="60%">
<img src="/guides/vm-architectures/single-az-architecture.webp" alt="Single AZ Architecture" width="60%">
</p>
### Application vs. Storage Replication
@@ -210,7 +210,7 @@ To ensure reliability, vmagent implements the bulkhead pattern: each destination
**Schema:**
<p align="center">
<img src="multi-az-architecture.webp" alt="Multi-AZ Architecture" width="65%">
<img src="/guides/vm-architectures/multi-az-architecture.webp" alt="Multi-AZ Architecture" width="65%">
</p>
### Unavailability Scenarios
@@ -257,7 +257,7 @@ For complete disaster recovery, this entire cell-based architecture is duplicate
A global, stateless layer of routing cells (vmagent, vmauth) sits on top. It routes traffic to several logical groups of storage cells. Each storage group contains multiple AZs, and data is replicated or sharded across them. There are several approaches to implementing it.
<p align="center">
<img src="hyperscale-architecture.webp" alt="Hyperscale Architecture" width="85%">
<img src="/guides/vm-architectures/hyperscale-architecture.webp" alt="Hyperscale Architecture" width="85%">
</p>
### Choosing Your Read Path Strategy
@@ -375,7 +375,7 @@ This multitenancy approach gives us another trade-off in the isolation implement
**Schema:**
<p align="center">
<img src="logical-layers-architecture.webp" alt="Logical Layers Architecture" width="80%">
<img src="/guides/vm-architectures/logical-layers-architecture.webp" alt="Logical Layers Architecture" width="80%">
</p>
**Path A: Shared resources.** We have a single, shared pool of all cluster components.

View File

@@ -155,15 +155,15 @@ These services will store and query the metrics scraped by vmagent.
# compose.yaml
services:
vmstorage:
image: victoriametrics/vmstorage:v1.146.0-cluster
image: victoriametrics/vmstorage:v1.148.0-cluster
vminsert:
image: victoriametrics/vminsert:v1.146.0-cluster
image: victoriametrics/vminsert:v1.148.0-cluster
command:
- -storageNode=vmstorage:8400
vmselect:
image: victoriametrics/vmselect:v1.146.0-cluster
image: victoriametrics/vmselect:v1.148.0-cluster
command:
- -storageNode=vmstorage:8401
ports:
@@ -196,7 +196,7 @@ Add the vmauth service to `compose.yaml`:
# compose.yaml
services:
vmauth:
image: victoriametrics/vmauth:v1.146.0-enterprise
image: victoriametrics/vmauth:v1.148.0-enterprise
ports:
- 8427:8427
volumes:
@@ -251,7 +251,7 @@ Add the vmagent service to `compose.yaml` with OAuth2 configuration:
# compose.yaml
services:
vmagent:
image: victoriametrics/vmagent:v1.146.0
image: victoriametrics/vmagent:v1.148.0
volumes:
- ./scrape.yaml:/etc/vmagent/config.yaml
command:

View File

@@ -107,7 +107,7 @@ The final piece is the Docker Compose file. This ties all the services together
# compose.yml
services:
victoriametrics:
image: victoriametrics/victoria-metrics:v1.146.0
image: victoriametrics/victoria-metrics:v1.148.0
command:
- "--storageDataPath=/victoria-metrics-data"
- "--selfScrapeInterval=10s"
@@ -128,7 +128,7 @@ services:
- ./alertmanager.yml:/etc/alertmanager/alertmanager.yml:ro
vmalert:
image: victoriametrics/vmalert:v1.146.0
image: victoriametrics/vmalert:v1.148.0
depends_on:
- victoriametrics
- alertmanager

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