Skip to content

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.

proxy {
  metrics {
    enabled = true
    port = 8082
    path = "metrics"
  }

  admin {
    enabled = true
    port = 8092
  }
}

Or via CLI: --metrics or METRICS_ENABLED=true

Default endpoint: http://proxy-host:8082/metrics

agent {
  metrics {
    enabled = true
    port = 8083
    path = "metrics"
  }

  admin {
    enabled = true
    port = 8093
  }
}

Or via CLI: --metrics or METRICS_ENABLED=true

Default endpoint: http://agent-host:8083/metrics

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:

  • bufferPoolExportsEnabled adds jvm_buffer_pool_used_bytes, jvm_buffer_pool_capacity_bytes and jvm_buffer_pool_used_buffers, by pool (direct and mapped): the memory the JVM's NIO buffer pools hold outside the heap.
  • compilationExportsEnabled adds jvm_compilation_time_seconds_total, the time the JIT compiler has spent.
  • nativeMemoryExportsEnabled adds jvm_native_memory_reserved_bytes and jvm_native_memory_committed_bytes, by pool. It reads the JVM's Native Memory Tracking, which is off unless the JVM starts with -XX:NativeMemoryTracking=summary (for the Docker images, set JAVA_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

# Error rate by type:
sum by (type) (rate(proxy_scrape_requests_total{type!="success"}[5m]))

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:

java -jar prometheus-proxy.jar --admin
java -jar prometheus-agent.jar --admin

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.