mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2026-06-06 18:42:00 +03:00
Compare commits
1 Commits
v1.102.7
...
compressor
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
60941a311f |
@@ -9,7 +9,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/datasource"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutils"
|
||||
"github.com/VictoriaMetrics/metricsql"
|
||||
)
|
||||
@@ -49,7 +48,7 @@ Outer:
|
||||
}
|
||||
var expSamples []parsedSample
|
||||
for _, s := range mt.ExpSamples {
|
||||
expLb := []prompbmarshal.Label{}
|
||||
expLb := datasource.Labels{}
|
||||
if s.Labels != "" {
|
||||
metricsqlExpr, err := metricsql.Parse(s.Labels)
|
||||
if err != nil {
|
||||
@@ -65,7 +64,7 @@ Outer:
|
||||
}
|
||||
if len(metricsqlMetricExpr.LabelFilterss) > 0 {
|
||||
for _, l := range metricsqlMetricExpr.LabelFilterss[0] {
|
||||
expLb = append(expLb, prompbmarshal.Label{
|
||||
expLb = append(expLb, datasource.Label{
|
||||
Name: l.Label,
|
||||
Value: l.Value,
|
||||
})
|
||||
|
||||
@@ -46,8 +46,8 @@ const (
|
||||
graphitePrefix = "/graphite"
|
||||
)
|
||||
|
||||
func (c *Client) setGraphiteReqParams(r *http.Request, query string) {
|
||||
if c.appendTypePrefix {
|
||||
func (s *Client) setGraphiteReqParams(r *http.Request, query string) {
|
||||
if s.appendTypePrefix {
|
||||
r.URL.Path += graphitePrefix
|
||||
}
|
||||
r.URL.Path += graphitePath
|
||||
@@ -58,7 +58,7 @@ func (c *Client) setGraphiteReqParams(r *http.Request, query string) {
|
||||
q.Set("target", query)
|
||||
q.Set("until", "now")
|
||||
|
||||
for k, vs := range c.extraParams {
|
||||
for k, vs := range s.extraParams {
|
||||
if q.Has(k) { // extraParams are prior to params in URL
|
||||
q.Del(k)
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
|
||||
|
||||
"github.com/valyala/fastjson"
|
||||
)
|
||||
@@ -17,8 +16,7 @@ import (
|
||||
var (
|
||||
disablePathAppend = flag.Bool("remoteRead.disablePathAppend", false, "Whether to disable automatic appending of '/api/v1/query' or '/select/logsql/stats_query' path "+
|
||||
"to the configured -datasource.url and -remoteRead.url")
|
||||
disableStepParam = flag.Bool("datasource.disableStepParam", false, "Whether to disable adding 'step' param in instant queries to the configured -datasource.url and -remoteRead.url. "+
|
||||
"Only valid for prometheus datasource. "+
|
||||
disableStepParam = flag.Bool("datasource.disableStepParam", false, "Whether to disable adding 'step' param to the issued instant queries. "+
|
||||
"This might be useful when using vmalert with datasources that do not support 'step' param for instant queries, like Google Managed Prometheus. "+
|
||||
"It is not recommended to enable this flag if you use vmalert with VictoriaMetrics.")
|
||||
)
|
||||
@@ -83,14 +81,14 @@ func (pi *promInstant) Unmarshal(b []byte) error {
|
||||
labels := metric.GetObject()
|
||||
|
||||
r := &pi.ms[i]
|
||||
r.Labels = make([]prompbmarshal.Label, 0, labels.Len())
|
||||
r.Labels = make([]Label, 0, labels.Len())
|
||||
labels.Visit(func(key []byte, v *fastjson.Value) {
|
||||
lv, errLocal := v.StringBytes()
|
||||
if errLocal != nil {
|
||||
err = fmt.Errorf("error when parsing label value %q: %s", v, errLocal)
|
||||
return
|
||||
}
|
||||
r.Labels = append(r.Labels, prompbmarshal.Label{
|
||||
r.Labels = append(r.Labels, Label{
|
||||
Name: string(key),
|
||||
Value: string(lv),
|
||||
})
|
||||
@@ -220,8 +218,8 @@ func parsePrometheusResponse(req *http.Request, resp *http.Response) (res Result
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (c *Client) setPrometheusInstantReqParams(r *http.Request, query string, timestamp time.Time) {
|
||||
if c.appendTypePrefix {
|
||||
func (s *Client) setPrometheusInstantReqParams(r *http.Request, query string, timestamp time.Time) {
|
||||
if s.appendTypePrefix {
|
||||
r.URL.Path += "/prometheus"
|
||||
}
|
||||
if !*disablePathAppend {
|
||||
@@ -229,22 +227,22 @@ func (c *Client) setPrometheusInstantReqParams(r *http.Request, query string, ti
|
||||
}
|
||||
q := r.URL.Query()
|
||||
q.Set("time", timestamp.Format(time.RFC3339))
|
||||
if !*disableStepParam && c.evaluationInterval > 0 { // set step as evaluationInterval by default
|
||||
if !*disableStepParam && s.evaluationInterval > 0 { // set step as evaluationInterval by default
|
||||
// always convert to seconds to keep compatibility with older
|
||||
// Prometheus versions. See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1943
|
||||
q.Set("step", fmt.Sprintf("%ds", int(c.evaluationInterval.Seconds())))
|
||||
q.Set("step", fmt.Sprintf("%ds", int(s.evaluationInterval.Seconds())))
|
||||
}
|
||||
if !*disableStepParam && c.queryStep > 0 { // override step with user-specified value
|
||||
if !*disableStepParam && s.queryStep > 0 { // override step with user-specified value
|
||||
// always convert to seconds to keep compatibility with older
|
||||
// Prometheus versions. See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1943
|
||||
q.Set("step", fmt.Sprintf("%ds", int(c.queryStep.Seconds())))
|
||||
q.Set("step", fmt.Sprintf("%ds", int(s.queryStep.Seconds())))
|
||||
}
|
||||
r.URL.RawQuery = q.Encode()
|
||||
c.setReqParams(r, query)
|
||||
s.setReqParams(r, query)
|
||||
}
|
||||
|
||||
func (c *Client) setPrometheusRangeReqParams(r *http.Request, query string, start, end time.Time) {
|
||||
if c.appendTypePrefix {
|
||||
func (s *Client) setPrometheusRangeReqParams(r *http.Request, query string, start, end time.Time) {
|
||||
if s.appendTypePrefix {
|
||||
r.URL.Path += "/prometheus"
|
||||
}
|
||||
if !*disablePathAppend {
|
||||
@@ -253,11 +251,11 @@ func (c *Client) setPrometheusRangeReqParams(r *http.Request, query string, star
|
||||
q := r.URL.Query()
|
||||
q.Add("start", start.Format(time.RFC3339))
|
||||
q.Add("end", end.Format(time.RFC3339))
|
||||
if c.evaluationInterval > 0 { // set step as evaluationInterval by default
|
||||
if s.evaluationInterval > 0 { // set step as evaluationInterval by default
|
||||
// always convert to seconds to keep compatibility with older
|
||||
// Prometheus versions. See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1943
|
||||
q.Set("step", fmt.Sprintf("%ds", int(c.evaluationInterval.Seconds())))
|
||||
q.Set("step", fmt.Sprintf("%ds", int(s.evaluationInterval.Seconds())))
|
||||
}
|
||||
r.URL.RawQuery = q.Encode()
|
||||
c.setReqParams(r, query)
|
||||
s.setReqParams(r, query)
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ import (
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/utils"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promauth"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -145,12 +144,12 @@ func TestVMInstantQuery(t *testing.T) {
|
||||
}
|
||||
expected := []Metric{
|
||||
{
|
||||
Labels: []prompbmarshal.Label{{Value: "vm_rows", Name: "__name__"}, {Value: "bar", Name: "foo"}},
|
||||
Labels: []Label{{Value: "vm_rows", Name: "__name__"}, {Value: "bar", Name: "foo"}},
|
||||
Timestamps: []int64{1583786142},
|
||||
Values: []float64{13763},
|
||||
},
|
||||
{
|
||||
Labels: []prompbmarshal.Label{{Value: "vm_requests", Name: "__name__"}, {Value: "baz", Name: "foo"}},
|
||||
Labels: []Label{{Value: "vm_requests", Name: "__name__"}, {Value: "baz", Name: "foo"}},
|
||||
Timestamps: []int64{1583786140},
|
||||
Values: []float64{2000},
|
||||
},
|
||||
@@ -215,7 +214,7 @@ func TestVMInstantQuery(t *testing.T) {
|
||||
}
|
||||
exp := []Metric{
|
||||
{
|
||||
Labels: []prompbmarshal.Label{{Value: "constantLine(10)", Name: "name"}},
|
||||
Labels: []Label{{Value: "constantLine(10)", Name: "name"}},
|
||||
Timestamps: []int64{1611758403},
|
||||
Values: []float64{10},
|
||||
},
|
||||
@@ -237,12 +236,12 @@ func TestVMInstantQuery(t *testing.T) {
|
||||
}
|
||||
expected = []Metric{
|
||||
{
|
||||
Labels: []prompbmarshal.Label{{Value: "total", Name: "stats_result"}, {Value: "bar", Name: "foo"}},
|
||||
Labels: []Label{{Value: "total", Name: "stats_result"}, {Value: "bar", Name: "foo"}},
|
||||
Timestamps: []int64{1583786142},
|
||||
Values: []float64{13763},
|
||||
},
|
||||
{
|
||||
Labels: []prompbmarshal.Label{{Value: "total", Name: "stats_result"}, {Value: "baz", Name: "foo"}},
|
||||
Labels: []Label{{Value: "total", Name: "stats_result"}, {Value: "baz", Name: "foo"}},
|
||||
Timestamps: []int64{1583786140},
|
||||
Values: []float64{2000},
|
||||
},
|
||||
@@ -445,7 +444,7 @@ func TestVMRangeQuery(t *testing.T) {
|
||||
t.Fatalf("expected 1 metric got %d in %+v", len(m), m)
|
||||
}
|
||||
expected := Metric{
|
||||
Labels: []prompbmarshal.Label{{Value: "vm_rows", Name: "__name__"}},
|
||||
Labels: []Label{{Value: "vm_rows", Name: "__name__"}},
|
||||
Timestamps: []int64{1583786142},
|
||||
Values: []float64{13763},
|
||||
}
|
||||
@@ -476,7 +475,7 @@ func TestVMRangeQuery(t *testing.T) {
|
||||
t.Fatalf("expected 1 metric got %d in %+v", len(m), m)
|
||||
}
|
||||
expected = Metric{
|
||||
Labels: []prompbmarshal.Label{{Value: "total", Name: "stats_result"}},
|
||||
Labels: []Label{{Value: "total", Name: "stats_result"}},
|
||||
Timestamps: []int64{1583786142},
|
||||
Values: []float64{10},
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func (c *Client) setVLogsInstantReqParams(r *http.Request, query string, timestamp time.Time) {
|
||||
func (s *Client) setVLogsInstantReqParams(r *http.Request, query string, timestamp time.Time) {
|
||||
// there is no type path prefix in victorialogs APIs right now, ignore appendTypePrefix.
|
||||
if !*disablePathAppend {
|
||||
r.URL.Path += "/select/logsql/stats_query"
|
||||
@@ -16,15 +16,15 @@ func (c *Client) setVLogsInstantReqParams(r *http.Request, query string, timesta
|
||||
q.Set("time", timestamp.Format(time.RFC3339))
|
||||
// set the `start` and `end` params if applyIntervalAsTimeFilter is enabled(time filter is missing in the rule expr),
|
||||
// so the query will be executed in time range [timestamp - evaluationInterval, timestamp].
|
||||
if c.applyIntervalAsTimeFilter && c.evaluationInterval > 0 {
|
||||
q.Set("start", timestamp.Add(-c.evaluationInterval).Format(time.RFC3339))
|
||||
if s.applyIntervalAsTimeFilter && s.evaluationInterval > 0 {
|
||||
q.Set("start", timestamp.Add(-s.evaluationInterval).Format(time.RFC3339))
|
||||
q.Set("end", timestamp.Format(time.RFC3339))
|
||||
}
|
||||
r.URL.RawQuery = q.Encode()
|
||||
c.setReqParams(r, query)
|
||||
s.setReqParams(r, query)
|
||||
}
|
||||
|
||||
func (c *Client) setVLogsRangeReqParams(r *http.Request, query string, start, end time.Time) {
|
||||
func (s *Client) setVLogsRangeReqParams(r *http.Request, query string, start, end time.Time) {
|
||||
// there is no type path prefix in victorialogs APIs right now, ignore appendTypePrefix.
|
||||
if !*disablePathAppend {
|
||||
r.URL.Path += "/select/logsql/stats_query_range"
|
||||
@@ -33,11 +33,11 @@ func (c *Client) setVLogsRangeReqParams(r *http.Request, query string, start, en
|
||||
q.Add("start", start.Format(time.RFC3339))
|
||||
q.Add("end", end.Format(time.RFC3339))
|
||||
// set step as evaluationInterval by default
|
||||
if c.evaluationInterval > 0 {
|
||||
q.Set("step", fmt.Sprintf("%ds", int(c.evaluationInterval.Seconds())))
|
||||
if s.evaluationInterval > 0 {
|
||||
q.Set("step", fmt.Sprintf("%ds", int(s.evaluationInterval.Seconds())))
|
||||
}
|
||||
r.URL.RawQuery = q.Encode()
|
||||
c.setReqParams(r, query)
|
||||
s.setReqParams(r, query)
|
||||
}
|
||||
|
||||
func parseVLogsResponse(req *http.Request, resp *http.Response) (res Result, err error) {
|
||||
|
||||
@@ -8,8 +8,6 @@ import (
|
||||
"sort"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
|
||||
)
|
||||
|
||||
// Querier interface wraps Query and QueryRange methods
|
||||
@@ -57,7 +55,7 @@ type QuerierParams struct {
|
||||
|
||||
// Metric is the basic entity which should be return by datasource
|
||||
type Metric struct {
|
||||
Labels []prompbmarshal.Label
|
||||
Labels []Label
|
||||
Timestamps []int64
|
||||
Values []float64
|
||||
}
|
||||
@@ -74,9 +72,22 @@ func (m *Metric) SetLabel(key, value string) {
|
||||
m.AddLabel(key, value)
|
||||
}
|
||||
|
||||
// SetLabels sets the given map as Metric labels
|
||||
func (m *Metric) SetLabels(ls map[string]string) {
|
||||
var i int
|
||||
m.Labels = make([]Label, len(ls))
|
||||
for k, v := range ls {
|
||||
m.Labels[i] = Label{
|
||||
Name: k,
|
||||
Value: v,
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
// AddLabel appends the given label to the label set
|
||||
func (m *Metric) AddLabel(key, value string) {
|
||||
m.Labels = append(m.Labels, prompbmarshal.Label{Name: key, Value: value})
|
||||
m.Labels = append(m.Labels, Label{Name: key, Value: value})
|
||||
}
|
||||
|
||||
// DelLabel deletes the given label from the label set
|
||||
@@ -99,8 +110,14 @@ func (m *Metric) Label(key string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Label represents metric's label
|
||||
type Label struct {
|
||||
Name string
|
||||
Value string
|
||||
}
|
||||
|
||||
// Labels is collection of Label
|
||||
type Labels []prompbmarshal.Label
|
||||
type Labels []Label
|
||||
|
||||
func (ls Labels) Len() int { return len(ls) }
|
||||
func (ls Labels) Swap(i, j int) { ls[i], ls[j] = ls[j], ls[i] }
|
||||
@@ -155,7 +172,7 @@ func LabelCompare(a, b Labels) int {
|
||||
// ConvertToLabels convert map to Labels
|
||||
func ConvertToLabels(m map[string]string) (labelset Labels) {
|
||||
for k, v := range m {
|
||||
labelset = append(labelset, prompbmarshal.Label{
|
||||
labelset = append(labelset, Label{
|
||||
Name: k,
|
||||
Value: v,
|
||||
})
|
||||
|
||||
@@ -51,7 +51,7 @@ var (
|
||||
lookBack = flag.Duration("datasource.lookback", 0, `Deprecated: please adjust "-search.latencyOffset" at datasource side `+
|
||||
`or specify "latency_offset" in rule group's params. Lookback defines how far into the past to look when evaluating queries. `+
|
||||
`For example, if the datasource.lookback=5m then param "time" with value now()-5m will be added to every query.`)
|
||||
queryStep = flag.Duration("datasource.queryStep", 5*time.Minute, "How far a value can fallback to when evaluating queries to the configured -datasource.url and -remoteRead.url. Only valid for prometheus datasource. "+
|
||||
queryStep = flag.Duration("datasource.queryStep", 5*time.Minute, "How far a value can fallback to when evaluating queries. "+
|
||||
"For example, if -datasource.queryStep=15s then param \"step\" with value \"15s\" will be added to every query. "+
|
||||
"If set to 0, rule's evaluation interval will be used instead.")
|
||||
queryTimeAlignment = flag.Bool("datasource.queryTimeAlignment", true, `Deprecated: please use "eval_alignment" in rule group instead. `+
|
||||
@@ -62,8 +62,8 @@ var (
|
||||
idleConnectionTimeout = flag.Duration("datasource.idleConnTimeout", 50*time.Second, `Defines a duration for idle (keep-alive connections) to exist. Consider setting this value less than "-http.idleConnTimeout". It must prevent possible "write: broken pipe" and "read: connection reset by peer" errors.`)
|
||||
disableKeepAlive = flag.Bool("datasource.disableKeepAlive", false, `Whether to disable long-lived connections to the datasource. `+
|
||||
`If true, disables HTTP keep-alive and will only use the connection to the server for a single HTTP request.`)
|
||||
roundDigits = flag.Int("datasource.roundDigits", 0, `Adds "round_digits" GET param to datasource requests which limits the number of digits after the decimal point in response values. `+
|
||||
`Only valid for VictoriaMetrics as the datasource.`)
|
||||
roundDigits = flag.Int("datasource.roundDigits", 0, `Adds "round_digits" GET param to datasource requests. `+
|
||||
`In VM "round_digits" limits the number of digits after the decimal point in response values.`)
|
||||
)
|
||||
|
||||
// InitSecretFlags must be called after flag.Parse and before any logging
|
||||
|
||||
@@ -3,8 +3,6 @@ package datasource
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
|
||||
)
|
||||
|
||||
func TestPromInstant_UnmarshalPositive(t *testing.T) {
|
||||
@@ -23,7 +21,7 @@ func TestPromInstant_UnmarshalPositive(t *testing.T) {
|
||||
|
||||
f(`[{"metric":{"__name__":"up"},"value":[1583780000,"42"]}]`, []Metric{
|
||||
{
|
||||
Labels: []prompbmarshal.Label{{Name: "__name__", Value: "up"}},
|
||||
Labels: []Label{{Name: "__name__", Value: "up"}},
|
||||
Timestamps: []int64{1583780000},
|
||||
Values: []float64{42},
|
||||
},
|
||||
@@ -33,17 +31,17 @@ func TestPromInstant_UnmarshalPositive(t *testing.T) {
|
||||
{"metric":{"__name__":"foo"},"value":[1583780001,"7"]},
|
||||
{"metric":{"__name__":"baz", "instance":"bar"},"value":[1583780002,"8"]}]`, []Metric{
|
||||
{
|
||||
Labels: []prompbmarshal.Label{{Name: "__name__", Value: "up"}},
|
||||
Labels: []Label{{Name: "__name__", Value: "up"}},
|
||||
Timestamps: []int64{1583780000},
|
||||
Values: []float64{42},
|
||||
},
|
||||
{
|
||||
Labels: []prompbmarshal.Label{{Name: "__name__", Value: "foo"}},
|
||||
Labels: []Label{{Name: "__name__", Value: "foo"}},
|
||||
Timestamps: []int64{1583780001},
|
||||
Values: []float64{7},
|
||||
},
|
||||
{
|
||||
Labels: []prompbmarshal.Label{{Name: "__name__", Value: "baz"}, {Name: "instance", Value: "bar"}},
|
||||
Labels: []Label{{Name: "__name__", Value: "baz"}, {Name: "instance", Value: "bar"}},
|
||||
Timestamps: []int64{1583780002},
|
||||
Values: []float64{8},
|
||||
},
|
||||
|
||||
@@ -167,8 +167,14 @@ type tplData struct {
|
||||
ExternalURL string
|
||||
}
|
||||
|
||||
func templateAnnotation(dst io.Writer, text string, data tplData, tpl *textTpl.Template, execute bool) error {
|
||||
tpl, err := tpl.Parse(text)
|
||||
func templateAnnotation(dst io.Writer, text string, data tplData, tmpl *textTpl.Template, execute bool) error {
|
||||
tpl, err := tmpl.Clone()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error cloning template before parse annotation: %w", err)
|
||||
}
|
||||
// Clone() doesn't copy tpl Options, so we set them manually
|
||||
tpl = tpl.Option("missingkey=zero")
|
||||
tpl, err = tpl.Parse(text)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing annotation template: %w", err)
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ func TestAlertExecTemplate(t *testing.T) {
|
||||
qFn := func(_ string) ([]datasource.Metric, error) {
|
||||
return []datasource.Metric{
|
||||
{
|
||||
Labels: []prompbmarshal.Label{
|
||||
Labels: []datasource.Label{
|
||||
{Name: "foo", Value: "bar"},
|
||||
{Name: "baz", Value: "qux"},
|
||||
},
|
||||
@@ -41,7 +41,7 @@ func TestAlertExecTemplate(t *testing.T) {
|
||||
Timestamps: []int64{1},
|
||||
},
|
||||
{
|
||||
Labels: []prompbmarshal.Label{
|
||||
Labels: []datasource.Label{
|
||||
{Name: "foo", Value: "garply"},
|
||||
{Name: "baz", Value: "fred"},
|
||||
},
|
||||
|
||||
@@ -14,10 +14,8 @@ import (
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/notifier"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/templates"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/utils"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/decimal"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promrelabel"
|
||||
)
|
||||
|
||||
// AlertingRule is basic alert entity
|
||||
@@ -456,16 +454,13 @@ func (ar *AlertingRule) exec(ctx context.Context, ts time.Time, limit int) ([]pr
|
||||
ar.logDebugf(ts, a, "created in state PENDING")
|
||||
}
|
||||
var numActivePending int
|
||||
var tss []prompbmarshal.TimeSeries
|
||||
for h, a := range ar.alerts {
|
||||
// if alert wasn't updated in this iteration
|
||||
// means it is resolved already
|
||||
if _, ok := updated[h]; !ok {
|
||||
if a.State == notifier.StatePending {
|
||||
// alert was in Pending state - it is not active anymore
|
||||
// add stale time series
|
||||
tss = append(tss, pendingAlertStaleTimeSeries(a.Labels, ts.Unix(), true)...)
|
||||
|
||||
// alert was in Pending state - it is not
|
||||
// active anymore
|
||||
delete(ar.alerts, h)
|
||||
ar.logDebugf(ts, a, "PENDING => DELETED: is absent in current evaluation round")
|
||||
continue
|
||||
@@ -483,9 +478,6 @@ func (ar *AlertingRule) exec(ctx context.Context, ts time.Time, limit int) ([]pr
|
||||
if ts.Sub(a.KeepFiringSince) >= ar.KeepFiringFor {
|
||||
a.State = notifier.StateInactive
|
||||
a.ResolvedAt = ts
|
||||
// add stale time series
|
||||
tss = append(tss, firingAlertStaleTimeSeries(a.Labels, ts.Unix())...)
|
||||
|
||||
ar.logDebugf(ts, a, "FIRING => INACTIVE: is absent in current evaluation round")
|
||||
continue
|
||||
}
|
||||
@@ -497,10 +489,6 @@ func (ar *AlertingRule) exec(ctx context.Context, ts time.Time, limit int) ([]pr
|
||||
a.State = notifier.StateFiring
|
||||
a.Start = ts
|
||||
alertsFired.Inc()
|
||||
if ar.For > 0 {
|
||||
// add stale time series
|
||||
tss = append(tss, pendingAlertStaleTimeSeries(a.Labels, ts.Unix(), false)...)
|
||||
}
|
||||
ar.logDebugf(ts, a, "PENDING => FIRING: %s since becoming active at %v", ts.Sub(a.ActiveAt), a.ActiveAt)
|
||||
}
|
||||
}
|
||||
@@ -509,7 +497,7 @@ func (ar *AlertingRule) exec(ctx context.Context, ts time.Time, limit int) ([]pr
|
||||
curState.Err = fmt.Errorf("exec exceeded limit of %d with %d alerts", limit, numActivePending)
|
||||
return nil, curState.Err
|
||||
}
|
||||
return append(tss, ar.toTimeSeries(ts.Unix())...), nil
|
||||
return ar.toTimeSeries(ts.Unix()), nil
|
||||
}
|
||||
|
||||
func (ar *AlertingRule) expandTemplates(m datasource.Metric, qFn templates.QueryFn, ts time.Time) (*labelSet, map[string]string, error) {
|
||||
@@ -534,7 +522,6 @@ func (ar *AlertingRule) expandTemplates(m datasource.Metric, qFn templates.Query
|
||||
return ls, as, nil
|
||||
}
|
||||
|
||||
// toTimeSeries creates `ALERTS` and `ALERTS_FOR_STATE` for active alerts
|
||||
func (ar *AlertingRule) toTimeSeries(timestamp int64) []prompbmarshal.TimeSeries {
|
||||
var tss []prompbmarshal.TimeSeries
|
||||
for _, a := range ar.alerts {
|
||||
@@ -614,83 +601,26 @@ func (ar *AlertingRule) alertToTimeSeries(a *notifier.Alert, timestamp int64) []
|
||||
}
|
||||
|
||||
func alertToTimeSeries(a *notifier.Alert, timestamp int64) prompbmarshal.TimeSeries {
|
||||
var labels []prompbmarshal.Label
|
||||
labels := make(map[string]string)
|
||||
for k, v := range a.Labels {
|
||||
labels = append(labels, prompbmarshal.Label{
|
||||
Name: k,
|
||||
Value: v,
|
||||
})
|
||||
}
|
||||
// __name__ already been dropped, no need to check duplication
|
||||
labels = append(labels, prompbmarshal.Label{Name: "__name__", Value: alertMetricName})
|
||||
if ol := promrelabel.GetLabelByName(labels, alertStateLabel); ol != nil {
|
||||
ol.Value = a.State.String()
|
||||
} else {
|
||||
labels = append(labels, prompbmarshal.Label{Name: alertStateLabel, Value: a.State.String()})
|
||||
labels[k] = v
|
||||
}
|
||||
labels["__name__"] = alertMetricName
|
||||
labels[alertStateLabel] = a.State.String()
|
||||
return newTimeSeries([]float64{1}, []int64{timestamp}, labels)
|
||||
}
|
||||
|
||||
// alertForToTimeSeries returns a time series that represents
|
||||
// alertForToTimeSeries returns a timeseries that represents
|
||||
// state of active alerts, where value is time when alert become active
|
||||
func alertForToTimeSeries(a *notifier.Alert, timestamp int64) prompbmarshal.TimeSeries {
|
||||
var labels []prompbmarshal.Label
|
||||
labels := make(map[string]string)
|
||||
for k, v := range a.Labels {
|
||||
labels = append(labels, prompbmarshal.Label{
|
||||
Name: k,
|
||||
Value: v,
|
||||
})
|
||||
labels[k] = v
|
||||
}
|
||||
// __name__ already been dropped, no need to check duplication
|
||||
labels = append(labels, prompbmarshal.Label{Name: "__name__", Value: alertForStateMetricName})
|
||||
labels["__name__"] = alertForStateMetricName
|
||||
return newTimeSeries([]float64{float64(a.ActiveAt.Unix())}, []int64{timestamp}, labels)
|
||||
}
|
||||
|
||||
// pendingAlertStaleTimeSeries returns stale `ALERTS` and `ALERTS_FOR_STATE` time series
|
||||
// for alerts which changed their state from Pending to Inactive or Firing.
|
||||
func pendingAlertStaleTimeSeries(ls map[string]string, timestamp int64, includeAlertForState bool) []prompbmarshal.TimeSeries {
|
||||
var result []prompbmarshal.TimeSeries
|
||||
var baseLabels []prompbmarshal.Label
|
||||
for k, v := range ls {
|
||||
baseLabels = append(baseLabels, prompbmarshal.Label{
|
||||
Name: k,
|
||||
Value: v,
|
||||
})
|
||||
}
|
||||
// __name__ already been dropped, no need to check duplication
|
||||
alertsLabels := append(baseLabels, prompbmarshal.Label{Name: "__name__", Value: alertMetricName})
|
||||
alertsLabels = append(alertsLabels, prompbmarshal.Label{Name: alertStateLabel, Value: notifier.StatePending.String()})
|
||||
result = append(result, newTimeSeries([]float64{decimal.StaleNaN}, []int64{timestamp}, alertsLabels))
|
||||
|
||||
if includeAlertForState {
|
||||
alertsForStateLabels := append(baseLabels, prompbmarshal.Label{Name: "__name__", Value: alertForStateMetricName})
|
||||
result = append(result, newTimeSeries([]float64{decimal.StaleNaN}, []int64{timestamp}, alertsForStateLabels))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// firingAlertStaleTimeSeries returns stale `ALERTS` and `ALERTS_FOR_STATE` time series
|
||||
// for alerts which changed their state from Firing to Inactive.
|
||||
func firingAlertStaleTimeSeries(ls map[string]string, timestamp int64) []prompbmarshal.TimeSeries {
|
||||
var baseLabels []prompbmarshal.Label
|
||||
for k, v := range ls {
|
||||
baseLabels = append(baseLabels, prompbmarshal.Label{
|
||||
Name: k,
|
||||
Value: v,
|
||||
})
|
||||
}
|
||||
// __name__ already been dropped, no need to check duplication
|
||||
alertsLabels := append(baseLabels, prompbmarshal.Label{Name: "__name__", Value: alertMetricName})
|
||||
alertsLabels = append(alertsLabels, prompbmarshal.Label{Name: alertStateLabel, Value: notifier.StateFiring.String()})
|
||||
|
||||
alertsForStateLabels := append(baseLabels, prompbmarshal.Label{Name: "__name__", Value: alertForStateMetricName})
|
||||
|
||||
return []prompbmarshal.TimeSeries{
|
||||
newTimeSeries([]float64{decimal.StaleNaN}, []int64{timestamp}, alertsLabels),
|
||||
newTimeSeries([]float64{decimal.StaleNaN}, []int64{timestamp}, alertsForStateLabels),
|
||||
}
|
||||
}
|
||||
|
||||
// restore restores the value of ActiveAt field for active alerts,
|
||||
// based on previously written time series `alertForStateMetricName`.
|
||||
// Only rules with For > 0 can be restored.
|
||||
|
||||
@@ -15,7 +15,6 @@ import (
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/datasource"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/notifier"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/utils"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/decimal"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutils"
|
||||
)
|
||||
@@ -29,7 +28,7 @@ func TestAlertingRuleToTimeSeries(t *testing.T) {
|
||||
rule.alerts[alert.ID] = alert
|
||||
tss := rule.toTimeSeries(timestamp.Unix())
|
||||
if err := compareTimeSeries(t, tssExpected, tss); err != nil {
|
||||
t.Fatalf("timeseries mismatch for rule %q: %s", rule.Name, err)
|
||||
t.Fatalf("timeseries mismatch: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,23 +36,14 @@ func TestAlertingRuleToTimeSeries(t *testing.T) {
|
||||
State: notifier.StateFiring,
|
||||
ActiveAt: timestamp.Add(time.Second),
|
||||
}, []prompbmarshal.TimeSeries{
|
||||
newTimeSeries([]float64{1}, []int64{timestamp.UnixNano()}, []prompbmarshal.Label{
|
||||
{
|
||||
Name: "__name__",
|
||||
Value: alertMetricName,
|
||||
},
|
||||
{
|
||||
Name: alertStateLabel,
|
||||
Value: notifier.StateFiring.String(),
|
||||
},
|
||||
newTimeSeries([]float64{1}, []int64{timestamp.UnixNano()}, map[string]string{
|
||||
"__name__": alertMetricName,
|
||||
alertStateLabel: notifier.StateFiring.String(),
|
||||
}),
|
||||
newTimeSeries([]float64{float64(timestamp.Add(time.Second).Unix())},
|
||||
[]int64{timestamp.UnixNano()},
|
||||
[]prompbmarshal.Label{
|
||||
{
|
||||
Name: "__name__",
|
||||
Value: alertForStateMetricName,
|
||||
},
|
||||
map[string]string{
|
||||
"__name__": alertForStateMetricName,
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -64,40 +54,18 @@ func TestAlertingRuleToTimeSeries(t *testing.T) {
|
||||
"instance": "bar",
|
||||
},
|
||||
}, []prompbmarshal.TimeSeries{
|
||||
newTimeSeries([]float64{1}, []int64{timestamp.UnixNano()},
|
||||
[]prompbmarshal.Label{
|
||||
{
|
||||
Name: "__name__",
|
||||
Value: alertMetricName,
|
||||
},
|
||||
{
|
||||
Name: alertStateLabel,
|
||||
Value: notifier.StateFiring.String(),
|
||||
},
|
||||
{
|
||||
Name: "job",
|
||||
Value: "foo",
|
||||
},
|
||||
{
|
||||
Name: "instance",
|
||||
Value: "bar",
|
||||
},
|
||||
}),
|
||||
newTimeSeries([]float64{1}, []int64{timestamp.UnixNano()}, map[string]string{
|
||||
"__name__": alertMetricName,
|
||||
alertStateLabel: notifier.StateFiring.String(),
|
||||
"job": "foo",
|
||||
"instance": "bar",
|
||||
}),
|
||||
newTimeSeries([]float64{float64(timestamp.Add(time.Second).Unix())},
|
||||
[]int64{timestamp.UnixNano()},
|
||||
[]prompbmarshal.Label{
|
||||
{
|
||||
Name: "__name__",
|
||||
Value: alertForStateMetricName,
|
||||
},
|
||||
{
|
||||
Name: "job",
|
||||
Value: "foo",
|
||||
},
|
||||
{
|
||||
Name: "instance",
|
||||
Value: "bar",
|
||||
},
|
||||
map[string]string{
|
||||
"__name__": alertForStateMetricName,
|
||||
"job": "foo",
|
||||
"instance": "bar",
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -105,29 +73,18 @@ func TestAlertingRuleToTimeSeries(t *testing.T) {
|
||||
State: notifier.StateFiring, ActiveAt: timestamp.Add(time.Second),
|
||||
Labels: map[string]string{
|
||||
alertStateLabel: "foo",
|
||||
"__name__": "bar",
|
||||
},
|
||||
}, []prompbmarshal.TimeSeries{
|
||||
newTimeSeries([]float64{1}, []int64{timestamp.UnixNano()}, []prompbmarshal.Label{
|
||||
{
|
||||
Name: "__name__",
|
||||
Value: alertMetricName,
|
||||
},
|
||||
{
|
||||
Name: alertStateLabel,
|
||||
Value: notifier.StateFiring.String(),
|
||||
},
|
||||
newTimeSeries([]float64{1}, []int64{timestamp.UnixNano()}, map[string]string{
|
||||
"__name__": alertMetricName,
|
||||
alertStateLabel: notifier.StateFiring.String(),
|
||||
}),
|
||||
newTimeSeries([]float64{float64(timestamp.Add(time.Second).Unix())},
|
||||
[]int64{timestamp.UnixNano()},
|
||||
[]prompbmarshal.Label{
|
||||
{
|
||||
Name: "__name__",
|
||||
Value: alertForStateMetricName,
|
||||
},
|
||||
{
|
||||
Name: alertStateLabel,
|
||||
Value: "foo",
|
||||
},
|
||||
map[string]string{
|
||||
"__name__": alertForStateMetricName,
|
||||
alertStateLabel: "foo",
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -135,23 +92,14 @@ func TestAlertingRuleToTimeSeries(t *testing.T) {
|
||||
State: notifier.StateFiring,
|
||||
ActiveAt: timestamp.Add(time.Second),
|
||||
}, []prompbmarshal.TimeSeries{
|
||||
newTimeSeries([]float64{1}, []int64{timestamp.UnixNano()}, []prompbmarshal.Label{
|
||||
{
|
||||
Name: "__name__",
|
||||
Value: alertMetricName,
|
||||
},
|
||||
{
|
||||
Name: alertStateLabel,
|
||||
Value: notifier.StateFiring.String(),
|
||||
},
|
||||
newTimeSeries([]float64{1}, []int64{timestamp.UnixNano()}, map[string]string{
|
||||
"__name__": alertMetricName,
|
||||
alertStateLabel: notifier.StateFiring.String(),
|
||||
}),
|
||||
newTimeSeries([]float64{float64(timestamp.Add(time.Second).Unix())},
|
||||
[]int64{timestamp.UnixNano()},
|
||||
[]prompbmarshal.Label{
|
||||
{
|
||||
Name: "__name__",
|
||||
Value: alertForStateMetricName,
|
||||
},
|
||||
map[string]string{
|
||||
"__name__": alertForStateMetricName,
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -159,21 +107,12 @@ func TestAlertingRuleToTimeSeries(t *testing.T) {
|
||||
State: notifier.StatePending,
|
||||
ActiveAt: timestamp.Add(time.Second),
|
||||
}, []prompbmarshal.TimeSeries{
|
||||
newTimeSeries([]float64{1}, []int64{timestamp.UnixNano()}, []prompbmarshal.Label{
|
||||
{
|
||||
Name: "__name__",
|
||||
Value: alertMetricName,
|
||||
},
|
||||
{
|
||||
Name: alertStateLabel,
|
||||
Value: notifier.StatePending.String(),
|
||||
},
|
||||
newTimeSeries([]float64{1}, []int64{timestamp.UnixNano()}, map[string]string{
|
||||
"__name__": alertMetricName,
|
||||
alertStateLabel: notifier.StatePending.String(),
|
||||
}),
|
||||
newTimeSeries([]float64{float64(timestamp.Add(time.Second).Unix())}, []int64{timestamp.UnixNano()}, []prompbmarshal.Label{
|
||||
{
|
||||
Name: "__name__",
|
||||
Value: alertForStateMetricName,
|
||||
},
|
||||
newTimeSeries([]float64{float64(timestamp.Add(time.Second).Unix())}, []int64{timestamp.UnixNano()}, map[string]string{
|
||||
"__name__": alertForStateMetricName,
|
||||
}),
|
||||
})
|
||||
}
|
||||
@@ -185,9 +124,7 @@ func TestAlertingRule_Exec(t *testing.T) {
|
||||
alert *notifier.Alert
|
||||
}
|
||||
|
||||
ts, _ := time.Parse(time.RFC3339, "2024-10-29T00:00:00Z")
|
||||
|
||||
f := func(rule *AlertingRule, steps [][]datasource.Metric, alertsExpected map[int][]testAlert, tssExpected map[int][]prompbmarshal.TimeSeries) {
|
||||
f := func(rule *AlertingRule, steps [][]datasource.Metric, alertsExpected map[int][]testAlert) {
|
||||
t.Helper()
|
||||
|
||||
fq := &datasource.FakeQuerier{}
|
||||
@@ -197,19 +134,13 @@ func TestAlertingRule_Exec(t *testing.T) {
|
||||
Name: "TestRule_Exec",
|
||||
}
|
||||
rule.GroupID = fakeGroup.ID()
|
||||
ts := time.Now()
|
||||
for i, step := range steps {
|
||||
fq.Reset()
|
||||
fq.Add(step...)
|
||||
tss, err := rule.exec(context.TODO(), ts, 0)
|
||||
if err != nil {
|
||||
if _, err := rule.exec(context.TODO(), ts, 0); err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
// check generate time series
|
||||
if _, ok := tssExpected[i]; ok {
|
||||
if err := compareTimeSeries(t, tssExpected[i], tss); err != nil {
|
||||
t.Fatalf("generated time series mismatch for rule %q in step %d: %s", rule.Name, i, err)
|
||||
}
|
||||
}
|
||||
|
||||
// shift the execution timestamp before the next iteration
|
||||
ts = ts.Add(defaultStep)
|
||||
@@ -243,21 +174,13 @@ func TestAlertingRule_Exec(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
f(newTestAlertingRule("empty", 0), [][]datasource.Metric{}, nil, nil)
|
||||
f(newTestAlertingRule("empty", 0), [][]datasource.Metric{}, nil)
|
||||
|
||||
f(newTestAlertingRule("empty_labels", 0), [][]datasource.Metric{
|
||||
f(newTestAlertingRule("empty labels", 0), [][]datasource.Metric{
|
||||
{datasource.Metric{Values: []float64{1}, Timestamps: []int64{1}}},
|
||||
}, map[int][]testAlert{
|
||||
0: {{alert: ¬ifier.Alert{State: notifier.StateFiring}}},
|
||||
},
|
||||
map[int][]prompbmarshal.TimeSeries{
|
||||
0: {
|
||||
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "empty_labels"}, {Name: "alertstate", Value: "firing"}},
|
||||
Samples: []prompbmarshal.Sample{{Value: 1, Timestamp: ts.UnixNano() / 1e6}}},
|
||||
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "empty_labels"}},
|
||||
Samples: []prompbmarshal.Sample{{Value: float64(ts.Unix()), Timestamp: ts.UnixNano() / 1e6}}},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
f(newTestAlertingRule("single-firing=>inactive=>firing=>inactive=>inactive", 0), [][]datasource.Metric{
|
||||
{metricWithLabels(t, "name", "foo")},
|
||||
@@ -271,25 +194,6 @@ func TestAlertingRule_Exec(t *testing.T) {
|
||||
2: {{labels: []string{"name", "foo"}, alert: ¬ifier.Alert{State: notifier.StateFiring}}},
|
||||
3: {{labels: []string{"name", "foo"}, alert: ¬ifier.Alert{State: notifier.StateInactive}}},
|
||||
4: {{labels: []string{"name", "foo"}, alert: ¬ifier.Alert{State: notifier.StateInactive}}},
|
||||
}, map[int][]prompbmarshal.TimeSeries{
|
||||
0: {
|
||||
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "single-firing=>inactive=>firing=>inactive=>inactive"}, {Name: "alertstate", Value: "firing"}, {Name: "name", Value: "foo"}},
|
||||
Samples: []prompbmarshal.Sample{{Value: 1, Timestamp: ts.UnixNano() / 1e6}}},
|
||||
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "single-firing=>inactive=>firing=>inactive=>inactive"}, {Name: "name", Value: "foo"}},
|
||||
Samples: []prompbmarshal.Sample{{Value: float64(ts.Unix()), Timestamp: ts.UnixNano() / 1e6}}},
|
||||
},
|
||||
1: {
|
||||
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "single-firing=>inactive=>firing=>inactive=>inactive"}, {Name: "alertstate", Value: "firing"}, {Name: "name", Value: "foo"}},
|
||||
Samples: []prompbmarshal.Sample{{Value: decimal.StaleNaN, Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}}},
|
||||
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "single-firing=>inactive=>firing=>inactive=>inactive"}, {Name: "name", Value: "foo"}},
|
||||
Samples: []prompbmarshal.Sample{{Value: decimal.StaleNaN, Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}}},
|
||||
},
|
||||
2: {
|
||||
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "single-firing=>inactive=>firing=>inactive=>inactive"}, {Name: "alertstate", Value: "firing"}, {Name: "name", Value: "foo"}},
|
||||
Samples: []prompbmarshal.Sample{{Value: 1, Timestamp: ts.Add(2*defaultStep).UnixNano() / 1e6}}},
|
||||
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "single-firing=>inactive=>firing=>inactive=>inactive"}, {Name: "name", Value: "foo"}},
|
||||
Samples: []prompbmarshal.Sample{{Value: float64(ts.Add(2 * defaultStep).Unix()), Timestamp: ts.Add(2*defaultStep).UnixNano() / 1e6}}},
|
||||
},
|
||||
})
|
||||
|
||||
f(newTestAlertingRule("single-firing=>inactive=>firing=>inactive=>inactive=>firing", 0), [][]datasource.Metric{
|
||||
@@ -306,7 +210,7 @@ func TestAlertingRule_Exec(t *testing.T) {
|
||||
3: {{labels: []string{"name", "foo"}, alert: ¬ifier.Alert{State: notifier.StateInactive}}},
|
||||
4: {{labels: []string{"name", "foo"}, alert: ¬ifier.Alert{State: notifier.StateInactive}}},
|
||||
5: {{labels: []string{"name", "foo"}, alert: ¬ifier.Alert{State: notifier.StateFiring}}},
|
||||
}, nil)
|
||||
})
|
||||
|
||||
f(newTestAlertingRule("multiple-firing", 0), [][]datasource.Metric{
|
||||
{
|
||||
@@ -320,7 +224,7 @@ func TestAlertingRule_Exec(t *testing.T) {
|
||||
{labels: []string{"name", "foo1"}, alert: ¬ifier.Alert{State: notifier.StateFiring}},
|
||||
{labels: []string{"name", "foo2"}, alert: ¬ifier.Alert{State: notifier.StateFiring}},
|
||||
},
|
||||
}, nil)
|
||||
})
|
||||
|
||||
// 1: fire first alert
|
||||
// 2: fire second alert, set first inactive
|
||||
@@ -329,57 +233,27 @@ func TestAlertingRule_Exec(t *testing.T) {
|
||||
{metricWithLabels(t, "name", "foo")},
|
||||
{metricWithLabels(t, "name", "foo1")},
|
||||
{metricWithLabels(t, "name", "foo2")},
|
||||
}, map[int][]testAlert{
|
||||
0: {
|
||||
{labels: []string{"name", "foo"}, alert: ¬ifier.Alert{State: notifier.StateFiring}},
|
||||
},
|
||||
1: {
|
||||
{labels: []string{"name", "foo"}, alert: ¬ifier.Alert{State: notifier.StateInactive}},
|
||||
{labels: []string{"name", "foo1"}, alert: ¬ifier.Alert{State: notifier.StateFiring}},
|
||||
},
|
||||
2: {
|
||||
{labels: []string{"name", "foo"}, alert: ¬ifier.Alert{State: notifier.StateInactive}},
|
||||
{labels: []string{"name", "foo1"}, alert: ¬ifier.Alert{State: notifier.StateInactive}},
|
||||
{labels: []string{"name", "foo2"}, alert: ¬ifier.Alert{State: notifier.StateFiring}},
|
||||
},
|
||||
}, map[int][]prompbmarshal.TimeSeries{
|
||||
0: {
|
||||
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "multiple-steps-firing"}, {Name: "alertstate", Value: "firing"}, {Name: "name", Value: "foo"}},
|
||||
Samples: []prompbmarshal.Sample{{Value: 1, Timestamp: ts.UnixNano() / 1e6}}},
|
||||
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "multiple-steps-firing"}, {Name: "name", Value: "foo"}},
|
||||
Samples: []prompbmarshal.Sample{{Value: float64(ts.Unix()), Timestamp: ts.UnixNano() / 1e6}}},
|
||||
},
|
||||
1: {
|
||||
// stale time series for foo, `firing -> inactive`
|
||||
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "multiple-steps-firing"}, {Name: "alertstate", Value: "firing"}, {Name: "name", Value: "foo"}},
|
||||
Samples: []prompbmarshal.Sample{{Value: decimal.StaleNaN, Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}}},
|
||||
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "multiple-steps-firing"}, {Name: "name", Value: "foo"}},
|
||||
Samples: []prompbmarshal.Sample{{Value: decimal.StaleNaN, Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}}},
|
||||
// new time series for foo1
|
||||
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "multiple-steps-firing"}, {Name: "alertstate", Value: "firing"}, {Name: "name", Value: "foo1"}},
|
||||
Samples: []prompbmarshal.Sample{{Value: 1, Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}}},
|
||||
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "multiple-steps-firing"}, {Name: "name", Value: "foo1"}},
|
||||
Samples: []prompbmarshal.Sample{{Value: float64(ts.Add(defaultStep).Unix()), Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}}},
|
||||
},
|
||||
2: {
|
||||
// stale time series for foo1
|
||||
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "multiple-steps-firing"}, {Name: "alertstate", Value: "firing"}, {Name: "name", Value: "foo1"}},
|
||||
Samples: []prompbmarshal.Sample{{Value: decimal.StaleNaN, Timestamp: ts.Add(2*defaultStep).UnixNano() / 1e6}}},
|
||||
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "multiple-steps-firing"}, {Name: "name", Value: "foo1"}},
|
||||
Samples: []prompbmarshal.Sample{{Value: decimal.StaleNaN, Timestamp: ts.Add(2*defaultStep).UnixNano() / 1e6}}},
|
||||
// new time series for foo2
|
||||
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "multiple-steps-firing"}, {Name: "alertstate", Value: "firing"}, {Name: "name", Value: "foo2"}},
|
||||
Samples: []prompbmarshal.Sample{{Value: 1, Timestamp: ts.Add(2*defaultStep).UnixNano() / 1e6}}},
|
||||
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "multiple-steps-firing"}, {Name: "name", Value: "foo2"}},
|
||||
Samples: []prompbmarshal.Sample{{Value: float64(ts.Add(2 * defaultStep).Unix()), Timestamp: ts.Add(2*defaultStep).UnixNano() / 1e6}}},
|
||||
},
|
||||
})
|
||||
},
|
||||
map[int][]testAlert{
|
||||
0: {
|
||||
{labels: []string{"name", "foo"}, alert: ¬ifier.Alert{State: notifier.StateFiring}},
|
||||
},
|
||||
1: {
|
||||
{labels: []string{"name", "foo"}, alert: ¬ifier.Alert{State: notifier.StateInactive}},
|
||||
{labels: []string{"name", "foo1"}, alert: ¬ifier.Alert{State: notifier.StateFiring}},
|
||||
},
|
||||
2: {
|
||||
{labels: []string{"name", "foo"}, alert: ¬ifier.Alert{State: notifier.StateInactive}},
|
||||
{labels: []string{"name", "foo1"}, alert: ¬ifier.Alert{State: notifier.StateInactive}},
|
||||
{labels: []string{"name", "foo2"}, alert: ¬ifier.Alert{State: notifier.StateFiring}},
|
||||
},
|
||||
})
|
||||
|
||||
f(newTestAlertingRule("for-pending", time.Minute), [][]datasource.Metric{
|
||||
{metricWithLabels(t, "name", "foo")},
|
||||
}, map[int][]testAlert{
|
||||
0: {{labels: []string{"name", "foo"}, alert: ¬ifier.Alert{State: notifier.StatePending}}},
|
||||
}, nil)
|
||||
})
|
||||
|
||||
f(newTestAlertingRule("for-fired", defaultStep), [][]datasource.Metric{
|
||||
{metricWithLabels(t, "name", "foo")},
|
||||
@@ -387,22 +261,6 @@ func TestAlertingRule_Exec(t *testing.T) {
|
||||
}, map[int][]testAlert{
|
||||
0: {{labels: []string{"name", "foo"}, alert: ¬ifier.Alert{State: notifier.StatePending}}},
|
||||
1: {{labels: []string{"name", "foo"}, alert: ¬ifier.Alert{State: notifier.StateFiring}}},
|
||||
}, map[int][]prompbmarshal.TimeSeries{
|
||||
0: {
|
||||
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "for-fired"}, {Name: "alertstate", Value: "pending"}, {Name: "name", Value: "foo"}},
|
||||
Samples: []prompbmarshal.Sample{{Value: 1, Timestamp: ts.UnixNano() / 1e6}}},
|
||||
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "for-fired"}, {Name: "name", Value: "foo"}},
|
||||
Samples: []prompbmarshal.Sample{{Value: float64(ts.Unix()), Timestamp: ts.UnixNano() / 1e6}}},
|
||||
},
|
||||
1: {
|
||||
// stale time series for `pending -> firing`
|
||||
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "for-fired"}, {Name: "alertstate", Value: "pending"}, {Name: "name", Value: "foo"}},
|
||||
Samples: []prompbmarshal.Sample{{Value: decimal.StaleNaN, Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}}},
|
||||
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "for-fired"}, {Name: "alertstate", Value: "firing"}, {Name: "name", Value: "foo"}},
|
||||
Samples: []prompbmarshal.Sample{{Value: 1, Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}}},
|
||||
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "for-fired"}, {Name: "name", Value: "foo"}},
|
||||
Samples: []prompbmarshal.Sample{{Value: float64(ts.Add(defaultStep).Unix()), Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}}},
|
||||
},
|
||||
})
|
||||
|
||||
f(newTestAlertingRule("for-pending=>empty", time.Second), [][]datasource.Metric{
|
||||
@@ -414,26 +272,6 @@ func TestAlertingRule_Exec(t *testing.T) {
|
||||
0: {{labels: []string{"name", "foo"}, alert: ¬ifier.Alert{State: notifier.StatePending}}},
|
||||
1: {{labels: []string{"name", "foo"}, alert: ¬ifier.Alert{State: notifier.StatePending}}},
|
||||
2: {},
|
||||
}, map[int][]prompbmarshal.TimeSeries{
|
||||
0: {
|
||||
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "for-pending=>empty"}, {Name: "alertstate", Value: "pending"}, {Name: "name", Value: "foo"}},
|
||||
Samples: []prompbmarshal.Sample{{Value: 1, Timestamp: ts.UnixNano() / 1e6}}},
|
||||
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "for-pending=>empty"}, {Name: "name", Value: "foo"}},
|
||||
Samples: []prompbmarshal.Sample{{Value: float64(ts.Unix()), Timestamp: ts.UnixNano() / 1e6}}},
|
||||
},
|
||||
1: {
|
||||
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "for-pending=>empty"}, {Name: "alertstate", Value: "pending"}, {Name: "name", Value: "foo"}},
|
||||
Samples: []prompbmarshal.Sample{{Value: 1, Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}}},
|
||||
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "for-pending=>empty"}, {Name: "name", Value: "foo"}},
|
||||
Samples: []prompbmarshal.Sample{{Value: float64(ts.Unix()), Timestamp: ts.Add(defaultStep).UnixNano() / 1e6}}},
|
||||
},
|
||||
// stale time series for `pending -> inactive`
|
||||
2: {
|
||||
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertMetricName}, {Name: "alertname", Value: "for-pending=>empty"}, {Name: "alertstate", Value: "pending"}, {Name: "name", Value: "foo"}},
|
||||
Samples: []prompbmarshal.Sample{{Value: decimal.StaleNaN, Timestamp: ts.Add(2*defaultStep).UnixNano() / 1e6}}},
|
||||
{Labels: []prompbmarshal.Label{{Name: "__name__", Value: alertForStateMetricName}, {Name: "alertname", Value: "for-pending=>empty"}, {Name: "name", Value: "foo"}},
|
||||
Samples: []prompbmarshal.Sample{{Value: decimal.StaleNaN, Timestamp: ts.Add(2*defaultStep).UnixNano() / 1e6}}},
|
||||
},
|
||||
})
|
||||
|
||||
f(newTestAlertingRule("for-pending=>firing=>inactive=>pending=>firing", defaultStep), [][]datasource.Metric{
|
||||
@@ -449,7 +287,7 @@ func TestAlertingRule_Exec(t *testing.T) {
|
||||
2: {{labels: []string{"name", "foo"}, alert: ¬ifier.Alert{State: notifier.StateInactive}}},
|
||||
3: {{labels: []string{"name", "foo"}, alert: ¬ifier.Alert{State: notifier.StatePending}}},
|
||||
4: {{labels: []string{"name", "foo"}, alert: ¬ifier.Alert{State: notifier.StateFiring}}},
|
||||
}, nil)
|
||||
})
|
||||
|
||||
f(newTestAlertingRuleWithCustomFields("for-pending=>firing=>keepfiring=>firing", defaultStep, 0, defaultStep, nil), [][]datasource.Metric{
|
||||
{metricWithLabels(t, "name", "foo")},
|
||||
@@ -462,7 +300,7 @@ func TestAlertingRule_Exec(t *testing.T) {
|
||||
1: {{labels: []string{"name", "foo"}, alert: ¬ifier.Alert{State: notifier.StateFiring}}},
|
||||
2: {{labels: []string{"name", "foo"}, alert: ¬ifier.Alert{State: notifier.StateFiring}}},
|
||||
3: {{labels: []string{"name", "foo"}, alert: ¬ifier.Alert{State: notifier.StateFiring}}},
|
||||
}, nil)
|
||||
})
|
||||
|
||||
f(newTestAlertingRuleWithCustomFields("for-pending=>firing=>keepfiring=>keepfiring=>inactive=>pending=>firing", defaultStep, 0, 2*defaultStep, nil), [][]datasource.Metric{
|
||||
{metricWithLabels(t, "name", "foo")},
|
||||
@@ -483,7 +321,7 @@ func TestAlertingRule_Exec(t *testing.T) {
|
||||
4: {{labels: []string{"name", "foo"}, alert: ¬ifier.Alert{State: notifier.StateInactive}}},
|
||||
5: {{labels: []string{"name", "foo"}, alert: ¬ifier.Alert{State: notifier.StatePending}}},
|
||||
6: {{labels: []string{"name", "foo"}, alert: ¬ifier.Alert{State: notifier.StateFiring}}},
|
||||
}, nil)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAlertingRuleExecRange(t *testing.T) {
|
||||
@@ -639,7 +477,7 @@ func TestAlertingRuleExecRange(t *testing.T) {
|
||||
{Values: []float64{1, 1, 1}, Timestamps: []int64{1, 3, 5}},
|
||||
{
|
||||
Values: []float64{1, 1}, Timestamps: []int64{1, 5},
|
||||
Labels: []prompbmarshal.Label{{Name: "foo", Value: "bar"}},
|
||||
Labels: []datasource.Label{{Name: "foo", Value: "bar"}},
|
||||
},
|
||||
}, []*notifier.Alert{
|
||||
{State: notifier.StatePending, ActiveAt: time.Unix(1, 0)},
|
||||
@@ -685,7 +523,7 @@ func TestAlertingRuleExecRange(t *testing.T) {
|
||||
{Values: []float64{1, 1}, Timestamps: []int64{1, 100}},
|
||||
{
|
||||
Values: []float64{1, 1}, Timestamps: []int64{1, 5},
|
||||
Labels: []prompbmarshal.Label{{Name: "foo", Value: "bar"}},
|
||||
Labels: []datasource.Label{{Name: "foo", Value: "bar"}},
|
||||
},
|
||||
}, []*notifier.Alert{
|
||||
{
|
||||
@@ -1209,7 +1047,7 @@ func newTestAlertingRuleWithCustomFields(name string, waitFor, evalInterval, kee
|
||||
|
||||
func TestAlertingRule_ToLabels(t *testing.T) {
|
||||
metric := datasource.Metric{
|
||||
Labels: []prompbmarshal.Label{
|
||||
Labels: []datasource.Label{
|
||||
{Name: "instance", Value: "0.0.0.0:8800"},
|
||||
{Name: "group", Value: "vmalert"},
|
||||
{Name: "alertname", Value: "ConfigurationReloadFailure"},
|
||||
|
||||
@@ -8,9 +8,12 @@ import (
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
||||
|
||||
"github.com/cheggaaa/pb/v3"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/config"
|
||||
@@ -18,6 +21,7 @@ import (
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/notifier"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/remotewrite"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/utils"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/decimal"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
|
||||
"github.com/VictoriaMetrics/metrics"
|
||||
@@ -346,9 +350,10 @@ func (g *Group) Start(ctx context.Context, nts func() []notifier.Notifier, rw re
|
||||
}
|
||||
|
||||
e := &executor{
|
||||
Rw: rw,
|
||||
Notifiers: nts,
|
||||
notifierHeaders: g.NotifierHeaders,
|
||||
Rw: rw,
|
||||
Notifiers: nts,
|
||||
notifierHeaders: g.NotifierHeaders,
|
||||
previouslySentSeriesToRW: make(map[uint64]map[string][]prompbmarshal.Label),
|
||||
}
|
||||
|
||||
g.infof("started")
|
||||
@@ -421,6 +426,8 @@ func (g *Group) Start(ctx context.Context, nts func() []notifier.Notifier, rw re
|
||||
continue
|
||||
}
|
||||
|
||||
// ensure that staleness is tracked for existing rules only
|
||||
e.purgeStaleSeries(g.Rules)
|
||||
e.notifierHeaders = g.NotifierHeaders
|
||||
g.mu.Unlock()
|
||||
|
||||
@@ -532,9 +539,10 @@ func (g *Group) Replay(start, end time.Time, rw remotewrite.RWClient, maxDataPoi
|
||||
// ExecOnce evaluates all the rules under group for once with given timestamp.
|
||||
func (g *Group) ExecOnce(ctx context.Context, nts func() []notifier.Notifier, rw remotewrite.RWClient, evalTS time.Time) chan error {
|
||||
e := &executor{
|
||||
Rw: rw,
|
||||
Notifiers: nts,
|
||||
notifierHeaders: g.NotifierHeaders,
|
||||
Rw: rw,
|
||||
Notifiers: nts,
|
||||
notifierHeaders: g.NotifierHeaders,
|
||||
previouslySentSeriesToRW: make(map[uint64]map[string][]prompbmarshal.Label),
|
||||
}
|
||||
if len(g.Rules) < 1 {
|
||||
return nil
|
||||
@@ -625,6 +633,13 @@ type executor struct {
|
||||
notifierHeaders map[string]string
|
||||
|
||||
Rw remotewrite.RWClient
|
||||
|
||||
previouslySentSeriesToRWMu sync.Mutex
|
||||
// previouslySentSeriesToRW stores series sent to RW on previous iteration
|
||||
// map[ruleID]map[ruleLabels][]prompb.Label
|
||||
// where `ruleID` is ID of the Rule within a Group
|
||||
// and `ruleLabels` is []prompb.Label marshalled to a string
|
||||
previouslySentSeriesToRW map[uint64]map[string][]prompbmarshal.Label
|
||||
}
|
||||
|
||||
// execConcurrently executes rules concurrently if concurrency>1
|
||||
@@ -691,6 +706,11 @@ func (e *executor) exec(ctx context.Context, r Rule, ts time.Time, resolveDurati
|
||||
if err := pushToRW(tss); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
staleSeries := e.getStaleSeries(r, tss, ts)
|
||||
if err := pushToRW(staleSeries); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
ar, ok := r.(*AlertingRule)
|
||||
@@ -717,3 +737,79 @@ func (e *executor) exec(ctx context.Context, r Rule, ts time.Time, resolveDurati
|
||||
wg.Wait()
|
||||
return errGr.Err()
|
||||
}
|
||||
|
||||
var bbPool bytesutil.ByteBufferPool
|
||||
|
||||
// getStaleSeries checks whether there are stale series from previously sent ones.
|
||||
func (e *executor) getStaleSeries(r Rule, tss []prompbmarshal.TimeSeries, timestamp time.Time) []prompbmarshal.TimeSeries {
|
||||
bb := bbPool.Get()
|
||||
defer bbPool.Put(bb)
|
||||
|
||||
ruleLabels := make(map[string][]prompbmarshal.Label, len(tss))
|
||||
for _, ts := range tss {
|
||||
// convert labels to strings, so we can compare with previously sent series
|
||||
bb.B = labelsToString(bb.B, ts.Labels)
|
||||
ruleLabels[string(bb.B)] = ts.Labels
|
||||
bb.Reset()
|
||||
}
|
||||
|
||||
rID := r.ID()
|
||||
var staleS []prompbmarshal.TimeSeries
|
||||
// check whether there are series which disappeared and need to be marked as stale
|
||||
e.previouslySentSeriesToRWMu.Lock()
|
||||
for key, labels := range e.previouslySentSeriesToRW[rID] {
|
||||
if _, ok := ruleLabels[key]; ok {
|
||||
continue
|
||||
}
|
||||
// previously sent series are missing in current series, so we mark them as stale
|
||||
ss := newTimeSeriesPB([]float64{decimal.StaleNaN}, []int64{timestamp.Unix()}, labels)
|
||||
staleS = append(staleS, ss)
|
||||
}
|
||||
// set previous series to current
|
||||
e.previouslySentSeriesToRW[rID] = ruleLabels
|
||||
e.previouslySentSeriesToRWMu.Unlock()
|
||||
|
||||
return staleS
|
||||
}
|
||||
|
||||
// purgeStaleSeries deletes references in tracked
|
||||
// previouslySentSeriesToRW list to Rules which aren't present
|
||||
// in the given activeRules list. The method is used when the list
|
||||
// of loaded rules has changed and executor has to remove
|
||||
// references to non-existing rules.
|
||||
func (e *executor) purgeStaleSeries(activeRules []Rule) {
|
||||
newPreviouslySentSeriesToRW := make(map[uint64]map[string][]prompbmarshal.Label)
|
||||
|
||||
e.previouslySentSeriesToRWMu.Lock()
|
||||
|
||||
for _, rule := range activeRules {
|
||||
id := rule.ID()
|
||||
prev, ok := e.previouslySentSeriesToRW[id]
|
||||
if ok {
|
||||
// keep previous series for staleness detection
|
||||
newPreviouslySentSeriesToRW[id] = prev
|
||||
}
|
||||
}
|
||||
e.previouslySentSeriesToRW = nil
|
||||
e.previouslySentSeriesToRW = newPreviouslySentSeriesToRW
|
||||
|
||||
e.previouslySentSeriesToRWMu.Unlock()
|
||||
}
|
||||
|
||||
func labelsToString(dst []byte, labels []prompbmarshal.Label) []byte {
|
||||
dst = append(dst, '{')
|
||||
for i, label := range labels {
|
||||
if len(label.Name) == 0 {
|
||||
dst = append(dst, "__name__"...)
|
||||
} else {
|
||||
dst = append(dst, label.Name...)
|
||||
}
|
||||
dst = append(dst, '=')
|
||||
dst = strconv.AppendQuote(dst, label.Value)
|
||||
if i < len(labels)-1 {
|
||||
dst = append(dst, ',')
|
||||
}
|
||||
}
|
||||
dst = append(dst, '}')
|
||||
return dst
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -16,6 +17,8 @@ import (
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/notifier"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/remotewrite"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/templates"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/decimal"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutils"
|
||||
)
|
||||
|
||||
@@ -380,6 +383,153 @@ func TestGetResolveDuration(t *testing.T) {
|
||||
f(2*time.Minute, 0, 1*time.Minute, 8*time.Minute)
|
||||
}
|
||||
|
||||
func TestGetStaleSeries(t *testing.T) {
|
||||
ts := time.Now()
|
||||
e := &executor{
|
||||
previouslySentSeriesToRW: make(map[uint64]map[string][]prompbmarshal.Label),
|
||||
}
|
||||
f := func(r Rule, labels, expLabels [][]prompbmarshal.Label) {
|
||||
t.Helper()
|
||||
|
||||
var tss []prompbmarshal.TimeSeries
|
||||
for _, l := range labels {
|
||||
tss = append(tss, newTimeSeriesPB([]float64{1}, []int64{ts.Unix()}, l))
|
||||
}
|
||||
staleS := e.getStaleSeries(r, tss, ts)
|
||||
if staleS == nil && expLabels == nil {
|
||||
return
|
||||
}
|
||||
if len(staleS) != len(expLabels) {
|
||||
t.Fatalf("expected to get %d stale series, got %d",
|
||||
len(expLabels), len(staleS))
|
||||
}
|
||||
for i, exp := range expLabels {
|
||||
got := staleS[i]
|
||||
if !reflect.DeepEqual(exp, got.Labels) {
|
||||
t.Fatalf("expected to get labels: \n%v;\ngot instead: \n%v",
|
||||
exp, got.Labels)
|
||||
}
|
||||
if len(got.Samples) != 1 {
|
||||
t.Fatalf("expected to have 1 sample; got %d", len(got.Samples))
|
||||
}
|
||||
if !decimal.IsStaleNaN(got.Samples[0].Value) {
|
||||
t.Fatalf("expected sample value to be %v; got %v", decimal.StaleNaN, got.Samples[0].Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// warn: keep in mind, that executor holds the state, so sequence of f calls matters
|
||||
|
||||
// single series
|
||||
f(&AlertingRule{RuleID: 1},
|
||||
[][]prompbmarshal.Label{toPromLabels(t, "__name__", "job:foo", "job", "foo")},
|
||||
nil)
|
||||
f(&AlertingRule{RuleID: 1},
|
||||
[][]prompbmarshal.Label{toPromLabels(t, "__name__", "job:foo", "job", "foo")},
|
||||
nil)
|
||||
f(&AlertingRule{RuleID: 1},
|
||||
nil,
|
||||
[][]prompbmarshal.Label{toPromLabels(t, "__name__", "job:foo", "job", "foo")})
|
||||
f(&AlertingRule{RuleID: 1},
|
||||
nil,
|
||||
nil)
|
||||
|
||||
// multiple series
|
||||
f(&AlertingRule{RuleID: 1},
|
||||
[][]prompbmarshal.Label{
|
||||
toPromLabels(t, "__name__", "job:foo", "job", "foo"),
|
||||
toPromLabels(t, "__name__", "job:foo", "job", "bar"),
|
||||
},
|
||||
nil)
|
||||
f(&AlertingRule{RuleID: 1},
|
||||
[][]prompbmarshal.Label{toPromLabels(t, "__name__", "job:foo", "job", "bar")},
|
||||
[][]prompbmarshal.Label{toPromLabels(t, "__name__", "job:foo", "job", "foo")})
|
||||
f(&AlertingRule{RuleID: 1},
|
||||
[][]prompbmarshal.Label{toPromLabels(t, "__name__", "job:foo", "job", "bar")},
|
||||
nil)
|
||||
f(&AlertingRule{RuleID: 1},
|
||||
nil,
|
||||
[][]prompbmarshal.Label{toPromLabels(t, "__name__", "job:foo", "job", "bar")})
|
||||
|
||||
// multiple rules and series
|
||||
f(&AlertingRule{RuleID: 1},
|
||||
[][]prompbmarshal.Label{
|
||||
toPromLabels(t, "__name__", "job:foo", "job", "foo"),
|
||||
toPromLabels(t, "__name__", "job:foo", "job", "bar"),
|
||||
},
|
||||
nil)
|
||||
f(&AlertingRule{RuleID: 2},
|
||||
[][]prompbmarshal.Label{
|
||||
toPromLabels(t, "__name__", "job:foo", "job", "foo"),
|
||||
toPromLabels(t, "__name__", "job:foo", "job", "bar"),
|
||||
},
|
||||
nil)
|
||||
f(&AlertingRule{RuleID: 1},
|
||||
[][]prompbmarshal.Label{toPromLabels(t, "__name__", "job:foo", "job", "bar")},
|
||||
[][]prompbmarshal.Label{toPromLabels(t, "__name__", "job:foo", "job", "foo")})
|
||||
f(&AlertingRule{RuleID: 1},
|
||||
[][]prompbmarshal.Label{toPromLabels(t, "__name__", "job:foo", "job", "bar")},
|
||||
nil)
|
||||
}
|
||||
|
||||
func TestPurgeStaleSeries(t *testing.T) {
|
||||
ts := time.Now()
|
||||
labels := toPromLabels(t, "__name__", "job:foo", "job", "foo")
|
||||
tss := []prompbmarshal.TimeSeries{newTimeSeriesPB([]float64{1}, []int64{ts.Unix()}, labels)}
|
||||
|
||||
f := func(curRules, newRules, expStaleRules []Rule) {
|
||||
t.Helper()
|
||||
e := &executor{
|
||||
previouslySentSeriesToRW: make(map[uint64]map[string][]prompbmarshal.Label),
|
||||
}
|
||||
// seed executor with series for
|
||||
// current rules
|
||||
for _, rule := range curRules {
|
||||
e.getStaleSeries(rule, tss, ts)
|
||||
}
|
||||
|
||||
e.purgeStaleSeries(newRules)
|
||||
|
||||
if len(e.previouslySentSeriesToRW) != len(expStaleRules) {
|
||||
t.Fatalf("expected to get %d stale series, got %d",
|
||||
len(expStaleRules), len(e.previouslySentSeriesToRW))
|
||||
}
|
||||
|
||||
for _, exp := range expStaleRules {
|
||||
if _, ok := e.previouslySentSeriesToRW[exp.ID()]; !ok {
|
||||
t.Fatalf("expected to have rule %d; got nil instead", exp.ID())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
f(nil, nil, nil)
|
||||
f(
|
||||
nil,
|
||||
[]Rule{&AlertingRule{RuleID: 1}},
|
||||
nil,
|
||||
)
|
||||
f(
|
||||
[]Rule{&AlertingRule{RuleID: 1}},
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
f(
|
||||
[]Rule{&AlertingRule{RuleID: 1}},
|
||||
[]Rule{&AlertingRule{RuleID: 2}},
|
||||
nil,
|
||||
)
|
||||
f(
|
||||
[]Rule{&AlertingRule{RuleID: 1}, &AlertingRule{RuleID: 2}},
|
||||
[]Rule{&AlertingRule{RuleID: 2}},
|
||||
[]Rule{&AlertingRule{RuleID: 2}},
|
||||
)
|
||||
f(
|
||||
[]Rule{&AlertingRule{RuleID: 1}, &AlertingRule{RuleID: 2}},
|
||||
[]Rule{&AlertingRule{RuleID: 1}, &AlertingRule{RuleID: 2}},
|
||||
[]Rule{&AlertingRule{RuleID: 1}, &AlertingRule{RuleID: 2}},
|
||||
)
|
||||
}
|
||||
|
||||
func TestFaultyNotifier(t *testing.T) {
|
||||
fq := &datasource.FakeQuerier{}
|
||||
fq.Add(metricWithValueAndLabels(t, 1, "__name__", "foo", "job", "bar"))
|
||||
@@ -430,7 +580,8 @@ func TestFaultyRW(t *testing.T) {
|
||||
}
|
||||
|
||||
e := &executor{
|
||||
Rw: &remotewrite.Client{},
|
||||
Rw: &remotewrite.Client{},
|
||||
previouslySentSeriesToRW: make(map[uint64]map[string][]prompbmarshal.Label),
|
||||
}
|
||||
|
||||
err := e.exec(context.Background(), r, time.Now(), 0, 10)
|
||||
|
||||
36
app/vmalert/rule/group_timing_test.go
Normal file
36
app/vmalert/rule/group_timing_test.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package rule
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
|
||||
)
|
||||
|
||||
func BenchmarkGetStaleSeries(b *testing.B) {
|
||||
ts := time.Now()
|
||||
n := 100
|
||||
payload := make([]prompbmarshal.TimeSeries, 0, n)
|
||||
for i := 0; i < n; i++ {
|
||||
s := fmt.Sprintf("%d", i)
|
||||
labels := toPromLabels(b,
|
||||
"__name__", "foo", ""+
|
||||
"instance", s,
|
||||
"job", s,
|
||||
"state", s,
|
||||
)
|
||||
payload = append(payload, newTimeSeriesPB([]float64{1}, []int64{ts.Unix()}, labels))
|
||||
}
|
||||
|
||||
e := &executor{
|
||||
previouslySentSeriesToRW: make(map[uint64]map[string][]prompbmarshal.Label),
|
||||
}
|
||||
ar := &AlertingRule{RuleID: 1}
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
e.getStaleSeries(ar, payload, ts)
|
||||
}
|
||||
}
|
||||
@@ -3,17 +3,16 @@ package rule
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/config"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/datasource"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/utils"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/decimal"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logstorage"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promrelabel"
|
||||
)
|
||||
|
||||
// RecordingRule is a Rule that supposed
|
||||
@@ -35,8 +34,6 @@ type RecordingRule struct {
|
||||
// during evaluations
|
||||
state *ruleState
|
||||
|
||||
lastEvaluation map[string]struct{}
|
||||
|
||||
metrics *recordingRuleMetrics
|
||||
}
|
||||
|
||||
@@ -116,7 +113,7 @@ func (rr *RecordingRule) execRange(ctx context.Context, start, end time.Time) ([
|
||||
var tss []prompbmarshal.TimeSeries
|
||||
for _, s := range res.Data {
|
||||
ts := rr.toTimeSeries(s)
|
||||
key := stringifyLabels(ts.Labels)
|
||||
key := stringifyLabels(ts)
|
||||
if _, ok := duplicates[key]; ok {
|
||||
return nil, fmt.Errorf("original metric %v; resulting labels %q: %w", s.Labels, key, errDuplicate)
|
||||
}
|
||||
@@ -158,47 +155,28 @@ func (rr *RecordingRule) exec(ctx context.Context, ts time.Time, limit int) ([]p
|
||||
return nil, curState.Err
|
||||
}
|
||||
|
||||
curEvaluation := make(map[string]struct{}, len(qMetrics))
|
||||
lastEvaluation := rr.lastEvaluation
|
||||
duplicates := make(map[string]struct{}, len(qMetrics))
|
||||
var tss []prompbmarshal.TimeSeries
|
||||
for _, r := range qMetrics {
|
||||
ts := rr.toTimeSeries(r)
|
||||
key := stringifyLabels(ts.Labels)
|
||||
if _, ok := curEvaluation[key]; ok {
|
||||
key := stringifyLabels(ts)
|
||||
if _, ok := duplicates[key]; ok {
|
||||
curState.Err = fmt.Errorf("original metric %v; resulting labels %q: %w", r, key, errDuplicate)
|
||||
return nil, curState.Err
|
||||
}
|
||||
curEvaluation[key] = struct{}{}
|
||||
delete(lastEvaluation, key)
|
||||
duplicates[key] = struct{}{}
|
||||
tss = append(tss, ts)
|
||||
}
|
||||
// check for stale time series
|
||||
for k := range lastEvaluation {
|
||||
tss = append(tss, prompbmarshal.TimeSeries{
|
||||
Labels: stringToLabels(k),
|
||||
Samples: []prompbmarshal.Sample{
|
||||
{Value: decimal.StaleNaN, Timestamp: ts.UnixNano() / 1e6},
|
||||
}})
|
||||
}
|
||||
rr.lastEvaluation = curEvaluation
|
||||
return tss, nil
|
||||
}
|
||||
|
||||
func stringToLabels(s string) []prompbmarshal.Label {
|
||||
labels := strings.Split(s, ",")
|
||||
rLabels := make([]prompbmarshal.Label, 0, len(labels))
|
||||
for i := range labels {
|
||||
if label := strings.Split(labels[i], "="); len(label) == 2 {
|
||||
rLabels = append(rLabels, prompbmarshal.Label{
|
||||
Name: label[0],
|
||||
Value: label[1],
|
||||
})
|
||||
}
|
||||
func stringifyLabels(ts prompbmarshal.TimeSeries) string {
|
||||
labels := ts.Labels
|
||||
if len(labels) > 1 {
|
||||
sort.Slice(labels, func(i, j int) bool {
|
||||
return labels[i].Name < labels[j].Name
|
||||
})
|
||||
}
|
||||
return rLabels
|
||||
}
|
||||
|
||||
func stringifyLabels(labels []prompbmarshal.Label) string {
|
||||
b := strings.Builder{}
|
||||
for i, l := range labels {
|
||||
b.WriteString(l.Name)
|
||||
@@ -212,27 +190,19 @@ func stringifyLabels(labels []prompbmarshal.Label) string {
|
||||
}
|
||||
|
||||
func (rr *RecordingRule) toTimeSeries(m datasource.Metric) prompbmarshal.TimeSeries {
|
||||
if preN := promrelabel.GetLabelByName(m.Labels, "__name__"); preN != nil {
|
||||
preN.Value = rr.Name
|
||||
} else {
|
||||
m.Labels = append(m.Labels, prompbmarshal.Label{
|
||||
Name: "__name__",
|
||||
Value: rr.Name,
|
||||
})
|
||||
labels := make(map[string]string)
|
||||
for _, l := range m.Labels {
|
||||
labels[l.Name] = l.Value
|
||||
}
|
||||
for k := range rr.Labels {
|
||||
prevLabel := promrelabel.GetLabelByName(m.Labels, k)
|
||||
if prevLabel != nil && prevLabel.Value != rr.Labels[k] {
|
||||
// Rename the prevLabel to "exported_" + label.Name
|
||||
prevLabel.Name = fmt.Sprintf("exported_%s", prevLabel.Name)
|
||||
labels["__name__"] = rr.Name
|
||||
// override existing labels with configured ones
|
||||
for k, v := range rr.Labels {
|
||||
if _, ok := labels[k]; ok && labels[k] != v {
|
||||
labels[fmt.Sprintf("exported_%s", k)] = labels[k]
|
||||
}
|
||||
m.Labels = append(m.Labels, prompbmarshal.Label{
|
||||
Name: k,
|
||||
Value: rr.Labels[k],
|
||||
})
|
||||
labels[k] = v
|
||||
}
|
||||
ts := newTimeSeries(m.Values, m.Timestamps, m.Labels)
|
||||
return ts
|
||||
return newTimeSeries(m.Values, m.Timestamps, labels)
|
||||
}
|
||||
|
||||
// updateWith copies all significant fields.
|
||||
|
||||
@@ -9,131 +9,59 @@ import (
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/datasource"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/utils"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/decimal"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
|
||||
)
|
||||
|
||||
func TestRecordingRule_Exec(t *testing.T) {
|
||||
ts, _ := time.Parse(time.RFC3339, "2024-10-29T00:00:00Z")
|
||||
const defaultStep = 5 * time.Millisecond
|
||||
|
||||
f := func(rule *RecordingRule, steps [][]datasource.Metric, tssExpected [][]prompbmarshal.TimeSeries) {
|
||||
f := func(rule *RecordingRule, metrics []datasource.Metric, tssExpected []prompbmarshal.TimeSeries) {
|
||||
t.Helper()
|
||||
|
||||
fq := &datasource.FakeQuerier{}
|
||||
for i, step := range steps {
|
||||
fq.Reset()
|
||||
fq.Add(step...)
|
||||
rule.q = fq
|
||||
rule.state = &ruleState{
|
||||
entries: make([]StateEntry, 10),
|
||||
}
|
||||
tss, err := rule.exec(context.TODO(), ts, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("fail to test rule %s: unexpected error: %s", rule.Name, err)
|
||||
}
|
||||
if err := compareTimeSeries(t, tssExpected[i], tss); err != nil {
|
||||
t.Fatalf("fail to test rule %s: time series mismatch on step %d: %s", rule.Name, i, err)
|
||||
}
|
||||
|
||||
ts = ts.Add(defaultStep)
|
||||
fq.Add(metrics...)
|
||||
rule.q = fq
|
||||
rule.state = &ruleState{
|
||||
entries: make([]StateEntry, 10),
|
||||
}
|
||||
tss, err := rule.exec(context.TODO(), time.Now(), 0)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected RecordingRule.exec error: %s", err)
|
||||
}
|
||||
if err := compareTimeSeries(t, tssExpected, tss); err != nil {
|
||||
t.Fatalf("timeseries missmatch: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
timestamp := time.Now()
|
||||
|
||||
f(&RecordingRule{
|
||||
Name: "foo",
|
||||
}, [][]datasource.Metric{{
|
||||
}, []datasource.Metric{
|
||||
metricWithValueAndLabels(t, 10, "__name__", "bar"),
|
||||
}}, [][]prompbmarshal.TimeSeries{{
|
||||
newTimeSeries([]float64{10}, []int64{ts.UnixNano()}, []prompbmarshal.Label{
|
||||
{
|
||||
Name: "__name__",
|
||||
Value: "foo",
|
||||
},
|
||||
}, []prompbmarshal.TimeSeries{
|
||||
newTimeSeries([]float64{10}, []int64{timestamp.UnixNano()}, map[string]string{
|
||||
"__name__": "foo",
|
||||
}),
|
||||
}})
|
||||
})
|
||||
|
||||
f(&RecordingRule{
|
||||
Name: "foobarbaz",
|
||||
}, [][]datasource.Metric{
|
||||
{
|
||||
metricWithValueAndLabels(t, 1, "__name__", "foo", "job", "foo"),
|
||||
metricWithValueAndLabels(t, 2, "__name__", "bar", "job", "bar"),
|
||||
},
|
||||
{
|
||||
metricWithValueAndLabels(t, 10, "__name__", "foo", "job", "foo"),
|
||||
},
|
||||
{
|
||||
metricWithValueAndLabels(t, 10, "__name__", "foo", "job", "bar"),
|
||||
},
|
||||
}, [][]prompbmarshal.TimeSeries{
|
||||
{
|
||||
newTimeSeries([]float64{1}, []int64{ts.UnixNano()}, []prompbmarshal.Label{
|
||||
{
|
||||
Name: "__name__",
|
||||
Value: "foobarbaz",
|
||||
},
|
||||
{
|
||||
Name: "job",
|
||||
Value: "foo",
|
||||
},
|
||||
}),
|
||||
newTimeSeries([]float64{2}, []int64{ts.UnixNano()}, []prompbmarshal.Label{
|
||||
{
|
||||
Name: "__name__",
|
||||
Value: "foobarbaz",
|
||||
},
|
||||
{
|
||||
Name: "job",
|
||||
Value: "bar",
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
newTimeSeries([]float64{10}, []int64{ts.Add(defaultStep).UnixNano()}, []prompbmarshal.Label{
|
||||
{
|
||||
Name: "__name__",
|
||||
Value: "foobarbaz",
|
||||
},
|
||||
{
|
||||
Name: "job",
|
||||
Value: "foo",
|
||||
},
|
||||
}),
|
||||
// stale time series
|
||||
newTimeSeries([]float64{decimal.StaleNaN}, []int64{ts.Add(defaultStep).UnixNano()}, []prompbmarshal.Label{
|
||||
{
|
||||
Name: "__name__",
|
||||
Value: "foobarbaz",
|
||||
},
|
||||
{
|
||||
Name: "job",
|
||||
Value: "bar",
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
newTimeSeries([]float64{10}, []int64{ts.Add(2 * defaultStep).UnixNano()}, []prompbmarshal.Label{
|
||||
{
|
||||
Name: "__name__",
|
||||
Value: "foobarbaz",
|
||||
},
|
||||
{
|
||||
Name: "job",
|
||||
Value: "bar",
|
||||
},
|
||||
}),
|
||||
newTimeSeries([]float64{decimal.StaleNaN}, []int64{ts.Add(2 * defaultStep).UnixNano()}, []prompbmarshal.Label{
|
||||
{
|
||||
Name: "__name__",
|
||||
Value: "foobarbaz",
|
||||
},
|
||||
{
|
||||
Name: "job",
|
||||
Value: "foo",
|
||||
},
|
||||
}),
|
||||
},
|
||||
}, []datasource.Metric{
|
||||
metricWithValueAndLabels(t, 1, "__name__", "foo", "job", "foo"),
|
||||
metricWithValueAndLabels(t, 2, "__name__", "bar", "job", "bar"),
|
||||
metricWithValueAndLabels(t, 3, "__name__", "baz", "job", "baz"),
|
||||
}, []prompbmarshal.TimeSeries{
|
||||
newTimeSeries([]float64{1}, []int64{timestamp.UnixNano()}, map[string]string{
|
||||
"__name__": "foobarbaz",
|
||||
"job": "foo",
|
||||
}),
|
||||
newTimeSeries([]float64{2}, []int64{timestamp.UnixNano()}, map[string]string{
|
||||
"__name__": "foobarbaz",
|
||||
"job": "bar",
|
||||
}),
|
||||
newTimeSeries([]float64{3}, []int64{timestamp.UnixNano()}, map[string]string{
|
||||
"__name__": "foobarbaz",
|
||||
"job": "baz",
|
||||
}),
|
||||
})
|
||||
|
||||
f(&RecordingRule{
|
||||
@@ -141,44 +69,22 @@ func TestRecordingRule_Exec(t *testing.T) {
|
||||
Labels: map[string]string{
|
||||
"source": "test",
|
||||
},
|
||||
}, [][]datasource.Metric{{
|
||||
}, []datasource.Metric{
|
||||
metricWithValueAndLabels(t, 2, "__name__", "foo", "job", "foo"),
|
||||
metricWithValueAndLabels(t, 1, "__name__", "bar", "job", "bar", "source", "origin"),
|
||||
}}, [][]prompbmarshal.TimeSeries{{
|
||||
newTimeSeries([]float64{2}, []int64{ts.UnixNano()}, []prompbmarshal.Label{
|
||||
{
|
||||
Name: "__name__",
|
||||
Value: "job:foo",
|
||||
},
|
||||
{
|
||||
Name: "job",
|
||||
Value: "foo",
|
||||
},
|
||||
{
|
||||
Name: "source",
|
||||
Value: "test",
|
||||
},
|
||||
}, []prompbmarshal.TimeSeries{
|
||||
newTimeSeries([]float64{2}, []int64{timestamp.UnixNano()}, map[string]string{
|
||||
"__name__": "job:foo",
|
||||
"job": "foo",
|
||||
"source": "test",
|
||||
}),
|
||||
newTimeSeries([]float64{1}, []int64{ts.UnixNano()},
|
||||
[]prompbmarshal.Label{
|
||||
{
|
||||
Name: "__name__",
|
||||
Value: "job:foo",
|
||||
},
|
||||
{
|
||||
Name: "job",
|
||||
Value: "bar",
|
||||
},
|
||||
{
|
||||
Name: "source",
|
||||
Value: "test",
|
||||
},
|
||||
{
|
||||
Name: "exported_source",
|
||||
Value: "origin",
|
||||
},
|
||||
}),
|
||||
}})
|
||||
newTimeSeries([]float64{1}, []int64{timestamp.UnixNano()}, map[string]string{
|
||||
"__name__": "job:foo",
|
||||
"job": "bar",
|
||||
"source": "test",
|
||||
"exported_source": "origin",
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
func TestRecordingRule_ExecRange(t *testing.T) {
|
||||
@@ -204,13 +110,9 @@ func TestRecordingRule_ExecRange(t *testing.T) {
|
||||
}, []datasource.Metric{
|
||||
metricWithValuesAndLabels(t, []float64{10, 20, 30}, "__name__", "bar"),
|
||||
}, []prompbmarshal.TimeSeries{
|
||||
newTimeSeries([]float64{10, 20, 30}, []int64{timestamp.UnixNano(), timestamp.UnixNano(), timestamp.UnixNano()},
|
||||
[]prompbmarshal.Label{
|
||||
{
|
||||
Name: "__name__",
|
||||
Value: "foo",
|
||||
},
|
||||
}),
|
||||
newTimeSeries([]float64{10, 20, 30}, []int64{timestamp.UnixNano(), timestamp.UnixNano(), timestamp.UnixNano()}, map[string]string{
|
||||
"__name__": "foo",
|
||||
}),
|
||||
})
|
||||
|
||||
f(&RecordingRule{
|
||||
@@ -220,36 +122,18 @@ func TestRecordingRule_ExecRange(t *testing.T) {
|
||||
metricWithValuesAndLabels(t, []float64{2, 3}, "__name__", "bar", "job", "bar"),
|
||||
metricWithValuesAndLabels(t, []float64{4, 5, 6}, "__name__", "baz", "job", "baz"),
|
||||
}, []prompbmarshal.TimeSeries{
|
||||
newTimeSeries([]float64{1}, []int64{timestamp.UnixNano()}, []prompbmarshal.Label{
|
||||
{
|
||||
Name: "__name__",
|
||||
Value: "foobarbaz",
|
||||
},
|
||||
{
|
||||
Name: "job",
|
||||
Value: "foo",
|
||||
},
|
||||
newTimeSeries([]float64{1}, []int64{timestamp.UnixNano()}, map[string]string{
|
||||
"__name__": "foobarbaz",
|
||||
"job": "foo",
|
||||
}),
|
||||
newTimeSeries([]float64{2, 3}, []int64{timestamp.UnixNano(), timestamp.UnixNano()}, []prompbmarshal.Label{
|
||||
{
|
||||
Name: "__name__",
|
||||
Value: "foobarbaz",
|
||||
},
|
||||
{
|
||||
Name: "job",
|
||||
Value: "bar",
|
||||
},
|
||||
newTimeSeries([]float64{2, 3}, []int64{timestamp.UnixNano(), timestamp.UnixNano()}, map[string]string{
|
||||
"__name__": "foobarbaz",
|
||||
"job": "bar",
|
||||
}),
|
||||
newTimeSeries([]float64{4, 5, 6},
|
||||
[]int64{timestamp.UnixNano(), timestamp.UnixNano(), timestamp.UnixNano()}, []prompbmarshal.Label{
|
||||
{
|
||||
Name: "__name__",
|
||||
Value: "foobarbaz",
|
||||
},
|
||||
{
|
||||
Name: "job",
|
||||
Value: "baz",
|
||||
},
|
||||
[]int64{timestamp.UnixNano(), timestamp.UnixNano(), timestamp.UnixNano()}, map[string]string{
|
||||
"__name__": "foobarbaz",
|
||||
"job": "baz",
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -262,35 +146,16 @@ func TestRecordingRule_ExecRange(t *testing.T) {
|
||||
metricWithValueAndLabels(t, 2, "__name__", "foo", "job", "foo"),
|
||||
metricWithValueAndLabels(t, 1, "__name__", "bar", "job", "bar"),
|
||||
}, []prompbmarshal.TimeSeries{
|
||||
newTimeSeries([]float64{2}, []int64{timestamp.UnixNano()}, []prompbmarshal.Label{
|
||||
{
|
||||
Name: "__name__",
|
||||
Value: "job:foo",
|
||||
},
|
||||
{
|
||||
Name: "job",
|
||||
Value: "foo",
|
||||
},
|
||||
{
|
||||
Name: "source",
|
||||
Value: "test",
|
||||
},
|
||||
newTimeSeries([]float64{2}, []int64{timestamp.UnixNano()}, map[string]string{
|
||||
"__name__": "job:foo",
|
||||
"job": "foo",
|
||||
"source": "test",
|
||||
}),
|
||||
newTimeSeries([]float64{1}, []int64{timestamp.UnixNano()}, map[string]string{
|
||||
"__name__": "job:foo",
|
||||
"job": "bar",
|
||||
"source": "test",
|
||||
}),
|
||||
newTimeSeries([]float64{1}, []int64{timestamp.UnixNano()},
|
||||
[]prompbmarshal.Label{
|
||||
{
|
||||
Name: "__name__",
|
||||
Value: "job:foo",
|
||||
},
|
||||
{
|
||||
Name: "job",
|
||||
Value: "bar",
|
||||
},
|
||||
{
|
||||
Name: "source",
|
||||
Value: "test",
|
||||
},
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/datasource"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/notifier"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/decimal"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
|
||||
)
|
||||
|
||||
@@ -88,7 +87,7 @@ func metricWithLabels(t *testing.T, labels ...string) datasource.Metric {
|
||||
}
|
||||
m := datasource.Metric{Values: []float64{1}, Timestamps: []int64{1}}
|
||||
for i := 0; i < len(labels); i += 2 {
|
||||
m.Labels = append(m.Labels, prompbmarshal.Label{
|
||||
m.Labels = append(m.Labels, datasource.Label{
|
||||
Name: labels[i],
|
||||
Value: labels[i+1],
|
||||
})
|
||||
@@ -96,6 +95,21 @@ func metricWithLabels(t *testing.T, labels ...string) datasource.Metric {
|
||||
return m
|
||||
}
|
||||
|
||||
func toPromLabels(t testing.TB, labels ...string) []prompbmarshal.Label {
|
||||
t.Helper()
|
||||
if len(labels) == 0 || len(labels)%2 != 0 {
|
||||
t.Fatalf("expected to get even number of labels")
|
||||
}
|
||||
var ls []prompbmarshal.Label
|
||||
for i := 0; i < len(labels); i += 2 {
|
||||
ls = append(ls, prompbmarshal.Label{
|
||||
Name: labels[i],
|
||||
Value: labels[i+1],
|
||||
})
|
||||
}
|
||||
return ls
|
||||
}
|
||||
|
||||
func compareTimeSeries(t *testing.T, a, b []prompbmarshal.TimeSeries) error {
|
||||
t.Helper()
|
||||
if len(a) != len(b) {
|
||||
@@ -108,7 +122,7 @@ func compareTimeSeries(t *testing.T, a, b []prompbmarshal.TimeSeries) error {
|
||||
}
|
||||
for i, exp := range expTS.Samples {
|
||||
got := gotTS.Samples[i]
|
||||
if got.Value != exp.Value && (!decimal.IsStaleNaN(got.Value) || !decimal.IsStaleNaN(exp.Value)) {
|
||||
if got.Value != exp.Value {
|
||||
return fmt.Errorf("expected value %.2f; got %.2f", exp.Value, got.Value)
|
||||
}
|
||||
// timestamp validation isn't always correct for now.
|
||||
|
||||
@@ -9,14 +9,10 @@ import (
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/datasource"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promrelabel"
|
||||
)
|
||||
|
||||
// newTimeSeries first sorts given labels, then returns new time series.
|
||||
func newTimeSeries(values []float64, timestamps []int64, labels []prompbmarshal.Label) prompbmarshal.TimeSeries {
|
||||
promrelabel.SortLabels(labels)
|
||||
func newTimeSeries(values []float64, timestamps []int64, labels map[string]string) prompbmarshal.TimeSeries {
|
||||
ts := prompbmarshal.TimeSeries{
|
||||
Labels: labels,
|
||||
Samples: make([]prompbmarshal.Sample, len(values)),
|
||||
}
|
||||
for i := range values {
|
||||
@@ -25,6 +21,34 @@ func newTimeSeries(values []float64, timestamps []int64, labels []prompbmarshal.
|
||||
Timestamp: time.Unix(timestamps[i], 0).UnixNano() / 1e6,
|
||||
}
|
||||
}
|
||||
keys := make([]string, 0, len(labels))
|
||||
for k := range labels {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys) // make order deterministic
|
||||
for _, key := range keys {
|
||||
ts.Labels = append(ts.Labels, prompbmarshal.Label{
|
||||
Name: key,
|
||||
Value: labels[key],
|
||||
})
|
||||
}
|
||||
return ts
|
||||
}
|
||||
|
||||
// newTimeSeriesPB creates prompbmarshal.TimeSeries with given
|
||||
// values, timestamps and labels.
|
||||
// It expects that labels are already sorted.
|
||||
func newTimeSeriesPB(values []float64, timestamps []int64, labels []prompbmarshal.Label) prompbmarshal.TimeSeries {
|
||||
ts := prompbmarshal.TimeSeries{
|
||||
Samples: make([]prompbmarshal.Sample, len(values)),
|
||||
}
|
||||
for i := range values {
|
||||
ts.Samples[i] = prompbmarshal.Sample{
|
||||
Value: values[i],
|
||||
Timestamp: time.Unix(timestamps[i], 0).UnixNano() / 1e6,
|
||||
}
|
||||
}
|
||||
ts.Labels = labels
|
||||
return ts
|
||||
}
|
||||
|
||||
|
||||
@@ -169,8 +169,6 @@ func GetWithFuncs(funcs textTpl.FuncMap) (*textTpl.Template, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Clone() doesn't copy tpl Options, so we set them manually
|
||||
tmpl = tmpl.Option("missingkey=zero")
|
||||
return tmpl.Funcs(funcs), nil
|
||||
}
|
||||
|
||||
|
||||
@@ -462,12 +462,17 @@ func getLeastLoadedBackendURL(bus []*backendURL, atomicCounter *atomic.Uint32) *
|
||||
|
||||
// Slow path - select other backend urls.
|
||||
n := atomicCounter.Add(1) - 1
|
||||
buMin := bus[n%uint32(len(bus))]
|
||||
for i := uint32(0); i < uint32(len(bus)); i++ {
|
||||
idx := (n + i) % uint32(len(bus))
|
||||
bu := bus[idx]
|
||||
if bu.isBroken() {
|
||||
continue
|
||||
}
|
||||
if buMin.isBroken() {
|
||||
// verify that buMin isn't set as broken
|
||||
buMin = bu
|
||||
}
|
||||
if bu.concurrentRequests.Load() == 0 {
|
||||
// Fast path - return the backend with zero concurrently executed requests.
|
||||
// Do not use CompareAndSwap() instead of Load(), since it is much slower on systems with many CPU cores.
|
||||
@@ -477,13 +482,12 @@ func getLeastLoadedBackendURL(bus []*backendURL, atomicCounter *atomic.Uint32) *
|
||||
}
|
||||
|
||||
// Slow path - return the backend with the minimum number of concurrently executed requests.
|
||||
buMin := bus[n%uint32(len(bus))]
|
||||
minRequests := buMin.concurrentRequests.Load()
|
||||
for _, bu := range bus {
|
||||
if bu.isBroken() {
|
||||
continue
|
||||
}
|
||||
if n := bu.concurrentRequests.Load(); n < minRequests || buMin.isBroken() {
|
||||
if n := bu.concurrentRequests.Load(); n < minRequests {
|
||||
buMin = bu
|
||||
minRequests = n
|
||||
}
|
||||
@@ -860,23 +864,22 @@ func (ui *UserInfo) initURLs() error {
|
||||
loadBalancingPolicy := *defaultLoadBalancingPolicy
|
||||
dropSrcPathPrefixParts := 0
|
||||
discoverBackendIPs := *discoverBackendIPsGlobal
|
||||
if ui.RetryStatusCodes != nil {
|
||||
retryStatusCodes = ui.RetryStatusCodes
|
||||
}
|
||||
if ui.LoadBalancingPolicy != "" {
|
||||
loadBalancingPolicy = ui.LoadBalancingPolicy
|
||||
}
|
||||
if ui.DropSrcPathPrefixParts != nil {
|
||||
dropSrcPathPrefixParts = *ui.DropSrcPathPrefixParts
|
||||
}
|
||||
if ui.DiscoverBackendIPs != nil {
|
||||
discoverBackendIPs = *ui.DiscoverBackendIPs
|
||||
}
|
||||
|
||||
if ui.URLPrefix != nil {
|
||||
if err := ui.URLPrefix.sanitizeAndInitialize(); err != nil {
|
||||
return err
|
||||
}
|
||||
if ui.RetryStatusCodes != nil {
|
||||
retryStatusCodes = ui.RetryStatusCodes
|
||||
}
|
||||
if ui.LoadBalancingPolicy != "" {
|
||||
loadBalancingPolicy = ui.LoadBalancingPolicy
|
||||
}
|
||||
if ui.DropSrcPathPrefixParts != nil {
|
||||
dropSrcPathPrefixParts = *ui.DropSrcPathPrefixParts
|
||||
}
|
||||
if ui.DiscoverBackendIPs != nil {
|
||||
discoverBackendIPs = *ui.DiscoverBackendIPs
|
||||
}
|
||||
ui.URLPrefix.retryStatusCodes = retryStatusCodes
|
||||
ui.URLPrefix.dropSrcPathPrefixParts = dropSrcPathPrefixParts
|
||||
ui.URLPrefix.discoverBackendIPs = discoverBackendIPs
|
||||
|
||||
@@ -123,12 +123,6 @@ func requestHandler(w http.ResponseWriter, r *http.Request) bool {
|
||||
|
||||
ui := getUserInfoByAuthTokens(ats)
|
||||
if ui == nil {
|
||||
uu := authConfig.Load().UnauthorizedUser
|
||||
if uu != nil {
|
||||
processUserRequest(w, r, uu)
|
||||
return true
|
||||
}
|
||||
|
||||
invalidAuthTokenRequests.Inc()
|
||||
if *logInvalidAuthTokens {
|
||||
err := fmt.Errorf("cannot authorize request with auth tokens %q", ats)
|
||||
|
||||
@@ -90,20 +90,6 @@ User-Agent: vmauth
|
||||
X-Forwarded-For: 12.34.56.78, 42.2.3.84`
|
||||
f(cfgStr, requestURL, backendHandler, responseExpected)
|
||||
|
||||
// routing of all failed to authorize requests to unauthorized_user (issue #7543)
|
||||
cfgStr = `
|
||||
unauthorized_user:
|
||||
url_prefix: "{BACKEND}/foo"
|
||||
keep_original_host: true`
|
||||
requestURL = "http://foo:invalid-secret@some-host.com/abc/def"
|
||||
backendHandler = func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "requested_url=http://%s%s", r.Host, r.URL)
|
||||
}
|
||||
responseExpected = `
|
||||
statusCode=200
|
||||
requested_url=http://some-host.com/foo/abc/def`
|
||||
f(cfgStr, requestURL, backendHandler, responseExpected)
|
||||
|
||||
// keep_original_host
|
||||
cfgStr = `
|
||||
unauthorized_user:
|
||||
|
||||
@@ -187,10 +187,6 @@ func TestCreateTargetURLSuccess(t *testing.T) {
|
||||
RetryStatusCodes: []int{},
|
||||
DropSrcPathPrefixParts: intp(0),
|
||||
},
|
||||
{
|
||||
SrcPaths: getRegexs([]string{"/metrics"}),
|
||||
URLPrefix: mustParseURL("http://metrics-server"),
|
||||
},
|
||||
},
|
||||
URLPrefix: mustParseURL("http://default-server"),
|
||||
HeadersConf: HeadersConf{
|
||||
@@ -210,35 +206,6 @@ func TestCreateTargetURLSuccess(t *testing.T) {
|
||||
"bb: aaa", "x: y", []int{502}, "least_loaded", 2)
|
||||
f(ui, "https://foo-host/api/v1/write", "http://vminsert/0/prometheus/api/v1/write", "", "", []int{}, "least_loaded", 0)
|
||||
f(ui, "https://foo-host/foo/bar/api/v1/query_range", "http://default-server/api/v1/query_range", "bb: aaa", "x: y", []int{502}, "least_loaded", 2)
|
||||
f(ui, "https://foo-host/metrics", "http://metrics-server", "", "", []int{502}, "least_loaded", 2)
|
||||
|
||||
// Complex routing with `url_map` without global url_prefix
|
||||
ui = &UserInfo{
|
||||
URLMaps: []URLMap{
|
||||
{
|
||||
SrcPaths: getRegexs([]string{"/api/v1/write"}),
|
||||
URLPrefix: mustParseURL("http://vminsert/0/prometheus"),
|
||||
RetryStatusCodes: []int{},
|
||||
DropSrcPathPrefixParts: intp(0),
|
||||
},
|
||||
{
|
||||
SrcPaths: getRegexs([]string{"/metrics/a/b"}),
|
||||
URLPrefix: mustParseURL("http://metrics-server"),
|
||||
},
|
||||
},
|
||||
HeadersConf: HeadersConf{
|
||||
RequestHeaders: []*Header{
|
||||
mustNewHeader("'bb: aaa'"),
|
||||
},
|
||||
ResponseHeaders: []*Header{
|
||||
mustNewHeader("'x: y'"),
|
||||
},
|
||||
},
|
||||
RetryStatusCodes: []int{502},
|
||||
DropSrcPathPrefixParts: intp(2),
|
||||
}
|
||||
f(ui, "https://foo-host/api/v1/write", "http://vminsert/0/prometheus/api/v1/write", "", "", []int{}, "least_loaded", 0)
|
||||
f(ui, "https://foo-host/metrics/a/b", "http://metrics-server/b", "", "", []int{502}, "least_loaded", 2)
|
||||
|
||||
// Complex routing regexp paths in `url_map`
|
||||
ui = &UserInfo{
|
||||
|
||||
@@ -616,7 +616,7 @@ var (
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: vmNativeDisableBinaryProtocol,
|
||||
Usage: "Whether to use https://docs.victoriametrics.com/#how-to-export-data-in-json-line-format " +
|
||||
Usage: "Whether to use https://docs.victoriametrics.com/#how-to-export-data-in-json-line-format" +
|
||||
"instead of https://docs.victoriametrics.com/#how-to-export-data-in-native-format API." +
|
||||
"Binary export/import API protocol implies less network and resource usage, as it transfers compressed binary data blocks." +
|
||||
"Non-binary export/import API is less efficient, but supports deduplication if it is configured on vm-native-src-addr side.",
|
||||
|
||||
@@ -266,7 +266,7 @@ func main() {
|
||||
},
|
||||
{
|
||||
Name: "vm-native",
|
||||
Usage: "Migrate time series between VictoriaMetrics installations",
|
||||
Usage: "Migrate time series between VictoriaMetrics installations via native binary format",
|
||||
Flags: mergeFlags(globalFlags, vmNativeFlags),
|
||||
Before: beforeFn,
|
||||
Action: func(c *cli.Context) error {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"files": {
|
||||
"main.css": "./static/css/main.d781989c.css",
|
||||
"main.js": "./static/js/main.a7037969.js",
|
||||
"main.js": "./static/js/main.7ec4e6eb.js",
|
||||
"static/js/685.f772060c.chunk.js": "./static/js/685.f772060c.chunk.js",
|
||||
"static/media/MetricsQL.md": "./static/media/MetricsQL.a00044c91d9781cf8557.md",
|
||||
"index.html": "./index.html"
|
||||
},
|
||||
"entrypoints": [
|
||||
"static/css/main.d781989c.css",
|
||||
"static/js/main.a7037969.js"
|
||||
"static/js/main.7ec4e6eb.js"
|
||||
]
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="./favicon.svg"/><link rel="apple-touch-icon" href="./favicon.svg"/><link rel="mask-icon" href="./favicon.svg" color="#000000"><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=5"/><meta name="theme-color" content="#000000"/><meta name="description" content="Explore and troubleshoot your VictoriaMetrics data"/><link rel="manifest" href="./manifest.json"/><title>vmui</title><script src="./dashboards/index.js" type="module"></script><meta name="twitter:card" content="summary"><meta name="twitter:title" content="UI for VictoriaMetrics"><meta name="twitter:site" content="@https://victoriametrics.com/"><meta name="twitter:description" content="Explore and troubleshoot your VictoriaMetrics data"><meta name="twitter:image" content="./preview.jpg"><meta property="og:type" content="website"><meta property="og:title" content="UI for VictoriaMetrics"><meta property="og:url" content="https://victoriametrics.com/"><meta property="og:description" content="Explore and troubleshoot your VictoriaMetrics data"><script defer="defer" src="./static/js/main.a7037969.js"></script><link href="./static/css/main.d781989c.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="./favicon.svg"/><link rel="apple-touch-icon" href="./favicon.svg"/><link rel="mask-icon" href="./favicon.svg" color="#000000"><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=5"/><meta name="theme-color" content="#000000"/><meta name="description" content="Explore and troubleshoot your VictoriaMetrics data"/><link rel="manifest" href="./manifest.json"/><title>vmui</title><script src="./dashboards/index.js" type="module"></script><meta name="twitter:card" content="summary"><meta name="twitter:title" content="UI for VictoriaMetrics"><meta name="twitter:site" content="@https://victoriametrics.com/"><meta name="twitter:description" content="Explore and troubleshoot your VictoriaMetrics data"><meta name="twitter:image" content="./preview.jpg"><meta property="og:type" content="website"><meta property="og:title" content="UI for VictoriaMetrics"><meta property="og:url" content="https://victoriametrics.com/"><meta property="og:description" content="Explore and troubleshoot your VictoriaMetrics data"><script defer="defer" src="./static/js/main.7ec4e6eb.js"></script><link href="./static/css/main.d781989c.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
||||
File diff suppressed because one or more lines are too long
@@ -1,12 +1,11 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/apptest"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Data used in examples in
|
||||
@@ -46,7 +45,6 @@ func TestSingleKeyConceptsQuery(t *testing.T) {
|
||||
|
||||
testInstantQuery(t, vmsingle, opts)
|
||||
testRangeQuery(t, vmsingle, opts)
|
||||
testRangeQueryIsEquivalentToManyInstantQueries(t, vmsingle, opts)
|
||||
}
|
||||
|
||||
// TestClusterKeyConceptsQuery verifies cases from https://docs.victoriametrics.com/keyconcepts/#query-data
|
||||
@@ -89,14 +87,13 @@ func TestClusterKeyConceptsQuery(t *testing.T) {
|
||||
|
||||
testInstantQuery(t, vmselect, opts)
|
||||
testRangeQuery(t, vmselect, opts)
|
||||
testRangeQueryIsEquivalentToManyInstantQueries(t, vmselect, opts)
|
||||
}
|
||||
|
||||
// testInstantQuery verifies the statements made in the `Instant query` section
|
||||
// of the VictoriaMetrics documentation. See:
|
||||
// vmsingleInstantQuery verifies the statements made in the
|
||||
// `Instant query` section of the VictoriaMetrics documentation. See:
|
||||
// https://docs.victoriametrics.com/keyconcepts/#instant-query
|
||||
func testInstantQuery(t *testing.T, q apptest.PrometheusQuerier, opts apptest.QueryOpts) {
|
||||
// Get the value of the foo_bar time series at 2022-05-10T08:03:00Z with the
|
||||
// Get the value of the foo_bar time series at 2022-05-10Z08:03:00Z with the
|
||||
// step of 5m and timeout 5s. There is no sample at exactly this timestamp.
|
||||
// Therefore, VictoriaMetrics will search for the nearest sample within the
|
||||
// [time-5m..time] interval.
|
||||
@@ -107,7 +104,7 @@ func testInstantQuery(t *testing.T, q apptest.PrometheusQuerier, opts apptest.Qu
|
||||
t.Errorf("unexpected response (-want, +got):\n%s", diff)
|
||||
}
|
||||
|
||||
// Get the value of the foo_bar time series at 2022-05-10T08:18:00Z with the
|
||||
// Get the value of the foo_bar time series at 2022-05-10Z08:18:00Z with the
|
||||
// step of 1m and timeout 5s. There is no sample at this timestamp.
|
||||
// Therefore, VictoriaMetrics will search for the nearest sample within the
|
||||
// [time-1m..time] interval. Since the nearest sample is 2m away and the
|
||||
@@ -118,117 +115,40 @@ func testInstantQuery(t *testing.T, q apptest.PrometheusQuerier, opts apptest.Qu
|
||||
}
|
||||
}
|
||||
|
||||
// testRangeQuery verifies the statements made in the `Range query` section of
|
||||
// the VictoriaMetrics documentation. See:
|
||||
// vmsingleRangeQuery verifies the statements made in the
|
||||
// `Range query` section of the VictoriaMetrics documentation. See:
|
||||
// https://docs.victoriametrics.com/keyconcepts/#range-query
|
||||
func testRangeQuery(t *testing.T, q apptest.PrometheusQuerier, opts apptest.QueryOpts) {
|
||||
f := func(start, end, step string, wantSamples []*apptest.Sample) {
|
||||
t.Helper()
|
||||
|
||||
got := q.PrometheusAPIV1QueryRange(t, "foo_bar", start, end, step, opts)
|
||||
want := apptest.NewPrometheusAPIV1QueryResponse(t, `{"data": {"result": [{"metric": {"__name__": "foo_bar"}, "values": []}]}}`)
|
||||
want.Data.Result[0].Samples = wantSamples
|
||||
opt := cmpopts.IgnoreFields(apptest.PrometheusAPIV1QueryResponse{}, "Status", "Data.ResultType")
|
||||
if diff := cmp.Diff(want, got, opt); diff != "" {
|
||||
t.Errorf("unexpected response (-want, +got):\n%s", diff)
|
||||
}
|
||||
// Get the values of the foo_bar time series for
|
||||
// [2022-05-10Z07:59:00Z..2022-05-10Z08:17:00Z] time interval with the step
|
||||
// of 1m and timeout 5s.
|
||||
got := q.PrometheusAPIV1QueryRange(t, "foo_bar", "2022-05-10T07:59:00.000Z", "2022-05-10T08:17:00.000Z", "1m", opts)
|
||||
want := apptest.NewPrometheusAPIV1QueryResponse(t, `{"data": {"result": [{"metric": {"__name__": "foo_bar"}, "values": []}]}}`)
|
||||
s := make([]*apptest.Sample, 17)
|
||||
// Sample for 2022-05-10T07:59:00Z is missing because the time series has
|
||||
// samples only starting from 8:00.
|
||||
s[0] = apptest.NewSample(t, "2022-05-10T08:00:00Z", 1)
|
||||
s[1] = apptest.NewSample(t, "2022-05-10T08:01:00Z", 2)
|
||||
s[2] = apptest.NewSample(t, "2022-05-10T08:02:00Z", 3)
|
||||
s[3] = apptest.NewSample(t, "2022-05-10T08:03:00Z", 3)
|
||||
s[4] = apptest.NewSample(t, "2022-05-10T08:04:00Z", 5)
|
||||
s[5] = apptest.NewSample(t, "2022-05-10T08:05:00Z", 5)
|
||||
s[6] = apptest.NewSample(t, "2022-05-10T08:06:00Z", 5.5)
|
||||
s[7] = apptest.NewSample(t, "2022-05-10T08:07:00Z", 5.5)
|
||||
s[8] = apptest.NewSample(t, "2022-05-10T08:08:00Z", 4)
|
||||
s[9] = apptest.NewSample(t, "2022-05-10T08:09:00Z", 4)
|
||||
// Sample for 2022-05-10T08:10:00Z is missing because there is no sample
|
||||
// within the [8:10 - 1m .. 8:10] interval.
|
||||
s[10] = apptest.NewSample(t, "2022-05-10T08:11:00Z", 3.5)
|
||||
s[11] = apptest.NewSample(t, "2022-05-10T08:12:00Z", 3.25)
|
||||
s[12] = apptest.NewSample(t, "2022-05-10T08:13:00Z", 3)
|
||||
s[13] = apptest.NewSample(t, "2022-05-10T08:14:00Z", 2)
|
||||
s[14] = apptest.NewSample(t, "2022-05-10T08:15:00Z", 1)
|
||||
s[15] = apptest.NewSample(t, "2022-05-10T08:16:00Z", 4)
|
||||
s[16] = apptest.NewSample(t, "2022-05-10T08:17:00Z", 4)
|
||||
want.Data.Result[0].Samples = s
|
||||
opt := cmpopts.IgnoreFields(apptest.PrometheusAPIV1QueryResponse{}, "Status", "Data.ResultType")
|
||||
if diff := cmp.Diff(want, got, opt); diff != "" {
|
||||
t.Errorf("unexpected response (-want, +got):\n%s", diff)
|
||||
}
|
||||
|
||||
// Verify the statement that the query result for
|
||||
// [2022-05-10T07:59:00Z..2022-05-10T08:17:00Z] time range and 1m step will
|
||||
// contain 17 points.
|
||||
f("2022-05-10T07:59:00.000Z", "2022-05-10T08:17:00.000Z", "1m", []*apptest.Sample{
|
||||
// Sample for 2022-05-10T07:59:00Z is missing because the time series has
|
||||
// samples only starting from 8:00.
|
||||
apptest.NewSample(t, "2022-05-10T08:00:00Z", 1),
|
||||
apptest.NewSample(t, "2022-05-10T08:01:00Z", 2),
|
||||
apptest.NewSample(t, "2022-05-10T08:02:00Z", 3),
|
||||
apptest.NewSample(t, "2022-05-10T08:03:00Z", 3),
|
||||
apptest.NewSample(t, "2022-05-10T08:04:00Z", 5),
|
||||
apptest.NewSample(t, "2022-05-10T08:05:00Z", 5),
|
||||
apptest.NewSample(t, "2022-05-10T08:06:00Z", 5.5),
|
||||
apptest.NewSample(t, "2022-05-10T08:07:00Z", 5.5),
|
||||
apptest.NewSample(t, "2022-05-10T08:08:00Z", 4),
|
||||
apptest.NewSample(t, "2022-05-10T08:09:00Z", 4),
|
||||
// Sample for 2022-05-10T08:10:00Z is missing because there is no sample
|
||||
// within the [8:10 - 1m .. 8:10] interval.
|
||||
apptest.NewSample(t, "2022-05-10T08:11:00Z", 3.5),
|
||||
apptest.NewSample(t, "2022-05-10T08:12:00Z", 3.25),
|
||||
apptest.NewSample(t, "2022-05-10T08:13:00Z", 3),
|
||||
apptest.NewSample(t, "2022-05-10T08:14:00Z", 2),
|
||||
apptest.NewSample(t, "2022-05-10T08:15:00Z", 1),
|
||||
apptest.NewSample(t, "2022-05-10T08:16:00Z", 4),
|
||||
apptest.NewSample(t, "2022-05-10T08:17:00Z", 4),
|
||||
})
|
||||
|
||||
// Verify the statement that a query is executed at start, start+step,
|
||||
// start+2*step, …, step+N*step timestamps, where N is the whole number
|
||||
// of steps that fit between start and end.
|
||||
f("2022-05-10T08:00:01.000Z", "2022-05-10T08:02:00.000Z", "1m", []*apptest.Sample{
|
||||
apptest.NewSample(t, "2022-05-10T08:00:01Z", 1),
|
||||
apptest.NewSample(t, "2022-05-10T08:01:01Z", 2),
|
||||
})
|
||||
|
||||
// Verify the statement that a query is executed at start, start+step,
|
||||
// start+2*step, …, end timestamps, when end = start + N*step.
|
||||
f("2022-05-10T08:00:00.000Z", "2022-05-10T08:02:00.000Z", "1m", []*apptest.Sample{
|
||||
apptest.NewSample(t, "2022-05-10T08:00:00Z", 1),
|
||||
apptest.NewSample(t, "2022-05-10T08:01:00Z", 2),
|
||||
apptest.NewSample(t, "2022-05-10T08:02:00Z", 3),
|
||||
})
|
||||
|
||||
// If the step isn’t set, then it defaults to 5m (5 minutes).
|
||||
f("2022-05-10T07:59:00.000Z", "2022-05-10T08:17:00.000Z", "", []*apptest.Sample{
|
||||
// Sample for 2022-05-10T07:59:00Z is missing because the time series has
|
||||
// samples only starting from 8:00.
|
||||
apptest.NewSample(t, "2022-05-10T08:04:00Z", 5),
|
||||
apptest.NewSample(t, "2022-05-10T08:09:00Z", 4),
|
||||
apptest.NewSample(t, "2022-05-10T08:14:00Z", 2),
|
||||
})
|
||||
}
|
||||
|
||||
// testRangeQueryIsEquivalentToManyInstantQueries verifies the statement made in
|
||||
// the `Range query` section of the VictoriaMetrics documentation that a range
|
||||
// query is actually an instant query executed 1 + (start-end)/step times on the
|
||||
// time range from start to end. See:
|
||||
// https://docs.victoriametrics.com/keyconcepts/#range-query
|
||||
func testRangeQueryIsEquivalentToManyInstantQueries(t *testing.T, q apptest.PrometheusQuerier, opts apptest.QueryOpts) {
|
||||
f := func(timestamp string, want *apptest.Sample) {
|
||||
t.Helper()
|
||||
|
||||
gotInstant := q.PrometheusAPIV1Query(t, "foo_bar", timestamp, "1m", opts)
|
||||
if want == nil {
|
||||
if got, want := len(gotInstant.Data.Result), 0; got != want {
|
||||
t.Errorf("unexpected instant result size: got %d, want %d", got, want)
|
||||
}
|
||||
} else {
|
||||
got := gotInstant.Data.Result[0].Sample
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
t.Errorf("unexpected instant sample (-want, +got):\n%s", diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rangeRes := q.PrometheusAPIV1QueryRange(t, "foo_bar", "2022-05-10T07:59:00.000Z", "2022-05-10T08:17:00.000Z", "1m", opts)
|
||||
rangeSamples := rangeRes.Data.Result[0].Samples
|
||||
|
||||
f("2022-05-10T07:59:00.000Z", nil)
|
||||
f("2022-05-10T08:00:00.000Z", rangeSamples[0])
|
||||
f("2022-05-10T08:01:00.000Z", rangeSamples[1])
|
||||
f("2022-05-10T08:02:00.000Z", rangeSamples[2])
|
||||
f("2022-05-10T08:03:00.000Z", rangeSamples[3])
|
||||
f("2022-05-10T08:04:00.000Z", rangeSamples[4])
|
||||
f("2022-05-10T08:05:00.000Z", rangeSamples[5])
|
||||
f("2022-05-10T08:06:00.000Z", rangeSamples[6])
|
||||
f("2022-05-10T08:07:00.000Z", rangeSamples[7])
|
||||
f("2022-05-10T08:08:00.000Z", rangeSamples[8])
|
||||
f("2022-05-10T08:09:00.000Z", rangeSamples[9])
|
||||
f("2022-05-10T08:10:00.000Z", nil)
|
||||
f("2022-05-10T08:11:00.000Z", rangeSamples[10])
|
||||
f("2022-05-10T08:12:00.000Z", rangeSamples[11])
|
||||
f("2022-05-10T08:13:00.000Z", rangeSamples[12])
|
||||
f("2022-05-10T08:14:00.000Z", rangeSamples[13])
|
||||
f("2022-05-10T08:15:00.000Z", rangeSamples[14])
|
||||
f("2022-05-10T08:16:00.000Z", rangeSamples[15])
|
||||
f("2022-05-10T08:17:00.000Z", rangeSamples[16])
|
||||
}
|
||||
|
||||
@@ -158,7 +158,6 @@
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"min": 0,
|
||||
"mappings": [
|
||||
{
|
||||
"options": {
|
||||
@@ -234,7 +233,6 @@
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"mappings": [],
|
||||
"min": 0,
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
@@ -299,7 +297,6 @@
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"mappings": [],
|
||||
"min": 0,
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
@@ -364,7 +361,6 @@
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"min": 0,
|
||||
"mappings": [
|
||||
{
|
||||
"options": {
|
||||
@@ -454,7 +450,6 @@
|
||||
"type": "value"
|
||||
}
|
||||
],
|
||||
"min": 0,
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
@@ -528,7 +523,6 @@
|
||||
"type": "value"
|
||||
}
|
||||
],
|
||||
"min": 0,
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
|
||||
@@ -101,7 +101,6 @@
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"mappings": [],
|
||||
"min": 0,
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
@@ -243,7 +242,6 @@
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"mappings": [],
|
||||
"min": 0,
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
@@ -313,7 +311,6 @@
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"mappings": [],
|
||||
"min": 0,
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
@@ -368,7 +365,7 @@
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Elected Leaders",
|
||||
"title": "Electer Leaders",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
@@ -385,7 +382,6 @@
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"min": 0,
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
|
||||
@@ -170,7 +170,7 @@
|
||||
"type": "prometheus",
|
||||
"uid": "$ds"
|
||||
},
|
||||
"description": "Shows the logs ingestion rate.",
|
||||
"description": "Shows the datapoints ingestion rate.",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
@@ -583,7 +583,7 @@
|
||||
},
|
||||
"editorMode": "code",
|
||||
"exemplar": false,
|
||||
"expr": "sum(rate(vl_http_requests_total{job=~\"$job\", instance=~\"$instance\", path=~\"/select/.*\"}[$__rate_interval]))",
|
||||
"expr": "sum(rate(vl_http_requests_total{job=~\"$job\", instance=~\"$instance\", path!~\".*(/insert|/metrics)\"}[$__rate_interval]))",
|
||||
"format": "time_series",
|
||||
"instant": true,
|
||||
"interval": "",
|
||||
@@ -682,7 +682,7 @@
|
||||
"type": "prometheus",
|
||||
"uid": "$ds"
|
||||
},
|
||||
"description": "How many logs are inserted into storage per second",
|
||||
"description": "How many datapoints are inserted into storage per second",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
@@ -781,7 +781,7 @@
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Logs ingestion rate ",
|
||||
"title": "Datapoints ingestion rate ",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -159,7 +159,6 @@
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"min": 0,
|
||||
"mappings": [
|
||||
{
|
||||
"options": {
|
||||
@@ -235,7 +234,6 @@
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"mappings": [],
|
||||
"min": 0,
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
@@ -300,7 +298,6 @@
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"mappings": [],
|
||||
"min": 0,
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
@@ -365,7 +362,6 @@
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"min": 0,
|
||||
"mappings": [
|
||||
{
|
||||
"options": {
|
||||
@@ -455,7 +451,6 @@
|
||||
"type": "value"
|
||||
}
|
||||
],
|
||||
"min": 0,
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
@@ -529,7 +524,6 @@
|
||||
"type": "value"
|
||||
}
|
||||
],
|
||||
"min": 0,
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
|
||||
@@ -102,7 +102,6 @@
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"mappings": [],
|
||||
"min": 0,
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
@@ -244,7 +243,6 @@
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"mappings": [],
|
||||
"min": 0,
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
@@ -314,7 +312,6 @@
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"mappings": [],
|
||||
"min": 0,
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
@@ -369,7 +366,7 @@
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Elected Leaders",
|
||||
"title": "Electer Leaders",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
@@ -386,7 +383,6 @@
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"min": 0,
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
|
||||
@@ -171,7 +171,7 @@
|
||||
"type": "victoriametrics-datasource",
|
||||
"uid": "$ds"
|
||||
},
|
||||
"description": "Shows the logs ingestion rate.",
|
||||
"description": "Shows the datapoints ingestion rate.",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
@@ -584,7 +584,7 @@
|
||||
},
|
||||
"editorMode": "code",
|
||||
"exemplar": false,
|
||||
"expr": "sum(rate(vl_http_requests_total{job=~\"$job\", instance=~\"$instance\", path=~\"/select/.*\"}[$__rate_interval]))",
|
||||
"expr": "sum(rate(vl_http_requests_total{job=~\"$job\", instance=~\"$instance\", path!~\".*(/insert|/metrics)\"}[$__rate_interval]))",
|
||||
"format": "time_series",
|
||||
"instant": true,
|
||||
"interval": "",
|
||||
@@ -683,7 +683,7 @@
|
||||
"type": "victoriametrics-datasource",
|
||||
"uid": "$ds"
|
||||
},
|
||||
"description": "How many logs are inserted into storage per second",
|
||||
"description": "How many datapoints are inserted into storage per second",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
@@ -782,7 +782,7 @@
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Logs ingestion rate ",
|
||||
"title": "Datapoints ingestion rate ",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
@@ -1109,118 +1109,13 @@
|
||||
"title": "Log stream churn rate",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "victoriametrics-datasource",
|
||||
"uid": "$ds"
|
||||
},
|
||||
"description": "Shows the number of restarts per job. The chart can be useful to identify periodic process restarts and correlate them with potential issues or anomalies. Normally, processes shouldn't restart unless restart was inited by user. The reason of restarts should be figured out by checking the logs of each specific service. ",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisBorderShow": false,
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"axisSoftMin": 0,
|
||||
"barAlignment": 0,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 0,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
},
|
||||
"insertNulls": false,
|
||||
"lineInterpolation": "stepAfter",
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"showPoints": "never",
|
||||
"spanNulls": false,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
"mode": "none"
|
||||
},
|
||||
"thresholdsStyle": {
|
||||
"mode": "off"
|
||||
}
|
||||
},
|
||||
"decimals": 0,
|
||||
"links": [],
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
"unit": "none"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 22
|
||||
},
|
||||
"id": 62,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"displayMode": "table",
|
||||
"placement": "bottom",
|
||||
"showLegend": true,
|
||||
"sortBy": "Last *",
|
||||
"sortDesc": true
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "multi",
|
||||
"sort": "desc"
|
||||
}
|
||||
},
|
||||
"pluginVersion": "9.1.0",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "victoriametrics-datasource",
|
||||
"uid": "$ds"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum(changes(vm_app_start_timestamp{job=~\"$job\", instance=~\"$instance\"}[$__rate_interval]) > 0) by(job)",
|
||||
"format": "time_series",
|
||||
"instant": false,
|
||||
"legendFormat": "{{job}}",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Restarts ($job)",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"collapsed": true,
|
||||
"gridPos": {
|
||||
"h": 1,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 30
|
||||
"y": 22
|
||||
},
|
||||
"id": 28,
|
||||
"panels": [
|
||||
@@ -1274,7 +1169,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -1290,7 +1186,7 @@
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 11
|
||||
"y": 3
|
||||
},
|
||||
"id": 38,
|
||||
"options": {
|
||||
@@ -1380,7 +1276,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -1396,7 +1293,7 @@
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 11
|
||||
"y": 3
|
||||
},
|
||||
"id": 40,
|
||||
"options": {
|
||||
@@ -1536,7 +1433,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -1552,7 +1450,7 @@
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 19
|
||||
"y": 11
|
||||
},
|
||||
"id": 42,
|
||||
"options": {
|
||||
@@ -1641,7 +1539,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -1657,7 +1556,7 @@
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 19
|
||||
"y": 11
|
||||
},
|
||||
"id": 44,
|
||||
"options": {
|
||||
@@ -1750,7 +1649,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -1782,7 +1682,7 @@
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 27
|
||||
"y": 19
|
||||
},
|
||||
"id": 46,
|
||||
"options": {
|
||||
@@ -1874,7 +1774,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -1906,7 +1807,7 @@
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 27
|
||||
"y": 19
|
||||
},
|
||||
"id": 48,
|
||||
"options": {
|
||||
@@ -2011,7 +1912,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -2027,7 +1929,7 @@
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 35
|
||||
"y": 27
|
||||
},
|
||||
"id": 50,
|
||||
"options": {
|
||||
@@ -2116,7 +2018,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -2145,7 +2048,7 @@
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 35
|
||||
"y": 27
|
||||
},
|
||||
"id": 52,
|
||||
"options": {
|
||||
@@ -2248,7 +2151,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -2264,7 +2168,7 @@
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 43
|
||||
"y": 35
|
||||
},
|
||||
"id": 54,
|
||||
"options": {
|
||||
@@ -2353,7 +2257,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -2382,7 +2287,7 @@
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 43
|
||||
"y": 35
|
||||
},
|
||||
"id": 56,
|
||||
"options": {
|
||||
@@ -2489,7 +2394,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -2505,7 +2411,7 @@
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 51
|
||||
"y": 43
|
||||
},
|
||||
"id": 58,
|
||||
"options": {
|
||||
@@ -2596,7 +2502,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -2612,7 +2519,7 @@
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 51
|
||||
"y": 43
|
||||
},
|
||||
"id": 60,
|
||||
"options": {
|
||||
@@ -2704,7 +2611,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -2720,7 +2628,7 @@
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 59
|
||||
"y": 51
|
||||
},
|
||||
"id": 61,
|
||||
"options": {
|
||||
@@ -2817,8 +2725,8 @@
|
||||
},
|
||||
"definition": "label_values(vm_app_version{job=~\"$job\"}, instance)",
|
||||
"hide": 0,
|
||||
"includeAll": true,
|
||||
"multi": true,
|
||||
"includeAll": false,
|
||||
"multi": false,
|
||||
"name": "instance",
|
||||
"options": [],
|
||||
"query": {
|
||||
@@ -2854,4 +2762,4 @@
|
||||
"uid": "OqPIZTX4z_vm",
|
||||
"version": 1,
|
||||
"weekStart": ""
|
||||
}
|
||||
}
|
||||
@@ -142,7 +142,6 @@
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"mappings": [],
|
||||
"min": 0,
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
@@ -212,7 +211,6 @@
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"mappings": [],
|
||||
"min": 0,
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
@@ -283,7 +281,6 @@
|
||||
"defaults": {
|
||||
"decimals": 1,
|
||||
"mappings": [],
|
||||
"min": 0,
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
@@ -347,7 +344,6 @@
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"mappings": [],
|
||||
"min": 0,
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
@@ -529,7 +525,6 @@
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"mappings": [],
|
||||
"min": 0,
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
@@ -1120,7 +1115,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -1229,7 +1225,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -1620,7 +1617,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -1734,7 +1732,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -1845,7 +1844,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -1980,7 +1980,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -2111,7 +2112,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -2216,7 +2218,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -2322,7 +2325,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -2428,7 +2432,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -2533,7 +2538,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -2662,7 +2668,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -2766,7 +2773,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -2873,7 +2881,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "transparent"
|
||||
"color": "transparent",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -2980,7 +2989,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "transparent"
|
||||
"color": "transparent",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -3086,7 +3096,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -3192,7 +3203,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -3304,7 +3316,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -3408,7 +3421,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -3484,7 +3498,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -3640,7 +3655,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
|
||||
@@ -232,7 +232,6 @@
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"mappings": [],
|
||||
"min": 0,
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
@@ -277,7 +276,7 @@
|
||||
"uid": "$ds"
|
||||
},
|
||||
"exemplar": false,
|
||||
"expr": "count(vmalert_alerting_rules_last_evaluation_samples{job=~\"$job\", instance=~\"$instance\", group=~\"$group\", file=~\"$file\"})",
|
||||
"expr": "count(vmalert_alerting_rules_last_evaluation_samples{job=~\"$job\", instance=~\"$instance\", group=~\"$group\"})",
|
||||
"interval": "",
|
||||
"legendFormat": "",
|
||||
"refId": "A"
|
||||
@@ -295,7 +294,6 @@
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"mappings": [],
|
||||
"min": 0,
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
@@ -340,7 +338,7 @@
|
||||
"uid": "$ds"
|
||||
},
|
||||
"exemplar": false,
|
||||
"expr": "count(vmalert_recording_rules_last_evaluation_samples{job=~\"$job\", instance=~\"$instance\", group=~\"$group\", file=~\"$file\"})",
|
||||
"expr": "count(vmalert_recording_rules_last_evaluation_samples{job=~\"$job\", instance=~\"$instance\", group=~\"$group\"})",
|
||||
"interval": "",
|
||||
"legendFormat": "",
|
||||
"refId": "A"
|
||||
@@ -358,7 +356,6 @@
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"mappings": [],
|
||||
"min": 0,
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
@@ -407,7 +404,7 @@
|
||||
"uid": "$ds"
|
||||
},
|
||||
"exemplar": false,
|
||||
"expr": "(sum(increase(vmalert_alerting_rules_errors_total{job=~\"$job\", instance=~\"$instance\", group=~\"$group\", file=~\"$file\"}[$__rate_interval])) or vector(0)) + \n(sum(increase(vmalert_recording_rules_errors_total{job=~\"$job\", instance=~\"$instance\", group=~\"$group\", file=~\"$file\"}[$__rate_interval])) or vector(0))",
|
||||
"expr": "(sum(increase(vmalert_alerting_rules_errors_total{job=~\"$job\", instance=~\"$instance\", group=~\"$group\"}[$__rate_interval])) or vector(0)) + \n(sum(increase(vmalert_recording_rules_errors_total{job=~\"$job\", instance=~\"$instance\", group=~\"$group\"}[$__rate_interval])) or vector(0))",
|
||||
"interval": "",
|
||||
"legendFormat": "",
|
||||
"refId": "A"
|
||||
@@ -425,7 +422,6 @@
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"mappings": [],
|
||||
"min": 0,
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
@@ -914,9 +910,9 @@
|
||||
},
|
||||
"editorMode": "code",
|
||||
"exemplar": false,
|
||||
"expr": "topk_max($topk, max(sum(\n rate(vmalert_iteration_duration_seconds_sum{job=~\"$job\", instance=~\"$instance\", group=~\"$group\", file=~\"$file\"}[$__rate_interval])\n/\n rate(vmalert_iteration_duration_seconds_count{job=~\"$job\", instance=~\"$instance\", group=~\"$group\", file=~\"$file\"}[$__rate_interval])\n) by(job, instance, group, file)) \nby(job, group, file))",
|
||||
"expr": "topk_max($topk, max(sum(\n rate(vmalert_iteration_duration_seconds_sum{job=~\"$job\", instance=~\"$instance\", group=~\"$group\"}[$__rate_interval])\n/\n rate(vmalert_iteration_duration_seconds_count{job=~\"$job\", instance=~\"$instance\", group=~\"$group\"}[$__rate_interval])\n) by(job, instance, group)) \nby(job, group))",
|
||||
"interval": "",
|
||||
"legendFormat": "({{job}}) {{group}}({{file}})",
|
||||
"legendFormat": "{{group}} ({{job}})",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
}
|
||||
@@ -972,7 +968,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -1074,7 +1071,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -2092,7 +2090,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -2248,7 +2247,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -2292,9 +2292,9 @@
|
||||
},
|
||||
"editorMode": "code",
|
||||
"exemplar": false,
|
||||
"expr": "sum(increase(vmalert_iteration_missed_total{job=~\"$job\", instance=~\"$instance\"}[$__rate_interval])) by(job, group, file) > 0",
|
||||
"expr": "sum(increase(vmalert_iteration_missed_total{job=~\"$job\", instance=~\"$instance\"}[$__rate_interval])) by(job, group) > 0",
|
||||
"interval": "1m",
|
||||
"legendFormat": "({{job}}) {{group}}({{file}})",
|
||||
"legendFormat": "__auto",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
}
|
||||
@@ -2353,7 +2353,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -2516,9 +2517,9 @@
|
||||
},
|
||||
"editorMode": "code",
|
||||
"exemplar": false,
|
||||
"expr": "topk_max($topk, sum(vmalert_alerts_firing{job=~\"$job\", instance=~\"$instance\", group=~\"$group\", file=~\"$file\"}) by(job, group, file, alertname) > 0)",
|
||||
"expr": "topk_max($topk, sum(vmalert_alerts_firing{job=~\"$job\", instance=~\"$instance\", group=~\"$group\"}) by(job, group, alertname) > 0)",
|
||||
"interval": "",
|
||||
"legendFormat": "({{job}}) {{group}}.{{alertname}}({{file}})",
|
||||
"legendFormat": "{{group}}.{{alertname}} ({{job}})",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
}
|
||||
@@ -2618,9 +2619,9 @@
|
||||
},
|
||||
"editorMode": "code",
|
||||
"exemplar": false,
|
||||
"expr": "sum(increase(vmalert_alerting_rules_errors_total{job=~\"$job\", instance=~\"$instance\", group=~\"$group\", file=~\"$file\"}[$__rate_interval])) by(job, group, file, alertname) > 0",
|
||||
"expr": "sum(increase(vmalert_alerting_rules_errors_total{job=~\"$job\", instance=~\"$instance\", group=~\"$group\"}[$__rate_interval])) by(job, group, alertname) > 0",
|
||||
"interval": "",
|
||||
"legendFormat": "({{job}}) {{group}}.{{alertname}}({{file}})",
|
||||
"legendFormat": "{{group}}.{{alertname}} ({{job}})",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
}
|
||||
@@ -2720,9 +2721,9 @@
|
||||
},
|
||||
"editorMode": "code",
|
||||
"exemplar": false,
|
||||
"expr": "sum(vmalert_alerts_pending{job=~\"$job\", instance=~\"$instance\", group=~\"$group\", file=~\"$file\"}) by(job, group, file, alertname) > 0",
|
||||
"expr": "sum(vmalert_alerts_pending{job=~\"$job\", instance=~\"$instance\", group=~\"$group\"}) by(job, group, alertname) > 0",
|
||||
"interval": "",
|
||||
"legendFormat": "({{job}}) {{group}}.{{alertname}}({{file}})",
|
||||
"legendFormat": "{{group}}.{{alertname}} ({{job}})",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
}
|
||||
@@ -3049,9 +3050,9 @@
|
||||
},
|
||||
"editorMode": "code",
|
||||
"exemplar": false,
|
||||
"expr": "topk_max($topk, \n max(\n sum(vmalert_recording_rules_last_evaluation_samples{job=~\"$job\", instance=~\"$instance\", group=~\"$group\", file=~\"$file\"}) by(job, instance, group, file, recording) > 0\n ) by(job, group, file, recording)\n)",
|
||||
"expr": "topk_max($topk, \n max(\n sum(vmalert_recording_rules_last_evaluation_samples{job=~\"$job\", instance=~\"$instance\", group=~\"$group\"}) by(job, instance, group, recording) > 0\n ) by(job, group, recording)\n)",
|
||||
"interval": "",
|
||||
"legendFormat": "({{job}}) {{group}}.{{recording}}({{file}})",
|
||||
"legendFormat": "{{group}}.{{recording}} ({{job}})",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
}
|
||||
@@ -3151,9 +3152,9 @@
|
||||
},
|
||||
"editorMode": "code",
|
||||
"exemplar": false,
|
||||
"expr": "count(vmalert_recording_rules_last_evaluation_samples{job=~\"$job\", instance=~\"$instance\", group=~\"$group\", file=~\"$file\"} < 1) by(job, group, file, recording)",
|
||||
"expr": "count(vmalert_recording_rules_last_evaluation_samples{job=~\"$job\", instance=~\"$instance\", group=~\"$group\"} < 1) by(job, group, recording)",
|
||||
"interval": "",
|
||||
"legendFormat": "({{job}}) {{group}}.{{recording}}({{file}})",
|
||||
"legendFormat": "{{group}}.{{recording}} ({{job}})",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
}
|
||||
@@ -3250,9 +3251,9 @@
|
||||
},
|
||||
"editorMode": "code",
|
||||
"exemplar": false,
|
||||
"expr": "sum(increase(vmalert_recording_rules_errors_total{job=~\"$job\", instance=~\"$instance\", group=~\"$group\", file=~\"$file\"}[$__rate_interval])) by(job, group, file, recording) > 0",
|
||||
"expr": "sum(increase(vmalert_recording_rules_errors_total{job=~\"$job\", instance=~\"$instance\", group=~\"$group\"}[$__rate_interval])) by(job, group, recording) > 0",
|
||||
"interval": "",
|
||||
"legendFormat": "({{job}}) {{group}}.{{recording}}({{file}})",
|
||||
"legendFormat": "{{group}}.{{recording}} ({{job}})",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
}
|
||||
@@ -3748,29 +3749,6 @@
|
||||
"sort": 0,
|
||||
"type": "query"
|
||||
},
|
||||
{
|
||||
"allValue": ".*",
|
||||
"current": {},
|
||||
"datasource": {
|
||||
"type": "victoriametrics-datasource",
|
||||
"uid": "$ds"
|
||||
},
|
||||
"definition": "label_values(vmalert_iteration_total{job=~\"$job\", instance=~\"$instance\"},file)",
|
||||
"hide": 0,
|
||||
"includeAll": true,
|
||||
"multi": true,
|
||||
"name": "file",
|
||||
"options": [],
|
||||
"query": {
|
||||
"query": "label_values(vmalert_iteration_total{job=~\"$job\", instance=~\"$instance\"},file)",
|
||||
"refId": "VictoriaMetricsVariableQueryEditor-VariableQuery"
|
||||
},
|
||||
"refresh": 1,
|
||||
"regex": "",
|
||||
"skipUrlSync": false,
|
||||
"sort": 0,
|
||||
"type": "query"
|
||||
},
|
||||
{
|
||||
"allValue": ".*",
|
||||
"current": {},
|
||||
|
||||
@@ -379,7 +379,6 @@
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"mappings": [],
|
||||
"min": 0,
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
|
||||
@@ -141,7 +141,6 @@
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"mappings": [],
|
||||
"min": 0,
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
@@ -211,7 +210,6 @@
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"mappings": [],
|
||||
"min": 0,
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
@@ -282,7 +280,6 @@
|
||||
"defaults": {
|
||||
"decimals": 1,
|
||||
"mappings": [],
|
||||
"min": 0,
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
@@ -346,7 +343,6 @@
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"mappings": [],
|
||||
"min": 0,
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
@@ -528,7 +524,6 @@
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"mappings": [],
|
||||
"min": 0,
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
@@ -1119,7 +1114,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -1228,7 +1224,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -1619,7 +1616,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -1733,7 +1731,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -1844,7 +1843,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -1979,7 +1979,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -2110,7 +2111,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -2215,7 +2217,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -2321,7 +2324,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -2427,7 +2431,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -2532,7 +2537,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -2661,7 +2667,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -2765,7 +2772,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -2872,7 +2880,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "transparent"
|
||||
"color": "transparent",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -2979,7 +2988,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "transparent"
|
||||
"color": "transparent",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -3085,7 +3095,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -3191,7 +3202,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -3303,7 +3315,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -3407,7 +3420,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -3483,7 +3497,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -3639,7 +3654,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
|
||||
@@ -231,7 +231,6 @@
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"mappings": [],
|
||||
"min": 0,
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
@@ -276,7 +275,7 @@
|
||||
"uid": "$ds"
|
||||
},
|
||||
"exemplar": false,
|
||||
"expr": "count(vmalert_alerting_rules_last_evaluation_samples{job=~\"$job\", instance=~\"$instance\", group=~\"$group\", file=~\"$file\"})",
|
||||
"expr": "count(vmalert_alerting_rules_last_evaluation_samples{job=~\"$job\", instance=~\"$instance\", group=~\"$group\"})",
|
||||
"interval": "",
|
||||
"legendFormat": "",
|
||||
"refId": "A"
|
||||
@@ -294,7 +293,6 @@
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"mappings": [],
|
||||
"min": 0,
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
@@ -339,7 +337,7 @@
|
||||
"uid": "$ds"
|
||||
},
|
||||
"exemplar": false,
|
||||
"expr": "count(vmalert_recording_rules_last_evaluation_samples{job=~\"$job\", instance=~\"$instance\", group=~\"$group\", file=~\"$file\"})",
|
||||
"expr": "count(vmalert_recording_rules_last_evaluation_samples{job=~\"$job\", instance=~\"$instance\", group=~\"$group\"})",
|
||||
"interval": "",
|
||||
"legendFormat": "",
|
||||
"refId": "A"
|
||||
@@ -357,7 +355,6 @@
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"mappings": [],
|
||||
"min": 0,
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
@@ -406,7 +403,7 @@
|
||||
"uid": "$ds"
|
||||
},
|
||||
"exemplar": false,
|
||||
"expr": "(sum(increase(vmalert_alerting_rules_errors_total{job=~\"$job\", instance=~\"$instance\", group=~\"$group\", file=~\"$file\"}[$__rate_interval])) or vector(0)) + \n(sum(increase(vmalert_recording_rules_errors_total{job=~\"$job\", instance=~\"$instance\", group=~\"$group\", file=~\"$file\"}[$__rate_interval])) or vector(0))",
|
||||
"expr": "(sum(increase(vmalert_alerting_rules_errors_total{job=~\"$job\", instance=~\"$instance\", group=~\"$group\"}[$__rate_interval])) or vector(0)) + \n(sum(increase(vmalert_recording_rules_errors_total{job=~\"$job\", instance=~\"$instance\", group=~\"$group\"}[$__rate_interval])) or vector(0))",
|
||||
"interval": "",
|
||||
"legendFormat": "",
|
||||
"refId": "A"
|
||||
@@ -424,7 +421,6 @@
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"mappings": [],
|
||||
"min": 0,
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
@@ -913,9 +909,9 @@
|
||||
},
|
||||
"editorMode": "code",
|
||||
"exemplar": false,
|
||||
"expr": "topk_max($topk, max(sum(\n rate(vmalert_iteration_duration_seconds_sum{job=~\"$job\", instance=~\"$instance\", group=~\"$group\", file=~\"$file\"}[$__rate_interval])\n/\n rate(vmalert_iteration_duration_seconds_count{job=~\"$job\", instance=~\"$instance\", group=~\"$group\", file=~\"$file\"}[$__rate_interval])\n) by(job, instance, group, file)) \nby(job, group, file))",
|
||||
"expr": "topk_max($topk, max(sum(\n rate(vmalert_iteration_duration_seconds_sum{job=~\"$job\", instance=~\"$instance\", group=~\"$group\"}[$__rate_interval])\n/\n rate(vmalert_iteration_duration_seconds_count{job=~\"$job\", instance=~\"$instance\", group=~\"$group\"}[$__rate_interval])\n) by(job, instance, group)) \nby(job, group))",
|
||||
"interval": "",
|
||||
"legendFormat": "({{job}}) {{group}}({{file}})",
|
||||
"legendFormat": "{{group}} ({{job}})",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
}
|
||||
@@ -971,7 +967,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -1073,7 +1070,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -2091,7 +2089,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -2247,7 +2246,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -2291,9 +2291,9 @@
|
||||
},
|
||||
"editorMode": "code",
|
||||
"exemplar": false,
|
||||
"expr": "sum(increase(vmalert_iteration_missed_total{job=~\"$job\", instance=~\"$instance\"}[$__rate_interval])) by(job, group, file) > 0",
|
||||
"expr": "sum(increase(vmalert_iteration_missed_total{job=~\"$job\", instance=~\"$instance\"}[$__rate_interval])) by(job, group) > 0",
|
||||
"interval": "1m",
|
||||
"legendFormat": "({{job}}) {{group}}({{file}})",
|
||||
"legendFormat": "__auto",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
}
|
||||
@@ -2352,7 +2352,8 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -2515,9 +2516,9 @@
|
||||
},
|
||||
"editorMode": "code",
|
||||
"exemplar": false,
|
||||
"expr": "topk_max($topk, sum(vmalert_alerts_firing{job=~\"$job\", instance=~\"$instance\", group=~\"$group\", file=~\"$file\"}) by(job, group, file, alertname) > 0)",
|
||||
"expr": "topk_max($topk, sum(vmalert_alerts_firing{job=~\"$job\", instance=~\"$instance\", group=~\"$group\"}) by(job, group, alertname) > 0)",
|
||||
"interval": "",
|
||||
"legendFormat": "({{job}}) {{group}}.{{alertname}}({{file}})",
|
||||
"legendFormat": "{{group}}.{{alertname}} ({{job}})",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
}
|
||||
@@ -2617,9 +2618,9 @@
|
||||
},
|
||||
"editorMode": "code",
|
||||
"exemplar": false,
|
||||
"expr": "sum(increase(vmalert_alerting_rules_errors_total{job=~\"$job\", instance=~\"$instance\", group=~\"$group\", file=~\"$file\"}[$__rate_interval])) by(job, group, file, alertname) > 0",
|
||||
"expr": "sum(increase(vmalert_alerting_rules_errors_total{job=~\"$job\", instance=~\"$instance\", group=~\"$group\"}[$__rate_interval])) by(job, group, alertname) > 0",
|
||||
"interval": "",
|
||||
"legendFormat": "({{job}}) {{group}}.{{alertname}}({{file}})",
|
||||
"legendFormat": "{{group}}.{{alertname}} ({{job}})",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
}
|
||||
@@ -2719,9 +2720,9 @@
|
||||
},
|
||||
"editorMode": "code",
|
||||
"exemplar": false,
|
||||
"expr": "sum(vmalert_alerts_pending{job=~\"$job\", instance=~\"$instance\", group=~\"$group\", file=~\"$file\"}) by(job, group, file, alertname) > 0",
|
||||
"expr": "sum(vmalert_alerts_pending{job=~\"$job\", instance=~\"$instance\", group=~\"$group\"}) by(job, group, alertname) > 0",
|
||||
"interval": "",
|
||||
"legendFormat": "({{job}}) {{group}}.{{alertname}}({{file}})",
|
||||
"legendFormat": "{{group}}.{{alertname}} ({{job}})",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
}
|
||||
@@ -3048,9 +3049,9 @@
|
||||
},
|
||||
"editorMode": "code",
|
||||
"exemplar": false,
|
||||
"expr": "topk_max($topk, \n max(\n sum(vmalert_recording_rules_last_evaluation_samples{job=~\"$job\", instance=~\"$instance\", group=~\"$group\", file=~\"$file\"}) by(job, instance, group, file, recording) > 0\n ) by(job, group, file, recording)\n)",
|
||||
"expr": "topk_max($topk, \n max(\n sum(vmalert_recording_rules_last_evaluation_samples{job=~\"$job\", instance=~\"$instance\", group=~\"$group\"}) by(job, instance, group, recording) > 0\n ) by(job, group, recording)\n)",
|
||||
"interval": "",
|
||||
"legendFormat": "({{job}}) {{group}}.{{recording}}({{file}})",
|
||||
"legendFormat": "{{group}}.{{recording}} ({{job}})",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
}
|
||||
@@ -3150,9 +3151,9 @@
|
||||
},
|
||||
"editorMode": "code",
|
||||
"exemplar": false,
|
||||
"expr": "count(vmalert_recording_rules_last_evaluation_samples{job=~\"$job\", instance=~\"$instance\", group=~\"$group\", file=~\"$file\"} < 1) by(job, group, file, recording)",
|
||||
"expr": "count(vmalert_recording_rules_last_evaluation_samples{job=~\"$job\", instance=~\"$instance\", group=~\"$group\"} < 1) by(job, group, recording)",
|
||||
"interval": "",
|
||||
"legendFormat": "({{job}}) {{group}}.{{recording}}({{file}})",
|
||||
"legendFormat": "{{group}}.{{recording}} ({{job}})",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
}
|
||||
@@ -3249,9 +3250,9 @@
|
||||
},
|
||||
"editorMode": "code",
|
||||
"exemplar": false,
|
||||
"expr": "sum(increase(vmalert_recording_rules_errors_total{job=~\"$job\", instance=~\"$instance\", group=~\"$group\", file=~\"$file\"}[$__rate_interval])) by(job, group, file, recording) > 0",
|
||||
"expr": "sum(increase(vmalert_recording_rules_errors_total{job=~\"$job\", instance=~\"$instance\", group=~\"$group\"}[$__rate_interval])) by(job, group, recording) > 0",
|
||||
"interval": "",
|
||||
"legendFormat": "({{job}}) {{group}}.{{recording}}({{file}})",
|
||||
"legendFormat": "{{group}}.{{recording}} ({{job}})",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
}
|
||||
@@ -3747,29 +3748,6 @@
|
||||
"sort": 0,
|
||||
"type": "query"
|
||||
},
|
||||
{
|
||||
"allValue": ".*",
|
||||
"current": {},
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "$ds"
|
||||
},
|
||||
"definition": "label_values(vmalert_iteration_total{job=~\"$job\", instance=~\"$instance\"},file)",
|
||||
"hide": 0,
|
||||
"includeAll": true,
|
||||
"multi": true,
|
||||
"name": "file",
|
||||
"options": [],
|
||||
"query": {
|
||||
"query": "label_values(vmalert_iteration_total{job=~\"$job\", instance=~\"$instance\"},file)",
|
||||
"refId": "PrometheusVariableQueryEditor-VariableQuery"
|
||||
},
|
||||
"refresh": 1,
|
||||
"regex": "",
|
||||
"skipUrlSync": false,
|
||||
"sort": 0,
|
||||
"type": "query"
|
||||
},
|
||||
{
|
||||
"allValue": ".*",
|
||||
"current": {},
|
||||
|
||||
@@ -378,7 +378,6 @@
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"mappings": [],
|
||||
"min": 0,
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
|
||||
@@ -40,7 +40,7 @@ services:
|
||||
# storing logs and serving read queries.
|
||||
victorialogs:
|
||||
container_name: victorialogs
|
||||
image: victoriametrics/victoria-logs:v1.0.0-victorialogs
|
||||
image: victoriametrics/victoria-logs:v0.42.0-victorialogs
|
||||
command:
|
||||
- "--storageDataPath=/vlogs"
|
||||
- "--httpListenAddr=:9428"
|
||||
@@ -104,8 +104,6 @@ services:
|
||||
- ./rules/alerts-health.yml:/etc/alerts/alerts-health.yml
|
||||
- ./rules/alerts-vmagent.yml:/etc/alerts/alerts-vmagent.yml
|
||||
- ./rules/alerts-vmalert.yml:/etc/alerts/alerts-vmalert.yml
|
||||
# vlogs rule
|
||||
- ./rules/vlogs-example-alerts.yml:/etc/alerts/vlogs-example-alerts.yml
|
||||
command:
|
||||
- "--datasource.url=http://vmauth:8427/"
|
||||
- "--remoteRead.url=http://victoriametrics:8428/"
|
||||
|
||||
@@ -23,9 +23,9 @@ groups:
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
dashboard: "http://localhost:3000/d/LzldHAVnz?viewPanel=13&var-instance={{ $labels.instance }}&var-file={{ $labels.file }}&var-group={{ $labels.group }}"
|
||||
dashboard: "http://localhost:3000/d/LzldHAVnz?viewPanel=13&var-instance={{ $labels.instance }}&var-group={{ $labels.group }}"
|
||||
summary: "Alerting rules are failing for vmalert instance {{ $labels.instance }}"
|
||||
description: "Alerting rules execution is failing for group \"{{ $labels.group }}\" in file \"{{ $labels.file }}\".
|
||||
description: "Alerting rules execution is failing for group \"{{ $labels.group }}\".
|
||||
Check vmalert's logs for detailed error message."
|
||||
|
||||
- alert: RecordingRulesError
|
||||
@@ -34,9 +34,9 @@ groups:
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
dashboard: "http://localhost:3000/d/LzldHAVnz?viewPanel=30&var-instance={{ $labels.instance }}&var-file={{ $labels.file }}&var-group={{ $labels.group }}"
|
||||
dashboard: "http://localhost:3000/d/LzldHAVnz?viewPanel=30&var-instance={{ $labels.instance }}&var-group={{ $labels.group }}"
|
||||
summary: "Recording rules are failing for vmalert instance {{ $labels.instance }}"
|
||||
description: "Recording rules execution is failing for group \"{{ $labels.group }}\" in file \"{{ $labels.file }}\".
|
||||
description: "Recording rules execution is failing for group \"{{ $labels.group }}\".
|
||||
Check vmalert's logs for detailed error message."
|
||||
|
||||
- alert: RecordingRulesNoData
|
||||
@@ -45,9 +45,9 @@ groups:
|
||||
labels:
|
||||
severity: info
|
||||
annotations:
|
||||
dashboard: "http://localhost:3000/d/LzldHAVnz?viewPanel=33&var-file={{ $labels.file }}&var-group={{ $labels.group }}"
|
||||
dashboard: "http://localhost:3000/d/LzldHAVnz?viewPanel=33&var-group={{ $labels.group }}"
|
||||
summary: "Recording rule {{ $labels.recording }} ({{ $labels.group }}) produces no data"
|
||||
description: "Recording rule \"{{ $labels.recording }}\" from group \"{{ $labels.group }}\ in file \"{{ $labels.file }}\"
|
||||
description: "Recording rule \"{{ $labels.recording }}\" from group \"{{ $labels.group }}\"
|
||||
produces 0 samples over the last 30min. It might be caused by a misconfiguration
|
||||
or incorrect query expression."
|
||||
|
||||
@@ -58,7 +58,7 @@ groups:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "vmalert instance {{ $labels.instance }} is missing rules evaluations"
|
||||
description: "vmalert instance {{ $labels.instance }} is missing rules evaluations for group \"{{ $labels.group }}\" in file \"{{ $labels.file }}\".
|
||||
description: "vmalert instance {{ $labels.instance }} is missing rules evaluations for group \"{{ $labels.group }}\".
|
||||
The group evaluation time takes longer than the configured evaluation interval. This may result in missed
|
||||
alerting notifications or recording rules samples. Try increasing evaluation interval or concurrency of
|
||||
group \"{{ $labels.group }}\". See https://docs.victoriametrics.com/vmalert/#groups.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
groups:
|
||||
- name: log-rules
|
||||
- name: TestGroup
|
||||
type: vlogs
|
||||
interval: 1m
|
||||
rules:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
services:
|
||||
# meta service will be ignored by compose
|
||||
.victorialogs:
|
||||
image: docker.io/victoriametrics/victoria-logs:v1.0.0-victorialogs
|
||||
image: docker.io/victoriametrics/victoria-logs:v0.42.0-victorialogs
|
||||
command:
|
||||
- -storageDataPath=/vlogs
|
||||
- -loggerFormat=json
|
||||
|
||||
@@ -72,7 +72,7 @@ services:
|
||||
restart: always
|
||||
vmanomaly:
|
||||
container_name: vmanomaly
|
||||
image: victoriametrics/vmanomaly:v1.18.3
|
||||
image: victoriametrics/vmanomaly:v1.18.0
|
||||
depends_on:
|
||||
- "victoriametrics"
|
||||
ports:
|
||||
|
||||
@@ -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.0.0-victorialogs
|
||||
image: docker.io/victoriametrics/victoria-logs:v0.42.0-victorialogs
|
||||
volumes:
|
||||
- vlogs:/vlogs
|
||||
ports:
|
||||
|
||||
@@ -92,7 +92,7 @@ See also [case studies](https://docs.victoriametrics.com/casestudies/).
|
||||
### Announcements
|
||||
|
||||
* [Open-source strategy at VictoriaMetrics](https://www.youtube.com/watch?v=-DbbIZzFHIY)
|
||||
* [Open-sourcing VictoriaMetrics](https://valyala.medium.com/open-sourcing-victoriametrics-f31e34485c2b)
|
||||
* [Open-sourcing VictoriaMetrics](https://blog.usejournal.com/open-sourcing-victoriametrics-f31e34485c2b)
|
||||
* [VictoriaMetrics — creating the best remote storage for Prometheus](https://faun.pub/victoriametrics-creating-the-best-remote-storage-for-prometheus-5d92d66787ac)
|
||||
* [Anomaly Detection in VictoriaMetrics](https://victoriametrics.medium.com/anomaly-detection-in-victoriametrics-9528538786a7)
|
||||
|
||||
@@ -119,7 +119,6 @@ See also [case studies](https://docs.victoriametrics.com/casestudies/).
|
||||
* [Improving histogram usability for Prometheus and Grafana](https://valyala.medium.com/improving-histogram-usability-for-prometheus-and-grafana-bc7e5df0e350)
|
||||
* [Why irate from Prometheus doesn't capture spikes](https://valyala.medium.com/why-irate-from-prometheus-doesnt-capture-spikes-45f9896d7832)
|
||||
* [VictoriaMetrics: PromQL compliance](https://medium.com/@romanhavronenko/victoriametrics-promql-compliance-d4318203f51e)
|
||||
* [How do open source solutions for logs work: Elasticsearch, Loki and VictoriaLogs](https://itnext.io/how-do-open-source-solutions-for-logs-work-elasticsearch-loki-and-victorialogs-9f7097ecbc2f)
|
||||
|
||||
### Tutorials, guides and how-to articles
|
||||
|
||||
|
||||
@@ -15,16 +15,6 @@ according to [these docs](https://docs.victoriametrics.com/victorialogs/quicksta
|
||||
|
||||
## tip
|
||||
|
||||
## [v1.0.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.0.0-victorialogs)
|
||||
|
||||
Released at 2024-11-12
|
||||
|
||||
This release is identical to [v0.42.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v0.42.0-victorialogs).
|
||||
|
||||
VictoriaLogs gained all [the planned features](https://docs.victoriametrics.com/victorialogs/)
|
||||
since the [initial v0.1.0 release](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v0.1.0-victorialogs) 1.5 years ago,
|
||||
and is ready for production!
|
||||
|
||||
## [v0.42.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v0.42.0-victorialogs)
|
||||
|
||||
Released at 2024-11-08
|
||||
|
||||
@@ -11,11 +11,6 @@ aliases:
|
||||
- /VictoriaLogs/FAQ.html
|
||||
- /VictoriaLogs/faq.html
|
||||
---
|
||||
|
||||
## Is VictoriaLogs ready for production use?
|
||||
|
||||
Yes. VictoriaLogs is ready for production use starting from [v1.0.0](https://docs.victoriametrics.com/victorialogs/changelog/).
|
||||
|
||||
## What is the difference between VictoriaLogs and Elasticsearch (OpenSearch)?
|
||||
|
||||
Both Elasticsearch and VictoriaLogs allow ingesting structured and unstructured logs
|
||||
@@ -34,7 +29,7 @@ VictoriaLogs is optimized specifically for logs. So it provides the following fe
|
||||
- Easy to setup and operate. There is no need in tuning configuration for optimal performance or in creating any indexes for various log types.
|
||||
Just run VictoriaLogs on the most suitable hardware, ingest logs into it via [supported data ingestion protocols](https://docs.victoriametrics.com/victorialogs/data-ingestion/)
|
||||
and get the best available performance out of the box.
|
||||
- Up to 30x less RAM usage than Elasticsearch for the same workload. See [this article](https://itnext.io/how-do-open-source-solutions-for-logs-work-elasticsearch-loki-and-victorialogs-9f7097ecbc2f) for details.
|
||||
- Up to 30x less RAM usage than Elasticsearch for the same workload.
|
||||
- Up to 15x less disk space usage than Elasticsearch for the same amounts of stored logs.
|
||||
- Ability to work efficiently with hundreds of terabytes of logs on a single node.
|
||||
- Easy to use query language optimized for typical log analysis tasks - [LogsQL](https://docs.victoriametrics.com/victorialogs/logsql/).
|
||||
@@ -174,4 +169,4 @@ And for the following log, its `_msg` will be `foo bar in body`:
|
||||
"message": "",
|
||||
"body": "foo bar in body"
|
||||
}
|
||||
```
|
||||
```
|
||||
@@ -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.0.0-victorialogs/victoria-logs-linux-amd64-v1.0.0-victorialogs.tar.gz
|
||||
tar xzf victoria-logs-linux-amd64-v1.0.0-victorialogs.tar.gz
|
||||
curl -L -O https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v0.42.0-victorialogs/victoria-logs-linux-amd64-v0.42.0-victorialogs.tar.gz
|
||||
tar xzf victoria-logs-linux-amd64-v0.42.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.0.0-victorialogs
|
||||
docker.io/victoriametrics/victoria-logs:v0.42.0-victorialogs
|
||||
```
|
||||
|
||||
See also:
|
||||
|
||||
@@ -15,7 +15,7 @@ VictoriaLogs provides the following features:
|
||||
- VictoriaLogs' capacity and performance scales linearly with the available resources (CPU, RAM, disk IO, disk space).
|
||||
It runs smoothly on Raspberry PI and on servers with hundreds of CPU cores and terabytes of RAM.
|
||||
- It can handle up to 30x bigger data volumes than Elasticsearch and Grafana Loki when running on the same hardware.
|
||||
See [these docs](#benchmarks) and [this article](https://itnext.io/how-do-open-source-solutions-for-logs-work-elasticsearch-loki-and-victorialogs-9f7097ecbc2f) for details.
|
||||
See [these docs](#benchmarks).
|
||||
- It provides fast full-text search out of the box for [log fields](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model)
|
||||
with high cardinality (e.g. high number of unique values) such as `trace_id`, `user_id` and `ip`.
|
||||
- It supports multitenancy - see [these docs](#multitenancy).
|
||||
|
||||
@@ -258,7 +258,7 @@ additionally to [HTTP query args](#http-query-string-parameters):
|
||||
- `VL-Ignore-Fields` - an optional comma-separated list of [log field](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model) names,
|
||||
which must be ignored during data ingestion.
|
||||
|
||||
- `VL-Extra-Fields` - an optional comma-separated list of [log fields](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model),
|
||||
- `VL-Extra-Field` - an optional comma-separated list of [log fields](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model),
|
||||
which must be added to all the ingested logs. The format of every `extra_fields` entry is `field_name=field_value`.
|
||||
If the log entry contains fields from the `extra_fields`, then they are overwritten by the values specified in `extra_fields`.
|
||||
|
||||
|
||||
@@ -23,33 +23,33 @@ or from [docker images](https://hub.docker.com/r/victoriametrics/vlogscli/tags):
|
||||
### Running `vlogscli` from release binary
|
||||
|
||||
```sh
|
||||
curl -L -O https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v1.0.0-victorialogs/vlogscli-linux-amd64-v1.0.0-victorialogs.tar.gz
|
||||
tar xzf vlogscli-linux-amd64-v1.0.0-victorialogs.tar.gz
|
||||
curl -L -O https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v0.42.0-victorialogs/vlogscli-linux-amd64-v0.42.0-victorialogs.tar.gz
|
||||
tar xzf vlogscli-linux-amd64-v0.42.0-victorialogs.tar.gz
|
||||
./vlogscli-prod
|
||||
```
|
||||
|
||||
### Running `vlogscli` from Docker image
|
||||
|
||||
```sh
|
||||
docker run --rm -it docker.io/victoriametrics/vlogscli:v1.0.0-victorialogs
|
||||
docker run --rm -it docker.io/victoriametrics/vlogscli:v0.42.0-victorialogs
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
By default `vlogscli` sends queries to [`http://localhost:8429/select/logsql/query`](https://docs.victoriametrics.com/victorialogs/querying/#querying-logs).
|
||||
The url to query can be changed via `-datasource.url` command-line flag. For example, the following command instructs
|
||||
`vlogscli` sending queries to `https://victoria-logs.some-domain.com/select/logsql/query`:
|
||||
`vlogsql` sending queries to `https://victoria-logs.some-domain.com/select/logsql/query`:
|
||||
|
||||
```sh
|
||||
./vlogscli -datasource.url='https://victoria-logs.some-domain.com/select/logsql/query'
|
||||
./vlogsql -datasource.url='https://victoria-logs.some-domain.com/select/logsql/query'
|
||||
```
|
||||
|
||||
If some HTTP request headers must be passed to the querying API, then set `-header` command-line flag.
|
||||
For example, the following command starts `vlogscli`,
|
||||
For example, the following command starts `vlogsql`,
|
||||
which queries `(AccountID=123, ProjectID=456)` [tenant](https://docs.victoriametrics.com/victorialogs/#multitenancy):
|
||||
|
||||
```sh
|
||||
./vlogscli -header='AccountID: 123' -header='ProjectID: 456'
|
||||
./vlogsql -header='AccountID: 123' -header='ProjectID: 456'
|
||||
```
|
||||
|
||||
|
||||
@@ -59,13 +59,13 @@ which queries `(AccountID=123, ProjectID=456)` [tenant](https://docs.victoriamet
|
||||
can be set via `-accountID` and `-projectID` command-line flags:
|
||||
|
||||
```sh
|
||||
./vlogscli -accountID=123 -projectID=456
|
||||
./vlogsql -accountID=123 -projectID=456
|
||||
```
|
||||
|
||||
|
||||
## Querying
|
||||
|
||||
After the start `vlogscli` provides a prompt for writing [LogsQL](https://docs.victoriametrics.com/victorialogs/logsql/) queries.
|
||||
After the start `vlogsql` provides a prompt for writing [LogsQL](https://docs.victoriametrics.com/victorialogs/logsql/) queries.
|
||||
The query can be multi-line. It is sent to VictoriaLogs as soon as it contains `;` at the end or if a blank line follows the query.
|
||||
For example:
|
||||
|
||||
@@ -85,7 +85,7 @@ and optimizing slow queries.
|
||||
|
||||
Query execution can be interrupted at any time by pressing `Ctrl+C`.
|
||||
|
||||
Type `q` and then press `Enter` for exit from `vlogscli` (if you want to search for `q` [word](https://docs.victoriametrics.com/victorialogs/logsql/#word),
|
||||
Type `q` and then press `Enter` for exit from `vlogsql` (if you want to search for `q` [word](https://docs.victoriametrics.com/victorialogs/logsql/#word),
|
||||
then just wrap it into quotes: `"q"` or `'q'`).
|
||||
|
||||
See also:
|
||||
@@ -98,9 +98,9 @@ See also:
|
||||
|
||||
## Scrolling query results
|
||||
|
||||
If the query response exceeds vertical screen space, `vlogscli` pipes query response to `less` utility,
|
||||
If the query response exceeds vertical screen space, `vlogsql` pipes query response to `less` utility,
|
||||
so you can scroll the response as needed. This allows executing queries, which potentially
|
||||
may return billions of rows, without any problems at both VictoriaLogs and `vlogscli` sides,
|
||||
may return billions of rows, without any problems at both VictoriaLogs and `vlogsql` sides,
|
||||
thanks to the way how `less` interacts with [`/select/logsql/query`](https://docs.victoriametrics.com/victorialogs/querying/#querying-logs):
|
||||
|
||||
- `less` reads the response when needed, e.g. when you scroll it down.
|
||||
@@ -117,7 +117,7 @@ See also [`less` docs](https://man7.org/linux/man-pages/man1/less.1.html) and
|
||||
|
||||
## Live tailing
|
||||
|
||||
`vlogscli` enters live tailing mode when the query is prepended with `\tail ` command. For example,
|
||||
`vlogsql` enters live tailing mode when the query is prepended with `\tail ` command. For example,
|
||||
the following query shows all the newly ingested logs with `error` [word](https://docs.victoriametrics.com/victorialogs/logsql/#word)
|
||||
in real time:
|
||||
|
||||
@@ -133,9 +133,9 @@ Live tailing can show query results in different formats - see [these docs](#out
|
||||
|
||||
## Query history
|
||||
|
||||
`vlogscli` supports query history - press `up` and `down` keys for navigating the history.
|
||||
By default the history is stored in the `vlogscli-history` file at the directory where `vlogscli` runs,
|
||||
so the history is available between `vlogscli` runs.
|
||||
`vlogsql` supports query history - press `up` and `down` keys for navigating the history.
|
||||
By default the history is stored in the `vlogsql-history` file at the directory where `vlogsql` runs,
|
||||
so the history is available between `vlogsql` runs.
|
||||
The path to the file can be changed via `-historyFile` command-line flag.
|
||||
|
||||
Quick tip: type some text and then press `Ctrl+R` for searching queries with the given text in the history.
|
||||
|
||||
@@ -11,39 +11,6 @@ aliases:
|
||||
---
|
||||
Please find the changelog for VictoriaMetrics Anomaly Detection below.
|
||||
|
||||
## v1.18.3
|
||||
Released: 2024-11-14
|
||||
|
||||
- FIX: This patch release resolves an issue that could cause a service crash when parallelizing data processing with `VmReader`. Affected releases: [v1.18.1](#v1181) - [v1.18.2](#v1182).
|
||||
|
||||
## v1.18.2
|
||||
Released: 2024-11-13
|
||||
|
||||
> **Note**: In release [v1.18.1](#v1181), an issue was identified that could lead to a service crash during parallelized data processing with [VmReader](https://docs.victoriametrics.com/anomaly-detection/components/reader/#vm-reader). Please update to patch [v1.18.3](#v1183), which resolves this issue.
|
||||
|
||||
- IMPROVEMENT: Enhanced the flexibility of the [`ProphetModel`](https://docs.victoriametrics.com/anomaly-detection/components/models/#prophet) for tz-aware data (`tz_aware = True`). The `tz_seasonalities` argument has been reformatted to align with the structure of the existing `seasonalities` argument. For more details, refer to the [model section here](https://docs.victoriametrics.com/anomaly-detection/components/models/#prophet). Additionally, tz-aware support for `ProphetModel` has been added to [`AutoTuned`](https://docs.victoriametrics.com/anomaly-detection/components/models/#autotuned) model wrapper. This feature is automatically enabled if the data is timezone-aware and its timezone is not set to the default ('UTC'), otherwise default timezone-free optimization flow will be used.
|
||||
|
||||
## v1.18.1
|
||||
Released: 2024-11-12
|
||||
|
||||
> **Note**: In release [v1.18.1](#v1181), an issue was identified that could lead to a service crash during parallelized data processing with [VmReader](https://docs.victoriametrics.com/anomaly-detection/components/reader/#vm-reader). Please update to patch [v1.18.3](#v1183), which resolves this issue.
|
||||
|
||||
- IMPROVEMENT: Added a [reader-level](https://docs.victoriametrics.com/anomaly-detection/components/reader/#vm-reader) `data_range` argument, allowing users to define a default *valid* data range for all input queries in `queries`. Individual queries can still override this default with their own `data_range` if needed.
|
||||
- IMPROVEMENT: Added the `url` label to enhance labelset consistency across [self-monitoring metrics](https://docs.victoriametrics.com/anomaly-detection/components/monitoring/#metrics-generated-by-vmanomaly) in both [reader](https://docs.victoriametrics.com/anomaly-detection/components/monitoring/#reader-behaviour-metrics) and [writer](https://docs.victoriametrics.com/anomaly-detection/components/monitoring/#writer-behaviour-metrics) components. Metrics affected:
|
||||
- `vmanomaly_reader_received_bytes`
|
||||
- `vmanomaly_reader_response_parsing_seconds`
|
||||
- `vmanomaly_reader_timeseries_received`
|
||||
- `vmanomaly_reader_datapoints_received`
|
||||
- `vmanomaly_writer_request_serialize_seconds`
|
||||
- `vmanomaly_writer_datapoints_sent`
|
||||
- `vmanomaly_writer_timeseries_sent`
|
||||
|
||||
- FIX: Resolved an issue where [rolling models](https://docs.victoriametrics.com/anomaly-detection/components/models/#rolling-models) incorrectly set their last seen `infer` timestamp during *first* `fit_infer` call, resulting in output being produced for *every datapoint* within the `fit_window` on its *first invocation*.
|
||||
- FIX: Resolved an issue in multi-[scheduler](https://docs.victoriametrics.com/anomaly-detection/components/scheduler/) configurations where [self-monitoring metric](https://docs.victoriametrics.com/anomaly-detection/components/monitoring/#metrics-generated-by-vmanomaly) values were overwriting each other.
|
||||
- FIX: Resolved an issue causing incorrect `query_key` label values in the `vmanomaly_model_datapoints_produced` [self-monitoring metric](https://docs.victoriametrics.com/anomaly-detection/components/monitoring/#models-behaviour-metrics) for [univariate models](https://docs.victoriametrics.com/anomaly-detection/components/models/#univariate-models).
|
||||
- FIX: Resolved an issue that caused the `vmanomaly_model_runs` [self-monitoring metric](https://docs.victoriametrics.com/anomaly-detection/components/monitoring/#models-behaviour-metrics) to miss increments for [rolling models](https://docs.victoriametrics.com/anomaly-detection/components/models/#rolling-models).
|
||||
- FIX: Aligned the calculations of `vmanomaly_model_datapoints_accepted` and `vmanomaly_model_datapoints_produced` [self-monitoring model metrics](https://docs.victoriametrics.com/anomaly-detection/components/monitoring/#models-behaviour-metrics) across all stages (`fit`, `infer`, and `fit_infer`) for consistency.
|
||||
|
||||
## v1.18.0
|
||||
Released: 2024-10-28
|
||||
|
||||
|
||||
@@ -159,7 +159,7 @@ services:
|
||||
# ...
|
||||
vmanomaly:
|
||||
container_name: vmanomaly
|
||||
image: victoriametrics/vmanomaly:v1.18.3
|
||||
image: victoriametrics/vmanomaly:v1.18.0
|
||||
# ...
|
||||
ports:
|
||||
- "8490:8490"
|
||||
|
||||
@@ -229,7 +229,7 @@ This will expose metrics at `http://0.0.0.0:8080/metrics` page.
|
||||
To use *vmanomaly* you need to pull docker image:
|
||||
|
||||
```sh
|
||||
docker pull victoriametrics/vmanomaly:v1.18.3
|
||||
docker pull victoriametrics/vmanomaly:v1.18.0
|
||||
```
|
||||
|
||||
> Note: please check what is latest release in [CHANGELOG](https://docs.victoriametrics.com/anomaly-detection/changelog/)
|
||||
@@ -239,7 +239,7 @@ docker pull victoriametrics/vmanomaly:v1.18.3
|
||||
You can put a tag on it for your convenience:
|
||||
|
||||
```sh
|
||||
docker image tag victoriametrics/vmanomaly:v1.18.3 vmanomaly
|
||||
docker image tag victoriametrics/vmanomaly:v1.18.0 vmanomaly
|
||||
```
|
||||
Here is an example of how to run *vmanomaly* docker container with [license file](#licensing):
|
||||
|
||||
|
||||
@@ -58,13 +58,13 @@ Below are the steps to get `vmanomaly` up and running inside a Docker container:
|
||||
1. Pull Docker image:
|
||||
|
||||
```sh
|
||||
docker pull victoriametrics/vmanomaly:v1.18.3
|
||||
docker pull victoriametrics/vmanomaly:v1.18.0
|
||||
```
|
||||
|
||||
2. (Optional step) tag the `vmanomaly` Docker image:
|
||||
|
||||
```sh
|
||||
docker image tag victoriametrics/vmanomaly:v1.18.3 vmanomaly
|
||||
docker image tag victoriametrics/vmanomaly:v1.18.0 vmanomaly
|
||||
```
|
||||
|
||||
3. Start the `vmanomaly` Docker container with a *license file*, use the command below.
|
||||
@@ -98,7 +98,7 @@ docker run -it --user 1000:1000 \
|
||||
services:
|
||||
# ...
|
||||
vmanomaly:
|
||||
image: victoriametrics/vmanomaly:v1.18.3
|
||||
image: victoriametrics/vmanomaly:v1.18.0
|
||||
volumes:
|
||||
$YOUR_LICENSE_FILE_PATH:/license
|
||||
$YOUR_CONFIG_FILE_PATH:/config.yml
|
||||
|
||||
@@ -456,15 +456,12 @@ models:
|
||||
|
||||
> **Note**: `ProphetModel` is a [univariate](#univariate-models), [non-rolling](#non-rolling-models), [offline](#offline-models) model.
|
||||
|
||||
|
||||
> **Note**: Starting with [v1.18.2](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1182), the format for `tz_seasonalities` has been updated to enhance flexibility. Previously, it accepted a list of strings (e.g., `['hod', 'minute']`). Now, it follows the same structure as custom seasonalities defined in the `seasonalities` argument (e.g., `{"name": "hod", "fourier_order": 5, "mode": "additive"}`). This change is backward-compatible, so older configurations will be automatically converted to the new format using default values.
|
||||
|
||||
*Parameters specific for vmanomaly*:
|
||||
|
||||
* `class` (string) - model class name `"model.prophet.ProphetModel"` (or `prophet` starting from [v1.13.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#1130) with class alias support)
|
||||
* `seasonalities` (list[dict], optional): Additional seasonal components to include in Prophet. See Prophet’s [`add_seasonality()`](https://facebook.github.io/prophet/docs/seasonality,_holiday_effects,_and_regressors.html#modeling-holidays-and-special-events:~:text=modeling%20the%20cycle-,Specifying,-Custom%20Seasonalities) documentation for details.
|
||||
- `tz_aware` (bool): (Available since [v1.18.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1180)) Enables handling of timezone-aware timestamps. Default is `False`. Should be used with `tz_seasonalities` and `tz_use_cyclical_encoding` parameters.
|
||||
- `tz_seasonalities` (list[dict]): (Available since [v1.18.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1180)) Specifies timezone-aware seasonal components. Requires `tz_aware=True`. Supported options include `minute`, `hod` (hour of day), `dow` (day of week), and `month` (month of year). Starting with [v1.18.2](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1182), users can configure additional parameters for each seasonality, such as `fourier_order`, `prior_scale`, and `mode`. For more details, please refer to the **Timezone-unaware** configuration example below.
|
||||
- `tz_seasonalities` (list[str]): (Available since [v1.18.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1180)) Specifies timezone-aware seasonal components. Requires `tz_aware=True`. Supported options include `minute`, `hod` (hour of the day), `dow` (day of the week), and `month` (month of the year).
|
||||
- `tz_use_cyclical_encoding` (bool): (Available since [v1.18.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1180)) If set to `True`, applies [cyclical encoding technique](https://www.kaggle.com/code/avanwyk/encoding-cyclical-features-for-deep-learning) to timezone-aware seasonalities. Should be used with `tz_aware=True` and `tz_seasonalities`.
|
||||
|
||||
> **Note**: Apart from standard [`vmanomaly` output](#vmanomaly-output), Prophet model can provide additional metrics.
|
||||
@@ -492,7 +489,6 @@ models:
|
||||
- name: 'hourly'
|
||||
period: 0.04166666666
|
||||
fourier_order: 30
|
||||
prior_scale: 20
|
||||
# inner model args (key-value pairs) accepted by
|
||||
# https://facebook.github.io/prophet/docs/quick_start.html#python-api
|
||||
args:
|
||||
@@ -508,13 +504,8 @@ models:
|
||||
class: 'prophet' # or 'model.prophet.ProphetModel' until v1.13.0
|
||||
provide_series: ['anomaly_score', 'yhat', 'yhat_lower', 'yhat_upper', 'trend']
|
||||
tz_aware: True
|
||||
tz_use_cyclical_encoding: True
|
||||
tz_seasonalities: # intra-day + intra-week seasonality, no intra-year / sub-hour seasonality
|
||||
- name: 'hod' # intra-day seasonality, hour of the day
|
||||
fourier_order: 5 # keep it 3-8 based on intraday pattern complexity
|
||||
prior_scale: 10
|
||||
- name: 'dow' # intra-week seasonality, time of the week
|
||||
fourier_order: 2 # keep it 2-4, as dependencies are learned separately for each weekday
|
||||
tz_seasonalities: ['hod', 'dow'] # intra-day + intra-week seasonality, no intra-year / sub-hour seasonality
|
||||
tz_use_cyclical_encoding: False
|
||||
# inner model args (key-value pairs) accepted by
|
||||
# https://facebook.github.io/prophet/docs/quick_start.html#python-api
|
||||
args:
|
||||
@@ -993,7 +984,7 @@ monitoring:
|
||||
Let's pull the docker image for `vmanomaly`:
|
||||
|
||||
```sh
|
||||
docker pull victoriametrics/vmanomaly:v1.18.3
|
||||
docker pull victoriametrics/vmanomaly:v1.18.0
|
||||
```
|
||||
|
||||
Now we can run the docker container putting as volumes both config and model file:
|
||||
@@ -1007,7 +998,7 @@ docker run -it \
|
||||
-v $(PWD)/license:/license \
|
||||
-v $(PWD)/custom_model.py:/vmanomaly/model/custom.py \
|
||||
-v $(PWD)/custom.yaml:/config.yaml \
|
||||
victoriametrics/vmanomaly:v1.18.3 /config.yaml \
|
||||
victoriametrics/vmanomaly:v1.18.0 /config.yaml \
|
||||
--licenseFile=/license
|
||||
```
|
||||
|
||||
|
||||
@@ -319,7 +319,7 @@ Label names [description](#labelnames)
|
||||
<td>The total number of bytes received in responses for the `query_key` query within the specified scheduler `scheduler_alias`, in the `vmanomaly` service running in `preset` mode.</td>
|
||||
<td>
|
||||
|
||||
`url`, `query_key`, `scheduler_alias`, `preset`
|
||||
`query_key`, `scheduler_alias`, `preset`
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -331,7 +331,7 @@ Label names [description](#labelnames)
|
||||
<td>The total time (in seconds) taken for data parsing at each `step` (json, dataframe) for the `query_key` query within the specified scheduler `scheduler_alias`, in the `vmanomaly` service running in `preset` mode.</td>
|
||||
<td>
|
||||
|
||||
`step`, `url`, `query_key`, `scheduler_alias`, `preset`
|
||||
`step`, `query_key`, `scheduler_alias`, `preset`
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -343,7 +343,7 @@ Label names [description](#labelnames)
|
||||
<td>The total number of timeseries received from VictoriaMetrics for the `query_key` query within the specified scheduler `scheduler_alias`, in the `vmanomaly` service running in `preset` mode.</td>
|
||||
<td>
|
||||
|
||||
`url`, `query_key`, `scheduler_alias`, `preset`
|
||||
`query_key`, `scheduler_alias`, `preset`
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -355,7 +355,7 @@ Label names [description](#labelnames)
|
||||
<td>The total number of datapoints received from VictoriaMetrics for the `query_key` query within the specified scheduler `scheduler_alias`, in the `vmanomaly` service running in `preset` mode.</td>
|
||||
<td>
|
||||
|
||||
`url`, `query_key`, `scheduler_alias`, `preset`
|
||||
`query_key`, `scheduler_alias`, `preset`
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -530,7 +530,7 @@ Label names [description](#labelnames)
|
||||
<td>The total time (in seconds) taken for serializing data for the `query_key` query within the specified scheduler `scheduler_alias`, in the `vmanomaly` service running in `preset` mode.</td>
|
||||
<td>
|
||||
|
||||
`url`, `query_key`, `scheduler_alias`, `preset`
|
||||
`query_key`, `scheduler_alias`, `preset`
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -542,7 +542,7 @@ Label names [description](#labelnames)
|
||||
<td>The total number of datapoints sent to VictoriaMetrics for the `query_key` query within the specified scheduler `scheduler_alias`, in the `vmanomaly` service running in `preset` mode.</td>
|
||||
<td>
|
||||
|
||||
`url`, `query_key`, `scheduler_alias`, `preset`
|
||||
`query_key`, `scheduler_alias`, `preset`
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -552,8 +552,7 @@ Label names [description](#labelnames)
|
||||
<td>`Counter`</td>
|
||||
<td>The total number of timeseries sent to VictoriaMetrics for the `query_key` query within the specified scheduler `scheduler_alias`, in the `vmanomaly` service running in `preset` mode.</td>
|
||||
<td>
|
||||
|
||||
`url`, `query_key`, `scheduler_alias`, `preset`
|
||||
`query_key`, `scheduler_alias`, `preset`
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@@ -68,8 +68,6 @@ Starting from [v1.13.0](https://docs.victoriametrics.com/anomaly-detection/chang
|
||||
- **High anomaly scores** (>1) when the *data falls outside the expected range*, indicating a data constraint violation.
|
||||
- **Lowest anomaly scores** (=0) when the *model's predictions (`yhat`) fall outside the expected range*, meaning uncertain predictions.
|
||||
|
||||
> **Note**: if not set explicitly (or if older config style prior to [v1.13.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1130)) is used, then it is set to reader-level `data_range` arg (since [v1.18.1](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1181))
|
||||
|
||||
- `max_points_per_query` (int): Introduced in [v1.17.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1170), optional arg overrides how `search.maxPointsPerTimeseries` flag (available since [v1.14.1](#v1141)) impacts `vmanomaly` on splitting long `fit_window` [queries](https://docs.victoriametrics.com/anomaly-detection/components/reader/?highlight=queries#vm-reader) into smaller sub-intervals. This helps users avoid hitting the `search.maxQueryDuration` limit for individual queries by distributing initial query across multiple subquery requests with minimal overhead. Set less than `search.maxPointsPerTimeseries` if hitting `maxQueryDuration` limits. If set on a query-level, it overrides the global `max_points_per_query` (reader-level).
|
||||
|
||||
- `tz` (string): Introduced in [v1.18.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1180), this optional argument enables timezone specification per query, overriding the reader’s default `tz`. This setting helps to account for local timezone shifts, such as [DST](https://en.wikipedia.org/wiki/Daylight_saving_time), in models that are sensitive to seasonal variations (e.g., [`ProphetModel`](https://docs.victoriametrics.com/anomaly-detection/components/models/#prophet) or [`OnlineQuantileModel`](https://docs.victoriametrics.com/anomaly-detection/components/models/#online-seasonal-quantile)).
|
||||
@@ -325,17 +323,6 @@ Introduced in [v1.17.0](https://docs.victoriametrics.com/anomaly-detection/chang
|
||||
Introduced in [v1.18.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1180), this optional argument specifies the [IANA](https://nodatime.org/TimeZones) timezone to account for local shifts, like [DST](https://en.wikipedia.org/wiki/Daylight_saving_time), in models sensitive to seasonal patterns (e.g., [`ProphetModel`](https://docs.victoriametrics.com/anomaly-detection/components/models/#prophet) or [`OnlineQuantileModel`](https://docs.victoriametrics.com/anomaly-detection/components/models/#online-seasonal-quantile)). Defaults to `UTC` if not set and can be overridden on a [per-query basis](#per-query-parameters).
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
`data_range`
|
||||
</td>
|
||||
<td>
|
||||
`["-inf", "inf"]`
|
||||
</td>
|
||||
<td>
|
||||
Added in [v1.18.1](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1180), this optional argument allows defining **valid** data ranges for input of all the queries in `queries`. Defaults to `["-inf", "inf"]` if not set and can be overridden on a [per-query basis](#per-query-parameters).
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -347,13 +334,12 @@ reader:
|
||||
datasource_url: "https://play.victoriametrics.com/"
|
||||
tenant_id: "0:0"
|
||||
tz: 'America/New_York'
|
||||
data_range: [1, 'inf'] # reader-level
|
||||
queries:
|
||||
ingestion_rate:
|
||||
expr: 'sum(rate(vm_rows_inserted_total[5m])) by (type) > 0'
|
||||
step: '1m' # can override reader-level `sampling_period` on per-query level
|
||||
data_range: [0, 'inf'] # if set, overrides reader-level data_range
|
||||
tz: 'Australia/Sydney' # if set, overrides reader-level tz
|
||||
step: '1m' # can override global `sampling_period` on per-query level
|
||||
data_range: [0, 'inf']
|
||||
tz: 'Australia/Sydney' # if set, overrides reader-wise tz
|
||||
sampling_period: '1m'
|
||||
query_from_last_seen_timestamp: True # false by default
|
||||
latency_offset: '1ms'
|
||||
|
||||
@@ -385,7 +385,7 @@ services:
|
||||
restart: always
|
||||
vmanomaly:
|
||||
container_name: vmanomaly
|
||||
image: victoriametrics/vmanomaly:v1.18.3
|
||||
image: victoriametrics/vmanomaly:v1.18.0
|
||||
depends_on:
|
||||
- "victoriametrics"
|
||||
ports:
|
||||
|
||||
@@ -18,56 +18,12 @@ See also [LTS releases](https://docs.victoriametrics.com/lts-releases/).
|
||||
|
||||
## tip
|
||||
|
||||
## [v1.106.1](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.106.1)
|
||||
|
||||
Released at 2024-11-15
|
||||
|
||||
* SECURITY: upgrade Go builder from Go1.23.1 to Go1.23.3. See the list of issues addressed in [Go1.23.2](https://github.com/golang/go/issues?q=milestone%3AGo1.23.2+label%3ACherryPickApproved) and [Go1.23.3](https://github.com/golang/go/issues?q=milestone%3AGo1.23.3+label%3ACherryPickApproved).
|
||||
|
||||
* FEATURE: [vmalert](https://docs.victoriametrics.com/vmalert/): reduces memory and CPU usage for high volume of triggered alerting rules. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6894) for details.
|
||||
|
||||
* BUGFIX: [vmctl](https://docs.victoriametrics.com/vmctl/): drop rows that do not belong to the current series during import. The dropped rows should belong to another series whose tags are a superset of the current series. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7301) and [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/7330). Thanks to @dpedu for reporting and cooperating with the test.
|
||||
* BUGFIX: [vmsingle](https://docs.victoriametrics.com/single-server-victoriametrics/), `vmselect` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/cluster-victoriametrics/): keep the order of resulting time series when `limit_offset` is applied. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7068).
|
||||
* BUGFIX: [graphite](https://docs.victoriametrics.com/#graphite-render-api-usage): properly handle xFilesFactor=0 for `transformRemoveEmptySeries` function. See [this PR](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/7337) for details.
|
||||
* BUGFIX: [vmauth](https://docs.victoriametrics.com/vmauth/): fixed unauthorized routing behavior inconsistency. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7543) for details.
|
||||
* BUGFIX: [vmauth](https://docs.victoriametrics.com/vmauth/): properly inherit [`drop_src_path_prefix_parts`](https://docs.victoriametrics.com/vmauth/#dropping-request-path-prefix), [`load_balancing_policy`](https://docs.victoriametrics.com/vmauth/#high-availability), [`retry_status_codes`](https://docs.victoriametrics.com/vmauth/#load-balancing) and [`discover_backend_ips`](https://docs.victoriametrics.com/vmauth/#discovering-backend-ips) options by `url_map` entries if `url_prefix` option isn't set at the [user config level](https://docs.victoriametrics.com/vmauth/#auth-config). These options were inherited only when the `url_prefix` option was set. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7519).
|
||||
* BUGFIX: [vmauth](https://docs.victoriametrics.com/vmauth): properly check availability of all the backends before giving up when proxying requests. Previously, vmauth could return an error even if there were healthy backends available. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3061) for details.
|
||||
* BUGFIX: [dashboards](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/dashboards): add `file` label filter to vmalert dashboard panels. Previously, metrics from groups with the same name but different rule files could be mixed in the results.
|
||||
* BUGFIX: [Single-node VictoriaMetrics](https://docs.victoriametrics.com/) and `vmstorage` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/cluster-victoriametrics/): Optimize resources usage for configured [downsampling](https://docs.victoriametrics.com/#downsampling) with time-series filter. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7440) for details.
|
||||
* BUGFIX: `vmstorage` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/cluster-victoriametrics/): Properly return query results for search requests after index rotation. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7417) for details.
|
||||
* BUGFIX: `vmselect` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/cluster-victoriametrics/): Properly handle [multitenant](https://docs.victoriametrics.com/cluster-victoriametrics/#multitenancy-via-labels) query request errors and correctly perform search for available tenants. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7549) for details.
|
||||
|
||||
## [v1.102.7](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.102.7)
|
||||
|
||||
Released at 2024-11-15
|
||||
|
||||
**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.1 to Go1.23.3. See the list of issues addressed in [Go1.23.2](https://github.com/golang/go/issues?q=milestone%3AGo1.23.2+label%3ACherryPickApproved) and [Go1.23.3](https://github.com/golang/go/issues?q=milestone%3AGo1.23.3+label%3ACherryPickApproved).
|
||||
|
||||
* BUGFIX: [vmctl](https://docs.victoriametrics.com/vmctl/): drop rows that do not belong to the current series during import. The dropped rows should belong to another series whose tags are a superset of the current series. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7301) and [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/7330). Thanks to @dpedu for reporting and cooperating with the test.
|
||||
* BUGFIX: [vmsingle](https://docs.victoriametrics.com/single-server-victoriametrics/), `vmselect` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/cluster-victoriametrics/): keep the order of resulting time series when `limit_offset` is applied. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7068).
|
||||
* BUGFIX: [graphite](https://docs.victoriametrics.com/#graphite-render-api-usage): properly handle xFilesFactor=0 for `transformRemoveEmptySeries` function. See [this PR](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/7337) for details.
|
||||
* BUGFIX: [vmauth](https://docs.victoriametrics.com/vmauth): properly check availability of all the backends before giving up when proxying requests. Previously, vmauth could return an error even if there were healthy backends available. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3061) for details.
|
||||
* BUGFIX: [vmauth](https://docs.victoriametrics.com/vmauth/): fixed unauthorized routing behavior inconsistency. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7543) for details.
|
||||
* BUGFIX: [vmauth](https://docs.victoriametrics.com/vmauth/): properly inherit [`drop_src_path_prefix_parts`](https://docs.victoriametrics.com/vmauth/#dropping-request-path-prefix), [`load_balancing_policy`](https://docs.victoriametrics.com/vmauth/#high-availability), [`retry_status_codes`](https://docs.victoriametrics.com/vmauth/#load-balancing) and [`discover_backend_ips`](https://docs.victoriametrics.com/vmauth/#discovering-backend-ips) options by `url_map` entries if `url_prefix` option isn't set at the [user config level](https://docs.victoriametrics.com/vmauth/#auth-config). These options were inherited only when the `url_prefix` option was set. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7519).
|
||||
* BUGFIX: [dashboards](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/dashboards): add `file` label filter to vmalert dashboard panels. Previously, metrics from groups with the same name but different rule files could be mixed in the results.
|
||||
|
||||
## [v1.97.12](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.97.12)
|
||||
|
||||
Released at 2024-11-15
|
||||
|
||||
**v1.97.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.97.x line will be supported for at least 12 months since [v1.97.0](https://docs.victoriametrics.com/CHANGELOG.html#v1970) release**
|
||||
|
||||
* BUGFIX: [vmctl](https://docs.victoriametrics.com/vmctl/): drop rows that do not belong to the current series during import. The dropped rows should belong to another series whose tags are a superset of the current series. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7301) and [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/7330). Thanks to @dpedu for reporting and cooperating with the test.
|
||||
* BUGFIX: [vmsingle](https://docs.victoriametrics.com/single-server-victoriametrics/), `vmselect` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/cluster-victoriametrics/): keep the order of resulting time series when `limit_offset` is applied. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7068).
|
||||
* BUGFIX: [graphite](https://docs.victoriametrics.com/#graphite-render-api-usage): properly handle xFilesFactor=0 for `transformRemoveEmptySeries` function. See [this PR](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/7337) for details.
|
||||
* BUGFIX: [vmauth](https://docs.victoriametrics.com/vmauth): properly check availability of all the backends before giving up when proxying requests. Previously, vmauth could return an error even if there were healthy backends available. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3061) for details.
|
||||
* BUGFIX: [vmauth](https://docs.victoriametrics.com/vmauth/): fixed unauthorized routing behavior inconsistency. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7543) for details.
|
||||
|
||||
## [v1.106.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.106.0)
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
## Next release
|
||||
|
||||
- Removed redundant `VECTOR_SELF_NODE_NAME` env variable from vector values. See [this issue](https://github.com/VictoriaMetrics/helm-charts/issues/1727).
|
||||
- Added Vector dashboard. See [this issue](https://github.com/VictoriaMetrics/helm-charts/issues/1721).
|
||||
- TODO
|
||||
|
||||
## 0.8.1
|
||||
|
||||
|
||||
@@ -270,7 +270,7 @@ Change the values according to the need of the environment in ``victoria-logs-si
|
||||
</code>
|
||||
</pre>
|
||||
</td>
|
||||
<td><p>Override chart name</p>
|
||||
<td><p>Global name override</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -1121,22 +1121,13 @@ readOnlyRootFilesystem: true
|
||||
<td>vector</td>
|
||||
<td>object</td>
|
||||
<td><pre class="helm-vars-default-value" language-yaml" lang="plaintext">
|
||||
<code class="language-yaml">containerPorts:
|
||||
- containerPort: 9090
|
||||
name: prom-exporter
|
||||
protocol: TCP
|
||||
customConfig:
|
||||
<code class="language-yaml">customConfig:
|
||||
api:
|
||||
address: 127.0.0.1:8686
|
||||
enabled: false
|
||||
playground: true
|
||||
data_dir: /vector-data-dir
|
||||
sinks:
|
||||
exporter:
|
||||
address: 0.0.0.0:9090
|
||||
inputs:
|
||||
- internal_metrics
|
||||
type: prometheus_exporter
|
||||
vlogs:
|
||||
api_version: v8
|
||||
compression: gzip
|
||||
@@ -1155,8 +1146,6 @@ customConfig:
|
||||
VL-Time-Field: timestamp
|
||||
type: elasticsearch
|
||||
sources:
|
||||
internal_metrics:
|
||||
type: internal_metrics
|
||||
k8s:
|
||||
type: kubernetes_logs
|
||||
transforms:
|
||||
@@ -1169,10 +1158,13 @@ customConfig:
|
||||
type: remap
|
||||
dataDir: /vector-data-dir
|
||||
enabled: false
|
||||
env:
|
||||
- name: VECTOR_SELF_NODE_NAME
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: spec.nodeName
|
||||
existingConfigMaps:
|
||||
- vl-config
|
||||
podMonitor:
|
||||
enabled: false
|
||||
resources: {}
|
||||
role: Agent
|
||||
service:
|
||||
|
||||
@@ -1,18 +1,7 @@
|
||||
## Next release
|
||||
|
||||
- TODO
|
||||
|
||||
## 0.14.7
|
||||
|
||||
**Release date:** 2024-11-12
|
||||
|
||||

|
||||

|
||||
|
||||
- set default DNS domain to `cluster.local.`
|
||||
- updated common dependency 0.0.19 -> 0.0.23
|
||||
- added template for configmap name
|
||||
- fixed statefulset variable name typo
|
||||
|
||||
## 0.14.6
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
 
|
||||
 
|
||||
[](https://artifacthub.io/packages/helm/victoriametrics/victoria-metrics-agent)
|
||||
[](https://slack.victoriametrics.com/)
|
||||
|
||||
@@ -597,7 +597,7 @@ loggerFormat: json
|
||||
</code>
|
||||
</pre>
|
||||
</td>
|
||||
<td><p>Override resources fullname</p>
|
||||
<td><p>Overrides the fullname prefix</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -924,7 +924,7 @@ name: ""
|
||||
</code>
|
||||
</pre>
|
||||
</td>
|
||||
<td><p>Override chart name</p>
|
||||
<td><p>Overrides fullname suffix</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -1166,9 +1166,9 @@ periodSeconds: 15
|
||||
</tr>
|
||||
<tr>
|
||||
<td>remoteWrite</td>
|
||||
<td>list</td>
|
||||
<td><pre class="helm-vars-default-value" language-yaml" lang="plaintext">
|
||||
<code class="language-yaml">[]
|
||||
<td>string</td>
|
||||
<td><pre class="helm-vars-default-value" language-yaml" lang="">
|
||||
<code class="language-yaml">null
|
||||
</code>
|
||||
</pre>
|
||||
</td>
|
||||
|
||||
@@ -1,20 +1,10 @@
|
||||
## Next release
|
||||
|
||||
- TODO
|
||||
|
||||
## 0.12.5
|
||||
|
||||
**Release date:** 2024-11-12
|
||||
|
||||

|
||||

|
||||
|
||||
- changed `alertmanager.podMetadata.labels` to `alertmanager.podLabels`
|
||||
- changed `alertmanager.podMetadata.annotations` to `alertmanager.podAnnotations`
|
||||
- fix Deployment/StatefulSets when `serviceAccount.name` is empty and `serviceAccount.create: false`. See [this issue](https://github.com/VictoriaMetrics/helm-charts/issues/1683).
|
||||
- set default DNS domain to `cluster.local.`
|
||||
- updated common dependency 0.0.19 -> 0.0.23
|
||||
- added template for configmap name. See [this issue](https://github.com/VictoriaMetrics/helm-charts/issues/1734)
|
||||
|
||||
## 0.12.4
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
 
|
||||
 
|
||||
[](https://artifacthub.io/packages/helm/victoriametrics/victoria-metrics-alert)
|
||||
[](https://slack.victoriametrics.com/)
|
||||
|
||||
@@ -273,17 +273,6 @@ route:
|
||||
</pre>
|
||||
</td>
|
||||
<td><p>Extra Volumes for the pod</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>alertmanager.fullnameOverride</td>
|
||||
<td>string</td>
|
||||
<td><pre class="helm-vars-default-value" language-yaml" lang="">
|
||||
<code class="language-yaml">""
|
||||
</code>
|
||||
</pre>
|
||||
</td>
|
||||
<td><p>Override Alertmanager resources fullname</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -902,17 +891,6 @@ name: ""
|
||||
</pre>
|
||||
</td>
|
||||
<td><p>Existing secret name</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>nameOverride</td>
|
||||
<td>string</td>
|
||||
<td><pre class="helm-vars-default-value" language-yaml" lang="">
|
||||
<code class="language-yaml">""
|
||||
</code>
|
||||
</pre>
|
||||
</td>
|
||||
<td><p>Override chart name</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -1109,7 +1087,7 @@ loggerFormat: json
|
||||
</code>
|
||||
</pre>
|
||||
</td>
|
||||
<td><p>Override vmalert resources fullname</p>
|
||||
<td><p>Full name prefix override</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -1256,7 +1234,18 @@ variant: ""
|
||||
</code>
|
||||
</pre>
|
||||
</td>
|
||||
<td><p>Override default <code>app</code> label name</p>
|
||||
<td><p>Override fullname suffix</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>server.nameOverride</td>
|
||||
<td>string</td>
|
||||
<td><pre class="helm-vars-default-value" language-yaml" lang="">
|
||||
<code class="language-yaml">""
|
||||
</code>
|
||||
</pre>
|
||||
</td>
|
||||
<td><p>Full name suffix override</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
@@ -1,15 +1,5 @@
|
||||
## Next release
|
||||
|
||||
- TODO
|
||||
|
||||
## 1.6.4
|
||||
|
||||
**Release date:** 2024-11-12
|
||||
|
||||

|
||||

|
||||
|
||||
- Upgraded [`vmanomaly`](https://docs.victoriametrics.com/anomaly-detection/) to [1.18.1](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1181)
|
||||
- use common templates
|
||||
- set default DNS domain to `cluster.local.`
|
||||
- added podLabels and podAnnotations to add extra pod labels and annotations
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
[](https://artifacthub.io/packages/helm/victoriametrics/victoria-metrics-anomaly)
|
||||
[](https://slack.victoriametrics.com/)
|
||||
[](https://github.com/VictoriaMetrics/helm-charts/blob/master/LICENSE)
|
||||
@@ -451,7 +451,7 @@ tenant_id: ""
|
||||
</code>
|
||||
</pre>
|
||||
</td>
|
||||
<td><p>Override resources fullname</p>
|
||||
<td><p>Full name prefix override</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -621,7 +621,7 @@ name: ""
|
||||
</code>
|
||||
</pre>
|
||||
</td>
|
||||
<td><p>Override chart name</p>
|
||||
<td><p>Full name suffix override</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
@@ -278,7 +278,7 @@ loggerFormat: json
|
||||
</code>
|
||||
</pre>
|
||||
</td>
|
||||
<td><p>Override resources fullname</p>
|
||||
<td><p>Full name prefix override</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -613,7 +613,7 @@ name: ""
|
||||
</code>
|
||||
</pre>
|
||||
</td>
|
||||
<td><p>Override chart name</p>
|
||||
<td><p>Full name suffix override</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
@@ -683,7 +683,7 @@ loggerFormat: json
|
||||
</code>
|
||||
</pre>
|
||||
</td>
|
||||
<td><p>Override default <code>app</code> label name</p>
|
||||
<td><p>VMAuth container name</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -1486,7 +1486,7 @@ loggerFormat: json
|
||||
</code>
|
||||
</pre>
|
||||
</td>
|
||||
<td><p>Override default <code>app</code> label name</p>
|
||||
<td><p>VMInsert name</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -2334,7 +2334,7 @@ loggerFormat: json
|
||||
</code>
|
||||
</pre>
|
||||
</td>
|
||||
<td><p>Override default <code>app</code> label name</p>
|
||||
<td><p>VMSelect container name</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -3205,7 +3205,7 @@ loggerFormat: json
|
||||
</code>
|
||||
</pre>
|
||||
</td>
|
||||
<td><p>Override default <code>app</code> label name</p>
|
||||
<td><p>VMStorage container name</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
|
||||
## Next release
|
||||
|
||||
- Disabled impact of `<component>.name` on resource name to avoid confusion
|
||||
- Fixed `vm.app.name` template for appCtx that contains slice
|
||||
- TODO
|
||||
|
||||
## 0.0.23
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
- set default DNS domain to `cluster.local.`
|
||||
- updated common dependency 0.0.19 -> 0.0.23
|
||||
- added template for configmap name
|
||||
|
||||
## 0.5.5
|
||||
|
||||
|
||||
@@ -368,7 +368,7 @@ loggerFormat: json
|
||||
</code>
|
||||
</pre>
|
||||
</td>
|
||||
<td><p>Override resources fullname</p>
|
||||
<td><p>Full name prefix</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -626,7 +626,7 @@ name: ""
|
||||
</code>
|
||||
</pre>
|
||||
</td>
|
||||
<td><p>Override chart name</p>
|
||||
<td><p>Full name suffix</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
- fix Deployment/StatefulSets when `serviceAccount.name` is empty and `serviceAccount.create: false`. See [this issue](https://github.com/VictoriaMetrics/helm-charts/issues/1683).
|
||||
- set default DNS domain to `cluster.local.`
|
||||
- updated common dependency 0.0.19 -> 0.0.23
|
||||
- added back `crds.enabled: false` option, which disables CRD creation, but due to limitation of dependencies condition it allows to disable only in combination with `crds.plain: false`
|
||||
|
||||
## 0.37.0
|
||||
|
||||
|
||||
@@ -348,17 +348,6 @@ requests:
|
||||
</pre>
|
||||
</td>
|
||||
<td><p>Cleanup hook resources</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>crds.enabled</td>
|
||||
<td>bool</td>
|
||||
<td><pre class="helm-vars-default-value" language-yaml" lang="">
|
||||
<code class="language-yaml">true
|
||||
</code>
|
||||
</pre>
|
||||
</td>
|
||||
<td><p>manages CRD creation. Disables CRD creation only in combination with <code>crds.plain: false</code> due to helm dependency conditions limitation</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -479,7 +468,7 @@ requests:
|
||||
</code>
|
||||
</pre>
|
||||
</td>
|
||||
<td><p>Overrides the full name of server component resources</p>
|
||||
<td><p>Overrides the full name of server component</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -627,7 +616,7 @@ variant: ""
|
||||
</code>
|
||||
</pre>
|
||||
</td>
|
||||
<td><p>Override chart name</p>
|
||||
<td><p>VM operatror deployment name override</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
@@ -606,7 +606,7 @@ loggerFormat: json
|
||||
</code>
|
||||
</pre>
|
||||
</td>
|
||||
<td><p>Override default <code>app</code> label name</p>
|
||||
<td><p>Server resource name prefix</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
@@ -581,9 +581,7 @@ Params:
|
||||
If the `end` isn't set, then the `end` is automatically set to the current time.
|
||||
* `step` - the [interval](https://prometheus.io/docs/prometheus/latest/querying/basics/#time-durations)
|
||||
between data points, which must be returned from the range query.
|
||||
The `query` is executed at `start`, `start+step`, `start+2*step`, ..., `start+N*step` timestamps,
|
||||
where `N` is the whole number of steps that fit between `start` and `end`.
|
||||
`end` is included only when it equals to `start+N*step`.
|
||||
The `query` is executed at `start`, `start+step`, `start+2*step`, ..., `end` timestamps.
|
||||
If the `step` isn't set, then it default to `5m` (5 minutes).
|
||||
* `timeout` - optional query timeout. For example, `timeout=5s`. Query is canceled when the timeout is reached.
|
||||
By default the timeout is set to the value of `-search.maxQueryDuration` command-line flag passed to single-node VictoriaMetrics
|
||||
@@ -591,8 +589,8 @@ Params:
|
||||
|
||||
The result of Range query is a list of [time series](https://docs.victoriametrics.com/keyconcepts/#time-series)
|
||||
matching the filter in `query` expression. Each returned series contains `(timestamp, value)` results for the `query` executed
|
||||
at `start`, `start+step`, `start+2*step`, ..., `start+N*step` timestamps. In other words, Range query is an [Instant query](#instant-query)
|
||||
executed independently at `start`, `start+step`, ..., `start+N*step` timestamps.
|
||||
at `start`, `start+step`, `start+2*step`, ..., `end` timestamps. In other words, Range query is an [Instant query](#instant-query)
|
||||
executed independently at `start`, `start+step`, ..., `end` timestamps.
|
||||
|
||||
For example, to get the values of `foo_bar` during the time range from `2022-05-10T07:59:00Z` to `2022-05-10T08:17:00Z`,
|
||||
we need to issue a range query:
|
||||
|
||||
@@ -13,13 +13,8 @@ aliases:
|
||||
|
||||
## tip
|
||||
|
||||
- TODO
|
||||
|
||||
## [v0.49.1](https://github.com/VictoriaMetrics/operator/releases/tag/v0.49.1) - 11 Nov 2024
|
||||
|
||||
- [vmrule](https://docs.victoriametrics.com/operator/resources/vmrule/): properly validate rules for [vlogs](https://docs.victoriametrics.com/victorialogs/vmalert/) group `type`.
|
||||
- [operator](https://docs.victoriametrics.com/operator/): properly apply changes to the [converted](https://docs.victoriametrics.com/operator/migration/#objects-conversion) `VMScrapeConfig` during operator start-up.
|
||||
- [operator](https://docs.victoriametrics.com/operator/): properly set `operational` update status for CRDs. Previously, `operational` status could be set before rollout finishes at Kubernetes due to bug at Kubernetes `controller-manager`.
|
||||
|
||||
|
||||
## [v0.49.0](https://github.com/VictoriaMetrics/operator/releases/tag/v0.49.0) - 15 Oct 2024
|
||||
|
||||
@@ -1052,7 +1052,7 @@ The shortlist of configuration flags is the following:
|
||||
-datasource.disableKeepAlive
|
||||
Whether to disable long-lived connections to the datasource. If true, disables HTTP keep-alive and will only use the connection to the server for a single HTTP request.
|
||||
-datasource.disableStepParam
|
||||
Whether to disable adding 'step' param in instant queries to the configured -datasource.url and -remoteRead.url. Only valid for prometheus datasource. This might be useful when using vmalert with datasources that do not support 'step' param for instant queries, like Google Managed Prometheus. It is not recommended to enable this flag if you use vmalert with VictoriaMetrics.
|
||||
Whether to disable adding 'step' param to the issued instant queries. This might be useful when using vmalert with datasources that do not support 'step' param for instant queries, like Google Managed Prometheus. It is not recommended to enable this flag if you use vmalert with VictoriaMetrics.
|
||||
-datasource.headers string
|
||||
Optional HTTP extraHeaders to send with each request to the corresponding -datasource.url. For example, -datasource.headers='My-Auth:foobar' would send 'My-Auth: foobar' HTTP header with every request to the corresponding -datasource.url. Multiple headers must be delimited by '^^': -datasource.headers='header1:value1^^header2:value2'
|
||||
-datasource.idleConnTimeout duration
|
||||
@@ -1074,11 +1074,11 @@ The shortlist of configuration flags is the following:
|
||||
-datasource.oauth2.tokenUrl string
|
||||
Optional OAuth2 tokenURL to use for -datasource.url
|
||||
-datasource.queryStep duration
|
||||
How far a value can fallback to when evaluating queries to the configured -datasource.url and -remoteRead.url. Only valid for prometheus datasource. For example, if -datasource.queryStep=15s then param "step" with value "15s" will be added to every query. If set to 0, rule's evaluation interval will be used instead. (default 5m0s)
|
||||
How far a value can fallback to when evaluating queries. For example, if -datasource.queryStep=15s then param "step" with value "15s" will be added to every query. If set to 0, rule's evaluation interval will be used instead. (default 5m0s)
|
||||
-datasource.queryTimeAlignment
|
||||
Deprecated: please use "eval_alignment" in rule group instead. Whether to align "time" parameter with evaluation interval. Alignment supposed to produce deterministic results despite number of vmalert replicas or time they were started. See more details at https://github.com/VictoriaMetrics/VictoriaMetrics/pull/1257 (default true)
|
||||
-datasource.roundDigits int
|
||||
Adds "round_digits" GET param to datasource requests which limits the number of digits after the decimal point in response values. Only valid for VictoriaMetrics as the datasource.
|
||||
Adds "round_digits" GET param to datasource requests. In VM "round_digits" limits the number of digits after the decimal point in response values.
|
||||
-datasource.showURL
|
||||
Whether to avoid stripping sensitive information such as auth headers or passwords from URLs in log messages or UI and exported metrics. It is hidden by default, since it can contain sensitive info such as auth key
|
||||
-datasource.tlsCAFile string
|
||||
|
||||
@@ -8,7 +8,7 @@ title: vmbackupmanager
|
||||
aliases:
|
||||
- /vmbackupmanager.html
|
||||
---
|
||||
## vmbackupmanager
|
||||
# vmbackupmanager
|
||||
|
||||
***vmbackupmanager is a part of [enterprise package](https://docs.victoriametrics.com/enterprise/).
|
||||
It is available for download and evaluation at [releases page](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/latest).
|
||||
@@ -47,10 +47,6 @@ The backup manager creates the following directory hierarchy at `-dst`:
|
||||
* `/weekly/` - contains weekly backups. Each backup is named as `YYYY-WW`
|
||||
* `/monthly/` - contains monthly backups. Each backup is named as `YYYY-MM`
|
||||
|
||||
The `vmbackupmanager` takes backups every hour if hourly backups are not disabled; otherwise,
|
||||
it defaults to taking backups every 24 hours. You can control the schedule using the `-backupInterval` flag.
|
||||
For example, if you want to take backups three times per day, set `-backupInterval=8h`.
|
||||
|
||||
To get the full list of supported flags please run the following command:
|
||||
|
||||
```sh
|
||||
@@ -71,7 +67,7 @@ There are two flags which could help with performance tuning:
|
||||
* `-maxBytesPerSecond` - the maximum upload speed. There is no limit if it is set to 0
|
||||
* `-concurrency` - The number of concurrent workers. Higher concurrency may improve upload speed (default 10)
|
||||
|
||||
### Example of Usage
|
||||
## Example of Usage
|
||||
|
||||
GCS and cluster version. You need to have a credentials file in json format with following structure:
|
||||
|
||||
@@ -135,7 +131,7 @@ which perform full object copy during server-side copying. This may be slow and
|
||||
Please, see [vmbackup docs](https://docs.victoriametrics.com/vmbackup/#advanced-usage) for more examples of authentication with different
|
||||
storage types.
|
||||
|
||||
### Backup Retention Policy
|
||||
## Backup Retention Policy
|
||||
|
||||
Backup retention policy is controlled by:
|
||||
|
||||
@@ -177,7 +173,7 @@ The result on the GCS bucket. We see only 3 daily backups:
|
||||
|
||||
[retention policy daily after retention cycle](vmbackupmanager_rp_daily_2.webp "retention policy daily after retention cycle")
|
||||
|
||||
#### Protection backups against deletion by retention policy
|
||||
### Protection backups against deletion by retention policy
|
||||
|
||||
You can protect any backup against deletion by retention policy with the `vmbackupmanager backups lock` command.
|
||||
|
||||
@@ -249,7 +245,7 @@ For example:
|
||||
|
||||
* DELETE `/api/v1/restore` - delete restore mark.
|
||||
|
||||
### CLI
|
||||
## CLI
|
||||
|
||||
`vmbackupmanager` exposes CLI commands to work with [API methods](#api-methods) without external dependencies.
|
||||
|
||||
@@ -286,7 +282,7 @@ It can be changed by using flag:
|
||||
vmbackupmanager address to perform API requests (default "http://127.0.0.1:8300")
|
||||
```
|
||||
|
||||
#### Backup commands
|
||||
### Backup commands
|
||||
|
||||
`vmbackupmanager backup list` lists backups in remote storage:
|
||||
```sh
|
||||
@@ -294,7 +290,7 @@ $ ./vmbackupmanager backup list
|
||||
[{"name":"daily/2023-04-07","size_bytes":318837,"size":"311.4ki","created_at":"2023-04-07T16:15:07+00:00"},{"name":"hourly/2023-04-07:11","size_bytes":318837,"size":"311.4ki","created_at":"2023-04-07T16:15:06+00:00"},{"name":"latest","size_bytes":318837,"size":"311.4ki","created_at":"2023-04-07T16:15:04+00:00"},{"name":"monthly/2023-04","size_bytes":318837,"size":"311.4ki","created_at":"2023-04-07T16:15:10+00:00"},{"name":"weekly/2023-14","size_bytes":318837,"size":"311.4ki","created_at":"2023-04-07T16:15:09+00:00"}]
|
||||
```
|
||||
|
||||
#### Restore commands
|
||||
### Restore commands
|
||||
|
||||
Restore commands are used to create, get and delete restore mark.
|
||||
Restore mark is used by `vmbackupmanager` to store backup name to restore when running restore.
|
||||
@@ -348,7 +344,7 @@ If restore mark doesn't exist at `storageDataPath`(restore wasn't requested) `vm
|
||||
1. Start `vmstorage` or `vmsingle` node
|
||||
|
||||
|
||||
#### How to restore in Kubernetes
|
||||
### How to restore in Kubernetes
|
||||
|
||||
1. Ensure there is an init container with `vmbackupmanager restore` in `vmstorage` or `vmsingle` pod.
|
||||
For [VictoriaMetrics operator](https://docs.victoriametrics.com/operator/) deployments it is required to add:
|
||||
@@ -376,7 +372,7 @@ If restore mark doesn't exist at `storageDataPath`(restore wasn't requested) `vm
|
||||
```
|
||||
1. Restart pod
|
||||
|
||||
##### Restore cluster into another cluster
|
||||
#### Restore cluster into another cluster
|
||||
|
||||
These steps are assuming that [VictoriaMetrics operator](https://docs.victoriametrics.com/operator/) is used to manage `VMCluster`.
|
||||
Clusters here are referred to as `source` and `destination`.
|
||||
@@ -405,7 +401,7 @@ Clusters here are referred to as `source` and `destination`.
|
||||
```
|
||||
1. Restart `vmstorage` pods of *destination* cluster. On pod start `vmbackupmanager` will restore data from the specified backup.
|
||||
|
||||
### Monitoring
|
||||
## Monitoring
|
||||
|
||||
`vmbackupmanager` exports various metrics in Prometheus exposition format at `http://vmbackupmanager:8300/metrics` page. It is recommended setting up regular scraping of this page
|
||||
either via [vmagent](https://docs.victoriametrics.com/vmagent/) or via Prometheus, so the exported metrics could be analyzed later.
|
||||
@@ -415,9 +411,9 @@ Graphs on this dashboard contain useful hints - hover the `i` icon in the top le
|
||||
If you have suggestions for improvements or have found a bug - please open an issue on github or add
|
||||
a review to the dashboard.
|
||||
|
||||
### Configuration
|
||||
## Configuration
|
||||
|
||||
#### Flags
|
||||
### Flags
|
||||
|
||||
Pass `-help` to `vmbackupmanager` in order to see the full list of supported
|
||||
command-line flags with their descriptions.
|
||||
|
||||
349
docs/vmctl.md
349
docs/vmctl.md
@@ -869,10 +869,6 @@ Importing tips:
|
||||
--vm-native-src-addr=http://<src-vmselect>:8481/select/0/prometheus
|
||||
--vm-native-dst-addr=http://<dst-vminsert>:8480/insert/0/prometheus
|
||||
```
|
||||
1. When migrating data from VM cluster to Single-node VictoriaMetrics, vmctl will use the `/api/v1/export/native` API of the VM cluster,
|
||||
which attaches `vm_account_id` and `vm_project_id` labels to each time series. If you don't need to distinguish between tenants
|
||||
or simply want to remove these labels, try setting the `--vm-native-disable-binary-protocol` flag, which will use the `/api/v1/export` API,
|
||||
exporting and importing data in JSON format. Deduplication should be enabled at `-vm-native-src-addr` side if needed.
|
||||
1. Migrating data from VM cluster which had replication (`-replicationFactor` > 1) enabled won't produce the same amount
|
||||
of data copies for the destination database, and will result only in creating duplicates. To remove duplicates,
|
||||
destination database need to be configured with `-dedup.minScrapeInterval=1ms`. To restore the replication factor
|
||||
@@ -1129,348 +1125,3 @@ ARM build may run on Raspberry Pi or on [energy-efficient ARM servers](https://b
|
||||
1. [Install docker](https://docs.docker.com/install/).
|
||||
1. Run `make vmctl-linux-arm-prod` or `make vmctl-linux-arm64-prod` from the root folder of [the repository](https://github.com/VictoriaMetrics/VictoriaMetrics).
|
||||
It builds `vmctl-linux-arm-prod` or `vmctl-linux-arm64-prod` binary respectively and puts it into the `bin` folder.
|
||||
|
||||
### Command-line flags
|
||||
|
||||
Run `vmctl -help` in order to see all the available options.
|
||||
|
||||
Commands:
|
||||
```shellhelp
|
||||
influx
|
||||
Migrate time series from InfluxDB
|
||||
opentsdb
|
||||
Migrate time series from OpenTSDB.
|
||||
prometheus
|
||||
Migrate time series from Prometheus.
|
||||
remote-read
|
||||
Migrate time series via Prometheus remote-read protocol.
|
||||
verify-block
|
||||
Verifies exported block with VictoriaMetrics Native format.
|
||||
vm-native
|
||||
Migrate time series between VictoriaMetrics installations.
|
||||
```
|
||||
|
||||
Flags available for all commands:
|
||||
|
||||
```shellhelp
|
||||
-s
|
||||
Whether to run in silent mode. If set to true no confirmation prompts will appear. (default false)
|
||||
-verbose
|
||||
Whether to enable verbosity in logs output. (default false)
|
||||
-disable-progress-bar
|
||||
Whether to disable progress bar during the import. (default false)
|
||||
```
|
||||
|
||||
Flags available only for the `opentsdb` command:
|
||||
|
||||
```shellhelp
|
||||
./vmctl influx -help
|
||||
|
||||
--influx-addr value
|
||||
InfluxDB server addr (default: "http://localhost:8086")
|
||||
--influx-user value
|
||||
InfluxDB user [$INFLUX_USERNAME]
|
||||
--influx-password value
|
||||
InfluxDB user password [$INFLUX_PASSWORD]
|
||||
--influx-database value
|
||||
InfluxDB database
|
||||
--influx-retention-policy value
|
||||
InfluxDB retention policy (default: "autogen")
|
||||
--influx-chunk-size value
|
||||
The chunkSize defines max amount of series to be returned in one chunk (default: 10000)
|
||||
--influx-concurrency value
|
||||
Number of concurrently running fetch queries to InfluxDB (default: 1)
|
||||
--influx-filter-series value
|
||||
InfluxDB filter expression to select series. E.g. "from cpu where arch='x86' AND hostname='host_2753'".
|
||||
See for details https://docs.influxdata.com/influxdb/v1.7/query_language/schema_exploration#show-series
|
||||
--influx-filter-time-start value
|
||||
The time filter to select timeseries with timestamp equal or higher than provided value. E.g. '2020-01-01T20:07:00Z'
|
||||
--influx-filter-time-end value
|
||||
The time filter to select timeseries with timestamp equal or lower than provided value. E.g. '2020-01-01T20:07:00Z'
|
||||
--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)
|
||||
--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
|
||||
Optional path to client-side TLS certificate file to use when connecting to -influx-addr
|
||||
--influx-key-file value
|
||||
Optional path to client-side TLS key to use when connecting to -influx-addr
|
||||
--influx-CA-file value
|
||||
Optional path to TLS CA file to use for verifying connections to -influx-addr. By default, system CA is used
|
||||
--influx-server-name value
|
||||
Optional TLS server name to use for connections to -influx-addr. By default, the server name from -influx-addr is used
|
||||
--influx-insecure-skip-verify
|
||||
Whether to skip tls verification when connecting to -influx-addr (default: false)
|
||||
|
||||
# There are flags available for influx, opentsdb, prometheus and remote-read commands. See below.
|
||||
```
|
||||
|
||||
|
||||
Flags available only for the `opentsdb` command:
|
||||
|
||||
```shellhelp
|
||||
./vmctl opentsdb -help
|
||||
|
||||
--otsdb-addr value
|
||||
OpenTSDB server addr (default: "http://localhost:4242")
|
||||
--otsdb-concurrency value
|
||||
Number of concurrently running fetch queries to OpenTSDB per metric (default: 1)
|
||||
--otsdb-retentions value [ --otsdb-retentions value ]
|
||||
Retentions patterns to collect on. Each pattern should describe the aggregation performed for the query,
|
||||
the row size (in HBase) that will define how long each individual query is, and the time range to query for.
|
||||
e.g. sum-1m-avg:1h:3d. The first time range defined should be a multiple of the row size in HBase.
|
||||
e.g. if the row size is 2 hours, 4h is good, 5h less so. We want each query to land on unique rows.
|
||||
--otsdb-filters value [ --otsdb-filters value ]
|
||||
Filters to process for discovering metrics in OpenTSDB (default: "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z")
|
||||
--otsdb-offset-days value
|
||||
Days to offset our 'starting' point for collecting data from OpenTSDB (default: 0)
|
||||
--otsdb-hard-ts-start value
|
||||
A specific timestamp to start from, will override using an offset (default: 0)
|
||||
--otsdb-query-limit value
|
||||
Result limit on meta queries to OpenTSDB (affects both metric name and tag value queries, recommended to use a value exceeding your largest series) (default: 100000000)
|
||||
--otsdb-msecstime
|
||||
Whether OpenTSDB is writing values in milliseconds or seconds (default: false)
|
||||
--otsdb-normalize
|
||||
Whether to normalize all data received to lower case before forwarding to VictoriaMetrics (default: false)
|
||||
--otsdb-cert-file value
|
||||
Optional path to client-side TLS certificate file to use when connecting to -otsdb-addr
|
||||
--otsdb-key-file value
|
||||
Optional path to client-side TLS key to use when connecting to -otsdb-addr
|
||||
--otsdb-CA-file value
|
||||
Optional path to TLS CA file to use for verifying connections to -otsdb-addr. By default, system CA is used
|
||||
--otsdb-server-name value
|
||||
Optional TLS server name to use for connections to -otsdb-addr. By default, the server name from -otsdb-addr is used
|
||||
--otsdb-insecure-skip-verify
|
||||
Whether to skip tls verification when connecting to -otsdb-addr (default: false)
|
||||
|
||||
# There are flags available for influx, opentsdb, prometheus and remote-read commands. See below.
|
||||
```
|
||||
|
||||
Flags available only for the `prometheus` command:
|
||||
|
||||
```shellhelp
|
||||
./vmctl prometheus -help
|
||||
|
||||
--prom-snapshot value
|
||||
Path to Prometheus snapshot. Pls see for details https://www.robustperception.io/taking-snapshots-of-prometheus-data
|
||||
--prom-concurrency value
|
||||
Number of concurrently running snapshot readers (default: 1)
|
||||
--prom-filter-time-start value
|
||||
The time filter in RFC3339 format to select timeseries with timestamp equal or higher than provided value. E.g. '2020-01-01T20:07:00Z'
|
||||
--prom-filter-time-end value
|
||||
The time filter in RFC3339 format to select timeseries with timestamp equal or lower than provided value. E.g. '2020-01-01T20:07:00Z'
|
||||
--prom-filter-label value
|
||||
Prometheus label name to filter timeseries by. E.g. '__name__' will filter timeseries by name.
|
||||
--prom-filter-label-value value
|
||||
Prometheus regular expression to filter label from "prom-filter-label" flag. (default: ".*")
|
||||
|
||||
# There are flags available for influx, opentsdb, prometheus and remote-read commands. See below.
|
||||
```
|
||||
|
||||
Flags available only for the `remote-read` command:
|
||||
|
||||
```shellhelp
|
||||
./vmctl remote-read -help
|
||||
|
||||
--remote-read-concurrency value
|
||||
Number of concurrently running remote read readers (default: 1)
|
||||
--remote-read-filter-time-start value
|
||||
The time filter in RFC3339 format to select timeseries with timestamp equal or higher than provided value. E.g. '2020-01-01T20:07:00Z'
|
||||
--remote-read-filter-time-end value
|
||||
The time filter in RFC3339 format to select timeseries with timestamp equal or lower than provided value. E.g. '2020-01-01T20:07:00Z'
|
||||
--remote-read-filter-label value
|
||||
Prometheus label name to filter timeseries by. E.g. '__name__' will filter timeseries by name. (default: "__name__")
|
||||
--remote-read-filter-label-value value
|
||||
Prometheus regular expression to filter label from "remote-read-filter-label-value" flag. (default: ".*")
|
||||
--remote-read
|
||||
Use Prometheus remote read protocol (default: false)
|
||||
--remote-read-use-stream
|
||||
Defines whether to use SAMPLES or STREAMED_XOR_CHUNKS mode. By default, is uses SAMPLES mode. See https://prometheus.io/docs/prometheus/latest/querying/remote_read_api/#streamed-chunks (default: false)
|
||||
--remote-read-step-interval value
|
||||
The time interval to split the migration into steps. For example, to migrate 1y of data with '--remote-read-step-interval=month' vmctl will execute it in 12 separate requests from the beginning of the time range to its end. To reverse the order use '--remote-read-filter-time-reverse'. Requires setting '--remote-read-filter-time-start'. Valid values are 'month','week','day','hour','minute'.
|
||||
--remote-read-filter-time-reverse
|
||||
Whether to reverse the order of time intervals split by '--remote-read-step-interval' cmd-line flag. When set, the migration will start from the newest to the oldest data. (default: false)
|
||||
--remote-read-src-addr value
|
||||
Remote read address to perform read from.
|
||||
--remote-read-user value
|
||||
Remote read username for basic auth [$REMOTE_READ_USERNAME]
|
||||
--remote-read-password value
|
||||
Remote read password for basic auth [$REMOTE_READ_PASSWORD]
|
||||
--remote-read-http-timeout value
|
||||
Timeout defines timeout for HTTP requests made by remote read client (default: 0s)
|
||||
--remote-read-headers value
|
||||
Optional HTTP headers to send with each request to the corresponding remote source storage
|
||||
For example, --remote-read-headers='My-Auth:foobar' would send 'My-Auth: foobar' HTTP header with every request to the corresponding remote source storage.
|
||||
Multiple headers must be delimited by '^^': --remote-read-headers='header1:value1^^header2:value2'
|
||||
--remote-read-cert-file value
|
||||
Optional path to client-side TLS certificate file to use when connecting to -remote-read-src-addr
|
||||
--remote-read-key-file value
|
||||
Optional path to client-side TLS key to use when connecting to -remote-read-src-addr
|
||||
--remote-read-CA-file value
|
||||
Optional path to TLS CA file to use for verifying connections to -remote-read-src-addr. By default, system CA is used
|
||||
--remote-read-server-name value
|
||||
Optional TLS server name to use for connections to remoteReadSrcAddr. By default, the server name from -remote-read-src-addr is used
|
||||
--remote-read-insecure-skip-verify
|
||||
Whether to skip TLS certificate verification when connecting to the remote read address (default: false)
|
||||
--remote-read-disable-path-append
|
||||
Whether to disable automatic appending of the /api/v1/read suffix to --remote-read-src-addr (default: false)
|
||||
|
||||
# There are flags available for influx, opentsdb, prometheus and remote-read commands. See below.
|
||||
```
|
||||
|
||||
Flags available only for the `verify-block` command:
|
||||
|
||||
```shellhelp
|
||||
./vmctl verify-block -help
|
||||
|
||||
--gunzip
|
||||
Use GNU zip decompression for exported block (default: false)
|
||||
```
|
||||
|
||||
Flags available only for the `vm-native` command:
|
||||
|
||||
```shellhelp
|
||||
./vmctl vm-native -help
|
||||
|
||||
--vm-native-filter-match value
|
||||
Time series selector to match series for export. For example, select {instance!="localhost"} will match all series with "instance" label different to "localhost".
|
||||
See more details here https://github.com/VictoriaMetrics/VictoriaMetrics#how-to-export-data-in-native-format (default: "{__name__!=\"\"}")
|
||||
--vm-native-filter-time-start value
|
||||
The time filter may contain different timestamp formats. See more details here https://docs.victoriametrics.com/single-server-victoriametrics/#timestamp-formats
|
||||
--vm-native-filter-time-end value
|
||||
The time filter may contain different timestamp formats. See more details here https://docs.victoriametrics.com/single-server-victoriametrics/#timestamp-formats
|
||||
--vm-native-step-interval value
|
||||
The time interval to split the migration into steps. For example, to migrate 1y of data with '--vm-native-step-interval=month' vmctl will execute it in 12 separate requests from the beginning of the time range to its end. To reverse the order use '--vm-native-filter-time-reverse'. Requires setting '--vm-native-filter-time-start'. Valid values are 'month','week','day','hour','minute'. (default: "month")
|
||||
--vm-native-filter-time-reverse
|
||||
Whether to reverse the order of time intervals split by '--vm-native-step-interval' cmd-line flag. When set, the migration will start from the newest to the oldest data. (default: false)
|
||||
--vm-native-disable-http-keep-alive
|
||||
Disable HTTP persistent connections for requests made to VictoriaMetrics components during export (default: false)
|
||||
--vm-native-src-addr value
|
||||
VictoriaMetrics address to perform export from.
|
||||
Should be the same as --httpListenAddr value for single-node version or vmselect component. If exporting from cluster version see https://docs.victoriametrics.com/cluster-victoriametrics/#url-format
|
||||
--vm-native-src-user value
|
||||
VictoriaMetrics username for basic auth [$VM_NATIVE_SRC_USERNAME]
|
||||
--vm-native-src-password value
|
||||
VictoriaMetrics password for basic auth [$VM_NATIVE_SRC_PASSWORD]
|
||||
--vm-native-src-headers value
|
||||
Optional HTTP headers to send with each request to the corresponding source address.
|
||||
For example, --vm-native-src-headers='My-Auth:foobar' would send 'My-Auth: foobar' HTTP header with every request to the corresponding source address.
|
||||
Multiple headers must be delimited by '^^': --vm-native-src-headers='header1:value1^^header2:value2'
|
||||
--vm-native-src-bearer-token --vm-native-src-addr
|
||||
Optional bearer auth token to use for the corresponding --vm-native-src-addr
|
||||
--vm-native-src-cert-file --vm-native-src-addr
|
||||
Optional path to client-side TLS certificate file to use when connecting to --vm-native-src-addr
|
||||
--vm-native-src-key-file --vm-native-src-addr
|
||||
Optional path to client-side TLS key to use when connecting to --vm-native-src-addr
|
||||
--vm-native-src-ca-file --vm-native-src-addr
|
||||
Optional path to TLS CA file to use for verifying connections to --vm-native-src-addr. By default, system CA is used
|
||||
--vm-native-src-server-name --vm-native-src-addr
|
||||
Optional TLS server name to use for connections to --vm-native-src-addr. By default, the server name from `--vm-native-src-addr` is used
|
||||
--vm-native-src-insecure-skip-verify --vm-native-src-addr
|
||||
Whether to skip TLS certificate verification when connecting to --vm-native-src-addr (default: false)
|
||||
--vm-native-dst-addr value
|
||||
VictoriaMetrics address to perform import to.
|
||||
Should be the same as --httpListenAddr value for single-node version or vminsert component.
|
||||
If importing into cluster version see https://docs.victoriametrics.com/cluster-victoriametrics/#url-format
|
||||
--vm-native-dst-user value
|
||||
VictoriaMetrics username for basic auth [$VM_NATIVE_DST_USERNAME]
|
||||
--vm-native-dst-password value
|
||||
VictoriaMetrics password for basic auth [$VM_NATIVE_DST_PASSWORD]
|
||||
--vm-native-dst-headers value
|
||||
Optional HTTP headers to send with each request to the corresponding destination address.
|
||||
For example, --vm-native-dst-headers='My-Auth:foobar' would send 'My-Auth: foobar' HTTP header with every request to the corresponding destination address.
|
||||
Multiple headers must be delimited by '^^': --vm-native-dst-headers='header1:value1^^header2:value2'
|
||||
--vm-native-dst-bearer-token --vm-native-dst-addr
|
||||
Optional bearer auth token to use for the corresponding --vm-native-dst-addr
|
||||
--vm-native-dst-cert-file --vm-native-dst-addr
|
||||
Optional path to client-side TLS certificate file to use when connecting to --vm-native-dst-addr
|
||||
--vm-native-dst-key-file --vm-native-dst-addr
|
||||
Optional path to client-side TLS key to use when connecting to --vm-native-dst-addr
|
||||
--vm-native-dst-ca-file --vm-native-dst-addr
|
||||
Optional path to TLS CA file to use for verifying connections to --vm-native-dst-addr. By default, system CA is used
|
||||
--vm-native-dst-server-name --vm-native-dst-addr
|
||||
Optional TLS server name to use for connections to --vm-native-dst-addr. By default, the server name from `--vm-native-dst-addr` is used
|
||||
--vm-native-dst-insecure-skip-verify --vm-native-dst-addr
|
||||
Whether to skip TLS certificate verification when connecting to --vm-native-dst-addr (default: false)
|
||||
--vm-extra-label value [ --vm-extra-label value ]
|
||||
Extra labels, that will be added to imported timeseries. In case of collision, label value defined by flagwill have priority.
|
||||
Flag can be set multiple times, to add few additional labels.
|
||||
--vm-rate-limit value
|
||||
Optional data transfer rate limit in bytes per second.
|
||||
By default, the rate limit is disabled. It can be useful for limiting load on source or destination databases. (default: 0)
|
||||
--vm-intercluster Enables cluster-to-cluster migration mode with automatic tenants data migration.
|
||||
In this mode --vm-native-src-addr flag format is: 'http://vmselect:8481/'. --vm-native-dst-addr flag format is: http://vminsert:8480/.
|
||||
TenantID will be appended automatically after discovering tenants from src. (default: false)
|
||||
--vm-concurrency value
|
||||
Number of workers concurrently performing import requests to VM (default: 2)
|
||||
--vm-native-disable-per-metric-migration
|
||||
Defines whether to disable per-metric migration and migrate all data via one connection.
|
||||
In this mode, vmctl makes less export/import requests, but can't provide a progress bar or retry failed requests. (default: false)
|
||||
--vm-native-disable-binary-protocol
|
||||
Whether to use https://docs.victoriametrics.com/#how-to-export-data-in-json-line-format instead of
|
||||
https://docs.victoriametrics.com/#how-to-export-data-in-native-format API.
|
||||
Binary export/import API protocol implies less network and resource usage, as it transfers compressed binary data blocks.
|
||||
Non-binary export/import API is less efficient, but supports deduplication if it is configured on vm-native-src-addr side. (default: false)
|
||||
--vm-native-backoff-retries value
|
||||
How many export/import retries to perform before giving up. (default: 10)
|
||||
--vm-native-backoff-factor value
|
||||
Factor to multiply the base duration after each failed export/import retry. Must be greater than 1.0 (default: 1.8)
|
||||
--vm-native-backoff-min-duration value
|
||||
Minimum duration to wait before the first export/import retry. Each subsequent export/import retry will be multiplied by the '--vm-native-backoff-factor'. (default: 2s)
|
||||
```
|
||||
|
||||
Flags available for the `influx`, `opentsdb`, `prometheus` and `remote-read` commands:
|
||||
|
||||
```shellhelp
|
||||
--vm-addr vmctl
|
||||
VictoriaMetrics address to perform import requests.
|
||||
Should be the same as --httpListenAddr value for single-node version or vminsert component.
|
||||
When importing into the clustered version do not forget to set additionally --vm-account-id flag.
|
||||
Please note, that vmctl performs initial readiness check for the given address by checking `/health` endpoint. (default: "http://localhost:8428")
|
||||
--vm-user value
|
||||
VictoriaMetrics username for basic auth [$VM_USERNAME]
|
||||
--vm-password value
|
||||
VictoriaMetrics password for basic auth [$VM_PASSWORD]
|
||||
--vm-account-id value
|
||||
AccountID is an arbitrary 32-bit integer identifying namespace for data ingestion (aka tenant).
|
||||
AccountID is required when importing into the clustered version of VictoriaMetrics.
|
||||
It is possible to set it as accountID:projectID, where projectID is also arbitrary 32-bit integer.
|
||||
If projectID isn't set, then it equals to 0
|
||||
--vm-concurrency value
|
||||
Number of workers concurrently performing import requests to VM (default: 2)
|
||||
--vm-compress
|
||||
Whether to apply gzip compression to import requests (default: true)
|
||||
--vm-batch-size value
|
||||
How many samples importer collects before sending the import request to VM (default: 200000)
|
||||
--vm-significant-figures value
|
||||
The number of significant figures to leave in metric values before importing. See https://en.wikipedia.org/wiki/Significant_figures.
|
||||
Zero value saves all the significant figures. This option may be used for increasing on-disk compression level for the stored metrics.
|
||||
See also --vm-round-digits option (default: 0)
|
||||
--vm-round-digits value
|
||||
Round metric values to the given number of decimal digits after the point. This option may be used for increasing
|
||||
on-disk compression level for the stored metrics (default: 100)
|
||||
--vm-extra-label value [ --vm-extra-label value ]
|
||||
Extra labels, that will be added to imported timeseries. In case of collision, label value defined by flagwill have priority.
|
||||
Flag can be set multiple times, to add few additional labels.
|
||||
--vm-rate-limit value
|
||||
Optional data transfer rate limit in bytes per second.
|
||||
By default, the rate limit is disabled. It can be useful for limiting load on configured via '--vmAddr' destination. (default: 0)
|
||||
--vm-cert-file value
|
||||
Optional path to client-side TLS certificate file to use when connecting to '--vmAddr'
|
||||
--vm-key-file value
|
||||
Optional path to client-side TLS key to use when connecting to '--vmAddr'
|
||||
--vm-CA-file value
|
||||
Optional path to TLS CA file to use for verifying connections to '--vmAddr'. By default, system CA is used
|
||||
--vm-server-name value
|
||||
Optional TLS server name to use for connections to '--vmAddr'. By default, the server name from '--vmAddr' is used
|
||||
--vm-insecure-skip-verify
|
||||
Whether to skip tls verification when connecting to '--vmAddr' (default: false)
|
||||
--vm-backoff-retries value
|
||||
How many import retries to perform before giving up. (default: 10)
|
||||
--vm-backoff-factor value
|
||||
Factor to multiply the base duration after each failed import retry. Must be greater than 1.0 (default: 1.8)
|
||||
--vm-backoff-min-duration value
|
||||
Minimum duration to wait before the first import retry. Each subsequent import retry will be multiplied by the '--vm-backoff-factor'. (default: 2s)
|
||||
```
|
||||
|
||||
@@ -11,16 +11,77 @@ import (
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
|
||||
)
|
||||
|
||||
type queue struct {
|
||||
in chan uint64
|
||||
out chan uint64
|
||||
storage []uint64
|
||||
}
|
||||
|
||||
func newQueue() *queue {
|
||||
q := &queue{
|
||||
out: make(chan uint64),
|
||||
in: make(chan uint64),
|
||||
}
|
||||
go func() {
|
||||
defer close(q.out)
|
||||
for {
|
||||
if len(q.storage) == 0 {
|
||||
item, ok := <-q.in
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
q.storage = append(q.storage, item)
|
||||
continue
|
||||
}
|
||||
|
||||
select {
|
||||
case item, ok := <-q.in:
|
||||
if ok {
|
||||
q.storage = append(q.storage, item)
|
||||
} else {
|
||||
// unwind storage
|
||||
for _, item := range q.storage {
|
||||
q.out <- item
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
case q.out <- q.storage[0]:
|
||||
if len(q.storage) == 1 {
|
||||
q.storage = nil
|
||||
} else {
|
||||
q.storage = q.storage[1:]
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
return q
|
||||
}
|
||||
|
||||
type compressedLabel struct {
|
||||
mu sync.Mutex
|
||||
code uint64
|
||||
deleted bool
|
||||
deleteDeadline int64
|
||||
}
|
||||
|
||||
// LabelsCompressor compresses []prompbmarshal.Label into short binary strings
|
||||
type LabelsCompressor struct {
|
||||
labelToIdx sync.Map
|
||||
idxToLabel labelsMap
|
||||
freeIdxs *queue
|
||||
|
||||
nextIdx atomic.Uint64
|
||||
|
||||
totalSizeBytes atomic.Uint64
|
||||
}
|
||||
|
||||
func NewLabelsCompressor() *LabelsCompressor {
|
||||
return &LabelsCompressor{
|
||||
freeIdxs: newQueue(),
|
||||
}
|
||||
}
|
||||
|
||||
// SizeBytes returns the size of lc data in bytes
|
||||
func (lc *LabelsCompressor) SizeBytes() uint64 {
|
||||
return uint64(unsafe.Sizeof(*lc)) + lc.totalSizeBytes.Load()
|
||||
@@ -28,13 +89,60 @@ func (lc *LabelsCompressor) SizeBytes() uint64 {
|
||||
|
||||
// ItemsCount returns the number of items in lc
|
||||
func (lc *LabelsCompressor) ItemsCount() uint64 {
|
||||
return lc.nextIdx.Load()
|
||||
return lc.nextIdx.Load() - uint64(len(lc.freeIdxs.storage))
|
||||
}
|
||||
|
||||
// Delete adds stale labels idx to lc.freeIdxs list
|
||||
func (lc *LabelsCompressor) Delete(src []byte, ts int64) {
|
||||
labelsLen, nSize := encoding.UnmarshalVarUint64(src)
|
||||
if nSize <= 0 {
|
||||
logger.Panicf("BUG: cannot unmarshal labels length from uvarint")
|
||||
}
|
||||
tail := src[nSize:]
|
||||
if labelsLen == 0 {
|
||||
// fast path - nothing to decode
|
||||
if len(tail) > 0 {
|
||||
logger.Panicf("BUG: unexpected non-empty tail left; len(tail)=%d; tail=%X", len(tail), tail)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
a := encoding.GetUint64s(int(labelsLen))
|
||||
var err error
|
||||
tail, err = encoding.UnmarshalVarUint64s(a.A, tail)
|
||||
if err != nil {
|
||||
logger.Panicf("BUG: cannot unmarshal label indexes: %s", err)
|
||||
}
|
||||
if len(tail) > 0 {
|
||||
logger.Panicf("BUG: unexpected non-empty tail left: len(tail)=%d; tail=%X", len(tail), tail)
|
||||
}
|
||||
for _, idx := range a.A {
|
||||
label, ok := lc.idxToLabel.Load(idx)
|
||||
if !ok {
|
||||
logger.Panicf("BUG: missing label for idx=%d", idx)
|
||||
}
|
||||
v, ok := lc.labelToIdx.Load(label)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
cl := v.(compressedLabel)
|
||||
cl.mu.Lock()
|
||||
if cl.deleteDeadline < ts {
|
||||
cl.deleted = true
|
||||
cl.mu.Unlock()
|
||||
lc.freeIdxs.in <- idx
|
||||
} else {
|
||||
cl.mu.Unlock()
|
||||
}
|
||||
|
||||
}
|
||||
encoding.PutUint64s(a)
|
||||
}
|
||||
|
||||
// Compress compresses labels, appends the compressed labels to dst and returns the result.
|
||||
//
|
||||
// It is safe calling Compress from concurrent goroutines.
|
||||
func (lc *LabelsCompressor) Compress(dst []byte, labels []prompbmarshal.Label) []byte {
|
||||
func (lc *LabelsCompressor) Compress(dst []byte, labels []prompbmarshal.Label, deleteDeadline int64) []byte {
|
||||
if len(labels) == 0 {
|
||||
// Fast path
|
||||
return append(dst, 0)
|
||||
@@ -42,22 +150,27 @@ func (lc *LabelsCompressor) Compress(dst []byte, labels []prompbmarshal.Label) [
|
||||
|
||||
a := encoding.GetUint64s(len(labels) + 1)
|
||||
a.A[0] = uint64(len(labels))
|
||||
lc.compress(a.A[1:], labels)
|
||||
lc.compress(a.A[1:], labels, deleteDeadline)
|
||||
dst = encoding.MarshalVarUint64s(dst, a.A)
|
||||
encoding.PutUint64s(a)
|
||||
return dst
|
||||
}
|
||||
|
||||
func (lc *LabelsCompressor) compress(dst []uint64, labels []prompbmarshal.Label) {
|
||||
func (lc *LabelsCompressor) compress(dst []uint64, labels []prompbmarshal.Label, deleteDeadline int64) {
|
||||
if len(labels) == 0 {
|
||||
return
|
||||
}
|
||||
_ = dst[len(labels)-1]
|
||||
for i, label := range labels {
|
||||
again:
|
||||
v, ok := lc.labelToIdx.Load(label)
|
||||
if !ok {
|
||||
idx := lc.nextIdx.Add(1)
|
||||
v = idx
|
||||
var idx uint64
|
||||
select {
|
||||
case idx = <-lc.freeIdxs.out:
|
||||
default:
|
||||
idx = lc.nextIdx.Add(1)
|
||||
}
|
||||
labelCopy := cloneLabel(label)
|
||||
|
||||
// Must store idxToLabel entry before labelToIdx,
|
||||
@@ -66,6 +179,10 @@ func (lc *LabelsCompressor) compress(dst []uint64, labels []prompbmarshal.Label)
|
||||
// We might store duplicated entries for single label with different indexes,
|
||||
// and it's fine, see https://github.com/VictoriaMetrics/VictoriaMetrics/pull/7118.
|
||||
lc.idxToLabel.Store(idx, labelCopy)
|
||||
v = &compressedLabel{
|
||||
deleteDeadline: deleteDeadline,
|
||||
code: idx,
|
||||
}
|
||||
vNew, loaded := lc.labelToIdx.LoadOrStore(labelCopy, v)
|
||||
if loaded {
|
||||
// This label has been stored by a concurrent goroutine with different index,
|
||||
@@ -78,7 +195,17 @@ func (lc *LabelsCompressor) compress(dst []uint64, labels []prompbmarshal.Label)
|
||||
entrySizeBytes := labelSizeBytes + uint64(2*(unsafe.Sizeof(label)+unsafe.Sizeof(&label))+unsafe.Sizeof(v))
|
||||
lc.totalSizeBytes.Add(entrySizeBytes)
|
||||
}
|
||||
dst[i] = v.(uint64)
|
||||
cl := v.(*compressedLabel)
|
||||
dst[i] = cl.code
|
||||
cl.mu.Lock()
|
||||
deleted := cl.deleted
|
||||
if !deleted {
|
||||
cl.deleteDeadline = deleteDeadline
|
||||
}
|
||||
cl.mu.Unlock()
|
||||
if deleted {
|
||||
goto again
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,14 +9,14 @@ import (
|
||||
)
|
||||
|
||||
func TestLabelsCompressorSerial(t *testing.T) {
|
||||
var lc LabelsCompressor
|
||||
lc := NewLabelsCompressor()
|
||||
|
||||
f := func(labels []prompbmarshal.Label) {
|
||||
t.Helper()
|
||||
|
||||
sExpected := labelsToString(labels)
|
||||
|
||||
data := lc.Compress(nil, labels)
|
||||
data := lc.Compress(nil, labels, 0)
|
||||
labelsResult := lc.Decompress(nil, data)
|
||||
|
||||
sResult := labelsToString(labelsResult)
|
||||
@@ -67,7 +67,7 @@ func TestLabelsCompressorSerial(t *testing.T) {
|
||||
|
||||
func TestLabelsCompressorConcurrent(t *testing.T) {
|
||||
const concurrency = 5
|
||||
var lc LabelsCompressor
|
||||
lc := NewLabelsCompressor()
|
||||
var expectCompressedKeys sync.Map
|
||||
|
||||
var wg sync.WaitGroup
|
||||
@@ -78,7 +78,7 @@ func TestLabelsCompressorConcurrent(t *testing.T) {
|
||||
series := newTestSeries(100, 20)
|
||||
for n, labels := range series {
|
||||
sExpected := labelsToString(labels)
|
||||
data := lc.Compress(nil, labels)
|
||||
data := lc.Compress(nil, labels, 0)
|
||||
if expectData, ok := expectCompressedKeys.LoadOrStore(n, data); ok {
|
||||
if string(data) != string(expectData.([]byte)) {
|
||||
panic(fmt.Errorf("unexpected compress result at series/%d in iteration %d ", n, i))
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
func BenchmarkLabelsCompressorCompress(b *testing.B) {
|
||||
var lc LabelsCompressor
|
||||
lc := NewLabelsCompressor()
|
||||
series := newTestSeries(100, 10)
|
||||
|
||||
b.ReportAllocs()
|
||||
@@ -19,7 +19,7 @@ func BenchmarkLabelsCompressorCompress(b *testing.B) {
|
||||
for pb.Next() {
|
||||
dst = dst[:0]
|
||||
for _, labels := range series {
|
||||
dst = lc.Compress(dst, labels)
|
||||
dst = lc.Compress(dst, labels, 0)
|
||||
}
|
||||
Sink.Add(uint64(len(dst)))
|
||||
}
|
||||
@@ -27,13 +27,13 @@ func BenchmarkLabelsCompressorCompress(b *testing.B) {
|
||||
}
|
||||
|
||||
func BenchmarkLabelsCompressorDecompress(b *testing.B) {
|
||||
var lc LabelsCompressor
|
||||
lc := NewLabelsCompressor()
|
||||
series := newTestSeries(100, 10)
|
||||
datas := make([][]byte, len(series))
|
||||
var dst []byte
|
||||
for i, labels := range series {
|
||||
dstLen := len(dst)
|
||||
dst = lc.Compress(dst, labels)
|
||||
dst = lc.Compress(dst, labels, 0)
|
||||
datas[i] = dst[dstLen:]
|
||||
}
|
||||
|
||||
|
||||
@@ -91,17 +91,13 @@ type indexDB struct {
|
||||
// The db must be automatically recovered after that.
|
||||
missingMetricNamesForMetricID atomic.Uint64
|
||||
|
||||
// minMissingTimestampByKey holds the minimum timestamps by index search key,
|
||||
// which is missing in the given indexDB.
|
||||
// Key must be formed with marshalCommonPrefix function.
|
||||
// minMissingTimestamp is the minimum timestamp, which is missing in the given indexDB.
|
||||
//
|
||||
// This field is used at containsTimeRange() function only for the previous indexDB,
|
||||
// since this indexDB is readonly.
|
||||
// This field cannot be used for the current indexDB, since it may receive data
|
||||
// with bigger timestamps at any time.
|
||||
minMissingTimestampByKey map[string]int64
|
||||
// protects minMissingTimestampByKey
|
||||
minMissingTimestampByKeyLock sync.RWMutex
|
||||
minMissingTimestamp atomic.Int64
|
||||
|
||||
// generation identifies the index generation ID
|
||||
// and is used for syncing items from different indexDBs
|
||||
@@ -166,7 +162,6 @@ func mustOpenIndexDB(path string, s *Storage, isReadOnly *atomic.Bool) *indexDB
|
||||
tb: tb,
|
||||
name: name,
|
||||
|
||||
minMissingTimestampByKey: make(map[string]int64),
|
||||
tagFiltersToMetricIDsCache: workingsetcache.New(tagFiltersCacheSize),
|
||||
s: s,
|
||||
loopsPerDateTagFilterCache: workingsetcache.New(mem / 128),
|
||||
@@ -1950,36 +1945,25 @@ func (is *indexSearch) containsTimeRange(tr TimeRange) bool {
|
||||
// This means that it may contain data for the given tr with probability close to 100%.
|
||||
return true
|
||||
}
|
||||
|
||||
// The db corresponds to the previous indexDB, which is readonly.
|
||||
// So it is safe caching the minimum timestamp, which isn't covered by the db.
|
||||
|
||||
// use common prefix as a key for minMissingTimestamp
|
||||
// it's needed to properly track timestamps for cluster version
|
||||
// which uses tenant labels for the index search
|
||||
kb := &is.kb
|
||||
kb.B = is.marshalCommonPrefix(kb.B[:0], nsPrefixDateToMetricID)
|
||||
key := kb.B
|
||||
|
||||
db.minMissingTimestampByKeyLock.RLock()
|
||||
minMissingTimestamp, ok := db.minMissingTimestampByKey[string(key)]
|
||||
db.minMissingTimestampByKeyLock.RUnlock()
|
||||
|
||||
if ok && tr.MinTimestamp >= minMissingTimestamp {
|
||||
minMissingTimestamp := db.minMissingTimestamp.Load()
|
||||
if minMissingTimestamp != 0 && tr.MinTimestamp >= minMissingTimestamp {
|
||||
return false
|
||||
}
|
||||
if is.containsTimeRangeSlowForPrefixBuf(kb, tr) {
|
||||
|
||||
if is.containsTimeRangeSlow(tr) {
|
||||
return true
|
||||
}
|
||||
|
||||
db.minMissingTimestampByKeyLock.Lock()
|
||||
db.minMissingTimestampByKey[string(key)] = tr.MinTimestamp
|
||||
db.minMissingTimestampByKeyLock.Unlock()
|
||||
|
||||
db.minMissingTimestamp.CompareAndSwap(minMissingTimestamp, tr.MinTimestamp)
|
||||
return false
|
||||
}
|
||||
|
||||
func (is *indexSearch) containsTimeRangeSlowForPrefixBuf(prefixBuf *bytesutil.ByteBuffer, tr TimeRange) bool {
|
||||
func (is *indexSearch) containsTimeRangeSlow(tr TimeRange) bool {
|
||||
ts := &is.ts
|
||||
kb := &is.kb
|
||||
|
||||
// Verify whether the tr.MinTimestamp is included into `ts` or is smaller than the minimum date stored in `ts`.
|
||||
// Do not check whether tr.MaxTimestamp is included into `ts` or is bigger than the max date stored in `ts` for performance reasons.
|
||||
@@ -1988,12 +1972,13 @@ func (is *indexSearch) containsTimeRangeSlowForPrefixBuf(prefixBuf *bytesutil.By
|
||||
// The main practical case allows skipping searching in prev indexdb (`ts`) when `tr`
|
||||
// is located above the max date stored there.
|
||||
minDate := uint64(tr.MinTimestamp) / msecPerDay
|
||||
prefix := prefixBuf.B
|
||||
prefixBuf.B = encoding.MarshalUint64(prefixBuf.B, minDate)
|
||||
ts.Seek(prefixBuf.B)
|
||||
kb.B = is.marshalCommonPrefix(kb.B[:0], nsPrefixDateToMetricID)
|
||||
prefix := kb.B
|
||||
kb.B = encoding.MarshalUint64(kb.B, minDate)
|
||||
ts.Seek(kb.B)
|
||||
if !ts.NextItem() {
|
||||
if err := ts.Error(); err != nil {
|
||||
logger.Panicf("FATAL: error when searching for minDate=%d, prefix %q: %w", minDate, prefixBuf.B, err)
|
||||
logger.Panicf("FATAL: error when searching for minDate=%d, prefix %q: %w", minDate, kb.B, err)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -2101,130 +2101,3 @@ func stopTestStorage(s *Storage) {
|
||||
s.tsidCache.Stop()
|
||||
fs.MustRemoveDirAtomic(s.cachePath)
|
||||
}
|
||||
|
||||
func TestSearchContainsTimeRange(t *testing.T) {
|
||||
path := t.Name()
|
||||
os.RemoveAll(path)
|
||||
s := MustOpenStorage(path, retentionMax, 0, 0)
|
||||
db := s.idb()
|
||||
|
||||
is := db.getIndexSearch(noDeadline)
|
||||
|
||||
// Create a bunch of per-day time series
|
||||
const (
|
||||
days = 6
|
||||
tenant2IngestionDay = 8
|
||||
metricsPerDay = 1000
|
||||
)
|
||||
rotationDay := time.Date(2019, time.October, 15, 5, 1, 0, 0, time.UTC)
|
||||
rotationMillis := uint64(rotationDay.UnixMilli())
|
||||
rotationDate := rotationMillis / msecPerDay
|
||||
var metricNameBuf []byte
|
||||
perDayMetricIDs := make(map[uint64]*uint64set.Set)
|
||||
labelNames := []string{
|
||||
"__name__", "constant", "day", "UniqueId", "some_unique_id",
|
||||
}
|
||||
|
||||
sort.Strings(labelNames)
|
||||
|
||||
newMN := func(name string, day, metric int) MetricName {
|
||||
var mn MetricName
|
||||
mn.MetricGroup = []byte(name)
|
||||
mn.AddTag(
|
||||
"constant",
|
||||
"const",
|
||||
)
|
||||
mn.AddTag(
|
||||
"day",
|
||||
fmt.Sprintf("%v", day),
|
||||
)
|
||||
mn.AddTag(
|
||||
"UniqueId",
|
||||
fmt.Sprintf("%v", metric),
|
||||
)
|
||||
mn.AddTag(
|
||||
"some_unique_id",
|
||||
fmt.Sprintf("%v", day),
|
||||
)
|
||||
mn.sortTags()
|
||||
return mn
|
||||
}
|
||||
|
||||
// ingest metrics for tenant 0:0
|
||||
for day := 0; day < days; day++ {
|
||||
date := rotationDate - uint64(day)
|
||||
|
||||
var metricIDs uint64set.Set
|
||||
for metric := range metricsPerDay {
|
||||
mn := newMN("testMetric", day, metric)
|
||||
metricNameBuf = mn.Marshal(metricNameBuf[:0])
|
||||
var genTSID generationTSID
|
||||
if !is.getTSIDByMetricName(&genTSID, metricNameBuf, date) {
|
||||
generateTSID(&genTSID.TSID, &mn)
|
||||
createAllIndexesForMetricName(is, &mn, &genTSID.TSID, date)
|
||||
}
|
||||
metricIDs.Add(genTSID.TSID.MetricID)
|
||||
}
|
||||
|
||||
perDayMetricIDs[date] = &metricIDs
|
||||
}
|
||||
db.putIndexSearch(is)
|
||||
|
||||
// Flush index to disk, so it becomes visible for search
|
||||
s.DebugFlush()
|
||||
|
||||
is2 := db.getIndexSearch(noDeadline)
|
||||
|
||||
// Check that all the metrics are found for all the days.
|
||||
for date := rotationDate - days + 1; date <= rotationDate; date++ {
|
||||
|
||||
metricIDs, err := is2.getMetricIDsForDate(date, metricsPerDay)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
if !perDayMetricIDs[date].Equal(metricIDs) {
|
||||
t.Fatalf("unexpected metricIDs found;\ngot\n%d\nwant\n%d", metricIDs.AppendTo(nil), perDayMetricIDs[date].AppendTo(nil))
|
||||
}
|
||||
}
|
||||
|
||||
db.putIndexSearch(is2)
|
||||
|
||||
// rotate indexdb
|
||||
s.mustRotateIndexDB(rotationDay)
|
||||
db = s.idb()
|
||||
|
||||
// perform search for 0:0 tenant
|
||||
// results of previous search requests shouldn't affect it
|
||||
|
||||
isExt := db.extDB.getIndexSearch(noDeadline)
|
||||
// search for range that covers prev indexDB for dates before ingestion
|
||||
tr := TimeRange{
|
||||
MinTimestamp: int64(rotationMillis - msecPerDay*(days)),
|
||||
MaxTimestamp: int64(rotationMillis),
|
||||
}
|
||||
if !isExt.containsTimeRange(tr) {
|
||||
t.Fatalf("expected to have given time range at prev IndexDB")
|
||||
}
|
||||
|
||||
// search for range not exist at prev indexDB
|
||||
tr = TimeRange{
|
||||
MinTimestamp: int64(rotationMillis + msecPerDay*(days+4)),
|
||||
MaxTimestamp: int64(rotationMillis + msecPerDay*(days+2)),
|
||||
}
|
||||
if isExt.containsTimeRange(tr) {
|
||||
t.Fatalf("not expected to have given time range at prev IndexDB")
|
||||
}
|
||||
key := isExt.marshalCommonPrefix(nil, nsPrefixDateToMetricID)
|
||||
|
||||
db.extDB.minMissingTimestampByKeyLock.Lock()
|
||||
minMissingTimetamp := db.extDB.minMissingTimestampByKey[string(key)]
|
||||
db.extDB.minMissingTimestampByKeyLock.Unlock()
|
||||
|
||||
if minMissingTimetamp != tr.MinTimestamp {
|
||||
t.Fatalf("unexpected minMissingTimestamp for 0:0 tenant got %d, want %d", minMissingTimetamp, tr.MinTimestamp)
|
||||
}
|
||||
|
||||
db.extDB.putIndexSearch(isExt)
|
||||
s.MustClose()
|
||||
fs.MustRemoveAll(path)
|
||||
}
|
||||
|
||||
@@ -12,37 +12,33 @@ type avgAggrState struct {
|
||||
}
|
||||
|
||||
type avgStateValue struct {
|
||||
mu sync.Mutex
|
||||
sum float64
|
||||
count int64
|
||||
deleted bool
|
||||
mu sync.Mutex
|
||||
sum float64
|
||||
count int64
|
||||
deleted bool
|
||||
deleteDeadline int64
|
||||
}
|
||||
|
||||
func newAvgAggrState() *avgAggrState {
|
||||
return &avgAggrState{}
|
||||
}
|
||||
|
||||
func (as *avgAggrState) pushSamples(samples []pushSample) {
|
||||
func (as *avgAggrState) pushSamples(samples []pushSample, deleteDeadline int64, includeInputKey bool) {
|
||||
for i := range samples {
|
||||
s := &samples[i]
|
||||
outputKey := getOutputKey(s.key)
|
||||
outputKey := getOutputKey(s.key, includeInputKey)
|
||||
|
||||
again:
|
||||
v, ok := as.m.Load(outputKey)
|
||||
if !ok {
|
||||
// The entry is missing in the map. Try creating it.
|
||||
v = &avgStateValue{
|
||||
sum: s.value,
|
||||
count: 1,
|
||||
}
|
||||
v = &avgStateValue{}
|
||||
outputKey = bytesutil.InternString(outputKey)
|
||||
vNew, loaded := as.m.LoadOrStore(outputKey, v)
|
||||
if !loaded {
|
||||
// The entry has been successfully stored
|
||||
continue
|
||||
if loaded {
|
||||
// Update the entry created by a concurrent goroutine.
|
||||
v = vNew
|
||||
}
|
||||
// Update the entry created by a concurrent goroutine.
|
||||
v = vNew
|
||||
}
|
||||
sv := v.(*avgStateValue)
|
||||
sv.mu.Lock()
|
||||
@@ -50,6 +46,7 @@ func (as *avgAggrState) pushSamples(samples []pushSample) {
|
||||
if !deleted {
|
||||
sv.sum += s.value
|
||||
sv.count++
|
||||
sv.deleteDeadline = deleteDeadline
|
||||
}
|
||||
sv.mu.Unlock()
|
||||
if deleted {
|
||||
@@ -68,9 +65,22 @@ func (as *avgAggrState) flushState(ctx *flushCtx) {
|
||||
|
||||
sv := v.(*avgStateValue)
|
||||
sv.mu.Lock()
|
||||
if ctx.flushTimestamp > sv.deleteDeadline {
|
||||
sv.deleted = true
|
||||
sv.mu.Unlock()
|
||||
key := k.(string)
|
||||
ctx.a.lc.Delete(bytesutil.ToUnsafeBytes(key), ctx.flushTimestamp)
|
||||
m.Delete(k)
|
||||
return true
|
||||
}
|
||||
if sv.count == 0 {
|
||||
sv.mu.Unlock()
|
||||
return true
|
||||
}
|
||||
|
||||
avg := sv.sum / float64(sv.count)
|
||||
// Mark the entry as deleted, so it won't be updated anymore by concurrent pushSample() calls.
|
||||
sv.deleted = true
|
||||
sv.sum = 0
|
||||
sv.count = 0
|
||||
sv.mu.Unlock()
|
||||
|
||||
key := k.(string)
|
||||
|
||||
@@ -12,41 +12,39 @@ type countSamplesAggrState struct {
|
||||
}
|
||||
|
||||
type countSamplesStateValue struct {
|
||||
mu sync.Mutex
|
||||
n uint64
|
||||
deleted bool
|
||||
mu sync.Mutex
|
||||
n uint64
|
||||
deleted bool
|
||||
deleteDeadline int64
|
||||
}
|
||||
|
||||
func newCountSamplesAggrState() *countSamplesAggrState {
|
||||
return &countSamplesAggrState{}
|
||||
}
|
||||
|
||||
func (as *countSamplesAggrState) pushSamples(samples []pushSample) {
|
||||
func (as *countSamplesAggrState) pushSamples(samples []pushSample, deleteDeadline int64, includeInputKey bool) {
|
||||
for i := range samples {
|
||||
s := &samples[i]
|
||||
outputKey := getOutputKey(s.key)
|
||||
outputKey := getOutputKey(s.key, includeInputKey)
|
||||
|
||||
again:
|
||||
v, ok := as.m.Load(outputKey)
|
||||
if !ok {
|
||||
// The entry is missing in the map. Try creating it.
|
||||
v = &countSamplesStateValue{
|
||||
n: 1,
|
||||
}
|
||||
v = &countSamplesStateValue{}
|
||||
outputKey = bytesutil.InternString(outputKey)
|
||||
vNew, loaded := as.m.LoadOrStore(outputKey, v)
|
||||
if !loaded {
|
||||
// The new entry has been successfully created.
|
||||
continue
|
||||
if loaded {
|
||||
// Use the entry created by a concurrent goroutine.
|
||||
v = vNew
|
||||
}
|
||||
// Use the entry created by a concurrent goroutine.
|
||||
v = vNew
|
||||
}
|
||||
sv := v.(*countSamplesStateValue)
|
||||
sv.mu.Lock()
|
||||
deleted := sv.deleted
|
||||
if !deleted {
|
||||
sv.n++
|
||||
sv.deleteDeadline = deleteDeadline
|
||||
}
|
||||
sv.mu.Unlock()
|
||||
if deleted {
|
||||
@@ -65,9 +63,21 @@ func (as *countSamplesAggrState) flushState(ctx *flushCtx) {
|
||||
|
||||
sv := v.(*countSamplesStateValue)
|
||||
sv.mu.Lock()
|
||||
if ctx.flushTimestamp > sv.deleteDeadline {
|
||||
sv.deleted = true
|
||||
sv.mu.Unlock()
|
||||
key := k.(string)
|
||||
ctx.a.lc.Delete(bytesutil.ToUnsafeBytes(key), ctx.flushTimestamp)
|
||||
m.Delete(k)
|
||||
return true
|
||||
}
|
||||
n := sv.n
|
||||
// Mark the entry as deleted, so it won't be updated anymore by concurrent pushSample() calls.
|
||||
sv.deleted = true
|
||||
if n == 0 {
|
||||
sv.mu.Unlock()
|
||||
return true
|
||||
}
|
||||
|
||||
sv.n = 0
|
||||
sv.mu.Unlock()
|
||||
|
||||
key := k.(string)
|
||||
|
||||
@@ -13,16 +13,17 @@ type countSeriesAggrState struct {
|
||||
}
|
||||
|
||||
type countSeriesStateValue struct {
|
||||
mu sync.Mutex
|
||||
m map[uint64]struct{}
|
||||
deleted bool
|
||||
mu sync.Mutex
|
||||
m map[uint64]struct{}
|
||||
deleted bool
|
||||
deleteDeadline int64
|
||||
}
|
||||
|
||||
func newCountSeriesAggrState() *countSeriesAggrState {
|
||||
return &countSeriesAggrState{}
|
||||
}
|
||||
|
||||
func (as *countSeriesAggrState) pushSamples(samples []pushSample) {
|
||||
func (as *countSeriesAggrState) pushSamples(samples []pushSample, deleteDeadline int64, _ bool) {
|
||||
for i := range samples {
|
||||
s := &samples[i]
|
||||
inputKey, outputKey := getInputOutputKey(s.key)
|
||||
@@ -36,18 +37,14 @@ func (as *countSeriesAggrState) pushSamples(samples []pushSample) {
|
||||
if !ok {
|
||||
// The entry is missing in the map. Try creating it.
|
||||
v = &countSeriesStateValue{
|
||||
m: map[uint64]struct{}{
|
||||
h: {},
|
||||
},
|
||||
m: make(map[uint64]struct{}),
|
||||
}
|
||||
outputKey = bytesutil.InternString(outputKey)
|
||||
vNew, loaded := as.m.LoadOrStore(outputKey, v)
|
||||
if !loaded {
|
||||
// The entry has been added to the map.
|
||||
continue
|
||||
if loaded {
|
||||
// Update the entry created by a concurrent goroutine.
|
||||
v = vNew
|
||||
}
|
||||
// Update the entry created by a concurrent goroutine.
|
||||
v = vNew
|
||||
}
|
||||
sv := v.(*countSeriesStateValue)
|
||||
sv.mu.Lock()
|
||||
@@ -56,6 +53,7 @@ func (as *countSeriesAggrState) pushSamples(samples []pushSample) {
|
||||
if _, ok := sv.m[h]; !ok {
|
||||
sv.m[h] = struct{}{}
|
||||
}
|
||||
sv.deleteDeadline = deleteDeadline
|
||||
}
|
||||
sv.mu.Unlock()
|
||||
if deleted {
|
||||
@@ -74,9 +72,21 @@ func (as *countSeriesAggrState) flushState(ctx *flushCtx) {
|
||||
|
||||
sv := v.(*countSeriesStateValue)
|
||||
sv.mu.Lock()
|
||||
if ctx.flushTimestamp > sv.deleteDeadline {
|
||||
sv.deleted = true
|
||||
sv.mu.Unlock()
|
||||
key := k.(string)
|
||||
ctx.a.lc.Delete(bytesutil.ToUnsafeBytes(key), ctx.flushTimestamp)
|
||||
m.Delete(k)
|
||||
return true
|
||||
}
|
||||
n := len(sv.m)
|
||||
// Mark the entry as deleted, so it won't be updated anymore by concurrent pushSample() calls.
|
||||
sv.deleted = true
|
||||
if n == 0 {
|
||||
sv.mu.Unlock()
|
||||
return true
|
||||
}
|
||||
|
||||
sv.m = make(map[uint64]struct{})
|
||||
sv.mu.Unlock()
|
||||
|
||||
key := k.(string)
|
||||
|
||||
@@ -8,12 +8,14 @@ import (
|
||||
"github.com/cespare/xxhash/v2"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutils"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/slicesutil"
|
||||
)
|
||||
|
||||
const dedupAggrShardsCount = 128
|
||||
|
||||
type dedupAggr struct {
|
||||
lc *promutils.LabelsCompressor
|
||||
shards []dedupAggrShard
|
||||
}
|
||||
|
||||
@@ -26,7 +28,7 @@ type dedupAggrShard struct {
|
||||
}
|
||||
|
||||
type dedupAggrShardNopad struct {
|
||||
mu sync.Mutex
|
||||
mu sync.RWMutex
|
||||
m map[string]*dedupAggrSample
|
||||
|
||||
samplesBuf []dedupAggrSample
|
||||
@@ -36,14 +38,16 @@ type dedupAggrShardNopad struct {
|
||||
}
|
||||
|
||||
type dedupAggrSample struct {
|
||||
value float64
|
||||
timestamp int64
|
||||
value float64
|
||||
timestamp int64
|
||||
deleteDeadline int64
|
||||
}
|
||||
|
||||
func newDedupAggr() *dedupAggr {
|
||||
func newDedupAggr(lc *promutils.LabelsCompressor) *dedupAggr {
|
||||
shards := make([]dedupAggrShard, dedupAggrShardsCount)
|
||||
return &dedupAggr{
|
||||
shards: shards,
|
||||
lc: lc,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +67,7 @@ func (da *dedupAggr) itemsCount() uint64 {
|
||||
return n
|
||||
}
|
||||
|
||||
func (da *dedupAggr) pushSamples(samples []pushSample) {
|
||||
func (da *dedupAggr) pushSamples(samples []pushSample, deleteDeadline int64) {
|
||||
pss := getPerShardSamples()
|
||||
shards := pss.shards
|
||||
for _, sample := range samples {
|
||||
@@ -75,17 +79,21 @@ func (da *dedupAggr) pushSamples(samples []pushSample) {
|
||||
if len(shardSamples) == 0 {
|
||||
continue
|
||||
}
|
||||
da.shards[i].pushSamples(shardSamples)
|
||||
da.shards[i].pushSamples(shardSamples, deleteDeadline)
|
||||
}
|
||||
putPerShardSamples(pss)
|
||||
}
|
||||
|
||||
func getDedupFlushCtx() *dedupFlushCtx {
|
||||
func getDedupFlushCtx(flushTimestamp, deleteDeadline int64, lc *promutils.LabelsCompressor) *dedupFlushCtx {
|
||||
v := dedupFlushCtxPool.Get()
|
||||
if v == nil {
|
||||
return &dedupFlushCtx{}
|
||||
v = &dedupFlushCtx{}
|
||||
}
|
||||
return v.(*dedupFlushCtx)
|
||||
ctx := v.(*dedupFlushCtx)
|
||||
ctx.lc = lc
|
||||
ctx.deleteDeadline = deleteDeadline
|
||||
ctx.flushTimestamp = flushTimestamp
|
||||
return ctx
|
||||
}
|
||||
|
||||
func putDedupFlushCtx(ctx *dedupFlushCtx) {
|
||||
@@ -96,15 +104,24 @@ func putDedupFlushCtx(ctx *dedupFlushCtx) {
|
||||
var dedupFlushCtxPool sync.Pool
|
||||
|
||||
type dedupFlushCtx struct {
|
||||
samples []pushSample
|
||||
keysToDelete []string
|
||||
samples []pushSample
|
||||
deleteDeadline int64
|
||||
flushTimestamp int64
|
||||
lc *promutils.LabelsCompressor
|
||||
}
|
||||
|
||||
func (ctx *dedupFlushCtx) reset() {
|
||||
ctx.deleteDeadline = 0
|
||||
ctx.flushTimestamp = 0
|
||||
ctx.lc = nil
|
||||
clear(ctx.keysToDelete)
|
||||
ctx.keysToDelete = ctx.keysToDelete[:0]
|
||||
clear(ctx.samples)
|
||||
ctx.samples = ctx.samples[:0]
|
||||
}
|
||||
|
||||
func (da *dedupAggr) flush(f func(samples []pushSample)) {
|
||||
func (da *dedupAggr) flush(f func(samples []pushSample, deleteDeadline int64), flushTimestamp, deleteDeadline int64) {
|
||||
var wg sync.WaitGroup
|
||||
for i := range da.shards {
|
||||
flushConcurrencyCh <- struct{}{}
|
||||
@@ -115,7 +132,7 @@ func (da *dedupAggr) flush(f func(samples []pushSample)) {
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
ctx := getDedupFlushCtx()
|
||||
ctx := getDedupFlushCtx(flushTimestamp, deleteDeadline, da.lc)
|
||||
shard.flush(ctx, f)
|
||||
putDedupFlushCtx(ctx)
|
||||
}(&da.shards[i])
|
||||
@@ -154,7 +171,7 @@ func putPerShardSamples(pss *perShardSamples) {
|
||||
|
||||
var perShardSamplesPool sync.Pool
|
||||
|
||||
func (das *dedupAggrShard) pushSamples(samples []pushSample) {
|
||||
func (das *dedupAggrShard) pushSamples(samples []pushSample, deleteDeadline int64) {
|
||||
das.mu.Lock()
|
||||
defer das.mu.Unlock()
|
||||
|
||||
@@ -171,6 +188,7 @@ func (das *dedupAggrShard) pushSamples(samples []pushSample) {
|
||||
s = &samplesBuf[len(samplesBuf)-1]
|
||||
s.value = sample.value
|
||||
s.timestamp = sample.timestamp
|
||||
s.deleteDeadline = deleteDeadline
|
||||
|
||||
key := bytesutil.InternString(sample.key)
|
||||
m[key] = s
|
||||
@@ -183,30 +201,28 @@ func (das *dedupAggrShard) pushSamples(samples []pushSample) {
|
||||
if sample.timestamp > s.timestamp || (sample.timestamp == s.timestamp && sample.value > s.value) {
|
||||
s.value = sample.value
|
||||
s.timestamp = sample.timestamp
|
||||
s.deleteDeadline = deleteDeadline
|
||||
}
|
||||
}
|
||||
das.samplesBuf = samplesBuf
|
||||
}
|
||||
|
||||
func (das *dedupAggrShard) flush(ctx *dedupFlushCtx, f func(samples []pushSample)) {
|
||||
das.mu.Lock()
|
||||
|
||||
m := das.m
|
||||
if len(m) > 0 {
|
||||
das.m = make(map[string]*dedupAggrSample, len(m))
|
||||
das.sizeBytes.Store(0)
|
||||
das.itemsCount.Store(0)
|
||||
das.samplesBuf = make([]dedupAggrSample, 0, len(das.samplesBuf))
|
||||
}
|
||||
|
||||
das.mu.Unlock()
|
||||
|
||||
if len(m) == 0 {
|
||||
func (das *dedupAggrShard) flush(ctx *dedupFlushCtx, f func(samples []pushSample, deleteDeadline int64)) {
|
||||
if len(das.m) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
keysToDelete := ctx.keysToDelete
|
||||
dstSamples := ctx.samples
|
||||
for key, s := range m {
|
||||
das.mu.RLock()
|
||||
for key, s := range das.m {
|
||||
if ctx.flushTimestamp > s.deleteDeadline {
|
||||
das.itemsCount.Add(^uint64(0))
|
||||
//ctx.lc.Delete(key)
|
||||
das.sizeBytes.Add(^(uint64(len(key)) + uint64(unsafe.Sizeof(key)+unsafe.Sizeof(s)+unsafe.Sizeof(*s)) - 1))
|
||||
keysToDelete = append(keysToDelete, key)
|
||||
continue
|
||||
}
|
||||
dstSamples = append(dstSamples, pushSample{
|
||||
key: key,
|
||||
value: s.value,
|
||||
@@ -215,11 +231,17 @@ func (das *dedupAggrShard) flush(ctx *dedupFlushCtx, f func(samples []pushSample
|
||||
|
||||
// Limit the number of samples per each flush in order to limit memory usage.
|
||||
if len(dstSamples) >= 10_000 {
|
||||
f(dstSamples)
|
||||
f(dstSamples, ctx.deleteDeadline)
|
||||
clear(dstSamples)
|
||||
dstSamples = dstSamples[:0]
|
||||
}
|
||||
}
|
||||
f(dstSamples)
|
||||
das.mu.RUnlock()
|
||||
das.mu.Lock()
|
||||
for _, key := range keysToDelete {
|
||||
delete(das.m, key)
|
||||
}
|
||||
das.mu.Unlock()
|
||||
f(dstSamples, ctx.deleteDeadline)
|
||||
ctx.samples = dstSamples
|
||||
}
|
||||
|
||||
@@ -5,10 +5,13 @@ import (
|
||||
"reflect"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutils"
|
||||
)
|
||||
|
||||
func TestDedupAggrSerial(t *testing.T) {
|
||||
da := newDedupAggr()
|
||||
var lc promutils.LabelsCompressor
|
||||
da := newDedupAggr(&lc)
|
||||
|
||||
const seriesCount = 100_000
|
||||
expectedSamplesMap := make(map[string]pushSample)
|
||||
@@ -20,11 +23,11 @@ func TestDedupAggrSerial(t *testing.T) {
|
||||
sample.value = float64(i + j)
|
||||
expectedSamplesMap[sample.key] = *sample
|
||||
}
|
||||
da.pushSamples(samples)
|
||||
da.pushSamples(samples, 0)
|
||||
}
|
||||
|
||||
if n := da.sizeBytes(); n > 5_000_000 {
|
||||
t.Fatalf("too big dedupAggr state before flush: %d bytes; it shouldn't exceed 5_000_000 bytes", n)
|
||||
if n := da.sizeBytes(); n > 6_000_000 {
|
||||
t.Fatalf("too big dedupAggr state before flush: %d bytes; it shouldn't exceed 6_000_000 bytes", n)
|
||||
}
|
||||
if n := da.itemsCount(); n != seriesCount {
|
||||
t.Fatalf("unexpected itemsCount; got %d; want %d", n, seriesCount)
|
||||
@@ -32,19 +35,21 @@ func TestDedupAggrSerial(t *testing.T) {
|
||||
|
||||
flushedSamplesMap := make(map[string]pushSample)
|
||||
var mu sync.Mutex
|
||||
flushSamples := func(samples []pushSample) {
|
||||
flushSamples := func(samples []pushSample, _ int64) {
|
||||
mu.Lock()
|
||||
for _, sample := range samples {
|
||||
flushedSamplesMap[sample.key] = sample
|
||||
}
|
||||
mu.Unlock()
|
||||
}
|
||||
da.flush(flushSamples)
|
||||
da.flush(flushSamples, 0, 0)
|
||||
|
||||
if !reflect.DeepEqual(expectedSamplesMap, flushedSamplesMap) {
|
||||
t.Fatalf("unexpected samples;\ngot\n%v\nwant\n%v", flushedSamplesMap, expectedSamplesMap)
|
||||
}
|
||||
|
||||
da.flush(flushSamples, 1, 0)
|
||||
|
||||
if n := da.sizeBytes(); n > 17_000 {
|
||||
t.Fatalf("too big dedupAggr state after flush; %d bytes; it shouldn't exceed 17_000 bytes", n)
|
||||
}
|
||||
@@ -54,9 +59,10 @@ func TestDedupAggrSerial(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDedupAggrConcurrent(_ *testing.T) {
|
||||
var lc promutils.LabelsCompressor
|
||||
const concurrency = 5
|
||||
const seriesCount = 10_000
|
||||
da := newDedupAggr()
|
||||
da := newDedupAggr(&lc)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < concurrency; i++ {
|
||||
@@ -70,7 +76,7 @@ func TestDedupAggrConcurrent(_ *testing.T) {
|
||||
sample.key = fmt.Sprintf("key_%d", j)
|
||||
sample.value = float64(i + j)
|
||||
}
|
||||
da.pushSamples(samples)
|
||||
da.pushSamples(samples, 0)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutils"
|
||||
)
|
||||
|
||||
func BenchmarkDedupAggr(b *testing.B) {
|
||||
@@ -17,9 +18,11 @@ func BenchmarkDedupAggr(b *testing.B) {
|
||||
}
|
||||
|
||||
func benchmarkDedupAggr(b *testing.B, samplesPerPush int) {
|
||||
var lc promutils.LabelsCompressor
|
||||
const loops = 2
|
||||
benchSamples := newBenchSamples(samplesPerPush)
|
||||
da := newDedupAggr()
|
||||
|
||||
da := newDedupAggr(&lc)
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
@@ -27,13 +30,14 @@ func benchmarkDedupAggr(b *testing.B, samplesPerPush int) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
for i := 0; i < loops; i++ {
|
||||
da.pushSamples(benchSamples)
|
||||
da.pushSamples(benchSamples, 0)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func newBenchSamples(count int) []pushSample {
|
||||
var lc promutils.LabelsCompressor
|
||||
labels := []prompbmarshal.Label{
|
||||
{
|
||||
Name: "app",
|
||||
@@ -65,7 +69,7 @@ func newBenchSamples(count int) []pushSample {
|
||||
Name: "app",
|
||||
Value: fmt.Sprintf("instance-%d", i),
|
||||
})
|
||||
keyBuf = compressLabels(keyBuf[:0], labels[:labelsLen], labels[labelsLen:])
|
||||
keyBuf = compressLabels(keyBuf[:0], &lc, labels[:labelsLen], labels[labelsLen:], false, 0)
|
||||
sample.key = string(keyBuf)
|
||||
sample.value = float64(i)
|
||||
}
|
||||
|
||||
@@ -16,8 +16,10 @@ import (
|
||||
// Deduplicator deduplicates samples per each time series.
|
||||
type Deduplicator struct {
|
||||
da *dedupAggr
|
||||
lc *promutils.LabelsCompressor
|
||||
|
||||
dropLabels []string
|
||||
dropLabels []string
|
||||
stalenessInterval int64
|
||||
|
||||
wg sync.WaitGroup
|
||||
stopCh chan struct{}
|
||||
@@ -40,13 +42,16 @@ type Deduplicator struct {
|
||||
// MustStop must be called on the returned deduplicator in order to free up occupied resources.
|
||||
func NewDeduplicator(pushFunc PushFunc, dedupInterval time.Duration, dropLabels []string, alias string) *Deduplicator {
|
||||
d := &Deduplicator{
|
||||
da: newDedupAggr(),
|
||||
dropLabels: dropLabels,
|
||||
dropLabels: dropLabels,
|
||||
stalenessInterval: 2 * dedupInterval.Milliseconds(),
|
||||
lc: promutils.NewLabelsCompressor(),
|
||||
|
||||
stopCh: make(chan struct{}),
|
||||
ms: metrics.NewSet(),
|
||||
}
|
||||
|
||||
d.da = newDedupAggr(d.lc)
|
||||
|
||||
ms := d.ms
|
||||
|
||||
metricLabels := fmt.Sprintf(`name="dedup",url=%q`, alias)
|
||||
@@ -57,6 +62,12 @@ func NewDeduplicator(pushFunc PushFunc, dedupInterval time.Duration, dropLabels
|
||||
_ = ms.NewGauge(fmt.Sprintf(`vm_streamaggr_dedup_state_items_count{%s}`, metricLabels), func() float64 {
|
||||
return float64(d.da.itemsCount())
|
||||
})
|
||||
_ = ms.NewGauge(fmt.Sprintf(`vm_streamaggr_labels_compressor_size_bytes{%s}`, metricLabels), func() float64 {
|
||||
return float64(d.lc.SizeBytes())
|
||||
})
|
||||
_ = ms.NewGauge(fmt.Sprintf(`vm_streamaggr_labels_compressor_items_count{%s}`, metricLabels), func() float64 {
|
||||
return float64(d.lc.ItemsCount())
|
||||
})
|
||||
|
||||
d.dedupFlushDuration = ms.NewHistogram(fmt.Sprintf(`vm_streamaggr_dedup_flush_duration_seconds{%s}`, metricLabels))
|
||||
d.dedupFlushTimeouts = ms.NewCounter(fmt.Sprintf(`vm_streamaggr_dedup_flush_timeouts_total{%s}`, metricLabels))
|
||||
@@ -87,6 +98,7 @@ func (d *Deduplicator) Push(tss []prompbmarshal.TimeSeries) {
|
||||
pss := ctx.pss
|
||||
labels := &ctx.labels
|
||||
buf := ctx.buf
|
||||
deleteDeadline := time.Now().UnixMilli() + d.stalenessInterval
|
||||
|
||||
dropLabels := d.dropLabels
|
||||
for _, ts := range tss {
|
||||
@@ -101,7 +113,7 @@ func (d *Deduplicator) Push(tss []prompbmarshal.TimeSeries) {
|
||||
labels.Sort()
|
||||
|
||||
bufLen := len(buf)
|
||||
buf = lc.Compress(buf, labels.Labels)
|
||||
buf = d.lc.Compress(buf, labels.Labels, deleteDeadline)
|
||||
key := bytesutil.ToUnsafeString(buf[bufLen:])
|
||||
for _, s := range ts.Samples {
|
||||
pss = append(pss, pushSample{
|
||||
@@ -112,7 +124,7 @@ func (d *Deduplicator) Push(tss []prompbmarshal.TimeSeries) {
|
||||
}
|
||||
}
|
||||
|
||||
d.da.pushSamples(pss)
|
||||
d.da.pushSamples(pss, deleteDeadline)
|
||||
|
||||
ctx.pss = pss
|
||||
ctx.buf = buf
|
||||
@@ -145,7 +157,8 @@ func (d *Deduplicator) flush(pushFunc PushFunc, dedupInterval time.Duration) {
|
||||
startTime := time.Now()
|
||||
|
||||
timestamp := startTime.UnixMilli()
|
||||
d.da.flush(func(pss []pushSample) {
|
||||
deleteDeadline := timestamp + d.stalenessInterval
|
||||
d.da.flush(func(pss []pushSample, deleteDeadline int64) {
|
||||
ctx := getDeduplicatorFlushCtx()
|
||||
|
||||
tss := ctx.tss
|
||||
@@ -153,7 +166,7 @@ func (d *Deduplicator) flush(pushFunc PushFunc, dedupInterval time.Duration) {
|
||||
samples := ctx.samples
|
||||
for _, ps := range pss {
|
||||
labelsLen := len(labels)
|
||||
labels = decompressLabels(labels, ps.key)
|
||||
labels = decompressLabels(labels, d.lc, ps.key)
|
||||
|
||||
samplesLen := len(samples)
|
||||
samples = append(samples, prompbmarshal.Sample{
|
||||
@@ -172,7 +185,7 @@ func (d *Deduplicator) flush(pushFunc PushFunc, dedupInterval time.Duration) {
|
||||
ctx.labels = labels
|
||||
ctx.samples = samples
|
||||
putDeduplicatorFlushCtx(ctx)
|
||||
})
|
||||
}, timestamp, deleteDeadline)
|
||||
|
||||
duration := time.Since(startTime)
|
||||
d.dedupFlushDuration.Update(duration.Seconds())
|
||||
|
||||
@@ -1,42 +1,32 @@
|
||||
package streamaggr
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime"
|
||||
"github.com/VictoriaMetrics/metrics"
|
||||
)
|
||||
|
||||
// histogramBucketAggrState calculates output=histogram_bucket, e.g. VictoriaMetrics histogram over input samples.
|
||||
type histogramBucketAggrState struct {
|
||||
m sync.Map
|
||||
|
||||
stalenessSecs uint64
|
||||
}
|
||||
|
||||
type histogramBucketStateValue struct {
|
||||
mu sync.Mutex
|
||||
h metrics.Histogram
|
||||
deleteDeadline uint64
|
||||
deleteDeadline int64
|
||||
deleted bool
|
||||
}
|
||||
|
||||
func newHistogramBucketAggrState(stalenessInterval time.Duration) *histogramBucketAggrState {
|
||||
stalenessSecs := roundDurationToSecs(stalenessInterval)
|
||||
return &histogramBucketAggrState{
|
||||
stalenessSecs: stalenessSecs,
|
||||
}
|
||||
func newHistogramBucketAggrState() *histogramBucketAggrState {
|
||||
return &histogramBucketAggrState{}
|
||||
}
|
||||
|
||||
func (as *histogramBucketAggrState) pushSamples(samples []pushSample) {
|
||||
currentTime := fasttime.UnixTimestamp()
|
||||
deleteDeadline := currentTime + as.stalenessSecs
|
||||
func (as *histogramBucketAggrState) pushSamples(samples []pushSample, deleteDeadline int64, includeInputKey bool) {
|
||||
for i := range samples {
|
||||
s := &samples[i]
|
||||
outputKey := getOutputKey(s.key)
|
||||
outputKey := getOutputKey(s.key, includeInputKey)
|
||||
|
||||
again:
|
||||
v, ok := as.m.Load(outputKey)
|
||||
@@ -66,13 +56,13 @@ func (as *histogramBucketAggrState) pushSamples(samples []pushSample) {
|
||||
}
|
||||
}
|
||||
|
||||
func (as *histogramBucketAggrState) removeOldEntries(currentTime uint64) {
|
||||
func (as *histogramBucketAggrState) removeOldEntries(ctx *flushCtx) {
|
||||
m := &as.m
|
||||
m.Range(func(k, v any) bool {
|
||||
sv := v.(*histogramBucketStateValue)
|
||||
|
||||
sv.mu.Lock()
|
||||
deleted := currentTime > sv.deleteDeadline
|
||||
deleted := ctx.flushTimestamp > sv.deleteDeadline
|
||||
if deleted {
|
||||
// Mark the current entry as deleted
|
||||
sv.deleted = deleted
|
||||
@@ -80,6 +70,8 @@ func (as *histogramBucketAggrState) removeOldEntries(currentTime uint64) {
|
||||
sv.mu.Unlock()
|
||||
|
||||
if deleted {
|
||||
key := k.(string)
|
||||
ctx.a.lc.Delete(bytesutil.ToUnsafeBytes(key), ctx.flushTimestamp)
|
||||
m.Delete(k)
|
||||
}
|
||||
return true
|
||||
@@ -87,9 +79,7 @@ func (as *histogramBucketAggrState) removeOldEntries(currentTime uint64) {
|
||||
}
|
||||
|
||||
func (as *histogramBucketAggrState) flushState(ctx *flushCtx) {
|
||||
currentTime := fasttime.UnixTimestamp()
|
||||
|
||||
as.removeOldEntries(currentTime)
|
||||
as.removeOldEntries(ctx)
|
||||
|
||||
m := &as.m
|
||||
m.Range(func(k, v any) bool {
|
||||
@@ -105,11 +95,3 @@ func (as *histogramBucketAggrState) flushState(ctx *flushCtx) {
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func roundDurationToSecs(d time.Duration) uint64 {
|
||||
if d < 0 {
|
||||
return 0
|
||||
}
|
||||
secs := d.Seconds()
|
||||
return uint64(math.Ceil(secs))
|
||||
}
|
||||
|
||||
@@ -12,46 +12,45 @@ type lastAggrState struct {
|
||||
}
|
||||
|
||||
type lastStateValue struct {
|
||||
mu sync.Mutex
|
||||
last float64
|
||||
timestamp int64
|
||||
deleted bool
|
||||
mu sync.Mutex
|
||||
last float64
|
||||
timestamp int64
|
||||
deleted bool
|
||||
defined bool
|
||||
deleteDeadline int64
|
||||
}
|
||||
|
||||
func newLastAggrState() *lastAggrState {
|
||||
return &lastAggrState{}
|
||||
}
|
||||
|
||||
func (as *lastAggrState) pushSamples(samples []pushSample) {
|
||||
func (as *lastAggrState) pushSamples(samples []pushSample, deleteDeadline int64, includeInputKey bool) {
|
||||
for i := range samples {
|
||||
s := &samples[i]
|
||||
outputKey := getOutputKey(s.key)
|
||||
outputKey := getOutputKey(s.key, includeInputKey)
|
||||
|
||||
again:
|
||||
v, ok := as.m.Load(outputKey)
|
||||
if !ok {
|
||||
// The entry is missing in the map. Try creating it.
|
||||
v = &lastStateValue{
|
||||
last: s.value,
|
||||
timestamp: s.timestamp,
|
||||
}
|
||||
v = &lastStateValue{}
|
||||
outputKey = bytesutil.InternString(outputKey)
|
||||
vNew, loaded := as.m.LoadOrStore(outputKey, v)
|
||||
if !loaded {
|
||||
// The new entry has been successfully created.
|
||||
continue
|
||||
if loaded {
|
||||
// Use the entry created by a concurrent goroutine.
|
||||
v = vNew
|
||||
}
|
||||
// Use the entry created by a concurrent goroutine.
|
||||
v = vNew
|
||||
}
|
||||
sv := v.(*lastStateValue)
|
||||
sv.mu.Lock()
|
||||
deleted := sv.deleted
|
||||
if !deleted {
|
||||
if s.timestamp >= sv.timestamp {
|
||||
if !sv.defined || s.timestamp >= sv.timestamp {
|
||||
sv.last = s.value
|
||||
sv.timestamp = s.timestamp
|
||||
sv.deleteDeadline = deleteDeadline
|
||||
}
|
||||
sv.defined = true
|
||||
}
|
||||
sv.mu.Unlock()
|
||||
if deleted {
|
||||
@@ -70,6 +69,14 @@ func (as *lastAggrState) flushState(ctx *flushCtx) {
|
||||
|
||||
sv := v.(*lastStateValue)
|
||||
sv.mu.Lock()
|
||||
if ctx.flushTimestamp > sv.deleteDeadline {
|
||||
sv.deleted = true
|
||||
sv.mu.Unlock()
|
||||
key := k.(string)
|
||||
ctx.a.lc.Delete(bytesutil.ToUnsafeBytes(key), ctx.flushTimestamp)
|
||||
m.Delete(k)
|
||||
return true
|
||||
}
|
||||
last := sv.last
|
||||
// Mark the entry as deleted, so it won't be updated anymore by concurrent pushSample() calls.
|
||||
sv.deleted = true
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user