client/local: add method to set gauge metric to a value

The existing client metric methods only support incrementing (or
decrementing) a delta value.  This new method allows setting the metric
to a specific value.

Updates tailscale/corp#35327

Change-Id: Ia101a4a3005adb9118051b3416f5a64a4a45987d
Signed-off-by: Will Norris <will@tailscale.com>
pull/18087/merge
Will Norris 5 days ago committed by Will Norris
parent f174ecb6fd
commit 0fd1670a59

@ -43,6 +43,7 @@ import (
"tailscale.com/types/appctype"
"tailscale.com/types/dnstype"
"tailscale.com/types/key"
"tailscale.com/util/clientmetric"
"tailscale.com/util/eventbus"
)
@ -385,18 +386,14 @@ func (lc *Client) IncrementCounter(ctx context.Context, name string, delta int)
if !buildfeatures.HasClientMetrics {
return nil
}
type metricUpdate struct {
Name string `json:"name"`
Type string `json:"type"`
Value int `json:"value"` // amount to increment by
}
if delta < 0 {
return errors.New("negative delta not allowed")
}
_, err := lc.send(ctx, "POST", "/localapi/v0/upload-client-metrics", 200, jsonBody([]metricUpdate{{
_, err := lc.send(ctx, "POST", "/localapi/v0/upload-client-metrics", 200, jsonBody([]clientmetric.MetricUpdate{{
Name: name,
Type: "counter",
Value: delta,
Op: "add",
}}))
return err
}
@ -405,15 +402,23 @@ func (lc *Client) IncrementCounter(ctx context.Context, name string, delta int)
// metric by the given delta. If the metric has yet to exist, a new gauge
// metric is created and initialized to delta. The delta value can be negative.
func (lc *Client) IncrementGauge(ctx context.Context, name string, delta int) error {
type metricUpdate struct {
Name string `json:"name"`
Type string `json:"type"`
Value int `json:"value"` // amount to increment by
}
_, err := lc.send(ctx, "POST", "/localapi/v0/upload-client-metrics", 200, jsonBody([]metricUpdate{{
_, err := lc.send(ctx, "POST", "/localapi/v0/upload-client-metrics", 200, jsonBody([]clientmetric.MetricUpdate{{
Name: name,
Type: "gauge",
Value: delta,
Op: "add",
}}))
return err
}
// SetGauge sets the value of a Tailscale daemon's gauge metric to the given value.
// If the metric has yet to exist, a new gauge metric is created and initialized to value.
func (lc *Client) SetGauge(ctx context.Context, name string, value int) error {
_, err := lc.send(ctx, "POST", "/localapi/v0/upload-client-metrics", 200, jsonBody([]clientmetric.MetricUpdate{{
Name: name,
Type: "gauge",
Value: value,
Op: "set",
}}))
return err
}

@ -66,8 +66,8 @@ func (menu *Menu) Run(client *local.Client) {
case <-menu.bgCtx.Done():
}
}()
go menu.lc.IncrementGauge(menu.bgCtx, "systray_running", 1)
defer menu.lc.IncrementGauge(menu.bgCtx, "systray_running", -1)
go menu.lc.SetGauge(menu.bgCtx, "systray_running", 1)
defer menu.lc.SetGauge(menu.bgCtx, "systray_running", 0)
systray.Run(menu.onReady, menu.onExit)
}

@ -143,7 +143,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
tailscale.com/types/tkatype from tailscale.com/client/local+
tailscale.com/types/views from tailscale.com/ipn+
tailscale.com/util/cibuild from tailscale.com/health+
tailscale.com/util/clientmetric from tailscale.com/net/netmon
tailscale.com/util/clientmetric from tailscale.com/net/netmon+
tailscale.com/util/cloudenv from tailscale.com/hostinfo+
tailscale.com/util/ctxkey from tailscale.com/tsweb+
💣 tailscale.com/util/deephash from tailscale.com/util/syspolicy/setting

@ -1283,13 +1283,8 @@ func (h *Handler) serveUploadClientMetrics(w http.ResponseWriter, r *http.Reques
http.Error(w, "unsupported method", http.StatusMethodNotAllowed)
return
}
type clientMetricJSON struct {
Name string `json:"name"`
Type string `json:"type"` // one of "counter" or "gauge"
Value int `json:"value"` // amount to increment metric by
}
var clientMetrics []clientMetricJSON
var clientMetrics []clientmetric.MetricUpdate
if err := json.NewDecoder(r.Body).Decode(&clientMetrics); err != nil {
http.Error(w, "invalid JSON body", http.StatusBadRequest)
return
@ -1299,14 +1294,12 @@ func (h *Handler) serveUploadClientMetrics(w http.ResponseWriter, r *http.Reques
defer metricsMu.Unlock()
for _, m := range clientMetrics {
if metric, ok := metrics[m.Name]; ok {
metric.Add(int64(m.Value))
} else {
metric, ok := metrics[m.Name]
if !ok {
if clientmetric.HasPublished(m.Name) {
http.Error(w, "Already have a metric named "+m.Name, http.StatusBadRequest)
return
}
var metric *clientmetric.Metric
switch m.Type {
case "counter":
metric = clientmetric.NewCounter(m.Name)
@ -1317,7 +1310,15 @@ func (h *Handler) serveUploadClientMetrics(w http.ResponseWriter, r *http.Reques
return
}
metrics[m.Name] = metric
}
switch m.Op {
case "add", "":
metric.Add(int64(m.Value))
case "set":
metric.Set(int64(m.Value))
default:
http.Error(w, "Unknown metric op "+m.Op, http.StatusBadRequest)
return
}
}

@ -58,6 +58,20 @@ const (
TypeCounter
)
// MetricUpdate requests that a client metric value be updated.
//
// This is the request body sent to /localapi/v0/upload-client-metrics.
type MetricUpdate struct {
Name string `json:"name"`
Type string `json:"type"` // one of "counter" or "gauge"
Value int `json:"value"` // amount to increment by or set
// Op indicates if Value is added to the existing metric value,
// or if the metric is set to Value.
// One of "add" or "set". If empty, defaults to "add".
Op string `json:"op"`
}
// Metric is an integer metric value that's tracked over time.
//
// It's safe for concurrent use.

@ -13,6 +13,13 @@ func (*Metric) Value() int64 { return 0 }
func (*Metric) Register(expvarInt any) {}
func (*Metric) UnregisterAll() {}
type MetricUpdate struct {
Name string `json:"name"`
Type string `json:"type"`
Value int `json:"value"`
Op string `json:"op"`
}
func HasPublished(string) bool { panic("unreachable") }
func EncodeLogTailMetricsDelta() string { return "" }
func WritePrometheusExpositionFormat(any) {}

Loading…
Cancel
Save