- Collects metrics from applications using Ebean ORM that send it their metrics.
- Performs a rollup of metrics on 1 min, 10 min, 1 hour basis.
- Supports ability to request Query plans and collect query plans
- For Postgres query plans, uses pev2 to view query plan details
- Can also forward ingested metrics to any OTLP/HTTP collector (Grafana Alloy, OpenTelemetry Collector, Grafana Cloud, etc.)
- Exposes a versioned, agent-friendly REST API at
/v1(contract-first via OpenAPI; seeapi/src/main/openapi/v1.yaml).
- Server — Kubernetes, Docker, or standalone native binary:
docs/install-server.md - CLI (
insight) — per-OS binaries for macOS, Linux, Windows:docs/install-cli.md
For the server's full configuration / mode reference see
docs/deployment-modes.md. To require OAuth2 JWT
bearer auth on the server's endpoints see docs/auth.md. For
day-to-day CLI usage see cli/README.md.
| Module | Purpose |
|---|---|
api |
OpenAPI spec (v1.yaml) and generated /v1 API interfaces + DTOs (record types). |
client |
Generated typed HTTP client (*HttpClient) for the /v1 API. |
server |
The running service: ingest endpoints, rollups, UI, and /v1 controllers. |
forwarder |
Library that maintains a stable local endpoint to the server via a supervised kubectl port-forward. See forwarder/README.md. |
cli |
insight command line tool over the /v1 API; compiles to a native binary. See cli/README.md. |
The /v1 API uses natural keys in the path (app name, env name) instead
of internal numeric ids — making it suitable for CLIs, agents, and ad-hoc
tooling. The OpenAPI spec is the source of truth:
All endpoints expect the Insight-Key header (same auth as /api).
Time windows accept either sinceMinutes or sinceHours (not both → 400).
Discover apps + envs:
curl -H "Insight-Key: $KEY" http://localhost:8090/v1/apps
curl -H "Insight-Key: $KEY" http://localhost:8090/v1/envs
curl -H "Insight-Key: $KEY" http://localhost:8090/v1/apps/myappTop-N most expensive metrics for an app (last hour):
curl -H "Insight-Key: $KEY" \
"http://localhost:8090/v1/apps/myapp/metrics/top?orderBy=total&sinceMinutes=60&limit=20"Top-N across all apps (last 24h, plan-capable only):
curl -H "Insight-Key: $KEY" \
"http://localhost:8090/v1/metrics/top?sinceHours=24&planCapable=true&limit=50"Find ORM metrics lacking a recent plan capture, ranked by execution cost
(omit the /apps/myapp segment to rank across all apps):
curl -H "Insight-Key: $KEY" \
"http://localhost:8090/v1/apps/myapp/metrics/missing-plans?by=total&sinceHours=24&limit=50"
curl -H "Insight-Key: $KEY" \
"http://localhost:8090/v1/metrics/missing-plans?by=total&limit=50"Trace → plan: take a hash from a span attribute (ebean.query_hash) and
look up the matching metric and any captured plans, optionally requesting
a fresh capture:
HASH=8a519a4c120289bd505a4a79c27f2895
curl -H "Insight-Key: $KEY" \
"http://localhost:8090/v1/apps/myapp/metrics/by-hash/$HASH"
curl -H "Insight-Key: $KEY" \
"http://localhost:8090/v1/apps/myapp/plans/by-hash/$HASH"
curl -X POST -H "Insight-Key: $KEY" \
"http://localhost:8090/v1/apps/myapp/plans/by-hash/$HASH/request?env=prod"Fetch a specific plan (full SQL + plan + bind values) by id:
curl -H "Insight-Key: $KEY" http://localhost:8090/v1/plans/12345List in-flight capture requests — requested but not yet collected (tracked durably; survives forwarder polls and server restarts; a request whose query never executes ages out after ~15 minutes):
curl -H "Insight-Key: $KEY" "http://localhost:8090/v1/plans/pending"
curl -H "Insight-Key: $KEY" "http://localhost:8090/v1/plans/pending?app=myapp&env=test"- Natural keys —
{app}and?env=are names, not ids. Unknown natural keys return200with an empty list (not404) — exceptGET /v1/apps/{app}andPOST .../plans/by-hash/{hash}/requestwhich return404when the app doesn't exist. - planCapable — derived from the metric name (
orm.,dto., orsql.query., excludingorm.update.) and stored onapp_metric.plan_capable. Only plan-capable metrics supportPOST .../plans/by-hash/{hash}/request; other metrics return400. - orderBy — allowlist on
/metrics/top:total(default),mean,max,count. Anything else →400. - Hash vs label —
hashis the metrickey(deterministic SQL hash; ORM-only),labelis the human metric name (e.g.orm.OrderDao.find).
For interactive / scripted use there is an insight CLI (cli/README.md)
that wraps these endpoints. It can reach the server via a static --url, or — by
default — a supervised kubectl port-forward that reuses your cluster RBAC as
auth (no Insight-Key needed), optionally held open by a background
insight forward daemon:
insight forward # optional: hold one supervised tunnel open
insight apps
insight plans -n 5
insight plan 12345 --raw| Mode | What it does | Postgres |
|---|---|---|
| Persist (default) | Stores metrics + query plans in Postgres, runs rollups, serves UI. | required |
| Forward-only | Pure smart-proxy — forwards each ingest via OTLP, optionally logs query plans. | not required |
Set both METRICS_STORE_ENABLED=false and PLANS_STORE_ENABLED=false to
enable forward-only mode. Either mode can additionally forward via
FORWARD_OTEL_ENABLED=true + FORWARD_OTEL_ENDPOINT=http://....
See docs/deployment-modes.md for full
configuration, env-var reference, Docker examples and expected startup logs.
- Support reporting aggregated metrics onto Graphite, StatsD, etc
- Provide automation for automatically collecting query plans for:
- new queries,
- queries that exceed a threshold (anomalies)
Requires GraalVM installed
sdk install java 24-graal
sdk use java 24-graalBuild on a Mac (no G1GC supported)
mvn clean package -P native,mac -DskipTestsBuild on a Linux (with G1GC)
mvn clean package -P native,linux -DskipTests- Requires docker to be installed locally
- Run the main method on src/test/java/main/StartPostgresDocker
Run the native application. We pass it an external configuration file via -Dprops.file=.
./target/ebean-insight -Dprops.file=application.yaml