The package formerly known as TACC Stats
A toolkit for monitoring resource usage on HPC systems at multiple levels of resolution.
The hpcperfstats package is split into two parts:
| Component | Build system | Role |
|---|---|---|
| monitor | Autotools | Online data collection and transmission in production |
| hpcperfstats | Python setuptools | Data curation and analysis (off-cluster) |
| Document | What it is for |
|---|---|
| MONITOR_VARIABLES.md | Canonical reference for monitor-reported variables: names, types, units, and semantics. Use this instead of any legacy “attributes definition” doc. |
| DEPLOY_CONCURRENCY_AND_NUMA.md | Thread/process limits vs PostgreSQL, total_cores / effective_cores, and pool sizing for web and pipeline. |
| design-document.md | As-built system design: architecture, data flow, components, contracts, and operations context. |
| measurements/ | Recorded ops measurements (for example node-daemon CPU overhead on Stampede3 SPR). |
| using-the-website-as-a-researcher.md | How to read the Django/React job UI—plots, metrics, and diagnostic themes—for HPC users and researchers. |
| TESTING.md | Test commands, CI, compose-backed workflows, Playwright/Vitest, and host vs container pytest notes. |
| OPERATOR_HOST_DATA_DEV_UNIQUENESS.md | host_data 5-col uniqueness: Stage 1 decompress (+ 02 PK normalize) and Stage 2 one-shot migrate for 0032 / compress_after 8d. |
Maintaining MONITOR_VARIABLES.md: the catalog is generated and augmented by maintainer scripts in the same folder: regenerate_monitor_variables_catalog.py, augment_monitor_variables_diagnostics.py.
REST API note: GET /api/jobs/{jid}/{type_name}/ (type detail) returns a Bokeh tplot_item (json_item payload) plus stats_data / schema. Legacy tscript / tdiv fields were removed; clients should embed only tplot_item via Bokeh embed_item.
API contract changes (2026-06):
GET /api/jobs/with filters that match zero jobs now returns HTTP 200 withnj: 0, an emptyjob_list, and afilter_summaryobject. Do not treat HTTP 404 as “no matches” for successful searches (404 is reserved for database/unavailable errors on this endpoint).GET /api/search/was removed; job-ID and host routing are handled in the React SPA. API clients should useGET /api/jobs/?jid=…(or open/machine/job/{jid}/in a browser) instead of the old search redirect endpoint.
Building and installing the hpcperfstatsd-3.0-1.el9.x86_64.rpm package (via monitor/hpcperfstats.spec) installs a systemd service hpcperfstats. Production sampling is typically configured for multi-minute intervals (often on the order of minutes), with samples at job start and end. On a Stampede3 Sapphire Rapids node (2026-08-14), a ten-minute pidstat observation of hpcperfstatsd showed sample-window peaks of at most 3.0% of one core and a 0.19% average over the full window; see docs/measurements/monitor_overhead_stampede3_spr_2026-08-14.md. The daemon hpcperfstatsd sends data to a RabbitMQ server over the administrative network. RabbitMQ must be installed and running on the server to receive data.
The hpcperfstats container orchestration sets up a Django/PostgreSQL ingest and archival stack plus a RabbitMQ server to receive data from the monitor on the nodes.
The monitor now uses a static-bundle build flow for packaging. The canonical path builds pinned static archives for libev, rabbitmq-c, and (on x86) LIKWID, then compiles hpcperfstatsd with --enable-all-static.
-
Install RPM build prerequisites (Rocky/EL-like systems):
sudo dnf install \ gcc gcc-c++ make autoconf automake libtool cmake pkgconfig \ systemd-rpm-macros gzip tar curl perl gawk pciutils rdma-core-devel \ rpm-build
On aarch64, install one of:
sudo dnf install datacenter-gpu-manager-4-devel # or sudo dnf install libdcgm-devel -
Prepare rpmbuild directories and source tarball (from
HPCPerfStats/monitor):./scripts/prepare_rpmbuild_dirs.sh
This script:
- creates
monitor/rpmbuild/{SPECS,SOURCES,BUILD,RPMS,SRPMS,BUILDROOT} - runs
scripts/build_static_bundle.sh --deps-onlyintomonitor/rpmbuild/static-prefix - runs
autoreconf -fi,./configure, andmake dist - copies
hpcperfstats-<version>.tar.gztorpmbuild/SOURCES
- creates
-
Build the RPM:
Use the
rpmbuildcommand printed byscripts/prepare_rpmbuild_dirs.sh. A typical script output is:rpmbuild -ba --define "_topdir $(pwd)/rpmbuild" rpmbuild/SPECS/hpcperfstats.spec -
Optional build options:
- Reuse existing static deps when already staged:
SKIP_DEPS=1 ./scripts/prepare_rpmbuild_dirs.sh
- Build dependencies + monitor binary directly (without rpmbuild staging):
./scripts/build_static_bundle.sh
- Build only pinned dependency archives:
./scripts/build_static_bundle.sh --deps-only
- Release-optimized monitor build:
./scripts/build_static_bundle.sh --release # equivalent to: HPC_BUNDLE_RELEASE_BUILD=1 ./scripts/build_static_bundle.sh - Pass extra configure args through bundle build (example):
./scripts/build_static_bundle.sh --disable-lustre
- Reuse existing static deps when already staged:
-
Configuration — after install, edit
/etc/hpcperfstats/hpcperfstats.conf:Field Description serverHostname or IP of the RabbitMQ server queueSystem/cluster name being monitored portRabbitMQ port (default 5672)freqSampling interval in seconds Example:
server localhost queue default port 5672 freq 600
Reload a running daemon with:
kill -HUP <pid>(or restart the service). -
Service control:
sudo systemctl start hpcperfstats sudo systemctl stop hpcperfstats sudo systemctl restart hpcperfstats
Job start/end: Notify hpcperfstats by writing to /var/run/stats_jobid on each node:
- Job start: echo the job ID into the file
- Job end: echo
-into the file
Do this from your scheduler’s prolog and epilog.
Accounting ingest (SLURM sacct): Use hpcperfstats-sacct-gen from the hpcperfstats-tools package. By default this command runs sacct for a date range and POSTs the results to the HPCPerfStats API ingest endpoint. Each successful POST also creates or overwrites a daily accounting file at {acct_path}/YYYY-MM-DD.txt (same pipe-delimited body Slurm returned). Alternatively, use -f DIR to write those YYYY-MM-DD.txt files locally (same format/naming) without calling the API; -f is mutually exclusive with --api-key, and DIR must already exist. The scheduled sync_acct.py job can reingest these files from disk. The API rejects payloads with fewer lines than the existing file for that date (HTTP 409) so a partial sacct export cannot replace a fuller one.
-
Install the tools (Python):
# Client CLIs live in-tree under hpcperfstats-tools/ (separate distribution). # From the HPCPerfStats git checkout: python3 -m pip install ./hpcperfstats-tools # Editable during development: # python3 -m pip install -e ./hpcperfstats-tools
-
Configure the API base URL:
Set
HPCPERFSTATS_TOOLS_INIto an INI file that contains[API] base_url(seehpcperfstats-tools/hpcperfstats-tools.ini.examplein the repo for a template). -
Run the ingest (requires a staff-capable API key):
# Ingest today only (default date range is today .. today, inclusive) hpcperfstats-sacct-gen --api-key YOUR_KEY # Ingest an explicit date range (both ends inclusive) hpcperfstats-sacct-gen 2024-01-01 2024-01-08 --api-key YOUR_KEY # Write daily YYYY-MM-DD.txt files locally instead of POSTing (DIR must exist; # mutually exclusive with --api-key) hpcperfstats-sacct-gen -f /path/to/accounting 2024-01-01 2024-01-08
Run-location and permissions requirements:
- Run
hpcperfstats-sacct-genon a host where Slurm’ssacctbinary exists and works (typically a Slurm login node). - Run it as a user that has the correct Slurm permissions to query the relevant jobs/accounts via
sacct. - API mode: the API key you pass with
--api-keymust be staff-capable for the ingest endpoint; thewebcontainer must have the shared data volume mounted at/hpcperfstats/(same aspipeline) so the API can write underacct_path(default/hpcperfstats/accounting). - File mode (
-f DIR):DIRmust already exist; no API URL or key is required. Place or sync the resulting.txtfiles wheresync_acct.pyexpects them (acct_path).
This is a container orchestration with Django/PostgreSQL, ingest/archival tools, and RabbitMQ. The steps below assume a Rocky Linux host.
-
Install Docker/Podman:
sudo dnf install docker git podman-compose
Redis / Linux kernel: The Compose stack runs Redis. Redis warns when
vm.overcommit_memoryis disabled; background RDB saves usefork(), and without overcommit the kernel can reject that fork even when RAM is sufficient. On the Linux machine whose kernel runs the Redis container (your Docker host on Rocky/Linux; not macOSsysctlwhen using Docker Desktop), enable overcommit:sudo sysctl -w vm.overcommit_memory=1
Persist across reboots:
echo 'vm.overcommit_memory = 1' | sudo tee /etc/sysctl.d/99-redis-overcommit.conf sudo sysctl --system
Alternatively, add
vm.overcommit_memory = 1to/etc/sysctl.confand reboot (or run thesysctl -wcommand once). On Docker Desktop for macOS, hostsysctldoes not apply to the inner Linux VM; tune there only if your setup exposes it, or treat the warning as informational for small dev stacks.Upgrading the Compose Redis server:
docker-compose.yamlpins Redis Open Source 8.10 (redis:8.10.0-alpine3.23) withmaxmemory 16gb,volatile-lru(Django cache keys keep TTL and remain evictable), and--io-threads 4/--io-threads-do-reads yes. Do not useallkeys-*for Django/listend cache keys.sync_timedbno longer stores queues or member maps in Redis; durable ingest/append state is.sync_timedb_job_store.jsonplus.sync_timedb_archive_members/. Redis has no persistence volume (appendonly no), so upgrading still clears cached pages/plots until they are recomputed. Size the host (or Colima) so Redis can use that cap alongside Postgresshm_size/shared_buffers. On the deployment host:docker compose pull redis docker compose up -d redis docker compose exec redis redis-cli INFO server | grep redis_version docker compose restart web pipeline
Expect
redis_version:8.8.x. Roll back by restoring the previous image tag indocker-compose.yaml, thenpull/up -d redisand restart web and pipeline. If[CACHE] redis_locationinhpcperfstats.inipoints at an external Redis host (not the Compose service), upgrade that server separately per Redis OSS standalone upgrade (supported path: 7.x → 8.x), then restart app services that use the cache. -
Enable container restart after reboot:
sudo systemctl enable podman-restart.service sudo systemctl start podman-restart.service -
Clone the repo:
git clone https://github.com/TACC/hpcperfstats.git cd hpcperfstats -
Compose settings (site bind volumes):
cp docker-compose.settings.yaml.example docker-compose.settings.yaml
docker-compose.settings.yamlis gitignored — treatdocker-compose.settings.yaml.exampleas the committed operator template. Basedocker-compose.yamlincludes the settings file automatically; you do not pass a second-ffor settings. Named volume definitions (binddevice:paths) live only in settings — base compose mounts them by name and must not declare empty volume stubs (podman-compose cannot merge null stubs with settings dicts). Aftercp, edit site-specificdevice:paths below; any new bind volumes or optional knobs must be added to.examplein the repo (seehpcperfstats/cursor-rules/docker-compose-settings-example-sync.mdc) so the next clone gets them.Edit
docker-compose.settings.yamland set at least:volumes → ssh_keys → device: host directory with pipeline SSH keys (permissions suitable for mount as/hpcperfstats/.ssh/)volumes → proxy_ssl_source → device: host directory containingfullchain.pemandprivkey.pem(flat PEM dir; default example uses/opt/certs). For Let's Encrypt, setdevice: /etc/letsencryptand uncomment the optionalservices.proxy.environmentblock in the settings example withHPCPERFSTATS_SSL_CERTS_REL=live/your.hostname— do not bind only thelive/hostnameleaf (archive symlinks break).
TLS PEMs are not baked into the image. The
proxyservice mountsproxy_ssl_sourceread-only at/mnt/ssl-source;proxy_entrypoint.shmaterializes real PEM files into/etc/ssl/hpcperfstatsbefore nginx starts (no.env, no manual resolve step). After cert renew,docker compose restart proxy(no image rebuild).Create host directories for every bind
device:beforeup(defaults from the example):sudo mkdir -p /data/hpcperfstats_data/site_data sudo mkdir -p /data/hpcperfstats_data/site_data/accounting sudo mkdir -p /data/hpcperfstats_data/site_data/archive sudo mkdir -p /data/hpcperfstats_data/site_data/daily_archive sudo mkdir -p /data/hpcperfstats_data/site_data/logs/current sudo mkdir -p /data/hpcperfstats_data/site_data/logs/log_archive sudo mkdir -p /data/hpcperfstats_data/rabbitmq sudo mkdir -p /data/hpcperfstats_site/staticfiles sudo mkdir -p /data/hpcperfstats_site/media sudo mkdir -p /data/hpcperfstats_db/pg15 # Also ensure the ssh_keys and proxy_ssl_source device paths you set above exist.The
hpcperfstatsdatabind maps to/hpcperfstats/in thepipelineandwebcontainers (for example/hpcperfstats/accounting,/hpcperfstats/archive,/hpcperfstats/daily_archive, and/hpcperfstats/logs/for cluster syslog).Daily monitor archive compression:
sync_timedbseals each day’sYYYY-MM-DD.tartoYYYY-MM-DD.tar.zstwith zstd. Defaults:archive_zstd_threads=0(-T0, niced seal/restore),ingest_zstd_threads=4(-T4, un-niced ingest/populate streams),archive_seal_parallel_workers=4(concurrent daily seals),nice/ionicedeprioritization so seal yields to web/db on shared hosts — seedocs/DEPLOY_CONCURRENCY_AND_NUMA.md(Archive zstd priority). Other[PIPELINE]keys:archive_zstd_level,archive_zstd_nice,archive_zstd_ionice_class,archive_zstd_ionice_levelinhpcperfstats.ini.example. When inspecting archives by hand, use for examplezstd -d -o YYYY-MM-DD.tar YYYY-MM-DD.tar.zst(legacy.tar.gzuseszstd -d --format=gzip). Before raisingarchive_zstd_levelabove 9 on production data, benchmark a representative daily tar on the pipeline host:zstd -b3 -e12 -T0 -S -- ./YYYY-MM-DD.tar.Daily archive member cache:
sync_timedbkeeps complete member maps in process memory and persists them under{archive_dir}/.sync_timedb_archive_members/. Dedicated[thread:populate-pool]workers stream sealed/tar archives into that store. Django[CACHE] redis_locationremains for the web/listend cache only. Ingest duplicate-check zstd runs at normal priority; janitor seal paths keep archivenice/ionice. Member maps are invalidated after tar append, dedupe, seal, and archive finalize — wipe day sidecars withscripts/invalidate_archive_members.py(never.sync_timedb_job_store.json). Seesync_archive_members_populate_*/sync_archive_members_wait_poll_secondsinhpcperfstats.ini.exampleanddocs/DEPLOY_CONCURRENCY_AND_NUMA.md.Ingest-first durability vs archive failure: with
sync_enable_ingest_first_durability_mode=yes(default), DB-ingested raw may be checkpointed as processed when archive append retries are exhausted (ingest_first_archive_abandoned_rawin logs; entry also in.sync_timedb_dead_letter.json). Raw is not deleted until a later pass verifies tar+sealed archive membership. Recovery: fix archive/tar issues, clear or replay dead-letter entries, and rescan — do not delete raw manually unless you have confirmed DB and archive parity.Startup snapshot wait: on large trees with
backlog, first pending rescan may wait up tosync_startup_snapshot_wait_seconds(default 300, min 120) for the janitor startup heavy pass to publishStartupArchiveScanCoordinatorsnapshot before single-flight fallback build. Grepsync_timedb: pending rescan begin,startup archive scan ready,janitor: discover_ready_day_close. BootDAY_CLOSEdiscover runs on[sync_timedb:thread:archive-janitor]only — ingest is not gated on day-close completion. Inspect async manifest.sync_timedb_async_day_close.jsonfor in-flight rows. These startup paths run only when the pipeline command includesbacklog(see below).Startup maintenance (
backlogonly): janitorreason=startupheavy snapshot + boot handoff for ingest catch-up, thenstartup ingest gate cleared; ingest may begin. CLIbacklogis ingest-only for day-close (current/ date-range own seal/verify/delete). Runs whensync_timedb.pyis invoked withbacklog. Date-window runs skip startup maintenance and begin ingest immediately.sync_timedb.pydate arguments: with no dates, ingest uses the last five calendar days through now. A singleYYYY-MM-DDlimits ingest to that day only. Two dates set an explicit start/end range. Prefixonceto exit after one idle rescan (for exampleonce 2024-01-15oronce backlog).Startup archive scan (single-flight): on large trees with
backlog, janitor startup maintenance publishes onebuild_archive_maintenance_snapshotviaStartupArchiveScanCoordinator— tunesync_startup_snapshot_wait_seconds(default 300, min 120) if logs show long waits beforestartup archive scan ready. Details:docs/DEPLOY_CONCURRENCY_AND_NUMA.md§ canonical startup archive scan.Cluster syslog (optional but typical on production clusters): the
pipelineservice publishes TCP and UDP port 514 on the Docker host. Compute or login nodes should forward syslog to<docker-host>:514(rsyslog examples: TCP@@host:514, UDP@host:514). Ingest runs insyslog-nginsidepipeline(notweb). Live files are written under/hpcperfstats/logs/current/as$HOST.$R_YEAR$R_MONTH$R_DAY.log(one file per host per calendar day). After local midnight,seal_syslog_daily(supervisord) packs the previous day’s files into/hpcperfstats/logs/log_archive/YYYY-MM-DD-syslog.tar.gzand removes the sealed sources. This mirrors the “currentvs sealed archive” story used for monitor data underarchive_dir/daily_archive.[SYSLOG]inhpcperfstats.ini: setallow_fromto a comma- or line-separated list of IPv4 CIDRs that may send remote syslog (for example10.0.0.0/8, 192.168.50.0/24). Ifallow_fromis blank or[SYSLOG]is omitted, all IPv4 sources are accepted (backward compatible). Changingallow_fromrequires a pipeline restart sorender_syslog_ng_generatedcan rewrite/var/lib/hpcperfstats-syslog/generated.conf(included bysyslog-ng.conf; not underservices-conf/so bind-mounts cannot remove it).listen_tcp/listen_udp(defaultyes) toggle listeners.Operational notes:
syslog-ngemits periodic internal stats (stats(freq(3600))inservices-conf/syslog-ng.conf); operators can runsyslog-ng-ctl stats(as root) insidepipelinefor counters. Monitor disk use on the data volume (logs/log_archivegrows with cluster size and retention). Troubleshooting: if packets reach the host but nothing is logged, check firewall rules, that traffic targets the published 514 on the host runningpipeline,allow_fromincludes the sender’s IPv4 address, and (for filenames) that forwarders preserve a sensible hostname/FQDN.Migrating from the old layout: if you previously used a separate host path for node logs (for example
/opt/hpcperfstats_logmounted at/hpcperfstatslog/), copy anycluster.loginto/data/hpcperfstats_data/site_data/logs/current/(or your editedhpcperfstatsdatadevice) if you need the history, then drop the extra compose volume. If you still have a localdocker-compose.app.yaml, delete it — the app overlay is obsolete; use settings + base compose only. -
Application config:
cp hpcperfstats.ini.example hpcperfstats.ini
In
hpcperfstats.iniunder[DEFAULT](install-required and site-wide):machine— cluster namehost_name_ext— FQDN of the clusterserver— FQDN of the host running the containersrestricted_queue_keywords- queues you want to filter out and prevent jobs in them from being displayedstaff_email_domain- the email domain of the institution/organization so authorized staff can see all jobstimezone- your machine's local timezonetotal_cores- CPU budget for app parallelism (omit to use code default 40; seedocs/DEPLOY_CONCURRENCY_AND_NUMA.md)secret_key- a random string- PostgreSQL connection:
engine_name,dbname,username,password,host,port(Compose useshost=dbin the image-built ini)
Optional tuning lives in other sections (see
hpcperfstats.ini.example):[PORTAL]— Gunicorn/Django web stack only:gunicorn_workers,parallel_db_prefetch_max,api_small_executor_max_workers,db_conn_max_age,db_statement_timeout_ms,db_idle_in_transaction_timeout_ms,cors_origin_scheme, and development-onlyseparate_test_login(default no)[PIPELINE]— ingest, archive, and metrics: required pathsacct_path,archive_dir,daily_archive_dir; optionalsync_*,metrics_*,archive_*,metrics_pool_processes, and related keys[RMQ],[OAUTH2], optional[CACHE],[SYSLOG],[XALT]— integration sections unchanged
For a fresh Docker install you typically edit
[DEFAULT]as above;[RMQ]and PostgreSQL defaults in the example are already wired for Compose. Do not change[RMQ]hostnames unless your RabbitMQ layout differs. For cluster syslog, add or edit the optional[SYSLOG]section (seehpcperfstats.ini.exampleand the compose step above).hpcperfstats.ini.examplelayout: each setting has a one-line#comment directly above it; optional tuning keys appear commented with defaults matchingconf_parser. Pipeline/archive behavior (zstd seal, DB-before-append gatesync_archive_require_db_ingestunder[PIPELINE], syslog allowlist) is described in the bullets above and indocs/DEPLOY_CONCURRENCY_AND_NUMA.md.Upgrading from an older ini layout: PostgreSQL keys moved from
[PORTAL]to[DEFAULT]; ingest/archive/metrics keys moved from[DEFAULT]/[PORTAL]to[PIPELINE]. Existing deployments keep working via legacy section fallbacks inconf_parseruntil you migrate keys into the new sections.PostgreSQL container (
docker-compose.yamldbservice):max_connectionsis 500 so overlapping Gunicorn workers, threaded API routes (job_plots,home_options,job_detailaux tasks), and pipeline pools rarely hittoo many clients. Parallel helpers:max_worker_processes=32,max_parallel_workers=24,max_parallel_workers_per_gather=4,max_parallel_maintenance_workers=2. Memory spikes are still controlled by a lowerwork_mem, smaller maintenance/autovacuum work mem,temp_buffers, and a slightly lowershared_buffers—see inline comments there. Summary plot aggregate prefetch uses at most two inner threads (seesummaryplot.compute_summary_aggregate_prefetch_pool_size) so nested thread pools do not stack against the shared API executor. If legitimate bulk jobs slow down, prefer raisingwork_memonly during batch windows or increasing thedbcontainermem_limitrather than unconstrained per-query memory.For memory-constrained deployments, start with the conservative baseline values documented in
hpcperfstats.ini.example, then scale up gradually after observing stable DB checkpoints and container RSS headroom.pipelinememory cap (docker-compose.yaml): on hosts with ~192 GiB RAM and no swap, setmem_limit: 128gandmemswap_limit: 128gon thepipelineservice (defaults in base compose; override indocker-compose.settings.yamlif needed) so ingest spikes cgroup-OOM inside the container before starvingdb/web.stop_grace_period(default 2m) allowssync_timedbto drain ondocker compose stop; override withHPCPERFSTATS_PIPELINE_STOP_GRACE. After changing limits or grace, recreate the container (docker compose up -d --force-recreate pipeline) and verifymemory.maxinside the cgroup is numeric (notmax). Pair with[PIPELINE]RSS knobs documented indocs/DEPLOY_CONCURRENCY_AND_NUMA.md§ OOM.Graceful stop (listend / sync_timedb / update_metrics): Prefer
docker compose stop -t 180 pipeline(or rebuild’s default 300s viaHPCPERFSTATS_PIPELINE_STOP_TIMEOUT) so wall clock exceeds sync_timedb’sSHUTDOWN_DRAIN_TIMEOUT_S(120s). Keep composestop_grace_period≥ 2m. Inservices-conf/supervisord.conf, the three Python programs setstopwaitsecs=130so supervisord does not SIGKILL after the default 10s wait. Approximate solo budgets: listend ~20s (15s DB pool join), update_metrics ~30–60s, sync_timedb up to 120s. Expected SIGTERM-driven exit 143 (seedocs/OPERATOR_SYNC_TIMEDB_STALL_VERIFY.md). Do notdocker kill/ SIGKILL unless wedged past grace. Startsleeplines in supervisord are boot stagger only; stop has no sleep.Python interpreters (image): web/gunicorn and helpers use GIL
python:3.14.7-trixie(/usr/local/bin/python3). Pipeline daemonslistend,sync_timedb, andupdate_metricsare baked onto free-threaded/opt/python3.14t/bin/python(no INI toggle).docker compose exec pipeline python3stays GIL for operator one-liners; greppable startup linespython_abi executable=… Py_GIL_DISABLED=…prove the live daemon ABI.sync_timedbingest/append/populate run as in-process threads; durable queues are the job-store sidecar.RabbitMQ memory cap (
docker-compose.yamlrabbitmqservice): defaultvm_memory_high_watermarkis 40% of detected host RAM, which can OOM the box under thousands of monitor publishers. Compose setsmem_limit: 96g/memswap_limit: 96gand mountsservices-conf/rabbitmq_vm_memory.conf(vm_memory_high_watermark.absolute = 80GiB) so publishers block ~16 GiB below the cgroup hard wall (avoids Erlangbinary_allocat the limit). Recreate the service after changing either value (docker compose up -d --force-recreate rabbitmq). On hosts with less than 96 GiB RAM, lower bothmem_limit/memswap_limitand the absolute watermark together. -
Supervisord and rsync:
Tracked
services-conf/supervisord.confis baked into the image — do not copy a supervisord.example. Thersync_dataprogram always runsrsync_data_wrapper.sh, which prefersrsync_data.shif present, otherwisersync_data.sh.example.Both scripts ship with a top-of-file guard (
sleep 43200,echo "rsync not yet configured",exit) so default deploys idle for 12 hours and never SSH/rsync to remote hosts. To enable site rsync: editrsync_data.sh(the wrapper-preferred file), remove those three guard lines, put your rsync commands in thewhile trueloop, and putsleep 43200at the end of the loop so a configured site does not tight-loop. Ensure SSH keys are configured in compose as documented for the pipeline service. -
Web server (nginx):
Committed
services-conf/nginx.confuses fixed in-container TLS paths (/etc/ssl/hpcperfstats/fullchain.pemandprivkey.pem). Cert PEMs are materialized at container start from theproxy_ssl_sourcesettings volume (mounted at/mnt/ssl-source) viaresolve_proxy_ssl_certs_dir.pyinproxy_entrypoint.sh. There is no Composessl_certsvolume, no build-time host/bind, noadditional_contexts/ manual resolve step, and no production.env. Do not edit TLS paths in nginx — setproxy_ssl_source.deviceindocker-compose.settings.yaml(and for Let's Encrypt, optionalHPCPERFSTATS_SSL_CERTS_RELin settings).After Let's Encrypt renew (or changing the TLS source path): restart
proxyonly —docker compose restart proxy. Never point productionproxy_ssl_source.deviceattests/fixtures/proxy-ssl../scripts/rebuild_pipeline.shdoes not restartproxy.Compose bind-mounts
./services-conf/nginx.confto/etc/nginx/http.d/default.confonproxy, and bind-mounts the shared snippets (nginx-static-files.conf,nginx-django-proxy-common.inc,nginx-edge-security-headers.inc,nginx-csp-no-active.inc,nginx-csp-django-html.inc) as the only runtime source for those files (they are not baked intoproxy.Dockerfile).The proxy image **
cp**snginx.confintodefault.confat build time for a non-Compose baseline, generateshps-proxy-allowed-hosts.incfrom[DEFAULT] server=, and shipsproxy_entrypoint.shplus the TLS/resolver helpers. Compose still replacesdefault.confwith the hostnginx.confmount. Runtimeproxy_entrypoint.shmaterializes TLS PEMs from/mnt/ssl-source, regenerates the OCSPresolverinclude from container/etc/resolv.conf, waits for SPA HTML under/srv/static/frontend/{machine,pub}/index.html, and may write private diagnostic CSP includes under/etc/nginx/(never under/srv/static). SPA shells carry their own hash CSP via HTML<meta http-equiv="Content-Security-Policy">(written at frontend export / SPA heal); nginx/machine/and/pub/locations must not send a competing hash CSP header. HTTP GETs for*.incunder/static/return 404. Nginx is the public authority for HSTS, framing, COOP, Permissions-Policy, Referrer-Policy, and CSP (hash-based for SPA shells; no-active for JSON/redirects). Certificates without an AIA OCSP URL will not staple; that must not take the site offline. Hostnames come from[DEFAULT] server=inhpcperfstats.ini(preferred in the build context, elsehpcperfstats.ini.example):parse_hpcperfstats_proxy_hosts.pyemits/etc/nginx/hps-proxy-allowed-hosts.inc, which the main configincludes forserver_name. Requests whoseHostheader does not match receive 404 on port 80; on port 443, unknown names get TLS handshake rejection (ssl_reject_handshake). Restartproxyafter changingserver=, TLS cert PEMs, ornginx.conf. Nodocker-compose.yamledits are required for TLS paths or hostnames (TLS paths are fixed; certs viaproxy_ssl_sourcesettings volume + entrypoint materialization).Static/media routing is split into a reusable include mounted at
services-conf/nginx-static-files.conf; nginx serves/static/and/media/directly, shells the SPA under/machine/and/pub/, and proxies only an explicit Django URL prefix list (sharedproxy_*directives inservices-conf/nginx-django-proxy-common.inc); every other path gets 404 from nginx. When you add a new top-level Django route, extend the allowlist innginx-static-files.confand keep it aligned with Django’s rooturlpatterns.Production: browsers must load
/static/*through theproxyservice (ports 80/443); nginx reads the samestaticfiles_datavolume mounted atSTATIC_ROOTonweb. Hittingweb:8000directly is not a supported way to load hashed SPA assets (Gunicorn does not implement/static/URL serving). For local parity with that layout, use full compose includingproxy, or runmanage.py runserver --nostaticand still obtain/static/via nginx rather than Django’s dev static handler. The proxy container is built fromservices-conf/proxy.Dockerfileand enables Brotli + gzip compression. -
Build and start:
sudo docker compose up --build -d
View logs (
docker-compose.yamluses thejson-filelogging driver withmax-size: 100mandmax-file: 3so stdout stays available to Compose on Docker and Podman and does not flood host syslog/journald):sudo docker compose logs
On first startup (or after updating the code), the
webcontainer runs Django migrations (manage.py migrateonly — schema changes ship as reviewed, committed migration files; production startup never runsmakemigrations) andcollectstaticsoSTATIC_ROOT(the volume nginx serves as/static/) is populated before Gunicorn starts. Aftercollectstatic, startup verifies SPA shells underSTATIC_ROOT/frontend/{machine,pub}/index.htmland compares a sha256 fingerprint of package vs volumemachine/index.html. If shells are missing (for example a Vite-era volume after upgrading to the Next export), or fingerprints differ after a from-scratch image rebuild whilestaticfiles_datastill holds an older Next tree, startup auto-heals by replacingSTATIC_ROOT/frontendfrom package static. Image buildcollectstaticalone cannot update the named volume (it masks the image layer). If the package image itself lacks the shells, web fail-closes — rebuild targethpcperfstats-full(primary) or run./scripts/rebuild_frontend.sh(SPA-only hot path).The compose DB service includes explicit PostgreSQL checkpoint/memory tuning (
max_connections,shared_buffers,work_mem,maintenance_work_mem,autovacuum_work_mem,checkpoint_*,min_wal_size,max_wal_size, and parallel-worker caps) plusshm_size. Keep these aligned with host RAM and service memory limits; tune upward one notch at a time only after confirming checkpoint stability and no OOM events. The pipeline service also setsshm_size: "10gb"so listend live-DB enqueue can use POSIX SharedMemory (hps-ld-*) without falling back under a default 64 MiB/dev/shm.If you change the codebase, bring the containers down, make your changes, and then rebuild and start the stack again. A full
docker compose up --build(or equivalent from-scratch image rebuild) plus recreatingwebis the primary way to land SPA fixes: startup fingerprint heal syncs the new package frontend intostaticfiles_data. Use./scripts/rebuild_frontend.shonly when you want an SPA-only refresh without rebuilding the image. SPA rebuilds and image builds bake the running git SHA into the staff actions menu (SITE_GIT_COMMIT). Image builds copy context.gitintofrontend-builderforgit rev-parse(then strip.gitfrom the runtime image afterCOPY . .). OptionalHPCPERFSTATS_GIT_COMMITbuild-arg / env still overrides when set. SPA-only./scripts/rebuild_frontend.shexports the host SHA the same way.
| Task | Command |
|---|---|
| Build and start container stack | sudo docker compose up --build -d |
| Stop and remove containers | sudo docker compose down |
| Rebuild SPA in running stack (optional hot path; no pipeline restart) | ./scripts/rebuild_frontend.sh |
| Rebuild web/pipeline image after Python-only changes (preserves live frontend, no npm) | ./scripts/rebuild_pipeline.sh |
| Temporary pipeline-only rebuild (no running web; recreate pipeline only) | ./scripts/rebuild_pipeline.sh --no-web |
| Rebuild just the app and keep persistent services running | docker compose stop -t 120 web pipeline proxy && docker compose up --build -d web pipeline && docker compose start proxy |
Restart proxy after cert renew or server= change |
docker compose restart proxy |
| View logs | sudo docker compose logs |
| PostgreSQL shell | docker compose exec db psql -h localhost -U hpcperfstats |
| Pipeline shell (data/processing) | docker compose exec pipeline su hpcperfstats |
| Get queues and message counts from rabbitmq | docker compose exec rabbitmq rabbitmqctl list_queues name messages consumers |
| RabbitMQ default queue type | Compose mounts services-conf/rabbitmq_default_queue_type.conf (default_queue_type = quorum). Classic queues OOM under thousands of monitor publisher connections. New durable monitor ingest queues are declared quorum; listend passive-attaches to an existing queue of any type (do not 406 x-queue-type against classic stampede3). Recreate the rabbitmq service after changing that conf file; existing queues keep their declared type — do not delete/recreate stampede3 (or other rmq_queue) as classic to clear listend INTERNAL_ERROR 541 “timed out consuming from quorum queue” (that is Ra consume-setup / listend cancel churn; reconnect with backoff). |
| RabbitMQ memory cap | Compose mem_limit / memswap_limit 96g plus services-conf/rabbitmq_vm_memory.conf (vm_memory_high_watermark.absolute = 80GiB headroom). Recreate rabbitmq after changing either. Inspect: docker compose exec rabbitmq rabbitmqctl status (Alarms + watermark; do not use rabbitmqctl list_alarms — absent on 4.3.x). |
| Admin Monitor RabbitMQ stats | Staff Admin Monitor → RabbitMQ statistics uses the management HTTP API on compose-internal http://rabbitmq:15672 (image rabbitmq:*-management-alpine). Port 15672 is not published on the host; loopback_users.guest = false in services-conf/rabbitmq_management.conf allows web→rabbitmq auth. |
- Comprehensive Resource Use Monitoring for HPC Systems with TACC Stats
- Understanding application and system performance through system-wide monitoring
- Amit Ruhela — aruhela@tacc.utexas.edu
- Stephen Lien Harrell — sharrell@tacc.utexas.edu
- Sangamithra Goutham — sgoutham@tacc.utexas.edu
- Chris Ramos — cramos@tacc.utexas.edu
John Hammond · R. Todd Evans · Bill Barth · Albert Lu · Junjie Li · John McCalpin
Copyright (c) 2011 University of Texas at Austin
This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful, but without any warranty; without even the implied warranty of merchantability or fitness for a particular purpose. See the GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.