Implement deletion based on partially matching labels (#1013)
* WIP partial match Signed-off-by: Zach Stone <zach@giantswarm.io> * Cleanup Signed-off-by: Zach Stone <zach@giantswarm.io> * Comments Signed-off-by: Zach Stone <zach@giantswarm.io> * Tests and comments Signed-off-by: Zach Stone <zach@giantswarm.io> * Handle properly deleting multiple metrics, update tests Signed-off-by: Zach Stone <zach@giantswarm.io> * Comments Signed-off-by: Zach Stone <zach@giantswarm.io> * Try using curry values Signed-off-by: Zach Stone <zach@giantswarm.io> * Skip curry value to demo Signed-off-by: Zach Stone <zach@giantswarm.io> * Fix curry deletion, remove outdated comment. Signed-off-by: Zach Stone <zach@giantswarm.io> * Fix logic for deletion of metrics from prior to currying Signed-off-by: Zach Stone <zach@giantswarm.io> * Don't match curried values. Update tests. Signed-off-by: Zach Stone <zach@giantswarm.io> * Remove unneccesasry helper and todo comments Signed-off-by: Zach Stone <zach@giantswarm.io> * Comment about partial matching curried labels Signed-off-by: Zach Stone <zach@giantswarm.io> * Simplify curried value check Signed-off-by: Zach Stone <zach@giantswarm.io>
This commit is contained in:
parent
48a686a603
commit
4dcf02ec7b
|
@ -99,6 +99,16 @@ func (m *MetricVec) Delete(labels Labels) bool {
|
|||
return m.metricMap.deleteByHashWithLabels(h, labels, m.curry)
|
||||
}
|
||||
|
||||
// DeletePartialMatch deletes all metrics where the variable labels contain all of those
|
||||
// passed in as labels. The order of the labels does not matter.
|
||||
// It returns the number of metrics deleted.
|
||||
//
|
||||
// Note that curried labels will never be matched if deleting from the curried vector.
|
||||
// To match curried labels with DeletePartialMatch, it must be called on the base vector.
|
||||
func (m *MetricVec) DeletePartialMatch(labels Labels) int {
|
||||
return m.metricMap.deleteByLabels(labels, m.curry)
|
||||
}
|
||||
|
||||
// Without explicit forwarding of Describe, Collect, Reset, those methods won't
|
||||
// show up in GoDoc.
|
||||
|
||||
|
@ -381,6 +391,82 @@ func (m *metricMap) deleteByHashWithLabels(
|
|||
return true
|
||||
}
|
||||
|
||||
// deleteByLabels deletes a metric if the given labels are present in the metric.
|
||||
func (m *metricMap) deleteByLabels(labels Labels, curry []curriedLabelValue) int {
|
||||
m.mtx.Lock()
|
||||
defer m.mtx.Unlock()
|
||||
|
||||
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
|
||||
}
|
||||
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) {
|
||||
for i, l := range items {
|
||||
if l == target {
|
||||
return i, true
|
||||
}
|
||||
}
|
||||
return len(items), false
|
||||
}
|
||||
|
||||
// valueMatchesVariableOrCurriedValue determines if a value was previously curried,
|
||||
// and returns whether it matches either the "base" value or the curried value accordingly.
|
||||
// It also indicates whether the match is against a curried or uncurried value.
|
||||
func valueMatchesVariableOrCurriedValue(targetValue string, index int, values []string, curry []curriedLabelValue) (bool, bool) {
|
||||
for _, curriedValue := range curry {
|
||||
if curriedValue.index == index {
|
||||
// This label was curried. See if the curried value matches our target.
|
||||
return curriedValue.value == targetValue, true
|
||||
}
|
||||
}
|
||||
// This label was not curried. See if the current value matches our target label.
|
||||
return values[index] == targetValue, false
|
||||
}
|
||||
|
||||
// matchPartialLabels searches the current metric and returns whether all of the target label:value pairs are present.
|
||||
func matchPartialLabels(desc *Desc, values []string, labels Labels, curry []curriedLabelValue) bool {
|
||||
for l, v := range labels {
|
||||
// Check if the target label exists in our metrics and get the index.
|
||||
varLabelIndex, validLabel := indexOf(l, desc.variableLabels)
|
||||
if validLabel {
|
||||
// Check the value of that label against the target value.
|
||||
// We don't consider curried values in partial matches.
|
||||
matches, curried := valueMatchesVariableOrCurriedValue(v, varLabelIndex, values, curry)
|
||||
if matches && !curried {
|
||||
continue
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// getOrCreateMetricWithLabelValues retrieves the metric by hash and label value
|
||||
// or creates it and returns the new one.
|
||||
//
|
||||
|
|
|
@ -125,6 +125,93 @@ func testDeleteLabelValues(t *testing.T, vec *GaugeVec) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestDeletePartialMatch(t *testing.T) {
|
||||
baseVec := NewGaugeVec(
|
||||
GaugeOpts{
|
||||
Name: "test",
|
||||
Help: "helpless",
|
||||
},
|
||||
[]string{"l1", "l2", "l3"},
|
||||
)
|
||||
|
||||
assertNoMetric := func(t *testing.T) {
|
||||
if n := len(baseVec.metricMap.metrics); n != 0 {
|
||||
t.Error("expected no metrics, got", n)
|
||||
}
|
||||
}
|
||||
|
||||
// No metric value is set.
|
||||
if got, want := baseVec.DeletePartialMatch(Labels{"l1": "v1", "l2": "v2"}), 0; got != want {
|
||||
t.Errorf("got %v, want %v", got, want)
|
||||
}
|
||||
|
||||
baseVec.With(Labels{"l1": "baseValue1", "l2": "baseValue2", "l3": "baseValue3"}).Inc()
|
||||
baseVec.With(Labels{"l1": "multiDeleteV1", "l2": "diff1BaseValue2", "l3": "v3"}).(Gauge).Set(42)
|
||||
baseVec.With(Labels{"l1": "multiDeleteV1", "l2": "diff2BaseValue2", "l3": "v3"}).(Gauge).Set(84)
|
||||
baseVec.With(Labels{"l1": "multiDeleteV1", "l2": "diff3BaseValue2", "l3": "v3"}).(Gauge).Set(168)
|
||||
|
||||
curriedVec := baseVec.MustCurryWith(Labels{"l2": "curriedValue2"})
|
||||
curriedVec.WithLabelValues("curriedValue1", "curriedValue3").Inc()
|
||||
curriedVec.WithLabelValues("curriedValue1", "differentCurriedValue3").Inc()
|
||||
curriedVec.WithLabelValues("differentCurriedValue1", "differentCurriedValue3").Inc()
|
||||
|
||||
// Try to delete nonexistent label with existent value from curried vector.
|
||||
if got, want := curriedVec.DeletePartialMatch(Labels{"lx": "curriedValue1"}), 0; got != want {
|
||||
t.Errorf("got %v, want %v", got, want)
|
||||
}
|
||||
|
||||
// Try to delete valid label with nonexistent value from curried vector.
|
||||
if got, want := curriedVec.DeletePartialMatch(Labels{"l1": "badValue1"}), 0; got != want {
|
||||
t.Errorf("got %v, want %v", got, want)
|
||||
}
|
||||
|
||||
// Try to delete from a curried vector based on labels which were curried.
|
||||
// This operation succeeds when run against the base vector below.
|
||||
if got, want := curriedVec.DeletePartialMatch(Labels{"l2": "curriedValue2"}), 0; got != want {
|
||||
t.Errorf("got %v, want %v", got, want)
|
||||
}
|
||||
|
||||
// Try to delete from a curried vector based on labels which were curried,
|
||||
// but the value actually exists in the base vector.
|
||||
if got, want := curriedVec.DeletePartialMatch(Labels{"l2": "baseValue2"}), 0; got != want {
|
||||
t.Errorf("got %v, want %v", got, want)
|
||||
}
|
||||
|
||||
// Delete multiple matching metrics from a curried vector based on partial values.
|
||||
if got, want := curriedVec.DeletePartialMatch(Labels{"l1": "curriedValue1"}), 2; got != want {
|
||||
t.Errorf("got %v, want %v", got, want)
|
||||
}
|
||||
|
||||
// Try to delete nonexistent label with existent value from base vector.
|
||||
if got, want := baseVec.DeletePartialMatch(Labels{"lx": "curriedValue1"}), 0; got != want {
|
||||
t.Errorf("got %v, want %v", got, want)
|
||||
}
|
||||
|
||||
// Try to delete partially invalid labels from base vector.
|
||||
if got, want := baseVec.DeletePartialMatch(Labels{"l1": "baseValue1", "l2": "badValue2"}), 0; got != want {
|
||||
t.Errorf("got %v, want %v", got, want)
|
||||
}
|
||||
|
||||
// Delete from the base vector based on values which were curried.
|
||||
// This operation fails when run against a curried vector above.
|
||||
if got, want := baseVec.DeletePartialMatch(Labels{"l2": "curriedValue2"}), 1; got != want {
|
||||
t.Errorf("got %v, want %v", got, want)
|
||||
}
|
||||
|
||||
// Delete multiple metrics from the base vector based on a single valid label.
|
||||
if got, want := baseVec.DeletePartialMatch(Labels{"l1": "multiDeleteV1"}), 3; got != want {
|
||||
t.Errorf("got %v, want %v", got, want)
|
||||
}
|
||||
|
||||
// Delete from the base vector based on values which were not curried.
|
||||
if got, want := baseVec.DeletePartialMatch(Labels{"l3": "baseValue3"}), 1; got != want {
|
||||
t.Errorf("got %v, want %v", got, want)
|
||||
}
|
||||
|
||||
// All metrics should have been deleted now.
|
||||
assertNoMetric(t)
|
||||
}
|
||||
|
||||
func TestMetricVec(t *testing.T) {
|
||||
vec := NewGaugeVec(
|
||||
GaugeOpts{
|
||||
|
|
Loading…
Reference in New Issue