Compare commits

..

6 Commits

Author SHA1 Message Date
Max Kotliar
9430395c18 wip 2025-11-14 22:35:41 +02:00
Max Kotliar
d9e26de0a6 wip 2025-11-14 22:33:14 +02:00
Max Kotliar
13e7e04727 wip 2025-11-14 22:03:18 +02:00
Max Kotliar
f8b1b918c4 wip 2025-11-14 21:47:17 +02:00
Max Kotliar
db52cca9df release: wip 2025-11-14 20:32:41 +02:00
Max Kotliar
cdc374d8dd docs: Release guide slight automation
The commit automates several steps so they can be copy-pasted and
executed without any manual adjustments.

On success, it prints a message starting with “SUCCESS:” to make it
easier to distinguish from failures.

It also introduces the CURR_TAG and NEXT_TAG environment variables,
which are used by the automated commands.
2025-11-13 15:46:09 +02:00
196 changed files with 4529 additions and 7610 deletions

View File

@@ -1,48 +0,0 @@
#!/usr/bin/env sh
set -e
CHANGELOG_FILE="docs/victoriametrics/changelog/CHANGELOG.md"
GITHUB_BASE_REF=${GITHUB_BASE_REF:-"master"}
GIT_REMOTE=${GIT_REMOTE:-"origin"}
git diff "${GIT_REMOTE}/${GITHUB_BASE_REF}"...HEAD -- $CHANGELOG_FILE > diff.txt
if ! grep -q "^+" diff.txt; then
echo "No additions in CHANGELOG.md"
exit 0
fi
ADDED_LINES=$(grep "^+\S" diff.txt | sed 's/^+//')
START_TIP=$(grep -n "^## tip" "$CHANGELOG_FILE" | head -1 | cut -d: -f1)
if [ -z "$START_TIP" ]; then
echo "ERROR: ${CHANGELOG_FILE} does not contain a ## tip section"
exit 1
fi
END_TIP=$(awk "NR>$START_TIP && /^## / {print NR; exit}" "${CHANGELOG_FILE}")
if [ -z "$END_TIP" ]; then
END_TIP=$(wc -l < "$CHANGELOG_FILE")
fi
BAD=0
while IFS= read -r line; do
# Grep exact line inside the file and get line numbers
MATCHES=$(grep -n -F "$line" "$CHANGELOG_FILE" | cut -d: -f1)
for m in $MATCHES; do
if [ "$m" -lt "$START_TIP" ] || [ "$m" -gt "$END_TIP" ]; then
echo "'$line' on line ${m} is outside ## tip section (lines ${START_TIP}-${END_TIP})"
BAD=1
fi
done
done << EOF
$ADDED_LINES
EOF
if [ "$BAD" -ne 0 ]; then
echo "CHANGELOG modifications must be placed inside the ## tip section."
exit 1
fi
echo "CHANGELOG modifications are valid."

View File

@@ -61,7 +61,7 @@ jobs:
arch: amd64
steps:
- name: Code checkout
uses: actions/checkout@v6
uses: actions/checkout@v5
- name: Setup Go
id: go

View File

@@ -1,19 +0,0 @@
name: 'changelog-linter'
on:
pull_request:
paths:
- "docs/victoriametrics/changelog/CHANGELOG.md"
jobs:
tip-lint:
runs-on: 'ubuntu-latest'
steps:
- uses: 'actions/checkout@v4'
with:
# needed for proper diff
fetch-depth: 0
- name: 'Validate that changelog changes are under ## tip'
run: |
GITHUB_BASE_REF=${{ github.base_ref }} ./.github/scripts/lint-changelog-tip.sh

View File

@@ -8,7 +8,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v6
uses: actions/checkout@v5
with:
fetch-depth: 0 # we need full history for commit verification

View File

@@ -29,7 +29,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v5
- name: Set up Go
id: go

View File

@@ -16,12 +16,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Code checkout
uses: actions/checkout@v6
uses: actions/checkout@v5
with:
path: __vm
- name: Checkout private code
uses: actions/checkout@v6
uses: actions/checkout@v5
with:
repository: VictoriaMetrics/vmdocs
token: ${{ secrets.VM_BOT_GH_TOKEN }}

View File

@@ -32,7 +32,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Code checkout
uses: actions/checkout@v6
uses: actions/checkout@v5
- name: Setup Go
id: go
@@ -71,7 +71,7 @@ jobs:
steps:
- name: Code checkout
uses: actions/checkout@v6
uses: actions/checkout@v5
- name: Setup Go
id: go
@@ -97,7 +97,7 @@ jobs:
steps:
- name: Code checkout
uses: actions/checkout@v6
uses: actions/checkout@v5
- name: Setup Go
id: go

View File

@@ -32,7 +32,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Code checkout
uses: actions/checkout@v6
uses: actions/checkout@v5
- name: Setup Node
uses: actions/setup-node@v6

View File

@@ -500,8 +500,7 @@ app-local-windows-goarch:
CGO_ENABLED=0 GOOS=windows GOARCH=$(GOARCH) go build $(RACE) -ldflags "$(GO_BUILDINFO)" -tags "$(EXTRA_GO_BUILD_TAGS)" -o bin/$(APP_NAME)-windows-$(GOARCH)$(RACE).exe $(PKG_PREFIX)/app/$(APP_NAME)
quicktemplate-gen: install-qtc
qtc -dir=lib
qtc -dir=app
qtc
install-qtc:
which qtc || go install github.com/valyala/quicktemplate/qtc@latest

View File

