System Health & Prometheus Metrics

The Athena server counts what it is doing: agent connections, heartbeat and database writes, REST API latency, commands, deployments, agent updates, CPU and memory. It shows those numbers in two places that read the same counters: the live Settings → System Health tab in the web console, and a Prometheus /metrics endpoint that Prometheus, Grafana or any other tool that reads the Prometheus text format can scrape. Because both read the same numbers, the console and your dashboards always agree.

/metrics is open by default

Out of the box the metrics endpoint listens on all network interfaces, TCP port 9464, over plain HTTP, with no sign-in. This is how Prometheus usually scrapes a service. The data is limited to counts, durations, versions and days until expiry, but if you do not want it reachable from your network, restrict it. See Restricting who can read /metrics.

This page covers the server's own performance counters. For the up/down health checks (/health, database, PKI, audit and disk status), see Server Health & Monitoring. For CPU, memory and disk on managed endpoints, see Resource Metrics & Performance.

Who can see it#

ToYou needBuilt-in roles
Open Settings → System Healthsettings.health.view and settings.manage (the Settings page itself requires it)Admin
Read the same report over REST (GET /api/metrics/summary)settings.health.viewAdmin, Operator
Generate, rotate or revoke the scrape tokensettings.manageAdmin
Scrape /metricsNo Athena account. Access is controlled only by the listener settings and the optional scrape token.n/a

Operators hold settings.health.view but cannot open Settings, so they read System Health over REST. See Permission Model & Role Bundles.

Opening System Health#

  1. Open Settings.
  2. Click the System Health tab, or go straight to https://<server>:8443/settings/system-health.

While the tab is open, the server sends it new numbers every 10 seconds. The page does not poll. A badge at the top right reads Live with the time of the last update. If the browser loses its live connection, the badge turns amber and reads Live updates paused. The tab keeps the last numbers and reconnects by itself.

Use the 15m / 1h range selector to choose how much history the charts draw. The server keeps history in memory for the last 60 minutes, at one point every 10 seconds. Restarting the server clears that history and resets every counter. For long-term trends, scrape /metrics with Prometheus.

Key indicators#

The tiles at the top summarise the last 5 minutes. A tile turns amber when it crosses the threshold below. p95 means 95 % of the operations finished faster than the value shown.

TileWhat it showsTurns amber when
Agents onlineAgents with a live connection, out of the agents registered, plus the licence agent limit and how many are left.n/a
Agent connect p95How long agent connections took to finish their start-up work (status, configuration and scan definitions sent). Shows the peak of the last hour.Above 2 s
REST p95Response time of the slowest 5 % of API requests.Above 500 ms
Error rateShare of API requests that failed with a 5xx server error.Above 1 %
Heartbeat batch p95How long the database took to write a batch of agent heartbeats, with the current queue length and the number of heartbeats dropped.Above 1 s
CPUServer process CPU, as a share of all cores.Above 85 %
MemoryProcess working set and .NET heap.n/a
Agent registrationsNew agent enrolments.n/a
Certificate expiryWhichever certificate expires first: the web certificate (port 8443), the agent gRPC certificate (port 8444) or the Athena Root CA.Under 14 days
LicenceDays until the installed licence expires.Under 14 days

Below the tiles, four charts plot Agents connected (against registered), REST latency, Heartbeat batch duration and CPU & memory over the selected range.

Database, Work and Process panels#

Scraping /metrics with Prometheus#

By default the server serves Prometheus text on its own port:

Closed on the virtual appliance

The appliance firewall allows only TCP 8443 and 8444, so port 9464 is closed on the appliance and Prometheus cannot scrape it from outside today.

http://<server>:9464/metrics

This port serves /metrics and nothing else. It is not the web console port. Do not point Prometheus at 8443/metrics, which only returns the web page. A minimal scrape configuration:

scrape_configs:
  - job_name: athena
    scrape_interval: 15s
    static_configs:
      - targets: ['athena.example.local:9464']

The Copy Prometheus scrape config button in the Prometheus endpoint box on the System Health tab copies a configuration like this for your server.

