Add stddev and stdvar aggregation functions.

This adds the population standard deviation and
variance as aggregation functions, useful for
spotting how many standard deviations some samples
are from the mean.
This commit is contained in:
Brian Brazil 2015-04-17 00:12:04 +01:00
parent 74aed55e55
commit c3a2b63fe9
12 changed files with 781 additions and 573 deletions

View file

@ -66,9 +66,10 @@ type Vector []*Sample
type Matrix []SampleStream
type groupedAggregation struct {
labels clientmodel.COWMetric
value clientmodel.SampleValue
groupCount int
labels clientmodel.COWMetric
value clientmodel.SampleValue
valuesSquaredSum clientmodel.SampleValue
groupCount int
}
// ----------------------------------------------------------------------------
@ -128,6 +129,8 @@ const (
Min
Max
Count
Stdvar
Stddev
)
// ----------------------------------------------------------------------------
@ -468,6 +471,12 @@ func (node *VectorAggregation) groupedAggregationsToVector(aggregations map[uint
aggregation.value = aggregation.value / clientmodel.SampleValue(aggregation.groupCount)
case Count:
aggregation.value = clientmodel.SampleValue(aggregation.groupCount)
case Stdvar:
avg := float64(aggregation.value) / float64(aggregation.groupCount)
aggregation.value = clientmodel.SampleValue(float64(aggregation.valuesSquaredSum)/float64(aggregation.groupCount) - avg*avg)
case Stddev:
avg := float64(aggregation.value) / float64(aggregation.groupCount)
aggregation.value = clientmodel.SampleValue(math.Sqrt(float64(aggregation.valuesSquaredSum)/float64(aggregation.groupCount) - avg*avg))
default:
// For other aggregations, we already have the right value.
}
@ -509,6 +518,10 @@ func (node *VectorAggregation) Eval(timestamp clientmodel.Timestamp) Vector {
}
case Count:
groupedResult.groupCount++
case Stdvar, Stddev:
groupedResult.value += sample.Value
groupedResult.valuesSquaredSum += sample.Value * sample.Value
groupedResult.groupCount++
default:
panic("Unknown aggregation type")
}
@ -529,9 +542,10 @@ func (node *VectorAggregation) Eval(timestamp clientmodel.Timestamp) Vector {
}
}
result[groupingKey] = &groupedAggregation{
labels: m,
value: sample.Value,
groupCount: 1,
labels: m,
value: sample.Value,
valuesSquaredSum: sample.Value * sample.Value,
groupCount: 1,
}
}
}

View file

