mirror of
				https://github.com/tailscale/tailscale.git
				synced 2025-10-31 08:11:32 +01:00 
			
		
		
		
	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 <bradfitz@tailscale.com> Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
		
			
				
	
	
		
			132 lines
		
	
	
		
			3.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			132 lines
		
	
	
		
			3.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright (c) Tailscale Inc & AUTHORS
 | |
| // SPDX-License-Identifier: BSD-3-Clause
 | |
| 
 | |
| // checkmetrics validates that all metrics in the tailscale client-metrics
 | |
| // are documented in a given path or URL.
 | |
| package main
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"flag"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"log"
 | |
| 	"net/http"
 | |
| 	"net/http/httptest"
 | |
| 	"os"
 | |
| 	"strings"
 | |
| 	"time"
 | |
| 
 | |
| 	"tailscale.com/ipn/store/mem"
 | |
| 	"tailscale.com/tsnet"
 | |
| 	"tailscale.com/tstest/integration/testcontrol"
 | |
| 	"tailscale.com/util/httpm"
 | |
| )
 | |
| 
 | |
| var (
 | |
| 	kbPath = flag.String("kb-path", "", "filepath to the client-metrics knowledge base")
 | |
| 	kbUrl  = flag.String("kb-url", "", "URL to the client-metrics knowledge base page")
 | |
| )
 | |
| 
 | |
| func main() {
 | |
| 	flag.Parse()
 | |
| 	if *kbPath == "" && *kbUrl == "" {
 | |
| 		log.Fatalf("either -kb-path or -kb-url must be set")
 | |
| 	}
 | |
| 
 | |
| 	var control testcontrol.Server
 | |
| 	ts := httptest.NewServer(&control)
 | |
| 	defer ts.Close()
 | |
| 
 | |
| 	td, err := os.MkdirTemp("", "testcontrol")
 | |
| 	if err != nil {
 | |
| 		log.Fatal(err)
 | |
| 	}
 | |
| 	defer os.RemoveAll(td)
 | |
| 
 | |
| 	// tsnet is used not used as a Tailscale client, but as a way to
 | |
| 	// boot up Tailscale, have all the metrics registered, and then
 | |
| 	// verifiy that all the metrics are documented.
 | |
| 	tsn := &tsnet.Server{
 | |
| 		Dir:        td,
 | |
| 		Store:      new(mem.Store),
 | |
| 		UserLogf:   log.Printf,
 | |
| 		Ephemeral:  true,
 | |
| 		ControlURL: ts.URL,
 | |
| 	}
 | |
| 	if err := tsn.Start(); err != nil {
 | |
| 		log.Fatal(err)
 | |
| 	}
 | |
| 	defer tsn.Close()
 | |
| 
 | |
| 	log.Printf("checking that all metrics are documented, looking for: %s", tsn.Sys().UserMetricsRegistry().MetricNames())
 | |
| 
 | |
| 	if *kbPath != "" {
 | |
| 		kb, err := readKB(*kbPath)
 | |
| 		if err != nil {
 | |
| 			log.Fatalf("reading kb: %v", err)
 | |
| 		}
 | |
| 		missing := undocumentedMetrics(kb, tsn.Sys().UserMetricsRegistry().MetricNames())
 | |
| 
 | |
| 		if len(missing) > 0 {
 | |
| 			log.Fatalf("found undocumented metrics in %q: %v", *kbPath, missing)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if *kbUrl != "" {
 | |
| 		ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
 | |
| 		defer cancel()
 | |
| 
 | |
| 		kb, err := getKB(ctx, *kbUrl)
 | |
| 		if err != nil {
 | |
| 			log.Fatalf("getting kb: %v", err)
 | |
| 		}
 | |
| 		missing := undocumentedMetrics(kb, tsn.Sys().UserMetricsRegistry().MetricNames())
 | |
| 
 | |
| 		if len(missing) > 0 {
 | |
| 			log.Fatalf("found undocumented metrics in %q: %v", *kbUrl, missing)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func readKB(path string) (string, error) {
 | |
| 	b, err := os.ReadFile(path)
 | |
| 	if err != nil {
 | |
| 		return "", fmt.Errorf("reading file: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	return string(b), nil
 | |
| }
 | |
| 
 | |
| func getKB(ctx context.Context, url string) (string, error) {
 | |
| 	req, err := http.NewRequestWithContext(ctx, httpm.GET, url, nil)
 | |
| 	if err != nil {
 | |
| 		return "", fmt.Errorf("creating request: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	resp, err := http.DefaultClient.Do(req)
 | |
| 	if err != nil {
 | |
| 		return "", fmt.Errorf("getting kb page: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	if resp.StatusCode != http.StatusOK {
 | |
| 		return "", fmt.Errorf("unexpected status code: %d", resp.StatusCode)
 | |
| 	}
 | |
| 
 | |
| 	b, err := io.ReadAll(resp.Body)
 | |
| 	if err != nil {
 | |
| 		return "", fmt.Errorf("reading body: %w", err)
 | |
| 	}
 | |
| 	return string(b), nil
 | |
| }
 | |
| 
 | |
| func undocumentedMetrics(b string, metrics []string) []string {
 | |
| 	var missing []string
 | |
| 	for _, metric := range metrics {
 | |
| 		if !strings.Contains(b, metric) {
 | |
| 			missing = append(missing, metric)
 | |
| 		}
 | |
| 	}
 | |
| 	return missing
 | |
| }
 |