Skip to content

feat: fs_slow_request diagnostic log for slow list operations#747

Open
qiffang wants to merge 2 commits into
mainfrom
feat/fs-slow-request-log
Open

feat: fs_slow_request diagnostic log for slow list operations#747
qiffang wants to merge 2 commits into
mainfrom
feat/fs-slow-request-log

Conversation

@qiffang

@qiffang qiffang commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add production-visible fs_slow_request log when handleList handler takes ≥500ms
  • Named constant slowFSRequestThreshold (500ms), follows existing slowWriteBodyReadThreshold pattern
  • Fields: op, path, entries, status, total_ms, read_dir_ms, tenant_id, api_key_id
  • Uses logger.Info (not InfoBenchTiming), so it fires in production without bench flag
  • Normal fast lists produce zero additional logs

Motivation

A production GET /v1/fs/.../upload?list took 1993ms for 11 entries. Existing timing logs (server_list_timing, tenant_auth_timing) are behind BenchTimingLogEnabled() and invisible in production. This adds a single slow-path log to diagnose where time is spent.

Known limitation

This PR only covers handler-internal time (ReadDir + serialization). Auth/middleware overhead is not included — if the 2s is in pool.Acquire() cold-open, this log won't capture it. A follow-up PR should inject auth start time via context to cover the full request lifecycle.

Test plan

  • TestListDirFastNoSlowLog: fast list produces no fs_slow_request log
  • TestLogSlowFSListThreshold: below threshold → no log; at threshold → log with correct fields; above threshold → log emitted
  • go build ./pkg/server/ clean
  • go vet ./pkg/server/ clean
  • CI tests pass

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Observability
    • Added conditional diagnostic logging for slow filesystem directory listing requests.
    • Logs include operation type, target path, entry count, HTTP status, and timing breakdown, along with relevant request identifiers.
  • Reliability
    • Added tests to confirm fast listings do not emit slow diagnostics.
    • Added threshold coverage to verify logs emit exactly at/above the configured slow-request timing, including slow error scenarios.

When handleList takes ≥500ms (handler-internal, excluding auth), emit
a production-visible fs_slow_request log with op, path, entries, status,
total_ms, read_dir_ms, tenant_id, and api_key_id.

This helps diagnose latency spikes like the 1993ms list observed in
production without enabling bench timing globally.

Known limitation: auth/middleware overhead is not included in this log —
that gap requires injecting auth start time via context (follow-up PR).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 061ae536-9a49-4f13-809b-24cb26bcfc28

📥 Commits

Reviewing files that changed from the base of the PR and between d8dd8e1 and 3cde92d.

📒 Files selected for processing (2)
  • pkg/server/server.go
  • pkg/server/server_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • pkg/server/server.go

📝 Walkthrough

Walkthrough

handleList now measures list request duration and emits structured fs_slow_request diagnostics at or above a 500ms threshold, including successful and error responses. Tests cover fast requests and threshold-boundary behavior.

Changes

Filesystem slow-request logging

Layer / File(s) Summary
List timing and diagnostic logging
pkg/server/server.go, pkg/server/server_test.go
handleList invokes logSlowFSList for successful and failed directory reads; the helper logs operation, path, status, timing, tenant, and API key fields at or above the threshold. Tests verify fast requests remain unlogged and threshold cases emit expected entries.

Estimated code review effort: 2 (Simple) | ~10 minutes

Sequence Diagram(s)

sequenceDiagram
  participant handleList
  participant ReadDirCtx
  participant logSlowFSList
  participant Logger
  handleList->>ReadDirCtx: Read directory entries
  handleList->>logSlowFSList: Pass path, count, status, and timing data
  logSlowFSList->>Logger: Emit fs_slow_request at threshold
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding fs_slow_request diagnostics for slow list operations.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/fs-slow-request-log

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.

@qiffang qiffang left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

adversary-2 R1 — GREEN at HEAD

