mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2026-05-29 14:51:27 +03:00
Compare commits
62 Commits
v1.110.2
...
vmui/fix-i
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a2ccee6d0 | ||
|
|
fad5c25187 | ||
|
|
f7457531d5 | ||
|
|
bc1883daca | ||
|
|
649f4d5e00 | ||
|
|
a55ac4495f | ||
|
|
52988ebdc8 | ||
|
|
065a3d068c | ||
|
|
43a742ba0c | ||
|
|
e69c744dd4 | ||
|
|
6db97d6f79 | ||
|
|
7451a3631a | ||
|
|
63f6ac3ff8 | ||
|
|
281f1a94e4 | ||
|
|
7f6156e46d | ||
|
|
4015db18bc | ||
|
|
99de272b72 | ||
|
|
6ff61a1c09 | ||
|
|
ed0db37d1d | ||
|
|
a36c8a9679 | ||
|
|
c80e2d16a5 | ||
|
|
e10b963a4d | ||
|
|
3a04d9aca8 | ||
|
|
bfa9fee424 | ||
|
|
d7431e69fd | ||
|
|
0e5e90fe74 | ||
|
|
5790c5c75d | ||
|
|
744ac496bd | ||
|
|
03a04b4408 | ||
|
|
117b7ce6b7 | ||
|
|
38d46d149f | ||
|
|
84d5771b41 | ||
|
|
f39ce2aeef | ||
|
|
ba2bf9e73a | ||
|
|
47d0bba8f0 | ||
|
|
da059d1ea1 | ||
|
|
ea6ed4232a | ||
|
|
b27e9437a3 | ||
|
|
3d3480140c | ||
|
|
ae5e28524e | ||
|
|
b484112f22 | ||
|
|
1da04db33a | ||
|
|
ef2415e0c5 | ||
|
|
0de7c8987c | ||
|
|
521af4d7a3 | ||
|
|
1f75e5bb59 | ||
|
|
fef2cb9dc7 | ||
|
|
72bee659f5 | ||
|
|
49fff7989b | ||
|
|
82cdcec6c6 | ||
|
|
128f6d78ff | ||
|
|
30974e7f3f | ||
|
|
edc750dd55 | ||
|
|
7a1c84b6ec | ||
|
|
788c740d62 | ||
|
|
cddccfde57 | ||
|
|
8f87427c81 | ||
|
|
6a675bb69b | ||
|
|
3828c01540 | ||
|
|
0314b79727 | ||
|
|
1e368add33 | ||
|
|
1064c15454 |
2
Makefile
2
Makefile
@@ -567,7 +567,7 @@ golangci-lint: install-golangci-lint
|
||||
golangci-lint run
|
||||
|
||||
install-golangci-lint:
|
||||
which golangci-lint || curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(shell go env GOPATH)/bin v1.64.4
|
||||
which golangci-lint || curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(shell go env GOPATH)/bin v1.64.5
|
||||
|
||||
remove-golangci-lint:
|
||||
rm -rf `which golangci-lint`
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
@@ -22,7 +22,7 @@ Here are some resources and information about VictoriaMetrics:
|
||||
|
||||
- Documentation: [docs.victoriametrics.com](https://docs.victoriametrics.com)
|
||||
- Case studies: [Grammarly, Roblox, Wix,...](https://docs.victoriametrics.com/casestudies/).
|
||||
- Available: [Binary releases](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/latest), [Docker images](https://hub.docker.com/r/victoriametrics/victoria-metrics/), [Source code](https://github.com/VictoriaMetrics/VictoriaMetrics)
|
||||
- Available: [Binary releases](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/latest), docker images [Docker Hub](https://hub.docker.com/r/victoriametrics/victoria-metrics/) and [Quay](https://quay.io/repository/victoriametrics/victoria-metrics), [Source code](https://github.com/VictoriaMetrics/VictoriaMetrics)
|
||||
- Deployment types: [Single-node version](https://docs.victoriametrics.com/), [Cluster version](https://docs.victoriametrics.com/cluster-victoriametrics/), and [Enterprise version](https://docs.victoriametrics.com/enterprise/)
|
||||
- Changelog: [CHANGELOG](https://docs.victoriametrics.com/changelog/), and [How to upgrade](https://docs.victoriametrics.com/#how-to-upgrade-victoriametrics)
|
||||
- Community: [Slack](https://slack.victoriametrics.com/), [X (Twitter)](https://x.com/VictoriaMetrics), [LinkedIn](https://www.linkedin.com/company/victoriametrics/), [YouTube](https://www.youtube.com/@VictoriaMetrics)
|
||||
|
||||
@@ -60,7 +60,7 @@ func RequestHandler(path string, w http.ResponseWriter, r *http.Request) bool {
|
||||
return true
|
||||
}
|
||||
switch path {
|
||||
case "/":
|
||||
case "/", "":
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
// Return fake response for Elasticsearch ping request.
|
||||
|
||||
@@ -46,7 +46,9 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
|
||||
return true
|
||||
}
|
||||
switch {
|
||||
case strings.HasPrefix(path, "/elasticsearch/"):
|
||||
case strings.HasPrefix(path, "/elasticsearch"):
|
||||
// some clients may omit trailing slash
|
||||
// see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8353
|
||||
path = strings.TrimPrefix(path, "/elasticsearch")
|
||||
return elasticsearch.RequestHandler(path, w, r)
|
||||
case strings.HasPrefix(path, "/loki/"):
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/buildinfo"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/envflag"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutils"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/timeutil"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -306,7 +306,7 @@ type timeFlag struct {
|
||||
}
|
||||
|
||||
func (tf *timeFlag) Set(s string) error {
|
||||
msec, err := promutils.ParseTimeMsec(s)
|
||||
msec, err := timeutil.ParseTimeMsec(s)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot parse time from %q: %w", s, err)
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httputils"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logstorage"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutils"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/timeutil"
|
||||
)
|
||||
|
||||
// ProcessFacetsRequest handles /select/logsql/facets request.
|
||||
@@ -116,7 +116,7 @@ func ProcessHitsRequest(ctx context.Context, w http.ResponseWriter, r *http.Requ
|
||||
if stepStr == "" {
|
||||
stepStr = "1d"
|
||||
}
|
||||
step, err := promutils.ParseDuration(stepStr)
|
||||
step, err := timeutil.ParseDuration(stepStr)
|
||||
if err != nil {
|
||||
httpserver.Errorf(w, r, "cannot parse 'step' arg: %s", err)
|
||||
return
|
||||
@@ -131,7 +131,7 @@ func ProcessHitsRequest(ctx context.Context, w http.ResponseWriter, r *http.Requ
|
||||
if offsetStr == "" {
|
||||
offsetStr = "0s"
|
||||
}
|
||||
offset, err := promutils.ParseDuration(offsetStr)
|
||||
offset, err := timeutil.ParseDuration(offsetStr)
|
||||
if err != nil {
|
||||
httpserver.Errorf(w, r, "cannot parse 'offset' arg: %s", err)
|
||||
return
|
||||
@@ -665,7 +665,7 @@ func ProcessStatsQueryRangeRequest(ctx context.Context, w http.ResponseWriter, r
|
||||
if stepStr == "" {
|
||||
stepStr = "1d"
|
||||
}
|
||||
step, err := promutils.ParseDuration(stepStr)
|
||||
step, err := timeutil.ParseDuration(stepStr)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("cannot parse 'step' arg: %s", err)
|
||||
httpserver.SendPrometheusError(w, r, err)
|
||||
@@ -688,19 +688,22 @@ func ProcessStatsQueryRangeRequest(ctx context.Context, w http.ResponseWriter, r
|
||||
m := make(map[string]*statsSeries)
|
||||
var mLock sync.Mutex
|
||||
|
||||
timestamp := q.GetTimestamp()
|
||||
writeBlock := func(_ uint, timestamps []int64, columns []logstorage.BlockColumn) {
|
||||
clonedColumnNames := make([]string, len(columns))
|
||||
for i, c := range columns {
|
||||
clonedColumnNames[i] = strings.Clone(c.Name)
|
||||
}
|
||||
for i := range timestamps {
|
||||
// Do not move q.GetTimestamp() outside writeBlock, since ts
|
||||
// must be initialized to query timestamp for every processed log row.
|
||||
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8312
|
||||
ts := q.GetTimestamp()
|
||||
labels := make([]logstorage.Field, 0, len(byFields))
|
||||
for j, c := range columns {
|
||||
if c.Name == "_time" {
|
||||
nsec, ok := logstorage.TryParseTimestampRFC3339Nano(c.Values[i])
|
||||
if ok {
|
||||
timestamp = nsec
|
||||
ts = nsec
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -721,7 +724,7 @@ func ProcessStatsQueryRangeRequest(ctx context.Context, w http.ResponseWriter, r
|
||||
dst = logstorage.MarshalFieldsToJSON(dst, labels)
|
||||
key := string(dst)
|
||||
p := statsPoint{
|
||||
Timestamp: timestamp,
|
||||
Timestamp: ts,
|
||||
Value: strings.Clone(c.Values[i]),
|
||||
}
|
||||
|
||||
@@ -1119,7 +1122,7 @@ func getTimeNsec(r *http.Request, argName string) (int64, bool, error) {
|
||||
return 0, false, nil
|
||||
}
|
||||
currentTimestamp := time.Now().UnixNano()
|
||||
nsecs, err := promutils.ParseTimeAt(s, currentTimestamp)
|
||||
nsecs, err := timeutil.ParseTimeAt(s, currentTimestamp)
|
||||
if err != nil {
|
||||
return 0, false, fmt.Errorf("cannot parse %s=%s: %w", argName, s, err)
|
||||
}
|
||||
|
||||
@@ -176,50 +176,62 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
|
||||
|
||||
func processSelectRequest(ctx context.Context, w http.ResponseWriter, r *http.Request, path string) bool {
|
||||
httpserver.EnableCORS(w, r)
|
||||
startTime := time.Now()
|
||||
switch path {
|
||||
case "/select/logsql/facets":
|
||||
logsqlFacetsRequests.Inc()
|
||||
logsql.ProcessFacetsRequest(ctx, w, r)
|
||||
logsqlFacetsDuration.UpdateDuration(startTime)
|
||||
return true
|
||||
case "/select/logsql/field_names":
|
||||
logsqlFieldNamesRequests.Inc()
|
||||
logsql.ProcessFieldNamesRequest(ctx, w, r)
|
||||
logsqlFieldNamesDuration.UpdateDuration(startTime)
|
||||
return true
|
||||
case "/select/logsql/field_values":
|
||||
logsqlFieldValuesRequests.Inc()
|
||||
logsql.ProcessFieldValuesRequest(ctx, w, r)
|
||||
logsqlFieldValuesDuration.UpdateDuration(startTime)
|
||||
return true
|
||||
case "/select/logsql/hits":
|
||||
logsqlHitsRequests.Inc()
|
||||
logsql.ProcessHitsRequest(ctx, w, r)
|
||||
logsqlHitsDuration.UpdateDuration(startTime)
|
||||
return true
|
||||
case "/select/logsql/query":
|
||||
logsqlQueryRequests.Inc()
|
||||
logsql.ProcessQueryRequest(ctx, w, r)
|
||||
logsqlQueryDuration.UpdateDuration(startTime)
|
||||
return true
|
||||
case "/select/logsql/stats_query":
|
||||
logsqlStatsQueryRequests.Inc()
|
||||
logsql.ProcessStatsQueryRequest(ctx, w, r)
|
||||
logsqlStatsQueryDuration.UpdateDuration(startTime)
|
||||
return true
|
||||
case "/select/logsql/stats_query_range":
|
||||
logsqlStatsQueryRangeRequests.Inc()
|
||||
logsql.ProcessStatsQueryRangeRequest(ctx, w, r)
|
||||
logsqlStatsQueryRangeDuration.UpdateDuration(startTime)
|
||||
return true
|
||||
case "/select/logsql/stream_field_names":
|
||||
logsqlStreamFieldNamesRequests.Inc()
|
||||
logsql.ProcessStreamFieldNamesRequest(ctx, w, r)
|
||||
logsqlStreamFieldNamesDuration.UpdateDuration(startTime)
|
||||
return true
|
||||
case "/select/logsql/stream_field_values":
|
||||
logsqlStreamFieldValuesRequests.Inc()
|
||||
logsql.ProcessStreamFieldValuesRequest(ctx, w, r)
|
||||
logsqlStreamFieldValuesDuration.UpdateDuration(startTime)
|
||||
return true
|
||||
case "/select/logsql/stream_ids":
|
||||
logsqlStreamIDsRequests.Inc()
|
||||
logsql.ProcessStreamIDsRequest(ctx, w, r)
|
||||
logsqlStreamIDsDuration.UpdateDuration(startTime)
|
||||
return true
|
||||
case "/select/logsql/streams":
|
||||
logsqlStreamsRequests.Inc()
|
||||
logsql.ProcessStreamsRequest(ctx, w, r)
|
||||
logsqlStreamsDuration.UpdateDuration(startTime)
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
@@ -240,16 +252,39 @@ func getMaxQueryDuration(r *http.Request) time.Duration {
|
||||
}
|
||||
|
||||
var (
|
||||
logsqlFacetsRequests = metrics.NewCounter(`vl_http_requests_total{path="/select/logsql/facets"}`)
|
||||
logsqlFieldNamesRequests = metrics.NewCounter(`vl_http_requests_total{path="/select/logsql/field_names"}`)
|
||||
logsqlFieldValuesRequests = metrics.NewCounter(`vl_http_requests_total{path="/select/logsql/field_values"}`)
|
||||
logsqlHitsRequests = metrics.NewCounter(`vl_http_requests_total{path="/select/logsql/hits"}`)
|
||||
logsqlQueryRequests = metrics.NewCounter(`vl_http_requests_total{path="/select/logsql/query"}`)
|
||||
logsqlStatsQueryRequests = metrics.NewCounter(`vl_http_requests_total{path="/select/logsql/stats_query"}`)
|
||||
logsqlStatsQueryRangeRequests = metrics.NewCounter(`vl_http_requests_total{path="/select/logsql/stats_query_range"}`)
|
||||
logsqlStreamFieldNamesRequests = metrics.NewCounter(`vl_http_requests_total{path="/select/logsql/stream_field_names"}`)
|
||||
logsqlFacetsRequests = metrics.NewCounter(`vl_http_requests_total{path="/select/logsql/facets"}`)
|
||||
logsqlFacetsDuration = metrics.NewSummary(`vl_http_request_duration_seconds{path="/select/logsql/facets"}`)
|
||||
|
||||
logsqlFieldNamesRequests = metrics.NewCounter(`vl_http_requests_total{path="/select/logsql/field_names"}`)
|
||||
logsqlFieldNamesDuration = metrics.NewSummary(`vl_http_request_duration_seconds{path="/select/logsql/field_names"}`)
|
||||
|
||||
logsqlFieldValuesRequests = metrics.NewCounter(`vl_http_requests_total{path="/select/logsql/field_values"}`)
|
||||
logsqlFieldValuesDuration = metrics.NewSummary(`vl_http_request_duration_seconds{path="/select/logsql/field_values"}`)
|
||||
|
||||
logsqlHitsRequests = metrics.NewCounter(`vl_http_requests_total{path="/select/logsql/hits"}`)
|
||||
logsqlHitsDuration = metrics.NewSummary(`vl_http_request_duration_seconds{path="/select/logsql/hits"}`)
|
||||
|
||||
logsqlQueryRequests = metrics.NewCounter(`vl_http_requests_total{path="/select/logsql/query"}`)
|
||||
logsqlQueryDuration = metrics.NewSummary(`vl_http_request_duration_seconds{path="/select/logsql/query"}`)
|
||||
|
||||
logsqlStatsQueryRequests = metrics.NewCounter(`vl_http_requests_total{path="/select/logsql/stats_query"}`)
|
||||
logsqlStatsQueryDuration = metrics.NewSummary(`vl_http_request_duration_seconds{path="/select/logsql/stats_query"}`)
|
||||
|
||||
logsqlStatsQueryRangeRequests = metrics.NewCounter(`vl_http_requests_total{path="/select/logsql/stats_query_range"}`)
|
||||
logsqlStatsQueryRangeDuration = metrics.NewSummary(`vl_http_request_duration_seconds{path="/select/logsql/stats_query_range"}`)
|
||||
|
||||
logsqlStreamFieldNamesRequests = metrics.NewCounter(`vl_http_requests_total{path="/select/logsql/stream_field_names"}`)
|
||||
logsqlStreamFieldNamesDuration = metrics.NewSummary(`vl_http_request_duration_seconds{path="/select/logsql/stream_field_names"}`)
|
||||
|
||||
logsqlStreamFieldValuesRequests = metrics.NewCounter(`vl_http_requests_total{path="/select/logsql/stream_field_values"}`)
|
||||
logsqlStreamIDsRequests = metrics.NewCounter(`vl_http_requests_total{path="/select/logsql/stream_ids"}`)
|
||||
logsqlStreamsRequests = metrics.NewCounter(`vl_http_requests_total{path="/select/logsql/streams"}`)
|
||||
logsqlTailRequests = metrics.NewCounter(`vl_http_requests_total{path="/select/logsql/tail"}`)
|
||||
logsqlStreamFieldValuesDuration = metrics.NewSummary(`vl_http_request_duration_seconds{path="/select/logsql/stream_field_values"}`)
|
||||
|
||||
logsqlStreamIDsRequests = metrics.NewCounter(`vl_http_requests_total{path="/select/logsql/stream_ids"}`)
|
||||
logsqlStreamIDsDuration = metrics.NewSummary(`vl_http_request_duration_seconds{path="/select/logsql/stream_ids"}`)
|
||||
|
||||
logsqlStreamsRequests = metrics.NewCounter(`vl_http_requests_total{path="/select/logsql/streams"}`)
|
||||
logsqlStreamsDuration = metrics.NewSummary(`vl_http_request_duration_seconds{path="/select/logsql/streams"}`)
|
||||
|
||||
// no need to track duration for tail requests, as they usually take long time
|
||||
logsqlTailRequests = metrics.NewCounter(`vl_http_requests_total{path="/select/logsql/tail"}`)
|
||||
)
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promrelabel"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/prometheus"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/streamaggr"
|
||||
"github.com/VictoriaMetrics/metrics"
|
||||
)
|
||||
@@ -94,7 +95,7 @@ func TestRemoteWriteContext_TryPush_ImmutableTimeseries(t *testing.T) {
|
||||
}
|
||||
|
||||
offsetMsecs := time.Now().UnixMilli()
|
||||
inputTss := prompbmarshal.MustParsePromMetrics(input, offsetMsecs)
|
||||
inputTss := prometheus.MustParsePromMetrics(input, offsetMsecs)
|
||||
expectedTss := make([]prompbmarshal.TimeSeries, len(inputTss))
|
||||
|
||||
// copy inputTss to make sure it is not mutated during TryPush call
|
||||
|
||||
@@ -51,6 +51,11 @@ Examples:
|
||||
Usage: `Optional external URL to template in rule's labels or annotations.`,
|
||||
Required: false,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "httpListenPort",
|
||||
Usage: `Optional local port for incoming HTTP requests. If not specified, a random unoccupied port will be used.`,
|
||||
Required: false,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "loggerLevel",
|
||||
Usage: `Minimum level of errors to log. Possible values: INFO, WARN, ERROR, FATAL, PANIC (default "ERROR").`,
|
||||
@@ -58,7 +63,7 @@ Examples:
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
if failed := unittest.UnitTest(c.StringSlice("files"), c.Bool("disableAlertgroupLabel"), c.StringSlice("external.label"), c.String("external.url"), c.String("loggerLevel")); failed {
|
||||
if failed := unittest.UnitTest(c.StringSlice("files"), c.Bool("disableAlertgroupLabel"), c.StringSlice("external.label"), c.String("external.url"), c.String("httpListenPort"), c.String("loggerLevel")); failed {
|
||||
return fmt.Errorf("unittest failed")
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -4,13 +4,18 @@ import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
@@ -28,7 +33,6 @@ import (
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmselect/prometheus"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmselect/promql"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmstorage"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/flagutil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httpserver"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
@@ -38,15 +42,9 @@ import (
|
||||
|
||||
var (
|
||||
storagePath string
|
||||
httpListenAddr = ":8880"
|
||||
httpListenAddr string
|
||||
// insert series from 1970-01-01T00:00:00
|
||||
testStartTime = time.Unix(0, 0).UTC()
|
||||
|
||||
testPromWriteHTTPPath = "http://127.0.0.1" + httpListenAddr + "/api/v1/write"
|
||||
testDataSourcePath = "http://127.0.0.1" + httpListenAddr + "/prometheus"
|
||||
testRemoteWritePath = "http://127.0.0.1" + httpListenAddr
|
||||
testHealthHTTPPath = "http://127.0.0.1" + httpListenAddr + "/health"
|
||||
|
||||
testStartTime = time.Unix(0, 0).UTC()
|
||||
testLogLevel = "ERROR"
|
||||
disableAlertgroupLabel bool
|
||||
)
|
||||
@@ -56,7 +54,7 @@ const (
|
||||
)
|
||||
|
||||
// UnitTest runs unittest for files
|
||||
func UnitTest(files []string, disableGroupLabel bool, externalLabels []string, externalURL, logLevel string) bool {
|
||||
func UnitTest(files []string, disableGroupLabel bool, externalLabels []string, externalURL, httpListenPort, logLevel string) bool {
|
||||
if logLevel != "" {
|
||||
testLogLevel = logLevel
|
||||
}
|
||||
@@ -67,7 +65,42 @@ func UnitTest(files []string, disableGroupLabel bool, externalLabels []string, e
|
||||
if err := templates.Load([]string{}, *eu); err != nil {
|
||||
logger.Fatalf("failed to load template: %v", err)
|
||||
}
|
||||
storagePath = filepath.Join(os.TempDir(), testStoragePath)
|
||||
|
||||
// set up http server
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/prometheus/api/v1/query":
|
||||
if err := prometheus.QueryHandler(nil, time.Now(), w, r); err != nil {
|
||||
httpserver.Errorf(w, r, "%s", err)
|
||||
}
|
||||
case "/prometheus/api/v1/write", "/api/v1/write":
|
||||
if err := promremotewrite.InsertHandler(r); err != nil {
|
||||
httpserver.Errorf(w, r, "%s", err)
|
||||
}
|
||||
default:
|
||||
}
|
||||
})
|
||||
if httpListenPort == "" {
|
||||
server := httptest.NewServer(handler)
|
||||
httpListenAddr = strings.Split(server.URL, ":")[2]
|
||||
defer server.Close()
|
||||
} else {
|
||||
httpListenAddr = httpListenPort
|
||||
ln, err := net.Listen("tcp", fmt.Sprintf(":%s", httpListenPort))
|
||||
if err != nil {
|
||||
logger.Fatalf("cannot listen on port %s: %v", httpListenPort, err)
|
||||
}
|
||||
go func() {
|
||||
err = http.Serve(ln, handler)
|
||||
if err != nil {
|
||||
logger.Fatalf("cannot start http server: %v", err)
|
||||
}
|
||||
defer ln.Close()
|
||||
}()
|
||||
}
|
||||
|
||||
// adding time.Now().UnixNano() to avoid possible file conflict when multiple processes run on a single host
|
||||
storagePath = filepath.Join(os.TempDir(), testStoragePath, strconv.FormatInt(time.Now().UnixNano(), 10))
|
||||
processFlags()
|
||||
vminsert.Init()
|
||||
vmselect.Init()
|
||||
@@ -102,16 +135,34 @@ func UnitTest(files []string, disableGroupLabel bool, externalLabels []string, e
|
||||
}
|
||||
|
||||
var failed bool
|
||||
for fileName, file := range testfiles {
|
||||
if err := ruleUnitTest(fileName, file, labels); err != nil {
|
||||
fmt.Println("FAILED")
|
||||
fmt.Printf("failed to run unit test for file %q: \n%v", fileName, err)
|
||||
failed = true
|
||||
} else {
|
||||
fmt.Println(" SUCCESS")
|
||||
runTest := func() bool {
|
||||
for fileName, file := range testfiles {
|
||||
if err := ruleUnitTest(fileName, file, labels); err != nil {
|
||||
fmt.Println("FAILED")
|
||||
fmt.Printf("failed to run unit test for file %q: \n%v", fileName, err)
|
||||
return true
|
||||
}
|
||||
fmt.Println("SUCCESS")
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
finishCh := make(chan struct{}, 1)
|
||||
go func() {
|
||||
failed = runTest()
|
||||
finishCh <- struct{}{}
|
||||
}()
|
||||
|
||||
sigs := make(chan os.Signal, 1)
|
||||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
||||
select {
|
||||
case sig := <-sigs:
|
||||
fmt.Printf("received signal %s\n", sig)
|
||||
failed = true
|
||||
break
|
||||
case <-finishCh:
|
||||
break
|
||||
}
|
||||
return failed
|
||||
}
|
||||
|
||||
@@ -211,8 +262,8 @@ func processFlags() {
|
||||
{flag: "search.disableCache", value: "true"},
|
||||
// set storage retention time to 100 years, allow to store series from 1970-01-01T00:00:00.
|
||||
{flag: "retentionPeriod", value: "100y"},
|
||||
{flag: "datasource.url", value: testDataSourcePath},
|
||||
{flag: "remoteWrite.url", value: testRemoteWritePath},
|
||||
{flag: "datasource.url", value: fmt.Sprintf("http://127.0.0.1:%s/prometheus", httpListenAddr)},
|
||||
{flag: "remoteWrite.url", value: fmt.Sprintf("http://127.0.0.1:%s", httpListenAddr)},
|
||||
{flag: "notifier.blackhole", value: "true"},
|
||||
} {
|
||||
// panics if flag doesn't exist
|
||||
@@ -224,27 +275,10 @@ func processFlags() {
|
||||
|
||||
func setUp() {
|
||||
vmstorage.Init(promql.ResetRollupResultCacheIfNeeded)
|
||||
var ab flagutil.ArrayBool
|
||||
go httpserver.Serve([]string{httpListenAddr}, &ab, func(w http.ResponseWriter, r *http.Request) bool {
|
||||
switch r.URL.Path {
|
||||
case "/prometheus/api/v1/query":
|
||||
if err := prometheus.QueryHandler(nil, time.Now(), w, r); err != nil {
|
||||
httpserver.Errorf(w, r, "%s", err)
|
||||
}
|
||||
return true
|
||||
case "/prometheus/api/v1/write", "/api/v1/write":
|
||||
if err := promremotewrite.InsertHandler(r); err != nil {
|
||||
httpserver.Errorf(w, r, "%s", err)
|
||||
}
|
||||
return true
|
||||
default:
|
||||
}
|
||||
return false
|
||||
})
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
readyCheckFunc := func() bool {
|
||||
resp, err := http.Get(testHealthHTTPPath)
|
||||
resp, err := http.Get(fmt.Sprintf("http://127.0.0.1:%s/health", httpListenAddr))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
@@ -266,9 +300,6 @@ checkCheck:
|
||||
}
|
||||
|
||||
func tearDown() {
|
||||
if err := httpserver.Stop([]string{httpListenAddr}); err != nil {
|
||||
logger.Errorf("cannot stop the webservice: %s", err)
|
||||
}
|
||||
vmstorage.Stop()
|
||||
metrics.UnregisterAllMetrics()
|
||||
fs.MustRemoveAll(storagePath)
|
||||
@@ -283,7 +314,7 @@ func (tg *testGroup) test(evalInterval time.Duration, groupOrderMap map[string]i
|
||||
if tg.Interval == nil {
|
||||
tg.Interval = promutils.NewDuration(evalInterval)
|
||||
}
|
||||
err := writeInputSeries(tg.InputSeries, tg.Interval, testStartTime, testPromWriteHTTPPath)
|
||||
err := writeInputSeries(tg.InputSeries, tg.Interval, testStartTime, fmt.Sprintf("http://127.0.0.1:%s/api/v1/write", httpListenAddr))
|
||||
if err != nil {
|
||||
return []error{err}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ func TestUnitTest_Failure(t *testing.T) {
|
||||
f := func(files []string) {
|
||||
t.Helper()
|
||||
|
||||
failed := UnitTest(files, false, nil, "", "")
|
||||
failed := UnitTest(files, false, nil, "", "", "")
|
||||
if !failed {
|
||||
t.Fatalf("expecting failed test")
|
||||
}
|
||||
@@ -20,19 +20,20 @@ func TestUnitTest_Failure(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUnitTest_Success(t *testing.T) {
|
||||
f := func(disableGroupLabel bool, files []string, externalLabels []string, externalURL string) {
|
||||
f := func(disableGroupLabel bool, files []string, externalLabels []string, externalURL, httpPort string) {
|
||||
t.Helper()
|
||||
|
||||
failed := UnitTest(files, disableGroupLabel, externalLabels, externalURL, "")
|
||||
failed := UnitTest(files, disableGroupLabel, externalLabels, externalURL, httpPort, "")
|
||||
if failed {
|
||||
t.Fatalf("unexpected failed test")
|
||||
}
|
||||
}
|
||||
|
||||
// run multi files
|
||||
f(false, []string{"./testdata/test1.yaml", "./testdata/test2.yaml"}, []string{"cluster=prod"}, "http://grafana:3000")
|
||||
// run multi files with random http port
|
||||
f(false, []string{"./testdata/test1.yaml", "./testdata/test2.yaml"}, []string{"cluster=prod"}, "http://grafana:3000", "")
|
||||
|
||||
// disable group label
|
||||
// template with null external values
|
||||
f(true, []string{"./testdata/disable-group-label.yaml"}, nil, "")
|
||||
// specify httpListenAddr
|
||||
f(true, []string{"./testdata/disable-group-label.yaml"}, nil, "", "8880")
|
||||
}
|
||||
|
||||
@@ -157,6 +157,19 @@ func TestGroupValidate_Failure(t *testing.T) {
|
||||
|
||||
f(&Group{}, false, "group name must be set")
|
||||
|
||||
f(&Group{
|
||||
Name: "both record and alert are not set",
|
||||
Rules: []Rule{
|
||||
{
|
||||
Expr: "sum(up == 0 ) by (host)",
|
||||
For: promutils.NewDuration(10 * time.Millisecond),
|
||||
},
|
||||
{
|
||||
Expr: "sumSeries(time('foo.bar',10))",
|
||||
},
|
||||
},
|
||||
}, false, "invalid rule")
|
||||
|
||||
f(&Group{
|
||||
Name: "negative interval",
|
||||
Interval: promutils.NewDuration(-1),
|
||||
@@ -240,59 +253,6 @@ func TestGroupValidate_Failure(t *testing.T) {
|
||||
},
|
||||
}, false, "duplicate")
|
||||
|
||||
f(&Group{
|
||||
Name: "test graphite with prometheus expr",
|
||||
Type: NewGraphiteType(),
|
||||
Rules: []Rule{
|
||||
{
|
||||
Expr: "sum(up == 0 ) by (host)",
|
||||
For: promutils.NewDuration(10 * time.Millisecond),
|
||||
},
|
||||
{
|
||||
Expr: "sumSeries(time('foo.bar',10))",
|
||||
},
|
||||
},
|
||||
}, false, "invalid rule")
|
||||
|
||||
f(&Group{
|
||||
Name: "test graphite inherit",
|
||||
Type: NewGraphiteType(),
|
||||
Rules: []Rule{
|
||||
{
|
||||
Expr: "sumSeries(time('foo.bar',10))",
|
||||
For: promutils.NewDuration(10 * time.Millisecond),
|
||||
},
|
||||
{
|
||||
Expr: "sum(up == 0 ) by (host)",
|
||||
},
|
||||
},
|
||||
}, false, "either `record` or `alert` must be set")
|
||||
|
||||
f(&Group{
|
||||
Name: "test vlogs with prometheus expr",
|
||||
Type: NewVLogsType(),
|
||||
Rules: []Rule{
|
||||
{
|
||||
Expr: "sum(up == 0 ) by (host)",
|
||||
For: promutils.NewDuration(10 * time.Millisecond),
|
||||
},
|
||||
{
|
||||
Expr: "sumSeries(time('foo.bar',10))",
|
||||
},
|
||||
},
|
||||
}, false, "invalid rule")
|
||||
|
||||
// validate expressions
|
||||
f(&Group{
|
||||
Name: "test",
|
||||
Rules: []Rule{
|
||||
{
|
||||
Record: "record",
|
||||
Expr: "up | 0",
|
||||
},
|
||||
},
|
||||
}, true, "invalid expression")
|
||||
|
||||
f(&Group{
|
||||
Name: "test thanos",
|
||||
Type: NewRawType("thanos"),
|
||||
@@ -303,8 +263,20 @@ func TestGroupValidate_Failure(t *testing.T) {
|
||||
},
|
||||
}, true, "unknown datasource type")
|
||||
|
||||
// validate expressions
|
||||
f(&Group{
|
||||
Name: "test graphite",
|
||||
Name: "test prometheus expr",
|
||||
Type: NewPrometheusType(),
|
||||
Rules: []Rule{
|
||||
{
|
||||
Record: "record",
|
||||
Expr: "up | 0",
|
||||
},
|
||||
},
|
||||
}, true, "bad prometheus expr")
|
||||
|
||||
f(&Group{
|
||||
Name: "test graphite expr",
|
||||
Type: NewGraphiteType(),
|
||||
Rules: []Rule{
|
||||
{Alert: "alert", Expr: "up == 1", Labels: map[string]string{
|
||||
@@ -314,14 +286,63 @@ func TestGroupValidate_Failure(t *testing.T) {
|
||||
}, true, "bad graphite expr")
|
||||
|
||||
f(&Group{
|
||||
Name: "test vlogs",
|
||||
Name: "test vlogs expr",
|
||||
Type: NewVLogsType(),
|
||||
Rules: []Rule{
|
||||
{Alert: "alert", Expr: "stats count(*) as requests", Labels: map[string]string{
|
||||
"description": "some-description",
|
||||
}},
|
||||
{Alert: "alert", Expr: "stats count(*) as requests"},
|
||||
},
|
||||
}, true, "bad LogsQL expr")
|
||||
|
||||
f(&Group{
|
||||
Name: "test vlogs expr",
|
||||
Type: NewVLogsType(),
|
||||
Rules: []Rule{
|
||||
{Alert: "alert", Expr: "_time: 1m | stats by (path, _time: 1m) count(*) as requests"},
|
||||
},
|
||||
}, true, "bad LogsQL expr")
|
||||
|
||||
f(&Group{
|
||||
Name: "test graphite with prometheus expr",
|
||||
Type: NewGraphiteType(),
|
||||
Rules: []Rule{
|
||||
{
|
||||
Record: "r1",
|
||||
ID: 1,
|
||||
Expr: "sumSeries(time('foo.bar',10))",
|
||||
For: promutils.NewDuration(10 * time.Millisecond),
|
||||
},
|
||||
{
|
||||
Record: "r2",
|
||||
ID: 2,
|
||||
Expr: "sum(up == 0 ) by (host)",
|
||||
},
|
||||
},
|
||||
}, true, "bad graphite expr")
|
||||
|
||||
f(&Group{
|
||||
Name: "test vlogs with prometheus exp",
|
||||
Type: NewVLogsType(),
|
||||
Rules: []Rule{
|
||||
{
|
||||
Record: "r1",
|
||||
Expr: "sum(up == 0 ) by (host)",
|
||||
For: promutils.NewDuration(10 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
}, true, "bad LogsQL expr")
|
||||
|
||||
f(&Group{
|
||||
Name: "test prometheus with vlogs exp",
|
||||
Type: NewPrometheusType(),
|
||||
Rules: []Rule{
|
||||
{
|
||||
Record: "r1",
|
||||
Expr: "* | stats by (path) count()",
|
||||
For: promutils.NewDuration(10 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
}, true, "bad prometheus expr")
|
||||
|
||||
}
|
||||
|
||||
func TestGroupValidate_Success(t *testing.T) {
|
||||
|
||||
@@ -71,9 +71,18 @@ func (t *Type) ValidateExpr(expr string) error {
|
||||
return fmt.Errorf("bad prometheus expr: %q, err: %w", expr, err)
|
||||
}
|
||||
case "vlogs":
|
||||
if _, err := logstorage.ParseStatsQuery(expr, 0); err != nil {
|
||||
q, err := logstorage.ParseStatsQuery(expr, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bad LogsQL expr: %q, err: %w", expr, err)
|
||||
}
|
||||
fields, _ := q.GetStatsByFields()
|
||||
for i := range fields {
|
||||
// VictoriaLogs inserts `_time` field as a label in result when query with `stats by (_time:step)`,
|
||||
// making the result meaningless and may lead to cardinality issues.
|
||||
if fields[i] == "_time" {
|
||||
return fmt.Errorf("bad LogsQL expr: %q, err: cannot contain time buckets stats pipe `stats by (_time:step)`", expr)
|
||||
}
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unknown datasource type=%q", t.Name)
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ var (
|
||||
"If multiple args are set, then they are applied independently for the corresponding -notifier.url")
|
||||
oauth2Scopes = flagutil.NewArrayString("notifier.oauth2.scopes", "Optional OAuth2 scopes to use for -notifier.url. Scopes must be delimited by ';'. "+
|
||||
"If multiple args are set, then they are applied independently for the corresponding -notifier.url")
|
||||
sendTimeout = flagutil.NewArrayDuration("notifier.sendTimeout", time.Second*10, "Timeout for pushing alerts to corresponding -notifier.url.")
|
||||
sendTimeout = flagutil.NewArrayDuration("notifier.sendTimeout", 10*time.Second, "Timeout when sending alerts to the corresponding -notifier.url")
|
||||
)
|
||||
|
||||
// cw holds a configWatcher for configPath configuration file
|
||||
|
||||
@@ -33,7 +33,7 @@ import (
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/datasource"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/formatutil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutils"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/timeutil"
|
||||
)
|
||||
|
||||
// go template execution fails when it's tree is empty
|
||||
@@ -259,7 +259,7 @@ func templateFuncs() textTpl.FuncMap {
|
||||
|
||||
// parseDuration parses a duration string such as "1h" into the number of seconds it represents
|
||||
"parseDuration": func(s string) (float64, error) {
|
||||
d, err := promutils.ParseDuration(s)
|
||||
d, err := timeutil.ParseDuration(s)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -268,7 +268,7 @@ func templateFuncs() textTpl.FuncMap {
|
||||
|
||||
// same with parseDuration but returns a time.Duration
|
||||
"parseDurationTime": func(s string) (time.Duration, error) {
|
||||
d, err := promutils.ParseDuration(s)
|
||||
d, err := timeutil.ParseDuration(s)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
@@ -344,7 +344,7 @@ var (
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: influxSkipDatabaseLabel,
|
||||
Usage: "Wether to skip adding the label 'db' to timeseries.",
|
||||
Usage: "Whether to skip adding the label 'db' to timeseries.",
|
||||
Value: false,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
|
||||
@@ -144,9 +144,9 @@ func timeFilter(start, end string) string {
|
||||
// by checking available (non-empty) tags, fields and measurements
|
||||
// which unique combination represents all possible
|
||||
// time series existing in database.
|
||||
// The explore required to reduce the load on influx
|
||||
// Explore is required to reduce the load on influx
|
||||
// by querying field of the exact time series at once,
|
||||
// instead of fetching all of the values over and over.
|
||||
// instead of fetching all the values over and over.
|
||||
//
|
||||
// May contain non-existing time series.
|
||||
func (c *Client) Explore() ([]*Series, error) {
|
||||
@@ -340,10 +340,7 @@ func (c *Client) fieldsByMeasurement() (map[string][]string, error) {
|
||||
}
|
||||
|
||||
func (c *Client) getSeries() ([]*Series, error) {
|
||||
com := "show series"
|
||||
if c.filterSeries != "" {
|
||||
com = fmt.Sprintf("%s %s", com, c.filterSeries)
|
||||
}
|
||||
com := c.getSeriesCommand()
|
||||
q := influx.Query{
|
||||
Command: com,
|
||||
Database: c.database,
|
||||
@@ -389,6 +386,21 @@ func (c *Client) getSeries() ([]*Series, error) {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (c *Client) getSeriesCommand() string {
|
||||
com := "show series"
|
||||
if c.filterSeries != "" {
|
||||
com = fmt.Sprintf("%s %s", com, c.filterSeries)
|
||||
}
|
||||
if c.filterTime != "" {
|
||||
joinStatement := " where "
|
||||
if strings.Contains(strings.ToLower(com), joinStatement) {
|
||||
joinStatement = " AND "
|
||||
}
|
||||
com = fmt.Sprintf("%s%s%s", com, joinStatement, c.filterTime)
|
||||
}
|
||||
return com
|
||||
}
|
||||
|
||||
// getMeasurementTags get the tags for each measurement.
|
||||
// tags are placed in a map without values (similar to a set) for quick lookups:
|
||||
// {"measurement1": {"tag1", "tag2"}, "measurement2": {"tag3", "tag4"}}
|
||||
|
||||
@@ -103,3 +103,26 @@ func TestTimeFilter(t *testing.T) {
|
||||
// both start and end filters
|
||||
f("2020-01-01T20:07:00Z", "2020-01-01T21:07:00Z", "time >= '2020-01-01T20:07:00Z' and time <= '2020-01-01T21:07:00Z'")
|
||||
}
|
||||
|
||||
func TestGetSeriesCommand(t *testing.T) {
|
||||
f := func(filterSeries, filterTime, expCommand string) {
|
||||
t.Helper()
|
||||
|
||||
c := &Client{
|
||||
filterTime: filterTime,
|
||||
filterSeries: filterSeries,
|
||||
}
|
||||
gotCommand := c.getSeriesCommand()
|
||||
if gotCommand != expCommand {
|
||||
t.Fatalf("unexpected command\ngot\n%s\nwant\n%s", gotCommand, expCommand)
|
||||
}
|
||||
}
|
||||
|
||||
f("", "", "show series")
|
||||
f("from cpu", "", "show series from cpu")
|
||||
f("from cpu where arch='x86'", "", "show series from cpu where arch='x86'")
|
||||
f("", "time >= '2020-01-01T20:07:00Z'", "show series where time >= '2020-01-01T20:07:00Z'")
|
||||
f("from cpu", "time >= '2020-01-01T20:07:00Z'", "show series from cpu where time >= '2020-01-01T20:07:00Z'")
|
||||
f("from cpu where arch='x86'", "time >= '2020-01-01T20:07:00Z'", "show series from cpu where arch='x86' AND time >= '2020-01-01T20:07:00Z'")
|
||||
f("from cpu where arch='x86' AND hostname='host_2753'", "time >= '2020-01-01T20:07:00Z'", "show series from cpu where arch='x86' AND hostname='host_2753' AND time >= '2020-01-01T20:07:00Z'")
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutils"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/timeutil"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -16,7 +16,7 @@ const (
|
||||
// ParseTime parses time in s string and returns time.Time object
|
||||
// if parse correctly or error if not
|
||||
func ParseTime(s string) (time.Time, error) {
|
||||
msecs, err := promutils.ParseTimeMsec(s)
|
||||
msecs, err := timeutil.ParseTimeMsec(s)
|
||||
if err != nil {
|
||||
return time.Time{}, fmt.Errorf("cannot parse %s: %w", s, err)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package prompush
|
||||
|
||||
import (
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vminsert/common"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vminsert/relabel"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
|
||||
"github.com/VictoriaMetrics/metrics"
|
||||
@@ -49,6 +50,7 @@ func push(ctx *common.InsertCtx, tss []prompbmarshal.TimeSeries) {
|
||||
}
|
||||
ctx.Reset(rowsLen)
|
||||
rowsTotal := 0
|
||||
hasRelabeling := relabel.HasRelabeling()
|
||||
for i := range tss {
|
||||
ts := &tss[i]
|
||||
rowsTotal += len(ts.Samples)
|
||||
@@ -57,7 +59,7 @@ func push(ctx *common.InsertCtx, tss []prompbmarshal.TimeSeries) {
|
||||
label := &ts.Labels[j]
|
||||
ctx.AddLabel(label.Name, label.Value)
|
||||
}
|
||||
if !ctx.TryPrepareLabels(false) {
|
||||
if !ctx.TryPrepareLabels(hasRelabeling) {
|
||||
continue
|
||||
}
|
||||
var metricNameRaw []byte
|
||||
|
||||
@@ -373,9 +373,17 @@ func getRollupConfigs(funcName string, rf rollupFunc, expr metricsql.Expr, start
|
||||
func(values []float64, timestamps []int64), []*rollupConfig, error) {
|
||||
preFunc := func(_ []float64, _ []int64) {}
|
||||
funcName = strings.ToLower(funcName)
|
||||
|
||||
// window > lookbackDelta could result in negative delta.
|
||||
// See issue: https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8342
|
||||
stalenessInterval := lookbackDelta
|
||||
if stalenessInterval != 0 && stalenessInterval < window {
|
||||
stalenessInterval = window
|
||||
}
|
||||
|
||||
if rollupFuncsRemoveCounterResets[funcName] {
|
||||
preFunc = func(values []float64, timestamps []int64) {
|
||||
removeCounterResets(values, timestamps, lookbackDelta)
|
||||
removeCounterResets(values, timestamps, stalenessInterval)
|
||||
}
|
||||
}
|
||||
samplesScannedPerCall := rollupFuncsSamplesScannedPerCall[funcName]
|
||||
@@ -487,7 +495,7 @@ func getRollupConfigs(funcName string, rf rollupFunc, expr metricsql.Expr, start
|
||||
if rollupFuncsRemoveCounterResets[aggrFuncName] {
|
||||
// There is no need to save the previous preFunc, since it is either empty or the same.
|
||||
preFunc = func(values []float64, timestamps []int64) {
|
||||
removeCounterResets(values, timestamps, lookbackDelta)
|
||||
removeCounterResets(values, timestamps, stalenessInterval)
|
||||
}
|
||||
}
|
||||
rf := rollupAggrFuncs[aggrFuncName]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.23.6 AS build-web-stage
|
||||
FROM golang:1.24.0 AS build-web-stage
|
||||
COPY build /build
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
@@ -9,7 +9,7 @@ export const useDebugDownsamplingFilters = () => {
|
||||
const { serverUrl } = useAppState();
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
const [data, setData] = useState<Map<string, string[]>>(new Map());
|
||||
const [data, setData] = useState<Map<string, string[] | null>>(new Map());
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [metricsError, setMetricsError] = useState<ErrorTypes | string>();
|
||||
const [flagsError, setFlagsError] = useState<ErrorTypes | string>();
|
||||
|
||||
@@ -7,6 +7,7 @@ import { PlayIcon, WikiIcon } from "../../components/Main/Icons";
|
||||
import { useDebugDownsamplingFilters } from "./hooks/useDebugDownsamplingFilters";
|
||||
import Spinner from "../../components/Main/Spinner/Spinner";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import classNames from "classnames";
|
||||
|
||||
const example = {
|
||||
flags: `-downsampling.period={env="dev"}:7d:5m,{env="dev"}:30d:30m
|
||||
@@ -54,7 +55,14 @@ const DownsamplingFilters: FC = () => {
|
||||
for (const [key, value] of data) {
|
||||
rows.push(<tr className="vm-table__row">
|
||||
<td className="vm-table-cell">{key}</td>
|
||||
<td className="vm-table-cell">{value.join(" ")}</td>
|
||||
<td
|
||||
className={classNames({
|
||||
"vm-table-cell": true,
|
||||
"vm-table-cell_empty": !value,
|
||||
})}
|
||||
>
|
||||
{value ? value.join(" ") : "No matching rules found!"}
|
||||
</td>
|
||||
</tr>);
|
||||
}
|
||||
return (
|
||||
|
||||
@@ -94,6 +94,11 @@
|
||||
white-space: nowrap;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
&_empty {
|
||||
color: $color-text-secondary;
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
|
||||
&__sort-icon {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,12 +1,13 @@
|
||||
# All these commands must run from repository root.
|
||||
|
||||
DOCKER_REGISTRIES ?= docker.io quay.io
|
||||
DOCKER_NAMESPACE ?= victoriametrics
|
||||
|
||||
ROOT_IMAGE ?= alpine:3.21.3
|
||||
ROOT_IMAGE_SCRATCH ?= scratch
|
||||
CERTS_IMAGE := alpine:3.21.3
|
||||
|
||||
GO_BUILDER_IMAGE := golang:1.23.6-alpine
|
||||
GO_BUILDER_IMAGE := golang:1.24.0-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
|
||||
@@ -92,8 +93,10 @@ publish-via-docker:
|
||||
--label "org.opencontainers.image.vendor=VictoriaMetrics" \
|
||||
--label "org.opencontainers.image.version=$(PKG_TAG)" \
|
||||
--label "org.opencontainers.image.created=$(shell date -u +"%Y-%m-%dT%H:%M:%SZ")" \
|
||||
--tag $(DOCKER_NAMESPACE)/$(APP_NAME):$(PKG_TAG)$(RACE) \
|
||||
--tag $(DOCKER_NAMESPACE)/$(APP_NAME):$(LATEST_TAG)$(RACE) \
|
||||
$(foreach registry,$(DOCKER_REGISTRIES),\
|
||||
--tag $(registry)/$(DOCKER_NAMESPACE)/$(APP_NAME):$(PKG_TAG)$(RACE) \
|
||||
--tag $(registry)/$(DOCKER_NAMESPACE)/$(APP_NAME):$(LATEST_TAG)$(RACE) \
|
||||
) \
|
||||
-o type=image \
|
||||
--provenance=false \
|
||||
-f app/$(APP_NAME)/multiarch/Dockerfile \
|
||||
@@ -110,8 +113,10 @@ publish-via-docker:
|
||||
--label "org.opencontainers.image.vendor=VictoriaMetrics" \
|
||||
--label "org.opencontainers.image.version=$(PKG_TAG)" \
|
||||
--label "org.opencontainers.image.created=$(shell date -u +"%Y-%m-%dT%H:%M:%SZ")" \
|
||||
--tag $(DOCKER_NAMESPACE)/$(APP_NAME):$(PKG_TAG)$(RACE)-scratch \
|
||||
--tag $(DOCKER_NAMESPACE)/$(APP_NAME):$(LATEST_TAG)$(RACE)-scratch \
|
||||
$(foreach registry,$(DOCKER_REGISTRIES),\
|
||||
--tag $(registry)/$(DOCKER_NAMESPACE)/$(APP_NAME):$(PKG_TAG)$(RACE)-scratch \
|
||||
--tag $(registry)/$(DOCKER_NAMESPACE)/$(APP_NAME):$(LATEST_TAG)$(RACE)-scratch \
|
||||
) \
|
||||
-o type=image \
|
||||
--provenance=false \
|
||||
-f app/$(APP_NAME)/multiarch/Dockerfile \
|
||||
|
||||
@@ -209,5 +209,5 @@ Please see more examples on integration of VictoriaLogs with other log shippers
|
||||
* [journald](https://github.com/VictoriaMetrics/VictoriaMetrics/tree/master/deployment/docker/victorialogs/journald)
|
||||
* [opentelemetry-collector](https://github.com/VictoriaMetrics/VictoriaMetrics/tree/master/deployment/docker/victorialogs/opentelemetry-collector)
|
||||
* [telegraf](https://github.com/VictoriaMetrics/VictoriaMetrics/tree/master/deployment/docker/victorialogs/telegraf)
|
||||
* [fluentd]((https://github.com/VictoriaMetrics/VictoriaMetrics/tree/master/deployment/docker/victorialogs/fluentd)
|
||||
* [fluentd](https://github.com/VictoriaMetrics/VictoriaMetrics/tree/master/deployment/docker/victorialogs/fluentd)
|
||||
* [datadog-serverless](https://github.com/VictoriaMetrics/VictoriaMetrics/tree/master/deployment/docker/victorialogs/datadog-serverless)
|
||||
|
||||
@@ -4,7 +4,7 @@ services:
|
||||
# And forward them to --remoteWrite.url
|
||||
vmagent:
|
||||
container_name: vmagent
|
||||
image: victoriametrics/vmagent:v1.111.0
|
||||
image: victoriametrics/vmagent:v1.112.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.111.0-cluster
|
||||
image: victoriametrics/vmstorage:v1.112.0-cluster
|
||||
ports:
|
||||
- 8482
|
||||
- 8400
|
||||
@@ -51,7 +51,7 @@ services:
|
||||
restart: always
|
||||
vmstorage-2:
|
||||
container_name: vmstorage-2
|
||||
image: victoriametrics/vmstorage:v1.111.0-cluster
|
||||
image: victoriametrics/vmstorage:v1.112.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.111.0-cluster
|
||||
image: victoriametrics/vminsert:v1.112.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.111.0-cluster
|
||||
image: victoriametrics/vmselect:v1.112.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.111.0-cluster
|
||||
image: victoriametrics/vmselect:v1.112.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.111.0
|
||||
image: victoriametrics/vmauth:v1.112.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.111.0
|
||||
image: victoriametrics/vmalert:v1.112.0
|
||||
depends_on:
|
||||
- "vmauth"
|
||||
ports:
|
||||
|
||||
@@ -44,7 +44,7 @@ services:
|
||||
# storing logs and serving read queries.
|
||||
victorialogs:
|
||||
container_name: victorialogs
|
||||
image: victoriametrics/victoria-logs:v1.13.0-victorialogs
|
||||
image: victoriametrics/victoria-logs:v1.15.0-victorialogs
|
||||
command:
|
||||
- "--storageDataPath=/vlogs"
|
||||
- "--httpListenAddr=:9428"
|
||||
@@ -59,7 +59,7 @@ services:
|
||||
# scraping, storing metrics and serve read requests.
|
||||
victoriametrics:
|
||||
container_name: victoriametrics
|
||||
image: victoriametrics/victoria-metrics:v1.111.0
|
||||
image: victoriametrics/victoria-metrics:v1.112.0
|
||||
ports:
|
||||
- 8428:8428
|
||||
volumes:
|
||||
@@ -78,7 +78,7 @@ services:
|
||||
# depending on the requested path.
|
||||
vmauth:
|
||||
container_name: vmauth
|
||||
image: victoriametrics/vmauth:v1.111.0
|
||||
image: victoriametrics/vmauth:v1.112.0
|
||||
depends_on:
|
||||
- "victoriametrics"
|
||||
- "victorialogs"
|
||||
@@ -95,7 +95,7 @@ services:
|
||||
# vmalert executes alerting and recording rules according to given rule type.
|
||||
vmalert:
|
||||
container_name: vmalert
|
||||
image: victoriametrics/vmalert:v1.111.0
|
||||
image: victoriametrics/vmalert:v1.112.0
|
||||
depends_on:
|
||||
- "vmauth"
|
||||
- "alertmanager"
|
||||
|
||||
@@ -4,7 +4,7 @@ services:
|
||||
# And forward them to --remoteWrite.url
|
||||
vmagent:
|
||||
container_name: vmagent
|
||||
image: victoriametrics/vmagent:v1.111.0
|
||||
image: victoriametrics/vmagent:v1.112.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.111.0
|
||||
image: victoriametrics/victoria-metrics:v1.112.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.111.0
|
||||
image: victoriametrics/vmalert:v1.112.0
|
||||
depends_on:
|
||||
- "victoriametrics"
|
||||
- "alertmanager"
|
||||
|
||||
@@ -128,3 +128,21 @@ groups:
|
||||
summary: "Some rows are rejected on \"{{ $labels.instance }}\" on ingestion attempt"
|
||||
description: "Ingested rows on instance \"{{ $labels.instance }}\" are rejected due to the
|
||||
following reason: \"{{ $labels.reason }}\""
|
||||
|
||||
- alert: TooHighQueryLoad
|
||||
expr: increase(vm_concurrent_select_limit_timeout_total[5m]) > 0
|
||||
for: 15m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Read queries fail with timeout for {{ $labels.job }} on instance {{ $labels.instance }}"
|
||||
description: |
|
||||
Instance {{ $labels.instance }} ({{ $labels.job }}) is failing to serve read queries during last 15m.
|
||||
Concurrency limit `-search.maxConcurrentRequests` was reached on this instance and extra queries were
|
||||
put into the queue for `-search.maxQueueDuration` interval. But even after waiting in the queue these queries weren't served.
|
||||
This happens if instance is overloaded with the current workload, or datasource is too slow to respond.
|
||||
Possible solutions are the following:
|
||||
* reduce the query load;
|
||||
* increase compute resources or number of replicas;
|
||||
* adjust limits `-search.maxConcurrentRequests` and `-search.maxQueueDuration`.
|
||||
See more at https://docs.victoriametrics.com/troubleshooting/#slow-queries.
|
||||
@@ -1,7 +1,7 @@
|
||||
services:
|
||||
# meta service will be ignored by compose
|
||||
.victorialogs:
|
||||
image: docker.io/victoriametrics/victoria-logs:v1.13.0-victorialogs
|
||||
image: docker.io/victoriametrics/victoria-logs:v1.15.0-victorialogs
|
||||
command:
|
||||
- -storageDataPath=/vlogs
|
||||
- -loggerFormat=json
|
||||
@@ -19,7 +19,7 @@ services:
|
||||
retries: 10
|
||||
|
||||
dd-proxy:
|
||||
image: docker.io/victoriametrics/vmauth:v1.111.0
|
||||
image: docker.io/victoriametrics/vmauth:v1.112.0
|
||||
restart: on-failure
|
||||
volumes:
|
||||
- ./:/etc/vmauth
|
||||
@@ -45,7 +45,7 @@ services:
|
||||
replicas: 0
|
||||
|
||||
victoriametrics:
|
||||
image: victoriametrics/victoria-metrics:v1.111.0
|
||||
image: victoriametrics/victoria-metrics:v1.112.0
|
||||
ports:
|
||||
- '8428:8428'
|
||||
command:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
services:
|
||||
vmagent:
|
||||
container_name: vmagent
|
||||
image: victoriametrics/vmagent:v1.111.0
|
||||
image: victoriametrics/vmagent:v1.112.0
|
||||
depends_on:
|
||||
- "victoriametrics"
|
||||
ports:
|
||||
@@ -18,7 +18,7 @@ services:
|
||||
|
||||
victoriametrics:
|
||||
container_name: victoriametrics
|
||||
image: victoriametrics/victoria-metrics:v1.111.0
|
||||
image: victoriametrics/victoria-metrics:v1.112.0
|
||||
ports:
|
||||
- 8428:8428
|
||||
volumes:
|
||||
@@ -50,7 +50,7 @@ services:
|
||||
|
||||
vmalert:
|
||||
container_name: vmalert
|
||||
image: victoriametrics/vmalert:v1.111.0
|
||||
image: victoriametrics/vmalert:v1.112.0
|
||||
depends_on:
|
||||
- "victoriametrics"
|
||||
ports:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
version: '3'
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
filebeat-elastic:
|
||||
@@ -18,7 +18,7 @@ services:
|
||||
- vlogs
|
||||
|
||||
generator:
|
||||
image: golang:1.23.6-alpine
|
||||
image: golang:1.24.0-alpine
|
||||
restart: always
|
||||
working_dir: /go/src/app
|
||||
volumes:
|
||||
@@ -33,7 +33,7 @@ services:
|
||||
- -syslog.addr=filebeat-elastic:12345
|
||||
- -syslog.addr2=filebeat-vlogs:12345
|
||||
- -logs.randomSuffix=false
|
||||
depends_on: [ filebeat-elastic, filebeat-vlogs ]
|
||||
depends_on: [filebeat-elastic, filebeat-vlogs]
|
||||
|
||||
elastic:
|
||||
image: docker.elastic.co/elasticsearch/elasticsearch:8.8.0
|
||||
@@ -48,8 +48,8 @@ services:
|
||||
volumes:
|
||||
- ./elk/kibana/kibana.yml:/usr/share/kibana/config/kibana.yml
|
||||
ports:
|
||||
- '5601:5601'
|
||||
depends_on: [ elastic ]
|
||||
- "5601:5601"
|
||||
depends_on: [elastic]
|
||||
|
||||
beat-exporter-elastic:
|
||||
image: trustpilot/beat-exporter:0.4.0
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
version: '3'
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
generator:
|
||||
image: golang:1.23.6-alpine
|
||||
image: golang:1.24.0-alpine
|
||||
restart: always
|
||||
working_dir: /go/src/app
|
||||
volumes:
|
||||
@@ -18,7 +18,7 @@ services:
|
||||
- -syslog.addr=rsyslog:514
|
||||
- -syslog.addr2=rsyslog:514
|
||||
- -logs.randomSuffix=false
|
||||
depends_on: [ rsyslog ]
|
||||
depends_on: [rsyslog]
|
||||
|
||||
loki:
|
||||
image: grafana/loki:2.9.0
|
||||
@@ -45,8 +45,7 @@ services:
|
||||
context: rsyslog
|
||||
volumes:
|
||||
- ./rsyslog/rsyslog.conf:/etc/rsyslog.conf
|
||||
depends_on: [ promtail ]
|
||||
|
||||
depends_on: [promtail]
|
||||
|
||||
volumes:
|
||||
loki:
|
||||
|
||||
@@ -3,7 +3,7 @@ version: "3"
|
||||
services:
|
||||
# Run `make package-victoria-logs` to build victoria-logs image
|
||||
vlogs:
|
||||
image: docker.io/victoriametrics/victoria-logs:v1.13.0-victorialogs
|
||||
image: docker.io/victoriametrics/victoria-logs:v1.15.0-victorialogs
|
||||
volumes:
|
||||
- vlogs:/vlogs
|
||||
ports:
|
||||
|
||||
@@ -174,9 +174,9 @@ Also see archives containing the word `cluster`.
|
||||
|
||||
Docker images for the cluster version are available here:
|
||||
|
||||
- `vminsert` - <https://hub.docker.com/r/victoriametrics/vminsert/tags>
|
||||
- `vmselect` - <https://hub.docker.com/r/victoriametrics/vmselect/tags>
|
||||
- `vmstorage` - <https://hub.docker.com/r/victoriametrics/vmstorage/tags>
|
||||
- `vminsert` - [Docker Hub](https://hub.docker.com/r/victoriametrics/vminsert/tags) and [Quay](https://quay.io/repository/victoriametrics/vminsert?tab=tags)
|
||||
- `vmselect` - [Docker Hub](https://hub.docker.com/r/victoriametrics/vmselect/tags) and [Quay](https://quay.io/repository/victoriametrics/vmselect?tab=tags)
|
||||
- `vmstorage` - [Docker Hub](https://hub.docker.com/r/victoriametrics/vmstorage/tags) and [Quay](https://quay.io/repository/victoriametrics/vmstorage?tab=tags)
|
||||
|
||||
## Building from sources
|
||||
|
||||
@@ -761,7 +761,7 @@ Some workloads may need fine-grained resource usage limits. In these cases the f
|
||||
- `-search.maxDeleteSeries` at `vmselect` limits the number of unique time
|
||||
series that can be deleted by a single
|
||||
[/api/v1/admin/tsdb/delete_series](https://docs.victoriametrics.com/url-examples/#apiv1admintsdbdelete_series)
|
||||
call. The duration is limited via `-search.maxDeleteDuration` flag{{% available_from "#tip" %}}. Deleting too many time series may require big
|
||||
call. The duration is limited via `-search.maxDeleteDuration` flag{{% available_from "v1.110.0" %}}. Deleting too many time series may require big
|
||||
amount of CPU and memory at `vmstorage` and this limit guards against unplanned resource usage spikes.
|
||||
Also see [How to delete time series](#how-to-delete-time-series) section to
|
||||
learn about different ways of deleting series.
|
||||
|
||||
@@ -22,5 +22,5 @@ to [the latest available releases](https://docs.victoriametrics.com/changelog/).
|
||||
|
||||
## Currently supported LTS release lines
|
||||
|
||||
- v1.110.x - the latest one is [v1.110.1 LTS release](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.110.1)
|
||||
- v1.102.x - the latest one is [v1.102.13 LTS release](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.102.13)
|
||||
- v1.110.x - the latest one is [v1.110.2 LTS release](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.110.2)
|
||||
- v1.102.x - the latest one is [v1.102.15 LTS release](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.102.15)
|
||||
|
||||
@@ -2349,4 +2349,4 @@ VictoriaMetrics performs the following implicit conversions for incoming queries
|
||||
is passed to [rollup function](#rollup-functions), then a [subquery](#subqueries) with `1i` lookbehind window and `1i` step is automatically formed.
|
||||
For example, `rate(sum(up))` is automatically converted to `rate((sum(default_rollup(up)))[1i:1i])`.
|
||||
This behavior can be disabled or logged via `-search.disableImplicitConversion` and `-search.logImplicitConversion` command-line flags
|
||||
starting from [`v1.101.0` release](https://docs.victoriametrics.com/changelog/).
|
||||
starting from [`v1.102.0-rc2` release](https://docs.victoriametrics.com/changelog/changelog_2024/#v11020-rc2).
|
||||
|
||||
@@ -23,7 +23,7 @@ VictoriaMetrics is distributed in the following forms:
|
||||
|
||||
VictoriaMetrics is available as:
|
||||
|
||||
* [Docker images](https://hub.docker.com/r/victoriametrics/victoria-metrics/)
|
||||
* docker images at [Docker Hub](https://hub.docker.com/r/victoriametrics/victoria-metrics/) and [Quay](https://quay.io/repository/victoriametrics/victoria-metrics?tab=tags)
|
||||
* [Helm Charts](https://github.com/VictoriaMetrics/helm-charts#list-of-charts)
|
||||
* [Binary releases](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/latest)
|
||||
* [Ansible Roles](https://github.com/VictoriaMetrics/ansible-playbooks)
|
||||
@@ -55,8 +55,8 @@ under the current directory:
|
||||
|
||||
|
||||
```sh
|
||||
docker pull victoriametrics/victoria-metrics:v1.111.0
|
||||
docker run -it --rm -v `pwd`/victoria-metrics-data:/victoria-metrics-data -p 8428:8428 victoriametrics/victoria-metrics:v1.111.0
|
||||
docker pull victoriametrics/victoria-metrics:v1.112.0
|
||||
docker run -it --rm -v `pwd`/victoria-metrics-data:/victoria-metrics-data -p 8428:8428 victoriametrics/victoria-metrics:v1.112.0
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
@@ -12,7 +12,7 @@ VictoriaMetrics is a fast, cost-effective and scalable monitoring solution and t
|
||||
See [case studies for VictoriaMetrics](https://docs.victoriametrics.com/casestudies/).
|
||||
|
||||
VictoriaMetrics is available in [binary releases](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/latest),
|
||||
[Docker images](https://hub.docker.com/r/victoriametrics/victoria-metrics/) and [source code](https://github.com/VictoriaMetrics/VictoriaMetrics).
|
||||
Docker images at [Docker Hub](https://hub.docker.com/r/victoriametrics/victoria-metrics/) and [Quay](https://quay.io/repository/victoriametrics/victoria-metrics?tab=tags), [source code](https://github.com/VictoriaMetrics/VictoriaMetrics).
|
||||
|
||||
Documentation for the cluster version of VictoriaMetrics is available [here](https://docs.victoriametrics.com/cluster-victoriametrics/).
|
||||
|
||||
@@ -124,7 +124,7 @@ VictoriaMetrics ecosystem contains the following components additionally to [sin
|
||||
### Install
|
||||
|
||||
To quickly try VictoriaMetrics, just download the [VictoriaMetrics executable](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/latest)
|
||||
or [Docker image](https://hub.docker.com/r/victoriametrics/victoria-metrics/) and start it with the desired command-line flags.
|
||||
or docker image from [Docker Hub](https://hub.docker.com/r/victoriametrics/victoria-metrics/) or [Quay](https://quay.io/repository/victoriametrics/victoria-metrics?tab=tags) and start it with the desired command-line flags.
|
||||
See also [QuickStart guide](https://docs.victoriametrics.com/quick-start/) for additional information.
|
||||
|
||||
VictoriaMetrics can also be installed via these installation methods:
|
||||
@@ -1076,7 +1076,7 @@ VictoriaMetrics supports the following handlers from [Graphite Tags API](https:/
|
||||
## How to build from sources
|
||||
|
||||
We recommend using either [binary releases](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/latest) or
|
||||
[docker images](https://hub.docker.com/r/victoriametrics/victoria-metrics/) instead of building VictoriaMetrics
|
||||
docker images ([Docker Hub](https://hub.docker.com/r/victoriametrics/victoria-metrics/) and [Quay](https://quay.io/repository/victoriametrics/victoria-metrics?tab=tags)) instead of building VictoriaMetrics
|
||||
from sources. Building from sources is reasonable when developing additional features specific
|
||||
to your needs or when testing bugfixes.
|
||||
|
||||
@@ -1770,7 +1770,7 @@ By default, VictoriaMetrics is tuned for an optimal resource usage under typical
|
||||
- `-search.maxDeleteSeries` limits the number of unique time series that can be
|
||||
deleted by a single
|
||||
[/api/v1/admin/tsdb/delete_series](https://docs.victoriametrics.com/url-examples/#apiv1admintsdbdelete_series)
|
||||
call. The duration is limited via `-search.maxDeleteDuration` flag{{% available_from "#tip" %}}. Deleting too many time series may require big
|
||||
call. The duration is limited via `-search.maxDeleteDuration` flag{{% available_from "v1.110.0" %}}. Deleting too many time series may require big
|
||||
amount of CPU and memory and this limit guards against unplanned resource usage spikes. Also see
|
||||
[How to delete time series](#how-to-delete-time-series) section to learn about
|
||||
different ways of deleting series.
|
||||
@@ -1998,7 +1998,7 @@ based on the time range of the query:
|
||||
Mappings are added to the indexes during the data ingestion:
|
||||
|
||||
- In global index each mapping is created only once per retention period.
|
||||
- In the per-day index each mapping is be created for each unique date that
|
||||
- In the per-day index each mapping is created for each unique date that
|
||||
has been seen in the samples for the corresponding time series.
|
||||
|
||||
IndexDB respects [retention period](#retention) and once it is over, the indexes
|
||||
@@ -2102,9 +2102,9 @@ while leaving the last sample per each 1-hour interval for samples older than 18
|
||||
VictoriaMetrics supports{{% available_from "v1.100.0" %}} configuring independent downsampling per different sets of [time series](https://docs.victoriametrics.com/keyconcepts/#time-series)
|
||||
via `-downsampling.period=filter:offset:interval` syntax. In this case the given `offset:interval` downsampling is applied only to time series matching the given `filter`.
|
||||
The `filter` can contain arbitrary [series filter](https://docs.victoriametrics.com/keyconcepts/#filtering).
|
||||
For example, `-downsampling.period='{__name__=~"(node|process)_.*"}:1d:1m` instructs VictoriaMetrics to deduplicate samples older than one day with one minute interval
|
||||
For example, `-downsampling.period='{__name__=~"(node|process)_.*"}:1d:1m` instructs VictoriaMetrics to downsample samples older than one day with one minute interval
|
||||
only for [time series](https://docs.victoriametrics.com/keyconcepts/#time-series) with names starting with `node_` or `process_` prefixes.
|
||||
The deduplication for other time series can be configured independently via additional `-downsampling.period` command-line flags.
|
||||
The downsampling for other time series can be configured independently via additional `-downsampling.period` command-line flags.
|
||||
Downsampling configuration can be tested in enterprise version of vmui on the page `Tools.Downsampling filters debug`.
|
||||
|
||||
If the time series doesn't match any `filter`, then it isn't downsampled. If the time series matches multiple filters, then the downsampling
|
||||
@@ -2608,7 +2608,7 @@ A prominent example is Kubernetes. Services in k8s expose big number of series w
|
||||
increasing churn rate. The per-day index speeds up data retrieval in this case.
|
||||
|
||||
But if your use case assumes low or no churn rate, then you might benefit from disabling the per-day index by setting
|
||||
the flag `-disablePerDayIndex`{{% available_from "#tip" %}}. This will improve the time series ingestion speed and decrease disk space usage,
|
||||
the flag `-disablePerDayIndex`{{% available_from "v1.112.0" %}}. This will improve the time series ingestion speed and decrease disk space usage,
|
||||
since no time or disk space is spent maintaining the per-day index.
|
||||
|
||||
Example use cases:
|
||||
|
||||
@@ -17,7 +17,7 @@ git remote add enterprise <url>
|
||||
1. Make sure you have singing key configured
|
||||
1. Make sure you have github token with at least `read:org, repo, write:packages` permissions exported under `GITHUB_TOKEN` env variable.
|
||||
You can create token [here](https://github.com/settings/tokens)
|
||||
1. Make sure you're [authorized](https://hub.docker.com/orgs/victoriametrics/settings/enforce-sign-in/windows) for pushing docker images
|
||||
1. Make sure you're [authorized](https://hub.docker.com/orgs/victoriametrics/settings/enforce-sign-in/windows) for pushing docker images to `docker.io` and `quay.io`
|
||||
|
||||
### For MacOS users
|
||||
|
||||
@@ -69,12 +69,6 @@ Bumping the limits may significantly improve build speed.
|
||||
* linux/ppc64le
|
||||
* linux/386
|
||||
This step can be run manually with the command `make publish` from the needed git tag.
|
||||
1. Verify that created images are stable and don't introduce regressions on [test environment](https://github.com/VictoriaMetrics/VictoriaMetrics-enterprise/blob/master/Release-Guide.md#testing-releases).
|
||||
1. Test new images on [sandbox](https://github.com/VictoriaMetrics/VictoriaMetrics-enterprise/blob/master/Release-Guide.md#testing-releases).
|
||||
1. Push the tags `v1.xx.y` and `v1.xx.y-cluster` created at previous steps to public GitHub repository at https://github.com/VictoriaMetrics/VictoriaMetrics.
|
||||
Push the tags `v1.xx.y`, `v1.xx.y-cluster`, `v1.xx.y-enterprise` and `v1.xx.y-enterprise-cluster` to the corresponding
|
||||
branches in private repository.
|
||||
**Important note:** do not push enterprise tags to public GitHub repository - they must be pushed only to private repository.
|
||||
1. Run `TAG=v1.xx.y make github-create-release github-upload-assets`. This command performs the following tasks:
|
||||
a) Create draft GitHub release with the name `TAG`. This step can be run manually
|
||||
with the command `TAG=v1.xx.y make github-create-release`.
|
||||
@@ -92,6 +86,13 @@ Bumping the limits may significantly improve build speed.
|
||||
1. Go to <https://github.com/VictoriaMetrics/VictoriaMetrics/releases> and verify that draft release with the name `TAG` has been created
|
||||
and this release contains all the needed binaries and checksums.
|
||||
1. Update the release description with the content of [CHANGELOG](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/docs/changelog/CHANGELOG.md) for this release.
|
||||
1. Follow the instructions in [LTS release](https://github.com/VictoriaMetrics/VictoriaMetrics-enterprise/blob/master/Release-Guide.md#lts-release).
|
||||
1. Verify that created images are stable and don't introduce regressions on [test environment](https://github.com/VictoriaMetrics/VictoriaMetrics-enterprise/blob/master/Release-Guide.md#testing-releases).
|
||||
1. Test new images on [sandbox](https://github.com/VictoriaMetrics/VictoriaMetrics-enterprise/blob/master/Release-Guide.md#testing-releases).
|
||||
1. Push the tags `v1.xx.y` and `v1.xx.y-cluster` created at previous steps to public GitHub repository at https://github.com/VictoriaMetrics/VictoriaMetrics.
|
||||
Push the tags `v1.xx.y`, `v1.xx.y-cluster`, `v1.xx.y-enterprise` and `v1.xx.y-enterprise-cluster` to the corresponding
|
||||
branches in private repository.
|
||||
**Important note:** do not push enterprise tags to public GitHub repository - they must be pushed only to private repository.
|
||||
1. Publish release by pressing "Publish release" green button in GitHub's UI.
|
||||
1. Update GitHub tickets related to the new release. Usually, such tickets have label [waiting for release](https://github.com/VictoriaMetrics/VictoriaMetrics/issues?q=is%3Aopen+is%3Aissue+label%3A%22waiting+for+release%22). Close such tickets by mentioning which release they were included into, and remove the label. See example [here](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6637#issuecomment-2390729511).
|
||||
1. Bump VictoriaMetrics version at `deployment/docker/docker-compose.yml` and at `deployment/docker/docker-compose-cluster.yml`.
|
||||
|
||||
@@ -16,6 +16,29 @@ according to [these docs](https://docs.victoriametrics.com/victorialogs/quicksta
|
||||
|
||||
## tip
|
||||
|
||||
## [v1.15.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.15.0-victorialogs)
|
||||
|
||||
Released at 2025-02-27
|
||||
|
||||
* FEATURE: [`pack_json` pipe](https://docs.victoriametrics.com/victorialogs/logsql/#pack_json-pipe): allow packing fields, which start with the given prefixes. For example, `pack_json fields (foo.*, bar.*)` creates a JSON containing all the fields, which start with either `foo.` or `bar.`.
|
||||
* FEATURE: [`pack_logfmt` pipe](https://docs.victoriametrics.com/victorialogs/logsql/#pack_logfmt-pipe): allow packing fields, which start with the given prefixes. For example, `pack_logfmt fields (foo.*, bar.*)` creates [logfmt](https://brandur.org/logfmt) message containing all the fields, which start with either `foo.` or `bar.`.
|
||||
* FEATURE: expose `vl_request_duration_seconds` [summaries](https://docs.victoriametrics.com/keyconcepts/#summary) for [select APIs](https://docs.victoriametrics.com/victorialogs/querying/#http-api) at the [/metrics](https://docs.victoriametrics.com/victorialogs/#monitoring) page.
|
||||
* FEATURE: allow passing `*` as a subquery inside [`in(*)`, `contains_any(*)` and `contains_all(*)` filters](https://docs.victoriametrics.com/victorialogs/logsql/#subquery-filter). Such filters are treated as `match all` aka `*`. This is going to be used by [Grafana plugin for VictoriaLogs](https://docs.victoriametrics.com/victorialogs/victorialogs-datasource/). See [this issue](https://github.com/VictoriaMetrics/victorialogs-datasource/issues/238#issuecomment-2685447673).
|
||||
* FEATURE: [victorialogs dashboard](https://grafana.com/grafana/dashboards/22084-victorialogs/): add panels to display amount of ingested logs in bytes, latency of [select APIs](https://docs.victoriametrics.com/victorialogs/querying/#http-api) calls, troubleshooting panels.
|
||||
* FEATURE: provide alternative registry for all VictoriaLogs components at [Quay.io](https://quay.io/organization/victoriametrics): [VictoriaLogs](https://quay.io/repository/victoriametrics/victoria-logs?tab=tags) and [vlogscli](https://quay.io/repository/victoriametrics/vlogscli?tab=tags).
|
||||
|
||||
* BUGFIX: do not treat a string containing leading zeros as a number during data ingestion and querying. For example, `00123` string shouldn't be treated as `123` number. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8361).
|
||||
|
||||
## [v1.14.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.14.0-victorialogs)
|
||||
|
||||
Released at 2025-02-25
|
||||
|
||||
* FEATURE: add [`lt_field` filter](https://docs.victoriametrics.com/victorialogs/logsql/#lt_field-filter), which can be used for obtaining logs where the given [field](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model) value doesn't exceed the other field value.
|
||||
* FEATURE: add [`le_field` filter](https://docs.victoriametrics.com/victorialogs/logsql/#le_field-filter), which can be used for obtaining logs where the given [field](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model) value is smaller or equal to the other field value.
|
||||
|
||||
* BUGFIX: [elasticsearch data ingestion](https://docs.victoriametrics.com/victorialogs/data-ingestion/#elasticsearch-bulk-api): support health-check endpoint requested by Jaeger v2. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8353).
|
||||
* BUGFIX: [`stats_query_range` HTTP endpoint](https://docs.victoriametrics.com/victorialogs/querying/#querying-log-range-stats): fix inconsistent result of `stats_query_range` API. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8312).
|
||||
|
||||
## [v1.13.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.13.0-victorialogs)
|
||||
|
||||
Released at 2025-02-22
|
||||
|
||||
@@ -91,8 +91,7 @@ VictoriaLogs is designed solely for logs. VictoriaLogs uses [similar design idea
|
||||
|
||||
VictoriaLogs provides easy to use query language with full-text search specifically optimized
|
||||
for log analysis - [LogsQL](https://docs.victoriametrics.com/victorialogs/logsql/).
|
||||
LogsQL is usually easier to use than SQL for typical log analysis tasks, while some
|
||||
non-trivial analytics may require SQL power.
|
||||
LogsQL is usually easier to use than SQL for typical log analysis tasks - see [these docs](https://docs.victoriametrics.com/victorialogs/sql-to-logsql/).
|
||||
|
||||
- VictoriaLogs accepts logs from popular log shippers out of the box - see [these docs](https://docs.victoriametrics.com/victorialogs/data-ingestion/).
|
||||
|
||||
@@ -141,28 +140,23 @@ for limiting the amounts of exported logs.
|
||||
|
||||
## I want to ingest logs without message field, is that possible?
|
||||
|
||||
Starting from version `v0.30.0`, VictoriaLogs started blocking the ingestion of logs **without a message field**, as it is a requirement of the [VictoriaLogs data model](https://docs.victoriametrics.com/victorialogs/keyconcepts/#message-field).
|
||||
VictoriaLogs [accepts](https://docs.victoriametrics.com/victorialogs/data-ingestion/) logs without [`_msg` field](https://docs.victoriametrics.com/victorialogs/keyconcepts/#message-field).
|
||||
In this case the `_msg` field is set to the default value, which can be configured via `-defaultMsgValue` command-line flag.
|
||||
|
||||
However, some logs do not have a message field and only contain other fields, such as logs in [this comment](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/7056#issuecomment-2434189718) and [this slack thread](https://victoriametrics.slack.com/archives/C05UNTPAEDN/p1730982146818249). Therefore, starting from version `v0.39.0`, logs without a message field are **allowed to be ingested**,
|
||||
and their message field will be recorded as:
|
||||
```json
|
||||
{"_msg": "missing _msg field; see https://docs.victoriametrics.com/victorialogs/keyconcepts/#message-field"}
|
||||
```
|
||||
|
||||
The default message field value can be changed using the `-defaultMsgValue` flag, for example, `-defaultMsgValue=foo`.
|
||||
|
||||
Please note that the message field is **crucial** for VictoriaLogs, so it is important to fill it with meaningful content.
|
||||
Please note that the `_msg` field is **crucial** for VictoriaLogs, so it is highly recommended to fill it with meaningful content.
|
||||
|
||||
## What if my logs have multiple message fields candidates?
|
||||
|
||||
When ingesting with VictoriaLogs, the message fields is specified through `_msg_field` param, which can accept **multiple fields**, and the **first non-empty field** will be used as the message field.
|
||||
Here is an example URL when pushing logs to VictoriaLogs with Promtail:
|
||||
```yaml
|
||||
clients:
|
||||
- url: http://localhost:9428/insert/loki/api/v1/push?_stream_fields=instance,job,host,app&_msg=message,body
|
||||
```
|
||||
If you [ingest](https://docs.victoriametrics.com/victorialogs/data-ingestion/) logs into VictoriaLogs
|
||||
without [`_msg` field](https://docs.victoriametrics.com/victorialogs/keyconcepts/#message-field), then this field
|
||||
is filled according to the `_msg_field` HTTP query arg and/or `VL-Msg-Field` HTTP header.
|
||||
See [these docs](https://docs.victoriametrics.com/victorialogs/data-ingestion/#http-parameters) for details.
|
||||
If the `_msg_field` HTTP query arg and/or `VL-Msg-Field` HTTP header contains a list of comma-separated field names,
|
||||
then the first non-empty field from this list is used as `_msg` field.
|
||||
|
||||
For example, if the following [log entry](https://docs.victoriametrics.com/victorialogs/keyconcepts/)
|
||||
is ingested into VictoriaLogs with `_msg_field=message,body`:
|
||||
|
||||
For the following log, its `_msg` will be `foo bar in message`:
|
||||
```json
|
||||
{
|
||||
"message": "foo bar in message",
|
||||
@@ -170,14 +164,18 @@ For the following log, its `_msg` will be `foo bar in message`:
|
||||
}
|
||||
```
|
||||
|
||||
And for the following log, its `_msg` will be `foo bar in body`:
|
||||
Then `_msg` field is set to `foo bar in message`.
|
||||
|
||||
If the following log entry is ingested into VictoriaLogs with `_msg_field=message,body`:
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "",
|
||||
"body": "foo bar in body"
|
||||
}
|
||||
```
|
||||
|
||||
Then `_msg` field is set to `foo bar in body`.
|
||||
|
||||
## What length a log record is expected to have?
|
||||
|
||||
VictoriaLogs works optimally with log records of up to `10KB`. It works OK with
|
||||
@@ -307,7 +305,7 @@ _time:1d | count_uniq(_stream)
|
||||
|
||||
## Does LogsQL support subqueries?
|
||||
|
||||
LogsQL supports subqieries via [`in(<subquery>)` filter](https://docs.victoriametrics.com/victorialogs/logsql/#multi-exact-filter).
|
||||
Yes. See [these docs](https://docs.victoriametrics.com/victorialogs/logsql/#subquery-filter).
|
||||
For example, the following query returns the total number of unique values for the `user_id` [field](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model)
|
||||
across top 3 [log streams](https://docs.victoriametrics.com/victorialogs/keyconcepts/#stream-fields) with the biggest number of logs during the last hour:
|
||||
|
||||
@@ -325,4 +323,27 @@ The query works in the following way:
|
||||
|
||||
- Then it selects all the logs across the selected log streams over the last hour with the help of [`_stream_id:...` filter](https://docs.victoriametrics.com/victorialogs/logsql/#_stream_id-filter).
|
||||
|
||||
See also [`subquery filters`](https://docs.victoriametrics.com/victorialogs/logsql/#subquery-filter).
|
||||
|
||||
## How to estimate the needed compute resources for the given workload?
|
||||
|
||||
The needed storage space depends on the following factors:
|
||||
|
||||
- Data compressibility. VictoraLogs compresses the ingested logs before storing them to disk. The compression ratio depends on the "randomness" of the ingested logs.
|
||||
Less "random" logs with many repeated field values and small differences between log messages compress the best (up to 100x and more).
|
||||
More "random" logs with many unique field values may have very low compression rate.
|
||||
|
||||
- [Data retention](https://docs.victoriametrics.com/victorialogs/#retention). For example, a year-long retention needs 52x more storage space than a week-long retention.
|
||||
|
||||
The needed RAM, CPU, storage IO and network bandwidth depends on the type and the rate of queries over the ingested logs.
|
||||
|
||||
- "Lightweight" queries over the recently ingested logs with very narrow [log stream filters](https://docs.victoriametrics.com/victorialogs/logsql/#stream-filter)
|
||||
require very low compute resources, even if they are executed at 1000 rps.
|
||||
|
||||
- "Heavy" queries over the long time range, which do not contain [log stream filters](https://docs.victoriametrics.com/victorialogs/logsql/#stream-filter)
|
||||
or have some heavy [pipe processing](https://docs.victoriametrics.com/victorialogs/logsql/#pipes) such as [analytics' calculations](https://docs.victoriametrics.com/victorialogs/logsql/#stats-pipe)
|
||||
or [sorting over billions of rows](https://docs.victoriametrics.com/victorialogs/logsql/#sort-pipe) may require hundreds of CPU cores and terabytes of RAM
|
||||
for fast execution. It is OK to execute such queries on machines with a few CPU cores and a few GiB of RAM - these queries will take more time to execute.
|
||||
|
||||
The best approach to estimate the needed compute resources for the given workload is to start a VictoriaLogs, to ingest a share (1%-10%) of your production logs into it,
|
||||
and to execute typical queries on it, while [measuring](https://docs.victoriametrics.com/victorialogs/#monitoring) the consumed compute resources.
|
||||
Then you can extrapolate the needed compute resources for the full production workload in your case.
|
||||
|
||||
@@ -277,6 +277,8 @@ The list of LogsQL filters:
|
||||
- [Length range filter](#length-range-filter) - matches logs with [field values](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model) of the given length range
|
||||
- [Value type filter](#value_type-filter) - matches logs with [fields](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model) stored under the given value type
|
||||
- [Fields' equality filter](#eq_field-filter) - matches logs, which contain identical values in the given [fields](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model)
|
||||
- [`Less than` filter](#lt_field-filter) - matches logs where the given field value is smaller than the other field value
|
||||
- [`Less than or equal` filter](#le_field-filter) - matches logs where the given field value doesn't exceed the other field value
|
||||
- [Logical filter](#logical-filter) - allows combining other filters
|
||||
|
||||
|
||||
@@ -749,6 +751,8 @@ See also:
|
||||
|
||||
- [String range filter](#string-range-filter)
|
||||
- [Range filter](#range-filter)
|
||||
- [`le_field` filter](#le_field-filter)
|
||||
- [`lt_field` filter](#lt_field-filter)
|
||||
|
||||
### Empty value filter
|
||||
|
||||
@@ -1365,6 +1369,48 @@ Quick tip: use `NOT user_id:eq_field(customer_id)` for finding logs where `user_
|
||||
See also:
|
||||
|
||||
- [`exact` filter](#exact-filter)
|
||||
- [`le_field` filter](#le_field-filter)
|
||||
- [`lt_field` filter](#lt_field-filter)
|
||||
|
||||
|
||||
### le_field filter
|
||||
|
||||
Sometimes it is needed to find logs where one [field](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model) value doesn't exceed the other field value.
|
||||
This can be done with `field1:le_field(field2)` filter.
|
||||
|
||||
For example, the following query matches logs where `duration` [field](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model) doesn't exceed the `max_duration` field:
|
||||
|
||||
```logsql
|
||||
duration:le_field(max_duration)
|
||||
```
|
||||
|
||||
Quick tip: use `NOT duration:le_field(max_duration)` for finding logs where `duration` exceeds the `max_duration`.
|
||||
|
||||
See also:
|
||||
|
||||
- [range comparison filter](#range-comparison-filter)
|
||||
- [`lt_field` filter](#lt_field-filter)
|
||||
- [`eq_field` filter](#eq_field-filter)
|
||||
|
||||
|
||||
### lt_field filter
|
||||
|
||||
Sometimes it is needed to find logs where one [field](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model) value is smaller than the other field value.
|
||||
This can be done with `field1:lt_field(field2)` filter.
|
||||
|
||||
For example, the following query matches logs where `duration` [field](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model) is smaller than the `max_duration` field:
|
||||
|
||||
```logsql
|
||||
duration:lt_field(max_duration)
|
||||
```
|
||||
|
||||
Quick tip: use `NOT duration:lt_field(max_duration)` for finding logs where `duration` is bigger or equal to the `max_duration`.
|
||||
|
||||
See also:
|
||||
|
||||
- [range comparison filter](#range-comparison-filter)
|
||||
- [`le_field` filter](#le_field-filter)
|
||||
- [`eq_field` filter](#eq_field-filter)
|
||||
|
||||
|
||||
### Logical filter
|
||||
@@ -2391,6 +2437,13 @@ only and stores the result in `baz` field:
|
||||
_time:5m | pack_json fields (foo, bar) as baz
|
||||
```
|
||||
|
||||
It is possible to pass field prefixes into `fields (...)` in order to pack only the fields, which start with the given prefixes.
|
||||
For example, the following query builds JSON with all the fields, which start with either `foo.` or `bar.`:
|
||||
|
||||
```logsql
|
||||
_time:5m | pack_json fields (foo.*, bar.*) as baz
|
||||
```
|
||||
|
||||
The `pack_json` doesn't modify or delete other labels. If you do not need them, then add [`| fields ...`](#fields-pipe) after the `pack_json` pipe. For example, the following query
|
||||
leaves only the `foo` label with the original log fields packed into JSON:
|
||||
|
||||
@@ -2430,6 +2483,13 @@ For example, the following query builds [logfmt](https://brandur.org/logfmt) mes
|
||||
_time:5m | pack_logfmt fields (foo, bar) as baz
|
||||
```
|
||||
|
||||
It is possible to pass field prefixes into `fields (...)` in order to pack only the fields, which start with the given prefixes.
|
||||
For example, the following query builds `logfmt` message with all the fields, which start with either `foo.` or `bar.`:
|
||||
|
||||
```logsql
|
||||
_time:5m | pack_logfmt fields (foo.*, bar.*) as baz
|
||||
```
|
||||
|
||||
The `pack_logfmt` doesn't modify or delete other labels. If you do not need them, then add [`| fields ...`](#fields-pipe) after the `pack_logfmt` pipe. For example, the following query
|
||||
leaves only the `foo` label with the original log fields packed into [logfmt](https://brandur.org/logfmt):
|
||||
|
||||
|
||||
@@ -33,8 +33,8 @@ Just download archive for the needed Operating system and architecture, unpack i
|
||||
For example, the following commands download VictoriaLogs archive for Linux/amd64, unpack and run it:
|
||||
|
||||
```sh
|
||||
curl -L -O https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v1.13.0-victorialogs/victoria-logs-linux-amd64-v1.13.0-victorialogs.tar.gz
|
||||
tar xzf victoria-logs-linux-amd64-v1.13.0-victorialogs.tar.gz
|
||||
curl -L -O https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v1.15.0-victorialogs/victoria-logs-linux-amd64-v1.15.0-victorialogs.tar.gz
|
||||
tar xzf victoria-logs-linux-amd64-v1.15.0-victorialogs.tar.gz
|
||||
./victoria-logs-prod
|
||||
```
|
||||
|
||||
@@ -58,7 +58,7 @@ Here is the command to run VictoriaLogs in a Docker container:
|
||||
|
||||
```sh
|
||||
docker run --rm -it -p 9428:9428 -v ./victoria-logs-data:/victoria-logs-data \
|
||||
docker.io/victoriametrics/victoria-logs:v1.13.0-victorialogs
|
||||
docker.io/victoriametrics/victoria-logs:v1.15.0-victorialogs
|
||||
```
|
||||
|
||||
See also:
|
||||
|
||||
@@ -18,13 +18,13 @@ It has the following features:
|
||||
- It supports live tailing - see [these docs](#live-tailing).
|
||||
|
||||
This tool can be obtained from the linked release pages at the [changelog](https://docs.victoriametrics.com/victorialogs/changelog/)
|
||||
or from [docker images](https://hub.docker.com/r/victoriametrics/vlogscli/tags).
|
||||
or from docker images at [Docker Hub](https://hub.docker.com/r/victoriametrics/vlogscli/tags) and [Quay](https://quay.io/repository/victoriametrics/vlogscli?tab=tags).
|
||||
|
||||
### Running `vlogscli` from release binary
|
||||
|
||||
```sh
|
||||
curl -L -O https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v1.13.0-victorialogs/vlogscli-linux-amd64-v1.13.0-victorialogs.tar.gz
|
||||
tar xzf vlogscli-linux-amd64-v1.13.0-victorialogs.tar.gz
|
||||
curl -L -O https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v1.15.0-victorialogs/vlogscli-linux-amd64-v1.15.0-victorialogs.tar.gz
|
||||
tar xzf vlogscli-linux-amd64-v1.15.0-victorialogs.tar.gz
|
||||
./vlogscli-prod
|
||||
```
|
||||
|
||||
|
||||
@@ -21,14 +21,16 @@ _Note: This page provides only integration instructions for vmalert and Victoria
|
||||
|
||||
Run vmalert with the following settings:
|
||||
```sh
|
||||
./bin/vmalert -rule=alert.rules \ # Path to the files or http url with alerting and/or recording rules in YAML format.
|
||||
-datasource.url=http://localhost:9428 \ # VictoriaLogs address.
|
||||
-notifier.url=http://localhost:9093 \ # AlertManager URL (required if alerting rules are used)
|
||||
-remoteWrite.url=http://localhost:8428 \ # Remote write compatible storage to persist rules and alerts state info (required for recording rules)
|
||||
-remoteRead.url=http://localhost:8428 \ # Prometheus HTTP API compatible datasource to restore alerts state from
|
||||
./bin/vmalert -rule=alert.rules \ # Path to the files or http url with alerting and/or recording rules in YAML format.
|
||||
-datasource.url=http://victorialogs:9428 \ # VictoriaLogs address.
|
||||
-notifier.url=http://alertmanager:9093 \ # AlertManager URL (required if alerting rules are used)
|
||||
-remoteWrite.url=http://victoriametrics:8428 \ # Remote write compatible storage to persist recording rules and alerts state info
|
||||
-remoteRead.url=http://victoriametrics:8428 \ # Prometheus HTTP API compatible datasource to restore alerts state from
|
||||
```
|
||||
|
||||
> Note: By default, vmalert assumes configured rules have `prometheus` type and will validate them accordingly. For rules in [LogsQL](https://docs.victoriametrics.com/victorialogs/logsql/) specify `type: vlogs` on [Group level](#groups). Or set `-rule.defaultRuleType=vlogs` cmd-line flag to automatically apply `type: vlogs` to all groups.
|
||||
> Note: By default, vmalert assumes all configured rules have `prometheus` type and will validate them accordingly.
|
||||
> For rules in [LogsQL](https://docs.victoriametrics.com/victorialogs/logsql/) specify `type: vlogs` on [Group level](#groups).
|
||||
> Or set `-rule.defaultRuleType=vlogs` cmd-line flag to apply `type: vlogs` to all configured groups.
|
||||
|
||||
Each `-rule` file may contain arbitrary number of [groups](https://docs.victoriametrics.com/vmalert/#groups).
|
||||
See examples in [Groups](#groups) section. See the full list of configuration flags and their descriptions in [configuration](#configuration) section.
|
||||
@@ -37,9 +39,11 @@ With configuration example above, vmalert will perform the following interaction
|
||||

|
||||
|
||||
1. Rules listed in `-rule` file are executed against VictoriaLogs service configured via `-datasource.url`;
|
||||
2. Triggered alerting notifications are sent to [Alertmanager](https://github.com/prometheus/alertmanager) service configured via `-notifier.url`;
|
||||
3. Results of recording rules expressions and alerts state are persisted to Prometheus-compatible remote-write endpoint (i.e. VictoriaMetrics) configured via `-remoteWrite.url`;
|
||||
4. On vmalert restarts, alerts state [can be restored](https://docs.victoriametrics.com/vmalert/#alerts-state-on-restarts) by querying Prometheus-compatible HTTP API endpoint (i.e. VictoriaMetrics) configured via `-remoteRead.url`.
|
||||
1. Triggered alerting notifications are sent to [Alertmanager](https://github.com/prometheus/alertmanager) service configured via `-notifier.url`;
|
||||
1. Results of recording rules expressions and alerts state are persisted to Prometheus-compatible remote-write endpoint
|
||||
(i.e. VictoriaMetrics) configured via `-remoteWrite.url`;
|
||||
1. On vmalert restarts, alerts state [can be restored](https://docs.victoriametrics.com/vmalert/#alerts-state-on-restarts)
|
||||
by querying Prometheus-compatible HTTP API endpoint (i.e. VictoriaMetrics) configured via `-remoteRead.url`.
|
||||
|
||||
## Configuration
|
||||
|
||||
@@ -82,7 +86,7 @@ The following are key flags related to integration with VictoriaLogs:
|
||||
Since there is no intentional search delay in VictoriaLogs, `-rule.evalDelay` can be reduced to a few seconds to accommodate network and ingestion time.
|
||||
```
|
||||
|
||||
For more configuration options, such as `notifiers`, visit https://docs.victoriametrics.com/vmalert/#configuration.
|
||||
See full list of configuration options [here](https://docs.victoriametrics.com/vmalert/#configuration).
|
||||
|
||||
### Groups
|
||||
|
||||
@@ -148,7 +152,7 @@ groups:
|
||||
description: "Connection from address {{$labels.ip}} has {{$value}}% failed requests in last 5 minutes"
|
||||
```
|
||||
|
||||
User can also specify a customized time filter if needed. For example, rule below will be evaluated every 5 minutes,
|
||||
User can specify a customized time filter if needed. For example, rule below will be evaluated every 5 minutes,
|
||||
but will calculate result over the logs from the last 10 minutes.
|
||||
```yaml
|
||||
groups:
|
||||
@@ -162,7 +166,7 @@ groups:
|
||||
description: "Connection from address {{$labels.ip}} has {{$value}}% failed requests in last 10 minutes"
|
||||
```
|
||||
|
||||
Please note, vmalert doesn't support [backfilling](#rules-backfilling) for rules with a customized time filter now. (Might be added in future)
|
||||
_Please note, vmalert doesn't support [backfilling](#rules-backfilling) for rules with a customized time filter now. (Might be added in future)._
|
||||
|
||||
## Rules backfilling
|
||||
|
||||
@@ -180,8 +184,8 @@ See more details about backfilling [here](https://docs.victoriametrics.com/vmale
|
||||
|
||||
## Performance tip
|
||||
|
||||
LogsQL allows users to obtain multiple stats from a single expression.
|
||||
For instance, the following query calculates 50th, 90th and 99th percentiles for the `request_duration_seconds` field over logs for the last 5 minutes:
|
||||
LogsQL allows users to obtain multiple stats from a single expression. For instance, the following query calculates
|
||||
50th, 90th and 99th percentiles for the `request_duration_seconds` field over logs for the last 5 minutes:
|
||||
|
||||
```logsql
|
||||
_time:5m | stats
|
||||
@@ -198,8 +202,9 @@ groups:
|
||||
interval: 5m
|
||||
rules:
|
||||
- record: requestDurationQuantile
|
||||
expr: '_time:5m | stats by (service) quantile(0.5, request_duration_seconds) p50, quantile(0.9, request_duration_seconds) p90, quantile(0.99, request_duration_seconds) p99'
|
||||
expr: '* | stats by (service) quantile(0.5, request_duration_seconds) p50, quantile(0.9, request_duration_seconds) p90, quantile(0.99, request_duration_seconds) p99'
|
||||
```
|
||||
|
||||
This creates three metrics for each service:
|
||||
```
|
||||
requestDurationQuantile{stats_result="p50", service="service-1"}
|
||||
@@ -217,6 +222,7 @@ For additional tips on writing LogsQL, refer to this [doc](https://docs.victoria
|
||||
## Frequently Asked Questions
|
||||
|
||||
### How to use [multitenancy](https://docs.victoriametrics.com/victorialogs/#multitenancy) in rules?
|
||||
|
||||
vmalert doesn't support multi-tenancy for VictoriaLogs in the same way as it [supports it for VictoriaMetrics in ENT version](https://docs.victoriametrics.com/vmalert/#multitenancy).
|
||||
However, it is possible to specify the queried tenant from VictoriaLogs datasource via `headers` param in [Group config](https://docs.victoriametrics.com/vmalert/#groups).
|
||||
For example, the following config will execute all the rules within the group against tenant with `AccountID=1` and `ProjectID=2`:
|
||||
@@ -288,10 +294,12 @@ To persist different rule results to different tenants in VictoriaMetrics, there
|
||||
```
|
||||
|
||||
### How to use one vmalert for VictoriaLogs and VictoriaMetrics rules in the same time?
|
||||
|
||||
We recommend running separate instances of vmalert for VictoriaMetrics and VictoriaLogs.
|
||||
However, vmalert allows having many groups with different rule types (`vlogs`, `prometheus`, `graphite`).
|
||||
But only one `-datasource.url` cmd-line flag can be specified, so it can't be configured with more than 1 datasource.
|
||||
However, VictoriaMetrics and VictoriaLogs datasources have different query path prefixes, and it is possible to use [vmauth](https://docs.victoriametrics.com/vmauth/) to route requests of different types between datasources.
|
||||
VictoriaMetrics and VictoriaLogs datasources have different query path prefixes, so it is possible to use
|
||||
[vmauth](https://docs.victoriametrics.com/vmauth/) to route requests of different types between datasources.
|
||||
See example of vmauth config for such routing below:
|
||||
```yaml
|
||||
unauthorized_user:
|
||||
@@ -303,4 +311,5 @@ See example of vmauth config for such routing below:
|
||||
- "/select/logsql/.*"
|
||||
url_prefix: "http://victorialogs:9428"
|
||||
```
|
||||
Now, vmalert needs to be configured with `--datasource.url=http://vmauth:8427/` to send queries to vmauth, and vmauth will route them to the specified destinations as in configuration example above.
|
||||
Now, vmalert can be configured with `--datasource.url=http://vmauth:8427/` to send queries to vmauth,
|
||||
and vmauth will route them to the specified destinations as in configuration example above.
|
||||
|
||||
@@ -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.111.0)
|
||||
- [vmalert](https://docs.victoriametrics.com/vmalert/) (v1.111.0)
|
||||
- [vmagent](https://docs.victoriametrics.com/vmagent/) (v1.111.0)
|
||||
- [VictoriaMetrics Single-Node](https://docs.victoriametrics.com/single-server-victoriametrics) (v1.112.0)
|
||||
- [vmalert](https://docs.victoriametrics.com/vmalert/) (v1.112.0)
|
||||
- [vmagent](https://docs.victoriametrics.com/vmagent/) (v1.112.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.111.0
|
||||
image: victoriametrics/vmagent:v1.112.0
|
||||
depends_on:
|
||||
- "victoriametrics"
|
||||
ports:
|
||||
@@ -332,7 +332,7 @@ services:
|
||||
|
||||
victoriametrics:
|
||||
container_name: victoriametrics
|
||||
image: victoriametrics/victoria-metrics:v1.111.0
|
||||
image: victoriametrics/victoria-metrics:v1.112.0
|
||||
ports:
|
||||
- 8428:8428
|
||||
volumes:
|
||||
@@ -365,7 +365,7 @@ services:
|
||||
|
||||
vmalert:
|
||||
container_name: vmalert
|
||||
image: victoriametrics/vmalert:v1.111.0
|
||||
image: victoriametrics/vmalert:v1.112.0
|
||||
depends_on:
|
||||
- "victoriametrics"
|
||||
ports:
|
||||
|
||||
@@ -18,10 +18,54 @@ See also [LTS releases](https://docs.victoriametrics.com/lts-releases/).
|
||||
|
||||
## tip
|
||||
|
||||
**Update note 1: [vmsingle](https://docs.victoriametrics.com/single-server-victoriametrics/) and [vmagent](https://docs.victoriametrics.com/vmagent/) include a fix which enforces IPv6 addresses escaping for containers discovered with [Kubernetes service-discovery](https://docs.victoriametrics.com/sd_configs/#kubernetes_sd_configs) and `role: pod` which do not have exposed ports defined. This means that `address` for these containers will always be wrapped in square brackets, this might affect some relabeling rules which were relying on previous behaviour.**
|
||||
**Update note 2: [vmalert](https://docs.victoriametrics.com/vmalert/) disallow using [time buckets stats pipe](https://docs.victoriametrics.com/victorialogs/logsql/#stats-by-time-buckets) in alerting or recording rules with VictoriaLogs as datasource. Time buckets used with [stats query API](https://docs.victoriametrics.com/victorialogs/querying/#querying-log-stats) may produce unexpected results for user and result into cardinality issues.**
|
||||
|
||||
* FEATURE: upgrade Go builder from Go1.23.6 to Go1.24. See [Go1.24 release notes](https://tip.golang.org/doc/go1.24).
|
||||
* FEATURE: [alerts](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/deployment/docker/rules/alerts-vmalert.yml): add alerting rule `TooHighQueryLoad` to notify user when VictoriaMetrics or vmselect weren't able to serve requests in timely manner during last 15min.
|
||||
* FEATURE: [dashboards/single](https://grafana.com/grafana/dashboards/10229) and [dashboards/cluster](https://grafana.com/grafana/dashboards/11176): add panel `Deduplication rate` that shows how many samples are [deduplicated](https://docs.victoriametrics.com/#deduplication) during merges or read queries by VictoriaMetrics components.
|
||||
* FEATURE: [dashboards/single](https://grafana.com/grafana/dashboards/10229) and [dashboards/cluster](https://grafana.com/grafana/dashboards/11176): add panel `Number of snapshots` that shows the max number of [snapshots](https://docs.victoriametrics.com/#how-to-work-with-snapshots) across vmstorage nodes. This panel should help in disk usage [troubleshooting](https://docs.victoriametrics.com/#snapshot-troubleshooting).
|
||||
* FEATURE: [dashboards/single](https://grafana.com/grafana/dashboards/10229) and [dashboards/cluster](https://grafana.com/grafana/dashboards/11176): account for samples dropped according to [relabeling config](https://docs.victoriametrics.com/#relabeling) in `Samples dropped for last 1h` panel.
|
||||
* FEATURE: [dashboards/single](https://grafana.com/grafana/dashboards/10229) and [dashboards/cluster](https://grafana.com/grafana/dashboards/11176): show number of parts in the last partition on `LSM parts max by type` panel. Before, the resulting graph could be skewed by the max number of parts across all partitions. Displaying parts for the latest partition is the correct way to show if storage is currently impacted by merge delays.
|
||||
* FEATURE: [dashboards/cluster](https://grafana.com/grafana/dashboards/11176): add panel `Partial query results` that shows the number of served [partial responses](https://docs.victoriametrics.com/cluster-victoriametrics/#cluster-availability) by vmselects.
|
||||
* FEATURE: provide alternative registry for all VictoriaMetrics components at [Quay.io](https://quay.io/organization/victoriametrics).
|
||||
* FEATURE: [vmalert-tool](https://docs.victoriametrics.com/vmalert-tool/): add command-line flag `-httpListenPort` to specify the port used during testing. If not provided, a random unoccupied port will be assigned. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8393).
|
||||
* FEATURE: [vmalert-tool](https://docs.victoriametrics.com/vmalert-tool/): make the temporary storage path for unittest unique, allowing user to run multiple vmalert-tool processes on a single host simultaneously. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8393).
|
||||
* FEATURE: [vmalert](https://docs.victoriametrics.com/vmalert/): disallow using [time buckets stats pipe](https://docs.victoriametrics.com/victorialogs/logsql/#stats-by-time-buckets) in VictoriaLogs rule expressions. Such construction produces meaningless results for [stats query API](https://docs.victoriametrics.com/victorialogs/querying/#querying-log-stats) and may lead to cardinality issues.
|
||||
|
||||
* BUGFIX: [Single-node VictoriaMetrics](https://docs.victoriametrics.com/) and [vmstorage](https://docs.victoriametrics.com/victoriametrics/): fix the incorrect caching of extMetricsIDs when a query timeout error occurs. This can lead to incorrect query results. Thanks to @changshun-shi for [the bug report issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8345).
|
||||
* BUGFIX: [vmctl](https://docs.victoriametrics.com/vmctl/): respect time filter when exploring time series for [influxdb mode](https://docs.victoriametrics.com/vmctl/#migrating-data-from-influxdb-1x). See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8259) for details.
|
||||
* BUGFIX: [vmsingle](https://docs.victoriametrics.com/single-server-victoriametrics/): properly apply global relabeling configuration, defined with `-relabelConfig` flag, for metrics scrapped with `-promscrape.config`. Bug was introduces in [v1.108.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.108.0). See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8389).
|
||||
* BUGFIX: [vmbackupmanager](https://docs.victoriametrics.com/vmbackupmanager/): properly propagate an error message when applying retention policy fails. Previously, an actual error messages was discarded.
|
||||
* BUGFIX: [vmgateway](https://docs.victoriametrics.com/vmgateway): fix data query in [rate limiter](https://docs.victoriametrics.com/vmgateway/#rate-limiter). The bug was introduced in [this commit](https://github.com/VictoriaMetrics/VictoriaMetrics/commit/68bad22fd26d1436ad0236b1f3ced8604c5d851c) starting from [v1.106.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.106.0).
|
||||
* BUGFIX: [vmsingle](https://docs.victoriametrics.com/single-server-victoriametrics/) and [vmagent](https://docs.victoriametrics.com/vmagent/): properly escape IPv6 address in [Kubernetes service-discovery](https://docs.victoriametrics.com/sd_configs/#kubernetes_sd_configs) with `role: pod` for containers without exposed ports. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8374).
|
||||
* BUGFIX: [vmalert-tool](https://docs.victoriametrics.com/vmalert-tool/): clean up the temporary storage path when process is terminated by SIGTERM or SIGINT. Previously, unclean shut down might affect the next run.
|
||||
* BUGFIX: [vmui](https://docs.victoriametrics.com/#vmui): fix an infinite loader on the [Downsampling filters debug page](https://docs.victoriametrics.com/#vmui) when provided configuration matches no series. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8339).
|
||||
|
||||
## [v1.102.15](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.102.15)
|
||||
|
||||
Released at 2025-02-28
|
||||
|
||||
**v1.102.x is a line of [LTS releases](https://docs.victoriametrics.com/lts-releases/). It contains important up-to-date bugfixes for [VictoriaMetrics enterprise](https://docs.victoriametrics.com/enterprise.html).
|
||||
All these fixes are also included in [the latest community release](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/latest).
|
||||
The v1.102.x line will be supported for at least 12 months since [v1.102.0](https://docs.victoriametrics.com/changelog/#v11020) release**
|
||||
|
||||
**Update note 1: [vmsingle](https://docs.victoriametrics.com/single-server-victoriametrics/) and [vmagent](https://docs.victoriametrics.com/vmagent/) include a fix which enforces IPv6 addresses escaping for containers discovered with [Kubernetes service-discovery](https://docs.victoriametrics.com/sd_configs/#kubernetes_sd_configs) and `role: pod` which do not have exposed ports defined. This means that `address` for these containers will always be wrapped in square brackets, this might affect some relabeling rules which were relying on previous behaviour.**
|
||||
|
||||
* SECURITY: upgrade golang.org/x/net from v0.31.0 to v0.33.0 to address [GHSA-w32m-9786-jp63](https://github.com/advisories/GHSA-w32m-9786-jp63).
|
||||
|
||||
* BUGFIX: [Single-node VictoriaMetrics](https://docs.victoriametrics.com/) and [vmstorage](https://docs.victoriametrics.com/victoriametrics/): fix the incorrect caching of extMetricsIDs when a query timeout error occurs. This can lead to incorrect query results. Thanks to @changshun-shi for [the bug report issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8345).
|
||||
* BUGFIX: [vmctl](https://docs.victoriametrics.com/vmctl/): respect time filter when exploring time series for [influxdb mode](https://docs.victoriametrics.com/vmctl/#migrating-data-from-influxdb-1x). See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8259) for details.
|
||||
* BUGFIX: [vmsingle](https://docs.victoriametrics.com/single-server-victoriametrics/) and [vmagent](https://docs.victoriametrics.com/vmagent/): properly escape IPv6 address in [Kubernetes service-discovery](https://docs.victoriametrics.com/sd_configs/#kubernetes_sd_configs) with `role: pod` for containers without exposed ports. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8374).
|
||||
|
||||
## [v1.112.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.112.0)
|
||||
|
||||
Released at 2025-02-21
|
||||
|
||||
* SECURITY: upgrade Go builder from Go1.23.5 to Go1.23.6. See the list of issues addressed in [Go1.23.6](https://github.com/golang/go/issues?q=milestone%3AGo1.23.6+label%3ACherryPickApproved).
|
||||
* SECURITY: upgrade base docker image (Alpine) from 3.21.2 to 3.21.3. See [Alpine 3.21.3 release notes](https://alpinelinux.org/posts/Alpine-3.18.12-3.19.7-3.20.6-3.21.3-released.html).
|
||||
|
||||
* FEATURE: [Single-node VictoriaMetrics](https://docs.victoriametrics.com/) and [vmstorage](https://docs.victoriametrics.com/victoriametrics/): allow disabling per-day indexes for workloads with [low or zero churn rates](https://docs.victoriametrics.com/#index-tuning-for-low-churn-rate) via `-disablePerDayIndex` cmd-line flag. This option works the best for IoT cases and significantly loads resource and disk usage. See [this PR](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/6976).
|
||||
* FEATURE: [Single-node VictoriaMetrics](https://docs.victoriametrics.com/) and [vmstorage](https://docs.victoriametrics.com/victoriametrics/): allow [disabling per-day indexes](https://docs.victoriametrics.com/#index-tuning-for-low-churn-rate) for workloads with low or zero churn rates via `-disablePerDayIndex` cmd-line flag. This option works the best for IoT cases and significantly loads resource and disk usage. See [this PR](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/6976).
|
||||
* FEATURE: [vmalert](https://docs.victoriametrics.com/vmalert/): add command-line flag `-notifier.sendTimeout(default 10s)` to allow configuring request timeout when sending alerts to the corresponding `-notifier.url`. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8287) for details. Thanks to @gjkim42 for the [pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/8297).
|
||||
* FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent/) and [vmsingle](https://docs.victoriametrics.com/single-server-victoriametrics/): add support of [aggregation windows](https://docs.victoriametrics.com/stream-aggregation/#aggregation-windows) to improve accuracy of stream aggregation. This feature is especially important for aggregation of [histrograms](https://docs.victoriametrics.com/keyconcepts/#histogram), but enabling this feature requires additional memory. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4580).
|
||||
* FEATURE: [vmsingle](https://docs.victoriametrics.com/single-server-victoriametrics/), `vminsert` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/cluster-victoriametrics/) and [vmagent](https://docs.victoriametrics.com/vmagent/): add `-influx.forceStreamMode` cmd-line flag to force stream processing for data ingested via [InfluxDB protocol](https://docs.victoriametrics.com/#how-to-send-data-from-influxdb-compatible-agents-such-as-telegraf). See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8269).
|
||||
@@ -39,8 +83,39 @@ See also [LTS releases](https://docs.victoriametrics.com/lts-releases/).
|
||||
* BUGFIX: [vmalert](https://docs.victoriametrics.com/vmalert/): fix polluted alert messages when multiple Alertmanager instances are configured with `alert_relabel_configs`. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8040), and thanks to @evkuzin for [the pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/8258).
|
||||
* BUGFIX: [vmalert](https://docs.victoriametrics.com/vmalert/): fix the auto-generated metrics for alerts and groups. Previously, metrics might be missing after reload. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8229) for the details.
|
||||
* BUGFIX: `vminsert` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/cluster-victoriametrics/) and `vmstorage` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/cluster-victoriametrics/): properly return parsing errors during data ingestion in [multi-level cluster setup](https://docs.victoriametrics.com/cluster-victoriametrics/#multi-level-cluster-setup). Before, some of the errors could have been silently ignored.
|
||||
* BUGFIX: [vmsingle](https://docs.victoriametrics.com/single-server-victoriametrics/), `vmselect` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/cluster-victoriametrics/): fix negative rate result when the lookbehind window is longer than `-search.maxLookback` or `-search.maxStalenessInterval` and data contains gap. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8342).
|
||||
* BUGFIX: [Single-node VictoriaMetrics](https://docs.victoriametrics.com/) and [vmselect](https://docs.victoriametrics.com/cluster-victoriametrics/): properly enforce presence of default `-retentionPeriod` configuration for [retention filters](https://docs.victoriametrics.com/#retention-filters) debug interface. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8343).
|
||||
|
||||
## [v1.110.2](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.110.2)
|
||||
|
||||
Released at 2025-02-21
|
||||
|
||||
**v1.110.x is a line of [LTS releases](https://docs.victoriametrics.com/lts-releases/). It contains important up-to-date bugfixes for [VictoriaMetrics enterprise](https://docs.victoriametrics.com/enterprise.html).
|
||||
All these fixes are also included in [the latest community release](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/latest).
|
||||
The v1.110.x line will be supported for at least 12 months since [v1.110.0](https://docs.victoriametrics.com/changelog/#v11100) release**
|
||||
|
||||
* SECURITY: upgrade Go builder from Go1.23.5 to Go1.23.6. See the list of issues addressed in [Go1.23.6](https://github.com/golang/go/issues?q=milestone%3AGo1.23.6+label%3ACherryPickApproved).
|
||||
* SECURITY: upgrade base docker image (Alpine) from 3.21.2 to 3.21.3. See [Alpine 3.21.3 release notes](https://alpinelinux.org/posts/Alpine-3.18.12-3.19.7-3.20.6-3.21.3-released.html).
|
||||
|
||||
* BUGFIX: [vmalert](https://docs.victoriametrics.com/vmalert/): fix polluted alert messages when multiple Alertmanager instances are configured with `alert_relabel_configs`. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8040), and thanks to @evkuzin for [the pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/8258).
|
||||
* BUGFIX: [vmalert](https://docs.victoriametrics.com/vmalert/): fix the auto-generated metrics for alerts and groups. Previously, metrics might be missing after reload. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8229) for the details.
|
||||
* BUGFIX: `vminsert` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/cluster-victoriametrics/) and `vmstorage` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/cluster-victoriametrics/): properly return parsing errors during data ingestion in [multi-level cluster setup](https://docs.victoriametrics.com/cluster-victoriametrics/#multi-level-cluster-setup). Before, some of the errors could have been silently ignored.
|
||||
* BUGFIX: [Single-node VictoriaMetrics](https://docs.victoriametrics.com/) and [vmselect](https://docs.victoriametrics.com/cluster-victoriametrics/): properly enforce presence of default `-retentionPeriod` configuration for [retention filters](https://docs.victoriametrics.com/#retention-filters) debug interface. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8343).
|
||||
|
||||
## [v1.102.14](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.102.14)
|
||||
|
||||
Released at 2025-02-21
|
||||
|
||||
**v1.102.x is a line of [LTS releases](https://docs.victoriametrics.com/lts-releases/). It contains important up-to-date bugfixes for [VictoriaMetrics enterprise](https://docs.victoriametrics.com/enterprise.html).
|
||||
All these fixes are also included in [the latest community release](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/latest).
|
||||
The v1.102.x line will be supported for at least 12 months since [v1.102.0](https://docs.victoriametrics.com/changelog/#v11020) release**
|
||||
|
||||
* SECURITY: upgrade Go builder from Go1.23.5 to Go1.23.6. See the list of issues addressed in [Go1.23.6](https://github.com/golang/go/issues?q=milestone%3AGo1.23.6+label%3ACherryPickApproved).
|
||||
* SECURITY: upgrade base docker image (Alpine) from 3.21.2 to 3.21.3. See [Alpine 3.21.3 release notes](https://alpinelinux.org/posts/Alpine-3.18.12-3.19.7-3.20.6-3.21.3-released.html).
|
||||
|
||||
* BUGFIX: `vminsert` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/cluster-victoriametrics/) and `vmstorage` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/cluster-victoriametrics/): properly return parsing error for data ingestion.
|
||||
* BUGFIX: [vmalert](https://docs.victoriametrics.com/vmalert/): fix the auto-generated metrics for alerts and groups. Previously, metrics might be missing after reload. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8229) for the details.
|
||||
|
||||
## [v1.111.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.111.0)
|
||||
|
||||
Released at 2025-02-10
|
||||
|
||||
@@ -16,7 +16,7 @@ VictoriaMetrics community components are open source and are free to use - see [
|
||||
and [the license](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/LICENSE).
|
||||
|
||||
VictoriaMetrics Enterprise components are available in binary form at [releases page](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/latest)
|
||||
and at [docker hub](https://hub.docker.com/u/victoriametrics). Enterprise binaries and packages have `enterprise` suffix in their names.
|
||||
and at [Docker Hub](https://hub.docker.com/u/victoriametrics) and [Quay](https://quay.io/organization/victoriametrics). Enterprise binaries and packages have `enterprise` suffix in their names.
|
||||
|
||||
## Valid cases for VictoriaMetrics Enterprise
|
||||
|
||||
@@ -82,7 +82,7 @@ VictoriaMetrics Enterprise components are available in the following forms:
|
||||
It is allowed to run VictoriaMetrics Enterprise components in [cases listed here](#valid-cases-for-victoriametrics-enterprise).
|
||||
|
||||
Binary releases of VictoriaMetrics Enterprise are available [at the releases page](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/latest).
|
||||
Enterprise binaries and packages have `enterprise` suffix in their names. For example, `victoria-metrics-linux-amd64-v1.111.0-enterprise.tar.gz`.
|
||||
Enterprise binaries and packages have `enterprise` suffix in their names. For example, `victoria-metrics-linux-amd64-v1.112.0-enterprise.tar.gz`.
|
||||
|
||||
In order to run binary release of VictoriaMetrics Enterprise component, please download the `*-enterprise.tar.gz` archive for your OS and architecture
|
||||
from the [releases page](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/latest) and unpack it. Then run the unpacked binary.
|
||||
@@ -100,8 +100,8 @@ For example, the following command runs VictoriaMetrics Enterprise binary with t
|
||||
obtained at [this page](https://victoriametrics.com/products/enterprise/trial/):
|
||||
|
||||
```sh
|
||||
wget https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v1.111.0/victoria-metrics-linux-amd64-v1.111.0-enterprise.tar.gz
|
||||
tar -xzf victoria-metrics-linux-amd64-v1.111.0-enterprise.tar.gz
|
||||
wget https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v1.112.0/victoria-metrics-linux-amd64-v1.112.0-enterprise.tar.gz
|
||||
tar -xzf victoria-metrics-linux-amd64-v1.112.0-enterprise.tar.gz
|
||||
./victoria-metrics-prod -license=BASE64_ENCODED_LICENSE_KEY
|
||||
```
|
||||
|
||||
@@ -115,8 +115,8 @@ Alternatively, VictoriaMetrics Enterprise license can be stored in the file and
|
||||
|
||||
It is allowed to run VictoriaMetrics Enterprise components in [cases listed here](#valid-cases-for-victoriametrics-enterprise).
|
||||
|
||||
Docker images for VictoriaMetrics Enterprise are available [at VictoriaMetrics DockerHub](https://hub.docker.com/u/victoriametrics).
|
||||
Enterprise docker images have `enterprise` suffix in their names. For example, `victoriametrics/victoria-metrics:v1.111.0-enterprise`.
|
||||
Docker images for VictoriaMetrics Enterprise are available at VictoriaMetrics [Docker Hub](https://hub.docker.com/u/victoriametrics) and [Quay](https://quay.io/organization/victoriametrics).
|
||||
Enterprise docker images have `enterprise` suffix in their names. For example, `victoriametrics/victoria-metrics:v1.112.0-enterprise`.
|
||||
|
||||
In order to run Docker image of VictoriaMetrics Enterprise component, it is required to provide the license key via command-line
|
||||
flag as described [here](#binary-releases).
|
||||
@@ -126,13 +126,13 @@ Enterprise license key can be obtained at [this page](https://victoriametrics.co
|
||||
For example, the following command runs VictoriaMetrics Enterprise Docker image with the specified license key:
|
||||
|
||||
```sh
|
||||
docker run --name=victoria-metrics victoriametrics/victoria-metrics:v1.111.0-enterprise -license=BASE64_ENCODED_LICENSE_KEY
|
||||
docker run --name=victoria-metrics victoriametrics/victoria-metrics:v1.112.0-enterprise -license=BASE64_ENCODED_LICENSE_KEY
|
||||
```
|
||||
|
||||
Alternatively, the license code can be stored in the file and then referred via `-licenseFile` command-line flag:
|
||||
|
||||
```sh
|
||||
docker run --name=victoria-metrics -v /vm-license:/vm-license victoriametrics/victoria-metrics:v1.111.0-enterprise -licenseFile=/path/to/vm-license
|
||||
docker run --name=victoria-metrics -v /vm-license:/vm-license victoriametrics/victoria-metrics:v1.112.0-enterprise -licenseFile=/path/to/vm-license
|
||||
```
|
||||
|
||||
Example docker-compose configuration:
|
||||
@@ -141,7 +141,7 @@ version: "3.5"
|
||||
services:
|
||||
victoriametrics:
|
||||
container_name: victoriametrics
|
||||
image: victoriametrics/victoria-metrics:v1.111.0
|
||||
image: victoriametrics/victoria-metrics:v1.112.0
|
||||
ports:
|
||||
- 8428:8428
|
||||
volumes:
|
||||
@@ -173,7 +173,7 @@ is used to provide key in plain-text:
|
||||
```yaml
|
||||
server:
|
||||
image:
|
||||
tag: v1.111.0-enterprise
|
||||
tag: v1.112.0-enterprise
|
||||
|
||||
license:
|
||||
key: {BASE64_ENCODED_LICENSE_KEY}
|
||||
@@ -184,7 +184,7 @@ In order to provide key via existing secret, the following values file is used:
|
||||
```yaml
|
||||
server:
|
||||
image:
|
||||
tag: v1.111.0-enterprise
|
||||
tag: v1.112.0-enterprise
|
||||
|
||||
license:
|
||||
secret:
|
||||
@@ -233,7 +233,7 @@ spec:
|
||||
license:
|
||||
key: {BASE64_ENCODED_LICENSE_KEY}
|
||||
image:
|
||||
tag: v1.111.0-enterprise
|
||||
tag: v1.112.0-enterprise
|
||||
```
|
||||
|
||||
In order to provide key via existing secret, the following custom resource is used:
|
||||
@@ -250,7 +250,7 @@ spec:
|
||||
name: vm-license
|
||||
key: license
|
||||
image:
|
||||
tag: v1.111.0-enterprise
|
||||
tag: v1.112.0-enterprise
|
||||
```
|
||||
|
||||
Example secret with license key:
|
||||
|
||||
@@ -236,27 +236,27 @@ services:
|
||||
- grafana_data:/var/lib/grafana/
|
||||
|
||||
vmsingle:
|
||||
image: victoriametrics/victoria-metrics:v1.111.0
|
||||
image: victoriametrics/victoria-metrics:v1.112.0
|
||||
command:
|
||||
- -httpListenAddr=0.0.0.0:8429
|
||||
|
||||
vmstorage:
|
||||
image: victoriametrics/vmstorage:v1.111.0-cluster
|
||||
image: victoriametrics/vmstorage:v1.112.0-cluster
|
||||
|
||||
vminsert:
|
||||
image: victoriametrics/vminsert:v1.111.0-cluster
|
||||
image: victoriametrics/vminsert:v1.112.0-cluster
|
||||
command:
|
||||
- -storageNode=vmstorage:8400
|
||||
- -httpListenAddr=0.0.0.0:8480
|
||||
|
||||
vmselect:
|
||||
image: victoriametrics/vmselect:v1.111.0-cluster
|
||||
image: victoriametrics/vmselect:v1.112.0-cluster
|
||||
command:
|
||||
- -storageNode=vmstorage:8401
|
||||
- -httpListenAddr=0.0.0.0:8481
|
||||
|
||||
vmagent:
|
||||
image: victoriametrics/vmagent:v1.111.0
|
||||
image: victoriametrics/vmagent:v1.112.0
|
||||
volumes:
|
||||
- ./scrape.yaml:/etc/vmagent/config.yaml
|
||||
command:
|
||||
@@ -265,7 +265,7 @@ services:
|
||||
- -remoteWrite.url=http://vmsingle:8429/api/v1/write
|
||||
|
||||
vmgateway-cluster:
|
||||
image: victoriametrics/vmgateway:v1.111.0-enterprise
|
||||
image: victoriametrics/vmgateway:v1.112.0-enterprise
|
||||
ports:
|
||||
- 8431:8431
|
||||
volumes:
|
||||
@@ -281,7 +281,7 @@ services:
|
||||
- -auth.oidcDiscoveryEndpoints=http://keycloak:8080/realms/master/.well-known/openid-configuration
|
||||
|
||||
vmgateway-single:
|
||||
image: victoriametrics/vmgateway:v1.111.0-enterprise
|
||||
image: victoriametrics/vmgateway:v1.112.0-enterprise
|
||||
ports:
|
||||
- 8432:8431
|
||||
volumes:
|
||||
@@ -393,7 +393,7 @@ Once iDP configuration is done, vmagent configuration needs to be updated to use
|
||||
|
||||
```yaml
|
||||
vmagent:
|
||||
image: victoriametrics/vmagent:v1.111.0
|
||||
image: victoriametrics/vmagent:v1.112.0
|
||||
volumes:
|
||||
- ./scrape.yaml:/etc/vmagent/config.yaml
|
||||
- ./vmagent-client-secret:/etc/vmagent/oauth2-client-secret
|
||||
|
||||
@@ -30,8 +30,8 @@ scrape_configs:
|
||||
After you created the `scrape.yaml` file, download and unpack [single-node VictoriaMetrics](https://docs.victoriametrics.com/) to the same directory:
|
||||
|
||||
```
|
||||
wget https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v1.111.0/victoria-metrics-linux-amd64-v1.111.0.tar.gz
|
||||
tar xzf victoria-metrics-linux-amd64-v1.111.0.tar.gz
|
||||
wget https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v1.112.0/victoria-metrics-linux-amd64-v1.112.0.tar.gz
|
||||
tar xzf victoria-metrics-linux-amd64-v1.112.0.tar.gz
|
||||
```
|
||||
|
||||
Then start VictoriaMetrics and instruct it to scrape targets defined in `scrape.yaml` and save scraped metrics
|
||||
@@ -146,8 +146,8 @@ Then start [single-node VictoriaMetrics](https://docs.victoriametrics.com/) acco
|
||||
|
||||
```yaml
|
||||
# Download and unpack single-node VictoriaMetrics
|
||||
wget https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v1.111.0/victoria-metrics-linux-amd64-v1.111.0.tar.gz
|
||||
tar xzf victoria-metrics-linux-amd64-v1.111.0.tar.gz
|
||||
wget https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v1.112.0/victoria-metrics-linux-amd64-v1.112.0.tar.gz
|
||||
tar xzf victoria-metrics-linux-amd64-v1.112.0.tar.gz
|
||||
|
||||
# Run single-node VictoriaMetrics with the given scrape.yaml
|
||||
./victoria-metrics-prod -promscrape.config=scrape.yaml
|
||||
|
||||
@@ -1164,9 +1164,8 @@ It depends on network lag, load, clock synchronization, etc. In most scenarios i
|
||||
deduplication results, which are consistent within margin of error. But for metrics represented as a collection of series,
|
||||
like [histograms](https://docs.victoriametrics.com/keyconcepts/#histogram), such inaccuracy leads to invalid aggregation results.
|
||||
|
||||
For this case, streaming aggregation and deduplication support mode with aggregation windows {{% available_from "#tip" %}}
|
||||
for current and previous state. With this mode, flush doesn't happen immediately but is shifted by a calculated samples
|
||||
lag that improves correctness for delayed data.
|
||||
For this case, streaming aggregation and deduplication support mode with aggregation windows for current and previous state.
|
||||
With this mode, flush doesn't happen immediately but is shifted by a calculated samples lag that improves correctness for delayed data. {{% available_from "v1.112.0" %}}
|
||||
|
||||
Enabling of this mode has increased resource usage: memory usage is expected to double as aggregation will store two states
|
||||
instead of one. However, this significantly improves accuracy of calculations. Aggregation windows can be enabled via
|
||||
|
||||
@@ -16,7 +16,7 @@ This guide explains the different ways in which you can use vmalert in conjuncti
|
||||
|
||||
## Preconditions
|
||||
|
||||
* [vmalert](https://docs.victoriametrics.com/vmalert/) is installed. You can obtain it by building it from [source](https://docs.victoriametrics.com/vmalert/#quickstart), downloading it from the [GitHub releases page](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/latest), or using the [docker image](https://hub.docker.com/r/victoriametrics/vmalert) for the container ecosystem (such as docker, k8s, etc.).
|
||||
* [vmalert](https://docs.victoriametrics.com/vmalert/) is installed. You can obtain it by building it from [source](https://docs.victoriametrics.com/vmalert/#quickstart), downloading it from the [GitHub releases page](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/latest), or using the docker image [Docker Hub](https://hub.docker.com/r/victoriametrics/vmalert) or [Quay](https://quay.io/repository/victoriametrics/vmalert?tab=tags) for the container ecosystem (such as docker, k8s, etc.).
|
||||
* [Alertmanager](https://prometheus.io/docs/alerting/latest/alertmanager/) is installed.
|
||||
* You have a [single or cluster](https://docs.victoriametrics.com/victoriametrics-cloud/quickstart/#creating-deployment) deployment in [VictoriaMetrics Cloud](https://docs.victoriametrics.com/victoriametrics-cloud/overview/).
|
||||
* If you are using helm, add the [VictoriaMetrics helm chart](https://docs.victoriametrics.com/helm/victoriametrics-alert#how-to-install) repository to your helm repositories. This step is optional.
|
||||
|
||||
@@ -55,7 +55,7 @@ additionally to [discovering Prometheus-compatible targets and scraping metrics
|
||||
## Quick Start
|
||||
|
||||
Please download `vmutils-*` archive from [releases page](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/latest) (
|
||||
`vmagent` is also available in [docker images](https://hub.docker.com/r/victoriametrics/vmagent/tags)),
|
||||
`vmagent` is also available in docker images [Docker Hub](https://hub.docker.com/r/victoriametrics/vmagent/tags) and [Quay](https://quay.io/repository/victoriametrics/vmagent?tab=tags)),
|
||||
unpack it and pass the following flags to the `vmagent` binary in order to start scraping Prometheus-compatible targets
|
||||
and sending the data to the Prometheus-compatible remote storage:
|
||||
|
||||
@@ -1368,7 +1368,7 @@ and gzipped [JSON line](https://docs.victoriametrics.com/#json-line-format) mess
|
||||
|
||||
These command-line flags are available only in [enterprise](https://docs.victoriametrics.com/enterprise/) version of `vmagent`,
|
||||
which can be downloaded for evaluation from [releases](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/latest) page
|
||||
(see `vmutils-...-enterprise.tar.gz` archives) and from [docker images](https://hub.docker.com/r/victoriametrics/vmagent/tags) with tags containing `enterprise` suffix.
|
||||
(see `vmutils-...-enterprise.tar.gz` archives) and from docker images [Docker Hub](https://hub.docker.com/r/victoriametrics/vmagent/tags) and [Quay](https://quay.io/repository/victoriametrics/vmagent?tab=tags) with tags containing `enterprise` suffix.
|
||||
|
||||
```sh
|
||||
-gcp.pubsub.subscribe.credentialsFile string
|
||||
@@ -1402,7 +1402,7 @@ These messages can be read later from Google PubSub by another `vmagent` instanc
|
||||
|
||||
These command-line flags are available only in [enterprise](https://docs.victoriametrics.com/enterprise/) version of `vmagent`,
|
||||
which can be downloaded for evaluation from [releases](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/latest) page
|
||||
(see `vmutils-...-enterprise.tar.gz` archives) and from [docker images](https://hub.docker.com/r/victoriametrics/vmagent/tags) with tags containing `enterprise` suffix.
|
||||
(see `vmutils-...-enterprise.tar.gz` archives) and from docker images [Docker Hub](https://hub.docker.com/r/victoriametrics/vmagent/tags) and [Quay](https://quay.io/repository/victoriametrics/vmagent?tab=tags) with tags containing `enterprise` suffix.
|
||||
|
||||
```sh
|
||||
-gcp.pubsub.publish.byteThreshold int
|
||||
@@ -1431,7 +1431,7 @@ which can be downloaded for evaluation from [releases](https://github.com/Victor
|
||||
* [Writing metrics to Kafka](#writing-metrics-to-kafka)
|
||||
|
||||
The enterprise version of vmagent is available for evaluation at [releases](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/latest) page
|
||||
in `vmutils-...-enterprise.tar.gz` archives and in [docker images](https://hub.docker.com/r/victoriametrics/vmagent/tags) with tags containing `enterprise` suffix.
|
||||
in `vmutils-...-enterprise.tar.gz` archives and in docker images [Docker Hub](https://hub.docker.com/r/victoriametrics/vmagent/tags) and [Quay](https://quay.io/repository/victoriametrics/vmagent?tab=tags) with tags containing `enterprise` suffix.
|
||||
See how to request a free trial license [here](https://victoriametrics.com/products/enterprise/trial/).
|
||||
|
||||
### Reading metrics from Kafka
|
||||
@@ -1507,7 +1507,7 @@ See also [how to write metrics to multiple distinct tenants](https://docs.victor
|
||||
|
||||
These command-line flags are available only in [enterprise](https://docs.victoriametrics.com/enterprise/) version of `vmagent`,
|
||||
which can be downloaded for evaluation from [releases](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/latest) page
|
||||
(see `vmutils-...-enterprise.tar.gz` archives) and from [docker images](https://hub.docker.com/r/victoriametrics/vmagent/tags) with tags containing `enterprise` suffix.
|
||||
(see `vmutils-...-enterprise.tar.gz` archives) and from docker images [Docker Hub](https://hub.docker.com/r/victoriametrics/vmagent/tags) and [Quay](https://quay.io/repository/victoriametrics/vmagent?tab=tags) with tags containing `enterprise` suffix.
|
||||
|
||||
```sh
|
||||
-kafka.consumer.topic array
|
||||
|
||||
@@ -312,6 +312,8 @@ Run `vmalert-tool unittest --help` to get all configuration options:
|
||||
Optional label in the form 'name=value' to add to all generated recording rules and alerts. Supports an array of values separated by comma or specified via multiple flags.
|
||||
-external.url
|
||||
Optional external URL to template in rule's labels or annotations.
|
||||
-httpListenPort
|
||||
Optional local port for incoming HTTP requests. If not specified, a random unoccupied port will be used.
|
||||
-loggerLevel
|
||||
Minimum level of errors to log. Possible values: INFO, WARN, ERROR, FATAL, PANIC (default "ERROR").
|
||||
```
|
||||
|
||||
@@ -8,8 +8,8 @@ title: vmalert
|
||||
aliases:
|
||||
- /vmalert.html
|
||||
---
|
||||
`vmalert` executes a list of the given [alerting](https://prometheus.io/docs/prometheus/latest/configuration/alerting_rules/)
|
||||
or [recording](https://prometheus.io/docs/prometheus/latest/configuration/recording_rules/)
|
||||
`vmalert` executes a list of the given [alerting](https://docs.victoriametrics.com/vmalert/#alerting-rules)
|
||||
or [recording](https://docs.victoriametrics.com/vmalert/#recording-rules)
|
||||
rules against configured `-datasource.url`. For sending alerting notifications
|
||||
`vmalert` relies on [Alertmanager](https://github.com/prometheus/alertmanager) configured via `-notifier.url` flag.
|
||||
Recording rules results are persisted via [remote write](https://prometheus.io/docs/prometheus/latest/storage/#remote-storage-integrations)
|
||||
@@ -521,7 +521,7 @@ is obtained from `-defaultTenant.prometheus` or `-defaultTenant.graphite` depend
|
||||
|
||||
The enterprise version of vmalert is available in `vmutils-*-enterprise.tar.gz` files
|
||||
at [release page](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/latest) and in `*-enterprise`
|
||||
tags at [Docker Hub](https://hub.docker.com/r/victoriametrics/vmalert/tags).
|
||||
tags at [Docker Hub](https://hub.docker.com/r/victoriametrics/vmalert/tags) and [Quay](https://quay.io/repository/victoriametrics/vmalert?tab=tags).
|
||||
|
||||
### Reading rules from object storage
|
||||
|
||||
@@ -952,7 +952,7 @@ Sensitive info is stripped from the `curl` examples - see [security](#security)
|
||||
|
||||
### Never-firing alerts
|
||||
|
||||
vmalert can detect if alert's expression doesn't match any time series in runtime
|
||||
vmalert can detect{{% available_from "v1.90.0" %}} if alert's expression doesn't match any time series in runtime
|
||||
starting from [v1.91](https://docs.victoriametrics.com/changelog/#v1910). This problem usually happens
|
||||
when alerting expression selects time series which aren't present in the datasource (i.e. wrong `job` label)
|
||||
or there is a typo in the series selector (i.e. `env=prod`). Such alerting rules will be marked with special icon in
|
||||
@@ -964,8 +964,8 @@ used to detect rules matching no series:
|
||||
max(vmalert_alerting_rules_last_evaluation_series_fetched) by(group, alertname) == 0
|
||||
```
|
||||
|
||||
See more details [here](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4039).
|
||||
This feature is available only if vmalert is using VictoriaMetrics v1.90 or higher as a datasource.
|
||||
See more details [here](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4039) and
|
||||
read [Never-firing alerts](https://victoriametrics.com/blog/never-firing-alerts/) blogpost.
|
||||
|
||||
### Series with the same labelset
|
||||
|
||||
@@ -1269,7 +1269,7 @@ The shortlist of configuration flags is the following:
|
||||
Supports an array of values separated by comma or specified via multiple flags.
|
||||
Value can contain comma inside single-quoted or double-quoted string, {}, [] and () braces.
|
||||
-notifier.sendTimeout
|
||||
Timeout for sending alerts to the configured -notifier.url. (default 10s)
|
||||
Timeout when sending alerts to the corresponding -notifier.url. (default 10s)
|
||||
-notifier.showURL
|
||||
Whether to avoid stripping sensitive information such as passwords from URL in log messages or UI for -notifier.url. It is hidden by default, since it can contain sensitive info such as auth key
|
||||
-notifier.suppressDuplicateTargetErrors
|
||||
|
||||
@@ -28,7 +28,7 @@ The port can be modified via `-httpListenAddr` command-line flag.
|
||||
|
||||
See [how to reload config without restart](#config-reload).
|
||||
|
||||
Docker images for `vmauth` are available [here](https://hub.docker.com/r/victoriametrics/vmauth/tags).
|
||||
Docker images for `vmauth` are available at [Docker Hub](https://hub.docker.com/r/victoriametrics/vmauth/tags) and [Quay](https://quay.io/repository/victoriametrics/vmauth?tab=tags).
|
||||
See how `vmauth` used in [docker-compose env](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/deployment/docker/README.md#victoriametrics-cluster).
|
||||
|
||||
Pass `-help` to `vmauth` in order to see all the supported command-line flags with their descriptions.
|
||||
|
||||
@@ -1191,7 +1191,7 @@ Flags available only for the `opentsdb` command:
|
||||
--influx-measurement-field-separator value
|
||||
The {separator} symbol used to concatenate {measurement} and {field} names into series name {measurement}{separator}{field}. (default: "_")
|
||||
--influx-skip-database-label
|
||||
Wether to skip adding the label 'db' to timeseries. (default: false)
|
||||
Whether to skip adding the label 'db' to timeseries. (default: false)
|
||||
--influx-prometheus-mode
|
||||
Whether to restore the original timeseries name previously written from Prometheus to InfluxDB v1 via remote_write. (default: false)
|
||||
--influx-cert-file value
|
||||
|
||||
2
go.mod
2
go.mod
@@ -1,6 +1,6 @@
|
||||
module github.com/VictoriaMetrics/VictoriaMetrics
|
||||
|
||||
go 1.23.6
|
||||
go 1.24.0
|
||||
|
||||
// This is needed in order to avoid vmbackup and vmrestore binary size increase by 20MB
|
||||
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8008
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutils"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/timeutil"
|
||||
)
|
||||
|
||||
// GetDuration returns duration in milliseconds from the given argKey query arg.
|
||||
@@ -21,7 +21,7 @@ func GetDuration(r *http.Request, argKey string, defaultValue int64) (int64, err
|
||||
secs, err := strconv.ParseFloat(argValue, 64)
|
||||
if err != nil {
|
||||
// Try parsing string format
|
||||
d, err := promutils.ParseDuration(argValue)
|
||||
d, err := timeutil.ParseDuration(argValue)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("cannot parse %q=%q: %w", argKey, argValue, err)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutils"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/timeutil"
|
||||
)
|
||||
|
||||
// GetTime returns time in milliseconds from the given argKey query arg.
|
||||
@@ -28,7 +28,7 @@ func GetTime(r *http.Request, argKey string, defaultMs int64) (int64, error) {
|
||||
return maxTimeMsecs, nil
|
||||
}
|
||||
// Parse argValue
|
||||
msecs, err := promutils.ParseTimeMsec(argValue)
|
||||
msecs, err := timeutil.ParseTimeMsec(argValue)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("cannot parse %s=%s: %w", argKey, argValue, err)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package logstorage
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
)
|
||||
|
||||
// filterEqField matches if the given fields have equivalent values.
|
||||
@@ -23,6 +25,10 @@ func (fe *filterEqField) updateNeededFields(neededFields fieldsSet) {
|
||||
}
|
||||
|
||||
func (fe *filterEqField) applyToBlockResult(br *blockResult, bm *bitmap) {
|
||||
if fe.fieldName == fe.otherFieldName {
|
||||
return
|
||||
}
|
||||
|
||||
c := br.getColumnByName(fe.fieldName)
|
||||
cOther := br.getColumnByName(fe.otherFieldName)
|
||||
|
||||
@@ -34,7 +40,44 @@ func (fe *filterEqField) applyToBlockResult(br *blockResult, bm *bitmap) {
|
||||
}
|
||||
return
|
||||
}
|
||||
if c.isTime && cOther.isTime {
|
||||
// c and cOther point to the same _time column, since only a single _time column may exist
|
||||
return
|
||||
}
|
||||
|
||||
if c.valueType != cOther.valueType {
|
||||
// Slow path - c and cOther have different valueType, so convert them to string values and compare them
|
||||
applyFilterEqString(br, bm, c, cOther)
|
||||
return
|
||||
}
|
||||
|
||||
switch c.valueType {
|
||||
case valueTypeString:
|
||||
applyFilterEqString(br, bm, c, cOther)
|
||||
case valueTypeDict:
|
||||
applyFilterEqDict(br, bm, c, cOther)
|
||||
case valueTypeUint8:
|
||||
applyFilterEqBinValues(br, bm, c, cOther)
|
||||
case valueTypeUint16:
|
||||
applyFilterEqBinValues(br, bm, c, cOther)
|
||||
case valueTypeUint32:
|
||||
applyFilterEqBinValues(br, bm, c, cOther)
|
||||
case valueTypeUint64:
|
||||
applyFilterEqBinValues(br, bm, c, cOther)
|
||||
case valueTypeInt64:
|
||||
applyFilterEqBinValues(br, bm, c, cOther)
|
||||
case valueTypeFloat64:
|
||||
applyFilterEqBinValues(br, bm, c, cOther)
|
||||
case valueTypeIPv4:
|
||||
applyFilterEqBinValues(br, bm, c, cOther)
|
||||
case valueTypeTimestampISO8601:
|
||||
applyFilterEqBinValues(br, bm, c, cOther)
|
||||
default:
|
||||
logger.Panicf("FATAL: unknown valueType=%d", c.valueType)
|
||||
}
|
||||
}
|
||||
|
||||
func applyFilterEqString(br *blockResult, bm *bitmap, c, cOther *blockResultColumn) {
|
||||
values := c.getValues(br)
|
||||
valuesOther := cOther.getValues(br)
|
||||
bm.forEachSetBit(func(idx int) bool {
|
||||
@@ -42,7 +85,87 @@ func (fe *filterEqField) applyToBlockResult(br *blockResult, bm *bitmap) {
|
||||
})
|
||||
}
|
||||
|
||||
func applyFilterEqDict(br *blockResult, bm *bitmap, c, cOther *blockResultColumn) {
|
||||
valuesEncoded := c.getValuesEncoded(br)
|
||||
valuesEncodedOther := cOther.getValuesEncoded(br)
|
||||
bm.forEachSetBit(func(idx int) bool {
|
||||
dictIdx := valuesEncoded[idx][0]
|
||||
dictIdxOther := valuesEncodedOther[idx][0]
|
||||
v := c.dictValues[dictIdx]
|
||||
vOther := cOther.dictValues[dictIdxOther]
|
||||
return v == vOther
|
||||
})
|
||||
}
|
||||
|
||||
func applyFilterEqBinValues(br *blockResult, bm *bitmap, c, cOther *blockResultColumn) {
|
||||
valuesEncoded := c.getValuesEncoded(br)
|
||||
valuesEncodedOther := cOther.getValuesEncoded(br)
|
||||
bm.forEachSetBit(func(idx int) bool {
|
||||
return valuesEncoded[idx] == valuesEncodedOther[idx]
|
||||
})
|
||||
}
|
||||
|
||||
func (fe *filterEqField) applyToBlockSearch(bs *blockSearch, bm *bitmap) {
|
||||
if fe.fieldName == fe.otherFieldName {
|
||||
return
|
||||
}
|
||||
|
||||
v := bs.getConstColumnValue(fe.fieldName)
|
||||
vOther := bs.getConstColumnValue(fe.otherFieldName)
|
||||
if v != "" || vOther != "" {
|
||||
if v != "" && vOther != "" {
|
||||
if v != vOther {
|
||||
bm.resetBits()
|
||||
}
|
||||
return
|
||||
}
|
||||
fe.applyFilterString(bs, bm)
|
||||
return
|
||||
}
|
||||
|
||||
ch := bs.getColumnHeader(fe.fieldName)
|
||||
chOther := bs.getColumnHeader(fe.otherFieldName)
|
||||
if ch == nil || chOther == nil {
|
||||
if ch == nil && chOther == nil {
|
||||
return
|
||||
}
|
||||
fe.applyFilterString(bs, bm)
|
||||
return
|
||||
}
|
||||
|
||||
if ch.valueType != chOther.valueType {
|
||||
// Slow path - c and cOther have different valueType, so convert them to string values and compare them
|
||||
fe.applyFilterString(bs, bm)
|
||||
return
|
||||
}
|
||||
|
||||
switch ch.valueType {
|
||||
case valueTypeString:
|
||||
fe.applyFilterString(bs, bm)
|
||||
case valueTypeDict:
|
||||
fe.applyFilterDict(bs, bm, ch, chOther)
|
||||
case valueTypeUint8:
|
||||
fe.applyFilterBinValue(bs, bm, ch, chOther)
|
||||
case valueTypeUint16:
|
||||
fe.applyFilterBinValue(bs, bm, ch, chOther)
|
||||
case valueTypeUint32:
|
||||
fe.applyFilterBinValue(bs, bm, ch, chOther)
|
||||
case valueTypeUint64:
|
||||
fe.applyFilterBinValue(bs, bm, ch, chOther)
|
||||
case valueTypeInt64:
|
||||
fe.applyFilterBinValue(bs, bm, ch, chOther)
|
||||
case valueTypeFloat64:
|
||||
fe.applyFilterBinValue(bs, bm, ch, chOther)
|
||||
case valueTypeIPv4:
|
||||
fe.applyFilterBinValue(bs, bm, ch, chOther)
|
||||
case valueTypeTimestampISO8601:
|
||||
fe.applyFilterBinValue(bs, bm, ch, chOther)
|
||||
default:
|
||||
logger.Panicf("FATAL: %s: unknown valueType=%d", bs.partPath(), ch.valueType)
|
||||
}
|
||||
}
|
||||
|
||||
func (fe *filterEqField) applyFilterString(bs *blockSearch, bm *bitmap) {
|
||||
br := getBlockResult()
|
||||
br.mustInit(bs, bm)
|
||||
br.initRequestedColumns([]string{fe.fieldName, fe.otherFieldName})
|
||||
@@ -63,6 +186,26 @@ func (fe *filterEqField) applyToBlockSearch(bs *blockSearch, bm *bitmap) {
|
||||
putBlockResult(br)
|
||||
}
|
||||
|
||||
func (fe *filterEqField) applyFilterDict(bs *blockSearch, bm *bitmap, ch, chOther *columnHeader) {
|
||||
valuesEncoded := bs.getValuesForColumn(ch)
|
||||
valuesEncodedOther := bs.getValuesForColumn(chOther)
|
||||
bm.forEachSetBit(func(idx int) bool {
|
||||
dictIdx := valuesEncoded[idx][0]
|
||||
dictIdxOther := valuesEncodedOther[idx][0]
|
||||
v := ch.valuesDict.values[dictIdx]
|
||||
vOther := chOther.valuesDict.values[dictIdxOther]
|
||||
return v == vOther
|
||||
})
|
||||
}
|
||||
|
||||
func (fe *filterEqField) applyFilterBinValue(bs *blockSearch, bm *bitmap, ch, chOther *columnHeader) {
|
||||
valuesEncoded := bs.getValuesForColumn(ch)
|
||||
valuesEncodedOther := bs.getValuesForColumn(chOther)
|
||||
bm.forEachSetBit(func(idx int) bool {
|
||||
return valuesEncoded[idx] == valuesEncodedOther[idx]
|
||||
})
|
||||
}
|
||||
|
||||
func getBlockResult() *blockResult {
|
||||
v := brPool.Get()
|
||||
if v == nil {
|
||||
|
||||
@@ -17,6 +17,18 @@ func TestFilterEqField(t *testing.T) {
|
||||
"abc def",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "bar",
|
||||
values: []string{
|
||||
"abc def",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "baz",
|
||||
values: []string{
|
||||
"qwerty",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// match
|
||||
@@ -26,6 +38,12 @@ func TestFilterEqField(t *testing.T) {
|
||||
}
|
||||
testFilterMatchForColumns(t, columns, fe, "foo", []int{0})
|
||||
|
||||
fe = &filterEqField{
|
||||
fieldName: "foo",
|
||||
otherFieldName: "bar",
|
||||
}
|
||||
testFilterMatchForColumns(t, columns, fe, "foo", []int{0})
|
||||
|
||||
fe = &filterEqField{
|
||||
fieldName: "non-existing-column",
|
||||
otherFieldName: "other-non-existing-column",
|
||||
@@ -33,6 +51,12 @@ func TestFilterEqField(t *testing.T) {
|
||||
testFilterMatchForColumns(t, columns, fe, "foo", []int{0})
|
||||
|
||||
// mismatch
|
||||
fe = &filterEqField{
|
||||
fieldName: "foo",
|
||||
otherFieldName: "baz",
|
||||
}
|
||||
testFilterMatchForColumns(t, columns, fe, "foo", nil)
|
||||
|
||||
fe = &filterEqField{
|
||||
fieldName: "foo",
|
||||
otherFieldName: "non-existing-column",
|
||||
@@ -56,6 +80,22 @@ func TestFilterEqField(t *testing.T) {
|
||||
"abc def",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "bar",
|
||||
values: []string{
|
||||
"abc def",
|
||||
"abc def",
|
||||
"abc def",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "baz",
|
||||
values: []string{
|
||||
"qwerty",
|
||||
"qwerty",
|
||||
"qwerty",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// match
|
||||
@@ -65,6 +105,12 @@ func TestFilterEqField(t *testing.T) {
|
||||
}
|
||||
testFilterMatchForColumns(t, columns, fe, "foo", []int{0, 1, 2})
|
||||
|
||||
fe = &filterEqField{
|
||||
fieldName: "foo",
|
||||
otherFieldName: "bar",
|
||||
}
|
||||
testFilterMatchForColumns(t, columns, fe, "foo", []int{0, 1, 2})
|
||||
|
||||
fe = &filterEqField{
|
||||
fieldName: "non-existing-column",
|
||||
otherFieldName: "non-existing-column",
|
||||
@@ -72,6 +118,12 @@ func TestFilterEqField(t *testing.T) {
|
||||
testFilterMatchForColumns(t, columns, fe, "foo", []int{0, 1, 2})
|
||||
|
||||
// mismatch
|
||||
fe = &filterEqField{
|
||||
fieldName: "foo",
|
||||
otherFieldName: "baz",
|
||||
}
|
||||
testFilterMatchForColumns(t, columns, fe, "foo", nil)
|
||||
|
||||
fe = &filterEqField{
|
||||
fieldName: "foo",
|
||||
otherFieldName: "non-existing-column",
|
||||
@@ -99,6 +151,30 @@ func TestFilterEqField(t *testing.T) {
|
||||
"foobar",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "bar",
|
||||
values: []string{
|
||||
"xabc",
|
||||
"xfoobar",
|
||||
"",
|
||||
"",
|
||||
"xfddf foobarbaz",
|
||||
"afoobarbaz",
|
||||
"xfoobar",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "baz",
|
||||
values: []string{
|
||||
"xabc",
|
||||
"xfoobar",
|
||||
"x",
|
||||
"x",
|
||||
"xfddf foobarbaz",
|
||||
"xafoobarbaz",
|
||||
"xfoobar",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// match
|
||||
@@ -108,6 +184,12 @@ func TestFilterEqField(t *testing.T) {
|
||||
}
|
||||
testFilterMatchForColumns(t, columns, fe, "foo", []int{0, 1, 2, 3, 4, 5, 6})
|
||||
|
||||
fe = &filterEqField{
|
||||
fieldName: "foo",
|
||||
otherFieldName: "bar",
|
||||
}
|
||||
testFilterMatchForColumns(t, columns, fe, "foo", []int{2, 5})
|
||||
|
||||
fe = &filterEqField{
|
||||
fieldName: "non-existing-column",
|
||||
otherFieldName: "other-non-existing-column",
|
||||
@@ -125,6 +207,13 @@ func TestFilterEqField(t *testing.T) {
|
||||
otherFieldName: "foo",
|
||||
}
|
||||
testFilterMatchForColumns(t, columns, fe, "foo", []int{2})
|
||||
|
||||
// mismatch
|
||||
fe = &filterEqField{
|
||||
fieldName: "foo",
|
||||
otherFieldName: "baz",
|
||||
}
|
||||
testFilterMatchForColumns(t, columns, fe, "foo", nil)
|
||||
})
|
||||
|
||||
t.Run("strings", func(t *testing.T) {
|
||||
@@ -144,6 +233,36 @@ func TestFilterEqField(t *testing.T) {
|
||||
"a !!,23.(!1)",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "bar",
|
||||
values: []string{
|
||||
"a foo",
|
||||
"xa foobar",
|
||||
"aa abc a",
|
||||
"",
|
||||
"xa fddf foobarbaz",
|
||||
"",
|
||||
"xa foobar baz",
|
||||
"a kjlkjf dfff",
|
||||
"a ТЕСТЙЦУК НГКШ ",
|
||||
"xa !!,23.(!1)",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "baz",
|
||||
values: []string{
|
||||
"xa foo",
|
||||
"xa foobar",
|
||||
"xaa abc a",
|
||||
"xca afdf a,foobar baz",
|
||||
"xa fddf foobarbaz",
|
||||
"x",
|
||||
"xa foobar baz",
|
||||
"xa kjlkjf dfff",
|
||||
"xa ТЕСТЙЦУК НГКШ ",
|
||||
"xa !!,23.(!1)",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// match
|
||||
@@ -153,6 +272,12 @@ func TestFilterEqField(t *testing.T) {
|
||||
}
|
||||
testFilterMatchForColumns(t, columns, fe, "foo", []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9})
|
||||
|
||||
fe = &filterEqField{
|
||||
fieldName: "foo",
|
||||
otherFieldName: "bar",
|
||||
}
|
||||
testFilterMatchForColumns(t, columns, fe, "foo", []int{0, 2, 5, 7, 8})
|
||||
|
||||
fe = &filterEqField{
|
||||
fieldName: "non-existing-column",
|
||||
otherFieldName: "non-existing-column",
|
||||
@@ -170,6 +295,13 @@ func TestFilterEqField(t *testing.T) {
|
||||
otherFieldName: "foo",
|
||||
}
|
||||
testFilterMatchForColumns(t, columns, fe, "foo", []int{5})
|
||||
|
||||
// mismatch
|
||||
fe = &filterEqField{
|
||||
fieldName: "foo",
|
||||
otherFieldName: "baz",
|
||||
}
|
||||
testFilterMatchForColumns(t, columns, fe, "foo", nil)
|
||||
})
|
||||
|
||||
t.Run("uint8", func(t *testing.T) {
|
||||
@@ -190,6 +322,38 @@ func TestFilterEqField(t *testing.T) {
|
||||
"5",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "bar",
|
||||
values: []string{
|
||||
"23",
|
||||
"12",
|
||||
"42",
|
||||
"0",
|
||||
"10",
|
||||
"12",
|
||||
"10",
|
||||
"2",
|
||||
"30",
|
||||
"4",
|
||||
"50",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "baz",
|
||||
values: []string{
|
||||
"230",
|
||||
"120",
|
||||
"20",
|
||||
"10",
|
||||
"20",
|
||||
"120",
|
||||
"10",
|
||||
"20",
|
||||
"30",
|
||||
"40",
|
||||
"50",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// match
|
||||
@@ -199,6 +363,12 @@ func TestFilterEqField(t *testing.T) {
|
||||
}
|
||||
testFilterMatchForColumns(t, columns, fe, "foo", []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
|
||||
|
||||
fe = &filterEqField{
|
||||
fieldName: "foo",
|
||||
otherFieldName: "bar",
|
||||
}
|
||||
testFilterMatchForColumns(t, columns, fe, "foo", []int{1, 3, 5, 7, 9})
|
||||
|
||||
fe = &filterEqField{
|
||||
fieldName: "non-existing-column",
|
||||
otherFieldName: "other-non-existing-column",
|
||||
@@ -206,6 +376,12 @@ func TestFilterEqField(t *testing.T) {
|
||||
testFilterMatchForColumns(t, columns, fe, "foo", []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
|
||||
|
||||
// mismatch
|
||||
fe = &filterEqField{
|
||||
fieldName: "foo",
|
||||
otherFieldName: "baz",
|
||||
}
|
||||
testFilterMatchForColumns(t, columns, fe, "foo", nil)
|
||||
|
||||
fe = &filterEqField{
|
||||
fieldName: "foo",
|
||||
otherFieldName: "non-exsiting-column",
|
||||
@@ -237,6 +413,38 @@ func TestFilterEqField(t *testing.T) {
|
||||
"5",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "bar",
|
||||
values: []string{
|
||||
"23",
|
||||
"12",
|
||||
"2",
|
||||
"0",
|
||||
"10",
|
||||
"12",
|
||||
"560",
|
||||
"2",
|
||||
"43",
|
||||
"4",
|
||||
"50",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "baz",
|
||||
values: []string{
|
||||
"1123",
|
||||
"112",
|
||||
"132",
|
||||
"10",
|
||||
"10",
|
||||
"112",
|
||||
"1256",
|
||||
"12",
|
||||
"13",
|
||||
"14",
|
||||
"15",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// match
|
||||
@@ -246,6 +454,12 @@ func TestFilterEqField(t *testing.T) {
|
||||
}
|
||||
testFilterMatchForColumns(t, columns, fe, "foo", []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
|
||||
|
||||
fe = &filterEqField{
|
||||
fieldName: "foo",
|
||||
otherFieldName: "bar",
|
||||
}
|
||||
testFilterMatchForColumns(t, columns, fe, "foo", []int{1, 3, 5, 7, 9})
|
||||
|
||||
fe = &filterEqField{
|
||||
fieldName: "non-existing-column",
|
||||
otherFieldName: "non-existing-column",
|
||||
@@ -253,6 +467,12 @@ func TestFilterEqField(t *testing.T) {
|
||||
testFilterMatchForColumns(t, columns, fe, "foo", []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
|
||||
|
||||
// mismatch
|
||||
fe = &filterEqField{
|
||||
fieldName: "foo",
|
||||
otherFieldName: "baz",
|
||||
}
|
||||
testFilterMatchForColumns(t, columns, fe, "foo", nil)
|
||||
|
||||
fe = &filterEqField{
|
||||
fieldName: "foo",
|
||||
otherFieldName: "non-existing-column",
|
||||
@@ -284,6 +504,38 @@ func TestFilterEqField(t *testing.T) {
|
||||
"5",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "bar",
|
||||
values: []string{
|
||||
"1123",
|
||||
"12",
|
||||
"132",
|
||||
"0",
|
||||
"10",
|
||||
"12",
|
||||
"165536",
|
||||
"2",
|
||||
"13",
|
||||
"4",
|
||||
"15",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "baz",
|
||||
values: []string{
|
||||
"2123",
|
||||
"212",
|
||||
"232",
|
||||
"20",
|
||||
"20",
|
||||
"212",
|
||||
"265536",
|
||||
"22",
|
||||
"23",
|
||||
"24",
|
||||
"25",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// match
|
||||
@@ -293,6 +545,12 @@ func TestFilterEqField(t *testing.T) {
|
||||
}
|
||||
testFilterMatchForColumns(t, columns, fe, "foo", []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
|
||||
|
||||
fe = &filterEqField{
|
||||
fieldName: "foo",
|
||||
otherFieldName: "bar",
|
||||
}
|
||||
testFilterMatchForColumns(t, columns, fe, "foo", []int{1, 3, 5, 7, 9})
|
||||
|
||||
fe = &filterEqField{
|
||||
fieldName: "non-existing-column",
|
||||
otherFieldName: "non-existing-column",
|
||||
@@ -300,6 +558,12 @@ func TestFilterEqField(t *testing.T) {
|
||||
testFilterMatchForColumns(t, columns, fe, "foo", []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
|
||||
|
||||
// mismatch
|
||||
fe = &filterEqField{
|
||||
fieldName: "foo",
|
||||
otherFieldName: "baz",
|
||||
}
|
||||
testFilterMatchForColumns(t, columns, fe, "foo", nil)
|
||||
|
||||
fe = &filterEqField{
|
||||
fieldName: "foo",
|
||||
otherFieldName: "non-existing-column",
|
||||
@@ -331,6 +595,38 @@ func TestFilterEqField(t *testing.T) {
|
||||
"5",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "bar",
|
||||
values: []string{
|
||||
"1123",
|
||||
"12",
|
||||
"132",
|
||||
"0",
|
||||
"10",
|
||||
"12",
|
||||
"112345678901",
|
||||
"2",
|
||||
"13",
|
||||
"4",
|
||||
"15",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "baz",
|
||||
values: []string{
|
||||
"2123",
|
||||
"212",
|
||||
"232",
|
||||
"20",
|
||||
"20",
|
||||
"212",
|
||||
"212345678901",
|
||||
"22",
|
||||
"23",
|
||||
"24",
|
||||
"25",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// match
|
||||
@@ -340,6 +636,12 @@ func TestFilterEqField(t *testing.T) {
|
||||
}
|
||||
testFilterMatchForColumns(t, columns, fe, "foo", []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
|
||||
|
||||
fe = &filterEqField{
|
||||
fieldName: "foo",
|
||||
otherFieldName: "bar",
|
||||
}
|
||||
testFilterMatchForColumns(t, columns, fe, "foo", []int{1, 3, 5, 7, 9})
|
||||
|
||||
fe = &filterEqField{
|
||||
fieldName: "non-existing-column",
|
||||
otherFieldName: "non-existing-column",
|
||||
@@ -347,6 +649,12 @@ func TestFilterEqField(t *testing.T) {
|
||||
testFilterMatchForColumns(t, columns, fe, "foo", []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
|
||||
|
||||
// mismatch
|
||||
fe = &filterEqField{
|
||||
fieldName: "foo",
|
||||
otherFieldName: "baz",
|
||||
}
|
||||
testFilterMatchForColumns(t, columns, fe, "foo", nil)
|
||||
|
||||
fe = &filterEqField{
|
||||
fieldName: "foo",
|
||||
otherFieldName: "non-existing-column",
|
||||
@@ -378,6 +686,38 @@ func TestFilterEqField(t *testing.T) {
|
||||
"5",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "bar",
|
||||
values: []string{
|
||||
"3123",
|
||||
"12",
|
||||
"332",
|
||||
"0",
|
||||
"30",
|
||||
"-12",
|
||||
"312345678901",
|
||||
"2",
|
||||
"33",
|
||||
"4",
|
||||
"35",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "baz",
|
||||
values: []string{
|
||||
"2123",
|
||||
"212",
|
||||
"232",
|
||||
"20",
|
||||
"20",
|
||||
"-212",
|
||||
"212345678901",
|
||||
"22",
|
||||
"23",
|
||||
"24",
|
||||
"25",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// match
|
||||
@@ -387,6 +727,12 @@ func TestFilterEqField(t *testing.T) {
|
||||
}
|
||||
testFilterMatchForColumns(t, columns, fe, "foo", []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
|
||||
|
||||
fe = &filterEqField{
|
||||
fieldName: "foo",
|
||||
otherFieldName: "bar",
|
||||
}
|
||||
testFilterMatchForColumns(t, columns, fe, "foo", []int{1, 3, 5, 7, 9})
|
||||
|
||||
fe = &filterEqField{
|
||||
fieldName: "non-existing-column",
|
||||
otherFieldName: "non-existing-column",
|
||||
@@ -394,6 +740,12 @@ func TestFilterEqField(t *testing.T) {
|
||||
testFilterMatchForColumns(t, columns, fe, "foo", []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
|
||||
|
||||
// mismatch
|
||||
fe = &filterEqField{
|
||||
fieldName: "foo",
|
||||
otherFieldName: "baz",
|
||||
}
|
||||
testFilterMatchForColumns(t, columns, fe, "foo", nil)
|
||||
|
||||
fe = &filterEqField{
|
||||
fieldName: "foo",
|
||||
otherFieldName: "non-existing-column",
|
||||
@@ -423,6 +775,34 @@ func TestFilterEqField(t *testing.T) {
|
||||
"4",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "bar",
|
||||
values: []string{
|
||||
"11234",
|
||||
"0",
|
||||
"23454",
|
||||
"-65536",
|
||||
"21234.5678901",
|
||||
"1",
|
||||
"22",
|
||||
"3",
|
||||
"24",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "baz",
|
||||
values: []string{
|
||||
"21234",
|
||||
"20",
|
||||
"23454",
|
||||
"-265536",
|
||||
"21234.5678901",
|
||||
"21",
|
||||
"22",
|
||||
"23",
|
||||
"24",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// match
|
||||
@@ -432,6 +812,12 @@ func TestFilterEqField(t *testing.T) {
|
||||
}
|
||||
testFilterMatchForColumns(t, columns, fe, "foo", []int{0, 1, 2, 3, 4, 5, 6, 7, 8})
|
||||
|
||||
fe = &filterEqField{
|
||||
fieldName: "foo",
|
||||
otherFieldName: "bar",
|
||||
}
|
||||
testFilterMatchForColumns(t, columns, fe, "foo", []int{1, 3, 5, 7})
|
||||
|
||||
fe = &filterEqField{
|
||||
fieldName: "non-existing-column",
|
||||
otherFieldName: "other-non-existing-column",
|
||||
@@ -439,6 +825,12 @@ func TestFilterEqField(t *testing.T) {
|
||||
testFilterMatchForColumns(t, columns, fe, "foo", []int{0, 1, 2, 3, 4, 5, 6, 7, 8})
|
||||
|
||||
// mismatch
|
||||
fe = &filterEqField{
|
||||
fieldName: "foo",
|
||||
otherFieldName: "baz",
|
||||
}
|
||||
testFilterMatchForColumns(t, columns, fe, "foo", nil)
|
||||
|
||||
fe = &filterEqField{
|
||||
fieldName: "foo",
|
||||
otherFieldName: "non-existing-column",
|
||||
@@ -471,6 +863,40 @@ func TestFilterEqField(t *testing.T) {
|
||||
"7.7.7.7",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "bar",
|
||||
values: []string{
|
||||
"21.2.3.4",
|
||||
"0.0.0.0",
|
||||
"227.0.0.1",
|
||||
"254.255.255.255",
|
||||
"227.0.0.1",
|
||||
"127.0.0.1",
|
||||
"227.0.4.2",
|
||||
"127.0.0.1",
|
||||
"212.0.127.6",
|
||||
"55.55.55.55",
|
||||
"26.66.66.66",
|
||||
"7.7.7.7",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "baz",
|
||||
values: []string{
|
||||
"31.2.3.4",
|
||||
"30.0.0.0",
|
||||
"37.0.0.1",
|
||||
"34.255.255.255",
|
||||
"37.0.0.1",
|
||||
"37.0.0.1",
|
||||
"37.0.4.2",
|
||||
"37.0.0.1",
|
||||
"32.0.127.6",
|
||||
"35.55.55.55",
|
||||
"36.66.66.66",
|
||||
"37.7.7.7",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// match
|
||||
@@ -480,6 +906,12 @@ func TestFilterEqField(t *testing.T) {
|
||||
}
|
||||
testFilterMatchForColumns(t, columns, fe, "foo", []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11})
|
||||
|
||||
fe = &filterEqField{
|
||||
fieldName: "foo",
|
||||
otherFieldName: "bar",
|
||||
}
|
||||
testFilterMatchForColumns(t, columns, fe, "foo", []int{1, 3, 5, 7, 9, 11})
|
||||
|
||||
fe = &filterEqField{
|
||||
fieldName: "non-existing-column",
|
||||
otherFieldName: "non-existing-column",
|
||||
@@ -487,6 +919,12 @@ func TestFilterEqField(t *testing.T) {
|
||||
testFilterMatchForColumns(t, columns, fe, "foo", []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11})
|
||||
|
||||
// mismatch
|
||||
fe = &filterEqField{
|
||||
fieldName: "foo",
|
||||
otherFieldName: "baz",
|
||||
}
|
||||
testFilterMatchForColumns(t, columns, fe, "foo", nil)
|
||||
|
||||
fe = &filterEqField{
|
||||
fieldName: "foo",
|
||||
otherFieldName: "non-existing-column",
|
||||
@@ -516,6 +954,34 @@ func TestFilterEqField(t *testing.T) {
|
||||
"2006-01-02T15:04:05.009Z",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "bar",
|
||||
values: []string{
|
||||
"2007-01-02T15:04:05.001Z",
|
||||
"2006-01-02T15:04:05.002Z",
|
||||
"2007-01-02T15:04:05.003Z",
|
||||
"2006-01-02T15:04:05.004Z",
|
||||
"2007-01-02T15:04:05.005Z",
|
||||
"2006-01-02T15:04:05.006Z",
|
||||
"2007-01-02T15:04:05.007Z",
|
||||
"2006-01-02T15:04:05.008Z",
|
||||
"2007-01-02T15:04:05.009Z",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "baz",
|
||||
values: []string{
|
||||
"2009-01-02T15:04:05.001Z",
|
||||
"2009-01-02T15:04:05.002Z",
|
||||
"2009-01-02T15:04:05.003Z",
|
||||
"2009-01-02T15:04:05.004Z",
|
||||
"2009-01-02T15:04:05.005Z",
|
||||
"2009-01-02T15:04:05.006Z",
|
||||
"2009-01-02T15:04:05.007Z",
|
||||
"2009-01-02T15:04:05.008Z",
|
||||
"2009-01-02T15:04:05.009Z",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// match
|
||||
@@ -525,6 +991,12 @@ func TestFilterEqField(t *testing.T) {
|
||||
}
|
||||
testFilterMatchForColumns(t, columns, fe, "_msg", []int{0, 1, 2, 3, 4, 5, 6, 7, 8})
|
||||
|
||||
fe = &filterEqField{
|
||||
fieldName: "_msg",
|
||||
otherFieldName: "bar",
|
||||
}
|
||||
testFilterMatchForColumns(t, columns, fe, "_msg", []int{1, 3, 5, 7})
|
||||
|
||||
fe = &filterEqField{
|
||||
fieldName: "non-existing-column",
|
||||
otherFieldName: "non-exsiting-column",
|
||||
@@ -532,6 +1004,12 @@ func TestFilterEqField(t *testing.T) {
|
||||
testFilterMatchForColumns(t, columns, fe, "_msg", []int{0, 1, 2, 3, 4, 5, 6, 7, 8})
|
||||
|
||||
// mimatch
|
||||
fe = &filterEqField{
|
||||
fieldName: "_msg",
|
||||
otherFieldName: "baz",
|
||||
}
|
||||
testFilterMatchForColumns(t, columns, fe, "_msg", nil)
|
||||
|
||||
fe = &filterEqField{
|
||||
fieldName: "_msg",
|
||||
otherFieldName: "non-existing-column",
|
||||
|
||||
296
lib/logstorage/filter_le_field.go
Normal file
296
lib/logstorage/filter_le_field.go
Normal file
@@ -0,0 +1,296 @@
|
||||
package logstorage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
)
|
||||
|
||||
// filterLeField matches if the fieldName field is smaller or equal to the otherFieldName field
|
||||
//
|
||||
// Example LogsQL: `fieldName:le_field(otherField)`
|
||||
type filterLeField struct {
|
||||
fieldName string
|
||||
otherFieldName string
|
||||
|
||||
excludeEqualValues bool
|
||||
}
|
||||
|
||||
func (fe *filterLeField) String() string {
|
||||
funcName := "le_field"
|
||||
if fe.excludeEqualValues {
|
||||
funcName = "lt_field"
|
||||
}
|
||||
return fmt.Sprintf("%s%s(%s)", quoteFieldNameIfNeeded(fe.fieldName), funcName, quoteTokenIfNeeded(fe.otherFieldName))
|
||||
}
|
||||
|
||||
func (fe *filterLeField) updateNeededFields(neededFields fieldsSet) {
|
||||
neededFields.add(fe.fieldName)
|
||||
neededFields.add(fe.otherFieldName)
|
||||
}
|
||||
|
||||
func (fe *filterLeField) applyToBlockResult(br *blockResult, bm *bitmap) {
|
||||
if fe.fieldName == fe.otherFieldName {
|
||||
if fe.excludeEqualValues {
|
||||
bm.resetBits()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
c := br.getColumnByName(fe.fieldName)
|
||||
cOther := br.getColumnByName(fe.otherFieldName)
|
||||
|
||||
if c.isConst && cOther.isConst {
|
||||
v := c.valuesEncoded[0]
|
||||
vOther := cOther.valuesEncoded[0]
|
||||
if !leValuesString(v, vOther, fe.excludeEqualValues) {
|
||||
bm.resetBits()
|
||||
}
|
||||
return
|
||||
}
|
||||
if c.isTime && cOther.isTime {
|
||||
// c and cOther point to the same _time column, since only a single _time column may exist
|
||||
if fe.excludeEqualValues {
|
||||
bm.resetBits()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if c.valueType != cOther.valueType {
|
||||
// Slow path - c and cOther have different valueType, so convert them to string values and compare them
|
||||
applyFilterLeString(br, bm, c, cOther, fe.excludeEqualValues)
|
||||
return
|
||||
}
|
||||
|
||||
switch c.valueType {
|
||||
case valueTypeString:
|
||||
applyFilterLeString(br, bm, c, cOther, fe.excludeEqualValues)
|
||||
case valueTypeDict:
|
||||
applyFilterLeDict(br, bm, c, cOther, fe.excludeEqualValues)
|
||||
case valueTypeUint8:
|
||||
applyFilterLeUint(br, bm, c, cOther, fe.excludeEqualValues)
|
||||
case valueTypeUint16:
|
||||
applyFilterLeUint(br, bm, c, cOther, fe.excludeEqualValues)
|
||||
case valueTypeUint32:
|
||||
applyFilterLeUint(br, bm, c, cOther, fe.excludeEqualValues)
|
||||
case valueTypeUint64:
|
||||
applyFilterLeUint(br, bm, c, cOther, fe.excludeEqualValues)
|
||||
case valueTypeInt64:
|
||||
applyFilterLeInt64(br, bm, c, cOther, fe.excludeEqualValues)
|
||||
case valueTypeFloat64:
|
||||
applyFilterLeFloat64(br, bm, c, cOther, fe.excludeEqualValues)
|
||||
case valueTypeIPv4:
|
||||
applyFilterLeUint(br, bm, c, cOther, fe.excludeEqualValues)
|
||||
case valueTypeTimestampISO8601:
|
||||
applyFilterLeUint(br, bm, c, cOther, fe.excludeEqualValues)
|
||||
default:
|
||||
logger.Panicf("FATAL: unknown valueType=%d", c.valueType)
|
||||
}
|
||||
}
|
||||
|
||||
func applyFilterLeString(br *blockResult, bm *bitmap, c, cOther *blockResultColumn, excludeEqualValues bool) {
|
||||
values := c.getValues(br)
|
||||
valuesOther := cOther.getValues(br)
|
||||
bm.forEachSetBit(func(idx int) bool {
|
||||
return leValuesString(values[idx], valuesOther[idx], excludeEqualValues)
|
||||
})
|
||||
}
|
||||
|
||||
func applyFilterLeDict(br *blockResult, bm *bitmap, c, cOther *blockResultColumn, excludeEqualValues bool) {
|
||||
valuesEncoded := c.getValuesEncoded(br)
|
||||
valuesEncodedOther := cOther.getValuesEncoded(br)
|
||||
bm.forEachSetBit(func(idx int) bool {
|
||||
dictIdx := valuesEncoded[idx][0]
|
||||
dictIdxOther := valuesEncodedOther[idx][0]
|
||||
v := c.dictValues[dictIdx]
|
||||
vOther := cOther.dictValues[dictIdxOther]
|
||||
return leValuesString(v, vOther, excludeEqualValues)
|
||||
})
|
||||
}
|
||||
|
||||
func applyFilterLeUint(br *blockResult, bm *bitmap, c, cOther *blockResultColumn, excludeEqualValues bool) {
|
||||
valuesEncoded := c.getValuesEncoded(br)
|
||||
valuesEncodedOther := cOther.getValuesEncoded(br)
|
||||
bm.forEachSetBit(func(idx int) bool {
|
||||
return leValuesString(valuesEncoded[idx], valuesEncodedOther[idx], excludeEqualValues)
|
||||
})
|
||||
}
|
||||
|
||||
func applyFilterLeInt64(br *blockResult, bm *bitmap, c, cOther *blockResultColumn, excludeEqualValues bool) {
|
||||
valuesEncoded := c.getValuesEncoded(br)
|
||||
valuesEncodedOther := cOther.getValuesEncoded(br)
|
||||
bm.forEachSetBit(func(idx int) bool {
|
||||
n := unmarshalInt64(valuesEncoded[idx])
|
||||
nOther := unmarshalInt64(valuesEncodedOther[idx])
|
||||
return leValuesInt64(n, nOther, excludeEqualValues)
|
||||
})
|
||||
}
|
||||
|
||||
func applyFilterLeFloat64(br *blockResult, bm *bitmap, c, cOther *blockResultColumn, excludeEqualValues bool) {
|
||||
valuesEncoded := c.getValuesEncoded(br)
|
||||
valuesEncodedOther := cOther.getValuesEncoded(br)
|
||||
bm.forEachSetBit(func(idx int) bool {
|
||||
f := unmarshalFloat64(valuesEncoded[idx])
|
||||
fOther := unmarshalFloat64(valuesEncodedOther[idx])
|
||||
return leValuesFloat64(f, fOther, excludeEqualValues)
|
||||
})
|
||||
}
|
||||
|
||||
func (fe *filterLeField) applyToBlockSearch(bs *blockSearch, bm *bitmap) {
|
||||
if fe.fieldName == fe.otherFieldName {
|
||||
if fe.excludeEqualValues {
|
||||
bm.resetBits()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
v := bs.getConstColumnValue(fe.fieldName)
|
||||
vOther := bs.getConstColumnValue(fe.otherFieldName)
|
||||
if v != "" || vOther != "" {
|
||||
if v != "" && vOther != "" {
|
||||
if !leValuesString(v, vOther, fe.excludeEqualValues) {
|
||||
bm.resetBits()
|
||||
}
|
||||
return
|
||||
}
|
||||
fe.applyFilterString(bs, bm)
|
||||
return
|
||||
}
|
||||
|
||||
ch := bs.getColumnHeader(fe.fieldName)
|
||||
chOther := bs.getColumnHeader(fe.otherFieldName)
|
||||
if ch == nil || chOther == nil {
|
||||
if ch == nil && chOther == nil {
|
||||
if fe.excludeEqualValues {
|
||||
bm.resetBits()
|
||||
}
|
||||
return
|
||||
}
|
||||
fe.applyFilterString(bs, bm)
|
||||
return
|
||||
}
|
||||
|
||||
if ch.valueType != chOther.valueType {
|
||||
// Slow path - c and cOther have different valueType, so convert them to string values and compare them
|
||||
fe.applyFilterString(bs, bm)
|
||||
return
|
||||
}
|
||||
|
||||
switch ch.valueType {
|
||||
case valueTypeString:
|
||||
fe.applyFilterString(bs, bm)
|
||||
case valueTypeDict:
|
||||
fe.applyFilterDict(bs, bm, ch, chOther)
|
||||
case valueTypeUint8:
|
||||
fe.applyFilterUint(bs, bm, ch, chOther)
|
||||
case valueTypeUint16:
|
||||
fe.applyFilterUint(bs, bm, ch, chOther)
|
||||
case valueTypeUint32:
|
||||
fe.applyFilterUint(bs, bm, ch, chOther)
|
||||
case valueTypeUint64:
|
||||
fe.applyFilterUint(bs, bm, ch, chOther)
|
||||
case valueTypeInt64:
|
||||
fe.applyFilterInt64(bs, bm, ch, chOther)
|
||||
case valueTypeFloat64:
|
||||
fe.applyFilterFloat64(bs, bm, ch, chOther)
|
||||
case valueTypeIPv4:
|
||||
fe.applyFilterUint(bs, bm, ch, chOther)
|
||||
case valueTypeTimestampISO8601:
|
||||
fe.applyFilterUint(bs, bm, ch, chOther)
|
||||
default:
|
||||
logger.Panicf("FATAL: %s: unknown valueType=%d", bs.partPath(), ch.valueType)
|
||||
}
|
||||
}
|
||||
|
||||
func (fe *filterLeField) applyFilterString(bs *blockSearch, bm *bitmap) {
|
||||
br := getBlockResult()
|
||||
br.mustInit(bs, bm)
|
||||
br.initRequestedColumns([]string{fe.fieldName, fe.otherFieldName})
|
||||
|
||||
c := br.getColumnByName(fe.fieldName)
|
||||
cOther := br.getColumnByName(fe.otherFieldName)
|
||||
|
||||
values := c.getValues(br)
|
||||
valuesOther := cOther.getValues(br)
|
||||
|
||||
srcIdx := 0
|
||||
bm.forEachSetBit(func(_ int) bool {
|
||||
ok := leValuesString(values[srcIdx], valuesOther[srcIdx], fe.excludeEqualValues)
|
||||
srcIdx++
|
||||
return ok
|
||||
})
|
||||
|
||||
putBlockResult(br)
|
||||
}
|
||||
|
||||
func (fe *filterLeField) applyFilterDict(bs *blockSearch, bm *bitmap, ch, chOther *columnHeader) {
|
||||
valuesEncoded := bs.getValuesForColumn(ch)
|
||||
valuesEncodedOther := bs.getValuesForColumn(chOther)
|
||||
bm.forEachSetBit(func(idx int) bool {
|
||||
dictIdx := valuesEncoded[idx][0]
|
||||
dictIdxOther := valuesEncodedOther[idx][0]
|
||||
v := ch.valuesDict.values[dictIdx]
|
||||
vOther := chOther.valuesDict.values[dictIdxOther]
|
||||
return leValuesString(v, vOther, fe.excludeEqualValues)
|
||||
})
|
||||
}
|
||||
|
||||
func (fe *filterLeField) applyFilterUint(bs *blockSearch, bm *bitmap, ch, chOther *columnHeader) {
|
||||
valuesEncoded := bs.getValuesForColumn(ch)
|
||||
valuesEncodedOther := bs.getValuesForColumn(chOther)
|
||||
bm.forEachSetBit(func(idx int) bool {
|
||||
return leValuesString(valuesEncoded[idx], valuesEncodedOther[idx], fe.excludeEqualValues)
|
||||
})
|
||||
}
|
||||
|
||||
func (fe *filterLeField) applyFilterInt64(bs *blockSearch, bm *bitmap, ch, chOther *columnHeader) {
|
||||
valuesEncoded := bs.getValuesForColumn(ch)
|
||||
valuesEncodedOther := bs.getValuesForColumn(chOther)
|
||||
bm.forEachSetBit(func(idx int) bool {
|
||||
n := unmarshalInt64(valuesEncoded[idx])
|
||||
nOther := unmarshalInt64(valuesEncodedOther[idx])
|
||||
return leValuesInt64(n, nOther, fe.excludeEqualValues)
|
||||
})
|
||||
}
|
||||
|
||||
func (fe *filterLeField) applyFilterFloat64(bs *blockSearch, bm *bitmap, ch, chOther *columnHeader) {
|
||||
valuesEncoded := bs.getValuesForColumn(ch)
|
||||
valuesEncodedOther := bs.getValuesForColumn(chOther)
|
||||
bm.forEachSetBit(func(idx int) bool {
|
||||
f := unmarshalFloat64(valuesEncoded[idx])
|
||||
fOther := unmarshalFloat64(valuesEncodedOther[idx])
|
||||
return leValuesFloat64(f, fOther, fe.excludeEqualValues)
|
||||
})
|
||||
}
|
||||
|
||||
func leValuesString(a, b string, excludeEqualValues bool) bool {
|
||||
fA := parseMathNumber(a)
|
||||
if !math.IsNaN(fA) {
|
||||
fB := parseMathNumber(b)
|
||||
if !math.IsNaN(fB) {
|
||||
if excludeEqualValues {
|
||||
return fA < fB
|
||||
}
|
||||
return fA <= fB
|
||||
}
|
||||
}
|
||||
if excludeEqualValues {
|
||||
return a < b
|
||||
}
|
||||
return a <= b
|
||||
}
|
||||
|
||||
func leValuesInt64(a, b int64, excludeEqualValues bool) bool {
|
||||
if excludeEqualValues {
|
||||
return a < b
|
||||
}
|
||||
return a <= b
|
||||
}
|
||||
|
||||
func leValuesFloat64(a, b float64, excludeEqualValues bool) bool {
|
||||
if excludeEqualValues {
|
||||
return a < b
|
||||
}
|
||||
return a <= b
|
||||
}
|
||||
1507
lib/logstorage/filter_le_field_test.go
Normal file
1507
lib/logstorage/filter_le_field_test.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -11,8 +11,8 @@ import (
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutils"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/regexutil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/timeutil"
|
||||
)
|
||||
|
||||
type lexer struct {
|
||||
@@ -1461,8 +1461,12 @@ func parseGenericFilter(lex *lexer, fieldName string) (filter, error) {
|
||||
return parseFilterIn(lex, fieldName)
|
||||
case lex.isKeyword("ipv4_range"):
|
||||
return parseFilterIPv4Range(lex, fieldName)
|
||||
case lex.isKeyword("le_field"):
|
||||
return parseFilterLeField(lex, fieldName)
|
||||
case lex.isKeyword("len_range"):
|
||||
return parseFilterLenRange(lex, fieldName)
|
||||
case lex.isKeyword("lt_field"):
|
||||
return parseFilterLtField(lex, fieldName)
|
||||
case lex.isKeyword("range"):
|
||||
return parseFilterRange(lex, fieldName)
|
||||
case lex.isKeyword("re"):
|
||||
@@ -1862,6 +1866,9 @@ func parseInValues(lex *lexer, fieldName string, f filter, iv *inValues) (filter
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if q == nil {
|
||||
return &filterNoop{}, nil
|
||||
}
|
||||
|
||||
iv.q = q
|
||||
iv.qFieldName = qFieldName
|
||||
@@ -1888,6 +1895,28 @@ func parseFilterEqField(lex *lexer, fieldName string) (filter, error) {
|
||||
})
|
||||
}
|
||||
|
||||
func parseFilterLeField(lex *lexer, fieldName string) (filter, error) {
|
||||
return parseFuncArg(lex, fieldName, func(arg string) (filter, error) {
|
||||
fe := &filterLeField{
|
||||
fieldName: fieldName,
|
||||
otherFieldName: arg,
|
||||
}
|
||||
return fe, nil
|
||||
})
|
||||
}
|
||||
|
||||
func parseFilterLtField(lex *lexer, fieldName string) (filter, error) {
|
||||
return parseFuncArg(lex, fieldName, func(arg string) (filter, error) {
|
||||
fe := &filterLeField{
|
||||
fieldName: fieldName,
|
||||
otherFieldName: arg,
|
||||
|
||||
excludeEqualValues: true,
|
||||
}
|
||||
return fe, nil
|
||||
})
|
||||
}
|
||||
|
||||
func parseFilterExact(lex *lexer, fieldName string) (filter, error) {
|
||||
return parseFuncArgMaybePrefix(lex, "exact", fieldName, func(phrase string, isFilterPrefix bool) (filter, error) {
|
||||
if isFilterPrefix {
|
||||
@@ -1906,23 +1935,23 @@ func parseFilterExact(lex *lexer, fieldName string) (filter, error) {
|
||||
}
|
||||
|
||||
func parseFilterRegexp(lex *lexer, fieldName string) (filter, error) {
|
||||
funcName := lex.token
|
||||
return parseFuncArg(lex, fieldName, func(arg string) (filter, error) {
|
||||
re, err := regexutil.NewRegex(arg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid regexp %q for %s(): %w", arg, funcName, err)
|
||||
}
|
||||
fr := &filterRegexp{
|
||||
fieldName: fieldName,
|
||||
re: re,
|
||||
}
|
||||
return fr, nil
|
||||
return newFilterRegexp(fieldName, arg)
|
||||
})
|
||||
}
|
||||
|
||||
func parseFilterTilda(lex *lexer, fieldName string) (filter, error) {
|
||||
lex.nextToken()
|
||||
arg := getCompoundFuncArg(lex)
|
||||
func newFilterRegexp(fieldName, arg string) (filter, error) {
|
||||
// Optimizations for typical regexps generated by Grafana
|
||||
if arg == "" || arg == ".*" {
|
||||
return &filterNoop{}, nil
|
||||
}
|
||||
if arg == ".+" {
|
||||
fp := &filterPrefix{
|
||||
fieldName: fieldName,
|
||||
}
|
||||
return fp, nil
|
||||
}
|
||||
|
||||
re, err := regexutil.NewRegex(arg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid regexp %q: %w", arg, err)
|
||||
@@ -1934,6 +1963,12 @@ func parseFilterTilda(lex *lexer, fieldName string) (filter, error) {
|
||||
return fr, nil
|
||||
}
|
||||
|
||||
func parseFilterTilda(lex *lexer, fieldName string) (filter, error) {
|
||||
lex.nextToken()
|
||||
arg := getCompoundFuncArg(lex)
|
||||
return newFilterRegexp(fieldName, arg)
|
||||
}
|
||||
|
||||
func parseFilterNotTilda(lex *lexer, fieldName string) (filter, error) {
|
||||
f, err := parseFilterTilda(lex, fieldName)
|
||||
if err != nil {
|
||||
@@ -2391,25 +2426,9 @@ func getDayRangeArg(lex *lexer) (int64, string, error) {
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
n := strings.IndexByte(argStr, ':')
|
||||
if n < 0 {
|
||||
return 0, "", fmt.Errorf("invalid format for day_range arg; want 'hh:mm'; got %q", argStr)
|
||||
}
|
||||
hoursStr := argStr[:n]
|
||||
minutesStr := argStr[n+1:]
|
||||
|
||||
hours, ok := tryParseUint64(hoursStr)
|
||||
offset, ok := tryParseHHMM(argStr)
|
||||
if !ok {
|
||||
return 0, "", fmt.Errorf("cannot parse hh from %q; expected format: 'hh:mm'", hoursStr)
|
||||
}
|
||||
minutes, ok := tryParseUint64(minutesStr)
|
||||
if !ok {
|
||||
return 0, "", fmt.Errorf("cannot parse mm from %q; expected format: 'hh:mm'", minutesStr)
|
||||
}
|
||||
|
||||
offset := int64(hours*nsecsPerHour + minutes*nsecsPerMinute)
|
||||
if offset < 0 {
|
||||
offset = 0
|
||||
return 0, "", fmt.Errorf("cannot parse %q as 'hh:mm'", argStr)
|
||||
}
|
||||
if offset >= nsecsPerDay {
|
||||
offset = nsecsPerDay - 1
|
||||
@@ -2803,6 +2822,9 @@ func parseFilterStreamIDIn(lex *lexer) (filter, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if q == nil {
|
||||
return &filterNoop{}, nil
|
||||
}
|
||||
|
||||
fs = &filterStreamID{
|
||||
q: q,
|
||||
@@ -2816,7 +2838,9 @@ func parseInQuery(lex *lexer) (*Query, string, error) {
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("cannot parse in(...) query: %w", err)
|
||||
}
|
||||
|
||||
if q.isStarQuery() {
|
||||
return nil, "", nil
|
||||
}
|
||||
qFieldName, err := getFieldNameFromPipes(q.pipes)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("cannot determine field name for values in 'in(%s)': %w", q, err)
|
||||
@@ -2824,6 +2848,20 @@ func parseInQuery(lex *lexer) (*Query, string, error) {
|
||||
return q, qFieldName, nil
|
||||
}
|
||||
|
||||
func (q *Query) isStarQuery() bool {
|
||||
if len(q.pipes) > 0 {
|
||||
return false
|
||||
}
|
||||
switch t := q.f.(type) {
|
||||
case *filterNoop:
|
||||
return true
|
||||
case *filterPrefix:
|
||||
return len(t.prefix) == 0
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func getFieldNameFromPipes(pipes []pipe) (string, error) {
|
||||
if len(pipes) == 0 {
|
||||
return "", fmt.Errorf("missing 'fields' or 'uniq' pipes at the end of query")
|
||||
@@ -2874,7 +2912,7 @@ func parseTime(lex *lexer) (int64, string, error) {
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
nsecs, err := promutils.ParseTimeAt(s, lex.currentTimestamp)
|
||||
nsecs, err := timeutil.ParseTimeAt(s, lex.currentTimestamp)
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
@@ -2998,7 +3036,9 @@ var reservedKeywords = func() map[string]struct{} {
|
||||
"i",
|
||||
"in",
|
||||
"ipv4_range",
|
||||
"le_field",
|
||||
"len_range",
|
||||
"lt_field",
|
||||
"range",
|
||||
"re",
|
||||
"seq",
|
||||
|
||||
@@ -169,7 +169,7 @@ func TestParseDayRange(t *testing.T) {
|
||||
}
|
||||
|
||||
f("[00:00, 24:00]", 0, nsecsPerDay-1, 0)
|
||||
f("[10:20, 125:00]", 10*nsecsPerHour+20*nsecsPerMinute, nsecsPerDay-1, 0)
|
||||
f("[10:20, 24:00]", 10*nsecsPerHour+20*nsecsPerMinute, nsecsPerDay-1, 0)
|
||||
f("(00:00, 24:00)", 1, nsecsPerDay-2, 0)
|
||||
f("[08:00, 18:00)", 8*nsecsPerHour, 18*nsecsPerHour-1, 0)
|
||||
f("[08:00, 18:00) offset 2h", 8*nsecsPerHour, 18*nsecsPerHour-1, 2*nsecsPerHour)
|
||||
@@ -522,6 +522,19 @@ func TestParseFilterIn(t *testing.T) {
|
||||
f(`a:in(* | fields bar)`, `a`, nil)
|
||||
}
|
||||
|
||||
func TestParseFilterInStar(t *testing.T) {
|
||||
s := "in(*)"
|
||||
|
||||
q, err := ParseQuery(s)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
_, ok := q.f.(*filterNoop)
|
||||
if !ok {
|
||||
t.Fatalf("unexpected filter type; got %T; want *filterNoop; filter: %s", q.f, q.f)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseFilterContainsAll(t *testing.T) {
|
||||
f := func(s, fieldNameExpected string, valuesExpected []string) {
|
||||
t.Helper()
|
||||
@@ -684,7 +697,7 @@ func TestParseFilterRegexp(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
f(`""`, ``)
|
||||
f(`"."`, `.`)
|
||||
f(`foo`, `foo`)
|
||||
f(`"foo.+|bar.*"`, `foo.+|bar.*`)
|
||||
f(`"foo(bar|baz),x[y]"`, `foo(bar|baz),x[y]`)
|
||||
@@ -969,6 +982,9 @@ func TestParseQuery_Success(t *testing.T) {
|
||||
`_stream_id:in(0000007b000001c8302bc96e02e54e5524b3a68ec271e55e,0000007b000001c850d9950ea6196b1a4812081265faa1c7)`)
|
||||
f(`_stream_id:in(_time:5m | fields _stream_id)`, `_stream_id:in(_time:5m | fields _stream_id)`)
|
||||
|
||||
// _stream_id filter with star
|
||||
f(`_stream_id:in(*)`, `*`)
|
||||
|
||||
// _stream filters
|
||||
f(`_stream:{}`, `{}`)
|
||||
f(`_stream:{foo="bar", baz=~"x" OR or!="b", "x=},"="d}{"}`, `{foo="bar",baz=~"x" or "or"!="b","x=},"="d}{"}`)
|
||||
@@ -1066,6 +1082,12 @@ func TestParseQuery_Success(t *testing.T) {
|
||||
f("eq_field", `"eq_field"`)
|
||||
f("eq_field:a", `"eq_field":a`)
|
||||
f("a:eq_field", `a:"eq_field"`)
|
||||
f("le_field", `"le_field"`)
|
||||
f("le_field:a", `"le_field":a`)
|
||||
f("a:le_field", `a:"le_field"`)
|
||||
f("lt_field", `"lt_field"`)
|
||||
f("lt_field:a", `"lt_field":a`)
|
||||
f("a:lt_field", `a:"lt_field"`)
|
||||
f("exact", `"exact"`)
|
||||
f("exact:a", `"exact":a`)
|
||||
f("exact-foo", `"exact-foo"`)
|
||||
@@ -1126,6 +1148,22 @@ func TestParseQuery_Success(t *testing.T) {
|
||||
f(`a:!eq_field(b)`, `!a:eq_field(b)`)
|
||||
f(`a:-eq_field(b)`, `!a:eq_field(b)`)
|
||||
|
||||
// le_field filter
|
||||
f("le_field(foo)", "le_field(foo)")
|
||||
f(`"a":le_field('b')`, "a:le_field(b)")
|
||||
f("-le_field(a)", `!le_field(a)`)
|
||||
f(`-a:le_field(b)`, `!a:le_field(b)`)
|
||||
f(`a:!le_field(b)`, `!a:le_field(b)`)
|
||||
f(`a:-le_field(b)`, `!a:le_field(b)`)
|
||||
|
||||
// lt_field filter
|
||||
f("lt_field(foo)", "lt_field(foo)")
|
||||
f(`"a":lt_field('b')`, "a:lt_field(b)")
|
||||
f("-lt_field(a)", `!lt_field(a)`)
|
||||
f(`-a:lt_field(b)`, `!a:lt_field(b)`)
|
||||
f(`a:!lt_field(b)`, `!a:lt_field(b)`)
|
||||
f(`a:-lt_field(b)`, `!a:lt_field(b)`)
|
||||
|
||||
// exact filter
|
||||
f("exact(foo)", `=foo`)
|
||||
f("exact(foo*)", `=foo*`)
|
||||
@@ -1154,6 +1192,10 @@ func TestParseQuery_Success(t *testing.T) {
|
||||
f(`in("foo bar", baz)`, `in("foo bar",baz)`)
|
||||
f(`foo:in(foo-bar/baz)`, `foo:in("foo-bar/baz")`)
|
||||
|
||||
// in filter with star
|
||||
f(`in(*)`, `*`)
|
||||
f(`foo:in(*)`, `*`)
|
||||
|
||||
// in filter with query
|
||||
f(`in(err|fields x)`, `in(err | fields x)`)
|
||||
f(`ip:in(foo and user:in(admin, moderator)|fields ip)`, `ip:in(foo user:in(admin,moderator) | fields ip)`)
|
||||
@@ -1168,6 +1210,10 @@ func TestParseQuery_Success(t *testing.T) {
|
||||
f(`contains_any("foo bar", baz)`, `contains_any("foo bar",baz)`)
|
||||
f(`foo:contains_any(foo-bar/baz)`, `foo:contains_any("foo-bar/baz")`)
|
||||
|
||||
// contains_any filter with star
|
||||
f(`contains_any(*)`, `*`)
|
||||
f(`foo:contains_any(*)`, `*`)
|
||||
|
||||
// contains_any filter with query
|
||||
f(`contains_any(err|fields x)`, `contains_any(err | fields x)`)
|
||||
f(`ip:contains_any(foo and user:contains_any(admin, moderator)|fields ip)`, `ip:contains_any(foo user:contains_any(admin,moderator) | fields ip)`)
|
||||
@@ -1182,6 +1228,10 @@ func TestParseQuery_Success(t *testing.T) {
|
||||
f(`contains_all("foo bar", baz)`, `contains_all("foo bar",baz)`)
|
||||
f(`foo:contains_all(foo-bar/baz)`, `foo:contains_all("foo-bar/baz")`)
|
||||
|
||||
// contains_all filter with star
|
||||
f(`contains_all(*)`, `*`)
|
||||
f(`foo:contains_all(*)`, `*`)
|
||||
|
||||
// contains_all filter with query
|
||||
f(`contains_all(err|fields x)`, `contains_all(err | fields x)`)
|
||||
f(`ip:contains_all(foo and user:contains_all(admin, moderator)|fields ip)`, `ip:contains_all(foo user:contains_all(admin,moderator) | fields ip)`)
|
||||
@@ -1237,7 +1287,13 @@ func TestParseQuery_Success(t *testing.T) {
|
||||
f(`foo:re(foo-bar/baz.)`, `foo:~"foo-bar/baz."`)
|
||||
f(`~foo.bar.baz !~bar`, `~foo.bar.baz !~bar`)
|
||||
f(`foo:~~foo~ba/ba>z`, `foo:~"~foo~ba/ba>z"`)
|
||||
f(`foo:~'.*'`, `foo:~".*"`)
|
||||
f(`foo:~'.*'`, `*`)
|
||||
f(`foo:~'.+'`, `foo:*`)
|
||||
f(`~".*"`, `*`)
|
||||
f(`~".+"`, `*`)
|
||||
f(`foo bar:~".*"`, `foo`)
|
||||
f(`foo bar:~""`, `foo`)
|
||||
f(`foo bar:~".+"`, `foo bar:*`)
|
||||
|
||||
// seq filter
|
||||
f(`seq()`, `seq()`)
|
||||
@@ -1799,6 +1855,20 @@ func TestParseQuery_Failure(t *testing.T) {
|
||||
f(`eq_field(foo`)
|
||||
f(`eq_field(foo,`)
|
||||
|
||||
// invalid le_field
|
||||
f(`le_field(`)
|
||||
f(`le_field(foo bar)`)
|
||||
f(`le_field(foo, bar)`)
|
||||
f(`le_field(foo`)
|
||||
f(`le_field(foo,`)
|
||||
|
||||
// invalid lt_field
|
||||
f(`lt_field(`)
|
||||
f(`lt_field(foo bar)`)
|
||||
f(`lt_field(foo, bar)`)
|
||||
f(`lt_field(foo`)
|
||||
f(`lt_field(foo,`)
|
||||
|
||||
// invalid exact
|
||||
f(`exact(`)
|
||||
f(`exact(f, b)`)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package logstorage
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
||||
@@ -72,14 +73,17 @@ func (ppp *pipePackProcessor) writeBlock(workerID uint, br *blockResult) {
|
||||
|
||||
shard.rc.name = ppp.resultField
|
||||
|
||||
csAll := br.getColumns()
|
||||
cs := shard.cs[:0]
|
||||
if len(ppp.fields) == 0 {
|
||||
csAll := br.getColumns()
|
||||
cs = append(cs, csAll...)
|
||||
} else {
|
||||
for _, f := range ppp.fields {
|
||||
c := br.getColumnByName(f)
|
||||
cs = append(cs, c)
|
||||
for _, c := range csAll {
|
||||
for _, f := range ppp.fields {
|
||||
if c.name == f || strings.HasSuffix(f, "*") && strings.HasPrefix(c.name, f[:len(f)-1]) {
|
||||
cs = append(cs, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
shard.cs = cs
|
||||
@@ -112,3 +116,15 @@ func (ppp *pipePackProcessor) writeBlock(workerID uint, br *blockResult) {
|
||||
func (ppp *pipePackProcessor) flush() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func fieldsWithOptionalStarsToString(fields []string) string {
|
||||
a := make([]string, len(fields))
|
||||
for i, f := range fields {
|
||||
if strings.HasSuffix(f, "*") {
|
||||
a[i] = quoteTokenIfNeeded(f[:len(f)-1]) + "*"
|
||||
} else {
|
||||
a[i] = quoteTokenIfNeeded(f)
|
||||
}
|
||||
}
|
||||
return strings.Join(a, ", ")
|
||||
}
|
||||
|
||||
@@ -11,13 +11,14 @@ import (
|
||||
type pipePackJSON struct {
|
||||
resultField string
|
||||
|
||||
// the field names and/or field name prefixes to put inside the packed json
|
||||
fields []string
|
||||
}
|
||||
|
||||
func (pp *pipePackJSON) String() string {
|
||||
s := "pack_json"
|
||||
if len(pp.fields) > 0 {
|
||||
s += " fields (" + fieldsToString(pp.fields) + ")"
|
||||
s += " fields (" + fieldsWithOptionalStarsToString(pp.fields) + ")"
|
||||
}
|
||||
if !isMsgFieldName(pp.resultField) {
|
||||
s += " as " + quoteTokenIfNeeded(pp.resultField)
|
||||
|
||||
@@ -14,6 +14,7 @@ func TestParsePipePackJSONSuccess(t *testing.T) {
|
||||
f(`pack_json as x`)
|
||||
f(`pack_json fields (a, b)`)
|
||||
f(`pack_json fields (a, b) as x`)
|
||||
f(`pack_json fields (foo.*, bar, baz.abc.*) as x`)
|
||||
}
|
||||
|
||||
func TestParsePipePackJSONFailure(t *testing.T) {
|
||||
|
||||
@@ -11,13 +11,14 @@ import (
|
||||
type pipePackLogfmt struct {
|
||||
resultField string
|
||||
|
||||
// the field names and/or field name prefixes to put inside the packed json
|
||||
fields []string
|
||||
}
|
||||
|
||||
func (pp *pipePackLogfmt) String() string {
|
||||
s := "pack_logfmt"
|
||||
if len(pp.fields) > 0 {
|
||||
s += " fields (" + fieldsToString(pp.fields) + ")"
|
||||
s += " fields (" + fieldsWithOptionalStarsToString(pp.fields) + ")"
|
||||
}
|
||||
if !isMsgFieldName(pp.resultField) {
|
||||
s += " as " + quoteTokenIfNeeded(pp.resultField)
|
||||
|
||||
@@ -14,6 +14,7 @@ func TestParsePipePackLogfmtSuccess(t *testing.T) {
|
||||
f(`pack_logfmt as x`)
|
||||
f(`pack_logfmt fields (a, b)`)
|
||||
f(`pack_logfmt fields (a, b) as x`)
|
||||
f(`pack_logfmt fields (foo.*, bar, baz.abc.*) as x`)
|
||||
}
|
||||
|
||||
func TestParsePipePackLogfmtFailure(t *testing.T) {
|
||||
@@ -96,10 +97,10 @@ func TestPipePackLogfmt(t *testing.T) {
|
||||
{"_msg", `x`},
|
||||
{"foo", `abc`},
|
||||
{"bar", `cde`},
|
||||
{"a", `foo=abc baz=`},
|
||||
{"a", `foo=abc`},
|
||||
},
|
||||
{
|
||||
{"a", `foo= baz=`},
|
||||
{"a", ""},
|
||||
{"c", "d"},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -367,7 +367,7 @@ func TryParseTimestampRFC3339Nano(s string) (int64, bool) {
|
||||
if digits > 9 {
|
||||
return 0, false
|
||||
}
|
||||
n64, ok := tryParseUint64(s)
|
||||
n64, ok := tryParseDateUint64(s)
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
@@ -394,7 +394,7 @@ func parseTimezoneOffset(s string) (int64, string, bool) {
|
||||
if len(offsetStr) == 0 {
|
||||
return 0, s, false
|
||||
}
|
||||
offsetNsecs, ok := tryParseTimezoneOffset(offsetStr)
|
||||
offsetNsecs, ok := tryParseHHMM(offsetStr)
|
||||
if !ok {
|
||||
return 0, s, false
|
||||
}
|
||||
@@ -404,18 +404,17 @@ func parseTimezoneOffset(s string) (int64, string, bool) {
|
||||
return offsetNsecs, s[:n], true
|
||||
}
|
||||
|
||||
func tryParseTimezoneOffset(offsetStr string) (int64, bool) {
|
||||
n := strings.IndexByte(offsetStr, ':')
|
||||
if n < 0 {
|
||||
func tryParseHHMM(s string) (int64, bool) {
|
||||
if len(s) != len("hh:mm") || s[2] != ':' {
|
||||
return 0, false
|
||||
}
|
||||
hourStr := offsetStr[:n]
|
||||
minuteStr := offsetStr[n+1:]
|
||||
hours, ok := tryParseUint64(hourStr)
|
||||
hourStr := s[:2]
|
||||
minuteStr := s[3:]
|
||||
hours, ok := tryParseDateUint64(hourStr)
|
||||
if !ok || hours > 24 {
|
||||
return 0, false
|
||||
}
|
||||
minutes, ok := tryParseUint64(minuteStr)
|
||||
minutes, ok := tryParseDateUint64(minuteStr)
|
||||
if !ok || minutes > 60 {
|
||||
return 0, false
|
||||
}
|
||||
@@ -451,7 +450,7 @@ func tryParseTimestampISO8601(s string) (int64, bool) {
|
||||
return 0, false
|
||||
}
|
||||
millisecondStr := s[:len("000")]
|
||||
msecs, ok := tryParseUint64(millisecondStr)
|
||||
msecs, ok := tryParseDateUint64(millisecondStr)
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
@@ -472,7 +471,7 @@ func tryParseTimestampSecs(s string) (int64, bool, string) {
|
||||
return 0, false, s
|
||||
}
|
||||
yearStr := s[:len("YYYY")]
|
||||
n, ok := tryParseUint64(yearStr)
|
||||
n, ok := tryParseDateUint64(yearStr)
|
||||
if !ok || n < 1677 || n > 2262 {
|
||||
return 0, false, s
|
||||
}
|
||||
@@ -484,7 +483,7 @@ func tryParseTimestampSecs(s string) (int64, bool, string) {
|
||||
return 0, false, s
|
||||
}
|
||||
monthStr := s[:len("MM")]
|
||||
n, ok = tryParseUint64(monthStr)
|
||||
n, ok = tryParseDateUint64(monthStr)
|
||||
if !ok {
|
||||
return 0, false, s
|
||||
}
|
||||
@@ -501,7 +500,7 @@ func tryParseTimestampSecs(s string) (int64, bool, string) {
|
||||
return 0, false, s
|
||||
}
|
||||
dayStr := s[:len("DD")]
|
||||
n, ok = tryParseUint64(dayStr)
|
||||
n, ok = tryParseDateUint64(dayStr)
|
||||
if !ok {
|
||||
return 0, false, s
|
||||
}
|
||||
@@ -513,7 +512,7 @@ func tryParseTimestampSecs(s string) (int64, bool, string) {
|
||||
return 0, false, s
|
||||
}
|
||||
hourStr := s[:len("HH")]
|
||||
n, ok = tryParseUint64(hourStr)
|
||||
n, ok = tryParseDateUint64(hourStr)
|
||||
if !ok {
|
||||
return 0, false, s
|
||||
}
|
||||
@@ -525,7 +524,7 @@ func tryParseTimestampSecs(s string) (int64, bool, string) {
|
||||
return 0, false, s
|
||||
}
|
||||
minuteStr := s[:len("MM")]
|
||||
n, ok = tryParseUint64(minuteStr)
|
||||
n, ok = tryParseDateUint64(minuteStr)
|
||||
if !ok {
|
||||
return 0, false, s
|
||||
}
|
||||
@@ -534,7 +533,7 @@ func tryParseTimestampSecs(s string) (int64, bool, string) {
|
||||
|
||||
// Parse second
|
||||
secondStr := s[:len("SS")]
|
||||
n, ok = tryParseUint64(secondStr)
|
||||
n, ok = tryParseDateUint64(secondStr)
|
||||
if !ok {
|
||||
return 0, false, s
|
||||
}
|
||||
@@ -554,6 +553,11 @@ func tryParseUint64(s string) (uint64, bool) {
|
||||
if len(s) == 0 || len(s) > len("18_446_744_073_709_551_615") {
|
||||
return 0, false
|
||||
}
|
||||
if len(s) > 1 && s[0] == '0' {
|
||||
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8361
|
||||
return 0, false
|
||||
}
|
||||
|
||||
n := uint64(0)
|
||||
for i := 0; i < len(s); i++ {
|
||||
ch := s[i]
|
||||
@@ -576,6 +580,40 @@ func tryParseUint64(s string) (uint64, bool) {
|
||||
return n, true
|
||||
}
|
||||
|
||||
// tryParseDateUint64 parses s (which is a part of some timestamp) as uint64 value.
|
||||
func tryParseDateUint64(s string) (uint64, bool) {
|
||||
if len(s) == 0 || len(s) > 9 {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
if len(s) == 2 {
|
||||
// fast path for two-digit number, which is used in hours, minutes and seconds
|
||||
if s[0] < '0' || s[0] > '9' {
|
||||
return 0, false
|
||||
}
|
||||
n := 10*uint64(s[0]-'0') + uint64(s[1]-'0')
|
||||
return n, true
|
||||
}
|
||||
|
||||
n := uint64(0)
|
||||
for i := 0; i < len(s); i++ {
|
||||
ch := s[i]
|
||||
if ch < '0' || ch > '9' {
|
||||
return 0, false
|
||||
}
|
||||
if n > ((1<<64)-1)/10 {
|
||||
return 0, false
|
||||
}
|
||||
n *= 10
|
||||
d := uint64(ch - '0')
|
||||
if n > (1<<64)-1-d {
|
||||
return 0, false
|
||||
}
|
||||
n += d
|
||||
}
|
||||
return n, true
|
||||
}
|
||||
|
||||
// tryParseInt64 parses s as int64 value.
|
||||
func tryParseInt64(s string) (int64, bool) {
|
||||
if len(s) == 0 {
|
||||
@@ -645,7 +683,7 @@ func tryParseIPv4(s string) (uint32, bool) {
|
||||
if n <= 0 || n > 3 {
|
||||
return 0, false
|
||||
}
|
||||
v, ok = tryParseUint64(s[:n])
|
||||
v, ok = tryParseDateUint64(s[:n])
|
||||
if !ok || v > 255 {
|
||||
return 0, false
|
||||
}
|
||||
@@ -657,7 +695,7 @@ func tryParseIPv4(s string) (uint32, bool) {
|
||||
if n <= 0 || n > 3 {
|
||||
return 0, false
|
||||
}
|
||||
v, ok = tryParseUint64(s[:n])
|
||||
v, ok = tryParseDateUint64(s[:n])
|
||||
if !ok || v > 255 {
|
||||
return 0, false
|
||||
}
|
||||
@@ -669,7 +707,7 @@ func tryParseIPv4(s string) (uint32, bool) {
|
||||
if n <= 0 || n > 3 {
|
||||
return 0, false
|
||||
}
|
||||
v, ok = tryParseUint64(s[:n])
|
||||
v, ok = tryParseDateUint64(s[:n])
|
||||
if !ok || v > 255 {
|
||||
return 0, false
|
||||
}
|
||||
@@ -677,7 +715,7 @@ func tryParseIPv4(s string) (uint32, bool) {
|
||||
s = s[n+1:]
|
||||
|
||||
// Parse octet 4
|
||||
v, ok = tryParseUint64(s)
|
||||
v, ok = tryParseDateUint64(s)
|
||||
if !ok || v > 255 {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
@@ -583,6 +583,9 @@ func TestTryParseFloat64_Failure(t *testing.T) {
|
||||
|
||||
// Minus in the middle of string isn't allowed
|
||||
f("12-5")
|
||||
|
||||
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8361
|
||||
f("01")
|
||||
}
|
||||
|
||||
func TestTryParseFloat64Exact_Success(t *testing.T) {
|
||||
@@ -716,6 +719,10 @@ func TestTryParseUint64_Failure(t *testing.T) {
|
||||
f("foo")
|
||||
f("1.2")
|
||||
f("1e3")
|
||||
|
||||
// uint with leading zeros shouldn't be parsed as uint
|
||||
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8361
|
||||
f("0123")
|
||||
}
|
||||
|
||||
func TestTryParseInt64_Success(t *testing.T) {
|
||||
@@ -768,6 +775,10 @@ func TestTryParseInt64_Failure(t *testing.T) {
|
||||
f("foo")
|
||||
f("1.2")
|
||||
f("1e3")
|
||||
|
||||
// int with leading zeros shouldn't be parsed as uint
|
||||
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8361
|
||||
f("-0123")
|
||||
}
|
||||
|
||||
func TestMarshalUint8String(t *testing.T) {
|
||||
|
||||
@@ -3,7 +3,6 @@ package prompbmarshal
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/prometheus"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/slicesutil"
|
||||
)
|
||||
|
||||
@@ -29,43 +28,3 @@ func ResetTimeSeries(tss []TimeSeries) []TimeSeries {
|
||||
clear(tss)
|
||||
return tss[:0]
|
||||
}
|
||||
|
||||
// MustParsePromMetrics parses metrics in Prometheus text exposition format from s and returns them.
|
||||
//
|
||||
// Metrics must be delimited with newlines.
|
||||
//
|
||||
// offsetMsecs is added to every timestamp in parsed metrics.
|
||||
//
|
||||
// This function is for testing purposes only. Do not use it in non-test code.
|
||||
func MustParsePromMetrics(s string, offsetMsecs int64) []TimeSeries {
|
||||
var rows prometheus.Rows
|
||||
errLogger := func(s string) {
|
||||
panic(fmt.Errorf("unexpected error when parsing Prometheus metrics: %s", s))
|
||||
}
|
||||
rows.UnmarshalWithErrLogger(s, errLogger)
|
||||
tss := make([]TimeSeries, 0, len(rows.Rows))
|
||||
samples := make([]Sample, 0, len(rows.Rows))
|
||||
for _, row := range rows.Rows {
|
||||
labels := make([]Label, 0, len(row.Tags)+1)
|
||||
labels = append(labels, Label{
|
||||
Name: "__name__",
|
||||
Value: row.Metric,
|
||||
})
|
||||
for _, tag := range row.Tags {
|
||||
labels = append(labels, Label{
|
||||
Name: tag.Key,
|
||||
Value: tag.Value,
|
||||
})
|
||||
}
|
||||
samples = append(samples, Sample{
|
||||
Value: row.Value,
|
||||
Timestamp: row.Timestamp + offsetMsecs,
|
||||
})
|
||||
ts := TimeSeries{
|
||||
Labels: labels,
|
||||
Samples: samples[len(samples)-1:],
|
||||
}
|
||||
tss = append(tss, ts)
|
||||
}
|
||||
return tss
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ import (
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discovery/yandexcloud"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutils"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/proxy"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/timeutil"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -1220,7 +1221,7 @@ func (swc *scrapeWorkConfig) getScrapeWork(target string, extraLabels, metaLabel
|
||||
// Read __scrape_interval__ and __scrape_timeout__ from labels.
|
||||
scrapeInterval := swc.scrapeInterval
|
||||
if s := labels.Get("__scrape_interval__"); len(s) > 0 {
|
||||
d, err := promutils.ParseDuration(s)
|
||||
d, err := timeutil.ParseDuration(s)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse __scrape_interval__=%q: %w", s, err)
|
||||
}
|
||||
@@ -1228,7 +1229,7 @@ func (swc *scrapeWorkConfig) getScrapeWork(target string, extraLabels, metaLabel
|
||||
}
|
||||
scrapeTimeout := swc.scrapeTimeout
|
||||
if s := labels.Get("__scrape_timeout__"); len(s) > 0 {
|
||||
d, err := promutils.ParseDuration(s)
|
||||
d, err := timeutil.ParseDuration(s)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse __scrape_timeout__=%q: %w", s, err)
|
||||
}
|
||||
|
||||
@@ -196,6 +196,8 @@ func appendPodLabelsInternal(ms []*promutils.Labels, gw *groupWatcher, p *Pod, c
|
||||
addr := p.Status.PodIP
|
||||
if cp != nil {
|
||||
addr = discoveryutils.JoinHostPort(addr, cp.ContainerPort)
|
||||
} else if discoveryutils.IsIPv6Host(addr) {
|
||||
addr = discoveryutils.EscapeIPv6Host(addr)
|
||||
}
|
||||
m := promutils.GetLabels()
|
||||
m.Add("__address__", addr)
|
||||
|
||||
@@ -248,18 +248,467 @@ const testPodsList = `
|
||||
}
|
||||
`
|
||||
|
||||
const testPodsListIPv6Address = `
|
||||
{
|
||||
"kind": "PodList",
|
||||
"apiVersion": "v1",
|
||||
"metadata": {
|
||||
"selfLink": "/api/v1/pods",
|
||||
"resourceVersion": "72425"
|
||||
},
|
||||
"items": [
|
||||
{
|
||||
"metadata": {
|
||||
"name": "etcd-m01",
|
||||
"namespace": "kube-system",
|
||||
"selfLink": "/api/v1/namespaces/kube-system/pods/etcd-m01",
|
||||
"uid": "9d328156-75d1-411a-bdd0-aeacb53a38de",
|
||||
"resourceVersion": "22318",
|
||||
"creationTimestamp": "2020-03-16T20:44:30Z",
|
||||
"labels": {
|
||||
"component": "etcd",
|
||||
"tier": "control-plane"
|
||||
},
|
||||
"annotations": {
|
||||
"kubernetes.io/config.hash": "3ec997b76fb6ed3b78da8e0b5676dac4",
|
||||
"kubernetes.io/config.mirror": "3ec997b76fb6ed3b78da8e0b5676dac4",
|
||||
"kubernetes.io/config.seen": "2020-03-16T20:44:26.538136233Z",
|
||||
"kubernetes.io/config.source": "file"
|
||||
},
|
||||
"ownerReferences": [
|
||||
{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Node",
|
||||
"name": "m01",
|
||||
"uid": "b48dd901-ead0-476a-b209-d2d908d65109",
|
||||
"controller": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"spec": {
|
||||
"volumes": [
|
||||
{
|
||||
"name": "etcd-certs",
|
||||
"hostPath": {
|
||||
"path": "/var/lib/minikube/certs/etcd",
|
||||
"type": "DirectoryOrCreate"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "etcd-data",
|
||||
"hostPath": {
|
||||
"path": "/var/lib/minikube/etcd",
|
||||
"type": "DirectoryOrCreate"
|
||||
}
|
||||
}
|
||||
],
|
||||
"containers": [
|
||||
{
|
||||
"name": "terminated-container",
|
||||
"image": "terminated-image",
|
||||
"ports": [
|
||||
{
|
||||
"name": "terminated-port",
|
||||
"containerPort": 4321,
|
||||
"protocol": "TCP"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "etcd",
|
||||
"image": "k8s.gcr.io/etcd:3.4.3-0",
|
||||
"command": [
|
||||
"etcd",
|
||||
"--advertise-client-urls=https://172.17.0.2:2379",
|
||||
"--cert-file=/var/lib/minikube/certs/etcd/server.crt",
|
||||
"--client-cert-auth=true",
|
||||
"--data-dir=/var/lib/minikube/etcd",
|
||||
"--initial-advertise-peer-urls=https://172.17.0.2:2380",
|
||||
"--initial-cluster=m01=https://172.17.0.2:2380",
|
||||
"--key-file=/var/lib/minikube/certs/etcd/server.key",
|
||||
"--listen-client-urls=https://127.0.0.1:2379,https://172.17.0.2:2379",
|
||||
"--listen-metrics-urls=http://127.0.0.1:2381",
|
||||
"--listen-peer-urls=https://172.17.0.2:2380",
|
||||
"--name=m01",
|
||||
"--peer-cert-file=/var/lib/minikube/certs/etcd/peer.crt",
|
||||
"--peer-client-cert-auth=true",
|
||||
"--peer-key-file=/var/lib/minikube/certs/etcd/peer.key",
|
||||
"--peer-trusted-ca-file=/var/lib/minikube/certs/etcd/ca.crt",
|
||||
"--snapshot-count=10000",
|
||||
"--trusted-ca-file=/var/lib/minikube/certs/etcd/ca.crt"
|
||||
],
|
||||
"resources": {
|
||||
|
||||
},
|
||||
"ports": [
|
||||
{
|
||||
"name": "foobar",
|
||||
"containerPort": 1234,
|
||||
"protocol": "TCP"
|
||||
}
|
||||
],
|
||||
"volumeMounts": [
|
||||
{
|
||||
"name": "etcd-data",
|
||||
"mountPath": "/var/lib/minikube/etcd"
|
||||
},
|
||||
{
|
||||
"name": "etcd-certs",
|
||||
"mountPath": "/var/lib/minikube/certs/etcd"
|
||||
}
|
||||
],
|
||||
"livenessProbe": {
|
||||
"httpGet": {
|
||||
"path": "/health",
|
||||
"port": 2381,
|
||||
"host": "127.0.0.1",
|
||||
"scheme": "HTTP"
|
||||
},
|
||||
"initialDelaySeconds": 15,
|
||||
"timeoutSeconds": 15,
|
||||
"periodSeconds": 10,
|
||||
"successThreshold": 1,
|
||||
"failureThreshold": 8
|
||||
},
|
||||
"terminationMessagePath": "/dev/termination-log",
|
||||
"terminationMessagePolicy": "File",
|
||||
"imagePullPolicy": "IfNotPresent"
|
||||
}
|
||||
],
|
||||
"restartPolicy": "Always",
|
||||
"terminationGracePeriodSeconds": 30,
|
||||
"dnsPolicy": "ClusterFirst",
|
||||
"nodeName": "test-node",
|
||||
"hostNetwork": true,
|
||||
"securityContext": {
|
||||
|
||||
},
|
||||
"schedulerName": "default-scheduler",
|
||||
"tolerations": [
|
||||
{
|
||||
"operator": "Exists",
|
||||
"effect": "NoExecute"
|
||||
}
|
||||
],
|
||||
"priorityClassName": "system-cluster-critical",
|
||||
"priority": 2000000000,
|
||||
"enableServiceLinks": true
|
||||
},
|
||||
"status": {
|
||||
"phase": "Running",
|
||||
"conditions": [
|
||||
{
|
||||
"type": "Initialized",
|
||||
"status": "True",
|
||||
"lastProbeTime": null,
|
||||
"lastTransitionTime": "2020-03-20T13:30:29Z"
|
||||
},
|
||||
{
|
||||
"type": "Ready",
|
||||
"status": "True",
|
||||
"lastProbeTime": null,
|
||||
"lastTransitionTime": "2020-03-20T13:30:32Z"
|
||||
},
|
||||
{
|
||||
"type": "ContainersReady",
|
||||
"status": "True",
|
||||
"lastProbeTime": null,
|
||||
"lastTransitionTime": "2020-03-20T13:30:32Z"
|
||||
},
|
||||
{
|
||||
"type": "PodScheduled",
|
||||
"status": "True",
|
||||
"lastProbeTime": null,
|
||||
"lastTransitionTime": "2020-03-20T13:30:29Z"
|
||||
}
|
||||
],
|
||||
"hostIP": "fd01:10:100:0:e38f:6f4b:9c53:9e4e",
|
||||
"podIP": "fd01:10:100:0:e38f:6f4b:9c53:9e4e",
|
||||
"podIPs": [
|
||||
{
|
||||
"ip": "fd01:10:100:0:e38f:6f4b:9c53:9e4e"
|
||||
}
|
||||
],
|
||||
"startTime": "2020-03-20T13:30:29Z",
|
||||
"containerStatuses": [
|
||||
{
|
||||
"name": "terminated-container",
|
||||
"state": {
|
||||
"terminated": {
|
||||
"exitCode": 432
|
||||
}
|
||||
},
|
||||
"containerID": "terminated-container-id"
|
||||
},
|
||||
{
|
||||
"name": "etcd",
|
||||
"state": {
|
||||
"running": {
|
||||
"startedAt": "2020-03-20T13:30:30Z"
|
||||
}
|
||||
},
|
||||
"lastState": {
|
||||
"terminated": {
|
||||
"exitCode": 0,
|
||||
"reason": "Completed",
|
||||
"startedAt": "2020-03-17T18:56:24Z",
|
||||
"finishedAt": "2020-03-20T13:29:54Z",
|
||||
"containerID": "docker://24eea6f192d4598fcc129b5f163a02d1457137f4ec34e8c80c6049a65604cb07"
|
||||
}
|
||||
},
|
||||
"ready": true,
|
||||
"restartCount": 2,
|
||||
"image": "k8s.gcr.io/etcd:3.4.3-0",
|
||||
"imageID": "docker-pullable://k8s.gcr.io/etcd@sha256:4afb99b4690b418ffc2ceb67e1a17376457e441c1f09ab55447f0aaf992fa646",
|
||||
"containerID": "docker://a28f0800855008485376c1eece1cf61de97cb7026b9188d138b0d55d92fc2f5c",
|
||||
"started": true
|
||||
}
|
||||
],
|
||||
"qosClass": "BestEffort"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
`
|
||||
|
||||
const testPodsListIPv6AddressNoPorts = `
|
||||
{
|
||||
"kind": "PodList",
|
||||
"apiVersion": "v1",
|
||||
"metadata": {
|
||||
"selfLink": "/api/v1/pods",
|
||||
"resourceVersion": "72425"
|
||||
},
|
||||
"items": [
|
||||
{
|
||||
"metadata": {
|
||||
"name": "etcd-m01",
|
||||
"namespace": "kube-system",
|
||||
"selfLink": "/api/v1/namespaces/kube-system/pods/etcd-m01",
|
||||
"uid": "9d328156-75d1-411a-bdd0-aeacb53a38de",
|
||||
"resourceVersion": "22318",
|
||||
"creationTimestamp": "2020-03-16T20:44:30Z",
|
||||
"labels": {
|
||||
"component": "etcd",
|
||||
"tier": "control-plane"
|
||||
},
|
||||
"annotations": {
|
||||
"kubernetes.io/config.hash": "3ec997b76fb6ed3b78da8e0b5676dac4",
|
||||
"kubernetes.io/config.mirror": "3ec997b76fb6ed3b78da8e0b5676dac4",
|
||||
"kubernetes.io/config.seen": "2020-03-16T20:44:26.538136233Z",
|
||||
"kubernetes.io/config.source": "file"
|
||||
},
|
||||
"ownerReferences": [
|
||||
{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Node",
|
||||
"name": "m01",
|
||||
"uid": "b48dd901-ead0-476a-b209-d2d908d65109",
|
||||
"controller": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"spec": {
|
||||
"volumes": [
|
||||
{
|
||||
"name": "etcd-certs",
|
||||
"hostPath": {
|
||||
"path": "/var/lib/minikube/certs/etcd",
|
||||
"type": "DirectoryOrCreate"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "etcd-data",
|
||||
"hostPath": {
|
||||
"path": "/var/lib/minikube/etcd",
|
||||
"type": "DirectoryOrCreate"
|
||||
}
|
||||
}
|
||||
],
|
||||
"containers": [
|
||||
{
|
||||
"name": "terminated-container",
|
||||
"image": "terminated-image",
|
||||
"ports": [
|
||||
{
|
||||
"name": "terminated-port",
|
||||
"containerPort": 4321,
|
||||
"protocol": "TCP"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "etcd",
|
||||
"image": "k8s.gcr.io/etcd:3.4.3-0",
|
||||
"command": [
|
||||
"etcd",
|
||||
"--advertise-client-urls=https://172.17.0.2:2379",
|
||||
"--cert-file=/var/lib/minikube/certs/etcd/server.crt",
|
||||
"--client-cert-auth=true",
|
||||
"--data-dir=/var/lib/minikube/etcd",
|
||||
"--initial-advertise-peer-urls=https://172.17.0.2:2380",
|
||||
"--initial-cluster=m01=https://172.17.0.2:2380",
|
||||
"--key-file=/var/lib/minikube/certs/etcd/server.key",
|
||||
"--listen-client-urls=https://127.0.0.1:2379,https://172.17.0.2:2379",
|
||||
"--listen-metrics-urls=http://127.0.0.1:2381",
|
||||
"--listen-peer-urls=https://172.17.0.2:2380",
|
||||
"--name=m01",
|
||||
"--peer-cert-file=/var/lib/minikube/certs/etcd/peer.crt",
|
||||
"--peer-client-cert-auth=true",
|
||||
"--peer-key-file=/var/lib/minikube/certs/etcd/peer.key",
|
||||
"--peer-trusted-ca-file=/var/lib/minikube/certs/etcd/ca.crt",
|
||||
"--snapshot-count=10000",
|
||||
"--trusted-ca-file=/var/lib/minikube/certs/etcd/ca.crt"
|
||||
],
|
||||
"resources": {
|
||||
|
||||
},
|
||||
"ports": [
|
||||
|
||||
],
|
||||
"volumeMounts": [
|
||||
{
|
||||
"name": "etcd-data",
|
||||
"mountPath": "/var/lib/minikube/etcd"
|
||||
},
|
||||
{
|
||||
"name": "etcd-certs",
|
||||
"mountPath": "/var/lib/minikube/certs/etcd"
|
||||
}
|
||||
],
|
||||
"livenessProbe": {
|
||||
"httpGet": {
|
||||
"path": "/health",
|
||||
"port": 2381,
|
||||
"host": "127.0.0.1",
|
||||
"scheme": "HTTP"
|
||||
},
|
||||
"initialDelaySeconds": 15,
|
||||
"timeoutSeconds": 15,
|
||||
"periodSeconds": 10,
|
||||
"successThreshold": 1,
|
||||
"failureThreshold": 8
|
||||
},
|
||||
"terminationMessagePath": "/dev/termination-log",
|
||||
"terminationMessagePolicy": "File",
|
||||
"imagePullPolicy": "IfNotPresent"
|
||||
}
|
||||
],
|
||||
"restartPolicy": "Always",
|
||||
"terminationGracePeriodSeconds": 30,
|
||||
"dnsPolicy": "ClusterFirst",
|
||||
"nodeName": "test-node",
|
||||
"hostNetwork": true,
|
||||
"securityContext": {
|
||||
|
||||
},
|
||||
"schedulerName": "default-scheduler",
|
||||
"tolerations": [
|
||||
{
|
||||
"operator": "Exists",
|
||||
"effect": "NoExecute"
|
||||
}
|
||||
],
|
||||
"priorityClassName": "system-cluster-critical",
|
||||
"priority": 2000000000,
|
||||
"enableServiceLinks": true
|
||||
},
|
||||
"status": {
|
||||
"phase": "Running",
|
||||
"conditions": [
|
||||
{
|
||||
"type": "Initialized",
|
||||
"status": "True",
|
||||
"lastProbeTime": null,
|
||||
"lastTransitionTime": "2020-03-20T13:30:29Z"
|
||||
},
|
||||
{
|
||||
"type": "Ready",
|
||||
"status": "True",
|
||||
"lastProbeTime": null,
|
||||
"lastTransitionTime": "2020-03-20T13:30:32Z"
|
||||
},
|
||||
{
|
||||
"type": "ContainersReady",
|
||||
"status": "True",
|
||||
"lastProbeTime": null,
|
||||
"lastTransitionTime": "2020-03-20T13:30:32Z"
|
||||
},
|
||||
{
|
||||
"type": "PodScheduled",
|
||||
"status": "True",
|
||||
"lastProbeTime": null,
|
||||
"lastTransitionTime": "2020-03-20T13:30:29Z"
|
||||
}
|
||||
],
|
||||
"hostIP": "fd01:10:100:0:e38f:6f4b:9c53:9e4e",
|
||||
"podIP": "fd01:10:100:0:e38f:6f4b:9c53:9e4e",
|
||||
"podIPs": [
|
||||
{
|
||||
"ip": "fd01:10:100:0:e38f:6f4b:9c53:9e4e"
|
||||
}
|
||||
],
|
||||
"startTime": "2020-03-20T13:30:29Z",
|
||||
"containerStatuses": [
|
||||
{
|
||||
"name": "terminated-container",
|
||||
"state": {
|
||||
"terminated": {
|
||||
"exitCode": 432
|
||||
}
|
||||
},
|
||||
"containerID": "terminated-container-id"
|
||||
},
|
||||
{
|
||||
"name": "etcd",
|
||||
"state": {
|
||||
"running": {
|
||||
"startedAt": "2020-03-20T13:30:30Z"
|
||||
}
|
||||
},
|
||||
"lastState": {
|
||||
"terminated": {
|
||||
"exitCode": 0,
|
||||
"reason": "Completed",
|
||||
"startedAt": "2020-03-17T18:56:24Z",
|
||||
"finishedAt": "2020-03-20T13:29:54Z",
|
||||
"containerID": "docker://24eea6f192d4598fcc129b5f163a02d1457137f4ec34e8c80c6049a65604cb07"
|
||||
}
|
||||
},
|
||||
"ready": true,
|
||||
"restartCount": 2,
|
||||
"image": "k8s.gcr.io/etcd:3.4.3-0",
|
||||
"imageID": "docker-pullable://k8s.gcr.io/etcd@sha256:4afb99b4690b418ffc2ceb67e1a17376457e441c1f09ab55447f0aaf992fa646",
|
||||
"containerID": "docker://a28f0800855008485376c1eece1cf61de97cb7026b9188d138b0d55d92fc2f5c",
|
||||
"started": true
|
||||
}
|
||||
],
|
||||
"qosClass": "BestEffort"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
`
|
||||
|
||||
func TestParsePodListSuccess(t *testing.T) {
|
||||
r := bytes.NewBufferString(testPodsList)
|
||||
objectsByKey, meta, err := parsePodList(r)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
f := func(response string, expectedLabels []*promutils.Labels) {
|
||||
t.Helper()
|
||||
r := bytes.NewBufferString(response)
|
||||
objectsByKey, meta, err := parsePodList(r)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
expectedResourceVersion := "72425"
|
||||
if meta.ResourceVersion != expectedResourceVersion {
|
||||
t.Fatalf("unexpected resource version; got %s; want %s", meta.ResourceVersion, expectedResourceVersion)
|
||||
}
|
||||
sortedLabelss := getSortedLabelss(objectsByKey)
|
||||
if !areEqualLabelss(sortedLabelss, expectedLabels) {
|
||||
t.Fatalf("unexpected labels:\ngot\n%v\nwant\n%v", sortedLabelss, expectedLabels)
|
||||
}
|
||||
}
|
||||
expectedResourceVersion := "72425"
|
||||
if meta.ResourceVersion != expectedResourceVersion {
|
||||
t.Fatalf("unexpected resource version; got %s; want %s", meta.ResourceVersion, expectedResourceVersion)
|
||||
}
|
||||
sortedLabelss := getSortedLabelss(objectsByKey)
|
||||
expectedLabelss := []*promutils.Labels{
|
||||
|
||||
f(testPodsList, []*promutils.Labels{
|
||||
promutils.NewLabelsFromMap(map[string]string{
|
||||
"__address__": "172.17.0.2:1234",
|
||||
|
||||
@@ -300,8 +749,87 @@ func TestParsePodListSuccess(t *testing.T) {
|
||||
"__meta_kubernetes_pod_annotationpresent_kubernetes_io_config_seen": "true",
|
||||
"__meta_kubernetes_pod_annotationpresent_kubernetes_io_config_source": "true",
|
||||
}),
|
||||
}
|
||||
if !areEqualLabelss(sortedLabelss, expectedLabelss) {
|
||||
t.Fatalf("unexpected labels:\ngot\n%v\nwant\n%v", sortedLabelss, expectedLabelss)
|
||||
}
|
||||
})
|
||||
|
||||
f(testPodsListIPv6Address, []*promutils.Labels{
|
||||
promutils.NewLabelsFromMap(map[string]string{
|
||||
"__address__": "[fd01:10:100:0:e38f:6f4b:9c53:9e4e]:1234",
|
||||
|
||||
"__meta_kubernetes_namespace": "kube-system",
|
||||
"__meta_kubernetes_node_label_node_label": "xyz",
|
||||
"__meta_kubernetes_node_labelpresent_node_label": "true",
|
||||
"__meta_kubernetes_node_name": "test-node",
|
||||
"__meta_kubernetes_pod_name": "etcd-m01",
|
||||
"__meta_kubernetes_pod_ip": "fd01:10:100:0:e38f:6f4b:9c53:9e4e",
|
||||
"__meta_kubernetes_pod_container_image": "k8s.gcr.io/etcd:3.4.3-0",
|
||||
"__meta_kubernetes_pod_container_name": "etcd",
|
||||
"__meta_kubernetes_pod_container_port_name": "foobar",
|
||||
"__meta_kubernetes_pod_container_port_number": "1234",
|
||||
"__meta_kubernetes_pod_container_port_protocol": "TCP",
|
||||
"__meta_kubernetes_pod_ready": "true",
|
||||
"__meta_kubernetes_pod_phase": "Running",
|
||||
"__meta_kubernetes_pod_node_name": "test-node",
|
||||
"__meta_kubernetes_pod_host_ip": "fd01:10:100:0:e38f:6f4b:9c53:9e4e",
|
||||
"__meta_kubernetes_pod_uid": "9d328156-75d1-411a-bdd0-aeacb53a38de",
|
||||
"__meta_kubernetes_pod_controller_kind": "Node",
|
||||
"__meta_kubernetes_pod_controller_name": "m01",
|
||||
"__meta_kubernetes_pod_container_init": "false",
|
||||
"__meta_kubernetes_pod_container_id": "docker://a28f0800855008485376c1eece1cf61de97cb7026b9188d138b0d55d92fc2f5c",
|
||||
|
||||
"__meta_kubernetes_pod_label_component": "etcd",
|
||||
"__meta_kubernetes_pod_label_tier": "control-plane",
|
||||
|
||||
"__meta_kubernetes_pod_labelpresent_component": "true",
|
||||
"__meta_kubernetes_pod_labelpresent_tier": "true",
|
||||
|
||||
"__meta_kubernetes_pod_annotation_kubernetes_io_config_hash": "3ec997b76fb6ed3b78da8e0b5676dac4",
|
||||
"__meta_kubernetes_pod_annotation_kubernetes_io_config_mirror": "3ec997b76fb6ed3b78da8e0b5676dac4",
|
||||
"__meta_kubernetes_pod_annotation_kubernetes_io_config_seen": "2020-03-16T20:44:26.538136233Z",
|
||||
"__meta_kubernetes_pod_annotation_kubernetes_io_config_source": "file",
|
||||
|
||||
"__meta_kubernetes_pod_annotationpresent_kubernetes_io_config_hash": "true",
|
||||
"__meta_kubernetes_pod_annotationpresent_kubernetes_io_config_mirror": "true",
|
||||
"__meta_kubernetes_pod_annotationpresent_kubernetes_io_config_seen": "true",
|
||||
"__meta_kubernetes_pod_annotationpresent_kubernetes_io_config_source": "true",
|
||||
}),
|
||||
})
|
||||
f(testPodsListIPv6AddressNoPorts, []*promutils.Labels{
|
||||
promutils.NewLabelsFromMap(map[string]string{
|
||||
"__address__": "[fd01:10:100:0:e38f:6f4b:9c53:9e4e]",
|
||||
|
||||
"__meta_kubernetes_namespace": "kube-system",
|
||||
"__meta_kubernetes_node_label_node_label": "xyz",
|
||||
"__meta_kubernetes_node_labelpresent_node_label": "true",
|
||||
"__meta_kubernetes_node_name": "test-node",
|
||||
"__meta_kubernetes_pod_name": "etcd-m01",
|
||||
"__meta_kubernetes_pod_ip": "fd01:10:100:0:e38f:6f4b:9c53:9e4e",
|
||||
"__meta_kubernetes_pod_container_image": "k8s.gcr.io/etcd:3.4.3-0",
|
||||
"__meta_kubernetes_pod_container_name": "etcd",
|
||||
"__meta_kubernetes_pod_ready": "true",
|
||||
"__meta_kubernetes_pod_phase": "Running",
|
||||
"__meta_kubernetes_pod_node_name": "test-node",
|
||||
"__meta_kubernetes_pod_host_ip": "fd01:10:100:0:e38f:6f4b:9c53:9e4e",
|
||||
"__meta_kubernetes_pod_uid": "9d328156-75d1-411a-bdd0-aeacb53a38de",
|
||||
"__meta_kubernetes_pod_controller_kind": "Node",
|
||||
"__meta_kubernetes_pod_controller_name": "m01",
|
||||
"__meta_kubernetes_pod_container_init": "false",
|
||||
"__meta_kubernetes_pod_container_id": "docker://a28f0800855008485376c1eece1cf61de97cb7026b9188d138b0d55d92fc2f5c",
|
||||
|
||||
"__meta_kubernetes_pod_label_component": "etcd",
|
||||
"__meta_kubernetes_pod_label_tier": "control-plane",
|
||||
|
||||
"__meta_kubernetes_pod_labelpresent_component": "true",
|
||||
"__meta_kubernetes_pod_labelpresent_tier": "true",
|
||||
|
||||
"__meta_kubernetes_pod_annotation_kubernetes_io_config_hash": "3ec997b76fb6ed3b78da8e0b5676dac4",
|
||||
"__meta_kubernetes_pod_annotation_kubernetes_io_config_mirror": "3ec997b76fb6ed3b78da8e0b5676dac4",
|
||||
"__meta_kubernetes_pod_annotation_kubernetes_io_config_seen": "2020-03-16T20:44:26.538136233Z",
|
||||
"__meta_kubernetes_pod_annotation_kubernetes_io_config_source": "file",
|
||||
|
||||
"__meta_kubernetes_pod_annotationpresent_kubernetes_io_config_hash": "true",
|
||||
"__meta_kubernetes_pod_annotationpresent_kubernetes_io_config_mirror": "true",
|
||||
"__meta_kubernetes_pod_annotationpresent_kubernetes_io_config_seen": "true",
|
||||
"__meta_kubernetes_pod_annotationpresent_kubernetes_io_config_source": "true",
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ var invalidLabelCharRE = regexp.MustCompile(`[^a-zA-Z0-9_]`)
|
||||
func JoinHostPort(host string, port int) string {
|
||||
bb := bbPool.Get()
|
||||
b := bb.B[:0]
|
||||
isIPv6 := strings.IndexByte(host, ':') >= 0
|
||||
isIPv6 := IsIPv6Host(host)
|
||||
if isIPv6 {
|
||||
b = append(b, '[')
|
||||
}
|
||||
@@ -47,6 +47,24 @@ func JoinHostPort(host string, port int) string {
|
||||
return s
|
||||
}
|
||||
|
||||
// IsIPv6Host returns true if host is ipv6 address.
|
||||
func IsIPv6Host(host string) bool {
|
||||
return strings.IndexByte(host, ':') >= 0
|
||||
}
|
||||
|
||||
// EscapeIPv6Host escapes ipv6 host with square brackets.
|
||||
// Note that host must be ipv6 address.
|
||||
func EscapeIPv6Host(host string) string {
|
||||
bb := bbPool.Get()
|
||||
b := bb.B[:0]
|
||||
b = append(b, '[')
|
||||
b = append(b, host...)
|
||||
b = append(b, ']')
|
||||
s := bytesutil.InternBytes(b)
|
||||
bbPool.Put(bb)
|
||||
return s
|
||||
}
|
||||
|
||||
var bbPool bytesutil.ByteBufferPool
|
||||
|
||||
// TestEqualLabelss tests whether got are equal to want.
|
||||
|
||||
@@ -65,3 +65,26 @@ func testSanitizeLabelName() error {
|
||||
}
|
||||
return f("foo-bar/baz", "foo_bar_baz")
|
||||
}
|
||||
|
||||
func TestIsIPv6Host(t *testing.T) {
|
||||
f := func(host string, isIPv6Expected bool) {
|
||||
t.Helper()
|
||||
isIPv6 := IsIPv6Host(host)
|
||||
if isIPv6 != isIPv6Expected {
|
||||
t.Fatalf("unexpected result for IsIPv6Host(%q); got %v; want %v", host, isIPv6, isIPv6Expected)
|
||||
}
|
||||
}
|
||||
f("foo", false)
|
||||
f("1:32::43", true)
|
||||
}
|
||||
|
||||
func TestEscapeIPv6Host(t *testing.T) {
|
||||
f := func(host string, escapedHostExpected string) {
|
||||
t.Helper()
|
||||
escapedHost := EscapeIPv6Host(host)
|
||||
if escapedHost != escapedHostExpected {
|
||||
t.Fatalf("unexpected result for EscapeIPv6Host(%q); got %q; want %q", host, escapedHost, escapedHostExpected)
|
||||
}
|
||||
}
|
||||
f("1:32::43", "[1:32::43]")
|
||||
}
|
||||
|
||||
@@ -775,7 +775,7 @@ func parsePromRow(data string) *parser.Row {
|
||||
}
|
||||
|
||||
func parseData(data string) []prompbmarshal.TimeSeries {
|
||||
return prompbmarshal.MustParsePromMetrics(data, 0)
|
||||
return parser.MustParsePromMetrics(data, 0)
|
||||
}
|
||||
|
||||
func expectEqualTimeseries(tss, tssExpected []prompbmarshal.TimeSeries) error {
|
||||
|
||||
@@ -44,12 +44,3 @@ func (pd *Duration) Duration() time.Duration {
|
||||
}
|
||||
return pd.D
|
||||
}
|
||||
|
||||
// ParseDuration parses duration string in Prometheus format
|
||||
func ParseDuration(s string) (time.Duration, error) {
|
||||
ms, err := metricsql.DurationValue(s, 0)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return time.Duration(ms) * time.Millisecond, nil
|
||||
}
|
||||
|
||||
@@ -3,13 +3,15 @@ package promutils
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/timeutil"
|
||||
)
|
||||
|
||||
func TestDuration(t *testing.T) {
|
||||
if _, err := ParseDuration("foobar"); err == nil {
|
||||
if _, err := timeutil.ParseDuration("foobar"); err == nil {
|
||||
t.Fatalf("expecting error for invalid duration")
|
||||
}
|
||||
dNative, err := ParseDuration("1w")
|
||||
dNative, err := timeutil.ParseDuration("1w")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
47
lib/protoparser/prometheus/util.go
Normal file
47
lib/protoparser/prometheus/util.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package prometheus
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
|
||||
)
|
||||
|
||||
// MustParsePromMetrics parses metrics in Prometheus text exposition format from s and returns them.
|
||||
//
|
||||
// Metrics must be delimited with newlines.
|
||||
//
|
||||
// offsetMsecs is added to every timestamp in parsed metrics.
|
||||
//
|
||||
// This function is for testing purposes only. Do not use it in non-test code.
|
||||
func MustParsePromMetrics(s string, offsetMsecs int64) []prompbmarshal.TimeSeries {
|
||||
var rows Rows
|
||||
errLogger := func(s string) {
|
||||
panic(fmt.Errorf("unexpected error when parsing Prometheus metrics: %s", s))
|
||||
}
|
||||
rows.UnmarshalWithErrLogger(s, errLogger)
|
||||
tss := make([]prompbmarshal.TimeSeries, 0, len(rows.Rows))
|
||||
samples := make([]prompbmarshal.Sample, 0, len(rows.Rows))
|
||||
for _, row := range rows.Rows {
|
||||
labels := make([]prompbmarshal.Label, 0, len(row.Tags)+1)
|
||||
labels = append(labels, prompbmarshal.Label{
|
||||
Name: "__name__",
|
||||
Value: row.Metric,
|
||||
})
|
||||
for _, tag := range row.Tags {
|
||||
labels = append(labels, prompbmarshal.Label{
|
||||
Name: tag.Key,
|
||||
Value: tag.Value,
|
||||
})
|
||||
}
|
||||
samples = append(samples, prompbmarshal.Sample{
|
||||
Value: row.Value,
|
||||
Timestamp: row.Timestamp + offsetMsecs,
|
||||
})
|
||||
ts := prompbmarshal.TimeSeries{
|
||||
Labels: labels,
|
||||
Samples: samples[len(samples)-1:],
|
||||
}
|
||||
tss = append(tss, ts)
|
||||
}
|
||||
return tss
|
||||
}
|
||||
@@ -42,7 +42,7 @@ type PromRegex struct {
|
||||
substrDotPlus string
|
||||
|
||||
// orValues contains or values for the suffix regex.
|
||||
// For example, orValues contain ["foo","bar","baz"] for regex suffix="foo|bar|baz"
|
||||
// For example, orValues contain ["foo","bar","baz"] for regex="foo|bar|baz"
|
||||
orValues []string
|
||||
|
||||
// reSuffixMatcher contains fast matcher for "^suffix$"
|
||||
|
||||
@@ -38,7 +38,7 @@ type Regex struct {
|
||||
substrDotPlus string
|
||||
|
||||
// orValues contains or values for the suffix regex.
|
||||
// For example, orValues contain ["foo","bar","baz"] for regex suffix="foo|bar|baz"
|
||||
// For example, orValues contain ["foo","bar","baz"] for regex="foo|bar|baz"
|
||||
orValues []string
|
||||
|
||||
// suffixRe is the regexp for suffix
|
||||
@@ -85,6 +85,9 @@ func NewRegex(expr string) (*Regex, error) {
|
||||
// MatchString returns true if s matches r.
|
||||
func (r *Regex) MatchString(s string) bool {
|
||||
if r.isOnlyPrefix {
|
||||
if len(r.prefix) == 0 {
|
||||
return true
|
||||
}
|
||||
return strings.Contains(s, r.prefix)
|
||||
}
|
||||
|
||||
|
||||
@@ -1751,7 +1751,9 @@ func (db *indexDB) searchMetricIDs(qt *querytracer.Tracer, tfss []*TagFilters, t
|
||||
is := extDB.getIndexSearch(deadline)
|
||||
extMetricIDs, err = is.searchMetricIDs(qtChild, tfss, tr, maxMetrics)
|
||||
extDB.putIndexSearch(is)
|
||||
extDB.putMetricIDsToTagFiltersCache(qtChild, extMetricIDs, tfKeyExtBuf.B)
|
||||
if err == nil {
|
||||
extDB.putMetricIDsToTagFiltersCache(qtChild, extMetricIDs, tfKeyExtBuf.B)
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error when searching for metricIDs in the previous indexdb: %w", err)
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutils"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/timeutil"
|
||||
)
|
||||
|
||||
// partHeader represents part header.
|
||||
@@ -58,7 +58,7 @@ func (ph *partHeader) readMinDedupInterval(partPath string) error {
|
||||
}
|
||||
return fmt.Errorf("cannot read %q: %w", filePath, err)
|
||||
}
|
||||
dedupInterval, err := promutils.ParseDuration(string(data))
|
||||
dedupInterval, err := timeutil.ParseDuration(string(data))
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot parse minimum dedup interval %q at %q: %w", data, filePath, err)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/prometheus"
|
||||
)
|
||||
|
||||
func TestDeduplicator(t *testing.T) {
|
||||
@@ -18,7 +19,7 @@ func TestDeduplicator(t *testing.T) {
|
||||
}
|
||||
|
||||
offsetMsecs := time.Now().Add(time.Minute).UnixMilli()
|
||||
tss := prompbmarshal.MustParsePromMetrics(`
|
||||
tss := prometheus.MustParsePromMetrics(`
|
||||
foo{instance="x",job="aaa",pod="sdfd-dfdfdfs",node="aosijjewrerfd",namespace="asdff",container="ohohffd"} 123
|
||||
bar{instance="x",job="aaa",pod="sdfd-dfdfdfs",node="aosijjewrerfd",namespace="asdff",container="ohohffd"} 34.54
|
||||
x 8943 1
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promrelabel"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/prometheus"
|
||||
)
|
||||
|
||||
func TestAggregatorsFailure(t *testing.T) {
|
||||
@@ -278,7 +279,7 @@ func TestAggregatorsSuccess(t *testing.T) {
|
||||
|
||||
// Push the inputMetrics to Aggregators
|
||||
offsetMsecs := time.Now().Add(15 * time.Second).UnixMilli()
|
||||
tssInput := prompbmarshal.MustParsePromMetrics(inputMetrics, offsetMsecs)
|
||||
tssInput := prometheus.MustParsePromMetrics(inputMetrics, offsetMsecs)
|
||||
matchIdxs := a.Push(tssInput, nil)
|
||||
a.MustStop()
|
||||
|
||||
@@ -1010,7 +1011,7 @@ func TestAggregatorsWithDedupInterval(t *testing.T) {
|
||||
|
||||
// Push the inputMetrics to Aggregators
|
||||
offsetMsecs := time.Now().Add(15 * time.Second).UnixMilli()
|
||||
tssInput := prompbmarshal.MustParsePromMetrics(inputMetrics, offsetMsecs)
|
||||
tssInput := prometheus.MustParsePromMetrics(inputMetrics, offsetMsecs)
|
||||
matchIdxs := a.Push(tssInput, nil)
|
||||
a.MustStop()
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/prometheus"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/stringsutil"
|
||||
)
|
||||
|
||||
@@ -87,7 +88,7 @@ func newBenchSeries(seriesCount int) []prompbmarshal.TimeSeries {
|
||||
}
|
||||
metrics := strings.Join(a, "\n")
|
||||
offsetMsecs := time.Now().UnixMilli()
|
||||
return prompbmarshal.MustParsePromMetrics(metrics, offsetMsecs)
|
||||
return prometheus.MustParsePromMetrics(metrics, offsetMsecs)
|
||||
}
|
||||
|
||||
const seriesCount = 10_000
|
||||
|
||||
16
lib/timeutil/duration.go
Normal file
16
lib/timeutil/duration.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package timeutil
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/metricsql"
|
||||
)
|
||||
|
||||
// ParseDuration parses duration string in Prometheus format
|
||||
func ParseDuration(s string) (time.Duration, error) {
|
||||
ms, err := metricsql.DurationValue(s, 0)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return time.Duration(ms) * time.Millisecond, nil
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user