Skip to content

Commit 550afe2

Browse files
warpforkFiloSottile
authored andcommitted
cmd/litewitness,internal/witness: add Prometheus metrics
Patterns heavily riffed from the sunlight codebase. Standard go and process stats are collected, and custom stats: these include the number of logs monitored, how large they are, how long requests are taking to service, etc. The prefix levels on the metrics might be a little high; what I'm following here is the sunlight project's distinction between the command's whole metrics, and the witness component. Right now, the only metrics I've introduced to litewitness are on the witness component (and that sounds doubly redundant due to the project name), but that might not remain the case forever. I disable the exposure of the metrics endpoint if the obscurity mode flag is engaged. Some metrics do contain log names, and so it matches the same enumeration concern that obscurity mode describes as its purpose. (Possibly future work: I'm not super satisfied with how sometimes the logging labels and the prometheus labels have parallel evolution, for example in `processAddCheckpointRequest`. But attempting to unify those feels like a potential portal to frameworkitis, so leaving that as a future itch to scratch. Or, perhaps not.) Closes #5
1 parent bf0bd0b commit 550afe2

6 files changed

Lines changed: 188 additions & 12 deletions

File tree

cmd/litewitness/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,13 @@ until one connects successfully. If the connection drops after establishing,
6969
litewitness exits.
7070

7171
-obscurity
72-
enable obscurity mode (disable / and /logz endpoints)
72+
enable obscurity mode (disable / and /logz and /metrics endpoints)
7373

7474
Note that the c2sp.org/tlog-witness protocol is not designed to keep the
7575
supported logs or their tree states secret. Moreover, litewitness has no access
76-
to any secrets (becuase the private key is in ssh-agent) except arguably the IP
76+
to any secrets (because the private key is in ssh-agent) except arguably the IP
7777
addresses of its clients (which are always redacted from /logz). Obscurity mode
78-
disables the `/` and `/logz` endpoints to make it harder to enumerate the logs
78+
disables the `/` and `/logz` and `/metrics` endpoints to make it harder to enumerate the logs
7979
known to the witness.
8080

8181
## witnessctl

cmd/litewitness/litewitness.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ import (
2626
"syscall"
2727
"time"
2828

29+
"github.com/prometheus/client_golang/prometheus"
30+
"github.com/prometheus/client_golang/prometheus/collectors"
31+
"github.com/prometheus/client_golang/prometheus/promhttp"
2932
"golang.org/x/crypto/ssh"
3033
"golang.org/x/crypto/ssh/agent"
3134
"golang.org/x/net/http2"
@@ -128,11 +131,23 @@ func main() {
128131
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
129132
defer stop()
130133

134+
metricsRegistry := prometheus.NewRegistry()
135+
metricsRegistry.MustRegister(collectors.NewGoCollector())
136+
metricsRegistry.MustRegister(collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}))
137+
litewitnessMetrics := prometheus.WrapRegistererWithPrefix("litewitness_", metricsRegistry)
138+
witnessMetrics := prometheus.WrapRegistererWithPrefix("witness_", litewitnessMetrics)
139+
witnessMetrics.MustRegister(w.Metrics()...)
140+
131141
mux := http.NewServeMux()
132142
mux.Handle("/", w)
133143
if !*obscurityFlag {
134144
mux.Handle("/logz", console)
135145
mux.Handle("/{$}", indexHandler(w))
146+
mux.Handle("/metrics", promhttp.HandlerFor(metricsRegistry, promhttp.HandlerOpts{
147+
ErrorLog: slog.NewLogLogger(slog.Default().Handler().WithAttrs(
148+
[]slog.Attr{slog.String("source", "metrics")},
149+
), slog.LevelWarn),
150+
}))
136151
}
137152

138153
srv := &http.Server{

go.mod

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ go 1.25.0
55
require (
66
filippo.io/age v1.3.1
77
filippo.io/mostly-harmless/vrf-r255 v0.0.0-20260605095812-d3620d7874c5
8+
github.com/prometheus/client_golang v1.23.2
89
github.com/rogpeppe/go-internal v1.15.0
910
github.com/transparency-dev/tessera v1.0.2
1011
golang.org/x/crypto v0.53.0
1112
golang.org/x/mod v0.37.0
1213
golang.org/x/net v0.55.0
1314
golang.org/x/sync v0.21.0
15+
golang.org/x/sys v0.46.0
1416
lukechampine.com/blake3 v1.4.1
1517
sigsum.org/sigsum-go v0.14.1
1618
zombiezen.com/go/sqlite v1.4.2
@@ -20,6 +22,7 @@ require (
2022
filippo.io/edwards25519 v1.2.0 // indirect
2123
filippo.io/hpke v0.4.0 // indirect
2224
filippo.io/mldsa v0.0.0-20260215214346-43d0283efc3e // indirect
25+
github.com/beorn7/perks v1.0.1 // indirect
2326
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
2427
github.com/cespare/xxhash/v2 v2.3.0 // indirect
2528
github.com/dustin/go-humanize v1.0.1 // indirect
@@ -30,19 +33,24 @@ require (
3033
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
3134
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
3235
github.com/mattn/go-isatty v0.0.22 // indirect
36+
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
3337
github.com/ncruces/go-strftime v1.0.0 // indirect
38+
github.com/prometheus/client_model v0.6.2 // indirect
39+
github.com/prometheus/common v0.66.1 // indirect
40+
github.com/prometheus/procfs v0.16.1 // indirect
3441
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
3542
github.com/transparency-dev/formats v0.1.1 // indirect
3643
github.com/transparency-dev/merkle v0.0.2 // indirect
3744
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
3845
go.opentelemetry.io/otel v1.44.0 // indirect
3946
go.opentelemetry.io/otel/metric v1.44.0 // indirect
4047
go.opentelemetry.io/otel/trace v1.44.0 // indirect
48+
go.yaml.in/yaml/v2 v2.4.2 // indirect
4149
golang.org/x/exp v0.0.0-20260603202125-055de637280b // indirect
42-
golang.org/x/sys v0.46.0 // indirect
4350
golang.org/x/term v0.44.0 // indirect
4451
golang.org/x/text v0.38.0 // indirect
4552
golang.org/x/tools v0.45.0 // indirect
53+
google.golang.org/protobuf v1.36.11 // indirect
4654
k8s.io/klog/v2 v2.140.0 // indirect
4755
modernc.org/libc v1.73.0 // indirect
4856
modernc.org/mathutil v1.7.1 // indirect

go.sum

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ filippo.io/mldsa v0.0.0-20260215214346-43d0283efc3e h1:VsUbObBMxXlc23Eb9VeeJYE4j
1010
filippo.io/mldsa v0.0.0-20260215214346-43d0283efc3e/go.mod h1:32qQ5yj3R24Eu03iWFWchdC3OB653wPvoepWejkefbY=
1111
filippo.io/mostly-harmless/vrf-r255 v0.0.0-20260605095812-d3620d7874c5 h1:YEPDyOyfzVugubcTaInzVisW4sdP2tNMRXhn49mU+sQ=
1212
filippo.io/mostly-harmless/vrf-r255 v0.0.0-20260605095812-d3620d7874c5/go.mod h1:ac5Gah0LmA0/YD4SHdO2M+WUjScWsc99zrAfJK4QViY=
13+
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
14+
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
1315
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
1416
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
1517
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
@@ -31,14 +33,32 @@ github.com/gtank/ristretto255 v0.2.0 h1:LeOuWr6giplWkkMizx2emfG03SRPJqKt1nfIHLVH
3133
github.com/gtank/ristretto255 v0.2.0/go.mod h1:OJ1ox/dWcp7sJ5grYDcZ+kkHYuj5nelW5aaL7ESVXBw=
3234
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
3335
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
36+
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
37+
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
3438
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
3539
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
40+
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
41+
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
42+
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
43+
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
44+
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
45+
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
3646
github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4=
3747
github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
48+
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
49+
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
3850
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
3951
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
4052
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
4153
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
54+
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
55+
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
56+
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
57+
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
58+
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
59+
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
60+
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
61+
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
4262
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
4363
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
4464
github.com/rogpeppe/go-internal v1.15.0 h1:D0RCU5rMAp+SpgkiNdrjfJ+LX4J1M32V2NeCY7EJ6hc=
@@ -59,6 +79,10 @@ go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSY
5979
go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo=
6080
go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=
6181
go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
82+
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
83+
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
84+
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
85+
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
6286
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
6387
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
6488
golang.org/x/exp v0.0.0-20260603202125-055de637280b h1:v1uXiEBHo8QA0LiGCo7UgHMzHT4Kdfpl2zmtH5vaP1Q=
@@ -77,6 +101,11 @@ golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
77101
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
78102
golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8=
79103
golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0=
104+
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
105+
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
106+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
107+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
108+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
80109
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
81110
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
82111
k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc=

internal/witness/metrics.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package witness
2+
3+
import (
4+
"reflect"
5+
"time"
6+
7+
"github.com/prometheus/client_golang/prometheus"
8+
)
9+
10+
type metrics struct {
11+
KnownLogs prometheus.Gauge
12+
LogSize *prometheus.GaugeVec
13+
AddCheckpointCount *prometheus.CounterVec
14+
15+
ReqCount *prometheus.CounterVec
16+
ReqInFlight *prometheus.GaugeVec
17+
ReqDuration *prometheus.SummaryVec
18+
}
19+
20+
func initMetrics() metrics {
21+
return metrics{
22+
KnownLogs: prometheus.NewGauge(prometheus.GaugeOpts{
23+
Name: "known_logs",
24+
Help: "Number of logs known to the witness.",
25+
}),
26+
LogSize: prometheus.NewGaugeVec(
27+
prometheus.GaugeOpts{
28+
Name: "log_tree_size",
29+
Help: "Latest cosigned tree size for each log, by origin.",
30+
// n.b. yes "tree size" is the term for number of entries, per language in c2sp.org/tlog-checkpoint .
31+
},
32+
[]string{"origin"},
33+
),
34+
AddCheckpointCount: prometheus.NewCounterVec(
35+
prometheus.CounterOpts{
36+
Name: "add_checkpoint_requests_total",
37+
Help: "Total add-checkpoint requests processed, by log origin.",
38+
},
39+
// error is empty on success;
40+
// origin is empty for requests that fail before the origin is parsed (e.g. malformed input);
41+
// progress is true if the new checkpoint is for a larger tree, false if same, or empty if earlier errors.
42+
[]string{"error", "origin", "progress"},
43+
),
44+
45+
ReqInFlight: prometheus.NewGaugeVec(
46+
prometheus.GaugeOpts{
47+
Name: "http_in_flight_requests",
48+
Help: "HTTP requests currently being served, by endpoint.",
49+
},
50+
[]string{"endpoint"},
51+
),
52+
ReqCount: prometheus.NewCounterVec(
53+
prometheus.CounterOpts{
54+
Name: "http_requests_total",
55+
Help: "HTTP requests served, by endpoint and response code.",
56+
},
57+
[]string{"endpoint", "code"},
58+
),
59+
ReqDuration: prometheus.NewSummaryVec(
60+
prometheus.SummaryOpts{
61+
Name: "http_request_duration_seconds",
62+
Help: "HTTP request latency in seconds, by endpoint.",
63+
Objectives: map[float64]float64{0.5: 0.05, 0.75: 0.025, 0.9: 0.01, 0.99: 0.001},
64+
MaxAge: 1 * time.Minute,
65+
AgeBuckets: 6,
66+
},
67+
[]string{"endpoint"},
68+
),
69+
}
70+
}
71+
72+
// Metrics returns all Prometheus collectors owned by this Witness.
73+
// The caller should register them to a [prometheus.Registry].
74+
func (w *Witness) Metrics() []prometheus.Collector {
75+
var collectors []prometheus.Collector
76+
for i := 0; i < reflect.ValueOf(w.metrics).NumField(); i++ {
77+
collectors = append(collectors, reflect.ValueOf(w.metrics).Field(i).Interface().(prometheus.Collector))
78+
}
79+
return collectors
80+
}

internal/witness/witness.go

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,19 @@ import (
1515
"sync"
1616

1717
"filippo.io/torchwood"
18+
"github.com/prometheus/client_golang/prometheus"
19+
"github.com/prometheus/client_golang/prometheus/promhttp"
1820
"golang.org/x/mod/sumdb/note"
1921
"golang.org/x/mod/sumdb/tlog"
2022
"zombiezen.com/go/sqlite"
2123
"zombiezen.com/go/sqlite/sqlitex"
2224
)
2325

2426
type Witness struct {
25-
s *torchwood.CosignatureSigner
26-
mux *http.ServeMux
27-
log *slog.Logger
27+
s *torchwood.CosignatureSigner
28+
mux *http.ServeMux
29+
log *slog.Logger
30+
metrics metrics
2831

2932
dmMu sync.Mutex
3033
db *sqlite.Conn
@@ -74,12 +77,24 @@ func NewWitness(dbPath, name string, key crypto.Signer, log *slog.Logger) (*Witn
7477
}
7578

7679
w := &Witness{
77-
db: db,
78-
s: s,
79-
log: log,
80-
mux: http.NewServeMux(),
80+
db: db,
81+
s: s,
82+
log: log,
83+
metrics: initMetrics(),
84+
mux: http.NewServeMux(),
8185
}
82-
w.mux.Handle("POST /add-checkpoint", http.HandlerFunc(w.serveAddCheckpoint))
86+
87+
// Give initial values to metrics that are valid on startup
88+
if n, err := w.countKnownLogs(); err == nil {
89+
w.metrics.KnownLogs.Set(float64(n))
90+
}
91+
92+
labels := prometheus.Labels{"endpoint": "add-checkpoint"}
93+
var addCheckpoint http.Handler = http.HandlerFunc(w.serveAddCheckpoint)
94+
addCheckpoint = promhttp.InstrumentHandlerCounter(w.metrics.ReqCount.MustCurryWith(labels), addCheckpoint)
95+
addCheckpoint = promhttp.InstrumentHandlerDuration(w.metrics.ReqDuration.MustCurryWith(labels), addCheckpoint)
96+
addCheckpoint = promhttp.InstrumentHandlerInFlight(w.metrics.ReqInFlight.With(labels), addCheckpoint)
97+
w.mux.Handle("POST /add-checkpoint", addCheckpoint)
8398
return w, nil
8499
}
85100

@@ -148,6 +163,14 @@ func (w *Witness) serveAddCheckpoint(rw http.ResponseWriter, r *http.Request) {
148163
fmt.Fprintf(rw, "%d\n", err.known)
149164
return
150165
}
166+
167+
// Metrics sidequest: ensure known logs counter is synced with reality.
168+
// (At the moment of writing, there's a dearth of more convenient time to notice this;
169+
// when this number changes, it's due to an external poke of the db.)
170+
if n, err := w.countKnownLogs(); err == nil {
171+
w.metrics.KnownLogs.Set(float64(n))
172+
}
173+
151174
switch err {
152175
case errUnknownLog:
153176
http.Error(rw, err.Error(), http.StatusNotFound)
@@ -172,12 +195,15 @@ func (w *Witness) serveAddCheckpoint(rw http.ResponseWriter, r *http.Request) {
172195
}
173196

174197
func (w *Witness) processAddCheckpointRequest(body []byte, bastion string) (cosig []byte, err error) {
198+
labels := prometheus.Labels{"error": "", "origin": "", "progress": ""}
175199
l := w.log.With("request", string(body))
176200
defer func() {
177201
if err != nil {
178202
l = l.With("error", err)
203+
labels["error"] = err.Error()
179204
}
180205
l.Debug("processed add-checkpoint request")
206+
w.metrics.AddCheckpointCount.With(labels).Inc()
181207
}()
182208
body, noteBytes, ok := bytes.Cut(body, []byte("\n\n"))
183209
if !ok {
@@ -212,6 +238,7 @@ func (w *Witness) processAddCheckpointRequest(body []byte, bastion string) (cosi
212238
return nil, errBadRequest
213239
}
214240
l = l.With("origin", origin)
241+
labels["origin"] = origin
215242
bastions, err := w.getBastions(origin)
216243
if err != nil {
217244
return nil, err
@@ -237,6 +264,10 @@ func (w *Witness) processAddCheckpointRequest(body []byte, bastion string) (cosi
237264
return nil, err
238265
}
239266
l = l.With("size", c.N)
267+
labels["progress"] = "false"
268+
if c.N > oldSize {
269+
labels["progress"] = "true"
270+
}
240271
if err := w.checkConsistency(c.Origin, oldSize, c.N, c.Hash, proof); err != nil {
241272
return nil, err
242273
}
@@ -308,9 +339,22 @@ func (w *Witness) persistTreeHead(origin string, oldSize, newSize int64, newHash
308339
}
309340
return &conflictError{knownSize}
310341
}
342+
if err == nil {
343+
w.metrics.LogSize.WithLabelValues(origin).Set(float64(newSize))
344+
}
311345
return err
312346
}
313347

348+
func (w *Witness) countKnownLogs() (int, error) {
349+
var n int
350+
err := w.dbExec("SELECT COUNT(*) AS n FROM log",
351+
func(stmt *sqlite.Stmt) error {
352+
n = int(stmt.GetInt64("n"))
353+
return nil
354+
})
355+
return n, err
356+
}
357+
314358
func (w *Witness) getLog(origin string) (treeSize int64, treeHash tlog.Hash, err error) {
315359
found := false
316360
err = w.dbExec("SELECT tree_size, tree_hash FROM log WHERE origin = ?",

0 commit comments

Comments
 (0)