Minimal, correct, well-tested.

  • logSlowFSList only fires at ≥500ms handler duration, zero noise on normal requests.
  • Named constant slowFSRequestThreshold — good, not a magic number.
  • Reuses existing serverDurationMs, requestMetricScope — no new abstractions.
  • Fields match what was agreed: op, path, entries, status, total_ms, read_dir_ms, tenant_id, api_key_id. No sensitive payload.
  • Tests cover below/at/above threshold + field assertions + integration test proving fast list produces no log.
  • Known gap (auth/middleware phase) explicitly documented in both code comment and PR description.

No issues found.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 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 `@pkg/server/server_test.go`:
- Around line 463-465: Update pkg/server/server_test.go lines 463-465 and
474-504 to use t.Errorf for assertion failures, including the fs_slow_request
absence, log presence, and field validations; retain t.Fatalf at line 482 for
the len(entries) != 1 guard because subsequent access depends on it.
- Around line 453-455: Update the request setup in the test around
http.NewRequest and http.DefaultClient.Do to check both returned errors and fail
the test immediately with the underlying error before accessing the response.
Only close resp.Body after the request succeeds and resp is confirmed non-nil.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 757813aa-bd43-4078-a38c-af6bfeab7fab

📥 Commits

Reviewing files that changed from the base of the PR and between 5f9bf66 and d8dd8e1.

📒 Files selected for processing (2)
  • pkg/server/server.go
  • pkg/server/server_test.go

Comment thread pkg/server/server_test.go Outdated
Comment thread pkg/server/server_test.go

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d8dd8e1833

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread pkg/server/server.go Outdated
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]any{"entries": out})

logSlowFSList(r.Context(), path, len(entries), time.Since(start), readDirDuration)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Log slow list failures before returning

Because this call is only reached on the successful path, any slow ReadDirCtx that eventually returns an error (for example a DB timeout/deadlock or a slow not-found lookup) exits above without the new production-visible fs_slow_request log, leaving only the bench-gated server_list_timing. Since the diagnostic is meant to catch slow list handlers and already includes a status field, consider recording the status and logging from a deferred path or each error return as well.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 3cde92d. logSlowFSList now takes a status int param, and handleList calls it on both error returns (404 not-found and 500 internal error) before returning — not just the success path. The real HTTP status is threaded through instead of hardcoding StatusOK, so a slow ReadDirCtx that eventually errors (DB timeout/deadlock, slow not-found) now emits fs_slow_request with the correct status. Added a regression assertion covering the 500 error-path status.

@qiffang

qiffang commented Jul 16, 2026

Copy link
Copy Markdown
Contributor Author

GREEN from adversary-1 on exact head d8dd8e18333ece260cf3b37c8c63aaf0c6417d9c.

No blockers found. The diff is scoped to pkg/server/server.go and pkg/server/server_test.go, adds one named-threshold fs_slow_request info log for slow successful handleList calls, and does not promote broad bench timing or add sensitive auth/file-entry payloads. Threshold semantics are explicit (total < threshold returns; at/above logs), and the PR documents the auth/middleware gap rather than claiming full-chain diagnosis.

Validation:

  • git diff --check origin/main...HEAD passed
  • go build ./pkg/server/ passed
  • go vet ./pkg/server/ passed
  • go test -c ./pkg/server passed
  • GitHub checks on d8dd8e1: ci, drive9-server-local e2e smoke, and CodeRabbit all successful

Local focused go test ./pkg/server -run 'TestListDirFastNoSlowLog|TestLogSlowFSListThreshold|TestListDir' -count=1 could not run on this machine because pkg/server TestMain starts testcontainers and this environment reports rootless Docker not found; CI covered the package tests.

@srstack srstack left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed (read-only diff — did not compile locally). Clean, focused, well-tested observability addition. Approving.

Logic is correct and follows the established pattern. logSlowFSList early-returns below slowFSRequestThreshold (500ms) and otherwise emits a production-visible logger.Info (not bench-gated), mirroring the existing slowWriteBodyReadThreshold/logSlowWriteBodyRead idiom. Fast lists stay silent.

