diff --git a/stackdriver_exporter.go b/stackdriver_exporter.go index 8726fc3..33cd2e0 100644 --- a/stackdriver_exporter.go +++ b/stackdriver_exporter.go @@ -18,6 +18,7 @@ import ( stdlog "log" "net/http" "os" + "slices" "strings" "github.com/PuerkitoBio/rehttp" @@ -311,7 +312,8 @@ func main() { "projectsFilter", *projectsFilter, ) - metricsTypePrefixes := strings.Split(*monitoringMetricsTypePrefixes, ",") + inputPrefixes := strings.Split(*monitoringMetricsTypePrefixes, ",") + metricsTypePrefixes := parseMetricTypePrefixes(inputPrefixes) metricExtraFilters := parseMetricExtraFilters() if *metricsPath == *stackdriverMetricsPath { @@ -361,6 +363,30 @@ func main() { } } +func parseMetricTypePrefixes(inputPrefixes []string) []string { + metricTypePrefixes := []string{} + + // Drop duplicate prefixes. + slices.Sort(inputPrefixes) + uniquePrefixes := slices.Compact(inputPrefixes) + + // Drop prefixes that start with another existing prefix to avoid error: + // "collected metric xxx was collected before with the same name and label values". + for i, prefix := range uniquePrefixes { + if i != 0 { + previousIndex := len(metricTypePrefixes) - 1 + + // Drop current prefix if it starts with the previous one. + if strings.HasPrefix(prefix, metricTypePrefixes[previousIndex]) { + continue + } + } + metricTypePrefixes = append(metricTypePrefixes, prefix) + } + + return metricTypePrefixes +} + func parseMetricExtraFilters() []collectors.MetricFilter { var extraFilters []collectors.MetricFilter for _, ef := range *monitoringMetricsExtraFilter { diff --git a/stackdriver_exporter_test.go b/stackdriver_exporter_test.go new file mode 100644 index 0000000..05d0e3e --- /dev/null +++ b/stackdriver_exporter_test.go @@ -0,0 +1,37 @@ +// Copyright 2024 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import "testing" +import "reflect" + +func TestParseMetricTypePrefixes(t *testing.T) { + inputPrefixes := []string{ + "redis.googleapis.com/stats/memory/usage", + "loadbalancing.googleapis.com/https/request_count", + "loadbalancing.googleapis.com", + "redis.googleapis.com/stats/memory/usage_ratio", + "redis.googleapis.com/stats/memory/usage_ratio", + } + expectedOutputPrefixes := []string{ + "loadbalancing.googleapis.com", + "redis.googleapis.com/stats/memory/usage", + } + + outputPrefixes := parseMetricTypePrefixes(inputPrefixes) + + if !reflect.DeepEqual(outputPrefixes, expectedOutputPrefixes) { + t.Errorf("Metric type prefix sanitization did not produce expected output. Expected:\n%s\nGot:\n%s", expectedOutputPrefixes, outputPrefixes) + } +}