Compare commits

..

25 Commits

Author SHA1 Message Date
Aliaksandr Valialkin
7967ad661e lib/fs: remove fsync for the parent directory from MustMkdirIfNotExist(), MustMkdirFailIfExist(), MustHardLinkFiles() and MustCopyDirectory()
This allows performing a single MustFsyncPath() for the parent directory after multiple calls to these functions.
This clarifies code paths, which call these functions, and makes them more maintainable.

This also removes a redundant fsync() call for the parent directory when creating a file-based part.
Previously the first fsync() was indirectly called when the directory was created via MustMkdirFailIfExist()
and the second fsync() was called via MustSyncPathAndParentDir() after all the data is written to the part.
2025-08-30 01:53:54 +02:00
Aliaksandr Valialkin
1aa72ecbfd lib/persistentqueue/persistentqueue.go: remove fs.MustSyncPath() call after fs.MustWriteSync()
The fs.MustWriteSync() already fsyncs the created file, so there is no need in additional fsync() call.

While at it, add missing fsync for the parent directory after creating a directory for persistent queue.
2025-08-30 01:47:10 +02:00
Aliaksandr Valialkin
3b656147ef lib/backup/fsremote/fsremote.go: remove unneeded fsync for the hard-linked file
The source file contents should be already fsynced to disk before creating a hard link,
so there is no sense in calling fsync() on the created hard link.
2025-08-30 01:44:21 +02:00
Aliaksandr Valialkin
3c4004673e docs/victoriametrics/sd_configs.md: fix internal links to different Kubernetes service discovery roles
This is a follow-up for the commit 51aebcd061
2025-08-29 16:26:31 +02:00
Aliaksandr Valialkin
45c9f31987 docs/victoriametrics/Articles.md: add https://amir-shams.medium.com/why-victoriametrics-a-practical-guide-to-scalable-and-faster-monitoring-than-prometheus-54ef21f10465 2025-08-29 16:24:24 +02:00
f41gh7
37013d36c0 follow-up after 24aef8ea90 fixes single-node branch build 2025-08-29 14:45:51 +02:00
f41gh7
c9b3088c9c CHANGELOG.md: cut v1.125.0 release 2025-08-29 14:36:08 +02:00
Charles-Antoine Mathieu
24aef8ea90 app/vmselect/graphite: enforce search.maxQueryLen for Graphite queries
This commit ensures that the -search.maxQueryLen flag applies to Graphite
queries, matching the behavior already present for Prometheus queries.
Previously, Graphite queries could bypass this limit, creating an
inconsistency and a potential vector for resource exhaustion.

Key changes:

Added getMaxQueryLen() to access the global query length limit.
Enforced query length validation in execExpr() for Graphite queries.
Added comprehensive tests for the new validation logic and edge cases.
Error messages are consistent with Prometheus query validation.
The default limit is 16KB (configurable via -search.maxQueryLen).
Setting the limit to 0 disables validation.
This change closes the gap where Graphite queries could exceed
configured length limits, providing consistent protection against
excessively long queries across both query APIs.

Follow-up for https://github.com/VictoriaMetrics/VictoriaMetrics/pull/9534
Related PR https://github.com/VictoriaMetrics/VictoriaMetrics/pull/9600
2025-08-29 14:29:37 +02:00
f41gh7
e540e5e381 make vmui-update 2025-08-29 14:16:58 +02:00
Aliaksandr Valialkin
51aebcd061 docs/victoriametrics/sd_configs.md: add titles per every target role in service discovery configs
This allows referring per-role docs via direct links to the correponsing sub-chapters with the given titles
2025-08-29 13:28:47 +02:00
Artem Fetishev
df7b752c7a lib/storage: fix double counting in vm_deleted_metrics_total
The vm_deleted_metrics_total metric value represents the number of
metricIDs stored in deletedMetricIDs cache. This cache lives at the
storage level and stores the deleted metrics from both prev and curr
idbs. However, the metric is populated at the idb level. Since there are
always 2 idbs (prev and curr), the value is populated twice. Hence the
doubled value of the metric.

The fix is to populate the metric value at the storage level.

Related issue https://github.com/VictoriaMetrics/VictoriaMetrics/issues/9602
2025-08-29 11:18:07 +02:00
Andrii Chubatiuk
6f74b139cc app/vmui: craft UI configuration on backend instead of using /flags endpoint and static config.json file
- load and parse static`/vmui/config.json`, modify it according to
runtime values and use it as a replacement for static config.json
- remove using `/flags` endpoint for checking features, that should be
enabled on VMUI

 Related PR https://github.com/VictoriaMetrics/VictoriaMetrics/pull/9635
2025-08-29 10:44:38 +02:00
Andrii Chubatiuk
e49609cbc2 app/vmui: removed home page hack
`router.home` represents `/` path, which is the same for all UI apps,
but content and title for root path differs depending on application
type. added `getDefaultOptions` function, which returns proper home
route configuration depending on application type, which allows to
remove renamings in respective layouts

 Related PR https://github.com/VictoriaMetrics/VictoriaMetrics/pull/9641
2025-08-29 10:25:55 +02:00
Hui Wang
2e655a91bc lib/httpserver: properly issue automatic TLS certificates
Bug was introduced at commit 93ad502d6dcb4724e8ec40a4a0351b0316853af0

Related PR https://github.com/VictoriaMetrics/VictoriaMetrics-enterprise/pull/930
2025-08-29 10:08:51 +02:00
Max Kotliar
1e927b2e53 lib/prommetadata: Extract -enableMetadata flag to separate package, avoid pulling in promscrape discovery flags into vminsert
The commit
25cd5637bc
introduced the `-enableMetadata` flag and the
`promscrape.IsMetadataEnabled()` function, which is now used in multiple
places, including the `app/vminsert/prometheusimport` [request
handler](b24b76ff08/app/vminsert/prometheusimport/request_handler.go (L36)).

Because of the use of `promscrape` package vminsert registered all
`-promscrape.*` service discovery flags, which were not relevant for
`vminsert`.

This change moves the metadata flag logic into a dedicated package,
preventing vminsert from unintentionally loading unrelated promscrape
flags.

Related PR https://github.com/VictoriaMetrics/VictoriaMetrics/pull/9631
2025-08-29 10:07:16 +02:00
Hui Wang
21963a1cad fix vmcluster docker-compose example (#9643)
1. fix vmcluster docker-compose example: vminsert scrape job and vmagent
remote write authorization.
2. upgrade grafana to v12.1.1
2025-08-29 14:36:46 +08:00
hagen1778
87b291debe deployment/docker: replace single-node image on transparent background version
Signed-off-by: hagen1778 <roman@victoriametrics.com>
2025-08-28 20:17:13 +02:00
hagen1778
cce1cdcb6d deployment/docker: strip victorialogs images from excalidraw sources
VictoriaLogs excalidraw images should be stored in VictoriaLogs repo

Signed-off-by: hagen1778 <roman@victoriametrics.com>
2025-08-28 20:12:19 +02:00
hagen1778
03e003c828 deployment/docker: use light and dark images for github markdown for cluster images
This is an attempt to adjust image styles to GitHub themes, because
existing images with transparent backround become unreadable on dark theme.

Signed-off-by: hagen1778 <roman@victoriametrics.com>
2025-08-28 20:08:19 +02:00
hagen1778
ad9d11ba3f deployment/docker: use light and dark images for github markdown
This is an attempt to adjust image styles to GitHub themes, because
existing images with transparent backround become unreadable on dark theme.

Signed-off-by: hagen1778 <roman@victoriametrics.com>
2025-08-28 20:05:42 +02:00
hagen1778
5c2ed99dab deployment/docker: rm victorialogs images
The vlogs images were moved to VictoriaLogs github repo
and aren't needed here anymore.

Signed-off-by: hagen1778 <roman@victoriametrics.com>
2025-08-28 20:04:24 +02:00
f41gh7
eaec80b7f3 follow-up after 76eb654e7e
mention change at changelog
2025-08-28 17:08:31 +02:00
Oron Sharabi
d6ef8a807b lib/storage: improve searchLabels and searchLabelValues performance
When having a `match` of `__name__` key alone for labels api, it's going
to hit max series limit in case of high cardinality metric name.
Instead, we can skip looking by `metricIDs` and fallback to inverted
index scan with a `composite key` since we only have some `__name__` and
a label name.

 Common requests for optimisations are:
1) /api/v1/labels?match=up or /api/v1/labels?extra_filters=up
2) /api/v1/label/job/values?match=up or /api/v1/labels?extra_filters =up

 It's widely used by grafana variables.

 Related PR https://github.com/VictoriaMetrics/VictoriaMetrics/pull/9489
2025-08-28 17:08:25 +02:00
f41gh7
c0318a84f0 deployment/docker: switch from musl to glibc
This should remove vertical scalability limit for data ingestion at VictoriaMetrics running on machines with big number of CPU cores.

Related issue: https://github.com/VictoriaMetrics/VictoriaLogs/issues/517
2025-08-28 16:25:18 +02:00
Artem Fetishev
5a056321af lib/uint64set: Optimize subtract operation
a.Subtract(b) perfomance degrades as b becomes bigger than a. For
example if len(b2) == 10xlen(b1) then time(a.Subtract(b2)) == 10x
time(a.Subtract(b1)).

A quick fix is to iterate over a elements in len(b) > len(a). Iterating
over a's elements and at the same time deleting should be safe since no
elements are actually deleted (i.e. memory freed, etc). Deletion here
means setting a corresponding bit from 1 to 0.

Fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/9602
2025-08-28 16:22:43 +02:00
68 changed files with 854 additions and 4260 deletions

View File

