Know where the lag starts.
A native macOS utility that monitors connection quality, explains likely causes, and keeps exact evidence when something goes wrong.
LagCulprit continuously measures latency, jitter, packet loss, router reachability, independent internet controls, DNS health, and available Wi-Fi context. It turns those signals into a readable state and an evidence-backed culprit domain:
- Local network or router
- ISP or upstream path
- DNS resolver
- Remote service or application path
- Inconclusive, when the evidence cannot safely separate the cause
Attribution is probabilistic by design. LagCulprit reports uncertainty instead of turning an ambiguous ping result into a confident but misleading verdict.
- Near-instant state changes — a packet-loss pipeline combines consecutive failures, rolling metrics, and hysteresis to react quickly without flickering.
- Independent controls — gateway, DNS, and multiple public controls help distinguish local, upstream, and resolver failures.
- Adaptive probing — monitoring runs at roughly five samples per second while healthy and accelerates during instability.
- Application-path context — the app conservatively detects active latency-sensitive sessions, follows helper processes, and monitors a stable remote endpoint as a separate leg when direct probing is supported.
- Wi-Fi and route evidence — signal strength and a one-shot path trace add context when an incident opens.
- Exact incident timeline — incidents preserve their first bad sample, recovery boundary, duration, local timestamps with milliseconds, evidence checks, charts, network context, and trace results.
- Lag Recap — detected calls, games, and other real-time sessions receive a local score, stable time, P95 latency, loss, incident attribution, and a recommended action.
- Mark Lag Now — press
Option-Shift-Mto correlate a felt problem with the measurements recorded at that moment. - Session Preflight — an eight-second latency-under-load check helps reveal bufferbloat before an important call or session.
- Network Report — summarize uptime, incidents, affected time, worst hour, latency trends, and per-network health.
- Local exports — create self-contained incident reports, session HTML reports, and PNG recap cards without uploading history.
- Menu-bar app — no permanent main window; built natively with SwiftUI and AppKit.
- Floating overlay — three sizes, click-through support, modifier-drag positioning, incident expansion, and a global visibility shortcut.
- Protected local API and browser overlay — optional localhost-only status JSON and an OBS-compatible browser source secured by an installation-specific token.
- Notifications and updates — native incident/recovery notifications plus Sparkle updates.
- No root access or privileged network helper is required.
- Measurements, incidents, sessions, and settings stay in local application storage.
- The optional HTTP surface binds to loopback and requires a token.
- Reports and recap images are generated locally.
- Preflight sends temporary, capped download traffic; it performs no upload test and does not retain the payload.
- Monitoring necessarily sends network probes and DNS requests. LagCulprit does not claim to make zero network requests.
┌──────────────────────────────────────────────────────┐
│ Native macOS app: SwiftUI/AppKit │
│ menu bar · history · reports · overlay │
└──────────────────────────┬───────────────────────────┘
│ Unix socket
│ length-prefixed protobuf
┌──────────────────────────┴───────────────────────────┐
│ Bundled Rust daemon │
│ probes · metrics · verdicts · incidents · SQLite │
│ sessions · reports · localhost API/browser overlay │
└──────────────────────────────────────────────────────┘
The Rust engine is the source of truth. The native app renders the state, verdict, evidence, and history published over the protobuf contract; it does not independently reinterpret measurements.
| Crate | Responsibility |
|---|---|
lc-core |
Pure metrics, packet-loss state machine, scoring, incident, and session logic |
lc-ipc |
Protobuf types and length-prefixed Unix-socket transport |
lc-daemon |
Probing, orchestration, persistence, reports, and local HTTP surface |
lc-cli |
Human-readable status, watch, and --json automation output |
- Download the current DMG from the latest GitHub release.
- Open the disk image and drag
LagCulprit.appto/Applications.
The DMG includes its matching Rust daemon. The native app starts it on launch and stops it on quit; there is no separate service or privileged helper to install.
Start the development daemon:
cd rust-core
cargo run -p lc-daemonThen generate and open the app project:
cd macos-app
xcodegen generate
open LagCulprit.xcodeprojPress Command-R in Xcode. Local Xcode builds do not contain the packaged helper, so they connect to the daemon running from the first terminal. Both Debug and Release configurations compile the live-daemon path; simulation remains available only for previews and explicit fixtures.
To force an incident during development, run the daemon with an unreachable documentation address:
LC_TARGET=192.0.2.1 cargo run -p lc-daemon# Rust workspace
cd rust-core
cargo test --workspace --locked
# Swift wire-layer proof
cd ../swift-proof
swift build
# Native app tests
cd ../macos-app
xcodegen generate
xcodebuild \
-project LagCulprit.xcodeproj \
-scheme LagCulprit \
-destination 'platform=macOS' \
testCI runs the locked Rust workspace and compiles the native macOS app. Tagged releases produce a signed and notarized DMG.
Pushing a version tag such as v1.2.0 starts .github/workflows/release.yml. The workflow:
- Tests the Rust workspace and native macOS app.
- Bundles
lc-daemoninside the app. - Signs, notarizes, staples, and Sparkle-signs the DMG.
- Publishes the DMG and appcast to the tagged GitHub release.
The app checks this stable feed URL:
https://github.com/SarKurd/LagCulprit/releases/latest/download/appcast.xml
Each appcast points to the DMG attached to its immutable, versioned GitHub Release. See BUILD.md for local packaging commands and required CI secrets.
proto/ Shared daemon-to-app protobuf contract
rust-core/ Rust engine, daemon, IPC, and CLI crates
macos-app/ SwiftUI/AppKit app and XcodeGen project specification
swift-proof/ SwiftPM build proof for the generated wire layer
packaging/ DMG signing and notarization script
- Edit
macos-app/project.yml, not the generated.xcodeproj. - Treat
proto/lagculprit/v1/lagculprit.protoas the wire-contract source of truth and runscripts/generate-proto.shafter schema changes. - Keep
lc-corefree of I/O and platform-specific behavior. - Run the relevant Rust and native-app checks before opening a pull request.
- Update this README and BUILD.md when user-visible behavior or distribution changes.
- App-path detection is conservative and cannot identify or directly probe every relay, encrypted transport, VPN path, or UDP-only IPv6 endpoint.
- Fault attribution says “likely” because endpoint behavior alone cannot prove every remote or upstream cause.
- The native overlay cannot guarantee visibility over every exclusive-fullscreen render pipeline.
MIT. See LICENSE.