mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2026-08-14 03:34:15 +03:00
Target metrics relabel debug UI now preloads relabeling rules configured in `-promscrape.config`, `-remoteWrite.relabelConfig` and `-remoteWrite.urlRelabelConfig` flags.
Metrics relabel debug UI now preloads relabeling rules configured in `-remoteWrite.relabelConfig` and `-remoteWrite.urlRelabelConfig` flags.
The configs are ordered in the same order they are executed by vmagent. They also contain hint comments that highlight from which flag they come.
The select at the top lets you change which remote write configuration to use. The first one is preloaded by default.
Other UI improvements:
- renamed "Labels" -> "A Time Series", "Reset" -> "Reset All",
"Relabel Configs" -> "Configs"
- pre-populate "A Time Series" field with an `up` metric placeholder
- display YAML comments (lines starting with #) in gray
- added links to "Relabeling Cookbook" and "Relabeling Stages" docs
- added JSONPath hints for -promscrape.config relabel config fields
- use POST when reloading config content larger than 1KB
- improved error messages when multiple time series are provided
Fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/9918
PR https://github.com/VictoriaMetrics/VictoriaMetrics/pull/10607
---------
Signed-off-by: Zhu Jiekun <jiekun@victoriametrics.com>
Co-authored-by: Max Kotliar <mkotlyar@victoriametrics.com>
173 lines
5.8 KiB
Go
173 lines
5.8 KiB
Go
package promrelabel
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutil"
|
|
)
|
|
|
|
// WriteMetricRelabelDebug writes /metric-relabel-debug page to w with the corresponding args.
|
|
func WriteMetricRelabelDebug(w io.Writer, targetID, metric, relabelConfigs string, urlRelabelIndexLength, urlRelabelIndexCurrent int, format string, err error) {
|
|
writeRelabelDebug(w, false, targetID, metric, relabelConfigs, urlRelabelIndexLength, urlRelabelIndexCurrent, format, err)
|
|
}
|
|
|
|
// WriteTargetRelabelDebug writes /target-relabel-debug page to w with the corresponding args.
|
|
func WriteTargetRelabelDebug(w io.Writer, targetID, metric, relabelConfigs, format string, err error) {
|
|
writeRelabelDebug(w, true, targetID, metric, relabelConfigs, 0, 0, format, err)
|
|
}
|
|
|
|
func writeRelabelDebug(w io.Writer, isTargetRelabel bool, targetID, metric, relabelConfigs string, urlRelabelIndexLength, urlRelabelIndexCurrent int, format string, err error) {
|
|
if metric == "" {
|
|
metric = "{}"
|
|
}
|
|
targetURL := ""
|
|
if err != nil {
|
|
WriteRelabelDebugSteps(w, targetURL, targetID, format, nil, metric, relabelConfigs, urlRelabelIndexLength, urlRelabelIndexCurrent, isTargetRelabel, err)
|
|
return
|
|
}
|
|
|
|
metric, err = normalizeInputLabels(metric)
|
|
if err != nil {
|
|
err = fmt.Errorf("cannot parse metric: %w", err)
|
|
WriteRelabelDebugSteps(w, targetURL, targetID, format, nil, metric, relabelConfigs, urlRelabelIndexLength, urlRelabelIndexCurrent, isTargetRelabel, err)
|
|
return
|
|
}
|
|
|
|
labels, err := promutil.NewLabelsFromString(metric)
|
|
if err != nil {
|
|
err = fmt.Errorf("cannot parse metric: %w", err)
|
|
WriteRelabelDebugSteps(w, targetURL, targetID, format, nil, metric, relabelConfigs, urlRelabelIndexLength, urlRelabelIndexCurrent, isTargetRelabel, err)
|
|
return
|
|
}
|
|
|
|
pcs, err := ParseRelabelConfigsData([]byte(relabelConfigs))
|
|
if err != nil {
|
|
err = fmt.Errorf("cannot parse relabel configs: %w", err)
|
|
WriteRelabelDebugSteps(w, targetURL, targetID, format, nil, metric, relabelConfigs, urlRelabelIndexLength, urlRelabelIndexCurrent, isTargetRelabel, err)
|
|
return
|
|
}
|
|
|
|
dss, targetURL := newDebugRelabelSteps(pcs, labels, isTargetRelabel)
|
|
WriteRelabelDebugSteps(w, targetURL, targetID, format, dss, metric, relabelConfigs, urlRelabelIndexLength, urlRelabelIndexCurrent, isTargetRelabel, nil)
|
|
}
|
|
|
|
func newDebugRelabelSteps(pcs *ParsedConfigs, labels *promutil.Labels, isTargetRelabel bool) ([]DebugStep, string) {
|
|
// The target relabeling below must be in sync with the code at scrapeWorkConfig.getScrapeWork if isTargetRelabel=true
|
|
// and with the code at scrapeWork.addRowToTimeseries when isTargetRelabeling=false
|
|
targetURL := ""
|
|
|
|
// Prevent from modifying the original labels
|
|
labels = labels.Clone()
|
|
|
|
// Apply relabeling
|
|
labelsResult, dss := pcs.ApplyDebug(labels.GetLabels())
|
|
labels.Labels = labelsResult
|
|
outStr := LabelsToString(labels.GetLabels())
|
|
|
|
if isTargetRelabel {
|
|
// Add missing instance label
|
|
if labels.Get("instance") == "" {
|
|
address := labels.Get("__address__")
|
|
if address != "" {
|
|
inStr := outStr
|
|
labels.Add("instance", address)
|
|
outStr = LabelsToString(labels.GetLabels())
|
|
dss = append(dss, DebugStep{
|
|
Rule: "add missing instance label from __address__ label",
|
|
In: inStr,
|
|
Out: outStr,
|
|
})
|
|
}
|
|
}
|
|
|
|
// Generate targetURL
|
|
targetURL, _ = GetScrapeURL(labels, nil)
|
|
|
|
// Remove labels with __ prefix
|
|
inStr := outStr
|
|
labels.RemoveLabelsWithDoubleUnderscorePrefix()
|
|
outStr = LabelsToString(labels.GetLabels())
|
|
if inStr != outStr {
|
|
dss = append(dss, DebugStep{
|
|
Rule: "remove labels with __ prefix",
|
|
In: inStr,
|
|
Out: outStr,
|
|
})
|
|
}
|
|
} else {
|
|
// Remove labels with __ prefix except of __name__
|
|
inStr := outStr
|
|
labels.Labels = FinalizeLabels(labels.Labels[:0], labels.Labels)
|
|
outStr = LabelsToString(labels.GetLabels())
|
|
if inStr != outStr {
|
|
dss = append(dss, DebugStep{
|
|
Rule: "remove labels with __ prefix except of __name__",
|
|
In: inStr,
|
|
Out: outStr,
|
|
})
|
|
}
|
|
}
|
|
|
|
// There is no need in labels' sorting, since LabelsToString() automatically sorts labels.
|
|
return dss, targetURL
|
|
}
|
|
|
|
func getChangedLabelNames(in, out *promutil.Labels) map[string]struct{} {
|
|
inMap := in.ToMap()
|
|
outMap := out.ToMap()
|
|
changed := make(map[string]struct{})
|
|
for k, v := range outMap {
|
|
inV, ok := inMap[k]
|
|
if !ok || inV != v {
|
|
changed[k] = struct{}{}
|
|
}
|
|
}
|
|
for k, v := range inMap {
|
|
outV, ok := outMap[k]
|
|
if !ok || outV != v {
|
|
changed[k] = struct{}{}
|
|
}
|
|
}
|
|
return changed
|
|
}
|
|
|
|
// normalizeInputLabels does two things:
|
|
// 1. check if the input is (not) surrounded by braces. Inputs with or without braces are valid, but unclosed braces are not allowed.
|
|
// 2. add missing `{` and `}` to the input if needed.
|
|
//
|
|
// it does not handle complex edge cases like `{` or `}` appear multiple times. they're invalid and will not pass `NewLabelsFromString`.
|
|
func normalizeInputLabels(metric string) (string, error) {
|
|
metric = strings.TrimSpace(metric)
|
|
|
|
if strings.ContainsRune(metric, '\n') {
|
|
return metric, fmt.Errorf("only one time series is allowed; got multiple lines")
|
|
}
|
|
|
|
openBrace := strings.Contains(metric, `{`)
|
|
closeBrace := strings.Contains(metric, `}`)
|
|
|
|
if openBrace != closeBrace {
|
|
// only either `{` or `}` exist, this must be an invalid expression.
|
|
return "", fmt.Errorf("cannot unmarshal Prometheus line %q", metric)
|
|
}
|
|
|
|
if openBrace && closeBrace {
|
|
return metric, nil
|
|
}
|
|
|
|
if strings.Contains(metric, `=`) {
|
|
// special case for input like:
|
|
// 1. __name__=metric_name, label1=value1, ...
|
|
// 2. label1=value1, ...
|
|
// 3. __name__=metric_name
|
|
// add curly braces to turn it into a more common format that `NewLabelsFromString` can handle.
|
|
//
|
|
// see: https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8584 and https://github.com/VictoriaMetrics/VictoriaMetrics/issues/9900
|
|
metric = `{` + metric + `}`
|
|
}
|
|
|
|
return metric, nil
|
|
}
|