forked from mirror/client_golang
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)
|
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
|
// Without explicit forwarding of Describe, Collect, Reset, those methods won't
|
||||||
// show up in GoDoc.
|
// show up in GoDoc.
|
||||||
|
|
||||||
|
@ -381,6 +391,82 @@ func (m *metricMap) deleteByHashWithLabels(
|
||||||
return true
|
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
|
// getOrCreateMetricWithLabelValues retrieves the metric by hash and label value
|
||||||
// or creates it and returns the new one.
|
// 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) {
|
func TestMetricVec(t *testing.T) {
|
||||||
vec := NewGaugeVec(
|
vec := NewGaugeVec(
|
||||||
GaugeOpts{
|
GaugeOpts{
|
||||||
|
|
Loading…
Reference in New Issue