You deployed. p99 latency spiked. Now what?
Open Grafana. Check Jaeger. Dig through logs. Read the commit diff. Connect the dots yourself — every single time.
I got tired of that loop, so I built lofi: a zero-config library that links deploy events to method-level latency and lets you diff them from the terminal.
$ lofi diff a3f9c1..d82e04
Deploy Diff a3f9c1 → d82e04
───────────────────────────────────────────────────────────────────
Method Before After Delta
───────────────────────────────────────────────────────────────────
OrderService.createOrder() 14.23ms → 91.00ms +76.77ms ▲
PaymentClient.validate() 22.10ms → 58.40ms +36.30ms ▲
UserService.findById() 3.05ms → 3.12ms +0.07ms —
───────────────────────────────────────────────────────────────────
2 regression(s) detected
Regressed methods show in red. No dashboards required.
How it works
lofi uses Spring AOP to automatically instrument every @Service, @Component, @Repository, @Controller, and @RestController bean in your application. No annotations on your business code. No agent. No code changes beyond adding the dependency.
When your app starts, it reads a GIT_COMMIT_HASH environment variable to know which deploy is running. Every method call gets timed (in nanoseconds) and flushed asynchronously to a local SQLite database
at ~/.lofi/metrics.db. When you're ready to compare two deploys, you run lofi diff and it calls the actuator endpoint to compute the regression diff.
The library is careful about what it instruments:
- Spring internals (org.springframework.*) → skipped
- Jakarta Servlet filters and MVC interceptors → skipped (avoids Security filter chain conflicts)
- AspectJ @aspect classes → skipped (avoids proxy-on-proxy chaos)
- JDK dynamic proxies like Spring Data JPA repositories → skipped (their time is already captured through the enclosing service call)
Getting started in 5 steps
1. Add the dependency
Gradle:
implementation 'io.github.closeup1202:lofi-spring-boot-starter:0.1.7'
Maven:
<dependency>
<groupId>io.github.closeup1202</groupId>
<artifactId>lofi-spring-boot-starter</artifactId>
<version>0.1.7</version>
</dependency>
2. Expose actuator endpoints
management:
endpoints:
web:
exposure:
include: lofi, lofiDiff
3. Set the commit hash and run
export GIT_COMMIT_HASH=$(git rev-parse --short HEAD)
./gradlew bootRun
# [LO-FI] Monitoring active — commit: a3f9c1 | store: sqlite | regression-threshold: 0.2
4. Verify metrics via actuator
Send some traffic to your app, then check the raw JSON directly:
curl http://localhost:8080/actuator/lofi/a3f9c1
{
"commitHash": "a3f9c1",
"deployedAt": "2024-11-01T09:00:00Z",
"metrics": [
{
"className": "com.example.OrderService",
"methodName": "createOrder",
"elapsedMs": 14.23,
"recordedAt": "2024-11-01T09:01:23Z"
}
]
}
Once you have two deploys' worth of data, you can also diff them directly:
curl "http://localhost:8080/actuator/lofiDiff?base=a3f9c1&head=d82e04"
5. Install the CLI for a better view
The CLI renders the same data as a formatted table with regression highlighting — easier to read at a glance than raw JSON.
Install the CLI
npm install -g @closeup1202/lofi-cli
Two commands:
- lofi diff .. — compare latency between two deploys
- lofi snapshot — inspect metrics for a single deploy
lofi diff a3f9c1..d82e04 --url http://localhost:9090
lofi snapshot a3f9c2 --url http://localhost:9090
What it stores (and where)
All data stays local. lofi writes a SQLite file to ~/.lofi/metrics.db. No telemetry, no cloud, nothing leaves your machine unless you opt in to a dashboard (coming later).
If you're running in Docker or Kubernetes, mount a volume at /root/.lofi so the database survives container restarts:
docker run \
-e GIT_COMMIT_HASH=$(git rev-parse --short HEAD) \
-v $HOME/.lofi:/root/.lofi \
my-app
Spring Security note
If your app uses Spring Security, the actuator endpoints return 403 by default. The cleanest fix is management port isolation — run actuator on a separate internal port that's never exposed publicly:
management:
server:
port: 9090
lofi diff a3f9c1..d82e04 --url http://localhost:9090
No security config changes needed.
Configuration
lofi:
store-type: sqlite # or in-memory (for tests/dev)
regression-threshold: 0.2 # 20% increase = regression
buffer:
flush-threshold: 100
flush-delay-ms: 5000
queue-capacity: 1000
JSR-303 validation runs at startup — if you misconfigure a value, all violations are reported at once rather than stopping at the first one.
Current state and roadmap
lofi is in early development. It currently works best in single-pod environments (each pod has its own SQLite file). Multi-pod metric aggregation and a team dashboard are on the roadmap.
Requires Spring Boot 3.x and Java 17+.
- GitHub: https://github.com/closeup1202/lofi
- npm: https://www.npmjs.com/package/@closeup1202/lofi-cli
- Maven Central:
https://central.sonatype.com/artifact/io.github.closeup1202/lofi-spring-boot-starter
Feedback welcome — open an issue or drop a comment below.
SOCIAL SHARE CARD GENERATOR