@@ -116,7 +116,7 @@ func TestParse_Failure(t *testing.T) {
f([]string{"testdata/rules/rules_interval_bad.rules"}, "eval_offset should be smaller than interval")
f([]string{"testdata/rules/rules0-bad.rules"}, "unexpected token")
f([]string{"testdata/dir/rules0-bad.rules"}, "invalid annotations")
f([]string{"testdata/dir/rules0-bad.rules"}, "error parsing annotation")
f([]string{"testdata/dir/rules1-bad.rules"}, "duplicate in file")
f([]string{"testdata/dir/rules2-bad.rules"}, "function \"unknown\" not defined")
f([]string{"testdata/dir/rules3-bad.rules"}, "either `record` or `alert` must be set")
@@ -343,6 +343,7 @@ func TestGroupValidate_Failure(t *testing.T) {
},
},
}, true, "bad prometheus expr")
}
func TestGroupValidate_Success(t *testing.T) {

View File

@@ -3,7 +3,6 @@ package main
import (
"context"
"fmt"
"strconv"
"sync"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/config"
@@ -46,15 +45,13 @@ func (m *manager) ruleAPI(gID, rID uint64) (rule.ApiRule, error) {
m.groupsMu.RLock()
defer m.groupsMu.RUnlock()
group, ok := m.groups[gID]
g, ok := m.groups[gID]
if !ok {
return rule.ApiRule{}, fmt.Errorf("can't find group with id %d", gID)
}
g := group.ToAPI()
ruleID := strconv.FormatUint(rID, 10)
for _, r := range g.Rules {
if r.ID == ruleID {
return r, nil
if r.ID() == rID {
return r.ToAPI(), nil
}
}
return rule.ApiRule{}, fmt.Errorf("can't find rule with id %d in group %q", rID, g.Name)
@@ -65,20 +62,17 @@ func (m *manager) alertAPI(gID, aID uint64) (*rule.ApiAlert, error) {
m.groupsMu.RLock()
defer m.groupsMu.RUnlock()
group, ok := m.groups[gID]
g, ok := m.groups[gID]
if !ok {
return nil, fmt.Errorf("can't find group with id %d", gID)
}
g := group.ToAPI()
for _, r := range g.Rules {
if r.Type != rule.TypeAlerting {
ar, ok := r.(*rule.AlertingRule)
if !ok {
continue
}
alertID := strconv.FormatUint(aID, 10)
for _, a := range r.Alerts {
if a.ID == alertID {
return a, nil
}
if apiAlert := ar.AlertToAPI(aID); apiAlert != nil {
return apiAlert, nil
}
}
return nil, fmt.Errorf("can't find alert with id %d in group %q", aID, g.Name)

View File

@@ -166,8 +166,8 @@ func templateAnnotations(annotations map[string]string, data AlertTplData, tmpl
ctmpl, _ := tmpl.Clone()
ctmpl = ctmpl.Option("missingkey=zero")
if err := templateAnnotation(&buf, builder.String(), tData, ctmpl, execute); err != nil {
r[key] = err.Error()
eg.Add(fmt.Errorf("(key: %q, value: %q): %w", key, text, err))
r[key] = text
eg.Add(fmt.Errorf("key %q, template %q: %w", key, text, err))
continue
}
r[key] = buf.String()
@@ -184,13 +184,13 @@ type tplData struct {
func templateAnnotation(dst io.Writer, text string, data tplData, tpl *textTpl.Template, execute bool) error {
tpl, err := tpl.Parse(text)
if err != nil {
return fmt.Errorf("error parsing template: %w", err)
return fmt.Errorf("error parsing annotation template: %w", err)
}
if !execute {
return nil
}
if err = tpl.Execute(dst, data); err != nil {
return fmt.Errorf("error evaluating template: %w", err)
return fmt.Errorf("error evaluating annotation template: %w", err)
}
return nil
}

View File

@@ -3,7 +3,6 @@ package notifier
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"net/http"
@@ -87,11 +86,6 @@ func (am *AlertManager) Send(ctx context.Context, alerts []Alert, alertLabels []
err := am.send(ctx, alerts, alertLabels, headers)
am.metrics.alertsSendDuration.UpdateDuration(startTime)
if err != nil {
// the context can be cancelled on graceful shutdown
// or on group update. So no need to handle the error as usual.
if errors.Is(err, context.Canceled) {
return nil
}
am.metrics.alertsSendErrors.Add(len(alerts))
am.lastError = err.Error()
} else {

View File

@@ -246,6 +246,16 @@ func (ar *AlertingRule) GetAlerts() []*notifier.Alert {
return alerts
}
// GetAlert returns alert if id exists
func (ar *AlertingRule) GetAlert(id uint64) *notifier.Alert {
ar.alertsMu.RLock()
defer ar.alertsMu.RUnlock()
if ar.alerts == nil {
return nil
}
return ar.alerts[id]
}
func (ar *AlertingRule) logDebugf(at time.Time, a *notifier.Alert, format string, args ...any) {
if !ar.Debug {
return
@@ -311,11 +321,6 @@ type labelSet struct {
// On k conflicts in origin set, the original value is preferred and copied
// to processed with `exported_%k` key. The copy happens only if passed v isn't equal to origin[k] value.
func (ls *labelSet) add(k, v string) {
// do not add label with empty value, since it has no meaning.
// see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/9984
if v == "" {
return
}
ls.processed[k] = v
ov, ok := ls.origin[k]
if !ok {
@@ -350,6 +355,9 @@ func (ar *AlertingRule) toLabels(m datasource.Metric, qFn templates.QueryFn) (*l
Value: m.Values[0],
Expr: ar.Expr,
})
if err != nil {
return nil, fmt.Errorf("failed to expand labels: %w", err)
}
for k, v := range extraLabels {
ls.add(k, v)
}
@@ -360,7 +368,7 @@ func (ar *AlertingRule) toLabels(m datasource.Metric, qFn templates.QueryFn) (*l
if !*disableAlertGroupLabel && ar.GroupName != "" {
ls.add(alertGroupNameLabel, ar.GroupName)
}
return ls, err
return ls, nil
}
// execRange executes alerting rule on the given time range similarly to exec.
@@ -476,9 +484,8 @@ func (ar *AlertingRule) exec(ctx context.Context, ts time.Time, limit int) ([]pr
for i, m := range res.Data {
ls, err := ar.expandLabelTemplates(m, qFn)
if err != nil {
// only set error in current state, but do not break alert processing
curState.Err = err
logger.Errorf("got templating error in rule %s: %q", ar.Name, err)
return nil, curState.Err
}
at := ts
alertID := hash(ls.processed)
@@ -490,9 +497,8 @@ func (ar *AlertingRule) exec(ctx context.Context, ts time.Time, limit int) ([]pr
}
as, err := ar.expandAnnotationTemplates(m, qFn, at, ls)
if err != nil {
// only set error in current state, but do not break alert processing
curState.Err = err
logger.Errorf("got templating error in rule %s: %q", ar.Name, err)
return nil, curState.Err
}
expandedLabels[i] = ls
expandedAnnotations[i] = as
@@ -601,7 +607,7 @@ func (ar *AlertingRule) exec(ctx context.Context, ts time.Time, limit int) ([]pr
func (ar *AlertingRule) expandLabelTemplates(m datasource.Metric, qFn templates.QueryFn) (*labelSet, error) {
ls, err := ar.toLabels(m, qFn)
if err != nil {
return ls, fmt.Errorf("failed to expand label templates: %s", err)
return nil, fmt.Errorf("failed to expand label templates: %s", err)
}
return ls, nil
}
@@ -619,7 +625,7 @@ func (ar *AlertingRule) expandAnnotationTemplates(m datasource.Metric, qFn templ
}
as, err := notifier.ExecTemplate(qFn, ar.Annotations, tplData)
if err != nil {
return as, fmt.Errorf("failed to expand annotation templates: %s", err)
return nil, fmt.Errorf("failed to expand annotation templates: %s", err)
}
return as, nil
}

View File

@@ -1370,10 +1370,8 @@ func TestAlertingRule_ToLabels(t *testing.T) {
ar := &AlertingRule{
Labels: map[string]string{
"instance": "override", // this should override instance with new value
"group": "vmalert", // this shouldn't have effect since value in metric is equal
"invalid_label": "{{ .Values.mustRuntimeFail }}",
"empty_label": "", // this should be dropped
"instance": "override", // this should override instance with new value
"group": "vmalert", // this shouldn't have effect since value in metric is equal
},
Expr: "sum(vmalert_alerting_rules_error) by(instance, group, alertname) > 0",
Name: "AlertingRulesError",
@@ -1381,11 +1379,10 @@ func TestAlertingRule_ToLabels(t *testing.T) {
}
expectedOriginLabels := map[string]string{
"instance": "0.0.0.0:8800",
"group": "vmalert",
"alertname": "ConfigurationReloadFailure",
"alertgroup": "vmalert",
"invalid_label": `error evaluating template: template: :1:268: executing "" at <.Values.mustRuntimeFail>: can't evaluate field Values in type notifier.tplData`,
"instance": "0.0.0.0:8800",
"group": "vmalert",
"alertname": "ConfigurationReloadFailure",
"alertgroup": "vmalert",
}
expectedProcessedLabels := map[string]string{
@@ -1395,12 +1392,11 @@ func TestAlertingRule_ToLabels(t *testing.T) {
"exported_alertname": "ConfigurationReloadFailure",
"group": "vmalert",
"alertgroup": "vmalert",
"invalid_label": `error evaluating template: template: :1:268: executing "" at <.Values.mustRuntimeFail>: can't evaluate field Values in type notifier.tplData`,
}
ls, err := ar.toLabels(metric, nil)
if err == nil || !strings.Contains(err.Error(), "error evaluating template") {
t.Fatalf("unexpected error %q", err.Error())
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if !reflect.DeepEqual(ls.origin, expectedOriginLabels) {

View File

@@ -236,8 +236,7 @@ func (rr *RecordingRule) exec(ctx context.Context, ts time.Time, limit int) ([]p
Labels: stringToLabels(k),
Samples: []prompb.Sample{
{Value: decimal.StaleNaN, Timestamp: ts.UnixNano() / 1e6},
},
})
}})
}
rr.lastEvaluation = curEvaluation
return tss, nil
@@ -292,11 +291,6 @@ func (rr *RecordingRule) toTimeSeries(m datasource.Metric) prompb.TimeSeries {
}
// add extra labels configured by user
for k := range rr.Labels {
// do not add label with empty value, since it has no meaning.
// see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/9984
if rr.Labels[k] == "" {
continue
}
existingLabel := promrelabel.GetLabelByName(m.Labels, k)
if existingLabel != nil { // there is a conflict between extra and existing label
if existingLabel.Value == rr.Labels[k] {

View File

@@ -209,6 +209,15 @@ func (ar *AlertingRule) AlertsToAPI() []*ApiAlert {
return alerts
}
// AlertToAPI generates apiAlert object from alert by its id(hash)
func (ar *AlertingRule) AlertToAPI(id uint64) *ApiAlert {
a := ar.GetAlert(id)
if a == nil {
return nil
}
return NewAlertAPI(ar, a)
}
// NewAlertAPI creates apiAlert for notifier.Alert
func NewAlertAPI(ar *AlertingRule, a *notifier.Alert) *ApiAlert {
aa := &ApiAlert{

View File

@@ -412,18 +412,18 @@ func (rh *requestHandler) groupAlerts() []rule.GroupAlerts {
defer rh.m.groupsMu.RUnlock()
var gAlerts []rule.GroupAlerts
for _, group := range rh.m.groups {
for _, g := range rh.m.groups {
var alerts []*rule.ApiAlert
g := group.ToAPI()
for _, r := range g.Rules {
if r.Type != rule.TypeAlerting {
a, ok := r.(*rule.AlertingRule)
if !ok {
continue
}
alerts = append(alerts, r.Alerts...)
alerts = append(alerts, a.AlertsToAPI()...)
}
if len(alerts) > 0 {
gAlerts = append(gAlerts, rule.GroupAlerts{
Group: g,
Group: g.ToAPI(),
Alerts: alerts,
})
}
@@ -444,12 +444,12 @@ func (rh *requestHandler) listAlerts(rf *rulesFilter) ([]byte, error) {
if !rf.matchesGroup(group) {
continue
}
g := group.ToAPI()
for _, r := range g.Rules {
if r.Type != rule.TypeAlerting {
for _, r := range group.Rules {
a, ok := r.(*rule.AlertingRule)
if !ok {
continue
}
lr.Data.Alerts = append(lr.Data.Alerts, r.Alerts...)
lr.Data.Alerts = append(lr.Data.Alerts, a.AlertsToAPI()...)
}
}

View File

@@ -602,11 +602,11 @@
<table class="table table-striped table-hover table-sm">
<thead>
<tr>
<th scope="col" title="The time when the rule was executed">Updated at</th>
<th scope="col" title="The time when event was created">Updated at</th>
<th scope="col" class="w-10 text-center" title="How many series expression returns. Each series will represent an alert.">Series returned</th>
{% if seriesFetchedEnabled %}<th scope="col" class="w-10 text-center" title="How many series were scanned by datasource during the evaluation">Series fetched</th>{% endif %}
<th scope="col" class="w-10 text-center" title="How many seconds request took">Duration</th>
<th scope="col" class="text-center" title="The time used in execution query request">Execution timestamp</th>
<th scope="col" class="text-center" title="Time used for rule execution">Executed at</th>
<th scope="col" class="text-center" title="cURL command with request example">cURL</th>
</tr>
</thead>

View File

@@ -1717,7 +1717,7 @@ func StreamRuleDetails(qw422016 *qt422016.Writer, r *http.Request, rule rule.Api
<table class="table table-striped table-hover table-sm">
<thead>
<tr>
<th scope="col" title="The time when the rule was executed">Updated at</th>
<th scope="col" title="The time when event was created">Updated at</th>
<th scope="col" class="w-10 text-center" title="How many series expression returns. Each series will represent an alert.">Series returned</th>
`)
//line app/vmalert/web.qtpl:607
@@ -1729,7 +1729,7 @@ func StreamRuleDetails(qw422016 *qt422016.Writer, r *http.Request, rule rule.Api
//line app/vmalert/web.qtpl:607
qw422016.N().S(`
<th scope="col" class="w-10 text-center" title="How many seconds request took">Duration</th>
<th scope="col" class="text-center" title="The time used in execution query request">Execution timestamp</th>
<th scope="col" class="text-center" title="Time used for rule execution">Executed at</th>
<th scope="col" class="text-center" title="cURL command with request example">cURL</th>
</tr>
</thead>

View File

@@ -212,7 +212,7 @@ func newSrcFS() (*fslocal.FS, error) {
}
func newDstFS(ctx context.Context) (common.RemoteFS, error) {
fs, err := actions.NewRemoteFS(ctx, *dst, nil)
fs, err := actions.NewRemoteFS(ctx, *dst)
if err != nil {
return nil, fmt.Errorf("cannot parse `-dst`=%q: %w", *dst, err)
}
@@ -255,7 +255,7 @@ func newOriginFS(ctx context.Context) (common.OriginFS, error) {
if len(*origin) == 0 {
return &fsnil.FS{}, nil
}
fs, err := actions.NewRemoteFS(ctx, *origin, nil)
fs, err := actions.NewRemoteFS(ctx, *origin)
if err != nil {
return nil, fmt.Errorf("cannot parse `-origin`=%q: %w", *origin, err)
}
@@ -266,7 +266,7 @@ func newRemoteOriginFS(ctx context.Context) (common.RemoteFS, error) {
if len(*origin) == 0 {
return nil, fmt.Errorf("-origin cannot be empty when -snapshotName and -snapshot.createURL aren't set")
}
fs, err := actions.NewRemoteFS(ctx, *origin, nil)
fs, err := actions.NewRemoteFS(ctx, *origin)
if err != nil {
return nil, fmt.Errorf("cannot parse `-origin`=%q: %w", *origin, err)
}

View File

@@ -11,11 +11,9 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httpserver"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompb"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/prometheus"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/ratelimiter"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/slicesutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage/metricsmetadata"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/timeserieslimits"
)
@@ -52,9 +50,8 @@ var (
type InsertCtx struct {
Labels sortedLabels
mrs []storage.MetricRow
mms []metricsmetadata.Row
metricNameBuf []byte
mrs []storage.MetricRow
metricNamesBuf []byte
relabelCtx relabel.Ctx
streamAggrCtx streamAggrCtx
@@ -76,13 +73,8 @@ func (ctx *InsertCtx) Reset(rowsLen int) {
}
mrs = slicesutil.SetLength(mrs, rowsLen)
ctx.mrs = mrs[:0]
mms := ctx.mms
for i := range mms {
cleanMetricMetadata(&mms[i])
}
ctx.mms = mms[:0]
ctx.metricNameBuf = ctx.metricNameBuf[:0]
ctx.metricNamesBuf = ctx.metricNamesBuf[:0]
ctx.relabelCtx.Reset()
ctx.streamAggrCtx.Reset()
ctx.skipStreamAggr = false
@@ -92,20 +84,11 @@ func cleanMetricRow(mr *storage.MetricRow) {
mr.MetricNameRaw = nil
}
func cleanMetricMetadata(mm *metricsmetadata.Row) {
mm.MetricFamilyName = nil
mm.Unit = nil
mm.Help = nil
mm.Type = 0
mm.ProjectID = 0
mm.AccountID = 0
}
func (ctx *InsertCtx) marshalMetricNameRaw(prefix []byte, labels []prompb.Label) []byte {
start := len(ctx.metricNameBuf)
ctx.metricNameBuf = append(ctx.metricNameBuf, prefix...)
ctx.metricNameBuf = storage.MarshalMetricNameRaw(ctx.metricNameBuf, labels)
metricNameRaw := ctx.metricNameBuf[start:]
start := len(ctx.metricNamesBuf)
ctx.metricNamesBuf = append(ctx.metricNamesBuf, prefix...)
ctx.metricNamesBuf = storage.MarshalMetricNameRaw(ctx.metricNamesBuf, labels)
metricNameRaw := ctx.metricNamesBuf[start:]
return metricNameRaw[:len(metricNameRaw):len(metricNameRaw)]
}
@@ -160,7 +143,7 @@ func (ctx *InsertCtx) addRow(metricNameRaw []byte, timestamp int64, value float6
mr.MetricNameRaw = metricNameRaw
mr.Timestamp = timestamp
mr.Value = value
if len(ctx.metricNameBuf) > 16*1024*1024 {
if len(ctx.metricNamesBuf) > 16*1024*1024 {
if err := ctx.FlushBufs(); err != nil {
return err
}
@@ -168,55 +151,6 @@ func (ctx *InsertCtx) addRow(metricNameRaw []byte, timestamp int64, value float6
return nil
}
// WriteMetadata writes given prometheus protobuf metadata into the storage.
func (ctx *InsertCtx) WriteMetadata(mmpbs []prompb.MetricMetadata) error {
if len(mmpbs) == 0 {
return nil
}
mms := ctx.mms
mms = slicesutil.SetLength(mms, len(mmpbs))
for idx, mmpb := range mmpbs {
mm := &mms[idx]
mm.MetricFamilyName = bytesutil.ToUnsafeBytes(mmpb.MetricFamilyName)
mm.Help = bytesutil.ToUnsafeBytes(mmpb.Help)
mm.Type = mmpb.Type
mm.Unit = bytesutil.ToUnsafeBytes(mmpb.Unit)
}
err := vmstorage.AddMetadataRows(mms)
if err != nil {
return &httpserver.ErrorWithStatusCode{
Err: fmt.Errorf("cannot store metrics metadata: %w", err),
StatusCode: http.StatusServiceUnavailable,
}
}
return nil
}
// WritePromMetadata writes given prometheus metric metadata into the storage
func (ctx *InsertCtx) WritePromMetadata(mmps []prometheus.Metadata) error {
if len(mmps) == 0 {
return nil
}
mms := ctx.mms
mms = slicesutil.SetLength(mms, len(mmps))
for idx, mmpb := range mmps {
mm := &mms[idx]
mm.MetricFamilyName = bytesutil.ToUnsafeBytes(mmpb.Metric)
mm.Help = bytesutil.ToUnsafeBytes(mmpb.Help)
mm.Type = mmpb.Type
}
err := vmstorage.AddMetadataRows(mms)
if err != nil {
return &httpserver.ErrorWithStatusCode{
Err: fmt.Errorf("cannot store prometheus metrics metadata: %w", err),
StatusCode: http.StatusServiceUnavailable,
}
}
return nil
}
// AddLabelBytes adds (name, value) label to ctx.Labels.
//
// name and value must exist until ctx.Labels is used.

View File

@@ -6,7 +6,6 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/app/vminsert/common"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vminsert/relabel"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prommetadata"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompb"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/opentelemetry/firehose"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/opentelemetry/stream"
@@ -15,9 +14,8 @@ import (
)
var (
rowsInserted = metrics.NewCounter(`vm_rows_inserted_total{type="opentelemetry"}`)
rowsPerInsert = metrics.NewHistogram(`vm_rows_per_insert{type="opentelemetry"}`)
metadataInserted = metrics.NewCounter(`vm_metadata_rows_inserted_total{type="opentelemetry"}`)
rowsInserted = metrics.NewCounter(`vm_rows_inserted_total{type="opentelemetry"}`)
rowsPerInsert = metrics.NewHistogram(`vm_rows_per_insert{type="opentelemetry"}`)
)
// InsertHandler processes opentelemetry metrics.
@@ -35,12 +33,12 @@ func InsertHandler(req *http.Request) error {
return fmt.Errorf("json encoding isn't supported for opentelemetry format. Use protobuf encoding")
}
}
return stream.ParseStream(req.Body, encoding, processBody, func(tss []prompb.TimeSeries, mms []prompb.MetricMetadata) error {
return insertRows(tss, mms, extraLabels)
return stream.ParseStream(req.Body, encoding, processBody, func(tss []prompb.TimeSeries, _ []prompb.MetricMetadata) error {
return insertRows(tss, extraLabels)
})
}
func insertRows(tss []prompb.TimeSeries, mms []prompb.MetricMetadata, extraLabels []prompb.Label) error {
func insertRows(tss []prompb.TimeSeries, extraLabels []prompb.Label) error {
ctx := common.GetInsertCtx()
defer common.PutInsertCtx(ctx)
@@ -77,14 +75,5 @@ func insertRows(tss []prompb.TimeSeries, mms []prompb.MetricMetadata, extraLabel
}
rowsInserted.Add(rowsTotal)
rowsPerInsert.Update(float64(rowsTotal))
if err := ctx.FlushBufs(); err != nil {
return fmt.Errorf("cannot flush metric bufs: %w", err)
}
if prommetadata.IsEnabled() {
if err := ctx.WriteMetadata(mms); err != nil {
return err
}
metadataInserted.Add(len(mms))
}
return nil
return ctx.FlushBufs()
}

View File

@@ -1,7 +1,6 @@
package prometheusimport
import (
"fmt"
"net/http"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vminsert/common"
@@ -16,9 +15,8 @@ import (
)
var (
rowsInserted = metrics.NewCounter(`vm_rows_inserted_total{type="prometheus"}`)
rowsPerInsert = metrics.NewHistogram(`vm_rows_per_insert{type="prometheus"}`)
metadataInserted = metrics.NewCounter(`vm_metadata_rows_inserted_total{type="prometheus"}`)
rowsInserted = metrics.NewCounter(`vm_rows_inserted_total{type="prometheus"}`)
rowsPerInsert = metrics.NewHistogram(`vm_rows_per_insert{type="prometheus"}`)
)
// InsertHandler processes `/api/v1/import/prometheus` request.
@@ -32,14 +30,14 @@ func InsertHandler(req *http.Request) error {
return err
}
encoding := req.Header.Get("Content-Encoding")
return stream.Parse(req.Body, defaultTimestamp, encoding, true, prommetadata.IsEnabled(), func(rows []prometheus.Row, mms []prometheus.Metadata) error {
return insertRows(rows, mms, extraLabels)
return stream.Parse(req.Body, defaultTimestamp, encoding, true, prommetadata.IsEnabled(), func(rows []prometheus.Row, _ []prometheus.Metadata) error {
return insertRows(rows, extraLabels)
}, func(s string) {
httpserver.LogError(req, s)
})
}
func insertRows(rows []prometheus.Row, mms []prometheus.Metadata, extraLabels []prompb.Label) error {
func insertRows(rows []prometheus.Row, extraLabels []prompb.Label) error {
ctx := common.GetInsertCtx()
defer common.PutInsertCtx(ctx)
@@ -66,15 +64,5 @@ func insertRows(rows []prometheus.Row, mms []prometheus.Metadata, extraLabels []
}
rowsInserted.Add(len(rows))
rowsPerInsert.Update(float64(len(rows)))
if err := ctx.FlushBufs(); err != nil {
return fmt.Errorf("cannot flush metric bufs: %w", err)
}
if prommetadata.IsEnabled() {
if err := ctx.WritePromMetadata(mms); err != nil {
return err
}
metadataInserted.Add(len(mms))
}
return nil
return ctx.FlushBufs()
}

View File

@@ -1,12 +1,10 @@
package promremotewrite
import (
"fmt"
"net/http"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vminsert/common"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vminsert/relabel"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prommetadata"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompb"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/promremotewrite/stream"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/protoparserutil"
@@ -14,9 +12,8 @@ import (
)
var (
rowsInserted = metrics.NewCounter(`vm_rows_inserted_total{type="promremotewrite"}`)
rowsPerInsert = metrics.NewHistogram(`vm_rows_per_insert{type="promremotewrite"}`)
metadataInserted = metrics.NewCounter(`vm_metadata_rows_inserted_total{type="promremotewrite"}`)
rowsInserted = metrics.NewCounter(`vm_rows_inserted_total{type="promremotewrite"}`)
rowsPerInsert = metrics.NewHistogram(`vm_rows_per_insert{type="promremotewrite"}`)
)
// InsertHandler processes remote write for prometheus.
@@ -26,12 +23,12 @@ func InsertHandler(req *http.Request) error {
return err
}
isVMRemoteWrite := req.Header.Get("Content-Encoding") == "zstd"
return stream.Parse(req.Body, isVMRemoteWrite, func(tss []prompb.TimeSeries, mms []prompb.MetricMetadata) error {
return insertRows(tss, mms, extraLabels)
return stream.Parse(req.Body, isVMRemoteWrite, func(tss []prompb.TimeSeries, _ []prompb.MetricMetadata) error {
return insertRows(tss, extraLabels)
})
}
func insertRows(timeseries []prompb.TimeSeries, mms []prompb.MetricMetadata, extraLabels []prompb.Label) error {
func insertRows(timeseries []prompb.TimeSeries, extraLabels []prompb.Label) error {
ctx := common.GetInsertCtx()
defer common.PutInsertCtx(ctx)
@@ -71,15 +68,5 @@ func insertRows(timeseries []prompb.TimeSeries, mms []prompb.MetricMetadata, ext
}
rowsInserted.Add(rowsTotal)
rowsPerInsert.Update(float64(rowsTotal))
if err := ctx.FlushBufs(); err != nil {
return fmt.Errorf("cannot flush metric bufs: %w", err)
}
if prommetadata.IsEnabled() {
if err := ctx.WriteMetadata(mms); err != nil {
return err
}
metadataInserted.Add(len(mms))
}
return nil
return ctx.FlushBufs()
}

View File

@@ -104,7 +104,7 @@ func newDstFS() (*fslocal.FS, error) {
}
func newSrcFS(ctx context.Context) (common.RemoteFS, error) {
fs, err := actions.NewRemoteFS(ctx, *src, nil)
fs, err := actions.NewRemoteFS(ctx, *src)
if err != nil {
return nil, fmt.Errorf("cannot parse `-src`=%q: %w", *src, err)
}

View File

@@ -421,16 +421,6 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
}
w.WriteHeader(http.StatusNoContent)
return true
case "/api/v1/metadata":
// Return dumb placeholder for https://prometheus.io/docs/prometheus/latest/querying/api/#querying-metric-metadata
metadataRequests.Inc()
if err := prometheus.MetadataHandler(qt, startTime, w, r); err != nil {
metadataErrors.Inc()
httpserver.SendPrometheusError(w, r, err)
return true
}
return true
default:
return false
}
@@ -584,6 +574,12 @@ func handleStaticAndSimpleRequests(w http.ResponseWriter, r *http.Request, path
w.Header().Set("Content-Type", "application/json")
fmt.Fprint(w, `{"status":"success","data":{"notifiers":[]}}`)
return true
case "/api/v1/metadata":
// Return dumb placeholder for https://prometheus.io/docs/prometheus/latest/querying/api/#querying-metric-metadata
metadataRequests.Inc()
w.Header().Set("Content-Type", "application/json")
fmt.Fprintf(w, "%s", `{"status":"success","data":{}}`)
return true
case "/api/v1/status/buildinfo":
buildInfoRequests.Inc()
w.Header().Set("Content-Type", "application/json")
@@ -712,9 +708,7 @@ var (
alertsRequests = metrics.NewCounter(`vm_http_requests_total{path="/api/v1/alerts"}`)
notifiersRequests = metrics.NewCounter(`vm_http_requests_total{path="/api/v1/notifiers"}`)
metadataRequests = metrics.NewCounter(`vm_http_requests_total{path="/api/v1/metadata"}`)
metadataErrors = metrics.NewCounter(`vm_http_request_errors_total{path="/api/v1/metadata"}`)
metadataRequests = metrics.NewCounter(`vm_http_requests_total{path="/api/v1/metadata"}`)
buildInfoRequests = metrics.NewCounter(`vm_http_requests_total{path="/api/v1/buildinfo"}`)
queryExemplarsRequests = metrics.NewCounter(`vm_http_requests_total{path="/api/v1/query_exemplars"}`)

View File

@@ -20,7 +20,6 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/querytracer"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage/metricsmetadata"
)
var (
@@ -866,23 +865,6 @@ func LabelValues(qt *querytracer.Tracer, labelName string, sq *storage.SearchQue
return labelValues, nil
}
// GetMetricsMetadata returns time series metric names metadata for the given args
func GetMetricsMetadata(qt *querytracer.Tracer, limit int, metricName string) ([]*metricsmetadata.Row, error) {
qt = qt.NewChild("get metrics metadata: limit=%d, metric_name=%q", limit, metricName)
defer qt.Done()
metadata := vmstorage.Storage.GetMetadataRows(qt, limit, metricName)
sort.Slice(metadata, func(i, j int) bool {
return string(metadata[i].MetricFamilyName) < string(metadata[j].MetricFamilyName)
})
if limit > 0 && len(metadata) >= limit {
metadata = metadata[:limit]
}
return metadata, nil
}
// GraphiteTagValues returns tag values for the given tagName until the given deadline.
func GraphiteTagValues(qt *querytracer.Tracer, tagName, filter string, limit int, deadline searchutil.Deadline) ([]string, error) {
qt = qt.NewChild("get graphite tag values for tagName=%s, filter=%s, limit=%d", tagName, filter, limit)

View File

@@ -1,36 +0,0 @@
{% import (
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage/metricsmetadata"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompb"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/querytracer"
) %}
{% stripspace %}
MetadataResponse generates response for /api/v1/metadata
See https://prometheus.io/docs/prometheus/latest/querying/api/#querying-metric-metadata
{% func MetadataResponse( result []*metricsmetadata.Row, qt *querytracer.Tracer) %}
{
"status":"success",
"data": {
{% code
mapItems := len(result)
currentItem := 0
%}
{% for _, row := range result %}
"{%s string(row.MetricFamilyName) %}": [
{
"type": {%q= prompb.MetricMetadataTypeToString(row.Type) %},
{% if len(row.Unit) > 0 -%}
"unit": {%q= string(row.Unit) %},
{% endif -%}
"help": {%q= string(row.Help) %}
}
]
{% if currentItem != mapItems-1 %},{% endif %}
{% code currentItem++ %}
{% endfor %}
}
{%= dumpQueryTrace(qt) %}
}
{% endfunc %}
{% endstripspace %}

View File

@@ -1,109 +0,0 @@
// Code generated by qtc from "metadata_response.qtpl". DO NOT EDIT.
// See https://github.com/valyala/quicktemplate for details.
//line app/vmselect/prometheus/metadata_response.qtpl:1
package prometheus
//line app/vmselect/prometheus/metadata_response.qtpl:1
import (
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompb"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/querytracer"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage/metricsmetadata"
)
// MetadataResponse generates response for /api/v1/metadataSee https://prometheus.io/docs/prometheus/latest/querying/api/#querying-metric-metadata
//line app/vmselect/prometheus/metadata_response.qtpl:10
import (
qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate"
)
//line app/vmselect/prometheus/metadata_response.qtpl:10
var (
_ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer
)
//line app/vmselect/prometheus/metadata_response.qtpl:10
func StreamMetadataResponse(qw422016 *qt422016.Writer, result []*metricsmetadata.Row, qt *querytracer.Tracer) {
//line app/vmselect/prometheus/metadata_response.qtpl:10
qw422016.N().S(`{"status":"success","data": {`)
//line app/vmselect/prometheus/metadata_response.qtpl:15
mapItems := len(result)
currentItem := 0
//line app/vmselect/prometheus/metadata_response.qtpl:18
for _, row := range result {
//line app/vmselect/prometheus/metadata_response.qtpl:18
qw422016.N().S(`"`)
//line app/vmselect/prometheus/metadata_response.qtpl:19
qw422016.E().S(string(row.MetricFamilyName))
//line app/vmselect/prometheus/metadata_response.qtpl:19
qw422016.N().S(`": [{"type":`)
//line app/vmselect/prometheus/metadata_response.qtpl:21
qw422016.N().Q(prompb.MetricMetadataTypeToString(row.Type))
//line app/vmselect/prometheus/metadata_response.qtpl:21
qw422016.N().S(`,`)
//line app/vmselect/prometheus/metadata_response.qtpl:22
if len(row.Unit) > 0 {
//line app/vmselect/prometheus/metadata_response.qtpl:22
qw422016.N().S(`"unit":`)
//line app/vmselect/prometheus/metadata_response.qtpl:23
qw422016.N().Q(string(row.Unit))
//line app/vmselect/prometheus/metadata_response.qtpl:23
qw422016.N().S(`,`)
//line app/vmselect/prometheus/metadata_response.qtpl:24
}
//line app/vmselect/prometheus/metadata_response.qtpl:24
qw422016.N().S(`"help":`)
//line app/vmselect/prometheus/metadata_response.qtpl:25
qw422016.N().Q(string(row.Help))
//line app/vmselect/prometheus/metadata_response.qtpl:25
qw422016.N().S(`}]`)
//line app/vmselect/prometheus/metadata_response.qtpl:28
if currentItem != mapItems-1 {
//line app/vmselect/prometheus/metadata_response.qtpl:28
qw422016.N().S(`,`)
//line app/vmselect/prometheus/metadata_response.qtpl:28
}
//line app/vmselect/prometheus/metadata_response.qtpl:29
currentItem++
//line app/vmselect/prometheus/metadata_response.qtpl:30
}
//line app/vmselect/prometheus/metadata_response.qtpl:30
qw422016.N().S(`}`)
//line app/vmselect/prometheus/metadata_response.qtpl:32
streamdumpQueryTrace(qw422016, qt)
//line app/vmselect/prometheus/metadata_response.qtpl:32
qw422016.N().S(`}`)
//line app/vmselect/prometheus/metadata_response.qtpl:34
}
//line app/vmselect/prometheus/metadata_response.qtpl:34
func WriteMetadataResponse(qq422016 qtio422016.Writer, result []*metricsmetadata.Row, qt *querytracer.Tracer) {
//line app/vmselect/prometheus/metadata_response.qtpl:34
qw422016 := qt422016.AcquireWriter(qq422016)
//line app/vmselect/prometheus/metadata_response.qtpl:34
StreamMetadataResponse(qw422016, result, qt)
//line app/vmselect/prometheus/metadata_response.qtpl:34
qt422016.ReleaseWriter(qw422016)
//line app/vmselect/prometheus/metadata_response.qtpl:34
}
//line app/vmselect/prometheus/metadata_response.qtpl:34
func MetadataResponse(result []*metricsmetadata.Row, qt *querytracer.Tracer) string {
//line app/vmselect/prometheus/metadata_response.qtpl:34
qb422016 := qt422016.AcquireByteBuffer()
//line app/vmselect/prometheus/metadata_response.qtpl:34
WriteMetadataResponse(qb422016, result, qt)
//line app/vmselect/prometheus/metadata_response.qtpl:34
qs422016 := string(qb422016.B)
//line app/vmselect/prometheus/metadata_response.qtpl:34
qt422016.ReleaseByteBuffer(qb422016)
//line app/vmselect/prometheus/metadata_response.qtpl:34
return qs422016
//line app/vmselect/prometheus/metadata_response.qtpl:34
}

View File

@@ -639,37 +639,6 @@ func LabelsHandler(qt *querytracer.Tracer, startTime time.Time, w http.ResponseW
return nil
}
// MetadataHandler processes /api/v1/metadata request.
//
// See https://prometheus.io/docs/prometheus/latest/querying/api/#querying-metric-metadata
func MetadataHandler(qt *querytracer.Tracer, startTime time.Time, w http.ResponseWriter, r *http.Request) error {
limit, err := httputil.GetInt(r, "limit")
if err != nil {
return err
}
if limit < 0 {
limit = 0
}
metricName := r.FormValue("metric")
metadata, err := netstorage.GetMetricsMetadata(qt, limit, metricName)
if err != nil {
return fmt.Errorf("cannot get metadata: %w", err)
}
qt.Done()
w.Header().Set("Content-Type", "application/json")
bw := bufferedwriter.Get(w)
defer bufferedwriter.Put(bw)
WriteMetadataResponse(bw, metadata, qt)
if err := bw.Flush(); err != nil {
return fmt.Errorf("cannot send metadata response to remote client: %w", err)
}
return nil
}
var labelsDuration = metrics.NewSummary(`vm_request_duration_seconds{path="/api/v1/labels"}`)
// SeriesCountHandler processes /api/v1/series/count request.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -37,10 +37,10 @@
<meta property="og:title" content="UI for VictoriaMetrics">
<meta property="og:url" content="https://victoriametrics.com/">
<meta property="og:description" content="Explore and troubleshoot your VictoriaMetrics data">
<script type="module" crossorigin src="./assets/index-C4E6lDpP.js"></script>
<link rel="modulepreload" crossorigin href="./assets/vendor-D5YL0cqB.js">
<script type="module" crossorigin src="./assets/index-zpalCSif.js"></script>
<link rel="modulepreload" crossorigin href="./assets/vendor-DY9kCvzk.js">
<link rel="stylesheet" crossorigin href="./assets/vendor-D1GxaB_c.css">
<link rel="stylesheet" crossorigin href="./assets/index-DACH7WjD.css">
<link rel="stylesheet" crossorigin href="./assets/index-CBxdwuZH.css">
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>

View File

@@ -22,7 +22,6 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/lib/mergeset"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/querytracer"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage/metricsmetadata"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/stringsutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/syncwg"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/timeutil"
@@ -91,9 +90,6 @@ var (
"In most cases, this value should not be changed. The maximum allowed value is 23h.")
logNewSeriesAuthKey = flagutil.NewPassword("logNewSeriesAuthKey", "authKey, which must be passed in query string to /internal/log_new_series. It overrides -httpAuth.*")
metadataStorageSize = flagutil.NewBytes("storage.maxMetadataStorageSize", 0, "Overrides max size for metrics metadata entries in-memory storage. "+
"If set to 0 or a negative value, defaults to 1% of allowed memory.")
)
// CheckTimeRange returns true if the given tr is denied for querying.
@@ -124,7 +120,6 @@ func Init(resetCacheIfNeeded func(mrs []storage.MetricRow)) {
storage.SetTagFiltersCacheSize(cacheSizeIndexDBTagFilters.IntN())
storage.SetMetricNamesStatsCacheSize(cacheSizeMetricNamesStats.IntN())
storage.SetMetricNameCacheSize(cacheSizeStorageMetricName.IntN())
storage.SetMetadataStorageSize(metadataStorageSize.IntN())
mergeset.SetIndexBlocksCacheSize(cacheSizeIndexDBIndexBlocks.IntN())
mergeset.SetDataBlocksCacheSize(cacheSizeIndexDBDataBlocks.IntN())
mergeset.SetDataBlocksSparseCacheSize(cacheSizeIndexDBDataBlocksSparse.IntN())
@@ -199,19 +194,6 @@ func AddRows(mrs []storage.MetricRow) error {
return nil
}
// AddMetadataRows adds mrs to the storage.
//
// The caller should limit the number of concurrent calls to AddMetadataRows() in order to limit memory usage.
func AddMetadataRows(mms []metricsmetadata.Row) error {
if Storage.IsReadOnly() {
return errReadOnly
}
WG.Add(1)
Storage.AddMetadataRows(mms)
WG.Done()
return nil
}
var errReadOnly = errors.New("the storage is in read-only mode; check -storage.minFreeDiskSpaceBytes command-line flag value")
// RegisterMetricNames registers all the metrics from mrs in the storage.
@@ -628,13 +610,13 @@ func writeStorageMetrics(w io.Writer, strg *storage.Storage) {
metrics.WriteCounterUint64(w, `vm_missing_metric_names_for_metric_id_total`, idbm.MissingMetricNamesForMetricID)
metrics.WriteCounterUint64(w, `vm_date_metric_id_cache_syncs_total`, idbm.DateMetricIDCacheSyncsCount)
metrics.WriteCounterUint64(w, `vm_date_metric_id_cache_resets_total`, idbm.DateMetricIDCacheResetsCount)
metrics.WriteCounterUint64(w, `vm_date_metric_id_cache_syncs_total`, m.DateMetricIDCacheSyncsCount)
metrics.WriteCounterUint64(w, `vm_date_metric_id_cache_resets_total`, m.DateMetricIDCacheResetsCount)
metrics.WriteGaugeUint64(w, `vm_cache_entries{type="storage/tsid"}`, m.TSIDCacheSize)
metrics.WriteGaugeUint64(w, `vm_cache_entries{type="storage/metricIDs"}`, m.MetricIDCacheSize)
metrics.WriteGaugeUint64(w, `vm_cache_entries{type="storage/metricName"}`, m.MetricNameCacheSize)
metrics.WriteGaugeUint64(w, `vm_cache_entries{type="storage/date_metricID"}`, idbm.DateMetricIDCacheSize)
metrics.WriteGaugeUint64(w, `vm_cache_entries{type="storage/date_metricID"}`, m.DateMetricIDCacheSize)
metrics.WriteGaugeUint64(w, `vm_cache_entries{type="storage/hour_metric_ids"}`, m.HourMetricIDCacheSize)
metrics.WriteGaugeUint64(w, `vm_cache_entries{type="storage/next_day_metric_ids"}`, m.NextDayMetricIDCacheSize)
metrics.WriteGaugeUint64(w, `vm_cache_entries{type="storage/indexBlocks"}`, tm.IndexBlocksCacheSize)
@@ -652,12 +634,12 @@ func writeStorageMetrics(w io.Writer, strg *storage.Storage) {
metrics.WriteGaugeUint64(w, `vm_cache_size_bytes{type="indexdb/dataBlocks"}`, idbm.DataBlocksCacheSizeBytes)
metrics.WriteGaugeUint64(w, `vm_cache_size_bytes{type="indexdb/dataBlocksSparse"}`, idbm.DataBlocksSparseCacheSizeBytes)
metrics.WriteGaugeUint64(w, `vm_cache_size_bytes{type="indexdb/indexBlocks"}`, idbm.IndexBlocksCacheSizeBytes)
metrics.WriteGaugeUint64(w, `vm_cache_size_bytes{type="storage/date_metricID"}`, idbm.DateMetricIDCacheSizeBytes)
metrics.WriteGaugeUint64(w, `vm_cache_size_bytes{type="storage/date_metricID"}`, m.DateMetricIDCacheSizeBytes)
metrics.WriteGaugeUint64(w, `vm_cache_size_bytes{type="storage/hour_metric_ids"}`, m.HourMetricIDCacheSizeBytes)
metrics.WriteGaugeUint64(w, `vm_cache_size_bytes{type="storage/next_day_metric_ids"}`, m.NextDayMetricIDCacheSizeBytes)
metrics.WriteGaugeUint64(w, `vm_cache_size_bytes{type="indexdb/tagFiltersToMetricIDs"}`, idbm.TagFiltersToMetricIDsCacheSizeBytes)
metrics.WriteGaugeUint64(w, `vm_cache_size_bytes{type="storage/regexps"}`, storage.RegexpCacheSizeBytes())
metrics.WriteGaugeUint64(w, `vm_cache_size_bytes{type="storage/regexpPrefixes"}`, storage.RegexpPrefixesCacheSizeBytes())
metrics.WriteGaugeUint64(w, `vm_cache_size_bytes{type="storage/regexps"}`, uint64(storage.RegexpCacheSizeBytes()))
metrics.WriteGaugeUint64(w, `vm_cache_size_bytes{type="storage/regexpPrefixes"}`, uint64(storage.RegexpPrefixesCacheSizeBytes()))
metrics.WriteGaugeUint64(w, `vm_cache_size_max_bytes{type="storage/tsid"}`, m.TSIDCacheSizeMaxBytes)
metrics.WriteGaugeUint64(w, `vm_cache_size_max_bytes{type="storage/metricIDs"}`, m.MetricIDCacheSizeMaxBytes)
@@ -667,8 +649,8 @@ func writeStorageMetrics(w io.Writer, strg *storage.Storage) {
metrics.WriteGaugeUint64(w, `vm_cache_size_max_bytes{type="indexdb/dataBlocksSparse"}`, idbm.DataBlocksSparseCacheSizeMaxBytes)
metrics.WriteGaugeUint64(w, `vm_cache_size_max_bytes{type="indexdb/indexBlocks"}`, idbm.IndexBlocksCacheSizeMaxBytes)
metrics.WriteGaugeUint64(w, `vm_cache_size_max_bytes{type="indexdb/tagFiltersToMetricIDs"}`, idbm.TagFiltersToMetricIDsCacheSizeMaxBytes)
metrics.WriteGaugeUint64(w, `vm_cache_size_max_bytes{type="storage/regexps"}`, storage.RegexpCacheMaxSizeBytes())
metrics.WriteGaugeUint64(w, `vm_cache_size_max_bytes{type="storage/regexpPrefixes"}`, storage.RegexpPrefixesCacheMaxSizeBytes())
metrics.WriteGaugeUint64(w, `vm_cache_size_max_bytes{type="storage/regexps"}`, uint64(storage.RegexpCacheMaxSizeBytes()))
metrics.WriteGaugeUint64(w, `vm_cache_size_max_bytes{type="storage/regexpPrefixes"}`, uint64(storage.RegexpPrefixesCacheMaxSizeBytes()))
metrics.WriteCounterUint64(w, `vm_cache_requests_total{type="storage/tsid"}`, m.TSIDCacheRequests)
metrics.WriteCounterUint64(w, `vm_cache_requests_total{type="storage/metricIDs"}`, m.MetricIDCacheRequests)
@@ -692,8 +674,6 @@ func writeStorageMetrics(w io.Writer, strg *storage.Storage) {
metrics.WriteCounterUint64(w, `vm_cache_misses_total{type="storage/regexps"}`, storage.RegexpCacheMisses())
metrics.WriteCounterUint64(w, `vm_cache_misses_total{type="storage/regexpPrefixes"}`, storage.RegexpPrefixesCacheMisses())
metrics.WriteCounterUint64(w, `vm_cache_resets_total{type="indexdb/tagFiltersToMetricIDs"}`, idbm.TagFiltersToMetricIDsCacheResets)
metrics.WriteCounterUint64(w, `vm_deleted_metrics_total{type="indexdb"}`, m.DeletedMetricsCount)
metrics.WriteCounterUint64(w, `vm_cache_collisions_total{type="storage/tsid"}`, m.TSIDCacheCollisions)
@@ -709,11 +689,6 @@ func writeStorageMetrics(w io.Writer, strg *storage.Storage) {
metrics.WriteGaugeUint64(w, `vm_downsampling_partitions_scheduled`, tm.ScheduledDownsamplingPartitions)
metrics.WriteGaugeUint64(w, `vm_downsampling_partitions_scheduled_size_bytes`, tm.ScheduledDownsamplingPartitionsSize)
metrics.WriteGaugeUint64(w, `vm_metrics_metadata_storage_items`, m.MetadataStorageItemsCurrent)
metrics.WriteCounterUint64(w, `vm_metrics_metadata_storage_size_bytes`, m.MetadataStorageCurrentSizeBytes)
metrics.WriteCounterUint64(w, `vm_metrics_metadata_storage_max_size_bytes`, m.MetadataStorageMaxSizeBytes)
}
func jsonResponseError(w http.ResponseWriter, err error) {

View File

@@ -20,7 +20,6 @@ export interface ChartTooltipProps {
info?: ReactNode;
marker?: string;
show?: boolean;
duplicateCount?: number;
onClose?: (id: string) => void;
}
@@ -36,7 +35,6 @@ const ChartTooltip: FC<ChartTooltipProps> = ({
statsFormatted,
isSticky,
marker,
duplicateCount = 0,
onClose
}) => {
const tooltipRef = useRef<HTMLDivElement>(null);
@@ -158,7 +156,6 @@ const ChartTooltip: FC<ChartTooltipProps> = ({
<p className="vm-chart-tooltip-data__value">
<b>{value}</b>{unit}
</p>
{duplicateCount > 1 && <p>(overlapping points: {duplicateCount})</p>}
</div>
{statsFormatted && (
<table className="vm-chart-tooltip-stats">

View File

@@ -132,7 +132,7 @@ const BaseRule = ({ item }: BaseRuleProps) => {
<th>Series returned</th>
<th>Series fetched</th>
<th>Duration</th>
<th>Execution timestamp</th>
<th>Executed at</th>
</tr>
</thead>
<tbody>
@@ -154,7 +154,7 @@ const BaseRule = ({ item }: BaseRuleProps) => {
{!!item?.alerts?.length && (
<>
<span className="vm-alerts-title">Alerts</span>
<table className="vm-alerts-table">
<table>
<colgroup>
<col className="vm-col-sm"/>
<col className="vm-col-sm"/>
@@ -190,7 +190,7 @@ const BaseRule = ({ item }: BaseRuleProps) => {
</td>
<td>
<Badges
align="start"
align="center"
items={Object.fromEntries(Object.entries(alert.labels || {}).map(([name, value]) => [name, {
color: "passive",
value: value,

View File

@@ -44,7 +44,6 @@
word-break: break-word;
table-layout: fixed;
width: 100%;
td, th {
line-height: 30px;
padding: 4px $padding-small;
@@ -53,33 +52,15 @@
overflow: hidden;
text-overflow: ellipsis;
}
th {
white-space: nowrap;
}
td.align-center {
text-align: center
}
th {
font-weight: bold;
padding: 0 $padding-small;
}
}
.vm-alerts-table {
tr {
border-bottom: $border-divider;
&:hover {
background: $color-background-hover;
}
}
td {
vertical-align: top;
padding-block: $padding-small;
}
}
}

View File

@@ -46,6 +46,9 @@
.vm-text-field__input {
padding: 11px 28px;
}
.vm-text-field__icon-start {
height: 42px;
}
}
&__clear-icon {

View File

@@ -8,7 +8,7 @@
flex-direction: column;
position: relative;
&:has(>.vm-accordion-header_open) {
&:has(>details[open]) {
background-color: $color-background-item;
}

View File

@@ -61,7 +61,7 @@ const RulesHeader: FC<RulesHeaderProps> = ({
value={states}
list={allStates}
label="State"
placeholder="Please select rule state"
placeholder="Please rule state"
onChange={onChangeStates}
noOptionsText={noStateText}
includeAll

View File

@@ -26,6 +26,9 @@
.vm-text-field__input {
padding: 11px 28px;
}
.vm-text-field__icon-start {
height: 42px;
}
}
&__clear-icon {

View File

@@ -34,7 +34,7 @@
position: relative;
border-radius: $border-radius-small;
&:has(>.vm-accordion-header_open) {
&:has(>details[open]) {
background-color: $color-background-item;
}

View File

@@ -43,7 +43,7 @@ const ExploreMetricItem: FC<ExploreMetricItemGraphProps> = ({
const step = isHeatmap && customStep === defaultStep ? heatmapStep : customStep;
const queries = useMemo(() => {
const query = useMemo(() => {
const params = Object.entries({ job, instance })
.filter(val => val[1])
.map(([key, val]) => `${key}=${JSON.stringify(val)}`);
@@ -55,19 +55,19 @@ const ExploreMetricItem: FC<ExploreMetricItemGraphProps> = ({
const base = `{${params.join(",")}}`;
if (isBucket) {
return [`sum(rate(${base})) by (vmrange, le)`];
return `sum(rate(${base})) by (vmrange, le)`;
}
const queryBase = rateEnabled ? `rollup_rate(${base})` : `rollup(${base})`;
return [`
return `
with (q = ${queryBase}) (
alias(min(label_match(q, "rollup", "min")), "min"),
alias(max(label_match(q, "rollup", "max")), "max"),
alias(avg(label_match(q, "rollup", "avg")), "avg"),
)`];
)`;
}, [name, job, instance, rateEnabled, isBucket]);
const { isLoading, graphData, error, queryErrors, warning, isHistogram } = useFetchQuery({
predefinedQuery: queries,
predefinedQuery: [query],
visible: true,
customStep: step,
showAllSeries
@@ -98,7 +98,7 @@ with (q = ${queryBase}) (
{warning && (
<WarningLimitSeries
warning={warning}
query={queries}
query={[query]}
onChange={setShowAllSeries}
/>
)}
@@ -107,7 +107,7 @@ with (q = ${queryBase}) (
data={graphData}
period={period}
customStep={step}
query={queries}
query={[query]}
yaxis={yaxis}
setYaxisLimits={setYaxisLimits}
setPeriod={setPeriod}

View File

@@ -1,5 +1,4 @@
import { FC, useState, useEffect } from "preact/compat";
import classNames from "classnames";
import { JSX } from "preact";
import { ArrowDownIcon } from "../Icons";
import "./style.scss";
@@ -32,12 +31,9 @@ const Accordion: FC<AccordionProps> = ({
event.preventDefault();
return; // If the text is selected, cancel the execution of toggle.
}
setIsOpen((prev) => {
const newState = !prev;
onChange && onChange(newState);
return newState;
});
const details = event.currentTarget.parentElement as HTMLDetailsElement;
onChange && onChange(details.open);
setIsOpen(details.open);
};
useEffect(() => {
@@ -46,32 +42,23 @@ const Accordion: FC<AccordionProps> = ({
return (
<>
<header
className={classNames({
"vm-accordion-header": true,
"vm-accordion-header_open": isOpen,
})}
onClick={toggleOpen}
<details
className="vm-accordion-section"
key="content"
open={isOpen}
id={id}
>
{title}
<div
className={classNames({
"vm-accordion-header__arrow": true,
"vm-accordion-header__arrow_open": isOpen,
})}
<summary
className="vm-accordion-header"
onClick={toggleOpen}
>
<ArrowDownIcon />
</div>
</header>
{isOpen && (
<section
className="vm-accordion-section"
key="content"
>
{children}
</section>
)}
{title}
<div className="vm-accordion-header__arrow">
<ArrowDownIcon />
</div>
</summary>
{children}
</details>
</>
);
};

View File

@@ -17,10 +17,6 @@
transform: rotate(0);
transition: transform 200ms ease-in-out;
&_open {
transform: rotate(180deg);
}
svg {
width: 14px;
height: auto;
@@ -28,6 +24,14 @@
}
}
.vm-accordion-section[open] > summary {
& > .vm-accordion-header {
&__arrow {
transform: rotate(180deg);
}
}
}
.accordion-section {
overflow: hidden;
}

View File

@@ -137,7 +137,6 @@ const Select: FC<SelectProps> = ({
"vm-select_disabled": disabled
})}
>
{label && <span className="vm-text-field__label">{label}</span>}
<div
className="vm-select-input"
onClick={handleToggleList}
@@ -151,7 +150,7 @@ const Select: FC<SelectProps> = ({
onRemoveItem={handleSelected}
/>
)}
{!hideInput && (
{!hideInput && !selectedValues?.length && (
<input
value={textFieldValue}
type="text"
@@ -165,6 +164,7 @@ const Select: FC<SelectProps> = ({
/>
)}
</div>
{label && <span className="vm-text-field__label">{label}</span>}
{clearable && value && (
<div
className="vm-select-input__icon"

View File

@@ -1,8 +1,6 @@
@use "src/styles/variables" as *;
.vm-select {
position: relative;
display: grid;
&-input {
position: relative;
display: flex;

View File

@@ -89,8 +89,8 @@ const GraphView: FC<GraphViewProps> = ({
const [legendValue, setLegendValue] = useState<ChartTooltipProps | null>(null);
const getSeriesItem = useMemo(() => {
return getSeriesItemContext(data, hideSeries, alias, showAllPoints, isAnomalyView, isRawQuery);
}, [data, hideSeries, alias, showAllPoints, isAnomalyView, isRawQuery]);
return getSeriesItemContext(data, hideSeries, alias, showAllPoints, isAnomalyView);
}, [data, hideSeries, alias, showAllPoints, isAnomalyView]);
const setLimitsYaxis = (minVal: number, maxVal: number) => {
let min = Number.isFinite(minVal) ? minVal : 0;
@@ -144,8 +144,8 @@ const GraphView: FC<GraphViewProps> = ({
useEffect(() => {
const dLen = data.length;
const tsAnchor = data?.[0]?.values?.[0]?.[0];
const tsArray: number[] = [];
const tsAnchor = data?.[0]?.values?.[0]?.[0]
const tsSet = new Set<number>([])
const tempLegend = new Array<LegendItemType>(dLen);
const tempSeries = new Array<uPlotSeries>(dLen + 1);
tempSeries[0] = {};
@@ -162,7 +162,7 @@ const GraphView: FC<GraphViewProps> = ({
const vals = d.values;
for (let j = 0, vLen = vals.length; j < vLen; j++) {
const v = vals[j];
if (isRawQuery) tsArray.push(v[0]);
if (isRawQuery) tsSet.add(v[0])
const num = promValueToNumber(v[1]);
if (Number.isFinite(num)) {
if (num < minVal) minVal = num;
@@ -171,12 +171,12 @@ const GraphView: FC<GraphViewProps> = ({
}
}
const dpr = window.devicePixelRatio || 1;
const dpr = window.devicePixelRatio || 1
const widthPx = containerSize.width || window.innerWidth || 4096;
const pixels = Math.max(1, Math.floor(widthPx * Math.max(1, dpr)));
const timeSeries = isRawQuery
? tsArray.sort((a, b) => a - b)
? Array.from(tsSet).sort((a,b) => a - b)
: getTimeSeries(currentStep, period, pixels, tsAnchor);
const timeDataSeries: (number | null)[][] = data.map(d => {
@@ -195,8 +195,6 @@ const GraphView: FC<GraphViewProps> = ({
// Treat special values as nulls in order to satisfy uPlot.
// Otherwise it may draw unexpected graphs.
v = Number.isFinite(num) ? num : null;
// Advance to next value
j++;
}
results[k] = v;
}
@@ -283,7 +281,7 @@ const GraphView: FC<GraphViewProps> = ({
height={height}
isAnomalyView={isAnomalyView}
spanGaps={spanGaps}
showAllPoints={isRawQuery ? true : showAllPoints}
showAllPoints={showAllPoints}
/>
)}
{isHistogram && (

View File

@@ -49,26 +49,6 @@ const useLineTooltip = ({ u, metrics, series, unit, isAnomalyView }: LineTooltip
const max = u?.scales?.[1]?.max || 1;
const date = u?.data?.[0]?.[dataIdx] || 0;
let duplicateCount = 1;
if (u && seriesIdx > 0 && dataIdx >= 0) {
const xs = u.data[0] as (number | null)[];
const ys = u.data[seriesIdx] as (number | null)[];
const xVal = xs[dataIdx];
const yVal = ys[dataIdx];
if (xVal != null && yVal != null) {
duplicateCount = 0;
for (let i = 0; i < xs.length; i++) {
if (xs[i] === xVal && ys[i] === yVal) {
duplicateCount++;
}
}
}
}
const point = {
top: u ? u.valToPos((value || 0), seriesItem?.scale || "1") : 0,
left: u ? u.valToPos(date, "x") : 0,
@@ -85,7 +65,6 @@ const useLineTooltip = ({ u, metrics, series, unit, isAnomalyView }: LineTooltip
info: getMetricName(metricItem, seriesItem),
statsFormatted: seriesItem?.statsFormatted,
marker: `${seriesItem?.stroke}`,
duplicateCount,
};
}, [u, tooltipIdx, metrics, series, unit, isAnomalyView]);

View File

@@ -1,4 +1,5 @@
import { FC, useState } from "preact/compat";
import { FC, useEffect, useState } from "preact/compat";
import { useLocation } from "react-router";
import { useNotifiersSetQueryParams as useSetQueryParams } from "./hooks/useSetQueryParams";
import Spinner from "../../components/Main/Spinner/Spinner";
import Alert from "../../components/Main/Alert/Alert";
@@ -32,6 +33,37 @@ const ExploreNotifiers: FC = () => {
search: searchInput,
});
const location = useLocation();
const pageLoaded = !isLoading && !error && !!notifiers?.length;
const savedScrollTop = localStorage.getItem("scrollTop");
useEffect(() => {
if (!pageLoaded) return;
if (location.hash) {
const target = document.querySelector(location.hash);
if (target) {
let parent = target.closest("details");
while (parent) {
parent.open = true;
if (!parent?.parentElement) return;
parent = parent.parentElement.closest("details");
}
target.scrollIntoView();
}
} else {
if (savedScrollTop) {
window.scrollTo(0, parseInt(savedScrollTop));
}
const handleBeforeUnload = () => {
localStorage.setItem("scrollTop", (window.scrollY || 0).toString());
};
window.addEventListener("beforeunload", handleBeforeUnload);
return () => {
window.removeEventListener("beforeunload", handleBeforeUnload);
};
}
}, [location, savedScrollTop, pageLoaded]);
const handleChangeSearch = (input: string) => {
if (!input) {
setSearchInput("");

View File

@@ -1,5 +1,5 @@
import { FC, useEffect, useMemo, useState, useCallback } from "preact/compat";
import { useSearchParams } from "react-router";
import { useNavigate, useLocation, useSearchParams } from "react-router";
import { useRulesSetQueryParams as useSetQueryParams } from "./hooks/useSetQueryParams";
import Spinner from "../../components/Main/Spinner/Spinner";
import Alert from "../../components/Main/Alert/Alert";
@@ -33,9 +33,16 @@ const ExploreRules: FC = () => {
const [modalOpen, setModalOpen] = useState(true);
const [searchParams, setSearchParams] = useSearchParams();
const navigate = useNavigate();
const location = useLocation();
useEffect(() => {
setModalOpen(!!groupId);
}, [groupId]);
if (!location.hash && groupId) {
setModalOpen(true);
} else {
setModalOpen(false);
}
}, [location.hash, groupId]);
useSetQueryParams({
types: types.join("&"),
@@ -55,29 +62,29 @@ const ExploreRules: FC = () => {
}, [searchInput]);
const getModal = () => {
if (ruleId) {
if (ruleId !== "") {
return (
<ExploreRule
groupId={groupId}
id={ruleId}
mode={ruleId ? "rule" : "alert"}
onClose={handleClose}
mode={ruleId !== "" ? "rule" : "alert"}
onClose={handleClose(`rule-${ruleId}`)}
/>
);
} else if (alertId) {
} else if (alertId !== "") {
return (
<ExploreAlert
groupId={groupId}
id={alertId}
mode={ruleId ? "rule" : "alert"}
onClose={handleClose}
mode={ruleId !== "" ? "rule" : "alert"}
onClose={handleClose(`alert-${alertId}`)}
/>
);
} else if (groupId) {
} else if (groupId !== "") {
return (
<ExploreGroup
id={groupId}
onClose={handleClose}
onClose={handleClose(`group-${groupId}`)}
/>
);
}
@@ -85,13 +92,18 @@ const ExploreRules: FC = () => {
const noRuleFound = "No rules found!";
const handleClose = () => {
const newParams = new URLSearchParams(searchParams);
newParams.delete("group_id");
newParams.delete("rule_id");
newParams.delete("alert_id");
setSearchParams(newParams);
setModalOpen(false);
const handleClose = (id: string) => {
return () => {
const newParams = new URLSearchParams(searchParams);
newParams.delete("group_id");
newParams.delete("rule_id");
newParams.delete("alert_id");
setSearchParams(newParams);
setModalOpen(false);
navigate({
hash: `#${id}`,
});
};
};
const {
@@ -100,6 +112,36 @@ const ExploreRules: FC = () => {
error,
} = useFetchGroups({ blockFetch: modalOpen });
const pageLoaded = !isLoading && !error && !!groups?.length;
const savedScrollTop = localStorage.getItem("scrollTop");
useEffect(() => {
if (!pageLoaded) return;
if (location.hash) {
const target = document.querySelector(location.hash);
if (target) {
let parent = target.closest("details");
while (parent) {
parent.open = true;
if (!parent?.parentElement) return;
parent = parent.parentElement.closest("details");
}
target.scrollIntoView();
}
} else {
if (savedScrollTop) {
window.scrollTo(0, parseInt(savedScrollTop));
}
const updateScrollPosition = () => {
localStorage.setItem("scrollTop", (window.scrollY || 0).toString());
};
window.addEventListener("scroll", updateScrollPosition);
return () => {
window.removeEventListener("scroll", updateScrollPosition);
};
}
}, [location, savedScrollTop, pageLoaded]);
const { filteredGroups, allTypes, allStates } = useMemo(
() => filterGroups(groups || [], types, states, searchInput),
[groups, types, states, searchInput]

View File

@@ -1,8 +1,11 @@
@use "src/styles/variables" as *;
.vm-explore-alert-group {
content-visibility: auto;
width: 100%;
&:has(.vm-accordion-header_open) {
border: $border-divider;
border-radius: $border-radius-small;
}
}
.vm-explore-alerts.vm-modal {

View File

@@ -71,15 +71,7 @@ export const routerOptions: { [key: string]: RouterOptions } = {
[router.home]: getDefaultOptions(APP_TYPE),
[router.rawQuery]: {
title: "Raw query",
header: {
tenant: true,
stepControl: false,
timeSelector: true,
executionControls: {
tooltip: "Refresh dashboard",
useAutorefresh: true,
}
},
...routerOptionsDefault,
},
[router.metrics]: {
title: "Explore Prometheus metrics",

View File

@@ -1,111 +0,0 @@
import uPlot, { OrientCallback } from "uplot";
const deg360 = 2 * Math.PI;
// Base point size multiplier (in device pixels)
const BASE_POINT_SIZE = 4;
// Square size scale relative to circle size
const SQUARE_SIZE_SCALE = 1.2;
export const drawPoints = (u: uPlot, seriesIdx: number) => {
const size = BASE_POINT_SIZE * uPlot.pxRatio;
const r = size / 2;
const squareSize = size * SQUARE_SIZE_SCALE;
const squareHalf = squareSize / 2;
const orientCallback: OrientCallback = (
series,
dataX,
dataY,
scaleX,
scaleY,
valToPosX,
valToPosY,
xOff,
yOff,
xDim,
yDim,
_moveTo,
_lineTo,
rect,
arc,
) => {
const stroke = series?.stroke as unknown;
if (typeof stroke === "function") {
u.ctx.fillStyle = (stroke as () => string)();
}
const circlesPath = new Path2D();
const squaresPath = new Path2D();
const xMin = Number(scaleX.min);
const xMax = Number(scaleX.max);
const yMin = Number(scaleY.min);
const yMax = Number(scaleY.max);
const counts = new Map<string, number>();
const len = dataX.length;
for (let i = 0; i < len; i++) {
const xv = dataX[i];
const yv = dataY[i];
if (xv == null || yv == null) continue;
const xVal = Number(xv);
const yVal = Number(yv);
if (!Number.isFinite(xVal) || !Number.isFinite(yVal)) continue;
const key = `${xVal}|${yVal}`;
counts.set(key, (counts.get(key) ?? 0) + 1);
}
const duplicates = new Set<string>();
for (const [key, count] of counts) {
if (count > 1) duplicates.add(key);
}
for (let i = 0; i < len; i++) {
const xv = dataX[i];
const yv = dataY[i];
if (xv == null || yv == null) continue;
const xVal = Number(xv);
const yVal = Number(yv);
if (
!Number.isFinite(xVal) ||
!Number.isFinite(yVal) ||
xVal < xMin || xVal > xMax ||
yVal < yMin || yVal > yMax
) {
continue;
}
const cx = valToPosX(xVal, scaleX, xDim, xOff);
const cy = valToPosY(yVal, scaleY, yDim, yOff);
const key = `${xVal}|${yVal}`;
const isDuplicate = duplicates.has(key);
if (isDuplicate) {
rect(squaresPath, cx - squareHalf, cy - squareHalf, squareSize, squareSize);
} else {
circlesPath.moveTo(cx + r, cy);
arc(circlesPath, cx, cy, r, 0, deg360);
}
}
u.ctx.fill(circlesPath);
u.ctx.lineWidth = 1.4 * uPlot.pxRatio;
u.ctx.strokeStyle = u.ctx.fillStyle;
u.ctx.stroke(squaresPath);
};
uPlot.orient(u, seriesIdx, orientCallback);
return null;
};

View File

@@ -5,7 +5,6 @@ import { ForecastType, HideSeriesArgs, LegendItemType, SeriesItem } from "../../
import { anomalyColors, baseContrastColors, getColorFromString } from "../color";
import { getMathStats } from "../math";
import { formatPrettyNumber } from "./helpers";
import { drawPoints } from "./scatter";
// Helper function to extract freeFormFields values as a comma-separated string
export const extractFields = (metric: MetricBase["metric"]): string => {
@@ -32,7 +31,7 @@ export const isForecast = (metric: MetricBase["metric"]): ForecastMetricInfo =>
};
};
export const getSeriesItemContext = (data: MetricResult[], hideSeries: string[], alias: string[], showPoints?: boolean, isAnomalyUI?: boolean, isRawQuery?: boolean) => {
export const getSeriesItemContext = (data: MetricResult[], hideSeries: string[], alias: string[], showPoints?: boolean, isAnomalyUI?: boolean) => {
const colorState: {[key: string]: string} = {};
const maxColors = isAnomalyUI ? 0 : Math.min(data.length, baseContrastColors.length);
@@ -52,14 +51,13 @@ export const getSeriesItemContext = (data: MetricResult[], hideSeries: string[],
dash: getDashSeries(metricInfo),
width: getWidthSeries(metricInfo),
stroke: getStrokeSeries({ metricInfo, label, isAnomalyUI, colorState }),
points: getPointsSeries(metricInfo, showPoints, isRawQuery),
points: getPointsSeries(metricInfo, showPoints),
spanGaps: false,
forecast: metricInfo?.value,
forecastGroup: metricInfo?.group,
freeFormFields: d.metric,
show: !includesHideSeries(label, hideSeries),
scale: "1",
paths: isRawQuery ? drawPoints : undefined,
...getSeriesStatistics(d),
};
};
@@ -120,10 +118,10 @@ export const delSeries = (u: uPlot) => {
}
};
export const addSeries = (u: uPlot, series: uPlotSeries[], spanGaps = false, showPoints = false, isRawQuery?: boolean) => {
export const addSeries = (u: uPlot, series: uPlotSeries[], spanGaps = false, showPoints = false) => {
series.forEach((s,i) => {
if (s.label) s.spanGaps = spanGaps;
if (s.points) s.points.filter = showPoints || isRawQuery ? undefined : filterPoints;
if (s.points) s.points.filter = showPoints ? undefined : filterPoints;
i && u.addSeries(s);
});
};
@@ -159,17 +157,17 @@ const getWidthSeries = (metricInfo: ForecastMetricInfo | null): number => {
return 1.4;
};
const getPointsSeries = (metricInfo: ForecastMetricInfo | null, showPoints: boolean = false, isRawQuery?: boolean): uPlotSeries.Points => {
const getPointsSeries = (metricInfo: ForecastMetricInfo | null, showPoints: boolean = false): uPlotSeries.Points => {
const isAnomalyMetric = metricInfo?.value === ForecastType.anomaly;
if (isAnomalyMetric) {
return { size: 8, width: 4, space: 0 };
}
return {
size: isRawQuery ? 0 : 4,
size: 4,
width: 0,
show: true,
filter: showPoints || isRawQuery ? null : filterPoints,
filter: showPoints ? null : filterPoints,
};
};

View File

@@ -25,7 +25,6 @@ type PrometheusQuerier interface {
PrometheusAPIV1Labels(t *testing.T, query string, opts QueryOpts) *PrometheusAPIV1LabelsResponse
PrometheusAPIV1LabelValues(t *testing.T, labelName, query string, opts QueryOpts) *PrometheusAPIV1LabelValuesResponse
PrometheusAPIV1ExportNative(t *testing.T, query string, opts QueryOpts) []byte
PrometheusAPIV1Metadata(t *testing.T, metric string, limit int, opts QueryOpts) *PrometheusAPIV1Metadata
APIV1AdminTSDBDeleteSeries(t *testing.T, matchQuery string, opts QueryOpts)
@@ -38,7 +37,7 @@ type PrometheusQuerier interface {
// Writer contains methods for writing new data
type Writer interface {
// Prometheus APIs
PrometheusAPIV1Write(t *testing.T, wr prompb.WriteRequest, opts QueryOpts)
PrometheusAPIV1Write(t *testing.T, records []prompb.TimeSeries, opts QueryOpts)
PrometheusAPIV1ImportPrometheus(t *testing.T, records []string, opts QueryOpts)
PrometheusAPIV1ImportCSV(t *testing.T, records []string, opts QueryOpts)
PrometheusAPIV1ImportNative(t *testing.T, data []byte, opts QueryOpts)
@@ -351,33 +350,6 @@ func NewPrometheusAPIV1LabelValuesResponse(t *testing.T, s string) *PrometheusAP
return res
}
// PrometheusAPIV1Metadata is an inmemory representation of the
// /prometheus/api/v1/metadata response.
type PrometheusAPIV1Metadata struct {
Status string
IsPartial bool
Data map[string][]MetadataEntry
Trace *Trace
}
type MetadataEntry struct {
Type string
Help string
Unit string
}
// NewPrometheusAPIV1Metadata is a test helper function that creates a new
// instance of PrometheusAPIV1Metadata by unmarshalling a json string.
func NewPrometheusAPIV1Metadata(t *testing.T, s string) *PrometheusAPIV1Metadata {
t.Helper()
res := &PrometheusAPIV1Metadata{}
if err := json.Unmarshal([]byte(s), res); err != nil {
t.Fatalf("could not unmarshal series response data:\n%s\n err: %v", string(s), err)
}
return res
}
// Trace provides the description and the duration of some unit of work that has
// been performed during the request processing.
type Trace struct {

View File

@@ -99,39 +99,37 @@ func testDeduplication(tc *apptest.TestCase, sut apptest.PrometheusWriteQuerier,
ts3 := start.Add(3 * time.Second).UnixMilli()
ts5 := start.Add(5 * time.Second).UnixMilli()
ts10 := start.Add(10 * time.Second).UnixMilli()
data := prompb.WriteRequest{
Timeseries: []prompb.TimeSeries{
{
Labels: []prompb.Label{{Name: "__name__", Value: "metric1"}},
Samples: []prompb.Sample{
{Timestamp: ts1, Value: 3},
{Timestamp: ts3, Value: 10},
{Timestamp: ts5, Value: 5},
},
data := []prompb.TimeSeries{
{
Labels: []prompb.Label{{Name: "__name__", Value: "metric1"}},
Samples: []prompb.Sample{
{Timestamp: ts1, Value: 3},
{Timestamp: ts3, Value: 10},
{Timestamp: ts5, Value: 5},
},
{
Labels: []prompb.Label{{Name: "__name__", Value: "metric2"}},
Samples: []prompb.Sample{
{Timestamp: ts1, Value: 3},
{Timestamp: ts3, Value: decimal.StaleNaN},
{Timestamp: ts5, Value: 5},
},
},
{
Labels: []prompb.Label{{Name: "__name__", Value: "metric2"}},
Samples: []prompb.Sample{
{Timestamp: ts1, Value: 3},
{Timestamp: ts3, Value: decimal.StaleNaN},
{Timestamp: ts5, Value: 5},
},
{
Labels: []prompb.Label{{Name: "__name__", Value: "metric3"}},
Samples: []prompb.Sample{
{Timestamp: ts10, Value: 30},
{Timestamp: ts10, Value: 100},
{Timestamp: ts10, Value: 50},
},
},
{
Labels: []prompb.Label{{Name: "__name__", Value: "metric3"}},
Samples: []prompb.Sample{
{Timestamp: ts10, Value: 30},
{Timestamp: ts10, Value: 100},
{Timestamp: ts10, Value: 50},
},
{
Labels: []prompb.Label{{Name: "__name__", Value: "metric4"}},
Samples: []prompb.Sample{
{Timestamp: ts10, Value: 30},
{Timestamp: ts10, Value: decimal.StaleNaN},
{Timestamp: ts10, Value: 50},
},
},
{
Labels: []prompb.Label{{Name: "__name__", Value: "metric4"}},
Samples: []prompb.Sample{
{Timestamp: ts10, Value: 30},
{Timestamp: ts10, Value: decimal.StaleNaN},
{Timestamp: ts10, Value: 50},
},
},
}

View File

@@ -158,11 +158,7 @@ func TestSingleIngestionProtocols(t *testing.T) {
// prometheus text exposition format
sut.PrometheusAPIV1ImportPrometheus(t, []string{
`# HELP importprometheus_series some help message`,
`# TYPE importprometheus_series gauge`,
`importprometheus_series 10 1707123456700`, // 2024-02-05T08:57:36.700Z
`# HELP importprometheus_series2 some help message second one`,
`# TYPE importprometheus_series2 gauge`,
`importprometheus_series 10 1707123456700`, // 2024-02-05T08:57:36.700Z
`importprometheus_series2{label="foo",label1="value1"} 20 1707123456800`, // 2024-02-05T08:57:36.800Z
}, apptest.QueryOpts{
ExtraLabels: []string{"el1=elv1", "el2=elv2"},
@@ -191,58 +187,42 @@ func TestSingleIngestionProtocols(t *testing.T) {
})
// prometheus remote write format
pbData := prompb.WriteRequest{
Timeseries: []prompb.TimeSeries{
{
Labels: []prompb.Label{
{
Name: "__name__",
Value: "prometheusrw_series",
},
},
Samples: []prompb.Sample{
{
Value: 10,
Timestamp: 1707123456700, // 2024-02-05T08:57:36.700Z
},
pbData := []prompb.TimeSeries{
{
Labels: []prompb.Label{
{
Name: "__name__",
Value: "prometheusrw_series",
},
},
{
Labels: []prompb.Label{
{
Name: "__name__",
Value: "prometheusrw_series2",
},
{
Name: "label",
Value: "foo2",
},
{
Name: "label1",
Value: "value1",
},
},
Samples: []prompb.Sample{
{
Value: 20,
Timestamp: 1707123456800, // 2024-02-05T08:57:36.800Z
},
Samples: []prompb.Sample{
{
Value: 10,
Timestamp: 1707123456700, // 2024-02-05T08:57:36.700Z
},
},
},
Metadata: []prompb.MetricMetadata{
{
Type: 1,
MetricFamilyName: "prometheusrw_series",
Help: "some help",
Unit: "",
{
Labels: []prompb.Label{
{
Name: "__name__",
Value: "prometheusrw_series2",
},
{
Name: "label",
Value: "foo2",
},
{
Name: "label1",
Value: "value1",
},
},
{
Type: 1,
MetricFamilyName: "prometheusrw_series2",
Help: "some help2",
Unit: "",
Samples: []prompb.Sample{
{
Value: 20,
Timestamp: 1707123456800, // 2024-02-05T08:57:36.800Z
},
},
},
}
@@ -265,6 +245,7 @@ func TestSingleIngestionProtocols(t *testing.T) {
{Timestamp: 1707123456800, Value: 20}, // 2024-02-05T08:57:36.700Z
},
})
}
func TestClusterIngestionProtocols(t *testing.T) {
@@ -316,11 +297,7 @@ func TestClusterIngestionProtocols(t *testing.T) {
// prometheus text exposition format
vminsert.PrometheusAPIV1ImportPrometheus(t, []string{
`# HELP importprometheus_series some help message`,
`# TYPE importprometheus_series gauge`,
`importprometheus_series 10 1707123456700`, // 2024-02-05T08:57:36.700Z
`# HELP importprometheus_series2 some help message second one`,
`# TYPE importprometheus_series2 gauge`,
`importprometheus_series 10 1707123456700`, // 2024-02-05T08:57:36.700Z
`importprometheus_series2{label="foo",label1="value1"} 20 1707123456800`, // 2024-02-05T08:57:36.800Z
}, apptest.QueryOpts{
ExtraLabels: []string{"el1=elv1", "el2=elv2"},
@@ -457,58 +434,42 @@ func TestClusterIngestionProtocols(t *testing.T) {
})
// prometheus remote write format
pbData := prompb.WriteRequest{
Timeseries: []prompb.TimeSeries{
{
Labels: []prompb.Label{
{
Name: "__name__",
Value: "prometheusrw_series",
},
},
Samples: []prompb.Sample{
{
Value: 10,
Timestamp: 1707123456700, // 2024-02-05T08:57:36.700Z
},
pbData := []prompb.TimeSeries{
{
Labels: []prompb.Label{
{
Name: "__name__",
Value: "prometheusrw_series",
},
},
{
Labels: []prompb.Label{
{
Name: "__name__",
Value: "prometheusrw_series2",
},
{
Name: "label",
Value: "foo2",
},
{
Name: "label1",
Value: "value1",
},
},
Samples: []prompb.Sample{
{
Value: 20,
Timestamp: 1707123456800, // 2024-02-05T08:57:36.800Z
},
Samples: []prompb.Sample{
{
Value: 10,
Timestamp: 1707123456700, // 2024-02-05T08:57:36.700Z
},
},
},
Metadata: []prompb.MetricMetadata{
{
Type: 1,
MetricFamilyName: "prometheusrw_series",
Help: "some help",
Unit: "",
{
Labels: []prompb.Label{
{
Name: "__name__",
Value: "prometheusrw_series2",
},
{
Name: "label",
Value: "foo2",
},
{
Name: "label1",
Value: "value1",
},
},
{
Type: 1,
MetricFamilyName: "prometheusrw_series2",
Help: "some help2",
Unit: "",
Samples: []prompb.Sample{
{
Value: 20,
Timestamp: 1707123456800, // 2024-02-05T08:57:36.800Z
},
},
},
}

View File

@@ -1,225 +0,0 @@
package tests
import (
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/VictoriaMetrics/VictoriaMetrics/apptest"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompb"
)
func TestSingleMetricsMetadata(t *testing.T) {
fs.MustRemoveDir(t.Name())
tc := apptest.NewTestCase(t)
defer tc.Stop()
sut := tc.MustStartVmsingle("vmsingle", []string{
"-storageDataPath=" + tc.Dir(),
"-retentionPeriod=100y",
"-enableMetadata",
})
// verify empty stats
resp := sut.PrometheusAPIV1Metadata(t, "", 0, apptest.QueryOpts{})
if len(resp.Data) != 0 {
t.Fatalf("unexpected resp Records: %d, want: %d", len(resp.Data), 0)
}
const ingestTimestamp = 1707123456700
prometheusTextDataSet := []string{
`# HELP metric_name_1 some help message`,
`# TYPE metric_name_1 gauge`,
`metric_name_1{label="foo"} 10`,
`metric_name_1{label="bar"} 10`,
`metric_name_1{label="baz"} 10`,
`# HELP metric_name_2 some help message`,
`# TYPE metric_name_2 counter`,
`metric_name_2{label="baz"} 20`,
`# HELP metric_name_3 some help message`,
`# TYPE metric_name_3 gauge`,
`metric_name_3{label="baz"} 30`,
}
prometheusRemoteWriteDataSet := prompb.WriteRequest{
Timeseries: []prompb.TimeSeries{
{Labels: []prompb.Label{{Name: "__name__", Value: "metric_name_4"}}, Samples: []prompb.Sample{{Value: 40, Timestamp: ingestTimestamp}}},
{Labels: []prompb.Label{{Name: "__name__", Value: "metric_name_5"}}, Samples: []prompb.Sample{{Value: 40, Timestamp: ingestTimestamp}}},
{Labels: []prompb.Label{{Name: "__name__", Value: "metric_name_6"}}, Samples: []prompb.Sample{{Value: 40, Timestamp: ingestTimestamp}}},
},
Metadata: []prompb.MetricMetadata{
{MetricFamilyName: "metric_name_4", Help: "some help message", Type: uint32(prompb.MetricMetadataSUMMARY)},
{MetricFamilyName: "metric_name_5", Help: "some help message", Type: uint32(prompb.MetricMetadataSUMMARY)},
{MetricFamilyName: "metric_name_6", Help: "some help message", Type: uint32(prompb.MetricMetadataSTATESET)},
},
}
sut.PrometheusAPIV1ImportPrometheus(t, prometheusTextDataSet, apptest.QueryOpts{})
sut.PrometheusAPIV1Write(t, prometheusRemoteWriteDataSet, apptest.QueryOpts{})
sut.ForceFlush(t)
expected := &apptest.PrometheusAPIV1Metadata{
Status: "success",
Data: map[string][]apptest.MetadataEntry{
"metric_name_1": {{Help: "some help message", Type: "gauge"}},
"metric_name_2": {{Help: "some help message", Type: "counter"}},
"metric_name_3": {{Help: "some help message", Type: "gauge"}},
"metric_name_4": {{Help: "some help message", Type: "summary"}},
"metric_name_5": {{Help: "some help message", Type: "summary"}},
"metric_name_6": {{Help: "some help message", Type: "stateset"}},
},
}
gotStats := sut.PrometheusAPIV1Metadata(t, "", 0, apptest.QueryOpts{})
if diff := cmp.Diff(expected, gotStats); diff != "" {
t.Errorf("unexpected response (-want, +got):\n%s", diff)
}
// check query metric name filter
tc.Assert(&apptest.AssertOptions{
Msg: "unexpected /api/v1/metadata response",
Got: func() any {
return sut.PrometheusAPIV1Metadata(t, "metric_name_4", 0, apptest.QueryOpts{})
},
Want: &apptest.PrometheusAPIV1Metadata{
Status: "success",
Data: map[string][]apptest.MetadataEntry{
"metric_name_4": {{Help: "some help message", Type: "summary"}},
},
},
})
// check query limit filter
tc.Assert(&apptest.AssertOptions{
Msg: "unexpected /api/v1/metadata response",
Got: func() any {
return sut.PrometheusAPIV1Metadata(t, "", 3, apptest.QueryOpts{})
},
Want: &apptest.PrometheusAPIV1Metadata{
Status: "success",
Data: map[string][]apptest.MetadataEntry{
"metric_name_1": {{Help: "some help message", Type: "gauge"}},
"metric_name_2": {{Help: "some help message", Type: "counter"}},
"metric_name_3": {{Help: "some help message", Type: "gauge"}},
},
},
})
}
func TestClusterMetricsMetadata(t *testing.T) {
fs.MustRemoveDir(t.Name())
tc := apptest.NewTestCase(t)
defer tc.Stop()
vmstorage1 := tc.MustStartVmstorage("vmstorage-1", []string{
"-storageDataPath=" + tc.Dir() + "/vmstorage-1",
"-retentionPeriod=100y",
})
vmstorage2 := tc.MustStartVmstorage("vmstorage-2", []string{
"-storageDataPath=" + tc.Dir() + "/vmstorage-2",
"-retentionPeriod=100y",
})
vminsert1 := tc.MustStartVminsert("vminsert1", []string{
fmt.Sprintf("-storageNode=%s,%s", vmstorage1.VminsertAddr(), vmstorage2.VminsertAddr()),
"-enableMetadata",
})
vminsert2 := tc.MustStartVminsert("vminsert-2", []string{
fmt.Sprintf("-storageNode=%s,%s", vmstorage1.VminsertAddr(), vmstorage2.VminsertAddr()),
"-enableMetadata",
})
vminsertGlobal := tc.MustStartVminsert("vminsert-global", []string{
fmt.Sprintf("-storageNode=%s,%s", vminsert1.ClusternativeListenAddr(), vminsert2.ClusternativeListenAddr()),
"-enableMetadata",
})
vmselect := tc.MustStartVmselect("vmselect", []string{
fmt.Sprintf("-storageNode=%s,%s", vmstorage1.VmselectAddr(), vmstorage2.VmselectAddr()),
})
// verify empty stats
resp := vmselect.PrometheusAPIV1Metadata(t, "", 0, apptest.QueryOpts{Tenant: "0:0"})
if len(resp.Data) != 0 {
t.Fatalf("unexpected resp Records: %d, want: %d", len(resp.Data), 0)
}
const ingestTimestamp = 1707123456700
prometheusTextDataSet := []string{
`# HELP metric_name_1 some help message`,
`# TYPE metric_name_1 gauge`,
`metric_name_1{label="foo"} 10`,
`metric_name_1{label="bar"} 10`,
`metric_name_1{label="baz"} 10`,
`# HELP metric_name_2 some help message`,
`# TYPE metric_name_2 counter`,
`metric_name_2{label="baz"} 20`,
`# HELP metric_name_3 some help message`,
`# TYPE metric_name_3 gauge`,
`metric_name_3{label="baz"} 30`,
}
prometheusRemoteWriteDataSet := prompb.WriteRequest{
Timeseries: []prompb.TimeSeries{
{Labels: []prompb.Label{{Name: "__name__", Value: "metric_name_4"}}, Samples: []prompb.Sample{{Value: 40, Timestamp: ingestTimestamp}}},
{Labels: []prompb.Label{{Name: "__name__", Value: "metric_name_5"}}, Samples: []prompb.Sample{{Value: 40, Timestamp: ingestTimestamp}}},
{Labels: []prompb.Label{{Name: "__name__", Value: "metric_name_6"}}, Samples: []prompb.Sample{{Value: 40, Timestamp: ingestTimestamp}}},
},
Metadata: []prompb.MetricMetadata{
{MetricFamilyName: "metric_name_4", Help: "some help message", Type: uint32(prompb.MetricMetadataSUMMARY)},
{MetricFamilyName: "metric_name_5", Help: "some help message", Type: uint32(prompb.MetricMetadataSUMMARY)},
{MetricFamilyName: "metric_name_6", Help: "some help message", Type: uint32(prompb.MetricMetadataSTATESET)},
},
}
assertMetadataIngestOn := func(t *testing.T, vminsert *apptest.Vminsert, tenantID string) {
t.Helper()
vminsert.PrometheusAPIV1ImportPrometheus(t, prometheusTextDataSet, apptest.QueryOpts{Tenant: tenantID})
vminsert.PrometheusAPIV1Write(t, prometheusRemoteWriteDataSet, apptest.QueryOpts{Tenant: tenantID})
vmstorage1.ForceFlush(t)
vmstorage2.ForceFlush(t)
expected := &apptest.PrometheusAPIV1Metadata{
Status: "success",
Data: map[string][]apptest.MetadataEntry{
"metric_name_1": {{Help: "some help message", Type: "gauge"}},
"metric_name_2": {{Help: "some help message", Type: "counter"}},
"metric_name_3": {{Help: "some help message", Type: "gauge"}},
"metric_name_4": {{Help: "some help message", Type: "summary"}},
"metric_name_5": {{Help: "some help message", Type: "summary"}},
"metric_name_6": {{Help: "some help message", Type: "stateset"}},
},
}
gotStats := vmselect.PrometheusAPIV1Metadata(t, "", 0, apptest.QueryOpts{Tenant: tenantID})
if diff := cmp.Diff(expected, gotStats); diff != "" {
t.Errorf("unexpected response (-want, +got):\n%s", diff)
}
}
assertMetadataIngestOn(t, vminsert1, "2:2")
assertMetadataIngestOn(t, vminsert2, "3:3")
assertMetadataIngestOn(t, vminsertGlobal, "5:5")
// check query metric name filter
tc.Assert(&apptest.AssertOptions{
Msg: "unexpected /api/v1/metadata response",
Got: func() any {
return vmselect.PrometheusAPIV1Metadata(t, "metric_name_4", 0, apptest.QueryOpts{Tenant: "multitenant"})
},
Want: &apptest.PrometheusAPIV1Metadata{
Status: "success",
Data: map[string][]apptest.MetadataEntry{
"metric_name_4": {{Help: "some help message", Type: "summary"}},
},
},
})
// check query limit filter
tc.Assert(&apptest.AssertOptions{
Msg: "unexpected /api/v1/metadata response",
Got: func() any {
return vmselect.PrometheusAPIV1Metadata(t, "", 3, apptest.QueryOpts{Tenant: "5:5"})
},
Want: &apptest.PrometheusAPIV1Metadata{
Status: "success",
Data: map[string][]apptest.MetadataEntry{
"metric_name_1": {{Help: "some help message", Type: "gauge"}},
"metric_name_2": {{Help: "some help message", Type: "counter"}},
"metric_name_3": {{Help: "some help message", Type: "gauge"}},
},
},
})
}

View File

@@ -47,16 +47,14 @@ func TestClusterInstantQuery(t *testing.T) {
}
func testInstantQueryWithUTFNames(t *testing.T, sut apptest.PrometheusWriteQuerier) {
data := prompb.WriteRequest{
Timeseries: []prompb.TimeSeries{
{
Labels: []prompb.Label{
{Name: "__name__", Value: "3fooµ¥"},
{Name: "3👋tfにちは", Value: "漢©®€£"},
},
Samples: []prompb.Sample{
{Value: 1, Timestamp: millis("2024-01-01T00:01:00Z")},
},
data := []prompb.TimeSeries{
{
Labels: []prompb.Label{
{Name: "__name__", Value: "3fooµ¥"},
{Name: "3👋tfにちは", Value: "漢©®€£"},
},
Samples: []prompb.Sample{
{Value: 1, Timestamp: millis("2024-01-01T00:01:00Z")},
},
},
}
@@ -91,25 +89,23 @@ func testInstantQueryWithUTFNames(t *testing.T, sut apptest.PrometheusWriteQueri
fn(`{"3👋tfにちは"="漢©®€£"}`)
}
var staleNaNsData = func() prompb.WriteRequest {
return prompb.WriteRequest{
Timeseries: []prompb.TimeSeries{
{
Labels: []prompb.Label{
{
Name: "__name__",
Value: "metric",
},
var staleNaNsData = func() []prompb.TimeSeries {
return []prompb.TimeSeries{
{
Labels: []prompb.Label{
{
Name: "__name__",
Value: "metric",
},
Samples: []prompb.Sample{
{
Value: 1,
Timestamp: millis("2024-01-01T00:01:00Z"),
},
{
Value: decimal.StaleNaN,
Timestamp: millis("2024-01-01T00:02:00Z"),
},
},
Samples: []prompb.Sample{
{
Value: 1,
Timestamp: millis("2024-01-01T00:01:00Z"),
},
{
Value: decimal.StaleNaN,
Timestamp: millis("2024-01-01T00:02:00Z"),
},
},
},
@@ -189,23 +185,21 @@ func testInstantQueryDoesNotReturnStaleNaNs(t *testing.T, sut apptest.Prometheus
// However, conversion of math.NaN to int64 could behave differently depending on platform and Go version.
// Hence, this test could succeed for some platforms even if fix is rolled back.
func testQueryRangeWithAtModifier(t *testing.T, sut apptest.PrometheusWriteQuerier) {
data := prompb.WriteRequest{
Timeseries: []prompb.TimeSeries{
{
Labels: []prompb.Label{
{Name: "__name__", Value: "up"},
},
Samples: []prompb.Sample{
{Value: 1, Timestamp: millis("2025-01-01T00:01:00Z")},
},
data := []prompb.TimeSeries{
{
Labels: []prompb.Label{
{Name: "__name__", Value: "up"},
},
{
Labels: []prompb.Label{
{Name: "__name__", Value: "metricNaN"},
},
Samples: []prompb.Sample{
{Value: decimal.StaleNaN, Timestamp: millis("2025-01-01T00:01:00Z")},
},
Samples: []prompb.Sample{
{Value: 1, Timestamp: millis("2025-01-01T00:01:00Z")},
},
},
{
Labels: []prompb.Label{
{Name: "__name__", Value: "metricNaN"},
},
Samples: []prompb.Sample{
{Value: decimal.StaleNaN, Timestamp: millis("2025-01-01T00:01:00Z")},
},
},
}

View File

@@ -139,43 +139,41 @@ func TestSingleIngestionWithRelabeling(t *testing.T) {
},
})
pbData := prompb.WriteRequest{
Timeseries: []prompb.TimeSeries{
{
Labels: []prompb.Label{
{
Name: "__name__",
Value: "prometheusrw_series",
},
{
Name: "label",
Value: "foo2",
},
pbData := []prompb.TimeSeries{
{
Labels: []prompb.Label{
{
Name: "__name__",
Value: "prometheusrw_series",
},
Samples: []prompb.Sample{
{
Value: 10,
Timestamp: 1707123456700, // 2024-02-05T08:57:36.700Z
},
{
Name: "label",
Value: "foo2",
},
},
{
Labels: []prompb.Label{
{
Name: "__name__",
Value: "must_drop_series",
},
{
Name: "label",
Value: "foo2",
},
Samples: []prompb.Sample{
{
Value: 10,
Timestamp: 1707123456700, // 2024-02-05T08:57:36.700Z
},
Samples: []prompb.Sample{
{
Value: 20,
Timestamp: 1707123456800, // 2024-02-05T08:57:36.800Z
},
},
},
{
Labels: []prompb.Label{
{
Name: "__name__",
Value: "must_drop_series",
},
{
Name: "label",
Value: "foo2",
},
},
Samples: []prompb.Sample{
{
Value: 20,
Timestamp: 1707123456800, // 2024-02-05T08:57:36.800Z
},
},
},

View File

@@ -973,7 +973,7 @@ func testGroupSkipSlowReplicas(tc *apptest.TestCase, opts *testGroupReplicationO
// The data is replicated across N groups of M nodes. Replication factor is
// globalRF. There is no replication across the nodes within each group or
// it is unknown it there is one.
//it is unknown it there is one.
//
// Max number of nodes to skip is M*(globalRF-1). This corresponds to the
// case when N-globalRF+1 groups have received the response from all of

View File

@@ -11,7 +11,6 @@ import (
"github.com/golang/snappy"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prommetadata"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompb"
)
@@ -59,11 +58,10 @@ func StartVminsert(instance string, flags []string, cli *Client, output io.Write
app, stderrExtracts, err := startApp(instance, "../../bin/vminsert", flags, &appOptions{
defaultFlags: map[string]string{
"-httpListenAddr": "127.0.0.1:0",
"-clusternativeListenAddr": "127.0.0.1:0",
"-graphiteListenAddr": ":0",
"-opentsdbListenAddr": "127.0.0.1:0",
"-clusternative.vminsertConnsShutdownDuration": "1ms",
"-httpListenAddr": "127.0.0.1:0",
"-clusternativeListenAddr": "127.0.0.1:0",
"-graphiteListenAddr": ":0",
"-opentsdbListenAddr": "127.0.0.1:0",
},
extractREs: extractREs,
output: output,
@@ -202,16 +200,13 @@ func (app *Vminsert) OpenTSDBAPIPut(t *testing.T, records []string, opts QueryOp
// PrometheusAPIV1Write is a test helper function that inserts a
// collection of records in Prometheus remote-write format by sending a HTTP
// POST request to /prometheus/api/v1/write vminsert endpoint.
func (app *Vminsert) PrometheusAPIV1Write(t *testing.T, wr prompb.WriteRequest, opts QueryOpts) {
func (app *Vminsert) PrometheusAPIV1Write(t *testing.T, records []prompb.TimeSeries, opts QueryOpts) {
t.Helper()
url := fmt.Sprintf("http://%s/insert/%s/prometheus/api/v1/write", app.httpListenAddr, opts.getTenant())
wr := prompb.WriteRequest{Timeseries: records}
data := snappy.Encode(nil, wr.MarshalProtobuf(nil))
recordsCount := len(wr.Timeseries)
if prommetadata.IsEnabled() {
recordsCount += len(wr.Metadata)
}
app.sendBlocking(t, recordsCount, func() {
app.sendBlocking(t, len(records), func() {
_, statusCode := app.cli.Post(t, url, "application/x-protobuf", data)
if statusCode != http.StatusNoContent {
t.Fatalf("unexpected status code: got %d, want %d", statusCode, http.StatusNoContent)
@@ -235,19 +230,7 @@ func (app *Vminsert) PrometheusAPIV1ImportPrometheus(t *testing.T, records []str
url += "?" + uvs
}
data := []byte(strings.Join(records, "\n"))
var recordsCount int
var metadataRecords int
for _, record := range records {
if strings.HasPrefix(record, "#") {
metadataRecords++
continue
}
recordsCount++
}
if prommetadata.IsEnabled() {
recordsCount += metadataRecords
}
app.sendBlocking(t, recordsCount, func() {
app.sendBlocking(t, len(records), func() {
_, statusCode := app.cli.Post(t, url, "text/plain", data)
if statusCode != http.StatusNoContent {
t.Fatalf("unexpected status code: got %d, want %d", statusCode, http.StatusNoContent)
@@ -284,8 +267,7 @@ func (app *Vminsert) sendBlocking(t *testing.T, numRecordsToSend int, send func(
)
wantRowsSentCount := app.rpcRowsSentTotal(t) + numRecordsToSend
for range retries {
d := app.rpcRowsSentTotal(t)
if d >= wantRowsSentCount {
if app.rpcRowsSentTotal(t) >= wantRowsSentCount {
return
}
time.Sleep(period)

View File

@@ -6,7 +6,6 @@ import (
"io"
"net/http"
"regexp"
"strconv"
"testing"
)
@@ -187,20 +186,6 @@ func (app *Vmselect) PrometheusAPIV1LabelValues(t *testing.T, labelName, matchQu
return NewPrometheusAPIV1LabelValuesResponse(t, res)
}
// PrometheusAPIV1Metadata sends a query to a /prometheus/api/v1/metadata endpoint
// and returns the results.
func (app *Vmselect) PrometheusAPIV1Metadata(t *testing.T, metric string, limit int, opts QueryOpts) *PrometheusAPIV1Metadata {
t.Helper()
values := opts.asURLValues()
values.Add("metric", metric)
values.Add("limit", strconv.Itoa(limit))
queryURL := fmt.Sprintf("http://%s/select/%s/prometheus/api/v1/metadata", app.httpListenAddr, opts.getTenant())
res, _ := app.cli.PostForm(t, queryURL, values)
return NewPrometheusAPIV1Metadata(t, res)
}
// APIV1AdminTSDBDeleteSeries deletes the series that match the query by sending
// a request to /api/v1/admin/tsdb/delete_series.
//

View File

@@ -7,7 +7,6 @@ import (
"net/http"
"os"
"regexp"
"strconv"
"strings"
"testing"
"time"
@@ -212,9 +211,10 @@ func (app *Vmsingle) OpenTSDBAPIPut(t *testing.T, records []string, opts QueryOp
// PrometheusAPIV1Write is a test helper function that inserts a
// collection of records in Prometheus remote-write format by sending a HTTP
// POST request to /prometheus/api/v1/write vmsingle endpoint.
func (app *Vmsingle) PrometheusAPIV1Write(t *testing.T, wr prompb.WriteRequest, _ QueryOpts) {
func (app *Vmsingle) PrometheusAPIV1Write(t *testing.T, records []prompb.TimeSeries, _ QueryOpts) {
t.Helper()
wr := prompb.WriteRequest{Timeseries: records}
data := snappy.Encode(nil, wr.MarshalProtobuf(nil))
_, statusCode := app.cli.Post(t, app.prometheusAPIV1WriteURL, "application/x-protobuf", data)
if statusCode != http.StatusNoContent {
@@ -364,20 +364,6 @@ func (app *Vmsingle) PrometheusAPIV1LabelValues(t *testing.T, labelName, matchQu
return NewPrometheusAPIV1LabelValuesResponse(t, res)
}
// PrometheusAPIV1Metadata sends a query to a /prometheus/api/v1/metadata endpoint
// and returns the results.
func (app *Vmsingle) PrometheusAPIV1Metadata(t *testing.T, metric string, limit int, opts QueryOpts) *PrometheusAPIV1Metadata {
t.Helper()
values := opts.asURLValues()
values.Add("metric", metric)
values.Add("limit", strconv.Itoa(limit))
queryURL := fmt.Sprintf("http://%s/prometheus/api/v1/metadata", app.httpListenAddr)
res, _ := app.cli.PostForm(t, queryURL, values)
return NewPrometheusAPIV1Metadata(t, res)
}
// APIV1AdminTSDBDeleteSeries deletes the series that match the query by sending
// a request to /api/v1/admin/tsdb/delete_series.
//

File diff suppressed because it is too large Load Diff

View File

@@ -352,6 +352,10 @@
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},

File diff suppressed because it is too large Load Diff

View File

@@ -353,6 +353,10 @@
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},

View File

@@ -119,75 +119,45 @@
"type": "victoriametrics-metrics-datasource",
"uid": "$ds"
},
"description": "Shows the rate of requests.",
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
"mode": "palette-classic"
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 3,
"w": 7,
"x": 0,
"y": 1
},
"id": 4,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "12.2.0",
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"uid": "$ds"
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"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"
}
},
"editorMode": "code",
"exemplar": false,
"expr": "(sum(rate(vmauth_user_requests_total{job=~\"$job\", instance=~\"$instance\", username=~\"$user\"}[$__rate_interval])) or 0) + (sum(rate(vmauth_unauthorized_user_requests_total{job=~\"$job\", instance=~\"$instance\"}[$__rate_interval])) or 0)",
"instant": true,
"range": false,
"refId": "A"
}
],
"title": "Requests rate",
"type": "stat"
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"uid": "$ds"
},
"description": "Shows the total number of users defined at configuration file.",
"fieldConfig": {
"defaults": {
"decimals": 0,
"links": [],
"mappings": [],
"min": 0,
"thresholds": {
@@ -195,106 +165,42 @@
"steps": [
{
"color": "green",
"value": 0
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 3,
"w": 6,
"x": 7,
"y": 1
},
"id": 31,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"last"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"text": {},
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "12.2.0",
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
"exemplar": false,
"expr": "count(vmauth_user_concurrent_requests_capacity{job=~\"$job\", instance=~\"$instance\"})",
"interval": "",
"legendFormat": "",
"range": true,
"refId": "A"
}
],
"title": "Users count",
"type": "stat"
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"uid": "$ds"
},
"description": "Shows the rate of request errors.",
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
"value": null
},
{
"color": "green",
"value": 0
"color": "red",
"value": 80
}
]
}
},
"unit": "s"
},
"overrides": []
},
"gridPos": {
"h": 3,
"w": 7,
"x": 13,
"h": 4,
"w": 11,
"x": 0,
"y": 1
},
"id": 36,
"id": 32,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"legend": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
"displayMode": "table",
"placement": "right",
"showLegend": true,
"sortBy": "Last *",
"sortDesc": true
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
"tooltip": {
"mode": "multi",
"sort": "asc"
}
},
"pluginVersion": "12.2.0",
"pluginVersion": "9.2.6",
"targets": [
{
"datasource": {
@@ -303,14 +209,16 @@
},
"editorMode": "code",
"exemplar": false,
"expr": "sum(rate(vmauth_http_request_errors_total{job=~\"$job\", instance=~\"$instance\"}[$__rate_interval]))",
"instant": true,
"range": false,
"expr": "sum(min_over_time(vm_app_uptime_seconds{job=~\"$job\", instance=~\"$instance\"}[$__rate_interval])) by (job)",
"format": "time_series",
"instant": false,
"interval": "",
"legendFormat": "{{job}}",
"refId": "A"
}
],
"title": "Errors rate",
"type": "stat"
"title": "Uptime",
"type": "timeseries"
},
{
"datasource": {
@@ -349,7 +257,7 @@
"steps": [
{
"color": "green",
"value": 0
"value": null
}
]
}
@@ -358,8 +266,8 @@
},
"gridPos": {
"h": 3,
"w": 4,
"x": 20,
"w": 6,
"x": 11,
"y": 1
},
"id": 30,
@@ -368,7 +276,6 @@
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"last"
@@ -381,7 +288,7 @@
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "12.2.0",
"pluginVersion": "10.4.2",
"targets": [
{
"datasource": {
@@ -398,6 +305,201 @@
"title": "Config update",
"type": "stat"
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"uid": "$ds"
},
"description": "Shows the rate of requests.",
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 3,
"w": 7,
"x": 17,
"y": 1
},
"id": 4,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "10.4.2",
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
"exemplar": false,
"expr": "(sum(rate(vmauth_user_requests_total{job=~\"$job\", instance=~\"$instance\", username=~\"$user\"}[$__rate_interval])) or 0) + (sum(rate(vmauth_unauthorized_user_requests_total{job=~\"$job\", instance=~\"$instance\"}[$__rate_interval])) or 0)",
"instant": true,
"range": false,
"refId": "A"
}
],
"title": "Requests rate",
"type": "stat"
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"uid": "$ds"
},
"description": "Shows the total number of users defined at configuration file.",
"fieldConfig": {
"defaults": {
"mappings": [],
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 3,
"w": 6,
"x": 11,
"y": 4
},
"id": 31,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"last"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"text": {},
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "10.4.2",
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
"exemplar": false,
"expr": "count(vmauth_user_concurrent_requests_capacity{job=~\"$job\", instance=~\"$instance\"})",
"interval": "",
"legendFormat": "",
"range": true,
"refId": "A"
}
],
"title": "Users count",
"type": "stat"
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"uid": "$ds"
},
"description": "Shows the rate of request errors.",
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 3,
"w": 7,
"x": 17,
"y": 4
},
"id": 36,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "10.4.2",
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
"exemplar": false,
"expr": "sum(rate(vmauth_http_request_errors_total{job=~\"$job\", instance=~\"$instance\"}[$__rate_interval]))",
"instant": true,
"range": false,
"refId": "A"
}
],
"title": "Errors rate",
"type": "stat"
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
@@ -414,9 +516,6 @@
"type": "auto"
},
"filterable": false,
"footer": {
"reducers": []
},
"inspect": false,
"minWidth": 50
},
@@ -426,7 +525,7 @@
"steps": [
{
"color": "green",
"value": 0
"value": null
}
]
}
@@ -439,7 +538,7 @@
},
"properties": [
{
"id": "custom.hideFrom.viz",
"id": "custom.hidden",
"value": true
}
]
@@ -462,15 +561,23 @@
"h": 4,
"w": 11,
"x": 0,
"y": 4
"y": 5
},
"id": 6,
"options": {
"cellHeight": "sm",
"footer": {
"countRows": false,
"fields": "",
"reducer": [
"sum"
],
"show": false
},
"frameIndex": 0,
"showHeader": true
},
"pluginVersion": "12.2.0",
"pluginVersion": "10.4.2",
"targets": [
{
"datasource": {
@@ -489,115 +596,6 @@
"title": "Version",
"type": "table"
},
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"uid": "$ds"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"barWidthFactor": 0.6,
"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",
"showValues": false,
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"decimals": 0,
"links": [],
"mappings": [],
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
},
{
"color": "red",
"value": 80
}
]
},
"unit": "s"
},
"overrides": []
},
"gridPos": {
"h": 4,
"w": 13,
"x": 11,
"y": 4
},
"id": 32,
"options": {
"legend": {
"calcs": [
"lastNotNull"
],
"displayMode": "table",
"placement": "right",
"showLegend": true,
"sortBy": "Last *",
"sortDesc": true
},
"tooltip": {
"hideZeros": false,
"mode": "multi",
"sort": "asc"
}
},
"pluginVersion": "12.2.0",
"targets": [
{
"datasource": {
"type": "victoriametrics-metrics-datasource",
"uid": "$ds"
},
"editorMode": "code",
"exemplar": false,
"expr": "sum(min_over_time(vm_app_uptime_seconds{job=~\"$job\", instance=~\"$instance\"}[$__rate_interval])) by (job)",
"format": "time_series",
"instant": false,
"interval": "",
"legendFormat": "{{job}}",
"refId": "A"
}
],
"title": "Uptime",
"type": "timeseries"
},
{
"collapsed": false,
"gridPos": {
@@ -1102,8 +1100,8 @@
"gridPos": {
"h": 9,
"w": 12,
"x": 0,
"y": 27
"x": 12,
"y": 28
},
"id": 19,
"options": {

View File

@@ -118,75 +118,45 @@
"type": "prometheus",
"uid": "$ds"
},
"description": "Shows the rate of requests.",
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
"mode": "palette-classic"
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 3,
"w": 7,
"x": 0,
"y": 1
},
"id": 4,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "12.2.0",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "$ds"
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"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"
}
},
"editorMode": "code",
"exemplar": false,
"expr": "(sum(rate(vmauth_user_requests_total{job=~\"$job\", instance=~\"$instance\", username=~\"$user\"}[$__rate_interval])) or 0) + (sum(rate(vmauth_unauthorized_user_requests_total{job=~\"$job\", instance=~\"$instance\"}[$__rate_interval])) or 0)",
"instant": true,
"range": false,
"refId": "A"
}
],
"title": "Requests rate",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "$ds"
},
"description": "Shows the total number of users defined at configuration file.",
"fieldConfig": {
"defaults": {
"decimals": 0,
"links": [],
"mappings": [],
"min": 0,
"thresholds": {
@@ -194,106 +164,42 @@
"steps": [
{
"color": "green",
"value": 0
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 3,
"w": 6,
"x": 7,
"y": 1
},
"id": 31,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"last"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"text": {},
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "12.2.0",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "$ds"
},
"editorMode": "code",
"exemplar": false,
"expr": "count(vmauth_user_concurrent_requests_capacity{job=~\"$job\", instance=~\"$instance\"})",
"interval": "",
"legendFormat": "",
"range": true,
"refId": "A"
}
],
"title": "Users count",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "$ds"
},
"description": "Shows the rate of request errors.",
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
"value": null
},
{
"color": "green",
"value": 0
"color": "red",
"value": 80
}
]
}
},
"unit": "s"
},
"overrides": []
},
"gridPos": {
"h": 3,
"w": 7,
"x": 13,
"h": 4,
"w": 11,
"x": 0,
"y": 1
},
"id": 36,
"id": 32,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"legend": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
"displayMode": "table",
"placement": "right",
"showLegend": true,
"sortBy": "Last *",
"sortDesc": true
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
"tooltip": {
"mode": "multi",
"sort": "asc"
}
},
"pluginVersion": "12.2.0",
"pluginVersion": "9.2.6",
"targets": [
{
"datasource": {
@@ -302,14 +208,16 @@
},
"editorMode": "code",
"exemplar": false,
"expr": "sum(rate(vmauth_http_request_errors_total{job=~\"$job\", instance=~\"$instance\"}[$__rate_interval]))",
"instant": true,
"range": false,
"expr": "sum(min_over_time(vm_app_uptime_seconds{job=~\"$job\", instance=~\"$instance\"}[$__rate_interval])) by (job)",
"format": "time_series",
"instant": false,
"interval": "",
"legendFormat": "{{job}}",
"refId": "A"
}
],
"title": "Errors rate",
"type": "stat"
"title": "Uptime",
"type": "timeseries"
},
{
"datasource": {
@@ -348,7 +256,7 @@
"steps": [
{
"color": "green",
"value": 0
"value": null
}
]
}
@@ -357,8 +265,8 @@
},
"gridPos": {
"h": 3,
"w": 4,
"x": 20,
"w": 6,
"x": 11,
"y": 1
},
"id": 30,
@@ -367,7 +275,6 @@
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"percentChangeColorMode": "standard",
"reduceOptions": {
"calcs": [
"last"
@@ -380,7 +287,7 @@
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "12.2.0",
"pluginVersion": "10.4.2",
"targets": [
{
"datasource": {
@@ -397,6 +304,201 @@
"title": "Config update",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "$ds"
},
"description": "Shows the rate of requests.",
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 3,
"w": 7,
"x": 17,
"y": 1
},
"id": 4,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "10.4.2",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "$ds"
},
"editorMode": "code",
"exemplar": false,
"expr": "(sum(rate(vmauth_user_requests_total{job=~\"$job\", instance=~\"$instance\", username=~\"$user\"}[$__rate_interval])) or 0) + (sum(rate(vmauth_unauthorized_user_requests_total{job=~\"$job\", instance=~\"$instance\"}[$__rate_interval])) or 0)",
"instant": true,
"range": false,
"refId": "A"
}
],
"title": "Requests rate",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "$ds"
},
"description": "Shows the total number of users defined at configuration file.",
"fieldConfig": {
"defaults": {
"mappings": [],
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 3,
"w": 6,
"x": 11,
"y": 4
},
"id": 31,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"last"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"text": {},
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "10.4.2",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "$ds"
},
"editorMode": "code",
"exemplar": false,
"expr": "count(vmauth_user_concurrent_requests_capacity{job=~\"$job\", instance=~\"$instance\"})",
"interval": "",
"legendFormat": "",
"range": true,
"refId": "A"
}
],
"title": "Users count",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "$ds"
},
"description": "Shows the rate of request errors.",
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 3,
"w": 7,
"x": 17,
"y": 4
},
"id": 36,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "10.4.2",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "$ds"
},
"editorMode": "code",
"exemplar": false,
"expr": "sum(rate(vmauth_http_request_errors_total{job=~\"$job\", instance=~\"$instance\"}[$__rate_interval]))",
"instant": true,
"range": false,
"refId": "A"
}
],
"title": "Errors rate",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
@@ -413,9 +515,6 @@
"type": "auto"
},
"filterable": false,
"footer": {
"reducers": []
},
"inspect": false,
"minWidth": 50
},
@@ -425,7 +524,7 @@
"steps": [
{
"color": "green",
"value": 0
"value": null
}
]
}
@@ -438,7 +537,7 @@
},
"properties": [
{
"id": "custom.hideFrom.viz",
"id": "custom.hidden",
"value": true
}
]
@@ -461,15 +560,23 @@
"h": 4,
"w": 11,
"x": 0,
"y": 4
"y": 5
},
"id": 6,
"options": {
"cellHeight": "sm",
"footer": {
"countRows": false,
"fields": "",
"reducer": [
"sum"
],
"show": false
},
"frameIndex": 0,
"showHeader": true
},
"pluginVersion": "12.2.0",
"pluginVersion": "10.4.2",
"targets": [
{
"datasource": {
@@ -488,115 +595,6 @@
"title": "Version",
"type": "table"
},
{
"datasource": {
"type": "prometheus",
"uid": "$ds"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"barWidthFactor": 0.6,
"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",
"showValues": false,
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"decimals": 0,
"links": [],
"mappings": [],
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
},
{
"color": "red",
"value": 80
}
]
},
"unit": "s"
},
"overrides": []
},
"gridPos": {
"h": 4,
"w": 13,
"x": 11,
"y": 4
},
"id": 32,
"options": {
"legend": {
"calcs": [
"lastNotNull"
],
"displayMode": "table",
"placement": "right",
"showLegend": true,
"sortBy": "Last *",
"sortDesc": true
},
"tooltip": {
"hideZeros": false,
"mode": "multi",
"sort": "asc"
}
},
"pluginVersion": "12.2.0",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "$ds"
},
"editorMode": "code",
"exemplar": false,
"expr": "sum(min_over_time(vm_app_uptime_seconds{job=~\"$job\", instance=~\"$instance\"}[$__rate_interval])) by (job)",
"format": "time_series",
"instant": false,
"interval": "",
"legendFormat": "{{job}}",
"refId": "A"
}
],
"title": "Uptime",
"type": "timeseries"
},
{
"collapsed": false,
"gridPos": {
@@ -1101,8 +1099,8 @@
"gridPos": {
"h": 9,
"w": 12,
"x": 0,
"y": 27
"x": 12,
"y": 28
},
"id": 19,
"options": {

View File

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

View File

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

View File

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

View File

@@ -109,8 +109,6 @@ docs-update-flags:
sed -i '/The maximum number of concurrent insert requests/ s/(default [0-9]\+)/(default 2*cgroup.AvailableCPUs())/' docs/victoriametrics/victoria_metrics_flags.md
sed -i '/The maximum number of concurrent search requests\./ s/(default [0-9]\+)/(default vmselect.getDefaultMaxConcurrentRequests())/' docs/victoriametrics/victoria_metrics_flags.md
sed -i '/The maximum number of CPU cores a single query can use\./ s/(default [0-9]\+)/(default netstorage.defaultMaxWorkersPerQuery())/' docs/victoriametrics/victoria_metrics_flags.md
sed -i '/The maximum number of concurrent goroutines to work with files;/ s/(default [0-9]\+)/(default fsutil.getDefaultConcurrency())/' docs/victoriametrics/victoria_metrics_flags.md
# ---- vmagent
(cd /tmp/vm-enterprise-single-node && make vmagent)
@@ -125,7 +123,6 @@ docs-update-flags:
# remove after https://github.com/VictoriaMetrics/VictoriaMetrics/issues/9680 implemented
sed -i '/The maximum number of concurrent insert requests/ s/(default [0-9]\+)/(default 2*cgroup.AvailableCPUs())/' docs/victoriametrics/vmagent_flags.md
sed -i '/The number of concurrent queues to each -remoteWrite.url./ s/(default [0-9]\+)/(default 2*cgroup.AvailableCPUs())/' docs/victoriametrics/vmagent_flags.md
sed -i '/The maximum number of concurrent goroutines to work with files;/ s/(default [0-9]\+)/(default fsutil.getDefaultConcurrency())/' docs/victoriametrics/vmagent_flags.md
# ---- vmalert
(cd /tmp/vm-enterprise-single-node && make vmalert)
@@ -139,7 +136,6 @@ docs-update-flags:
# adjust flags with dynamic default values
# remove after https://github.com/VictoriaMetrics/VictoriaMetrics/issues/9680 implemented
sed -i '/Defines number of writers for concurrent writing into remote write endpoint./ s/(default [0-9]\+)/(default 2*cgroup.AvailableCPUs())/' docs/victoriametrics/vmalert_flags.md
sed -i '/The maximum number of concurrent goroutines to work with files;/ s/(default [0-9]\+)/(default fsutil.getDefaultConcurrency())/' docs/victoriametrics/vmalert_flags.md
# ---- vminsert
(cd /tmp/vm-enterprise-cluster && make vminsert)
@@ -162,7 +158,6 @@ docs-update-flags:
# adjust flags with dynamic default values
# remove after https://github.com/VictoriaMetrics/VictoriaMetrics/issues/9680 implemented
sed -i '/The maximum number of concurrent insert requests/ s/(default [0-9]\+)/(default 2*cgroup.AvailableCPUs())/' docs/victoriametrics/vminsert_flags.md
sed -i '/The maximum number of concurrent goroutines to work with files;/ s/(default [0-9]\+)/(default fsutil.getDefaultConcurrency())/' docs/victoriametrics/vminsert_flags.md
# ---- vmselect
(cd /tmp/vm-enterprise-cluster && make vmselect)
@@ -178,7 +173,6 @@ docs-update-flags:
sed -i '/The maximum number of concurrent search requests\./ s/(default [0-9]\+)/(default vmselect.getDefaultMaxConcurrentRequests())/' docs/victoriametrics/vmselect_flags.md
sed -i '/The maximum number of CPU cores a single query can use\./ s/(default [0-9]\+)/(default netstorage.defaultMaxWorkersPerQuery())/' docs/victoriametrics/vmselect_flags.md
sed -i '/The maximum number of concurrent vmselect requests the server can process at -clusternativeListenAddr/ s/(default [0-9]\+)/(default 2*cgroup.AvailableCPUs())/' docs/victoriametrics/vmselect_flags.md
sed -i '/The maximum number of concurrent goroutines to work with files;/ s/(default [0-9]\+)/(default fsutil.getDefaultConcurrency())/' docs/victoriametrics/vmselect_flags.md
# ---- vmstorage
(cd /tmp/vm-enterprise-cluster && make vmstorage)
@@ -192,5 +186,4 @@ docs-update-flags:
# adjust flags with dynamic default values
# remove after https://github.com/VictoriaMetrics/VictoriaMetrics/issues/9680 implemented
sed -i '/The maximum number of concurrent insert requests/ s/(default [0-9]\+)/(default 2*cgroup.AvailableCPUs())/' docs/victoriametrics/vmstorage_flags.md
sed -i '/The maximum number of concurrent vmselect requests the vmstorage can process at./ s/(default [0-9]\+)/(default 2*cgroup.AvailableCPUs())/' docs/victoriametrics/vmstorage_flags.md
sed -i '/The maximum number of concurrent goroutines to work with files;/ s/(default [0-9]\+)/(default fsutil.getDefaultConcurrency())/' docs/victoriametrics/vmstorage_flags.md
sed -i '/The maximum number of concurrent vmselect requests the vmstorage can process at./ s/(default [0-9]\+)/(default 2*cgroup.AvailableCPUs())/' docs/victoriametrics/vmstorage_flags.md

View File

@@ -14,15 +14,6 @@ aliases:
---
Please find the changelog for VictoriaMetrics Anomaly Detection below.
## v1.28.0
Released: 2025-11-17
- IMPROVEMENT: Deprecated [rolling models](https://docs.victoriametrics.com/anomaly-detection/components/models/#rolling-models) class. Reworked [`RollingQuantileModel`](https://docs.victoriametrics.com/anomaly-detection/components/models/#rolling-quantile) and [`StdModel`](https://docs.victoriametrics.com/anomaly-detection/components/models/#seasonal-trend-decomposition) into [online](https://docs.victoriametrics.com/anomaly-detection/components/models/#online-models) type. Using full class format in config (e.g. `class: model.rolling_quantile.RollingQuantileModel`) is supported for backward compatibility and raises deprecation warnings, however it's recommended to just use alias format (`class: rolling_quantile`) which redirects to the new online version.
- IMPROVEMENT: Added "exact" mode to [Backtesting Scheduler](https://docs.victoriametrics.com/anomaly-detection/components/scheduler/#backtesting-scheduler) to use in combination with "infer every" control for [online models](https://docs.victoriametrics.com/anomaly-detection/components/models/#online-models) such as `mad_online` or `quantile_online`, to provide unbiased estimates of how production scheduler would perform anomaly detection on incoming data streams. In "exact" mode, the model is updated exactly at every "infer every" micro-batch interval, at a cost of increased computation time. See [Backtesting Scheduler](https://docs.victoriametrics.com/anomaly-detection/components/scheduler/#backtesting-scheduler) for details.
- UI: Updated [vmanomaly UI](https://docs.victoriametrics.com/anomaly-detection/ui/) from [v1.1.0](https://docs.victoriametrics.com/anomaly-detection/ui/#v110) to [v1.2.0](https://docs.victoriametrics.com/anomaly-detection/ui/#v120), including dynamic alerting rule and exact mode for [online](https://docs.victoriametrics.com/anomaly-detection/components/models/#online-models) models for production scheduling imitation, see full [release notes](https://docs.victoriametrics.com/anomaly-detection/ui/#v120) for details.
## v1.27.1
Released: 2025-11-05

View File

@@ -243,9 +243,7 @@ schedulers:
inference_only: True # to treat from-to as inference period, with automated fit intervals construction
# copy these from your PeriodicScheduler args
fit_window: 'P14D'
fit_every: 'PT1D'
exact: True # to imitate exact fit/infer calls as in PeriodicScheduler for online models
infer_every: 'PT1H' # used only for exact=True, to imitate PeriodicScheduler behavior
fit_every: 'PT1H'
# number of parallel jobs to run. Default is 1, each job is a separate OneOffScheduler fit/inference run.
n_jobs: 1
@@ -403,30 +401,30 @@ services:
# ...
vmanomaly:
container_name: vmanomaly
image: victoriametrics/vmanomaly:v1.28.0
image: victoriametrics/vmanomaly:v1.27.1
# ...
restart: always
volumes:
- ./config.yaml:/config.yaml
- ./license:/license
# map the host directory to the container directory
- vmanomaly_data:/tmp/vmanomaly
environment:
# set the environment variable for the model dump directory
- VMANOMALY_MODEL_DUMPS_DIR=/tmp/vmanomaly/models
- VMANOMALY_DATA_DUMPS_DIR=/tmp/vmanomaly/data
ports:
- "8490:8490"
restart: always
volumes:
- ./vmanomaly_config.yml:/config.yaml
- ./vmanomaly_license:/license
# map the host directory to the container directory
- vmanomaly_model_dump_dir:/vmanomaly/tmp/models
- vmanomaly_data_dump_dir:/vmanomaly/tmp/data
environment:
# set the environment variable for the model dump directory
- VMANOMALY_MODEL_DUMPS_DIR=/vmanomaly/tmp/models/
- VMANOMALY_DATA_DUMPS_DIR=/vmanomaly/tmp/data/
platform: "linux/amd64"
command:
- "/config.yaml"
- "--licenseFile=/license"
- "--loggerLevel=INFO"
- "--watch"
volumes:
# ...
# Enable if settings.restore_state is True
vmanomaly_data: {}
vmanomaly_model_dump_dir: {}
vmanomaly_data_dump_dir: {}
```
For Helm chart users, refer to the `persistentVolume` [section](https://github.com/VictoriaMetrics/helm-charts/blob/7f5a2c00b14c2c088d7d8d8bcee7a440a5ff11c6/charts/victoria-metrics-anomaly/values.yaml#L183) in the [`values.yaml`](https://github.com/VictoriaMetrics/helm-charts/blob/master/charts/victoria-metrics-anomaly/values.yaml) file. Ensure that the boolean flags `dumpModels` and `dumpData` are set as needed (both are *enabled* by default).
@@ -618,7 +616,7 @@ options:
Heres an example of using the config splitter to divide configurations based on the `extra_filters` argument from the reader section:
```sh
docker pull victoriametrics/vmanomaly:v1.28.0 && docker image tag victoriametrics/vmanomaly:v1.28.0 vmanomaly
docker pull victoriametrics/vmanomaly:v1.27.1 && docker image tag victoriametrics/vmanomaly:v1.27.1 vmanomaly
```
```sh

View File

@@ -45,8 +45,8 @@ There are 2 types of compatibilitity to consider when migrating in stateful mode
| Group start | Group end | Compatibility | Notes |
|---------|--------- |------------|-------|
| [v1.28.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1280) | Latest* | Fully Compatible | Just a placeholder for new releases |
| [v1.26.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1262) | [v1.28.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1280) | Fully Compatible | [v1.28.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1280) introduced [rolling](https://docs.victoriametrics.com/anomaly-detection/components/models/#rolling-models) model class drop in favor of [online](https://docs.victoriametrics.com/anomaly-detection/components/models/#online-models) models (`rolling_quantile` and `std` models), however, it does not impact compatibility, as artifacts were not produced by default for rolling models. |
| [v1.27.1](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1271) | Latest* | Fully Compatible | Just a placeholder for new releases |
| [v1.26.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1262) | [v1.27.1](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1270) | Fully Compatible | - |
| [v1.25.3](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1253) | [v1.26.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1270) | Partially Compatible* | [v1.25.3](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1253) introduced `forecast_at` argument for base [univariate](https://docs.victoriametrics.com/anomaly-detection/components/models/#univariate-models) and `Prophet` [models](https://docs.victoriametrics.com/anomaly-detection/components/models/#prophet), however, itself remains backward-reversible from newer states like [v1.26.2](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1262), [v1.27.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1270). (All models except `isolation_forest_multivariate` class will be dropped) |
| [v1.25.1](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1251) | [v1.25.2](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1252) | Fully Compatible | In [v1.25.1](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1251) there was a change to `vmanomaly.db` metadata database format, so migrating from v1.24.0-v1.25.0 requires deletion of a state, see note above the table |
| [v1.24.1](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1241) | [v1.25.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1250) | Partially Compatible* | In [v1.25.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1250) there were changes to **data dump layout** and to `online_quantile` and `isolation_forest_multivariate` [model](https://docs.victoriametrics.com/anomaly-detection/components/models/) states, so to migrate from v1.24.0-v1.24.1 it is recommended to drop the state |

View File

@@ -121,86 +121,64 @@ Below are the steps to get `vmanomaly` up and running inside a Docker container:
1. Pull Docker image:
```sh
docker pull victoriametrics/vmanomaly:v1.28.0
docker pull victoriametrics/vmanomaly:v1.27.1
```
2. Create the license file with your license key.
2. (Optional step) tag the `vmanomaly` Docker image:
```sh
export LICENSE_KEY=YOUR_LICENSE_KEY
echo $LICENSE_KEY > license
docker image tag victoriametrics/vmanomaly:v1.27.1 vmanomaly
```
3. Create and modify your `config.yaml` file to your liking. An example can be found [here](https://docs.victoriametrics.com/anomaly-detection/quickstart/#example)
4. Start the `vmanomaly` Docker container with a *license file*, use the command below.
3. Start the `vmanomaly` Docker container with a *license file*, use the command below.
**Make sure to replace `YOUR_LICENSE_FILE_PATH`, and `YOUR_CONFIG_FILE_PATH` with your specific details**:
```sh
docker run -it \
-v ./license:/license \
-v ./config.yaml:/config.yaml \
-p 8490:8490 \
victoriametrics/vmanomaly:v1.28.0 \
/config.yaml \
--licenseFile=/license \
--loggerLevel=INFO \
--watch
export YOUR_LICENSE_FILE_PATH=path/to/license/file
export YOUR_CONFIG_FILE_PATH=path/to/config/file
docker run -it -v $YOUR_LICENSE_FILE_PATH:/license \
-v $YOUR_CONFIG_FILE_PATH:/config.yml \
vmanomaly /config.yml \
--licenseFile=/license \
--loggerLevel=INFO \
--watch
```
Use the below configuration if settings.restore_state is True (vmanomaly runs in [stateful](https://docs.victoriametrics.com/anomaly-detection/components/settings/#state-restoration) mode) or [on-disk mode](https://docs.victoriametrics.com/anomaly-detection/faq/#on-disk-mode) is preferred over in-memory.
In case you found `PermissionError: [Errno 13] Permission denied:` in `vmanomaly` logs, set user/user group to 1000 in the run command above / in a docker-compose file:
```sh
docker run -it \
-v ./license:/license \
-v ./config.yaml:/config.yaml \
-v vmanomaly_data:/tmp/vmanomaly \
-e VMANOMALY_DATA_DUMPS_DIR=/tmp/vmanomaly/data \
-e VMANOMALY_MODEL_DUMPS_DIR=/tmp/vmanomaly/models \
-p 8490:8490 \
victoriametrics/vmanomaly:v1.28.0 \
/config.yaml \
--licenseFile=/license \
--loggerLevel=INFO \
--watch
export YOUR_LICENSE_FILE_PATH=path/to/license/file
export YOUR_CONFIG_FILE_PATH=path/to/config/file
docker run -it --user 1000:1000 \
-v $YOUR_LICENSE_FILE_PATH:/license \
-v $YOUR_CONFIG_FILE_PATH:/config.yml \
vmanomaly /config.yml \
--licenseFile=/license \
--loggerLevel=INFO \
--watch
```
```yaml
# docker-compose.yml file
# docker-compose file
services:
# ...
vmanomaly:
container_name: vmanomaly
image: victoriametrics/vmanomaly:v1.28.0
# ...
restart: always
image: victoriametrics/vmanomaly:v1.27.1
volumes:
- ./config.yaml:/config.yaml
- ./license:/license
# Enable if settings.restore_state is True
# - vmanomaly_data:/tmp/vmanomaly
environment:
# Enable if on-disk mode over in-memory is preferred
# Required, if settings.restore_state is True
- VMANOMALY_MODEL_DUMPS_DIR=/tmp/vmanomaly/models
- VMANOMALY_DATA_DUMPS_DIR=/tmp/vmanomaly/data
ports:
- "8490:8490"
$YOUR_LICENSE_FILE_PATH:/license
$YOUR_CONFIG_FILE_PATH:/config.yml
command:
- "/config.yaml"
- "/config.yml"
- "--licenseFile=/license"
- "--loggerLevel=INFO"
- "--watch"
volumes:
# ...
# Enable if on-disk mode over in-memory is preferred
# Required, if settings.restore_state is True
vmanomaly_data: {}
# ...
```
For a complete docker-compose example please refer to [our alerting guide](https://docs.victoriametrics.com/anomaly-detection/guides/guide-vmanomaly-vmalert/), chapter [docker-compose](https://docs.victoriametrics.com/anomaly-detection/guides/guide-vmanomaly-vmalert/#docker-compose)
See also:
- Verify the license online OR offline. See the details [here](https://docs.victoriametrics.com/anomaly-detection/quickstart/#licensing).
@@ -230,32 +208,32 @@ To run `vmanomaly`, use YAML files or directories containing YAML files. The con
> vmanomaly config1.yaml config2.yaml ./config_dir/
> ```
Before deploying, check the correctness of your configuration validate config file(s) with `--dryRun` [command-line](#command-line-arguments) flag for chosen deployment method (Docker, Kubernetes, etc.). This will parse and merge all YAML files, run schema checks, log errors and warnings (if found) and then exit without starting the service or requiring a license. {{% available_from "v1.27.0" anomaly %}} it can be also used to check for migration compatibility issues when upgrading to a newer version of `vmanomaly`. See [Migration](https://docs.victoriametrics.com/anomaly-detection/migration/) section for more details.
Before deploying, check the correctness of your configuration validate config file(s) with `--dryRun` [command-line](#command-line-arguments) flag for chosen deployment method (Docker, Kubernetes, etc.). This will parse and merge all YAML files, run schema checks, log errors and warnings (if found) and then exit without starting the service and requiring a license. {{% available_from "v1.27.0" anomaly %}} it can be also used to check for migration compatibility issues when upgrading to a newer version of `vmanomaly`. See [Migration](https://docs.victoriametrics.com/anomaly-detection/migration/) section for more details.
### Example
Here is an example of a config file that will run the [Prophet](https://docs.victoriametrics.com/anomaly-detection/components/models/#prophet) model on `vm_cache_entries` metric, with periodic scheduler that runs inference every minute and fits the model every day. The model will be trained on the last 2 weeks of data each time it is (re)fitted. The model will produce `anomaly_score`, `yhat`, `yhat_lower`, and `yhat_upper` [series](https://docs.victoriametrics.com/anomaly-detection/components/models/#vmanomaly-output) for debugging purposes. The model will be timezone-aware and will use cyclical encoding for the hour of the day and day of the week seasonality.
Here is an example of config file that will run [Prophet](https://docs.victoriametrics.com/anomaly-detection/components/models/#prophet) model on `vm_cache_entries` metric, with periodic scheduler that runs inference every minute and fits the model every day. The model will be trained on the last 2 weeks of data each time it is (re)fitted. The model will produce `anomaly_score`, `yhat`, `yhat_lower`, and `yhat_upper` [series](https://docs.victoriametrics.com/anomaly-detection/components/models/#vmanomaly-output) for debugging purposes. The model will be timezone-aware and will use cyclical encoding for the hour of the day and day of the week seasonality.
```yaml
settings:
# https://docs.victoriametrics.com/anomaly-detection/components/settings/
n_workers: 2 # number of workers to run workload in parallel, set to 0 or negative number to use all available CPU cores
n_workers: 4 # number of workers to run workload in parallel, set to 0 or negative number to use all available CPU cores
anomaly_score_outside_data_range: 5.0 # default anomaly score for anomalies outside expected data range
restore_state: true # restore state from previous run, available since v1.24.0
restore_state: True # restore state from previous run, available since v1.24.0
# https://docs.victoriametrics.com/anomaly-detection/components/settings/#logger-levels
# to override service-global logger levels, use the `logger_levels` section
logger_levels:
# vmanomaly: INFO
# scheduler: INFO
# reader: INFO
# writer: INFO
model.prophet: WARNING
# vmanomaly: info
# scheduler: info
# reader: info
# writer: info
model.prophet: warning
schedulers:
1d_1m:
# https://docs.victoriametrics.com/anomaly-detection/components/scheduler/#periodic-scheduler
class: 'periodic'
infer_every: '5m'
infer_every: '1m'
fit_every: '1d'
fit_window: '2w'
@@ -280,30 +258,18 @@ models:
reader:
class: 'vm' # use VictoriaMetrics as a data source
# https://docs.victoriametrics.com/anomaly-detection/components/reader/#vm-reader
datasource_url: "https://play.victoriametrics.com/" # [YOUR_DATASOURCE_URL]
tenant_id: '0:0'
sampling_period: "5m"
datasource_url: "http://victoriametrics:8428/" # [YOUR_DATASOURCE_URL]
sampling_period: "1m"
queries:
# define your queries with MetricsQL - https://docs.victoriametrics.com/victoriametrics/metricsql/
cpu_user:
expr: 'sum(rate(node_cpu_seconds_total{mode=~"user"}[10m])) by (container)'
max_datapoints_per_query: 15000 # to deal with longer queries hitting seach.MaxPointsPerTimeseries
# other queries ...
cache: "sum(rate(vm_cache_entries))"
writer:
class: 'vm' # use VictoriaMetrics as a data destination
# https://docs.victoriametrics.com/anomaly-detection/components/writer/#vm-writer
datasource_url: "http://victoriametrics:8428/" # [YOUR_DATASOURCE_URL]
# optional tenant ID
# tenant_id: "0:0"
```
### UI
{{% available_from "v1.26.0" anomaly %}} `vmanomaly`'s built-in web UI can be used for prototyping and interactive experimenting to produce vmanomaly's and vmalert's configuration files. Please refer to the [UI documentation](https://docs.victoriametrics.com/anomaly-detection/ui/) for detailed instructions and examples.
![vmanomaly-ui-overview](vmanomaly-ui-overview.webp)
### Recommended steps
For optimal service behavior, consider the following tweaks when configuring `vmanomaly`:

View File

@@ -134,11 +134,9 @@ Also, timeseries (such as `y`, `y_hat`, etc.) can be toggled on/off by clicking
The Model Panel provides:
Parameters, such as "Fit Every", "Fit Window" and {{% available_from "v1.28.0" anomaly %}} "Infer Every" to imitate [production scheduling](https://docs.victoriametrics.com/anomaly-detection/components/scheduler/#periodic-scheduler), as well as overriding default [anomaly detection threshold](https://docs.victoriametrics.com/anomaly-detection/faq/#what-is-anomaly-score) (1.0).
Parameters, such as "Fit Every" and "Fit Window", to control how often and over what time window the model is retrained on new data to imitate production behavior, as well as overriding default anomaly detection thresholds (1.0).
> {{% available_from "v1.28.0" anomaly %}} "Exact" mode checkbox is used in combination with "Infer Every" control for [online models](https://docs.victoriametrics.com/anomaly-detection/components/models/#online-models) such as `mad_online` or `quantile_online`, to provide unbiased estimates of how production scheduler would perform anomaly detection on incoming data streams. In "exact" mode, the model is updated exactly at every "infer every" micro-batch interval, at a cost of increased computation time.
Controls for running/canceling anomaly detection on the queried data, downloading the results as CSV/JSON, accessing and downloading the model configuration or example alerting rules in YAML format.
Controls for running/canceling anomaly detection on the queried data, downloading the results as CSV/JSON, accessing and downloading the model configuration in YAML format.
A form-based menu for finetuning model hyperparameters and applying domain knowledge settings:
@@ -354,29 +352,12 @@ If the **results** do not look good, the model hyperparameters and domain knowle
If the **results** look good, but should be shared with others first, timeseries can be downloaded as files by hitting the respective button in the Model Panel. See also [configuration sharing](#configuration-sharing) section for details.
If the **results** look good and the **model configuration should be deployed in production jobs of anomaly detection**, the equivalent configuration in production-ready YAML format can be obtained by accessing the "YAML" Tab in the model configuration section and hitting the "Show Config" button to access (model-only or full) configuration to download/copy as a YAML file.
If the **results** look good and the **model configuration should be deployed in production jobs of anomaly detection**, the equivalent configuration in production-ready YAML format can be obtained by accessing the "YAML" Tab in the model configuration section and hitting the "Show Config" button to access (model-only or full) configuration and hitting "Download" button to get the configuration as a YAML file.
![vmanomaly-ui-open-config-menu](vmanomaly-ui-open-config-menu.webp)
{{% available_from "v1.28.0" anomaly %}} **Example alerting rules are generated** based on the current UI configuration. Hit the "Example Alert" button in the Model Panel to access and copy/download an example of parametrized [vmalert](https://docs.victoriametrics.com/victoriametrics/vmalert/) alerting rule snippet in YAML format to finetune and use in production alerting setup.
![vmanomaly-ui-example-alert-btn](vmanomaly-ui-example-alert-btn.webp)
![vmanomaly-ui-example-alert-menu](vmanomaly-ui-example-alert-menu.webp)
## Changelog
### v1.2.0
Released: 2025-11-17
vmanomaly version: [v1.28.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1280)
- FEATURE: Added "exact" mode to use in combination with "infer every" control for [online models](https://docs.victoriametrics.com/anomaly-detection/components/models/#online-models) such as `mad_online` or `quantile_online`, to provide unbiased estimates of how production scheduler would perform anomaly detection on incoming data streams. In "exact" mode, the model is updated exactly at every "infer every" micro-batch interval, at a cost of increased computation time. See [model panel](#model-panel) for details.
- FEATURE: Added "Example Alert" button in the [Model Panel](#model-panel) to provide an example of parametrized [vmalert](https://docs.victoriametrics.com/victoriametrics/vmalert/) alerting rule snippet based on the current UI configuration.
- IMPROVEMENT: Added support for clipboard copy in addition to existing file download of the model/service configuration in YAML format from the [Model Panel](#model-panel) configuration menu.
### v1.1.0
Released: 2025-10-31

View File

@@ -440,10 +440,10 @@ There are **2 model types**, supported in `vmanomaly`, resulting in **4 possible
- [Multivariate models](#multivariate-models)
Each of these models can be of type
- [Rolling](#rolling-models) - **no longer present {{% deprecated_from "v1.28.0" anomaly %}}, being reworked into [online models](#online-models)**
- [Rolling](#rolling-models)
- [Non-rolling](#non-rolling-models)
Moreover, {{% available_from "v1.15.0" anomaly %}}, there exist **[online (incremental) models](#online-models)** subclass for effective streaming-like data processing. Please refer to the [correspondent section](#online-models) for more details.
Moreover, {{% available_from "v1.15.0" anomaly %}}, there exist **[online (incremental) models](#online-models)** subclass. Please refer to the [correspondent section](#online-models) for more details.
### Univariate Models
@@ -479,8 +479,6 @@ If during an inference, you got a **different amount of series** or some series
### Rolling Models
> Rolling models as a class were deprecated {{% deprecated_from "v1.28.0" anomaly %}} in favor of [online models](#online-models), which provide similar benefits with additional advantages. Respective rolling models are refactored into online models (e.g., [RollingQuantile](#rolling-quantile)). Existing configurations that use rolling models' aliases will continue to function, with less limitations (e.g. no constraint on `fit_every` == `infer_every`).
A rolling model is a model that, once trained, **cannot be (naturally) used to make inference on data, not seen during its fit phase**.
An instance of rolling model is **simultaneously fit and used for inference** during its `infer` method call.
@@ -498,8 +496,6 @@ Such models put **more pressure** on your reader's source, i.e. if your model sh
### Non-Rolling Models
> Every model type is now {{% available_from "v1.28.0" anomaly %}} non-rolling. Configurations that used rolling models' aliases will continue to function, with less limitations (e.g. no constraint on `fit_every` == `infer_every`).
Everything that is not classified as [rolling](#rolling-models).
Produced models can be explicitly used to **infer on data, not seen during its fit phase**, thus, it **doesn't require re-fit procedure**.
@@ -654,7 +650,7 @@ models:
### [Prophet](https://facebook.github.io/prophet/)
`vmanomaly` uses the Facebook Prophet implementation for time series forecasting, with detailed usage provided in the [Prophet library documentation](https://facebook.github.io/prophet/docs/quick_start#python-api). All original Prophet parameters are supported and can be directly passed to the model via `args` argument.
> `ProphetModel` is a [univariate](#univariate-models), [offline](#offline-models) model.
> `ProphetModel` is a [univariate](#univariate-models), [non-rolling](#non-rolling-models), [offline](#offline-models) model.
> {{% available_from "v1.25.3" anomaly %}} Producing forecasts for future timestamps is now supported. To enable this, set the `forecast_at` argument to a list of relative future offsets (e.g., `['1h', '1d']`). The model will then generate forecasts for these future timestamps, which can be useful for planning and resource allocation. Output series are affected by [provide_series](#provide-series) argument, which need to include at least `yhat` for point-wise forecasts (and `yhat_lower` or/and `yhat_upper` for respective confidence intervals). See the example below for more details.
@@ -753,7 +749,7 @@ Resulting metrics of the model are described [here](#vmanomaly-output)
### [Z-score](https://en.wikipedia.org/wiki/Standard_score)
> `ZScoreModel` is a [univariate](#univariate-models), [offline](#offline-models) model.
> `ZScoreModel` is a [univariate](#univariate-models), [non-rolling](#non-rolling-models), [offline](#offline-models) model.
Model is useful for initial testing and for simpler data ([de-trended](https://victoriametrics.com/blog/victoriametrics-anomaly-detection-handbook-chapter-1/#trend) data without strict [seasonality](https://victoriametrics.com/blog/victoriametrics-anomaly-detection-handbook-chapter-1/#seasonality) and with anomalies of similar magnitude as your "normal" data).
@@ -786,9 +782,9 @@ Resulting metrics of the model are described [here](#vmanomaly-output).
### Online Z-score
> `OnlineZScoreModel` is a [univariate](#univariate-models), [online](#online-models) model.
> `OnlineZScoreModel` is a [univariate](#univariate-models), [non-rolling](#non-rolling-models), [online](#online-models) model.
Online version of existing [Z-score](#z-score) implementation with the same exact behavior and implications {{% available_from "v1.15.0" anomaly %}}.
Online version of existing [Z-score](#z-score) implementation with the same exact behavior and implications{{% available_from "v1.15.0" anomaly %}}.
*Parameters specific for vmanomaly*:
@@ -823,7 +819,7 @@ Resulting metrics of the model are described [here](#vmanomaly-output).
### [Holt-Winters](https://en.wikipedia.org/wiki/Exponential_smoothing)
> `HoltWinters` is a [univariate](#univariate-models), [offline](#offline-models) model.
> `HoltWinters` is a [univariate](#univariate-models), [non-rolling](#non-rolling-models), [offline](#offline-models) model.
Here we use Holt-Winters Exponential Smoothing implementation from `statsmodels` [library](https://www.statsmodels.org/dev/generated/statsmodels.tsa.holtwinters.ExponentialSmoothing). All parameters from this library can be passed to the model.
@@ -880,7 +876,7 @@ Resulting metrics of the model are described [here](#vmanomaly-output).
### [MAD (Median Absolute Deviation)](https://en.wikipedia.org/wiki/Median_absolute_deviation)
> `MADModel` is a [univariate](#univariate-models), [offline](#offline-models) model.
> `MADModel` is a [univariate](#univariate-models), [non-rolling](#non-rolling-models), [offline](#offline-models) model.
The MAD model is a robust method for anomaly detection that is *less sensitive* to outliers in data compared to standard deviation-based models. It considers a point as an anomaly if the absolute deviation from the median is significantly large.
@@ -915,7 +911,7 @@ Resulting metrics of the model are described [here](#vmanomaly-output).
### Online MAD
> `OnlineMADModel` is a [univariate](#univariate-models), [online](#online-models) model.
> `OnlineMADModel` is a [univariate](#univariate-models), [non-rolling](#non-rolling-models), [online](#online-models) model.
The MAD model is a robust method for anomaly detection that is *less sensitive* to outliers in data compared to standard deviation-based models. It considers a point as an anomaly if the absolute deviation from the median is significantly large. This is the online approximate version, based on [t-digests](https://www.sciencedirect.com/science/article/pii/S2665963820300403) for online quantile estimation{{% available_from "v1.15.0" anomaly %}}.
@@ -955,7 +951,7 @@ Resulting metrics of the model are described [here](#vmanomaly-output).
### [Rolling Quantile](https://en.wikipedia.org/wiki/Quantile)
> `RollingQuantileModel` **is** {{% available_from "v1.28.0" anomaly %}} a [univariate](#univariate-models), [online](#online-models) model. It **was** {{% deprecated_from "v1.28.0" anomaly %}} a [univariate](#univariate-models), [rolling](#rolling-models), [offline](#offline-models) model.
> `RollingQuantileModel` is a [univariate](#univariate-models), [rolling](#rolling-models), [offline](#offline-models) model.
This model is best used on **data with short evolving patterns** (i.e. 10-100 datapoints of particular frequency), as it adapts to changes over a rolling window.
@@ -970,7 +966,7 @@ This model is best used on **data with short evolving patterns** (i.e. 10-100 da
```yaml
models:
your_desired_alias_for_a_model:
class: "rolling_quantile"
class: "rolling_quantile" # or 'model.rolling_quantile.RollingQuantileModel' until v1.13.0
quantile: 0.9
window_steps: 96
# Common arguments for built-in model, if not set, default to
@@ -991,11 +987,11 @@ Resulting metrics of the model are described [here](#vmanomaly-output).
### Online Seasonal Quantile
> `OnlineQuantileModel` is a [univariate](#univariate-models), [online](#online-models) model.
> `OnlineQuantileModel` is a [univariate](#univariate-models), [non-rolling](#non-rolling-models), [online](#online-models) model.
Online (seasonal) quantile utilizes a set of approximate distributions, based on [t-digests](https://www.sciencedirect.com/science/article/pii/S2665963820300403) for online quantile estimation {{% available_from "v1.15.0" anomaly %}}.
Online (seasonal) quantile utilizes a set of approximate distributions, based on [t-digests](https://www.sciencedirect.com/science/article/pii/S2665963820300403) for online quantile estimation{{% available_from "v1.15.0" anomaly %}}.
Best used on **[de-trended](https://victoriametrics.com/blog/victoriametrics-anomaly-detection-handbook-chapter-1/#trend) data with strong (potentially multiple) [seasonalities](https://victoriametrics.com/blog/victoriametrics-anomaly-detection-handbook-chapter-1/#seasonality)**. Can act as a (slightly less flexible) replacement to [`ProphetModel`](#prophet).
Best used on **[de-trended](https://victoriametrics.com/blog/victoriametrics-anomaly-detection-handbook-chapter-1/#trend) data with strong (possibly multiple) [seasonalities](https://victoriametrics.com/blog/victoriametrics-anomaly-detection-handbook-chapter-1/#seasonality)**. Can act as a (slightly less powerful) replacement to [`ProphetModel`](#prophet).
It uses the `quantiles` triplet to calculate `yhat_lower`, `yhat`, and `yhat_upper` [output](#vmanomaly-output), respectively, for each of the `min_subseasons` sub-intervals contained in `seasonal_interval`. For example, with '4d' + '2h' seasonality patterns (multiple), it will hold and update 24*4 / 2 = 48 consecutive estimates (each 2 hours long).
@@ -1048,9 +1044,9 @@ Resulting metrics of the model are described [here](#vmanomaly-output).
### [Seasonal Trend Decomposition](https://en.wikipedia.org/wiki/Seasonal_adjustment)
> `StdModel` **is** {{% available_from "v1.28.0" anomaly %}} a [univariate](#univariate-models), [online](#online-models) model. It **was** {{% deprecated_from "v1.28.0" anomaly %}} a [univariate](#univariate-models), [rolling](#rolling-models), [offline](#offline-models) model.
> `StdModel` is a [univariate](#univariate-models), [rolling](#rolling-models), [offline](#offline-models) model.
Here we use Seasonal Decompose implementation from `statsmodels` [library](https://www.statsmodels.org/dev/generated/statsmodels.tsa.seasonal.seasonal_decompose). Parameters from this library can be passed to the model. Some parameters are specifically predefined in `vmanomaly` and can't be changed by user (`model`='additive', `two_sided`=False).
Here we use Seasonal Decompose implementation from `statsmodels` [library](https://www.statsmodels.org/dev/generated/statsmodels.tsa.seasonal.seasonal_decompose). Parameters from this library can be passed to the model. Some parameters are specifically predefined in `vmanomaly` and can't be changed by user(`model`='additive', `two_sided`=False).
*Parameters specific for vmanomaly*:
@@ -1091,9 +1087,9 @@ Resulting metrics of the model are described [here](#vmanomaly-output).
### [Isolation forest](https://en.wikipedia.org/wiki/Isolation_forest) (Multivariate)
> `IsolationForestModel` is a [univariate](#univariate-models), [offline](#offline-models) model.
> `IsolationForestModel` is a [univariate](#univariate-models), [non-rolling](#non-rolling-models), [offline](#offline-models) model.
> `IsolationForestMultivariateModel` is a [multivariate](#multivariate-models), [offline](#offline-models) model.
> `IsolationForestMultivariateModel` is a [multivariate](#multivariate-models), [non-rolling](#non-rolling-models), [offline](#offline-models) model.
Detects anomalies using binary trees. The algorithm has a linear time complexity and a low memory requirement, which works well with high-volume data. It can be used on both univariate and multivariate data, but it is more effective in multivariate case.
@@ -1103,7 +1099,7 @@ Here we use Isolation Forest implementation from `scikit-learn` [library](https:
*Parameters specific for vmanomaly*:
* `class` (string) - model class name `"model.isolation_forest.IsolationForestMultivariateModel"` (or `isolation_forest_multivariate` with class alias support {{% available_from "v1.13.0" anomaly %}})
* `class` (string) - model class name `"model.isolation_forest.IsolationForestMultivariateModel"` (or `isolation_forest_multivariate` with class alias support{{% available_from "v1.13.0" anomaly %}})
* `contamination` (float or string, optional) - The amount of contamination of the data set, i.e. the proportion of outliers in the data set. Used when fitting to define the threshold on the scores of the samples. Default value - "auto". Should be either `"auto"` or be in the range (0.0, 0.5].
@@ -1182,11 +1178,11 @@ Here in this guide, we will
- Define VictoriaMetrics Anomaly Detection config file to use our custom model
- Run service
> The file containing the model should be written in [Python language](https://www.python.org/) (3.12+)
> The file containing the model should be written in [Python language](https://www.python.org/) (3.11+)
### 1. Custom model
> By default, each custom model is created as [**univariate**](#univariate-models) model. If you want to override this behavior, define models having `is_multivariate` class argument set to `True` (please refer to the code example below).
> By default, each custom model is created as [**univariate**](#univariate-models) / [**non-rolling**](#non-rolling-models) model. If you want to override this behavior, define models inherited from `RollingModel` (to get a rolling model), or having `is_multivariate` class arg set to `True` (please refer to the code example below).
We'll create `custom_model.py` file with `CustomModel` class that will inherit from `vmanomaly`'s `Model` base class.
In the `CustomModel` class, the following methods are required: - `__init__`, `fit`, `infer`, `serialize` and `deserialize`:
@@ -1198,7 +1194,7 @@ In the `CustomModel` class, the following methods are required: - `__init__`, `f
super().__init__(**kwargs)
```
to initialize the base class each model derives from
* `fit` method should contain the model training process.
* `fit` method should contain the model training process. Please be aware that for `RollingModel` defining `fit` method is not needed, as the whole fit/infer process should be defined completely in `infer` method.
* `infer` should return Pandas.DataFrame object with model's inferences.
* `serialize` method that saves the model on disk.
* `deserialize` load the saved model from disk.
@@ -1270,7 +1266,7 @@ class CustomModel(Model):
### 2. Configuration file
Next, we need to create `config.yaml` file with `vmanomaly` configuration and model input parameters.
In the config file's `models` section we need to set our model class to `model.custom.CustomModel` (or `custom` with class alias support {{% available_from "v1.13.0" anomaly %}}) and define all parameters used in `__init__` method.
In the config file's `models` section we need to set our model class to `model.custom.CustomModel` (or `custom` with class alias support{{% available_from "v1.13.0" anomaly %}}) and define all parameters used in `__init__` method.
You can find out more about configuration parameters in `vmanomaly` [config docs](https://docs.victoriametrics.com/anomaly-detection/components/).
```yaml
@@ -1318,7 +1314,7 @@ monitoring:
Let's pull the docker image for `vmanomaly`:
```sh
docker pull victoriametrics/vmanomaly:v1.28.0
docker pull victoriametrics/vmanomaly:v1.27.1
```
Now we can run the docker container putting as volumes both config and model file:
@@ -1332,7 +1328,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.28.0 /config.yaml \
victoriametrics/vmanomaly:v1.27.1 /config.yaml \
--licenseFile=/license
--watch
```

View File

@@ -454,8 +454,6 @@ In **Inference only** mode {{% available_from "v1.22.1" anomaly %}}, the schedul
- `from`, `to` (or `from_iso`, `to_iso`): Overall inference-only timeframe.
- `fit_window`: Duration of historical data used for each training run (e.g. `P7D`, `PT1H`).
- `fit_every`: Interval between consecutive training/inference cycles.
- {{% available_from "v1.28.0" anomaly %}} `exact`: If set to `true`, BacktestingScheduler will execute inference for online models in small chronological batches equal to `infer_every` to mimic the production scheduler. (default: `false`)
- {{% available_from "v1.28.0" anomaly %}} `infer_every`: Optional inference cadence for exact mode, defining how often the scheduler should call infer between two fits, otherwise defaults to `fit_every` when unset.
- `n_jobs`: Number of parallel jobs for backtesting (default: `1`).
#### Example
@@ -469,8 +467,6 @@ schedulers:
class: "backtesting"
fit_window: "P7D" # train on the 7-day window preceding each inference
fit_every: "PT12H" # inference interval of 12 hours
exact: true # enable exact mode for online models, doesn't affect offline models
infer_every: "PT1H" # inference cadence for exact mode, only used if exact: true
inference_only: true # use [from, to] to construct inference windows only
from_iso: "2025-05-08T03:00:00Z"
to_iso: "2025-05-09T00:00:00Z"
@@ -603,9 +599,6 @@ The same *explicit* logic as in [Periodic scheduler](#periodic-scheduler)
</table>
### Defining inference timeframe
> {{% available_from "v1.28.0" anomaly %}} The inference window can be *explicitly* defined by `infer_every: {xxx}{unit}` and `exact=True` parameters above for [online](https://docs.victoriametrics.com/anomaly-detection/components/models/#online-models) models, otherwise the legacy *implicit* logic below is used for both [offline](https://docs.victoriametrics.com/anomaly-detection/components/models/#offline-models) and online models with `exact=False`.
In `BacktestingScheduler`, the inference window is *implicitly* defined as a period between 2 consecutive model `fit_every` runs. The *latest* inference window starts from `to_s` - `fit_every` and ends on the *latest available* time point, which is `to_s`. The previous periods for fit/infer are defined the same way, by shifting `fit_every` seconds backwards until we get the last full fit period of `fit_window` size, which start is >= `from_s`.
<table class="params">
<thead>
@@ -650,9 +643,7 @@ schedulers:
from_iso: '2021-01-01T00:00:00Z'
to_iso: '2021-01-14T00:00:00Z'
fit_window: 'P14D'
fit_every: 'PT1D'
exact: true # enable exact mode for online models, doesn't affect offline models
infer_every: 'PT1H' # inference cadence for exact mode, only used if exact=true
fit_every: 'PT1H'
n_jobs: 1 # default = 1 (sequential), set it up to # of CPUs for parallel execution
```
@@ -665,8 +656,6 @@ schedulers:
from_s: 167253120
to_s: 167443200
fit_window: '14d'
fit_every: '1d'
exact: true # enable exact mode for online models, doesn't affect offline models
infer_every: '1h' # inference cadence for exact mode, only used if exact=true
fit_every: '1h'
n_jobs: 1 # default = 1 (sequential), set it up to # of CPUs for parallel execution
```

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 KiB

After

Width:  |  Height:  |  Size: 227 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -249,27 +249,27 @@ services:
- grafana_data:/var/lib/grafana/
vmsingle:
image: victoriametrics/victoria-metrics:v1.130.0
image: victoriametrics/victoria-metrics:v1.129.1
command:
- -httpListenAddr=0.0.0.0:8429
vmstorage:
image: victoriametrics/vmstorage:v1.130.0-cluster
image: victoriametrics/vmstorage:v1.129.1-cluster
vminsert:
image: victoriametrics/vminsert:v1.130.0-cluster
image: victoriametrics/vminsert:v1.129.1-cluster
command:
- -storageNode=vmstorage:8400
- -httpListenAddr=0.0.0.0:8480
vmselect:
image: victoriametrics/vmselect:v1.130.0-cluster
image: victoriametrics/vmselect:v1.129.1-cluster
command:
- -storageNode=vmstorage:8401
- -httpListenAddr=0.0.0.0:8481
vmagent:
image: victoriametrics/vmagent:v1.130.0
image: victoriametrics/vmagent:v1.129.1
volumes:
- ./scrape.yaml:/etc/vmagent/config.yaml
command:
@@ -278,7 +278,7 @@ services:
- -remoteWrite.url=http://vmsingle:8429/api/v1/write
vmgateway-cluster:
image: victoriametrics/vmgateway:v1.130.0-enterprise
image: victoriametrics/vmgateway:v1.129.1-enterprise
ports:
- 8431:8431
volumes:
@@ -294,7 +294,7 @@ services:
- -auth.oidcDiscoveryEndpoints=http://keycloak:8080/realms/master/.well-known/openid-configuration
vmgateway-single:
image: victoriametrics/vmgateway:v1.130.0-enterprise
image: victoriametrics/vmgateway:v1.129.1-enterprise
ports:
- 8432:8431
volumes:
@@ -405,7 +405,7 @@ Once iDP configuration is done, vmagent configuration needs to be updated to use
```yaml
vmagent:
image: victoriametrics/vmagent:v1.130.0
image: victoriametrics/vmagent:v1.129.1
volumes:
- ./scrape.yaml:/etc/vmagent/config.yaml
- ./vmagent-client-secret:/etc/vmagent/oauth2-client-secret

View File

@@ -146,7 +146,7 @@ A Kubernetes environment that produces 5k time series per second with 1-year of
`(1 byte-per-sample * 5000 time series * 2 replication factor * 34128000 seconds) * 1.2 ) / 2^30 = 381 GB`
VictoriaMetrics requires additional disk space for the index. The lower Churn Rate, the lower is disk space usage for the index.
Usually, index takes about **20%** of the disk space for storing data. High cardinality setups may use **>50%** of storage size for index. If your indexdb looks unexpectedly large, see [FAQ: Why indexdb size is so large?](https://docs.victoriametrics.com/victoriametrics/faq/#why-indexdb-size-is-so-large) for typical ratios and troubleshooting tips.
Usually, index takes about **20%** of the disk space for storing data. High cardinality setups may use **>50%** of storage size for index.
You can significantly reduce the amount of disk usage by using [Downsampling](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#downsampling)
and [Retention Filters](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#retention-filters). These settings are available in VictoriaMetrics Cloud and Enterprise.
@@ -156,16 +156,13 @@ See a blog post about [reducing expenses on monitoring](https://victoriametrics.
It is [recommended](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/#cluster-setup) to run many small vmstorage
nodes over a few big vmstorage nodes. This reduces the workload increase on the remaining vmstorage nodes when some of
vmstorage nodes become temporarily unavailable. Prefer allocating the whole number of vCPU cores per each vmstorage node
for optimal performance.
vmstorage nodes become temporarily unavailable. Prefer giving at least 2 vCPU per each vmstorage node.
In general, the optimal number of vmstorage nodes is between 10 and 50. Please note, while adding more vmstorage nodes
is a straightforward process, decreasing number of vmstorage nodes is a very complex process that should be avoided.
vminsert and vmselect components are stateless, and can be easily scaled up or down. Scale them accordingly to your load.
See also [Capacity planning docs for VictoriaMetrics cluster](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/#capacity-planning).
## Align Terms with VictoriaMetrics setups
### VictoriaMetrics Cloud

View File

@@ -25,7 +25,6 @@ See also [case studies](https://docs.victoriametrics.com/victoriametrics/casestu
* [Forbes: The Agility In Cloud Observability](https://www.forbes.com/sites/adrianbridgwater/2023/07/05/the-agility-in-cloud-observability/)
* [Bedrock: Monitoring at scale with Victoria Metrics](https://tech.bedrockstreaming.com/2022/09/06/monitoring-at-scale-with-victoriametrics.html)
* [TiDB by PingCap: Scaling Observability: Why TiDB Moved from Prometheus to VictoriaMetrics](https://www.pingcap.com/blog/tidb-observability-migrating-prometheus-victoriametrics/)
* [TigrisData: We do our billing with Prometheus](https://www.tigrisdata.com/blog/billing-prometheus/)
* [Percona: Optimizing the Storage of Large Volumes of Metrics for a Long Time in VictoriaMetrics](https://percona.community/blog/2022/06/02/long-time-keeping-metrics-victoriametrics/)
* [Percona: Foiled by the Firewall: A Tale of Transition From Prometheus to VictoriaMetrics](https://www.percona.com/blog/2020/12/01/foiled-by-the-firewall-a-tale-of-transition-from-prometheus-to-victoriametrics/)
* [Percona: Observations on Better Resource Usage with Percona Monitoring and Management v2.12.0](https://www.percona.com/blog/2020/12/23/observations-on-better-resource-usage-with-percona-monitoring-and-management-v2-12-0/)
@@ -103,8 +102,7 @@ See also [case studies](https://docs.victoriametrics.com/victoriametrics/casestu
* [When metrics leak secrets: Kubernetes CTF lessons](https://programmerprodigy.code.blog/2025/09/01/when-metrics-leak-secrets-kubernetes-ctf-lessons/)
* [K3S and monitoring with VictoriaMetrics, kube-state-metrics, node-exporter and Grafana](https://j.hommet.net/k3s-victoriametrics-kube-state-metrics-node-exporter-grafana/)
* [How We Eliminated $10K+/Year in AWS Cross-Zone Data Transfer Costs with Zone-Aware Kubernetes Monitoring](https://medium.com/@vijayrauniyar1818/how-we-eliminated-10k-year-in-aws-cross-zone-data-transfer-costs-with-zone-aware-kubernetes-09fff0c2435b)
* [Why I Switched to VictoriaMetrics: Scaling from Small Business to Enterprise](https://blackmetalz.github.io/why-i-switched-to-victoriametrics-scaling-from-small-business-to-enterprise.html)
* [Backing up VictoriaMetrics Data: A Complete Guide](https://medium.com/@kanakaraju896/backing-up-victoriametrics-data-a-complete-guide-24473c74450f)
* [We do our billing with Prometheus](https://www.tigrisdata.com/blog/billing-prometheus/)
## Third-party articles and slides about VictoriaLogs

View File

@@ -803,36 +803,6 @@ Some capacity planning tips for VictoriaMetrics cluster:
See also [resource usage limits docs](#resource-usage-limits).
## Rebalancing
Every `vminsert` node evenly spreads (shards) incoming data among `vmstorage` nodes specified in the `-storageNode` command-line flag.
This guarantees even distribution of the ingested data among `vmstorage` nodes. When new `vmstorage` nodes are added to the `-storageNode`
command-line flag at `vminsert`, then only newly ingested data is distributed evenly among old and new `vmstorage` nodes, while
historical data remains on the old `vmstorage` nodes. This speeds up data ingestion and querying for the majority of production workloads,
since newly ingested data is evenly distributed among all the `vmstorage` nodes, while querying is usually performed over recently ingested data,
which is already stored among all the `vmstorage` nodes. This also provides the following benefits:
- Cluster availability and performance remains stable just after adding new `vmstorage` nodes, since network bandwidth, disk IO and CPU
isn't spent on data rebalancing among `vmstorage` nodes.
- This eliminates all the possible hard-to-troubleshoot failures which may happen during automatic data rebalancing.
For example, what happens when some of `vmstorage` nodes become unavailable during data rebalancing?
Or what happens if new `vmstorage` nodes are added to the cluster while the previous data rebalancing isn't finished yet?
- This allows building flexible cluster schemes when distinct subsets of `vminsert` nodes distribute incoming
data among different subsets of `vmstorage` nodes with different configs and hardware resources.
There are the following approaches exist for data rebalancing among old and new `vmstorage` nodes:
- To wait until historical data on the old `vmstorage` nodes is automatically deleted according
to the configured [retention](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#retention).
- To pass only new `vmstorage` addresses to `-storageNode` command-line flag at `vminsert` nodes, while passing all the `vmstorage`
addresses to `-storageNode` command-line flag at `vmselect` nodes. This enables writing new data only to new `vmstorage` nodes,
while historical data from old `vmstorage` nodes remain available for querying via `vmselect` together with the newly ingested data.
Then wait until data sizes among old and new `vmstorage` nodes become equal and then add old `vmstorage` nodes the `-storageNode`
command-line flag at `vminsert` nodes.
See also [capacity planning docs](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/#capacity-planning)
and [Why VictoriaMetrics misses automatic data re-balancing among vmstorage nodes?](https://docs.victoriametrics.com/victoriametrics/faq/#why-victoriametrics-misses-automatic-data-re-balancing-between-vmstorage-nodes).
## Resource usage limits
By default, cluster components of VictoriaMetrics are tuned for an optimal resource usage under typical workloads.
@@ -990,18 +960,6 @@ deduplication can't be guaranteed when samples and sample duplicates for the sam
It is recommended to set **the same** `-dedup.minScrapeInterval` command-line flag value to both `vmselect` and `vmstorage` nodes
to ensure query results consistency, even if storage layer didn't complete deduplication yet.
## Metrics Metadata
Cluster version of VictoriaMetrics can store metric metadata (TYPE, HELP, UNIT) {{% available_from "v1.130.0" %}}.
Metadata ingestion is disabled by default. To enable it, set `-enableMetadata=true` on `vminsert` and `vmagent`.
The metadata is stored in memory and can use up to 1% of available memory by default. The size could be adjusted by `-storage.maxMetadataStorageSize` flag.
Please note that metadata is lost after `vmstorage` restarts. It is ingested independently from metrics, so a metric may exist without metadata, and vice versa.
Metadata can be queried via the `/select/0/prometheus/api/v1/metadata` endpoint, which provides a response compatible with the Prometheus [metadata API](https://prometheus.io/docs/prometheus/latest/querying/api/#querying-metric-metadata).
If multiple vmstorage nodes return metadata for the same metric family name, or if multiple tenants have metadata for the same metric family name, vmselect returns only the first matching result.
Duplicate metadata entries are not merged or deduplicated across storages or tenants. See [/api/v1/metadata](https://docs.victoriametrics.com/victoriametrics/url-examples/#apiv1metadata) example.
## Backups
For backup configuration, please refer for [vmbackup documentation](https://docs.victoriametrics.com/victoriametrics/vmbackup/).

View File

@@ -521,8 +521,6 @@ from [alerting and recording rules](https://docs.victoriametrics.com/victoriamet
such as a few hours to a few days at most. This means that the query load between old `vmstorage` nodes and new `vmstorage` nodes
should become even within few hours / days after adding new `vmstorage` nodes.
See also [rebalancing docs at VictoriaMetrics cluster](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/#rebalancing).
## Why VictoriaMetrics misses automatic recovery of replication factor?
VictoriaMetrics doesn't restore [replication factor](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/#replication-and-data-safety)

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