Warning, /swf-monitor/docs/POOL_REPORTER.md is written in an unsupported language. File is not indexed.
0001 # Batch pool reporter
0002
0003 A PanDA queue's workers wait in somebody's batch pool, and the wait is
0004 decided there rather than in PanDA. Two figures decide it: what share of
0005 the pool is already claimed, and how many jobs are idle ahead of ours.
0006 Neither is in any PanDA record. The pool's collector is the only source
0007 and it answers the HTCondor protocol, so a reporter asks it and posts
0008 the answer to the host-report ingest, on the pattern of the submit-host
0009 and server-host reporters ([OSG_SUBMIT_REPORTER.md](OSG_SUBMIT_REPORTER.md),
0010 [PANDA_SERVER_REPORTER.md](PANDA_SERVER_REPORTER.md)).
0011
0012 The occasion is a standing question: workers at the E1 queues have taken
0013 twenty minutes and more to start, and the answer each time has been that
0014 the shared pool was busy. The pool reading turns that from an answer
0015 somebody gives into a number the page shows.
0016
0017 ## What is read
0018
0019 `scripts/pool-reporter.py` queries a collector and delivers one record:
0020
0021 | Field | Meaning |
0022 |---|---|
0023 | `slots` | total, claimed, unclaimed, and the full state histogram |
0024 | `cores` | cores in the pool and cores claimed |
0025 | `claimed_fraction` | the fullness the queue pages show |
0026 | `queue` | across every schedd in the pool: jobs running, idle and held, with the busiest schedds named |
0027 | `machine_domains` | the domains of the pool's machines, bounded, which is how a queue is matched to its pool |
0028
0029 A query that fails is delivered as an error field rather than dropped.
0030
0031 The pool it reads by default is `bnl-scdf`, the SCDF shared pool
0032 (collector `condorspool01.sdcc.bnl.gov:9665`), which the E1 and BNL
0033 queues run in. The reporter takes `--pool` and `--collector`, so a
0034 second pool needs no new code.
0035
0036 ## Where it runs
0037
0038 On the monitor host, unlike the other reporters: the SCDF collector
0039 answers pandaserver02 directly, so no foreign host is involved. It runs
0040 in its own virtual environment, `/opt/swf-monitor/shared/pool-venv`,
0041 because the HTCondor bindings are large and belong to this reporter
0042 rather than to the web tier's dependencies. The web tier never speaks
0043 the HTCondor protocol and never talks to a collector in a request path.
0044
0045 Delivery is `POST /api/host-reports/bnl-scdf/` with a reporter token,
0046 the same ingest and the same cached-product store the other reporters
0047 use, so the record carries its own freshness and an absent record is a
0048 fact a page can state.
0049
0050 ## Which pool a queue draws on
0051
0052 Nobody declares it, and a declared table would go stale. It is derived
0053 from the queue's own finished jobs: a job record names the worker node
0054 it ran on, the node's domain says whose pool it was in, and a queue
0055 whose running jobs are overwhelmingly on one pool's domains draws on
0056 that pool. The OSG queues are matched more directly still — the submit
0057 host reports which queues it submits.
0058
0059 Two rules keep the derivation honest. Only jobs that started are
0060 counted, because a job that never ran carries the harvester host rather
0061 than a worker node, and those rows would put every queue in whatever
0062 pool the harvester sits in. And a queue is attributed only on a strong
0063 majority, so a stray node elsewhere does not carry it into the wrong
0064 pool. A queue whose workers run in a pool we cannot see — NERSC, GREX,
0065 the Google cloud queue — is attributed to nothing and its pool fields
0066 are absent, which is the honest rendering.
0067
0068 The map is a cached product rebuilt every six hours
0069 ([CACHED_PRODUCTS.md](CACHED_PRODUCTS.md)); where a queue's workers run
0070 changes over days, not minutes.
0071
0072 ## Where it shows
0073
0074 - The ePIC queues page: the pool's fullness as a percentage and the
0075 idle jobs ahead, per queue, sortable.
0076 - The queue detail page: a Batch pool card with the claimed share,
0077 slots and cores, the queue ahead across the pool's schedds, and the
0078 reading's age in the header. The attribution evidence stays in the
0079 record (the queue page showed it until 2026-09-20; it is the
0080 developer's provenance, not the operator's information).
0081
0082 Both read the stored record. Neither builds it.
0083
0084 ## Pools we cannot read
0085
0086 A pool whose scheduler answers no collector of ours, NERSC Perlmutter
0087 first, is read by its own jobs. The pilot launch production operations
0088 publishes for the site (swf-epicprod docs/NERSC_PERLMUTTER.md, the pool
0089 sample) runs `squeue` and `sinfo` on the worker node before the
0090 container starts, once per worker, and writes `pool-sample.json`
0091 (schema `pool-sample/1`): the machine's and the allocation's running and
0092 pending jobs and nodes, the allocation's oldest pending age, and the
0093 partition's node states. The payload carries the sample in its report
0094 (EPICPROD_PAYLOAD.md item 14), the pilot ships the report as job
0095 metadata, and the monitor takes the newest sample among the finished
0096 jobs of the pool's queues in the last three days as the pool's reading,
0097 a cached product (`pool_sample:v1:<pool>`, five minutes) so no page
0098 reads the PanDA record. Such a pool is declared in `pools.py` with
0099 `source: sample` and the queue-name prefix that identifies its queues;
0100 attribution is by name, since nothing else runs there.
0101
0102 The reading renders in the same card and columns as a collector's:
0103 claimed fraction and slots become allocated and total nodes of the
0104 partition; the queue ahead is the machine's pending jobs and the nodes
0105 they ask for; a row the collector pools do not have shows our own jobs
0106 as the batch system counts them. The card names the sample's node and
0107 the job that carried it, and its age is the sample's, not the store's.
0108 Until a job of the pool's queues has carried a sample, the card says so.
0109 The sample arrives with the jobs, so a pool that has stopped giving us
0110 nodes stops reporting: the Workers card beside it is what says how long
0111 we have been waiting.
0112
0113 ## The queue's own workers
0114
0115 The pool reading says how full the pool is; the other half of the wait
0116 question is what that costs at one queue, and that half is in a record
0117 every queue has whether or not its pool is one we can see: the
0118 harvester's worker table in the PanDA database. `monitor_app/workers.py`
0119 reads it into a cached product per queue
0120 (`harvester_workers:v1:<queue>`, two minutes) and the queue detail page
0121 shows it as a Workers card beside the Batch pool card: the workers
0122 submitted and not started, with the age of the oldest; the workers
0123 running; the workers started in the last day and in the last hour; the
0124 median and 90th-percentile wait from the harvester's submission to the
0125 batch start over the workers that started in the last day; and the
0126 workers that ended in the day by their final status.
0127
0128 At a pool we cannot read, NERSC Perlmutter above all, this card is the
0129 whole reading: on 2026-09-19, 100 workers waiting, the oldest for 21
0130 hours, a median wait of ten hours, which is the answer to "is something
0131 wrong at Perlmutter" (nothing of ours: the machine is full).
0132
0133 ## Install
0134
0135 1. The virtual environment with the HTCondor bindings.
0136 2. The environment file at mode 600 naming `SWF_MONITOR_URL` and
0137 `SWF_REPORT_TOKEN`.
0138 3. A cron entry on the reporting interval.
0139
0140 ## Related
0141
0142 - [OSG_SUBMIT_REPORTER.md](OSG_SUBMIT_REPORTER.md) — the submit host's
0143 reporter, whose pool block covers the OSG side.
0144 - [SNAPPER_PLATFORM.md](SNAPPER_PLATFORM.md) — the platform component
0145 the reading joins, so pool fullness is recorded history rather than
0146 only a current number.
0147 - [CACHED_PRODUCTS.md](CACHED_PRODUCTS.md) — the store the record and
0148 the attribution map live in.