feat: add daemon cycle duration and skip metrics - #566
sarah-obasi-analytics wants to merge 1 commit into
Conversation
|
@sarah-obasi-analytics Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
|
Note Currently processing new changes in this PR. This may take a few minutes, please wait... ⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/daemon/loop.ts`:
- Around line 127-128: Measure daemon cycle duration from executeCycle() entry
through successful completion rather than using MonitorCycleResult timestamps;
update the duration observation in src/daemon/loop.ts lines 127-128 accordingly.
Update the timing assertion in tests/daemon/loop.test.ts lines 441-454 to verify
the full-cycle duration contract.
In `@src/observability/metrics/daemon.ts`:
- Around line 1-7: Replace the no-op collectors in daemonCycleDuration and
daemonCyclesSkipped with registered histogram and counter metrics, using the
names sorokeep_daemon_cycle_duration_seconds and
sorokeep_daemon_cycles_skipped_total. Preserve the existing observe and inc
interfaces while wiring both collectors into the project’s metrics registry so
their values appear in /metrics.
In `@tests/daemon/loop.test.ts`:
- Around line 441-454: Update the test around startDaemon and
mockRunMonitorCycle to cover the complete executeCycle() boundary, including
delivery, introspection, extensions, aggregation, and callback work rather than
only varying MonitorCycleResult timestamps. Arrange measurable delays or
controlled timestamps for each phase, then assert mockMetrics.observe receives
the total elapsed cycle duration in seconds.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 24c4d72c-e308-40ab-9bfe-56030ced86f7
📒 Files selected for processing (4)
src/daemon/loop.tssrc/observability/metrics/daemon.tssrc/observability/registry.tstests/daemon/loop.test.ts
📜 Review details
⚠️ CI failures not shown inline (2)
GitHub Actions: CI Pipeline / 0_build-and-test (22.x).txt: feat: add daemon cycle duration and skip metrics
Conclusion: failure
##[group]Run npm audit --audit-level=high
�[36;1mnpm audit --audit-level=high�[0m
shell: /usr/bin/bash -e {0}
##[endgroup]
# npm audit report
`@hono/node-server` <2.0.5
Severity: moderate
Node.js Adapter for Hono: Path traversal in `serve-static` on Windows via encoded backslash (`%5C`) - https://github.com/advisories/GHSA-frvp-7c67-39w9
fix available via `npm audit fix`
node_modules/@hono/node-server
`@modelcontextprotocol/sdk` 1.25.0 - 1.29.0
Depends on vulnerable versions of `@hono/node-server`
node_modules/@modelcontextprotocol/sdk
axios 1.0.0 - 1.17.0
Severity: high
Axios: Excessive recursion in formDataToJSON can cause denial of service - https://github.com/advisories/GHSA-42h9-826w-cgv3
Axios: Prototype pollution auth subfields can inject Basic auth - https://github.com/advisories/GHSA-xj6q-8x83-jv6g
Axios: Deep formToJSON Key Recursion Can Cause Denial of Service - https://github.com/advisories/GHSA-pmv8-rq9r-6j72
Axios: Fetch adapter `ReadableStream` uploads bypass `maxBodyLength` - https://github.com/advisories/GHSA-jqh4-m9w3-8hp9
Axios: Prototype pollution gadgets can alter axios request construction - https://github.com/advisories/GHSA-mmx7-hfxf-jppx
Axios: NO_PROXY bypass for 0.0.0.0 local addresses in axios - https://github.com/advisories/GHSA-f4gw-2p7v-4548
Axios Node HTTP adapter can use an inherited proxy after interceptor config cloning - https://github.com/advisories/GHSA-gcfj-64vw-6mp9
Axios form serializer maxDepth bypass via {} metatoken - https://github.com/advisories/GHSA-hcpx-6fm6-wx23
Axios: Nested axios option objects can consume polluted prototype values - https://github.com/advisories/GHSA-7q8q-rj6j-mhjq
Axios: HTTP/2 streamed uploads bypass `maxBodyLength` - https://github.com/advisories/GHSA-mwf2-3pr3-8698
fix available via `npm audit fix`
node_modules/axios
`@stellar/stellar-sdk` 15.0.1 - 16.0.1
Depends on vulnerable versions of axios
node_modules/@stellar/stellar-sdk
brace-expansion <=5.0.7
...
GitHub Actions: CI Pipeline / build-and-test (22.x): feat: add daemon cycle duration and skip metrics
Conclusion: failure
##[group]Run npm audit --audit-level=high
�[36;1mnpm audit --audit-level=high�[0m
shell: /usr/bin/bash -e {0}
##[endgroup]
# npm audit report
`@hono/node-server` <2.0.5
Severity: moderate
Node.js Adapter for Hono: Path traversal in `serve-static` on Windows via encoded backslash (`%5C`) - https://github.com/advisories/GHSA-frvp-7c67-39w9
fix available via `npm audit fix`
node_modules/@hono/node-server
`@modelcontextprotocol/sdk` 1.25.0 - 1.29.0
Depends on vulnerable versions of `@hono/node-server`
node_modules/@modelcontextprotocol/sdk
axios 1.0.0 - 1.17.0
Severity: high
Axios: Excessive recursion in formDataToJSON can cause denial of service - https://github.com/advisories/GHSA-42h9-826w-cgv3
Axios: Prototype pollution auth subfields can inject Basic auth - https://github.com/advisories/GHSA-xj6q-8x83-jv6g
Axios: Deep formToJSON Key Recursion Can Cause Denial of Service - https://github.com/advisories/GHSA-pmv8-rq9r-6j72
Axios: Fetch adapter `ReadableStream` uploads bypass `maxBodyLength` - https://github.com/advisories/GHSA-jqh4-m9w3-8hp9
Axios: Prototype pollution gadgets can alter axios request construction - https://github.com/advisories/GHSA-mmx7-hfxf-jppx
Axios: NO_PROXY bypass for 0.0.0.0 local addresses in axios - https://github.com/advisories/GHSA-f4gw-2p7v-4548
Axios Node HTTP adapter can use an inherited proxy after interceptor config cloning - https://github.com/advisories/GHSA-gcfj-64vw-6mp9
Axios form serializer maxDepth bypass via {} metatoken - https://github.com/advisories/GHSA-hcpx-6fm6-wx23
Axios: Nested axios option objects can consume polluted prototype values - https://github.com/advisories/GHSA-7q8q-rj6j-mhjq
Axios: HTTP/2 streamed uploads bypass `maxBodyLength` - https://github.com/advisories/GHSA-mwf2-3pr3-8698
fix available via `npm audit fix`
node_modules/axios
`@stellar/stellar-sdk` 15.0.1 - 16.0.1
Depends on vulnerable versions of axios
node_modules/@stellar/stellar-sdk
brace-expansion <=5.0.7
...
🔇 Additional comments (3)
src/observability/registry.ts (1)
1-5: LGTM!src/daemon/loop.ts (1)
10-10: LGTM!Also applies to: 204-204
tests/daemon/loop.test.ts (1)
33-45: LGTM!Also applies to: 100-101, 456-482
| const cycleDurationSec = (result.cycleFinishedAt.getTime() - result.cycleStartedAt.getTime()) / 1000; | ||
| daemonCycleDuration.observe(cycleDurationSec); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Use the full executeCycle() duration consistently.
The implementation and test currently measure only the monitor result’s timestamps, excluding later daemon-cycle work.
src/daemon/loop.ts#L127-L128: time fromexecuteCycle()entry through successful completion.tests/daemon/loop.test.ts#L441-L454: assert the full-cycle timing contract rather than only theMonitorCycleResulttimestamp difference.
📍 Affects 2 files
src/daemon/loop.ts#L127-L128(this comment)tests/daemon/loop.test.ts#L441-L454
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/daemon/loop.ts` around lines 127 - 128, Measure daemon cycle duration
from executeCycle() entry through successful completion rather than using
MonitorCycleResult timestamps; update the duration observation in
src/daemon/loop.ts lines 127-128 accordingly. Update the timing assertion in
tests/daemon/loop.test.ts lines 441-454 to verify the full-cycle duration
contract.
| export const daemonCycleDuration = { | ||
| observe: (value: number) => {}, | ||
| }; | ||
|
|
||
| export const daemonCyclesSkipped = { | ||
| inc: (value?: number) => {}, | ||
| }; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
Implement real registered metrics instead of no-op stubs.
Both observe and inc discard their inputs, so the daemon calls cannot produce sorokeep_daemon_cycle_duration_seconds or sorokeep_daemon_cycles_skipped_total in /metrics. Replace these with actual histogram/counter collectors using the required metric names and registry integration.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/observability/metrics/daemon.ts` around lines 1 - 7, Replace the no-op
collectors in daemonCycleDuration and daemonCyclesSkipped with registered
histogram and counter metrics, using the names
sorokeep_daemon_cycle_duration_seconds and sorokeep_daemon_cycles_skipped_total.
Preserve the existing observe and inc interfaces while wiring both collectors
into the project’s metrics registry so their values appear in /metrics.
| it("records the cycle duration histogram upon completion", async () => { | ||
| const start = new Date(1000); | ||
| const finish = new Date(3500); // 2.5s duration | ||
| mockRunMonitorCycle.mockResolvedValue(makeCycleResult({ | ||
| cycleStartedAt: start, | ||
| cycleFinishedAt: finish, | ||
| })); | ||
|
|
||
| await startDaemon(db, "testnet", { intervalMs: 5000 }); | ||
|
|
||
| // Should be called with the duration in seconds | ||
| expect(mockMetrics.observe).toHaveBeenCalledTimes(1); | ||
| expect(mockMetrics.observe).toHaveBeenCalledWith(2.5); | ||
| }); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Make the duration test cover the full cycle contract.
This test varies only MonitorCycleResult timestamps, so it passes even when delivery, introspection, extensions, aggregation, or callback work is excluded from the histogram. Assert that observation reflects the full executeCycle() boundary instead.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/daemon/loop.test.ts` around lines 441 - 454, Update the test around
startDaemon and mockRunMonitorCycle to cover the complete executeCycle()
boundary, including delivery, introspection, extensions, aggregation, and
callback work rather than only varying MonitorCycleResult timestamps. Arrange
measurable delays or controlled timestamps for each phase, then assert
mockMetrics.observe receives the total elapsed cycle duration in seconds.
|
| GitGuardian id | GitGuardian status | Secret | Commit | Filename | |
|---|---|---|---|---|---|
| - | - | Generic High Entropy Secret | ded54f4 | tests/commands/guard-cli-export-import.test.ts | View secret |
🛠 Guidelines to remediate hardcoded secrets
- Understand the implications of revoking this secret by investigating where it is used in your code.
- Replace and store your secret safely. Learn here the best practices.
- Revoke and rotate this secret.
- If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.
To avoid such incidents in the future consider
- following these best practices for managing and storing secrets including API keys and other credentials
- install secret detection on pre-commit to catch secret before it leaves your machine and ease remediation.
🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.
…#334) PR #566's registry.ts diff replaced the established prom-client Registry pattern (and would have deleted the budget/TTL/cost metrics already wired into it) with an unrelated namespace-object design, and its metrics/daemon.ts was a non-functional stub (observe/inc were both no-ops). Its daemon/loop.ts diff was based on a very old snapshot of the file and, beyond the instrumentation, would have reintroduced a real bug: resetting cycleInFlight inside stopDaemon() — exactly the re-entrance-guard race the current code deliberately avoids (see the "Do NOT reset cycleInFlight here" comment in stopDaemon) — plus it silently deleted the metrics-port wiring from #569, the feeSponsorSecret option, and the per-network poll-interval override. Implemented the same two metrics narrowly against the current loop.ts instead: - src/observability/metrics/daemon.ts: sorokeep_daemon_cycle_duration_seconds (Histogram) and sorokeep_daemon_cycles_skipped_total (Counter), both labeled by network to match the codebase's existing per-metric labeling convention (enforced by an existing regression test in budget.test.ts). - registry.ts: two registration lines, no changes to collectAllMetrics needed — these are event-driven (observed at the point of occurrence), not recomputed from the DB on scrape. - daemon/loop.ts: exactly the two instrumentation calls the issue asked for (histogram.observe() using the cycle's own cycleStartedAt/ cycleFinishedAt, and counter.inc() at the existing "Skipping tick" line) — no other changes to executeCycle, scheduledTick, or the re-entrance guard. - Added two tests to the existing (not replaced) loop.test.ts suite, using the real prom-client objects rather than mocks. Verified: tsc clean, full suite 1279/1279, npm audit clean, build succeeds. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Thanks for tackling this — the metrics themselves (cycle duration histogram, skip counter) are exactly what the issue asked for. Closing manually rather than merging: your Implemented the same two metrics narrowly on top of current Verified: tsc clean, full suite 1279/1279, npm audit clean, build succeeds, and tested via 2 new tests exercising the real (non-mocked) metric objects through the actual |
… the HTTP server (#540, #339) PR #540's registry.ts diff was, again, a complete standalone reimplementation (computeMetrics/formatPrometheus hand-rolling its own SQL queries and Prometheus serializer, bypassing prom-client and every already-registered metric entirely) — the exact same collision pattern as #543/#566/#591/#542. The issue's own step 1 ("extract the metric-computation logic... reusable by both the HTTP route and this command") was actually already satisfied by the real registry.ts — collectAllMetrics(db) + register.metrics() is exactly that shared logic, already used by the /metrics route. Wrote src/commands/metrics.ts fresh against the real registry: collectAllMetrics(db) then register.metrics() for the default Prometheus-text output, or register.getMetricsAsJSON() (prom-client's own structured format) for --json — no new serialization logic needed. Registered in src/cli/program.ts (the current entry point; src/index.ts is now just a thin wrapper around createProgram()). Added tests asserting the command's default output matches what collectAllMetrics + register.metrics() produce directly for the same DB state (the acceptance criteria's actual claim), that --json produces valid parseable JSON with the expected metric/label shape, and the empty-database case. Verified: tsc clean, full suite 1300/1300, npm audit clean, build succeeds, and manually smoke-tested both `sorokeep metrics` and `sorokeep metrics --json` against the compiled CLI. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…#334) PR #566's registry.ts diff replaced the established prom-client Registry pattern (and would have deleted the budget/TTL/cost metrics already wired into it) with an unrelated namespace-object design, and its metrics/daemon.ts was a non-functional stub (observe/inc were both no-ops). Its daemon/loop.ts diff was based on a very old snapshot of the file and, beyond the instrumentation, would have reintroduced a real bug: resetting cycleInFlight inside stopDaemon() — exactly the re-entrance-guard race the current code deliberately avoids (see the "Do NOT reset cycleInFlight here" comment in stopDaemon) — plus it silently deleted the metrics-port wiring from #569, the feeSponsorSecret option, and the per-network poll-interval override. Implemented the same two metrics narrowly against the current loop.ts instead: - src/observability/metrics/daemon.ts: sorokeep_daemon_cycle_duration_seconds (Histogram) and sorokeep_daemon_cycles_skipped_total (Counter), both labeled by network to match the codebase's existing per-metric labeling convention (enforced by an existing regression test in budget.test.ts). - registry.ts: two registration lines, no changes to collectAllMetrics needed — these are event-driven (observed at the point of occurrence), not recomputed from the DB on scrape. - daemon/loop.ts: exactly the two instrumentation calls the issue asked for (histogram.observe() using the cycle's own cycleStartedAt/ cycleFinishedAt, and counter.inc() at the existing "Skipping tick" line) — no other changes to executeCycle, scheduledTick, or the re-entrance guard. - Added two tests to the existing (not replaced) loop.test.ts suite, using the real prom-client objects rather than mocks. Verified: tsc clean, full suite 1279/1279, npm audit clean, build succeeds.
… the HTTP server (#540, #339) PR #540's registry.ts diff was, again, a complete standalone reimplementation (computeMetrics/formatPrometheus hand-rolling its own SQL queries and Prometheus serializer, bypassing prom-client and every already-registered metric entirely) — the exact same collision pattern as #543/#566/#591/#542. The issue's own step 1 ("extract the metric-computation logic... reusable by both the HTTP route and this command") was actually already satisfied by the real registry.ts — collectAllMetrics(db) + register.metrics() is exactly that shared logic, already used by the /metrics route. Wrote src/commands/metrics.ts fresh against the real registry: collectAllMetrics(db) then register.metrics() for the default Prometheus-text output, or register.getMetricsAsJSON() (prom-client's own structured format) for --json — no new serialization logic needed. Registered in src/cli/program.ts (the current entry point; src/index.ts is now just a thin wrapper around createProgram()). Added tests asserting the command's default output matches what collectAllMetrics + register.metrics() produce directly for the same DB state (the acceptance criteria's actual claim), that --json produces valid parseable JSON with the expected metric/label shape, and the empty-database case. Verified: tsc clean, full suite 1300/1300, npm audit clean, build succeeds, and manually smoke-tested both `sorokeep metrics` and `sorokeep metrics --json` against the compiled CLI.
…le phases (#572, #341) PR #572's daemon/loop.ts and core/monitor.ts diffs were based on a stale snapshot of both files, predating everything merged into them this wave — taking them as-is would have deleted the metrics-port wiring (#569), daemonCycleDuration/daemonCyclesSkipped instrumentation (#566), the /readyz RPC client construction (#542), and — critically — reintroduced a real bug: resetting cycleInFlight inside stopDaemon() in loop.ts, the exact re-entrance-guard race the current code deliberately avoids. In monitor.ts, it would have deleted the fan-out delivery feature (#541) and the per-config enabled check (#580). Its package.json diff also downgraded @stellar/stellar-sdk and dropped hono/nodemailer entirely. Ported the actual tracing work — a self-contained tracing.ts module (tracer provider setup, OTLP/in-memory exporter config via env vars, defensive span-error/end helpers) needed no changes and was taken as written, since the issue's own scope kept it isolated from registry.ts/metrics. Manually re-applied the span instrumentation against the current, unmodified control flow of both files: - daemon/loop.ts: a DaemonCycle parent span wrapping executeCycle, with Monitor/Deliver/CostAggregation child spans (renamed from the issue's "Auto-Extend" — the third phase actually wraps aggregateDailyCostSnapshots; real auto-extension happens inside runMonitorCycle's own "Monitor" phase, so labeling it Auto-Extend would have been actively misleading). No changes to cycleInFlight, stopDaemon, or scheduledTick's skip logic — span calls only. - core/monitor.ts: a process-contract span per contract inside the existing loop, tagged with contract.id. No changes to processContract itself (fan-out, enabled check untouched). - Fixed a real gap in the ported code: initTracing() was never called before getTracer() in the original diff, meaning OTLP/in-memory exporter configuration would never actually take effect in production — getTracer()'s lazy fallback would silently lock in an uninstrumented provider on first use. Added the missing call. - Added tracing tests to the existing loop.test.ts and monitor.test.ts suites (not a separate stale-mocked file) verifying the acceptance criteria directly: a parent span with Monitor/Deliver/CostAggregation children, error status recorded on a failed Monitor span, no measurable behavior change when tracing is off, and one process-contract span per contract. Verified: tsc clean, full suite 1304/1304, npm audit clean, build succeeds, and manually smoke-tested real span creation/parent-child linking/in-memory export end-to-end against the compiled module.
What does this PR do?
closes #334
Why?
Does this touch secret-key handling or transaction submission?
Checklist
npm test)npx tsc --noEmit)npm run lint)console.login core logic