Skip to content

Add offline store-and-forward buffer for unsent build metrics - #5

Open
joeldickson wants to merge 1 commit into
mainfrom
feature/offline-metrics-buffer
Open

Add offline store-and-forward buffer for unsent build metrics#5
joeldickson wants to merge 1 commit into
mainfrom
feature/offline-metrics-buffer

Conversation

@joeldickson

Copy link
Copy Markdown
Contributor

Closes #4.

Build metrics were lost whenever the endpoint was unreachable — offline, VPN down, DNS failure, endpoint outage — which are exactly the builds we'd still like to capture once connectivity returns. A failed POST now parks the payload on disk, and the next build that sends successfully flushes it.

What changed

MetricsPublisher now distinguishes three send outcomes instead of swallowing everything:

Outcome Meaning Action
SUCCEEDED 2xx response flush the buffer
UNREACHABLE IOException / timeout anywhere in the cause chain buffer the payload
REJECTED reached the endpoint, non-2xx (or a non-connectivity error) drop it — retrying won't help

Flushing drains oldest first and stops as soon as the endpoint goes unreachable again, so a flaky connection can't turn into a burst of doomed requests. A buffered payload refused by a reachable endpoint is deleted rather than retried forever.

UnsentMetricsBuffer is a plain class — no interface, no abstraction — over a directory: save/list/read/delete/prune. One file per payload, named by the payload id (sanitized so it can't escape the directory). It's capped at 200 payloads and seven days; entries beyond either cap are dropped oldest-first. Writes go through a temp file and rename. Every operation swallows its own errors.

Wiring: BuildMetricsService.Parameters gains a bufferDirectory, set by the plugin to <gradle user home>/kotlin-local-metrics/unsent-build-metrics.

Non-blocking

post() still returns immediately and close() still ignores the result — all buffer I/O and flushing happens on the existing daemon HTTP thread pool. post() now returns a CompletableFuture purely so tests can await a send instead of sleeping; nothing on the build's critical path waits on it.

Tests

15 new tests, all deterministic — no sleeps, no retries, no wall-clock dependence.

  • UnsentMetricsBufferTest (9): naming by id, overwrite, path-separator sanitization, oldest-first listing, delete, count cap via prune, age cap via prune, count cap enforced through save, and a missing directory. Cap tests pass a fixed nowMillis and set lastModified explicitly.
  • MetricsPublisherTest (6): buffers on an unreachable endpoint (a loopback port bound then released, so the connect refusal is immediate); keeps earlier entries buffered while still unreachable; flushes and deletes all buffered payloads once a send succeeds; neither buffers nor flushes when a reachable endpoint returns 500; sends fine with no buffer configured; and never throws on a malformed endpoint.

./gradlew build is green, including the existing configuration-cache functional test, which exercises the new service parameter.

Open questions from the issue

  • Inline flush on next success vs. background attempt at build start — went with flush-on-next-success, as the issue leaned. No extra network calls while still offline.
  • Cap defaults — 200 payloads and 7 days. Both are constructor parameters, so they're easy to change.
  • Anything sensitive persisted? — no. The buffered bytes are exactly the JSON already being POSTed: task timings, counts, and build context (branch, commit SHA, repo URL, hostname, OS, username). Nothing new is written that wasn't already leaving the machine.

🤖 Generated with Claude Code

Metrics were lost whenever the endpoint was unreachable — exactly the
offline and flaky-connection builds we still want to capture. A failed
POST now parks the payload under the Gradle user home and the next build
that sends successfully flushes it.

MetricsPublisher distinguishes three send outcomes: accepted, unreachable
(worth retrying), and refused by a reachable endpoint (retrying will not
help, so the payload is dropped). Flushing drains oldest first and stops
as soon as the endpoint goes unreachable again, so a flaky connection
cannot turn into a burst of doomed requests.

UnsentMetricsBuffer caps the store at 200 payloads and seven days, and
swallows every I/O error. All of it stays off the build's critical path:
post() still returns immediately, and the returned future exists only so
tests can await the send without sleeping.

Closes #4

Co-Authored-By: Claude Opus 5 (1M context) <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.

Add offline store-and-forward buffer for unsent build metrics

2 participants