diff --git a/prometheus/vec.go b/prometheus/vec.go index 121d2a963..9749dceb2 100644 --- a/prometheus/vec.go +++ b/prometheus/vec.go @@ -416,31 +416,30 @@ func (m *metricMap) deleteByLabels(labels Labels, curry []curriedLabelValue) int var numDeleted int for h, metrics := range m.metrics { - i := findMetricWithPartialLabels(m.desc, metrics, labels, curry) - if i >= len(metrics) { - // Didn't find matching labels in this metric slice. - continue + remaining := metrics[:0] + for _, metric := range metrics { + if matchPartialLabels(m.desc, metric.values, labels, curry) { + numDeleted++ + continue + } + remaining = append(remaining, metric) + } + // Clear the tail of the backing array so deleted metrics don't + // stay reachable (and hence unable to be garbage collected) + // through the array's spare capacity. + for i := len(remaining); i < len(metrics); i++ { + metrics[i] = metricWithLabelValues{} + } + if len(remaining) == 0 { + delete(m.metrics, h) + } else { + m.metrics[h] = remaining } - delete(m.metrics, h) - numDeleted++ } return numDeleted } -// findMetricWithPartialLabel returns the index of the matching metric or -// len(metrics) if not found. -func findMetricWithPartialLabels( - desc *Desc, metrics []metricWithLabelValues, labels Labels, curry []curriedLabelValue, -) int { - for i, metric := range metrics { - if matchPartialLabels(desc, metric.values, labels, curry) { - return i - } - } - return len(metrics) -} - // indexOf searches the given slice of strings for the target string and returns // the index or len(items) as well as a boolean whether the search succeeded. func indexOf(target string, items []string) (int, bool) { diff --git a/prometheus/vec_test.go b/prometheus/vec_test.go index 03223f2f6..1c8b86df8 100644 --- a/prometheus/vec_test.go +++ b/prometheus/vec_test.go @@ -166,6 +166,19 @@ func TestDeletePartialMatch(t *testing.T) { testDeletePartialMatch(t, vec) } +func TestDeletePartialMatchWithCollisions(t *testing.T) { + vec := NewGaugeVec( + GaugeOpts{ + Name: "test", + Help: "helpless", + }, + []string{"l1", "l2", "l3"}, + ) + vec.hashAdd = func(h uint64, s string) uint64 { return 1 } + vec.hashAddByte = func(h uint64, b byte) uint64 { return 1 } + testDeletePartialMatch(t, vec) +} + func TestDeletePartialMatchWithConstraints(t *testing.T) { vec := V2.NewGaugeVec(GaugeVecOpts{ GaugeOpts{