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("