Counters reset when the server restarts. In Grafana, use rate() or increase() on any metric whose name ends in _total. Two useful queries:

# REST p95 over 5 minutes, excluding the long-lived agent stream
histogram_quantile(0.95, sum by (le) (rate(http_server_request_duration_seconds_bucket{http_route!~".*Connect"}[5m])))

# Heartbeat batch p95 - the database write bottleneck
histogram_quantile(0.95, sum by (le) (rate(athena_heartbeat_batch_duration_seconds_bucket[5m])))

Useful metrics#

Athena's own metrics start with athena_. Durations end in _seconds, sizes in _bytes, and counters in _total. Durations are histograms, so they also have _bucket, _sum and _count series. The endpoint also carries the standard ASP.NET Core HTTP, Kestrel and SignalR metrics (for example http_server_request_duration_seconds). On PostgreSQL it adds the Npgsql connection-pool metrics. A selection:

MetricLabelsWhat it measures
athena_agents_connectedAgents with a live connection now.
athena_agents_registeredAgents registered.
athena_agents_statusstatusAgents by status.
athena_agent_connects_totalresultAgent connections accepted or refused.
athena_agent_disconnects_totalreasonAgent connections that ended.
athena_agent_connect_duration_secondsConnection accepted to start-up work done.
athena_agent_registrations_totalresultAgent enrolment calls.
athena_heartbeat_batch_duration_secondsresultHeartbeat batch write time (one transaction per batch).
athena_heartbeat_queue_lengthHeartbeats waiting to be written.
athena_heartbeats_dropped_totalHeartbeats dropped because the queue was full.
athena_db_write_lock_wait_secondsTime spent waiting for the database write lock.
athena_db_size_bytesfileDatabase size on disk.
athena_logins_totalmethod, resultREST login attempts.
athena_commands_totalstatusCommands that reached a final status.
athena_deployments_totalstatusDeployment executions that reached a final status.
athena_agent_updates_totaltrigger, resultAgent update pushes, automatic or manual.
athena_alerts_fired_totalseverityAlerts raised.
athena_alerts_firingAlerts firing now.
athena_scheduled_job_runs_totaljob_type, resultScheduled job executions.
athena_process_cpu_seconds_totalProcess CPU time. rate() divided by the core count gives CPU %.
athena_process_working_set_bytesProcess working set.
athena_uptime_secondsSeconds since the server started. Every counter resets with it.
athena_build_infoversion, db_providerAlways 1. The labels carry the server version and database provider.
athena_licence_days_remainingDays until the licence expires. Absent when no licence is installed.
athena_licence_agents_limitThe licence agent limit.
athena_certificate_expiry_dayscert = ca, server_web, server_grpcDays until each certificate expires.
athena_metrics_scrapes_totalresult = ok, unauthorized, forbiddenRequests to /metrics. Refused scrapes show up here.

For alerting, athena_certificate_expiry_days and athena_licence_days_remaining let you warn well before an expiry, and a rising athena_heartbeat_queue_length or athena_heartbeats_dropped_total warns that the database cannot keep up.

Restricting who can read /metrics#

/metrics exposes only counts, durations, versions, days until expiry and licence limits. It never contains host names, user names, agent IDs, IP addresses, the licence customer name or ID, certificate subjects or any secret. Every label comes from a fixed list, and any other label is dropped before it is published.

To lock it down further, use any combination of the following. All of them are off by default.

ToDo this
Require a tokenGenerate a scrape token on the System Health tab (see below). From then on every scrape must send it. Scrapes without it get HTTP 401.
Allow only some addressesSet Metrics:AllowedIps to a list of IP addresses or CIDR ranges, for example 10.20.0.0/16. A single address is treated as a /32 (IPv4) or /128 (IPv6). Other addresses get HTTP 403.
Serve it over HTTPSSet Metrics:UseHttps to true. The listener uses the server's own web certificate with TLS 1.2/1.3, so server TLS must be enabled (see Server TLS Certificate).
Allow only this machineSet Metrics:ListenAddress to 127.0.0.1. The default 0.0.0.0 means all interfaces.
Change the portSet Metrics:Port (1–65535). Default 9464.
Turn it offSet Metrics:Enabled to false.
Block it at the networkClose TCP 9464 in the host or network firewall. See Network Ports & Firewall.

