feat: /metrics endpoint, cache eviction, and observability fixes #16
No reviewers
Labels
No labels
bug
documentation
duplicate
enhancement
good first issue
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: shekhar/windows-iso-downloader#16
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "feat/metrics"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
/metricsendpoint — auth-protected (METRICS_SECRET env var) JSON endpoint exposing per-endpoint request counts, cache hits, MS fetch counts, neg-cache hits, stale serves, hit rates, and live cache sizescleanupCaches()goroutine runs every 30min, evicts expired SKU/neg entries and link entries past ExpiresAt + 4h stale window — fixes unbounded map growthrand.Randreplaces global rand to avoid mutex contention at high QPSsfKeyseparated fromnegKeyin both handlerssfGroup.Do, so concurrent stale hits still produce only one Microsoft callSet up
Add
METRICS_SECRET=<strong-value>in Coolify before deploying, then check via:Example response
Test plan
METRICS_SECRETin Coolify env vars/healthpassesGET /metrics?secret=YOUR_SECRET→ should return JSON with live counters/metricswith no secret → 403, wrong secret → 401cache cleanup: evicted ...should only appear when entries expireSummary by CodeRabbit
New Features
Improvements
Chores
📝 Walkthrough
Walkthrough
This PR adds internal observability and improves cache resilience to the backend HTTP service. It introduces package-level metrics counters across
/skuinfo,/proxy, and/evallinksendpoints; exposes metrics via a new/metricsendpoint; implements periodic cache cleanup; and enhances/proxystale-on-failure handling with background refresh. The/skuinfosingleflight key is refactored from negative-cache key to a dedicatedsku:<product_id>pattern, and globalrandis replaced with a package-level instance for concurrent safety in jitter generation.Changes
Metrics and Cache Resilience
backend/main.go(lines 19–29, 98–119, 164)rand.Randinstance seeded at startup; atomic counter variables for request volume, cache hits, negative-cache hits, fetches, and stale serves across three endpoints; jitter function updated to use package-level RNG.backend/main.go(lines 945–1070)cleanupCaches()function periodically evicts expired SKU, link, and negative-cache entries;/metricshandler authenticates viaMETRICS_SECRET, reports atomic counter values, computes hit rates, and exposes cache sizes in Prometheus-like plaintext format.backend/main.go(lines 699–730, 765)sku:<product_id>key with result struct wrapper.backend/main.go(lines 790, 800, 812–847, 881)backend/main.go(lines 902–908)/evallinksendpoint.backend/main.go(lines 1089–1097)/metricsroute registered inmain()alongside existing session cleanup and eval cache warmup.Poem
🎯 3 (Moderate) | ⏱️ ~25 minutes
🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings.
✨ Finishing Touches
📝 Generate docstrings
🧪 Generate unit tests (beta)
feat/metricsComment
@coderabbitai helpto get the list of available commands and usage tips.Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID:
659481bd-0af9-4bb5-b443-92890b7ad5c3📥 Commits
Reviewing files that changed from the base of the PR and between
3329cd1b52and90c498233b.📒 Files selected for processing (1)
backend/main.go@ -24,1 +25,4 @@// Package-level rand source — avoids global mutex contention under concurrent load.// Go 1.20+ auto-seeds the global rand, but a local source is faster at high QPS.var rng = rand.New(rand.NewSource(time.Now().UnixNano()))⚠️ Potential issue | 🔴 Critical | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
Is Go rand.Rand safe for concurrent use?💡 Result:
No, instances of rand.Rand are not safe for concurrent use [1][2]. According to the official Go documentation, both the rand.Rand type and the underlying rand.Source type must be used by only a single goroutine at a time [1][2]. If you need to share a random number generator across multiple goroutines, you must implement your own synchronization, such as using a sync.Mutex to guard access to the rand.Rand instance [3][4]. In contrast, the top-level functions provided by the math/rand package (such as rand.Float64 and rand.Int) are safe for concurrent use because they utilize a global, synchronized source [1][5]. If you require a cryptographically secure random number generator that is safe for concurrent use, you should use the crypto/rand package, which provides a global, thread-safe reader [6].
Citations:
🏁 Script executed:
Repository: starkSV/windows-iso-downloader
Length of output: 132
🏁 Script executed:
Repository: starkSV/windows-iso-downloader
Length of output: 198
🏁 Script executed:
Repository: starkSV/windows-iso-downloader
Length of output: 225
🏁 Script executed:
Repository: starkSV/windows-iso-downloader
Length of output: 212
🏁 Script executed:
Repository: starkSV/windows-iso-downloader
Length of output: 321
🏁 Script executed:
Repository: starkSV/windows-iso-downloader
Length of output: 648
Fix data race:
rand.Randis not safe for concurrent use.The
jitter()function at lines 163–166 accesses an unsynchronizedrand.Randinstance from concurrent HTTP handlers (lines 174, 209, 761), creating a data race. According to Go documentation,rand.Randmust be accessed by only a single goroutine at a time.Add
sync.Mutexprotection:Fix: Add mutex protection to rng
Alternatively, use the global
randfunctions (e.g.,rand.Int63n()), which are mutex-protected by the runtime, though contention is minimal at typical QPS.🤖 Prompt for AI Agents
@ -885,0 +962,4 @@// Keep link entries for 4h past soft expiry — stale-on-failure windowlinkCacheMu.Lock()for k, v := range linkCache {if now.After(v.ExpiresAt.Add(4 * time.Hour)) {⚠️ Potential issue | 🟡 Minor | ⚡ Quick win
Background refresh does not increment
mLinkFetcheson success.When the background refresh successfully fetches from Microsoft (line 835), it updates the cache but doesn't increment
mLinkFetches. This means the/metricsendpoint will undercount actual Microsoft API calls when stale-on-failure triggers background refreshes.📊 Proposed fix
🤖 Prompt for AI Agents