cloud/bmaas/bmdb/scruffy: initialize, implement BMDB metrics
This creates a new BMaaS component, Scruffy the Janitor.
Scruffy will run a bunch of housekeeping jobs that aren't tied to a
particular provider or even region. Currently Scruffy just collects BMDB
metrics by periodically polling the BMDB SQL database.
Change-Id: Icafa714811757eaaf31fed43184ded8512bde067
Reviewed-on: https://review.monogon.dev/c/monogon/+/1819
Tested-by: Jenkins CI
Reviewed-by: Tim Windelschmidt <tim@monogon.tech>
diff --git a/cloud/bmaas/scruffy/labels.go b/cloud/bmaas/scruffy/labels.go
new file mode 100644
index 0000000..b88f799
--- /dev/null
+++ b/cloud/bmaas/scruffy/labels.go
@@ -0,0 +1,94 @@
+package scruffy
+
+import (
+ "github.com/prometheus/client_golang/prometheus"
+
+ "source.monogon.dev/go/algorithm/cartesian"
+)
+
+// A labelDefinition describes a key/value pair that's a metric dimension. It
+// consists of the label key/name (a string), and a list of possible values of
+// this key. The list of values will be used to initialize the metrics at startup
+// with zero values.
+//
+// The initialValues system is intended to be used with labels that are
+// low-cardinality enums, e.g. the name of a subsystem.
+//
+// All labelDefinitions for a single metric will then create a cartesian product
+// of all initialValues.
+type labelDefinition struct {
+ // name/key of the label.
+ name string
+ // initialValues defines the default values for this label key/name that will be
+ // used to generate a list of initial zero-filled metrics.
+ initialValues []string
+}
+
+// labelDefinitions is a list of labelDefinition which define the label
+// dimensions of a metric. All the initialValues of the respective
+// labelDefinitions will create a cartesian set of default zero-filled metric
+// values when the metric susbsystem gets initialized. These zero values will
+// then get overridden by real data as it is collected.
+type labelDefinitions []labelDefinition
+
+// initialLabels generates the list of initial labels key/values that should be
+// used to generate zero-filled metrics on startup. This is a cartesian product
+// of all initialValues of all labelDefinitions.
+func (l labelDefinitions) initialLabels() []prometheus.Labels {
+ // Nothing to do if this is an empty labelDefinitions.
+ if len(l) == 0 {
+ return nil
+ }
+
+ // Given:
+ //
+ // labelDefinitions := []labelDefinition{
+ // { name: "a", initialValues: []string{"foo", "bar"}},
+ // { name: "b", initialValues: []string{"baz", "barf"}},
+ // }
+ //
+ // This creates:
+ //
+ // values := []string{
+ // { "foo", "bar" }, // label 'a'
+ // { "baz", "barf" }, // label 'b'
+ // }
+ var values [][]string
+ for _, ld := range l {
+ values = append(values, ld.initialValues)
+ }
+
+ // Given the above:
+ //
+ // valuesProduct := []string{
+ // // a b
+ // { "foo", "baz" },
+ // { "foo", "barf" },
+ // { "bar", "baz" },
+ // { "bar", "barf" },
+ // }
+ valuesProduct := cartesian.Product[string](values...)
+
+ // This converts valuesProduct into an actual prometheus-compatible type,
+ // re-attaching the label names back into the columns as seen above.
+ var res []prometheus.Labels
+ for _, vp := range valuesProduct {
+ labels := make(prometheus.Labels)
+ for i, lv := range vp {
+ labelDef := l[i]
+ labels[labelDef.name] = lv
+ }
+ res = append(res, labels)
+ }
+ return res
+}
+
+// names returns the keys/names of all the metric labels from these
+// labelDefinitions.
+func (l labelDefinitions) names() []string {
+ var res []string
+ for _, ld := range l {
+ res = append(res, ld.name)
+ }
+ return res
+}