Skip to content

Commit a68efe2

Browse files
bradfitzkradalby
authored andcommitted
cmd/checkmetrics: add command for checking metrics against kb
This commit adds a command to validate that all the metrics that are registring in the client are also present in a path or url. It is intended to be ran from the KB against the latest version of tailscale. Updates tailscale/corp#24066 Updates tailscale/corp#22075 Co-Authored-By: Brad Fitzpatrick <[email protected]> Signed-off-by: Kristoffer Dalby <[email protected]>
1 parent 13faa64 commit a68efe2

File tree

2 files changed

+142
-0
lines changed

2 files changed

+142
-0
lines changed

cmd/checkmetrics/checkmetrics.go

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
// Copyright (c) Tailscale Inc & AUTHORS
2+
// SPDX-License-Identifier: BSD-3-Clause
3+
4+
// checkmetrics validates that all metrics in the tailscale client-metrics
5+
// are documented in a given path or URL.
6+
package main
7+
8+
import (
9+
"context"
10+
"flag"
11+
"fmt"
12+
"io"
13+
"log"
14+
"net/http"
15+
"net/http/httptest"
16+
"os"
17+
"strings"
18+
"time"
19+
20+
"tailscale.com/ipn/store/mem"
21+
"tailscale.com/tsnet"
22+
"tailscale.com/tstest/integration/testcontrol"
23+
"tailscale.com/util/httpm"
24+
)
25+
26+
var (
27+
kbPath = flag.String("kb-path", "", "filepath to the client-metrics knowledge base")
28+
kbUrl = flag.String("kb-url", "", "URL to the client-metrics knowledge base page")
29+
)
30+
31+
func main() {
32+
flag.Parse()
33+
if *kbPath == "" && *kbUrl == "" {
34+
log.Fatalf("either -kb-path or -kb-url must be set")
35+
}
36+
37+
var control testcontrol.Server
38+
ts := httptest.NewServer(&control)
39+
defer ts.Close()
40+
41+
td, err := os.MkdirTemp("", "testcontrol")
42+
if err != nil {
43+
log.Fatal(err)
44+
}
45+
defer os.RemoveAll(td)
46+
47+
// tsnet is used not used as a Tailscale client, but as a way to
48+
// boot up Tailscale, have all the metrics registered, and then
49+
// verifiy that all the metrics are documented.
50+
tsn := &tsnet.Server{
51+
Dir: td,
52+
Store: new(mem.Store),
53+
UserLogf: log.Printf,
54+
Ephemeral: true,
55+
ControlURL: ts.URL,
56+
}
57+
if err := tsn.Start(); err != nil {
58+
log.Fatal(err)
59+
}
60+
defer tsn.Close()
61+
62+
log.Printf("checking that all metrics are documented, looking for: %s", tsn.Sys().UserMetricsRegistry().MetricNames())
63+
64+
if *kbPath != "" {
65+
kb, err := readKB(*kbPath)
66+
if err != nil {
67+
log.Fatalf("reading kb: %v", err)
68+
}
69+
missing := undocumentedMetrics(kb, tsn.Sys().UserMetricsRegistry().MetricNames())
70+
71+
if len(missing) > 0 {
72+
log.Fatalf("found undocumented metrics in %q: %v", *kbPath, missing)
73+
}
74+
}
75+
76+
if *kbUrl != "" {
77+
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
78+
defer cancel()
79+
80+
kb, err := getKB(ctx, *kbUrl)
81+
if err != nil {
82+
log.Fatalf("getting kb: %v", err)
83+
}
84+
missing := undocumentedMetrics(kb, tsn.Sys().UserMetricsRegistry().MetricNames())
85+
86+
if len(missing) > 0 {
87+
log.Fatalf("found undocumented metrics in %q: %v", *kbUrl, missing)
88+
}
89+
}
90+
}
91+
92+
func readKB(path string) (string, error) {
93+
b, err := os.ReadFile(path)
94+
if err != nil {
95+
return "", fmt.Errorf("reading file: %w", err)
96+
}
97+
98+
return string(b), nil
99+
}
100+
101+
func getKB(ctx context.Context, url string) (string, error) {
102+
req, err := http.NewRequestWithContext(ctx, httpm.GET, url, nil)
103+
if err != nil {
104+
return "", fmt.Errorf("creating request: %w", err)
105+
}
106+
107+
resp, err := http.DefaultClient.Do(req)
108+
if err != nil {
109+
return "", fmt.Errorf("getting kb page: %w", err)
110+
}
111+
112+
if resp.StatusCode != http.StatusOK {
113+
return "", fmt.Errorf("unexpected status code: %d", resp.StatusCode)
114+
}
115+
116+
b, err := io.ReadAll(resp.Body)
117+
if err != nil {
118+
return "", fmt.Errorf("reading body: %w", err)
119+
}
120+
return string(b), nil
121+
}
122+
123+
func undocumentedMetrics(b string, metrics []string) []string {
124+
var missing []string
125+
for _, metric := range metrics {
126+
if !strings.Contains(b, metric) {
127+
missing = append(missing, metric)
128+
}
129+
}
130+
return missing
131+
}

util/usermetric/usermetric.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414

1515
"tailscale.com/metrics"
1616
"tailscale.com/tsweb/varz"
17+
"tailscale.com/util/set"
1718
)
1819

1920
// Registry tracks user-facing metrics of various Tailscale subsystems.
@@ -106,3 +107,13 @@ func (r *Registry) String() string {
106107

107108
return sb.String()
108109
}
110+
111+
// Metrics returns the name of all the metrics in the registry.
112+
func (r *Registry) MetricNames() []string {
113+
ret := make(set.Set[string])
114+
r.vars.Do(func(kv expvar.KeyValue) {
115+
ret.Add(kv.Key)
116+
})
117+
118+
return ret.Slice()
119+
}

0 commit comments

Comments
 (0)