Compare commits

...

82 Commits

Author SHA1 Message Date
dmitry-shur
1d27079ec8 Improvements for backup description and configuration for single node, cluster , quick start 2025-07-16 17:58:49 +02:00
dmitry-shur
26ce7316a0 Adding "Note: If custom S3 endpoint is used, URL should contain only name of the bucket, while hostname of S3 server must be specified via the -customS3Endpoint command-line flag." across flags and docs 2025-07-04 14:02:01 +02:00
dmitry-shur
4c825bf31c Adding note for -dst config.
Adding additional reference for snapshot troubleshooting for better accessibility
2025-07-01 15:21:21 +02:00
Artem Navoiev
3108376d95 docs: changelog fix the link to cluster version in 114 release.2
Signed-off-by: Artem Navoiev <tenmozes@gmail.com>
2025-04-10 09:39:15 +02:00
Artem Navoiev
494fe4403a docs: changelog fix the link to cluster version in 114 release
Signed-off-by: Artem Navoiev <tenmozes@gmail.com>
2025-04-09 21:28:40 +02:00
Andrii Chubatiuk
303b425fa3 lib/protoparser/datadog*: support Content-Encoding: identity value
introduction of common decompression logic in
https://github.com/VictoriaMetrics/VictoriaMetrics/pull/8416 removed
ability to treat unsupported compression algorithms as uncompressed data
for datadog v1 endpoint. This PR adds support of `identity`
Content-Encoding header value, though according to RFC 2616 this value
is only expected in `Accept-Encoding` header

related issue:
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8649
2025-04-08 16:17:19 +02:00
Nikolay
8f3efde55d lib/httpserver: mask authKey at PostFrom
'authKey' is well-known url and form param for VictoriaMetrics
components authorization. Previously, it could be printed into stdout
via httpserver error logger. It makes this authKey insecure and hard to
use.

This commit prevents from logging authKey defined at PostForm or as part
of url.Query.

It's recommneded to transfer authKey via PostForm and it should be
implemented at separate PRs.

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

---------
Signed-off-by: f41gh7 <nik@victoriametrics.com>
2025-04-08 16:15:48 +02:00
Nikolay
f16938bba9 lib/backup/s3: properly set ProfileName
Previously, if ProfileName is set to empty value (as default). AWS s3
lib ignored any profile config defined with `-configProfilePath`.

This commit correctly configure client options and set profile name only
if it's set to non-empty value.

Related issue:
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8668
2025-04-08 16:15:07 +02:00
nemobis
65ff04bc09 docs: Fix typo in changelog for v113
Fix a typo `scrapped` for `scraped`.
2025-04-08 16:14:50 +02:00
Zakhar Bessarab
e2715f94af docs/guides/vmgateway-grafana-oidc: update guide for recent versions of components
- update grafana & keycloak to latest versions
- update UI images with the latest screenshots
- update wording to reflect UI changes
2025-04-08 16:13:58 +02:00
Zakhar Bessarab
582160f566 make: fix make package for vmalert-tool
`make package` relies on presence of `APP_NAME/deployment/Dockerfile`
which was missing for vmalert-tool.
2025-04-08 16:13:28 +02:00
nemobis
638f9839d5 docs: fix typo in pull request template
The verb is _adhere to_, see https://en.wiktionary.org/wiki/adhere .
2025-04-08 16:12:51 +02:00
Max Kotliar
3f5bf4bd03 vmagent/remotewrite: set content encoding header based on actual body
Improve remote write handling in vmagent by setting the
`Content-Encoding` header based on the actual request body, rather than
relying on configuration.

- Detects Zstd compression via the Zstd magic number.
- Falls back to Snappy if Zstd is not detected.
- Persistent queue may now contain mixed-encoding content.
- Add basic vmagent integration tests

Follow up on
https://github.com/VictoriaMetrics/VictoriaMetrics/pull/5344 and
12cd32fd75.

Extracted from
https://github.com/VictoriaMetrics/VictoriaMetrics/pull/8462

Related issue:
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5301
2025-04-08 16:12:06 +02:00
f41gh7
038419663b docs: release follow-up
* mention lts release changes
* update vm apps versions at docs and deployment examples

Signed-off-by: f41gh7 <nik@victoriametrics.com>
2025-04-07 12:59:53 +02:00
f41gh7
123f373537 CHANGELOG.md: cut v1.115.0 release 2025-04-04 14:30:16 +02:00
f41gh7
57121c828f make docs-update-version 2025-04-04 14:23:08 +02:00
f41gh7
aa5edbc706 make vmui-update 2025-04-04 14:20:37 +02:00
Andrii Chubatiuk
f9d8c86b0a lib/streamaggr: fix panic in rate output
This commit properly reset aggregator state. Previously, it was not checked for `nil` and it lead to the panic on access.

