diff --git a/monitor/metric/metric.go b/monitor/metric/metric.go index a327c6d0..f2e88e42 100644 --- a/monitor/metric/metric.go +++ b/monitor/metric/metric.go @@ -12,7 +12,7 @@ type Pattern interface { Name() string // Match returns whether a map of labels with its label values - // match this pattern. + // match this pattern. All labels have to be present and need to match. Match(labels map[string]string) bool // IsValid returns whether the pattern is valid. @@ -26,7 +26,7 @@ type pattern struct { } // NewPattern creates a new pattern with the given prefix and group name. There -// has to be an even number of parameter, which is ("label", "labelvalue", "label", +// has to be an even number of labels, which is ("label", "labelvalue", "label", // "labelvalue" ...). The label value will be interpreted as regular expression. func NewPattern(name string, labels ...string) Pattern { p := &pattern{ @@ -38,7 +38,6 @@ func NewPattern(name string, labels ...string) Pattern { for i := 0; i < len(labels); i += 2 { exp, err := regexp.Compile(labels[i+1]) if err != nil { - fmt.Printf("error: %s\n", err) continue } @@ -84,19 +83,35 @@ func (p *pattern) IsValid() bool { return p.valid } +// Metrics is a collection of values type Metrics interface { + // Value returns the first value that matches the name and the labels. The labels + // are used to create a pattern and therefore must obey to the rules of NewPattern. Value(name string, labels ...string) Value + + // Values returns all values that matches the name and the labels. The labels + // are used to create a pattern and therefore must obey to the rules of NewPattern. Values(name string, labels ...string) []Value + + // Labels return a list of all values for a label. Labels(name string, label string) []string + + // All returns all values currently stored in the collection. All() []Value + + // Add adds a value to the collection. Add(v Value) + + // String return a string representation of all collected values. String() string } +// metrics is an implementation of the Metrics interface. type metrics struct { values []Value } +// NewMetrics returns a new metrics instance. func NewMetrics() *metrics { return &metrics{} } @@ -231,8 +246,15 @@ func (v *value) Hash() string { func (v *value) String() string { s := fmt.Sprintf("%s: %f {", v.name, v.value) - for k, v := range v.labels { - s += k + "=" + v + " " + keys := []string{} + for k := range v.labels { + keys = append(keys, k) + } + + sort.Strings(keys) + + for _, k := range keys { + s += k + "=" + v.labels[k] + " " } s += "}" diff --git a/monitor/metric/metric_test.go b/monitor/metric/metric_test.go index 743739a7..615ce7cb 100644 --- a/monitor/metric/metric_test.go +++ b/monitor/metric/metric_test.go @@ -2,25 +2,154 @@ package metric import ( "testing" + + "github.com/stretchr/testify/require" ) -func TestValue(t *testing.T) { - d := NewDesc("group", "", []string{"name"}) - v := NewValue(d, 42, "foobar") +func TestPattern(t *testing.T) { + p := NewPattern("bla", "label1", "value1", "label2") + require.Equal(t, false, p.IsValid()) - if v.L("name") != "foobar" { - t.Fatalf("label name doesn't have the expected value") - } + p = NewPattern("bla", "label1", "value1", "label2", "valu(e2") + require.Equal(t, false, p.IsValid()) + + p = NewPattern("bla") + require.Equal(t, true, p.IsValid()) + require.Equal(t, "bla", p.Name()) + + p = NewPattern("bla", "label1", "value1", "label2", "value2") + require.Equal(t, true, p.IsValid()) +} + +func TestPatternMatch(t *testing.T) { + p := NewPattern("bla", "label1", "value1", "label2") + require.Equal(t, false, p.IsValid()) + require.Equal(t, false, p.Match(map[string]string{"label1": "value1"})) + + p0 := NewPattern("bla") + require.Equal(t, true, p0.IsValid()) + require.Equal(t, true, p0.Match(map[string]string{})) + require.Equal(t, true, p0.Match(map[string]string{"labelX": "foobar"})) + + p = NewPattern("bla", "label1", "value.", "label2", "val?ue2") + require.Equal(t, true, p.IsValid()) + require.Equal(t, false, p.Match(map[string]string{})) + require.Equal(t, false, p.Match(map[string]string{"label1": "value1"})) + require.Equal(t, true, p.Match(map[string]string{"label1": "value1", "label2": "value2"})) + require.Equal(t, true, p.Match(map[string]string{"label1": "value5", "label2": "vaue2"})) +} + +func TestValue(t *testing.T) { + d := NewDesc("group", "", []string{"label1", "label2"}) + v := NewValue(d, 42, "foobar") + require.Nil(t, v) + + v = NewValue(d, 42, "foobar", "foobaz") + require.NotNil(t, v) + require.Equal(t, float64(42), v.Val()) + + require.Equal(t, "", v.L("labelX")) + require.Equal(t, "foobar", v.L("label1")) + require.Equal(t, "foobaz", v.L("label2")) + require.Equal(t, "group", v.Name()) + require.Equal(t, "group:label1=foobar label2=foobaz ", v.Hash()) + require.Equal(t, "group: 42.000000 {label1=foobar label2=foobaz }", v.String()) + + require.Equal(t, map[string]string{"label1": "foobar", "label2": "foobaz"}, v.Labels()) +} + +func TestValuePattern(t *testing.T) { + d := NewDesc("group", "", []string{"label1", "label2"}) + v := NewValue(d, 42, "foobar", "foobaz") p1 := NewPattern("group") + p2 := NewPattern("group", "label1", "foobar") + p3 := NewPattern("group", "label2", "foobaz") + p4 := NewPattern("group", "label2", "foobaz", "label1", "foobar") - if v.Match([]Pattern{p1}) == false { - t.Fatalf("pattern p1 should have matched") - } + require.Equal(t, true, v.Match(nil)) + require.Equal(t, true, v.Match([]Pattern{p1})) + require.Equal(t, true, v.Match([]Pattern{p2})) + require.Equal(t, true, v.Match([]Pattern{p3})) + require.Equal(t, true, v.Match([]Pattern{p4})) + require.Equal(t, true, v.Match([]Pattern{p1, p2, p3, p4})) - p2 := NewPattern("group", "name", "foobar") + p5 := NewPattern("group", "label1", "foobaz") - if v.Match([]Pattern{p2}) == false { - t.Fatalf("pattern p2 should have matched") - } + require.Equal(t, false, v.Match([]Pattern{p5})) + + require.Equal(t, true, v.Match([]Pattern{p4, p5})) + require.Equal(t, true, v.Match([]Pattern{p5, p4})) +} + +func TestDescription(t *testing.T) { + d := NewDesc("name", "blabla", []string{"label"}) + + require.Equal(t, "name", d.Name()) + require.Equal(t, "blabla", d.Description()) + require.ElementsMatch(t, []string{"label"}, d.Labels()) + require.Equal(t, "name: blabla (label)", d.String()) +} + +func TestMetri(t *testing.T) { + m := NewMetrics() + + require.Equal(t, "", m.String()) + require.Equal(t, 0, len(m.All())) + + d := NewDesc("group", "", []string{"label1", "label2"}) + v1 := NewValue(d, 42, "foobar", "foobaz") + require.NotNil(t, v1) + + m.Add(v1) + + require.Equal(t, v1.String(), m.String()) + require.Equal(t, 1, len(m.All())) + + l := m.Labels("group", "label2") + + require.ElementsMatch(t, []string{"foobaz"}, l) + + v2 := NewValue(d, 77, "barfoo", "bazfoo") + + m.Add(v2) + + require.Equal(t, v1.String()+v2.String(), m.String()) + require.Equal(t, 2, len(m.All())) + + l = m.Labels("group", "label2") + + require.ElementsMatch(t, []string{"foobaz", "bazfoo"}, l) + + v := m.Value("bla", "label1", "foo*") + + require.Equal(t, nullValue, v) + + v = m.Value("group") + + require.NotEqual(t, nullValue, v) + + v = m.Value("group", "label1", "foo*") + + require.NotEqual(t, nullValue, v) + + v = m.Value("group", "label2", "baz") + + require.NotEqual(t, nullValue, v) + + vs := m.Values("group") + + require.Equal(t, 2, len(vs)) + + vs = m.Values("group", "label1", "foo*") + + require.Equal(t, 2, len(vs)) + + vs = m.Values("group", "label2", "*baz*") + + require.NotEqual(t, 2, len(vs)) + + vs = m.Values("group", "label1") + + require.Equal(t, 0, len(vs)) }