mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2026-07-28 03:22:38 +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>
299 lines
13 KiB
Plaintext
299 lines
13 KiB
Plaintext
{% import (
|
|
"fmt"
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/htmlcomponents"
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutil"
|
|
) %}
|
|
|
|
{% stripspace %}
|
|
|
|
{% func RelabelDebugSteps(targetURL, targetID, format string, dss []DebugStep, metric, relabelConfigs string, urlRelabelIndexLength, urlRelabelIndexCurrent int, isTargetRelabel bool, err error) %}
|
|
{% if format == "json" %}
|
|
{%= RelabelDebugStepsJSON(targetURL, targetID, dss, metric, relabelConfigs, urlRelabelIndexLength, urlRelabelIndexCurrent, isTargetRelabel, err) %}
|
|
{% else %}
|
|
{%= RelabelDebugStepsHTML(targetURL, targetID, dss, metric, relabelConfigs, urlRelabelIndexLength, urlRelabelIndexCurrent, isTargetRelabel, err) %}
|
|
{% endif %}
|
|
{% endfunc %}
|
|
|
|
{% func RelabelDebugStepsHTML(targetURL, targetID string, dss []DebugStep, metric, relabelConfigs string, urlRelabelIndexLength, urlRelabelIndexCurrent int, isTargetRelabel bool, err error) %}
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
{%= htmlcomponents.CommonHeader() %}
|
|
<title>Metric relabel debug</title>
|
|
<script>
|
|
function setRelabelDebugFormMethod(form) {
|
|
form.method = (form.elements["relabel_configs"].value.length + form.elements["metric"].value.length > 1000) ? "POST" : "GET";
|
|
}
|
|
|
|
function reloadRelabelConfigs(select) {
|
|
if (!confirm('Reload will discard all modifications to the current configuration. Continue?')) {
|
|
select.value = select.prevValue;
|
|
return;
|
|
}
|
|
document.getElementById('reload_url_relabel_configs').value = '1';
|
|
setRelabelDebugFormMethod(select.form);
|
|
select.form.submit();
|
|
}
|
|
|
|
function initRelabelConfigsHighlight() {
|
|
var ta = document.getElementById('relabel-configs-input');
|
|
var bd = document.getElementById('relabel-configs-backdrop');
|
|
if (!ta || !bd) return;
|
|
function escapeHtml(s) {
|
|
return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
|
}
|
|
function highlight(text) {
|
|
return text.split('\n').map(function(line) {
|
|
var e = escapeHtml(line);
|
|
return /^\s*#/.test(line)
|
|
? '<span style="color:#999">'+e+'</span>'
|
|
: '<span style="color:#212529">'+e+'</span>';
|
|
}).join('\n');
|
|
}
|
|
function update() { bd.innerHTML = highlight(ta.value)+'\n'; }
|
|
ta.addEventListener('input', update);
|
|
ta.addEventListener('scroll', function() { bd.scrollTop = ta.scrollTop; });
|
|
update();
|
|
}
|
|
document.addEventListener('DOMContentLoaded', initRelabelConfigsHighlight);
|
|
</script>
|
|
</head>
|
|
<body>
|
|
{%= htmlcomponents.Navbar() %}
|
|
<div class="container-fluid">
|
|
<a href="https://docs.victoriametrics.com/victoriametrics/relabeling/" target="_blank">Relabeling Cookbook</a>{% space %}|{% space %}
|
|
<a href="https://docs.victoriametrics.com/victoriametrics/relabeling/#relabeling-stages" target="_blank">Relabeling Stages</a>
|
|
|
|
{% if targetID != "" %}
|
|
{% space %}|{% space %}
|
|
{% if targetURL != "" %}
|
|
<a href="metric-relabel-debug?id={%s targetID %}">Metric relabel debug</a>
|
|
{% else %}
|
|
<a href="target-relabel-debug?id={%s targetID %}">Target relabel debug</a>
|
|
{% endif %}
|
|
{% endif %}
|
|
|
|
<br>
|
|
{% if err != nil %}
|
|
{%= htmlcomponents.ErrorNotification(err) %}
|
|
{% endif %}
|
|
|
|
<div class="m-3">
|
|
<form method="POST" onsubmit="setRelabelDebugFormMethod(this)">
|
|
{%= relabelDebugFormInputs(metric, relabelConfigs, urlRelabelIndexLength, urlRelabelIndexCurrent, isTargetRelabel, targetID) %}
|
|
{% if targetID != "" %}
|
|
<input type="hidden" name="id" value="{%s targetID %}" />
|
|
{% endif %}
|
|
<input type="submit" value="Submit" class="btn btn-primary m-1" />
|
|
{% if targetID != "" %}
|
|
<button type="button" onclick="location.href='?id={%s targetID %}'" class="btn btn-secondary m-1">Reset All</button>
|
|
{% endif %}
|
|
</form>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<main class="col-12">
|
|
{%= relabelDebugSteps(dss, targetURL, targetID) %}
|
|
</main>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
{% endfunc %}
|
|
|
|
{% func relabelDebugFormInputs(metric, relabelConfigs string, urlRelabelIndexLength, urlRelabelIndexCurrent int, isTargetRelabel bool, targetID string) %}
|
|
<div>
|
|
<!-- show remote write relabel reload only for scrape metric relabel debug and pure relabel debug. discovery debug should not display this section -->
|
|
{% if !isTargetRelabel %}
|
|
<div>
|
|
<div class="m-1">
|
|
<div class="d-flex align-items-center gap-2 mt-1">
|
|
Configs:
|
|
{% if urlRelabelIndexLength > 0 %}
|
|
<input type="hidden" name="reload_url_relabel_configs" id="reload_url_relabel_configs" value="" />
|
|
<select name="url_relabel_configs_index" class="form-select form-select-sm w-auto" onfocus="this.prevValue=this.value" onchange="reloadRelabelConfigs(this)">
|
|
{% for i := range urlRelabelIndexLength %}
|
|
{% if urlRelabelIndexCurrent == i %}
|
|
<option value="{%d i %}" selected="selected">remote-write-url-{%d i %}</option>
|
|
{% else %}
|
|
<option value="{%d i %}">remote-write-url-{%d i %}</option>
|
|
{% endif %}
|
|
{% endfor %}
|
|
</select>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
Configs:
|
|
{% endif %}
|
|
|
|
<!-- the following text area css was generated with the help of AI to display yaml comments (starting with #) in gray. it could be rewritten in the future -->
|
|
<div class="m-1" style="position:relative;height:15em;">
|
|
<div id="relabel-configs-backdrop" style="position:absolute;top:0;left:0;right:0;bottom:0;pointer-events:none;overflow:hidden;font-family:monospace;white-space:pre-wrap;padding:0.375rem 0.75rem;border:1px solid transparent;"></div>
|
|
<textarea id="relabel-configs-input" name="relabel_configs" class="form-control" style="position:absolute;top:0;left:0;height:100%;font-family:monospace;color:transparent;caret-color:#212529;background:transparent;resize:none;overflow-y:scroll;">
|
|
{%s relabelConfigs %}
|
|
</textarea>
|
|
</div>
|
|
</div>
|
|
<div class="mt-2">
|
|
A Time Series:<br/>
|
|
<textarea name="metric" style="width: 100%; height: 5em; font-family: monospace" class="m-1" placeholder="up{job="job_name",instance="host:port"}">{%s metric %}</textarea>
|
|
</div>
|
|
{% endfunc %}
|
|
|
|
{% func relabelDebugSteps(dss []DebugStep, targetURL, targetID string) %}
|
|
{% if len(dss) > 0 %}
|
|
<div class="m-3">
|
|
<b>Original labels:</b> <samp>{%= mustFormatLabels(dss[0].In) %}</samp>
|
|
</div>
|
|
{% endif %}
|
|
<table class="table table-striped table-hover table-bordered table-sm">
|
|
<thead>
|
|
<tr>
|
|
<th scope="col" style="width: 5%">Step</th>
|
|
<th scope="col" style="width: 25%">Relabeling Rule</th>
|
|
<th scope="col" style="width: 35%">Input Labels</th>
|
|
<th scope="col" stile="width: 35%">Output labels</a>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for i, ds := range dss %}
|
|
{% code
|
|
inLabels, inErr := promutil.NewLabelsFromString(ds.In)
|
|
outLabels, outErr := promutil.NewLabelsFromString(ds.Out)
|
|
changedLabels := getChangedLabelNames(inLabels, outLabels)
|
|
%}
|
|
<tr>
|
|
<td>{%d i %}</td>
|
|
<td><b><pre class="m-2">{%s ds.Rule %}</pre></b></td>
|
|
<td>
|
|
{% if inErr == nil %}
|
|
<div class="m-2" style="font-size: 0.9em" title="deleted and updated labels highlighted in red">
|
|
{%= labelsWithHighlight(inLabels, changedLabels, "#D15757") %}
|
|
</div>
|
|
{% else %}
|
|
<div class="m-2" style="font-size: 0.9em; color: red" title="error parsing input labels">
|
|
<pre>{%s inErr.Error() %}</pre>
|
|
</div>
|
|
{% break %}
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
{% if outErr == nil %}
|
|
<div class="m-2" style="font-size: 0.9em" title="added and updated labels highlighted in blue">
|
|
{%= labelsWithHighlight(outLabels, changedLabels, "#4495e0") %}
|
|
</div>
|
|
{% else %}
|
|
<div class="m-2" style="font-size: 0.9em; color: red" title="error parsing output labels">
|
|
<pre>{%s outErr.Error() %}</pre>
|
|
</div>
|
|
{% break %}
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
{% if len(dss) > 0 %}
|
|
<div class="m-3">
|
|
<b>Resulting labels:</b> <samp>{%= mustFormatLabels(dss[len(dss)-1].Out) %}</samp>
|
|
{% if targetURL != "" %}
|
|
<div>
|
|
<b>Target URL:</b>{% space %}<a href="{%s targetURL %}" target="_blank">{%s targetURL %}</a>
|
|
{% if targetID != "" %}
|
|
{% space %}
|
|
(<a href="target_response?id={%s targetID %}" target="_blank" title="click to fetch target response on behalf of the scraper">response</a>)
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
{% endfunc %}
|
|
|
|
{% func RelabelDebugStepsJSON(targetURL, targetID string, dss []DebugStep, metric, relabelConfigs string, urlRelabelIndexLength, urlRelabelIndexCurrent int, isTargetRelabel bool, err error) %}
|
|
{
|
|
{% if err != nil %}
|
|
"status": "error",
|
|
"error": {%q= fmt.Sprintf("Error: %s", err) %}
|
|
{% else %}
|
|
{% code var hasError bool %}
|
|
"status": "success",
|
|
"steps": [
|
|
{% for i, ds := range dss %}
|
|
{% code
|
|
inLabels, inErr := promutil.NewLabelsFromString(ds.In)
|
|
outLabels, outErr := promutil.NewLabelsFromString(ds.Out)
|
|
changedLabels := getChangedLabelNames(inLabels, outLabels)
|
|
%}
|
|
{
|
|
"inLabels": {%q= labelsWithHighlight(inLabels, changedLabels, "#D15757") %},
|
|
"outLabels": {%q= labelsWithHighlight(outLabels, changedLabels, "#4495e0") %},
|
|
"rule": {%q= ds.Rule %},
|
|
"errors": {
|
|
{% if inErr != nil %}
|
|
"inLabels": {%q= `<span style="color: #D15757">`+inErr.Error()+`</span>` %}{% if outErr != nil %},{% endif %}
|
|
{%code hasError = true %}
|
|
{% else %}
|
|
{% endif %}
|
|
{% if outErr != nil %}
|
|
"outLabels": {%q= `<span style="color: #D15757">`+outErr.Error()+`</span>` %}
|
|
{%code hasError = true %}
|
|
{% endif %}
|
|
}
|
|
}
|
|
{% if i != len(dss)-1 %},{% endif %}
|
|
{% endfor %}
|
|
]
|
|
{% if len(dss) > 0 && !hasError %}
|
|
,
|
|
"originalLabels": {%q= mustFormatLabels(dss[0].In) %},
|
|
"resultingLabels": {%q= mustFormatLabels(dss[len(dss)-1].Out) %}
|
|
{% endif %}
|
|
{% endif %}
|
|
}
|
|
{% endfunc %}
|
|
|
|
{% func labelsWithHighlight(labels *promutil.Labels, highlight map[string]struct{}, color string) %}
|
|
{% code
|
|
labelsList := labels.GetLabels()
|
|
metricName := ""
|
|
for i, label := range labelsList {
|
|
if label.Name == "__name__" {
|
|
metricName = label.Value
|
|
labelsList = append(labelsList[:i], labelsList[i+1:]...)
|
|
break
|
|
}
|
|
}
|
|
%}
|
|
{% if metricName != "" %}
|
|
{% if _, ok := highlight["__name__"]; ok %}
|
|
<span style="font-weight:bold;color:{%s color %}">{%s metricName %}</span>
|
|
{% else %}
|
|
{%s metricName %}
|
|
{% endif %}
|
|
{% if len(labelsList) == 0 %}{% return %}{% endif %}
|
|
{% endif %}
|
|
{
|
|
{% for i, label := range labelsList %}
|
|
{% if _, ok := highlight[label.Name]; ok %}
|
|
<span style="font-weight:bold;color:{%s color %}">{%s label.Name %}={%q label.Value %}</span>
|
|
{% else %}
|
|
{%s label.Name %}={%q label.Value %}
|
|
{% endif %}
|
|
{% if i < len(labelsList)-1 %},{% space %}{% endif %}
|
|
{% endfor %}
|
|
}
|
|
{% endfunc %}
|
|
|
|
{% func mustFormatLabels(s string) %}
|
|
{% code labels, err := promutil.NewLabelsFromString(s) %}
|
|
{% if err != nil %}
|
|
<span style="color: red" title="error parsing labels: {%s err.Error() %}">{%s "error parsing labels: " + err.Error() %}</span>
|
|
{% else %}
|
|
{%= labelsWithHighlight(labels, nil, "") %}
|
|
{% endif %}
|
|
{% endfunc %}
|
|
|
|
{% endstripspace %}
|