Rate limiting observability (metrics and tracing) PR B - #5800
Conversation
There was a problem hiding this comment.
Large PR Detected
This PR exceeds 1000 lines of changes and requires justification before it can be reviewed.
How to unblock this PR:
Add a section to your PR description with the following format:
## Large PR Justification
[Explain why this PR must be large, such as:]
- Generated code that cannot be split
- Large refactoring that must be atomic
- Multiple related changes that would break if separated
- Migration or data transformationAlternative:
Consider splitting this PR into smaller, focused changes (< 1000 lines each) for easier review and reduced risk.
See our Contributing Guidelines for more details.
This review will be automatically dismissed once you add the justification section.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #5800 +/- ##
==========================================
+ Coverage 72.69% 72.71% +0.01%
==========================================
Files 742 742
Lines 77239 77265 +26
==========================================
+ Hits 56152 56181 +29
+ Misses 17126 17124 -2
+ Partials 3961 3960 -1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
Most of this diff is generated swagger docs, not your rate-limiting code. Likely cause: Can you rerun |
103b54a to
6d5de2e
Compare
6d5de2e to
9ff840f
Compare
9ff840f to
e618f4b
Compare
Thanks, Jeremy. I reran task docs and reproduced the large Swagger-only diff locally. I also tested the pinned swag version from #5802, but my local run still rewrote generated schema names without any API change. |
aponcedeleonch
left a comment
There was a problem hiding this comment.
the change looks good but it needs a rebase 😄
|
Heads-up on an interaction with #6136 (fixes #6133), since we'd otherwise discover it after one of us merges. The overlap. #6136 makes the mutating webhook republish the parsed MCP request, because This PR moves telemetry to immediately after the parser. That puts telemetry outside the mutation, and republishing can't reach it there: a derived context only flows downstream, and Net effect once both land in this order: span names, No textual conflict. #6136 doesn't touch One option you may not have considered. The requirement here is a live span before the limiter runs, which you can get by moving the limiter later instead of telemetry earlier: The limiter's only stated constraint is the one in its own comment at But I'm not recommending it outright, because it has a real cost: rate-limited requests would then pay the webhook round-trips before being rejected. For a quota control that's backwards — outbound HTTP per rejected request is amplification on exactly the traffic you're shedding. If webhooks aren't configured it's free, but that's not something to rely on. So the alternative worth weighing is splitting span creation (early, so the limiter has something to annotate) from MCP attribute population (late, post-mutation) — If this merges first, #6136 will reconcile — I'd update the ordering list and the limitations bullet in If #6136 merges first (it's small, green, and fixes a live mis-authorization, whereas this one has changes requested), this PR would need those same four spots updated, plus a decision on the telemetry-accuracy question above. No action needed from you on #6136 itself — flagging so the second one through isn't a surprise. |
|
@jerm-dro @jhrozek @aponcedeleonch Apologies for the delayed followup. I was away for a while due to a family medical emergency. I am back now and will review the feedback in the comment and continue working on the PR soon. |
|
@Sanskarzz no need to apologize. I hope all is well, and there's no rush here. We are thankful for your contributions. |
e618f4b to
bb3b764
Compare
|
@jhrozek Thanks for flagging the interaction with #6136. I rebased onto the current main and followed the split approach you outlined. |
Signed-off-by: Sanskarzz <sanskar.gur@gmail.com>
bb3b764 to
8dd3ac8
Compare
Summary
individual request can be correlated with the limiter decision.
rate_limit.rejected_byvalues from the limiter's privatescope and operation dimensions without changing public rate-limit types.
limiter can annotate an active request span.
processing, using the parsed request republished by mutating webhooks. This
attributes allowed requests to the tool and arguments that actually execute.
traffic and the existing vMCP core decorator composition.
Part of #4553
Type of change
Test plan
Unit tests (focused affected-package tests and full race run; details below)
E2E tests (
task test-e2e)Linting (
task lint-fix)Manual testing (describe below)
Focused tests passed for
pkg/telemetry,pkg/runner,pkg/mcp,pkg/webhook/mutating, andpkg/vmcp/server.task lint-fixpassed with zero issues.git diff --checkpassed.task testran the repository race suite and reported one unrelated localenvironment failure in
pkg/client/TestDetectProjectRoot: the local/tmpcontains a synthetic
.gitdirectory. The exact test passed when rerun withTMPDIR=/var/tmp.Changes
pkg/ratelimit/limiter.gopkg/ratelimit/observability.gopkg/ratelimit/observability_test.gopkg/ratelimit/middleware_test.gopkg/runner/middleware.gopkg/runner/middleware_test.gopkg/telemetry/middleware.gopkg/telemetry/integration_test.gopkg/mcp/parser.gopkg/webhook/mutating/middleware.gopkg/vmcp/ratelimit/decorator_test.gopkg/vmcp/server/server.godocs/observability.mddocs/middleware.mdDoes this introduce a user-facing change?
Yes. Traces for Redis-backed rate-limited tool calls now expose whether the
request was allowed or rejected and which bounded bucket type rejected it.
For allowed requests rewritten by a mutating webhook, completed telemetry uses
the MCP method, tool, and arguments that proceed to execution.
Rate-limit enforcement, responses, Redis keys, retry timing, and fail-open
behavior are unchanged. Rate limiting intentionally continues to evaluate the
request as received because it runs before outbound webhook calls.
Implementation plan
Approved implementation plan
decision branches; do not create a dedicated rate-limit span.
rate_limit.rejected_byfrom private scope and operation dimensionswithout expanding
Limiter,Decision, orRateLimitedError.webhook, then validating webhook. This creates the span before limiting and
rejects excess traffic before webhook round trips.
ParsedRequestHolderand finalize MCP span names,attributes, and metrics after the inner chain returns, so republished
mutations are reflected in completed telemetry.
telemetry with OpenTelemetry SDK recorders, then update the observability
and middleware documentation.
Special notes for reviewers
Redis failures intentionally receive no normal-outcome attributes in this PR:
the limiter cannot know whether its caller applies fail-open behavior. A
follow-up PR will add the fail-open counter and set
rate_limit.fail_open=trueat the shared enforcement adapter.This PR incorporates the interaction identified after #6136. Telemetry remains
outside rate limiting and webhooks, but it reads the final parsed request from
the shared holder after downstream processing. Therefore, completed spans and
MCP metrics identify the request the backend executes, while rate limiting
continues to debit the request-as-received bucket and avoids webhook calls for
rejected traffic.