Skip to content

A CPU-bound workload

This page answers what a single-threaded bottleneck looks like in the per-core data, and why the device total hides it. The workload was provoked on purpose.

Measured on RB5009UG+S+ · 4 × 1.4 GHz Cortex-A72 · RouterOS 7.24.2 · Linux 5.6.3 · · agent at 10 Hz in an ephemeral privileged container

With no configuration change and no external dependency — a console loop that burns one core and ends on its own:

:local i 0; :while ($i < 4000000) do={ :set i ($i + 1) }

It ran 58.8 s.

In 5-second buckets. The agent ships raw tick deltas; the percentages below were computed from them afterwards.

t busy% per-core busy% ctxt/s temp MHz
0 22.3 31.8 7.4 40.4 9.6 9192 36.5 1400
1 30.9 11.7 11.4 89.7 10.6 11325 37.0 1400
2 29.9 14.3 9.4 19.6 76.0 9476 37.0 1400
3 29.1 7.4 9.4 17.6 81.6 10805 37.0 1400
4 29.3 3.1 9.4 4.9 99.8 9257 37.2 1400
8 28.4 2.2 7.0 4.6 99.8 11947 37.2 1400
11 33.0 11.8 11.2 9.0 99.7 12222 37.3 1400
  • Total ~29 % is one core of four. A device total is a trap; always look at the per-core row. “29 % CPU” here means “one core is saturated and three are idle”, which for a single-threaded bottleneck is the whole story.
  • The scheduler took ~20 s to settle. Buckets 0–3 show the work moving between cores (40 % → 90 % → 76 % → 82 %) before pinning on core 3 at 99.8 %. If you sample at 1 s or slower you see a vague plateau; at 10 Hz you see the migration. This is the resolution earning its cost.
  • Temperature followed: 36.5 → 37.3 °C. One saturated core is worth ~0.8 °C on this passively cooled board. Small, but it tracks, and it is read from /sys/class/thermal with no API call.
  • Frequency stayed flat at 1400 MHz. On this device that is a configuration, not an observation: the owner has pinned the clock to maximum, which /system/routerboard/settings reports as Warning: cpu not running at default frequency. On a device that scales, the freq_khz field is where you would see a busy tick that was earned slowly. The agent reads the frequency every tick and stores freq_khz only when it changes, plus a heartbeat once every 60 s, so on a pinned clock you get one row a minute, and on a scaling clock you get every step that lasts at least one tick.

Before you conclude anything from a CPU number, confirm mikroscope_slipped_total is 0. A slipped tick is one whose read finished after the next tick was due, and the sampler’s own accounting is then the first thing you should distrust.

Provoked on purpose ·

  • A device total near 100 % divided by the core count — here ~29 % on four cores — with one per-core column at 99.7–99.8 %.
  • Before it settles, the load visibly hopping between cores for tens of seconds.
  • A temperature rise of under a degree that tracks the load.