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
  e237cf15c6
  in branch "julius/int-fingerprints" (to be merged with new storage)

- prometheus/promdash needs to be at
  dd7691c9c2

Change-Id: Ib3c8cad8d647d9854e8c653c424b8c235ccc231d
This commit is contained in:
Julius Volz 2014-10-15 17:03:08 +02:00 committed by Bjoern Rabenstein
parent 53c0a43754
commit 3d47f94149
4 changed files with 116 additions and 81 deletions

View file

@ -83,6 +83,17 @@ const (
OR 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. // AggrType is an enum for aggregation types.
type AggrType int type AggrType int
@ -487,8 +498,8 @@ func (node *VectorAggregation) Eval(timestamp clientmodel.Timestamp) Vector {
m := clientmodel.Metric{} m := clientmodel.Metric{}
if node.keepExtraLabels { if node.keepExtraLabels {
m = sample.Metric m = sample.Metric
delete(m, clientmodel.MetricNameLabel)
} else { } else {
m[clientmodel.MetricNameLabel] = sample.Metric[clientmodel.MetricNameLabel]
for _, l := range node.groupBy { for _, l := range node.groupBy {
if v, ok := sample.Metric[l]; ok { if v, ok := sample.Metric[l]; ok {
m[l] = v m[l] = v
@ -708,9 +719,6 @@ func evalVectorBinop(opType BinOpType,
} }
func labelsEqual(labels1, labels2 clientmodel.Metric) bool { func labelsEqual(labels1, labels2 clientmodel.Metric) bool {
if len(labels1) != len(labels2) {
return false
}
for label, value := range labels1 { for label, value := range labels1 {
if labels2[label] != value && label != clientmodel.MetricNameLabel { if labels2[label] != value && label != clientmodel.MetricNameLabel {
return false return false
@ -730,6 +738,9 @@ func (node *VectorArithExpr) Eval(timestamp clientmodel.Timestamp) Vector {
value, keep := evalVectorBinop(node.opType, lhs, rhsSample.Value) value, keep := evalVectorBinop(node.opType, lhs, rhsSample.Value)
if keep { if keep {
rhsSample.Value = value rhsSample.Value = value
if node.opType.shouldDropMetric() {
delete(rhsSample.Metric, clientmodel.MetricNameLabel)
}
result = append(result, rhsSample) result = append(result, rhsSample)
} }
} }
@ -741,6 +752,9 @@ func (node *VectorArithExpr) Eval(timestamp clientmodel.Timestamp) Vector {
value, keep := evalVectorBinop(node.opType, lhsSample.Value, rhs) value, keep := evalVectorBinop(node.opType, lhsSample.Value, rhs)
if keep { if keep {
lhsSample.Value = value lhsSample.Value = value
if node.opType.shouldDropMetric() {
delete(lhsSample.Metric, clientmodel.MetricNameLabel)
}
result = append(result, lhsSample) 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) value, keep := evalVectorBinop(node.opType, lhsSample.Value, rhsSample.Value)
if keep { if keep {
lhsSample.Value = value lhsSample.Value = value
if node.opType.shouldDropMetric() {
delete(lhsSample.Metric, clientmodel.MetricNameLabel)
}
result = append(result, lhsSample) result = append(result, lhsSample)
} }
} }

View file

@ -133,6 +133,7 @@ func deltaImpl(timestamp clientmodel.Timestamp, args []Node) interface{} {
Value: resultValue, Value: resultValue,
Timestamp: timestamp, Timestamp: timestamp,
} }
delete(resultSample.Metric, clientmodel.MetricNameLabel)
resultVector = append(resultVector, resultSample) resultVector = append(resultVector, resultSample)
} }
return resultVector return resultVector
@ -381,6 +382,7 @@ func aggrOverTime(timestamp clientmodel.Timestamp, args []Node, aggrFn func(metr
continue continue
} }
delete(el.Metric, clientmodel.MetricNameLabel)
resultVector = append(resultVector, &clientmodel.Sample{ resultVector = append(resultVector, &clientmodel.Sample{
Metric: el.Metric, Metric: el.Metric,
Value: aggrFn(el.Values), Value: aggrFn(el.Values),
@ -446,6 +448,7 @@ func absImpl(timestamp clientmodel.Timestamp, args []Node) interface{} {
n := args[0].(VectorNode) n := args[0].(VectorNode)
vector := n.Eval(timestamp) vector := n.Eval(timestamp)
for _, el := range vector { for _, el := range vector {
delete(el.Metric, clientmodel.MetricNameLabel)
el.Value = clientmodel.SampleValue(math.Abs(float64(el.Value))) el.Value = clientmodel.SampleValue(math.Abs(float64(el.Value)))
} }
return vector return vector

View file

@ -70,30 +70,30 @@ func TestExpressions(t *testing.T) {
}{ }{
{ {
expr: `SUM(http_requests)`, expr: `SUM(http_requests)`,
output: []string{`http_requests => 3600 @[%v]`}, output: []string{`{} => 3600 @[%v]`},
fullRanges: 0, fullRanges: 0,
intervalRanges: 8, intervalRanges: 8,
}, { }, {
expr: `SUM(http_requests{instance="0"}) BY(job)`, expr: `SUM(http_requests{instance="0"}) BY(job)`,
output: []string{ output: []string{
`http_requests{job="api-server"} => 400 @[%v]`, `{job="api-server"} => 400 @[%v]`,
`http_requests{job="app-server"} => 1200 @[%v]`, `{job="app-server"} => 1200 @[%v]`,
}, },
fullRanges: 0, fullRanges: 0,
intervalRanges: 4, intervalRanges: 4,
}, { }, {
expr: `SUM(http_requests{instance="0"}) BY(job) KEEPING_EXTRA`, expr: `SUM(http_requests{instance="0"}) BY(job) KEEPING_EXTRA`,
output: []string{ output: []string{
`http_requests{instance="0", job="api-server"} => 400 @[%v]`, `{instance="0", job="api-server"} => 400 @[%v]`,
`http_requests{instance="0", job="app-server"} => 1200 @[%v]`, `{instance="0", job="app-server"} => 1200 @[%v]`,
}, },
fullRanges: 0, fullRanges: 0,
intervalRanges: 4, intervalRanges: 4,
}, { }, {
expr: `SUM(http_requests) BY (job)`, expr: `SUM(http_requests) BY (job)`,
output: []string{ output: []string{
`http_requests{job="api-server"} => 1000 @[%v]`, `{job="api-server"} => 1000 @[%v]`,
`http_requests{job="app-server"} => 2600 @[%v]`, `{job="app-server"} => 2600 @[%v]`,
}, },
fullRanges: 0, fullRanges: 0,
intervalRanges: 8, intervalRanges: 8,
@ -101,8 +101,8 @@ func TestExpressions(t *testing.T) {
// Non-existent labels mentioned in BY-clauses shouldn't propagate to output. // Non-existent labels mentioned in BY-clauses shouldn't propagate to output.
expr: `SUM(http_requests) BY (job, nonexistent)`, expr: `SUM(http_requests) BY (job, nonexistent)`,
output: []string{ output: []string{
`http_requests{job="api-server"} => 1000 @[%v]`, `{job="api-server"} => 1000 @[%v]`,
`http_requests{job="app-server"} => 2600 @[%v]`, `{job="app-server"} => 2600 @[%v]`,
}, },
fullRanges: 0, fullRanges: 0,
intervalRanges: 8, intervalRanges: 8,
@ -112,141 +112,141 @@ func TestExpressions(t *testing.T) {
SUM(http_requests) BY /* comments shouldn't SUM(http_requests) BY /* comments shouldn't
have any effect */ (job) // another comment`, have any effect */ (job) // another comment`,
output: []string{ output: []string{
`http_requests{job="api-server"} => 1000 @[%v]`, `{job="api-server"} => 1000 @[%v]`,
`http_requests{job="app-server"} => 2600 @[%v]`, `{job="app-server"} => 2600 @[%v]`,
}, },
fullRanges: 0, fullRanges: 0,
intervalRanges: 8, intervalRanges: 8,
}, { }, {
expr: `COUNT(http_requests) BY (job)`, expr: `COUNT(http_requests) BY (job)`,
output: []string{ output: []string{
`http_requests{job="api-server"} => 4 @[%v]`, `{job="api-server"} => 4 @[%v]`,
`http_requests{job="app-server"} => 4 @[%v]`, `{job="app-server"} => 4 @[%v]`,
}, },
fullRanges: 0, fullRanges: 0,
intervalRanges: 8, intervalRanges: 8,
}, { }, {
expr: `SUM(http_requests) BY (job, group)`, expr: `SUM(http_requests) BY (job, group)`,
output: []string{ output: []string{
`http_requests{group="canary", job="api-server"} => 700 @[%v]`, `{group="canary", job="api-server"} => 700 @[%v]`,
`http_requests{group="canary", job="app-server"} => 1500 @[%v]`, `{group="canary", job="app-server"} => 1500 @[%v]`,
`http_requests{group="production", job="api-server"} => 300 @[%v]`, `{group="production", job="api-server"} => 300 @[%v]`,
`http_requests{group="production", job="app-server"} => 1100 @[%v]`, `{group="production", job="app-server"} => 1100 @[%v]`,
}, },
fullRanges: 0, fullRanges: 0,
intervalRanges: 8, intervalRanges: 8,
}, { }, {
expr: `AVG(http_requests) BY (job)`, expr: `AVG(http_requests) BY (job)`,
output: []string{ output: []string{
`http_requests{job="api-server"} => 250 @[%v]`, `{job="api-server"} => 250 @[%v]`,
`http_requests{job="app-server"} => 650 @[%v]`, `{job="app-server"} => 650 @[%v]`,
}, },
fullRanges: 0, fullRanges: 0,
intervalRanges: 8, intervalRanges: 8,
}, { }, {
expr: `MIN(http_requests) BY (job)`, expr: `MIN(http_requests) BY (job)`,
output: []string{ output: []string{
`http_requests{job="api-server"} => 100 @[%v]`, `{job="api-server"} => 100 @[%v]`,
`http_requests{job="app-server"} => 500 @[%v]`, `{job="app-server"} => 500 @[%v]`,
}, },
fullRanges: 0, fullRanges: 0,
intervalRanges: 8, intervalRanges: 8,
}, { }, {
expr: `MAX(http_requests) BY (job)`, expr: `MAX(http_requests) BY (job)`,
output: []string{ output: []string{
`http_requests{job="api-server"} => 400 @[%v]`, `{job="api-server"} => 400 @[%v]`,
`http_requests{job="app-server"} => 800 @[%v]`, `{job="app-server"} => 800 @[%v]`,
}, },
fullRanges: 0, fullRanges: 0,
intervalRanges: 8, intervalRanges: 8,
}, { }, {
expr: `SUM(http_requests) BY (job) - COUNT(http_requests) BY (job)`, expr: `SUM(http_requests) BY (job) - COUNT(http_requests) BY (job)`,
output: []string{ output: []string{
`http_requests{job="api-server"} => 996 @[%v]`, `{job="api-server"} => 996 @[%v]`,
`http_requests{job="app-server"} => 2596 @[%v]`, `{job="app-server"} => 2596 @[%v]`,
}, },
fullRanges: 0, fullRanges: 0,
intervalRanges: 8, intervalRanges: 8,
}, { }, {
expr: `2 - SUM(http_requests) BY (job)`, expr: `2 - SUM(http_requests) BY (job)`,
output: []string{ output: []string{
`http_requests{job="api-server"} => -998 @[%v]`, `{job="api-server"} => -998 @[%v]`,
`http_requests{job="app-server"} => -2598 @[%v]`, `{job="app-server"} => -2598 @[%v]`,
}, },
fullRanges: 0, fullRanges: 0,
intervalRanges: 8, intervalRanges: 8,
}, { }, {
expr: `1000 / SUM(http_requests) BY (job)`, expr: `1000 / SUM(http_requests) BY (job)`,
output: []string{ output: []string{
`http_requests{job="api-server"} => 1 @[%v]`, `{job="api-server"} => 1 @[%v]`,
`http_requests{job="app-server"} => 0.38461538461538464 @[%v]`, `{job="app-server"} => 0.38461538461538464 @[%v]`,
}, },
fullRanges: 0, fullRanges: 0,
intervalRanges: 8, intervalRanges: 8,
}, { }, {
expr: `SUM(http_requests) BY (job) - 2`, expr: `SUM(http_requests) BY (job) - 2`,
output: []string{ output: []string{
`http_requests{job="api-server"} => 998 @[%v]`, `{job="api-server"} => 998 @[%v]`,
`http_requests{job="app-server"} => 2598 @[%v]`, `{job="app-server"} => 2598 @[%v]`,
}, },
fullRanges: 0, fullRanges: 0,
intervalRanges: 8, intervalRanges: 8,
}, { }, {
expr: `SUM(http_requests) BY (job) % 3`, expr: `SUM(http_requests) BY (job) % 3`,
output: []string{ output: []string{
`http_requests{job="api-server"} => 1 @[%v]`, `{job="api-server"} => 1 @[%v]`,
`http_requests{job="app-server"} => 2 @[%v]`, `{job="app-server"} => 2 @[%v]`,
}, },
fullRanges: 0, fullRanges: 0,
intervalRanges: 8, intervalRanges: 8,
}, { }, {
expr: `SUM(http_requests) BY (job) / 0`, expr: `SUM(http_requests) BY (job) / 0`,
output: []string{ output: []string{
`http_requests{job="api-server"} => +Inf @[%v]`, `{job="api-server"} => +Inf @[%v]`,
`http_requests{job="app-server"} => +Inf @[%v]`, `{job="app-server"} => +Inf @[%v]`,
}, },
fullRanges: 0, fullRanges: 0,
intervalRanges: 8, intervalRanges: 8,
}, { }, {
expr: `SUM(http_requests) BY (job) > 1000`, expr: `SUM(http_requests) BY (job) > 1000`,
output: []string{ output: []string{
`http_requests{job="app-server"} => 2600 @[%v]`, `{job="app-server"} => 2600 @[%v]`,
}, },
fullRanges: 0, fullRanges: 0,
intervalRanges: 8, intervalRanges: 8,
}, { }, {
expr: `1000 < SUM(http_requests) BY (job)`, expr: `1000 < SUM(http_requests) BY (job)`,
output: []string{ output: []string{
`http_requests{job="app-server"} => 1000 @[%v]`, `{job="app-server"} => 1000 @[%v]`,
}, },
fullRanges: 0, fullRanges: 0,
intervalRanges: 8, intervalRanges: 8,
}, { }, {
expr: `SUM(http_requests) BY (job) <= 1000`, expr: `SUM(http_requests) BY (job) <= 1000`,
output: []string{ output: []string{
`http_requests{job="api-server"} => 1000 @[%v]`, `{job="api-server"} => 1000 @[%v]`,
}, },
fullRanges: 0, fullRanges: 0,
intervalRanges: 8, intervalRanges: 8,
}, { }, {
expr: `SUM(http_requests) BY (job) != 1000`, expr: `SUM(http_requests) BY (job) != 1000`,
output: []string{ output: []string{
`http_requests{job="app-server"} => 2600 @[%v]`, `{job="app-server"} => 2600 @[%v]`,
}, },
fullRanges: 0, fullRanges: 0,
intervalRanges: 8, intervalRanges: 8,
}, { }, {
expr: `SUM(http_requests) BY (job) == 1000`, expr: `SUM(http_requests) BY (job) == 1000`,
output: []string{ output: []string{
`http_requests{job="api-server"} => 1000 @[%v]`, `{job="api-server"} => 1000 @[%v]`,
}, },
fullRanges: 0, fullRanges: 0,
intervalRanges: 8, intervalRanges: 8,
}, { }, {
expr: `SUM(http_requests) BY (job) + SUM(http_requests) BY (job)`, expr: `SUM(http_requests) BY (job) + SUM(http_requests) BY (job)`,
output: []string{ output: []string{
`http_requests{job="api-server"} => 2000 @[%v]`, `{job="api-server"} => 2000 @[%v]`,
`http_requests{job="app-server"} => 5200 @[%v]`, `{job="app-server"} => 5200 @[%v]`,
}, },
fullRanges: 0, fullRanges: 0,
intervalRanges: 8, 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)`, expr: `http_requests{job="api-server", group="canary"} + delta(http_requests{job="api-server"}[5m], 1)`,
output: []string{ output: []string{
`http_requests{group="canary", instance="0", job="api-server"} => 330 @[%v]`, `{group="canary", instance="0", job="api-server"} => 330 @[%v]`,
`http_requests{group="canary", instance="1", job="api-server"} => 440 @[%v]`, `{group="canary", instance="1", job="api-server"} => 440 @[%v]`,
}, },
fullRanges: 4, fullRanges: 4,
intervalRanges: 0, intervalRanges: 0,
}, { }, {
expr: `delta(http_requests[25m], 1)`, expr: `delta(http_requests[25m], 1)`,
output: []string{ output: []string{
`http_requests{group="canary", instance="0", job="api-server"} => 150 @[%v]`, `{group="canary", instance="0", job="api-server"} => 150 @[%v]`,
`http_requests{group="canary", instance="0", job="app-server"} => 350 @[%v]`, `{group="canary", instance="0", job="app-server"} => 350 @[%v]`,
`http_requests{group="canary", instance="1", job="api-server"} => 200 @[%v]`, `{group="canary", instance="1", job="api-server"} => 200 @[%v]`,
`http_requests{group="canary", instance="1", job="app-server"} => 400 @[%v]`, `{group="canary", instance="1", job="app-server"} => 400 @[%v]`,
`http_requests{group="production", instance="0", job="api-server"} => 50 @[%v]`, `{group="production", instance="0", job="api-server"} => 50 @[%v]`,
`http_requests{group="production", instance="0", job="app-server"} => 250 @[%v]`, `{group="production", instance="0", job="app-server"} => 250 @[%v]`,
`http_requests{group="production", instance="1", job="api-server"} => 100 @[%v]`, `{group="production", instance="1", job="api-server"} => 100 @[%v]`,
`http_requests{group="production", instance="1", job="app-server"} => 300 @[%v]`, `{group="production", instance="1", job="app-server"} => 300 @[%v]`,
}, },
fullRanges: 8, fullRanges: 8,
intervalRanges: 0, intervalRanges: 0,
@ -360,45 +360,45 @@ func TestExpressions(t *testing.T) {
// Lower-cased aggregation operators should work too. // 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)`, expr: `sum(http_requests) by (job) + min(http_requests) by (job) + max(http_requests) by (job) + avg(http_requests) by (job)`,
output: []string{ output: []string{
`http_requests{job="app-server"} => 4550 @[%v]`, `{job="app-server"} => 4550 @[%v]`,
`http_requests{job="api-server"} => 1750 @[%v]`, `{job="api-server"} => 1750 @[%v]`,
}, },
fullRanges: 0, fullRanges: 0,
intervalRanges: 8, intervalRanges: 8,
}, { }, {
// Deltas should be adjusted for target interval vs. samples under target interval. // 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)`, 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, fullRanges: 1,
intervalRanges: 0, intervalRanges: 0,
}, { }, {
// Rates should transform per-interval deltas to per-second rates. // Rates should transform per-interval deltas to per-second rates.
expr: `rate(http_requests{group="canary", instance="1", job="app-server"}[10m])`, 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, fullRanges: 1,
intervalRanges: 0, intervalRanges: 0,
}, { }, {
// Counter resets in middle of range are ignored by delta() if counter == 1. // Counter resets in middle of range are ignored by delta() if counter == 1.
expr: `delta(testcounter_reset_middle[50m], 1)`, expr: `delta(testcounter_reset_middle[50m], 1)`,
output: []string{`testcounter_reset_middle => 90 @[%v]`}, output: []string{`{} => 90 @[%v]`},
fullRanges: 1, fullRanges: 1,
intervalRanges: 0, intervalRanges: 0,
}, { }, {
// Counter resets in middle of range are not ignored by delta() if counter == 0. // Counter resets in middle of range are not ignored by delta() if counter == 0.
expr: `delta(testcounter_reset_middle[50m], 0)`, expr: `delta(testcounter_reset_middle[50m], 0)`,
output: []string{`testcounter_reset_middle => 50 @[%v]`}, output: []string{`{} => 50 @[%v]`},
fullRanges: 1, fullRanges: 1,
intervalRanges: 0, intervalRanges: 0,
}, { }, {
// Counter resets at end of range are ignored by delta() if counter == 1. // Counter resets at end of range are ignored by delta() if counter == 1.
expr: `delta(testcounter_reset_end[5m], 1)`, expr: `delta(testcounter_reset_end[5m], 1)`,
output: []string{`testcounter_reset_end => 0 @[%v]`}, output: []string{`{} => 0 @[%v]`},
fullRanges: 1, fullRanges: 1,
intervalRanges: 0, intervalRanges: 0,
}, { }, {
// Counter resets at end of range are not ignored by delta() if counter == 0. // Counter resets at end of range are not ignored by delta() if counter == 0.
expr: `delta(testcounter_reset_end[5m], 0)`, expr: `delta(testcounter_reset_end[5m], 0)`,
output: []string{`testcounter_reset_end => -90 @[%v]`}, output: []string{`{} => -90 @[%v]`},
fullRanges: 1, fullRanges: 1,
intervalRanges: 0, intervalRanges: 0,
}, { }, {
@ -470,8 +470,8 @@ func TestExpressions(t *testing.T) {
{ {
expr: `abs(-1 * http_requests{group="production",job="api-server"})`, expr: `abs(-1 * http_requests{group="production",job="api-server"})`,
output: []string{ output: []string{
`http_requests{group="production", instance="0", job="api-server"} => 100 @[%v]`, `{group="production", instance="0", job="api-server"} => 100 @[%v]`,
`http_requests{group="production", instance="1", job="api-server"} => 200 @[%v]`, `{group="production", instance="1", job="api-server"} => 200 @[%v]`,
}, },
fullRanges: 0, fullRanges: 0,
intervalRanges: 2, intervalRanges: 2,
@ -479,8 +479,8 @@ func TestExpressions(t *testing.T) {
{ {
expr: `avg_over_time(http_requests{group="production",job="api-server"}[1h])`, expr: `avg_over_time(http_requests{group="production",job="api-server"}[1h])`,
output: []string{ output: []string{
`http_requests{group="production", instance="0", job="api-server"} => 50 @[%v]`, `{group="production", instance="0", job="api-server"} => 50 @[%v]`,
`http_requests{group="production", instance="1", job="api-server"} => 100 @[%v]`, `{group="production", instance="1", job="api-server"} => 100 @[%v]`,
}, },
fullRanges: 2, fullRanges: 2,
intervalRanges: 0, intervalRanges: 0,
@ -488,8 +488,8 @@ func TestExpressions(t *testing.T) {
{ {
expr: `count_over_time(http_requests{group="production",job="api-server"}[1h])`, expr: `count_over_time(http_requests{group="production",job="api-server"}[1h])`,
output: []string{ output: []string{
`http_requests{group="production", instance="0", job="api-server"} => 11 @[%v]`, `{group="production", instance="0", job="api-server"} => 11 @[%v]`,
`http_requests{group="production", instance="1", job="api-server"} => 11 @[%v]`, `{group="production", instance="1", job="api-server"} => 11 @[%v]`,
}, },
fullRanges: 2, fullRanges: 2,
intervalRanges: 0, intervalRanges: 0,
@ -497,8 +497,8 @@ func TestExpressions(t *testing.T) {
{ {
expr: `max_over_time(http_requests{group="production",job="api-server"}[1h])`, expr: `max_over_time(http_requests{group="production",job="api-server"}[1h])`,
output: []string{ output: []string{
`http_requests{group="production", instance="0", job="api-server"} => 100 @[%v]`, `{group="production", instance="0", job="api-server"} => 100 @[%v]`,
`http_requests{group="production", instance="1", job="api-server"} => 200 @[%v]`, `{group="production", instance="1", job="api-server"} => 200 @[%v]`,
}, },
fullRanges: 2, fullRanges: 2,
intervalRanges: 0, intervalRanges: 0,
@ -506,8 +506,8 @@ func TestExpressions(t *testing.T) {
{ {
expr: `min_over_time(http_requests{group="production",job="api-server"}[1h])`, expr: `min_over_time(http_requests{group="production",job="api-server"}[1h])`,
output: []string{ output: []string{
`http_requests{group="production", instance="0", job="api-server"} => 0 @[%v]`, `{group="production", instance="0", job="api-server"} => 0 @[%v]`,
`http_requests{group="production", instance="1", job="api-server"} => 0 @[%v]`, `{group="production", instance="1", job="api-server"} => 0 @[%v]`,
}, },
fullRanges: 2, fullRanges: 2,
intervalRanges: 0, intervalRanges: 0,
@ -515,8 +515,8 @@ func TestExpressions(t *testing.T) {
{ {
expr: `sum_over_time(http_requests{group="production",job="api-server"}[1h])`, expr: `sum_over_time(http_requests{group="production",job="api-server"}[1h])`,
output: []string{ output: []string{
`http_requests{group="production", instance="0", job="api-server"} => 550 @[%v]`, `{group="production", instance="0", job="api-server"} => 550 @[%v]`,
`http_requests{group="production", instance="1", job="api-server"} => 1100 @[%v]`, `{group="production", instance="1", job="api-server"} => 1100 @[%v]`,
}, },
fullRanges: 2, fullRanges: 2,
intervalRanges: 0, intervalRanges: 0,

View file

@ -350,7 +350,7 @@ Prometheus.Graph.prototype.renderLabels = function(labels) {
} }
Prometheus.Graph.prototype.metricToTsName = function(labels) { Prometheus.Graph.prototype.metricToTsName = function(labels) {
var tsName = labels["__name__"] + "{"; var tsName = (labels["__name__"] || '') + "{";
var labelStrings = []; var labelStrings = [];
for (label in labels) { for (label in labels) {
if (label != "__name__") { if (label != "__name__") {
@ -378,7 +378,7 @@ Prometheus.Graph.prototype.transformData = function(json) {
} }
var data = json.Value.map(function(ts) { var data = json.Value.map(function(ts) {
return { return {
name: self.metricToTsName(ts.Metric), name: escapeHTML(self.metricToTsName(ts.Metric)),
labels: ts.Metric, labels: ts.Metric,
data: ts.Values.map(function(value) { data: ts.Values.map(function(value) {
return { return {
@ -438,7 +438,7 @@ Prometheus.Graph.prototype.updateGraph = function(reloadGraph) {
graph: self.rickshawGraph, graph: self.rickshawGraph,
formatter: function(series, x, y) { formatter: function(series, x, y) {
var swatch = '<span class="detail_swatch" style="background-color: ' + series.color + '"></span>'; var swatch = '<span class="detail_swatch" style="background-color: ' + series.color + '"></span>';
var content = swatch + series.labels["__name__"] + ": <strong>" + y + '</strong><br>'; var content = swatch + (series.labels["__name__"] || 'value') + ": <strong>" + y + '</strong><br>';
return content + self.renderLabels(series.labels); return content + self.renderLabels(series.labels);
}, },
onRender: function() { onRender: function() {
@ -518,7 +518,7 @@ Prometheus.Graph.prototype.handleConsoleResponse = function(data, textStatus) {
for (var i = 0; i < data.Value.length; i++) { for (var i = 0; i < data.Value.length; i++) {
var v = data.Value[i]; var v = data.Value[i];
var tsName = self.metricToTsName(v.Metric); var tsName = self.metricToTsName(v.Metric);
tBody.append("<tr><td>" + tsName + "</td><td>" + v.Value + "</td></tr>") tBody.append("<tr><td>" + escapeHTML(tsName) + "</td><td>" + v.Value + "</td></tr>")
} }
break; break;
case "matrix": case "matrix":
@ -529,7 +529,7 @@ Prometheus.Graph.prototype.handleConsoleResponse = function(data, textStatus) {
for (var j = 0; j < v.Values.length; j++) { for (var j = 0; j < v.Values.length; j++) {
valueText += v.Values[j].Value + " @" + v.Values[j].Timestamp + "<br/>"; valueText += v.Values[j].Value + " @" + v.Values[j].Timestamp + "<br/>";
} }
tBody.append("<tr><td>" + tsName + "</td><td>" + valueText + "</td></tr>") tBody.append("<tr><td>" + escapeHTML(tsName) + "</td><td>" + valueText + "</td></tr>")
} }
break; break;
case "scalar": case "scalar":
@ -575,6 +575,21 @@ function addGraph(options) {
}); });
} }
function escapeHTML(string) {
var entityMap = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': '&quot;',
"'": '&#39;',
"/": '&#x2F;'
};
return String(string).replace(/[&<>"'\/]/g, function (s) {
return entityMap[s];
});
}
function init() { function init() {
$.ajaxSetup({ $.ajaxSetup({
cache: false cache: false