@ -60,11 +60,13 @@ func (opType BinOpType) String() string {
func (aggrType AggrType) String() string {
aggrTypeMap := map[AggrType]string{
Sum: "SUM",
Avg: "AVG",
Min: "MIN",
Max: "MAX",
Count: "COUNT",
Sum: "SUM",
Avg: "AVG",
Min: "MIN",
Max: "MAX",
Count: "COUNT",
Stdvar: "STDVAR",
Stddev: "STDDEV",
}
return aggrTypeMap[aggrType]
}

View file

@ -78,11 +78,13 @@ func NewVectorAggregation(aggrTypeStr string, vector ast.Node, groupBy clientmod
return nil, fmt.Errorf("operand of %v aggregation must be of vector type", aggrTypeStr)
}
var aggrTypes = map[string]ast.AggrType{
"SUM": ast.Sum,
"MAX": ast.Max,
"MIN": ast.Min,
"AVG": ast.Avg,
"COUNT": ast.Count,
"SUM": ast.Sum,
"MAX": ast.Max,
"MIN": ast.Min,
"AVG": ast.Avg,
"COUNT": ast.Count,
"STDVAR": ast.Stdvar,
"STDDEV": ast.Stddev,
}
aggrType, ok := aggrTypes[aggrTypeStr]
if !ok {

View file

@ -89,8 +89,8 @@ GROUP_LEFT|GROUP_RIGHT lval.str = lexer.token(); return MATCH_MOD
group_left|group_right lval.str = strings.ToUpper(lexer.token()); return MATCH_MOD
KEEPING_EXTRA|keeping_extra return KEEPING_EXTRA
OFFSET|offset return OFFSET
AVG|SUM|MAX|MIN|COUNT lval.str = lexer.token(); return AGGR_OP
avg|sum|max|min|count lval.str = strings.ToUpper(lexer.token()); return AGGR_OP
AVG|SUM|MAX|MIN|COUNT|STDVAR|STDDEV lval.str = lexer.token(); return AGGR_OP
avg|sum|max|min|count|stdvar|stddev lval.str = strings.ToUpper(lexer.token()); return AGGR_OP
\<|>|AND|OR|and|or lval.str = strings.ToUpper(lexer.token()); return CMP_OP
==|!=|>=|<=|=~|!~ lval.str = lexer.token(); return CMP_OP
[+\-] lval.str = lexer.token(); return ADDITIVE_OP

File diff suppressed because it is too large Load diff

View file

@ -99,7 +99,7 @@ type ruleManager struct {
notificationHandler *notification.NotificationHandler
prometheusURL string
pathPrefix string
pathPrefix string
}
// RuleManagerOptions bundles options for the RuleManager.
@ -111,7 +111,7 @@ type RuleManagerOptions struct {
SampleAppender storage.SampleAppender
PrometheusURL string
PathPrefix string
PathPrefix string
}
// NewRuleManager returns an implementation of RuleManager, ready to be started

View file

@ -1197,13 +1197,13 @@ func TestExpressions(t *testing.T) {
`{group="canary", instance="0", job="api-server"} => NaN @[%v]`,
},
},
{
{
expr: `sqrt(vector_matching_a)`,
output: []string{
`{l="x"} => 3.1622776601683795 @[%v]`,
`{l="y"} => 4.47213595499958 @[%v]`,
},
},
},
{
expr: `exp(vector_matching_a)`,
output: []string{
@ -1295,6 +1295,32 @@ func TestExpressions(t *testing.T) {
`{l="y"} => -Inf @[%v]`,
},
},
{
expr: `stddev(http_requests)`,
output: []string{
`{} => 229.12878474779 @[%v]`,
},
},
{
expr: `stddev by (instance)(http_requests)`,
output: []string{
`{instance="0"} => 223.60679774998 @[%v]`,
`{instance="1"} => 223.60679774998 @[%v]`,
},
},
{
expr: `stdvar(http_requests)`,
output: []string{
`{} => 52500 @[%v]`,
},
},
{
expr: `stdvar by (instance)(http_requests)`,
output: []string{
`{instance="0"} => 50000 @[%v]`,
`{instance="1"} => 50000 @[%v]`,
},
},
}
storage, closer := newTestStorage(t)

View file

@ -219,7 +219,7 @@ func NewTemplateExpander(text string, name string, data interface{}, timestamp c
return fmt.Sprintf("%.4g%ss", v, prefix)
},
"pathPrefix": func() string {
return pathPrefix;
return pathPrefix
},
},
}

View file

