Skip to content

feat(observability): add extension cost and count Prometheus counters - #543

Closed
faruke24 wants to merge 1 commit into
TegoLabs:mainfrom
faruke24:feat/extension-cost-prometheus-counters
Closed

faruke24 wants to merge 1 commit into
TegoLabs:mainfrom
faruke24:feat/extension-cost-prometheus-counters

Conversation

@faruke24

Copy link
Copy Markdown

Add sorokeep_extension_cost_xlm_total and sorokeep_extensions_total counters labeled by contract_id and entry_type.

  • src/observability/metrics/cost.ts: collectCostMetrics(db) queries extension_history joined with contract_entries, summing cost_xlm with COALESCE(..., 0) to handle NULLs. A zero-value baseline is emitted for every registered (contract_id, entry_type) pair so counters are never absent, satisfying the Prometheus counter contract.
  • src/observability/registry.ts: MetricsCollector registry with registerMetricsCollector / collectAllMetrics / _resetRegistryForTesting. collectCostMetrics is registered as the first built-in collector.
  • tests/observability/metrics/cost.test.ts: 16 tests written before implementation (TDD). Covers correct summation from 3 seeded rows, NULL cost handling, per-label isolation across entry types and contracts, and zero-value emission for contracts with no extensions.

What does this PR do?

Why?

Does this touch secret-key handling or transaction submission?

  • Yes — see notes above
  • No

Checklist

  • Tests pass (npm test)
  • Type check passes (npx tsc --noEmit)
  • Lint passes (npm run lint)
  • Tests cover the new functionality (TDD preferred — see CONTRIBUTING.md)
  • No unnecessary dependencies added
  • Commit messages follow conventional format
  • No console.log in core logic
  • ADR added if this is a significant design decision (see docs/adr)
  • E2E sandbox tested, if this touches RPC or daemon behavior (see docs/e2e-sandbox.md)

closes #332

Add sorokeep_extension_cost_xlm_total and sorokeep_extensions_total
counters labeled by contract_id and entry_type.

- src/observability/metrics/cost.ts: collectCostMetrics(db) queries
  extension_history joined with contract_entries, summing cost_xlm with
  COALESCE(..., 0) to handle NULLs. A zero-value baseline is emitted for
  every registered (contract_id, entry_type) pair so counters are never
  absent, satisfying the Prometheus counter contract.
- src/observability/registry.ts: MetricsCollector registry with
  registerMetricsCollector / collectAllMetrics / _resetRegistryForTesting.
  collectCostMetrics is registered as the first built-in collector.
- tests/observability/metrics/cost.test.ts: 16 tests written before
  implementation (TDD). Covers correct summation from 3 seeded rows,
  NULL cost handling, per-label isolation across entry types and contracts,
  and zero-value emission for contracts with no extensions.
@drips-wave

drips-wave Bot commented Jul 29, 2026

Copy link
Copy Markdown

@faruke24 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! 🚀

Learn more about application limits

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@faruke24, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 47 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: c2fda804-c58a-44d7-81b2-63262d7239f2

📥 Commits

Reviewing files that changed from the base of the PR and between 35d9237 and 11906ce.

📒 Files selected for processing (3)
  • src/observability/metrics/cost.ts
  • src/observability/registry.ts
  • tests/observability/metrics/cost.test.ts

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gitguardian

gitguardian Bot commented Jul 29, 2026

Copy link
Copy Markdown

⚠️ GitGuardian has uncovered 1 secret following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

Since your pull request originates from a forked repository, GitGuardian is not able to associate the secrets uncovered with secret incidents on your GitGuardian dashboard.
Skipping this check run and merging your pull request will create secret incidents on your GitGuardian dashboard.

🔎 Detected hardcoded secret in your pull request
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
  1. Understand the implications of revoking this secret by investigating where it is used in your code.
  2. Replace and store your secret safely. Learn here the best practices.
  3. Revoke and rotate this secret.
  4. 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


🦉 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.

AbdulmalikAlayande added a commit that referenced this pull request Aug 1, 2026
…#543, #332)

