# YABS node board A public board of [YABS](https://github.com/masonr/yet-another-bench-script) runs (disk and network benchmarks) from several nodes. One PHP file, one SQLite database, no framework. Board URL: PMSS serves this account's docroot (`~/www/public`) at two addresses: the panel path `https://le4-0-78-102caroline.pulsedmedia.com/public-vasilves/yabs/` and the root of a customer domain. Use the customer domain: the panel path names the account in every URL a stranger copies, and PMSS's proxy only forwards that path, so `/yabs/` on the panel hostname answers PMSS's own 404. ## Install on a node Requirements: `bash`, `curl`, a writable `$HOME`, and the right to edit your own crontab. No root. ```sh curl -fsSLO https://05877bc1b477317f.mcx.fi/yabs/probe.sh sh probe.sh ``` This does three things: 1. downloads `yabs.sh` from into `~/.yabs-probe/`; 2. registers the short hostname with the board and stores the node token in `~/.yabs-probe/conf` (mode 0600); 3. adds one crontab line: a monthly benchmark at a random time between 01:00 and 02:59. Re-running `probe.sh` is safe: it keeps the existing token and does not add a second cron line. A node can have two cron lines: the benchmark (`run.sh`) and a self-update (`probe.sh update`). Each command below changes one line, never the other: ```sh sh probe.sh # install: benchmark monthly if no line exists sh probe.sh schedule weekly # benchmark rate only sh probe.sh update hourly # self-update rate only sh probe.sh update off # drop the self-update line ``` `schedule` takes `daily`, `weekly` or `monthly`; `update` takes `hourly`, `daily`, `weekly`, `monthly` or `off`. The benchmark refuses `hourly`: one run takes 10 to 20 minutes, where an update is a single 7 KB download. A bare `sh probe.sh` never resets a rate and never adds a line that already exists. `schedule` draws a new random slot for the benchmark between 01:00 and 02:59, so several nodes installed the same day run at different times instead of all hitting their disks at once. That hour is the quiet part of the night: it stays clear of the 00:13 disk probe, the 03:00 backup tarball and the 04:00 configarr run. `update` never moves the benchmark. Move the window with `BENCH_FROM` and `BENCH_TO`: ```sh BENCH_FROM=5 BENCH_TO=9 sh probe.sh schedule daily # daily between 05:00 and 08:59 ``` ## History [history](https://05877bc1b477317f.mcx.fi/yabs/history) plots every stored run, one chart per metric (4k, IOPS, 64k, 512k, 1M, send, receive) and one colour per node. Hover a point for its date and value; the legend compares the last 1M value with the previous run. The page is server-rendered SVG, so it needs no JavaScript and no chart library. ## Read the source [source](https://05877bc1b477317f.mcx.fi/yabs/source) prints the exact bytes of every file this board serves, with a SHA-256 per file and a download link (`?f=probe.sh&raw=1`). Download a file, run `sha256sum` on it, compare with the hash on the page: a match proves the code you read is the code that runs. ## 3D hangar [racks](https://05877bc1b477317f.mcx.fi/yabs/racks) draws one hall per hostname family (`le4`, `le6`) and one rack per node. Rack height and colour come from the latest 1M fio throughput; hover a rack for its full numbers, drag to orbit, wheel to zoom. The page reads the same database, read-only, and adds no dependency: the projection is about 100 lines of canvas code. The hall is the hostname family, and its label adds the datacenter row that the first two letters of the name describe: `le` and `lt` are Lauttasaari, `ke` and `kt` are Kerava, and a second Kerava DC will be `k2`. Names older than that scheme show the family alone. Neither YABS nor the database records where a node really sits, so any grouping here follows the name. ## Use it ```sh sh probe.sh now # one run in the foreground, output on screen sh probe.sh status # running or not, elapsed, last log line sh probe.sh refresh # fetch the newest probe.sh and re-install ``` `refresh` fetches `${BOARD}probe.sh`, checks it with `sh -n`, stores it as `~/.yabs-probe/probe.sh` and re-runs it with no argument. The token, the benchmark rate and both cron lines stay as they are, so `refresh` never rewrites the crontab. It prints nothing when the copy it already has is current, which keeps the hourly cron run from mailing anything. A bare `sh probe.sh update` is the old spelling of `refresh`: the cron lines installed before 2026-10-27 call it that way. The update cron line is separate from the benchmark one, so changing one never disturbs the other. `now` and `status` install nothing: they never register, never add a cron line and never print the token. Run `sh probe.sh` once first; without a registration `now` stops with `not installed`. A run takes 10 to 20 minutes, mostly generating the fio test file. The cron run logs to `~/.yabs-probe/last.log` and writes its state to `~/.yabs-probe/progress`. Geekbench is skipped (`-g`): one score costs several GB of downloads. A node token can only post its own name. The board owner also holds an admin token that can post any name; you do not need it. ## What the board stores Per run: CPU model, cores, RAM and swap (GiB), disk size (GB), distribution, kernel, virtualisation type, GPU, fio 4k/64k/512k/1m in MB/s with IOPS, iperf3 send and receive in Mbit/s, and Geekbench single and multi when present. YABS cannot report a GPU, so `probe.sh` sends the `/dev/dri/renderD*` name. The HTML pages print every number the same way: a dot for the decimal mark and a space for the thousands separator, with two decimals for MB/s. So you read `6.69`, `43.70` and `10 644 IOPS`, and a bigger number is always the bigger figure, whatever its column. The JSON API returns raw numbers, not strings. Two more fields come from the node's kernel, because YABS reports neither: - **Disk media** (`virtio`, `HDD`, `SSD`, `NVMe`): `probe.sh` resolves the device under `$HOME` in `/sys/block`, reads its `queue/rotational` flag and its `lsblk` transport, and lets `ID_ATA_ROTATION_RATE_RPM` override the flag. NVMe settles it on the transport alone. A device under a `virtio` node in `/sys` gets `virtio`: QEMU leaves the rotational flag at 1 whatever the backing store is, so the flag says nothing there. The board never guesses the type from the measured IOPS: a stalled disk measures low, not slow. - **IO stall** (%): the share of the run window that a task spent blocked on IO, from the kernel's pressure stall information (`/proc/pressure/io`). The probe reads the cumulative `some` and `full` counters before and after the run, so the difference over the elapsed time is the average stall of that window. The disk averages are therefore weighted. A run measured while the disk was stalled understates the hardware, so it counts as `1 - stall` (floored at 0.25) instead of 1. The per-run tables keep the raw numbers, and a run with no stall reading weighs 1, so numbers from an older probe stay in the average. Averages cover the last 6 months, by run date. The header counts every stored run, and how many started today (Europe/Helsinki, the node's own clock). The chips above the node table filter the page without JavaScript. Tick one or more node names: the node table, the averages and the per-node run sections keep only those rows. Tick none and every node comes back. The header counters do not follow the chips, because the server counts them before the page is sent, and a reload clears the choice. ## API | Request | Answer | |---|---| | `POST ?do=register` with `{"name":"node","gpu":"renderD128"}` | 200 `{"ok":true,"token":"..."}`; the token is returned once | | `POST /` with header `X-Upload-Token: ` and the raw `yabs.sh -j` JSON | 200 `{"ok":true,"saved":["node"]}` | | `GET ?do=api` | JSON per node: hardware, run count, first and last run, averages | | `GET ?do=list` | JSON, the 500 newest runs | The node name comes from `?node=NAME` (or `?nodes=a,b` for one run posted under several names). A node token forces its own name, whatever `?node=` says. `?gpu=NAME` is optional and overrides the stored GPU for that run. | Code | Meaning | |---|---| | 400 | not JSON, no node name, or a name that is not hostname-shaped | | 401 | wrong or missing token | | 405 | `?do=register` called with GET | | 409 | this node already posted at this run time, or the name is taken | | 413 | body over 256 KB | | 422 | no disk or network number found, or a value outside the plausible range | | 507 | the registry is full (500 nodes) | ## Known limits - **A name is a label, not a proof.** Registration is open and uploads are not signed, so anyone can claim a free name and post numbers under it. - **The token travels in the URL** when you use `yabs.sh -s`, so it lands in the web server log. A node token can only write its own name. - **Numbers vary between runs.** The nodes share storage with other accounts, so a second run on the same box can differ by a large factor. - **IO stall is the node's own view.** Pressure stall information counts the tasks on that machine. A slow host disk makes its requests slow, so the stall rises, but the number also rises when the node itself is busy: it says the measurement was congested, not who caused it. - **The disk media comes from the node.** A virtual disk declares nothing: QEMU reports `rotational=1` for an SSD too, so the board shows `virtio` instead of `HDD`. A RAID array reports itself, not its members, and the probe does not read SMART, so a case like "flash on a SATA bridge" can be wrong. - **Rows from before 2026-10-27 carry neither stall nor `virt`.** They keep their numbers and weigh 1 in the averages. Their KVM rows were backfilled to `virtio` on 2026-10-27, because a virtual disk declares nothing either way; the rows from bare-metal nodes keep the flag verdict. - **No delete.** Ask the board owner to remove a row. - **The update runs what the board serves.** `sh probe.sh refresh` downloads `${BOARD}probe.sh` and runs it, and with the hourly line it does so from cron. Whoever controls that URL runs code on every node that installed the update. The download is checked with `sh -n` and rejected if it is not shell, but that only stops a broken file, not a hostile one. Use `sh probe.sh update off` on a node that must not trust the board. - **The token stays out of cron mail.** `probe.sh` prints it only on a terminal. ## For an AI agent Use this checklist when you are asked to benchmark a machine and publish the result: 1. Read `llms.txt` next to this file. It holds the same facts in one screen. 2. Check that you are on the machine to be measured: `hostname -s` must match the name you will register. Do not post a run executed elsewhere. 3. Install and register: `curl -fsSLO /probe.sh && sh probe.sh`. 4. Run once and wait for the result: `sh probe.sh now`. Expect 10 to 20 minutes; do not interrupt it. 5. Confirm the upload: `curl -s '?do=api' | head -c 400`. The node must appear with its hardware and at least one run. 6. If the node is already registered and you only need a fresh run, skip step 3 and call `~/.yabs-probe/run.sh`. Rules: do not re-register to work around a 409; the default is one run per month, and each node's owner decides its own rate. Report the exact node name and the run time with the numbers.