@ -37,13 +37,13 @@ func (msrv *MetricsService) RegisterHandler(pathPrefix string) {
Handler: http.HandlerFunc(h),
}
}
http.Handle(pathPrefix + "api/query", prometheus.InstrumentHandler(
pathPrefix + "api/query", handler(msrv.Query),
http.Handle(pathPrefix+"api/query", prometheus.InstrumentHandler(
pathPrefix+"api/query", handler(msrv.Query),
))
http.Handle(pathPrefix + "api/query_range", prometheus.InstrumentHandler(
pathPrefix + "api/query_range", handler(msrv.QueryRange),
http.Handle(pathPrefix+"api/query_range", prometheus.InstrumentHandler(
pathPrefix+"api/query_range", handler(msrv.QueryRange),
))
http.Handle(pathPrefix + "api/metrics", prometheus.InstrumentHandler(
pathPrefix + "api/metrics", handler(msrv.Metrics),
http.Handle(pathPrefix+"api/metrics", prometheus.InstrumentHandler(
pathPrefix+"api/metrics", handler(msrv.Metrics),
))
}

View file

@ -33,7 +33,7 @@ var (
// ConsolesHandler implements http.Handler.
type ConsolesHandler struct {
Storage local.Storage
Storage local.Storage
PathPrefix string
}

View file

@ -32,7 +32,7 @@ type PrometheusStatusHandler struct {
RuleManager manager.RuleManager
TargetPools map[string]*retrieval.TargetPool
Birth time.Time
Birth time.Time
PathPrefix string
}

View file

@ -63,39 +63,39 @@ func (ws WebService) ServeForever(pathPrefix string) error {
http.Handle(pathPrefix, prometheus.InstrumentHandler(
pathPrefix, ws.StatusHandler,
))
http.Handle(pathPrefix + "alerts", prometheus.InstrumentHandler(
pathPrefix + "alerts", ws.AlertsHandler,
http.Handle(pathPrefix+"alerts", prometheus.InstrumentHandler(
pathPrefix+"alerts", ws.AlertsHandler,
))
http.Handle(pathPrefix + "consoles/", prometheus.InstrumentHandler(
pathPrefix + "consoles/", http.StripPrefix(pathPrefix + "consoles/", ws.ConsolesHandler),
http.Handle(pathPrefix+"consoles/", prometheus.InstrumentHandler(
pathPrefix+"consoles/", http.StripPrefix(pathPrefix+"consoles/", ws.ConsolesHandler),
))
http.Handle(pathPrefix + "graph", prometheus.InstrumentHandler(
pathPrefix + "graph", ws.GraphsHandler,
http.Handle(pathPrefix+"graph", prometheus.InstrumentHandler(
pathPrefix+"graph", ws.GraphsHandler,
))
http.Handle(pathPrefix + "heap", prometheus.InstrumentHandler(
pathPrefix + "heap", http.HandlerFunc(dumpHeap),
http.Handle(pathPrefix+"heap", prometheus.InstrumentHandler(
pathPrefix+"heap", http.HandlerFunc(dumpHeap),
))
ws.MetricsHandler.RegisterHandler(pathPrefix)
http.Handle(pathPrefix + strings.TrimLeft(*metricsPath, "/"), prometheus.Handler())
http.Handle(pathPrefix+strings.TrimLeft(*metricsPath, "/"), prometheus.Handler())
if *useLocalAssets {
http.Handle(pathPrefix + "static/", prometheus.InstrumentHandler(
pathPrefix + "static/", http.StripPrefix(pathPrefix + "static/", http.FileServer(http.Dir("web/static"))),
http.Handle(pathPrefix+"static/", prometheus.InstrumentHandler(
pathPrefix+"static/", http.StripPrefix(pathPrefix+"static/", http.FileServer(http.Dir("web/static"))),
))
} else {
http.Handle(pathPrefix + "static/", prometheus.InstrumentHandler(
pathPrefix + "static/", http.StripPrefix(pathPrefix + "static/", new(blob.Handler)),
http.Handle(pathPrefix+"static/", prometheus.InstrumentHandler(
pathPrefix+"static/", http.StripPrefix(pathPrefix+"static/", new(blob.Handler)),
))
}
if *userAssetsPath != "" {
http.Handle(pathPrefix + "user/", prometheus.InstrumentHandler(
pathPrefix + "user/", http.StripPrefix(pathPrefix + "user/", http.FileServer(http.Dir(*userAssetsPath))),
http.Handle(pathPrefix+"user/", prometheus.InstrumentHandler(
pathPrefix+"user/", http.StripPrefix(pathPrefix+"user/", http.FileServer(http.Dir(*userAssetsPath))),
))
}
if *enableQuit {
http.Handle(pathPrefix + "-/quit", http.HandlerFunc(ws.quitHandler))
http.Handle(pathPrefix+"-/quit", http.HandlerFunc(ws.quitHandler))
}
if pathPrefix != "/" {