From 1c76b054e454e9fc297fe2a4322134197a6f6c09 Mon Sep 17 00:00:00 2001 From: TheHuman00 <35613095+TheHuman00@users.noreply.github.com> Date: Sun, 24 May 2026 19:00:00 +0200 Subject: [PATCH 1/3] feat(server): add -stats-interval flag --- README.md | 1 + cmd/roughtime/bootstrap_test.go | 12 ++++++++++++ cmd/roughtime/main.go | 7 ++++++- cmd/roughtime/main_test.go | 2 ++ cmd/roughtime/stats.go | 7 ++----- cmd/roughtime/stats_test.go | 2 +- 6 files changed, 24 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index f6ffa8d..b63be70 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,7 @@ roughtime -root-key-file /path/to/root.key -pq-root-key-file /path/to/pq-root.ke | `-grease-rate` | 0.01 | Fraction of responses to grease (0 disables) | | `-log-level` | info | `debug`, `info`, `warn`, or `error` | | `-metrics-addr` | | `host:port` for Prometheus `/metrics` (no auth)| +| `-stats-interval` | 60s | Cadence of the periodic stats log (e.g. `10s`, `5m`) | ### Architecture diff --git a/cmd/roughtime/bootstrap_test.go b/cmd/roughtime/bootstrap_test.go index 47794e5..8099697 100644 --- a/cmd/roughtime/bootstrap_test.go +++ b/cmd/roughtime/bootstrap_test.go @@ -145,6 +145,18 @@ func TestValidateFlagsRejects(t *testing.T) { *greaseRate = 1.5 t.Cleanup(func() { *greaseRate = prev }) }, "-grease-rate"}, + {"stats interval zero", func(t *testing.T) { + setRootKeyPath(t, "/x") + prev := *statsIntervalFlag + *statsIntervalFlag = 0 + t.Cleanup(func() { *statsIntervalFlag = prev }) + }, "-stats-interval"}, + {"stats interval negative", func(t *testing.T) { + setRootKeyPath(t, "/x") + prev := *statsIntervalFlag + *statsIntervalFlag = -time.Second + t.Cleanup(func() { *statsIntervalFlag = prev }) + }, "-stats-interval"}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { diff --git a/cmd/roughtime/main.go b/cmd/roughtime/main.go index 654f2bd..e44d03c 100644 --- a/cmd/roughtime/main.go +++ b/cmd/roughtime/main.go @@ -69,6 +69,8 @@ var ( // metricsAddr is the host:port for the optional Prometheus /metrics // endpoint; empty disables the listener entirely. metricsAddr = flag.String("metrics-addr", "", "address (host:port) for the Prometheus /metrics endpoint; empty disables. No auth — use 127.0.0.1:PORT to restrict to loopback") + // statsIntervalFlag is the cadence of the periodic stats log. + statsIntervalFlag = flag.Duration("stats-interval", 60*time.Second, "cadence of the periodic stats log (e.g. 10s, 5m); must be positive") ) // Server-wide tunable constants. @@ -108,6 +110,9 @@ func validateFlags() error { return fmt.Errorf("-metrics-addr %q invalid (want host:port): %w", *metricsAddr, err) } } + if *statsIntervalFlag <= 0 { + return fmt.Errorf("-stats-interval %v must be positive", *statsIntervalFlag) + } return nil } @@ -194,7 +199,7 @@ func serve(ctx context.Context) error { enc.AddDuration("cert_end_offset", certEndOffset) enc.AddDuration("cert_refresh_threshold", certRefreshThreshold) enc.AddDuration("cert_check_interval", certCheckInterval) - enc.AddDuration("stats_interval", statsInterval) + enc.AddDuration("stats_interval", *statsIntervalFlag) return nil })), ) diff --git a/cmd/roughtime/main_test.go b/cmd/roughtime/main_test.go index cbf761a..5f8f0f1 100644 --- a/cmd/roughtime/main_test.go +++ b/cmd/roughtime/main_test.go @@ -34,6 +34,7 @@ func withFlagGlobals(t *testing.T, edKey, pqKey, level string, p int, grease flo origPQKeygen := *pqKeygen origPQPubkey := *pqPubkey origMetrics := *metricsAddr + origStatsInterval := *statsIntervalFlag t.Cleanup(func() { *port = origPort *rootKeySeedHexFile = origEd @@ -46,6 +47,7 @@ func withFlagGlobals(t *testing.T, edKey, pqKey, level string, p int, grease flo *pqKeygen = origPQKeygen *pqPubkey = origPQPubkey *metricsAddr = origMetrics + *statsIntervalFlag = origStatsInterval }) *port = p *rootKeySeedHexFile = edKey diff --git a/cmd/roughtime/stats.go b/cmd/roughtime/stats.go index c76d4b5..491b096 100644 --- a/cmd/roughtime/stats.go +++ b/cmd/roughtime/stats.go @@ -13,9 +13,6 @@ import ( "go.uber.org/zap" ) -// statsInterval is the cadence of the periodic stats log. -var statsInterval = 60 * time.Second - // Server-wide un-labeled counters; labeled request/response/drop counters live // in metrics.go. var ( @@ -33,9 +30,9 @@ var ( // statsLoop emits a periodic summary of server activity until ctx is cancelled. func statsLoop(ctx context.Context, log *zap.Logger, edState, pqState *atomic.Pointer[certState]) { - ticker := time.NewTicker(statsInterval) + ticker := time.NewTicker(*statsIntervalFlag) defer ticker.Stop() - log.Info("stats loop started", zap.Duration("interval", statsInterval)) + log.Info("stats loop started", zap.Duration("interval", *statsIntervalFlag)) var lastReceived, lastResponded, lastDropped, lastPanics, lastBatchCount, lastBatchTotal, lastBatchErrs uint64 var lastTCPAccepted, lastTCPRejected, lastTCPCompleted, lastAmp uint64 diff --git a/cmd/roughtime/stats_test.go b/cmd/roughtime/stats_test.go index 9a2c93d..92760b6 100644 --- a/cmd/roughtime/stats_test.go +++ b/cmd/roughtime/stats_test.go @@ -39,7 +39,7 @@ func TestStatsLoopExitsOnCtxCancel(t *testing.T) { // TestStatsLoopTicks verifies statsLoop emits at least one log line on its tick // interval. func TestStatsLoopTicks(t *testing.T) { - withInterval(t, &statsInterval, 5*time.Millisecond) + withInterval(t, statsIntervalFlag, 5*time.Millisecond) _, st := newUnitCertState(t) statePtr := &atomic.Pointer[certState]{} statePtr.Store(st) From a4742f2b939ee3840391b95f785b46a2d3aad97a Mon Sep 17 00:00:00 2001 From: TheHuman00 <35613095+TheHuman00@users.noreply.github.com> Date: Sun, 24 May 2026 19:23:36 +0200 Subject: [PATCH 2/3] refactor(stats): read statsIntervalFlag once into local variable --- cmd/roughtime/stats.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cmd/roughtime/stats.go b/cmd/roughtime/stats.go index 491b096..9430329 100644 --- a/cmd/roughtime/stats.go +++ b/cmd/roughtime/stats.go @@ -30,9 +30,10 @@ var ( // statsLoop emits a periodic summary of server activity until ctx is cancelled. func statsLoop(ctx context.Context, log *zap.Logger, edState, pqState *atomic.Pointer[certState]) { - ticker := time.NewTicker(*statsIntervalFlag) + interval := *statsIntervalFlag + ticker := time.NewTicker(interval) defer ticker.Stop() - log.Info("stats loop started", zap.Duration("interval", *statsIntervalFlag)) + log.Info("stats loop started", zap.Duration("interval", interval)) var lastReceived, lastResponded, lastDropped, lastPanics, lastBatchCount, lastBatchTotal, lastBatchErrs uint64 var lastTCPAccepted, lastTCPRejected, lastTCPCompleted, lastAmp uint64 From e3e2d29df3287125a5f7d4bc6dc84b19f6c45379 Mon Sep 17 00:00:00 2001 From: Tanner Ryan Date: Mon, 25 May 2026 09:03:16 -0500 Subject: [PATCH 3/3] feat(server): require -stats-interval >= 1s, rename flag var --- README.md | 2 +- cmd/roughtime/bootstrap_test.go | 18 ++++++++++++------ cmd/roughtime/main.go | 10 +++++----- cmd/roughtime/main_test.go | 4 ++-- cmd/roughtime/stats.go | 2 +- cmd/roughtime/stats_test.go | 2 +- 6 files changed, 22 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index b63be70..2fad713 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ roughtime -root-key-file /path/to/root.key -pq-root-key-file /path/to/pq-root.ke | `-grease-rate` | 0.01 | Fraction of responses to grease (0 disables) | | `-log-level` | info | `debug`, `info`, `warn`, or `error` | | `-metrics-addr` | | `host:port` for Prometheus `/metrics` (no auth)| -| `-stats-interval` | 60s | Cadence of the periodic stats log (e.g. `10s`, `5m`) | +| `-stats-interval` | 60s | Cadence of the periodic stats log (e.g. `10s`, `5m`); minimum 1s | ### Architecture diff --git a/cmd/roughtime/bootstrap_test.go b/cmd/roughtime/bootstrap_test.go index 8099697..9ec578a 100644 --- a/cmd/roughtime/bootstrap_test.go +++ b/cmd/roughtime/bootstrap_test.go @@ -147,15 +147,21 @@ func TestValidateFlagsRejects(t *testing.T) { }, "-grease-rate"}, {"stats interval zero", func(t *testing.T) { setRootKeyPath(t, "/x") - prev := *statsIntervalFlag - *statsIntervalFlag = 0 - t.Cleanup(func() { *statsIntervalFlag = prev }) + prev := *statsInterval + *statsInterval = 0 + t.Cleanup(func() { *statsInterval = prev }) }, "-stats-interval"}, {"stats interval negative", func(t *testing.T) { setRootKeyPath(t, "/x") - prev := *statsIntervalFlag - *statsIntervalFlag = -time.Second - t.Cleanup(func() { *statsIntervalFlag = prev }) + prev := *statsInterval + *statsInterval = -time.Second + t.Cleanup(func() { *statsInterval = prev }) + }, "-stats-interval"}, + {"stats interval below floor", func(t *testing.T) { + setRootKeyPath(t, "/x") + prev := *statsInterval + *statsInterval = 500 * time.Millisecond + t.Cleanup(func() { *statsInterval = prev }) }, "-stats-interval"}, } for _, tc := range cases { diff --git a/cmd/roughtime/main.go b/cmd/roughtime/main.go index e44d03c..41f1205 100644 --- a/cmd/roughtime/main.go +++ b/cmd/roughtime/main.go @@ -69,8 +69,8 @@ var ( // metricsAddr is the host:port for the optional Prometheus /metrics // endpoint; empty disables the listener entirely. metricsAddr = flag.String("metrics-addr", "", "address (host:port) for the Prometheus /metrics endpoint; empty disables. No auth — use 127.0.0.1:PORT to restrict to loopback") - // statsIntervalFlag is the cadence of the periodic stats log. - statsIntervalFlag = flag.Duration("stats-interval", 60*time.Second, "cadence of the periodic stats log (e.g. 10s, 5m); must be positive") + // statsInterval is the cadence of the periodic stats log. + statsInterval = flag.Duration("stats-interval", 60*time.Second, "cadence of the periodic stats log (e.g. 10s, 5m); minimum 1s") ) // Server-wide tunable constants. @@ -110,8 +110,8 @@ func validateFlags() error { return fmt.Errorf("-metrics-addr %q invalid (want host:port): %w", *metricsAddr, err) } } - if *statsIntervalFlag <= 0 { - return fmt.Errorf("-stats-interval %v must be positive", *statsIntervalFlag) + if *statsInterval < time.Second { + return fmt.Errorf("-stats-interval %v must be at least 1s", *statsInterval) } return nil } @@ -199,7 +199,7 @@ func serve(ctx context.Context) error { enc.AddDuration("cert_end_offset", certEndOffset) enc.AddDuration("cert_refresh_threshold", certRefreshThreshold) enc.AddDuration("cert_check_interval", certCheckInterval) - enc.AddDuration("stats_interval", *statsIntervalFlag) + enc.AddDuration("stats_interval", *statsInterval) return nil })), ) diff --git a/cmd/roughtime/main_test.go b/cmd/roughtime/main_test.go index 5f8f0f1..24febe1 100644 --- a/cmd/roughtime/main_test.go +++ b/cmd/roughtime/main_test.go @@ -34,7 +34,7 @@ func withFlagGlobals(t *testing.T, edKey, pqKey, level string, p int, grease flo origPQKeygen := *pqKeygen origPQPubkey := *pqPubkey origMetrics := *metricsAddr - origStatsInterval := *statsIntervalFlag + origStatsInterval := *statsInterval t.Cleanup(func() { *port = origPort *rootKeySeedHexFile = origEd @@ -47,7 +47,7 @@ func withFlagGlobals(t *testing.T, edKey, pqKey, level string, p int, grease flo *pqKeygen = origPQKeygen *pqPubkey = origPQPubkey *metricsAddr = origMetrics - *statsIntervalFlag = origStatsInterval + *statsInterval = origStatsInterval }) *port = p *rootKeySeedHexFile = edKey diff --git a/cmd/roughtime/stats.go b/cmd/roughtime/stats.go index 9430329..17316a9 100644 --- a/cmd/roughtime/stats.go +++ b/cmd/roughtime/stats.go @@ -30,7 +30,7 @@ var ( // statsLoop emits a periodic summary of server activity until ctx is cancelled. func statsLoop(ctx context.Context, log *zap.Logger, edState, pqState *atomic.Pointer[certState]) { - interval := *statsIntervalFlag + interval := *statsInterval ticker := time.NewTicker(interval) defer ticker.Stop() log.Info("stats loop started", zap.Duration("interval", interval)) diff --git a/cmd/roughtime/stats_test.go b/cmd/roughtime/stats_test.go index 92760b6..05a6868 100644 --- a/cmd/roughtime/stats_test.go +++ b/cmd/roughtime/stats_test.go @@ -39,7 +39,7 @@ func TestStatsLoopExitsOnCtxCancel(t *testing.T) { // TestStatsLoopTicks verifies statsLoop emits at least one log line on its tick // interval. func TestStatsLoopTicks(t *testing.T) { - withInterval(t, statsIntervalFlag, 5*time.Millisecond) + withInterval(t, statsInterval, 5*time.Millisecond) _, st := newUnitCertState(t) statePtr := &atomic.Pointer[certState]{} statePtr.Store(st)