diff --git a/rules/ast/functions.go b/rules/ast/functions.go index 0c5fcafd9..3a207b976 100644 --- a/rules/ast/functions.go +++ b/rules/ast/functions.go @@ -514,12 +514,11 @@ func histogramQuantileImpl(timestamp clientmodel.Timestamp, args []Node) interfa // TODO(beorn7): Issue a warning somehow. continue } - // TODO avoid copying each time by using a custom fingerprint - el.Metric.Delete(clientmodel.BucketLabel) - el.Metric.Delete(clientmodel.MetricNameLabel) - fp := el.Metric.Metric.Fingerprint() + fp := bucketFingerprint(el.Metric.Metric) mb, ok := fpToMetricWithBuckets[fp] if !ok { + el.Metric.Delete(clientmodel.BucketLabel) + el.Metric.Delete(clientmodel.MetricNameLabel) mb = &metricWithBuckets{el.Metric, nil} fpToMetricWithBuckets[fp] = mb } diff --git a/rules/ast/quantile.go b/rules/ast/quantile.go index f38313313..0f628153f 100644 --- a/rules/ast/quantile.go +++ b/rules/ast/quantile.go @@ -14,6 +14,8 @@ package ast import ( + "encoding/binary" + "hash/fnv" "math" "sort" @@ -97,3 +99,46 @@ func quantile(q clientmodel.SampleValue, buckets buckets) float64 { } return bucketStart + (bucketEnd-bucketStart)*float64(rank/count) } + +// bucketFingerprint works like the Fingerprint method of Metric, but ignores +// the name and the bucket label. +func bucketFingerprint(m clientmodel.Metric) clientmodel.Fingerprint { + numLabels := 0 + if len(m) > 2 { + numLabels = len(m) - 2 + } + labelNames := make([]string, 0, numLabels) + maxLength := 0 + + for labelName, labelValue := range m { + if labelName == clientmodel.MetricNameLabel || labelName == clientmodel.BucketLabel { + continue + } + labelNames = append(labelNames, string(labelName)) + if len(labelName) > maxLength { + maxLength = len(labelName) + } + if len(labelValue) > maxLength { + maxLength = len(labelValue) + } + } + + sort.Strings(labelNames) + + summer := fnv.New64a() + buf := make([]byte, maxLength) + + for _, labelName := range labelNames { + labelValue := m[clientmodel.LabelName(labelName)] + + copy(buf, labelName) + summer.Write(buf[:len(labelName)]) + summer.Write([]byte{clientmodel.SeparatorByte}) + + copy(buf, labelValue) + summer.Write(buf[:len(labelValue)]) + summer.Write([]byte{clientmodel.SeparatorByte}) + } + + return clientmodel.Fingerprint(binary.LittleEndian.Uint64(summer.Sum(nil))) +}