From 7ff7dca02515ecc6a41c1ef85105488bfab8ab97 Mon Sep 17 00:00:00 2001 From: Max Englander Date: Wed, 18 Oct 2023 13:50:19 +0100 Subject: [PATCH 1/2] stats: export vttablet_tablet_type to prometheus Signed-off-by: Max Englander --- go/stats/export.go | 57 ++++++++++++++++--- go/stats/export_test.go | 21 ++++++- go/stats/prometheusbackend/collectors.go | 33 +++++++++++ .../prometheusbackend/prometheusbackend.go | 4 +- .../prometheusbackend_test.go | 10 ++++ go/stats/statsd/statsd.go | 2 +- go/vt/grpccommon/options.go | 2 +- go/vt/servenv/buildinfo.go | 14 ++--- go/vt/servenv/exporter_test.go | 2 +- go/vt/vttablet/tabletmanager/restore.go | 8 +-- go/vt/vttablet/tabletmanager/tm_init.go | 14 ++--- 11 files changed, 134 insertions(+), 33 deletions(-) diff --git a/go/stats/export.go b/go/stats/export.go index 58be67e13f9..603b2648db6 100644 --- a/go/stats/export.go +++ b/go/stats/export.go @@ -139,6 +139,47 @@ func pushOne(name string, v Variable) error { return backend.PushOne(name, v) } +// StringValueWithLabel is string publisher. +// +// The valueLabel is intended for use by backends which must export a stats in +// numeric form, and is not used by expvars. +// +// Backends which need to provide a numeric value can set a constant value of 1 +// (or whatever is appropriate for the backend). +type StringValueWithLabel struct { + *StringValue + help string + label string +} + +// Help returns the descriptive help message. +func (s StringValueWithLabel) Help() string { + return s.help +} + +// Label returns the value label. +func (s StringValueWithLabel) Label() string { + return s.label +} + +// NewStringValueWithLabel creates a new StringFuncWithValueLabel. +func NewStringValueWithLabel(name, help string, label, initialValue string) *StringValueWithLabel { + s := NewStringValue("" /* omit name to avoid publish conflicts */) + s.Set(initialValue) + + t := &StringValueWithLabel{ + StringValue: s, + help: help, + label: label, + } + + if name != "" { + publish(name, t) + } + + return t +} + // StringMapFuncWithMultiLabels is a multidimensional string map publisher. // // Map keys are compound names made with joining multiple strings with '.', @@ -270,28 +311,28 @@ func (f FloatFunc) String() string { return strconv.FormatFloat(f(), 'g', -1, 64) } -// String is expvar.String+Get+hook -type String struct { +// StringValue is expvar.StringValue+Get+hook +type StringValue struct { mu sync.Mutex s string } -// NewString returns a new String -func NewString(name string) *String { - v := new(String) +// NewStringValue returns a new String +func NewStringValue(name string) *StringValue { + v := new(StringValue) publish(name, v) return v } // Set sets the value -func (v *String) Set(value string) { +func (v *StringValue) Set(value string) { v.mu.Lock() v.s = value v.mu.Unlock() } // Get returns the value -func (v *String) Get() string { +func (v *StringValue) Get() string { v.mu.Lock() s := v.s v.mu.Unlock() @@ -299,7 +340,7 @@ func (v *String) Get() string { } // String is the implementation of expvar.var -func (v *String) String() string { +func (v *StringValue) String() string { return strconv.Quote(v.Get()) } diff --git a/go/stats/export_test.go b/go/stats/export_test.go index e6160f77184..4db5f6b6e10 100644 --- a/go/stats/export_test.go +++ b/go/stats/export_test.go @@ -44,13 +44,13 @@ func TestNoHook(t *testing.T) { func TestString(t *testing.T) { var gotname string - var gotv *String + var gotv *StringValue clearStats() Register(func(name string, v expvar.Var) { gotname = name - gotv = v.(*String) + gotv = v.(*StringValue) }) - v := NewString("String") + v := NewStringValue("String") if gotname != "String" { t.Errorf("want String, got %s", gotname) } @@ -189,3 +189,18 @@ func TestStringMapWithMultiLabels(t *testing.T) { require.Equal(t, c.ValueLabel(), "ccc") } + +func TestStringValueWithLabel(t *testing.T) { + clearStats() + c := NewStringValueWithLabel("stringValue1", "help", "aaa", "ccc") + + s := c.String() + require.Equal(t, "\"ccc\"", s) + + label := c.Label() + require.Equal(t, "aaa", label) + + c.Set("ddd") + s = c.String() + require.Equal(t, "\"ddd\"", s) +} diff --git a/go/stats/prometheusbackend/collectors.go b/go/stats/prometheusbackend/collectors.go index 7469167cf74..9bdeb997e42 100644 --- a/go/stats/prometheusbackend/collectors.go +++ b/go/stats/prometheusbackend/collectors.go @@ -431,3 +431,36 @@ func (c *stringMapFuncWithMultiLabelsCollector) Collect(ch chan<- prometheus.Met } } } + +type stringValueWithLabelCollector struct { + svl *stats.StringValueWithLabel + desc *prometheus.Desc +} + +func newStringValueWithLabelCollector(svl *stats.StringValueWithLabel, name string) { + c := &stringValueWithLabelCollector{ + svl: svl, + desc: prometheus.NewDesc( + name, + svl.Help(), + labelsToSnake([]string{svl.Label()}), + nil), + } + + prometheus.MustRegister(c) +} + +// Describe implements Collector. +func (c *stringValueWithLabelCollector) Describe(ch chan<- *prometheus.Desc) { + ch <- c.desc +} + +// Collect implements Collector. +func (c *stringValueWithLabelCollector) Collect(ch chan<- prometheus.Metric) { + metric, err := prometheus.NewConstMetric(c.desc, prometheus.GaugeValue, 1.0, c.svl.Get()) + if err != nil { + log.Errorf("Error adding metric: %s", c.desc) + } else { + ch <- metric + } +} diff --git a/go/stats/prometheusbackend/prometheusbackend.go b/go/stats/prometheusbackend/prometheusbackend.go index 62165e117c1..52ade2ae6c9 100644 --- a/go/stats/prometheusbackend/prometheusbackend.go +++ b/go/stats/prometheusbackend/prometheusbackend.go @@ -87,7 +87,9 @@ func (be PromBackend) publishPrometheusMetric(name string, v expvar.Var) { newHistogramCollector(st, be.buildPromName(name)) case *stats.StringMapFuncWithMultiLabels: newStringMapFuncWithMultiLabelsCollector(st, be.buildPromName(name)) - case *stats.String, stats.StringFunc, stats.StringMapFunc, *stats.Rates, *stats.RatesFunc: + case *stats.StringValueWithLabel: + newStringValueWithLabelCollector(st, be.buildPromName(name)) + case *stats.StringValue, stats.StringFunc, stats.StringMapFunc, *stats.Rates, *stats.RatesFunc: // Silently ignore these types since they don't make sense to // export to Prometheus' data model. default: diff --git a/go/stats/prometheusbackend/prometheusbackend_test.go b/go/stats/prometheusbackend/prometheusbackend_test.go index 594265153f7..60151159716 100644 --- a/go/stats/prometheusbackend/prometheusbackend_test.go +++ b/go/stats/prometheusbackend/prometheusbackend_test.go @@ -258,6 +258,16 @@ func TestPrometheusStringMapFuncWithMultiLabels(t *testing.T) { checkHandlerForMetricWithMultiLabels(t, name, allLabels, []string{"bar", "baz", "world"}, 1) } +func TestPrometheusStringValueWithLabel(t *testing.T) { + name := "blah_stringvaluewithlabel" + label := "label" + value := "value" + + stats.NewStringValueWithLabel(name, "help", label, value) + + checkHandlerForMetricWithMultiLabels(t, name, []string{label}, []string{value}, 1) +} + func checkHandlerForMetricWithMultiLabels(t *testing.T, metric string, labels []string, labelValues []string, value int64) { response := testMetricsHandler(t) diff --git a/go/stats/statsd/statsd.go b/go/stats/statsd/statsd.go index f791d7b742d..da0a643315a 100644 --- a/go/stats/statsd/statsd.go +++ b/go/stats/statsd/statsd.go @@ -202,7 +202,7 @@ func (sb StatsBackend) addExpVar(kv expvar.KeyValue) { } } } - case *stats.String: + case *stats.StringValue: if k == "BuildGitRev" { buildGitRecOnce.Do(func() { checksum := crc32.ChecksumIEEE([]byte(v.Get())) diff --git a/go/vt/grpccommon/options.go b/go/vt/grpccommon/options.go index 7013b95b95a..54179e44005 100644 --- a/go/vt/grpccommon/options.go +++ b/go/vt/grpccommon/options.go @@ -54,5 +54,5 @@ func MaxMessageSize() int { } func init() { - stats.NewString("GrpcVersion").Set(grpc.Version) + stats.NewStringValue("GrpcVersion").Set(grpc.Version) } diff --git a/go/vt/servenv/buildinfo.go b/go/vt/servenv/buildinfo.go index 15e34217dae..7a56edb1479 100644 --- a/go/vt/servenv/buildinfo.go +++ b/go/vt/servenv/buildinfo.go @@ -118,15 +118,15 @@ func init() { goArch: runtime.GOARCH, version: versionName, } - stats.NewString("BuildHost").Set(AppVersion.buildHost) - stats.NewString("BuildUser").Set(AppVersion.buildUser) + stats.NewStringValue("BuildHost").Set(AppVersion.buildHost) + stats.NewStringValue("BuildUser").Set(AppVersion.buildUser) stats.NewGauge("BuildTimestamp", "build timestamp").Set(AppVersion.buildTime) - stats.NewString("BuildGitRev").Set(AppVersion.buildGitRev) - stats.NewString("BuildGitBranch").Set(AppVersion.buildGitBranch) + stats.NewStringValue("BuildGitRev").Set(AppVersion.buildGitRev) + stats.NewStringValue("BuildGitBranch").Set(AppVersion.buildGitBranch) stats.NewGauge("BuildNumber", "build number").Set(AppVersion.jenkinsBuildNumber) - stats.NewString("GoVersion").Set(AppVersion.goVersion) - stats.NewString("GoOS").Set(AppVersion.goOS) - stats.NewString("GoArch").Set(AppVersion.goArch) + stats.NewStringValue("GoVersion").Set(AppVersion.goVersion) + stats.NewStringValue("GoOS").Set(AppVersion.goOS) + stats.NewStringValue("GoArch").Set(AppVersion.goArch) buildLabels := []string{"BuildHost", "BuildUser", "BuildTimestamp", "BuildGitRev", "BuildGitBranch", "BuildNumber"} buildValues := []string{ diff --git a/go/vt/servenv/exporter_test.go b/go/vt/servenv/exporter_test.go index f692e7d5d03..643762d48cf 100644 --- a/go/vt/servenv/exporter_test.go +++ b/go/vt/servenv/exporter_test.go @@ -624,7 +624,7 @@ func TestHistogram(t *testing.T) { func TestPublish(t *testing.T) { ebd := NewExporter("", "") - s := stats.NewString("") + s := stats.NewStringValue("") ebd.Publish("gpub", s) s.Set("1") assert.Equal(t, `"1"`, expvar.Get("gpub").String()) diff --git a/go/vt/vttablet/tabletmanager/restore.go b/go/vt/vttablet/tabletmanager/restore.go index 4512b546f2c..45f7e9867bb 100644 --- a/go/vt/vttablet/tabletmanager/restore.go +++ b/go/vt/vttablet/tabletmanager/restore.go @@ -59,8 +59,8 @@ var ( restoreConcurrency = 4 waitForBackupInterval time.Duration - statsRestoreBackupTime *stats.String - statsRestoreBackupPosition *stats.String + statsRestoreBackupTime *stats.StringValue + statsRestoreBackupPosition *stats.StringValue ) func registerRestoreFlags(fs *pflag.FlagSet) { @@ -116,8 +116,8 @@ func init() { servenv.OnParseFor("vtcombo", registerPointInTimeRestoreFlags) servenv.OnParseFor("vttablet", registerPointInTimeRestoreFlags) - statsRestoreBackupTime = stats.NewString("RestoredBackupTime") - statsRestoreBackupPosition = stats.NewString("RestorePosition") + statsRestoreBackupTime = stats.NewStringValue("RestoredBackupTime") + statsRestoreBackupPosition = stats.NewStringValue("RestorePosition") } // RestoreData is the main entry point for backup restore. diff --git a/go/vt/vttablet/tabletmanager/tm_init.go b/go/vt/vttablet/tabletmanager/tm_init.go index 2cd21c09a21..62038349a47 100644 --- a/go/vt/vttablet/tabletmanager/tm_init.go +++ b/go/vt/vttablet/tabletmanager/tm_init.go @@ -103,7 +103,7 @@ func registerInitFlags(fs *pflag.FlagSet) { var ( // statsTabletType is set to expose the current tablet type. - statsTabletType *stats.String + statsTabletType *stats.StringValueWithLabel // statsTabletTypeCount exposes the current tablet type as a label, // with the value counting the occurrences of the respective tablet type. @@ -116,11 +116,11 @@ var ( // statsIsInSrvKeyspace is set to 1 (true), 0 (false) whether the tablet is in the serving keyspace statsIsInSrvKeyspace *stats.Gauge - statsKeyspace = stats.NewString("TabletKeyspace") - statsShard = stats.NewString("TabletShard") - statsKeyRangeStart = stats.NewString("TabletKeyRangeStart") - statsKeyRangeEnd = stats.NewString("TabletKeyRangeEnd") - statsAlias = stats.NewString("TabletAlias") + statsKeyspace = stats.NewStringValue("TabletKeyspace") + statsShard = stats.NewStringValue("TabletShard") + statsKeyRangeStart = stats.NewStringValue("TabletKeyRangeStart") + statsKeyRangeEnd = stats.NewStringValue("TabletKeyRangeEnd") + statsAlias = stats.NewStringValue("TabletAlias") // The following variables can be changed to speed up tests. mysqlPortRetryInterval = 1 * time.Second @@ -131,7 +131,7 @@ func init() { servenv.OnParseFor("vtcombo", registerInitFlags) servenv.OnParseFor("vttablet", registerInitFlags) - statsTabletType = stats.NewString("TabletType") + statsTabletType = stats.NewStringValueWithLabel("TabletType", "Current tablet type", "TabletType", "") statsTabletTypeCount = stats.NewCountersWithSingleLabel("TabletTypeCount", "Number of times the tablet changed to the labeled type", "type") statsBackupIsRunning = stats.NewGaugesWithMultiLabels("BackupIsRunning", "Whether a backup is running", []string{"mode"}) statsIsInSrvKeyspace = stats.NewGauge("IsInSrvKeyspace", "Whether the vttablet is in the serving keyspace (1 = true / 0 = false)") From 9b5bcc0f59c0b9341fcc0aff1118bc35b849a453 Mon Sep 17 00:00:00 2001 From: Max Englander Date: Wed, 18 Oct 2023 14:13:27 +0100 Subject: [PATCH 2/2] changelog/19.0: document vttablet_tablet_type for prometheus Signed-off-by: Max Englander --- changelog/19.0/19.0.0/summary.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/changelog/19.0/19.0.0/summary.md b/changelog/19.0/19.0.0/summary.md index 5d413c25cae..259c673b61f 100644 --- a/changelog/19.0/19.0.0/summary.md +++ b/changelog/19.0/19.0.0/summary.md @@ -6,6 +6,8 @@ - **[Deprecations and Deletions](#deprecations-and-deletions)** - **[Docker](#docker)** - [New MySQL Image](#mysql-image) + - **[New stats](#new-stats)** + - [VTTablet tablet type for Prometheus](#vttablet-tablet-type-for-prometheus) ## Major Changes @@ -21,3 +23,21 @@ In `v19.0` the Vitess team is shipping a new image: `vitess/mysql`. This lightweight image is a replacement of `vitess/lite` to only run `mysqld`. Several tags are available to let you choose what version of MySQL you want to use: `vitess/mysql:8.0.30`, `vitess/mysql:8.0.34`. + +### New stats + + +#### VTTablet tablet type for Prometheus + +VTTablet publishes the `TabletType` status which reports the current type of the tablet. For example, here's what it looks like in the local cluster example after setting up the initial cluster: + +``` +"TabletType": "primary" +``` + +Prior to v19, this data was not available via the Prometheus backend. In v19, this is also published as a Prometheus gauge with a `tablet_type` label and a value of `1.0`. For example: + +``` +$ curl -s localhost:15100/metrics | grep tablet_type{ +vttablet_tablet_type{tablet_type="primary"} 1 +```