Related issue:
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8634
2025-04-04 14:14:52 +02:00
hansemschnokeloch
b733fc5b83 docs/vlogs: fix typo in README 2025-04-04 14:10:59 +02:00
Zakhar Bessarab
f2eaad62dc docs/changelog: correct entry location after 298f862f
Signed-off-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>
2025-04-04 12:24:16 +04:00
Aliaksandr Valialkin
adae788b18 lib/logstorage: pad pipeStatsProcessorShard.groupMapShards in order to avoid false sharing when merging these shards in parallel on many CPU cores 2025-04-03 22:21:18 +02:00
Aliaksandr Valialkin
a65d10fcce lib/logstorage: add padding between hitsMap items at hitsMapAdaptive.shards in order to avoid false sharing when processing the hitsMapAdaptive.shards on multiple CPU cores 2025-04-03 20:14:20 +02:00
Zakhar Bessarab
298f862fc0 deps: downgrade AWS dependencies
Pin AWS libraries to version before 2025-01-15 (see
https://github.com/aws/aws-sdk-go-v2/releases/tag/release-2025-01-15).

This version enabled request and response checksum verification by
default which breaks compatibility with non-AWS S3-compatible storage
providers.

See: https://github.com/victoriaMetrics/victoriaMetrics/issues/8622

Supersedes https://github.com/VictoriaMetrics/VictoriaMetrics/pull/8630

---------

Signed-off-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>
2025-04-03 18:05:07 +04:00
Zakhar Bessarab
aff1580a1d app/vmauth: return non-OK response for timeouts and request cancellation
Currently, requests failing due to network timeout would receive "200
OK" while producing a warning log message about the timeout. This
behaviour is confusing and might produce unexpected issues as it is not
possible to retry errors properly.

Change this to return "502 Bad Gateway" response so that error can be
handled by the client.

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

Config for testing:
```
unauthorized_user:
  url_prefix: "http://example.com:9800"
```

Before the change:
```
*   Trying 127.0.0.1:8427...
* Connected to 127.0.0.1 (127.0.0.1) port 8427
* using HTTP/1.x
> HEAD /api/v1/query HTTP/1.1
> Host: 127.0.0.1:8427
> User-Agent: curl/8.12.1
> Accept: */*
>
* Request completely sent off
/* NOTE: 30 seconds timeout passes */
< HTTP/1.1 200 OK
HTTP/1.1 200 OK
< Vary: Accept-Encoding
Vary: Accept-Encoding
< X-Server-Hostname: pc
X-Server-Hostname: pc
< Date: Tue, 01 Apr 2025 08:54:05 GMT
Date: Tue, 01 Apr 2025 08:54:05 GMT
<

* Connection #0 to host 127.0.0.1 left intact
```

After:
```
*   Trying 127.0.0.1:8427...
* Connected to 127.0.0.1 (127.0.0.1) port 8427
* using HTTP/1.x
> HEAD /api/v1/query HTTP/1.1
> Host: 127.0.0.1:8427
> User-Agent: curl/8.12.1
> Accept: */*
>
* Request completely sent off
< HTTP/1.1 502 Bad Gateway
HTTP/1.1 502 Bad Gateway
< Content-Type: text/plain; charset=utf-8
Content-Type: text/plain; charset=utf-8
< Vary: Accept-Encoding
Vary: Accept-Encoding
< X-Content-Type-Options: nosniff
X-Content-Type-Options: nosniff
< X-Server-Hostname: pc
X-Server-Hostname: pc
< Date: Tue, 01 Apr 2025 09:13:57 GMT
Date: Tue, 01 Apr 2025 09:13:57 GMT
< Content-Length: 109
Content-Length: 109
<

* Connection #0 to host 127.0.0.1 left intact
```

Signed-off-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>
2025-04-03 13:44:51 +04:00
hagen1778
d4c0a42c1b docs: improve wording for recent vmalert changes
follow-up for https://github.com/VictoriaMetrics/VictoriaMetrics/pull/8522

Signed-off-by: hagen1778 <roman@victoriametrics.com>
(cherry picked from commit 3cbc3eb19f)
2025-04-03 09:47:34 +01:00
Emre Yazıcı
a9736a5bfb app/vmalert: show partial responses in debug logs (#8522)
### Describe Your Changes

Log when the data response from vmselect is partial during
rule(recording, alertingrule) evaluations.

vmselect returns `isPartial: true` in case data is not fully fetched
from scattered vmstorages. At the time of rule evals, it may be drifting
apart from real values due to missing points. This is an important event
that should be logged to inform users to see how often that happens as
it may lead to false positive alerts.

### Checklist

The following checks are **mandatory**:

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

---------

Signed-off-by: emreya <emre.yazici@adyen.com>
Signed-off-by: emreya <e.yazici1990@gmail.com>
Signed-off-by: Emre Yazici <e.yazici1990@gmail.com>
(cherry picked from commit 56f60e8be9)
2025-04-03 09:47:34 +01:00
Artem Fetishev
2e4beeefb1 Update series count docs (#8631)
Signed-off-by: Artem Fetishev <rtm@victoriametrics.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-04-03 10:37:35 +02:00
Aliaksandr Valialkin
635c5c9feb app/vlselect: run /select/logsql/tail queries without concurrency limit
The concurrency limit is intended for short-running queries. If it is applied to tail queries,
then this can affect short-running queries.
2025-04-02 20:22:27 +02:00
Aliaksandr Valialkin
4e1260e189 app/vlselect: do not log canceled requests, since they are expected and legal 2025-04-02 19:14:44 +02:00
Aliaksandr Valialkin
ca3910748f deployment: update Go builder from Go1.24.1 to Go1.24.2
See https://github.com/golang/go/issues?q=milestone%3AGo1.24.2+label%3ACherryPickApproved
2025-04-02 18:01:21 +02:00
Artem Fetishev
2f0796ff40 lib/storage: When creating and listing snapshots, panic instead of returning an error (#8585)
When creating and listing snapshots, panic instead of returning an error
since errors are not recoverable anyway.
Also do not cleanup the filesystem on panic. Leave as is for further
manual inspection.

Signed-off-by: Artem Fetishev <rtm@victoriametrics.com>
2025-04-02 15:47:23 +02:00
Artem Fetishev
cdba6dbc0e lib/storage: Pass the partition time range during the partition creation and opening (#8571)
Signed-off-by: Artem Fetishev <rtm@victoriametrics.com>
2025-04-02 14:57:59 +02:00
Aliaksandr Valialkin
f18daaeac5 app/vmui: replace old-style links to https://docs.victoriametrics.com/MetricsQL.html with https://docs.victoriametrics.com/metricsql/
Replace also https://docs.victoriametrics.com/keyConcepts.html with https://docs.victoriametrics.com/keyconcepts/

This is the follow-up for the commit ee1da35071
2025-04-02 13:22:58 +02:00
Artem Fetishev
a9f124388f lib/storage: mergeBlockStreams(): replace the dependency on Storage with dependency on the set of deleted metricIDs (#8569)
This should narrow down the function dependencies and simplify testing.

Signed-off-by: Artem Fetishev <rtm@victoriametrics.com>
2025-04-02 13:16:26 +02:00
Aliaksandr Valialkin
4b2276608b docs/victoriametrics/vmagent.md: mention that increasing scrape_interval can reduce CPU usage 2025-04-02 12:41:57 +02:00
Aliaksandr Valialkin
b352470ae1 docs/victoriametrics/vmagent.md: mention that -promscrape.disableKeepAlive option can reduce RAM usage when scraping thousands of targets 2025-04-01 23:22:13 +02:00
Aliaksandr Valialkin
b3261a1b87 lib/promscrape: do not clutter logs with cannot scrape target ...: context canceled errors when vmagent is stopped 2025-04-01 23:20:43 +02:00
Aliaksandr Valialkin
6d5973dcb0 docs/victoriametrics/vmagent.md: change GOGC from 50 to 100 in the example of optimized config for vmagent
This is a follow-up after bf024d3dce,
2025-04-01 21:36:04 +02:00
Aliaksandr Valialkin
74f17bb67e docs/victoriametrics/vmagent.md: remove the recommendation to set GOGC to 50 at vmagent in order to reduce CPU usage
The default GOGC is set to 50 at vmagent after bf024d3dce,
so this recommendation makes no sense. Leave the recommendation to increase GOGC to 100.
2025-04-01 21:14:33 +02:00
Aliaksandr Valialkin
bf024d3dce app/vmagent: increase the default GOGC from 30 to 50
This reduces CPU usage by up to 30% in exchange of the increased RAM usage by 10%
when scraping thousands of targets, which expose millions of metrics in summary.

This looks like a good tradeoff after the commit edac875179 ,
which reduced RAM usage by more than 10%, so the final RAM usage for vmagent
is still lower than the RAM usage at v1.114.0 by ~15%, while CPU usage drops by 30%.
2025-04-01 21:04:28 +02:00
Aliaksandr Valialkin
5b87aff830 lib/promscrape: use chunkedbuffer.Buffer instead of bytesutil.ByteBuffer for reading response body from scrape targets
This reduces memory usage when reading large response bodies because the underlying buffer
doesn't need to be re-allocated during the read of large response body in the buffer.

Also decompress response body under the processScrapedDataConcurrencyLimitCh .
This reduces CPU usage and RAM usage a bit when scraping thousands of targets.
2025-04-01 20:30:39 +02:00
Aliaksandr Valialkin
34d35869fa docs/victoriametrics/vmagent.md: add Performance optimizations chapter
Enumerate the most commonly used options for reducing CPU usage and RAM usage
for vmagent, which scrapes thousands of targets.

See https://docs.victoriametrics.com/vmagent/#performance-optimizations
2025-04-01 18:35:43 +02:00
Max Kotliar
b1d1f1f461 vmagent/remotewrite: fix golangci-lint code style issue
### Describe Your Changes

Fixes golangci-lint issues introduced in
98f1e32e39

```
--- a/app/vmagent/remotewrite/pendingseries.go
+++ b/app/vmagent/remotewrite/pendingseries.go
@@ -202,7 +202,7 @@ func (wr *writeRequest) copyTimeSeries(dst, src *prompbmarshal.TimeSeries) {

 	// Pre-allocate memory for labels.
 	labelsLen := len(wr.labels)
-	wr.labels = slicesutil.SetLength(wr.labels, labelsLen + len(labelsSrc))
+	wr.labels = slicesutil.SetLength(wr.labels, labelsLen+len(labelsSrc))
 	labelsDst := wr.labels[labelsLen:]

 	// Pre-allocate memory for byte slice needed for storing label names and values.
@@ -212,7 +212,7 @@ func (wr *writeRequest) copyTimeSeries(dst, src *prompbmarshal.TimeSeries) {
 		neededBufLen += len(label.Name) + len(label.Value)
 	}
 	bufLen := len(wr.buf)
-	wr.buf = slicesutil.SetLength(wr.buf, bufLen + neededBufLen)
+	wr.buf = slicesutil.SetLength(wr.buf, bufLen+neededBufLen) buf := wr.buf[:bufLen]

 	// Copy labels

```

### Checklist

The following checks are **mandatory**:

- [x] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).
2025-04-01 18:37:02 +04:00
Aliaksandr Valialkin
98f1e32e39 app/vmagent/remotewrite: optimize writeRequest.copyTimeSeries a bit
Pre-allocate memory for labels and for the needed byte buffer used
for holding the copied label names and values.
2025-04-01 15:58:04 +02:00
Aliaksandr Valialkin
edac875179 lib/promscrape: always store the last response per every scrape target in compressed form
This reduces memory usage for vmagent when scraping big number of targets at the cost of slightly higher CPU usage.

The increased CPU usage can be decreased by disabling tracking of stale markers either via -promscrape.noStaleMarkers
command-line flag or via `no_stale_markers: true` option at the scrape config pointed by -promscrape.config command-line flag.
See https://docs.victoriametrics.com/vmagent/#prometheus-staleness-markers
2025-04-01 15:27:11 +02:00
Aliaksandr Valialkin
0ff1a3b154 lib/leveledbytebufferpool: start with the pools[0] for byte slices up to 256 bytes
The pool is used mostly for obtaining byte buffers for responses from scrape targets.
There are no responses smaller than 256 bytes in practice, so there is no sense in maintaining
pools for byte slices up to 64 and 128 bytes.
2025-04-01 12:01:21 +02:00
Aliaksandr Valialkin
bbe58cc37b lib/promscrape: make sure that the maxLabelsLen contains really the maximum len(wc.labels) among concurrently running callbacks at stream.Parse
Previously the maxLabelsLen could be updated with smaller value after it is updated to bigger value by concurrently running goroutines.
Prevent this by loading the latest maxLabelsLen value and updating it only if it is smaller than the current len(wc.labels)
before the exit from callback passed to stream.Parse.

While at it, return early from the callback on the sample_limit exceeding error,
since the rest of the code in the callback becomes no-op after wc.reset().
This simplifies following the logic in the code a bit.

Also remove outdated misleading comment in front of sw.pushData() call inside callbacks passed to stream.Parse.
This comment has no sense after every callback start working with its own goroutine-local wc.
2025-04-01 11:49:35 +02:00
Aliaksandr Valialkin
78dca6ee6e lib/promscrape: tune leveledWriteRequestCtxPool a bit
Start with writeRequestCtx containing up to 256 labels instead of 8 labels,
since a typical response from scrape target contains much more than 8 labels across all the exposed metrics.

Do not pre-allocate labels at writeRequestCtx, since they are pre-allocated inside writeRequestCtx.addRows(),
together with the pre-allocation of samples and writeRequest.Timeseries.
2025-04-01 02:11:14 +02:00
Aliaksandr Valialkin
12f26668a6 lib/promscrape: make sure that the writeRequestCtxPool is efficiently used when sending automatically generated metrics to remote storage 2025-04-01 01:56:07 +02:00
Aliaksandr Valialkin
6c90843aab lib/protoparser/prometheus: use clear() instead of for { ... } loops for clearing Rows.Rows and Rows.tagsPool at Rows.Reset()
This simplifies the code a bit.
2025-04-01 01:39:19 +02:00
Aliaksandr Valialkin
4aad4c64bb lib/promscrape: attach applySeriesLimit to writeRequestCtx instead of scrapeWork
The applySeriesLimit applies the limit to samples stored at writeRequestCtx,
while the scrapeWork is used as read-only configuration source.
That's why it is better from maintainability PoV to attach the applySeriesLimit
method to writeRequestCtx.

While at it, clarify docs for the applySeriesLimit function.
2025-04-01 01:11:06 +02:00
Aliaksandr Valialkin
5630c0108e lib/promscrape: remove writeRequestCtx.resetNoRows() funtion
This function can be safely replaced with writeRequestCtx.reset() after the commit 188325f0fc,
which makes sure that all the rows inside writeRequestCtx.rows are pushed to the remote storage before returning
from stream parsing callback.

Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/825
Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/753
2025-04-01 01:11:06 +02:00
Aliaksandr Valialkin
732a549cff lib/promscrape: clarify the comment for scrapeWork.pushData() 2025-04-01 01:11:05 +02:00
Aliaksandr Valialkin
af637bc2a2 lib/promscrape: remove the remaining writeRequestCtx.reset() calls before writeRequestCtxPool.Put() calls
These calls aren't needed, since they are performed by the writeRequestCtxPool.Put()
2025-04-01 01:11:05 +02:00
Aliaksandr Valialkin
6600916344 lib/promscrape: pass cfg *ScrapeWork as an arg to areIdentialSeries instead of attaching it to the ScrapeWork struct
This makes the code more consistent with other functions, which accept `cfg *ScrapeWork` as the first arg.
2025-04-01 01:11:04 +02:00
Aliaksandr Valialkin
e9cb95c5d4 lib/promscrape: replace scrapeWork.addRowToTimeseries with writeRequestCtx.addRows
The rows are added to writeRequestCtx, while the scrapeWork is used only as a read-only configuration source.
So it is better from maintainability PoV to attach addRows function to writeRequestCtx instead of scrapeWork.

Also attach addAutoMetrics to writeRequestCtx instead of scrapeWork due to the same reason:
addAutoMetrics adds metrics to the writeRequestCtx, while using scrapeWork as a read-only configuration source.

While at it, remove tmpRow from scrapeWork struct in order to reduce the complexity of this struct.
2025-04-01 01:11:04 +02:00
Aliaksandr Valialkin
8617faa160 lib/promscrape: remove wc.resetNoRows() call before returning wc to the pool, since this function is called inside writeRequestCtxPool.Put() 2025-04-01 01:11:03 +02:00
Aliaksandr Valialkin
fe888be58c lib/promscrape: remove at *auth.Token arg from scrapeWork.pushData(), since it always equals to sw.Config.AuthToken
This simplifies the code a bit.

While at it, mention that scrapeWork.PushData callback must be safe for calling from concurrently running goroutines.
2025-04-01 01:11:03 +02:00
Aliaksandr Valialkin
a38de1c242 lib/promscrape: attach areIdentialSeries method to ScrapeWork instead of scrapeWork
areIdenticalSeries doesn't access scrapeWork members except of sw.Config of *ScrapeWork type.
It is better from maintainability PoV to attach this methos to ScrapeWork then.

While at it, replace sw.Config with cfg shortcut at scrapeWork.processDataOneShot()
and scrapeWork.processDataInStreamMode().
2025-04-01 01:11:03 +02:00
Aliaksandr Valialkin
8dc0f6cfab lib/promscrape: remove unused scrapeWork arg from getSeriesAdded 2025-04-01 01:11:02 +02:00
Phuong Le
346db8a606 vmui: fix auto-suggestion doesn't work inside functions (#8473)
Fixes #8379

---------

Signed-off-by: hagen1778 <roman@victoriametrics.com>
Co-authored-by: hagen1778 <roman@victoriametrics.com>
2025-03-31 16:22:25 +02:00
hagen1778
b277a62e94 docs: mention pull request checklist in doc guides
Checklist is a more practical list of actions than a full Contributing doc.

Signed-off-by: hagen1778 <roman@victoriametrics.com>
2025-03-31 15:48:10 +02:00
hagen1778
e2535fcb28 app/vmui: fix path to metricsql doc
This is follow-up after f152021521

Signed-off-by: hagen1778 <roman@victoriametrics.com>
2025-03-31 14:47:38 +02:00
Roman Khavronenko
66e7b908ec dashboards: drop all dashboards tags except victoriametrics or victorialogs tags for consistency (#8620)
Having `victoriametrics` or `victorialogs` tags should be enough for
filtering dashboards related to VictoriaMetrics components.

Related ticket
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8618

### Describe Your Changes

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

### Checklist

The following checks are **mandatory**:

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

Signed-off-by: hagen1778 <roman@victoriametrics.com>
2025-03-31 14:27:05 +02:00
Roman Khavronenko
a2ba37be68 lib/promscrape: support filtering targets via scrapePool GET param in /api/v1/targets API (#8611)
This improves compatibility with Prometheus `/api/v1/targets` API.

https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5343

---------

Signed-off-by: hagen1778 <roman@victoriametrics.com>
Co-authored-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>
2025-03-31 14:26:25 +02:00
Aliaksandr Valialkin
004e5ff38b lib/promscrape: hide sw.seriesLimiter behind sw.getSeriesLimiter()
This guarantees that the sw.seriesLimiter is always read after the initialization.
2025-03-29 02:05:20 +01:00
Aliaksandr Valialkin
4160fbecc0 lib/promscrape: pass a string instead of a byte slice to scrapeWork.storeLastScrape
This removes superflouos references to the "body" variable.

While at it, remove obsolete misleading comment.
2025-03-29 01:59:20 +01:00
Aliaksandr Valialkin
44844b7fbe lib/promscrape: use "time.Time.UnixMilli()" instead of "time.Time.UnixNano() / 1e6"
This improves readability a bit
2025-03-29 01:43:31 +01:00
Aliaksandr Valialkin
f60458d5fa lib/protoparser/prometheus: add a fast path to AreIdenticalSeriesFast when two identical strings are passed to it
This may be the case when repeated scrapes return the same set of metrics with the same values
2025-03-29 01:43:31 +01:00
Zakhar Bessarab
e242dd5bf2 make vendor-update
Support of the latest prometheus/common is not released yet so pin to previous version.
Related commit at prometheus/prometheus: 95f49dd84b

Signed-off-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>
2025-03-28 18:31:49 +04:00
Zakhar Bessarab
f863b331c1 app/vmalert/rule: follow-up for d8fe739aba
Remove tenancy-related part of the commit as it is not relevant to OS vmalert version.

Signed-off-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>
2025-03-28 18:29:41 +04:00
Aliaksandr Valialkin
a98779770c lib/promscrape: run BenchmarkScrapeWorkScrapeInternalStreamBigData on all the available CPU cores
This allows verifying how the benchmark performance scales with the number of available CPU cores
and makes the results of the benchmark consistent with other BenchmarkScrapeWorkScrapeInternal* benchmarks.

Also reduce the amounts of memory allocations inside generateScrape() function in order to reduce
measurement noise during the BenchmarkScrapeWorkScrapeInternalStreamBigData run.

This is a follow-up after c05ffa906d
Updates https://github.com/VictoriaMetrics/VictoriaMetrics/pull/8515
Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8159
2025-03-28 13:38:13 +01:00
Aliaksandr Valialkin
bcbcae2309 lib/promscrape: improve the performance of getLabelsHash() after c05ffa906d
Before the commit:

BenchmarkScrapeWorkGetLabelsHash-16    	23226468	       249.5 ns/op	   4.01 MB/s	       0 B/op	       0 allocs/op

After the commit:

BenchmarkScrapeWorkGetLabelsHash-16    	39100964	       154.7 ns/op	   6.46 MB/s	       0 B/op	       0 allocs/op

Updates https://github.com/VictoriaMetrics/VictoriaMetrics/pull/8515
Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8159
2025-03-28 13:38:12 +01:00
Aliaksandr Valialkin
5b8c10d08e lib/promscrape: run the BenchmarkScrapeWorkGetLabelsHash benchmark in parallel on all the available CPU cores
It is always better to run benchmarks in parallel on all the available CPU cores
in order to see how their performance scales with the number of CPU cores (GOMAXPROCS).

The commit also performs the following modifications:

- Removes the dependency of on the scrapeWork from getLabelsHash() function.

- Makes sure that the benchmark cannot be optimized out by the compiler, by introducing a dependency
  on a global Sink variable. Previously the getLabelsHash() function call could be optimized out
  by the compiler, since this call has no side effects, and the returned result is ignored.

- Reduces the amounts of memory allocations inside the BenchmarkScrapeWorkGetLabelsHash
  when preparing the labels for the benchmark. This should reduce measurements' noise during the benchmark.

This is a follow-up for c05ffa906d

Updates https://github.com/VictoriaMetrics/VictoriaMetrics/pull/8515
Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8159
2025-03-28 13:38:12 +01:00
Aliaksandr Valialkin
588fa4d90d lib/promscrape: consistently use io.LimitReader across all the VictoriaMetrics repository 2025-03-28 13:38:11 +01:00
Hui Wang
b9c777a578 vmgateway: properly set the Host header when routing requests to `-… (#869)
* vmgateway: properly set the `Host` header when routing requests to `-write.url` and `-read.url`

* Update docs/victoriametrics/changelog/CHANGELOG.md

---------

Co-authored-by: Roman Khavronenko <roman@victoriametrics.com>
Signed-off-by: hagen1778 <roman@victoriametrics.com>
2025-03-28 12:27:19 +01:00
Hui Wang
d8fe739aba vmalert: properly attach tenant labels vm_account_id and `vm_projec… (#866)
* vmalert: properly attach tenant labels `vm_account_id` and `vm_project_id` to alerting rules when enabling `-clusterMode`

Previously, these labels were lost in alert messages to Alertmanager. Bug was introduced in [v1.112.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.112.0).
2025-03-28 12:26:55 +01:00
Aliaksandr Valialkin
e5ddf475b8 lib/promscrape/scrapework.go: typo fix in the comment: replace 'parsing parsing' with 'parsing' 2025-03-27 15:03:53 +01:00
Aliaksandr Valialkin
3c3c8668d6 lib/bytesutil: grow the buffer at ByteBuffer.ReadFrom more smoothly
Previously the buffer was increased by 30% after it became 50% full.
For example, if more than 5MB of data is read into 10MB buffer, then its' size
was increased to 13MB, leading to 13MB-5MB = 8MB of waste.
This translates to 8MB/5MB = 160% waste in the worst case.

The updated algorithm increases the buffer by 30% after it becomes ~94% full.
This means that if more than 9.4MB of data is read into 10MB buffer,
then its' size is increased to 13MB, leading to 13MB-9.4MB = 3.6MB of waste.
This translates to 3.6MB / 9.4MB = ~38% waste in the worst case.

This should reduce memory usage when vmagent reads big responses from scrape targets.

While at it, properly append the data to buffer if it already has more than 4KiB of data.
Previously the data over 4KiB in the buffer was lost after ReadFrom call.

This is a follow-up for f28f496a9d
Updates https://github.com/VictoriaMetrics/VictoriaMetrics/pull/6761
Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6759
2025-03-27 15:03:53 +01:00
Aliaksandr Valialkin
22d1b916bf lib/protoparser/protoparserutil: optimize ReadUncompressedData for zstd and snappy
It is faster to read the whole data and then decompress it in one go for zstd and snappy encodings.
This reduces the number of potential read() syscalls and decompress CGO calls needed
for reading and decompressing the data.
2025-03-27 15:03:52 +01:00
Aliaksandr Valialkin
35b31f904d lib/httputil: automatically initialize data transfer metrics for the created HTTP transports via NewTransport() 2025-03-27 15:03:52 +01:00
Dmytro Kozlov
7c05ec42fe app/vmctl: fix show logs for prometheus migration mode (#8529)
### Describe Your Changes
Fixed issue an issue with show stats at the end of the process. Please
check the images below
Before the fix

![image](https://github.com/user-attachments/assets/d549c327-ed2b-46c5-965c-4f3581f54d83)


After the fix


![image](https://github.com/user-attachments/assets/c3200aff-dd50-40cf-92a9-b09800a25834)

I fixed it by moving logic to the function. Now it works correctly.

Added the tests for the Prometheus migration mode (make tests great
again).

The main discussion was introduced in this
[PR](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/7863).

### Checklist

The following checks are **mandatory**:

- [x] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).
2025-03-27 14:12:10 +01:00
370 changed files with 4190 additions and 4916 deletions

View File

@@ -6,4 +6,4 @@ Please provide a brief description of the changes you made. Be as specific as po
The following checks are **mandatory**:
- [ ] My change adheres [VictoriaMetrics contributing guidelines](https://docs.victoriametrics.com/contributing/).
- [ ] My change adheres to [VictoriaMetrics contributing guidelines](https://docs.victoriametrics.com/contributing/).

View File

@@ -99,9 +99,17 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
return true
}
ctx := r.Context()
if path == "/select/logsql/tail" {
logsqlTailRequests.Inc()
// Process live tailing request without timeout, since it is OK to run live tailing requests for very long time.
// Also do not apply concurrency limit to tail requests, since these limits are intended for non-tail requests.
logsql.ProcessLiveTailRequest(ctx, w, r)
return true
}
// Limit the number of concurrent queries, which can consume big amounts of CPU time.
startTime := time.Now()
ctx := r.Context()
d := getMaxQueryDuration(r)
ctxWithTimeout, cancel := context.WithTimeout(ctx, d)
defer cancel()
@@ -111,14 +119,6 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
}
defer decRequestConcurrency()
if path == "/select/logsql/tail" {
logsqlTailRequests.Inc()
// Process live tailing request without timeout (e.g. use ctx instead of ctxWithTimeout),
// since it is OK to run live tailing requests for very long time.
logsql.ProcessLiveTailRequest(ctx, w, r)
return true
}
ok := processSelectRequest(ctxWithTimeout, w, r, path)
if !ok {
return false
@@ -129,10 +129,7 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
case nil:
// nothing to do
case context.Canceled:
remoteAddr := httpserver.GetQuotedRemoteAddr(r)
requestURI := httpserver.GetRequestURI(r)
logger.Infof("client has canceled the request after %.3f seconds: remoteAddr=%s, requestURI: %q",
time.Since(startTime).Seconds(), remoteAddr, requestURI)
// do not log canceled requests, since they are expected and legal.
case context.DeadlineExceeded:
err = &httpserver.ErrorWithStatusCode{
Err: fmt.Errorf("the request couldn't be executed in %.3f seconds; possible solutions: "+

View File

@@ -105,7 +105,7 @@ func main() {
// Some workloads may need increased GOGC values. Then such values can be set via GOGC environment variable.
// It is recommended increasing GOGC if go_memstats_gc_cpu_fraction metric exposed at /metrics page
// exceeds 0.05 for extended periods of time.
cgroup.SetGOGC(30)
cgroup.SetGOGC(50)
// Write flags and help message to stdout, since it is easier to grep or pipe.
flag.CommandLine.SetOutput(os.Stdout)
@@ -443,8 +443,10 @@ func requestHandler(w http.ResponseWriter, r *http.Request) bool {
case "/prometheus/api/v1/targets", "/api/v1/targets":
promscrapeAPIV1TargetsRequests.Inc()
w.Header().Set("Content-Type", "application/json")
// https://prometheus.io/docs/prometheus/latest/querying/api/#targets
state := r.FormValue("state")
promscrape.WriteAPIV1Targets(w, state)
scrapePool := r.FormValue("scrapePool")
promscrape.WriteAPIV1Targets(w, state, scrapePool)
return true
case "/prometheus/target_response", "/target_response":
promscrapeTargetResponseRequests.Inc()

View File

@@ -13,10 +13,10 @@ import (
"time"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/awsapi"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/encoding"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/flagutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httputil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/netutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/persistentqueue"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promauth"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/protoparserutil"
@@ -126,8 +126,7 @@ func newHTTPClient(argIdx int, remoteWriteURL, sanitizedURL string, fq *persiste
logger.Fatalf("cannot initialize AWS Config for -remoteWrite.url=%q: %s", remoteWriteURL, err)
}
tr := httputil.NewTransport(false)
tr.DialContext = netutil.NewStatDialFunc("vmagent_remotewrite")
tr := httputil.NewTransport(false, "vmagent_remotewrite")
tr.TLSHandshakeTimeout = tlsHandshakeTimeout.GetOptionalArg(argIdx)
tr.MaxConnsPerHost = 2 * concurrency
tr.MaxIdleConnsPerHost = 2 * concurrency
@@ -386,7 +385,7 @@ func (c *client) newRequest(url string, body []byte) (*http.Request, error) {
h := req.Header
h.Set("User-Agent", "vmagent")
h.Set("Content-Type", "application/x-protobuf")
if c.useVMProto {
if encoding.IsZstd(body) {
h.Set("Content-Encoding", "zstd")
h.Set("X-VictoriaMetrics-Remote-Write-Version", "1")
} else {

View File

@@ -16,6 +16,7 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/lib/persistentqueue"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promrelabel"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/slicesutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/timeutil"
"github.com/VictoriaMetrics/metrics"
"github.com/golang/snappy"
@@ -197,28 +198,43 @@ func (wr *writeRequest) tryPush(src []prompbmarshal.TimeSeries) bool {
}
func (wr *writeRequest) copyTimeSeries(dst, src *prompbmarshal.TimeSeries) {
labelsDst := wr.labels
labelsSrc := src.Labels
// Pre-allocate memory for labels.
labelsLen := len(wr.labels)
samplesDst := wr.samples
buf := wr.buf
for i := range src.Labels {
labelsDst = append(labelsDst, prompbmarshal.Label{})
dstLabel := &labelsDst[len(labelsDst)-1]
srcLabel := &src.Labels[i]
wr.labels = slicesutil.SetLength(wr.labels, labelsLen+len(labelsSrc))
labelsDst := wr.labels[labelsLen:]
buf = append(buf, srcLabel.Name...)
dstLabel.Name = bytesutil.ToUnsafeString(buf[len(buf)-len(srcLabel.Name):])
buf = append(buf, srcLabel.Value...)
dstLabel.Value = bytesutil.ToUnsafeString(buf[len(buf)-len(srcLabel.Value):])
// Pre-allocate memory for byte slice needed for storing label names and values.
neededBufLen := 0
for i := range labelsSrc {
label := &labelsSrc[i]
neededBufLen += len(label.Name) + len(label.Value)
}
dst.Labels = labelsDst[labelsLen:]
bufLen := len(wr.buf)
wr.buf = slicesutil.SetLength(wr.buf, bufLen+neededBufLen)
buf := wr.buf[:bufLen]
samplesDst = append(samplesDst, src.Samples...)
dst.Samples = samplesDst[len(samplesDst)-len(src.Samples):]
// Copy labels
for i := range labelsSrc {
dstLabel := &labelsDst[i]
srcLabel := &labelsSrc[i]
wr.samples = samplesDst
wr.labels = labelsDst
bufLen := len(buf)
buf = append(buf, srcLabel.Name...)
dstLabel.Name = bytesutil.ToUnsafeString(buf[bufLen:])
bufLen = len(buf)
buf = append(buf, srcLabel.Value...)
dstLabel.Value = bytesutil.ToUnsafeString(buf[bufLen:])
}
wr.buf = buf
dst.Labels = labelsDst
// Copy samples
samplesLen := len(wr.samples)
wr.samples = append(wr.samples, src.Samples...)
dst.Samples = wr.samples[samplesLen:]
}
// marshalConcurrency limits the maximum number of concurrent workers, which marshal and compress WriteRequest.

View File

@@ -0,0 +1,8 @@
ARG base_image=non-existing
FROM $base_image
EXPOSE 8880
ENTRYPOINT ["/vmalert-tool-prod"]
ARG src_binary=non-existing
COPY $src_binary ./vmalert-tool-prod

View File

@@ -35,6 +35,8 @@ type promResponse struct {
Stats struct {
SeriesFetched *string `json:"seriesFetched,omitempty"`
} `json:"stats,omitempty"`
// IsPartial supported by VictoriaMetrics
IsPartial *bool `json:"isPartial,omitempty"`
}
// see https://prometheus.io/docs/prometheus/latest/querying/api/#instant-queries
@@ -209,7 +211,7 @@ func parsePrometheusResponse(req *http.Request, resp *http.Response) (res Result
if err != nil {
return res, err
}
res = Result{Data: ms}
res = Result{Data: ms, IsPartial: r.IsPartial}
if r.Stats.SeriesFetched != nil {
intV, err := strconv.Atoi(*r.Stats.SeriesFetched)
if err != nil {

View File

@@ -72,12 +72,14 @@ func TestVMInstantQuery(t *testing.T) {
w.Write([]byte(`{"status":"success","data":{"resultType":"scalar","result":[1583786142, "1"]}}`))
case 7:
w.Write([]byte(`{"status":"success","data":{"resultType":"scalar","result":[1583786142, "1"]},"stats":{"seriesFetched": "42"}}`))
case 8:
w.Write([]byte(`{"status":"success", "isPartial":true, "data":{"resultType":"scalar","result":[1583786142, "1"]}}`))
}
})
mux.HandleFunc("/render", func(w http.ResponseWriter, _ *http.Request) {
c++
switch c {
case 8:
case 9:
w.Write([]byte(`[{"target":"constantLine(10)","tags":{"name":"constantLine(10)"},"datapoints":[[10,1611758343],[10,1611758373],[10,1611758403]]}]`))
}
})
@@ -100,9 +102,9 @@ func TestVMInstantQuery(t *testing.T) {
t.Fatalf("failed to parse 'time' query param %q: %s", timeParam, err)
}
switch c {
case 9:
w.Write([]byte("[]"))
case 10:
w.Write([]byte("[]"))
case 11:
w.Write([]byte(`{"status":"success","data":{"resultType":"vector","result":[{"metric":{"__name__":"total","foo":"bar"},"value":[1583786142,"13763"]},{"metric":{"__name__":"total","foo":"baz"},"value":[1583786140,"2000"]}]}}`))
}
})
@@ -203,10 +205,18 @@ func TestVMInstantQuery(t *testing.T) {
*res.SeriesFetched)
}
res, _, err = pq.Query(ctx, vmQuery, ts) // 8
if err != nil {
t.Fatalf("unexpected %s", err)
}
if res.IsPartial != nil && !*res.IsPartial {
t.Fatalf("unexpected metric isPartial want %+v", true)
}
// test graphite
gq := s.BuildWithParams(QuerierParams{DataSourceType: string(datasourceGraphite)})
res, _, err = gq.Query(ctx, queryRender, ts) // 8 - graphite
res, _, err = gq.Query(ctx, queryRender, ts) // 9 - graphite
if err != nil {
t.Fatalf("unexpected %s", err)
}
@@ -226,9 +236,9 @@ func TestVMInstantQuery(t *testing.T) {
vlogs := datasourceVLogs
pq = s.BuildWithParams(QuerierParams{DataSourceType: string(vlogs), EvaluationInterval: 15 * time.Second})
expErr(vlogsQuery, "error parsing response") // 9
expErr(vlogsQuery, "error parsing response") // 10
res, _, err = pq.Query(ctx, vlogsQuery, ts) // 10
res, _, err = pq.Query(ctx, vlogsQuery, ts) // 11
if err != nil {
t.Fatalf("unexpected %s", err)
}

View File

@@ -34,6 +34,9 @@ type Result struct {
// If nil, then this feature is not supported by the datasource.
// SeriesFetched is supported by VictoriaMetrics since v1.90.
SeriesFetched *int
// IsPartial is used by VictoriaMetrics to indicate
// whether response data is partial.
IsPartial *bool
}
// QuerierBuilder builds Querier with given params.

View File

@@ -10,8 +10,9 @@ import (
// FakeQuerier is a mock querier that return predefined results and error message
type FakeQuerier struct {
sync.Mutex
metrics []Metric
err error
metrics []Metric
err error
isPartial *bool
}
// SetErr sets query error message
@@ -21,11 +22,19 @@ func (fq *FakeQuerier) SetErr(err error) {
fq.Unlock()
}
// SetPartialResponse marks query response as partial
func (fq *FakeQuerier) SetPartialResponse(partial bool) {
fq.Lock()
fq.isPartial = &partial
fq.Unlock()
}
// Reset reset querier's error message and results
func (fq *FakeQuerier) Reset() {
fq.Lock()
fq.err = nil
fq.metrics = fq.metrics[:0]
fq.isPartial = nil
fq.Unlock()
}
@@ -57,7 +66,7 @@ func (fq *FakeQuerier) Query(_ context.Context, _ string, _ time.Time) (Result,
cp := make([]Metric, len(fq.metrics))
copy(cp, fq.metrics)
req, _ := http.NewRequest(http.MethodPost, "foo.com", nil)
return Result{Data: cp}, req, nil
return Result{Data: cp, IsPartial: fq.isPartial}, req, nil
}
// FakeQuerierWithRegistry can store different results for different query expr

View File

@@ -11,7 +11,6 @@ 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/netutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promauth"
)
@@ -83,11 +82,10 @@ func Init(extraParams url.Values) (QuerierBuilder, error) {
if err := httputil.CheckURL(*addr); err != nil {
return nil, fmt.Errorf("invalid -datasource.url: %w", err)
}
tr, err := promauth.NewTLSTransport(*tlsCertFile, *tlsKeyFile, *tlsCAFile, *tlsServerName, *tlsInsecureSkipVerify)
tr, err := promauth.NewTLSTransport(*tlsCertFile, *tlsKeyFile, *tlsCAFile, *tlsServerName, *tlsInsecureSkipVerify, "vmalert_datasource")
if err != nil {
return nil, fmt.Errorf("failed to create transport for -datasource.url=%q: %w", *addr, err)
}
tr.DialContext = netutil.NewStatDialFunc("vmalert_datasource")
tr.DisableKeepAlives = *disableKeepAlive
tr.MaxIdleConnsPerHost = *maxIdleConnections
if tr.MaxIdleConns != 0 && tr.MaxIdleConns < tr.MaxIdleConnsPerHost {

View File

@@ -161,7 +161,7 @@ func NewAlertManager(alertManagerURL string, fn AlertURLGenerator, authCfg proma
if authCfg.TLSConfig != nil {
tls = authCfg.TLSConfig
}
tr, err := promauth.NewTLSTransport(tls.CertFile, tls.KeyFile, tls.CAFile, tls.ServerName, tls.InsecureSkipVerify)
tr, err := promauth.NewTLSTransport(tls.CertFile, tls.KeyFile, tls.CAFile, tls.ServerName, tls.InsecureSkipVerify, "vmalert_notifier")
if err != nil {
return nil, fmt.Errorf("failed to create transport for alertmanager URL=%q: %w", alertManagerURL, err)
@@ -198,8 +198,10 @@ func NewAlertManager(alertManagerURL string, fn AlertURLGenerator, authCfg proma
argFunc: fn,
authCfg: aCfg,
relabelConfigs: relabelCfg,
client: &http.Client{Transport: tr},
timeout: timeout,
metrics: newNotifierMetrics(alertManagerURL),
client: &http.Client{
Transport: tr,
},
timeout: timeout,
metrics: newNotifierMetrics(alertManagerURL),
}, nil
}

View File

@@ -10,7 +10,6 @@ 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/netutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promauth"
)
@@ -70,12 +69,11 @@ func Init() (datasource.QuerierBuilder, error) {
if err := httputil.CheckURL(*addr); err != nil {
return nil, fmt.Errorf("invalid -remoteRead.url: %w", err)
}
tr, err := promauth.NewTLSTransport(*tlsCertFile, *tlsKeyFile, *tlsCAFile, *tlsServerName, *tlsInsecureSkipVerify)
tr, err := promauth.NewTLSTransport(*tlsCertFile, *tlsKeyFile, *tlsCAFile, *tlsServerName, *tlsInsecureSkipVerify, "vmalert_remoteread")
if err != nil {
return nil, fmt.Errorf("failed to create transport for -remoteRead.url=%q: %w", *addr, err)
}
tr.IdleConnTimeout = *idleConnectionTimeout
tr.DialContext = netutil.NewStatDialFunc("vmalert_remoteread")
endpointParams, err := flagutil.ParseJSONMap(*oauth2EndpointParams)
if err != nil {

View File

@@ -93,7 +93,7 @@ func NewClient(ctx context.Context, cfg Config) (*Client, error) {
cfg.FlushInterval = defaultFlushInterval
}
if cfg.Transport == nil {
cfg.Transport = httputil.NewTransport(false)
cfg.Transport = httputil.NewTransport(false, "vmalert_remotewrite")
}
cc := defaultConcurrency
if cfg.Concurrency > 0 {

View File

@@ -33,7 +33,7 @@ func NewDebugClient() (*DebugClient, error) {
if err := httputil.CheckURL(*addr); err != nil {
return nil, fmt.Errorf("invalid -remoteWrite.url: %w", err)
}
tr, err := promauth.NewTLSTransport(*tlsCertFile, *tlsKeyFile, *tlsCAFile, *tlsServerName, *tlsInsecureSkipVerify)
tr, err := promauth.NewTLSTransport(*tlsCertFile, *tlsKeyFile, *tlsCAFile, *tlsServerName, *tlsInsecureSkipVerify, "vmalert_remotewrite_debug")
if err != nil {
return nil, fmt.Errorf("failed to create transport for -remoteWrite.url=%q: %w", *addr, err)
}

View File

@@ -9,7 +9,6 @@ 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/netutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promauth"
)
@@ -73,12 +72,11 @@ func Init(ctx context.Context) (*Client, error) {
if err := httputil.CheckURL(*addr); err != nil {
return nil, fmt.Errorf("invalid -remoteWrite.url: %w", err)
}
tr, err := promauth.NewTLSTransport(*tlsCertFile, *tlsKeyFile, *tlsCAFile, *tlsServerName, *tlsInsecureSkipVerify)
tr, err := promauth.NewTLSTransport(*tlsCertFile, *tlsKeyFile, *tlsCAFile, *tlsServerName, *tlsInsecureSkipVerify, "vmalert_remotewrite")
if err != nil {
return nil, fmt.Errorf("failed to create transport for -remoteWrite.url=%q: %w", *addr, err)
}
tr.IdleConnTimeout = *idleConnectionTimeout
tr.DialContext = netutil.NewStatDialFunc("vmalert_remotewrite")
endpointParams, err := flagutil.ParseJSONMap(*oauth2EndpointParams)
if err != nil {

View File

@@ -207,8 +207,8 @@ func (ar *AlertingRule) logDebugf(at time.Time, a *notifier.Alert, format string
if !ar.Debug {
return
}
prefix := fmt.Sprintf("DEBUG rule %q:%q (%d) at %v: ",
ar.GroupName, ar.Name, ar.RuleID, at.Format(time.RFC3339))
prefix := fmt.Sprintf("DEBUG alerting rule %q, %q:%q (%d) at %v: ",
ar.File, ar.GroupName, ar.Name, ar.RuleID, at.Format(time.RFC3339))
if a != nil {
labelKeys := make([]string, len(a.Labels))
@@ -408,8 +408,8 @@ func (ar *AlertingRule) exec(ctx context.Context, ts time.Time, limit int) ([]pr
if err != nil {
return nil, fmt.Errorf("failed to execute query %q: %w", ar.Expr, err)
}
ar.logDebugf(ts, nil, "query returned %d samples (elapsed: %s)", curState.Samples, curState.Duration)
ar.logDebugf(ts, nil, "query returned %d samples (elapsed: %s, isPartial: %t)", curState.Samples, curState.Duration, isPartialResponse(res))
qFn := func(query string) ([]datasource.Metric, error) {
res, _, err := ar.q.Query(ctx, query, ts)
return res.Data, err

View File

@@ -22,6 +22,32 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutil"
)
func TestNewAlertingRule(t *testing.T) {
f := func(group *Group, rule config.Rule, expectRule *AlertingRule) {
t.Helper()
r := NewAlertingRule(&datasource.FakeQuerier{}, group, rule)
if err := CompareRules(t, expectRule, r); err != nil {
t.Fatalf("unexpected rule mismatch: %s", err)
}
}
f(&Group{Name: "foo"},
config.Rule{
Alert: "health",
Expr: "up == 0",
Labels: map[string]string{
"foo": "bar",
},
}, &AlertingRule{
Name: "health",
Expr: "up == 0",
Labels: map[string]string{
"foo": "bar",
},
})
}
func TestAlertingRuleToTimeSeries(t *testing.T) {
timestamp := time.Now()
@@ -1322,3 +1348,23 @@ func TestAlertingRule_ToLabels(t *testing.T) {
t.Fatalf("processed labels mismatch, got: %v, want: %v", ls.processed, expectedProcessedLabels)
}
}
func TestAlertingRuleExec_Partial(t *testing.T) {
fq := &datasource.FakeQuerier{}
fq.Add(metricWithValueAndLabels(t, 10, "__name__", "bar"))
fq.SetPartialResponse(true)
ar := newTestAlertingRule("test", 0)
ar.Debug = true
ar.Labels = map[string]string{"job": "test"}
ar.q = fq
ar.For = time.Second
fq.Add(metricWithValueAndLabels(t, 1, "__name__", "foo", "job", "bar"))
ts := time.Now()
_, err := ar.exec(context.TODO(), ts, 0)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
}

View File

@@ -143,6 +143,7 @@ func TestUpdateWith(t *testing.T) {
}}, []config.Rule{{
Record: "foo",
Expr: "min(up)",
Debug: true,
Labels: map[string]string{
"baz": "bar",
},

View File

@@ -30,6 +30,7 @@ type RecordingRule struct {
GroupID uint64
GroupName string
File string
Debug bool
q datasource.Querier
@@ -91,12 +92,14 @@ func NewRecordingRule(qb datasource.QuerierBuilder, group *Group, cfg config.Rul
GroupID: group.GetID(),
GroupName: group.Name,
File: group.File,
Debug: cfg.Debug,
q: qb.BuildWithParams(datasource.QuerierParams{
DataSourceType: group.Type.String(),
ApplyIntervalAsTimeFilter: setIntervalAsTimeFilter(group.Type.String(), cfg.Expr),
EvaluationInterval: group.Interval,
QueryParams: group.Params,
Headers: group.Headers,
Debug: cfg.Debug,
}),
}
@@ -169,6 +172,8 @@ func (rr *RecordingRule) exec(ctx context.Context, ts time.Time, limit int) ([]p
return nil, curState.Err
}
rr.logDebugf(ts, "query returned %d samples (elapsed: %s, isPartial: %t)", curState.Samples, curState.Duration, isPartialResponse(res))
qMetrics := res.Data
numSeries := len(qMetrics)
if limit > 0 && numSeries > limit {
@@ -202,6 +207,17 @@ func (rr *RecordingRule) exec(ctx context.Context, ts time.Time, limit int) ([]p
return tss, nil
}
func (rr *RecordingRule) logDebugf(at time.Time, format string, args ...any) {
if !rr.Debug {
return
}
prefix := fmt.Sprintf("DEBUG recording rule %q, %q:%q (%d) at %v: ",
rr.File, rr.GroupName, rr.Name, rr.RuleID, at.Format(time.RFC3339))
msg := fmt.Sprintf(format, args...)
logger.Infof("%s", prefix+msg)
}
func stringToLabels(s string) []prompbmarshal.Label {
labels := strings.Split(s, ",")
rLabels := make([]prompbmarshal.Label, 0, len(labels))

View File

@@ -9,11 +9,34 @@ import (
"github.com/VictoriaMetrics/metrics"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/config"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/datasource"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/decimal"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
)
func TestNewRecordingRule(t *testing.T) {
f := func(group *Group, rule config.Rule, expectRule *AlertingRule) {
t.Helper()
r := NewAlertingRule(&datasource.FakeQuerier{}, group, rule)
if err := CompareRules(t, expectRule, r); err != nil {
t.Fatalf("unexpected rule mismatch: %s", err)
}
}
f(&Group{Name: "foo"},
config.Rule{
Alert: "health",
Expr: "up == 0",
Labels: map[string]string{},
}, &AlertingRule{
Name: "health",
Expr: "up == 0",
Labels: map[string]string{},
})
}
func TestRecordingRule_Exec(t *testing.T) {
ts, _ := time.Parse(time.RFC3339, "2024-10-29T00:00:00Z")
const defaultStep = 5 * time.Millisecond
@@ -307,7 +330,8 @@ func TestRecordingRuleLimit_Failure(t *testing.T) {
fq := &datasource.FakeQuerier{}
fq.Add(testMetrics...)
rule := &RecordingRule{Name: "job:foo",
rule := &RecordingRule{
Name: "job:foo",
state: &ruleState{entries: make([]StateEntry, 10)},
Labels: map[string]string{
"source": "test_limit",
@@ -342,7 +366,8 @@ func TestRecordingRuleLimit_Success(t *testing.T) {
fq := &datasource.FakeQuerier{}
fq.Add(testMetrics...)
rule := &RecordingRule{Name: "job:foo",
rule := &RecordingRule{
Name: "job:foo",
state: &ruleState{entries: make([]StateEntry, 10)},
Labels: map[string]string{
"source": "test_limit",
@@ -421,3 +446,36 @@ func TestSetIntervalAsTimeFilter(t *testing.T) {
f(`error AND _time:5m | count()`, "vlogs", false)
f(`* | error AND _time:5m | count()`, "vlogs", false)
}
func TestRecordingRuleExec_Partial(t *testing.T) {
ts, _ := time.Parse(time.RFC3339, "2024-10-29T00:00:00Z")
fq := &datasource.FakeQuerier{}
m := metricWithValueAndLabels(t, 10, "__name__", "bar")
fq.Add(m)
fq.SetPartialResponse(true)
rule := &RecordingRule{
GroupName: "Bar",
Name: "foo",
state: &ruleState{
entries: make([]StateEntry, 10),
},
}
rule.Debug = true
rule.q = fq
got, err := rule.exec(context.TODO(), ts, 0)
want := []prompbmarshal.TimeSeries{
newTimeSeries([]float64{10}, []int64{ts.UnixNano()}, []prompbmarshal.Label{
{
Name: "__name__",
Value: "foo",
},
}),
}
if err != nil {
t.Fatalf("fail to test rule %s: unexpected error: %s", rule.Name, err)
}
if err := compareTimeSeries(t, want, got); err != nil {
t.Fatalf("fail to test rule %s: time series mismatch: %s", rule.Name, err)
}
}

View File

@@ -38,6 +38,9 @@ func compareRecordingRules(t *testing.T, a, b *RecordingRule) error {
if a.Expr != b.Expr {
return fmt.Errorf("expected to have expression %q; got %q", a.Expr, b.Expr)
}
if a.Debug != b.Debug {
return fmt.Errorf("expected to have debug=%t; got %t", a.Debug, b.Debug)
}
if !reflect.DeepEqual(a.Labels, b.Labels) {
return fmt.Errorf("expected to have labels %#v; got %#v", a.Labels, b.Labels)
}
@@ -64,6 +67,9 @@ func compareAlertingRules(t *testing.T, a, b *AlertingRule) error {
if a.Type.String() != b.Type.String() {
return fmt.Errorf("expected to have Type %#v; got %#v", a.Type.String(), b.Type.String())
}
if a.Debug != b.Debug {
return fmt.Errorf("expected to have debug=%t; got %t", a.Debug, b.Debug)
}
return nil
}

View File

@@ -105,3 +105,10 @@ func isSecreteHeader(str string) bool {
}
return false
}
func isPartialResponse(res datasource.Result) bool {
if res.IsPartial != nil && *res.IsPartial {
return true
}
return false
}

View File

@@ -319,7 +319,7 @@ func tryProcessingRequest(w http.ResponseWriter, r *http.Request, targetURL *url
// Timed out request must be counted as errors, since this usually means that the backend is slow.
ui.backendErrors.Inc()
}
return true, false
return false, false
}
if !rtbOK || !rtb.canRetry() {
// Request body cannot be re-sent to another backend. Return the error to the client then.
@@ -508,7 +508,7 @@ func newRoundTripper(caFileOpt, certFileOpt, keyFileOpt, serverNameOpt string, i
return nil, fmt.Errorf("cannot initialize promauth.Config: %w", err)
}
tr := httputil.NewTransport(false)
tr := httputil.NewTransport(false, "vmauth_backend")
tr.ResponseHeaderTimeout = *responseTimeout
// Automatic compression must be disabled in order to fix https://github.com/VictoriaMetrics/VictoriaMetrics/issues/535
tr.DisableCompression = true
@@ -517,7 +517,6 @@ func newRoundTripper(caFileOpt, certFileOpt, keyFileOpt, serverNameOpt string, i
if tr.MaxIdleConns != 0 && tr.MaxIdleConns < tr.MaxIdleConnsPerHost {
tr.MaxIdleConns = tr.MaxIdleConnsPerHost
}
tr.DialContext = netutil.NewStatDialFunc("vmauth_backend")
rt := cfg.NewRoundTripper(tr)
return rt, nil

View File

@@ -33,7 +33,8 @@ var (
"All created snapshots will be automatically deleted. Example: http://victoriametrics:8428/snapshot/delete")
dst = flag.String("dst", "", "Where to put the backup on the remote storage. "+
"Example: gs://bucket/path/to/backup, s3://bucket/path/to/backup, azblob://container/path/to/backup or fs:///path/to/local/backup/dir\n"+
"-dst can point to the previous backup. In this case incremental backup is performed, i.e. only changed data is uploaded")
"-dst can point to the previous backup. In this case incremental backup is performed, i.e. only changed data is uploaded\n"+
"Note: If custom S3 endpoint is used, URL should contain only name of the bucket, while hostname of S3 server must be specified via the -customS3Endpoint command-line flag.")
origin = flag.String("origin", "", "Optional origin directory on the remote storage with old backup for server-side copying when performing full backup. This speeds up full backups")
concurrency = flag.Int("concurrency", 10, "The number of concurrent workers. Higher concurrency may reduce backup duration")
maxBytesPerSecond = flagutil.NewBytes("maxBytesPerSecond", 0, "The maximum upload speed. There is no limit if it is set to 0")

View File

@@ -70,7 +70,7 @@ func main() {
return fmt.Errorf("invalid -%s: %w", otsdbAddr, err)
}
tr, err := promauth.NewTLSTransport(certFile, keyFile, caFile, serverName, insecureSkipVerify)
tr, err := promauth.NewTLSTransport(certFile, keyFile, caFile, serverName, insecureSkipVerify, "vmctl_opentsdb")
if err != nil {
return fmt.Errorf("failed to create transport for -%s=%q: %s", otsdbAddr, addr, err)
}
@@ -185,7 +185,7 @@ func main() {
serverName := c.String(remoteReadServerName)
insecureSkipVerify := c.Bool(remoteReadInsecureSkipVerify)
tr, err := promauth.NewTLSTransport(certFile, keyFile, caFile, serverName, insecureSkipVerify)
tr, err := promauth.NewTLSTransport(certFile, keyFile, caFile, serverName, insecureSkipVerify, "vmctl_remoteread")
if err != nil {
return fmt.Errorf("failed to create transport for -%s=%q: %s", remoteReadSrcAddr, addr, err)
}
@@ -315,7 +315,7 @@ func main() {
return fmt.Errorf("failed to create TLS Config: %s", err)
}
trSrc := httputil.NewTransport(false)
trSrc := httputil.NewTransport(false, "vmctl_src")
trSrc.DisableKeepAlives = disableKeepAlive
trSrc.TLSClientConfig = srcTC
@@ -345,7 +345,7 @@ func main() {
return fmt.Errorf("failed to create TLS Config: %s", err)
}
trDst := httputil.NewTransport(false)
trDst := httputil.NewTransport(false, "vmctl_dst")
trDst.DisableKeepAlives = disableKeepAlive
trDst.TLSClientConfig = dstTC
@@ -455,7 +455,7 @@ func initConfigVM(c *cli.Context) (vm.Config, error) {
serverName := c.String(vmServerName)
insecureSkipVerify := c.Bool(vmInsecureSkipVerify)
tr, err := promauth.NewTLSTransport(certFile, keyFile, caFile, serverName, insecureSkipVerify)
tr, err := promauth.NewTLSTransport(certFile, keyFile, caFile, serverName, insecureSkipVerify, "vmctl_client")
if err != nil {
return vm.Config{}, fmt.Errorf("failed to create transport for -%s=%q: %s", vmAddr, addr, err)
}

View File

@@ -43,60 +43,12 @@ func (pp *prometheusProcessor) run() error {
return nil
}
bar := barpool.AddWithTemplate(fmt.Sprintf(barTpl, "Processing blocks"), len(blocks))
if err := barpool.Start(); err != nil {
return err
}
defer barpool.Stop()
blockReadersCh := make(chan tsdb.BlockReader)
errCh := make(chan error, pp.cc)
pp.im.ResetStats()
var wg sync.WaitGroup
wg.Add(pp.cc)
for i := 0; i < pp.cc; i++ {
go func() {
defer wg.Done()
for br := range blockReadersCh {
if err := pp.do(br); err != nil {
errCh <- fmt.Errorf("read failed for block %q: %s", br.Meta().ULID, err)
return
}
bar.Increment()
}
}()
}
// any error breaks the import
for _, br := range blocks {
select {
case promErr := <-errCh:
close(blockReadersCh)
return fmt.Errorf("prometheus error: %s", promErr)
case vmErr := <-pp.im.Errors():
close(blockReadersCh)
return fmt.Errorf("import process failed: %s", wrapErr(vmErr, pp.isVerbose))
case blockReadersCh <- br:
}
}
close(blockReadersCh)
wg.Wait()
// wait for all buffers to flush
pp.im.Close()
close(errCh)
// drain import errors channel
for vmErr := range pp.im.Errors() {
if vmErr.Err != nil {
return fmt.Errorf("import process failed: %s", wrapErr(vmErr, pp.isVerbose))
}
}
for err := range errCh {
return fmt.Errorf("import process failed: %s", err)
if err := pp.processBlocks(blocks); err != nil {
return fmt.Errorf("migration failed: %s", err)
}
log.Println("Import finished!")
log.Print(pp.im.Stats())
log.Println(pp.im.Stats())
return nil
}
@@ -156,3 +108,59 @@ func (pp *prometheusProcessor) do(b tsdb.BlockReader) error {
}
return ss.Err()
}
func (pp *prometheusProcessor) processBlocks(blocks []tsdb.BlockReader) error {
bar := barpool.AddWithTemplate(fmt.Sprintf(barTpl, "Processing blocks"), len(blocks))
if err := barpool.Start(); err != nil {
return err
}
defer barpool.Stop()
blockReadersCh := make(chan tsdb.BlockReader)
errCh := make(chan error, pp.cc)
pp.im.ResetStats()
var wg sync.WaitGroup
wg.Add(pp.cc)
for i := 0; i < pp.cc; i++ {
go func() {
defer wg.Done()
for br := range blockReadersCh {
if err := pp.do(br); err != nil {
errCh <- fmt.Errorf("read failed for block %q: %s", br.Meta().ULID, err)
return
}
bar.Increment()
}
}()
}
// any error breaks the import
for _, br := range blocks {
select {
case promErr := <-errCh:
close(blockReadersCh)
return fmt.Errorf("prometheus error: %s", promErr)
case vmErr := <-pp.im.Errors():
close(blockReadersCh)
return fmt.Errorf("import process failed: %s", wrapErr(vmErr, pp.isVerbose))
case blockReadersCh <- br:
}
}
close(blockReadersCh)
wg.Wait()
// wait for all buffers to flush
pp.im.Close()
close(errCh)
// drain import errors channel
for vmErr := range pp.im.Errors() {
if vmErr.Err != nil {
return fmt.Errorf("import process failed: %s", wrapErr(vmErr, pp.isVerbose))
}
}
for err := range errCh {
return fmt.Errorf("import process failed: %s", err)
}
return nil
}

View File

@@ -2,167 +2,214 @@ package main
import (
"context"
"fmt"
"log"
"os"
"testing"
"time"
"github.com/prometheus/prometheus/model/labels"
"github.com/prometheus/prometheus/storage"
"github.com/prometheus/prometheus/tsdb"
"github.com/prometheus/prometheus/tsdb/chunkenc"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmctl/backoff"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmctl/barpool"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmctl/prometheus"
remote_read_integration "github.com/VictoriaMetrics/VictoriaMetrics/app/vmctl/testdata/servers_integration_test"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmctl/vm"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmselect/promql"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmstorage"
)
// If you want to run this test:
// 1. provide test snapshot path in const testSnapshot
// 2. define httpAddr const with your victoriametrics address
// 3. run victoria metrics with defined address
// 4. remove t.Skip() from Test_prometheusProcessor_run
// 5. run tests one by one not all at one time
const (
httpAddr = "http://127.0.0.1:8428/"
testSnapshot = "./testdata/20220427T130947Z-70ba49b1093fd0bf"
testSnapshot = "./testdata/snapshots/20250118T124506Z-59d1b952d7eaf547"
blockData = "./testdata/snapshots/20250118T124506Z-59d1b952d7eaf547/01JHWQ445Y2P1TDYB05AEKD6MC"
)
// This test simulates close process if user abort it
func TestPrometheusProcessorRun(t *testing.T) {
t.Skip()
defer func() { isSilent = false }()
f := func(startStr, endStr string, numOfSeries int, resultExpected []vm.TimeSeries) {
t.Helper()
type fields struct {
cfg prometheus.Config
vmCfg vm.Config
cl func(prometheus.Config) *prometheus.Client
im func(vm.Config) *vm.Importer
closer func(importer *vm.Importer)
cc int
}
type args struct {
silent bool
verbose bool
}
dst := remote_read_integration.NewRemoteWriteServer(t)
tests := []struct {
name string
fields fields
args args
wantErr bool
}{
{
name: "simulate syscall.SIGINT",
fields: fields{
cfg: prometheus.Config{
Snapshot: testSnapshot,
Filter: prometheus.Filter{},
},
cl: func(cfg prometheus.Config) *prometheus.Client {
client, err := prometheus.NewClient(cfg)
if err != nil {
t.Fatalf("error init prometeus client: %s", err)
}
return client
},
im: func(vmCfg vm.Config) *vm.Importer {
importer, err := vm.NewImporter(context.Background(), vmCfg)
if err != nil {
t.Fatalf("error init importer: %s", err)
}
return importer
},
closer: func(importer *vm.Importer) {
// simulate syscall.SIGINT
time.Sleep(time.Second * 5)
if importer != nil {
importer.Close()
}
},
vmCfg: vm.Config{Addr: httpAddr, Concurrency: 1},
cc: 2,
},
args: args{
silent: false,
verbose: false,
},
wantErr: true,
},
{
name: "simulate correct work",
fields: fields{
cfg: prometheus.Config{
Snapshot: testSnapshot,
Filter: prometheus.Filter{},
},
cl: func(cfg prometheus.Config) *prometheus.Client {
client, err := prometheus.NewClient(cfg)
if err != nil {
t.Fatalf("error init prometeus client: %s", err)
}
return client
},
im: func(vmCfg vm.Config) *vm.Importer {
importer, err := vm.NewImporter(context.Background(), vmCfg)
if err != nil {
t.Fatalf("error init importer: %s", err)
}
return importer
},
closer: nil,
vmCfg: vm.Config{Addr: httpAddr, Concurrency: 5},
cc: 2,
},
args: args{
silent: true,
verbose: false,
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
client := tt.fields.cl(tt.fields.cfg)
importer := tt.fields.im(tt.fields.vmCfg)
isSilent = tt.args.silent
pp := &prometheusProcessor{
cl: client,
im: importer,
cc: tt.fields.cc,
isVerbose: tt.args.verbose,
}
defer func() {
dst.Close()
}()
// we should answer on prompt
if !tt.args.silent {
input := []byte("Y\n")
dst.Series(resultExpected)
dst.ExpectedSeries(resultExpected)
r, w, err := os.Pipe()
if err != nil {
t.Fatal(err)
}
if err := fillStorage(resultExpected); err != nil {
t.Fatalf("cannot fill storage: %s", err)
}
_, err = w.Write(input)
if err != nil {
t.Fatalf("cannot send 'Y' to importer: %s", err)
}
err = w.Close()
if err != nil {
t.Fatalf("cannot close writer: %s", err)
}
isSilent = true
defer func() { isSilent = false }()
stdin := os.Stdin
// Restore stdin right after the test.
defer func() {
os.Stdin = stdin
_ = r.Close()
}()
os.Stdin = r
}
bf, err := backoff.New(1, 1.8, time.Second*2)
if err != nil {
t.Fatalf("cannot create backoff: %s", err)
}
// simulate close if needed
if tt.fields.closer != nil {
go tt.fields.closer(importer)
}
importerCfg := vm.Config{
Addr: dst.URL(),
Transport: nil,
Concurrency: 1,
Backoff: bf,
}
if err := pp.run(); (err != nil) != tt.wantErr {
t.Fatalf("run() error = %v, wantErr %v", err, tt.wantErr)
}
ctx := context.Background()
importer, err := vm.NewImporter(ctx, importerCfg)
if err != nil {
t.Fatalf("cannot create importer: %s", err)
}
defer importer.Close()
matchName := "__name__"
matchValue := ".*"
filter := prometheus.Filter{
TimeMin: startStr,
TimeMax: endStr,
Label: matchName,
LabelValue: matchValue,
}
runnner, err := prometheus.NewClient(prometheus.Config{
Snapshot: testSnapshot,
Filter: filter,
})
if err != nil {
t.Fatalf("cannot create prometheus client: %s", err)
}
p := &prometheusProcessor{
cl: runnner,
im: importer,
cc: 1,
}
if err := p.run(); err != nil {
t.Fatalf("run() error: %s", err)
}
collectedTs := dst.GetCollectedTimeSeries()
t.Logf("collected timeseries: %d; expected timeseries: %d", len(collectedTs), len(resultExpected))
if len(collectedTs) != len(resultExpected) {
t.Fatalf("unexpected number of collected time series; got %d; want %d", len(collectedTs), numOfSeries)
}
deleted, err := deleteSeries(matchName, matchValue)
if err != nil {
t.Fatalf("cannot delete series: %s", err)
}
if deleted != numOfSeries {
t.Fatalf("unexpected number of deleted series; got %d; want %d", deleted, numOfSeries)
}
}
processFlags()
vmstorage.Init(promql.ResetRollupResultCacheIfNeeded)
defer func() {
vmstorage.Stop()
if err := os.RemoveAll(storagePath); err != nil {
log.Fatalf("cannot remove %q: %s", storagePath, err)
}
}()
barpool.Disable(true)
defer func() {
barpool.Disable(false)
}()
b, err := tsdb.OpenBlock(nil, blockData, nil, nil)
if err != nil {
t.Fatalf("cannot open block: %s", err)
}
// timestamp is equal to minTime and maxTime from meta.json
ss, err := readBlock(b, 1737204082361, 1737204302539)
if err != nil {
t.Fatalf("cannot read block: %s", err)
}
resultExpected, err := prepareExpectedData(ss)
if err != nil {
t.Fatalf("cannot prepare expected data: %s", err)
}
f("2025-01-18T12:40:00Z", "2025-01-18T12:46:00Z", 2792, resultExpected)
}
func readBlock(b tsdb.BlockReader, timeMin int64, timeMax int64) (storage.SeriesSet, error) {
minTime, maxTime := b.Meta().MinTime, b.Meta().MaxTime
if timeMin != 0 {
minTime = timeMin
}
if timeMax != 0 {
maxTime = timeMax
}
q, err := tsdb.NewBlockQuerier(b, minTime, maxTime)
if err != nil {
return nil, err
}
matchName := "__name__"
matchValue := ".*"
ctx := context.Background()
ss := q.Select(ctx, false, nil, labels.MustNewMatcher(labels.MatchRegexp, matchName, matchValue))
return ss, nil
}
func prepareExpectedData(ss storage.SeriesSet) ([]vm.TimeSeries, error) {
var expectedSeriesSet []vm.TimeSeries
var it chunkenc.Iterator
for ss.Next() {
var name string
var labelPairs []vm.LabelPair
series := ss.At()
for _, label := range series.Labels() {
if label.Name == "__name__" {
name = label.Value
continue
}
labelPairs = append(labelPairs, vm.LabelPair{
Name: label.Name,
Value: label.Value,
})
}
if name == "" {
return nil, fmt.Errorf("failed to find `__name__` label in labelset for block")
}
var timestamps []int64
var values []float64
it = series.Iterator(it)
for {
typ := it.Next()
if typ == chunkenc.ValNone {
break
}
if typ != chunkenc.ValFloat {
// Skip unsupported values
continue
}
t, v := it.At()
timestamps = append(timestamps, t)
values = append(values, v)
}
if err := it.Err(); err != nil {
return nil, err
}
ts := vm.TimeSeries{
Name: name,
LabelPairs: labelPairs,
Timestamps: timestamps,
Values: values,
}
expectedSeriesSet = append(expectedSeriesSet, ts)
}
return expectedSeriesSet, nil
}

View File

@@ -73,7 +73,7 @@ func TestRemoteRead(t *testing.T) {
vmCfg: vm.Config{
Addr: "",
Concurrency: 1,
Transport: httputil.NewTransport(false),
Transport: httputil.NewTransport(false, "vmctl_test_read"),
},
start: "2022-09-26T11:23:05+02:00",
end: "2022-11-26T11:24:05+02:00",

View File

@@ -41,6 +41,7 @@ type RemoteWriteServer struct {
server *httptest.Server
series []vm.TimeSeries
expectedSeries []vm.TimeSeries
tss []vm.TimeSeries
}
// NewRemoteWriteServer prepares test remote write server
@@ -73,6 +74,10 @@ func (rws *RemoteWriteServer) ExpectedSeries(series []vm.TimeSeries) {
rws.expectedSeries = append(rws.expectedSeries, series...)
}
func (rws *RemoteWriteServer) GetCollectedTimeSeries() []vm.TimeSeries {
return rws.tss
}
// URL returns server url
func (rws *RemoteWriteServer) URL() string {
return rws.server.URL
@@ -80,7 +85,6 @@ func (rws *RemoteWriteServer) URL() string {
func (rws *RemoteWriteServer) getWriteHandler(t *testing.T) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var tss []vm.TimeSeries
scanner := bufio.NewScanner(r.Body)
var rows parser.Rows
for scanner.Scan() {
@@ -102,17 +106,11 @@ func (rws *RemoteWriteServer) getWriteHandler(t *testing.T) http.Handler {
ts.Timestamps = append(ts.Timestamps, row.Timestamps...)
ts.Name = nameValue
ts.LabelPairs = labelPairs
tss = append(tss, ts)
rws.tss = append(rws.tss, ts)
}
rows.Reset()
}
if !reflect.DeepEqual(tss, rws.expectedSeries) {
w.WriteHeader(http.StatusInternalServerError)
t.Fatalf("datasets not equal, expected: %#v; \n got: %#v", rws.expectedSeries, tss)
return
}
w.WriteHeader(http.StatusNoContent)
return
})

View File

@@ -0,0 +1,17 @@
{
"ulid": "01JHWQ445Y2P1TDYB05AEKD6MC",
"minTime": 1737204082361,
"maxTime": 1737204302539,
"stats": {
"numSamples": 60275,
"numSeries": 2792,
"numChunks": 2792
},
"compaction": {
"level": 1,
"sources": [
"01JHWQ445Y2P1TDYB05AEKD6MC"
]
},
"version": 1
}

View File

@@ -23,8 +23,9 @@ import (
)
const (
storagePath = "TestStorage"
retentionPeriod = "100y"
storagePath = "TestStorage"
retentionPeriod = "100y"
deleteSeriesLimit = 3e3
)
func TestVMNativeProcessorRun(t *testing.T) {
@@ -66,7 +67,7 @@ func TestVMNativeProcessorRun(t *testing.T) {
t.Fatalf("cannot add series to storage: %s", err)
}
tr := httputil.NewTransport(false)
tr := httputil.NewTransport(false, "test_client")
tr.DisableKeepAlives = false
srcClient := &native.Client{
@@ -259,7 +260,7 @@ func deleteSeries(name, value string) (int, error) {
if err := tfs.Add([]byte(name), []byte(value), false, true); err != nil {
return 0, fmt.Errorf("unexpected error in TagFilters.Add: %w", err)
}
return vmstorage.DeleteSeries(nil, []*storage.TagFilters{tfs}, 1e3)
return vmstorage.DeleteSeries(nil, []*storage.TagFilters{tfs}, deleteSeriesLimit)
}
func TestBuildMatchWithFilter_Failure(t *testing.T) {

View File

@@ -320,8 +320,10 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
case "/prometheus/api/v1/targets", "/api/v1/targets":
promscrapeAPIV1TargetsRequests.Inc()
w.Header().Set("Content-Type", "application/json")
// https://prometheus.io/docs/prometheus/latest/querying/api/#targets
state := r.FormValue("state")
promscrape.WriteAPIV1Targets(w, state)
scrapePool := r.FormValue("scrapePool")
promscrape.WriteAPIV1Targets(w, state, scrapePool)
return true
case "/prometheus/target_response", "/target_response":
promscrapeTargetResponseRequests.Inc()

View File

@@ -20,7 +20,8 @@ import (
var (
httpListenAddr = flag.String("httpListenAddr", ":8421", "TCP address for exporting metrics at /metrics page")
src = flag.String("src", "", "Source path with backup on the remote storage. "+
"Example: gs://bucket/path/to/backup, s3://bucket/path/to/backup, azblob://container/path/to/backup or fs:///path/to/local/backup")
"Example: gs://bucket/path/to/backup, s3://bucket/path/to/backup, azblob://container/path/to/backup or fs:///path/to/local/backup\n"+
"Note: If custom S3 endpoint is used, URL should contain only name of the bucket, while hostname of S3 server must be specified via the -customS3Endpoint command-line flag.")
storageDataPath = flag.String("storageDataPath", "victoria-metrics-data", "Destination path where backup must be restored. "+
"VictoriaMetrics must be stopped when restoring from backup. -storageDataPath dir can be non-empty. In this case the contents of -storageDataPath dir "+
"is synchronized with -src contents, i.e. it works like 'rsync --delete'")

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -36,8 +36,8 @@
<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-Bc6E56oa.js"></script>
<link rel="modulepreload" crossorigin href="./assets/vendor-DojlIpLz.js">
<script type="module" crossorigin src="./assets/index-Clv2OTUl.js"></script>
<link rel="modulepreload" crossorigin href="./assets/vendor-PQqNLyna.js">
<link rel="stylesheet" crossorigin href="./assets/vendor-D1GxaB_c.css">
<link rel="stylesheet" crossorigin href="./assets/index-u4IOGr0E.css">
</head>

View File

@@ -336,13 +336,7 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
case "/create":
snapshotsCreateTotal.Inc()
w.Header().Set("Content-Type", "application/json")
snapshotPath, err := Storage.CreateSnapshot()
if err != nil {
err = fmt.Errorf("cannot create snapshot: %w", err)
jsonResponseError(w, err)
snapshotsCreateErrorsTotal.Inc()
return true
}
snapshotPath := Storage.MustCreateSnapshot()
if prometheusCompatibleResponse {
fmt.Fprintf(w, `{"status":"success","data":{"name":%s}}`, stringsutil.JSONString(snapshotPath))
} else {
@@ -352,13 +346,7 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
case "/list":
snapshotsListTotal.Inc()
w.Header().Set("Content-Type", "application/json")
snapshots, err := Storage.ListSnapshots()
if err != nil {
err = fmt.Errorf("cannot list snapshots: %w", err)
jsonResponseError(w, err)
snapshotsListErrorsTotal.Inc()
return true
}
snapshots := Storage.MustListSnapshots()
fmt.Fprintf(w, `{"status":"ok","snapshots":[`)
if len(snapshots) > 0 {
for _, snapshot := range snapshots[:len(snapshots)-1] {
@@ -373,13 +361,7 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
w.Header().Set("Content-Type", "application/json")
snapshotName := r.FormValue("snapshot")
snapshots, err := Storage.ListSnapshots()
if err != nil {
err = fmt.Errorf("cannot list snapshots: %w", err)
jsonResponseError(w, err)
snapshotsDeleteErrorsTotal.Inc()
return true
}
snapshots := Storage.MustListSnapshots()
for _, snName := range snapshots {
if snName == snapshotName {
if err := Storage.DeleteSnapshot(snName); err != nil {
@@ -393,19 +375,13 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
}
}
err = fmt.Errorf("cannot find snapshot %q", snapshotName)
err := fmt.Errorf("cannot find snapshot %q", snapshotName)
jsonResponseError(w, err)
return true
case "/delete_all":
snapshotsDeleteAllTotal.Inc()
w.Header().Set("Content-Type", "application/json")
snapshots, err := Storage.ListSnapshots()
if err != nil {
err = fmt.Errorf("cannot list snapshots: %w", err)
jsonResponseError(w, err)
snapshotsDeleteAllErrorsTotal.Inc()
return true
}
snapshots := Storage.MustListSnapshots()
for _, snapshotName := range snapshots {
if err := Storage.DeleteSnapshot(snapshotName); err != nil {
err = fmt.Errorf("cannot delete snapshot %q: %w", snapshotName, err)
@@ -439,10 +415,7 @@ func initStaleSnapshotsRemover(strg *storage.Storage) {
return
case <-t.C:
}
if err := strg.DeleteStaleSnapshots(snapshotsMaxAgeDur); err != nil {
// Use logger.Errorf instead of logger.Fatalf in the hope the error is temporary.
logger.Errorf("cannot delete stale snapshots: %s", err)
}
strg.MustDeleteStaleSnapshots(snapshotsMaxAgeDur)
}
}()
}
@@ -460,11 +433,9 @@ var (
var (
activeForceMerges = metrics.NewCounter("vm_active_force_merges")
snapshotsCreateTotal = metrics.NewCounter(`vm_http_requests_total{path="/snapshot/create"}`)
snapshotsCreateErrorsTotal = metrics.NewCounter(`vm_http_request_errors_total{path="/snapshot/create"}`)
snapshotsCreateTotal = metrics.NewCounter(`vm_http_requests_total{path="/snapshot/create"}`)
snapshotsListTotal = metrics.NewCounter(`vm_http_requests_total{path="/snapshot/list"}`)
snapshotsListErrorsTotal = metrics.NewCounter(`vm_http_request_errors_total{path="/snapshot/list"}`)
snapshotsListTotal = metrics.NewCounter(`vm_http_requests_total{path="/snapshot/list"}`)
snapshotsDeleteTotal = metrics.NewCounter(`vm_http_requests_total{path="/snapshot/delete"}`)
snapshotsDeleteErrorsTotal = metrics.NewCounter(`vm_http_request_errors_total{path="/snapshot/delete"}`)

View File

@@ -1,4 +1,4 @@
FROM golang:1.24.1 AS build-web-stage
FROM golang:1.24.2 AS build-web-stage
COPY build /build
WORKDIR /build

View File

@@ -1,7 +1,7 @@
# All these commands must run from repository root.
copy-metricsql-docs:
cp docs/MetricsQL.md app/vmui/packages/vmui/src/assets/MetricsQL.md
cp docs/victoriametrics/MetricsQL.md app/vmui/packages/vmui/src/assets/MetricsQL.md
vmui-package-base-image:
docker build -t vmui-builder-image -f app/vmui/Dockerfile-build ./app/vmui

View File

@@ -6,6 +6,7 @@ import useGetMetricsQL from "../../../hooks/useGetMetricsQL";
import { QueryContextType } from "../../../types";
import { AUTOCOMPLETE_LIMITS } from "../../../constants/queryAutocomplete";
import { QueryEditorAutocompleteProps } from "./QueryEditor";
import { getExprLastPart, getValueByContext, getContext } from "./autocompleteUtils";
const QueryEditorAutocomplete: FC<QueryEditorAutocompleteProps> = ({
value,
@@ -26,11 +27,7 @@ const QueryEditorAutocomplete: FC<QueryEditorAutocompleteProps> = ({
return { beforeCursor, afterCursor };
}, [value, caretPosition]);
const exprLastPart = useMemo(() => {
const regexpSplit = /\s(or|and|unless|default|ifnot|if|group_left|group_right)\s|}|\+|\|-|\*|\/|\^/i;
const parts = values.beforeCursor.split(regexpSplit);
return parts[parts.length - 1];
}, [values]);
const exprLastPart = useMemo(() => getExprLastPart(values.beforeCursor), [values]);
const metric = useMemo(() => {
const regex1 = /\w+\((?<metricName>[^)]+)\)\s+(by|without|on|ignoring)\s*\(\w*/gi;
@@ -54,44 +51,9 @@ const QueryEditorAutocomplete: FC<QueryEditorAutocompleteProps> = ({
return match ? match[match.length - 1] : "";
}, [exprLastPart]);
const shouldSuppressAutoSuggestion = (value: string) => {
const pattern = /([{(),+\-*/^]|\b(?:or|and|unless|default|ifnot|if|group_left|group_right|by|without|on|ignoring)\b)/i;
const parts = value.split(/\s+/);
const partsCount = parts.length;
const lastPart = parts[partsCount - 1];
const preLastPart = parts[partsCount - 2];
const context = useMemo(() => getContext(values.beforeCursor, metric, label), [values, metric, label]);
const hasEmptyPartAndQuotes = !lastPart && hasUnclosedQuotes(value);
const suppressPreLast = (!lastPart || parts.length > 1) && !pattern.test(preLastPart);
return hasEmptyPartAndQuotes || suppressPreLast;
};
const context = useMemo(() => {
const valueBeforeCursor = values.beforeCursor.trim();
const endOfClosedBrackets = ["}", ")"].some(char => valueBeforeCursor.endsWith(char));
const endOfClosedQuotes = !hasUnclosedQuotes(valueBeforeCursor) && ["`", "'", "\""].some(char => valueBeforeCursor.endsWith(char));
if (!values.beforeCursor || endOfClosedBrackets || endOfClosedQuotes || shouldSuppressAutoSuggestion(values.beforeCursor)) {
return QueryContextType.empty;
}
const labelRegexp = /(?:by|without|on|ignoring)\s*\(\s*[^)]*$|\{[^}]*$/i;
const patternLabelValue = `(${escapeRegexp(metric)})?{?.+${escapeRegexp(label)}(=|!=|=~|!~)"?([^"]*)$`;
const labelValueRegexp = new RegExp(patternLabelValue, "g");
switch (true) {
case labelValueRegexp.test(values.beforeCursor):
return QueryContextType.labelValue;
case labelRegexp.test(values.beforeCursor):
return QueryContextType.label;
default:
return QueryContextType.metricsql;
}
}, [values, metric, label]);
const valueByContext = useMemo(() => {
const wordMatch = values.beforeCursor.match(/([\w_.:]+(?![},]))$/);
return wordMatch ? wordMatch[0] : "";
}, [values.beforeCursor]);
const valueByContext = useMemo(() => getValueByContext(values.beforeCursor), [values.beforeCursor]);
const { metrics, labels, labelValues, loading } = useFetchQueryOptions({
valueByContext,

View File

@@ -0,0 +1,87 @@
import { describe, it, expect } from "vitest";
import { getContext, getExprLastPart, getValueByContext } from "./autocompleteUtils";
// Mock QueryContextType enum
const QueryContextType = {
empty: "empty",
metricsql: "metricsql",
label: "label",
labelValue: "labelValue",
};
describe("autocompleteUtils", () => {
describe("getExprLastPart", () => {
const tests = [
{ input: "", expected: "" },
{ input: "metric", expected: "metric" },
{ input: "metric1 + metric2", expected: "metric2" },
{ input: "rate(proc", expected: "proc" },
{ input: "sum(rate(proc", expected: "proc" },
{ input: "rate(metric{label=\"value\",", expected: "metric{label=\"value\"," },
{ input: "quantile_over_time(0.9, me", expected: "me" },
{ input: "quantile_over_time(0.9, metric{label=", expected: "metric{label=" },
{ input: "quantile_over_time(0.9, metric{label=\"value\",", expected: "metric{label=\"value\"," },
{ input: "quantile_over_time(0.9, metric{label=\"value\"}", expected: "" },
{ input: "sum by (instance) (rate(proc", expected: "proc" },
{ input: "metric{label=", expected: "metric{label=" },
{ input: "metric{label1=\"value1\",label2=", expected: "metric{label1=\"value1\",label2=" },
{ input: "quantile_over_time(0.9, node_cpu", expected: "node_cpu" },
{ input: "sum(max_over_time(rate(node_cpu", expected: "node_cpu" },
{ input: "clamp_min(metric1, m", expected: "m" },
];
tests.forEach(({ input, expected }) => {
it(`should return "${expected}" for input "${input}"`, () => {
const result = getExprLastPart(input);
expect(result).toBe(expected);
});
});
});
describe("getValueByContext", () => {
const tests = [
{ input: "", expected: "" },
{ input: "metric", expected: "metric" },
{ input: "metric1 + metric2", expected: "metric2" },
{ input: "rate(proc", expected: "proc" },
{ input: "sum(rate(proc", expected: "proc" },
{ input: "quantile_over_time(0.9, node_cpu", expected: "node_cpu" },
{ input: "clamp_min(metric1, m", expected: "m" },
];
tests.forEach(({ input, expected }) => {
it(`should return "${expected}" for input "${input}"`, () => {
const result = getValueByContext(input);
expect(result).toBe(expected);
});
});
});
describe("getContext", () => {
const tests = [
{ input: { beforeCursor: "" }, expected: QueryContextType.empty },
{ input: { beforeCursor: "metric" }, expected: QueryContextType.metricsql },
{ input: { beforeCursor: "rate(proc" }, expected: QueryContextType.metricsql },
{ input: { beforeCursor: "sum(rate(proc" }, expected: QueryContextType.metricsql },
{ input: { beforeCursor: "sum by (instance) (rate(proc" }, expected: QueryContextType.metricsql },
{ input: { beforeCursor: "metric{" }, expected: QueryContextType.label },
{ input: { beforeCursor: "metric{label=" }, expected: QueryContextType.labelValue, metric: "metric", label: "label" },
{ input: { beforeCursor: "metric{label1=\"value1\",label2=" }, expected: QueryContextType.labelValue, metric: "metric", label: "label2" },
{ input: { beforeCursor: "sum by (" }, expected: QueryContextType.label },
{ input: { beforeCursor: "quantile_over_time(0.9, node_cpu" }, expected: QueryContextType.metricsql },
{ input: { beforeCursor: "sum(max_over_time(rate(node_cpu" }, expected: QueryContextType.metricsql },
{ input: { beforeCursor: "clamp_min(metric1, m" }, expected: QueryContextType.metricsql },
{ input: { beforeCursor: "rate(node_cpu_seconds_total)" }, expected: QueryContextType.empty },
];
tests.forEach((test) => {
const { beforeCursor } = test.input;
const metric = test.metric || "";
const label = test.label || "";
it(`should return "${test.expected}" for input "${beforeCursor}"`, () => {
const result = getContext(beforeCursor, metric, label);
expect(result).toBe(test.expected);
});
});
});
});

View File

@@ -0,0 +1,112 @@
/**
* Utility functions for query editor autocomplete functionality
*/
import { QueryContextType } from "../../../types";
import { escapeRegexp, hasUnclosedQuotes } from "../../../utils/regexp";
/**
* Extracts the last part of an expression that's relevant for auto-suggestion
* @param beforeCursor The text before the cursor position
* @returns The relevant part of the expression for auto-suggestion
*/
export function getExprLastPart(beforeCursor: string): string {
const regexpSplit =
/\s(or|and|unless|default|ifnot|if|group_left|group_right)\s|}|\+|\|-|\*|\/|\^/i;
const parts = beforeCursor.split(regexpSplit);
const lastPart = parts[parts.length - 1].trim();
// Check if we're inside a function's parameters
const functionRegex = /.*\(([^)]*)$/;
const functionMatch = lastPart.match(functionRegex);
if (functionMatch && functionMatch[1]) {
const params = functionMatch[1];
if (params.lastIndexOf("{") > params.lastIndexOf("}")) {
const wordMatch = params.match(/([\w_.:]+)\{[^}]*$/);
return wordMatch ? wordMatch[0] : lastPart;
}
const lastCommaPos = params.lastIndexOf(",");
if (lastCommaPos !== -1) {
return params.substring(lastCommaPos + 1).trim();
}
return params;
}
return lastPart;
}
/**
* Extracts the current word or value at the cursor position for auto-suggestion matching
* @param beforeCursor The text before the cursor position
* @returns The current word or value at the cursor position
*/
export function getValueByContext(beforeCursor: string): string {
const wordMatch = beforeCursor.match(/([\w_.:]+(?![},]))$/);
return wordMatch ? wordMatch[0] : "";
}
/**
* Determines if auto-suggestion should be suppressed based on the query
* @param value The query value
* @returns Whether auto-suggestion should be suppressed
*/
export function shouldSuppressAutoSuggestion(value: string): boolean {
const pattern =
/([{(),+\-*/^]|\b(?:or|and|unless|default|ifnot|if|group_left|group_right|by|without|on|ignoring)\b)/i;
const parts = value.split(/\s+/);
const partsCount = parts.length;
const lastPart = parts[partsCount - 1];
const preLastPart = parts[partsCount - 2];
const hasEmptyPartAndQuotes = !lastPart && hasUnclosedQuotes(value);
const suppressPreLast =
(!lastPart || parts.length > 1) && !pattern.test(preLastPart);
return hasEmptyPartAndQuotes || suppressPreLast;
}
/**
* Determines the context type for auto-suggestion based on the query
* @param beforeCursor The text before the cursor position
* @param metric The current metric name
* @param label The current label name
* @returns The context type for auto-suggestion
*/
export function getContext(
beforeCursor: string,
metric: string = "",
label: string = ""
): QueryContextType {
const valueBeforeCursor = beforeCursor.trim();
const endOfClosedBrackets = ["}", ")"].some((char) =>
valueBeforeCursor.endsWith(char)
);
const endOfClosedQuotes =
!hasUnclosedQuotes(valueBeforeCursor) &&
["`", "'", '"'].some((char) => valueBeforeCursor.endsWith(char));
if (
!valueBeforeCursor ||
endOfClosedBrackets ||
endOfClosedQuotes ||
shouldSuppressAutoSuggestion(valueBeforeCursor)
) {
return QueryContextType.empty;
}
const labelRegexp = /(?:by|without|on|ignoring)\s*\(\s*[^)]*$|\{[^}]*$/i;
const patternLabelValue = `(${escapeRegexp(metric)})?{?.+${escapeRegexp(
label
)}(=|!=|=~|!~)"?([^"]*)$`;
const labelValueRegexp = new RegExp(patternLabelValue, "g");
switch (true) {
case labelValueRegexp.test(valueBeforeCursor):
return QueryContextType.labelValue;
case labelRegexp.test(valueBeforeCursor):
return QueryContextType.label;
default:
return QueryContextType.metricsql;
}
}

View File

@@ -191,7 +191,7 @@ const StepConfigurator: FC = () => {
</p>
<p>
Read more about <Hyperlink
href="https://docs.victoriametrics.com/keyConcepts.html#range-query"
href="https://docs.victoriametrics.com/keyconcepts/#range-query"
text="Range"
/> and <Hyperlink
href="https://docs.victoriametrics.com/keyconcepts/#instant-query"

View File

@@ -8,7 +8,7 @@ const issueLink = {
export const footerLinksByDefault = [
{
href: "https://docs.victoriametrics.com/MetricsQL.html",
href: "https://docs.victoriametrics.com/metricsql/",
Icon: CodeIcon,
title: "MetricsQL",
},

View File

@@ -9,7 +9,7 @@ const CATEGORY_TAG = "h3";
const FUNCTION_TAG = "h4";
const DESCRIPTION_TAG = "p";
const docsUrl = "https://docs.victoriametrics.com/MetricsQL.html";
const docsUrl = "https://docs.victoriametrics.com/metricsql/";
const classLink = "vm-link vm-link_colored";
const prepareDescription = (text: string): string => {

View File

@@ -4,13 +4,13 @@ import { useGraphState } from "../../../state/graph/GraphStateContext";
const last_over_time = <Hyperlink
text="last_over_time"
href="https://docs.victoriametrics.com/MetricsQL.html#last_over_time"
href="https://docs.victoriametrics.com/metricsql/#last_over_time"
underlined
/>;
const instant_query = <Hyperlink
text="instant query"
href="https://docs.victoriametrics.com/keyConcepts.html#instant-query"
href="https://docs.victoriametrics.com/keyconcepts/#instant-query"
underlined
/>;

View File

@@ -5,7 +5,7 @@ import CodeExample from "../../../components/Main/CodeExample/CodeExample";
const MetricsQL = () => (
<a
className="vm-link vm-link_colored"
href="https://docs.victoriametrics.com/MetricsQL.html"
href="https://docs.victoriametrics.com/metricsql/"
target="_blank"
rel="help noreferrer"
>

View File

@@ -24,7 +24,7 @@ type Client struct {
func NewClient() *Client {
return &Client{
httpCli: &http.Client{
Transport: httputil.NewTransport(false),
Transport: httputil.NewTransport(false, "apptest_client"),
},
}
}
@@ -55,6 +55,13 @@ func (c *Client) PostForm(t *testing.T, url string, data url.Values) (string, in
return c.Post(t, url, "application/x-www-form-urlencoded", []byte(data.Encode()))
}
// Delete sends a HTTP DELETE request and returns the response body and status code
// to the caller.
func (c *Client) Delete(t *testing.T, url string) (string, int) {
t.Helper()
return c.do(t, http.MethodDelete, url, "", nil)
}
// do prepares a HTTP request, sends it to the server, receives the response
// from the server, returns the response body and status code to the caller.
func (c *Client) do(t *testing.T, method, url, contentType string, data []byte) (string, int) {

View File

@@ -304,3 +304,43 @@ type MetricNamesStatsRecord struct {
MetricName string
QueryRequestsCount uint64
}
// SnapshotCreateResponse is an in-memory reprensentation of the json response
// returned by the /snapshot/create endpoint.
type SnapshotCreateResponse struct {
Status string
Snapshot string
}
// APIV1AdminTSDBSnapshotResponse is an in-memory reprensentation of the json
// response returned by the /api/v1/admin/tsdb/snapshot endpoint.
type APIV1AdminTSDBSnapshotResponse struct {
Status string
Data *SnapshotData
}
// SnapshotData holds the info about the snapshot created via
// /api/v1/admin/tsdb/snapshot endpoint.
type SnapshotData struct {
Name string
}
// SnapshotListResponse is an in-memory reprensentation of the json response
// returned by the /snapshot/list endpoint.
type SnapshotListResponse struct {
Status string
Snapshots []string
}
// SnapshotDeleteResponse is an in-memory reprensentation of the json response
// returned by the /snapshot/delete endpoint.
type SnapshotDeleteResponse struct {
Status string
Msg string
}
// SnapshotDeleteAllResponse is an in-memory reprensentation of the json response
// returned by the /snapshot/delete_all endpoint.
type SnapshotDeleteAllResponse struct {
Status string
}

View File

@@ -124,6 +124,23 @@ func (tc *TestCase) MustStartVminsert(instance string, flags []string) *Vminsert
return app
}
// MustStartVmagent is a test helper function that starts an instance of
// vmagent and fails the test if the app fails to start.
func (tc *TestCase) MustStartVmagent(instance string, flags []string, promScrapeConfigFileYAML string) *Vmagent {
tc.t.Helper()
promScrapeConfigFilePath := path.Join(tc.t.TempDir(), "prometheus.yml")
if err := os.WriteFile(promScrapeConfigFilePath, []byte(promScrapeConfigFileYAML), os.ModePerm); err != nil {
tc.t.Fatalf("cannot init vmagent: prom config file write failed: %s", err)
}
app, err := StartVmagent(instance, flags, tc.cli, promScrapeConfigFilePath)
if err != nil {
tc.t.Fatalf("Could not start %s: %v", instance, err)
}
tc.addApp(instance, app)
return app
}
// Vmcluster represents a typical cluster setup: several vmstorage replicas, one
// vminsert, and one vmselect.
//

View File

@@ -0,0 +1,201 @@
package tests
import (
"fmt"
"regexp"
"testing"
at "github.com/VictoriaMetrics/VictoriaMetrics/apptest"
"github.com/google/go-cmp/cmp"
)
// snapshotNameRE convers years 1970-2099.
// Corner case examples:
// - 19700101000000-0000000000000000
// - 20991231235959-38EECC8925ED5FFF
var snapshotNameRE = regexp.MustCompile(`^(19[789]\d|20[0-9]{2})(0\d|1[0-2])([0-2]\d|3[01])([01]\d|2[0-3])[0-5]\d[0-5]\d-[0-9,A-F]{16}$`)
func TestSingleSnapshots_CreateListDelete(t *testing.T) {
tc := at.NewTestCase(t)
defer tc.Stop()
sut := tc.MustStartDefaultVmsingle()
// Insert some data.
const numSamples = 1000
samples := make([]string, numSamples)
for i := range numSamples {
samples[i] = fmt.Sprintf("metric_%03d %d", i, i)
}
sut.PrometheusAPIV1ImportPrometheus(t, samples, at.QueryOpts{})
sut.ForceFlush(t)
// Create several snapshots using VictoriaMetrics and Prometheus endpoints.
const numSnapshots = 4
snapshots := make([]string, numSnapshots*2)
i := 0
for range numSnapshots {
res := sut.SnapshotCreate(t)
if got, want := res.Status, "ok"; got != want {
t.Fatalf("unexpected snapshot creation status: got %q, want %q", got, want)
}
if !snapshotNameRE.MatchString(res.Snapshot) {
t.Fatalf("unexpected snapshot name format: %q", res.Snapshot)
}
snapshots[i] = res.Snapshot
i++
}
for range numSnapshots {
res := sut.APIV1AdminTSDBSnapshot(t)
if got, want := res.Status, "success"; got != want {
t.Fatalf("unexpected snapshot creation status: got %q, want %q", got, want)
}
if !snapshotNameRE.MatchString(res.Data.Name) {
t.Fatalf("unexpected snapshot name format: %q", res.Data.Name)
}
snapshots[i] = res.Data.Name
i++
}
assertSnapshotList := func(want []string) {
gotRes := sut.SnapshotList(t)
wantRes := &at.SnapshotListResponse{
Status: "ok",
Snapshots: want,
}
if diff := cmp.Diff(wantRes, gotRes); diff != "" {
t.Fatalf("unexpected response (-want, +got):\n%s", diff)
}
}
assertSnapshotList(snapshots)
// Delete non-existent snapshot.
gotDeletedSnapshot := sut.SnapshotDelete(t, "does-not-exist")
wantDeletedSnapshot := &at.SnapshotDeleteResponse{
Status: "error",
Msg: `cannot find snapshot "does-not-exist"`,
}
if diff := cmp.Diff(wantDeletedSnapshot, gotDeletedSnapshot); diff != "" {
t.Fatalf("unexpected response (-want, +got):\n%s", diff)
}
// Delete the first snapshot.
gotDeletedSnapshot = sut.SnapshotDelete(t, snapshots[0])
wantDeletedSnapshot = &at.SnapshotDeleteResponse{
Status: "ok",
}
if diff := cmp.Diff(wantDeletedSnapshot, gotDeletedSnapshot); diff != "" {
t.Fatalf("unexpected response (-want, +got):\n%s", diff)
}
assertSnapshotList(snapshots[1:])
// Delete the rest of the snapshots.
gotDeleteAllRes := sut.SnapshotDeleteAll(t)
wantDeleteAllRes := &at.SnapshotDeleteAllResponse{
Status: "ok",
}
if diff := cmp.Diff(wantDeleteAllRes, gotDeleteAllRes); diff != "" {
t.Fatalf("unexpected response (-want, +got):\n%s", diff)
}
assertSnapshotList([]string{})
}
func TestClusterSnapshots_CreateListDelete(t *testing.T) {
tc := at.NewTestCase(t)
defer tc.Stop()
sut := tc.MustStartDefaultCluster().(*at.Vmcluster)
// Insert some data.
const numSamples = 1000
samples := make([]string, numSamples)
for i := range numSamples {
samples[i] = fmt.Sprintf("metric_%03d %d", i, i)
}
sut.PrometheusAPIV1ImportPrometheus(t, samples, at.QueryOpts{})
sut.ForceFlush(t)
// Create several snapshots for both vmstorage replicas using
// VictoriaMetrics endpoints only (cluster version does not have Prometheus
// endpoint)
createSnapshot := func(i int) string {
t.Helper()
res := sut.Vmstorages[i].SnapshotCreate(t)
if got, want := res.Status, "ok"; got != want {
t.Fatalf("unexpected snapshot creation status: got %q, want %q", got, want)
}
if !snapshotNameRE.MatchString(res.Snapshot) {
t.Fatalf("unexpected snapshot name format: %q", res.Snapshot)
}
return res.Snapshot
}
const numSnapshots = 4
snapshots0 := make([]string, numSnapshots)
snapshots1 := make([]string, numSnapshots)
for i := range numSnapshots {
snapshots0[i] = createSnapshot(0)
snapshots1[i] = createSnapshot(1)
}
assertSnapshotList := func(i int, wantNames []string) {
t.Helper()
got := sut.Vmstorages[i].SnapshotList(t)
want := &at.SnapshotListResponse{
Status: "ok",
Snapshots: wantNames,
}
if diff := cmp.Diff(want, got); diff != "" {
t.Fatalf("unexpected response (-want, +got):\n%s", diff)
}
}
assertSnapshotList(0, snapshots0)
assertSnapshotList(1, snapshots1)
// Delete non-existent snapshot.
assertDeleteNonExistent := func(i int) {
t.Helper()
got := sut.Vmstorages[i].SnapshotDelete(t, "does-not-exist")
want := &at.SnapshotDeleteResponse{
Status: "error",
Msg: `cannot find snapshot "does-not-exist"`,
}
if diff := cmp.Diff(want, got); diff != "" {
t.Fatalf("unexpected response (-want, +got):\n%s", diff)
}
}
assertDeleteNonExistent(0)
assertDeleteNonExistent(1)
// Delete the first snapshot.
deleteSnapshot := func(i int, snapshotName string) {
t.Helper()
got := sut.Vmstorages[i].SnapshotDelete(t, snapshotName)
want := &at.SnapshotDeleteResponse{
Status: "ok",
}
if diff := cmp.Diff(want, got); diff != "" {
t.Fatalf("unexpected response (-want, +got):\n%s", diff)
}
}
deleteSnapshot(0, snapshots0[0])
assertSnapshotList(0, snapshots0[1:])
deleteSnapshot(1, snapshots1[0])
assertSnapshotList(1, snapshots1[1:])
// Delete the rest of the snapshots.
deleteAllSnapshots := func(i int) {
t.Helper()
got := sut.Vmstorages[i].SnapshotDeleteAll(t)
want := &at.SnapshotDeleteAllResponse{
Status: "ok",
}
if diff := cmp.Diff(want, got); diff != "" {
t.Fatalf("unexpected response (-want, +got):\n%s", diff)
}
}
deleteAllSnapshots(0)
assertSnapshotList(0, []string{})
deleteAllSnapshots(1)
assertSnapshotList(1, []string{})
}

View File

@@ -0,0 +1,54 @@
package tests
import (
"fmt"
"testing"
"github.com/VictoriaMetrics/VictoriaMetrics/apptest"
at "github.com/VictoriaMetrics/VictoriaMetrics/apptest"
)
// TestSingleVMAgentZstdRemoteWrite verifies that vmagent can successfully perform
// a remote write to vmsingle using VM protocol (zstd).
func TestSingleVMAgentZstdRemoteWrite(t *testing.T) {
testSingleVMAgentRemoteWrite(t, false)
}
// TestSingleVMAgentSnappyRemoteWrite verifies that vmagent can successfully perform
// a remote write to vmsingle using Prometheus protocol (snappy).
func TestSingleVMAgentSnappyRemoteWrite(t *testing.T) {
testSingleVMAgentRemoteWrite(t, true)
}
func testSingleVMAgentRemoteWrite(t *testing.T, forcePromProto bool) {
tc := apptest.NewTestCase(t)
defer tc.Stop()
vmsingle := tc.MustStartDefaultVmsingle()
vmagent := tc.MustStartVmagent("vmagent", []string{
`-remoteWrite.flushInterval=50ms`,
fmt.Sprintf(`-remoteWrite.forcePromProto=%v`, forcePromProto),
fmt.Sprintf(`-remoteWrite.url=http://%s/api/v1/write`, vmsingle.HTTPAddr()),
}, ``)
vmagent.APIV1ImportPrometheus(t, []string{
"foo_bar 1 1652169600000", // 2022-05-10T08:00:00Z
}, apptest.QueryOpts{})
vmsingle.ForceFlush(t)
tc.Assert(&at.AssertOptions{
Msg: `unexpected metrics stored on vmagent remote write`,
Got: func() any {
return vmsingle.PrometheusAPIV1Series(t, `{__name__="foo_bar"}`, at.QueryOpts{
Start: "2022-05-10T00:00:00Z",
End: "2022-05-10T23:59:59Z",
}).Sort()
},
Want: &at.PrometheusAPIV1SeriesResponse{
Status: "success",
Data: []map[string]string{{"__name__": "foo_bar"}},
},
})
}

107
apptest/vmagent.go Normal file
View File

@@ -0,0 +1,107 @@
package apptest
import (
"fmt"
"net/http"
"regexp"
"strings"
"testing"
"time"
)
// Vmagent holds the state of a vmagent app and provides vmagent-specific functions
type Vmagent struct {
*app
*ServesMetrics
httpListenAddr string
apiV1ImportPrometheusURL string
}
// StartVmagent starts an instance of vmagent with the given flags. It also
// sets the default flags and populates the app instance state with runtime
// values extracted from the application log (such as httpListenAddr)
func StartVmagent(instance string, flags []string, cli *Client, promScrapeConfigFilePath string) (*Vmagent, error) {
extractREs := []*regexp.Regexp{
httpListenAddrRE,
}
app, stderrExtracts, err := startApp(instance, "../../bin/vmagent", flags, &appOptions{
defaultFlags: map[string]string{
"-httpListenAddr": "127.0.0.1:0",
"-promscrape.config": promScrapeConfigFilePath,
},
extractREs: extractREs,
})
if err != nil {
return nil, err
}
return &Vmagent{
app: app,
ServesMetrics: &ServesMetrics{
metricsURL: fmt.Sprintf("http://%s/metrics", stderrExtracts[0]),
cli: cli,
},
httpListenAddr: stderrExtracts[0],
apiV1ImportPrometheusURL: fmt.Sprintf("http://%s/api/v1/import/prometheus", stderrExtracts[0]),
}, nil
}
// APIV1ImportPrometheus is a test helper function that inserts a
// collection of records in Prometheus text exposition format for the given
// tenant by sending a HTTP POST request to /api/v1/import/prometheus vmagent endpoint.
//
// The call is blocked until the data is flushed to vmstorage or the timeout is reached.
//
// See https://docs.victoriametrics.com/url-examples/#apiv1importprometheus
func (app *Vmagent) APIV1ImportPrometheus(t *testing.T, records []string, _ QueryOpts) {
t.Helper()
data := []byte(strings.Join(records, "\n"))
app.sendBlocking(t, len(records), func() {
_, statusCode := app.cli.Post(t, app.apiV1ImportPrometheusURL, "text/plain", data)
if statusCode != http.StatusNoContent {
t.Fatalf("unexpected status code: got %d, want %d", statusCode, http.StatusNoContent)
}
})
}
// sendBlocking sends the data to vmstorage by executing `send` function and
// waits until the data is actually sent.
//
// vmagent does not send the data immediately. It first puts the data into a
// buffer. Then a background goroutine takes the data from the buffer sends it
// to the vmstorage. This happens every 1s by default.
//
// Waiting is implemented a retrieving the value of `vmagent_remotewrite_requests_total`
// metric and checking whether it is equal or greater than the wanted value.
// If it is, then the data has been sent to vmstorage.
//
// Unreliable if the records are inserted concurrently.
func (app *Vmagent) sendBlocking(t *testing.T, numRecordsToSend int, send func()) {
t.Helper()
send()
const (
retries = 20
period = 100 * time.Millisecond
)
wantRowsSentCount := app.remoteWriteRequestsTotal(t) + numRecordsToSend
for range retries {
if app.remoteWriteRequestsTotal(t) >= wantRowsSentCount {
return
}
time.Sleep(period)
}
t.Fatalf("timed out while waiting for inserted rows to be sent to vmstorage")
}
func (app *Vmagent) remoteWriteRequestsTotal(t *testing.T) int {
total := 0.0
for _, v := range app.GetMetricsByPrefix(t, "vmagent_remotewrite_requests_total") {
total += v
}
return int(total)
}

View File

@@ -66,8 +66,9 @@ func StartVmsingle(instance string, flags []string, cli *Client) (*Vmsingle, err
storageDataPath: stderrExtracts[0],
httpListenAddr: stderrExtracts[1],
forceFlushURL: fmt.Sprintf("http://%s/internal/force_flush", stderrExtracts[1]),
forceMergeURL: fmt.Sprintf("http://%s/internal/force_merge", stderrExtracts[1]),
forceFlushURL: fmt.Sprintf("http://%s/internal/force_flush", stderrExtracts[1]),
forceMergeURL: fmt.Sprintf("http://%s/internal/force_merge", stderrExtracts[1]),
influxLineWriteURL: fmt.Sprintf("http://%s/influx/write", stderrExtracts[1]),
prometheusAPIV1ImportPrometheusURL: fmt.Sprintf("http://%s/prometheus/api/v1/import/prometheus", stderrExtracts[1]),
prometheusAPIV1WriteURL: fmt.Sprintf("http://%s/prometheus/api/v1/write", stderrExtracts[1]),
@@ -240,6 +241,121 @@ func (app *Vmsingle) APIV1AdminStatusMetricNamesStatsReset(t *testing.T, opts Qu
}
}
// SnapshotCreate creates a database snapshot by sending a query to the
// /snapshot/create endpoint.
//
// See https://docs.victoriametrics.com/single-server-victoriametrics/#how-to-work-with-snapshots
func (app *Vmsingle) SnapshotCreate(t *testing.T) *SnapshotCreateResponse {
t.Helper()
queryURL := fmt.Sprintf("http://%s/snapshot/create", app.httpListenAddr)
data, statusCode := app.cli.Post(t, queryURL, "", nil)
if got, want := statusCode, http.StatusOK; got != want {
t.Fatalf("unexpected status code: got %d, want %d, resp text=%q", got, want, data)
}
var res SnapshotCreateResponse
if err := json.Unmarshal([]byte(data), &res); err != nil {
t.Fatalf("could not unmarshal snapshot create response: data=%q, err: %v", data, err)
}
return &res
}
// APIV1AdminTSDBSnapshot creates a database snapshot by sending a query to the
// /api/v1/admin/tsdb/snapshot endpoint.
//
// See https://prometheus.io/docs/prometheus/latest/querying/api/#snapshot.
func (app *Vmsingle) APIV1AdminTSDBSnapshot(t *testing.T) *APIV1AdminTSDBSnapshotResponse {
t.Helper()
queryURL := fmt.Sprintf("http://%s/api/v1/admin/tsdb/snapshot", app.httpListenAddr)
data, statusCode := app.cli.Post(t, queryURL, "", nil)
if got, want := statusCode, http.StatusOK; got != want {
t.Fatalf("unexpected status code: got %d, want %d, resp text=%q", got, want, data)
}
var res APIV1AdminTSDBSnapshotResponse
if err := json.Unmarshal([]byte(data), &res); err != nil {
t.Fatalf("could not unmarshal prometheus snapshot create response: data=%q, err: %v", data, err)
}
return &res
}
// SnapshotList lists existing database snapshots by sending a query to the
// /snapshot/list endpoint.
//
// See https://docs.victoriametrics.com/single-server-victoriametrics/#how-to-work-with-snapshots
func (app *Vmsingle) SnapshotList(t *testing.T) *SnapshotListResponse {
t.Helper()
queryURL := fmt.Sprintf("http://%s/snapshot/list", app.httpListenAddr)
data, statusCode := app.cli.Get(t, queryURL)
if got, want := statusCode, http.StatusOK; got != want {
t.Fatalf("unexpected status code: got %d, want %d, resp text=%q", got, want, data)
}
var res SnapshotListResponse
if err := json.Unmarshal([]byte(data), &res); err != nil {
t.Fatalf("could not unmarshal snapshot list response: data=%q, err: %v", data, err)
}
return &res
}
// SnapshotDelete deletes a snapshot by sending a query to the
// /snapshot/delete endpoint.
//
// See https://docs.victoriametrics.com/single-server-victoriametrics/#how-to-work-with-snapshots
func (app *Vmsingle) SnapshotDelete(t *testing.T, snapshotName string) *SnapshotDeleteResponse {
t.Helper()
queryURL := fmt.Sprintf("http://%s/snapshot/delete?snapshot=%s", app.httpListenAddr, snapshotName)
data, statusCode := app.cli.Delete(t, queryURL)
wantStatusCodes := map[int]bool{
http.StatusOK: true,
http.StatusInternalServerError: true,
}
if !wantStatusCodes[statusCode] {
t.Fatalf("unexpected status code: got %d, want %v, resp text=%q", statusCode, wantStatusCodes, data)
}
var res SnapshotDeleteResponse
if err := json.Unmarshal([]byte(data), &res); err != nil {
t.Fatalf("could not unmarshal snapshot delete response: data=%q, err: %v", data, err)
}
return &res
}
// SnapshotDeleteAll deletes all snapshots by sending a query to the
// /snapshot/delete_all endpoint.
//
// See https://docs.victoriametrics.com/single-server-victoriametrics/#how-to-work-with-snapshots
func (app *Vmsingle) SnapshotDeleteAll(t *testing.T) *SnapshotDeleteAllResponse {
t.Helper()
queryURL := fmt.Sprintf("http://%s/snapshot/delete_all", app.httpListenAddr)
data, statusCode := app.cli.Get(t, queryURL)
if got, want := statusCode, http.StatusOK; got != want {
t.Fatalf("unexpected status code: got %d, want %d, resp text=%q", got, want, data)
}
var res SnapshotDeleteAllResponse
if err := json.Unmarshal([]byte(data), &res); err != nil {
t.Fatalf("could not unmarshal snapshot delete all response: data=%q, err: %v", data, err)
}
return &res
}
// HTTPAddr returns the address at which the vmstorage process is listening
// for http connections.
func (app *Vmsingle) HTTPAddr() string {
return app.httpListenAddr
}
// 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,6 +1,7 @@
package apptest
import (
"encoding/json"
"fmt"
"net/http"
"os"
@@ -19,9 +20,6 @@ type Vmstorage struct {
httpListenAddr string
vminsertAddr string
vmselectAddr string
forceFlushURL string
forceMergeURL string
}
// StartVmstorage starts an instance of vmstorage with the given flags. It also
@@ -56,9 +54,6 @@ func StartVmstorage(instance string, flags []string, cli *Client) (*Vmstorage, e
httpListenAddr: stderrExtracts[1],
vminsertAddr: stderrExtracts[2],
vmselectAddr: stderrExtracts[3],
forceFlushURL: fmt.Sprintf("http://%s/internal/force_flush", stderrExtracts[1]),
forceMergeURL: fmt.Sprintf("http://%s/internal/force_merge", stderrExtracts[1]),
}, nil
}
@@ -79,7 +74,8 @@ func (app *Vmstorage) VmselectAddr() string {
func (app *Vmstorage) ForceFlush(t *testing.T) {
t.Helper()
_, statusCode := app.cli.Get(t, app.forceFlushURL)
forceFlushURL := fmt.Sprintf("http://%s/internal/force_flush", app.httpListenAddr)
_, statusCode := app.cli.Get(t, forceFlushURL)
if statusCode != http.StatusOK {
t.Fatalf("unexpected status code: got %d, want %d", statusCode, http.StatusOK)
}
@@ -88,12 +84,102 @@ func (app *Vmstorage) ForceFlush(t *testing.T) {
// ForceMerge is a test helper function that forces the merging of parts.
func (app *Vmstorage) ForceMerge(t *testing.T) {
t.Helper()
_, statusCode := app.cli.Get(t, app.forceMergeURL)
forceMergeURL := fmt.Sprintf("http://%s/internal/force_merge", app.httpListenAddr)
_, statusCode := app.cli.Get(t, forceMergeURL)
if statusCode != http.StatusOK {
t.Fatalf("unexpected status code: got %d, want %d", statusCode, http.StatusOK)
}
}
// SnapshotCreate creates a database snapshot by sending a query to the
// /snapshot/create endpoint.
//
// See https://docs.victoriametrics.com/single-server-victoriametrics/#how-to-work-with-snapshots
func (app *Vmstorage) SnapshotCreate(t *testing.T) *SnapshotCreateResponse {
t.Helper()
queryURL := fmt.Sprintf("http://%s/snapshot/create", app.httpListenAddr)
data, statusCode := app.cli.Post(t, queryURL, "", nil)
if got, want := statusCode, http.StatusOK; got != want {
t.Fatalf("unexpected status code: got %d, want %d, resp text=%q", got, want, data)
}
var res SnapshotCreateResponse
if err := json.Unmarshal([]byte(data), &res); err != nil {
t.Fatalf("could not unmarshal snapshot create response: data=%q, err: %v", data, err)
}
return &res
}
// SnapshotList lists existing database snapshots by sending a query to the
// /snapshot/list endpoint.
//
// See https://docs.victoriametrics.com/single-server-victoriametrics/#how-to-work-with-snapshots
func (app *Vmstorage) SnapshotList(t *testing.T) *SnapshotListResponse {
t.Helper()
queryURL := fmt.Sprintf("http://%s/snapshot/list", app.httpListenAddr)
data, statusCode := app.cli.Get(t, queryURL)
if got, want := statusCode, http.StatusOK; got != want {
t.Fatalf("unexpected status code: got %d, want %d, resp text=%q", got, want, data)
}
var res SnapshotListResponse
if err := json.Unmarshal([]byte(data), &res); err != nil {
t.Fatalf("could not unmarshal snapshot list response: data=%q, err: %v", data, err)
}
return &res
}
// SnapshotDelete deletes a snapshot by sending a query to the
// /snapshot/delete endpoint.
//
// See https://docs.victoriametrics.com/single-server-victoriametrics/#how-to-work-with-snapshots
func (app *Vmstorage) SnapshotDelete(t *testing.T, snapshotName string) *SnapshotDeleteResponse {
t.Helper()
queryURL := fmt.Sprintf("http://%s/snapshot/delete?snapshot=%s", app.httpListenAddr, snapshotName)
data, statusCode := app.cli.Delete(t, queryURL)
wantStatusCodes := map[int]bool{
http.StatusOK: true,
http.StatusInternalServerError: true,
}
if !wantStatusCodes[statusCode] {
t.Fatalf("unexpected status code: got %d, want %v, resp text=%q", statusCode, wantStatusCodes, data)
}
var res SnapshotDeleteResponse
if err := json.Unmarshal([]byte(data), &res); err != nil {
t.Fatalf("could not unmarshal snapshot delete response: data=%q, err: %v", data, err)
}
return &res
}
// SnapshotDeleteAll deletes all snapshots by sending a query to the
// /snapshot/delete_all endpoint.
//
// See https://docs.victoriametrics.com/single-server-victoriametrics/#how-to-work-with-snapshots
func (app *Vmstorage) SnapshotDeleteAll(t *testing.T) *SnapshotDeleteAllResponse {
t.Helper()
queryURL := fmt.Sprintf("http://%s/snapshot/delete_all", app.httpListenAddr)
data, statusCode := app.cli.Post(t, queryURL, "", nil)
if got, want := statusCode, http.StatusOK; got != want {
t.Fatalf("unexpected status code: got %d, want %d, resp text=%q", got, want, data)
}
var res SnapshotDeleteAllResponse
if err := json.Unmarshal([]byte(data), &res); err != nil {
t.Fatalf("could not unmarshal snapshot delete all response: data=%q, err: %v", data, err)
}
return &res
}
// String returns the string representation of the vmstorage app state.
func (app *Vmstorage) String() string {
return fmt.Sprintf("{app: %s storageDataPath: %q httpListenAddr: %q vminsertAddr: %q vmselectAddr: %q}", []any{

View File

@@ -1690,7 +1690,9 @@
],
"refresh": "1m",
"schemaVersion": 39,
"tags": [],
"tags": [
"victoriametrics"
],
"templating": {
"list": [
{

View File

@@ -1851,8 +1851,7 @@
"refresh": false,
"schemaVersion": 39,
"tags": [
"VictoriaMetrics",
"monitoring"
"victoriametrics"
],
"templating": {
"list": [

View File

@@ -2052,8 +2052,7 @@
"refresh": "",
"schemaVersion": 39,
"tags": [
"operator",
"VictoriaMetrics"
"victoriametrics"
],
"templating": {
"list": [

View File

@@ -3494,7 +3494,10 @@
"preload": false,
"refresh": "",
"schemaVersion": 40,
"tags": [],
"tags": [
"victoriametrics",
"victorialogs"
],
"templating": {
"list": [
{

View File

@@ -10061,7 +10061,9 @@
"preload": false,
"refresh": "",
"schemaVersion": 40,
"tags": [],
"tags": [
"victoriametrics"
],
"templating": {
"list": [
{

View File

@@ -6193,8 +6193,7 @@
"refresh": "",
"schemaVersion": 40,
"tags": [
"victoriametrics",
"vmsingle"
"victoriametrics"
],
"templating": {
"list": [

View File

@@ -1691,7 +1691,9 @@
],
"refresh": "1m",
"schemaVersion": 39,
"tags": [],
"tags": [
"victoriametrics"
],
"templating": {
"list": [
{

View File

@@ -1852,8 +1852,7 @@
"refresh": false,
"schemaVersion": 39,
"tags": [
"VictoriaMetrics",
"monitoring"
"victoriametrics"
],
"templating": {
"list": [

View File

@@ -2053,8 +2053,7 @@
"refresh": "",
"schemaVersion": 39,
"tags": [
"operator",
"VictoriaMetrics"
"victoriametrics"
],
"templating": {
"list": [

View File

@@ -3495,7 +3495,10 @@
"preload": false,
"refresh": "",
"schemaVersion": 40,
"tags": [],
"tags": [
"victoriametrics",
"victorialogs"
],
"templating": {
"list": [
{

View File

@@ -10062,7 +10062,9 @@
"preload": false,
"refresh": "",
"schemaVersion": 40,
"tags": [],
"tags": [
"victoriametrics"
],
"templating": {
"list": [
{

View File

@@ -6194,8 +6194,7 @@
"refresh": "",
"schemaVersion": 40,
"tags": [
"victoriametrics",
"vmsingle"
"victoriametrics"
],
"templating": {
"list": [

View File

@@ -7647,7 +7647,6 @@
"refresh": "",
"schemaVersion": 40,
"tags": [
"vmagent",
"victoriametrics"
],
"templating": {

View File

@@ -3733,8 +3733,7 @@
"refresh": "",
"schemaVersion": 40,
"tags": [
"victoriametrics",
"vmalert"
"victoriametrics"
],
"templating": {
"list": [

View File

@@ -2559,7 +2559,9 @@
"refresh": "30s",
"revision": 1,
"schemaVersion": 39,
"tags": [],
"tags": [
"victoriametrics"
],
"templating": {
"list": [
{

View File

@@ -7646,7 +7646,6 @@
"refresh": "",
"schemaVersion": 40,
"tags": [
"vmagent",
"victoriametrics"
],
"templating": {

View File

@@ -3732,8 +3732,7 @@
"refresh": "",
"schemaVersion": 40,
"tags": [
"victoriametrics",
"vmalert"
"victoriametrics"
],
"templating": {
"list": [

View File

@@ -2558,7 +2558,9 @@
"refresh": "30s",
"revision": 1,
"schemaVersion": 39,
"tags": [],
"tags": [
"victoriametrics"
],
"templating": {
"list": [
{

View File

@@ -7,7 +7,7 @@ ROOT_IMAGE ?= alpine:3.21.3
ROOT_IMAGE_SCRATCH ?= scratch
CERTS_IMAGE := alpine:3.21.3
GO_BUILDER_IMAGE := golang:1.24.1-alpine
GO_BUILDER_IMAGE := golang:1.24.2-alpine
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 :/ __)
DOCKER ?= docker

View File

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

View File

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

View File

@@ -8,7 +8,7 @@ RUN \
cd /tmp/aws-lambda-rie && \
CGO_ENABLED=0 go build -buildvcs=false -ldflags "-s -w" -o /aws-lambda-rie ./cmd/aws-lambda-rie
FROM python:3.12-bullseye
FROM python:3.13-bullseye
RUN \
apt update && \
@@ -26,7 +26,7 @@ RUN \
WORKDIR /var/task
COPY --from=aws-lambda-rie /aws-lambda-rie /var/task/aws-lambda-rie
COPY main.py /var/task/
COPY --from=public.ecr.aws/datadog/lambda-extension:68 /opt/. /opt/
COPY --from=public.ecr.aws/datadog/lambda-extension:73 /opt/. /opt/
ENTRYPOINT ["/var/task/aws-lambda-rie"]
CMD ["/usr/local/bin/python", "-m", "awslambdaric", "main.lambda_handler"]

View File

@@ -14,7 +14,7 @@ services:
DD_API_KEY: test
DD_DD_URL: http://dd-proxy:8427
DD_PROFILING_ENABLED: false
DD_ENHANCED_METRICS: false
DD_ENHANCED_METRICS: true
DD_LOGS_CONFIG_LOGS_DD_URL: http://dd-proxy:8427
DD_SERVERLESS_FLUSH_STRATEGY: periodically,100
depends_on:

View File

@@ -18,7 +18,7 @@ services:
- vlogs
generator:
image: golang:1.24.1-alpine
image: golang:1.24.2-alpine
restart: always
working_dir: /go/src/app
volumes:

View File

@@ -2,7 +2,7 @@ version: "3"
services:
generator:
image: golang:1.24.1-alpine
image: golang:1.24.2-alpine
restart: always
working_dir: /go/src/app
volumes:

View File

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

View File

@@ -49,7 +49,7 @@ See details about all supported options in the [vmgateway documentation](https:/
![Client secret](client-secret.webp)
Copy the value of `Client secret`. It will be used later in Grafana configuration.<br>
1. Go to `Clients` -> `grafana` -> `Client scopes`.<br>
Click at `grafana-dedicated` -> `Add mapper` -> `By configuration` -> `User attribute`.<br>
Click at `grafana-dedicated` -> `Configure a new mapper` -> `User attribute`.<br>
![Create mapper 1](create-mapper-1.webp)
![Create mapper 2](create-mapper-2.webp)
Configure the mapper as follows<br>
@@ -61,8 +61,13 @@ See details about all supported options in the [vmgateway documentation](https:/
![Create mapper 3](create-mapper-3.webp)
Click `Save`.<br>
1. Go to `Users` -> select user to configure claims -> `Attributes`.<br>
Specify `vm_access` as `Key`.<br>
1. Go to `Realm settings` -> `User profile`.<br>
Click `Create attribute`.<br>
Specify `vm_access` as `Attribute [Name]`.<br>
![User attributes](create-attribute.webp)
Click `Save`.<br>
1. Go to `Users` -> select user to configure.<br>
Modify value of `vm_access` attribute.<br>
For the purpose of this example, we will use 2 users:<br>
- for the first user we will specify `{"tenant_id" : {"account_id": 0, "project_id": 0 },"extra_labels":{ "team": "admin" }}` as `Value`.
- for the second user we will specify `{"tenant_id" : {"account_id": 0, "project_id": 1 },"extra_labels":{ "team": "dev" }}` as `Value`.
@@ -219,44 +224,44 @@ version: '3'
services:
keycloak:
image: quay.io/keycloak/keycloak:21.0
image: quay.io/keycloak/keycloak:26.1
command:
- start-dev
ports:
- 3001:8080
environment:
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: change_me
KC_BOOTSTRAP_ADMIN_USERNAME: admin
KC_BOOTSTRAP_ADMIN_PASSWORD: change_me
grafana:
image: grafana/grafana:10.4.2
image: grafana/grafana:11.5.2
network_mode: host
volumes:
- ./grafana.ini:/etc/grafana/grafana.ini
- grafana_data:/var/lib/grafana/
vmsingle:
image: victoriametrics/victoria-metrics:v1.114.0
image: victoriametrics/victoria-metrics:v1.115.0
command:
- -httpListenAddr=0.0.0.0:8429
vmstorage:
image: victoriametrics/vmstorage:v1.114.0-cluster
image: victoriametrics/vmstorage:v1.115.0-cluster
vminsert:
image: victoriametrics/vminsert:v1.114.0-cluster
image: victoriametrics/vminsert:v1.115.0-cluster
command:
- -storageNode=vmstorage:8400
- -httpListenAddr=0.0.0.0:8480
vmselect:
image: victoriametrics/vmselect:v1.114.0-cluster
image: victoriametrics/vmselect:v1.115.0-cluster
command:
- -storageNode=vmstorage:8401
- -httpListenAddr=0.0.0.0:8481
vmagent:
image: victoriametrics/vmagent:v1.114.0
image: victoriametrics/vmagent:v1.115.0
volumes:
- ./scrape.yaml:/etc/vmagent/config.yaml
command:
@@ -265,7 +270,7 @@ services:
- -remoteWrite.url=http://vmsingle:8429/api/v1/write
vmgateway-cluster:
image: victoriametrics/vmgateway:v1.114.0-enterprise
image: victoriametrics/vmgateway:v1.115.0-enterprise
ports:
- 8431:8431
volumes:
@@ -281,7 +286,7 @@ services:
- -auth.oidcDiscoveryEndpoints=http://keycloak:8080/realms/master/.well-known/openid-configuration
vmgateway-single:
image: victoriametrics/vmgateway:v1.114.0-enterprise
image: victoriametrics/vmgateway:v1.115.0-enterprise
ports:
- 8432:8431
volumes:
@@ -369,9 +374,9 @@ In order to create a client for vmagent to use, follow the steps below:
![Client secret](vmagent-client-secret.webp)
Copy the value of `Client secret`. It will be used later in vmagent configuration.<br>
1. Go to `Clients` -> `vmagent` -> `Client scopes`.<br>
Click at `vmagent-dedicated` -> `Add mapper` -> `By configuration` -> `User attribute`.<br>
![Create mapper 1](create-mapper-1.webp)
![Create mapper 2](create-mapper-2.webp)
Click at `vmagent-dedicated` -> `Configure a new mapper` -> `User attribute`.<br>
![Create mapper 1](vmagent-create-mapper-1.webp)
![Create mapper 2](vmagent-create-mapper-2.webp)
Configure the mapper as follows<br>
- `Name` as `vm_access`.
- `Token Claim Name` as `vm_access`.
@@ -379,13 +384,12 @@ In order to create a client for vmagent to use, follow the steps below:
- `Claim JSON Type` as `JSON`.
Enable `Add to ID token` and `Add to access token`.<br>
![Create mapper 3](create-mapper-3.webp)
![Create mapper 3](vmagent-create-mapper-3.webp)
Click `Save`.<br>
1. Go to `Service account roles` -> click on `service-account-vmagent`.<br>
![vmagent service account](vmagent-sa.webp)
1. Go to `Attributes` tab and add an attribute.
Specify `vm_access` as `Key`.<br>
Specify `{"tenant_id" : {"account_id": 0, "project_id": 0 }}` as a value.<br>
Change `vm_access` attribute value to `{"tenant_id" : {"account_id": 0, "project_id": 0 }}`. <br>
![User attributes](vmagent-sa-attributes.webp)
Click `Save`.
@@ -393,7 +397,7 @@ Once iDP configuration is done, vmagent configuration needs to be updated to use
```yaml
vmagent:
image: victoriametrics/vmagent:v1.114.0
image: victoriametrics/vmagent:v1.115.0
volumes:
- ./scrape.yaml:/etc/vmagent/config.yaml
- ./vmagent-client-secret:/etc/vmagent/oauth2-client-secret

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 17 KiB

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