Monitoring & Observability
Both the proxy and agent expose their own operational metrics and admin endpoints.
Enabling Metrics
Metrics are disabled by default. Enable them via CLI, environment variables, or config file.
Scraping Internal Metrics
The proxy runs next to Prometheus, so Prometheus scrapes its metrics port directly. The agents sit behind firewalls,
so give each agent a path for its own /metrics and scrape that path through the proxy, one job per agent (the
Agents dashboard tells agents apart by job):
agent {
pathConfigs: [
# ... the agent's targets, plus its own metrics:
{ name: "Agent metrics", path: cluster_a_agent_metrics, url: "http://localhost:8083/metrics" }
]
}
Then add the scrape jobs to your prometheus.yml:
scrape_configs:
# Scrape proxy internal metrics
- job_name: 'prometheus-proxy'
metrics_path: /metrics
static_configs:
- targets: ['proxy-host.example.com:8082']
# Scrape each agent's internal metrics through the proxy, one job per agent. The agent
# registers its own /metrics as a path (cluster_a_agent_metrics here).
- job_name: 'agent-cluster-a'
metrics_path: /cluster_a_agent_metrics
static_configs:
- targets: ['proxy-host.example.com:8080']
JVM Metrics
Both components support optional JVM metrics:
proxy.metrics {
enabled = true
// Optional JVM metrics
standardExportsEnabled = true
memoryPoolsExportsEnabled = true
garbageCollectorExportsEnabled = true
threadExportsEnabled = true
classLoadingExportsEnabled = false
versionInfoExportsEnabled = false
bufferPoolExportsEnabled = true
compilationExportsEnabled = true
nativeMemoryExportsEnabled = false // Needs the JVM's Native Memory Tracking; see below
}
The same options are available under agent.metrics.
The JVM metrics come from the Prometheus Java client 1.x. Since the move to it, the memory metrics put the unit
last (jvm_memory_used_bytes, not jvm_memory_bytes_used; jvm_memory_pool_used_bytes, not
jvm_memory_pool_bytes_used; the same for committed, max and init), jvm_info is jvm_runtime_info, and
memoryPoolsExportsEnabled also adds jvm_memory_pool_allocated_bytes_total.
Since 4.3.0, three more groups are available, also off by default:
bufferPoolExportsEnabledaddsjvm_buffer_pool_used_bytes,jvm_buffer_pool_capacity_bytesandjvm_buffer_pool_used_buffers, bypool(directandmapped): the memory the JVM's NIO buffer pools hold outside the heap.compilationExportsEnabledaddsjvm_compilation_time_seconds_total, the time the JIT compiler has spent.nativeMemoryExportsEnabledaddsjvm_native_memory_reserved_bytesandjvm_native_memory_committed_bytes, bypool. It reads the JVM's Native Memory Tracking, which is off unless the JVM starts with-XX:NativeMemoryTracking=summary(for the Docker images, setJAVA_TOOL_OPTIONS=-XX:NativeMemoryTracking=summary). Without it the flag exports nothing, and tracking adds some overhead of its own.
Proxy Metrics
Counters
| Metric | Labels | Description |
|---|---|---|
proxy_scrape_requests_total |
type |
Scrape request outcomes (see below) |
proxy_connect_count_total |
-- | Agent connection count |
proxy_eviction_count_total |
-- | Stale agent evictions |
proxy_heartbeat_count_total |
-- | Heartbeats received from agents |
proxy_chunk_validation_failures_total |
stage |
Chunk integrity failures (chunk or summary) |
proxy_chunked_transfers_abandoned_total |
-- | Chunked transfers abandoned mid-stream |
proxy_agent_displacement_total |
-- | Path registrations that displaced another agent |
proxy_scrape_requests_total type labels:
| Value | Meaning |
|---|---|
success |
Scrape completed successfully |
timed_out |
Agent did not answer within the proxy's scrapeRequestTimeoutSecs |
upstream_timed_out |
Agent answered, but its own fetch of the target timed out (408/504) |
invalid_path |
No agent has registered the requested path |
missing_path |
The request had no path (a scrape of /) |
agent_disconnected |
Agent disconnected, evicted, or displaced before answering (503) |
agent_backlog_full |
Agent's queue was full (2 × scrapeRequestBacklogUnhealthySize) |
proxy_in_flight_limit |
In-flight scrapes across agents hit maxInFlightScrapeRequests |
client_cancelled |
Prometheus hung up before the agent answered |
missing_results |
Internal error: results object was null |
path_not_found |
Agent has no registration for the target path (404) |
upstream_error |
Target returned a non-2xx status not covered above, e.g. 5xx |
invalid_response |
Agent answered, but a chunk or summary failed validation (502) |
content_too_large |
Target response exceeded the agent's content-length limit (413) |
payload_too_large |
Unzipped content exceeded the proxy's size limit |
invalid_gzip |
Gzip decompression failed |
proxy_stopped |
Proxy is shutting down (503) |
invalid_agent_context |
All agents for the path are in an invalid state |
Proxy-side and agent-side pairs
timed_out means the proxy gave up waiting on the agent; upstream_timed_out means the agent
answered promptly to report that the target was slow. Since agent.scrapeTimeoutSecs (15s)
is well under the proxy's scrapeRequestTimeoutSecs (90s), upstream_timed_out is the one you
will usually see for a slow endpoint. The agent also stops at Prometheus's own scrape_timeout when that is
shorter.
Likewise content_too_large is the agent's maxContentLengthMBytes limit, while
payload_too_large is the proxy's unzip limit. Both surface as HTTP 413.
upstream_error is always the target's own status. A scrape the proxy fails itself names why
instead: agent_disconnected (look at the agent's connection, not the target), proxy_stopped,
or invalid_response.
Histograms
| Metric | Labels | Description |
|---|---|---|
proxy_scrape_request_latency_seconds |
path, outcome |
End-to-end scrape latency |
proxy_scrape_response_bytes |
path, encoding |
Response payload size after decompression |
A path's series are removed when its last registration goes away, whether it is unregistered or its agent disconnects, so a retired path stops appearing on /metrics. A scrape still in flight when that happens is not recorded, so it cannot bring the series back.
The latency histogram's outcome label takes the proxy_scrape_requests_total type values above, so latency can be split
by result.
Latency buckets: 5ms, 10ms, 25ms, 50ms, 100ms, 250ms, 500ms, 1s, 2.5s, 5s, 10s, 15s, 30s, 60s, 90s
Response size buckets: 1KB, 10KB, 100KB, 500KB, 1MB, 5MB, 10MB
Gauges
| Metric | Description |
|---|---|
proxy_start_time_seconds |
Proxy start time (Unix epoch) |
proxy_agent_map_size |
Number of connected agents |
proxy_path_map_size |
Number of registered scrape paths |
proxy_scrape_map_size |
Number of in-flight scrape requests |
proxy_chunk_context_map_size |
Number of in-flight chunked transfers |
proxy_cumulative_agent_backlog_size |
Total queued scrape requests across all agents |
Agent Metrics
Counters
| Metric | Labels | Description |
|---|---|---|
agent_scrape_request_count_total |
launch_id, type |
Scrape requests processed (success, unsuccessful, timed_out, failure, invalid_path) |
agent_scrape_result_count_total |
launch_id, type |
Results sent (non-gzipped, gzipped, chunked, dropped) |
agent_connect_count_total |
launch_id, type |
Connection attempts (success, failure) |
agent_filter_lines_dropped_total |
launch_id, path |
Lines removed by the path's metric filter |
agent_filter_bytes_saved_total |
launch_id, path |
Bytes saved before gzip by the metric filter |
The launch_id label uniquely identifies each agent process lifetime. unsuccessful means the target answered with
a non-2xx status or a response over agent.http.maxContentLengthMBytes; timed_out means the target didn't answer
within the agent's scrape timeout; failure means the fetch failed without an answer, such as a refused connection
or a DNS failure; invalid_path means the proxy asked for a path the agent doesn't serve; dropped is a result lost
because the connection to the proxy closed before it was sent.
The two agent_filter_* counters only create series for paths that have a filter configured, so
they are absent entirely unless metric filtering is in
use. See that section for what the filter does and does not drop.
Histograms
| Metric | Labels | Description |
|---|---|---|
agent_scrape_request_latency_seconds |
launch_id, agent_name |
Time to fetch from target endpoint |
It has the proxy latency histogram's buckets, up to 90s, past the agent's 15s scrape timeout.
Gauges
| Metric | Labels | Description |
|---|---|---|
agent_start_time_seconds |
launch_id |
Agent start time (Unix epoch) |
agent_scrape_backlog_size |
launch_id |
Pending scrape requests queued |
agent_client_cache_size |
launch_id |
Number of cached HTTP clients |
Metric Flow
Prometheus --- HTTP GET ---> Proxy Agent
| |
records the start time |
| |
writeScrapeRequest() -- gRPC stream --> fetchScrapeUrl()
| agentLatency.startTimer()
| |
| HTTP GET to target
| |
| agentLatency.observeDuration()
| scrapeRequestCount.inc()
| scrapeResultCount.inc()
| |
assignScrapeResults() <-- gRPC -----------+
|
responseBytes.observe()
latency.observe(elapsed)
scrapeRequestCount.labelValues(outcome).inc()
|
<-- HTTP response ---
PromQL Examples
Scrape Success Rate
# Scrape success rate (last 5 minutes):
sum(rate(proxy_scrape_requests_total{type="success"}[5m]))
/ sum(rate(proxy_scrape_requests_total[5m])) * 100
P99 Scrape Latency
# P99 scrape latency:
histogram_quantile(0.99,
sum by (le) (rate(proxy_scrape_request_latency_seconds_bucket[5m]))
)
P99 Latency Per Path
# P99 latency per path:
histogram_quantile(0.99,
sum by (le, path) (rate(proxy_scrape_request_latency_seconds_bucket[5m]))
)
Error Rate by Type
Agent Latency by Name
# Agent scrape latency by agent name:
histogram_quantile(0.99,
sum by (le, agent_name) (rate(agent_scrape_request_latency_seconds_bucket[5m]))
)
Admin Endpoints
Admin endpoints (when admin is enabled):
Proxy (default port 8092):
GET /ping - Returns "pong" (liveness check)
GET /healthcheck - Returns health status JSON
GET /version - Returns version info
GET /threaddump - Returns JVM thread dump
GET /debug - Returns proxy debug info (if debug enabled)
Agent (default port 8093):
GET /ping - Returns "pong" (liveness check)
GET /healthcheck - Returns health status JSON
GET /version - Returns version info
GET /threaddump - Returns JVM thread dump
GET /debug - Returns agent debug info (if debug enabled)
Enable admin endpoints:
Grafana Dashboards
Importing Grafana dashboards:
1. In Grafana, go to Dashboards > Import
2. Upload the JSON file or paste its contents
3. Open the dashboard and pick your Prometheus datasource in its Datasource drop-down
Dashboard files in the repository:
grafana/prometheus-proxy.json - Proxy health, throughput, latency, errors
grafana/prometheus-agents.json - Agent health, scrape activity, per-agent latency
Requirements:
- Grafana 10.0 or later
- A Prometheus datasource scraping the proxy's and the agents' metrics endpoints,
with each agent in its own job
Proxy Dashboard
Key panels to monitor:
| Section | What to Watch |
|---|---|
| Overview | Success rate dropping below 99%, error count spikes |
| Throughput | Sudden changes in request volume or error ratio |
| Latency | P99 creeping up indicates slow targets or network issues |
| Payload | Unexpectedly large responses, gzip vs plain distribution |
| Internal State | Growing backlog means agents can't keep up; agents heartbeat only when idle, so zero heartbeats is normal under load |
| Errors & Events | Which error types dominate, frequent evictions |
| Chunk Health | Any chunk validation failure or abandoned transfer |
The Job and Instance pickers pick out one proxy when Prometheus scrapes more than one, such as a high-availability pair.
Agents Dashboard
Key panels to monitor:
| Section | What to Watch |
|---|---|
| Overview | Unexpected agent count changes |
| Connections | Failure spikes indicate proxy or network issues |
| Scrape Activity | Imbalanced load; unsuccessful scrapes point at a failing target; any dropped result |
| Latency | Per-agent latency outliers point to slow targets |
| Agent Internals | Growing backlog means the agent is falling behind |
Every agent panel keys on job, so scrape each agent in its own job, as in
Scraping Internal Metrics.