diff --git a/tsweb/tsweb.go b/tsweb/tsweb.go index 59ebbf1e2..63b2e6071 100644 --- a/tsweb/tsweb.go +++ b/tsweb/tsweb.go @@ -349,6 +349,15 @@ func Error(code int, msg string, err error) HTTPError { return HTTPError{Code: code, Msg: msg, Err: err} } +// PrometheusVar is a value that knows how to format itself into +// Prometheus metric syntax. +type PrometheusVar interface { + // WritePrometheus writes the value of the var to w, in Prometheus + // metric syntax. All variables names written out must start with + // prefix (or write out a single variable named exactly prefix) + WritePrometheus(w io.Writer, prefix string) +} + // WritePrometheusExpvar writes kv to w in Prometheus metrics format. // // See VarzHandler for conventions. This is exported primarily for @@ -379,6 +388,9 @@ func writePromExpVar(w io.Writer, prefix string, kv expvar.KeyValue) { name := prefix + key switch v := kv.Value.(type) { + case PrometheusVar: + v.WritePrometheus(w, name) + return case *expvar.Int: if typ == "" { typ = "counter" diff --git a/tsweb/tsweb_test.go b/tsweb/tsweb_test.go index c7469f5da..b5c081b09 100644 --- a/tsweb/tsweb_test.go +++ b/tsweb/tsweb_test.go @@ -9,6 +9,8 @@ import ( "context" "errors" "expvar" + "fmt" + "io" "net" "net/http" "net/http/httptest" @@ -469,6 +471,12 @@ foo_AUint16 65535 expvar.Func(func() any { return 123 }), "num_goroutines 123\n", }, + { + "var_that_exports_itself", + "custom_var", + promWriter{}, + "custom_var_value 42\n", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -534,3 +542,13 @@ func (expvarAdapter) String() string { return "{}" } // expvar JSON; unused in t func (a expvarAdapter) PrometheusMetricsReflectRoot() any { return a.st } + +type promWriter struct{} + +func (promWriter) WritePrometheus(w io.Writer, prefix string) { + fmt.Fprintf(w, "%s_value 42\n", prefix) +} + +func (promWriter) String() string { + return "" +}