Reuse the tested API helper, explain point/source and access distinctions, and preserve data before storage tuning. Copied examples now exercise private success/error responses, redirects, incomplete transport and encoded IDs. Contract-Neutral: Documentation and recipe checks align with existing history API behaviour; no runtime, storage, entitlement or authentication contract changes. Change-source: pulse-maintainer
9.0 KiB
Metrics History (Persistent)
Pulse persists metrics history to disk so trend views and sparklines survive restarts.
Storage Location
Metrics history is stored in a SQLite database named metrics.db under the Pulse data directory:
- systemd/LXC installs: typically
/etc/pulse/metrics.db - Docker/Kubernetes installs: typically
/data/metrics.db
Retention Model (Tiered)
Pulse keeps multiple resolutions of the same data, which allows longer history without storing raw samples forever:
- Raw (high-resolution, short window)
- Minute aggregates
- Hourly aggregates
- Daily aggregates
Default retention values (subject to change) are:
- Raw: 2 hours
- Minute: 24 hours
- Hourly: 7 days
- Daily: 90 days
- Rollups: every 15 minutes by default, bounded so rollups still run well before raw samples expire
Advanced: Retention Tuning
Do not change retention or polling merely because a chart is empty or writes are high. First follow Troubleshooting and preserve the original observations. Shorter retention removes history; slower polling also changes how quickly monitoring can notice a problem.
Tiered retention is stored in system.json in the Pulse data directory:
- systemd/LXC installs: typically
/etc/pulse/system.json - Docker/Kubernetes installs: typically
/data/system.json
Keys:
{
"metricsRetentionRawHours": 2,
"metricsRetentionMinuteHours": 24,
"metricsRetentionHourlyDays": 7,
"metricsRetentionDailyDays": 90
}
After changing these values, restart Pulse.
Advanced: Disk Write Tuning
This is a deliberate storage trade-off, not a repair for missing history or unexplained writes. Changing the path opens a different history store; it does not migrate the existing history. Keep the original durable data and establish the writer using bounded performance checks before changing storage.
Pulse keeps metrics history on disk by default. SSD-sensitive installs can move only the metrics SQLite database without moving secrets or general config:
PULSE_METRICS_DB_PATH=/dev/shm/pulse/metrics.db
Use a dedicated directory owned by the account running Pulse. Do not place the
database directly in a shared parent such as /tmp/metrics.db: Pulse secures
the database directory to mode 0700 and will reject a directory owned by
another user. On systemd installs, PrivateTmp=true also means a path below
/tmp is private to the service and will not appear in the host's /tmp.
The default systemd sandbox permits /dev/shm/pulse and Pulse can create that
subdirectory itself. A dedicated mount elsewhere, such as /mnt/ramdisk, also
needs an explicit writable-path grant:
sudo install -d -o pulse -g pulse -m 0700 /mnt/ramdisk/pulse
sudo systemctl edit pulse
Add this drop-in (replace pulse with the installed unit name if different):
[Service]
ReadWritePaths=/mnt/ramdisk/pulse
Then set PULSE_METRICS_DB_PATH=/mnt/ramdisk/pulse/metrics.db in
/etc/pulse/.env and restart Pulse. ProtectSystem=strict deliberately keeps
paths outside the unit's writable allowlist read-only even when Unix ownership
would otherwise permit writes.
For Docker, mount a tmpfs at the selected directory and keep /data on a
persistent volume:
services:
pulse:
environment:
PULSE_METRICS_DB_PATH: /metrics-tmpfs/metrics.db
tmpfs:
- /metrics-tmpfs:size=512m,uid=1000,gid=1000,mode=0700
Using tmpfs loses that history when the RAM-backed mount is discarded, for
example on a host reboot or container recreation. A service restart alone is
not guaranteed to clear it. Do not use tmpfs for /data: it also contains
config, encrypted credentials, tokens, and other state that must remain durable.
The aggregation cadence can also be lengthened when an install prefers fewer, larger rollup writes over more frequent smaller writes:
PULSE_METRICS_ROLLUP_INTERVAL=30m
Values below 5 minutes are ignored. Values longer than half of the raw-retention window are capped by the metrics store so raw samples are still rolled up before retention pruning can remove them.
API Access
Pulse exposes the persistent metrics store via:
GET /api/metrics-store/statsGET /api/metrics-store/history
These endpoints require authentication with the monitoring:read scope.
History Query Parameters
GET /api/metrics-store/history supports:
resourceType(required):node,storage,agent,disk,k8s,vm,system-container,oci-container,app-container, ordocker-hostresourceId(required): the source identifier used by that chart, not its display name (for Proxmox guests this is normallyinstance:node:vmid)metric(optional):cpu,memory,disk, etc. Omit to return all metrics for the resource.range(optional):1h,6h,12h,24h,1d,7d,30d,90d(default24h; duration strings also accepted)maxPoints(optional): Downsample to a target number of points
For a one-off check, open the affected resource's History panel in your signed-in
Pulse browser. If an API read is needed, use the path and query from that
panel's request on the same instance; do not extract its session cookie, use
Copy as cURL, or share a full network export. /api/metrics-store/stats
describes the store overall, not whether this resource has the requested metric.
For curl, follow API authentication to prepare the
private header file with only monitoring:read and define its pulse_api
helper in the same Bash session. It is example code, not an installed Pulse
command. The default helper runs on the Pulse host; for remote access,
follow that guide's HTTPS-origin and verified-CA instructions. Then make one
read-only request (curl 7.76 or later):
pulse_api GET '/api/metrics-store/history?resourceType=vm&resourceId=pve1%3Anode1%3A100&range=7d&metric=cpu'
Replace the example type and ID with the chart's values, URL-encoding each
query value (: becomes %3A; an ID's & must be %26, not another query
parameter). Do not substitute a hostname or strip a provider prefix to guess
an ID. The helper saves each response, including an error body, in a new
owner-only file and prints only the HTTP status and file location. Open that
file privately; do not print or post it. HTTP 401, 402, 403 and server errors,
redirects, or incomplete transport are not an empty history or zero usage.
There is no automatic retry. Never put a token in a command, URL or report;
share only the relevant manually redacted error and bounded observations.
With metric=cpu (or another explicit metric), the response has a points
array. Omitting metric instead returns a metrics object keyed by metric
name, not the same single-series shape. Inspect timestamps and source too:
store is persisted history; memory or live fallback does not establish
durable historical coverage. An HTTP 200 with no points proves neither zero
usage nor successful collection, and a non-zero current value does not prove
every historical interval is present.
License: Requests beyond Community's
7dfloor require the paidlong_term_metricsentitlement. Relay unlocks14d, Pro and legacy Pro+ unlock90d, and requests beyond the active tier's limit return402 Payment Required. Older examples:container,dockerHost,dockerContainer,guestanddockerare not currentresourceTypequery values. Use the chart's current type rather than a display label or internal storage name.
Troubleshooting
- Missing or stale history: retain the affected panel, metric, range,
collection time and current server/agent versions. Check the connection's
last normal poll or agent report in Settings → Infrastructure, without
running Diagnostics, a guest-agent probe or a restart during backup freeze/thaw.
Compare the panel's actual request, HTTP result, point timestamps and source.
A valid connection test, another resource's graph or the existence of
metrics.dbdoes not establish this metric's collection or display. - Access or query error: keep it separate from an empty result. Check the
selected organisation,
monitoring:readaccess, exact query type/ID and the requested range. A 402 is a history-range entitlement failure, not evidence that storage is empty; try an allowed range without buying another licence or resetting data. - Storage warning: inspect a bounded existing startup/runtime error and the deployment's persistent mount and service-account access privately. Follow the safe troubleshooting guide for high writes or space pressure. Do not delete history, edit a database, shorten retention, move data to tmpfs or re-enrol a host as a diagnostic fix.
- Report still needed: share the affected metric/range, versions, whether normal collection advances, and a manually redacted error; keep resource identities, response files and databases private. Follow Getting Help, not a full response dump.