From 51ef08a13ac2e98889c01eb6c4a34bfcedce1b1d Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Mon, 21 Jan 2019 14:03:06 -0600 Subject: [PATCH] feat(prometheus): add label and family transformers --- prometheus/sort.go | 35 +++++ prometheus/transformer.go | 83 +++++++++++- prometheus/transformer_test.go | 241 +++++++++++++++++++++++++++++++++ 3 files changed, 358 insertions(+), 1 deletion(-) create mode 100644 prometheus/sort.go create mode 100644 prometheus/transformer_test.go diff --git a/prometheus/sort.go b/prometheus/sort.go new file mode 100644 index 0000000000..28789ccfe1 --- /dev/null +++ b/prometheus/sort.go @@ -0,0 +1,35 @@ +package prometheus + +import dto "github.com/prometheus/client_model/go" + +// labelPairSorter implements sort.Interface. It is used to sort a slice of +// dto.LabelPair pointers. +type labelPairSorter []*dto.LabelPair + +func (s labelPairSorter) Len() int { + return len(s) +} + +func (s labelPairSorter) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} + +func (s labelPairSorter) Less(i, j int) bool { + return s[i].GetName() < s[j].GetName() +} + +// familySorter implements sort.Interface. It is used to sort a slice of +// dto.MetricFamily pointers. +type familySorter []*dto.MetricFamily + +func (s familySorter) Len() int { + return len(s) +} + +func (s familySorter) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} + +func (s familySorter) Less(i, j int) bool { + return s[i].GetName() < s[j].GetName() +} diff --git a/prometheus/transformer.go b/prometheus/transformer.go index 64e224a622..6ca8a552d8 100644 --- a/prometheus/transformer.go +++ b/prometheus/transformer.go @@ -1,9 +1,90 @@ package prometheus -import dto "github.com/prometheus/client_model/go" +import ( + "sort" + + dto "github.com/prometheus/client_model/go" +) // Transformer modifies prometheus metrics families. type Transformer interface { // Transform updates the metrics family Transform(mfs []*dto.MetricFamily) []*dto.MetricFamily } + +var _ Transformer = (*AddLabels)(nil) + +// AddLabels adds labels to all metrics. It will overwrite +// the label if it already exists. +type AddLabels struct { + Labels map[string]string +} + +// Transform adds labels to the metrics. +func (a *AddLabels) Transform(mfs []*dto.MetricFamily) []*dto.MetricFamily { + for i := range mfs { + for j, m := range mfs[i].Metric { + // Filter out labels to add + labels := m.Label[:0] + for _, l := range m.Label { + if _, ok := a.Labels[l.GetName()]; !ok { + labels = append(labels, l) + } + } + + // Add all new labels to the metric + for k, v := range a.Labels { + labels = append(labels, L(k, v)) + } + sort.Sort(labelPairSorter(labels)) + mfs[i].Metric[j].Label = labels + } + } + return mfs +} + +var _ Transformer = (*RemoveLabels)(nil) + +// RemoveLabels adds labels to all metrics. It will overwrite +// the label if it already exists. +type RemoveLabels struct { + Labels map[string]struct{} +} + +// Transform removes labels from the metrics. +func (r *RemoveLabels) Transform(mfs []*dto.MetricFamily) []*dto.MetricFamily { + for i := range mfs { + for j, m := range mfs[i].Metric { + // Filter out labels + labels := m.Label[:0] + for _, l := range m.Label { + if _, ok := r.Labels[l.GetName()]; !ok { + labels = append(labels, l) + } + } + mfs[i].Metric[j].Label = labels + } + } + return mfs +} + +var _ Transformer = (*RenameFamilies)(nil) + +// RenameFamilies changes the name of families to another name +type RenameFamilies struct { + FromTo map[string]string +} + +// Transform renames metric families names. +func (r *RenameFamilies) Transform(mfs []*dto.MetricFamily) []*dto.MetricFamily { + renamed := mfs[:0] + for _, mf := range mfs { + if to, ok := r.FromTo[mf.GetName()]; ok { + mf.Name = &to + } + renamed = append(renamed, mf) + + } + sort.Sort(familySorter(renamed)) + return renamed +} diff --git a/prometheus/transformer_test.go b/prometheus/transformer_test.go new file mode 100644 index 0000000000..90b371601c --- /dev/null +++ b/prometheus/transformer_test.go @@ -0,0 +1,241 @@ +package prometheus_test + +import ( + "reflect" + "testing" + + pr "github.com/influxdata/influxdb/prometheus" + dto "github.com/prometheus/client_model/go" +) + +func TestAddLabels_Transform(t *testing.T) { + type fields struct { + Labels map[string]string + } + type args struct { + mfs []*dto.MetricFamily + } + tests := []struct { + name string + fields fields + args args + want []*dto.MetricFamily + }{ + { + name: "add label from metric replaces label", + fields: fields{ + Labels: map[string]string{ + "handler": "influxdb", + }, + }, + args: args{ + mfs: []*dto.MetricFamily{ + NewCounter("http_api_requests_total", 10, + pr.L("handler", "platform"), + pr.L("method", "GET"), + pr.L("path", "/api/v2"), + pr.L("status", "2XX"), + ), + }, + }, + want: []*dto.MetricFamily{ + NewCounter("http_api_requests_total", 10, + pr.L("handler", "influxdb"), + pr.L("method", "GET"), + pr.L("path", "/api/v2"), + pr.L("status", "2XX"), + ), + }, + }, + { + name: "add label from metric replaces label", + fields: fields{ + Labels: map[string]string{ + "org": "myorg", + }, + }, + args: args{ + mfs: []*dto.MetricFamily{ + NewCounter("http_api_requests_total", 10, + pr.L("handler", "platform"), + pr.L("method", "GET"), + pr.L("path", "/api/v2"), + pr.L("status", "2XX"), + ), + }, + }, + want: []*dto.MetricFamily{ + NewCounter("http_api_requests_total", 10, + pr.L("handler", "platform"), + pr.L("method", "GET"), + pr.L("org", "myorg"), + pr.L("path", "/api/v2"), + pr.L("status", "2XX"), + ), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := &pr.AddLabels{ + Labels: tt.fields.Labels, + } + if got := a.Transform(tt.args.mfs); !reflect.DeepEqual(got, tt.want) { + t.Errorf("AddLabels.Transform() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestRemoveLabels_Transform(t *testing.T) { + type fields struct { + Labels map[string]struct{} + } + type args struct { + mfs []*dto.MetricFamily + } + tests := []struct { + name string + fields fields + args args + want []*dto.MetricFamily + }{ + { + name: "remove label from metric", + fields: fields{ + Labels: map[string]struct{}{ + "handler": struct{}{}, + }, + }, + args: args{ + mfs: []*dto.MetricFamily{ + NewCounter("http_api_requests_total", 10, + pr.L("handler", "platform"), + pr.L("method", "GET"), + pr.L("path", "/api/v2"), + pr.L("status", "2XX"), + ), + }, + }, + want: []*dto.MetricFamily{ + NewCounter("http_api_requests_total", 10, + pr.L("method", "GET"), + pr.L("path", "/api/v2"), + pr.L("status", "2XX"), + ), + }, + }, + { + name: "no match removes no labels", + fields: fields{ + Labels: map[string]struct{}{ + "handler": struct{}{}, + }, + }, + args: args{ + mfs: []*dto.MetricFamily{ + NewCounter("http_api_requests_total", 10, + pr.L("method", "GET"), + pr.L("path", "/api/v2"), + pr.L("status", "2XX"), + ), + }, + }, + want: []*dto.MetricFamily{ + NewCounter("http_api_requests_total", 10, + pr.L("method", "GET"), + pr.L("path", "/api/v2"), + pr.L("status", "2XX"), + ), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &pr.RemoveLabels{ + Labels: tt.fields.Labels, + } + if got := r.Transform(tt.args.mfs); !reflect.DeepEqual(got, tt.want) { + t.Errorf("RemoveLabels.Transform() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestRenameFamilies_Transform(t *testing.T) { + type fields struct { + FromTo map[string]string + } + type args struct { + mfs []*dto.MetricFamily + } + tests := []struct { + name string + fields fields + args args + want []*dto.MetricFamily + }{ + { + name: "rename metric family in sort order", + fields: fields{ + FromTo: map[string]string{ + "http_api_requests_total": "api_requests_total", + }, + }, + args: args{ + mfs: []*dto.MetricFamily{ + NewCounter("handler", 10, + pr.L("handler", "platform"), + ), + NewCounter("http_api_requests_total", 10, + pr.L("handler", "platform"), + pr.L("method", "GET"), + pr.L("path", "/api/v2"), + pr.L("status", "2XX"), + ), + }, + }, + want: []*dto.MetricFamily{ + NewCounter("api_requests_total", 10, + pr.L("handler", "platform"), + pr.L("method", "GET"), + pr.L("path", "/api/v2"), + pr.L("status", "2XX"), + ), + NewCounter("handler", 10, + pr.L("handler", "platform"), + ), + }, + }, + { + name: "ignored if not found", + fields: fields{ + FromTo: map[string]string{ + "http_api_requests_total": "api_requests_total", + }, + }, + args: args{ + mfs: []*dto.MetricFamily{ + NewCounter("handler", 10, + pr.L("handler", "platform"), + ), + }, + }, + want: []*dto.MetricFamily{ + NewCounter("handler", 10, + pr.L("handler", "platform"), + ), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &pr.RenameFamilies{ + FromTo: tt.fields.FromTo, + } + if got := r.Transform(tt.args.mfs); !reflect.DeepEqual(got, tt.want) { + t.Errorf("RenameFamilies.Transform() = %v, want %v", got, tt.want) + } + }) + } +}