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
+```
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)")