Verified the wiring against the actual code (not just the diff):

  • start (handler entry, line 5) and readDirDuration (readDirStart/time.Since, lines 13-15) are both defined in handleList, so time.Since(start) (full handler span) and readDirDuration (ReadDir-only) are the right two durations.
  • requestMetricScope(ctx) exists in instrumentation.go with signature (tenantID, apiKeyID, provider); discarding provider here matches its 5 existing call sites.
  • serverDurationMs exists. status is hardcoded http.StatusOK, which is correct because the log fires only after the response is successfully encoded (error paths return earlier and are logged elsewhere).

Tests are solid: TestListDirFastNoSlowLog (end-to-end fast list → zero fs_slow_request), and TestLogSlowFSListThreshold pinning the 499→no-log / 500→log / 2s→log boundary plus asserting op/path/entries/status/total_ms/read_dir_ms fields. The < (strict) comparison means exactly-500ms logs, which matches the "at threshold → log" assertion.

One thing worth emphasizing (you already flagged it, not blocking): this covers only handler-internal time (ReadDir + serialization), not auth/middleware. That matters for the motivating case — a 1993ms list where the time may well be in pool.Acquire() cold-open. If so, this log will show a small total_ms and could read as "handler was fast" while the real latency is upstream and invisible here. The known-limitation note and the follow-up plan to inject auth start time via context are the right call; I'd prioritize that follow-up since cold-open is the likeliest culprit for the exact symptom this PR is chasing. As-is, this is still a strict improvement (it catches genuinely slow ReadDir/serialization, which the bench-gated logs couldn't surface in prod).

LGTM.

@srstack

srstack commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

Follow-up to my review: the cold-open blind spot I flagged is now covered by #748 (merged), so fs_slow_request and #748's timing log are complementary rather than leaving a gap.

What #748 does (now on main):

  • Adds a production-visible slow-open-pool timing primitive InfoOpenPoolTiming(ctx, msg, total, ...) that logs only when total >= threshold. It is gated by DRIVE9_OPEN_POOL_TIMING_LOG_ENABLED (default true) and DRIVE9_OPEN_POOL_TIMING_SLOW_MS (default 500, 0=all). Crucially it uses logger.Info — not the bench-gated InfoBenchTiming — so it surfaces in production without the bench flag.
  • Switches tenant_auth_timing, tenant_pool_acquire_timing, and tenant_pool_create_backend_timing from InfoBenchTiming to InfoOpenPoolTiming.
  • The tenant_auth_timing log now includes cold-open time. In auth.go, authStart is taken before pool.Acquire() (where a cold open happens) and totalDuration = time.Since(authStart) after it, so a slow request whose cost is in pool.Acquire() cold-open is captured there (when >= 500ms).
  • Also retunes DB-pool defaults to cut cold-open reconnect churn (user/schema idle conns 0 -> 2, longer lifetimes) with more aggressive idle reclamation (IdleTimeout 10m -> 5m, reap 5m -> 1-2m) to offset the higher resident-connection footprint.

How this pairs with #747:

Together they split the request lifecycle, so for a slow request you can now tell in production whether the time went to cold-open (tenant_auth_timing) or ReadDir/serialization (fs_slow_request). That resolves the "1993ms could be in pool.Acquire() and this log wouldn't see it" limitation I raised — no context-injection follow-up is strictly needed anymore, since #748 records the cold-open-inclusive span on the auth side. This PR remains a clean, complementary net-add. LGTM stands.

- server_test.go: check http.NewRequest/Do errors to prevent nil-pointer
  panic on resp.Body.Close() (coderabbit r3593155751)
- server_test.go: use t.Errorf for assertion failures, keep t.Fatalf only
  for the len(entries)!=1 guard that would otherwise panic (coderabbit
  r3593155768)
- server.go: log fs_slow_request on slow list error paths (not-found /
  internal error), threading the real HTTP status through logSlowFSList
  instead of hardcoding StatusOK (codex r3593156384)
- add regression assertion for the error-path status field

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

2 participants