Browse Source

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 <[email protected]>
Will Norris 2 months ago
parent
commit
0fd1670a59

+ 17 - 12
client/local/local.go

@@ -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
 }

+ 2 - 2
client/systray/systray.go

@@ -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)
 }

+ 1 - 1
cmd/derper/depaware.txt

@@ -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

+ 11 - 10
ipn/localapi/localapi.go

@@ -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
 		}
 	}
 

+ 14 - 0
util/clientmetric/clientmetric.go

@@ -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.

+ 7 - 0
util/clientmetric/omit.go

@@ -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) {}