@@ -10,7 +10,6 @@ import (
"strings"
"time"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/configwatcher"
"github.com/VictoriaMetrics/metrics"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmagent/csvimport"
@@ -113,7 +112,6 @@ func main() {
flag.Usage = usage
envflag.Parse()
remotewrite.InitSecretFlags()
configwatcher.Init()
buildinfo.Init()
logger.Init()
timeserieslimits.Init(*maxLabelsPerTimeseries, *maxLabelNameLen, *maxLabelValueLen)
@@ -201,7 +199,6 @@ func main() {
}
protoparserutil.StopUnmarshalWorkers()
remotewrite.Stop()
configwatcher.Stop()
logger.Infof("successfully stopped vmagent in %.3f seconds", time.Since(startTime).Seconds())
}

View File

@@ -7,8 +7,8 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmagent/common"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmagent/remotewrite"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/auth"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prommetadata"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompb"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/opentelemetry/firehose"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/opentelemetry/stream"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/protoparserutil"
@@ -68,7 +68,7 @@ func insertRows(at *auth.Token, tss []prompb.TimeSeries, mms []prompb.MetricMeta
ctx.WriteRequest.Timeseries = tssDst
var metadataTotal int
if promscrape.IsMetadataEnabled() {
if prommetadata.IsEnabled() {
var accountID, projectID uint32
if at != nil {
accountID = at.AccountID

View File

@@ -7,8 +7,8 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmagent/remotewrite"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/auth"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httpserver"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prommetadata"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompb"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/prometheus"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/prometheus/stream"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/protoparserutil"
@@ -36,7 +36,7 @@ func InsertHandler(at *auth.Token, req *http.Request) error {
return err
}
encoding := req.Header.Get("Content-Encoding")
return stream.Parse(req.Body, defaultTimestamp, encoding, true, promscrape.IsMetadataEnabled(), func(rows []prometheus.Row, mms []prometheus.Metadata) error {
return stream.Parse(req.Body, defaultTimestamp, encoding, true, prommetadata.IsEnabled(), func(rows []prometheus.Row, mms []prometheus.Metadata) error {
return insertRows(at, rows, mms, extraLabels)
}, func(s string) {
httpserver.LogError(req, s)

View File

@@ -6,8 +6,8 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmagent/common"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmagent/remotewrite"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/auth"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prommetadata"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompb"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/promremotewrite/stream"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/protoparserutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/tenantmetrics"
@@ -71,7 +71,7 @@ func insertRows(at *auth.Token, timeseries []prompb.TimeSeries, mms []prompb.Met
ctx.WriteRequest.Timeseries = tssDst
var metadataTotal int
if promscrape.IsMetadataEnabled() {
if prommetadata.IsEnabled() {
var accountID, projectID uint32
if at != nil {
accountID = at.AccountID

View File

@@ -6,8 +6,8 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/app/vminsert/common"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vminsert/relabel"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httpserver"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prommetadata"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompb"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/prometheus"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/prometheus/stream"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/protoparserutil"
@@ -30,7 +30,7 @@ func InsertHandler(req *http.Request) error {
return err
}
encoding := req.Header.Get("Content-Encoding")
return stream.Parse(req.Body, defaultTimestamp, encoding, true, promscrape.IsMetadataEnabled(), func(rows []prometheus.Row, _ []prometheus.Metadata) error {
return stream.Parse(req.Body, defaultTimestamp, encoding, true, prommetadata.IsEnabled(), func(rows []prometheus.Row, _ []prometheus.Metadata) error {
return insertRows(rows, extraLabels)
}, func(s string) {
httpserver.LogError(req, s)

View File

@@ -142,6 +142,12 @@ func (s *series) summarize(aggrFunc aggrFunc, startTime, endTime, step int64, xF
}
func execExpr(ec *evalConfig, query string) (nextSeriesFunc, error) {
// Validate query length to prevent memory exhaustion
maxLen := searchutil.GetMaxQueryLen()
if len(query) > maxLen {
return nil, fmt.Errorf("too long query; got %d bytes; mustn't exceed `-search.maxQueryLen=%d` bytes", len(query), maxLen)
}
expr, err := graphiteql.Parse(query)
if err != nil {
return nil, fmt.Errorf("cannot parse %q: %w", query, err)

View File

@@ -4070,6 +4070,9 @@ func TestExecExprFailure(t *testing.T) {
f(`holtWintersConfidenceArea(group(time("foo.baz",15),time("foo.baz",15)))`)
f(`holtWintersConfidenceArea()`)
// too long query
f(`sumSeries(` + strings.Repeat("metric.very.long.name.that.takes.space,", 500) + `metric.final)`)
}
func compareSeries(ss, ssExpected []*series, expr graphiteql.Expr) error {

View File

@@ -2,6 +2,7 @@ package vmselect
import (
"embed"
"encoding/json"
"flag"
"fmt"
"net/http"
@@ -67,6 +68,7 @@ func Init() {
prometheus.InitMaxUniqueTimeseries(*maxConcurrentRequests)
concurrencyLimitCh = make(chan struct{}, *maxConcurrentRequests)
initVMUIConfig()
initVMAlertProxy()
}
@@ -460,6 +462,11 @@ func handleStaticAndSimpleRequests(w http.ResponseWriter, r *http.Request, path
return true
}
if strings.HasPrefix(path, "/vmui/") {
if path == "/vmui/config.json" {
w.Header().Set("Content-Type", "application/json")
fmt.Fprint(w, vmuiConfig)
return true
}
if strings.HasPrefix(path, "/vmui/static/") {
// Allow clients caching static contents for long period of time, since it shouldn't change over time.
// Path to static contents (such as js and css) must be changed whenever its contents is changed.
@@ -734,8 +741,34 @@ func proxyVMAlertRequests(w http.ResponseWriter, r *http.Request) {
var (
vmalertProxyHost string
vmalertProxy *nethttputil.ReverseProxy
vmuiConfig string
)
func initVMUIConfig() {
var cfg struct {
License struct {
Type string `json:"type"`
} `json:"license"`
VMAlert struct {
Enabled bool `json:"enabled"`
} `json:"vmalert"`
}
data, err := vmuiFiles.ReadFile("vmui/config.json")
if err != nil {
logger.Fatalf("cannot read vmui default config: %s", err)
}
err = json.Unmarshal(data, &cfg)
if err != nil {
logger.Fatalf("cannot parse vmui default config: %s", err)
}
cfg.VMAlert.Enabled = len(*vmalertProxyURL) != 0
data, err = json.Marshal(&cfg)
if err != nil {
logger.Fatalf("cannot create vmui config: %s", err)
}
vmuiConfig = string(data)
}
// initVMAlertProxy must be called after flag.Parse(), since it uses command-line flags.
func initVMAlertProxy() {
if len(*vmalertProxyURL) == 0 {

View File

@@ -24,7 +24,6 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/encoding"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/flagutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httpserver"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httputil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
@@ -38,7 +37,6 @@ var (
latencyOffset = flag.Duration("search.latencyOffset", time.Second*30, "The time when data points become visible in query results after the collection. "+
"It can be overridden on per-query basis via latency_offset arg. "+
"Too small value can result in incomplete last points for query results")
maxQueryLen = flagutil.NewBytes("search.maxQueryLen", 16*1024, "The maximum search query length in bytes")
maxLookback = flag.Duration("search.maxLookback", 0, "Synonym to -query.lookback-delta from Prometheus. "+
"The value is dynamically detected from interval between time series datapoints if not set. It can be overridden on per-query basis via max_lookback arg. "+
"See also '-search.maxStalenessInterval' flag, which has the same meaning due to historical reasons")
@@ -733,8 +731,9 @@ func QueryHandler(qt *querytracer.Tracer, startTime time.Time, w http.ResponseWr
step = defaultStep
}
if len(query) > maxQueryLen.IntN() {
return fmt.Errorf("too long query; got %d bytes; mustn't exceed `-search.maxQueryLen=%d` bytes", len(query), maxQueryLen.N)
maxLen := searchutil.GetMaxQueryLen()
if len(query) > maxLen {
return fmt.Errorf("too long query; got %d bytes; mustn't exceed `-search.maxQueryLen=%d` bytes", len(query), maxLen)
}
etfs, err := searchutil.GetExtraTagFilters(r)
if err != nil {
@@ -904,8 +903,9 @@ func queryRangeHandler(qt *querytracer.Tracer, startTime time.Time, w http.Respo
}
// Validate input args.
if len(query) > maxQueryLen.IntN() {
return fmt.Errorf("too long query; got %d bytes; mustn't exceed `-search.maxQueryLen=%d` bytes", len(query), maxQueryLen.N)
maxLen := searchutil.GetMaxQueryLen()
if len(query) > maxLen {
return fmt.Errorf("too long query; got %d bytes; mustn't exceed `-search.maxQueryLen=%d` bytes", len(query), maxLen)
}
if start > end {
end = start + defaultStep

View File

@@ -7,10 +7,12 @@ import (
"strings"
"time"
"github.com/VictoriaMetrics/metricsql"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/flagutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httputil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage"
"github.com/VictoriaMetrics/metricsql"
)
var (
@@ -20,6 +22,7 @@ var (
maxStatusRequestDuration = flag.Duration("search.maxStatusRequestDuration", time.Minute*5, "The maximum duration for /api/v1/status/* requests")
maxLabelsAPIDuration = flag.Duration("search.maxLabelsAPIDuration", time.Second*5, "The maximum duration for /api/v1/labels, /api/v1/label/.../values and /api/v1/series requests. "+
"See also -search.maxLabelsAPISeries and -search.ignoreExtraFiltersAtLabelsAPI")
maxQueryLen = flagutil.NewBytes("search.maxQueryLen", 16*1024, "The maximum search query length in bytes")
)
// GetMaxQueryDuration returns the maximum duration for query from r.
@@ -227,3 +230,8 @@ func toTagFilter(dst *storage.TagFilter, src *metricsql.LabelFilter) {
dst.IsRegexp = src.IsRegexp
dst.IsNegative = src.IsNegative
}
// GetMaxQueryLen returns the current value of the search.maxQueryLen flag.
func GetMaxQueryLen() int {
return maxQueryLen.IntN()
}

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

File diff suppressed because one or more lines are too long

View File

@@ -36,10 +36,10 @@
<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-SqjehVXD.js"></script>
<script type="module" crossorigin src="./assets/index-DY3sj68d.js"></script>
<link rel="modulepreload" crossorigin href="./assets/vendor-DBOs1yKE.js">
<link rel="stylesheet" crossorigin href="./assets/vendor-D1GxaB_c.css">
<link rel="stylesheet" crossorigin href="./assets/index-B7vIex3g.css">
<link rel="stylesheet" crossorigin href="./assets/index-XlRqIMog.css">
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>

View File

@@ -683,7 +683,7 @@ func writeStorageMetrics(w io.Writer, strg *storage.Storage) {
metrics.WriteCounterUint64(w, `vm_cache_eviction_bytes_total{type="storage/metricIDs", reason="miss_percentage"}`, m.MetricIDCacheMissEvictionBytes)
metrics.WriteCounterUint64(w, `vm_cache_eviction_bytes_total{type="storage/metricIDs", reason="expiration"}`, m.MetricIDCacheExpireEvictionBytes)
metrics.WriteCounterUint64(w, `vm_deleted_metrics_total{type="indexdb"}`, idbm.DeletedMetricsCount)
metrics.WriteCounterUint64(w, `vm_deleted_metrics_total{type="indexdb"}`, m.DeletedMetricsCount)
metrics.WriteCounterUint64(w, `vm_cache_collisions_total{type="storage/tsid"}`, m.TSIDCacheCollisions)
metrics.WriteCounterUint64(w, `vm_cache_collisions_total{type="storage/metricName"}`, m.MetricNameCacheCollisions)

View File

@@ -6,12 +6,3 @@ export enum AppType {
export const APP_TYPE = import.meta.env.VITE_APP_TYPE;
export const APP_TYPE_VM = APP_TYPE === AppType.victoriametrics;
export const APP_TYPE_ANOMALY = APP_TYPE === AppType.vmanomaly;
export const isDefaultDatasourceType = (datasourceType: string): boolean => {
switch (APP_TYPE) {
case AppType.victoriametrics:
return datasourceType == "prometheus" || datasourceType == "";
default:
return false;
}
};

View File

@@ -3,7 +3,7 @@ import { useEffect, useState } from "preact/compat";
import { ErrorTypes } from "../types";
import { APP_TYPE_VM } from "../constants/appType";
const useFetchFlags = () => {
const useFetchAppConfig = () => {
const dispatch = useAppDispatch();
const [isLoading, setIsLoading] = useState(false);
@@ -31,5 +31,5 @@ const useFetchFlags = () => {
return { isLoading, error };
};
export default useFetchFlags;
export default useFetchAppConfig;

View File

@@ -1,45 +0,0 @@
import { useAppDispatch, useAppState } from "../state/common/StateContext";
import { useEffect, useState } from "preact/compat";
import { ErrorTypes } from "../types";
import { APP_TYPE_VM } from "../constants/appType";
import { getUrlWithoutTenant } from "../utils/tenants";
const useFetchFlags = () => {
const { serverUrl } = useAppState();
const dispatch = useAppDispatch();
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<ErrorTypes | string>("");
useEffect(() => {
const fetchFlags = async () => {
if (!serverUrl || !APP_TYPE_VM) return;
setError("");
setIsLoading(true);
try {
const url = getUrlWithoutTenant(serverUrl);
const response = await fetch(`${url}/flags`);
const data = await response.text();
const flags = data.split("\n").filter(flag => flag.trim() !== "")
.reduce((acc, flag) => {
const [keyRaw, valueRaw] = flag.split("=");
const key = keyRaw.trim().replace(/^-/, "");
acc[key.trim()] = valueRaw ? valueRaw.trim().replace(/^"(.*)"$/, "$1") : null;
return acc;
}, {} as Record<string, string|null>);
dispatch({ type: "SET_FLAGS", payload: flags });
} catch (e) {
setIsLoading(false);
if (e instanceof Error) setError(`${e.name}: ${e.message}`);
}
};
fetchFlags();
}, [serverUrl]);
return { isLoading, error };
};
export default useFetchFlags;

View File

@@ -1,12 +1,11 @@
import Header from "../Header/Header";
import { FC, useEffect } from "preact/compat";
import { Outlet, useLocation, useSearchParams } from "react-router-dom";
import { Outlet, useSearchParams } from "react-router-dom";
import qs from "qs";
import "../MainLayout/style.scss";
import { getAppModeEnable } from "../../utils/app-mode";
import classNames from "classnames";
import Footer from "../Footer/Footer";
import { routerOptions } from "../../router";
import useFetchDefaultTimezone from "../../hooks/useFetchDefaultTimezone";
import useDeviceDetect from "../../hooks/useDeviceDetect";
import ControlsAnomalyLayout from "./ControlsAnomalyLayout";
@@ -14,17 +13,10 @@ import ControlsAnomalyLayout from "./ControlsAnomalyLayout";
const AnomalyLayout: FC = () => {
const appModeEnable = getAppModeEnable();
const { isMobile } = useDeviceDetect();
const { pathname } = useLocation();
const [searchParams, setSearchParams] = useSearchParams();
useFetchDefaultTimezone();
const setDocumentTitle = () => {
const defaultTitle = "vmui for vmanomaly";
const routeTitle = routerOptions[pathname]?.title;
document.title = routeTitle ? `${routeTitle} - ${defaultTitle}` : defaultTitle;
};
// for support old links with search params
const redirectSearchToHashParams = () => {
const { search, href } = window.location;
@@ -38,7 +30,6 @@ const AnomalyLayout: FC = () => {
if (newHref !== href) window.location.replace(newHref);
};
useEffect(setDocumentTitle, [pathname]);
useEffect(redirectSearchToHashParams, []);
return <section className="vm-container">

View File

@@ -11,7 +11,6 @@ import { useFetchDashboards } from "../../pages/PredefinedPanels/hooks/useFetchD
import useDeviceDetect from "../../hooks/useDeviceDetect";
import ControlsMainLayout from "./ControlsMainLayout";
import useFetchDefaultTimezone from "../../hooks/useFetchDefaultTimezone";
import useFetchFlags from "../../hooks/useFetchFlags";
import useFetchAppConfig from "../../hooks/useFetchAppConfig";
const MainLayout: FC = () => {
@@ -23,7 +22,6 @@ const MainLayout: FC = () => {
useFetchDashboards();
useFetchDefaultTimezone();
useFetchAppConfig();
useFetchFlags();
const setDocumentTitle = () => {
const defaultTitle = "vmui";

View File

@@ -54,7 +54,7 @@
display: flex;
flex-direction: column;
align-items: flex-start;
align-items: stretch;
gap: $padding-medium;
max-width: calc(100vw - var(--scrollbar-width));
@@ -67,7 +67,7 @@
width: 100%;
font-size: 12px;
flex-direction: column;
align-items: flex-start;
align-items: stretch;
gap: $padding-medium;
@media (max-width: 500px) {

View File

@@ -1,3 +1,5 @@
import { APP_TYPE, AppType } from "../constants/appType";
const router = {
home: "/",
metrics: "/metrics",
@@ -15,7 +17,6 @@ const router = {
rawQuery: "/raw-query",
downsamplingDebug: "/downsampling-filters-debug",
retentionDebug: "/retention-filters-debug",
alerts: "/alerts",
rules: "/rules",
notifiers: "/notifiers",
};
@@ -51,11 +52,23 @@ const routerOptionsDefault = {
},
};
const getDefaultOptions = (appType: AppType) => {
switch (appType) {
case AppType.vmanomaly:
return {
title: "Anomaly exploration",
...routerOptionsDefault,
};
default:
return {
title: "Query",
...routerOptionsDefault,
};
}
};
export const routerOptions: { [key: string]: RouterOptions } = {
[router.home]: {
title: "Query",
...routerOptionsDefault,
},
[router.home]: getDefaultOptions(APP_TYPE),
[router.rawQuery]: {
title: "Raw query",
...routerOptionsDefault,
@@ -127,10 +140,7 @@ export const routerOptions: { [key: string]: RouterOptions } = {
title: "Icons",
header: {},
},
[router.anomaly]: {
title: "Anomaly exploration",
...routerOptionsDefault,
},
[router.anomaly]: getDefaultOptions(AppType.vmanomaly),
[router.query]: {
title: "Query",
...routerOptionsDefault,

View File

@@ -17,7 +17,7 @@ interface NavigationConfig {
serverUrl: string,
isEnterpriseLicense: boolean,
showPredefinedDashboards: boolean,
showAlertLink: boolean,
showAlerting: boolean,
}
/**
@@ -43,7 +43,7 @@ const getExploreNav = () => [
];
/**
* Submenu for Alerts tab
* Submenu for Alerting tab
*/
const getAlertingNav = () => [
@@ -57,14 +57,14 @@ const getAlertingNav = () => [
export const getDefaultNavigation = ({
isEnterpriseLicense,
showPredefinedDashboards,
showAlertLink,
showAlerting,
}: NavigationConfig): NavigationItem[] => [
{ value: router.home },
{ value: router.rawQuery },
{ label: "Explore", submenu: getExploreNav() },
{ label: "Tools", submenu: getToolsNav(isEnterpriseLicense) },
{ value: router.dashboards, hide: !showPredefinedDashboards },
{ value: "Alerting", submenu: getAlertingNav(), hide: !showAlertLink },
{ value: "Alerting", submenu: getAlertingNav(), hide: !showAlerting },
];
/**

View File

@@ -9,17 +9,17 @@ import { APP_TYPE, AppType } from "../constants/appType";
const useNavigationMenu = () => {
const appModeEnable = getAppModeEnable();
const { dashboardsSettings } = useDashboardsState();
const { serverUrl, flags, appConfig } = useAppState();
const { serverUrl, appConfig } = useAppState();
const isEnterpriseLicense = appConfig.license?.type === "enterprise";
const showAlertLink = Boolean(flags["vmalert.proxyURL"]);
const showAlerting = appConfig?.vmalert?.enabled || false;
const showPredefinedDashboards = Boolean(!appModeEnable && dashboardsSettings.length);
const navigationConfig = useMemo(() => ({
serverUrl,
isEnterpriseLicense,
showAlertLink,
showAlerting,
showPredefinedDashboards
}), [serverUrl, isEnterpriseLicense, showAlertLink, showPredefinedDashboards]);
}), [serverUrl, isEnterpriseLicense, showAlerting, showPredefinedDashboards]);
const menu = useMemo(() => {

View File

@@ -10,7 +10,6 @@ export interface AppState {
tenantId: string;
theme: Theme;
isDarkTheme: boolean | null;
flags: Record<string, string | null>;
appConfig: AppConfig
}
@@ -18,7 +17,6 @@ export type Action =
| { type: "SET_SERVER", payload: string }
| { type: "SET_THEME", payload: Theme }
| { type: "SET_TENANT_ID", payload: string }
| { type: "SET_FLAGS", payload: Record<string, string | null> }
| { type: "SET_APP_CONFIG", payload: AppConfig }
| { type: "SET_DARK_THEME" }
@@ -29,7 +27,6 @@ export const initialState: AppState = {
tenantId,
theme: (getFromStorage("THEME") || Theme.system) as Theme,
isDarkTheme: null,
flags: {},
appConfig: {}
};
@@ -56,11 +53,6 @@ export function reducer(state: AppState, action: Action): AppState {
...state,
isDarkTheme: isDarkTheme(state.theme)
};
case "SET_FLAGS":
return {
...state,
flags: action.payload
};
case "SET_APP_CONFIG":
return {
...state,

View File

@@ -180,6 +180,9 @@ export interface AppConfig {
license?: {
type?: "enterprise" | "opensource";
};
vmalert?: {
enabled: boolean;
};
}
export interface Group {

View File

@@ -7,7 +7,8 @@ ROOT_IMAGE ?= alpine:3.22.1
ROOT_IMAGE_SCRATCH ?= scratch
CERTS_IMAGE := alpine:3.22.1
GO_BUILDER_IMAGE := golang:1.25.0-alpine
GO_BUILDER_IMAGE := golang:1.25.0
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
@@ -43,7 +44,7 @@ app-via-docker: package-builder
$(BUILDER_IMAGE) \
go build $(RACE) -trimpath -buildvcs=false \
-ldflags "-extldflags '-static' $(GO_BUILDINFO)" \
-tags 'netgo osusergo musl $(EXTRA_GO_BUILD_TAGS)' \
-tags 'netgo osusergo $(EXTRA_GO_BUILD_TAGS)' \
-o bin/$(APP_NAME)$(APP_SUFFIX)-prod $(PKG_PREFIX)/app/$(APP_NAME)
app-via-docker-windows: package-builder
@@ -158,11 +159,11 @@ app-via-docker-pure:
APP_SUFFIX='-pure' DOCKER_OPTS='--env CGO_ENABLED=0' $(MAKE) app-via-docker
app-via-docker-linux-amd64:
EXTRA_DOCKER_ENVS='CC=/opt/cross-builder/x86_64-linux-musl-cross/bin/x86_64-linux-musl-gcc' \
EXTRA_DOCKER_ENVS='CC=x86_64-linux-gnu-gcc' \
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 $(MAKE) app-via-docker-goos-goarch
app-via-docker-linux-arm64:
EXTRA_DOCKER_ENVS='CC=/opt/cross-builder/aarch64-linux-musl-cross/bin/aarch64-linux-musl-gcc' \
EXTRA_DOCKER_ENVS='CC=aarch64-linux-gnu-gcc' \
CGO_ENABLED=1 GOOS=linux GOARCH=arm64 $(MAKE) app-via-docker-goos-goarch
app-via-docker-linux-arm:
@@ -201,11 +202,11 @@ package-via-docker-pure:
APP_SUFFIX='-pure' DOCKER_OPTS='--env CGO_ENABLED=0' $(MAKE) package-via-docker
package-via-docker-amd64:
EXTRA_DOCKER_ENVS='CC=/opt/cross-builder/x86_64-linux-musl-cross/bin/x86_64-linux-musl-gcc' \
EXTRA_DOCKER_ENVS='CC=x86_64-linux-gnu-gcc' \
CGO_ENABLED=1 GOARCH=amd64 $(MAKE) package-via-docker-goarch
package-via-docker-arm64:
EXTRA_DOCKER_ENVS='CC=/opt/cross-builder/aarch64-linux-musl-cross/bin/aarch64-linux-musl-gcc' \
EXTRA_DOCKER_ENVS='CC=aarch64-linux-gnu-gcc' \
CGO_ENABLED=1 GOARCH=arm64 $(MAKE) package-via-docker-goarch
package-via-docker-arm:

View File

@@ -40,7 +40,11 @@ The communication scheme between components is the following:
and recording rules results back to `vmagent`;
* [alertmanager](#alertmanager) is configured to receive notifications from `vmalert`.
<img alt="VictoriaMetrics single-server deployment" width="500" src="assets/vm-single-server.png">
<picture>
<source srcset="assets/vm-single-server-dark.png" media="(prefers-color-scheme: dark)">
<source srcset="assets/vm-single-server-light.png" media="(prefers-color-scheme: light)">
<img src="assets/vm-single-server-light.png" alt="VictoriaMetrics single-server deployment" width="500" >
</picture>
To access Grafana use link [http://localhost:3000](http://localhost:3000).
@@ -78,7 +82,11 @@ The communication scheme between components is the following:
and recording rules to `vmagent`;
* [alertmanager](#alertmanager) is configured to receive notifications from `vmalert`.
<img alt="VictoriaMetrics cluster deployment" width="500" src="assets/vm-cluster.png">
<picture>
<source srcset="assets/vm-cluster-dark.png" media="(prefers-color-scheme: dark)">
<source srcset="assets/vm-cluster-light.png" media="(prefers-color-scheme: light)">
<img src="assets/vm-cluster-light.png" alt="VictoriaMetrics cluster deployment" width="500" src="assets/vm-cluster-light.png" >
</picture>
To access Grafana use link [http://localhost:3000](http://localhost:3000).

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

View File

@@ -1,5 +1,5 @@
# balance load among vmselects
# see https://docs.victoriametrics.com/victoriametrics/vmauth/#load-balancing
# balance load among vminsert and vmselect instances,
# see https://docs.victoriametrics.com/victoriametrics/vmauth/#load-balancing.
# Note: if username and password are changes, please update the Grafana datasource configuration
# and check other places where these credentials are used.
users:

View File

@@ -1,14 +1,6 @@
ARG go_builder_image=non-existing
FROM $go_builder_image
STOPSIGNAL SIGINT
RUN apk add git gcc musl-dev make wget --no-cache && \
mkdir /opt/cross-builder && \
cd /opt/cross-builder && \
for arch in aarch64 x86_64; do \
wget \
https://github.com/VictoriaMetrics/muslcc-mirror/releases/download/v1.0.0/${arch}-linux-musl-cross.tgz \
-O /opt/cross-builder/${arch}-musl.tgz \
--no-verbose && \
tar zxf ${arch}-musl.tgz -C ./ && \
rm /opt/cross-builder/${arch}-musl.tgz; \
done
RUN apt update && apt install -y \
gcc-x86-64-linux-gnu \
gcc-aarch64-linux-gnu

View File

@@ -14,10 +14,12 @@ services:
command:
- "--promscrape.config=/etc/prometheus/prometheus.yml"
- "--remoteWrite.url=http://vmauth:8427/insert/0/prometheus/api/v1/write"
- "--remoteWrite.basicAuth.username=foo"
- "--remoteWrite.basicAuth.password=bar"
restart: always
grafana:
image: grafana/grafana:12.0.2
image: grafana/grafana:12.1.1
depends_on:
- "vmauth"
ports:

View File

@@ -38,7 +38,7 @@ services:
restart: always
grafana:
image: grafana/grafana:12.0.2
image: grafana/grafana:12.1.1
depends_on:
- "victoriametrics"
ports:

View File

@@ -17,7 +17,8 @@ scrape_configs:
- job_name: vminsert
static_configs:
- targets:
- vminsert:8480
- vminsert-1:8480
- vminsert-2:8480
- job_name: vmselect
static_configs:
- targets:

View File

@@ -27,7 +27,7 @@ services:
restart: always
grafana:
image: grafana/grafana:12.0.2
image: grafana/grafana:12.1.1
depends_on:
- "victoriametrics"
ports:

View File

@@ -58,7 +58,7 @@ services:
- ./vmsingle/promscrape.yml:/promscrape.yml
grafana:
image: grafana/grafana:12.0.2
image: grafana/grafana:12.1.1
depends_on: [vmsingle]
ports:
- 3000:3000

View File

@@ -97,6 +97,7 @@ See also [case studies](https://docs.victoriametrics.com/victoriametrics/casestu
* [Building a Scalable and Cost-Effective Monitoring Stack on AWS with Victoria Metrics](https://medium.com/@heliodevhub/construindo-uma-stack-de-monitoramento-escal%C3%A1vel-e-econ%C3%B4mica-na-aws-com-victoria-metrics-532d535dcfb6)
* [Optimizing Datadog Costs with VictoriaMetrics: A Practical Step-by-Step Guide](https://medium.com/p/optimizing-datadog-costs-with-victoriametrics-a-practical-step-by-step-guide-c984d32c7423)
* [Kubernetes Monitoring — A Complete Solution](https://itnext.io/kubernetes-monitoring-a-complete-solution-part-1-architecture-eb5b998658d5)
* [Why VictoriaMetrics? A Practical Guide to Scalable and Faster Monitoring Than Prometheus](https://amir-shams.medium.com/why-victoriametrics-a-practical-guide-to-scalable-and-faster-monitoring-than-prometheus-54ef21f10465)
## Third-party articles and slides about VictoriaLogs

View File

@@ -24,15 +24,23 @@ See also [LTS releases](https://docs.victoriametrics.com/victoriametrics/lts-rel
## tip
## [v1.125.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.125.0)
Released at 2025-08-29
* FEATURE: [vmsingle](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/) and [vmselect](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/) in [VictoriaMetrics cluster](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/): apply `-search.maxQueryLen` limit to Graphite queries. Previously, this limit was only applied to Prometheus queries.
* FEATURE: upgrade Go builder from Go1.24.6 to Go1.25. See [Go1.25 release notes](https://tip.golang.org/doc/go1.25).
* FEATURE: [vmui](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#vmui): add export functionality for Query (Table view) and RawQuery tabs in CSV/JSON format. See [#9332](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/9332).
* FEATURE: [vmui](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#vmui): replace `Alerts` tab with `Alerting` tab in vmui. The new `Alerting` tab displays vmalert groups and rules directly in vmui interface without redirecting user to vmalert's WEB UI. Links of format `.*/prometheus/vmalert/.*` will continue working by redirecting to vmalert's UI. This functionality is available only if `-vmalert.proxyURL` is set on vmselect. Some functionality of the new `Alerting` tab requires vmalert to be of the same version as vmselect, or higher.
* FEATURE: [vmalert](https://docs.victoriametrics.com/victoriametrics/vmalert/): add `/api/v1/group?group_id=<id>` API endpoint for viewing details of a specific [rules group](https://docs.victoriametrics.com/vmalert.html#groups). The new handler is used by new `Alerting` tab in vmselect.
* FEATURE: [vmalert](https://docs.victoriametrics.com/victoriametrics/vmalert/): add `lastError` field to `/api/v1/notifiers` response. The new field contains an error message if the last attempt to send data to the notifier failed. The error can be also viewed in `Alerting. Notifiers` tab.
* FEATURE: [vmsingle](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/) and `vmstorage` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/): optimize `/api/v1/labels` and `/api/v1/label/TAG/values` requests with a single `match` or `extra_filters` filter containing timeseries metric name. See this PR [#9489](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/9489) for details.
* BUGFIX: [vmagent](https://docs.victoriametrics.com/victoriametrics/vmagent/): prevent remote write ingestion stop on push error for [Google Pub/Sub](https://docs.victoriametrics.com/victoriametrics/vmagent/#writing-metrics-to-pubsub) integration.
* BUGFIX: [vmauth](https://docs.victoriametrics.com/victoriametrics/vmauth/): properly handle [mTLS authorization and routing](https://docs.victoriametrics.com/victoriametrics/vmauth/#mtls-based-request-routing). Previously it didn't work. See [#29](https://github.com/VictoriaMetrics/VictoriaLogs/issues/29).
* BUGFIX: [MetricsQL](https://docs.victoriametrics.com/victoriametrics/metricsql/): fix `timestamp` function compatibility with Prometheus when used with sub-expressions such as `timestamp(sum(foo))`. The fix applies only when `-search.disableImplicitConversion` flag is set. See more in [#9527-comment](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/9527#issuecomment-3200646020) and [metricsql#55](https://github.com/VictoriaMetrics/metricsql/pull/55).
* BUGFIX: [vmsingle](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/) and `vmstorage` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/): optimize subtract operation on uint64 sets. This should potentially improve index search with huge number of deleted series. See this issue [9602](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/9602) for details.
* BUGFIX: all VictoriaMetrics [enterprise](https://docs.victoriametrics.com/enterprise/) components: fix support for automatic issuing of TLS certificates for HTTPS server at `-httpListenAddr` via [Let's Encrypt service](https://letsencrypt.org/). See [these docs](https://docs.victoriametrics.com/#automatic-issuing-of-tls-certificates).
## [v1.124.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.124.0)

View File

@@ -515,9 +515,9 @@ scrape_configs:
One of the following roles can be configured to discover targets:
* `role: services`
### Dockerswarm role: services
The `services` role discovers all Swarm services.
The `role: services` discovers all Swarm services.
Each discovered target has an [`__address__`](https://docs.victoriametrics.com/victoriametrics/relabeling/#how-to-modify-scrape-urls-in-targets) label set
to `<ip>:<port>`, where `<ip>` is the endpoint's virtual IP, while the `<port>` is the published port of the service.
@@ -542,9 +542,9 @@ One of the following roles can be configured to discover targets:
* `__meta_dockerswarm_network_label_<labelname>`: each label of the network
* `__meta_dockerswarm_network_scope`: the scope of the network
* `role: tasks`
### Dockerswarm role: tasks
The `tasks` role discovers all Swarm tasks.
The `role: tasks` discovers all Swarm tasks.
Each discovered target has an [`__address__`](https://docs.victoriametrics.com/victoriametrics/relabeling/#how-to-modify-scrape-urls-in-targets) label set
to `<ip>:<port>`, where the `<ip>` is the node IP, while the `<port>` is the published port of the task.
@@ -583,9 +583,9 @@ One of the following roles can be configured to discover targets:
The `__meta_dockerswarm_network_*` meta labels are not populated for ports which are published with `mode=host`.
* `role: nodes`
### Dockerswarm role: nodes
The `nodes` role is used to discover Swarm nodes.
The `role: nodes` is used to discover Swarm nodes.
Each discovered target has an [`__address__`](https://docs.victoriametrics.com/victoriametrics/relabeling/#how-to-modify-scrape-urls-in-targets) label set
to `<ip>:<port>`, where `<ip>` is the node IP, while the `<port>` is the `port` value obtained from the `dockerswarm_sd_configs`.
@@ -1072,7 +1072,7 @@ See [these examples](https://docs.victoriametrics.com/victoriametrics/scrape_con
One of the following `role` types can be configured to discover targets:
* `role: node`
### Kubernetes role: node
The `role: node` discovers one target per cluster node.
@@ -1093,7 +1093,7 @@ One of the following `role` types can be configured to discover targets:
In addition, the `instance` label for the node will be set to the node name as retrieved from the API server.
* `role: service`
### Kubernetes role: service
The `role: service` discovers Kubernetes services.
@@ -1120,7 +1120,7 @@ One of the following `role` types can be configured to discover targets:
* `__meta_kubernetes_service_port_protocol`: Protocol of the service port for the target.
* `__meta_kubernetes_service_type`: The type of the service.
* `role: pod`
### Kubernetes role: pod
The `role: pod` discovers all pods and exposes their containers as targets.
@@ -1153,8 +1153,7 @@ One of the following `role` types can be configured to discover targets:
* `__meta_kubernetes_pod_controller_kind`: Object kind of the pod controller.
* `__meta_kubernetes_pod_controller_name`: Name of the pod controller.
* `role: endpoints`
### Kubernetes role: endpoints
The `role: endpoints` discovers targets from listed endpoints of a service.
@@ -1180,10 +1179,10 @@ One of the following `role` types can be configured to discover targets:
* `__meta_kubernetes_endpoint_address_target_kind`: Kind of the endpoint address target.
* `__meta_kubernetes_endpoint_address_target_name`: Name of the endpoint address target.
If the endpoints belong to a service, all labels of the `role: service` are attached.
For all targets backed by a pod, all labels of the `role: pod` are attached.
If the endpoints belong to a service, all labels of the [`role: service`](#kubernetes-role-service) are attached.
For all targets backed by a pod, all labels of the [`role: pod`](#kubernetes-role-pod) are attached.
* `role: endpointslice`
### Kubernetes role: endpointslice
The `role: endpointslice` discovers targets from existing endpointslices.
@@ -1209,10 +1208,10 @@ One of the following `role` types can be configured to discover targets:
* `__meta_kubernetes_endpointslice_port_name`: Named port of the referenced endpoint.
* `__meta_kubernetes_endpointslice_port_protocol`: Protocol of the referenced endpoint.
If the endpoints belong to a service, all labels of the `role: service` are attached.
For all targets backed by a pod, all labels of the `role: pod` are attached.
If the endpoints belong to a service, all labels of the [`role: service`](#kubernetes-role-service) are attached.
For all targets backed by a pod, all labels of the [`role: pod`](#kubernetes-role-pod) are attached.
* `role: ingress`
### Kubernetes role: ingress
The `role: ingress` discovers a target for each path of each ingress.
@@ -1469,9 +1468,9 @@ scrape_configs:
# ...
```
One of the following `role` types can be configured to discover targets:
One of the following `role` types can be configured to discover OpenStack targets:
* `role: hypervisor`
### OpenStack role: hypervisor
The `role: hypervisor` discovers one target per Nova hypervisor node.
@@ -1487,7 +1486,7 @@ One of the following `role` types can be configured to discover targets:
* `__meta_openstack_hypervisor_status`: the hypervisor node's status.
* `__meta_openstack_hypervisor_type`: the hypervisor node's type.
* `role: instance`
### OpenStack role: instance
The `role: instance` discovers one target per network interface of Nova instance.

View File

@@ -9,7 +9,6 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/common"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/backup/fscommon"
libfs "github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
)
@@ -103,7 +102,7 @@ func (fs *FS) CopyPart(srcFS common.OriginFS, p common.Part) error {
}
// Attempt to create hardlink from srcPath to dstPath.
if err := os.Link(srcPath, dstPath); err == nil {
libfs.MustSyncPath(dstPath)
// There is no need in fsync for dstPath, since its contents must be already synced at srcPath.
return nil
}

View File

@@ -1,121 +0,0 @@
package configwatcher
import (
"flag"
"sync"
"time"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/procutil"
)
// TBD: migrate /-/reload handler to the package
// TBD: print registered configs in 10s after the start
// TBD: print what reload methods enabled
type handler struct {
flag string
handler func()
}
var configCheckInterval = flag.Duration("configCheckInterval", 0, "TBD")
var signalHandlers []handler
var checkIntervalHandlers []handler
var mux = sync.Mutex{}
func RegisterHandler(flag string, handlerFn func()) {
RegisterSignalHandler(flag, handlerFn)
RegisterCheckIntervalHandler(flag, handlerFn)
}
func RegisterSignalHandler(flag string, handlerFn func()) {
mux.Lock()
defer mux.Unlock()
signalHandlers = append(signalHandlers, handler{
flag: flag,
handler: handlerFn,
})
}
func RegisterCheckIntervalHandler(flag string, handlerFn func()) {
mux.Lock()
defer mux.Unlock()
checkIntervalHandlers = append(checkIntervalHandlers, handler{
flag: flag,
handler: handlerFn,
})
}
func UnregisterHandler(flag string) {
mux.Lock()
defer mux.Unlock()
newCheckIntervalHandlers := make([]handler, 0, len(checkIntervalHandlers))
for _, h := range checkIntervalHandlers {
if h.flag != flag {
newCheckIntervalHandlers = append(newCheckIntervalHandlers, h)
}
}
newSignalHandlers := make([]handler, 0, len(signalHandlers))
for _, h := range signalHandlers {
if h.flag != flag {
newSignalHandlers = append(newSignalHandlers, h)
}
}
checkIntervalHandlers = newCheckIntervalHandlers
}
var stopChan chan struct{}
func Init() {
stopChan = make(chan struct{})
go func() {
sighupCh := procutil.NewSighupChan()
var tickerCh <-chan time.Time
if *configCheckInterval > 0 {
ticker := time.NewTicker(*configCheckInterval)
tickerCh = ticker.C
defer ticker.Stop()
}
for {
select {
case <-sighupCh:
mux.Lock()
for _, h := range signalHandlers {
h.handler()
}
mux.Unlock()
case <-tickerCh:
mux.Lock()
for _, h := range checkIntervalHandlers {
h.handler()
}
mux.Unlock()
case <-stopChan:
return
}
}
}()
}
// Method for BC
func EnableCheckInterval(dur time.Duration) {
mux.Lock()
defer mux.Unlock()
if dur > *configCheckInterval {
*configCheckInterval = dur
}
}
// Stop stops Prometheus scraper.
func Stop() {
close(stopChan)
}

View File

@@ -107,31 +107,32 @@ func IsTemporaryFileName(fn string) bool {
var tmpFileNameRe = regexp.MustCompile(`\.tmp\.\d+$`)
// MustMkdirIfNotExist creates the given path dir if it isn't exist.
//
// The caller is responsible for MustSyncPath() call for the parent directory for the path.
func MustMkdirIfNotExist(path string) {
if IsPathExist(path) {
return
}
mustMkdirSync(path)
mustMkdir(path)
}
// MustMkdirFailIfExist creates the given path dir if it isn't exist.
//
// If the directory at the given path already exists, then the function logs the error and exits.
// If the directory at the given path already exists, then the function logs the fatal error and exits the process.
//
// The caller is responsible for MustSyncPath() call for the parent directory for the path.
func MustMkdirFailIfExist(path string) {
if IsPathExist(path) {
logger.Panicf("FATAL: the %q already exists", path)
}
mustMkdirSync(path)
mustMkdir(path)
}
func mustMkdirSync(path string) {
func mustMkdir(path string) {
if err := os.MkdirAll(path, 0755); err != nil {
logger.Panicf("FATAL: cannot create directory: %s", err)
}
// Sync the parent directory, so the created directory becomes visible
// in the fs after power loss.
parentDirPath := filepath.Dir(path)
MustSyncPath(parentDirPath)
// Do not sync the parent directory - this is the responsibility of the caller.
}
// MustClose must close the given file f.
@@ -174,9 +175,11 @@ func MustReadDir(dir string) []os.DirEntry {
return des
}
// MustHardLinkFiles makes hard links for all the files from srcDir in dstDir.
// MustHardLinkFiles creates dstDir and makes hard links for all the files from srcDir in dstDir.
//
// The caller is responsible for calling MustSyncPath for the parent directory of dstDir.
func MustHardLinkFiles(srcDir, dstDir string) {
mustMkdirSync(dstDir)
mustMkdir(dstDir)
des := MustReadDir(srcDir)
for _, de := range des {
@@ -196,6 +199,8 @@ func MustHardLinkFiles(srcDir, dstDir string) {
}
// MustSymlinkRelative creates relative symlink for srcPath in dstPath.
//
// The caller is responsible for calling MustSyncPath() for the parent directory of dstPath.
func MustSymlinkRelative(srcPath, dstPath string) {
baseDir := filepath.Dir(dstPath)
srcPathRel, err := filepath.Rel(baseDir, srcPath)
@@ -207,10 +212,13 @@ func MustSymlinkRelative(srcPath, dstPath string) {
}
}
// MustCopyDirectory copies all the files in srcPath to dstPath.
// MustCopyDirectory creates dstPath and copies all the files in srcPath to dstPath.
//
// The caller is responsible for calling MustSyncPath() for the parent directory of dstPath.
func MustCopyDirectory(srcPath, dstPath string) {
mustMkdir(dstPath)
des := MustReadDir(srcPath)
MustMkdirIfNotExist(dstPath)
for _, de := range des {
if !de.Type().IsRegular() {
// Skip non-files
@@ -220,6 +228,7 @@ func MustCopyDirectory(srcPath, dstPath string) {
dst := filepath.Join(dstPath, de.Name())
MustCopyFile(src, dst)
}
MustSyncPath(dstPath)
}

View File

@@ -351,12 +351,15 @@ func MustOpenTable(path string, flushInterval time.Duration, flushCallback func(
flushInterval = pendingItemsFlushInterval
}
// Create a directory for the table if it doesn't exist yet.
// Create a directory at the path if it doesn't exist yet.
fs.MustMkdirIfNotExist(path)
// Open table parts.
pws := mustOpenParts(path)
// Sync the path and the parent dir, so the path becomes visible in the parent dir.
fs.MustSyncPathAndParentDir(path)
tb := &Table{
path: path,
flushInterval: flushInterval,
@@ -1481,9 +1484,6 @@ func (tb *Table) nextMergeIdx() uint64 {
}
func mustOpenParts(path string) []*partWrapper {
// The path can be missing after restoring from backup, so create it if needed.
fs.MustMkdirIfNotExist(path)
// Remove txn and tmp directories, which may be left after the upgrade
// to v1.90.0 and newer versions.
fs.MustRemoveDir(filepath.Join(path, "txn"))
@@ -1522,7 +1522,6 @@ func mustOpenParts(path string) []*partWrapper {
fs.MustRemoveDir(deletePath)
}
}
fs.MustSyncPath(path)
// Open parts
var pws []*partWrapper
@@ -1594,9 +1593,7 @@ func (tb *Table) MustCreateSnapshotAt(dstDir string) {
fs.MustHardLinkFiles(srcPartPath, dstPartPath)
}
fs.MustSyncPath(dstDir)
parentDir := filepath.Dir(dstDir)
fs.MustSyncPath(parentDir)
fs.MustSyncPathAndParentDir(dstDir)
logger.Infof("created Table snapshot of %q at %q in %.3f seconds", srcDir, dstDir, time.Since(startTime).Seconds())
}

View File

@@ -178,6 +178,7 @@ func tryOpeningQueue(path, name string, chunkFileSize, maxBlockSize, maxPendingB
fs.MustClose(q.flockF)
}
}()
fs.MustSyncPathAndParentDir(path)
// Read metainfo.
var mi metainfo
@@ -643,7 +644,6 @@ func (mi *metainfo) WriteToFile(path string) error {
return fmt.Errorf("cannot marshal persistent queue metainfo %#v: %w", mi, err)
}
fs.MustWriteSync(path, data)
fs.MustSyncPath(path)
return nil
}

View File

@@ -0,0 +1,19 @@
package prommetadata
import "flag"
var enableMetadata = flag.Bool("enableMetadata", false, "Whether to enable metadata processing for metrics scraped from targets, received via VictoriaMetrics remote write, Prometheus remote write v1 or OpenTelemetry protocol. "+
"See also remoteWrite.maxMetadataPerBlock")
// IsEnabled reports whether metadata processing is enabled.
func IsEnabled() bool {
return *enableMetadata
}
// SetEnabled sets enableMetadata to v and returns the previous value of enableMetadata.
// This function is intended for promscrape tests.
func SetEnabled(v bool) bool {
prev := *enableMetadata
*enableMetadata = v
return prev
}

View File

@@ -52,8 +52,6 @@ import (
)
var (
enableMetadata = flag.Bool("enableMetadata", false, "Whether to enable metadata processing for metrics scraped from targets, received via VictoriaMetrics remote write, Prometheus remote write v1 or OpenTelemetry protocol. "+
"See also remoteWrite.maxMetadataPerBlock")
noStaleMarkers = flag.Bool("promscrape.noStaleMarkers", false, "Whether to disable sending Prometheus stale markers for metrics when scrape target disappears. This option may reduce memory usage if stale markers aren't needed for your setup. This option also disables populating the scrape_series_added metric. See https://prometheus.io/docs/concepts/jobs_instances/#automatically-generated-labels-and-time-series")
seriesLimitPerTarget = flag.Int("promscrape.seriesLimitPerTarget", 0, "Optional limit on the number of unique time series a single scrape target can expose. See https://docs.victoriametrics.com/victoriametrics/vmagent/#cardinality-limiter for more info")
strictParse = flag.Bool("promscrape.config.strictParse", true, "Whether to deny unsupported fields in -promscrape.config . Set to false in order to silently skip unsupported fields")
@@ -90,11 +88,6 @@ var (
var clusterMemberID int
// IsMetadataEnabled returns true if metadata is enabled.
func IsMetadataEnabled() bool {
return *enableMetadata
}
func mustInitClusterMemberID() {
s := *clusterMemberNum
// special case for kubernetes deployment, where pod-name formatted at some-pod-name-1

View File

@@ -9,12 +9,12 @@ import (
"sync/atomic"
"time"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/configwatcher"
"github.com/VictoriaMetrics/metrics"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/auth"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/procutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompb"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discovery/azure"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discovery/consul"
@@ -113,7 +113,7 @@ func runScraper(configFile string, pushData func(at *auth.Token, wr *prompb.Writ
// Register SIGHUP handler for config reload before loadConfig.
// This guarantees that the config will be re-read if the signal arrives just after loadConfig.
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1240
//sighupCh := procutil.NewSighupChan()
sighupCh := procutil.NewSighupChan()
logger.Infof("reading scrape configs from %q", configFile)
cfg, err := loadConfig(configFile)
@@ -152,69 +152,61 @@ func runScraper(configFile string, pushData func(at *auth.Token, wr *prompb.Writ
scs.add("yandexcloud_sd_configs", *yandexcloud.SDCheckInterval, func(cfg *Config, swsPrev []*ScrapeWork) []*ScrapeWork { return cfg.getYandexCloudSDScrapeWork(swsPrev) })
scs.add("static_configs", 0, func(cfg *Config, _ []*ScrapeWork) []*ScrapeWork { return cfg.getStaticScrapeWork() })
scs.updateConfig(cfg)
var tickerCh <-chan time.Time
if *configCheckInterval > 0 {
// TBD print notice that deprecated -promscrape.configCheckInterval is used
configwatcher.EnableCheckInterval(*configCheckInterval)
configwatcher.RegisterCheckIntervalHandler("-promscrape.config", func() {
ticker := time.NewTicker(*configCheckInterval)
tickerCh = ticker.C
defer ticker.Stop()
}
for {
scs.updateConfig(cfg)
waitForChans:
select {
case <-sighupCh:
logger.Infof("SIGHUP received; reloading Prometheus configs from %q", configFile)
cfgNew, err := loadConfig(configFile)
if err != nil {
configReloadErrors.Inc()
configSuccess.Set(0)
logger.Errorf("cannot read %q: %s; continuing with the previous config", configFile, err)
return
logger.Errorf("cannot read %q on SIGHUP: %s; continuing with the previous config", configFile, err)
goto waitForChans
}
configSuccess.Set(1)
if !cfgNew.mustRestart(cfg) {
return
logger.Infof("nothing changed in %q", configFile)
goto waitForChans
}
cfg = cfgNew
marshaledData = cfg.marshal()
configData.Store(&marshaledData)
configReloads.Inc()
configTimestamp.Set(fasttime.UnixTimestamp())
scs.updateConfig(cfg)
})
case <-tickerCh:
cfgNew, err := loadConfig(configFile)
if err != nil {
configReloadErrors.Inc()
configSuccess.Set(0)
logger.Errorf("cannot read %q: %s; continuing with the previous config", configFile, err)
goto waitForChans
}
configSuccess.Set(1)
if !cfgNew.mustRestart(cfg) {
goto waitForChans
}
cfg = cfgNew
marshaledData = cfg.marshal()
configData.Store(&marshaledData)
configReloads.Inc()
configTimestamp.Set(fasttime.UnixTimestamp())
case <-globalStopCh:
cfg.mustStop()
logger.Infof("stopping Prometheus scrapers")
startTime := time.Now()
scs.stop()
logger.Infof("stopped Prometheus scrapers in %.3f seconds", time.Since(startTime).Seconds())
return
}
}
configwatcher.RegisterSignalHandler("-promscrape.config", func() {
logger.Infof("SIGHUP received; reloading Prometheus configs from %q", configFile)
cfgNew, err := loadConfig(configFile)
if err != nil {
configReloadErrors.Inc()
configSuccess.Set(0)
logger.Errorf("cannot read %q on SIGHUP: %s; continuing with the previous config", configFile, err)
return
}
configSuccess.Set(1)
if !cfgNew.mustRestart(cfg) {
logger.Infof("nothing changed in %q", configFile)
return
}
cfg = cfgNew
marshaledData = cfg.marshal()
configData.Store(&marshaledData)
configReloads.Inc()
configTimestamp.Set(fasttime.UnixTimestamp())
scs.updateConfig(cfg)
})
go func() {
<-globalStopCh
configwatcher.UnregisterHandler("-promscrape.config")
cfg.mustStop()
logger.Infof("stopping Prometheus scrapers")
startTime := time.Now()
scs.stop()
logger.Infof("stopped Prometheus scrapers in %.3f seconds", time.Since(startTime).Seconds())
return
}()
}
var (

View File

@@ -24,6 +24,7 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/lib/leveledbytebufferpool"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promauth"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prommetadata"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompb"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promrelabel"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutil"
@@ -520,7 +521,7 @@ func (sw *scrapeWork) processDataOneShot(scrapeTimestamp, realTimestamp int64, b
up = 0
scrapesFailed.Inc()
} else {
if IsMetadataEnabled() {
if prommetadata.IsEnabled() {
wc.rows, wc.metadataRows = parser.UnmarshalWithMetadata(wc.rows, wc.metadataRows, bodyString, sw.logError)
} else {
wc.rows.UnmarshalWithErrLogger(bodyString, sw.logError)
@@ -616,7 +617,7 @@ func (sw *scrapeWork) processDataInStreamMode(scrapeTimestamp, realTimestamp int
areIdenticalSeries := areIdenticalSeries(cfg, lastScrapeStr, bodyString)
r := body.NewReader()
err := stream.Parse(r, scrapeTimestamp, "", false, IsMetadataEnabled(), func(rows []parser.Row, mms []parser.Metadata) error {
err := stream.Parse(r, scrapeTimestamp, "", false, prommetadata.IsEnabled(), func(rows []parser.Row, mms []parser.Metadata) error {
labelsLen := maxLabelsLen.Load()
wc := writeRequestCtxPool.Get(int(labelsLen))
defer func() {

View File

@@ -11,6 +11,7 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/lib/auth"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/chunkedbuffer"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prommetadata"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompb"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promrelabel"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutil"
@@ -144,11 +145,10 @@ func TestScrapeWorkScrapeInternalSuccess(t *testing.T) {
}
func testScrapeWorkScrapeInternalSuccess(t *testing.T, streamParse bool) {
oldIsmetadataEnabled := *enableMetadata
oldMetadataEnabled := prommetadata.SetEnabled(true)
defer func() {
*enableMetadata = oldIsmetadataEnabled
prommetadata.SetEnabled(oldMetadataEnabled)
}()
*enableMetadata = true
f := func(data string, cfg *ScrapeWork, dataExpected string, metaDataExpected []prompb.MetricMetadata) {
t.Helper()
@@ -599,11 +599,10 @@ func testScrapeWorkScrapeInternalSuccess(t *testing.T, streamParse bool) {
//
// The core parsing functionality is validated separately in TestScrapeWorkScrapeInternalSuccess.
func TestScrapeWorkScrapeInternalStreamConcurrency(t *testing.T) {
oldIsmetadataEnabled := *enableMetadata
oldMetadataEnabled := prommetadata.SetEnabled(true)
defer func() {
*enableMetadata = oldIsmetadataEnabled
prommetadata.SetEnabled(oldMetadataEnabled)
}()
*enableMetadata = true
f := func(data string, cfg *ScrapeWork, pushDataCallsExpected int64, timeseriesExpected, timeseriesExpectedDelta, metadataExpected int64) {
t.Helper()

View File

@@ -9,6 +9,7 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/lib/auth"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/chunkedbuffer"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prommetadata"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompb"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/protoparserutil"
)
@@ -130,11 +131,10 @@ func BenchmarkScrapeWorkScrapeInternalStreamBigData(b *testing.B) {
}
func BenchmarkScrapeWorkScrapeInternalOneShotWithMetadata(b *testing.B) {
oldIsmetadataEnabled := *enableMetadata
oldMetadataEnabled := prommetadata.SetEnabled(true)
defer func() {
*enableMetadata = oldIsmetadataEnabled
prommetadata.SetEnabled(oldMetadataEnabled)
}()
*enableMetadata = true
data := `
# TYPE vm_tcplistener_accepts_total counter
# HELP vm_tcplistener_accepts_total some useless help message

View File

@@ -199,8 +199,6 @@ type IndexDBMetrics struct {
TagFiltersToMetricIDsCacheRequests uint64
TagFiltersToMetricIDsCacheMisses uint64
DeletedMetricsCount uint64
IndexDBRefCount uint64
MissingTSIDsForMetricID uint64
@@ -231,8 +229,6 @@ func (db *indexDB) scheduleToDrop() {
// UpdateMetrics updates m with metrics from the db.
func (db *indexDB) UpdateMetrics(m *IndexDBMetrics) {
// global index metrics
m.DeletedMetricsCount += uint64(db.s.getDeletedMetricIDs().Len())
m.IndexBlocksWithMetricIDsProcessed = indexBlocksWithMetricIDsProcessed.Load()
m.IndexBlocksWithMetricIDsIncorrectOrder = indexBlocksWithMetricIDsIncorrectOrder.Load()
@@ -623,18 +619,21 @@ func (is *indexSearch) searchLabelNamesWithFiltersOnTimeRange(qt *querytracer.Tr
}
func (is *indexSearch) searchLabelNamesWithFiltersOnDate(qt *querytracer.Tracer, tfss []*TagFilters, date uint64, maxLabelNames, maxMetrics int) (map[string]struct{}, error) {
filter, err := is.searchMetricIDsWithFiltersOnDate(qt, tfss, date, maxMetrics)
if err != nil {
return nil, err
}
if filter != nil && filter.Len() <= 100e3 {
// It is faster to obtain label names by metricIDs from the filter
// instead of scanning the inverted index for the matching filters.
// This should help https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2978
metricIDs := filter.AppendTo(nil)
qt.Printf("sort %d metricIDs", len(metricIDs))
lns := is.getLabelNamesForMetricIDs(qt, metricIDs, maxLabelNames)
return lns, nil
var filter *uint64set.Set
if !isSingleMetricNameFilter(tfss) {
filter, err := is.searchMetricIDsWithFiltersOnDate(qt, tfss, date, maxMetrics)
if err != nil {
return nil, err
}
if filter != nil && filter.Len() <= 100e3 {
// It is faster to obtain label names by metricIDs from the filter
// instead of scanning the inverted index for the matching filters.
// This should help https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2978
metricIDs := filter.AppendTo(nil)
qt.Printf("sort %d metricIDs", len(metricIDs))
lns := is.getLabelNamesForMetricIDs(qt, metricIDs, maxLabelNames)
return lns, nil
}
}
var prevLabelName []byte
@@ -885,23 +884,27 @@ func (is *indexSearch) searchLabelValuesOnTimeRange(qt *querytracer.Tracer, labe
}
func (is *indexSearch) searchLabelValuesOnDate(qt *querytracer.Tracer, labelName string, tfss []*TagFilters, date uint64, maxLabelValues, maxMetrics int) (map[string]struct{}, error) {
filter, err := is.searchMetricIDsWithFiltersOnDate(qt, tfss, date, maxMetrics)
if err != nil {
return nil, err
}
if filter != nil && filter.Len() <= 100e3 {
// It is faster to obtain label values by metricIDs from the filter
// instead of scanning the inverted index for the matching filters.
// This should help https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2978
metricIDs := filter.AppendTo(nil)
qt.Printf("sort %d metricIDs", len(metricIDs))
lvs := is.getLabelValuesForMetricIDs(qt, labelName, metricIDs, maxLabelValues)
return lvs, nil
}
if labelName == "__name__" {
// __name__ label is encoded as empty string in indexdb.
labelName = ""
}
useCompositeScan := labelName != "" && isSingleMetricNameFilter(tfss)
var filter *uint64set.Set
if !useCompositeScan {
filter, err := is.searchMetricIDsWithFiltersOnDate(qt, tfss, date, maxMetrics)
if err != nil {
return nil, err
}
if filter != nil && filter.Len() <= 100e3 {
// It is faster to obtain label values by metricIDs from the filter
// instead of scanning the inverted index for the matching filters.
// This should help https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2978
metricIDs := filter.AppendTo(nil)
qt.Printf("sort %d metricIDs", len(metricIDs))
lvs := is.getLabelValuesForMetricIDs(qt, labelName, metricIDs, maxLabelValues)
return lvs, nil
}
}
labelNameBytes := bytesutil.ToUnsafeBytes(labelName)
if name := getCommonMetricNameForTagFilterss(tfss); len(name) > 0 && labelName != "" {
@@ -1350,6 +1353,7 @@ func (is *indexSearch) getTSDBStatus(qt *querytracer.Tracer, tfss []*TagFilters,
qt.Printf("no matching series for filter=%s", tfss)
return &TSDBStatus{}, nil
}
ts := &is.ts
kb := &is.kb
mp := &is.mp
@@ -2190,6 +2194,11 @@ func matchTagFilters(mn *MetricName, tfs []*tagFilter, kb *bytesutil.ByteBuffer)
return true, nil
}
func isSingleMetricNameFilter(tfss []*TagFilters) bool {
// We check if tfss contain only single filter which is __name__
return len(tfss) == 1 && len(tfss[0].tfs) == 1 && getMetricNameFilter(tfss[0]) != nil
}
func (is *indexSearch) searchMetricIDsWithFiltersOnDate(qt *querytracer.Tracer, tfss []*TagFilters, date uint64, maxMetrics int) (*uint64set.Set, error) {
if len(tfss) == 0 {
return nil, nil

View File

@@ -1704,6 +1704,10 @@ func TestSearchTSIDWithTimeRange(t *testing.T) {
if err := tfsMetricName.Add(nil, []byte("testMetric"), false, false); err != nil {
t.Fatalf("cannot add filter on metric name: %s", err)
}
tfsComposite := NewTagFilters()
if err := tfsComposite.Add(nil, []byte("testMetric"), false, false); err != nil {
t.Fatalf("cannot add filter: %s", err)
}
// Perform a search within a day.
// This should return the metrics for the day
@@ -1749,6 +1753,16 @@ func TestSearchTSIDWithTimeRange(t *testing.T) {
t.Fatalf("unexpected labelNames; got\n%s\nwant\n%s", got, labelNames)
}
// Check SearchLabelNames with filters on composite key and time range.
lns, err = idbCurr.SearchLabelNames(nil, []*TagFilters{tfsComposite}, tr, 10000, 1e9, noDeadline)
if err != nil {
t.Fatalf("unexpected error in SearchLabelNames(filters=%s, timeRange=%s): %s", tfs, &tr, err)
}
got = sortedSlice(lns)
if !reflect.DeepEqual(got, labelNames) {
t.Fatalf("unexpected labelNames; got\n%s\nwant\n%s", got, labelNames)
}
// Check SearchLabelValues with the specified filter.
lvs, err = idbCurr.SearchLabelValues(nil, "", []*TagFilters{tfs}, TimeRange{}, 10000, 1e9, noDeadline)
if err != nil {
@@ -1779,6 +1793,17 @@ func TestSearchTSIDWithTimeRange(t *testing.T) {
t.Fatalf("unexpected labelValues; got\n%s\nwant\n%s", got, labelValues)
}
// Check SearchLabelValues with filters on composite key and time range.
lvs, err = idbCurr.SearchLabelValues(nil, "constant", []*TagFilters{tfsComposite}, tr, 10000, 1e9, noDeadline)
if err != nil {
t.Fatalf("unexpected error in SearchLabelValues(filters=%s, timeRange=%s): %s", tfs, &tr, err)
}
got = sortedSlice(lvs)
labelValues = []string{"const"}
if !reflect.DeepEqual(got, labelValues) {
t.Fatalf("unexpected labelValues; got\n%s\nwant\n%s", got, labelValues)
}
// Perform a search across all the days, should match all metrics
tr = TimeRange{
MinTimestamp: int64(timestamp - msecPerDay*days),

View File

@@ -192,8 +192,7 @@ func (pw *partWrapper) decRef() {
}
}
// mustCreatePartition creates new partition for the given timestamp and the given paths
// to small and big partitions.
// mustCreatePartition creates new partition for the given timestamp and the given paths to small and big partitions.
func mustCreatePartition(timestamp int64, smallPartitionsPath, bigPartitionsPath string, s *Storage) *partition {
name := timestampToPartitionName(timestamp)
smallPartsPath := filepath.Join(filepath.Clean(smallPartitionsPath), name)
@@ -208,6 +207,9 @@ func mustCreatePartition(timestamp int64, smallPartitionsPath, bigPartitionsPath
pt := newPartition(name, smallPartsPath, bigPartsPath, tr, s)
fs.MustSyncPathAndParentDir(smallPartsPath)
fs.MustSyncPathAndParentDir(bigPartsPath)
pt.startBackgroundWorkers()
logger.Infof("partition %q has been created", name)
@@ -242,6 +244,10 @@ func mustOpenPartition(smallPartsPath, bigPartsPath string, s *Storage) *partiti
smallPartsPath = filepath.Clean(smallPartsPath)
bigPartsPath = filepath.Clean(bigPartsPath)
// Create paths to parts if they are missing.
fs.MustMkdirIfNotExist(smallPartsPath)
fs.MustMkdirIfNotExist(bigPartsPath)
name := filepath.Base(smallPartsPath)
var tr TimeRange
if err := tr.fromPartitionName(name); err != nil {
@@ -268,6 +274,9 @@ func mustOpenPartition(smallPartsPath, bigPartsPath string, s *Storage) *partiti
pt.smallParts = smallParts
pt.bigParts = bigParts
fs.MustSyncPathAndParentDir(smallPartsPath)
fs.MustSyncPathAndParentDir(bigPartsPath)
pt.startBackgroundWorkers()
return pt
@@ -1917,9 +1926,6 @@ func getPartsSize(pws []*partWrapper) uint64 {
}
func mustOpenParts(partsFile, path string, partNames []string) []*partWrapper {
// The path can be missing after restoring from backup, so create it if needed.
fs.MustMkdirIfNotExist(path)
// Remove txn and tmp directories, which may be left after the upgrade
// to v1.90.0 and newer versions.
fs.MustRemoveDir(filepath.Join(path, "txn"))
@@ -1955,7 +1961,6 @@ func mustOpenParts(partsFile, path string, partNames []string) []*partWrapper {
fs.MustRemoveDir(deletePath)
}
}
fs.MustSyncPath(path)
// Open parts
var pws []*partWrapper
@@ -2003,6 +2008,9 @@ func (pt *partition) MustCreateSnapshotAt(smallPath, bigPath string) {
pt.mustCreateSnapshot(pt.smallPartsPath, smallPath, pwsSmall)
pt.mustCreateSnapshot(pt.bigPartsPath, bigPath, pwsBig)
fs.MustSyncPathAndParentDir(smallPath)
fs.MustSyncPathAndParentDir(bigPath)
logger.Infof("created partition snapshot of %q and %q at %q and %q in %.3f seconds",
pt.smallPartsPath, pt.bigPartsPath, smallPath, bigPath, time.Since(startTime).Seconds())
}
@@ -2025,10 +2033,6 @@ func (pt *partition) mustCreateSnapshot(srcDir, dstDir string, pws []*partWrappe
dstPath := filepath.Join(dstDir, filepath.Base(srcPath))
fs.MustCopyFile(srcPath, dstPath)
}
fs.MustSyncPath(dstDir)
parentDir := filepath.Dir(dstDir)
fs.MustSyncPath(parentDir)
}
type partNamesJSON struct {

View File

@@ -456,7 +456,7 @@ func (s *Storage) MustCreateSnapshot() string {
dstIdbDir := filepath.Join(dstDir, indexdbDirname)
fs.MustSymlinkRelative(idbSnapshot, dstIdbDir)
fs.MustSyncPath(dstDir)
fs.MustSyncPathAndParentDir(dstDir)
logger.Infof("created Storage snapshot for %q at %q in %.3f seconds", srcDir, dstDir, time.Since(startTime).Seconds())
return snapshotName
@@ -655,6 +655,8 @@ type Metrics struct {
MetricNamesUsageTrackerSizeBytes uint64
MetricNamesUsageTrackerSizeMaxBytes uint64
DeletedMetricsCount uint64
IndexDBMetrics IndexDBMetrics
TableMetrics TableMetrics
}
@@ -763,6 +765,8 @@ func (s *Storage) UpdateMetrics(m *Metrics) {
}
m.NextRetentionSeconds = uint64(d)
m.DeletedMetricsCount += uint64(s.getDeletedMetricIDs().Len())
idbPrev, idbCurr := s.getPrevAndCurrIndexDBs()
defer s.putPrevAndCurrIndexDBs(idbPrev, idbCurr)
idbCurr.UpdateMetrics(&m.IndexDBMetrics)

View File

@@ -1439,6 +1439,55 @@ func TestStorageDeleteSeries_CachesAreUpdatedOrReset(t *testing.T) {
assertDeletedMetricIDsCacheSize(3)
}
func TestStorageDeleteSeriesFromPrevAndCurrIndexDB(t *testing.T) {
defer testRemoveAll(t)
rng := rand.New(rand.NewSource(1))
const numSeries = 100
trPrev := TimeRange{
MinTimestamp: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC).UnixMilli(),
MaxTimestamp: time.Date(2020, 1, 1, 23, 59, 59, 999_999_999, time.UTC).UnixMilli(),
}
mrsPrev := testGenerateMetricRowsWithPrefix(rng, numSeries, "prev", trPrev)
trCurr := TimeRange{
MinTimestamp: time.Date(2020, 1, 2, 0, 0, 0, 0, time.UTC).UnixMilli(),
MaxTimestamp: time.Date(2020, 1, 2, 23, 59, 59, 999_999_999, time.UTC).UnixMilli(),
}
mrsCurr := testGenerateMetricRowsWithPrefix(rng, numSeries, "curr", trCurr)
deleteSeries := func(s *Storage, want, wantTotal int) {
t.Helper()
tfs := NewTagFilters()
if err := tfs.Add(nil, []byte(".*"), false, true); err != nil {
t.Fatalf("unexpected error in TagFilters.Add: %v", err)
}
got, err := s.DeleteSeries(nil, []*TagFilters{tfs}, 1e9)
if err != nil {
t.Fatalf("could not delete series unexpectedly: %v", err)
}
if got != want {
t.Fatalf("unexpected number of deleted series: got %d, want %d", got, want)
}
var m Metrics
s.UpdateMetrics(&m)
if got, want := m.DeletedMetricsCount, uint64(wantTotal); got != want {
t.Fatalf("unexpected number of total deleted series: got %d, want %d", got, want)
}
}
s := MustOpenStorage(t.Name(), OpenOptions{})
defer s.MustClose()
s.AddRows(mrsPrev, defaultPrecisionBits)
s.DebugFlush()
deleteSeries(s, numSeries, numSeries)
s.mustRotateIndexDB(time.Now())
s.AddRows(mrsCurr, defaultPrecisionBits)
s.DebugFlush()
deleteSeries(s, numSeries, 2*numSeries)
}
func TestStorageRegisterMetricNamesSerial(t *testing.T) {
path := "TestStorageRegisterMetricNamesSerial"
s := MustOpenStorage(path, OpenOptions{})

View File

@@ -108,6 +108,9 @@ func mustOpenTable(path string, s *Storage) *table {
// Open partitions.
pts := mustOpenPartitions(smallPartitionsPath, bigPartitionsPath, s)
// Make sure all the directories inside the path are properly synced.
fs.MustSyncPathAndParentDir(path)
tb := &table{
path: path,
smallPartitionsPath: smallPartitionsPath,
@@ -144,10 +147,8 @@ func (tb *table) MustCreateSnapshot(snapshotName string) (string, string) {
ptw.pt.MustCreateSnapshotAt(smallPath, bigPath)
}
fs.MustSyncPath(dstSmallDir)
fs.MustSyncPath(dstBigDir)
fs.MustSyncPath(filepath.Dir(dstSmallDir))
fs.MustSyncPath(filepath.Dir(dstBigDir))
fs.MustSyncPathAndParentDir(dstSmallDir)
fs.MustSyncPathAndParentDir(dstBigDir)
logger.Infof("created table snapshot for %q at (%q, %q) in %.3f seconds", tb.path, dstSmallDir, dstBigDir, time.Since(startTime).Seconds())
return dstSmallDir, dstBigDir

View File

@@ -359,12 +359,23 @@ func (s *Set) Subtract(a *Set) {
// Fast path - nothing to subtract.
return
}
a.ForEach(func(part []uint64) bool {
for _, x := range part {
s.Del(x)
}
return true
})
if s.Len() >= a.Len() {
a.ForEach(func(part []uint64) bool {
for _, x := range part {
s.Del(x)
}
return true
})
} else {
s.ForEach(func(part []uint64) bool {
for _, x := range part {
if a.Has(x) {
s.Del(x)
}
}
return true
})
}
}
// Equal returns true if s contains the same items as a.

View File

@@ -7,6 +7,8 @@ import (
"sort"
"testing"
"time"
"github.com/google/go-cmp/cmp"
)
func TestSetOps(t *testing.T) {
@@ -758,3 +760,141 @@ func TestAddMulti(t *testing.T) {
}
f(a)
}
func TestSubtract(t *testing.T) {
f := func(a, b, want *Set) {
t.Helper()
bBefore := b.AppendTo(nil)
a.Subtract(b)
bAfter := b.AppendTo(nil)
gotValues := []uint64{}
gotValues = a.AppendTo(gotValues)
wantValues := []uint64{}
wantValues = want.AppendTo(wantValues)
if diff := cmp.Diff(wantValues, gotValues); diff != "" {
t.Fatalf("unexpected a set (-want, +got):\n%s", diff)
}
if diff := cmp.Diff(bBefore, bAfter); diff != "" {
t.Fatalf("unexpected b set (-want, +got):\n%s", diff)
}
}
s := func(start, end uint64, se ...uint64) *Set {
s := &Set{}
for i := start; i <= end; i++ {
s.Add(i)
}
if len(se)%2 != 0 {
t.Fatalf("the number of additional starts and ends must be an even number since they go in pairs")
}
for i := 0; i < len(se); i += 2 {
start := se[i]
end := se[i+1]
for j := start; j <= end; j++ {
s.Add(j)
}
}
return s
}
var a, b, want *Set
// - no overlap
// - a values before b values
// - len(a) > len(b)
a = s(1, 500_000)
b = s(500_001, 600_000)
want = s(1, 500_000)
f(a, b, want)
// - no overlap
// - a values after b values
// - len(a) > len(b)
a = s(500_001, 1_000_000)
b = s(400_000, 500_000)
want = s(500_001, 1_000_000)
f(a, b, want)
// - no overlap
// - a values before b values
// - len(a) < len(b)
a = s(400_000, 500_000)
b = s(500_001, 1_000_000)
want = s(400_000, 500_000)
f(a, b, want)
// - no overlap
// - a values after b values
// - len(a) < len(b)
a = s(500_001, 600_000)
b = s(1, 500_000)
want = s(500_001, 600_000)
f(a, b, want)
// - overlap on the left side
// - len(a) > len(b)
a = s(500_000, 1_000_000)
b = s(400_000, 600_000)
want = s(600_001, 1_000_000)
f(a, b, want)
// - overlap on the right side
// - len(a) > len(b)
a = s(1, 500_000)
b = s(400_001, 600_000)
want = s(1, 400_000)
f(a, b, want)
// - overlap on the left side
// - len(a) < len(b)
a = s(400_000, 600_000)
b = s(500_001, 1_000_000)
want = s(400_000, 500_000)
f(a, b, want)
// - overlap on the right side
// - len(a) < len(b)
a = s(400_000, 600_000)
b = s(1, 500_000)
want = s(500_001, 600_000)
f(a, b, want)
// same
a = s(1, 500_000)
b = s(1, 500_000)
want = &Set{}
f(a, b, want)
// b is a subset of a
a = s(1, 500_000)
b = s(100_001, 400_000)
want = s(1, 100_000, 400_001, 500_000)
f(a, b, want)
// a is a subset of b
a = s(100_001, 400_000)
b = s(1, 500_000)
want = &Set{}
f(a, b, want)
// a with intervals
a = s(1, 200_000, 400_000, 500_000)
b = s(150_001, 450_000)
want = s(1, 150_000, 450_001, 500_000)
f(a, b, want)
// b with intervals
a = s(1, 500_000)
b = s(200_001, 300_000, 400_001, 500_000)
want = s(1, 200_000, 300_001, 400_000)
f(a, b, want)
// subtract more than once.
a = s(1, 500_000)
b1 := s(100_001, 200_000)
want1 := s(1, 100_000, 200_001, 500_000)
b2 := s(300_001, 400_000)
want2 := s(1, 100_000, 200_001, 300_000, 400_001, 500_000)
f(a, b1, want1)
f(a, b2, want2)
}

View File

@@ -154,6 +154,66 @@ func benchmarkIntersect(b *testing.B, sa, sb *Set) {
})
}
func BenchmarkSubtract(b *testing.B) {
f := func(b *testing.B, startA, itemsCountA, startB, itemsCountB uint64) {
sa := createRangeSet(startA, int(itemsCountA))
sb := createRangeSet(startB, int(itemsCountB))
b.ReportAllocs()
b.SetBytes(int64(sa.Len() + sb.Len()))
for b.Loop() {
saCopy := sa.Clone()
saCopy.Subtract(sb)
}
}
start := uint64(time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC).UnixNano())
itemsACounts := []uint64{1e3, 1e4, 1e5, 1e6, 1e7}
itemsBCounts := []uint64{1e7, 1e6, 1e5, 1e4, 1e3}
for i := range len(itemsACounts) {
itemsCountA := itemsACounts[i]
itemsCountB := itemsBCounts[i]
b.Run(fmt.Sprintf("-----NoOverlap-AbeforeB-A%d-B%d", itemsCountA, itemsCountB), func(b *testing.B) {
f(b, start, itemsCountA, start+itemsCountA, itemsCountB)
})
b.Run(fmt.Sprintf("-----NoOverlap-AbeforeB-B%d-A%d", itemsCountB, itemsCountA), func(b *testing.B) {
f(b, start+itemsCountA, itemsCountB, start, itemsCountA)
})
b.Run(fmt.Sprintf("-----NoOverlap-BbeforeA-A%d-B%d", itemsCountA, itemsCountB), func(b *testing.B) {
f(b, start-itemsCountA, itemsCountA, start, itemsCountB)
})
b.Run(fmt.Sprintf("-----NoOverlap-BbeforeA-B%d-A%d", itemsCountB, itemsCountA), func(b *testing.B) {
f(b, start, itemsCountB, start-itemsCountA, itemsCountA)
})
b.Run(fmt.Sprintf("PartialOverlap-AbeforeB-A%d-B%d", itemsCountA, itemsCountB), func(b *testing.B) {
f(b, start, itemsCountA, start+itemsCountA-itemsCountB/2, itemsCountB)
})
b.Run(fmt.Sprintf("PartialOverlap-AbeforeB-B%d-A%d", itemsCountB, itemsCountA), func(b *testing.B) {
f(b, start+itemsCountA-itemsCountB/2, itemsCountB, start, itemsCountA)
})
b.Run(fmt.Sprintf("PartialOverlap-BbeforeA-A%d-B%d", itemsCountA, itemsCountB), func(b *testing.B) {
f(b, start+itemsCountB/2, itemsCountA, start, itemsCountB)
})
b.Run(fmt.Sprintf("PartialOverlap-BbeforeA-B%d-A%d", itemsCountB, itemsCountA), func(b *testing.B) {
f(b, start, itemsCountB, start+itemsCountB/2, itemsCountA)
})
b.Run(fmt.Sprintf("---FullOverlap-AbeforeB-A%d-B%d", itemsCountA, itemsCountB), func(b *testing.B) {
f(b, start, itemsCountA, start+itemsCountA-itemsCountB, itemsCountB)
})
b.Run(fmt.Sprintf("---FullOverlap-AbeforeB-B%d-A%d", itemsCountB, itemsCountA), func(b *testing.B) {
f(b, start+itemsCountA-itemsCountB, itemsCountB, start, itemsCountA)
})
b.Run(fmt.Sprintf("---FullOverlap-BbeforeA-A%d-B%d", itemsCountA, itemsCountB), func(b *testing.B) {
f(b, start+itemsCountB, itemsCountA, start, itemsCountB)
})
b.Run(fmt.Sprintf("---FullOverlap-BbeforeA-B%d-A%d", itemsCountB, itemsCountA), func(b *testing.B) {
f(b, start, itemsCountB, start+itemsCountB, itemsCountA)
})
}
}
func createRangeSet(start uint64, itemsCount int) *Set {
var s Set
for i := 0; i < itemsCount; i++ {