Skip to content

Commit cf42cb1

Browse files
calmhacolomb
andauthored
Add documentation for metrics endpoint (syncthing#816)
Co-authored-by: André Colomb <[email protected]>
1 parent c118b8d commit cf42cb1

File tree

9 files changed

+359
-2
lines changed

9 files changed

+359
-2
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
_build/
22
_deployed
33
_deployed.old
4+
_syncthing

_script/find-metrics/find-metrics.go

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
// Copyright (C) 2023 The Syncthing Authors.
2+
//
3+
// This Source Code Form is subject to the terms of the Mozilla Public
4+
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
5+
// You can obtain one at https://mozilla.org/MPL/2.0/.
6+
7+
// Usage: go run script/find-metrics.go > metrics.md
8+
//
9+
// This script finds all of the metrics in the Syncthing codebase and prints
10+
// them in Markdown format. It's used to generate the metrics documentation
11+
// for the Syncthing docs.
12+
package main
13+
14+
import (
15+
"flag"
16+
"fmt"
17+
"go/ast"
18+
"go/token"
19+
"log"
20+
"os"
21+
"strconv"
22+
"strings"
23+
24+
"golang.org/x/exp/slices"
25+
"golang.org/x/tools/go/packages"
26+
)
27+
28+
type metric struct {
29+
subsystem string
30+
name string
31+
help string
32+
kind string
33+
}
34+
35+
func main() {
36+
flag.Parse()
37+
if flag.NArg() != 1 {
38+
fmt.Println("Usage: find-metrics <path>")
39+
os.Exit(1)
40+
}
41+
42+
opts := &packages.Config{
43+
Dir: flag.Arg(0),
44+
Mode: packages.NeedSyntax | packages.NeedName | packages.NeedTypes | packages.NeedTypesInfo | packages.NeedImports | packages.NeedDeps,
45+
}
46+
47+
pkgs, err := packages.Load(opts, "github.com/syncthing/syncthing/...")
48+
if err != nil {
49+
log.Fatalln(err)
50+
}
51+
52+
var coll metricCollector
53+
for _, pkg := range pkgs {
54+
for _, file := range pkg.Syntax {
55+
ast.Inspect(file, coll.Visit)
56+
}
57+
}
58+
coll.print()
59+
}
60+
61+
type metricCollector struct {
62+
metrics []metric
63+
}
64+
65+
func (c *metricCollector) Visit(n ast.Node) bool {
66+
if gen, ok := n.(*ast.GenDecl); ok {
67+
// We're only interested in var declarations (var metricWhatever =
68+
// promauto.NewCounter(...) etc).
69+
if gen.Tok != token.VAR {
70+
return false
71+
}
72+
73+
for _, spec := range gen.Specs {
74+
// We want to look at the value given to a var (the NewCounter()
75+
// etc call).
76+
if vsp, ok := spec.(*ast.ValueSpec); ok {
77+
// There should be only one value.
78+
if len(vsp.Values) != 1 {
79+
continue
80+
}
81+
82+
// The value should be a function call.
83+
call, ok := vsp.Values[0].(*ast.CallExpr)
84+
if !ok {
85+
continue
86+
}
87+
88+
// The call should be a selector expression
89+
// (package.Identifer).
90+
sel, ok := call.Fun.(*ast.SelectorExpr)
91+
if !ok {
92+
continue
93+
}
94+
95+
// The package selector should be `promauto`.
96+
selID, ok := sel.X.(*ast.Ident)
97+
if !ok || selID.Name != "promauto" {
98+
continue
99+
}
100+
101+
// The function should be one of the New* functions.
102+
var kind string
103+
switch sel.Sel.Name {
104+
case "NewCounter":
105+
kind = "counter"
106+
case "NewGauge":
107+
kind = "gauge"
108+
case "NewCounterVec":
109+
kind = "counter vector"
110+
case "NewGaugeVec":
111+
kind = "gauge vector"
112+
default:
113+
continue
114+
}
115+
116+
// The arguments to the function should be a single
117+
// composite (struct literal). Grab all of the fields in the
118+
// declaration into a map so we can easily access them.
119+
args := make(map[string]string)
120+
for _, el := range call.Args[0].(*ast.CompositeLit).Elts {
121+
kv := el.(*ast.KeyValueExpr)
122+
key := kv.Key.(*ast.Ident).Name // e.g., "Name"
123+
val := kv.Value.(*ast.BasicLit).Value // e.g., `"foo"`
124+
args[key], _ = strconv.Unquote(val)
125+
}
126+
127+
// Build the full name of the metric from the namespace +
128+
// subsystem + name, like Prometheus does.
129+
var parts []string
130+
if v := args["Namespace"]; v != "" {
131+
parts = append(parts, v)
132+
}
133+
if v := args["Subsystem"]; v != "" {
134+
parts = append(parts, v)
135+
}
136+
if v := args["Name"]; v != "" {
137+
parts = append(parts, v)
138+
}
139+
fullName := strings.Join(parts, "_")
140+
141+
// Add the metric to the list.
142+
c.metrics = append(c.metrics, metric{
143+
subsystem: args["Subsystem"],
144+
name: fullName,
145+
help: args["Help"],
146+
kind: kind,
147+
})
148+
}
149+
}
150+
}
151+
return true
152+
}
153+
154+
func (c *metricCollector) print() {
155+
slices.SortFunc(c.metrics, func(a, b metric) int {
156+
if a.subsystem != b.subsystem {
157+
return strings.Compare(a.subsystem, b.subsystem)
158+
}
159+
return strings.Compare(a.name, b.name)
160+
})
161+
162+
var prevSubsystem string
163+
for _, m := range c.metrics {
164+
if m.subsystem != prevSubsystem {
165+
fmt.Println(header(fmt.Sprintf("Package *%s*", m.subsystem), "~"))
166+
prevSubsystem = m.subsystem
167+
}
168+
fmt.Println(header(fmt.Sprintf("Metric *%v* (%s)", m.name, m.kind), "^"))
169+
fmt.Println(wordwrap(sentenceize(m.help), 72))
170+
fmt.Println()
171+
}
172+
}
173+
174+
func header(header, underline string) string {
175+
under := strings.Repeat(underline, len(header))
176+
return fmt.Sprintf("%s\n%s\n", header, under)
177+
}
178+
179+
func sentenceize(s string) string {
180+
if s == "" {
181+
return ""
182+
}
183+
if !strings.HasSuffix(s, ".") {
184+
return s + "."
185+
}
186+
return s
187+
}
188+
189+
func wordwrap(s string, width int) string {
190+
var lines []string
191+
for _, line := range strings.Split(s, "\n") {
192+
for len(line) > width {
193+
i := strings.LastIndex(line[:width], " ")
194+
if i == -1 {
195+
i = width
196+
}
197+
lines = append(lines, line[:i])
198+
line = line[i+1:]
199+
}
200+
lines = append(lines, line)
201+
}
202+
return strings.Join(lines, "\n")
203+
}

_script/go.mod

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,15 @@ module syncthing.net/docs
22

33
go 1.20
44

5-
require github.com/google/go-github/v49 v49.1.0
5+
require (
6+
github.com/google/go-github/v49 v49.1.0
7+
golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb
8+
golang.org/x/tools v0.12.0
9+
)
610

711
require (
812
github.com/google/go-querystring v1.1.0 // indirect
913
golang.org/x/crypto v0.6.0 // indirect
14+
golang.org/x/mod v0.12.0 // indirect
15+
golang.org/x/sys v0.11.0 // indirect
1016
)

_script/go.sum

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,13 @@ github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD
66
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
77
golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
88
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
9+
golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb h1:mIKbk8weKhSeLH2GmUTrvx8CjkyJmnU1wFmg59CUjFA=
10+
golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
11+
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
12+
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
13+
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
14+
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
15+
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
16+
golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss=
17+
golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
918
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@
8383

8484
# List of patterns, relative to source directory, that match files and
8585
# directories to ignore when looking for source files.
86-
exclude_patterns = ['_build', 'draft', 'README.rst', 'users/faq-parts']
86+
exclude_patterns = ['_build', '_syncthing', 'draft', 'README.rst', 'users/faq-parts']
8787

8888
# The reST default role (used for this markup: `text`) to use for all
8989
# documents.

includes/metrics-list.rst

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
Package *events*
2+
~~~~~~~~~~~~~~~~
3+
4+
Metric *syncthing_events_total* (counter vector)
5+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
7+
Total number of created/forwarded/dropped events.
8+
9+
Package *fs*
10+
~~~~~~~~~~~~
11+
12+
Metric *syncthing_fs_operation_bytes_total* (counter vector)
13+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
14+
15+
Total number of filesystem bytes transferred, per filesystem root and
16+
operation.
17+
18+
Metric *syncthing_fs_operation_seconds_total* (counter vector)
19+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
20+
21+
Total time spent in filesystem operations, per filesystem root and
22+
operation.
23+
24+
Metric *syncthing_fs_operations_total* (counter vector)
25+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
26+
27+
Total number of filesystem operations, per filesystem root and
28+
operation.
29+
30+
Package *model*
31+
~~~~~~~~~~~~~~~
32+
33+
Metric *syncthing_model_folder_processed_bytes_total* (counter vector)
34+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
35+
36+
Total amount of data processed during folder syncing, per folder ID and
37+
data source (network/local_origin/local_other/local_shifted/skipped).
38+
39+
Metric *syncthing_model_folder_pull_seconds_total* (counter vector)
40+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
41+
42+
Total time spent in folder pull iterations, per folder ID.
43+
44+
Metric *syncthing_model_folder_pulls_total* (counter vector)
45+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
46+
47+
Total number of folder pull iterations, per folder ID.
48+
49+
Metric *syncthing_model_folder_scan_seconds_total* (counter vector)
50+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
51+
52+
Total time spent in folder scan iterations, per folder ID.
53+
54+
Metric *syncthing_model_folder_scans_total* (counter vector)
55+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
56+
57+
Total number of folder scan iterations, per folder ID.
58+
59+
Metric *syncthing_model_folder_state* (gauge vector)
60+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
61+
62+
Current folder state.
63+
64+
Metric *syncthing_model_folder_summary* (gauge vector)
65+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
66+
67+
Current folder summary data (counts for global/local/need
68+
files/directories/symlinks/deleted/bytes).
69+
70+
Package *protocol*
71+
~~~~~~~~~~~~~~~~~~
72+
73+
Metric *syncthing_protocol_recv_bytes_total* (counter vector)
74+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
75+
76+
Total amount of data received, per device.
77+
78+
Metric *syncthing_protocol_recv_decompressed_bytes_total* (counter vector)
79+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
80+
81+
Total amount of data received, after decompression, per device.
82+
83+
Metric *syncthing_protocol_recv_messages_total* (counter vector)
84+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
85+
86+
Total number of messages received, per device.
87+
88+
Metric *syncthing_protocol_sent_bytes_total* (counter vector)
89+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
90+
91+
Total amount of data sent, per device.
92+
93+
Metric *syncthing_protocol_sent_messages_total* (counter vector)
94+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
95+
96+
Total number of messages sent, per device.
97+
98+
Metric *syncthing_protocol_sent_uncompressed_bytes_total* (counter vector)
99+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
100+
101+
Total amount of data sent, before compression, per device.
102+
103+
Package *scanner*
104+
~~~~~~~~~~~~~~~~~
105+
106+
Metric *syncthing_scanner_hashed_bytes_total* (counter vector)
107+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
108+
109+
Total amount of data hashed, per folder.
110+
111+
Metric *syncthing_scanner_scanned_items_total* (counter vector)
112+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
113+
114+
Total number of items (files/directories) inspected, per folder.
115+

refresh-metrics.sh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/bin/sh
2+
set -euo pipefail
3+
4+
rm -rf _syncthing
5+
git clone --depth 1 https://github.com/syncthing/syncthing.git _syncthing
6+
pushd _script
7+
go run ./find-metrics ../_syncthing > ../includes/metrics-list.rst
8+
popd

users/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Usage
1515
guilisten
1616
ldap
1717
tuning
18+
metrics
1819

1920
syncing
2021
untrusted

users/metrics.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
Prometheus-Style Metrics
2+
========================
3+
4+
Syncthing provides an endpoint for Prometheus-style metrics. Metrics are
5+
served on the ``/metrics`` path on the GUI / API address. The metrics endpoint
6+
requires authentication when the GUI / API is configured to require
7+
authentication; see :doc:`/dev/rest` for details.
8+
9+
Metrics
10+
-------
11+
12+
The following metrics are available.
13+
14+
.. include:: ../includes/metrics-list.rst

0 commit comments

Comments
 (0)