diff --git a/rules/ast/ast.go b/rules/ast/ast.go index be379f9311..cf720eaed2 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 9568efe5c2..b838f09a55 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 b61ae78cce..eff731f9fd 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 a392373595..ef2962ad52 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