From 3d47f94149b0c974c1631cc291016e545940282a Mon Sep 17 00:00:00 2001 From: Julius Volz Date: Wed, 15 Oct 2014 17:03:08 +0200 Subject: [PATCH] Drop metric names after transformations. After many transformations, it doesn't make sense to keep the metric names, since the result of the transformation is no longer that metric. This drops the metric name after such transformations and makes the web UI deal well with missing metric names. This depends on the current branch on the following things: - prometheus/client_golang needs to be at https://github.com/prometheus/client_golang/commit/e237cf15c6967a6184e423a08154d9c6eecb330c in branch "julius/int-fingerprints" (to be merged with new storage) - prometheus/promdash needs to be at https://github.com/prometheus/promdash/commit/dd7691c9c28c36767ae9559db8eab6d6715dffa8 Change-Id: Ib3c8cad8d647d9854e8c653c424b8c235ccc231d --- rules/ast/ast.go | 25 +++++-- rules/ast/functions.go | 3 + rules/rules_test.go | 144 ++++++++++++++++++++--------------------- web/static/js/graph.js | 25 +++++-- 4 files changed, 116 insertions(+), 81 deletions(-) diff --git a/rules/ast/ast.go b/rules/ast/ast.go index be379f931..cf720eaed 100644 --- a/rules/ast/ast.go +++ b/rules/ast/ast.go @@ -83,6 +83,17 @@ const ( OR ) +// shouldDropMetric indicates whether the metric name should be dropped after +// applying this operator to a vector. +func (opType BinOpType) shouldDropMetric() bool { + switch opType { + case ADD, SUB, MUL, DIV, MOD: + return true + default: + return false + } +} + // AggrType is an enum for aggregation types. type AggrType int @@ -487,8 +498,8 @@ func (node *VectorAggregation) Eval(timestamp clientmodel.Timestamp) Vector { m := clientmodel.Metric{} if node.keepExtraLabels { m = sample.Metric + delete(m, clientmodel.MetricNameLabel) } else { - m[clientmodel.MetricNameLabel] = sample.Metric[clientmodel.MetricNameLabel] for _, l := range node.groupBy { if v, ok := sample.Metric[l]; ok { m[l] = v @@ -708,9 +719,6 @@ func evalVectorBinop(opType BinOpType, } func labelsEqual(labels1, labels2 clientmodel.Metric) bool { - if len(labels1) != len(labels2) { - return false - } for label, value := range labels1 { if labels2[label] != value && label != clientmodel.MetricNameLabel { return false @@ -730,6 +738,9 @@ func (node *VectorArithExpr) Eval(timestamp clientmodel.Timestamp) Vector { value, keep := evalVectorBinop(node.opType, lhs, rhsSample.Value) if keep { rhsSample.Value = value + if node.opType.shouldDropMetric() { + delete(rhsSample.Metric, clientmodel.MetricNameLabel) + } result = append(result, rhsSample) } } @@ -741,6 +752,9 @@ func (node *VectorArithExpr) Eval(timestamp clientmodel.Timestamp) Vector { value, keep := evalVectorBinop(node.opType, lhsSample.Value, rhs) if keep { lhsSample.Value = value + if node.opType.shouldDropMetric() { + delete(lhsSample.Metric, clientmodel.MetricNameLabel) + } result = append(result, lhsSample) } } @@ -754,6 +768,9 @@ func (node *VectorArithExpr) Eval(timestamp clientmodel.Timestamp) Vector { value, keep := evalVectorBinop(node.opType, lhsSample.Value, rhsSample.Value) if keep { lhsSample.Value = value + if node.opType.shouldDropMetric() { + delete(lhsSample.Metric, clientmodel.MetricNameLabel) + } result = append(result, lhsSample) } } diff --git a/rules/ast/functions.go b/rules/ast/functions.go index 9568efe5c..b838f09a5 100644 --- a/rules/ast/functions.go +++ b/rules/ast/functions.go @@ -133,6 +133,7 @@ func deltaImpl(timestamp clientmodel.Timestamp, args []Node) interface{} { Value: resultValue, Timestamp: timestamp, } + delete(resultSample.Metric, clientmodel.MetricNameLabel) resultVector = append(resultVector, resultSample) } return resultVector @@ -381,6 +382,7 @@ func aggrOverTime(timestamp clientmodel.Timestamp, args []Node, aggrFn func(metr continue } + delete(el.Metric, clientmodel.MetricNameLabel) resultVector = append(resultVector, &clientmodel.Sample{ Metric: el.Metric, Value: aggrFn(el.Values), @@ -446,6 +448,7 @@ func absImpl(timestamp clientmodel.Timestamp, args []Node) interface{} { n := args[0].(VectorNode) vector := n.Eval(timestamp) for _, el := range vector { + delete(el.Metric, clientmodel.MetricNameLabel) el.Value = clientmodel.SampleValue(math.Abs(float64(el.Value))) } return vector diff --git a/rules/rules_test.go b/rules/rules_test.go index b61ae78cc..eff731f9f 100644 --- a/rules/rules_test.go +++ b/rules/rules_test.go @@ -70,30 +70,30 @@ func TestExpressions(t *testing.T) { }{ { expr: `SUM(http_requests)`, - output: []string{`http_requests => 3600 @[%v]`}, + output: []string{`{} => 3600 @[%v]`}, fullRanges: 0, intervalRanges: 8, }, { expr: `SUM(http_requests{instance="0"}) BY(job)`, output: []string{ - `http_requests{job="api-server"} => 400 @[%v]`, - `http_requests{job="app-server"} => 1200 @[%v]`, + `{job="api-server"} => 400 @[%v]`, + `{job="app-server"} => 1200 @[%v]`, }, fullRanges: 0, intervalRanges: 4, }, { expr: `SUM(http_requests{instance="0"}) BY(job) KEEPING_EXTRA`, output: []string{ - `http_requests{instance="0", job="api-server"} => 400 @[%v]`, - `http_requests{instance="0", job="app-server"} => 1200 @[%v]`, + `{instance="0", job="api-server"} => 400 @[%v]`, + `{instance="0", job="app-server"} => 1200 @[%v]`, }, fullRanges: 0, intervalRanges: 4, }, { expr: `SUM(http_requests) BY (job)`, output: []string{ - `http_requests{job="api-server"} => 1000 @[%v]`, - `http_requests{job="app-server"} => 2600 @[%v]`, + `{job="api-server"} => 1000 @[%v]`, + `{job="app-server"} => 2600 @[%v]`, }, fullRanges: 0, intervalRanges: 8, @@ -101,8 +101,8 @@ func TestExpressions(t *testing.T) { // Non-existent labels mentioned in BY-clauses shouldn't propagate to output. expr: `SUM(http_requests) BY (job, nonexistent)`, output: []string{ - `http_requests{job="api-server"} => 1000 @[%v]`, - `http_requests{job="app-server"} => 2600 @[%v]`, + `{job="api-server"} => 1000 @[%v]`, + `{job="app-server"} => 2600 @[%v]`, }, fullRanges: 0, intervalRanges: 8, @@ -112,141 +112,141 @@ func TestExpressions(t *testing.T) { SUM(http_requests) BY /* comments shouldn't have any effect */ (job) // another comment`, output: []string{ - `http_requests{job="api-server"} => 1000 @[%v]`, - `http_requests{job="app-server"} => 2600 @[%v]`, + `{job="api-server"} => 1000 @[%v]`, + `{job="app-server"} => 2600 @[%v]`, }, fullRanges: 0, intervalRanges: 8, }, { expr: `COUNT(http_requests) BY (job)`, output: []string{ - `http_requests{job="api-server"} => 4 @[%v]`, - `http_requests{job="app-server"} => 4 @[%v]`, + `{job="api-server"} => 4 @[%v]`, + `{job="app-server"} => 4 @[%v]`, }, fullRanges: 0, intervalRanges: 8, }, { expr: `SUM(http_requests) BY (job, group)`, output: []string{ - `http_requests{group="canary", job="api-server"} => 700 @[%v]`, - `http_requests{group="canary", job="app-server"} => 1500 @[%v]`, - `http_requests{group="production", job="api-server"} => 300 @[%v]`, - `http_requests{group="production", job="app-server"} => 1100 @[%v]`, + `{group="canary", job="api-server"} => 700 @[%v]`, + `{group="canary", job="app-server"} => 1500 @[%v]`, + `{group="production", job="api-server"} => 300 @[%v]`, + `{group="production", job="app-server"} => 1100 @[%v]`, }, fullRanges: 0, intervalRanges: 8, }, { expr: `AVG(http_requests) BY (job)`, output: []string{ - `http_requests{job="api-server"} => 250 @[%v]`, - `http_requests{job="app-server"} => 650 @[%v]`, + `{job="api-server"} => 250 @[%v]`, + `{job="app-server"} => 650 @[%v]`, }, fullRanges: 0, intervalRanges: 8, }, { expr: `MIN(http_requests) BY (job)`, output: []string{ - `http_requests{job="api-server"} => 100 @[%v]`, - `http_requests{job="app-server"} => 500 @[%v]`, + `{job="api-server"} => 100 @[%v]`, + `{job="app-server"} => 500 @[%v]`, }, fullRanges: 0, intervalRanges: 8, }, { expr: `MAX(http_requests) BY (job)`, output: []string{ - `http_requests{job="api-server"} => 400 @[%v]`, - `http_requests{job="app-server"} => 800 @[%v]`, + `{job="api-server"} => 400 @[%v]`, + `{job="app-server"} => 800 @[%v]`, }, fullRanges: 0, intervalRanges: 8, }, { expr: `SUM(http_requests) BY (job) - COUNT(http_requests) BY (job)`, output: []string{ - `http_requests{job="api-server"} => 996 @[%v]`, - `http_requests{job="app-server"} => 2596 @[%v]`, + `{job="api-server"} => 996 @[%v]`, + `{job="app-server"} => 2596 @[%v]`, }, fullRanges: 0, intervalRanges: 8, }, { expr: `2 - SUM(http_requests) BY (job)`, output: []string{ - `http_requests{job="api-server"} => -998 @[%v]`, - `http_requests{job="app-server"} => -2598 @[%v]`, + `{job="api-server"} => -998 @[%v]`, + `{job="app-server"} => -2598 @[%v]`, }, fullRanges: 0, intervalRanges: 8, }, { expr: `1000 / SUM(http_requests) BY (job)`, output: []string{ - `http_requests{job="api-server"} => 1 @[%v]`, - `http_requests{job="app-server"} => 0.38461538461538464 @[%v]`, + `{job="api-server"} => 1 @[%v]`, + `{job="app-server"} => 0.38461538461538464 @[%v]`, }, fullRanges: 0, intervalRanges: 8, }, { expr: `SUM(http_requests) BY (job) - 2`, output: []string{ - `http_requests{job="api-server"} => 998 @[%v]`, - `http_requests{job="app-server"} => 2598 @[%v]`, + `{job="api-server"} => 998 @[%v]`, + `{job="app-server"} => 2598 @[%v]`, }, fullRanges: 0, intervalRanges: 8, }, { expr: `SUM(http_requests) BY (job) % 3`, output: []string{ - `http_requests{job="api-server"} => 1 @[%v]`, - `http_requests{job="app-server"} => 2 @[%v]`, + `{job="api-server"} => 1 @[%v]`, + `{job="app-server"} => 2 @[%v]`, }, fullRanges: 0, intervalRanges: 8, }, { expr: `SUM(http_requests) BY (job) / 0`, output: []string{ - `http_requests{job="api-server"} => +Inf @[%v]`, - `http_requests{job="app-server"} => +Inf @[%v]`, + `{job="api-server"} => +Inf @[%v]`, + `{job="app-server"} => +Inf @[%v]`, }, fullRanges: 0, intervalRanges: 8, }, { expr: `SUM(http_requests) BY (job) > 1000`, output: []string{ - `http_requests{job="app-server"} => 2600 @[%v]`, + `{job="app-server"} => 2600 @[%v]`, }, fullRanges: 0, intervalRanges: 8, }, { expr: `1000 < SUM(http_requests) BY (job)`, output: []string{ - `http_requests{job="app-server"} => 1000 @[%v]`, + `{job="app-server"} => 1000 @[%v]`, }, fullRanges: 0, intervalRanges: 8, }, { expr: `SUM(http_requests) BY (job) <= 1000`, output: []string{ - `http_requests{job="api-server"} => 1000 @[%v]`, + `{job="api-server"} => 1000 @[%v]`, }, fullRanges: 0, intervalRanges: 8, }, { expr: `SUM(http_requests) BY (job) != 1000`, output: []string{ - `http_requests{job="app-server"} => 2600 @[%v]`, + `{job="app-server"} => 2600 @[%v]`, }, fullRanges: 0, intervalRanges: 8, }, { expr: `SUM(http_requests) BY (job) == 1000`, output: []string{ - `http_requests{job="api-server"} => 1000 @[%v]`, + `{job="api-server"} => 1000 @[%v]`, }, fullRanges: 0, intervalRanges: 8, }, { expr: `SUM(http_requests) BY (job) + SUM(http_requests) BY (job)`, output: []string{ - `http_requests{job="api-server"} => 2000 @[%v]`, - `http_requests{job="app-server"} => 5200 @[%v]`, + `{job="api-server"} => 2000 @[%v]`, + `{job="app-server"} => 5200 @[%v]`, }, fullRanges: 0, intervalRanges: 8, @@ -261,22 +261,22 @@ func TestExpressions(t *testing.T) { }, { expr: `http_requests{job="api-server", group="canary"} + delta(http_requests{job="api-server"}[5m], 1)`, output: []string{ - `http_requests{group="canary", instance="0", job="api-server"} => 330 @[%v]`, - `http_requests{group="canary", instance="1", job="api-server"} => 440 @[%v]`, + `{group="canary", instance="0", job="api-server"} => 330 @[%v]`, + `{group="canary", instance="1", job="api-server"} => 440 @[%v]`, }, fullRanges: 4, intervalRanges: 0, }, { expr: `delta(http_requests[25m], 1)`, output: []string{ - `http_requests{group="canary", instance="0", job="api-server"} => 150 @[%v]`, - `http_requests{group="canary", instance="0", job="app-server"} => 350 @[%v]`, - `http_requests{group="canary", instance="1", job="api-server"} => 200 @[%v]`, - `http_requests{group="canary", instance="1", job="app-server"} => 400 @[%v]`, - `http_requests{group="production", instance="0", job="api-server"} => 50 @[%v]`, - `http_requests{group="production", instance="0", job="app-server"} => 250 @[%v]`, - `http_requests{group="production", instance="1", job="api-server"} => 100 @[%v]`, - `http_requests{group="production", instance="1", job="app-server"} => 300 @[%v]`, + `{group="canary", instance="0", job="api-server"} => 150 @[%v]`, + `{group="canary", instance="0", job="app-server"} => 350 @[%v]`, + `{group="canary", instance="1", job="api-server"} => 200 @[%v]`, + `{group="canary", instance="1", job="app-server"} => 400 @[%v]`, + `{group="production", instance="0", job="api-server"} => 50 @[%v]`, + `{group="production", instance="0", job="app-server"} => 250 @[%v]`, + `{group="production", instance="1", job="api-server"} => 100 @[%v]`, + `{group="production", instance="1", job="app-server"} => 300 @[%v]`, }, fullRanges: 8, intervalRanges: 0, @@ -360,45 +360,45 @@ func TestExpressions(t *testing.T) { // Lower-cased aggregation operators should work too. expr: `sum(http_requests) by (job) + min(http_requests) by (job) + max(http_requests) by (job) + avg(http_requests) by (job)`, output: []string{ - `http_requests{job="app-server"} => 4550 @[%v]`, - `http_requests{job="api-server"} => 1750 @[%v]`, + `{job="app-server"} => 4550 @[%v]`, + `{job="api-server"} => 1750 @[%v]`, }, fullRanges: 0, intervalRanges: 8, }, { // Deltas should be adjusted for target interval vs. samples under target interval. expr: `delta(http_requests{group="canary", instance="1", job="app-server"}[18m], 1)`, - output: []string{`http_requests{group="canary", instance="1", job="app-server"} => 288 @[%v]`}, + output: []string{`{group="canary", instance="1", job="app-server"} => 288 @[%v]`}, fullRanges: 1, intervalRanges: 0, }, { // Rates should transform per-interval deltas to per-second rates. expr: `rate(http_requests{group="canary", instance="1", job="app-server"}[10m])`, - output: []string{`http_requests{group="canary", instance="1", job="app-server"} => 0.26666666666666666 @[%v]`}, + output: []string{`{group="canary", instance="1", job="app-server"} => 0.26666666666666666 @[%v]`}, fullRanges: 1, intervalRanges: 0, }, { // Counter resets in middle of range are ignored by delta() if counter == 1. expr: `delta(testcounter_reset_middle[50m], 1)`, - output: []string{`testcounter_reset_middle => 90 @[%v]`}, + output: []string{`{} => 90 @[%v]`}, fullRanges: 1, intervalRanges: 0, }, { // Counter resets in middle of range are not ignored by delta() if counter == 0. expr: `delta(testcounter_reset_middle[50m], 0)`, - output: []string{`testcounter_reset_middle => 50 @[%v]`}, + output: []string{`{} => 50 @[%v]`}, fullRanges: 1, intervalRanges: 0, }, { // Counter resets at end of range are ignored by delta() if counter == 1. expr: `delta(testcounter_reset_end[5m], 1)`, - output: []string{`testcounter_reset_end => 0 @[%v]`}, + output: []string{`{} => 0 @[%v]`}, fullRanges: 1, intervalRanges: 0, }, { // Counter resets at end of range are not ignored by delta() if counter == 0. expr: `delta(testcounter_reset_end[5m], 0)`, - output: []string{`testcounter_reset_end => -90 @[%v]`}, + output: []string{`{} => -90 @[%v]`}, fullRanges: 1, intervalRanges: 0, }, { @@ -470,8 +470,8 @@ func TestExpressions(t *testing.T) { { expr: `abs(-1 * http_requests{group="production",job="api-server"})`, output: []string{ - `http_requests{group="production", instance="0", job="api-server"} => 100 @[%v]`, - `http_requests{group="production", instance="1", job="api-server"} => 200 @[%v]`, + `{group="production", instance="0", job="api-server"} => 100 @[%v]`, + `{group="production", instance="1", job="api-server"} => 200 @[%v]`, }, fullRanges: 0, intervalRanges: 2, @@ -479,8 +479,8 @@ func TestExpressions(t *testing.T) { { expr: `avg_over_time(http_requests{group="production",job="api-server"}[1h])`, output: []string{ - `http_requests{group="production", instance="0", job="api-server"} => 50 @[%v]`, - `http_requests{group="production", instance="1", job="api-server"} => 100 @[%v]`, + `{group="production", instance="0", job="api-server"} => 50 @[%v]`, + `{group="production", instance="1", job="api-server"} => 100 @[%v]`, }, fullRanges: 2, intervalRanges: 0, @@ -488,8 +488,8 @@ func TestExpressions(t *testing.T) { { expr: `count_over_time(http_requests{group="production",job="api-server"}[1h])`, output: []string{ - `http_requests{group="production", instance="0", job="api-server"} => 11 @[%v]`, - `http_requests{group="production", instance="1", job="api-server"} => 11 @[%v]`, + `{group="production", instance="0", job="api-server"} => 11 @[%v]`, + `{group="production", instance="1", job="api-server"} => 11 @[%v]`, }, fullRanges: 2, intervalRanges: 0, @@ -497,8 +497,8 @@ func TestExpressions(t *testing.T) { { expr: `max_over_time(http_requests{group="production",job="api-server"}[1h])`, output: []string{ - `http_requests{group="production", instance="0", job="api-server"} => 100 @[%v]`, - `http_requests{group="production", instance="1", job="api-server"} => 200 @[%v]`, + `{group="production", instance="0", job="api-server"} => 100 @[%v]`, + `{group="production", instance="1", job="api-server"} => 200 @[%v]`, }, fullRanges: 2, intervalRanges: 0, @@ -506,8 +506,8 @@ func TestExpressions(t *testing.T) { { expr: `min_over_time(http_requests{group="production",job="api-server"}[1h])`, output: []string{ - `http_requests{group="production", instance="0", job="api-server"} => 0 @[%v]`, - `http_requests{group="production", instance="1", job="api-server"} => 0 @[%v]`, + `{group="production", instance="0", job="api-server"} => 0 @[%v]`, + `{group="production", instance="1", job="api-server"} => 0 @[%v]`, }, fullRanges: 2, intervalRanges: 0, @@ -515,8 +515,8 @@ func TestExpressions(t *testing.T) { { expr: `sum_over_time(http_requests{group="production",job="api-server"}[1h])`, output: []string{ - `http_requests{group="production", instance="0", job="api-server"} => 550 @[%v]`, - `http_requests{group="production", instance="1", job="api-server"} => 1100 @[%v]`, + `{group="production", instance="0", job="api-server"} => 550 @[%v]`, + `{group="production", instance="1", job="api-server"} => 1100 @[%v]`, }, fullRanges: 2, intervalRanges: 0, diff --git a/web/static/js/graph.js b/web/static/js/graph.js index a39237359..ef2962ad5 100644 --- a/web/static/js/graph.js +++ b/web/static/js/graph.js @@ -350,7 +350,7 @@ Prometheus.Graph.prototype.renderLabels = function(labels) { } Prometheus.Graph.prototype.metricToTsName = function(labels) { - var tsName = labels["__name__"] + "{"; + var tsName = (labels["__name__"] || '') + "{"; var labelStrings = []; for (label in labels) { if (label != "__name__") { @@ -378,7 +378,7 @@ Prometheus.Graph.prototype.transformData = function(json) { } var data = json.Value.map(function(ts) { return { - name: self.metricToTsName(ts.Metric), + name: escapeHTML(self.metricToTsName(ts.Metric)), labels: ts.Metric, data: ts.Values.map(function(value) { return { @@ -438,7 +438,7 @@ Prometheus.Graph.prototype.updateGraph = function(reloadGraph) { graph: self.rickshawGraph, formatter: function(series, x, y) { var swatch = ''; - var content = swatch + series.labels["__name__"] + ": " + y + '
'; + var content = swatch + (series.labels["__name__"] || 'value') + ": " + y + '
'; return content + self.renderLabels(series.labels); }, onRender: function() { @@ -518,7 +518,7 @@ Prometheus.Graph.prototype.handleConsoleResponse = function(data, textStatus) { for (var i = 0; i < data.Value.length; i++) { var v = data.Value[i]; var tsName = self.metricToTsName(v.Metric); - tBody.append("" + tsName + "" + v.Value + "") + tBody.append("" + escapeHTML(tsName) + "" + v.Value + "") } break; case "matrix": @@ -529,7 +529,7 @@ Prometheus.Graph.prototype.handleConsoleResponse = function(data, textStatus) { for (var j = 0; j < v.Values.length; j++) { valueText += v.Values[j].Value + " @" + v.Values[j].Timestamp + "
"; } - tBody.append("" + tsName + "" + valueText + "") + tBody.append("" + escapeHTML(tsName) + "" + valueText + "") } break; case "scalar": @@ -575,6 +575,21 @@ function addGraph(options) { }); } +function escapeHTML(string) { + var entityMap = { + "&": "&", + "<": "<", + ">": ">", + '"': '"', + "'": ''', + "/": '/' + }; + + return String(string).replace(/[&<>"'\/]/g, function (s) { + return entityMap[s]; + }); +} + function init() { $.ajaxSetup({ cache: false