When both an allow-list and a token are set, the address is checked first (403), then the token (401).

Setting the Metrics options#

The Metrics:* options go in appsettings.local.json in the server's data directory (see Data Directories & Backup), or in environment variables with a double underscore, such as Metrics__ListenAddress. They take effect after a server restart.

{
  "Metrics": {
    "Enabled": true,
    "ListenAddress": "0.0.0.0",
    "Port": 9464,
    "UseHttps": true,
    "AllowedIps": [ "10.20.0.15", "10.30.0.0/24" ]
  }
}
# The same as environment variables (double underscore for the colon)
Metrics__UseHttps=true
Metrics__AllowedIps__0=10.20.0.15
Metrics__AllowedIps__1=10.30.0.0/24
A bad setting never stops the server

If a Metrics:* value cannot be used (an address that is not an IP address, a port out of range, an invalid allow-list entry, UseHttps without server TLS, or a port already in use), only the metrics listener stays down. The rest of Athena starts normally. The reason is written to the server log and shown in the Prometheus endpoint box.

Scrape token#

  1. Open Settings → System Health and find the Prometheus endpoint box.
  2. Click the Generate / rotate scrape token button.
  3. Copy the token right away. It is shown once and cannot be displayed again. The server keeps only a hash of it.
  4. Save the token in a file on the Prometheus host and reference it from the scrape configuration. Every scrape must now send it as a Bearer token.
scrape_configs:
  - job_name: athena
    scrape_interval: 15s
    scheme: https                      # only if Metrics:UseHttps is true
    tls_config:
      ca_file: /etc/prometheus/athena-ca.crt   # CA that issued the server's web certificate
    authorization:
      type: Bearer
      credentials_file: /etc/prometheus/athena-scrape-token
    static_configs:
      - targets: ['athena.example.local:9464']

REST API#

EndpointPermissionDoes
GET /api/metrics/summarysettings.health.viewReturns the System Health report as JSON (tiles, Database, Work and Process panels, and the Prometheus listener state). Optional ?historyMinutes=15 or 60 adds the 10-second chart points. Any other value returns HTTP 400.
POST /api/metrics/scrape-tokensettings.manageGenerates or rotates the scrape token. The response is the only time the token is returned.
DELETE /api/metrics/scrape-tokensettings.manageRevokes the scrape token.
Invoke-RestMethod "https://athena.contoso.com:8443/api/metrics/summary?historyMinutes=15" `
  -Headers @{ Authorization = "Bearer $token" } -SkipCertificateCheck |
  Select-Object -ExpandProperty data

See API Reference for signing in and getting a token.

Audit trail#

Event typeRecorded when
SystemHealthViewedSomeone opens Settings → System Health, or reads /api/metrics/summary.
MetricsScrapeTokenGeneratedA scrape token is generated or rotated.
MetricsScrapeTokenRevokedThe scrape token is revoked.

Individual scrapes are counted (athena_metrics_scrapes_total), not audited. See Audit & SIEM.

Troubleshooting#

SymptomWhat to check
Prometheus reports the target as downAt startup the server log shows Metrics: Prometheus endpoint http://…:9464/metrics when the listener is up. If it shows /metrics listener NOT started, the reason follows, usually a port already in use or an invalid Metrics:* setting. Also check the host firewall. See Server Logging.
Prometheus gets HTTP 401A scrape token is required and the scrape sent none or an old one. Rotate the token, put the new value in the credentials file and reload Prometheus.
Prometheus gets HTTP 403The Prometheus host is not in Metrics:AllowedIps.
Prometheus gets an HTML pageIt is scraping the web console port (8443). Use port 9464.
The log warns refused a /metrics scrapeA scraper is being turned away (wrong address or token). The warning appears at most once a minute. Further refusals are only counted.
The tab shows Live updates pausedThe browser lost its live connection. The tab reconnects by itself. If it stays paused, reload the page.
Charts are empty after a restartExpected. History is kept in memory only and starts again when the server restarts.