Skip to content

Commit b3247d4

Browse files
Sanitize metric type prefixes
When more than one prefix matches the same metric descriptor, this will throw the error "collected metric xxx was collected before with the same name and label values". For example, using the metric type prefixes foo.googleapis.com/bar (a prefix) and foo.googleapis.com/bar/baz (a metric) will result in an error because both match the metric foo.googleapis.com/bar/baz. Further, using the metric type prefixes foo.googleapis.com/bar/baz (a metric) and foo.googleapis.com/bar/baz_count (a metric) will result in an error because both match the metric foo.googleapis.com/bar/baz_count. While the first pitfall could be expected by the user, the latter will come as a complete surprise to anyone who is not aware that stackdriver-exporter internally uses an MQL query in the form of metric.type = starts_with("<prefix>") to filter the metrics. Avoid this by sanitizing the provided metric type prefixes in the following way: - Drop any duplicate prefixes - Sort the prefixes (required by the next step) - Drop any prefixes that start with another prefix present in the input Signed-off-by: Edwin Mackenzie-Owen <[email protected]>
1 parent 205417f commit b3247d4

File tree

1 file changed

+43
-1
lines changed

1 file changed

+43
-1
lines changed

stackdriver_exporter.go

+43-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"fmt"
1818
"net/http"
1919
"os"
20+
"slices"
2021
"strings"
2122

2223
"github.com/PuerkitoBio/rehttp"
@@ -303,7 +304,7 @@ func main() {
303304

304305
level.Info(logger).Log("msg", "Using Google Cloud Project IDs", "projectIDs", fmt.Sprintf("%v", projectIDs))
305306

306-
metricsTypePrefixes := strings.Split(*monitoringMetricsTypePrefixes, ",")
307+
metricsTypePrefixes := parseMetricTypePrefixes()
307308
metricExtraFilters := parseMetricExtraFilters()
308309

309310
if *metricsPath == *stackdriverMetricsPath {
@@ -353,6 +354,47 @@ func main() {
353354
}
354355
}
355356

357+
func parseMetricTypePrefixes() (metricTypePrefixes []string) {
358+
inputPrefixes := strings.Split(*monitoringMetricsTypePrefixes, ",")
359+
360+
// only keep unique prefixes
361+
uniqueKeys := make(map[string]bool)
362+
uniquePrefixes := []string{}
363+
for _, prefix := range inputPrefixes {
364+
if _, ok := uniqueKeys[prefix]; !ok {
365+
uniqueKeys[prefix] = true
366+
uniquePrefixes = append(uniquePrefixes, prefix)
367+
}
368+
}
369+
370+
// drop prefixes that start with another existing prefix to avoid error:
371+
// "collected metric xxx was collected before with the same name and label values"
372+
slices.Sort(uniquePrefixes)
373+
for i, prefix := range uniquePrefixes {
374+
if i == 0 {
375+
metricTypePrefixes = []string{prefix}
376+
} else {
377+
previousIndex := len(metricTypePrefixes) - 1
378+
379+
// current prefix starts with previous one
380+
if strings.HasPrefix(prefix, metricTypePrefixes[previousIndex]) {
381+
// drop current prefix
382+
continue
383+
}
384+
385+
// previous prefix starts with current prefix
386+
if strings.HasPrefix(metricTypePrefixes[previousIndex], prefix) {
387+
// drop previous prefix
388+
metricTypePrefixes = metricTypePrefixes[:previousIndex]
389+
}
390+
391+
metricTypePrefixes = append(metricTypePrefixes, prefix)
392+
}
393+
}
394+
395+
return metricTypePrefixes
396+
}
397+
356398
func parseMetricExtraFilters() []collectors.MetricFilter {
357399
var extraFilters []collectors.MetricFilter
358400
for _, ef := range *monitoringMetricsExtraFilter {

0 commit comments

Comments
 (0)