PR #543's query/aggregation logic (zero-baseline per contract_entries
row, COALESCE for NULL cost_xlm) was solid, but its registry.ts diff
replaced the dedicated prom-client Registry + collectAllMetrics wiring
that #527 had just established with a completely different
architecture — a custom MetricSample[] type and a hand-rolled
collector-registry, bypassing prom-client entirely ("deliberately
avoids importing prom-client"). Taking that diff as-is would have
silently deleted the working budget and TTL gauges.

Reimplemented as prom-client Counters instead, keeping the same query
logic and zero-baseline behavior, integrated into the existing
registry.ts pattern:

- src/observability/metrics/cost.ts: sorokeep_extension_cost_xlm_total
  and sorokeep_extensions_total as prom-client Counters, recomputed via
  reset() + inc(labels, value) each scrape (mirrors budget.ts/ttl.ts).
- registry.ts: two registration lines + added to collectAllMetrics so
  both counters get live data on every /metrics scrape automatically,
  same as the other two metrics.
- Rewrote the test suite to assert against the counters' own .get()
  and against register.metrics() exposition output (the original tests
  only checked a custom sample-array shape that's no longer produced),
  preserving every original test scenario.

Verified: tsc clean, full suite 1277/1277, npm audit clean, build
succeeds, and manually smoke-tested end-to-end against an isolated
temp database — confirmed both counters serve correct live values
(0.25 XLM / 1 extension for a seeded record) through the real HTTP
endpoint.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@AbdulmalikAlayande

Copy link
Copy Markdown
Collaborator

Thanks — the query logic here (zero-baseline per contract_entries row, COALESCE for NULL cost_xlm, mirroring aggregateDailyCostSnapshots) was correct and well-tested.

Closing manually rather than merging via GitHub: your registry.ts diff replaced the dedicated prom-client Registry + collectAllMetrics wiring that #527 (TTL gauge, merged just before this) had established, with a different architecture — a custom MetricSample[] type and a hand-rolled collector registry that deliberately avoids prom-client. Taking that as-is would have silently deleted the already-working budget and TTL gauges.

Reimplemented as prom-client Counters instead (commit 5f7683b on main), keeping your query logic and zero-baseline behavior exactly, just wired into the existing pattern (reset() + inc(labels, value) per scrape, same as the other two metrics). Rewrote the tests to assert against the counters' own state and register.metrics() output instead of the custom sample-array shape, since that's what's actually reachable through the real /metrics endpoint — kept every original test scenario.

Verified: tsc clean, full suite 1277/1277, npm audit clean, build succeeds, manually smoke-tested end-to-end. Thanks for the solid aggregation logic — good work overall.

AbdulmalikAlayande added a commit that referenced this pull request Aug 2, 2026
… 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>
AbdulmalikAlayande added a commit that referenced this pull request Aug 2, 2026
…#543, #332)

PR #543's query/aggregation logic (zero-baseline per contract_entries
row, COALESCE for NULL cost_xlm) was solid, but its registry.ts diff
replaced the dedicated prom-client Registry + collectAllMetrics wiring
that #527 had just established with a completely different
architecture — a custom MetricSample[] type and a hand-rolled
collector-registry, bypassing prom-client entirely ("deliberately
avoids importing prom-client"). Taking that diff as-is would have
silently deleted the working budget and TTL gauges.

Reimplemented as prom-client Counters instead, keeping the same query
logic and zero-baseline behavior, integrated into the existing
registry.ts pattern:

- src/observability/metrics/cost.ts: sorokeep_extension_cost_xlm_total
  and sorokeep_extensions_total as prom-client Counters, recomputed via
  reset() + inc(labels, value) each scrape (mirrors budget.ts/ttl.ts).
- registry.ts: two registration lines + added to collectAllMetrics so
  both counters get live data on every /metrics scrape automatically,
  same as the other two metrics.
- Rewrote the test suite to assert against the counters' own .get()
  and against register.metrics() exposition output (the original tests
  only checked a custom sample-array shape that's no longer produced),
  preserving every original test scenario.

Verified: tsc clean, full suite 1277/1277, npm audit clean, build
succeeds, and manually smoke-tested end-to-end against an isolated
temp database — confirmed both counters serve correct live values
(0.25 XLM / 1 extension for a seeded record) through the real HTTP
endpoint.
AbdulmalikAlayande added a commit that referenced this pull request Aug 2, 2026
… 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.
@faruke24
faruke24 deleted the feat/extension-cost-prometheus-counters branch September 4, 2026 23:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(observability): expose extension-cost counter metrics

2 participants