Serge Bazanski | 6f59951 | 2023-04-26 19:08:19 +0200 | [diff] [blame] | 1 | package scruffy |
| 2 | |
| 3 | import ( |
| 4 | "github.com/prometheus/client_golang/prometheus" |
| 5 | |
| 6 | "source.monogon.dev/go/algorithm/cartesian" |
| 7 | ) |
| 8 | |
| 9 | // A labelDefinition describes a key/value pair that's a metric dimension. It |
| 10 | // consists of the label key/name (a string), and a list of possible values of |
| 11 | // this key. The list of values will be used to initialize the metrics at startup |
| 12 | // with zero values. |
| 13 | // |
| 14 | // The initialValues system is intended to be used with labels that are |
| 15 | // low-cardinality enums, e.g. the name of a subsystem. |
| 16 | // |
| 17 | // All labelDefinitions for a single metric will then create a cartesian product |
| 18 | // of all initialValues. |
| 19 | type labelDefinition struct { |
| 20 | // name/key of the label. |
| 21 | name string |
| 22 | // initialValues defines the default values for this label key/name that will be |
| 23 | // used to generate a list of initial zero-filled metrics. |
| 24 | initialValues []string |
| 25 | } |
| 26 | |
| 27 | // labelDefinitions is a list of labelDefinition which define the label |
| 28 | // dimensions of a metric. All the initialValues of the respective |
| 29 | // labelDefinitions will create a cartesian set of default zero-filled metric |
| 30 | // values when the metric susbsystem gets initialized. These zero values will |
| 31 | // then get overridden by real data as it is collected. |
| 32 | type labelDefinitions []labelDefinition |
| 33 | |
| 34 | // initialLabels generates the list of initial labels key/values that should be |
| 35 | // used to generate zero-filled metrics on startup. This is a cartesian product |
| 36 | // of all initialValues of all labelDefinitions. |
| 37 | func (l labelDefinitions) initialLabels() []prometheus.Labels { |
| 38 | // Nothing to do if this is an empty labelDefinitions. |
| 39 | if len(l) == 0 { |
| 40 | return nil |
| 41 | } |
| 42 | |
| 43 | // Given: |
| 44 | // |
| 45 | // labelDefinitions := []labelDefinition{ |
| 46 | // { name: "a", initialValues: []string{"foo", "bar"}}, |
| 47 | // { name: "b", initialValues: []string{"baz", "barf"}}, |
| 48 | // } |
| 49 | // |
| 50 | // This creates: |
| 51 | // |
| 52 | // values := []string{ |
| 53 | // { "foo", "bar" }, // label 'a' |
| 54 | // { "baz", "barf" }, // label 'b' |
| 55 | // } |
| 56 | var values [][]string |
| 57 | for _, ld := range l { |
| 58 | values = append(values, ld.initialValues) |
| 59 | } |
| 60 | |
| 61 | // Given the above: |
| 62 | // |
| 63 | // valuesProduct := []string{ |
| 64 | // // a b |
| 65 | // { "foo", "baz" }, |
| 66 | // { "foo", "barf" }, |
| 67 | // { "bar", "baz" }, |
| 68 | // { "bar", "barf" }, |
| 69 | // } |
| 70 | valuesProduct := cartesian.Product[string](values...) |
| 71 | |
| 72 | // This converts valuesProduct into an actual prometheus-compatible type, |
| 73 | // re-attaching the label names back into the columns as seen above. |
| 74 | var res []prometheus.Labels |
| 75 | for _, vp := range valuesProduct { |
| 76 | labels := make(prometheus.Labels) |
| 77 | for i, lv := range vp { |
| 78 | labelDef := l[i] |
| 79 | labels[labelDef.name] = lv |
| 80 | } |
| 81 | res = append(res, labels) |
| 82 | } |
| 83 | return res |
| 84 | } |
| 85 | |
| 86 | // names returns the keys/names of all the metric labels from these |
| 87 | // labelDefinitions. |
| 88 | func (l labelDefinitions) names() []string { |
| 89 | var res []string |
| 90 | for _, ld := range l { |
| 91 | res = append(res, ld.name) |
| 92 | } |
| 93 | return res |
| 94 | } |