diff --git a/script/find-metrics.go b/script/find-metrics.go deleted file mode 100644 index dbb6549b0..000000000 --- a/script/find-metrics.go +++ /dev/null @@ -1,187 +0,0 @@ -// Copyright (C) 2023 The Syncthing Authors. -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this file, -// You can obtain one at https://mozilla.org/MPL/2.0/. - -// Usage: go run script/find-metrics.go > metrics.md -// -// This script finds all of the metrics in the Syncthing codebase and prints -// them in Markdown format. It's used to generate the metrics documentation -// for the Syncthing docs. -package main - -import ( - "fmt" - "go/ast" - "go/token" - "log" - "strconv" - "strings" - - "golang.org/x/exp/slices" - "golang.org/x/tools/go/packages" -) - -type metric struct { - subsystem string - name string - help string - kind string -} - -func main() { - opts := &packages.Config{ - Mode: packages.NeedSyntax | packages.NeedName | packages.NeedTypes | packages.NeedTypesInfo | packages.NeedImports | packages.NeedDeps, - } - - pkgs, err := packages.Load(opts, "github.com/syncthing/syncthing/...") - if err != nil { - log.Fatalln(err) - } - - var coll metricCollector - for _, pkg := range pkgs { - for _, file := range pkg.Syntax { - ast.Inspect(file, coll.Visit) - } - } - coll.print() -} - -type metricCollector struct { - metrics []metric -} - -func (c *metricCollector) Visit(n ast.Node) bool { - if gen, ok := n.(*ast.GenDecl); ok { - // We're only interested in var declarations (var metricWhatever = - // promauto.NewCounter(...) etc). - if gen.Tok != token.VAR { - return false - } - - for _, spec := range gen.Specs { - // We want to look at the value given to a var (the NewCounter() - // etc call). - if vsp, ok := spec.(*ast.ValueSpec); ok { - // There should be only one value. - if len(vsp.Values) != 1 { - continue - } - - // The value should be a function call. - call, ok := vsp.Values[0].(*ast.CallExpr) - if !ok { - continue - } - - // The call should be a selector expression - // (package.Identifer). - sel, ok := call.Fun.(*ast.SelectorExpr) - if !ok { - continue - } - - // The package selector should be `promauto`. - selID, ok := sel.X.(*ast.Ident) - if !ok || selID.Name != "promauto" { - continue - } - - // The function should be one of the New* functions. - var kind string - switch sel.Sel.Name { - case "NewCounter": - kind = "counter" - case "NewGauge": - kind = "gauge" - case "NewCounterVec": - kind = "counter vector" - case "NewGaugeVec": - kind = "gauge vector" - default: - continue - } - - // The arguments to the function should be a single - // composite (struct literal). Grab all of the fields in the - // declaration into a map so we can easily access them. - args := make(map[string]string) - for _, el := range call.Args[0].(*ast.CompositeLit).Elts { - kv := el.(*ast.KeyValueExpr) - key := kv.Key.(*ast.Ident).Name // e.g., "Name" - val := kv.Value.(*ast.BasicLit).Value // e.g., `"foo"` - args[key], _ = strconv.Unquote(val) - } - - // Build the full name of the metric from the namespace + - // subsystem + name, like Prometheus does. - var parts []string - if v := args["Namespace"]; v != "" { - parts = append(parts, v) - } - if v := args["Subsystem"]; v != "" { - parts = append(parts, v) - } - if v := args["Name"]; v != "" { - parts = append(parts, v) - } - fullName := strings.Join(parts, "_") - - // Add the metric to the list. - c.metrics = append(c.metrics, metric{ - subsystem: args["Subsystem"], - name: fullName, - help: args["Help"], - kind: kind, - }) - } - } - } - return true -} - -func (c *metricCollector) print() { - slices.SortFunc(c.metrics, func(a, b metric) bool { - if a.subsystem != b.subsystem { - return a.subsystem < b.subsystem - } - return a.name < b.name - }) - - var prevSubsystem string - for _, m := range c.metrics { - if m.subsystem != prevSubsystem { - fmt.Printf("## Package `%s`\n\n", m.subsystem) - prevSubsystem = m.subsystem - } - fmt.Printf("### `%v` (%s)\n\n%s\n\n", m.name, m.kind, wordwrap(sentenceize(m.help), 72)) - } -} - -func sentenceize(s string) string { - if s == "" { - return "" - } - if !strings.HasSuffix(s, ".") { - return s + "." - } - return s -} - -func wordwrap(s string, width int) string { - var lines []string - for _, line := range strings.Split(s, "\n") { - for len(line) > width { - i := strings.LastIndex(line[:width], " ") - if i == -1 { - i = width - } - lines = append(lines, line[:i]) - line = line[i+1:] - } - lines = append(lines, line) - } - return strings.Join(lines, "\n") -}