From 6fa43f1e0825c09f75d9bf1e5e20ca7d1bf80570 Mon Sep 17 00:00:00 2001 From: pgdalmeida Date: Thu, 8 Oct 2026 17:21:58 +0200 Subject: [PATCH] Slow down, then pause, the scan when the tower computer runs hot or cannot keep up --- CMakeLists.txt | 1 + config/config.example.ini | 3 + config/hardware.example.ini | 36 ++ docs/configuration.md | 33 ++ docs/modules-reference.md | 5 +- docs/mqtt-api.md | 38 ++ docs/thermal-throttling.md | 247 +++++++++ include/fgc/Application.h | 1 + include/fgc/CaptureScheduler.h | 30 +- include/fgc/ChannelTopics.h | 3 + include/fgc/Config.h | 6 + include/fgc/HostMetrics.h | 64 ++- include/fgc/IControlChannel.h | 27 + include/fgc/ImagePipeline.h | 21 + include/fgc/MqttControlChannel.h | 21 + include/fgc/ThermalGuard.h | 196 +++++++ include/fgc/mock/NullControlChannel.h | 9 + include/fgc/ui/UiSnapshot.h | 50 +- main.cpp | 7 + src/camera/ImagePipeline.cpp | 55 ++ src/core/Application.cpp | 292 ++++++++++- src/core/CaptureScheduler.cpp | 36 +- src/core/ChannelTopics.cpp | 29 ++ src/core/Config.cpp | 48 ++ src/core/HelpText.cpp | 3 + src/core/HostMetrics.cpp | 148 +++++- src/core/OperatingMode.cpp | 2 +- src/core/ThermalGuard.cpp | 321 ++++++++++++ src/mqtt/MqttControlChannel.cpp | 86 +++- src/ui/TuiUi.cpp | 85 ++- src/ui/UiSnapshot.cpp | 9 + tests/CMakeLists.txt | 1 + tests/test_imagepipeline.cpp | 16 + tests/test_scheduler.cpp | 106 ++++ tests/test_thermalguard.cpp | 483 ++++++++++++++++++ .../cap1200_flat.jsonl | 62 +++ .../cap1200_paced.jsonl | 122 +++++ .../cap1600_flat.jsonl | 62 +++ .../cap1600_paced.jsonl | 121 +++++ .../cap800_flat.jsonl | 62 +++ .../cap800_paced.jsonl | 122 +++++ .../cap800load2_paced.jsonl | 122 +++++ .../guard_run.log | 229 +++++++++ .../guard_run.sh | 57 +++ .../idle_a.jsonl | 61 +++ .../idle_after1200.jsonl | 61 +++ .../idle_after1600.jsonl | 61 +++ .../idle_after800.jsonl | 61 +++ .../idle_afterload.jsonl | 61 +++ .../idle_afterpower.jsonl | 61 +++ .../idle_start.jsonl | 61 +++ .../load2_paced.jsonl | 121 +++++ .../matrix.log | 53 ++ .../matrix.sh | 78 +++ .../none_flat.jsonl | 61 +++ .../none_paced.jsonl | 121 +++++ .../power6w_flat.jsonl | 62 +++ .../power6w_paced.jsonl | 121 +++++ .../stream_cost.sh | 34 ++ tools/thermal-sim/cap-cpu.sh | 38 ++ tools/thermal-sim/cap-power.sh | 31 ++ tools/thermal-sim/fake-temp.sh | 77 +++ tools/thermal-sim/heat-load.sh | 26 + 63 files changed, 4685 insertions(+), 42 deletions(-) create mode 100644 docs/thermal-throttling.md create mode 100644 include/fgc/ThermalGuard.h create mode 100644 src/core/ThermalGuard.cpp create mode 100644 tests/test_thermalguard.cpp create mode 100644 tools/camera-bench/results-2026-10-08-tower111-throttle/cap1200_flat.jsonl create mode 100644 tools/camera-bench/results-2026-10-08-tower111-throttle/cap1200_paced.jsonl create mode 100644 tools/camera-bench/results-2026-10-08-tower111-throttle/cap1600_flat.jsonl create mode 100644 tools/camera-bench/results-2026-10-08-tower111-throttle/cap1600_paced.jsonl create mode 100644 tools/camera-bench/results-2026-10-08-tower111-throttle/cap800_flat.jsonl create mode 100644 tools/camera-bench/results-2026-10-08-tower111-throttle/cap800_paced.jsonl create mode 100644 tools/camera-bench/results-2026-10-08-tower111-throttle/cap800load2_paced.jsonl create mode 100644 tools/camera-bench/results-2026-10-08-tower111-throttle/guard_run.log create mode 100644 tools/camera-bench/results-2026-10-08-tower111-throttle/guard_run.sh create mode 100644 tools/camera-bench/results-2026-10-08-tower111-throttle/idle_a.jsonl create mode 100644 tools/camera-bench/results-2026-10-08-tower111-throttle/idle_after1200.jsonl create mode 100644 tools/camera-bench/results-2026-10-08-tower111-throttle/idle_after1600.jsonl create mode 100644 tools/camera-bench/results-2026-10-08-tower111-throttle/idle_after800.jsonl create mode 100644 tools/camera-bench/results-2026-10-08-tower111-throttle/idle_afterload.jsonl create mode 100644 tools/camera-bench/results-2026-10-08-tower111-throttle/idle_afterpower.jsonl create mode 100644 tools/camera-bench/results-2026-10-08-tower111-throttle/idle_start.jsonl create mode 100644 tools/camera-bench/results-2026-10-08-tower111-throttle/load2_paced.jsonl create mode 100644 tools/camera-bench/results-2026-10-08-tower111-throttle/matrix.log create mode 100644 tools/camera-bench/results-2026-10-08-tower111-throttle/matrix.sh create mode 100644 tools/camera-bench/results-2026-10-08-tower111-throttle/none_flat.jsonl create mode 100644 tools/camera-bench/results-2026-10-08-tower111-throttle/none_paced.jsonl create mode 100644 tools/camera-bench/results-2026-10-08-tower111-throttle/power6w_flat.jsonl create mode 100644 tools/camera-bench/results-2026-10-08-tower111-throttle/power6w_paced.jsonl create mode 100644 tools/camera-bench/results-2026-10-08-tower111-throttle/stream_cost.sh create mode 100755 tools/thermal-sim/cap-cpu.sh create mode 100755 tools/thermal-sim/cap-power.sh create mode 100755 tools/thermal-sim/fake-temp.sh create mode 100755 tools/thermal-sim/heat-load.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index 5e1c8d6..041855a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -95,6 +95,7 @@ add_library(fgc_core STATIC src/core/ChannelTopics.cpp src/core/RawImage.cpp src/core/SettleRule.cpp + src/core/ThermalGuard.cpp src/sensors/I2cBus.cpp src/sensors/Sht41Protocol.cpp src/sensors/McuEnvSensor.cpp diff --git a/config/config.example.ini b/config/config.example.ini index 7694c13..fd1bf5d 100644 --- a/config/config.example.ini +++ b/config/config.example.ini @@ -273,6 +273,9 @@ enable_env = false ; The mocks are NOT legacy: substituting a simulated device is a choice about ; this run, not a statement about what is bolted to the tower, so it stays here. mock_camera = false +; The simulated camera's frames: small (640x480 colour) or full (the real +; camera's 5328x3040 12-bit raw - saving then loads the CPU as on a tower). +;mock_camera_size = small mock_serial = false mock_imu = false mock_env = false diff --git a/config/hardware.example.ini b/config/hardware.example.ini index 2dd931e..fc75b3d 100644 --- a/config/hardware.example.ini +++ b/config/hardware.example.ini @@ -76,6 +76,42 @@ save_queue_max = 4 ; A tower with no hardware.ini at all is never reconciled, whatever ; strict_presence says: gaining this file must not change how a tower behaves. +[Thermal] +; The CPU guard: in summer the sun and the gimbal heat this computer until its +; CPU slows itself down. The guard spaces pictures out, then pauses them (gimbal +; parked, camera stream stopped) until it cools - it never stops the program and +; never touches picture quality. Every key may be left out; these are the +; defaults. See docs/thermal-throttling.md. +;enabled = true +; Slowed: pictures at least slow_factor times further apart (more if saving +; needs it: 1.5 x the save time per worker), from slow_c, or when the CPU +; throttles, or when saving is the bottleneck - the scan waited for it +; held_share_pct of the last minute, or held_slow_s in one go. Back to full pace +; once below slow_c - hysteresis_c, keeping up, and saving fast enough for it, +; for calm_s. +;slow_c = 85 +;hysteresis_c = 5 +;slow_factor = 2 +;calm_s = 120 +;held_share_pct = 25 +;held_slow_s = 30 +; Paused: at pause_c for pause_after_s, or saving still the bottleneck +; held_pause_s after slowing (or one wait that long). Resumes (slowed) after +; min_pause_s once at resume_c or below and nothing is left to save. +;pause_c = 92 +;pause_after_s = 20 +;held_pause_s = 120 +;resume_c = 75 +;min_pause_s = 300 +; Throttling is also judged from the clock - below clock_low_pct of the chip's +; top speed while at least busy_high_pct busy - which catches the power-limit +; slow-downs the kernel does not count. +;clock_low_pct = 50 +;busy_high_pct = 50 +;sample_ms = 2000 +; A fake /sys + /proc tree for a simulation (tools/thermal-sim/fake-temp.sh). +;sysfs_root = / + ; --- Where to find it ----------------------------------------------------- ; Device bindings. These follow the hardware, not the tower, so they belong here ; rather than in config.ini. diff --git a/docs/configuration.md b/docs/configuration.md index 16c72df..63a921b 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -84,6 +84,7 @@ Parsed and validated by `ConfigLoader` ([src/core/Config.cpp](../src/core/Config | `Features` | `enable_serial` | bool | `true` | (reserved) | | `Features` | `enable_imu` | bool | `false` | **Legacy.** Derived from `[Hardware] imu` wherever that is declared; kept only for towers without a `hardware.ini`. A flag contradicting the declaration is warned about and ignored | | `Features` | `mock_camera` | bool | `false` | Use the simulated camera | +| `Features` | `mock_camera_size` | `small`/`full` | `small` | The simulated camera's frames: `small` = 640×480 colour; `full` = the real camera's 5328×3040 12-bit raw, so saving costs the CPU what it does on a tower (throttling simulations) | | `Features` | `mock_serial` | bool | `false` | Use the simulated motor controller | | `Features` | `mock_imu` | bool | `false` | Use the simulated IMU instead of the MTi | | `Features` | `enable_env` | bool | `false` | **Legacy.** Derived from `[Hardware] env` wherever that is declared; same rule as `enable_imu` | @@ -541,6 +542,35 @@ measured), both edges included - so `mode pan 90 30` takes six pictures, 75° to 105°. It is a property of the camera and lens, so it lives in `hardware.ini`. Must be above 0. See [mqtt-api.md](mqtt-api.md#modes-auto-pan-manual). +### When the computer runs hot: `[Thermal]` + +The CPU guard ([ThermalGuard.h](../include/fgc/ThermalGuard.h)) reads the CPU's +temperature, clock, throttle counters and busy time every `sample_ms` and paces +the scan to them. It never stops the program, never changes the mode and never +touches picture quality. Per computer, so normally in `hardware.ini` +(`config.ini` works too). Every key is optional. + +| Key | Default | Meaning | +|---|---|---| +| `enabled` | `true` | `false`: full pace however hot (readings and throttling log go on) | +| `slow_c` | 85 | Slowed from this CPU temperature (°C) | +| `hysteresis_c` | 5 | Back to normal needs `slow_c - hysteresis_c` | +| `slow_factor` | 2 | Slowed = pictures at least this many times further apart (1..10); further still when saving needs it (1.5 × the save time per worker) | +| `calm_s` | 120 | Slowed → normal once all clear this long, and the normal pace fits what saving takes | +| `held_share_pct` | 25 | Slowed when the scan spent this share of the last minute waiting for saving | +| `held_slow_s` | 30 | Slowed when saving has kept the scan waiting this long in one go | +| `pause_c` | 92 | Paused once at this temperature for `pause_after_s` | +| `pause_after_s` | 20 | | +| `held_pause_s` | 120 | Paused when saving has kept the scan waiting this long in one go, or is still the bottleneck this long after slowing down | +| `resume_c` | 75 | A pause ends at or below this, after `min_pause_s`, with nothing left to save | +| `min_pause_s` | 300 | | +| `clock_low_pct` / `busy_high_pct` | 50 / 50 | Throttling judged from the clock: below this share of the top clock while at least this busy | +| `sample_ms` | 2000 | 500..60000 | +| `sysfs_root` | `/` | A fake `/sys` + `/proc` tree for a simulation ([tools/thermal-sim](../tools/thermal-sim/)) | + +Must hold: `resume_c < slow_c < pause_c`, `held_slow_s <= held_pause_s`. The +whole story, and how to simulate it, is in [thermal-throttling.md](thermal-throttling.md). + ### The legacy towers: a DHT11 and a fan on the motor MCU Four towers carry an ambient DHT11 and an enclosure fan wired to the Leonardo @@ -683,6 +713,7 @@ Parsed by Boost.Program_options ([main.cpp](../main.cpp)). Flags override `[Feat | `-c, --config ` | Explicit config file path | | `--no-mqtt` | Disable MQTT (use the null channel) | | `--mock-camera` | Use the simulated camera | +| `--mock-camera-full` | Simulated camera with tower-size 12-bit frames (implies `--mock-camera`) | | `--mock-serial` | Use the simulated motor controller | | `--mock-env` | Use the simulated ambient temp/humidity sensor (implies `[Features] enable_env`) | | `--degraded` | Start even though declared hardware is missing (field triage; overrides `strict_presence` and says so loudly in the log) | @@ -725,6 +756,8 @@ Handled in `Application::Impl::handleCommand`. | `snapshot` | One image now, through the scan's settle-and-trigger path (manual mode, homed) | | `capture [on\|off]` | Continuous capture where the gimbal is aimed: one image per `image_interval` since the last, each once the gimbal has stood still for `settle_wait_ms`. Manual mode, homed; ends on any mode change, homing, heading setup or test. Bare: report | | `camera reset` | Reboot the cameras and put them back on their profiles | +| `thermal` | The CPU's temperature, clock, load and throttling, and whether the scan is slowed or paused for it. Any mode | +| `thermal resume` / `thermal off` / `thermal on` | Back to full pace now (the rules then apply afresh) / turn the CPU guard off or on until restart. Any mode | | `test camera [frames\|dark\|bright]` | Check the camera delivers frames and responds to exposure/gain | | `test [ []] []` | Run the hardware self-test suite (see below) | | `test list` / `test baseline` | List profiles / pin the last report as the comparison baseline | diff --git a/docs/modules-reference.md b/docs/modules-reference.md index 9b03f5c..8c85c5c 100644 --- a/docs/modules-reference.md +++ b/docs/modules-reference.md @@ -26,7 +26,7 @@ Per-file reference for the refactored tree, plus the shared data structures. | [include/fgc/DiagParser.h](../include/fgc/DiagParser.h), [src/core/DiagParser.cpp](../src/core/DiagParser.cpp) | `parseDiag` firmware `DG` self-test stream → `DiagResult` (per-axis tests, PASS/FAIL); `formatDiag` | | [include/fgc/TestRunner.h](../include/fgc/TestRunner.h), [src/core/TestRunner.cpp](../src/core/TestRunner.cpp) | The `test` command: resolves `test [ []]` to a set of leaf tests, knows which need a homed gimbal (the `test hardware` leaves deliberately do not), and runs them on a worker thread with live progress, handing back a `TestReport` | | [include/fgc/TestReport.h](../include/fgc/TestReport.h), [src/core/TestReport.cpp](../src/core/TestReport.cpp) | A self-test run's result as plain data: formats a human-readable text report that `parseTestReport` reads back, so a run can be compared with the baseline or the previous one | -| [include/fgc/HostMetrics.h](../include/fgc/HostMetrics.h), [src/core/HostMetrics.cpp](../src/core/HostMetrics.cpp) | Tower computer health for `test host`: CPU temperature, disk, memory and load. Pure parsers split from the live `/sys` and `/proc` readers so they unit-test anywhere; readers report `ok = false` rather than throw | +| [include/fgc/HostMetrics.h](../include/fgc/HostMetrics.h), [src/core/HostMetrics.cpp](../src/core/HostMetrics.cpp) | Tower computer health for `test host` and the CPU guard: CPU temperature, clock, kernel throttle counters, busy time, disk, memory and load. Every `/sys` and `/proc` path sits under a `root`, so a fake tree stands in for a simulation. Pure parsers split from the live `/sys` and `/proc` readers so they unit-test anywhere; readers report `ok = false` rather than throw | | [include/fgc/Calibration.h](../include/fgc/Calibration.h), [src/core/Calibration.cpp](../src/core/Calibration.cpp) | `linearFit` (least-squares, R²) + `circularMeanDeg` for the IMU-referenced calibration | | [include/fgc/PitchSetup.h](../include/fgc/PitchSetup.h), [src/core/PitchSetup.cpp](../src/core/PitchSetup.cpp) | `gimbal pitch setup` worker thread: home-if-needed → pitch sweep dwelling on the gravity-referenced IMU pitch → least-squares fit → park level; tunable via `CalibParams`; exposes `progress()`/`report()`/`takeResult()`. Yaw is not touched | | [include/fgc/HeadingFit.h](../include/fgc/HeadingFit.h), [src/core/HeadingFit.cpp](../src/core/HeadingFit.cpp) | `gimbal heading fit`: least-squares fit of the yaw map from operator sightings of landmarks with known true bearings. Sorts by encoder position, unwraps bearings so a set spanning north fits one line, and refuses a baseline under 10° (two points always fit exactly, so a short baseline looks perfect and is wrong). No IMU involved | @@ -41,7 +41,8 @@ Per-file reference for the refactored tree, plus the shared data structures. | [include/fgc/ChannelHistory.h](../include/fgc/ChannelHistory.h), [src/core/ChannelHistory.cpp](../src/core/ChannelHistory.cpp) | Ring buffer of recently published messages (time, topic, payload, outcome) plus the delivery counters — sent, failed, connects, losses, last status. Feeds the expanded Connectivity view so a publish can be confirmed without a broker-side subscriber | | [include/fgc/RemoteCommands.h](../include/fgc/RemoteCommands.h), [src/core/RemoteCommands.cpp](../src/core/RemoteCommands.cpp) | Console commands that arrive over MQTT: run through the same handler as typed ones, on the control thread, and answered on `CommandReply` with every line they logged. Retained messages are never run, a repeated id is answered again rather than re-run, each is audited in the log, and a reply that depends on the firmware (`gimbal dump`) waits for its answer | | [include/fgc/Hash.h](../include/fgc/Hash.h), [src/core/Hash.cpp](../src/core/Hash.cpp) | FNV-1a 64 over a string or a file, with an algorithm-prefixed label. Change detection for the profile records, deliberately not a crypto hash | -| [include/fgc/CaptureScheduler.h](../include/fgc/CaptureScheduler.h), [src/core/CaptureScheduler.cpp](../src/core/CaptureScheduler.cpp) | Capture state machine over the interfaces: move, standstill, settle wait (+ `SettleRule`), the rest of the interval since the last frame, keep; holds while saving is behind; injectable clock; `setGeometry` adopts a recalibration | +| [include/fgc/CaptureScheduler.h](../include/fgc/CaptureScheduler.h), [src/core/CaptureScheduler.cpp](../src/core/CaptureScheduler.cpp) | Capture state machine over the interfaces: move, standstill, settle wait (+ `SettleRule`), the rest of the interval since the last frame, keep; holds while saving is behind; slowed (`setIntervalScale`) or paused (`setPaused`) by the CPU guard; injectable clock; `setGeometry` adopts a recalibration | +| [include/fgc/ThermalGuard.h](../include/fgc/ThermalGuard.h), [src/core/ThermalGuard.cpp](../src/core/ThermalGuard.cpp) | The CPU guard, pure: fed one host sample at a time (temperature, clock, throttle counters, busy %, whether saving holds the scan), it says normal / slowed / paused and returns the log lines; `ThrottleWatch` logs throttling as it starts, once a minute while it lasts, and when it ends. Never stops the program or changes the mode ([thermal-throttling.md](thermal-throttling.md)) | | [include/fgc/SettleRule.h](../include/fgc/SettleRule.h), [src/core/SettleRule.cpp](../src/core/SettleRule.cpp) | The `settle_rule = brightness` keep rule, pure: fed one frame's brightness, exposure and gain at a time, says when the image has settled (in the camera's target band and steady for 3 frames) or is pinned at the auto-exposure limits | | [include/fgc/RawImage.h](../include/fgc/RawImage.h), [src/core/RawImage.cpp](../src/core/RawImage.cpp) | Pixel layouts the program accepts (`Mono8`, `RGB8`, `BayerRG8`, `BayerRG12p`), their sizes and bit depths, `unpack12p`, and `sampleBrightness` - the cheap per-frame sample the keep rule reads | | [include/fgc/CameraStall.h](../include/fgc/CameraStall.h), [src/core/CameraStall.cpp](../src/core/CameraStall.cpp) | When a camera stream delivers nothing new: wait, restart the stream, or reset the camera. Pure policy, scaled by the exposure and frame period so a slow frame is not taken for a stall | diff --git a/docs/mqtt-api.md b/docs/mqtt-api.md index a47f35d..d50fd6c 100644 --- a/docs/mqtt-api.md +++ b/docs/mqtt-api.md @@ -78,6 +78,7 @@ setup, or while a test or calibration runs. |-------|------|---------|--------------| | `GGS/FWT//StatusCode` | At startup, on every mode change, and again on every reconnect | the mode: `"0"` auto, `"1"` pan, `"2"` manual | 1 / retained | | `GGS/FWT//CamEvent` | After each image is saved | JSON object (below) | 1 / retained | +| `GGS/FWT//Health` | When the CPU guard changes state or throttling starts/ends, once a minute, after a `thermal` command, and on every reconnect | JSON object (below) | 1 / retained | StatusCode says what the tower **is doing**, not what it was last asked to do: it is published by the mode change itself, whatever caused it. Being retained, @@ -134,6 +135,42 @@ Both `hdg` and `pit` are sampled **when the frame arrives**, not when its JPEG X finishes, so a slow encode or a backed-up queue cannot attribute a later pose to earlier pixels. +CamEvent and Health leave through a small queue on their own thread, so a slow link +never holds an image save or the control loop. If 64 are waiting, the oldest is +dropped (and shows as dropped in the connectivity view). + +### Health payload + +The tower computer's heat and load, and whether the scan is slowed or paused for it +(see [thermal-throttling.md](thermal-throttling.md)). The mode in StatusCode does +**not** change: a paused auto scan is still `"0"`, and carries on by itself once cool. + +```json +{"fwt":"t121","state":"paused","reason":"CPU at 93 °C for 20 s","guard":true, + "cpu_c":93.0,"cpu_mhz":1188,"cpu_busy_pct":41,"throttling":true, + "throttle_events_last_min":14,"enclosure_c":48.2,"saved_last_min":0, + "save_ms_mean":null,"save_ms_max":null,"held_s":0,"interval_s":10.0, + "time":1759917600000} +``` + +| Field | Type | Meaning | +|-------|------|---------| +| `state` | string | `normal`, `slowed` (pictures `slow_factor`× further apart) or `paused` (gimbal parked, camera stream stopped) | +| `reason` | string | Why not normal, in words; `""` when normal | +| `guard` | bool | `false` after `thermal off`: full pace whatever the heat | +| `cpu_c` | number/null | Hottest CPU thermal zone, °C | +| `cpu_mhz` | number/null | Mean clock over the cores | +| `cpu_busy_pct` | number/null | How busy the CPU was since the last sample | +| `throttling` | bool | The CPU is slowing itself (kernel counters, or a low clock while busy) | +| `throttle_events_last_min` | int | Kernel throttle events counted in the last minute | +| `enclosure_c` | number/null | The ambient sensor (SHT41/DHT11), when there is one | +| `saved_last_min`, `save_ms_mean`, `save_ms_max` | int, number/null | Pictures saved in the last minute and how long each took | +| `held_s` | int | How long the scan has been waiting for saving to catch up | +| `interval_s` | number | Time between pictures now | +| `time` | int | Unix epoch ms | + +A missing reading is `null`, never `0`. + ## Remote commands: `Command` / `CommandReply` For development and maintenance: **any console command** - everything that can @@ -239,6 +276,7 @@ subscribe: GGS/FWT//Command (any console command: JSON {id, cmd} publish: GGS/FWT//StatusCode (the mode: 0 auto, 1 pan, 2 manual) GGS/FWT//CamEvent (JSON: fwt, cam, hdg×10, pit×10, time-ms) + GGS/FWT//Health (JSON: CPU guard state, CPU °C/MHz/busy, throttling, saving) GGS/FWT//CommandReply (JSON: id, cmd, level, lines, truncated - not retained) ``` diff --git a/docs/thermal-throttling.md b/docs/thermal-throttling.md new file mode 100644 index 0000000..cad5529 --- /dev/null +++ b/docs/thermal-throttling.md @@ -0,0 +1,247 @@ +# When the tower computer runs hot + +In summer the sun and the gimbal heat the LattePanda until its CPU slows itself +down (throttles). Since the 12-bit raw pipeline, the tower also de-Bayers and +encodes every picture, ~1.4 CPU-seconds each +([raw12-pipeline-measurements.md](raw12-pipeline-measurements.md)). This page +says what happens then, what the program does about it, and how to simulate it. + +**The rule:** heat or overload only ever **pauses** the program - it never +exits, never changes the mode, never touches picture quality or resolution. It +slows the scan, then parks it, and carries on by itself once the computer has +cooled. An exit would leave the tower dark until someone restarted it; a paused +tower costs some pictures and comes back alone. + +## What was already safe, and what was not + +| | Before | Now | +|---|---|---| +| Saving falls behind | The scan holds at its waypoint (`save_queue_max`), so frames never pile up in memory | Same, and a long hold now slows, then pauses the scan | +| CPU temperature | Read only by `test host` | Read every 2 s; on the Sensors panel, in the log, on MQTT `Health` | +| Throttling | Invisible (`ThermalInfo::throttled` was never set) | Logged when it starts, once a minute while it lasts, and when it ends | +| A hot, struggling CPU | Kept at full pace: the camera streaming, the encoder busy, the modem starved | Pictures spaced out (to fit the measured save time), then paused with the camera stream off | +| Slow 5G link | Each picture's CamEvent held the save worker up to 3 s | CamEvent and Health leave through their own queue and thread | +| Save workers | Same priority as the control loop, serial, IMU, MQTT | nice +5 (libjxl's threads inherit it) | +| Camera streaming, nothing saved | ~40% of one core on tower 111, always | Stopped while paused (buffer reuse was tried and measured no gain, so dropped) | + +## The CPU guard + +[ThermalGuard.h](../include/fgc/ThermalGuard.h). Every `sample_ms` (2 s) the +control loop reads the hottest thermal zone, the cores' clock, the kernel's +throttle counters and how busy the CPU was, plus whether saving is holding the +scan. The guard is pure - a reading in, a state and log lines out - so every +rule is unit-tested ([test_thermalguard.cpp](../tests/test_thermalguard.cpp)). + +``` + CPU >= slow_c (85), or throttling, or the scan waited for saving + 25% of the last minute (or 30 s in one go) + NORMAL -----------------------------------------------------------> SLOWED + (as <-- 120 s all clear: CPU < 80, no throttling, waiting under pictures 2x further + configured) 12%, and saving fast enough for the normal pace --------- apart, or 1.5x the + | save time if longer + | CPU >= pause_c (92) for 20 s, or one 120 s wait for saving, | + | or saving still the bottleneck 120 s after slowing | + +--------------------------------> PAUSED <-------------------------+ + gimbal parked where it is, + camera stream stopped, saving drains + | + | after 300 s, CPU <= resume_c (75) + v and nothing left to save + SLOWED +``` + +- **Slowed** stretches the interval (`CaptureScheduler::setIntervalScale`): + at least `slow_factor`, and as far as 1.5 × the measured save time per worker + when that is longer - so a CPU that saves slowly runs about two-thirds busy + instead of flat out. Fewer pictures per minute is less encoding and less motor + work. It returns to the normal pace only once that pace fits what saving + takes, so a saver that is simply too slow does not flap between the two. +- **Saving as the bottleneck** is judged by the share of each minute the scan + waits for it, not only by one long wait: a saver a little too slow holds the + scan 2-3 s at every waypoint and never once for long, yet keeps the CPU + pegged (see the results below). +- **Paused** finishes the waypoint under way, then starts no new move + (`setPaused`); once parked, the camera stream stops, so the CPU all but idles. + A snapshot an operator asks for is still taken. +- **Throttling** counts two ways: the kernel's `thermal_throttle` counters rising + (heat), or the clock below 50% of the chip's top while at least 50% busy, two + samples running. The second catches the power-limit slow-downs the counters + never count - and a clock capped by `cap-cpu.sh`. +- Missing readings (no thermal zone, no cpufreq) are left out of the rules, never + read as 0 °C or as hot. +- The mode does not change: StatusCode still says `0` for a paused auto scan. + Health says `paused`, and why. + +All thresholds are `[Thermal]` keys, normally in `hardware.ini` +([configuration.md](configuration.md#when-the-computer-runs-hot-thermal)). + +### Seeing it + +- **Sensors panel** (`s`): a *LattePanda CPU* group - CPU temp, clock, load, + throttled - with a `HOT` / `THROTTLED` / `SLOWED` / `PAUSED` badge. The + expanded view adds the per-core clocks and any cap, load averages, throttle + counts, save time per picture, and the guard's state and reason. +- **Log**, in plain words: + ``` + CPU is slowing itself to stay cool: 94 °C, running at 1.2 GHz of 3.6 GHz + CPU still slowed: 14 times in the last minute, 94 °C, 1.1 GHz + CPU at 94 °C for 20 s: pausing pictures for at least 5 min, until the CPU is at 75 °C or below and saving has caught up + camera stream stopped while pictures are paused + CPU at 72 °C: pictures resume, one picture every 10.0 s instead of 5.0 until it has been calm for 2 min + Saving kept the scan waiting 27% of the last minute: one picture every 6.9 s instead of 2.0 until saving is fast enough for the normal pace, for 2 min + CPU no longer slowed: 72 °C + saving: 28 pictures in the last minute, 653 ms each on average (longest 817 ms) + ``` +- **MQTT** `GGS/FWT//Health`, retained ([mqtt-api.md](mqtt-api.md#health-payload)). +- **Console / MQTT Command**: `thermal` (readings and pace), `thermal resume`, + `thermal off|on`. Allowed in every mode. + +## Simulating it + +[tools/thermal-sim/](../tools/thermal-sim/): + +| Script | What it does | Root | +|---|---|---| +| `fake-temp.sh init\| [throttle]\|clock \|ramp ...` | A fake `/sys` tree (busy % stays the real `/proc/stat`); the program reads it with `[Thermal] sysfs_root = ` | no | +| `cap-cpu.sh \|restore` | Caps every core's clock (`scaling_max_freq`) | yes | +| `cap-power.sh \|restore` | Caps package power (RAPL), like the 6 W TDP mode | yes | +| `heat-load.sh ` | Busy cores competing with the program | no | + +With `--mock-camera-full` (or `[Features] mock_camera_size = full`) the mock +camera delivers the real camera's 5328×3040 12-bit raw frames, so the program +saves - and loads the CPU - as on a tower, on any machine: + +``` +fire_gimbal_control -c sim/config.ini --mock-camera-full --mock-serial --no-mqtt +``` + +A CPU that cannot keep up, without root: `systemd-run --user --scope -p CPUQuota=50% fire_gimbal_control ...`. + +## Results on the desk (2026-10-08) + +i5-7300U (4 threads, 3.5 GHz - near the IOTA's N150), mock gimbal, tower-size +12-bit mock frames, `image_interval = 2`, short guard timings for the test +(`pause_after_s 10`, `calm_s 20`, `min_pause_s 30`, `held_pause_s 60`). A save +took 0.65-0.85 s. + +**A. Heat (fake temperatures, real program).** `fake-temp.sh` walked the CPU +70 → 88 → 94 (+ throttle events) → 72 °C while the scan ran: + +| Time | CPU | What the program did | Picture spacing | +|---|---|---|---| +| 0:00 | 70 °C | normal | 2.1-2.2 s | +| +0:01 | 88 °C | slowed (within one sample) | 4.2 s | +| +0:30 | 94 °C, throttling | "CPU is slowing itself to stay cool" at once; paused after 11 s, camera stream off 2 s later once the waypoint finished | none for 29 s | +| +1:11 | 72 °C | resumed, still slowed, after the 30 s minimum pause | 4.2 s | +| +2:09 | 72 °C | "CPU no longer slowed" one quiet minute after the last throttle event | 4.2 s | +| +2:51 | 72 °C | back to the normal pace after 20 s calm | 2.1 s | + +No waypoint skipped, no overrun warnings, clean shutdown, 68 pictures. + +**B. A CPU that cannot keep up (no heat).** The program held to half of one core +(`CPUQuota=50%`), so a save took 4.5-5 s against the 2 s interval: + +- *Before* the share rule: the save gate held the scan 2-3 s at every waypoint, + pictures came every ~4.6 s, and the CPU sat **pegged for all 5 minutes**. The + guard never acted - no single wait reached 30 s - and the log said "saving is + behind / caught up" twice per picture. +- *After:* "Saving kept the scan waiting 27% of the last minute: one picture every + 6.9 s instead of 2.0" after the first minute; from then on no holds at all and + the spacing followed the save time (7-10 s), stable for the rest of the run - + no flapping back to the 2 s pace it could not hold, no pause needed. The hold + warning is now said once, not at every waypoint. + +The dashboard was checked in a real terminal: the Sensors panel's *LattePanda +CPU* group (88.0 °C, 2.8 GHz, 37 %, "yes, 2x last min", `SLOWED`) and the +expanded view's CPU section. + +## Results on tower 111 (3 Delta, N5105, 2026-10-08) + +Ambient: the enclosure sensor read 21 °C. The tower program was stopped for the +benchmark and replaced by the test build for the run; the tower's own build and +config were not touched. Raw data: `tools/camera-bench/results-2026-10-08-tower111-throttle/`. + +### What a throttled Delta can sustain (`raw12_bench`) + +The synthetic 5328×3040 12-bit frame (`make_synthetic.py`), edge-aware +de-Bayer, JPEG XL distance 1.0 effort 3, the tower's 1 worker × 4 threads. Each +condition: 60 s flat out, then 120 s at one picture per 2 s. `cap-cpu.sh` and +`cap-power.sh` set the limits; `stress-ng --cpu 2` is "2 busy cores". + +| Condition | Save time | Most per second | CPU-s / picture | Pile-up after 2 min at one per 2 s | Temp mean / max | +|---|---|---|---|---|---| +| none (2.9 GHz) | 1.0 s | 0.98 | 2.3 | 0 | 63 / 80 °C | +| clock capped 1.6 GHz | 1.7 s | 0.58 | 4.0 | 0 | 57 / 59 °C | +| clock capped 1.2 GHz | 2.2 s | 0.45 | 5.2 | 6 frames (latency to 14.6 s) | 56 / 58 °C | +| clock capped 0.8 GHz | 3.3 s | 0.30 | 7.6 | 23 frames (latency to 51 s) | 54 / 56 °C | +| power capped 6 W | 1.0 s | 0.87 | 2.4-2.7 | 0 | 61 / 74 °C | +| 2 busy cores | 1.4 s | - | 2.3 | 0 | 79 / **88** °C | +| 0.8 GHz + 2 busy cores | 4.4 s | 0.22 | 6.9 | 33 frames (latency to 69 s) | 52 / 54 °C | + +- At the tower's real pace, a picture every **5 s**, it keeps up in every case: + at 0.8 GHz saving takes two-thirds of the time, and with 2 busy cores on top + (4.4 s per save) it is on the edge - where the guard's headroom rule would + space pictures to ~6.6 s. +- The bare benchmark has no brake, so a slow CPU piles frames up (23-33 in two + minutes, ~24 MB each). In the program the save gate stops that and the guard + then slows the scan. +- The 6 W power cap hardly matters: saving draws only 6-8 W of the 10 W limit. +- The hottest run was competing load, not saving: 88 °C with 2 busy cores. +- **The kernel never counted a throttle event** in any run (all counters 0) - + 21 °C was not hot enough. The counters exist on this kernel, so real heat + will be counted; the clock caps here are seen by the clock rule instead. +- Tower 111's camera is a 1800 U-1240c, 4024×3036: its real saves are 0.64-0.68 s, + about two-thirds of the synthetic frame's. + +### The program on the tower, CPU capped to 0.8 GHz with 2 busy cores + +The test build with the tower's own config, camera, gimbal and local MQTT +broker: 3 min normal, 6 min capped + loaded, 3 min restored. + +| Time | What happened | Pictures | Save time | +|---|---|---|---| +| 16:57 | homed, auto scan, Health `normal`, CPU 51 °C | one per 5 s | 0.64-0.68 s | +| 16:59:32 | clock capped, 2 cores busy | | | +| 16:59:36 | "CPU is running slow while busy (judged from its clock...): 53 °C, running at 0.8 GHz of 2.9 GHz"; Health `slowed` | one per 10 s | 3.6-3.8 s | +| every minute | "CPU still slowed: clock low all minute" | | | +| 17:05:32 | cap lifted | | 0.64-0.72 s | +| 17:06:31 | "CPU no longer slowed" | one per 10 s | | +| 17:08:31 | "back to the normal picture pace"; Health `normal` | one per 5 s | | + +The scan never waited for saving (`held_s` 0 throughout), the CPU ran 66-91% +busy while capped and loaded, and the program shut down cleanly; 34 pictures, +13 Health messages on `GGS/FWT/dev/Health`. + +### What the camera stream costs on its own + +Manual mode, one snapshot to start the stream, then 60 s of the program's own +CPU time with nothing saved (`stream_cost.sh`): + +| Build | CPU while streaming | +|---|---| +| installed (main) | 39.2% of one core | +| reusing the frame buffer instead of a fresh 24 MB per frame | 40.8% of one core | + +Reusing the buffer made no difference, so it was dropped. The stream itself +costs **about 40% of one core - a tenth of the computer - all the time it runs**, +whether or not pictures are saved: the program copies every frame it streams. +That is why a pause stops the stream. A lower streaming frame rate, or copying +only the frame that is kept, would cut it while scanning too; both touch the +fragile camera stream, so they are left for a separate change. + +### Found on the way + +- `x86_pkg_temp` is not the only thermal zone on the towers: tower 111 also lists + the Wi-Fi card (`iwlwifi_1`) and `acpitz`. The guard now reads the CPU package + zone, so a hot Wi-Fi card cannot pause the scan; `test host` still reports the + hottest zone. +- Tower 111's camera refuses its profile at every start since 2026-10-07 17:22 + ("applying profile 'standard' ... failed (error -39)"), with the installed + build as with this one, and captures anyway. Unrelated to this work; worth a + look. + +## Still to do + +- Tower 121 (the IOTA), when cleared: the same matrix and program run. +- A real hot day: the clock caps stand in for throttling; the kernel counters + have not yet seen any. diff --git a/include/fgc/Application.h b/include/fgc/Application.h index 5826f65..672186e 100644 --- a/include/fgc/Application.h +++ b/include/fgc/Application.h @@ -21,6 +21,7 @@ struct RuntimeOptions { // Tri-state feature overrides (unset => use the config value). std::optional use_mqtt; std::optional mock_camera; + std::optional mock_camera_full; // --mock-camera-full: tower-size 12-bit frames std::optional mock_serial; std::optional mock_env; // implies [Features] enable_env std::optional use_tui; // --tui / --no-tui; unset => [UI] enable_tui diff --git a/include/fgc/CaptureScheduler.h b/include/fgc/CaptureScheduler.h index 06e251b..f891a71 100644 --- a/include/fgc/CaptureScheduler.h +++ b/include/fgc/CaptureScheduler.h @@ -86,6 +86,27 @@ public: void setSaveGate(std::function can_save) { can_save_ = std::move(can_save); } // True while the scan is held for saving to catch up. bool holdingForSave() const { return holding_; } + // All the time spent held for saving since construction, the current hold + // included (ms). Only rises; the CPU guard takes its share of each minute. + long long heldForSaveTotalMs() const; + + // The CPU guard (ThermalGuard) pacing the scan to the computer's heat. + // Interval scale: pictures this many times further apart (>= 1; 1 = as + // configured). Takes effect from the next picture. + void setIntervalScale(double scale); + double intervalScale() const { return interval_scale_; } + // Paused: the waypoint under way finishes, then no new move starts, and in + // manual no continuous-capture frame is kept - the gimbal stays where it is. + // A requested snapshot is still taken: an operator asked for it. Resuming + // carries on from the next waypoint with no overrun warning for the wait. + void setPaused(bool paused); + bool paused() const { return paused_; } + // Paused and nothing under way: what the camera stream may stop for. + bool pausedAtRest() const { + return paused_ && !moving_ && !snapshot_pending_ && continuous_hold_.due_ms == 0; + } + // The current interval between pictures, scale included. 0 = never. + double intervalMs() const { return interval_ms_; } // Per-camera-profile timing (see CameraProfile): how long to wait after standstill // for the exposure to settle, and how long to keep retrying before abandoning @@ -166,8 +187,15 @@ private: std::function now_ms_; std::function can_save_; bool holding_ = false; + long long hold_started_ms_ = 0; + long long held_total_ms_ = 0; + long long last_hold_end_ms_ = -1; + bool hold_logged_ = false; double image_rate_; - double interval_ms_; // 1000 / image_rate_; 0 = never + double base_interval_ms_; // 1000 / image_rate_; 0 = never + double interval_ms_; // base_interval_ms_ x interval_scale_ + double interval_scale_ = 1.0; + bool paused_ = false; long long timer_start_ = 0; // the last frame kept or given up, or the mode start Mode mode_ = Mode::Manual; diff --git a/include/fgc/ChannelTopics.h b/include/fgc/ChannelTopics.h index 01891c9..547bb05 100644 --- a/include/fgc/ChannelTopics.h +++ b/include/fgc/ChannelTopics.h @@ -17,6 +17,8 @@ namespace fgc { // The tower's mode, out (0 auto, 1 pan, 2 manual - see OperatingMode.h). inline constexpr const char* kTopicStatusCode = "StatusCode"; inline constexpr const char* kTopicCamEvent = "CamEvent"; +// The computer's heat and load, and whether the scan is slowed or paused for it. +inline constexpr const char* kTopicHealth = "Health"; // Any console command, in; its answer, out. See docs/mqtt-api.md. inline constexpr const char* kTopicCommand = "Command"; inline constexpr const char* kTopicCommandReply = "CommandReply"; @@ -25,6 +27,7 @@ std::string towerTopic(const std::string& tower, const std::string& leaf); // Payload builders. One definition each, used by both channels. std::string camEventJson(const CamEvent& e); +std::string healthJson(const HealthReport& h); // A Command payload: {"id":"...","cmd":"..."} (a flat JSON object of strings; // other keys ignored), or a bare command line with no id - for testing from a diff --git a/include/fgc/Config.h b/include/fgc/Config.h index 759ce34..e3d4ed4 100644 --- a/include/fgc/Config.h +++ b/include/fgc/Config.h @@ -3,6 +3,7 @@ #include "fgc/ImuMount.h" #include "fgc/Geometry.h" #include "fgc/OperatingMode.h" +#include "fgc/ThermalGuard.h" #include #include @@ -244,6 +245,10 @@ struct FeaturesConfig { bool enable_serial = true; bool enable_imu = false; // Xsens MTi orientation/IMU (off by default) bool mock_camera = false; // use a simulated camera instead of Vimba X + // The simulated camera's frames: false = small 640x480 colour (cheap), true = + // the real camera's 5328x3040 12-bit raw, so saving costs the CPU what it + // does on a tower (throttling simulations, tools/thermal-sim/). + bool mock_camera_full = false; bool mock_serial = false; // use a simulated motor controller bool mock_imu = false; // use a simulated IMU instead of the MTi bool enable_env = false; // ambient temp/humidity sensor (off by default) @@ -389,6 +394,7 @@ struct AppConfig { PathsConfig paths; BoardConfig board; // [Board] from hardware.ini HardwareConfig hardware; // [Hardware] from hardware.ini + ThermalParams thermal; // [Thermal] the CPU guard, normally from hardware.ini FeaturesConfig features; LoggingConfig logging; UiConfig ui; // [UI] terminal dashboard toggle diff --git a/include/fgc/HostMetrics.h b/include/fgc/HostMetrics.h index 0e4d180..98d111b 100644 --- a/include/fgc/HostMetrics.h +++ b/include/fgc/HostMetrics.h @@ -1,18 +1,58 @@ #pragma once #include +#include namespace fgc::hostmetrics { -// Host (LattePanda / Linux) health reads for the `test host` subsystem. Pure -// parsers are split from the live readers so they unit-test without a real /sys -// or /proc. All readers degrade gracefully (ok=false) rather than throw. +// Host (LattePanda / Linux) health reads, for the `test host` subsystem and the +// running program's CPU watch (ThermalGuard). Pure parsers are split from the +// live readers so they unit-test without a real /sys or /proc. All readers +// degrade gracefully (ok=false) rather than throw. +// +// `root` is put in front of every /sys and /proc path ("/" = the real ones), so +// a test or a throttling simulation can hand in a fake tree: +// /sys/class/thermal/thermal_zone*/{temp,type} +// /sys/devices/system/cpu/cpu*/cpufreq/{scaling_cur_freq,cpuinfo_max_freq,scaling_max_freq} +// /sys/devices/system/cpu/cpu*/thermal_throttle/{package,core}_throttle_count +// /proc/stat (when the tree has none, the real /proc/stat) struct ThermalInfo { bool ok = false; double max_temp_c = 0.0; // hottest thermal zone std::string hottest_zone; // e.g. "x86_pkg_temp" bool throttled = false; // best-effort (false if unknown) + // The CPU's own zone, for the CPU guard: x86_pkg_temp where there is one, + // else the hottest. Not simply the hottest: the towers also list the Wi-Fi + // card (iwlwifi_1) and the ACPI zone, and the guard watches the CPU. + double cpu_temp_c = 0.0; + std::string cpu_zone; +}; + +// CPU clock, from cpufreq. MHz. +struct CpuFreqInfo { + bool ok = false; + double mean_mhz = 0.0; // mean of scaling_cur_freq over the cores + double hw_max_mhz = 0.0; // cpuinfo_max_freq: what the chip can do + double cap_mhz = 0.0; // lowest scaling_max_freq: what it may do now + std::vector core_mhz; // per core, cpu0 first +}; + +// The kernel's count of times the CPU slowed itself because it ran too hot +// (x86 thermal_throttle). It only rises; a rise between two reads is throttling. +// Slow-downs to stay inside the power limit are NOT counted here - ThrottleWatch +// (ThermalGuard.h) also watches the clock for those. +struct ThrottleCount { + bool ok = false; + long long events = 0; // package count (cpu0) + every core's count +}; + +// Cumulative CPU time from the first line of /proc/stat, in clock ticks. Busy % +// is the change in `busy` over the change in `total` between two reads. +struct CpuTimes { + bool ok = false; + long long busy = 0; + long long total = 0; }; struct DiskInfo { @@ -37,13 +77,19 @@ struct LoadInfo { }; // ---- live readers ---- -ThermalInfo readThermal(); // scans /sys/class/thermal -DiskInfo readDisk(const std::string& path); // statvfs() on the filesystem holding `path` -MemInfo readMeminfo(); // /proc/meminfo -LoadInfo readLoad(); // getloadavg() + nproc +ThermalInfo readThermal(const std::string& root = "/"); // scans sys/class/thermal +CpuFreqInfo readCpuFreq(const std::string& root = "/"); +ThrottleCount readThrottleCount(const std::string& root = "/"); +CpuTimes readCpuTimes(const std::string& root = "/"); +DiskInfo readDisk(const std::string& path); // statvfs() on the filesystem holding `path` +MemInfo readMeminfo(); // /proc/meminfo +LoadInfo readLoad(); // getloadavg() + nproc // ---- pure parsers (reused by the readers; exposed for tests) ---- -MemInfo parseMeminfo(const std::string& proc_meminfo); -double parseMilliCelsius(const std::string& zone_temp_contents); // "52000\n" -> 52.0 +MemInfo parseMeminfo(const std::string& proc_meminfo); +double parseMilliCelsius(const std::string& zone_temp_contents); // "52000\n" -> 52.0 +CpuTimes parseProcStatCpu(const std::string& proc_stat); // its "cpu " line +// Busy percentage between two reads; -1 when either is bad or no time passed. +double busyPercent(const CpuTimes& before, const CpuTimes& after); } // namespace fgc::hostmetrics diff --git a/include/fgc/IControlChannel.h b/include/fgc/IControlChannel.h index 53734ce..0a5d2c1 100644 --- a/include/fgc/IControlChannel.h +++ b/include/fgc/IControlChannel.h @@ -14,6 +14,31 @@ struct CamEvent { long long timestamp_ms = 0; // Unix epoch ms; matches the image filename }; +// The tower computer's health, on GGS/FWT//Health (retained): the CPU +// guard's state and the readings behind it (ThermalGuard.h). A reading that is +// unknown goes out as null. +struct HealthReport { + std::string tower; + std::string state = "normal"; // normal / slowed / paused + std::string reason; // why not normal, in plain words; "" when normal + bool guard_on = true; // false after `thermal off` + bool cpu_c_ok = false; + double cpu_c = 0.0; + bool cpu_mhz_ok = false; + double cpu_mhz = 0.0; + double cpu_busy_pct = -1.0; // -1 = unknown + bool throttling = false; + long long throttle_events_last_min = 0; + bool enclosure_c_ok = false; + double enclosure_c = 0.0; + int save_count_last_min = 0; + double save_ms_mean = 0.0; + double save_ms_max = 0.0; + long long held_s = 0; // the scan waiting for saving, seconds + double interval_s = 0.0; // the time between pictures now + long long timestamp_ms = 0; // Unix epoch ms +}; + // A console command that arrived over MQTT, on GGS/FWT//Command. It is // run by the same handleCommand() as a typed one, on the control thread. struct RemoteCommand { @@ -75,6 +100,8 @@ public: // StatusCode - on every change, and again on every reconnect. virtual void publishStatus(int code) = 0; virtual void publishCamEvent(const CamEvent& event) = 0; + // The computer's health (HealthReport), retained. Default: dropped. + virtual void publishHealth(const HealthReport& /*report*/) {} // Commands received since the last call, oldest first; clears them. And the // answer to one. Defaults: none / dropped, for a channel that takes no commands. diff --git a/include/fgc/ImagePipeline.h b/include/fgc/ImagePipeline.h index c9d00fa..b39cf25 100644 --- a/include/fgc/ImagePipeline.h +++ b/include/fgc/ImagePipeline.h @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -92,6 +93,20 @@ public: // Number of images successfully written this session (for the camera view). long long imagesSaved() const { return saved_.load(); } + // How long saving took over the last minute: de-Bayer, encode and write, per + // picture (the MQTT publish after it is not included). count = 0: none saved. + struct SaveTiming { + int count = 0; + double mean_ms = 0.0; + double max_ms = 0.0; + }; + SaveTiming saveTiming() const; + + // Workers run at this nice value (and libjxl's threads, which inherit it), so + // the control loop, serial, IMU and MQTT threads keep their share when the + // CPU is short. 0 = leave as is. + static constexpr int kWorkerNice = 5; + private: // A frame and the pose it was taken at, captured together at submit() time so // encoding latency and queue depth cannot separate them. @@ -122,6 +137,12 @@ private: mutable std::mutex last_event_mutex_; std::optional last_event_; std::atomic saved_{0}; // images successfully written this session + + // (steady ms when done, save ms) of each picture saved in the last minute. + void noteSaveTime(double ms); + mutable std::mutex timing_mutex_; + std::deque> save_times_; + long long timing_logged_ms_ = 0; }; } // namespace fgc diff --git a/include/fgc/MqttControlChannel.h b/include/fgc/MqttControlChannel.h index 811cb78..75eb8e8 100644 --- a/include/fgc/MqttControlChannel.h +++ b/include/fgc/MqttControlChannel.h @@ -4,9 +4,13 @@ #include "fgc/IControlChannel.h" #include +#include +#include #include #include #include +#include +#include #include #include @@ -35,6 +39,7 @@ public: void publishStatus(int code) override; void publishCamEvent(const CamEvent& event) override; + void publishHealth(const HealthReport& report) override; std::vector takeCommands() override; void publishCommandReply(const CommandReply& reply) override; @@ -52,11 +57,20 @@ private: void publish(const std::string& topic, const std::string& payload, bool retained = true); + // CamEvent and Health go out through a small queue with its own thread, so a + // slow link (each publish waits up to 3 s for the broker) never holds an + // image-save worker or the control loop. Bounded: when full, the oldest + // waiting message is dropped and recorded as such. + void post(const std::string& topic, const std::string& payload); + void runOutbox(); + void stopOutbox(); // sends what is waiting (a few seconds at most), then ends + ChannelHistory history_; std::string broker_uri_; std::string tower_; std::string topic_status_; std::string topic_cam_event_; + std::string topic_health_; std::string topic_command_; std::string topic_command_reply_; @@ -66,7 +80,14 @@ private: std::mutex mutex_; std::string last_status_; // republished on reconnect (under mutex_) + std::string last_health_; // republished on reconnect (under mutex_) std::vector commands_; // received, not yet taken (under mutex_) + + std::mutex outbox_mutex_; + std::condition_variable outbox_cv_; + std::deque> outbox_; // (topic, payload) + bool outbox_stop_ = false; + std::thread outbox_thread_; }; } // namespace fgc diff --git a/include/fgc/ThermalGuard.h b/include/fgc/ThermalGuard.h new file mode 100644 index 0000000..8732f2c --- /dev/null +++ b/include/fgc/ThermalGuard.h @@ -0,0 +1,196 @@ +#pragma once + +#include +#include +#include +#include + +namespace fgc { + +// Watching the tower computer's CPU heat and load, and pacing the scan to it. +// +// In summer the sun and the gimbal heat the LattePanda until its CPU slows itself +// down (throttles). Saving a 12-bit picture costs ~1.4 CPU-seconds, so a slowed +// CPU may stop keeping up. The scan already holds while saving is behind +// (CaptureScheduler::setSaveGate), so nothing piles up in memory - but a computer +// held at its limit stays hot, and starves the modem and the control loop with it. +// So the guard backs off before that: +// +// Normal - full pace. +// Slowed - pictures further apart: slow_factor times the interval, or more +// when saving needs it - 1.5 x the measured save time per worker, so +// the saver keeps some headroom instead of running flat out. Entered +// when the CPU is at slow_c or more, when it is throttling, or when +// saving is the bottleneck: the scan spent held_share_pct of the last +// minute waiting for it, or held_slow_s in one go. Back to Normal once +// all of that has been clear for calm_s. +// Paused - no new pictures: the gimbal parks where it is and the camera stream +// stops, so the CPU all but idles. Entered when the CPU has stayed at +// pause_c or more for pause_after_s, or saving is still the +// bottleneck held_pause_s after slowing down (or kept the scan waiting +// held_pause_s in one go). Resumes (to Slowed) after min_pause_s, once +// the CPU is at resume_c or below and nothing is waiting to be saved. +// +// Why a share and not only one long wait: a CPU that saves a picture in 4.6 s +// against a 2 s interval never makes the scan wait long - the save gate lets it +// on after 2-3 s each time - yet it runs pegged for as long as the scan does +// (measured on the desk with a CPU quota, docs/thermal-throttling.md). +// +// It never stops the program and never changes the operating mode: auto stays +// auto, so the scan picks up by itself once the computer has cooled. Picture +// quality is never touched - only the pace. +// +// Pure: fed one reading at a time with its own clock, no I/O, so every rule here +// is unit-tested (test_thermalguard.cpp). Application samples the host every +// sample_ms and logs what update() returns. + +struct ThermalParams { + bool enabled = true; + std::string sysfs_root = "/"; // fake /sys + /proc tree for a simulation + int sample_ms = 2000; + double slow_c = 85.0; + double pause_c = 92.0; + double resume_c = 75.0; + double hysteresis_c = 5.0; // Slowed -> Normal needs slow_c - this + int pause_after_s = 20; + int calm_s = 120; + int min_pause_s = 300; + int held_slow_s = 30; + int held_pause_s = 120; + double held_share_pct = 25.0; // of the last minute spent waiting for saving + double slow_factor = 2.0; + // Throttling is also inferred from the clock (see ThrottleWatch): below this + // share of the chip's top clock while at least this busy. + double clock_low_pct = 50.0; + double busy_high_pct = 50.0; +}; + +// One sample of the host. Each part has its own ok flag: a reading that is +// missing is left out of the rules, never taken as zero. +struct HostReading { + long long now_ms = 0; + bool temp_ok = false; + double cpu_c = 0.0; + std::string zone; // hottest thermal zone's name + bool clock_ok = false; + double mhz = 0.0; // mean over the cores + double hw_max_mhz = 0.0; // the chip's top clock + bool count_ok = false; + long long throttle_count = 0; // kernel thermal_throttle counters, summed + double busy_pct = -1.0; // since the last sample; -1 = unknown + bool held_for_save = false; // the scan is waiting for saving + long long held_total_ms = 0; // all the time it has waited so far (only rises) + int save_pending = 0; // frames waiting or being saved + // For the slowed pace: what the scan is configured to, and what saving takes. + double base_interval_ms = 0.0; // 0 = unknown + double save_ms_mean = 0.0; // last minute; 0 = unknown + int save_workers = 1; +}; + +enum class GuardLevel { Info, Warn }; +using GuardLog = std::pair; + +// "93 °C", "1.2 GHz" - for log lines and the UI. +std::string celsiusText(double c); +std::string ghzText(double mhz); + +// Is the CPU slowing itself down? Two signs: +// - the kernel's thermal_throttle counters rose since the last sample (heat); +// - the clock is below clock_low_pct of the chip's top clock while the CPU is +// at least busy_high_pct busy. This catches the power-limit slow-downs the +// counters never count, and a clock capped for a simulation (cap-cpu.sh). +// Logs one line when it starts, at most one summary a minute while it lasts, +// and one when it has been clear for a minute. +class ThrottleWatch { +public: + explicit ThrottleWatch(const ThermalParams& p = {}) : p_(p) {} + void setParams(const ThermalParams& p) { p_ = p; } + + void update(const HostReading& r, std::vector& log); + + bool throttling() const { return throttling_; } + // Samples that showed throttling in the last minute, and counter rises since start. + int samplesLastMinute() const { return static_cast(recent_.size()); } + long long eventsTotal() const { return events_total_; } + long long eventsLastMinute() const; + // "counters" / "clock" / "counters and clock" - what said so last time. + const std::string& cause() const { return cause_; } + +private: + ThermalParams p_; + bool have_count_ = false; + long long last_count_ = 0; + long long events_total_ = 0; + // (time, counter rise) of each sample that showed throttling, last 60 s. + std::deque> recent_; + bool throttling_ = false; + long long last_seen_ms_ = 0; // last sample that showed throttling + long long last_summary_ms_ = 0; + int low_clock_run_ = 0; // consecutive samples with a low, busy clock + std::string cause_; +}; + +enum class GuardState { Normal, Slowed, Paused }; +const char* guardStateName(GuardState s); // "normal" / "slowed" / "paused" + +class ThermalGuard { +public: + explicit ThermalGuard(const ThermalParams& p = {}); + + // One sample in, the log lines it produced out (throttling start/summary/end + // and state changes). Call every sample_ms. + std::vector update(const HostReading& r); + + // What the scan should do now. + GuardState state() const { return state_; } + // 1 when Normal; when Slowed or Paused, slow_factor or what saving needs + // (see the top of this file), at most 10. + double intervalScale() const { return state_ == GuardState::Normal ? 1.0 : scale_; } + bool pauseCapture() const { return state_ == GuardState::Paused; } + // Why it is not Normal, in plain words ("" when Normal). + const std::string& reason() const { return reason_; } + + // `thermal resume`: back to Normal now, every timer restarted. The rules then + // apply afresh - a CPU still at pause_c pauses again after pause_after_s. + std::vector forceResume(long long now_ms); + // `thermal off|on`: off = always Normal; the readings and the throttling log go on. + std::vector setEnabled(bool on, long long now_ms); + bool enabled() const { return enabled_; } + + const ThermalParams& params() const { return p_; } + const HostReading& last() const { return last_; } + bool haveReading() const { return have_last_; } + const ThrottleWatch& throttle() const { return watch_; } + long long heldForSaveMs() const; // how long the scan has been waiting + long long pausedForMs() const; + // Share of the last minute the scan spent waiting for saving, in %; -1 until + // there is half a minute of history to judge from. + double heldSharePct() const; + +private: + void enter(GuardState s, const std::string& why, std::vector& log); + void updateScale(const HostReading& r); + std::string paceText() const; // "one picture every 4.0 s instead of 2.0" + + ThermalParams p_; + ThrottleWatch watch_; + bool enabled_ = true; + GuardState state_ = GuardState::Normal; + std::string reason_; + HostReading last_; + bool have_last_ = false; + + long long hot_since_ms_ = -1; // CPU at pause_c or more since (-1 = not) + long long held_since_ms_ = -1; // the scan waiting for saving since (-1 = not) + long long calm_since_ms_ = -1; // Slowed, with every reason clear, since (-1 = not) + long long paused_at_ms_ = 0; + long long slowed_at_ms_ = 0; + long long overload_since_ms_ = -1; // Slowed, and saving still the bottleneck, since + // (time, held_total_ms) per sample over the last minute, for heldSharePct(). + std::deque> held_hist_; + double scale_ = 1.0; + double last_save_ms_ = 0.0; // the last save time seen (0 = none yet) + double need_scale_ = 0.0; // the scale saving needs; <= 1: the normal pace fits +}; + +} // namespace fgc diff --git a/include/fgc/mock/NullControlChannel.h b/include/fgc/mock/NullControlChannel.h index f4ef6ee..f8cfb66 100644 --- a/include/fgc/mock/NullControlChannel.h +++ b/include/fgc/mock/NullControlChannel.h @@ -38,6 +38,13 @@ public: << " (null channel)"; drop(kTopicCamEvent, camEventJson(e)); } + void publishHealth(const HealthReport& h) override { + ++health_published_; + last_health_ = h; + drop(kTopicHealth, healthJson(h)); + } + int healthPublished() const { return health_published_; } + const HealthReport& lastHealth() const { return last_health_; } // Commands: none arrive by themselves; inject() stands in for the broker, // so --no-mqtt runs and tests can drive the remote-command path. @@ -71,6 +78,8 @@ private: ChannelHistory history_; std::vector pending_; std::vector replies_; + int health_published_ = 0; + HealthReport last_health_; }; } // namespace fgc diff --git a/include/fgc/ui/UiSnapshot.h b/include/fgc/ui/UiSnapshot.h index c583e85..0d19689 100644 --- a/include/fgc/ui/UiSnapshot.h +++ b/include/fgc/ui/UiSnapshot.h @@ -81,15 +81,20 @@ struct SensorGroup { // The Sensors panel, split by source so it is clear which parameters come from // which device. Each group is filled with pending placeholders until its driver -// lands. NOTE: the only temperature here is the env sensor's *ambient* one — the +// lands. NOTE: two temperatures show here, kept apart by their labels: the env +// sensor's *ambient* "Temp" and the computer's own "CPU temp" (host group). The // MTi's *internal* device temperature is diagnostic detail and lives solely in -// the expanded view (ImuView::temp_c), so the compact panel never shows two -// temperatures the operator has to tell apart. The env group's title/backing -// sensor is swappable (currently SHT41 over I2C; see IEnvSensor) without -// touching this struct. +// the expanded view (ImuView::temp_c). The env group's title/backing sensor is +// swappable (currently SHT41 over I2C; see IEnvSensor) without touching this +// struct. struct SensorsView { SensorGroup imu; // Xsens MTi: Roll/Pitch/Yaw SensorGroup env; // ambient temperature + humidity (SHT41) + SensorGroup host; // the LattePanda's CPU: temperature, clock, load, throttling + // The CPU is hot, slowing itself, or the scan is slowed/paused for it: the + // badge text ("HOT" / "SLOWED" / "PAUSED"), "" for none, and how loud. + std::string cpu_badge; + bool cpu_badge_red = false; // The MTi is not streaming what the host configured (the detail is in // ImuConfigView::problems). Flagged on the compact panel too, because the @@ -277,6 +282,40 @@ struct EnvView { bool fan_stalled = false; // driven, but the tach reads nothing }; +// The tower computer's CPU and the guard pacing the scan to it (ThermalGuard), +// for the expanded Sensors view. Filled from the guard's last sample - the UI +// never reads /sys itself. +struct HostView { + bool present = false; // at least one sample taken + bool temp_ok = false; + double cpu_c = 0.0; + std::string zone; // hottest thermal zone, e.g. "x86_pkg_temp" + bool clock_ok = false; + double mean_mhz = 0.0; + std::vector core_mhz; + double hw_max_mhz = 0.0; // what the chip can do + double cap_mhz = 0.0; // what it is allowed now (scaling_max_freq) + double busy_pct = -1.0; // -1 = unknown + double load1 = 0.0, load5 = 0.0, load15 = 0.0; + int cpus = 0; + bool throttling = false; + std::string throttle_cause; // "counters" / "clock" / "counters and clock" + bool counters_ok = false; // the kernel counts throttling on this host + long long throttle_total = 0; // counter rises since start + long long throttle_last_min = 0; + // The guard. + bool guard_on = true; + std::string state = "normal"; // normal / slowed / paused + std::string reason; + double slow_c = 0.0, pause_c = 0.0, resume_c = 0.0; + double interval_s = 0.0; // between pictures now + long long held_s = 0; // the scan waiting for saving + long long paused_s = 0; + // Saving, over the last minute. + int save_count = 0; + double save_mean_ms = 0.0, save_max_ms = 0.0; +}; + // Status of the currently-running special operation (calibration, diagnostics, // homing, capture) plus the last completed result, for the activity strip below // the log. The result block persists until replaced so it never scrolls away. @@ -359,6 +398,7 @@ struct UiSnapshot { DumpView dump; ImuView imu; EnvView env; + HostView host; ActivityView activity; CalibResultView calib; DiagResultView diag; diff --git a/main.cpp b/main.cpp index ba4a125..e4c0146 100644 --- a/main.cpp +++ b/main.cpp @@ -50,6 +50,9 @@ int main(int argc, char* argv[]) { "or one beside config.ini)") ("no-mqtt", po::bool_switch(), "disable MQTT (use a null control channel)") ("mock-camera", po::bool_switch(), "use a simulated camera (no hardware)") + ("mock-camera-full", po::bool_switch(), + "simulated camera with the real camera's 5328x3040 12-bit frames, so saving loads " + "the CPU as on a tower (implies --mock-camera)") ("mock-serial", po::bool_switch(), "use a simulated motor controller (no hardware)") ("mock-env", po::bool_switch(), "use a simulated ambient temp/humidity sensor (implies enable_env)") @@ -222,6 +225,10 @@ int main(int argc, char* argv[]) { RuntimeOptions opts; if (vm["no-mqtt"].as()) opts.use_mqtt = false; if (vm["mock-camera"].as()) opts.mock_camera = true; + if (vm["mock-camera-full"].as()) { + opts.mock_camera = true; + opts.mock_camera_full = true; + } if (vm["mock-serial"].as()) opts.mock_serial = true; if (vm["mock-env"].as()) opts.mock_env = true; opts.degraded = vm["degraded"].as(); diff --git a/src/camera/ImagePipeline.cpp b/src/camera/ImagePipeline.cpp index d323b1d..4184202 100644 --- a/src/camera/ImagePipeline.cpp +++ b/src/camera/ImagePipeline.cpp @@ -5,8 +5,13 @@ #include "fgc/RawImage.h" #include +#include +#include #include +#include +#include + #include #include @@ -71,7 +76,54 @@ void ImagePipeline::submit(const Frame& frame) { cv_.notify_one(); } +static long long steadyMs() { + using namespace std::chrono; + return duration_cast(steady_clock::now().time_since_epoch()).count(); +} + +void ImagePipeline::noteSaveTime(double ms) { + const long long now = steadyMs(); + std::lock_guard lock(timing_mutex_); + save_times_.emplace_back(now, ms); + while (!save_times_.empty() && now - save_times_.front().first > 60000) save_times_.pop_front(); + if (timing_logged_ms_ == 0) timing_logged_ms_ = now; + if (now - timing_logged_ms_ < 60000) return; + timing_logged_ms_ = now; + double sum = 0.0, max = 0.0; + for (const auto& s : save_times_) { + sum += s.second; + max = std::max(max, s.second); + } + LOG_INFO << "saving: " << save_times_.size() << " picture" + << (save_times_.size() == 1 ? "" : "s") << " in the last minute, " + << static_cast(sum / static_cast(save_times_.size()) + 0.5) + << " ms each on average (longest " << static_cast(max + 0.5) << " ms)"; +} + +ImagePipeline::SaveTiming ImagePipeline::saveTiming() const { + const long long now = steadyMs(); + std::lock_guard lock(timing_mutex_); + SaveTiming t; + double sum = 0.0; + for (const auto& s : save_times_) { + if (now - s.first > 60000) continue; + ++t.count; + sum += s.second; + t.max_ms = std::max(t.max_ms, s.second); + } + if (t.count > 0) t.mean_ms = sum / t.count; + return t; +} + void ImagePipeline::run() { + // Lower this worker's priority (Linux: nice is per thread, and the encoder's + // own threads inherit it). Best effort: a failure only costs the head start. + if (kWorkerNice != 0) { + errno = 0; + const int now = getpriority(PRIO_PROCESS, static_cast(::gettid())); + if (errno == 0 && now < kWorkerNice) + setpriority(PRIO_PROCESS, static_cast(::gettid()), kWorkerNice); + } while (running_) { QueuedFrame queued; { @@ -121,7 +173,10 @@ void ImagePipeline::process(const QueuedFrame& queued) { return; } const fs::path file = dir / (std::to_string(frame.timestamp_ms) + ".jxl"); + const auto t0 = std::chrono::steady_clock::now(); if (!save(frame, out, file.string())) return; + noteSaveTime(std::chrono::duration(std::chrono::steady_clock::now() - t0) + .count()); // Sampled at submit(), not here: by this point the encode above has run and // the gimbal may already be slewing to the next waypoint. diff --git a/src/core/Application.cpp b/src/core/Application.cpp index ae15d13..37446ed 100644 --- a/src/core/Application.cpp +++ b/src/core/Application.cpp @@ -30,6 +30,8 @@ #include "fgc/OperatingMode.h" #include "fgc/RemoteCommands.h" #include "fgc/TestRunner.h" +#include "fgc/HostMetrics.h" +#include "fgc/ThermalGuard.h" #include "fgc/mock/MockCameraSource.h" #include "fgc/mock/MockEnvSensor.h" #include "fgc/mock/MockImuSource.h" @@ -297,6 +299,16 @@ struct Application::Impl { long long fan_seen_ms_ = 0; // last ENV reading checkFan() judged int fan_stall_count_ = 0; // consecutive stalled readings bool fan_stalled_ = false; + // The CPU guard (ThermalGuard.h): sampled every [Thermal] sample_ms from the + // control loop; it slows or pauses the scan when the computer runs hot. + ThermalGuard thermal_; + long long thermal_next_ms_ = 0; // steady ms of the next sample + hostmetrics::CpuTimes cpu_prev_; // for busy % between samples + hostmetrics::CpuFreqInfo cpu_freq_; // last sample, for the UI + hostmetrics::LoadInfo cpu_load_; + bool thermal_camera_stopped_ = false; // the guard stopped the stream + long long health_sent_ms_ = 0; // steady ms of the last Health + std::string health_sent_key_; // state+throttling last published std::vector heading_marks_; // `gimbal heading mark`s this setup std::atomic running{true}; @@ -421,8 +433,12 @@ struct Application::Impl { #endif const auto active = cfg.camera.active(); if (mock) { - return std::make_unique( - active.empty() ? 1 : static_cast(active.size())); + const int n = active.empty() ? 1 : static_cast(active.size()); + if (opts.mock_camera_full.value_or(cfg.features.mock_camera_full)) { + LOG_INFO << "[mock] camera frames are tower-size: 5328x3040 12-bit raw"; + return std::make_unique(n, 5328, 3040, PixelLayout::BayerRG12p); + } + return std::make_unique(n); } #if FGC_WITH_VIMBA std::vector specs; @@ -903,6 +919,80 @@ struct Application::Impl { } } + // --- The computer's CPU (the guard's last sample; no /sys reads here) --- + if (thermal_.haveReading()) { + const HostReading& r = thermal_.last(); + const ThermalParams& p = thermal_.params(); + HostView& h = s.host; + h.present = true; + h.temp_ok = r.temp_ok; + h.cpu_c = r.cpu_c; + h.zone = r.zone; + h.clock_ok = cpu_freq_.ok; + h.mean_mhz = cpu_freq_.mean_mhz; + h.core_mhz = cpu_freq_.core_mhz; + h.hw_max_mhz = cpu_freq_.hw_max_mhz; + h.cap_mhz = cpu_freq_.cap_mhz; + h.busy_pct = r.busy_pct; + h.load1 = cpu_load_.load1; + h.load5 = cpu_load_.load5; + h.load15 = cpu_load_.load15; + h.cpus = cpu_load_.cpus; + h.throttling = thermal_.throttle().throttling(); + h.throttle_cause = thermal_.throttle().cause(); + h.counters_ok = r.count_ok; + h.throttle_total = thermal_.throttle().eventsTotal(); + h.throttle_last_min = thermal_.throttle().eventsLastMinute(); + h.guard_on = thermal_.enabled(); + h.state = guardStateName(thermal_.state()); + h.reason = thermal_.reason(); + h.slow_c = p.slow_c; + h.pause_c = p.pause_c; + h.resume_c = p.resume_c; + h.interval_s = scheduler ? scheduler->intervalMs() / 1000.0 : 0.0; + h.held_s = thermal_.heldForSaveMs() / 1000; + h.paused_s = thermal_.pausedForMs() / 1000; + if (pipeline) { + const auto t = pipeline->saveTiming(); + h.save_count = t.count; + h.save_mean_ms = t.mean_ms; + h.save_max_ms = t.max_ms; + } + + auto& g = s.sensors.host; + g.present = true; + g.status = "live"; + auto set = [](SensorField& f, double v, int decimals) { + char b[32]; + std::snprintf(b, sizeof(b), "%.*f", decimals, v); + f.value = b; + f.present = true; + }; + if (g.fields.size() >= 4) { + if (r.temp_ok) set(g.fields[0], r.cpu_c, 1); + if (cpu_freq_.ok) set(g.fields[1], cpu_freq_.mean_mhz / 1000.0, 1); + if (r.busy_pct >= 0.0) set(g.fields[2], r.busy_pct, 0); + const long long n = thermal_.throttle().eventsLastMinute(); + g.fields[3].value = !h.throttling ? "no" + : n > 0 ? "yes, " + std::to_string(n) + "x last min" + : "yes (clock)"; + g.fields[3].present = true; + } + // The loudest thing that is true: paused, slowed, throttling, hot. + if (thermal_.state() == GuardState::Paused) { + s.sensors.cpu_badge = "PAUSED"; + s.sensors.cpu_badge_red = true; + } else if (thermal_.state() == GuardState::Slowed) { + s.sensors.cpu_badge = "SLOWED"; + } else if (h.throttling) { + s.sensors.cpu_badge = "THROTTLED"; + } else if (r.temp_ok && r.cpu_c >= p.slow_c) { + s.sensors.cpu_badge = "HOT"; + } + if (!thermal_.enabled() && !s.sensors.cpu_badge.empty()) + s.sensors.cpu_badge += " (guard off)"; + } + // --- Camera / capture --- s.capture.present = true; s.capture.continuous = scheduler && scheduler->continuous(); @@ -1143,6 +1233,184 @@ struct Application::Impl { fan_stalled_ = stalled; } + static long long steadyNowMs() { + using namespace std::chrono; + return duration_cast(steady_clock::now().time_since_epoch()).count(); + } + + static void logGuard(const std::vector& lines) { + for (const auto& l : lines) { + if (l.first == GuardLevel::Warn) LOG_WARN << l.second; + else LOG_INFO << l.second; + } + } + + // Every [Thermal] sample_ms: read the CPU's temperature, clock, throttle + // counters and busy time, hand them to the guard, log what it says, and + // publish Health when anything changed (or once a minute). + void watchHost() { + const long long now = steadyNowMs(); + if (now < thermal_next_ms_) return; + thermal_next_ms_ = now + thermal_.params().sample_ms; + + const std::string& root = thermal_.params().sysfs_root; + const auto th = hostmetrics::readThermal(root); + cpu_freq_ = hostmetrics::readCpuFreq(root); + const auto count = hostmetrics::readThrottleCount(root); + const auto times = hostmetrics::readCpuTimes(root); + cpu_load_ = hostmetrics::readLoad(); + + HostReading r; + r.now_ms = now; + r.temp_ok = th.ok; + r.cpu_c = th.cpu_temp_c; + r.zone = th.cpu_zone; + r.clock_ok = cpu_freq_.ok; + r.mhz = cpu_freq_.mean_mhz; + r.hw_max_mhz = cpu_freq_.hw_max_mhz; + r.count_ok = count.ok; + r.throttle_count = count.events; + r.busy_pct = hostmetrics::busyPercent(cpu_prev_, times); + cpu_prev_ = times; + // While paused the scan is not waiting for saving - it is parked. + r.held_for_save = scheduler && scheduler->holdingForSave() && !scheduler->paused(); + r.held_total_ms = scheduler ? scheduler->heldForSaveTotalMs() : 0; + r.save_pending = pipeline ? static_cast(pipeline->pending()) : 0; + r.base_interval_ms = cfg.image_rate() > 0.0 ? 1000.0 / cfg.image_rate() : 0.0; + r.save_ms_mean = pipeline ? pipeline->saveTiming().mean_ms : 0.0; + r.save_workers = cfg.hardware.save_workers; + logGuard(thermal_.update(r)); + applyGuard(); + publishHealth(false); + } + + // The guard's verdict, applied to the scan - every control-loop pass, since + // the camera may only stop once the waypoint under way has finished. + void applyGuard() { + if (!scheduler) return; + scheduler->setIntervalScale(thermal_.intervalScale()); + scheduler->setPaused(thermal_.pauseCapture()); + if (!camera) return; + // Paused and parked: stop the camera stream, so the CPU all but idles (it + // copies every frame it streams). Never under a running test or homing, + // which may need the camera. + const bool shooting = scheduler->mode() != Mode::Manual || scheduler->continuous(); + if (scheduler->pausedAtRest() && shooting && !thermal_camera_stopped_ && + !procedureBusy()) { + camera->stop(); + thermal_camera_stopped_ = true; + LOG_INFO << "camera stream stopped while pictures are paused"; + } else if (!scheduler->paused() && thermal_camera_stopped_) { + thermal_camera_stopped_ = false; + // Back on only where something will take pictures: a scan, or + // continuous capture. In plain manual it stays off, as setMode leaves it. + if (scheduler->mode() != Mode::Manual || scheduler->continuous()) { + camera->start(); + LOG_INFO << "camera stream started again"; + } + } + } + + HealthReport healthReport() const { + HealthReport h; + const HostReading& r = thermal_.last(); + h.tower = cfg.general.tower_name; + h.state = guardStateName(thermal_.state()); + h.reason = thermal_.reason(); + h.guard_on = thermal_.enabled(); + h.cpu_c_ok = r.temp_ok; + h.cpu_c = r.cpu_c; + h.cpu_mhz_ok = r.clock_ok; + h.cpu_mhz = r.mhz; + h.cpu_busy_pct = r.busy_pct; + h.throttling = thermal_.throttle().throttling(); + h.throttle_events_last_min = thermal_.throttle().eventsLastMinute(); + if (env) { + if (auto m = env->sample()) { + h.enclosure_c_ok = true; + h.enclosure_c = m->temp_c; + } + } + if (pipeline) { + const auto t = pipeline->saveTiming(); + h.save_count_last_min = t.count; + h.save_ms_mean = t.mean_ms; + h.save_ms_max = t.max_ms; + } + h.held_s = thermal_.heldForSaveMs() / 1000; + h.interval_s = scheduler ? scheduler->intervalMs() / 1000.0 : 0.0; + h.timestamp_ms = nowEpochMs(); + return h; + } + + // On a change of state or throttling, or once a minute; `force` for a command. + void publishHealth(bool force) { + if (!channel || !thermal_.haveReading()) return; + const long long now = steadyNowMs(); + const std::string key = std::string(guardStateName(thermal_.state())) + + (thermal_.throttle().throttling() ? "+t" : "") + + (thermal_.enabled() ? "" : "+off"); + if (!force && key == health_sent_key_ && now - health_sent_ms_ < 60000) return; + health_sent_key_ = key; + health_sent_ms_ = now; + channel->publishHealth(healthReport()); + } + + // `thermal [resume|off|on]`: the CPU's heat and the scan's pace for it. + void handleThermal(const std::string& what) { + const long long now = steadyNowMs(); + if (what == "resume") { + logGuard(thermal_.forceResume(now)); + } else if (what == "off" || what == "on") { + logGuard(thermal_.setEnabled(what == "on", now)); + } else if (!what.empty()) { + LOG_WARN << "usage: thermal [resume|off|on]"; + return; + } else { + const HostReading& r = thermal_.last(); + const ThermalParams& p = thermal_.params(); + if (!thermal_.haveReading()) { + LOG_INFO << "CPU: no reading yet"; + } else { + LOG_INFO << "CPU: " + << (r.temp_ok ? celsiusText(r.cpu_c) + " (" + r.zone + ")" + : std::string("temperature unknown")) + << ", " << (r.clock_ok ? ghzText(r.mhz) : std::string("clock unknown")) + << (r.hw_max_mhz > 0.0 ? " of " + ghzText(r.hw_max_mhz) : std::string()) + << ", " << (r.busy_pct >= 0.0 ? std::to_string(static_cast(r.busy_pct + 0.5)) + "% busy" + : std::string("busy unknown")); + LOG_INFO << "throttling: " + << (thermal_.throttle().throttling() + ? "yes (" + thermal_.throttle().cause() + ")" + : std::string("no")) + << ", " << thermal_.throttle().eventsLastMinute() + << " in the last minute, " << thermal_.throttle().eventsTotal() + << " since start"; + } + char every[48] = ""; + if (scheduler) + std::snprintf(every, sizeof(every), ", one picture every %.1f s", + scheduler->intervalMs() / 1000.0); + LOG_INFO << "scan pace: " << (thermal_.enabled() ? guardStateName(thermal_.state()) + : "guard off") + << (thermal_.reason().empty() ? "" : " - " + thermal_.reason()) << every; + if (pipeline) { + const auto t = pipeline->saveTiming(); + if (t.count > 0) + LOG_INFO << "saving: " << t.count << " in the last minute, " + << static_cast(t.mean_ms + 0.5) << " ms each (longest " + << static_cast(t.max_ms + 0.5) << " ms)"; + } + LOG_INFO << "limits: slows at " << celsiusText(p.slow_c) << ", pauses at " + << celsiusText(p.pause_c) << " for " << p.pause_after_s + << " s, resumes at " << celsiusText(p.resume_c) << " after at least " + << p.min_pause_s << " s"; + return; + } + applyGuard(); + publishHealth(true); + } + void checkTrackingError() { const long limit = cfg.enc_error_warn_counts; if (limit <= 0) return; // disabled @@ -1848,6 +2116,9 @@ struct Application::Impl { if (scheduler->continuous()) LOG_INFO << "continuous capture off (mode change)"; if (m == Mode::Manual) camera->stop(); else camera->start(); + // The stream is as the mode left it; the CPU guard stops it again if a + // pause still holds (applyGuard). + thermal_camera_stopped_ = false; scheduler->setMode(m, pan); if (m == Mode::Pan) { const auto& h = scheduler->panHeadingsNow(); @@ -2878,6 +3149,8 @@ struct Application::Impl { takeSnapshot(); } else if (c.verb == "capture") { handleCapture(c.device); + } else if (c.verb == "thermal") { + handleThermal(c.device); } else if (c.verb == "mode") { handleMode(line); } else if (c.verb == "debug") { @@ -3144,6 +3417,19 @@ struct Application::Impl { // the wait is the longest across them, not the first one's. configureSettle(); + thermal_ = ThermalGuard(cfg.thermal); + if (cfg.thermal.enabled) + LOG_INFO << "CPU guard: pictures slow down at " << celsiusText(cfg.thermal.slow_c) + << " or when the CPU throttles, and pause at " + << celsiusText(cfg.thermal.pause_c) << " (resume at " + << celsiusText(cfg.thermal.resume_c) << ")" + << (cfg.thermal.sysfs_root != "/" ? "; readings from the simulated tree " + + cfg.thermal.sysfs_root + : std::string()); + else + LOG_WARN << "CPU guard off ([Thermal] enabled = false): the scan keeps full pace " + "however hot the CPU runs"; + // Before the firmware is talked to: the registers are wanted by the very // first sendAnchorConfig(), on both the homing and the no-homing startup path. planMotorCurrents(); @@ -3216,6 +3502,8 @@ struct Application::Impl { scheduler->tick(); checkTrackingError(); // warn on live encoder-error excursions checkFan(); // a legacy tower's fan, driven but not turning + watchHost(); // CPU heat and load: slow or pause the scan for it + applyGuard(); publishSnapshot(); std::this_thread::sleep_for(std::chrono::milliseconds(10)); } diff --git a/src/core/CaptureScheduler.cpp b/src/core/CaptureScheduler.cpp index f74f439..41661bc 100644 --- a/src/core/CaptureScheduler.cpp +++ b/src/core/CaptureScheduler.cpp @@ -42,10 +42,23 @@ CaptureScheduler::CaptureScheduler(IMotorController& motor, ICameraSource& camer grid_(grid), now_ms_(now_ms ? std::move(now_ms) : steadyNowMs), image_rate_(image_rate), - interval_ms_(image_rate > 0.0 ? 1000.0 / image_rate : 0.0) { + base_interval_ms_(image_rate > 0.0 ? 1000.0 / image_rate : 0.0), + interval_ms_(base_interval_ms_) { timer_start_ = now_ms_(); } +void CaptureScheduler::setIntervalScale(double scale) { + interval_scale_ = scale > 1.0 ? scale : 1.0; + interval_ms_ = base_interval_ms_ * interval_scale_; +} + +void CaptureScheduler::setPaused(bool paused) { + if (paused == paused_) return; + paused_ = paused; + // The wait is announced by the guard, so the first move after it is no overrun. + if (paused) save_held_ = true; +} + void CaptureScheduler::setMode(Mode mode, const PanSpec& pan) { mode_ = mode; pan_ = pan; @@ -109,16 +122,28 @@ bool CaptureScheduler::settledAt(const MotorTelemetry& t) const { bool CaptureScheduler::roomToSave() { const bool room = !can_save_ || can_save_(); + const long long now = now_ms_(); if (!room && !holding_) { - holding_ = true; - LOG_WARN << "saving is behind: holding the scan here until it catches up"; + holding_ = true; + hold_started_ms_ = now; + // Said once, not at every waypoint: a saver that is a little too slow + // holds the scan briefly each time, and the CPU guard (ThermalGuard) is + // what speaks for that. A hold a minute after the last one is news again. + hold_logged_ = last_hold_end_ms_ < 0 || now - last_hold_end_ms_ >= 60000; + if (hold_logged_) LOG_WARN << "saving is behind: holding the scan here until it catches up"; } else if (room && holding_) { holding_ = false; - LOG_INFO << "saving caught up: the scan carries on"; + held_total_ms_ += now - hold_started_ms_; + last_hold_end_ms_ = now; + if (hold_logged_) LOG_INFO << "saving caught up: the scan carries on"; } return room; } +long long CaptureScheduler::heldForSaveTotalMs() const { + return held_total_ms_ + (holding_ ? now_ms_() - hold_started_ms_ : 0); +} + const char* CaptureScheduler::phase() const { if (!moving_ || hold_.settled_ms == 0) return "moving"; return hold_.ready_ms == 0 ? "settling" : "waiting"; @@ -343,7 +368,7 @@ void CaptureScheduler::tick() { // on its own clock afterwards. if (mode_ == Mode::Manual) { if (snapshot_pending_) serviceSnapshot(t); - else if (continuous_) serviceContinuous(t); + else if (continuous_ && (!paused_ || continuous_hold_.due_ms != 0)) serviceContinuous(t); return; } @@ -352,6 +377,7 @@ void CaptureScheduler::tick() { // and wait out what is left of the interval at the end. if (interval_ms_ <= 0.0) return; if (!moving_) { + if (paused_) return; // the CPU guard: park here (setPaused) if (!roomToSave()) { save_held_ = true; return; diff --git a/src/core/ChannelTopics.cpp b/src/core/ChannelTopics.cpp index 1157814..a5508e0 100644 --- a/src/core/ChannelTopics.cpp +++ b/src/core/ChannelTopics.cpp @@ -16,6 +16,35 @@ std::string camEventJson(const CamEvent& e) { ", \"time\":" + std::to_string(e.timestamp_ms) + " }"; } +namespace { + +std::string num(double v, int decimals) { + char b[32]; + std::snprintf(b, sizeof(b), "%.*f", decimals, v); + return b; +} + +} // namespace + +std::string healthJson(const HealthReport& h) { + auto opt = [](bool ok, double v, int d) { return ok ? num(v, d) : std::string("null"); }; + return "{\"fwt\":" + jsonString(h.tower) + ",\"state\":" + jsonString(h.state) + + ",\"reason\":" + jsonString(h.reason) + + ",\"guard\":" + (h.guard_on ? "true" : "false") + + ",\"cpu_c\":" + opt(h.cpu_c_ok, h.cpu_c, 1) + + ",\"cpu_mhz\":" + opt(h.cpu_mhz_ok, h.cpu_mhz, 0) + + ",\"cpu_busy_pct\":" + opt(h.cpu_busy_pct >= 0.0, h.cpu_busy_pct, 0) + + ",\"throttling\":" + (h.throttling ? "true" : "false") + + ",\"throttle_events_last_min\":" + std::to_string(h.throttle_events_last_min) + + ",\"enclosure_c\":" + opt(h.enclosure_c_ok, h.enclosure_c, 1) + + ",\"saved_last_min\":" + std::to_string(h.save_count_last_min) + + ",\"save_ms_mean\":" + opt(h.save_count_last_min > 0, h.save_ms_mean, 0) + + ",\"save_ms_max\":" + opt(h.save_count_last_min > 0, h.save_ms_max, 0) + + ",\"held_s\":" + std::to_string(h.held_s) + + ",\"interval_s\":" + num(h.interval_s, 1) + + ",\"time\":" + std::to_string(h.timestamp_ms) + "}"; +} + std::string jsonString(const std::string& s) { std::string out = "\""; for (unsigned char c : s) { diff --git a/src/core/Config.cpp b/src/core/Config.cpp index f9d265e..f148f8f 100644 --- a/src/core/Config.cpp +++ b/src/core/Config.cpp @@ -289,6 +289,48 @@ AppConfig ConfigLoader::fromMap(const std::map& kv) { if (cfg.hardware.save_queue_max < 1 || cfg.hardware.save_queue_max > 32) throw std::runtime_error("Hardware.save_queue_max must be in 1..32 (each raw frame " "waiting holds ~24 MB)"); + // [Thermal]: the CPU guard (ThermalGuard.h). Per computer, so normally in + // hardware.ini; config.ini works too (hardware.ini wins). + { + ThermalParams& t = cfg.thermal; + t.enabled = getBool(kv, "Thermal.enabled", t.enabled); + t.sysfs_root = get(kv, "Thermal.sysfs_root", t.sysfs_root); + if (t.sysfs_root.empty()) t.sysfs_root = "/"; + t.sample_ms = getInt(kv, "Thermal.sample_ms", t.sample_ms); + t.slow_c = getDouble(kv, "Thermal.slow_c", t.slow_c); + t.pause_c = getDouble(kv, "Thermal.pause_c", t.pause_c); + t.resume_c = getDouble(kv, "Thermal.resume_c", t.resume_c); + t.hysteresis_c = getDouble(kv, "Thermal.hysteresis_c", t.hysteresis_c); + t.pause_after_s = getInt(kv, "Thermal.pause_after_s", t.pause_after_s); + t.calm_s = getInt(kv, "Thermal.calm_s", t.calm_s); + t.min_pause_s = getInt(kv, "Thermal.min_pause_s", t.min_pause_s); + t.held_slow_s = getInt(kv, "Thermal.held_slow_s", t.held_slow_s); + t.held_pause_s = getInt(kv, "Thermal.held_pause_s", t.held_pause_s); + t.slow_factor = getDouble(kv, "Thermal.slow_factor", t.slow_factor); + t.held_share_pct = getDouble(kv, "Thermal.held_share_pct", t.held_share_pct); + if (t.held_share_pct <= 0.0 || t.held_share_pct > 100.0) + throw std::runtime_error("Thermal.held_share_pct must be in 0..100 (exclusive of 0)"); + t.clock_low_pct = getDouble(kv, "Thermal.clock_low_pct", t.clock_low_pct); + t.busy_high_pct = getDouble(kv, "Thermal.busy_high_pct", t.busy_high_pct); + if (t.sample_ms < 500 || t.sample_ms > 60000) + throw std::runtime_error("Thermal.sample_ms must be in 500..60000"); + if (!(t.resume_c < t.slow_c && t.slow_c < t.pause_c)) + throw std::runtime_error("Thermal: need resume_c < slow_c < pause_c (degrees C)"); + if (t.hysteresis_c < 0.0) + throw std::runtime_error("Thermal.hysteresis_c must be >= 0"); + if (t.slow_factor < 1.0 || t.slow_factor > 10.0) + throw std::runtime_error("Thermal.slow_factor must be in 1..10 (times the interval)"); + if (t.pause_after_s < 0 || t.calm_s < 0 || t.min_pause_s < 0 || t.held_slow_s < 1 || + t.held_pause_s < 1) + throw std::runtime_error("Thermal: the *_s durations must be >= 0 (held_*_s >= 1)"); + if (t.held_pause_s < t.held_slow_s) + throw std::runtime_error("Thermal: held_pause_s must be >= held_slow_s"); + if (t.clock_low_pct <= 0.0 || t.clock_low_pct >= 100.0 || t.busy_high_pct < 0.0 || + t.busy_high_pct > 100.0) + throw std::runtime_error("Thermal: clock_low_pct must be in 0..100 (exclusive), " + "busy_high_pct in 0..100"); + } + for (const auto& a : cfg.hardware.axes) if (a != "yaw" && a != "pitch") throw std::runtime_error("Hardware.axes: unknown axis '" + a + @@ -398,6 +440,12 @@ AppConfig ConfigLoader::fromMap(const std::map& kv) { cfg.features.enable_serial = getBool(kv, "Features.enable_serial", cfg.features.enable_serial); cfg.features.enable_imu = getBool(kv, "Features.enable_imu", cfg.features.enable_imu); cfg.features.mock_camera = getBool(kv, "Features.mock_camera", cfg.features.mock_camera); + { + const std::string size = get(kv, "Features.mock_camera_size", "small"); + if (size != "small" && size != "full") + throw std::runtime_error("Features.mock_camera_size must be small or full"); + cfg.features.mock_camera_full = size == "full"; + } cfg.features.mock_serial = getBool(kv, "Features.mock_serial", cfg.features.mock_serial); cfg.features.mock_imu = getBool(kv, "Features.mock_imu", cfg.features.mock_imu); cfg.features.enable_env = getBool(kv, "Features.enable_env", cfg.features.enable_env); diff --git a/src/core/HelpText.cpp b/src/core/HelpText.cpp index 02c1fb9..66d31ae 100644 --- a/src/core/HelpText.cpp +++ b/src/core/HelpText.cpp @@ -122,6 +122,9 @@ const std::vector& helpCatalog() { {"help [topic]", "This list; 'help ' adds detail.", { "Example: help capture / help positioning"}}, {"cancel", "Cancel the running test, homing or calibration.", {}}, + {"thermal [resume|off|on]", "CPU heat, and the scan slowed or paused for it.", { + "Bare: temperature, clock, throttling, and the scan's pace. Any mode.", + "resume: back to full pace now; off/on: the guard, until restart."}}, {"debug", "Toggle debug-level logging.", {}}, {"trace [on|off]", "Toggle wire-trace categories.", { "Example: trace serial on"}}, diff --git a/src/core/HostMetrics.cpp b/src/core/HostMetrics.cpp index 9cf4316..13c2dc2 100644 --- a/src/core/HostMetrics.cpp +++ b/src/core/HostMetrics.cpp @@ -1,5 +1,6 @@ #include "fgc/HostMetrics.h" +#include #include #include #include @@ -57,26 +58,161 @@ MemInfo parseMeminfo(const std::string& text) { return m; } -ThermalInfo readThermal() { +CpuTimes parseProcStatCpu(const std::string& text) { + CpuTimes t; + std::istringstream in(text); + std::string line; + while (std::getline(in, line)) { + std::istringstream ls(line); + std::string key; + if (!(ls >> key) || key != "cpu") continue; + // user nice system idle iowait irq softirq steal [guest guest_nice] + // guest time is already inside user/nice, so it is not added again. + long long v[8] = {0, 0, 0, 0, 0, 0, 0, 0}; + int n = 0; + while (n < 8 && (ls >> v[n])) ++n; + if (n < 4) return t; + const long long idle = v[3] + v[4]; + long long total = 0; + for (int i = 0; i < n; ++i) total += v[i]; + t.ok = true; + t.total = total; + t.busy = total - idle; + return t; + } + return t; +} + +double busyPercent(const CpuTimes& before, const CpuTimes& after) { + if (!before.ok || !after.ok) return -1.0; + const long long dt = after.total - before.total; + if (dt <= 0) return -1.0; + const double pct = 100.0 * static_cast(after.busy - before.busy) / static_cast(dt); + return pct < 0.0 ? 0.0 : (pct > 100.0 ? 100.0 : pct); +} + +namespace { + +fs::path under(const std::string& root, const char* path) { + return fs::path(root.empty() ? "/" : root) / path; +} + +// cpu0, cpu1, ... in number order - not cpufreq/, cpuidle/ and the rest. +std::vector cpuDirs(const std::string& root) { + std::vector> found; + std::error_code ec; + for (auto& e : fs::directory_iterator(under(root, "sys/devices/system/cpu"), ec)) { + const auto name = e.path().filename().string(); + if (name.size() < 4 || name.rfind("cpu", 0) != 0) continue; + bool digits = true; + for (size_t i = 3; i < name.size(); ++i) + if (name[i] < '0' || name[i] > '9') digits = false; + if (digits) found.emplace_back(std::stoi(name.substr(3)), e.path()); + } + std::sort(found.begin(), found.end(), + [](const auto& a, const auto& b) { return a.first < b.first; }); + std::vector out; + for (auto& f : found) out.push_back(f.second); + return out; +} + +// A sysfs number, or -1 when the file is missing or unreadable. +long long readNumber(const fs::path& p) { + std::string s = readFile(p); + if (s.empty()) return -1; + try { + return std::stoll(s); + } catch (...) { + return -1; + } +} + +} // namespace + +CpuFreqInfo readCpuFreq(const std::string& root) { + CpuFreqInfo f; + double sum = 0.0; + for (const auto& dir : cpuDirs(root)) { + const long long cur = readNumber(dir / "cpufreq/scaling_cur_freq"); // kHz + if (cur < 0) continue; + f.core_mhz.push_back(static_cast(cur) / 1000.0); + sum += static_cast(cur) / 1000.0; + const long long hw = readNumber(dir / "cpufreq/cpuinfo_max_freq"); + if (hw > 0) f.hw_max_mhz = std::max(f.hw_max_mhz, static_cast(hw) / 1000.0); + const long long cap = readNumber(dir / "cpufreq/scaling_max_freq"); + if (cap > 0) { + const double mhz = static_cast(cap) / 1000.0; + f.cap_mhz = f.cap_mhz > 0.0 ? std::min(f.cap_mhz, mhz) : mhz; + } + } + if (!f.core_mhz.empty()) { + f.ok = true; + f.mean_mhz = sum / static_cast(f.core_mhz.size()); + } + return f; +} + +ThrottleCount readThrottleCount(const std::string& root) { + ThrottleCount c; + bool package_seen = false; + for (const auto& dir : cpuDirs(root)) { + // Every core shows the same package count; take it once. + if (!package_seen) { + const long long pkg = readNumber(dir / "thermal_throttle/package_throttle_count"); + if (pkg >= 0) { + package_seen = true; + c.ok = true; + c.events += pkg; + } + } + const long long core = readNumber(dir / "thermal_throttle/core_throttle_count"); + if (core >= 0) { + c.ok = true; + c.events += core; + } + } + return c; +} + +CpuTimes readCpuTimes(const std::string& root) { + // A fake tree without its own proc/stat uses the real one: how busy the CPU + // is stays real in a simulation (raise it with tools/thermal-sim/heat-load.sh). + const fs::path p = under(root, "proc/stat"); + std::error_code ec; + return parseProcStatCpu(readFile(fs::exists(p, ec) ? p : fs::path("/proc/stat"))); +} + +ThermalInfo readThermal(const std::string& root_dir) { ThermalInfo t; - const fs::path root = "/sys/class/thermal"; + const fs::path root = under(root_dir, "sys/class/thermal"); std::error_code ec; if (!fs::exists(root, ec)) return t; + bool have_pkg = false; for (auto& e : fs::directory_iterator(root, ec)) { const auto name = e.path().filename().string(); if (name.rfind("thermal_zone", 0) != 0) continue; std::string temp_s = readFile(e.path() / "temp"); if (temp_s.empty()) continue; double c = parseMilliCelsius(temp_s); + std::string type = readFile(e.path() / "type"); + // strip trailing newline + while (!type.empty() && (type.back() == '\n' || type.back() == '\r')) type.pop_back(); + if (type.empty()) type = name; + if (type == "x86_pkg_temp" && (!have_pkg || c > t.cpu_temp_c)) { + have_pkg = true; + t.cpu_temp_c = c; + t.cpu_zone = type; + } if (!t.ok || c > t.max_temp_c) { t.ok = true; t.max_temp_c = c; - std::string type = readFile(e.path() / "type"); - // strip trailing newline - while (!type.empty() && (type.back() == '\n' || type.back() == '\r')) type.pop_back(); - t.hottest_zone = type.empty() ? name : type; + t.hottest_zone = type; } } + if (t.ok && !have_pkg) { + t.cpu_temp_c = t.max_temp_c; + t.cpu_zone = t.hottest_zone; + } return t; } diff --git a/src/core/OperatingMode.cpp b/src/core/OperatingMode.cpp index 089e68a..e46a7b9 100644 --- a/src/core/OperatingMode.cpp +++ b/src/core/OperatingMode.cpp @@ -106,7 +106,7 @@ bool allowedIn(Mode mode, const std::string& line) { const std::string& verb = w[0]; const std::string sub = w.size() > 1 ? w[1] : ""; if (verb == "help" || verb == "mode" || verb == "cancel" || verb == "exit" || - verb == "debug" || verb == "trace") + verb == "debug" || verb == "trace" || verb == "thermal") return true; if (verb == "gimbal") return sub == "status" || sub == "dump"; if (verb == "imu") return sub == "status" || sub == "config"; diff --git a/src/core/ThermalGuard.cpp b/src/core/ThermalGuard.cpp new file mode 100644 index 0000000..ae143f7 --- /dev/null +++ b/src/core/ThermalGuard.cpp @@ -0,0 +1,321 @@ +#include "fgc/ThermalGuard.h" + +#include +#include + +namespace fgc { + +namespace { + +constexpr long long kMinuteMs = 60000; +// How much longer than one save the slowed interval is: the saver then runs +// about two-thirds busy, not flat out. +constexpr double kSaveHeadroom = 1.5; + +std::string seconds(long long ms) { + const long long s = (ms + 500) / 1000; + if (s < 120) return std::to_string(s) + " s"; + return std::to_string((s + 30) / 60) + " min"; +} + +std::string factorText(double f) { + char b[16]; + std::snprintf(b, sizeof(b), "%g", f); + return b; +} + +} // namespace + +std::string celsiusText(double c) { + char b[24]; + std::snprintf(b, sizeof(b), "%.0f \xC2\xB0""C", c); + return b; +} + +std::string ghzText(double mhz) { + char b[24]; + std::snprintf(b, sizeof(b), "%.1f GHz", mhz / 1000.0); + return b; +} + +const char* guardStateName(GuardState s) { + switch (s) { + case GuardState::Normal: return "normal"; + case GuardState::Slowed: return "slowed"; + case GuardState::Paused: return "paused"; + } + return "?"; +} + +// ---- ThrottleWatch ----------------------------------------------------------- + +long long ThrottleWatch::eventsLastMinute() const { + long long n = 0; + for (const auto& e : recent_) n += e.second; + return n; +} + +void ThrottleWatch::update(const HostReading& r, std::vector& log) { + // Counter rise since the last sample. A counter that went backwards (the + // driver reloaded, or a simulation reset it) is a fresh start, not throttling. + long long rise = 0; + if (r.count_ok) { + if (have_count_ && r.throttle_count > last_count_) rise = r.throttle_count - last_count_; + last_count_ = r.throttle_count; + have_count_ = true; + } + // A low clock while busy, two samples running: one low sample is as likely + // the governor catching up with a burst. + const bool low_clock = r.clock_ok && r.hw_max_mhz > 0.0 && r.busy_pct >= 0.0 && + r.mhz < r.hw_max_mhz * p_.clock_low_pct / 100.0 && + r.busy_pct >= p_.busy_high_pct; + low_clock_run_ = low_clock ? low_clock_run_ + 1 : 0; + const bool by_clock = low_clock_run_ >= 2; + + while (!recent_.empty() && r.now_ms - recent_.front().first >= kMinuteMs) recent_.pop_front(); + + if (rise > 0 || by_clock) { + events_total_ += rise; + recent_.emplace_back(r.now_ms, rise); + last_seen_ms_ = r.now_ms; + cause_ = rise > 0 && by_clock ? "counters and clock" : (rise > 0 ? "counters" : "clock"); + const std::string where = + (r.temp_ok ? celsiusText(r.cpu_c) + ", " : std::string()) + + (r.clock_ok ? "running at " + ghzText(r.mhz) + + (r.hw_max_mhz > 0.0 ? " of " + ghzText(r.hw_max_mhz) : std::string()) + : std::string()); + if (!throttling_) { + throttling_ = true; + last_summary_ms_ = r.now_ms; + log.emplace_back(GuardLevel::Warn, + rise > 0 ? "CPU is slowing itself to stay cool: " + where + : "CPU is running slow while busy (judged from its clock, " + "not the kernel's count): " + where); + } else if (r.now_ms - last_summary_ms_ >= kMinuteMs) { + last_summary_ms_ = r.now_ms; + const long long n = eventsLastMinute(); + log.emplace_back(GuardLevel::Warn, + "CPU still slowed: " + + (n > 0 ? std::to_string(n) + " times in the last minute" + : std::string("clock low all minute")) + + ", " + where); + } + } else if (throttling_ && r.now_ms - last_seen_ms_ >= kMinuteMs) { + throttling_ = false; + // No clock here: a CPU with nothing to do idles at a low clock, and + // "back to full speed: 0.8 GHz" (seen on tower 111) reads as a contradiction. + log.emplace_back(GuardLevel::Info, + "CPU no longer slowed" + + (r.temp_ok ? ": " + celsiusText(r.cpu_c) : std::string())); + } +} + +// ---- ThermalGuard ------------------------------------------------------------ + +ThermalGuard::ThermalGuard(const ThermalParams& p) : p_(p), watch_(p), enabled_(p.enabled) {} + +long long ThermalGuard::heldForSaveMs() const { + return held_since_ms_ >= 0 && have_last_ ? last_.now_ms - held_since_ms_ : 0; +} + +long long ThermalGuard::pausedForMs() const { + return state_ == GuardState::Paused && have_last_ ? last_.now_ms - paused_at_ms_ : 0; +} + +double ThermalGuard::heldSharePct() const { + if (held_hist_.size() < 2) return -1.0; + const auto& a = held_hist_.front(); + const auto& b = held_hist_.back(); + const long long span = b.first - a.first; + if (span < kMinuteMs / 2) return -1.0; + const double pct = 100.0 * static_cast(b.second - a.second) / static_cast(span); + return pct < 0.0 ? 0.0 : (pct > 100.0 ? 100.0 : pct); +} + +// Slowed: slow_factor, or kSaveHeadroom x what one picture takes to save per worker, +// whichever is longer - so a slow saver gets headroom, not a scan that waits +// on it at every waypoint. The last save time seen is kept: after a pause there +// is none for a while, and the pace should not forget why it was slowed. +void ThermalGuard::updateScale(const HostReading& r) { + if (r.save_ms_mean > 0.0) last_save_ms_ = r.save_ms_mean; + need_scale_ = 0.0; + if (r.base_interval_ms > 0.0 && last_save_ms_ > 0.0) + need_scale_ = kSaveHeadroom * last_save_ms_ / std::max(1, r.save_workers) / r.base_interval_ms; + scale_ = std::min(std::max(p_.slow_factor, need_scale_), 10.0); +} + +std::string ThermalGuard::paceText() const { + if (last_.base_interval_ms > 0.0) { + char b[64]; + std::snprintf(b, sizeof(b), "one picture every %.1f s instead of %.1f", + last_.base_interval_ms * scale_ / 1000.0, last_.base_interval_ms / 1000.0); + return b; + } + return "pictures " + factorText(scale_) + "x further apart"; +} + +void ThermalGuard::enter(GuardState s, const std::string& why, std::vector& log) { + const GuardState was = state_; + state_ = s; + reason_ = s == GuardState::Normal ? std::string() : why; + calm_since_ms_ = -1; + overload_since_ms_ = -1; + const long long now = last_.now_ms; + const std::string temp = last_.temp_ok ? "CPU at " + celsiusText(last_.cpu_c) : "CPU"; + switch (s) { + case GuardState::Slowed: + slowed_at_ms_ = now; + if (was == GuardState::Paused) + log.emplace_back(GuardLevel::Info, + temp + ": pictures resume, " + paceText() + + " until it has been calm for " + seconds(p_.calm_s * 1000LL)); + else if (why.rfind("Saving", 0) == 0) + log.emplace_back(GuardLevel::Warn, + why + ": " + paceText() + " until saving is fast enough for " + "the normal pace, for " + seconds(p_.calm_s * 1000LL)); + else + log.emplace_back(GuardLevel::Warn, + why + ": " + paceText() + " until the CPU is below " + + celsiusText(p_.slow_c - p_.hysteresis_c) + + " and keeping up for " + seconds(p_.calm_s * 1000LL)); + break; + case GuardState::Paused: + paused_at_ms_ = now; + log.emplace_back(GuardLevel::Warn, + why + ": pausing pictures for at least " + + seconds(p_.min_pause_s * 1000LL) + ", until the CPU is at " + + celsiusText(p_.resume_c) + " or below and saving has caught up"); + break; + case GuardState::Normal: + log.emplace_back(GuardLevel::Info, temp + ": back to the normal picture pace"); + break; + } +} + +std::vector ThermalGuard::update(const HostReading& r) { + std::vector log; + last_ = r; + have_last_ = true; + watch_.update(r, log); + + const long long now = r.now_ms; + hot_since_ms_ = r.temp_ok && r.cpu_c >= p_.pause_c ? (hot_since_ms_ >= 0 ? hot_since_ms_ : now) : -1; + held_since_ms_ = r.held_for_save ? (held_since_ms_ >= 0 ? held_since_ms_ : now) : -1; + // The waiting total only rises; one that fell is a restarted count. + if (!held_hist_.empty() && r.held_total_ms < held_hist_.back().second) held_hist_.clear(); + held_hist_.emplace_back(now, r.held_total_ms); + while (held_hist_.size() > 1 && now - held_hist_.front().first > kMinuteMs) held_hist_.pop_front(); + updateScale(r); + if (!enabled_) return log; + + const long long hot_ms = hot_since_ms_ >= 0 ? now - hot_since_ms_ : 0; + const long long held_ms = heldForSaveMs(); + const double share = heldSharePct(); + const bool bottleneck = share >= p_.held_share_pct; + auto shareText = [&] { + return "Saving kept the scan waiting " + std::to_string(static_cast(share + 0.5)) + + "% of the last minute"; + }; + + // Pause: from either running state. + if (state_ != GuardState::Paused) { + if (hot_since_ms_ >= 0 && hot_ms >= p_.pause_after_s * 1000LL) { + enter(GuardState::Paused, + "CPU at " + celsiusText(r.cpu_c) + " for " + seconds(hot_ms), log); + return log; + } + if (held_since_ms_ >= 0 && held_ms >= p_.held_pause_s * 1000LL) { + enter(GuardState::Paused, + "Saving has kept the scan waiting for " + seconds(held_ms), log); + return log; + } + // Slowed, and saving still the bottleneck once the minute it is judged + // over holds only slowed pictures: the slower pace did not help. + if (state_ == GuardState::Slowed) { + const bool judged = now - slowed_at_ms_ >= kMinuteMs; + overload_since_ms_ = judged && bottleneck + ? (overload_since_ms_ >= 0 ? overload_since_ms_ : now) + : -1; + if (overload_since_ms_ >= 0 && now - overload_since_ms_ >= p_.held_pause_s * 1000LL) { + enter(GuardState::Paused, shareText() + ", even slowed", log); + return log; + } + } + } + + switch (state_) { + case GuardState::Normal: { + std::string why; + if (r.temp_ok && r.cpu_c >= p_.slow_c) why = "CPU at " + celsiusText(r.cpu_c); + else if (watch_.throttling()) why = "CPU is slowing itself down"; + else if (held_since_ms_ >= 0 && held_ms >= p_.held_slow_s * 1000LL) + why = "Saving has kept the scan waiting for " + seconds(held_ms); + else if (bottleneck) why = shareText(); + if (!why.empty()) enter(GuardState::Slowed, why, log); + break; + } + case GuardState::Slowed: { + // Calm: cool enough, not throttling, not waiting for saving now, and + // under half of held_share_pct over the minute - a brief wait now and + // then is a saver with headroom, as it should be. + // And the normal pace must fit what saving takes now: a saver that is + // simply too slow for it would overload again at once, and flap. + const bool calm = !(r.temp_ok && r.cpu_c >= p_.slow_c - p_.hysteresis_c) && + !watch_.throttling() && held_since_ms_ < 0 && + !(share >= p_.held_share_pct / 2.0) && need_scale_ <= 1.0; + if (!calm) { + calm_since_ms_ = -1; + } else { + if (calm_since_ms_ < 0) calm_since_ms_ = now; + if (now - calm_since_ms_ >= p_.calm_s * 1000LL) enter(GuardState::Normal, "", log); + } + break; + } + case GuardState::Paused: { + // Saving drains while paused; resume once it has and the CPU cooled. + const bool cool = !r.temp_ok || r.cpu_c <= p_.resume_c; + if (now - paused_at_ms_ >= p_.min_pause_s * 1000LL && cool && r.save_pending == 0) + enter(GuardState::Slowed, "Resuming after a pause", log); + break; + } + } + return log; +} + +std::vector ThermalGuard::forceResume(long long now_ms) { + std::vector log; + hot_since_ms_ = held_since_ms_ = calm_since_ms_ = overload_since_ms_ = -1; + held_hist_.clear(); + if (have_last_) last_.now_ms = now_ms; + if (state_ != GuardState::Normal) { + state_ = GuardState::Normal; + reason_.clear(); + log.emplace_back(GuardLevel::Info, + "thermal resume: back to the normal picture pace (the CPU is still " + "watched; it will slow or pause again if it stays hot)"); + } else { + log.emplace_back(GuardLevel::Info, "thermal resume: already at the normal picture pace"); + } + return log; +} + +std::vector ThermalGuard::setEnabled(bool on, long long now_ms) { + std::vector log; + if (on == enabled_) { + log.emplace_back(GuardLevel::Info, std::string("CPU guard already ") + (on ? "on" : "off")); + return log; + } + enabled_ = on; + hot_since_ms_ = held_since_ms_ = calm_since_ms_ = overload_since_ms_ = -1; + held_hist_.clear(); + if (have_last_) last_.now_ms = now_ms; + state_ = GuardState::Normal; + reason_.clear(); + log.emplace_back(on ? GuardLevel::Info : GuardLevel::Warn, + on ? "CPU guard on: the scan slows or pauses when the CPU runs hot" + : "CPU guard off until restart: the scan keeps full pace however hot the " + "CPU runs (temperature and throttling are still shown and logged)"); + return log; +} + +} // namespace fgc diff --git a/src/mqtt/MqttControlChannel.cpp b/src/mqtt/MqttControlChannel.cpp index a6b1241..003db18 100644 --- a/src/mqtt/MqttControlChannel.cpp +++ b/src/mqtt/MqttControlChannel.cpp @@ -12,6 +12,8 @@ namespace { constexpr int kQos = 1; const auto kConnectTimeout = std::chrono::seconds(5); const auto kPublishTimeout = std::chrono::seconds(3); +constexpr size_t kOutboxMax = 64; +const auto kOutboxFlush = std::chrono::seconds(5); } // namespace MqttControlChannel::MqttControlChannel(const std::string& broker_uri, const std::string& tower, @@ -20,6 +22,7 @@ MqttControlChannel::MqttControlChannel(const std::string& broker_uri, const std: tower_(tower), topic_status_(towerTopic(tower, kTopicStatusCode)), topic_cam_event_(towerTopic(tower, kTopicCamEvent)), + topic_health_(towerTopic(tower, kTopicHealth)), topic_command_(towerTopic(tower, kTopicCommand)), topic_command_reply_(towerTopic(tower, kTopicCommandReply)), client_(broker_uri, tower) { @@ -29,9 +32,60 @@ MqttControlChannel::MqttControlChannel(const std::string& broker_uri, const std: if (!user.empty()) conn_opts_.set_user_name(user); if (!password.empty()) conn_opts_.set_password(password); client_.set_callback(*this); + outbox_thread_ = std::thread(&MqttControlChannel::runOutbox, this); } -MqttControlChannel::~MqttControlChannel() { disconnect(); } +MqttControlChannel::~MqttControlChannel() { + disconnect(); + stopOutbox(); +} + +void MqttControlChannel::post(const std::string& topic, const std::string& payload) { + { + std::lock_guard lock(outbox_mutex_); + if (outbox_.size() >= kOutboxMax) { + history_.record(/*outbound=*/true, Delivery::Dropped, outbox_.front().first, + outbox_.front().second); + outbox_.pop_front(); + } + outbox_.emplace_back(topic, payload); + } + outbox_cv_.notify_one(); +} + +void MqttControlChannel::runOutbox() { + std::unique_lock lock(outbox_mutex_); + for (;;) { + outbox_cv_.wait(lock, [this] { return outbox_stop_ || !outbox_.empty(); }); + if (outbox_.empty()) return; // stopping, and nothing left + auto m = std::move(outbox_.front()); + outbox_.pop_front(); + lock.unlock(); + publish(m.first, m.second); + lock.lock(); + } +} + +void MqttControlChannel::stopOutbox() { + if (!outbox_thread_.joinable()) return; + // Give what is waiting a few seconds to go out; drop (and record) the rest. + const auto deadline = std::chrono::steady_clock::now() + kOutboxFlush; + for (;;) { + { + std::lock_guard lock(outbox_mutex_); + if (outbox_.empty() || std::chrono::steady_clock::now() >= deadline) { + for (const auto& m : outbox_) + history_.record(/*outbound=*/true, Delivery::Dropped, m.first, m.second); + outbox_.clear(); + outbox_stop_ = true; + break; + } + } + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + } + outbox_cv_.notify_all(); + outbox_thread_.join(); +} bool MqttControlChannel::connect() { try { @@ -47,6 +101,7 @@ bool MqttControlChannel::connect() { } void MqttControlChannel::disconnect() { + stopOutbox(); // the last pictures' events go out before the link closes if (!client_.is_connected()) return; try { client_.disconnect()->wait(); @@ -66,22 +121,28 @@ void MqttControlChannel::connected(const std::string& /*cause*/) { // Say the mode again: whoever watches StatusCode may have missed changes // while the link was down. Not waited on - this runs on Paho's callback // thread, which is the one that would deliver the acknowledgement. - std::string status; + // Health likewise: a paused tower must not look healthy to a page that + // subscribed while the link was down. + std::string status, health; { std::lock_guard lock(mutex_); status = last_status_; + health = last_health_; } - if (!status.empty()) { + auto republish = [this](const std::string& topic, const std::string& payload) { + if (payload.empty()) return; try { - auto msg = mqtt::make_message(topic_status_, status); + auto msg = mqtt::make_message(topic, payload); msg->set_qos(kQos); msg->set_retained(true); client_.publish(msg); - history_.record(/*outbound=*/true, Delivery::Ok, topic_status_, status); + history_.record(/*outbound=*/true, Delivery::Ok, topic, payload); } catch (const mqtt::exception& e) { - LOG_WARN << "MQTT status republish failed: " << e.what(); + LOG_WARN << "MQTT republish of " << topic << " failed: " << e.what(); } - } + }; + republish(topic_status_, status); + republish(topic_health_, health); } void MqttControlChannel::connection_lost(const std::string& cause) { @@ -160,7 +221,16 @@ void MqttControlChannel::publishStatus(int code) { } void MqttControlChannel::publishCamEvent(const CamEvent& e) { - publish(topic_cam_event_, camEventJson(e)); + post(topic_cam_event_, camEventJson(e)); +} + +void MqttControlChannel::publishHealth(const HealthReport& h) { + const std::string payload = healthJson(h); + { + std::lock_guard lock(mutex_); + last_health_ = payload; + } + post(topic_health_, payload); } } // namespace fgc diff --git a/src/ui/TuiUi.cpp b/src/ui/TuiUi.cpp index 8159438..10c7e88 100644 --- a/src/ui/TuiUi.cpp +++ b/src/ui/TuiUi.cpp @@ -369,9 +369,14 @@ Element sensorsPanel(const SensorsView& s) { ? (text(" IMU CFG ") | color(Color::Red) | bold) : text(""); Element fan_badge = s.fan_alert ? (text(" FAN ") | color(Color::Red) | bold) : text(""); + Element cpu_badge = s.cpu_badge.empty() + ? text("") + : (text(" " + s.cpu_badge + " ") | + color(s.cpu_badge_red ? Color::Red : Color::Yellow) | bold); return panel("[s] SENSORS", Color::Magenta, vbox({group(s.imu, std::move(imu_badge)), separator(), - group(s.env, std::move(fan_badge))})); + group(s.env, std::move(fan_badge)), separator(), + group(s.host, std::move(cpu_badge))})); } Element cameraPanel(const CaptureView& c) { @@ -967,7 +972,8 @@ Element gimbalDetailPanel(const GimbalView& g, const DumpView& dump, const Calib // then the ambient sensor. The two devices get their own headed sections, and // each temperature is labelled with its provenance ("internal" vs "ambient"), // so the reading the main window shows is never confused with the MTi's. -Element sensorsDetailPanel(const ImuView& v, const EnvView& e, ScrollState& st) { +Element sensorsDetailPanel(const ImuView& v, const EnvView& e, const HostView& h, + ScrollState& st) { // Fixed-width, right-aligned to 2 decimals. The constant width keeps the sign // column and decimal point from jumping as values cross zero or change digit // count, so the readout stays steady instead of flickering. The width is kept @@ -1085,6 +1091,79 @@ Element sensorsDetailPanel(const ImuView& v, const EnvView& e, ScrollState& st) } } + // LATTEPANDA CPU section: the computer's own heat and load, and how the CPU + // guard (ThermalGuard) is pacing the scan for it. Live like the two above, + // so it sits above the static IMU CONFIG block too. + { + auto row = [](const std::string& k, Element val) { + return hbox({text(k) | dim | size(WIDTH, EQUAL, 16), std::move(val)}); + }; + auto fx = [](double x, int d) { + char b[32]; + std::snprintf(b, sizeof(b), "%.*f", d, x); + return std::string(b); + }; + body.push_back(separator()); + body.push_back(text("LattePanda \xE2\x80\x94 CPU") | bold | color(Color::Magenta)); + if (!h.present) { + body.push_back(text("(no reading yet)") | dim); + } else { + const bool hot = h.temp_ok && h.cpu_c >= h.slow_c; + Element temp = h.temp_ok ? text(fx(h.cpu_c, 1) + " \xC2\xB0""C") | bold + : text("unknown") | dim; + if (hot) temp = temp | color(h.cpu_c >= h.pause_c ? Color::Red : Color::Yellow); + body.push_back(row("CPU temp", hbox({temp, text(" " + h.zone) | dim}))); + if (h.clock_ok) { + std::string cores; + for (double m : h.core_mhz) cores += (cores.empty() ? "" : " ") + fx(m / 1000.0, 1); + std::string cap; + if (h.cap_mhz > 0.0 && h.hw_max_mhz > 0.0 && h.cap_mhz < h.hw_max_mhz - 1.0) + cap = ", capped at " + fx(h.cap_mhz / 1000.0, 1); + body.push_back(row("Clock", hbox({text(fx(h.mean_mhz / 1000.0, 2) + " GHz") | bold, + text(" of " + fx(h.hw_max_mhz / 1000.0, 1) + + cap + " (cores " + cores + ")") | dim}))); + } else { + body.push_back(row("Clock", text("unknown") | dim)); + } + body.push_back(row("Load", + hbox({text(h.busy_pct >= 0.0 ? fx(h.busy_pct, 0) + " % busy" + : std::string("-- % busy")) | bold, + text(" load " + fx(h.load1, 2) + " / " + fx(h.load5, 2) + + " / " + fx(h.load15, 2) + " on " + + std::to_string(h.cpus) + " cores") | dim}))); + std::string thr = h.throttling ? "yes, now (" + h.throttle_cause + ")" : "no"; + std::string thr_detail = h.counters_ok + ? " " + std::to_string(h.throttle_last_min) + " in the last minute, " + + std::to_string(h.throttle_total) + " since start" + : " (the kernel does not count it here; judged from the clock)"; + body.push_back(row("Throttled", + hbox({h.throttling ? text(thr) | color(Color::Red) | bold + : text(thr) | bold, + text(thr_detail) | dim}))); + if (h.save_count > 0) + body.push_back(row("Saving", hbox({text(fx(h.save_mean_ms, 0) + " ms") | bold, + text(" per picture, longest " + + fx(h.save_max_ms, 0) + " ms, " + + std::to_string(h.save_count) + + " in the last minute") | dim}))); + Element st = text(h.state); + if (!h.guard_on) st = text("off (full pace whatever the heat)") | color(Color::Yellow) | bold; + else if (h.state == "paused") st = st | color(Color::Red) | bold; + else if (h.state == "slowed") st = st | color(Color::Yellow) | bold; + else st = st | color(Color::Green); + std::string pace = " one picture every " + fx(h.interval_s, 1) + " s"; + if (h.state == "paused") pace = " for " + std::to_string(h.paused_s) + " s"; + body.push_back(row("Scan pace", hbox({st, text(pace) | dim}))); + if (!h.reason.empty()) body.push_back(row("", text(h.reason) | dim)); + if (h.held_s > 0) + body.push_back(row("", text("waiting " + std::to_string(h.held_s) + + " s for saving to catch up") | color(Color::Yellow))); + body.push_back(text("slows at " + fx(h.slow_c, 0) + " \xC2\xB0""C, pauses at " + + fx(h.pause_c, 0) + ", resumes at " + fx(h.resume_c, 0) + + " ('thermal' to see, 'thermal off|on|resume')") | dim); + } + } + // IMU CONFIG section (device configuration read back at startup). Shown // whenever it is known, even if the live stream is offline. const auto& c = v.config; @@ -1370,7 +1449,7 @@ void TuiUi::uiLoop() { bottom}); case Overlay::Sensors: return vbox({header, separator(), - sensorsDetailPanel(s.imu, s.env, sensors_scroll) | flex, bottom}); + sensorsDetailPanel(s.imu, s.env, s.host, sensors_scroll) | flex, bottom}); case Overlay::Cameras: return vbox({header, separator(), cameraDetailPanel(s.capture, cam_scroll) | flex, bottom}); case Overlay::Conn: diff --git a/src/ui/UiSnapshot.cpp b/src/ui/UiSnapshot.cpp index acea9cb..137c7e2 100644 --- a/src/ui/UiSnapshot.cpp +++ b/src/ui/UiSnapshot.cpp @@ -99,6 +99,15 @@ SensorsView pendingSensorsView() { v.env.status = "pending"; v.env.fields.push_back({"Temp", "--.-", "\xC2\xB0""C", false}); v.env.fields.push_back({"Humid", "--", "%RH", false}); + // The computer itself: its CPU's own temperature (not the box's - that is + // the ambient Temp above), clock, how busy it is, and whether it is + // slowing itself down to stay cool. + v.host.title = "LattePanda CPU"; + v.host.status = "pending"; + v.host.fields.push_back({"CPU temp", "--.-", "\xC2\xB0""C", false}); + v.host.fields.push_back({"Clock", "-.-", "GHz", false}); + v.host.fields.push_back({"Load", "--", "%", false}); + v.host.fields.push_back({"Throttled", "--", "", false}); return v; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 5c83884..afcb915 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -59,6 +59,7 @@ add_executable(fgc_tests test_testrunner.cpp test_rawimage.cpp test_settlerule.cpp + test_thermalguard.cpp ) target_link_libraries(fgc_tests PRIVATE fgc_core doctest::doctest) # So a test can read the shipped config/config.example.ini and hold it to what diff --git a/tests/test_imagepipeline.cpp b/tests/test_imagepipeline.cpp index 79799bd..dac0f5b 100644 --- a/tests/test_imagepipeline.cpp +++ b/tests/test_imagepipeline.cpp @@ -185,6 +185,22 @@ TEST_CASE("several workers save every frame") { CHECK(files == 9); } +TEST_CASE("save timing counts the pictures saved in the last minute and how long they took") { + TempDir t; + FakeChannel ch; + ImagePipeline pipe(ch, nullptr, params(t, 1.0)); + CHECK(pipe.saveTiming().count == 0); + pipe.start(); + for (int i = 0; i < 3; ++i) + pipe.submit(bayerFrame(PixelLayout::BayerRG12p, 64, 48, 3000, 1500, 700, 7000 + i)); + REQUIRE(waitSaved(pipe, 3)); + pipe.stop(); + const auto s = pipe.saveTiming(); + CHECK(s.count == 3); + CHECK(s.mean_ms > 0.0); + CHECK(s.max_ms >= s.mean_ms); +} + TEST_CASE("canAccept says no once queue_max frames are waiting, yes once they are saved") { TempDir t; FakeChannel ch; diff --git a/tests/test_scheduler.cpp b/tests/test_scheduler.cpp index dfd1f33..a18aea6 100644 --- a/tests/test_scheduler.cpp +++ b/tests/test_scheduler.cpp @@ -914,3 +914,109 @@ TEST_CASE("the keep rule starts afresh at each waypoint") { } CHECK(r.cam.triggers == 2); } + +TEST_CASE("the CPU guard's pause finishes the waypoint under way, parks, and loses nothing") { + long long clock = 0; + FakeMotor motor; + FakeCamera cam; + ScanGrid grid({{0.0, 0.0}, {90.0, 0.0}}); + CaptureScheduler sch(motor, cam, 1.0 /*1000 ms*/, testGeometry(), grid, + [&] { return clock; }); + sch.setSettleWait(0, 0); + sch.setMode(Mode::Auto); + + clock = 1100; sch.tick(); // first move + REQUIRE(motor.cmds.size() == 1); + sch.setPaused(true); // paused mid-waypoint + CHECK_FALSE(sch.pausedAtRest()); // the waypoint is still under way + settleAt(motor, 0, 0); + clock = 1200; sch.tick(); + clock = 1300; sch.tick(); + CHECK(cam.triggers == 1); // ...and keeps its frame + CHECK(sch.pausedAtRest()); + + clock = 60000; sch.tick(); + CHECK(motor.cmds.size() == 1); // parked: no next move however long + CHECK(cam.triggers == 1); + + sch.setPaused(false); + clock = 60100; sch.tick(); + REQUIRE(motor.cmds.size() == 2); + CHECK(motor.cmds[1] == "MOVE 900,0"); // the waypoint it was due, not skipped + motor.tel.yaw.standstill = false; + clock = 60200; settleAt(motor, 900, 0); sch.tick(); + clock = 60300; sch.tick(); + CHECK(cam.triggers == 2); + CHECK(sch.overruns() == 0); // the pause is not an overrun +} + +TEST_CASE("the time held for saving adds up, the hold under way included") { + long long clock = 0; + FakeMotor motor; + FakeCamera cam; + ScanGrid grid({{0.0, 0.0}, {0.0, 0.0}}); + CaptureScheduler sch(motor, cam, 1.0 /*1000 ms*/, testGeometry(), grid, + [&] { return clock; }); + sch.setSettleWait(0, 0); + bool room = false; + sch.setSaveGate([&room] { return room; }); + sch.setMode(Mode::Auto); + settleAt(motor, 0, 0); + + clock = 1000; sch.tick(); // held from here + clock = 3000; sch.tick(); + CHECK(sch.heldForSaveTotalMs() == 2000); // still held: counted so far + room = true; + clock = 3500; sch.tick(); // released after 2.5 s + CHECK(sch.heldForSaveTotalMs() == 2500); + clock = 9000; + CHECK(sch.heldForSaveTotalMs() == 2500); // not held: no more +} + +TEST_CASE("the CPU guard's interval scale spaces pictures further apart") { + long long clock = 0; + FakeMotor motor; + FakeCamera cam; + ScanGrid grid({{0.0, 0.0}, {0.0, 0.0}}); + CaptureScheduler sch(motor, cam, 1.0 /*1000 ms*/, testGeometry(), grid, + [&] { return clock; }); + sch.setSettleWait(0, 0); + sch.setMode(Mode::Auto); + sch.setIntervalScale(2.0); + CHECK(sch.intervalMs() == doctest::Approx(2000.0)); + + settleAt(motor, 0, 0); + clock = 10; sch.tick(); // move + clock = 100; sch.tick(); // standstill + clock = 1500; sch.tick(); + CHECK(cam.triggers == 0); // 1 s would have fired; 2 s has not + clock = 2000; sch.tick(); + CHECK(cam.triggers == 1); + + sch.setIntervalScale(0.5); // never faster than configured + CHECK(sch.intervalScale() == doctest::Approx(1.0)); + CHECK(sch.intervalMs() == doctest::Approx(1000.0)); +} + +TEST_CASE("the CPU guard's pause stops continuous capture keeping frames; a snapshot still fires") { + long long clock = 0; + FakeMotor motor; + FakeCamera cam; + ScanGrid grid({{0.0, 0.0}}); + CaptureScheduler sch(motor, cam, 1.0 /*1000 ms*/, testGeometry(), grid, + [&] { return clock; }); + sch.setSettleWait(0, 0); + sch.setContinuous(true); + settleAt(motor, 0, 0); + sch.setPaused(true); + for (clock = 100; clock < 5000; clock += 100) sch.tick(); + CHECK(cam.triggers == 0); + + sch.requestSnapshot(); + for (clock = 5000; clock < 5300; clock += 100) sch.tick(); + CHECK(cam.triggers == 1); + + sch.setPaused(false); + for (clock = 5300; clock < 7000; clock += 100) sch.tick(); + CHECK(cam.triggers >= 2); +} diff --git a/tests/test_thermalguard.cpp b/tests/test_thermalguard.cpp new file mode 100644 index 0000000..6744636 --- /dev/null +++ b/tests/test_thermalguard.cpp @@ -0,0 +1,483 @@ +#include + +#include "fgc/ChannelTopics.h" +#include "fgc/Config.h" +#include "fgc/HostMetrics.h" +#include "fgc/OperatingMode.h" +#include "fgc/ThermalGuard.h" + +#include +#include +#include +#include + +#include + +using namespace fgc; +namespace fs = std::filesystem; + +namespace { + +// A host at `c` degrees, 2.8 of 3.6 GHz, 30% busy, counters at `count`. +HostReading at(long long t_s, double c, long long count = 0) { + HostReading r; + r.now_ms = t_s * 1000; + r.temp_ok = true; + r.cpu_c = c; + r.zone = "x86_pkg_temp"; + r.clock_ok = true; + r.mhz = 2800; + r.hw_max_mhz = 3600; + r.count_ok = true; + r.throttle_count = count; + r.busy_pct = 30; + return r; +} + +bool logged(const std::vector& log, const std::string& part) { + for (const auto& l : log) + if (l.second.find(part) != std::string::npos) return true; + return false; +} + +// Feed a reading every 2 s from t0 to t1 (inclusive), collecting the log. +std::vector run(ThermalGuard& g, long long t0, long long t1, double c, + long long count = 0) { + std::vector all; + for (long long t = t0; t <= t1; t += 2) { + auto l = g.update(at(t, c, count)); + all.insert(all.end(), l.begin(), l.end()); + } + return all; +} + +struct FakeSys { + fs::path root; + FakeSys() { + root = fs::temp_directory_path() / ("fgc_fakesys_" + std::to_string(::getpid())); + fs::remove_all(root); + } + ~FakeSys() { fs::remove_all(root); } + void put(const std::string& rel, const std::string& text) { + fs::path p = root / rel; + fs::create_directories(p.parent_path()); + std::ofstream(p) << text; + } +}; + +} // namespace + +TEST_CASE("a cool, idle CPU stays at the normal pace") { + ThermalGuard g; + auto log = run(g, 0, 600, 60.0); + CHECK(g.state() == GuardState::Normal); + CHECK(g.intervalScale() == doctest::Approx(1.0)); + CHECK_FALSE(g.pauseCapture()); + CHECK(log.empty()); +} + +TEST_CASE("a hot CPU slows the scan, and the slow-down lifts only after it has been calm") { + ThermalParams p; + ThermalGuard g(p); + run(g, 0, 10, 70.0); + auto log = g.update(at(12, 86.0)); + CHECK(g.state() == GuardState::Slowed); + CHECK(g.intervalScale() == doctest::Approx(p.slow_factor)); + CHECK(logged(log, "CPU at 86 \xC2\xB0""C")); + CHECK(logged(log, "2x further apart")); + + // Below slow_c but inside the hysteresis band: stays slowed. + run(g, 14, 400, 82.0); + CHECK(g.state() == GuardState::Slowed); + + // Below slow_c - hysteresis, but not yet for calm_s. + run(g, 402, 402 + p.calm_s - 4, 78.0); + CHECK(g.state() == GuardState::Slowed); + // A hot blip restarts the calm clock. + g.update(at(402 + p.calm_s - 2, 81.0)); + run(g, 402 + p.calm_s, 402 + 2 * p.calm_s - 2, 78.0); + CHECK(g.state() == GuardState::Slowed); + log = run(g, 402 + 2 * p.calm_s, 402 + 2 * p.calm_s + 4, 78.0); + CHECK(g.state() == GuardState::Normal); + CHECK(logged(log, "back to the normal picture pace")); +} + +TEST_CASE("a CPU that stays very hot pauses pictures, and resumes once cool and the pause is served") { + ThermalParams p; + ThermalGuard g(p); + // One hot sample is not enough: pause_after_s of it is. + g.update(at(0, 93.0)); + CHECK(g.state() == GuardState::Slowed); + auto log = run(g, 2, p.pause_after_s, 93.0); + CHECK(g.state() == GuardState::Paused); + CHECK(g.pauseCapture()); + CHECK(logged(log, "pausing pictures")); + + // Cooled quickly, but the pause runs its minimum. + const long long paused_at = p.pause_after_s; + run(g, paused_at + 2, paused_at + p.min_pause_s - 2, 70.0); + CHECK(g.state() == GuardState::Paused); + log = run(g, paused_at + p.min_pause_s, paused_at + p.min_pause_s + 2, 70.0); + CHECK(g.state() == GuardState::Slowed); // resumes slowed, not straight to full pace + CHECK(logged(log, "pictures resume")); +} + +TEST_CASE("a pause lasts while the CPU is above resume_c, or saving has not caught up") { + ThermalParams p; + ThermalGuard g(p); + run(g, 0, p.pause_after_s + 2, 95.0); + REQUIRE(g.state() == GuardState::Paused); + run(g, 30, 30 + 2 * p.min_pause_s, p.resume_c + 3); + CHECK(g.state() == GuardState::Paused); // served its time, still too warm + + HostReading r = at(1000, 70.0); + r.save_pending = 1; + g.update(r); + CHECK(g.state() == GuardState::Paused); // cool, but a frame is still saving + g.update(at(1002, 70.0)); + CHECK(g.state() == GuardState::Slowed); +} + +TEST_CASE("saving that keeps the scan waiting slows it, then pauses it") { + ThermalParams p; + ThermalGuard g(p); + auto held = [](long long t) { + HostReading r = at(t, 60.0); + r.held_for_save = true; + r.save_pending = 4; + return r; + }; + // Brief holds are normal. + for (long long t = 0; t < p.held_slow_s; t += 2) g.update(held(t)); + CHECK(g.state() == GuardState::Normal); + g.update(at(p.held_slow_s, 60.0)); // caught up: the clock restarts + for (long long t = p.held_slow_s + 2; t < 2 * p.held_slow_s; t += 2) g.update(held(t)); + CHECK(g.state() == GuardState::Normal); + + std::vector log; + long long t = 100; + for (; t <= 100 + p.held_slow_s; t += 2) { + auto l = g.update(held(t)); + log.insert(log.end(), l.begin(), l.end()); + } + CHECK(g.state() == GuardState::Slowed); + CHECK(logged(log, "Saving has kept the scan waiting")); + for (; t <= 100 + p.held_pause_s; t += 2) g.update(held(t)); + CHECK(g.state() == GuardState::Paused); +} + +// A saver too slow for the interval: the scan waits `wait_s` out of every +// `cycle_s`, never long in one go - the overload the desk run with a CPU quota +// showed (save 4.6 s against a 2 s interval). Feeds samples every 2 s from t0 +// to t1, keeping the running waiting total in `held`. +std::vector runSlowSaver(ThermalGuard& g, long long t0, long long t1, double wait_frac, + long long& held, double save_ms = 4600.0) { + std::vector all; + for (long long t = t0; t <= t1; t += 2) { + HostReading r = at(t, 60.0); + held += static_cast(2000 * wait_frac); + r.held_total_ms = held; + r.held_for_save = (t / 2) % 2 == 0 && wait_frac > 0.0; // short waits, on and off + r.save_pending = 2; + r.base_interval_ms = 2000; + r.save_ms_mean = save_ms; + r.save_workers = 1; + auto l = g.update(r); + all.insert(all.end(), l.begin(), l.end()); + } + return all; +} + +TEST_CASE("a saver that holds the scan briefly but often slows it, to a pace saving can keep") { + ThermalParams p; + ThermalGuard g(p); + long long held = 0; + // 10% of the time waiting: a saver with headroom; nothing to do. + runSlowSaver(g, 0, 120, 0.10, held); + CHECK(g.state() == GuardState::Normal); + // 50%: the bottleneck. Judged on half a minute at least, then slowed. + auto log = runSlowSaver(g, 122, 200, 0.50, held); + CHECK(g.state() == GuardState::Slowed); + CHECK(logged(log, "of the last minute")); + // The pace follows what saving takes: 1.5 x 4.6 s = 6.9 s, not 2 x 2 s. + CHECK(g.intervalScale() == doctest::Approx(1.5 * 4600.0 / 2000.0)); + CHECK(logged(log, "one picture every 6.9 s instead of 2.0")); +} + +TEST_CASE("slowed, but saving still the bottleneck: paused; with headroom: back to normal") { + ThermalParams p; + { + ThermalGuard g(p); + long long held = 0; + long long s = 0; // when it slowed + while (g.state() == GuardState::Normal && s < 120) { + s += 2; + runSlowSaver(g, s, s, 0.5, held); + } + REQUIRE(g.state() == GuardState::Slowed); + // Judged only once a minute of slowed pictures is in the window, then + // held_pause_s of it. + runSlowSaver(g, s + 2, s + 60 + p.held_pause_s - 4, 0.5, held); + CHECK(g.state() == GuardState::Slowed); + auto log = runSlowSaver(g, s + 60 + p.held_pause_s - 2, s + 60 + p.held_pause_s + 4, + 0.5, held); + CHECK(g.state() == GuardState::Paused); + CHECK(logged(log, "even slowed")); + } + { + ThermalGuard g(p); + long long held = 0; + runSlowSaver(g, 0, 60, 0.5, held); + REQUIRE(g.state() == GuardState::Slowed); + // The slower pace gave saving its headroom: no waiting at all. But a + // save still 4.6 s long does not fit the 2 s pace, so going back would + // only overload again: it stays slowed, at a pace it can hold, no flapping. + runSlowSaver(g, 62, 62 + 60 + p.calm_s, 0.0, held); + CHECK(g.state() == GuardState::Slowed); + // Saving fast again (the CPU cooled, the load went): back to normal. + long long t = 62 + 60 + p.calm_s + 2; + runSlowSaver(g, t, t + p.calm_s + 4, 0.0, held, 1000.0); + CHECK(g.state() == GuardState::Normal); + } +} + +TEST_CASE("the slowed pace never goes past 10x, and remembers the save time through a pause") { + ThermalGuard g; + long long held = 0; + runSlowSaver(g, 0, 60, 0.5, held, 60000.0); // a minute per picture + REQUIRE(g.state() == GuardState::Slowed); + CHECK(g.intervalScale() == doctest::Approx(10.0)); +} + +TEST_CASE("missing readings are left out of the rules, not taken as zero or as hot") { + ThermalGuard g; + for (long long t = 0; t < 600; t += 2) { + HostReading r; + r.now_ms = t * 1000; + auto log = g.update(r); + CHECK(log.empty()); + } + CHECK(g.state() == GuardState::Normal); +} + +TEST_CASE("thermal off keeps full pace; thermal resume clears a pause") { + ThermalParams p; + ThermalGuard g(p); + run(g, 0, p.pause_after_s + 2, 95.0); + REQUIRE(g.state() == GuardState::Paused); + + auto log = g.forceResume(30000); + CHECK(g.state() == GuardState::Normal); + CHECK_FALSE(log.empty()); + // Still hot: the rules apply afresh - slowed at once, paused after pause_after_s. + g.update(at(32, 95.0)); + CHECK(g.state() == GuardState::Slowed); + run(g, 34, 32 + p.pause_after_s, 95.0); + CHECK(g.state() == GuardState::Paused); + + g.setEnabled(false, 100000); + CHECK_FALSE(g.enabled()); + CHECK(g.state() == GuardState::Normal); + run(g, 100, 1000, 99.0); + CHECK(g.state() == GuardState::Normal); + g.setEnabled(true, 1002000); + g.update(at(1004, 99.0)); + CHECK(g.state() == GuardState::Slowed); +} + +TEST_CASE("throttling is logged when it starts, summarised once a minute, and when it ends") { + ThermalGuard g; + // Counters still: nothing. + auto log = run(g, 0, 10, 80.0, 100); + CHECK_FALSE(g.throttle().throttling()); + CHECK(log.empty()); + + // The counter rises: one start line, with the temperature and clock. + log = g.update(at(12, 90.0, 103)); + CHECK(g.throttle().throttling()); + CHECK(logged(log, "CPU is slowing itself to stay cool: 90 \xC2\xB0""C, running at 2.8 GHz of 3.6 GHz")); + CHECK(g.state() == GuardState::Slowed); + + // Rising every sample for 70 s: exactly one summary. + int summaries = 0; + long long count = 103; + for (long long t = 14; t <= 82; t += 2) { + count += 2; + for (const auto& l : g.update(at(t, 90.0, count))) + if (l.second.find("CPU still slowed") != std::string::npos) ++summaries; + } + CHECK(summaries == 1); + CHECK(g.throttle().eventsLastMinute() > 0); + + // Counter stops: the end line comes after a quiet minute, not before. + log = run(g, 84, 140, 80.0, count); + CHECK(g.throttle().throttling()); + CHECK_FALSE(logged(log, "no longer slowed")); + log = run(g, 142, 146, 80.0, count); + CHECK_FALSE(g.throttle().throttling()); + CHECK(logged(log, "CPU no longer slowed: 80 \xC2\xB0""C")); +} + +TEST_CASE("throttling is inferred from a low clock while busy, as a capped or power-limited CPU shows") { + ThermalGuard g; + auto slow = [](long long t, double busy) { + HostReading r = at(t, 70.0); + r.count_ok = false; // no kernel counters on this host + r.mhz = 800; + r.busy_pct = busy; + return r; + }; + // Low clock while idle is just an idle CPU. + for (long long t = 0; t < 20; t += 2) g.update(slow(t, 5)); + CHECK_FALSE(g.throttle().throttling()); + // One busy sample may be the governor catching up; two are not. + auto log = g.update(slow(20, 90)); + CHECK_FALSE(g.throttle().throttling()); + log = g.update(slow(22, 90)); + CHECK(g.throttle().throttling()); + CHECK(g.throttle().cause() == "clock"); + CHECK(logged(log, "judged from its clock")); +} + +TEST_CASE("a throttle counter that goes backwards is a fresh start, not throttling") { + ThermalGuard g; + g.update(at(0, 70.0, 500)); + auto log = g.update(at(2, 70.0, 3)); + CHECK_FALSE(g.throttle().throttling()); + CHECK(log.empty()); +} + +TEST_CASE("host readers on a fake sysfs tree") { + FakeSys s; + s.put("sys/class/thermal/thermal_zone0/temp", "48000\n"); + s.put("sys/class/thermal/thermal_zone0/type", "acpitz\n"); + s.put("sys/class/thermal/thermal_zone1/temp", "91500\n"); + s.put("sys/class/thermal/thermal_zone1/type", "x86_pkg_temp\n"); + for (int c = 0; c < 4; ++c) { + const std::string cpu = "sys/devices/system/cpu/cpu" + std::to_string(c) + "/"; + s.put(cpu + "cpufreq/scaling_cur_freq", std::to_string(1000000 + c * 200000) + "\n"); + s.put(cpu + "cpufreq/cpuinfo_max_freq", "3600000\n"); + s.put(cpu + "cpufreq/scaling_max_freq", c == 2 ? "1600000\n" : "3600000\n"); + s.put(cpu + "thermal_throttle/package_throttle_count", "7\n"); + s.put(cpu + "thermal_throttle/core_throttle_count", "1\n"); + } + s.put("sys/devices/system/cpu/cpufreq/policy0/scaling_cur_freq", "999\n"); // not a core + s.put("proc/stat", "cpu 100 0 50 800 50 0 0 0 0 0\ncpu0 1 2 3 4\n"); + const std::string root = s.root.string(); + + auto th = hostmetrics::readThermal(root); + REQUIRE(th.ok); + CHECK(th.max_temp_c == doctest::Approx(91.5)); + CHECK(th.hottest_zone == "x86_pkg_temp"); + CHECK(th.cpu_temp_c == doctest::Approx(91.5)); + + // A Wi-Fi card hotter than the CPU (the towers list iwlwifi_1): the hottest + // zone is the card, but the CPU guard still reads the CPU package. + s.put("sys/class/thermal/thermal_zone2/temp", "97000\n"); + s.put("sys/class/thermal/thermal_zone2/type", "iwlwifi_1\n"); + th = hostmetrics::readThermal(root); + CHECK(th.hottest_zone == "iwlwifi_1"); + CHECK(th.cpu_zone == "x86_pkg_temp"); + CHECK(th.cpu_temp_c == doctest::Approx(91.5)); + + auto f = hostmetrics::readCpuFreq(root); + REQUIRE(f.ok); + CHECK(f.core_mhz.size() == 4); + CHECK(f.core_mhz[0] == doctest::Approx(1000.0)); + CHECK(f.mean_mhz == doctest::Approx(1300.0)); + CHECK(f.hw_max_mhz == doctest::Approx(3600.0)); + CHECK(f.cap_mhz == doctest::Approx(1600.0)); + + auto c = hostmetrics::readThrottleCount(root); + REQUIRE(c.ok); + CHECK(c.events == 7 + 4); // the package once, every core + + auto t = hostmetrics::readCpuTimes(root); + REQUIRE(t.ok); + CHECK(t.total == 1000); + CHECK(t.busy == 150); + + // A tree without proc/stat reads the real one: busy % stays real in a simulation. + fs::remove(s.root / "proc/stat"); + CHECK(hostmetrics::readCpuTimes(root).ok); + + // Nothing there: not ok, no throw. + CHECK_FALSE(hostmetrics::readCpuFreq("/nonexistent-fgc").ok); + CHECK_FALSE(hostmetrics::readThrottleCount("/nonexistent-fgc").ok); + CHECK(hostmetrics::readCpuTimes("/nonexistent-fgc").ok); // the real /proc/stat + CHECK_FALSE(hostmetrics::readThermal("/nonexistent-fgc").ok); +} + +TEST_CASE("[Thermal] config: defaults, overrides and the order of the limits") { + AppConfig d = ConfigLoader::fromMap({}); + CHECK(d.thermal.enabled); + CHECK(d.thermal.sysfs_root == "/"); + CHECK(d.thermal.slow_c == doctest::Approx(85.0)); + CHECK(d.thermal.pause_c == doctest::Approx(92.0)); + CHECK(d.thermal.resume_c == doctest::Approx(75.0)); + CHECK_FALSE(d.features.mock_camera_full); + + AppConfig c = ConfigLoader::fromMap({{"Thermal.slow_c", "80"}, + {"Thermal.pause_c", "88"}, + {"Thermal.resume_c", "70"}, + {"Thermal.slow_factor", "3"}, + {"Thermal.sysfs_root", "/tmp/fake"}, + {"Thermal.enabled", "false"}, + {"Features.mock_camera_size", "full"}}); + CHECK_FALSE(c.thermal.enabled); + CHECK(c.thermal.slow_c == doctest::Approx(80.0)); + CHECK(c.thermal.slow_factor == doctest::Approx(3.0)); + CHECK(c.thermal.sysfs_root == "/tmp/fake"); + CHECK(c.features.mock_camera_full); + + CHECK_THROWS(ConfigLoader::fromMap({{"Thermal.slow_c", "95"}})); // above pause_c + CHECK_THROWS(ConfigLoader::fromMap({{"Thermal.resume_c", "90"}})); // above slow_c + CHECK_THROWS(ConfigLoader::fromMap({{"Thermal.slow_factor", "0.5"}})); + CHECK_THROWS(ConfigLoader::fromMap({{"Thermal.held_pause_s", "10"}})); // under held_slow_s + CHECK_THROWS(ConfigLoader::fromMap({{"Thermal.sample_ms", "10"}})); + CHECK_THROWS(ConfigLoader::fromMap({{"Features.mock_camera_size", "huge"}})); +} + +TEST_CASE("Health payload: unknown readings are null, numbers rounded") { + HealthReport h; + h.tower = "t121"; + h.state = "paused"; + h.reason = "CPU at 93 \xC2\xB0""C for 20 s"; + h.cpu_c_ok = true; + h.cpu_c = 93.04; + h.cpu_mhz_ok = true; + h.cpu_mhz = 1187.6; + h.throttling = true; + h.throttle_events_last_min = 14; + h.interval_s = 10.0; + h.held_s = 3; + h.timestamp_ms = 1700000000000; + const std::string j = healthJson(h); + CHECK(j.find("\"state\":\"paused\"") != std::string::npos); + CHECK(j.find("\"cpu_c\":93.0") != std::string::npos); + CHECK(j.find("\"cpu_mhz\":1188") != std::string::npos); + CHECK(j.find("\"cpu_busy_pct\":null") != std::string::npos); + CHECK(j.find("\"enclosure_c\":null") != std::string::npos); + CHECK(j.find("\"save_ms_mean\":null") != std::string::npos); + CHECK(j.find("\"throttling\":true") != std::string::npos); + CHECK(j.find("\"throttle_events_last_min\":14") != std::string::npos); + CHECK(j.find("\"interval_s\":10.0") != std::string::npos); + CHECK(j.find("\"time\":1700000000000") != std::string::npos); + CHECK(j.front() == '{'); + CHECK(j.back() == '}'); +} + +TEST_CASE("the CPU guard's commands work in every mode") { + CHECK(allowedIn(Mode::Auto, "thermal")); + CHECK(allowedIn(Mode::Pan, "thermal resume")); + CHECK(allowedIn(Mode::Auto, "thermal off")); +} + +TEST_CASE("busy percent between two /proc/stat reads") { + auto a = hostmetrics::parseProcStatCpu("cpu 100 0 100 700 100 0 0 0\n"); + auto b = hostmetrics::parseProcStatCpu("cpu 400 0 100 1000 100 0 0 0\n"); + CHECK(hostmetrics::busyPercent(a, b) == doctest::Approx(50.0)); + CHECK(hostmetrics::busyPercent(a, a) == doctest::Approx(-1.0)); + CHECK(hostmetrics::busyPercent({}, b) == doctest::Approx(-1.0)); + CHECK_FALSE(hostmetrics::parseProcStatCpu("intr 1 2 3\n").ok); +} diff --git a/tools/camera-bench/results-2026-10-08-tower111-throttle/cap1200_flat.jsonl b/tools/camera-bench/results-2026-10-08-tower111-throttle/cap1200_flat.jsonl new file mode 100644 index 0000000..4c8fa68 --- /dev/null +++ b/tools/camera-bench/results-2026-10-08-tower111-throttle/cap1200_flat.jsonl @@ -0,0 +1,62 @@ +{"t": 1, "temp_c": 54.0, "mhz": 1200, "cpu_pct": 62.8, "watts": 4.84, "rss_mb": 565, "done": 0, "queue": 0} +{"t": 2, "temp_c": 54.0, "mhz": 1200, "cpu_pct": 65.9, "watts": 4.93, "rss_mb": 534, "done": 0, "queue": 0} +{"t": 3, "temp_c": 54.0, "mhz": 1200, "cpu_pct": 60.1, "watts": 5.00, "rss_mb": 588, "done": 1, "queue": 0} +{"t": 4, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 63.1, "watts": 3.74, "rss_mb": 617, "done": 1, "queue": 0} +{"t": 5, "temp_c": 54.0, "mhz": 1200, "cpu_pct": 57.8, "watts": 4.96, "rss_mb": 473, "done": 2, "queue": 0} +{"t": 6, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 65.9, "watts": 5.09, "rss_mb": 634, "done": 2, "queue": 0} +{"t": 7, "temp_c": 54.0, "mhz": 1051, "cpu_pct": 63.2, "watts": 5.06, "rss_mb": 398, "done": 2, "queue": 0} +{"t": 8, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 62.4, "watts": 4.93, "rss_mb": 610, "done": 3, "queue": 0} +{"t": 9, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 62.9, "watts": 3.77, "rss_mb": 576, "done": 3, "queue": 0} +{"t": 10, "temp_c": 54.0, "mhz": 1000, "cpu_pct": 60.2, "watts": 4.93, "rss_mb": 661, "done": 4, "queue": 0} +{"t": 11, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 69.8, "watts": 5.07, "rss_mb": 680, "done": 4, "queue": 0} +{"t": 12, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 57.1, "watts": 4.76, "rss_mb": 638, "done": 5, "queue": 0} +{"t": 13, "temp_c": 54.0, "mhz": 1000, "cpu_pct": 67.2, "watts": 5.15, "rss_mb": 691, "done": 5, "queue": 0} +{"t": 14, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 61.5, "watts": 4.93, "rss_mb": 562, "done": 6, "queue": 0} +{"t": 15, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 64.1, "watts": 5.07, "rss_mb": 683, "done": 6, "queue": 0} +{"t": 16, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 64.2, "watts": 3.80, "rss_mb": 559, "done": 6, "queue": 0} +{"t": 17, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 67.9, "watts": 5.03, "rss_mb": 689, "done": 7, "queue": 0} +{"t": 18, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 61.8, "watts": 5.09, "rss_mb": 690, "done": 7, "queue": 0} +{"t": 19, "temp_c": 54.0, "mhz": 1200, "cpu_pct": 55.8, "watts": 4.77, "rss_mb": 691, "done": 8, "queue": 0} +{"t": 20, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 68.2, "watts": 5.14, "rss_mb": 693, "done": 8, "queue": 0} +{"t": 21, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 78.1, "watts": 5.65, "rss_mb": 687, "done": 9, "queue": 0} +{"t": 22, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 75.0, "watts": 4.17, "rss_mb": 695, "done": 9, "queue": 0} +{"t": 23, "temp_c": 55.0, "mhz": 1100, "cpu_pct": 60.3, "watts": 5.08, "rss_mb": 589, "done": 10, "queue": 0} +{"t": 24, "temp_c": 54.0, "mhz": 1200, "cpu_pct": 63.9, "watts": 5.08, "rss_mb": 696, "done": 10, "queue": 0} +{"t": 25, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 66.5, "watts": 4.95, "rss_mb": 696, "done": 10, "queue": 0} +{"t": 26, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 62.6, "watts": 5.07, "rss_mb": 699, "done": 11, "queue": 0} +{"t": 27, "temp_c": 54.0, "mhz": 1200, "cpu_pct": 64.3, "watts": 5.04, "rss_mb": 700, "done": 11, "queue": 0} +{"t": 28, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 61.6, "watts": 3.77, "rss_mb": 702, "done": 12, "queue": 0} +{"t": 29, "temp_c": 54.0, "mhz": 1200, "cpu_pct": 67.6, "watts": 5.10, "rss_mb": 702, "done": 12, "queue": 0} +{"t": 30, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 61.7, "watts": 4.88, "rss_mb": 702, "done": 13, "queue": 0} +{"t": 31, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 64.6, "watts": 5.06, "rss_mb": 704, "done": 13, "queue": 0} +{"t": 32, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 59.9, "watts": 4.90, "rss_mb": 672, "done": 14, "queue": 0} +{"t": 33, "temp_c": 55.0, "mhz": 1199, "cpu_pct": 66.9, "watts": 5.21, "rss_mb": 704, "done": 14, "queue": 0} +{"t": 34, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 60.1, "watts": 3.73, "rss_mb": 596, "done": 15, "queue": 0} +{"t": 35, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 66.5, "watts": 5.18, "rss_mb": 705, "done": 15, "queue": 0} +{"t": 36, "temp_c": 54.0, "mhz": 1200, "cpu_pct": 63.3, "watts": 5.06, "rss_mb": 581, "done": 15, "queue": 0} +{"t": 37, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 63.8, "watts": 4.90, "rss_mb": 706, "done": 16, "queue": 0} +{"t": 38, "temp_c": 55.0, "mhz": 1100, "cpu_pct": 64.3, "watts": 5.14, "rss_mb": 706, "done": 16, "queue": 0} +{"t": 39, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 57.3, "watts": 4.79, "rss_mb": 706, "done": 17, "queue": 0} +{"t": 40, "temp_c": 54.0, "mhz": 1200, "cpu_pct": 67.0, "watts": 3.80, "rss_mb": 723, "done": 17, "queue": 0} +{"t": 41, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 62.7, "watts": 4.89, "rss_mb": 707, "done": 18, "queue": 0} +{"t": 42, "temp_c": 54.0, "mhz": 1200, "cpu_pct": 65.2, "watts": 5.13, "rss_mb": 708, "done": 18, "queue": 0} +{"t": 43, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 57.3, "watts": 4.92, "rss_mb": 676, "done": 19, "queue": 0} +{"t": 44, "temp_c": 54.0, "mhz": 1200, "cpu_pct": 68.9, "watts": 5.00, "rss_mb": 725, "done": 19, "queue": 0} +{"t": 45, "temp_c": 55.0, "mhz": 807, "cpu_pct": 61.1, "watts": 5.04, "rss_mb": 584, "done": 19, "queue": 0} +{"t": 46, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 67.7, "watts": 5.05, "rss_mb": 725, "done": 20, "queue": 0} +{"t": 47, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 65.4, "watts": 3.88, "rss_mb": 725, "done": 20, "queue": 0} +{"t": 48, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 62.2, "watts": 5.00, "rss_mb": 725, "done": 21, "queue": 0} +{"t": 49, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 67.0, "watts": 5.02, "rss_mb": 726, "done": 21, "queue": 0} +{"t": 50, "temp_c": 55.0, "mhz": 1100, "cpu_pct": 59.2, "watts": 4.99, "rss_mb": 726, "done": 22, "queue": 0} +{"t": 51, "temp_c": 54.0, "mhz": 1200, "cpu_pct": 67.8, "watts": 5.13, "rss_mb": 726, "done": 22, "queue": 0} +{"t": 52, "temp_c": 57.0, "mhz": 1200, "cpu_pct": 75.3, "watts": 5.67, "rss_mb": 678, "done": 23, "queue": 0} +{"t": 53, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 70.9, "watts": 4.11, "rss_mb": 710, "done": 23, "queue": 0} +{"t": 54, "temp_c": 54.0, "mhz": 1000, "cpu_pct": 58.8, "watts": 5.06, "rss_mb": 493, "done": 24, "queue": 0} +{"t": 55, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 68.4, "watts": 4.97, "rss_mb": 710, "done": 24, "queue": 0} +{"t": 56, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 65.0, "watts": 5.00, "rss_mb": 711, "done": 24, "queue": 0} +{"t": 57, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 59.4, "watts": 4.99, "rss_mb": 727, "done": 25, "queue": 0} +{"t": 58, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 69.2, "watts": 3.86, "rss_mb": 727, "done": 25, "queue": 0} +{"t": 59, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 57.6, "watts": 5.12, "rss_mb": 711, "done": 26, "queue": 0} +{"t": 60, "temp_c": 56.0, "mhz": 1100, "cpu_pct": 67.6, "watts": 5.12, "rss_mb": 711, "done": 26, "queue": 0} +{"t": 61, "temp_c": 53.0, "mhz": 1000, "cpu_pct": 37.3, "watts": 4.36, "rss_mb": 381, "done": 27, "queue": 0} +{"summary": "pipeline", "kind": "c12_ea", "workers": 1, "threads": 4, "effort": 3, "distance": 1.00, "period_ms": 0, "secs": 60.7, "frames": 27, "frames_per_s": 0.445, "job_ms_p50": 2219, "latency_ms_p50": 2240, "latency_ms_p95": 2339, "latency_ms_max": 2454, "max_queue": 0, "backlog_at_end": 0, "cpu_s_per_frame": 5.193, "peak_rss_mb": 726, "temp_c_mean": 54.9, "temp_c_max": 57.0, "mhz_mean": 1171, "mhz_min": 807, "cpu_pct_mean": 63.8, "watts_mean": 4.83} diff --git a/tools/camera-bench/results-2026-10-08-tower111-throttle/cap1200_paced.jsonl b/tools/camera-bench/results-2026-10-08-tower111-throttle/cap1200_paced.jsonl new file mode 100644 index 0000000..8b63d2d --- /dev/null +++ b/tools/camera-bench/results-2026-10-08-tower111-throttle/cap1200_paced.jsonl @@ -0,0 +1,122 @@ +{"t": 1, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 62.5, "watts": 4.85, "rss_mb": 575, "done": 0, "queue": 0} +{"t": 2, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 68.7, "watts": 5.08, "rss_mb": 534, "done": 0, "queue": 1} +{"t": 3, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 59.8, "watts": 4.94, "rss_mb": 596, "done": 1, "queue": 0} +{"t": 4, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 64.4, "watts": 3.82, "rss_mb": 620, "done": 1, "queue": 1} +{"t": 5, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 60.0, "watts": 4.95, "rss_mb": 508, "done": 2, "queue": 0} +{"t": 6, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 67.3, "watts": 4.98, "rss_mb": 636, "done": 2, "queue": 1} +{"t": 7, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 56.9, "watts": 5.00, "rss_mb": 306, "done": 3, "queue": 0} +{"t": 8, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 66.9, "watts": 5.01, "rss_mb": 614, "done": 3, "queue": 1} +{"t": 9, "temp_c": 56.0, "mhz": 1199, "cpu_pct": 64.2, "watts": 5.06, "rss_mb": 579, "done": 3, "queue": 1} +{"t": 10, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 57.9, "watts": 3.64, "rss_mb": 669, "done": 4, "queue": 1} +{"t": 11, "temp_c": 55.0, "mhz": 1100, "cpu_pct": 67.8, "watts": 5.10, "rss_mb": 689, "done": 4, "queue": 1} +{"t": 12, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 57.9, "watts": 4.81, "rss_mb": 658, "done": 5, "queue": 1} +{"t": 13, "temp_c": 56.0, "mhz": 1000, "cpu_pct": 66.8, "watts": 5.21, "rss_mb": 695, "done": 5, "queue": 1} +{"t": 14, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 58.4, "watts": 4.94, "rss_mb": 597, "done": 6, "queue": 1} +{"t": 15, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 65.8, "watts": 3.96, "rss_mb": 684, "done": 6, "queue": 1} +{"t": 16, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 64.3, "watts": 4.93, "rss_mb": 560, "done": 6, "queue": 2} +{"t": 17, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 65.2, "watts": 5.00, "rss_mb": 686, "done": 7, "queue": 1} +{"t": 18, "temp_c": 55.0, "mhz": 1000, "cpu_pct": 61.3, "watts": 5.11, "rss_mb": 687, "done": 7, "queue": 2} +{"t": 19, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 60.7, "watts": 4.79, "rss_mb": 688, "done": 8, "queue": 1} +{"t": 20, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 71.4, "watts": 5.35, "rss_mb": 689, "done": 8, "queue": 2} +{"t": 21, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 79.3, "watts": 4.40, "rss_mb": 690, "done": 9, "queue": 1} +{"t": 22, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 65.4, "watts": 5.25, "rss_mb": 692, "done": 9, "queue": 2} +{"t": 23, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 57.9, "watts": 4.88, "rss_mb": 660, "done": 10, "queue": 1} +{"t": 24, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 69.7, "watts": 5.01, "rss_mb": 694, "done": 10, "queue": 2} +{"t": 25, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 58.2, "watts": 4.92, "rss_mb": 525, "done": 11, "queue": 1} +{"t": 26, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 65.8, "watts": 5.15, "rss_mb": 695, "done": 11, "queue": 2} +{"t": 27, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 66.9, "watts": 3.85, "rss_mb": 696, "done": 11, "queue": 2} +{"t": 28, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 61.9, "watts": 4.79, "rss_mb": 700, "done": 12, "queue": 2} +{"t": 29, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 65.1, "watts": 5.16, "rss_mb": 701, "done": 12, "queue": 2} +{"t": 30, "temp_c": 55.0, "mhz": 1000, "cpu_pct": 56.0, "watts": 4.96, "rss_mb": 702, "done": 13, "queue": 2} +{"t": 31, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 68.3, "watts": 5.08, "rss_mb": 703, "done": 13, "queue": 2} +{"t": 32, "temp_c": 57.0, "mhz": 1200, "cpu_pct": 63.2, "watts": 4.99, "rss_mb": 702, "done": 14, "queue": 2} +{"t": 33, "temp_c": 56.0, "mhz": 1100, "cpu_pct": 64.2, "watts": 3.80, "rss_mb": 707, "done": 14, "queue": 2} +{"t": 34, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 60.1, "watts": 4.93, "rss_mb": 631, "done": 15, "queue": 2} +{"t": 35, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 65.8, "watts": 5.18, "rss_mb": 708, "done": 15, "queue": 2} +{"t": 36, "temp_c": 54.0, "mhz": 950, "cpu_pct": 62.8, "watts": 5.06, "rss_mb": 583, "done": 15, "queue": 3} +{"t": 37, "temp_c": 56.0, "mhz": 1100, "cpu_pct": 67.2, "watts": 5.09, "rss_mb": 708, "done": 16, "queue": 2} +{"t": 38, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 62.0, "watts": 3.68, "rss_mb": 709, "done": 16, "queue": 3} +{"t": 39, "temp_c": 57.0, "mhz": 1200, "cpu_pct": 60.6, "watts": 5.03, "rss_mb": 709, "done": 17, "queue": 2} +{"t": 40, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 67.5, "watts": 5.07, "rss_mb": 725, "done": 17, "queue": 3} +{"t": 41, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 63.4, "watts": 4.99, "rss_mb": 709, "done": 18, "queue": 2} +{"t": 42, "temp_c": 57.0, "mhz": 1200, "cpu_pct": 66.7, "watts": 5.09, "rss_mb": 710, "done": 18, "queue": 3} +{"t": 43, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 57.4, "watts": 4.97, "rss_mb": 678, "done": 19, "queue": 2} +{"t": 44, "temp_c": 57.0, "mhz": 1200, "cpu_pct": 69.1, "watts": 3.78, "rss_mb": 726, "done": 19, "queue": 3} +{"t": 45, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 58.0, "watts": 5.05, "rss_mb": 559, "done": 20, "queue": 2} +{"t": 46, "temp_c": 56.0, "mhz": 1100, "cpu_pct": 64.8, "watts": 5.13, "rss_mb": 726, "done": 20, "queue": 3} +{"t": 47, "temp_c": 55.0, "mhz": 1000, "cpu_pct": 67.9, "watts": 5.03, "rss_mb": 727, "done": 20, "queue": 3} +{"t": 48, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 61.2, "watts": 4.89, "rss_mb": 727, "done": 21, "queue": 3} +{"t": 49, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 66.8, "watts": 5.16, "rss_mb": 727, "done": 21, "queue": 3} +{"t": 50, "temp_c": 55.0, "mhz": 1000, "cpu_pct": 58.1, "watts": 3.67, "rss_mb": 727, "done": 22, "queue": 3} +{"t": 51, "temp_c": 58.0, "mhz": 1200, "cpu_pct": 71.7, "watts": 5.04, "rss_mb": 728, "done": 22, "queue": 3} +{"t": 52, "temp_c": 57.0, "mhz": 1200, "cpu_pct": 75.7, "watts": 5.95, "rss_mb": 674, "done": 23, "queue": 3} +{"t": 53, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 70.6, "watts": 5.31, "rss_mb": 712, "done": 23, "queue": 3} +{"t": 54, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 59.9, "watts": 5.00, "rss_mb": 507, "done": 24, "queue": 3} +{"t": 55, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 65.7, "watts": 5.08, "rss_mb": 714, "done": 24, "queue": 3} +{"t": 56, "temp_c": 56.0, "mhz": 1100, "cpu_pct": 67.2, "watts": 3.92, "rss_mb": 714, "done": 24, "queue": 4} +{"t": 57, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 61.8, "watts": 4.74, "rss_mb": 714, "done": 25, "queue": 3} +{"t": 58, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 64.8, "watts": 5.08, "rss_mb": 715, "done": 25, "queue": 4} +{"t": 59, "temp_c": 54.0, "mhz": 1200, "cpu_pct": 57.9, "watts": 5.10, "rss_mb": 715, "done": 26, "queue": 3} +{"t": 60, "temp_c": 55.0, "mhz": 1000, "cpu_pct": 69.2, "watts": 5.07, "rss_mb": 715, "done": 26, "queue": 4} +{"t": 61, "temp_c": 57.0, "mhz": 1200, "cpu_pct": 61.2, "watts": 4.91, "rss_mb": 715, "done": 27, "queue": 3} +{"t": 62, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 66.1, "watts": 3.89, "rss_mb": 716, "done": 27, "queue": 4} +{"t": 63, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 58.5, "watts": 4.99, "rss_mb": 684, "done": 28, "queue": 3} +{"t": 64, "temp_c": 57.0, "mhz": 1200, "cpu_pct": 71.8, "watts": 5.09, "rss_mb": 716, "done": 28, "queue": 4} +{"t": 65, "temp_c": 55.0, "mhz": 800, "cpu_pct": 57.5, "watts": 5.08, "rss_mb": 515, "done": 28, "queue": 4} +{"t": 66, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 68.3, "watts": 5.03, "rss_mb": 732, "done": 29, "queue": 4} +{"t": 67, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 64.4, "watts": 5.04, "rss_mb": 732, "done": 29, "queue": 4} +{"t": 68, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 56.4, "watts": 3.68, "rss_mb": 716, "done": 30, "queue": 4} +{"t": 69, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 66.7, "watts": 5.03, "rss_mb": 716, "done": 30, "queue": 4} +{"t": 70, "temp_c": 57.0, "mhz": 1100, "cpu_pct": 61.1, "watts": 4.92, "rss_mb": 716, "done": 31, "queue": 4} +{"t": 71, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 66.2, "watts": 5.00, "rss_mb": 716, "done": 31, "queue": 4} +{"t": 72, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 58.0, "watts": 4.87, "rss_mb": 684, "done": 32, "queue": 4} +{"t": 73, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 70.5, "watts": 5.20, "rss_mb": 717, "done": 32, "queue": 4} +{"t": 74, "temp_c": 56.0, "mhz": 1199, "cpu_pct": 61.2, "watts": 5.01, "rss_mb": 601, "done": 33, "queue": 4} +{"t": 75, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 63.9, "watts": 3.93, "rss_mb": 717, "done": 33, "queue": 4} +{"t": 76, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 65.2, "watts": 4.96, "rss_mb": 652, "done": 33, "queue": 5} +{"t": 77, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 65.0, "watts": 4.99, "rss_mb": 717, "done": 34, "queue": 4} +{"t": 78, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 63.2, "watts": 5.08, "rss_mb": 717, "done": 34, "queue": 5} +{"t": 79, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 58.4, "watts": 4.77, "rss_mb": 717, "done": 35, "queue": 4} +{"t": 80, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 67.5, "watts": 5.16, "rss_mb": 717, "done": 35, "queue": 5} +{"t": 81, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 63.1, "watts": 3.64, "rss_mb": 717, "done": 36, "queue": 4} +{"t": 82, "temp_c": 57.0, "mhz": 1200, "cpu_pct": 70.9, "watts": 5.25, "rss_mb": 718, "done": 36, "queue": 5} +{"t": 83, "temp_c": 56.0, "mhz": 1100, "cpu_pct": 79.0, "watts": 5.91, "rss_mb": 618, "done": 37, "queue": 4} +{"t": 84, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 65.2, "watts": 5.21, "rss_mb": 718, "done": 37, "queue": 5} +{"t": 85, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 62.8, "watts": 5.03, "rss_mb": 593, "done": 37, "queue": 5} +{"t": 86, "temp_c": 56.0, "mhz": 1199, "cpu_pct": 67.6, "watts": 5.23, "rss_mb": 718, "done": 38, "queue": 5} +{"t": 87, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 63.2, "watts": 3.70, "rss_mb": 718, "done": 38, "queue": 5} +{"t": 88, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 60.1, "watts": 4.96, "rss_mb": 718, "done": 39, "queue": 5} +{"t": 89, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 68.2, "watts": 5.14, "rss_mb": 718, "done": 39, "queue": 5} +{"t": 90, "temp_c": 56.0, "mhz": 1099, "cpu_pct": 61.4, "watts": 4.95, "rss_mb": 718, "done": 40, "queue": 5} +{"t": 91, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 65.9, "watts": 5.04, "rss_mb": 718, "done": 40, "queue": 5} +{"t": 92, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 58.8, "watts": 4.94, "rss_mb": 686, "done": 41, "queue": 5} +{"t": 93, "temp_c": 56.0, "mhz": 1100, "cpu_pct": 69.1, "watts": 3.83, "rss_mb": 718, "done": 41, "queue": 5} +{"t": 94, "temp_c": 56.0, "mhz": 1199, "cpu_pct": 60.8, "watts": 5.00, "rss_mb": 635, "done": 42, "queue": 5} +{"t": 95, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 67.2, "watts": 5.20, "rss_mb": 719, "done": 42, "queue": 5} +{"t": 96, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 63.4, "watts": 5.03, "rss_mb": 594, "done": 42, "queue": 6} +{"t": 97, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 68.2, "watts": 5.10, "rss_mb": 719, "done": 43, "queue": 5} +{"t": 98, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 65.0, "watts": 5.10, "rss_mb": 719, "done": 43, "queue": 6} +{"t": 99, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 59.3, "watts": 3.71, "rss_mb": 735, "done": 44, "queue": 5} +{"t": 100, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 66.4, "watts": 5.08, "rss_mb": 735, "done": 44, "queue": 6} +{"t": 101, "temp_c": 55.0, "mhz": 1000, "cpu_pct": 61.4, "watts": 4.88, "rss_mb": 719, "done": 45, "queue": 5} +{"t": 102, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 66.2, "watts": 5.09, "rss_mb": 719, "done": 45, "queue": 6} +{"t": 103, "temp_c": 57.0, "mhz": 1200, "cpu_pct": 59.8, "watts": 5.08, "rss_mb": 700, "done": 46, "queue": 5} +{"t": 104, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 69.1, "watts": 5.23, "rss_mb": 719, "done": 46, "queue": 6} +{"t": 105, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 61.6, "watts": 3.65, "rss_mb": 646, "done": 47, "queue": 5} +{"t": 106, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 66.3, "watts": 5.15, "rss_mb": 719, "done": 47, "queue": 6} +{"t": 107, "temp_c": 55.0, "mhz": 1118, "cpu_pct": 62.1, "watts": 5.07, "rss_mb": 502, "done": 47, "queue": 6} +{"t": 108, "temp_c": 56.0, "mhz": 900, "cpu_pct": 67.2, "watts": 5.01, "rss_mb": 720, "done": 48, "queue": 6} +{"t": 109, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 64.2, "watts": 4.91, "rss_mb": 720, "done": 48, "queue": 6} +{"t": 110, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 61.2, "watts": 4.98, "rss_mb": 720, "done": 49, "queue": 6} +{"t": 111, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 68.6, "watts": 3.92, "rss_mb": 720, "done": 49, "queue": 6} +{"t": 112, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 58.4, "watts": 5.02, "rss_mb": 720, "done": 50, "queue": 6} +{"t": 113, "temp_c": 57.0, "mhz": 1200, "cpu_pct": 73.2, "watts": 5.16, "rss_mb": 720, "done": 50, "queue": 6} +{"t": 114, "temp_c": 56.0, "mhz": 1100, "cpu_pct": 72.0, "watts": 5.80, "rss_mb": 659, "done": 51, "queue": 6} +{"t": 115, "temp_c": 57.0, "mhz": 1200, "cpu_pct": 68.2, "watts": 5.13, "rss_mb": 720, "done": 51, "queue": 6} +{"t": 116, "temp_c": 55.0, "mhz": 1100, "cpu_pct": 60.4, "watts": 3.83, "rss_mb": 503, "done": 52, "queue": 6} +{"t": 117, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 68.2, "watts": 4.97, "rss_mb": 737, "done": 52, "queue": 6} +{"t": 118, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 64.4, "watts": 5.13, "rss_mb": 738, "done": 52, "queue": 7} +{"t": 119, "temp_c": 57.0, "mhz": 1200, "cpu_pct": 59.6, "watts": 4.81, "rss_mb": 722, "done": 53, "queue": 6} +{"t": 120, "temp_c": 57.0, "mhz": 1200, "cpu_pct": 67.4, "watts": 5.09, "rss_mb": 722, "done": 53, "queue": 6} +{"t": 121, "temp_c": 53.0, "mhz": 784, "cpu_pct": 25.3, "watts": 3.88, "rss_mb": 392, "done": 54, "queue": 6} +{"summary": "pipeline", "kind": "c12_ea", "workers": 1, "threads": 4, "effort": 3, "distance": 1.00, "period_ms": 2000, "secs": 120.6, "frames": 54, "frames_per_s": 0.448, "job_ms_p50": 2202, "latency_ms_p50": 8839, "latency_ms_p95": 13952, "latency_ms_max": 14584, "max_queue": 7, "backlog_at_end": 6, "cpu_s_per_frame": 5.184, "peak_rss_mb": 738, "temp_c_mean": 55.8, "temp_c_max": 58.0, "mhz_mean": 1167, "mhz_min": 784, "cpu_pct_mean": 64.1, "watts_mean": 4.85} diff --git a/tools/camera-bench/results-2026-10-08-tower111-throttle/cap1600_flat.jsonl b/tools/camera-bench/results-2026-10-08-tower111-throttle/cap1600_flat.jsonl new file mode 100644 index 0000000..27d246a --- /dev/null +++ b/tools/camera-bench/results-2026-10-08-tower111-throttle/cap1600_flat.jsonl @@ -0,0 +1,62 @@ +{"t": 1, "temp_c": 55.0, "mhz": 1400, "cpu_pct": 59.9, "watts": 5.29, "rss_mb": 578, "done": 0, "queue": 0} +{"t": 2, "temp_c": 56.0, "mhz": 1000, "cpu_pct": 62.6, "watts": 5.37, "rss_mb": 372, "done": 1, "queue": 0} +{"t": 3, "temp_c": 55.0, "mhz": 1600, "cpu_pct": 64.2, "watts": 5.57, "rss_mb": 620, "done": 1, "queue": 0} +{"t": 4, "temp_c": 56.0, "mhz": 1600, "cpu_pct": 59.3, "watts": 3.95, "rss_mb": 606, "done": 2, "queue": 0} +{"t": 5, "temp_c": 57.0, "mhz": 1600, "cpu_pct": 64.8, "watts": 5.45, "rss_mb": 632, "done": 2, "queue": 0} +{"t": 6, "temp_c": 56.0, "mhz": 1600, "cpu_pct": 57.0, "watts": 5.41, "rss_mb": 567, "done": 3, "queue": 0} +{"t": 7, "temp_c": 56.0, "mhz": 1600, "cpu_pct": 63.5, "watts": 5.29, "rss_mb": 571, "done": 3, "queue": 0} +{"t": 8, "temp_c": 57.0, "mhz": 1600, "cpu_pct": 61.6, "watts": 5.42, "rss_mb": 684, "done": 4, "queue": 0} +{"t": 9, "temp_c": 54.0, "mhz": 1000, "cpu_pct": 57.7, "watts": 5.51, "rss_mb": 452, "done": 5, "queue": 0} +{"t": 10, "temp_c": 56.0, "mhz": 1600, "cpu_pct": 68.0, "watts": 5.47, "rss_mb": 693, "done": 5, "queue": 0} +{"t": 11, "temp_c": 58.0, "mhz": 1600, "cpu_pct": 59.5, "watts": 5.41, "rss_mb": 669, "done": 6, "queue": 0} +{"t": 12, "temp_c": 57.0, "mhz": 1600, "cpu_pct": 65.5, "watts": 4.17, "rss_mb": 681, "done": 6, "queue": 0} +{"t": 13, "temp_c": 58.0, "mhz": 1600, "cpu_pct": 58.5, "watts": 5.45, "rss_mb": 687, "done": 7, "queue": 0} +{"t": 14, "temp_c": 57.0, "mhz": 1600, "cpu_pct": 65.4, "watts": 5.52, "rss_mb": 688, "done": 7, "queue": 0} +{"t": 15, "temp_c": 58.0, "mhz": 1600, "cpu_pct": 63.2, "watts": 5.57, "rss_mb": 690, "done": 8, "queue": 0} +{"t": 16, "temp_c": 57.0, "mhz": 1600, "cpu_pct": 58.2, "watts": 5.33, "rss_mb": 658, "done": 9, "queue": 0} +{"t": 17, "temp_c": 59.0, "mhz": 1600, "cpu_pct": 75.3, "watts": 5.93, "rss_mb": 696, "done": 9, "queue": 0} +{"t": 18, "temp_c": 56.0, "mhz": 1600, "cpu_pct": 65.2, "watts": 5.80, "rss_mb": 697, "done": 10, "queue": 0} +{"t": 19, "temp_c": 56.0, "mhz": 1600, "cpu_pct": 65.3, "watts": 4.09, "rss_mb": 699, "done": 10, "queue": 0} +{"t": 20, "temp_c": 58.0, "mhz": 1600, "cpu_pct": 63.2, "watts": 5.43, "rss_mb": 700, "done": 11, "queue": 0} +{"t": 21, "temp_c": 57.0, "mhz": 1600, "cpu_pct": 58.1, "watts": 5.68, "rss_mb": 525, "done": 12, "queue": 0} +{"t": 22, "temp_c": 56.0, "mhz": 1400, "cpu_pct": 64.9, "watts": 5.45, "rss_mb": 702, "done": 12, "queue": 0} +{"t": 23, "temp_c": 58.0, "mhz": 1600, "cpu_pct": 64.8, "watts": 5.56, "rss_mb": 702, "done": 13, "queue": 0} +{"t": 24, "temp_c": 57.0, "mhz": 1600, "cpu_pct": 65.2, "watts": 5.60, "rss_mb": 703, "done": 13, "queue": 0} +{"t": 25, "temp_c": 58.0, "mhz": 1600, "cpu_pct": 60.6, "watts": 5.49, "rss_mb": 704, "done": 14, "queue": 0} +{"t": 26, "temp_c": 55.0, "mhz": 789, "cpu_pct": 57.9, "watts": 5.44, "rss_mb": 580, "done": 14, "queue": 0} +{"t": 27, "temp_c": 58.0, "mhz": 1600, "cpu_pct": 66.2, "watts": 4.26, "rss_mb": 705, "done": 15, "queue": 0} +{"t": 28, "temp_c": 58.0, "mhz": 1600, "cpu_pct": 60.2, "watts": 5.29, "rss_mb": 701, "done": 16, "queue": 0} +{"t": 29, "temp_c": 56.0, "mhz": 1600, "cpu_pct": 65.9, "watts": 5.55, "rss_mb": 705, "done": 16, "queue": 0} +{"t": 30, "temp_c": 58.0, "mhz": 1600, "cpu_pct": 60.0, "watts": 5.51, "rss_mb": 709, "done": 17, "queue": 0} +{"t": 31, "temp_c": 56.0, "mhz": 1591, "cpu_pct": 64.1, "watts": 5.52, "rss_mb": 722, "done": 17, "queue": 0} +{"t": 32, "temp_c": 57.0, "mhz": 1600, "cpu_pct": 61.7, "watts": 5.53, "rss_mb": 707, "done": 18, "queue": 0} +{"t": 33, "temp_c": 58.0, "mhz": 1600, "cpu_pct": 59.8, "watts": 5.48, "rss_mb": 675, "done": 19, "queue": 0} +{"t": 34, "temp_c": 57.0, "mhz": 1600, "cpu_pct": 66.8, "watts": 5.60, "rss_mb": 724, "done": 19, "queue": 0} +{"t": 35, "temp_c": 57.0, "mhz": 1600, "cpu_pct": 58.5, "watts": 4.14, "rss_mb": 724, "done": 20, "queue": 0} +{"t": 36, "temp_c": 58.0, "mhz": 1587, "cpu_pct": 67.7, "watts": 5.62, "rss_mb": 724, "done": 20, "queue": 0} +{"t": 37, "temp_c": 56.0, "mhz": 1600, "cpu_pct": 58.8, "watts": 5.43, "rss_mb": 724, "done": 21, "queue": 0} +{"t": 38, "temp_c": 57.0, "mhz": 1600, "cpu_pct": 62.6, "watts": 5.38, "rss_mb": 648, "done": 22, "queue": 0} +{"t": 39, "temp_c": 58.0, "mhz": 1600, "cpu_pct": 65.9, "watts": 5.69, "rss_mb": 726, "done": 22, "queue": 0} +{"t": 40, "temp_c": 56.0, "mhz": 1400, "cpu_pct": 60.4, "watts": 5.55, "rss_mb": 710, "done": 23, "queue": 0} +{"t": 41, "temp_c": 57.0, "mhz": 1600, "cpu_pct": 66.2, "watts": 5.56, "rss_mb": 710, "done": 23, "queue": 0} +{"t": 42, "temp_c": 57.0, "mhz": 1600, "cpu_pct": 62.0, "watts": 4.01, "rss_mb": 710, "done": 24, "queue": 0} +{"t": 43, "temp_c": 58.0, "mhz": 1600, "cpu_pct": 59.8, "watts": 5.66, "rss_mb": 585, "done": 25, "queue": 0} +{"t": 44, "temp_c": 56.0, "mhz": 1000, "cpu_pct": 63.8, "watts": 5.49, "rss_mb": 726, "done": 25, "queue": 0} +{"t": 45, "temp_c": 58.0, "mhz": 1600, "cpu_pct": 64.4, "watts": 5.54, "rss_mb": 710, "done": 26, "queue": 0} +{"t": 46, "temp_c": 58.0, "mhz": 1600, "cpu_pct": 65.2, "watts": 5.50, "rss_mb": 711, "done": 26, "queue": 0} +{"t": 47, "temp_c": 58.0, "mhz": 1600, "cpu_pct": 61.2, "watts": 5.57, "rss_mb": 712, "done": 27, "queue": 0} +{"t": 48, "temp_c": 58.0, "mhz": 1600, "cpu_pct": 72.9, "watts": 5.92, "rss_mb": 712, "done": 27, "queue": 0} +{"t": 49, "temp_c": 57.0, "mhz": 1600, "cpu_pct": 66.2, "watts": 4.49, "rss_mb": 712, "done": 28, "queue": 0} +{"t": 50, "temp_c": 56.0, "mhz": 1600, "cpu_pct": 59.9, "watts": 5.39, "rss_mb": 673, "done": 29, "queue": 0} +{"t": 51, "temp_c": 59.0, "mhz": 1600, "cpu_pct": 67.8, "watts": 5.64, "rss_mb": 712, "done": 29, "queue": 0} +{"t": 52, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 57.3, "watts": 5.47, "rss_mb": 712, "done": 30, "queue": 0} +{"t": 53, "temp_c": 57.0, "mhz": 1600, "cpu_pct": 67.6, "watts": 5.68, "rss_mb": 713, "done": 30, "queue": 0} +{"t": 54, "temp_c": 59.0, "mhz": 1600, "cpu_pct": 60.6, "watts": 5.40, "rss_mb": 713, "done": 31, "queue": 0} +{"t": 55, "temp_c": 57.0, "mhz": 1600, "cpu_pct": 58.5, "watts": 5.34, "rss_mb": 519, "done": 32, "queue": 0} +{"t": 56, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 65.0, "watts": 4.19, "rss_mb": 714, "done": 32, "queue": 0} +{"t": 57, "temp_c": 58.0, "mhz": 1600, "cpu_pct": 62.2, "watts": 5.39, "rss_mb": 714, "done": 33, "queue": 0} +{"t": 58, "temp_c": 58.0, "mhz": 1600, "cpu_pct": 65.2, "watts": 5.57, "rss_mb": 715, "done": 33, "queue": 0} +{"t": 59, "temp_c": 58.0, "mhz": 1600, "cpu_pct": 61.8, "watts": 5.39, "rss_mb": 731, "done": 34, "queue": 0} +{"t": 60, "temp_c": 56.0, "mhz": 1498, "cpu_pct": 59.9, "watts": 5.56, "rss_mb": 591, "done": 34, "queue": 0} +{"t": 61, "temp_c": 53.0, "mhz": 795, "cpu_pct": 5.3, "watts": 3.18, "rss_mb": 386, "done": 35, "queue": 0} +{"summary": "pipeline", "kind": "c12_ea", "workers": 1, "threads": 4, "effort": 3, "distance": 1.00, "period_ms": 0, "secs": 60.1, "frames": 35, "frames_per_s": 0.582, "job_ms_p50": 1681, "latency_ms_p50": 1695, "latency_ms_p95": 1794, "latency_ms_max": 1881, "max_queue": 0, "backlog_at_end": 0, "cpu_s_per_frame": 3.972, "peak_rss_mb": 731, "temp_c_mean": 56.9, "temp_c_max": 59.0, "mhz_mean": 1519, "mhz_min": 789, "cpu_pct_mean": 62.0, "watts_mean": 5.30} diff --git a/tools/camera-bench/results-2026-10-08-tower111-throttle/cap1600_paced.jsonl b/tools/camera-bench/results-2026-10-08-tower111-throttle/cap1600_paced.jsonl new file mode 100644 index 0000000..1038cdd --- /dev/null +++ b/tools/camera-bench/results-2026-10-08-tower111-throttle/cap1600_paced.jsonl @@ -0,0 +1,121 @@ +{"t": 1, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 59.8, "watts": 5.29, "rss_mb": 579, "done": 0, "queue": 0} +{"t": 2, "temp_c": 55.0, "mhz": 1600, "cpu_pct": 54.7, "watts": 5.09, "rss_mb": 248, "done": 1, "queue": 0} +{"t": 3, "temp_c": 58.0, "mhz": 1600, "cpu_pct": 64.9, "watts": 4.16, "rss_mb": 616, "done": 1, "queue": 0} +{"t": 4, "temp_c": 53.0, "mhz": 800, "cpu_pct": 45.8, "watts": 5.52, "rss_mb": 288, "done": 2, "queue": 0} +{"t": 5, "temp_c": 57.0, "mhz": 1600, "cpu_pct": 64.2, "watts": 4.92, "rss_mb": 632, "done": 2, "queue": 0} +{"t": 6, "temp_c": 54.0, "mhz": 794, "cpu_pct": 46.4, "watts": 5.12, "rss_mb": 302, "done": 3, "queue": 0} +{"t": 7, "temp_c": 58.0, "mhz": 1600, "cpu_pct": 62.1, "watts": 5.11, "rss_mb": 553, "done": 3, "queue": 0} +{"t": 8, "temp_c": 54.0, "mhz": 796, "cpu_pct": 49.2, "watts": 5.15, "rss_mb": 388, "done": 4, "queue": 0} +{"t": 9, "temp_c": 58.0, "mhz": 1600, "cpu_pct": 65.8, "watts": 5.32, "rss_mb": 686, "done": 4, "queue": 0} +{"t": 10, "temp_c": 54.0, "mhz": 866, "cpu_pct": 45.0, "watts": 4.87, "rss_mb": 454, "done": 5, "queue": 0} +{"t": 11, "temp_c": 58.0, "mhz": 1600, "cpu_pct": 67.4, "watts": 4.22, "rss_mb": 694, "done": 5, "queue": 0} +{"t": 12, "temp_c": 54.0, "mhz": 800, "cpu_pct": 39.9, "watts": 5.13, "rss_mb": 461, "done": 6, "queue": 0} +{"t": 13, "temp_c": 58.0, "mhz": 1600, "cpu_pct": 67.4, "watts": 5.05, "rss_mb": 681, "done": 6, "queue": 0} +{"t": 14, "temp_c": 54.0, "mhz": 835, "cpu_pct": 41.5, "watts": 5.07, "rss_mb": 464, "done": 7, "queue": 0} +{"t": 15, "temp_c": 57.0, "mhz": 1600, "cpu_pct": 66.8, "watts": 5.23, "rss_mb": 684, "done": 7, "queue": 0} +{"t": 16, "temp_c": 54.0, "mhz": 900, "cpu_pct": 40.3, "watts": 4.72, "rss_mb": 467, "done": 8, "queue": 0} +{"t": 17, "temp_c": 59.0, "mhz": 1600, "cpu_pct": 80.7, "watts": 6.22, "rss_mb": 686, "done": 8, "queue": 0} +{"t": 18, "temp_c": 55.0, "mhz": 942, "cpu_pct": 55.8, "watts": 4.27, "rss_mb": 469, "done": 9, "queue": 0} +{"t": 19, "temp_c": 58.0, "mhz": 1600, "cpu_pct": 67.0, "watts": 5.21, "rss_mb": 688, "done": 9, "queue": 0} +{"t": 20, "temp_c": 54.0, "mhz": 1119, "cpu_pct": 39.2, "watts": 4.99, "rss_mb": 471, "done": 10, "queue": 0} +{"t": 21, "temp_c": 58.0, "mhz": 1600, "cpu_pct": 68.5, "watts": 5.30, "rss_mb": 690, "done": 10, "queue": 0} +{"t": 22, "temp_c": 55.0, "mhz": 1244, "cpu_pct": 41.4, "watts": 4.95, "rss_mb": 473, "done": 11, "queue": 0} +{"t": 23, "temp_c": 57.0, "mhz": 1600, "cpu_pct": 65.9, "watts": 5.49, "rss_mb": 694, "done": 11, "queue": 0} +{"t": 24, "temp_c": 55.0, "mhz": 1600, "cpu_pct": 40.9, "watts": 4.52, "rss_mb": 476, "done": 12, "queue": 0} +{"t": 25, "temp_c": 57.0, "mhz": 1600, "cpu_pct": 67.3, "watts": 4.26, "rss_mb": 695, "done": 12, "queue": 0} +{"t": 26, "temp_c": 54.0, "mhz": 1421, "cpu_pct": 41.1, "watts": 5.08, "rss_mb": 478, "done": 13, "queue": 0} +{"t": 27, "temp_c": 58.0, "mhz": 1200, "cpu_pct": 66.7, "watts": 5.16, "rss_mb": 697, "done": 13, "queue": 0} +{"t": 28, "temp_c": 54.0, "mhz": 1026, "cpu_pct": 38.9, "watts": 4.88, "rss_mb": 479, "done": 14, "queue": 0} +{"t": 29, "temp_c": 58.0, "mhz": 1600, "cpu_pct": 67.0, "watts": 5.39, "rss_mb": 700, "done": 14, "queue": 0} +{"t": 30, "temp_c": 55.0, "mhz": 1200, "cpu_pct": 41.2, "watts": 4.79, "rss_mb": 483, "done": 15, "queue": 0} +{"t": 31, "temp_c": 56.0, "mhz": 1600, "cpu_pct": 66.3, "watts": 5.57, "rss_mb": 702, "done": 15, "queue": 0} +{"t": 32, "temp_c": 55.0, "mhz": 1255, "cpu_pct": 39.6, "watts": 3.70, "rss_mb": 484, "done": 16, "queue": 0} +{"t": 33, "temp_c": 57.0, "mhz": 1600, "cpu_pct": 67.6, "watts": 5.03, "rss_mb": 702, "done": 16, "queue": 0} +{"t": 34, "temp_c": 54.0, "mhz": 1400, "cpu_pct": 40.9, "watts": 5.04, "rss_mb": 485, "done": 17, "queue": 0} +{"t": 35, "temp_c": 58.0, "mhz": 1600, "cpu_pct": 66.5, "watts": 5.09, "rss_mb": 719, "done": 17, "queue": 0} +{"t": 36, "temp_c": 54.0, "mhz": 1404, "cpu_pct": 41.5, "watts": 4.91, "rss_mb": 486, "done": 18, "queue": 0} +{"t": 37, "temp_c": 57.0, "mhz": 1600, "cpu_pct": 67.3, "watts": 5.39, "rss_mb": 704, "done": 18, "queue": 0} +{"t": 38, "temp_c": 55.0, "mhz": 1600, "cpu_pct": 40.7, "watts": 4.70, "rss_mb": 494, "done": 19, "queue": 0} +{"t": 39, "temp_c": 58.0, "mhz": 1600, "cpu_pct": 66.6, "watts": 4.22, "rss_mb": 722, "done": 19, "queue": 0} +{"t": 40, "temp_c": 55.0, "mhz": 1600, "cpu_pct": 42.0, "watts": 5.14, "rss_mb": 504, "done": 20, "queue": 0} +{"t": 41, "temp_c": 57.0, "mhz": 1600, "cpu_pct": 66.7, "watts": 4.99, "rss_mb": 722, "done": 20, "queue": 0} +{"t": 42, "temp_c": 55.0, "mhz": 1600, "cpu_pct": 43.1, "watts": 5.13, "rss_mb": 517, "done": 21, "queue": 0} +{"t": 43, "temp_c": 57.0, "mhz": 1400, "cpu_pct": 66.8, "watts": 5.25, "rss_mb": 722, "done": 21, "queue": 0} +{"t": 44, "temp_c": 56.0, "mhz": 1600, "cpu_pct": 42.6, "watts": 4.71, "rss_mb": 516, "done": 22, "queue": 0} +{"t": 45, "temp_c": 57.0, "mhz": 1200, "cpu_pct": 64.9, "watts": 5.57, "rss_mb": 722, "done": 22, "queue": 0} +{"t": 46, "temp_c": 55.0, "mhz": 1600, "cpu_pct": 41.9, "watts": 4.55, "rss_mb": 529, "done": 23, "queue": 0} +{"t": 47, "temp_c": 57.0, "mhz": 1400, "cpu_pct": 66.5, "watts": 4.18, "rss_mb": 707, "done": 23, "queue": 0} +{"t": 48, "temp_c": 55.0, "mhz": 1300, "cpu_pct": 60.5, "watts": 5.89, "rss_mb": 503, "done": 24, "queue": 0} +{"t": 49, "temp_c": 57.0, "mhz": 1600, "cpu_pct": 66.0, "watts": 5.10, "rss_mb": 707, "done": 24, "queue": 0} +{"t": 50, "temp_c": 57.0, "mhz": 1600, "cpu_pct": 43.4, "watts": 4.93, "rss_mb": 555, "done": 25, "queue": 0} +{"t": 51, "temp_c": 58.0, "mhz": 1600, "cpu_pct": 63.8, "watts": 5.48, "rss_mb": 724, "done": 25, "queue": 0} +{"t": 52, "temp_c": 56.0, "mhz": 1600, "cpu_pct": 43.4, "watts": 4.58, "rss_mb": 541, "done": 26, "queue": 0} +{"t": 53, "temp_c": 57.0, "mhz": 1599, "cpu_pct": 64.1, "watts": 5.53, "rss_mb": 708, "done": 26, "queue": 0} +{"t": 54, "temp_c": 55.0, "mhz": 1600, "cpu_pct": 44.3, "watts": 3.82, "rss_mb": 564, "done": 27, "queue": 0} +{"t": 55, "temp_c": 58.0, "mhz": 1400, "cpu_pct": 63.9, "watts": 5.03, "rss_mb": 709, "done": 27, "queue": 0} +{"t": 56, "temp_c": 56.0, "mhz": 1600, "cpu_pct": 43.7, "watts": 4.86, "rss_mb": 554, "done": 28, "queue": 0} +{"t": 57, "temp_c": 57.0, "mhz": 1400, "cpu_pct": 64.1, "watts": 5.39, "rss_mb": 709, "done": 28, "queue": 0} +{"t": 58, "temp_c": 57.0, "mhz": 1600, "cpu_pct": 45.5, "watts": 4.79, "rss_mb": 580, "done": 29, "queue": 0} +{"t": 59, "temp_c": 57.0, "mhz": 1400, "cpu_pct": 64.1, "watts": 5.49, "rss_mb": 709, "done": 29, "queue": 0} +{"t": 60, "temp_c": 57.0, "mhz": 1600, "cpu_pct": 45.4, "watts": 4.66, "rss_mb": 567, "done": 30, "queue": 0} +{"t": 61, "temp_c": 57.0, "mhz": 1600, "cpu_pct": 62.6, "watts": 5.49, "rss_mb": 710, "done": 30, "queue": 0} +{"t": 62, "temp_c": 58.0, "mhz": 1599, "cpu_pct": 44.5, "watts": 3.73, "rss_mb": 586, "done": 31, "queue": 0} +{"t": 63, "temp_c": 57.0, "mhz": 1400, "cpu_pct": 63.9, "watts": 5.17, "rss_mb": 710, "done": 31, "queue": 0} +{"t": 64, "temp_c": 57.0, "mhz": 1600, "cpu_pct": 45.2, "watts": 4.82, "rss_mb": 595, "done": 32, "queue": 0} +{"t": 65, "temp_c": 57.0, "mhz": 1600, "cpu_pct": 62.4, "watts": 5.48, "rss_mb": 710, "done": 32, "queue": 0} +{"t": 66, "temp_c": 58.0, "mhz": 1600, "cpu_pct": 44.8, "watts": 4.66, "rss_mb": 590, "done": 33, "queue": 0} +{"t": 67, "temp_c": 56.0, "mhz": 1600, "cpu_pct": 63.9, "watts": 5.62, "rss_mb": 710, "done": 33, "queue": 0} +{"t": 68, "temp_c": 57.0, "mhz": 1600, "cpu_pct": 44.0, "watts": 4.69, "rss_mb": 598, "done": 34, "queue": 0} +{"t": 69, "temp_c": 57.0, "mhz": 1600, "cpu_pct": 63.9, "watts": 4.11, "rss_mb": 727, "done": 34, "queue": 0} +{"t": 70, "temp_c": 57.0, "mhz": 1600, "cpu_pct": 42.4, "watts": 4.89, "rss_mb": 599, "done": 35, "queue": 0} +{"t": 71, "temp_c": 56.0, "mhz": 1600, "cpu_pct": 64.1, "watts": 5.35, "rss_mb": 711, "done": 35, "queue": 0} +{"t": 72, "temp_c": 56.0, "mhz": 1400, "cpu_pct": 46.5, "watts": 4.87, "rss_mb": 605, "done": 36, "queue": 0} +{"t": 73, "temp_c": 58.0, "mhz": 1600, "cpu_pct": 66.4, "watts": 5.52, "rss_mb": 711, "done": 36, "queue": 0} +{"t": 74, "temp_c": 56.0, "mhz": 1600, "cpu_pct": 40.5, "watts": 4.55, "rss_mb": 604, "done": 37, "queue": 0} +{"t": 75, "temp_c": 58.0, "mhz": 1600, "cpu_pct": 65.6, "watts": 5.67, "rss_mb": 712, "done": 37, "queue": 0} +{"t": 76, "temp_c": 56.0, "mhz": 1600, "cpu_pct": 42.2, "watts": 3.55, "rss_mb": 608, "done": 38, "queue": 0} +{"t": 77, "temp_c": 58.0, "mhz": 1600, "cpu_pct": 65.8, "watts": 5.29, "rss_mb": 712, "done": 38, "queue": 0} +{"t": 78, "temp_c": 56.0, "mhz": 1600, "cpu_pct": 43.9, "watts": 4.92, "rss_mb": 609, "done": 39, "queue": 0} +{"t": 79, "temp_c": 59.0, "mhz": 1600, "cpu_pct": 82.8, "watts": 6.23, "rss_mb": 713, "done": 39, "queue": 0} +{"t": 80, "temp_c": 57.0, "mhz": 1600, "cpu_pct": 52.3, "watts": 5.16, "rss_mb": 613, "done": 40, "queue": 0} +{"t": 81, "temp_c": 57.0, "mhz": 1600, "cpu_pct": 65.6, "watts": 5.65, "rss_mb": 713, "done": 40, "queue": 0} +{"t": 82, "temp_c": 57.0, "mhz": 1600, "cpu_pct": 43.5, "watts": 4.63, "rss_mb": 611, "done": 41, "queue": 0} +{"t": 83, "temp_c": 58.0, "mhz": 1600, "cpu_pct": 64.3, "watts": 4.16, "rss_mb": 713, "done": 41, "queue": 0} +{"t": 84, "temp_c": 56.0, "mhz": 1400, "cpu_pct": 43.2, "watts": 5.07, "rss_mb": 619, "done": 42, "queue": 0} +{"t": 85, "temp_c": 58.0, "mhz": 1600, "cpu_pct": 65.9, "watts": 5.34, "rss_mb": 713, "done": 42, "queue": 0} +{"t": 86, "temp_c": 57.0, "mhz": 1600, "cpu_pct": 40.5, "watts": 4.53, "rss_mb": 615, "done": 43, "queue": 0} +{"t": 87, "temp_c": 57.0, "mhz": 1600, "cpu_pct": 65.1, "watts": 5.67, "rss_mb": 714, "done": 43, "queue": 0} +{"t": 88, "temp_c": 57.0, "mhz": 1600, "cpu_pct": 43.4, "watts": 4.60, "rss_mb": 620, "done": 44, "queue": 0} +{"t": 89, "temp_c": 57.0, "mhz": 1600, "cpu_pct": 63.8, "watts": 5.52, "rss_mb": 731, "done": 44, "queue": 0} +{"t": 90, "temp_c": 57.0, "mhz": 1600, "cpu_pct": 46.0, "watts": 4.79, "rss_mb": 611, "done": 45, "queue": 0} +{"t": 91, "temp_c": 57.0, "mhz": 1600, "cpu_pct": 63.2, "watts": 4.04, "rss_mb": 731, "done": 45, "queue": 0} +{"t": 92, "temp_c": 57.0, "mhz": 1400, "cpu_pct": 44.9, "watts": 4.95, "rss_mb": 629, "done": 46, "queue": 0} +{"t": 93, "temp_c": 57.0, "mhz": 1600, "cpu_pct": 66.0, "watts": 5.47, "rss_mb": 731, "done": 46, "queue": 0} +{"t": 94, "temp_c": 57.0, "mhz": 1400, "cpu_pct": 43.2, "watts": 4.55, "rss_mb": 615, "done": 47, "queue": 0} +{"t": 95, "temp_c": 57.0, "mhz": 1600, "cpu_pct": 64.2, "watts": 5.62, "rss_mb": 715, "done": 47, "queue": 0} +{"t": 96, "temp_c": 57.0, "mhz": 1600, "cpu_pct": 43.9, "watts": 4.80, "rss_mb": 631, "done": 48, "queue": 0} +{"t": 97, "temp_c": 58.0, "mhz": 1600, "cpu_pct": 65.2, "watts": 4.12, "rss_mb": 731, "done": 48, "queue": 0} +{"t": 98, "temp_c": 57.0, "mhz": 1600, "cpu_pct": 43.4, "watts": 4.91, "rss_mb": 622, "done": 49, "queue": 0} +{"t": 99, "temp_c": 58.0, "mhz": 1600, "cpu_pct": 65.5, "watts": 5.37, "rss_mb": 731, "done": 49, "queue": 0} +{"t": 100, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 41.4, "watts": 4.67, "rss_mb": 640, "done": 50, "queue": 0} +{"t": 101, "temp_c": 58.0, "mhz": 1600, "cpu_pct": 66.4, "watts": 5.59, "rss_mb": 733, "done": 50, "queue": 0} +{"t": 102, "temp_c": 56.0, "mhz": 1600, "cpu_pct": 43.3, "watts": 4.68, "rss_mb": 630, "done": 51, "queue": 0} +{"t": 103, "temp_c": 58.0, "mhz": 1600, "cpu_pct": 64.7, "watts": 5.52, "rss_mb": 733, "done": 51, "queue": 0} +{"t": 104, "temp_c": 56.0, "mhz": 1600, "cpu_pct": 42.1, "watts": 4.66, "rss_mb": 641, "done": 52, "queue": 0} +{"t": 105, "temp_c": 58.0, "mhz": 1600, "cpu_pct": 66.5, "watts": 4.27, "rss_mb": 733, "done": 52, "queue": 0} +{"t": 106, "temp_c": 56.0, "mhz": 1400, "cpu_pct": 43.0, "watts": 4.77, "rss_mb": 632, "done": 53, "queue": 0} +{"t": 107, "temp_c": 58.0, "mhz": 1600, "cpu_pct": 65.2, "watts": 5.50, "rss_mb": 717, "done": 53, "queue": 0} +{"t": 108, "temp_c": 56.0, "mhz": 1400, "cpu_pct": 43.4, "watts": 4.68, "rss_mb": 644, "done": 54, "queue": 0} +{"t": 109, "temp_c": 58.0, "mhz": 1600, "cpu_pct": 68.2, "watts": 5.60, "rss_mb": 734, "done": 54, "queue": 0} +{"t": 110, "temp_c": 58.0, "mhz": 1600, "cpu_pct": 67.5, "watts": 5.85, "rss_mb": 612, "done": 55, "queue": 0} +{"t": 111, "temp_c": 57.0, "mhz": 1600, "cpu_pct": 63.2, "watts": 5.52, "rss_mb": 718, "done": 55, "queue": 0} +{"t": 112, "temp_c": 56.0, "mhz": 1400, "cpu_pct": 44.7, "watts": 3.63, "rss_mb": 654, "done": 56, "queue": 0} +{"t": 113, "temp_c": 58.0, "mhz": 1600, "cpu_pct": 65.9, "watts": 5.42, "rss_mb": 719, "done": 56, "queue": 0} +{"t": 114, "temp_c": 57.0, "mhz": 1400, "cpu_pct": 42.3, "watts": 4.64, "rss_mb": 644, "done": 57, "queue": 0} +{"t": 115, "temp_c": 58.0, "mhz": 1600, "cpu_pct": 64.9, "watts": 5.55, "rss_mb": 720, "done": 57, "queue": 0} +{"t": 116, "temp_c": 57.0, "mhz": 1400, "cpu_pct": 40.7, "watts": 4.68, "rss_mb": 659, "done": 58, "queue": 0} +{"t": 117, "temp_c": 58.0, "mhz": 1600, "cpu_pct": 67.2, "watts": 5.55, "rss_mb": 720, "done": 58, "queue": 0} +{"t": 118, "temp_c": 56.0, "mhz": 1200, "cpu_pct": 42.0, "watts": 3.51, "rss_mb": 653, "done": 59, "queue": 0} +{"t": 119, "temp_c": 59.0, "mhz": 1600, "cpu_pct": 66.4, "watts": 5.35, "rss_mb": 737, "done": 59, "queue": 0} +{"t": 120, "temp_c": 55.0, "mhz": 800, "cpu_pct": 32.7, "watts": 4.70, "rss_mb": 391, "done": 60, "queue": 0} +{"summary": "pipeline", "kind": "c12_ea", "workers": 1, "threads": 4, "effort": 3, "distance": 1.00, "period_ms": 2000, "secs": 120.0, "frames": 60, "frames_per_s": 0.500, "job_ms_p50": 1703, "latency_ms_p50": 1718, "latency_ms_p95": 1843, "latency_ms_max": 1872, "max_queue": 1, "backlog_at_end": 0, "cpu_s_per_frame": 3.977, "peak_rss_mb": 736, "temp_c_mean": 56.6, "temp_c_max": 59.0, "mhz_mean": 1476, "mhz_min": 794, "cpu_pct_mean": 55.0, "watts_mean": 4.98} diff --git a/tools/camera-bench/results-2026-10-08-tower111-throttle/cap800_flat.jsonl b/tools/camera-bench/results-2026-10-08-tower111-throttle/cap800_flat.jsonl new file mode 100644 index 0000000..e6fd0f5 --- /dev/null +++ b/tools/camera-bench/results-2026-10-08-tower111-throttle/cap800_flat.jsonl @@ -0,0 +1,62 @@ +{"t": 1, "temp_c": 53.0, "mhz": 800, "cpu_pct": 64.9, "watts": 4.52, "rss_mb": 527, "done": 0, "queue": 0} +{"t": 2, "temp_c": 53.0, "mhz": 800, "cpu_pct": 64.8, "watts": 4.60, "rss_mb": 531, "done": 0, "queue": 0} +{"t": 3, "temp_c": 52.0, "mhz": 800, "cpu_pct": 72.1, "watts": 4.91, "rss_mb": 534, "done": 0, "queue": 0} +{"t": 4, "temp_c": 54.0, "mhz": 800, "cpu_pct": 55.7, "watts": 3.30, "rss_mb": 478, "done": 1, "queue": 0} +{"t": 5, "temp_c": 54.0, "mhz": 800, "cpu_pct": 69.0, "watts": 4.86, "rss_mb": 621, "done": 1, "queue": 0} +{"t": 6, "temp_c": 54.0, "mhz": 800, "cpu_pct": 67.3, "watts": 4.68, "rss_mb": 626, "done": 1, "queue": 0} +{"t": 7, "temp_c": 53.0, "mhz": 800, "cpu_pct": 53.1, "watts": 4.58, "rss_mb": 296, "done": 2, "queue": 0} +{"t": 8, "temp_c": 52.0, "mhz": 800, "cpu_pct": 64.8, "watts": 3.65, "rss_mb": 614, "done": 2, "queue": 0} +{"t": 9, "temp_c": 53.0, "mhz": 800, "cpu_pct": 72.0, "watts": 4.73, "rss_mb": 635, "done": 2, "queue": 0} +{"t": 10, "temp_c": 53.0, "mhz": 800, "cpu_pct": 65.5, "watts": 4.68, "rss_mb": 636, "done": 2, "queue": 0} +{"t": 11, "temp_c": 53.0, "mhz": 800, "cpu_pct": 61.2, "watts": 4.67, "rss_mb": 601, "done": 3, "queue": 0} +{"t": 12, "temp_c": 54.0, "mhz": 800, "cpu_pct": 62.9, "watts": 4.72, "rss_mb": 618, "done": 3, "queue": 0} +{"t": 13, "temp_c": 53.0, "mhz": 800, "cpu_pct": 64.2, "watts": 3.45, "rss_mb": 582, "done": 3, "queue": 0} +{"t": 14, "temp_c": 54.0, "mhz": 800, "cpu_pct": 61.4, "watts": 4.61, "rss_mb": 469, "done": 4, "queue": 0} +{"t": 15, "temp_c": 54.0, "mhz": 800, "cpu_pct": 63.3, "watts": 4.65, "rss_mb": 670, "done": 4, "queue": 0} +{"t": 16, "temp_c": 53.0, "mhz": 800, "cpu_pct": 66.9, "watts": 4.91, "rss_mb": 687, "done": 4, "queue": 0} +{"t": 17, "temp_c": 53.0, "mhz": 800, "cpu_pct": 67.3, "watts": 3.62, "rss_mb": 688, "done": 4, "queue": 0} +{"t": 18, "temp_c": 53.0, "mhz": 800, "cpu_pct": 64.2, "watts": 4.73, "rss_mb": 693, "done": 5, "queue": 0} +{"t": 19, "temp_c": 54.0, "mhz": 800, "cpu_pct": 65.2, "watts": 4.70, "rss_mb": 696, "done": 5, "queue": 0} +{"t": 20, "temp_c": 53.0, "mhz": 800, "cpu_pct": 68.4, "watts": 4.99, "rss_mb": 696, "done": 5, "queue": 0} +{"t": 21, "temp_c": 54.0, "mhz": 800, "cpu_pct": 61.4, "watts": 3.28, "rss_mb": 664, "done": 6, "queue": 0} +{"t": 22, "temp_c": 53.0, "mhz": 800, "cpu_pct": 65.3, "watts": 4.97, "rss_mb": 684, "done": 6, "queue": 0} +{"t": 23, "temp_c": 54.0, "mhz": 800, "cpu_pct": 78.5, "watts": 4.95, "rss_mb": 685, "done": 6, "queue": 0} +{"t": 24, "temp_c": 54.0, "mhz": 800, "cpu_pct": 74.2, "watts": 5.27, "rss_mb": 532, "done": 7, "queue": 0} +{"t": 25, "temp_c": 54.0, "mhz": 800, "cpu_pct": 68.5, "watts": 3.66, "rss_mb": 688, "done": 7, "queue": 0} +{"t": 26, "temp_c": 53.0, "mhz": 800, "cpu_pct": 68.5, "watts": 5.06, "rss_mb": 689, "done": 7, "queue": 0} +{"t": 27, "temp_c": 54.0, "mhz": 800, "cpu_pct": 65.1, "watts": 4.67, "rss_mb": 689, "done": 7, "queue": 0} +{"t": 28, "temp_c": 53.0, "mhz": 800, "cpu_pct": 62.4, "watts": 4.71, "rss_mb": 690, "done": 8, "queue": 0} +{"t": 29, "temp_c": 54.0, "mhz": 800, "cpu_pct": 70.8, "watts": 4.77, "rss_mb": 692, "done": 8, "queue": 0} +{"t": 30, "temp_c": 54.0, "mhz": 800, "cpu_pct": 63.8, "watts": 3.59, "rss_mb": 692, "done": 8, "queue": 0} +{"t": 31, "temp_c": 55.0, "mhz": 800, "cpu_pct": 62.2, "watts": 4.51, "rss_mb": 692, "done": 9, "queue": 0} +{"t": 32, "temp_c": 52.0, "mhz": 800, "cpu_pct": 62.1, "watts": 4.97, "rss_mb": 692, "done": 9, "queue": 0} +{"t": 33, "temp_c": 54.0, "mhz": 800, "cpu_pct": 71.0, "watts": 4.81, "rss_mb": 693, "done": 9, "queue": 0} +{"t": 34, "temp_c": 53.0, "mhz": 800, "cpu_pct": 54.8, "watts": 3.41, "rss_mb": 661, "done": 10, "queue": 0} +{"t": 35, "temp_c": 54.0, "mhz": 800, "cpu_pct": 76.5, "watts": 4.96, "rss_mb": 694, "done": 10, "queue": 0} +{"t": 36, "temp_c": 54.0, "mhz": 800, "cpu_pct": 68.0, "watts": 4.79, "rss_mb": 694, "done": 10, "queue": 0} +{"t": 37, "temp_c": 53.0, "mhz": 800, "cpu_pct": 55.2, "watts": 4.47, "rss_mb": 517, "done": 11, "queue": 0} +{"t": 38, "temp_c": 54.0, "mhz": 800, "cpu_pct": 70.4, "watts": 3.70, "rss_mb": 696, "done": 11, "queue": 0} +{"t": 39, "temp_c": 54.0, "mhz": 800, "cpu_pct": 68.8, "watts": 4.89, "rss_mb": 697, "done": 11, "queue": 0} +{"t": 40, "temp_c": 53.0, "mhz": 800, "cpu_pct": 64.2, "watts": 4.70, "rss_mb": 627, "done": 11, "queue": 0} +{"t": 41, "temp_c": 52.0, "mhz": 800, "cpu_pct": 63.2, "watts": 4.66, "rss_mb": 697, "done": 12, "queue": 0} +{"t": 42, "temp_c": 54.0, "mhz": 800, "cpu_pct": 69.8, "watts": 4.74, "rss_mb": 698, "done": 12, "queue": 0} +{"t": 43, "temp_c": 54.0, "mhz": 800, "cpu_pct": 64.9, "watts": 3.59, "rss_mb": 698, "done": 12, "queue": 0} +{"t": 44, "temp_c": 54.0, "mhz": 800, "cpu_pct": 65.1, "watts": 4.65, "rss_mb": 698, "done": 13, "queue": 0} +{"t": 45, "temp_c": 54.0, "mhz": 800, "cpu_pct": 61.2, "watts": 4.91, "rss_mb": 699, "done": 13, "queue": 0} +{"t": 46, "temp_c": 54.0, "mhz": 800, "cpu_pct": 72.6, "watts": 4.78, "rss_mb": 699, "done": 13, "queue": 0} +{"t": 47, "temp_c": 54.0, "mhz": 800, "cpu_pct": 55.4, "watts": 3.50, "rss_mb": 667, "done": 14, "queue": 0} +{"t": 48, "temp_c": 54.0, "mhz": 800, "cpu_pct": 74.2, "watts": 4.86, "rss_mb": 700, "done": 14, "queue": 0} +{"t": 49, "temp_c": 54.0, "mhz": 800, "cpu_pct": 64.2, "watts": 4.76, "rss_mb": 700, "done": 14, "queue": 0} +{"t": 50, "temp_c": 54.0, "mhz": 800, "cpu_pct": 56.8, "watts": 4.54, "rss_mb": 506, "done": 15, "queue": 0} +{"t": 51, "temp_c": 54.0, "mhz": 800, "cpu_pct": 68.2, "watts": 4.79, "rss_mb": 701, "done": 15, "queue": 0} +{"t": 52, "temp_c": 53.0, "mhz": 800, "cpu_pct": 66.6, "watts": 3.70, "rss_mb": 701, "done": 15, "queue": 0} +{"t": 53, "temp_c": 53.0, "mhz": 800, "cpu_pct": 67.8, "watts": 4.63, "rss_mb": 701, "done": 15, "queue": 0} +{"t": 54, "temp_c": 55.0, "mhz": 800, "cpu_pct": 70.1, "watts": 4.84, "rss_mb": 703, "done": 16, "queue": 0} +{"t": 55, "temp_c": 54.0, "mhz": 800, "cpu_pct": 84.9, "watts": 4.39, "rss_mb": 706, "done": 16, "queue": 0} +{"t": 56, "temp_c": 54.0, "mhz": 800, "cpu_pct": 73.2, "watts": 4.94, "rss_mb": 706, "done": 16, "queue": 0} +{"t": 57, "temp_c": 54.0, "mhz": 800, "cpu_pct": 51.4, "watts": 4.60, "rss_mb": 674, "done": 17, "queue": 0} +{"t": 58, "temp_c": 55.0, "mhz": 800, "cpu_pct": 73.6, "watts": 4.82, "rss_mb": 723, "done": 17, "queue": 0} +{"t": 59, "temp_c": 54.0, "mhz": 800, "cpu_pct": 65.4, "watts": 4.85, "rss_mb": 723, "done": 17, "queue": 0} +{"t": 60, "temp_c": 52.0, "mhz": 800, "cpu_pct": 58.9, "watts": 3.52, "rss_mb": 454, "done": 18, "queue": 0} +{"t": 61, "temp_c": 51.0, "mhz": 798, "cpu_pct": 7.8, "watts": 3.02, "rss_mb": 377, "done": 18, "queue": 0} +{"summary": "pipeline", "kind": "c12_ea", "workers": 1, "threads": 4, "effort": 3, "distance": 1.00, "period_ms": 0, "secs": 60.1, "frames": 18, "frames_per_s": 0.299, "job_ms_p50": 3294, "latency_ms_p50": 3312, "latency_ms_p95": 3537, "latency_ms_max": 3567, "max_queue": 0, "backlog_at_end": 0, "cpu_s_per_frame": 7.664, "peak_rss_mb": 723, "temp_c_mean": 53.5, "temp_c_max": 55.0, "mhz_mean": 800, "mhz_min": 798, "cpu_pct_mean": 64.9, "watts_mean": 4.47} diff --git a/tools/camera-bench/results-2026-10-08-tower111-throttle/cap800_paced.jsonl b/tools/camera-bench/results-2026-10-08-tower111-throttle/cap800_paced.jsonl new file mode 100644 index 0000000..d6b2a90 --- /dev/null +++ b/tools/camera-bench/results-2026-10-08-tower111-throttle/cap800_paced.jsonl @@ -0,0 +1,122 @@ +{"t": 1, "temp_c": 54.0, "mhz": 800, "cpu_pct": 62.3, "watts": 4.46, "rss_mb": 525, "done": 0, "queue": 0} +{"t": 2, "temp_c": 53.0, "mhz": 800, "cpu_pct": 65.1, "watts": 4.70, "rss_mb": 531, "done": 0, "queue": 1} +{"t": 3, "temp_c": 53.0, "mhz": 800, "cpu_pct": 72.4, "watts": 4.95, "rss_mb": 534, "done": 0, "queue": 1} +{"t": 4, "temp_c": 54.0, "mhz": 800, "cpu_pct": 54.9, "watts": 3.32, "rss_mb": 465, "done": 1, "queue": 1} +{"t": 5, "temp_c": 54.0, "mhz": 800, "cpu_pct": 69.2, "watts": 4.74, "rss_mb": 604, "done": 1, "queue": 1} +{"t": 6, "temp_c": 54.0, "mhz": 800, "cpu_pct": 66.7, "watts": 4.75, "rss_mb": 609, "done": 1, "queue": 2} +{"t": 7, "temp_c": 53.0, "mhz": 800, "cpu_pct": 57.5, "watts": 4.57, "rss_mb": 284, "done": 2, "queue": 1} +{"t": 8, "temp_c": 53.0, "mhz": 800, "cpu_pct": 64.5, "watts": 4.66, "rss_mb": 602, "done": 2, "queue": 2} +{"t": 9, "temp_c": 53.0, "mhz": 800, "cpu_pct": 73.6, "watts": 3.63, "rss_mb": 623, "done": 2, "queue": 2} +{"t": 10, "temp_c": 54.0, "mhz": 800, "cpu_pct": 67.1, "watts": 4.83, "rss_mb": 625, "done": 2, "queue": 3} +{"t": 11, "temp_c": 54.0, "mhz": 800, "cpu_pct": 58.2, "watts": 4.50, "rss_mb": 592, "done": 3, "queue": 2} +{"t": 12, "temp_c": 54.0, "mhz": 800, "cpu_pct": 64.6, "watts": 4.87, "rss_mb": 597, "done": 3, "queue": 3} +{"t": 13, "temp_c": 54.0, "mhz": 800, "cpu_pct": 61.8, "watts": 4.51, "rss_mb": 562, "done": 3, "queue": 3} +{"t": 14, "temp_c": 54.0, "mhz": 800, "cpu_pct": 65.3, "watts": 3.58, "rss_mb": 479, "done": 4, "queue": 3} +{"t": 15, "temp_c": 55.0, "mhz": 800, "cpu_pct": 64.2, "watts": 4.58, "rss_mb": 662, "done": 4, "queue": 3} +{"t": 16, "temp_c": 54.0, "mhz": 800, "cpu_pct": 65.1, "watts": 4.94, "rss_mb": 680, "done": 4, "queue": 4} +{"t": 17, "temp_c": 54.0, "mhz": 800, "cpu_pct": 68.9, "watts": 4.77, "rss_mb": 681, "done": 4, "queue": 4} +{"t": 18, "temp_c": 54.0, "mhz": 800, "cpu_pct": 62.8, "watts": 3.43, "rss_mb": 683, "done": 5, "queue": 4} +{"t": 19, "temp_c": 54.0, "mhz": 800, "cpu_pct": 69.8, "watts": 4.79, "rss_mb": 686, "done": 5, "queue": 4} +{"t": 20, "temp_c": 53.0, "mhz": 800, "cpu_pct": 64.5, "watts": 4.88, "rss_mb": 686, "done": 5, "queue": 5} +{"t": 21, "temp_c": 54.0, "mhz": 800, "cpu_pct": 62.2, "watts": 4.57, "rss_mb": 671, "done": 6, "queue": 4} +{"t": 22, "temp_c": 54.0, "mhz": 800, "cpu_pct": 66.2, "watts": 4.78, "rss_mb": 679, "done": 6, "queue": 5} +{"t": 23, "temp_c": 56.0, "mhz": 800, "cpu_pct": 88.9, "watts": 4.14, "rss_mb": 680, "done": 6, "queue": 5} +{"t": 24, "temp_c": 54.0, "mhz": 800, "cpu_pct": 68.7, "watts": 5.29, "rss_mb": 463, "done": 7, "queue": 5} +{"t": 25, "temp_c": 54.0, "mhz": 800, "cpu_pct": 67.6, "watts": 4.70, "rss_mb": 684, "done": 7, "queue": 5} +{"t": 26, "temp_c": 54.0, "mhz": 800, "cpu_pct": 70.9, "watts": 4.95, "rss_mb": 685, "done": 7, "queue": 6} +{"t": 27, "temp_c": 53.0, "mhz": 800, "cpu_pct": 68.6, "watts": 3.59, "rss_mb": 686, "done": 7, "queue": 6} +{"t": 28, "temp_c": 53.0, "mhz": 800, "cpu_pct": 62.0, "watts": 4.72, "rss_mb": 686, "done": 8, "queue": 6} +{"t": 29, "temp_c": 54.0, "mhz": 800, "cpu_pct": 68.1, "watts": 4.81, "rss_mb": 688, "done": 8, "queue": 6} +{"t": 30, "temp_c": 54.0, "mhz": 800, "cpu_pct": 65.5, "watts": 4.80, "rss_mb": 688, "done": 8, "queue": 7} +{"t": 31, "temp_c": 54.0, "mhz": 800, "cpu_pct": 61.8, "watts": 3.31, "rss_mb": 686, "done": 9, "queue": 6} +{"t": 32, "temp_c": 54.0, "mhz": 800, "cpu_pct": 65.2, "watts": 5.06, "rss_mb": 691, "done": 9, "queue": 7} +{"t": 33, "temp_c": 54.0, "mhz": 800, "cpu_pct": 69.9, "watts": 4.56, "rss_mb": 692, "done": 9, "queue": 7} +{"t": 34, "temp_c": 53.0, "mhz": 800, "cpu_pct": 55.8, "watts": 4.59, "rss_mb": 660, "done": 10, "queue": 7} +{"t": 35, "temp_c": 54.0, "mhz": 800, "cpu_pct": 75.3, "watts": 5.00, "rss_mb": 694, "done": 10, "queue": 7} +{"t": 36, "temp_c": 54.0, "mhz": 800, "cpu_pct": 66.8, "watts": 3.44, "rss_mb": 695, "done": 10, "queue": 8} +{"t": 37, "temp_c": 54.0, "mhz": 800, "cpu_pct": 58.8, "watts": 4.74, "rss_mb": 525, "done": 11, "queue": 7} +{"t": 38, "temp_c": 54.0, "mhz": 800, "cpu_pct": 64.7, "watts": 4.60, "rss_mb": 697, "done": 11, "queue": 8} +{"t": 39, "temp_c": 53.0, "mhz": 800, "cpu_pct": 66.3, "watts": 4.95, "rss_mb": 697, "done": 11, "queue": 8} +{"t": 40, "temp_c": 54.0, "mhz": 800, "cpu_pct": 67.7, "watts": 3.59, "rss_mb": 697, "done": 11, "queue": 9} +{"t": 41, "temp_c": 54.0, "mhz": 800, "cpu_pct": 61.7, "watts": 4.93, "rss_mb": 697, "done": 12, "queue": 8} +{"t": 42, "temp_c": 54.0, "mhz": 800, "cpu_pct": 71.2, "watts": 4.77, "rss_mb": 698, "done": 12, "queue": 9} +{"t": 43, "temp_c": 54.0, "mhz": 800, "cpu_pct": 65.1, "watts": 4.75, "rss_mb": 698, "done": 12, "queue": 9} +{"t": 44, "temp_c": 54.0, "mhz": 800, "cpu_pct": 58.8, "watts": 3.32, "rss_mb": 684, "done": 13, "queue": 9} +{"t": 45, "temp_c": 54.0, "mhz": 800, "cpu_pct": 65.8, "watts": 5.05, "rss_mb": 699, "done": 13, "queue": 9} +{"t": 46, "temp_c": 54.0, "mhz": 800, "cpu_pct": 68.6, "watts": 4.62, "rss_mb": 699, "done": 13, "queue": 10} +{"t": 47, "temp_c": 53.0, "mhz": 801, "cpu_pct": 59.1, "watts": 4.68, "rss_mb": 652, "done": 14, "queue": 9} +{"t": 48, "temp_c": 54.0, "mhz": 800, "cpu_pct": 74.1, "watts": 3.62, "rss_mb": 700, "done": 14, "queue": 10} +{"t": 49, "temp_c": 53.0, "mhz": 800, "cpu_pct": 65.7, "watts": 4.80, "rss_mb": 700, "done": 14, "queue": 10} +{"t": 50, "temp_c": 54.0, "mhz": 800, "cpu_pct": 57.0, "watts": 4.76, "rss_mb": 493, "done": 15, "queue": 10} +{"t": 51, "temp_c": 54.0, "mhz": 800, "cpu_pct": 64.3, "watts": 4.57, "rss_mb": 701, "done": 15, "queue": 10} +{"t": 52, "temp_c": 54.0, "mhz": 800, "cpu_pct": 69.4, "watts": 3.77, "rss_mb": 702, "done": 15, "queue": 11} +{"t": 53, "temp_c": 54.0, "mhz": 800, "cpu_pct": 70.0, "watts": 4.80, "rss_mb": 702, "done": 15, "queue": 11} +{"t": 54, "temp_c": 55.0, "mhz": 800, "cpu_pct": 75.5, "watts": 5.33, "rss_mb": 688, "done": 16, "queue": 11} +{"t": 55, "temp_c": 54.0, "mhz": 800, "cpu_pct": 87.9, "watts": 5.57, "rss_mb": 704, "done": 16, "queue": 11} +{"t": 56, "temp_c": 54.0, "mhz": 800, "cpu_pct": 66.1, "watts": 3.65, "rss_mb": 705, "done": 16, "queue": 12} +{"t": 57, "temp_c": 55.0, "mhz": 800, "cpu_pct": 62.4, "watts": 4.67, "rss_mb": 565, "done": 17, "queue": 11} +{"t": 58, "temp_c": 54.0, "mhz": 800, "cpu_pct": 64.4, "watts": 4.61, "rss_mb": 705, "done": 17, "queue": 12} +{"t": 59, "temp_c": 54.0, "mhz": 800, "cpu_pct": 66.7, "watts": 4.98, "rss_mb": 721, "done": 17, "queue": 12} +{"t": 60, "temp_c": 54.0, "mhz": 800, "cpu_pct": 68.6, "watts": 3.54, "rss_mb": 721, "done": 17, "queue": 13} +{"t": 61, "temp_c": 53.0, "mhz": 800, "cpu_pct": 61.2, "watts": 4.78, "rss_mb": 705, "done": 18, "queue": 12} +{"t": 62, "temp_c": 54.0, "mhz": 800, "cpu_pct": 71.4, "watts": 4.73, "rss_mb": 706, "done": 18, "queue": 13} +{"t": 63, "temp_c": 54.0, "mhz": 800, "cpu_pct": 65.2, "watts": 4.85, "rss_mb": 706, "done": 18, "queue": 13} +{"t": 64, "temp_c": 54.0, "mhz": 800, "cpu_pct": 63.3, "watts": 4.63, "rss_mb": 699, "done": 19, "queue": 13} +{"t": 65, "temp_c": 53.0, "mhz": 800, "cpu_pct": 66.6, "watts": 3.70, "rss_mb": 723, "done": 19, "queue": 13} +{"t": 66, "temp_c": 54.0, "mhz": 800, "cpu_pct": 68.6, "watts": 4.75, "rss_mb": 723, "done": 19, "queue": 14} +{"t": 67, "temp_c": 54.0, "mhz": 800, "cpu_pct": 55.8, "watts": 4.63, "rss_mb": 666, "done": 20, "queue": 13} +{"t": 68, "temp_c": 55.0, "mhz": 800, "cpu_pct": 74.7, "watts": 4.97, "rss_mb": 723, "done": 20, "queue": 14} +{"t": 69, "temp_c": 54.0, "mhz": 800, "cpu_pct": 65.1, "watts": 3.47, "rss_mb": 723, "done": 20, "queue": 14} +{"t": 70, "temp_c": 54.0, "mhz": 800, "cpu_pct": 55.6, "watts": 4.73, "rss_mb": 493, "done": 21, "queue": 14} +{"t": 71, "temp_c": 54.0, "mhz": 800, "cpu_pct": 64.5, "watts": 4.70, "rss_mb": 724, "done": 21, "queue": 14} +{"t": 72, "temp_c": 54.0, "mhz": 800, "cpu_pct": 71.2, "watts": 4.94, "rss_mb": 724, "done": 21, "queue": 15} +{"t": 73, "temp_c": 54.0, "mhz": 800, "cpu_pct": 66.4, "watts": 3.57, "rss_mb": 724, "done": 21, "queue": 15} +{"t": 74, "temp_c": 54.0, "mhz": 800, "cpu_pct": 64.7, "watts": 4.61, "rss_mb": 724, "done": 22, "queue": 15} +{"t": 75, "temp_c": 54.0, "mhz": 800, "cpu_pct": 66.1, "watts": 4.77, "rss_mb": 724, "done": 22, "queue": 15} +{"t": 76, "temp_c": 54.0, "mhz": 800, "cpu_pct": 67.8, "watts": 4.95, "rss_mb": 724, "done": 22, "queue": 16} +{"t": 77, "temp_c": 54.0, "mhz": 800, "cpu_pct": 59.6, "watts": 4.58, "rss_mb": 679, "done": 23, "queue": 15} +{"t": 78, "temp_c": 53.0, "mhz": 800, "cpu_pct": 71.2, "watts": 3.69, "rss_mb": 708, "done": 23, "queue": 16} +{"t": 79, "temp_c": 55.0, "mhz": 800, "cpu_pct": 66.2, "watts": 4.74, "rss_mb": 709, "done": 23, "queue": 16} +{"t": 80, "temp_c": 54.0, "mhz": 800, "cpu_pct": 60.3, "watts": 4.70, "rss_mb": 649, "done": 24, "queue": 16} +{"t": 81, "temp_c": 54.0, "mhz": 800, "cpu_pct": 71.8, "watts": 4.91, "rss_mb": 709, "done": 24, "queue": 16} +{"t": 82, "temp_c": 54.0, "mhz": 800, "cpu_pct": 66.5, "watts": 3.47, "rss_mb": 709, "done": 24, "queue": 17} +{"t": 83, "temp_c": 54.0, "mhz": 800, "cpu_pct": 57.4, "watts": 4.79, "rss_mb": 496, "done": 25, "queue": 16} +{"t": 84, "temp_c": 54.0, "mhz": 800, "cpu_pct": 64.7, "watts": 4.70, "rss_mb": 709, "done": 25, "queue": 17} +{"t": 85, "temp_c": 54.0, "mhz": 800, "cpu_pct": 83.2, "watts": 5.34, "rss_mb": 726, "done": 25, "queue": 17} +{"t": 86, "temp_c": 54.0, "mhz": 800, "cpu_pct": 83.3, "watts": 4.00, "rss_mb": 726, "done": 25, "queue": 18} +{"t": 87, "temp_c": 54.0, "mhz": 800, "cpu_pct": 57.5, "watts": 4.54, "rss_mb": 682, "done": 26, "queue": 17} +{"t": 88, "temp_c": 55.0, "mhz": 800, "cpu_pct": 70.7, "watts": 5.03, "rss_mb": 710, "done": 26, "queue": 18} +{"t": 89, "temp_c": 54.0, "mhz": 800, "cpu_pct": 68.8, "watts": 4.69, "rss_mb": 710, "done": 26, "queue": 18} +{"t": 90, "temp_c": 54.0, "mhz": 800, "cpu_pct": 56.5, "watts": 3.47, "rss_mb": 664, "done": 27, "queue": 18} +{"t": 91, "temp_c": 55.0, "mhz": 800, "cpu_pct": 72.8, "watts": 4.82, "rss_mb": 711, "done": 27, "queue": 18} +{"t": 92, "temp_c": 53.0, "mhz": 800, "cpu_pct": 66.3, "watts": 4.85, "rss_mb": 711, "done": 27, "queue": 19} +{"t": 93, "temp_c": 54.0, "mhz": 800, "cpu_pct": 58.0, "watts": 4.62, "rss_mb": 522, "done": 28, "queue": 18} +{"t": 94, "temp_c": 54.0, "mhz": 800, "cpu_pct": 64.1, "watts": 4.70, "rss_mb": 711, "done": 28, "queue": 19} +{"t": 95, "temp_c": 53.0, "mhz": 800, "cpu_pct": 72.4, "watts": 3.76, "rss_mb": 711, "done": 28, "queue": 19} +{"t": 96, "temp_c": 54.0, "mhz": 800, "cpu_pct": 68.8, "watts": 4.74, "rss_mb": 711, "done": 28, "queue": 20} +{"t": 97, "temp_c": 53.0, "mhz": 800, "cpu_pct": 60.2, "watts": 4.78, "rss_mb": 711, "done": 29, "queue": 19} +{"t": 98, "temp_c": 54.0, "mhz": 800, "cpu_pct": 68.0, "watts": 4.79, "rss_mb": 712, "done": 29, "queue": 20} +{"t": 99, "temp_c": 54.0, "mhz": 800, "cpu_pct": 66.0, "watts": 3.54, "rss_mb": 712, "done": 29, "queue": 20} +{"t": 100, "temp_c": 54.0, "mhz": 800, "cpu_pct": 60.4, "watts": 4.52, "rss_mb": 691, "done": 30, "queue": 20} +{"t": 101, "temp_c": 54.0, "mhz": 800, "cpu_pct": 71.9, "watts": 5.11, "rss_mb": 712, "done": 30, "queue": 20} +{"t": 102, "temp_c": 54.0, "mhz": 800, "cpu_pct": 67.2, "watts": 4.64, "rss_mb": 713, "done": 30, "queue": 21} +{"t": 103, "temp_c": 53.0, "mhz": 800, "cpu_pct": 56.9, "watts": 3.45, "rss_mb": 662, "done": 31, "queue": 20} +{"t": 104, "temp_c": 54.0, "mhz": 800, "cpu_pct": 72.9, "watts": 4.85, "rss_mb": 713, "done": 31, "queue": 21} +{"t": 105, "temp_c": 54.0, "mhz": 800, "cpu_pct": 66.9, "watts": 4.87, "rss_mb": 713, "done": 31, "queue": 21} +{"t": 106, "temp_c": 54.0, "mhz": 800, "cpu_pct": 55.9, "watts": 4.62, "rss_mb": 495, "done": 32, "queue": 21} +{"t": 107, "temp_c": 54.0, "mhz": 800, "cpu_pct": 65.8, "watts": 3.75, "rss_mb": 713, "done": 32, "queue": 21} +{"t": 108, "temp_c": 54.0, "mhz": 800, "cpu_pct": 71.8, "watts": 4.72, "rss_mb": 713, "done": 32, "queue": 22} +{"t": 109, "temp_c": 54.0, "mhz": 800, "cpu_pct": 66.7, "watts": 4.71, "rss_mb": 713, "done": 32, "queue": 22} +{"t": 110, "temp_c": 55.0, "mhz": 800, "cpu_pct": 63.0, "watts": 4.88, "rss_mb": 713, "done": 33, "queue": 22} +{"t": 111, "temp_c": 54.0, "mhz": 800, "cpu_pct": 65.5, "watts": 4.77, "rss_mb": 713, "done": 33, "queue": 22} +{"t": 112, "temp_c": 54.0, "mhz": 800, "cpu_pct": 65.7, "watts": 3.55, "rss_mb": 713, "done": 33, "queue": 23} +{"t": 113, "temp_c": 55.0, "mhz": 800, "cpu_pct": 61.8, "watts": 4.61, "rss_mb": 685, "done": 34, "queue": 22} +{"t": 114, "temp_c": 54.0, "mhz": 800, "cpu_pct": 72.0, "watts": 5.02, "rss_mb": 729, "done": 34, "queue": 23} +{"t": 115, "temp_c": 54.0, "mhz": 800, "cpu_pct": 65.8, "watts": 4.65, "rss_mb": 729, "done": 34, "queue": 23} +{"t": 116, "temp_c": 54.0, "mhz": 800, "cpu_pct": 63.8, "watts": 3.50, "rss_mb": 634, "done": 35, "queue": 23} +{"t": 117, "temp_c": 55.0, "mhz": 800, "cpu_pct": 85.3, "watts": 5.43, "rss_mb": 714, "done": 35, "queue": 23} +{"t": 118, "temp_c": 54.0, "mhz": 800, "cpu_pct": 68.2, "watts": 5.08, "rss_mb": 714, "done": 35, "queue": 24} +{"t": 119, "temp_c": 53.0, "mhz": 800, "cpu_pct": 69.2, "watts": 4.74, "rss_mb": 714, "done": 35, "queue": 24} +{"t": 120, "temp_c": 54.0, "mhz": 800, "cpu_pct": 59.9, "watts": 4.59, "rss_mb": 714, "done": 36, "queue": 23} +{"t": 121, "temp_c": 54.0, "mhz": 800, "cpu_pct": 65.3, "watts": 3.50, "rss_mb": 714, "done": 36, "queue": 23} +{"summary": "pipeline", "kind": "c12_ea", "workers": 1, "threads": 4, "effort": 3, "distance": 1.00, "period_ms": 2000, "secs": 122.8, "frames": 37, "frames_per_s": 0.301, "job_ms_p50": 3246, "latency_ms_p50": 27515, "latency_ms_p95": 47924, "latency_ms_max": 50735, "max_queue": 24, "backlog_at_end": 23, "cpu_s_per_frame": 7.634, "peak_rss_mb": 729, "temp_c_mean": 53.9, "temp_c_max": 56.0, "mhz_mean": 800, "mhz_min": 800, "cpu_pct_mean": 66.3, "watts_mean": 4.51} diff --git a/tools/camera-bench/results-2026-10-08-tower111-throttle/cap800load2_paced.jsonl b/tools/camera-bench/results-2026-10-08-tower111-throttle/cap800load2_paced.jsonl new file mode 100644 index 0000000..f4bfb34 --- /dev/null +++ b/tools/camera-bench/results-2026-10-08-tower111-throttle/cap800load2_paced.jsonl @@ -0,0 +1,122 @@ +{"t": 1, "temp_c": 51.0, "mhz": 800, "cpu_pct": 91.3, "watts": -1.00, "rss_mb": 505, "done": 0, "queue": 0} +{"t": 2, "temp_c": 50.0, "mhz": 800, "cpu_pct": 94.0, "watts": -1.00, "rss_mb": 562, "done": 0, "queue": 1} +{"t": 3, "temp_c": 51.0, "mhz": 800, "cpu_pct": 92.2, "watts": -1.00, "rss_mb": 541, "done": 0, "queue": 1} +{"t": 4, "temp_c": 51.0, "mhz": 800, "cpu_pct": 91.0, "watts": -1.00, "rss_mb": 534, "done": 0, "queue": 2} +{"t": 5, "temp_c": 50.0, "mhz": 800, "cpu_pct": 91.5, "watts": -1.00, "rss_mb": 336, "done": 1, "queue": 1} +{"t": 6, "temp_c": 51.0, "mhz": 800, "cpu_pct": 92.7, "watts": -1.00, "rss_mb": 591, "done": 1, "queue": 2} +{"t": 7, "temp_c": 51.0, "mhz": 800, "cpu_pct": 91.5, "watts": -1.00, "rss_mb": 612, "done": 1, "queue": 2} +{"t": 8, "temp_c": 51.0, "mhz": 800, "cpu_pct": 95.2, "watts": -1.00, "rss_mb": 615, "done": 1, "queue": 3} +{"t": 9, "temp_c": 51.0, "mhz": 800, "cpu_pct": 92.3, "watts": -1.00, "rss_mb": 616, "done": 1, "queue": 3} +{"t": 10, "temp_c": 50.0, "mhz": 800, "cpu_pct": 85.8, "watts": -1.00, "rss_mb": 504, "done": 2, "queue": 3} +{"t": 11, "temp_c": 51.0, "mhz": 800, "cpu_pct": 92.0, "watts": -1.00, "rss_mb": 623, "done": 2, "queue": 3} +{"t": 12, "temp_c": 51.0, "mhz": 800, "cpu_pct": 92.8, "watts": -1.00, "rss_mb": 642, "done": 2, "queue": 4} +{"t": 13, "temp_c": 51.0, "mhz": 800, "cpu_pct": 91.6, "watts": -1.00, "rss_mb": 643, "done": 2, "queue": 4} +{"t": 14, "temp_c": 52.0, "mhz": 800, "cpu_pct": 96.5, "watts": -1.00, "rss_mb": 644, "done": 2, "queue": 5} +{"t": 15, "temp_c": 51.0, "mhz": 800, "cpu_pct": 99.5, "watts": -1.00, "rss_mb": 515, "done": 3, "queue": 4} +{"t": 16, "temp_c": 51.0, "mhz": 800, "cpu_pct": 91.8, "watts": -1.00, "rss_mb": 562, "done": 3, "queue": 5} +{"t": 17, "temp_c": 51.0, "mhz": 800, "cpu_pct": 92.3, "watts": -1.00, "rss_mb": 572, "done": 3, "queue": 5} +{"t": 18, "temp_c": 52.0, "mhz": 800, "cpu_pct": 91.8, "watts": -1.00, "rss_mb": 591, "done": 3, "queue": 6} +{"t": 19, "temp_c": 51.0, "mhz": 800, "cpu_pct": 90.8, "watts": -1.00, "rss_mb": 499, "done": 3, "queue": 6} +{"t": 20, "temp_c": 51.0, "mhz": 800, "cpu_pct": 93.5, "watts": -1.00, "rss_mb": 675, "done": 4, "queue": 6} +{"t": 21, "temp_c": 51.0, "mhz": 800, "cpu_pct": 94.8, "watts": -1.00, "rss_mb": 695, "done": 4, "queue": 6} +{"t": 22, "temp_c": 51.0, "mhz": 800, "cpu_pct": 90.0, "watts": -1.00, "rss_mb": 697, "done": 4, "queue": 7} +{"t": 23, "temp_c": 51.0, "mhz": 800, "cpu_pct": 92.6, "watts": -1.00, "rss_mb": 697, "done": 4, "queue": 7} +{"t": 24, "temp_c": 52.0, "mhz": 800, "cpu_pct": 90.8, "watts": -1.00, "rss_mb": 649, "done": 5, "queue": 7} +{"t": 25, "temp_c": 51.0, "mhz": 800, "cpu_pct": 95.0, "watts": -1.00, "rss_mb": 700, "done": 5, "queue": 7} +{"t": 26, "temp_c": 51.0, "mhz": 800, "cpu_pct": 93.8, "watts": -1.00, "rss_mb": 702, "done": 5, "queue": 8} +{"t": 27, "temp_c": 52.0, "mhz": 800, "cpu_pct": 92.5, "watts": -1.00, "rss_mb": 703, "done": 5, "queue": 8} +{"t": 28, "temp_c": 52.0, "mhz": 800, "cpu_pct": 90.3, "watts": -1.00, "rss_mb": 533, "done": 6, "queue": 8} +{"t": 29, "temp_c": 51.0, "mhz": 800, "cpu_pct": 90.5, "watts": -1.00, "rss_mb": 691, "done": 6, "queue": 8} +{"t": 30, "temp_c": 51.0, "mhz": 800, "cpu_pct": 94.0, "watts": -1.00, "rss_mb": 693, "done": 6, "queue": 9} +{"t": 31, "temp_c": 51.0, "mhz": 800, "cpu_pct": 94.5, "watts": -1.00, "rss_mb": 694, "done": 6, "queue": 9} +{"t": 32, "temp_c": 51.0, "mhz": 800, "cpu_pct": 90.5, "watts": -1.00, "rss_mb": 694, "done": 6, "queue": 10} +{"t": 33, "temp_c": 51.0, "mhz": 800, "cpu_pct": 91.0, "watts": -1.00, "rss_mb": 682, "done": 7, "queue": 9} +{"t": 34, "temp_c": 52.0, "mhz": 800, "cpu_pct": 95.3, "watts": -1.00, "rss_mb": 695, "done": 7, "queue": 10} +{"t": 35, "temp_c": 51.0, "mhz": 800, "cpu_pct": 89.8, "watts": -1.00, "rss_mb": 697, "done": 7, "queue": 10} +{"t": 36, "temp_c": 51.0, "mhz": 800, "cpu_pct": 93.7, "watts": -1.00, "rss_mb": 697, "done": 7, "queue": 11} +{"t": 37, "temp_c": 52.0, "mhz": 800, "cpu_pct": 88.8, "watts": -1.00, "rss_mb": 665, "done": 8, "queue": 10} +{"t": 38, "temp_c": 51.0, "mhz": 800, "cpu_pct": 94.8, "watts": -1.00, "rss_mb": 701, "done": 8, "queue": 11} +{"t": 39, "temp_c": 51.0, "mhz": 800, "cpu_pct": 94.0, "watts": -1.00, "rss_mb": 703, "done": 8, "queue": 11} +{"t": 40, "temp_c": 53.0, "mhz": 800, "cpu_pct": 91.5, "watts": -1.00, "rss_mb": 703, "done": 8, "queue": 12} +{"t": 41, "temp_c": 52.0, "mhz": 800, "cpu_pct": 92.3, "watts": -1.00, "rss_mb": 509, "done": 9, "queue": 11} +{"t": 42, "temp_c": 52.0, "mhz": 800, "cpu_pct": 93.0, "watts": -1.00, "rss_mb": 703, "done": 9, "queue": 12} +{"t": 43, "temp_c": 51.0, "mhz": 800, "cpu_pct": 94.5, "watts": -1.00, "rss_mb": 704, "done": 9, "queue": 12} +{"t": 44, "temp_c": 52.0, "mhz": 800, "cpu_pct": 91.8, "watts": -1.00, "rss_mb": 705, "done": 9, "queue": 13} +{"t": 45, "temp_c": 52.0, "mhz": 800, "cpu_pct": 94.0, "watts": -1.00, "rss_mb": 705, "done": 9, "queue": 13} +{"t": 46, "temp_c": 52.0, "mhz": 800, "cpu_pct": 99.0, "watts": -1.00, "rss_mb": 658, "done": 10, "queue": 13} +{"t": 47, "temp_c": 52.0, "mhz": 800, "cpu_pct": 95.0, "watts": -1.00, "rss_mb": 707, "done": 10, "queue": 13} +{"t": 48, "temp_c": 52.0, "mhz": 800, "cpu_pct": 93.7, "watts": -1.00, "rss_mb": 708, "done": 10, "queue": 14} +{"t": 49, "temp_c": 52.0, "mhz": 800, "cpu_pct": 91.2, "watts": -1.00, "rss_mb": 708, "done": 10, "queue": 14} +{"t": 50, "temp_c": 52.0, "mhz": 800, "cpu_pct": 91.8, "watts": -1.00, "rss_mb": 584, "done": 10, "queue": 15} +{"t": 51, "temp_c": 52.0, "mhz": 800, "cpu_pct": 91.3, "watts": -1.00, "rss_mb": 708, "done": 11, "queue": 14} +{"t": 52, "temp_c": 52.0, "mhz": 800, "cpu_pct": 93.8, "watts": -1.00, "rss_mb": 709, "done": 11, "queue": 15} +{"t": 53, "temp_c": 52.0, "mhz": 800, "cpu_pct": 91.5, "watts": -1.00, "rss_mb": 710, "done": 11, "queue": 15} +{"t": 54, "temp_c": 52.0, "mhz": 800, "cpu_pct": 92.5, "watts": -1.00, "rss_mb": 710, "done": 11, "queue": 16} +{"t": 55, "temp_c": 52.0, "mhz": 800, "cpu_pct": 90.3, "watts": -1.00, "rss_mb": 678, "done": 12, "queue": 15} +{"t": 56, "temp_c": 53.0, "mhz": 800, "cpu_pct": 94.5, "watts": -1.00, "rss_mb": 717, "done": 12, "queue": 16} +{"t": 57, "temp_c": 52.0, "mhz": 800, "cpu_pct": 94.0, "watts": -1.00, "rss_mb": 718, "done": 12, "queue": 16} +{"t": 58, "temp_c": 52.0, "mhz": 800, "cpu_pct": 91.8, "watts": -1.00, "rss_mb": 718, "done": 12, "queue": 17} +{"t": 59, "temp_c": 52.0, "mhz": 800, "cpu_pct": 90.8, "watts": -1.00, "rss_mb": 588, "done": 13, "queue": 16} +{"t": 60, "temp_c": 53.0, "mhz": 800, "cpu_pct": 92.5, "watts": -1.00, "rss_mb": 724, "done": 13, "queue": 17} +{"t": 61, "temp_c": 52.0, "mhz": 800, "cpu_pct": 92.0, "watts": -1.00, "rss_mb": 726, "done": 13, "queue": 17} +{"t": 62, "temp_c": 52.0, "mhz": 800, "cpu_pct": 93.2, "watts": -1.00, "rss_mb": 726, "done": 13, "queue": 18} +{"t": 63, "temp_c": 52.0, "mhz": 800, "cpu_pct": 90.5, "watts": -1.00, "rss_mb": 726, "done": 13, "queue": 18} +{"t": 64, "temp_c": 52.0, "mhz": 800, "cpu_pct": 91.8, "watts": -1.00, "rss_mb": 726, "done": 14, "queue": 18} +{"t": 65, "temp_c": 52.0, "mhz": 800, "cpu_pct": 94.8, "watts": -1.00, "rss_mb": 728, "done": 14, "queue": 18} +{"t": 66, "temp_c": 52.0, "mhz": 800, "cpu_pct": 90.0, "watts": -1.00, "rss_mb": 729, "done": 14, "queue": 19} +{"t": 67, "temp_c": 52.0, "mhz": 800, "cpu_pct": 92.5, "watts": -1.00, "rss_mb": 729, "done": 14, "queue": 19} +{"t": 68, "temp_c": 52.0, "mhz": 800, "cpu_pct": 88.6, "watts": -1.00, "rss_mb": 697, "done": 15, "queue": 19} +{"t": 69, "temp_c": 54.0, "mhz": 800, "cpu_pct": 94.5, "watts": -1.00, "rss_mb": 730, "done": 15, "queue": 19} +{"t": 70, "temp_c": 52.0, "mhz": 800, "cpu_pct": 93.8, "watts": -1.00, "rss_mb": 730, "done": 15, "queue": 20} +{"t": 71, "temp_c": 53.0, "mhz": 800, "cpu_pct": 91.2, "watts": -1.00, "rss_mb": 730, "done": 15, "queue": 20} +{"t": 72, "temp_c": 53.0, "mhz": 800, "cpu_pct": 89.3, "watts": -1.00, "rss_mb": 527, "done": 16, "queue": 20} +{"t": 73, "temp_c": 52.0, "mhz": 800, "cpu_pct": 91.2, "watts": -1.00, "rss_mb": 733, "done": 16, "queue": 20} +{"t": 74, "temp_c": 52.0, "mhz": 800, "cpu_pct": 94.8, "watts": -1.00, "rss_mb": 734, "done": 16, "queue": 21} +{"t": 75, "temp_c": 53.0, "mhz": 800, "cpu_pct": 95.3, "watts": -1.00, "rss_mb": 735, "done": 16, "queue": 21} +{"t": 76, "temp_c": 52.0, "mhz": 800, "cpu_pct": 92.5, "watts": -1.00, "rss_mb": 735, "done": 16, "queue": 22} +{"t": 77, "temp_c": 53.0, "mhz": 800, "cpu_pct": 97.8, "watts": -1.00, "rss_mb": 703, "done": 17, "queue": 21} +{"t": 78, "temp_c": 53.0, "mhz": 800, "cpu_pct": 99.8, "watts": -1.00, "rss_mb": 735, "done": 17, "queue": 22} +{"t": 79, "temp_c": 53.0, "mhz": 800, "cpu_pct": 95.0, "watts": -1.00, "rss_mb": 735, "done": 17, "queue": 22} +{"t": 80, "temp_c": 53.0, "mhz": 800, "cpu_pct": 94.8, "watts": -1.00, "rss_mb": 735, "done": 17, "queue": 23} +{"t": 81, "temp_c": 52.0, "mhz": 800, "cpu_pct": 91.2, "watts": -1.00, "rss_mb": 735, "done": 17, "queue": 23} +{"t": 82, "temp_c": 53.0, "mhz": 800, "cpu_pct": 91.8, "watts": -1.00, "rss_mb": 751, "done": 18, "queue": 23} +{"t": 83, "temp_c": 53.0, "mhz": 800, "cpu_pct": 92.8, "watts": -1.00, "rss_mb": 752, "done": 18, "queue": 23} +{"t": 84, "temp_c": 52.0, "mhz": 800, "cpu_pct": 94.0, "watts": -1.00, "rss_mb": 752, "done": 18, "queue": 24} +{"t": 85, "temp_c": 52.0, "mhz": 800, "cpu_pct": 91.5, "watts": -1.00, "rss_mb": 752, "done": 18, "queue": 24} +{"t": 86, "temp_c": 52.0, "mhz": 800, "cpu_pct": 89.5, "watts": -1.00, "rss_mb": 696, "done": 19, "queue": 24} +{"t": 87, "temp_c": 52.0, "mhz": 800, "cpu_pct": 93.0, "watts": -1.00, "rss_mb": 737, "done": 19, "queue": 24} +{"t": 88, "temp_c": 52.0, "mhz": 800, "cpu_pct": 95.2, "watts": -1.00, "rss_mb": 737, "done": 19, "queue": 25} +{"t": 89, "temp_c": 53.0, "mhz": 800, "cpu_pct": 94.2, "watts": -1.00, "rss_mb": 737, "done": 19, "queue": 25} +{"t": 90, "temp_c": 53.0, "mhz": 800, "cpu_pct": 89.0, "watts": -1.00, "rss_mb": 519, "done": 20, "queue": 25} +{"t": 91, "temp_c": 53.0, "mhz": 800, "cpu_pct": 93.5, "watts": -1.00, "rss_mb": 737, "done": 20, "queue": 25} +{"t": 92, "temp_c": 52.0, "mhz": 800, "cpu_pct": 91.2, "watts": -1.00, "rss_mb": 737, "done": 20, "queue": 26} +{"t": 93, "temp_c": 53.0, "mhz": 800, "cpu_pct": 96.3, "watts": -1.00, "rss_mb": 738, "done": 20, "queue": 26} +{"t": 94, "temp_c": 52.0, "mhz": 800, "cpu_pct": 91.8, "watts": -1.00, "rss_mb": 738, "done": 20, "queue": 27} +{"t": 95, "temp_c": 53.0, "mhz": 800, "cpu_pct": 87.5, "watts": -1.00, "rss_mb": 705, "done": 21, "queue": 26} +{"t": 96, "temp_c": 52.0, "mhz": 800, "cpu_pct": 93.3, "watts": -1.00, "rss_mb": 754, "done": 21, "queue": 27} +{"t": 97, "temp_c": 53.0, "mhz": 800, "cpu_pct": 94.5, "watts": -1.00, "rss_mb": 754, "done": 21, "queue": 27} +{"t": 98, "temp_c": 54.0, "mhz": 800, "cpu_pct": 94.3, "watts": -1.00, "rss_mb": 754, "done": 21, "queue": 28} +{"t": 99, "temp_c": 53.0, "mhz": 800, "cpu_pct": 88.7, "watts": -1.00, "rss_mb": 629, "done": 22, "queue": 27} +{"t": 100, "temp_c": 54.0, "mhz": 800, "cpu_pct": 87.2, "watts": -1.00, "rss_mb": 755, "done": 22, "queue": 28} +{"t": 101, "temp_c": 53.0, "mhz": 800, "cpu_pct": 95.3, "watts": -1.00, "rss_mb": 756, "done": 22, "queue": 28} +{"t": 102, "temp_c": 53.0, "mhz": 800, "cpu_pct": 94.0, "watts": -1.00, "rss_mb": 757, "done": 22, "queue": 29} +{"t": 103, "temp_c": 52.0, "mhz": 800, "cpu_pct": 90.7, "watts": -1.00, "rss_mb": 616, "done": 22, "queue": 29} +{"t": 104, "temp_c": 53.0, "mhz": 800, "cpu_pct": 92.7, "watts": -1.00, "rss_mb": 757, "done": 23, "queue": 29} +{"t": 105, "temp_c": 53.0, "mhz": 800, "cpu_pct": 92.3, "watts": -1.00, "rss_mb": 757, "done": 23, "queue": 29} +{"t": 106, "temp_c": 53.0, "mhz": 800, "cpu_pct": 94.5, "watts": -1.00, "rss_mb": 758, "done": 23, "queue": 30} +{"t": 107, "temp_c": 53.0, "mhz": 800, "cpu_pct": 91.2, "watts": -1.00, "rss_mb": 758, "done": 23, "queue": 30} +{"t": 108, "temp_c": 53.0, "mhz": 800, "cpu_pct": 92.2, "watts": -1.00, "rss_mb": 653, "done": 24, "queue": 30} +{"t": 109, "temp_c": 53.0, "mhz": 800, "cpu_pct": 99.5, "watts": -1.00, "rss_mb": 758, "done": 24, "queue": 30} +{"t": 110, "temp_c": 53.0, "mhz": 800, "cpu_pct": 97.0, "watts": -1.00, "rss_mb": 758, "done": 24, "queue": 31} +{"t": 111, "temp_c": 53.0, "mhz": 800, "cpu_pct": 95.2, "watts": -1.00, "rss_mb": 758, "done": 24, "queue": 31} +{"t": 112, "temp_c": 53.0, "mhz": 800, "cpu_pct": 91.2, "watts": -1.00, "rss_mb": 758, "done": 24, "queue": 32} +{"t": 113, "temp_c": 53.0, "mhz": 800, "cpu_pct": 90.2, "watts": -1.00, "rss_mb": 743, "done": 25, "queue": 31} +{"t": 114, "temp_c": 53.0, "mhz": 800, "cpu_pct": 93.0, "watts": -1.00, "rss_mb": 759, "done": 25, "queue": 32} +{"t": 115, "temp_c": 54.0, "mhz": 800, "cpu_pct": 94.8, "watts": -1.00, "rss_mb": 760, "done": 25, "queue": 32} +{"t": 116, "temp_c": 53.0, "mhz": 800, "cpu_pct": 92.2, "watts": -1.00, "rss_mb": 760, "done": 25, "queue": 33} +{"t": 117, "temp_c": 54.0, "mhz": 800, "cpu_pct": 89.0, "watts": -1.00, "rss_mb": 712, "done": 26, "queue": 32} +{"t": 118, "temp_c": 54.0, "mhz": 800, "cpu_pct": 93.3, "watts": -1.00, "rss_mb": 762, "done": 26, "queue": 33} +{"t": 119, "temp_c": 53.0, "mhz": 800, "cpu_pct": 95.0, "watts": -1.00, "rss_mb": 762, "done": 26, "queue": 33} +{"t": 120, "temp_c": 54.0, "mhz": 800, "cpu_pct": 95.0, "watts": -1.00, "rss_mb": 762, "done": 26, "queue": 33} +{"t": 121, "temp_c": 52.0, "mhz": 742, "cpu_pct": 83.7, "watts": -1.00, "rss_mb": 372, "done": 27, "queue": 33} +{"summary": "pipeline", "kind": "c12_ea", "workers": 1, "threads": 4, "effort": 3, "distance": 1.00, "period_ms": 2000, "secs": 121.3, "frames": 27, "frames_per_s": 0.223, "job_ms_p50": 4410, "latency_ms_p50": 37333, "latency_ms_p95": 66872, "latency_ms_max": 69294, "max_queue": 33, "backlog_at_end": 33, "cpu_s_per_frame": 6.880, "peak_rss_mb": 761, "temp_c_mean": 52.1, "temp_c_max": 54.0, "mhz_mean": 800, "mhz_min": 742, "cpu_pct_mean": 92.7, "watts_mean": -1.00} diff --git a/tools/camera-bench/results-2026-10-08-tower111-throttle/guard_run.log b/tools/camera-bench/results-2026-10-08-tower111-throttle/guard_run.log new file mode 100644 index 0000000..b07e51e --- /dev/null +++ b/tools/camera-bench/results-2026-10-08-tower111-throttle/guard_run.log @@ -0,0 +1,229 @@ +16:56:32.187 [INFO ] hardware: /etc/fire_gimbal_control/hardware.ini board lattepanda-3-delta axes yaw, pitch +16:56:32.188 [INFO ] logging to /home/ggs/.local/share/fire_gimbal_control/logs/fgc_20261008-165632.000.log (level debug, 10 x 20 MB) +16:56:32.188 [INFO ] Loaded config: /home/ggs/.config/fire_gimbal_control/config.ini +16:56:32.188 [INFO ] Single-instance lock held: /run/user/1000/fire_gimbal_control.lock +16:56:32.188 [INFO ] wire trace enabled: mqtt +16:56:32.246 [INFO ] camera 'rgb': 3 profile(s) from /home/ggs/.config/fire_gimbal_control/camera_profiles/rgb; active 'standard' +16:56:32.247 [INFO ] IMU frames: reference NWU -> NED; mount x 180 y 0 z 0 (deg, Z-X-Y); readings are in the gimbal frame +16:56:32.258 [INFO ] MQTT (re)connected; subscribing +16:56:32.258 [INFO ] MQTT connected to tcp://127.0.0.1:1883 as 'dev' +16:56:32.258 [INFO ] capture output dir: /home/ggs/projects/fwt_2a/images +16:56:32.258 [INFO ] image saving: 1 worker(s) x 4 thread(s), up to 4 frame(s) waiting +16:56:32.258 [INFO ] scan grid: 15 waypoints +16:56:32.258 [INFO ] keep rule: fixed wait of 1000 ms after standstill +16:56:32.259 [INFO ] CPU guard: pictures slow down at 85 °C or when the CPU throttles, and pause at 92 °C (resume at 75 °C) +16:56:32.259 [INFO ] yaw motor current: run 756 mA (GLOBALSCALER 80, IRUN 31), homing 756 mA (IRUN 31) -- R_sense 0.075 ohm from hardware.ini [Motor.yaw] +16:56:32.259 [INFO ] pitch motor current: run 472 mA (GLOBALSCALER 50, IRUN 31), homing 472 mA (IRUN 31) -- R_sense 0.075 ohm from hardware.ini [Motor.pitch] +16:56:32.259 [INFO ] Serial controller started on /dev/serial/by-id/usb-Arduino_LLC_Arduino_Leonardo-if00 @ 115200 +16:56:32.941 [INFO ] MTi config: MTi-28A53G35 fw 2.6.1 scenario 7 (machine_nomagfield) @ 100 Hz, mode 0x0007 settings 0x00000005 +16:56:33.002 [INFO ] MTi IMU started on /dev/ttyS0 @ 115200 +16:56:34.025 [INFO ] Opened camera 'rgb' DEV_1AB22C049CD1 (1800 U-1240c) +16:56:43.189 [ERROR] camera: applying profile 'standard' from /home/ggs/.config/fire_gimbal_control/camera_profiles/rgb/standard.xml failed (error -39) +16:56:43.190 [INFO ] hardware: declared assembly confirmed (yaw, pitch) +16:56:43.190 [MQTT] PUB GGS/FWT/dev/StatusCode 2 +16:56:43.282 [INFO ] Running gimbal init sequence (enable + home) +16:56:43.282 [INFO ] drivetrain re-sent to axis Y: 12 values from hardware.ini +16:56:43.282 [INFO ] drivetrain re-sent to axis P: 12 values from hardware.ini +16:57:26.384 [INFO ] Gimbal homed (both axes READY) +16:57:26.441 [INFO ] firmware dump: +DUMP BEGIN build=8efa63b uptime=84894318 mcusr=0x0 reset=none free_ram=1607 +DUMP Y state=3 hsub=8 enabled=1 lim_neg=408820 lim_pos=591181 hold_target=500000 speed=5908 center=500000 soft_margin=5000 gs=80 irun=31 irun_home=31 steps_per_rev=177000 enc_dev=1500 homing_speed=12362 center_speed=25753 sw_mode=2208 cfg_rx=0x1FFE limp=0 hold_corr=4 hold_peak=142693 hold_cumul=2969 has_encoder=1 cs=8 en=7 +DUMP Y TMC GCONF=0x0000000C GSTAT=0x00000000 IOIN=0x3000002C TSTEP=0x000FFFFF RAMPMODE=0x00000000 XACTUAL=0x0007A120 VACTUAL=0x00000000 XTARGET=0x0007A120 SW_MODE=0x000008A0 RAMP_STAT=0x00000680 X_ENC=0x0007A117 ENC_STATUS=0x00000000 CHOPCONF=0x00410043 DRV_STATUS=0x801F4000 PWM_SCALE=0x00000040 PWM_AUTO=0x006C003E +DUMP P state=3 hsub=8 enabled=1 lim_neg=213898 lim_pos=786103 hold_target=500000 speed=157403 center=500000 soft_margin=5000 gs=50 irun=31 irun_home=31 steps_per_rev=500000 enc_dev=1500 homing_speed=170978 center_speed=68470 sw_mode=2211 cfg_rx=0x1FFE limp=0 hold_corr=2 hold_peak=245129 hold_cumul=615 has_encoder=1 cs=1 en=0 +DUMP P TMC GCONF=0x0000000C GSTAT=0x00000000 IOIN=0x30000008 TSTEP=0x000FFFFF RAMPMODE=0x00000000 XACTUAL=0x0007A120 VACTUAL=0x00000000 XTARGET=0x0007A120 SW_MODE=0x000008A3 RAMP_STAT=0x00001688 X_ENC=0x0007A113 ENC_STATUS=0x00000000 CHOPCONF=0x00410043 DRV_STATUS=0x80084000 PWM_SCALE=0x0000000F PWM_AUTO=0x001E0038 +DUMP ENV t=21 h=52 ok=1 fan=0 rpm=0 dht=9 fan_pwm=10 fan_tach=11 +DUMP END + +16:57:26.500 [INFO ] yaw counts_per_deg 528.254 agrees with the homed travel (182361 counts / 345 deg = 528.583); leaving the calibration alone +16:57:26.500 [INFO ] pitch counts_per_deg -14072.9 agrees with the homed travel (572205 counts / 40.5 deg = -14128.5); leaving the calibration alone +16:57:26.500 [INFO ] homed yaw travel: 112.4..438.684 deg (headings 112.4..78.6844 true), centre 500000 = 275.541 deg +16:57:26.500 [INFO ] homed pitch travel: -27.3943..12.5553 deg, centre 500000 = -7.41947 deg +16:57:26.500 [WARN ] 3 of 15 scan waypoints lie outside the reachable yaw arc (112.4..78.6844 true) and will be skipped +16:57:26.500 [INFO ] yaw speed: 7.99943 deg/s (VMAX 5908) -- 528.254 counts/deg +16:57:26.500 [INFO ] pitch speed: 8.00003 deg/s (VMAX 157403) -- -14072.9 counts/deg +16:57:26.500 [INFO ] Gimbal init finished +16:57:26.509 [INFO ] mode auto +16:57:26.509 [MQTT] PUB GGS/FWT/dev/StatusCode 0 +16:57:26.509 [INFO ] Entering control loop (type 'exit' to quit, or Ctrl-C) +16:57:26.509 [INFO ] scan move 1/15: yaw 0 pitch 10 (deg) +16:57:26.511 [MQTT] PUB GGS/FWT/dev/Health {"fwt":"dev","state":"normal","reason":"","guard":true,"cpu_c":51.0,"cpu_mhz":2800,"cpu_busy_pct":null,"throttling":false,"throttle_events_last_min":0,"enclosure_c":21.0,"saved_last_min":0,"save_ms_mean":null,"save_ms_max":null,"held_s":0,"interval_s":5.0,"time":1791471446511} +16:57:38.211 [INFO ] captured waypoint 1/15 +16:57:38.222 [INFO ] scan move 2/15: yaw 45 pitch 10 (deg) +16:57:39.022 [MQTT] PUB GGS/FWT/dev/CamEvent { "fwt":"dev", "cam":"RGB", "hdg":3598, "pit":86, "time":1791471458131 } +16:57:45.060 [WARN ] waypoint 2/15: 6.8 s since the last picture, over the 5.0 s interval (move 5.8 s, exposure 1.0 s) +16:57:45.084 [INFO ] captured waypoint 2/15 +16:57:45.095 [WARN ] scan waypoint 3/15 heading 90 deg is outside the reachable yaw arc; skipping it (further unreachable waypoints are skipped silently) +16:57:45.095 [INFO ] scan move 4/15: yaw 135 pitch 10 (deg) +16:57:45.692 [MQTT] PUB GGS/FWT/dev/CamEvent { "fwt":"dev", "cam":"RGB", "hdg":450, "pit":86, "time":1791471464990 } +16:58:19.746 [WARN ] waypoint 4/15: 34.7 s since the last picture, over the 5.0 s interval (move 33.6 s, exposure 1.0 s) +16:58:19.760 [INFO ] captured waypoint 4/15 +16:58:19.770 [INFO ] scan move 5/15: yaw 180 pitch 10 (deg) +16:58:20.384 [MQTT] PUB GGS/FWT/dev/CamEvent { "fwt":"dev", "cam":"RGB", "hdg":1353, "pit":93, "time":1791471499666 } +16:58:26.626 [WARN ] waypoint 5/15: 6.9 s since the last picture, over the 5.0 s interval (move 5.8 s, exposure 1.0 s) +16:58:26.637 [INFO ] captured waypoint 5/15 +16:58:26.639 [MQTT] PUB GGS/FWT/dev/Health {"fwt":"dev","state":"normal","reason":"","guard":true,"cpu_c":53.0,"cpu_mhz":1077,"cpu_busy_pct":15,"throttling":false,"throttle_events_last_min":0,"enclosure_c":21.0,"saved_last_min":3,"save_ms_mean":681,"save_ms_max":812,"held_s":0,"interval_s":5.0,"time":1791471506639} +16:58:26.650 [INFO ] scan move 6/15: yaw 0 pitch 5 (deg) +16:58:27.239 [MQTT] PUB GGS/FWT/dev/CamEvent { "fwt":"dev", "cam":"RGB", "hdg":1800, "pit":98, "time":1791471506525 } +16:58:50.079 [WARN ] waypoint 6/15: 23.4 s since the last picture, over the 5.0 s interval (move 22.4 s, exposure 1.0 s) +16:58:50.087 [INFO ] captured waypoint 6/15 +16:58:50.097 [INFO ] scan move 7/15: yaw 45 pitch 5 (deg) +16:58:50.779 [INFO ] saving: 3 pictures in the last minute, 639 ms each on average (longest 692 ms) +16:58:50.779 [MQTT] PUB GGS/FWT/dev/CamEvent { "fwt":"dev", "cam":"RGB", "hdg":3598, "pit":36, "time":1791471530055 } +16:58:56.963 [WARN ] waypoint 7/15: 6.9 s since the last picture, over the 5.0 s interval (move 5.9 s, exposure 1.0 s) +16:58:56.977 [INFO ] captured waypoint 7/15 +16:58:56.987 [INFO ] scan move 9/15: yaw 135 pitch 5 (deg) +16:58:57.604 [MQTT] PUB GGS/FWT/dev/CamEvent { "fwt":"dev", "cam":"RGB", "hdg":451, "pit":36, "time":1791471536914 } +16:59:26.757 [MQTT] PUB GGS/FWT/dev/Health {"fwt":"dev","state":"normal","reason":"","guard":true,"cpu_c":52.0,"cpu_mhz":778,"cpu_busy_pct":14,"throttling":false,"throttle_events_last_min":0,"enclosure_c":21.0,"saved_last_min":3,"save_ms_mean":640,"save_ms_max":692,"held_s":0,"interval_s":5.0,"time":1791471566757} +16:59:31.624 [WARN ] waypoint 9/15: 34.6 s since the last picture, over the 5.0 s interval (move 33.6 s, exposure 1.0 s) +16:59:31.660 [INFO ] captured waypoint 9/15 +16:59:31.670 [INFO ] scan move 10/15: yaw 180 pitch 5 (deg) +16:59:32.194 [INFO ] CPU: 52 °C (x86_pkg_temp), 2.8 GHz of 2.9 GHz, 14% busy +16:59:32.194 [INFO ] throttling: no, 0 in the last minute, 0 since start +16:59:32.194 [INFO ] scan pace: normal, one picture every 5.0 s +16:59:32.194 [INFO ] saving: 2 in the last minute, 659 ms each (longest 692 ms) +16:59:32.194 [INFO ] limits: slows at 85 °C, pauses at 92 °C for 20 s, resumes at 75 °C after at least 300 s +16:59:32.571 [MQTT] PUB GGS/FWT/dev/CamEvent { "fwt":"dev", "cam":"RGB", "hdg":1352, "pit":43, "time":1791471571589 } +16:59:36.780 [WARN ] CPU is running slow while busy (judged from its clock, not the kernel's count): 53 °C, running at 0.8 GHz of 2.9 GHz +16:59:36.780 [WARN ] CPU is slowing itself down: one picture every 10.0 s instead of 5.0 until the CPU is below 80 °C and keeping up for 2 min +16:59:36.780 [MQTT] PUB GGS/FWT/dev/Health {"fwt":"dev","state":"slowed","reason":"CPU is slowing itself down","guard":true,"cpu_c":53.0,"cpu_mhz":800,"cpu_busy_pct":68,"throttling":true,"throttle_events_last_min":0,"enclosure_c":21.0,"saved_last_min":3,"save_ms_mean":743,"save_ms_max":911,"held_s":0,"interval_s":10.0,"time":1791471576780} +16:59:41.678 [INFO ] captured waypoint 10/15 +16:59:41.690 [INFO ] scan move 11/15: yaw 0 pitch 15 (deg) +16:59:45.715 [MQTT] PUB GGS/FWT/dev/CamEvent { "fwt":"dev", "cam":"RGB", "hdg":1800, "pit":48, "time":1791471581592 } +17:00:05.030 [WARN ] waypoint 11/15: 23.4 s since the last picture, over the 10.0 s interval (move 22.3 s, exposure 1.0 s) +17:00:05.052 [INFO ] captured waypoint 11/15 +17:00:05.064 [INFO ] scan move 12/15: yaw 45 pitch 15 (deg) +17:00:08.937 [INFO ] saving: 3 pictures in the last minute, 2943 ms each on average (longest 4035 ms) +17:00:08.937 [MQTT] PUB GGS/FWT/dev/CamEvent { "fwt":"dev", "cam":"RGB", "hdg":3598, "pit":111, "time":1791471604932 } +17:00:15.076 [INFO ] captured waypoint 12/15 +17:00:15.087 [INFO ] scan move 14/15: yaw 135 pitch 15 (deg) +17:00:18.694 [MQTT] PUB GGS/FWT/dev/CamEvent { "fwt":"dev", "cam":"RGB", "hdg":451, "pit":111, "time":1791471615030 } +17:00:36.922 [WARN ] CPU still slowed: clock low all minute, 53 °C, running at 0.8 GHz of 2.9 GHz +17:00:36.923 [MQTT] PUB GGS/FWT/dev/Health {"fwt":"dev","state":"slowed","reason":"CPU is slowing itself down","guard":true,"cpu_c":53.0,"cpu_mhz":800,"cpu_busy_pct":68,"throttling":true,"throttle_events_last_min":0,"enclosure_c":21.0,"saved_last_min":3,"save_ms_mean":3846,"save_ms_max":4035,"held_s":0,"interval_s":10.0,"time":1791471636922} +17:00:49.639 [WARN ] waypoint 14/15: 34.6 s since the last picture, over the 10.0 s interval (move 33.5 s, exposure 1.0 s) +17:00:49.658 [INFO ] captured waypoint 14/15 +17:00:49.669 [INFO ] scan move 15/15: yaw 180 pitch 15 (deg) +17:00:53.137 [MQTT] PUB GGS/FWT/dev/CamEvent { "fwt":"dev", "cam":"RGB", "hdg":1353, "pit":119, "time":1791471649611 } +17:00:59.678 [INFO ] captured waypoint 15/15 +17:00:59.691 [INFO ] scan move 1/15: yaw 0 pitch 10 (deg) +17:01:03.230 [MQTT] PUB GGS/FWT/dev/CamEvent { "fwt":"dev", "cam":"RGB", "hdg":1800, "pit":123, "time":1791471659613 } +17:01:23.125 [INFO ] captured waypoint 1/15 +17:01:23.136 [INFO ] scan move 2/15: yaw 45 pitch 10 (deg) +17:01:27.270 [INFO ] saving: 3 pictures in the last minute, 3725 ms each on average (longest 4145 ms) +17:01:27.270 [MQTT] PUB GGS/FWT/dev/CamEvent { "fwt":"dev", "cam":"RGB", "hdg":3598, "pit":86, "time":1791471682953 } +17:01:32.235 [INFO ] CPU: 54 °C (x86_pkg_temp), 0.8 GHz of 2.9 GHz, 69% busy +17:01:32.235 [INFO ] throttling: yes (clock), 0 in the last minute, 0 since start +17:01:32.235 [INFO ] scan pace: slowed - CPU is slowing itself down, one picture every 10.0 s +17:01:32.235 [INFO ] saving: 3 in the last minute, 3725 ms each (longest 4145 ms) +17:01:32.235 [INFO ] limits: slows at 85 °C, pauses at 92 °C for 20 s, resumes at 75 °C after at least 300 s +17:01:33.145 [INFO ] captured waypoint 2/15 +17:01:33.157 [INFO ] scan move 4/15: yaw 135 pitch 10 (deg) +17:01:36.745 [MQTT] PUB GGS/FWT/dev/CamEvent { "fwt":"dev", "cam":"RGB", "hdg":451, "pit":85, "time":1791471693053 } +17:01:37.060 [WARN ] CPU still slowed: clock low all minute, 55 °C, running at 0.8 GHz of 2.9 GHz +17:01:37.061 [MQTT] PUB GGS/FWT/dev/Health {"fwt":"dev","state":"slowed","reason":"CPU is slowing itself down","guard":true,"cpu_c":55.0,"cpu_mhz":800,"cpu_busy_pct":91,"throttling":true,"throttle_events_last_min":0,"enclosure_c":21.0,"saved_last_min":4,"save_ms_mean":3693,"save_ms_max":4145,"held_s":0,"interval_s":10.0,"time":1791471697060} +17:02:07.648 [WARN ] waypoint 4/15: 34.5 s since the last picture, over the 10.0 s interval (move 33.5 s, exposure 1.0 s) +17:02:07.666 [INFO ] captured waypoint 4/15 +17:02:07.677 [INFO ] scan move 5/15: yaw 180 pitch 10 (deg) +17:02:11.187 [MQTT] PUB GGS/FWT/dev/CamEvent { "fwt":"dev", "cam":"RGB", "hdg":1353, "pit":93, "time":1791471727633 } +17:02:17.729 [INFO ] captured waypoint 5/15 +17:02:17.741 [INFO ] scan move 6/15: yaw 0 pitch 5 (deg) +17:02:21.530 [MQTT] PUB GGS/FWT/dev/CamEvent { "fwt":"dev", "cam":"RGB", "hdg":1800, "pit":98, "time":1791471737641 } +17:02:37.221 [WARN ] CPU still slowed: clock low all minute, 54 °C, running at 0.8 GHz of 2.9 GHz +17:02:37.222 [MQTT] PUB GGS/FWT/dev/Health {"fwt":"dev","state":"slowed","reason":"CPU is slowing itself down","guard":true,"cpu_c":54.0,"cpu_mhz":800,"cpu_busy_pct":66,"throttling":true,"throttle_events_last_min":0,"enclosure_c":21.0,"saved_last_min":2,"save_ms_mean":3660,"save_ms_max":3799,"held_s":0,"interval_s":10.0,"time":1791471757221} +17:02:41.302 [WARN ] waypoint 6/15: 23.6 s since the last picture, over the 10.0 s interval (move 22.6 s, exposure 1.0 s) +17:02:41.322 [INFO ] captured waypoint 6/15 +17:02:41.332 [INFO ] scan move 7/15: yaw 45 pitch 5 (deg) +17:02:45.136 [INFO ] saving: 3 pictures in the last minute, 3711 ms each on average (longest 3814 ms) +17:02:45.136 [MQTT] PUB GGS/FWT/dev/CamEvent { "fwt":"dev", "cam":"RGB", "hdg":3598, "pit":34, "time":1791471761260 } +17:02:51.339 [INFO ] captured waypoint 7/15 +17:02:51.351 [INFO ] scan move 9/15: yaw 135 pitch 5 (deg) +17:02:54.779 [MQTT] PUB GGS/FWT/dev/CamEvent { "fwt":"dev", "cam":"RGB", "hdg":451, "pit":36, "time":1791471771262 } +17:03:25.914 [WARN ] waypoint 9/15: 34.6 s since the last picture, over the 10.0 s interval (move 33.6 s, exposure 1.0 s) +17:03:25.932 [INFO ] captured waypoint 9/15 +17:03:25.945 [INFO ] scan move 10/15: yaw 180 pitch 5 (deg) +17:03:29.611 [MQTT] PUB GGS/FWT/dev/CamEvent { "fwt":"dev", "cam":"RGB", "hdg":1352, "pit":43, "time":1791471805843 } +17:03:32.234 [INFO ] CPU: 56 °C (x86_pkg_temp), 0.8 GHz of 2.9 GHz, 72% busy +17:03:32.234 [INFO ] throttling: yes (clock), 0 in the last minute, 0 since start +17:03:32.234 [INFO ] scan pace: slowed - CPU is slowing itself down, one picture every 10.0 s +17:03:32.234 [INFO ] saving: 3 in the last minute, 3644 ms each (longest 3814 ms) +17:03:32.234 [INFO ] limits: slows at 85 °C, pauses at 92 °C for 20 s, resumes at 75 °C after at least 300 s +17:03:35.956 [INFO ] captured waypoint 10/15 +17:03:35.967 [INFO ] scan move 11/15: yaw 0 pitch 15 (deg) +17:03:37.373 [WARN ] CPU still slowed: clock low all minute, 55 °C, running at 0.8 GHz of 2.9 GHz +17:03:37.374 [MQTT] PUB GGS/FWT/dev/Health {"fwt":"dev","state":"slowed","reason":"CPU is slowing itself down","guard":true,"cpu_c":55.0,"cpu_mhz":800,"cpu_busy_pct":88,"throttling":true,"throttle_events_last_min":0,"enclosure_c":21.0,"saved_last_min":3,"save_ms_mean":3644,"save_ms_max":3814,"held_s":0,"interval_s":10.0,"time":1791471817373} +17:03:39.531 [MQTT] PUB GGS/FWT/dev/CamEvent { "fwt":"dev", "cam":"RGB", "hdg":1800, "pit":46, "time":1791471815845 } +17:03:59.314 [WARN ] waypoint 11/15: 23.4 s since the last picture, over the 10.0 s interval (move 22.3 s, exposure 1.0 s) +17:03:59.419 [INFO ] captured waypoint 11/15 +17:03:59.433 [INFO ] scan move 12/15: yaw 45 pitch 15 (deg) +17:04:03.336 [INFO ] saving: 3 pictures in the last minute, 3723 ms each on average (longest 3917 ms) +17:04:03.336 [MQTT] PUB GGS/FWT/dev/CamEvent { "fwt":"dev", "cam":"RGB", "hdg":3598, "pit":110, "time":1791471839281 } +17:04:09.440 [INFO ] captured waypoint 12/15 +17:04:09.460 [INFO ] scan move 14/15: yaw 135 pitch 15 (deg) +17:04:12.912 [MQTT] PUB GGS/FWT/dev/CamEvent { "fwt":"dev", "cam":"RGB", "hdg":451, "pit":110, "time":1791471849379 } +17:04:37.498 [WARN ] CPU still slowed: clock low all minute, 55 °C, running at 0.8 GHz of 2.9 GHz +17:04:37.498 [MQTT] PUB GGS/FWT/dev/Health {"fwt":"dev","state":"slowed","reason":"CPU is slowing itself down","guard":true,"cpu_c":55.0,"cpu_mhz":800,"cpu_busy_pct":68,"throttling":true,"throttle_events_last_min":0,"enclosure_c":21.0,"saved_last_min":3,"save_ms_mean":3654,"save_ms_max":3917,"held_s":0,"interval_s":10.0,"time":1791471877498} +17:04:43.926 [WARN ] waypoint 14/15: 34.5 s since the last picture, over the 10.0 s interval (move 33.5 s, exposure 1.0 s) +17:04:43.945 [INFO ] captured waypoint 14/15 +17:04:43.958 [INFO ] scan move 15/15: yaw 180 pitch 15 (deg) +17:04:47.588 [MQTT] PUB GGS/FWT/dev/CamEvent { "fwt":"dev", "cam":"RGB", "hdg":1353, "pit":118, "time":1791471883866 } +17:04:54.005 [INFO ] captured waypoint 15/15 +17:04:54.015 [INFO ] scan move 1/15: yaw 0 pitch 10 (deg) +17:04:57.678 [MQTT] PUB GGS/FWT/dev/CamEvent { "fwt":"dev", "cam":"RGB", "hdg":1800, "pit":122, "time":1791471893874 } +17:05:17.599 [INFO ] captured waypoint 1/15 +17:05:17.618 [INFO ] scan move 2/15: yaw 45 pitch 10 (deg) +17:05:21.659 [INFO ] saving: 3 pictures in the last minute, 3790 ms each on average (longest 4059 ms) +17:05:21.659 [MQTT] PUB GGS/FWT/dev/CamEvent { "fwt":"dev", "cam":"RGB", "hdg":3598, "pit":86, "time":1791471917492 } +17:05:27.622 [INFO ] captured waypoint 2/15 +17:05:27.636 [INFO ] scan move 4/15: yaw 135 pitch 10 (deg) +17:05:31.188 [MQTT] PUB GGS/FWT/dev/CamEvent { "fwt":"dev", "cam":"RGB", "hdg":451, "pit":85, "time":1791471927590 } +17:05:32.236 [INFO ] CPU: 55 °C (x86_pkg_temp), 0.8 GHz of 2.9 GHz, 89% busy +17:05:32.236 [INFO ] throttling: yes (clock), 0 in the last minute, 0 since start +17:05:32.236 [INFO ] scan pace: slowed - CPU is slowing itself down, one picture every 10.0 s +17:05:32.237 [INFO ] saving: 4 in the last minute, 3734 ms each (longest 4059 ms) +17:05:32.237 [INFO ] limits: slows at 85 °C, pauses at 92 °C for 20 s, resumes at 75 °C after at least 300 s +17:05:37.647 [MQTT] PUB GGS/FWT/dev/Health {"fwt":"dev","state":"slowed","reason":"CPU is slowing itself down","guard":true,"cpu_c":54.0,"cpu_mhz":800,"cpu_busy_pct":14,"throttling":true,"throttle_events_last_min":0,"enclosure_c":21.0,"saved_last_min":4,"save_ms_mean":3734,"save_ms_max":4059,"held_s":0,"interval_s":10.0,"time":1791471937647} +17:06:02.200 [WARN ] waypoint 4/15: 34.6 s since the last picture, over the 10.0 s interval (move 33.6 s, exposure 1.0 s) +17:06:02.214 [INFO ] captured waypoint 4/15 +17:06:02.225 [INFO ] scan move 5/15: yaw 180 pitch 10 (deg) +17:06:02.858 [MQTT] PUB GGS/FWT/dev/CamEvent { "fwt":"dev", "cam":"RGB", "hdg":1352, "pit":92, "time":1791471962171 } +17:06:12.236 [INFO ] captured waypoint 5/15 +17:06:12.247 [INFO ] scan move 6/15: yaw 0 pitch 5 (deg) +17:06:12.864 [MQTT] PUB GGS/FWT/dev/CamEvent { "fwt":"dev", "cam":"RGB", "hdg":1800, "pit":97, "time":1791471972173 } +17:06:31.739 [INFO ] CPU back to full speed: 55 °C, 0.8 GHz +17:06:31.740 [MQTT] PUB GGS/FWT/dev/Health {"fwt":"dev","state":"slowed","reason":"CPU is slowing itself down","guard":true,"cpu_c":55.0,"cpu_mhz":800,"cpu_busy_pct":12,"throttling":false,"throttle_events_last_min":0,"enclosure_c":21.0,"saved_last_min":2,"save_ms_mean":636,"save_ms_max":644,"held_s":0,"interval_s":10.0,"time":1791471991739} +17:06:35.594 [WARN ] waypoint 6/15: 23.4 s since the last picture, over the 10.0 s interval (move 22.3 s, exposure 1.0 s) +17:06:35.608 [INFO ] captured waypoint 6/15 +17:06:35.619 [INFO ] scan move 7/15: yaw 45 pitch 5 (deg) +17:06:36.330 [INFO ] saving: 3 pictures in the last minute, 664 ms each on average (longest 722 ms) +17:06:36.330 [MQTT] PUB GGS/FWT/dev/CamEvent { "fwt":"dev", "cam":"RGB", "hdg":3598, "pit":36, "time":1791471995513 } +17:06:45.624 [INFO ] captured waypoint 7/15 +17:06:45.634 [INFO ] scan move 9/15: yaw 135 pitch 5 (deg) +17:06:46.258 [MQTT] PUB GGS/FWT/dev/CamEvent { "fwt":"dev", "cam":"RGB", "hdg":451, "pit":33, "time":1791472005515 } +17:07:02.295 [INFO ] CPU: 55 °C (x86_pkg_temp), 2.8 GHz of 2.9 GHz, 13% busy +17:07:02.295 [INFO ] throttling: no, 0 in the last minute, 0 since start +17:07:02.295 [INFO ] scan pace: slowed - CPU is slowing itself down, one picture every 10.0 s +17:07:02.295 [INFO ] saving: 4 in the last minute, 657 ms each (longest 722 ms) +17:07:02.295 [INFO ] limits: slows at 85 °C, pauses at 92 °C for 20 s, resumes at 75 °C after at least 300 s +17:07:20.203 [WARN ] waypoint 9/15: 34.6 s since the last picture, over the 10.0 s interval (move 33.6 s, exposure 1.0 s) +17:07:20.215 [INFO ] captured waypoint 9/15 +17:07:20.226 [INFO ] scan move 10/15: yaw 180 pitch 5 (deg) +17:07:20.864 [MQTT] PUB GGS/FWT/dev/CamEvent { "fwt":"dev", "cam":"RGB", "hdg":1352, "pit":43, "time":1791472040191 } +17:07:30.234 [INFO ] captured waypoint 10/15 +17:07:30.244 [INFO ] scan move 11/15: yaw 0 pitch 15 (deg) +17:07:30.864 [MQTT] PUB GGS/FWT/dev/CamEvent { "fwt":"dev", "cam":"RGB", "hdg":1800, "pit":47, "time":1791472050194 } +17:07:31.859 [MQTT] PUB GGS/FWT/dev/Health {"fwt":"dev","state":"slowed","reason":"CPU is slowing itself down","guard":true,"cpu_c":55.0,"cpu_mhz":800,"cpu_busy_pct":29,"throttling":false,"throttle_events_last_min":0,"enclosure_c":21.0,"saved_last_min":4,"save_ms_mean":659,"save_ms_max":722,"held_s":0,"interval_s":10.0,"time":1791472051859} +17:07:53.593 [WARN ] waypoint 11/15: 23.4 s since the last picture, over the 10.0 s interval (move 22.3 s, exposure 1.0 s) +17:07:53.604 [INFO ] captured waypoint 11/15 +17:07:53.614 [INFO ] scan move 12/15: yaw 45 pitch 15 (deg) +17:07:54.293 [INFO ] saving: 3 pictures in the last minute, 656 ms each on average (longest 689 ms) +17:07:54.293 [MQTT] PUB GGS/FWT/dev/CamEvent { "fwt":"dev", "cam":"RGB", "hdg":3598, "pit":111, "time":1791472073533 } +17:08:03.621 [INFO ] captured waypoint 12/15 +17:08:03.631 [INFO ] scan move 14/15: yaw 135 pitch 15 (deg) +17:08:04.270 [MQTT] PUB GGS/FWT/dev/CamEvent { "fwt":"dev", "cam":"RGB", "hdg":451, "pit":110, "time":1791472083536 } +17:08:31.957 [INFO ] CPU at 54 °C: back to the normal picture pace +17:08:31.957 [MQTT] PUB GGS/FWT/dev/Health {"fwt":"dev","state":"normal","reason":"","guard":true,"cpu_c":54.0,"cpu_mhz":800,"cpu_busy_pct":15,"throttling":false,"throttle_events_last_min":0,"enclosure_c":21.0,"saved_last_min":2,"save_ms_mean":669,"save_ms_max":689,"held_s":0,"interval_s":5.0,"time":1791472111957} +17:08:32.296 [INFO ] CPU: 54 °C (x86_pkg_temp), 0.8 GHz of 2.9 GHz, 15% busy +17:08:32.296 [INFO ] throttling: no, 0 in the last minute, 0 since start +17:08:32.296 [INFO ] scan pace: normal, one picture every 5.0 s +17:08:32.296 [INFO ] saving: 2 in the last minute, 669 ms each (longest 689 ms) +17:08:32.296 [INFO ] limits: slows at 85 °C, pauses at 92 °C for 20 s, resumes at 75 °C after at least 300 s +17:08:37.309 [INFO ] Shutting down diff --git a/tools/camera-bench/results-2026-10-08-tower111-throttle/guard_run.sh b/tools/camera-bench/results-2026-10-08-tower111-throttle/guard_run.sh new file mode 100644 index 0000000..e28b37e --- /dev/null +++ b/tools/camera-bench/results-2026-10-08-tower111-throttle/guard_run.sh @@ -0,0 +1,57 @@ +#!/bin/sh +# The program itself on tower 111 - real camera, gimbal, the tower's own config - +# with the CPU throttled: 3 min normal, 6 min at 800 MHz + 2 busy cores, 3 min +# restored. ~13 min. nohup sh ~/thermal-test/guard_run.sh > ~/thermal-test/guard_run.out 2>&1 & +set -u +# What scripts/fgc sets up for the program: the camera SDK's transport layer, +# and the MQTT credentials (read, not sourced). +export GENICAM_GENTL64_PATH=/opt/VimbaX/cti +CRED=/etc/fire_gimbal_control/credentials.env +if [ -r "$CRED" ]; then + FGC_MQTT_USER=$(sed -n 's/^FGC_MQTT_USER=//p' "$CRED") + FGC_MQTT_PW=$(sed -n 's/^FGC_MQTT_PW=//p' "$CRED") + export FGC_MQTT_USER FGC_MQTT_PW +fi +T=~/thermal-test +SIM=$T/software/tools/thermal-sim +BIN=$T/software/build/fire_gimbal_control +LOG=$T/guard_run.log +FIFO=$T/cmd.fifo + +restore() { + sudo -n "$SIM/cap-cpu.sh" restore + pkill -f "stress-ng --cpu" 2>/dev/null +} +trap restore EXIT INT TERM + +rm -f "$FIFO"; mkfifo "$FIFO" +sleep 100000 > "$FIFO" & # holds the console's stdin open +holder=$! +"$BIN" -c ~/.config/fire_gimbal_control/config.ini --no-tui --trace mqtt \ + < "$FIFO" > "$LOG" 2>&1 & +pid=$! +say() { echo "$(date +%T) $*"; } +cmd() { echo "$1" > "$FIFO"; } + +say "started pid $pid; normal for 3 min" +sleep 180 +cmd thermal +say "cap 800 MHz + 2 busy cores for 6 min" +sudo -n "$SIM/cap-cpu.sh" 800 +stress-ng --cpu 2 --timeout 360s --quiet & +sleep 120; cmd thermal +sleep 120; cmd thermal +sleep 120; cmd thermal +wait $! 2>/dev/null +sudo -n "$SIM/cap-cpu.sh" restore +say "restored; 3 min more" +sleep 90; cmd thermal +sleep 90; cmd thermal +sleep 5 +say "stopping" +kill -TERM "$pid" +sleep 15 +kill "$holder" 2>/dev/null +sleep 3 +kill -0 "$pid" 2>/dev/null && { say "still running, killing"; kill -KILL "$pid"; } || say "program exited" +rm -f "$FIFO" diff --git a/tools/camera-bench/results-2026-10-08-tower111-throttle/idle_a.jsonl b/tools/camera-bench/results-2026-10-08-tower111-throttle/idle_a.jsonl new file mode 100644 index 0000000..058b05f --- /dev/null +++ b/tools/camera-bench/results-2026-10-08-tower111-throttle/idle_a.jsonl @@ -0,0 +1,61 @@ +{"t": 1, "temp_c": 55.0, "mhz": 757, "cpu_pct": 3.5, "watts": 3.22, "rss_mb": 43} +{"t": 2, "temp_c": 54.0, "mhz": 797, "cpu_pct": 3.8, "watts": 2.30, "rss_mb": 43} +{"t": 3, "temp_c": 54.0, "mhz": 763, "cpu_pct": 5.0, "watts": 2.96, "rss_mb": 43} +{"t": 4, "temp_c": 54.0, "mhz": 795, "cpu_pct": 4.3, "watts": 2.96, "rss_mb": 43} +{"t": 5, "temp_c": 54.0, "mhz": 794, "cpu_pct": 4.3, "watts": 2.92, "rss_mb": 43} +{"t": 6, "temp_c": 54.0, "mhz": 800, "cpu_pct": 4.0, "watts": 2.95, "rss_mb": 43} +{"t": 7, "temp_c": 54.0, "mhz": 768, "cpu_pct": 4.0, "watts": 3.05, "rss_mb": 43} +{"t": 8, "temp_c": 54.0, "mhz": 728, "cpu_pct": 3.8, "watts": 2.98, "rss_mb": 43} +{"t": 9, "temp_c": 54.0, "mhz": 796, "cpu_pct": 4.8, "watts": 2.22, "rss_mb": 43} +{"t": 10, "temp_c": 54.0, "mhz": 777, "cpu_pct": 3.5, "watts": 2.88, "rss_mb": 43} +{"t": 11, "temp_c": 54.0, "mhz": 762, "cpu_pct": 4.5, "watts": 2.94, "rss_mb": 43} +{"t": 12, "temp_c": 53.0, "mhz": 800, "cpu_pct": 3.5, "watts": 2.91, "rss_mb": 43} +{"t": 13, "temp_c": 53.0, "mhz": 798, "cpu_pct": 4.3, "watts": 3.03, "rss_mb": 43} +{"t": 14, "temp_c": 53.0, "mhz": 771, "cpu_pct": 3.0, "watts": 3.00, "rss_mb": 43} +{"t": 15, "temp_c": 54.0, "mhz": 747, "cpu_pct": 5.3, "watts": 3.01, "rss_mb": 43} +{"t": 16, "temp_c": 53.0, "mhz": 775, "cpu_pct": 5.3, "watts": 2.13, "rss_mb": 43} +{"t": 17, "temp_c": 54.0, "mhz": 911, "cpu_pct": 21.3, "watts": 4.54, "rss_mb": 43} +{"t": 18, "temp_c": 53.0, "mhz": 803, "cpu_pct": 3.3, "watts": 2.94, "rss_mb": 43} +{"t": 19, "temp_c": 53.0, "mhz": 775, "cpu_pct": 4.0, "watts": 3.01, "rss_mb": 43} +{"t": 20, "temp_c": 53.0, "mhz": 788, "cpu_pct": 3.3, "watts": 2.94, "rss_mb": 43} +{"t": 21, "temp_c": 53.0, "mhz": 800, "cpu_pct": 6.3, "watts": 3.07, "rss_mb": 43} +{"t": 22, "temp_c": 54.0, "mhz": 773, "cpu_pct": 5.0, "watts": 3.04, "rss_mb": 43} +{"t": 23, "temp_c": 53.0, "mhz": 794, "cpu_pct": 4.0, "watts": 2.14, "rss_mb": 43} +{"t": 24, "temp_c": 53.0, "mhz": 830, "cpu_pct": 4.1, "watts": 2.94, "rss_mb": 43} +{"t": 25, "temp_c": 52.0, "mhz": 783, "cpu_pct": 3.5, "watts": 3.01, "rss_mb": 43} +{"t": 26, "temp_c": 52.0, "mhz": 785, "cpu_pct": 4.0, "watts": 2.91, "rss_mb": 43} +{"t": 27, "temp_c": 52.0, "mhz": 1565, "cpu_pct": 6.3, "watts": 2.99, "rss_mb": 43} +{"t": 28, "temp_c": 52.0, "mhz": 788, "cpu_pct": 3.8, "watts": 2.97, "rss_mb": 43} +{"t": 29, "temp_c": 53.0, "mhz": 767, "cpu_pct": 3.8, "watts": 2.94, "rss_mb": 43} +{"t": 30, "temp_c": 53.0, "mhz": 800, "cpu_pct": 3.8, "watts": 2.12, "rss_mb": 43} +{"t": 31, "temp_c": 52.0, "mhz": 787, "cpu_pct": 3.5, "watts": 2.97, "rss_mb": 43} +{"t": 32, "temp_c": 52.0, "mhz": 794, "cpu_pct": 4.5, "watts": 2.95, "rss_mb": 43} +{"t": 33, "temp_c": 53.0, "mhz": 789, "cpu_pct": 5.0, "watts": 2.98, "rss_mb": 43} +{"t": 34, "temp_c": 53.0, "mhz": 793, "cpu_pct": 3.8, "watts": 2.98, "rss_mb": 43} +{"t": 35, "temp_c": 54.0, "mhz": 800, "cpu_pct": 3.8, "watts": 2.81, "rss_mb": 43} +{"t": 36, "temp_c": 53.0, "mhz": 1300, "cpu_pct": 4.0, "watts": 2.93, "rss_mb": 43} +{"t": 37, "temp_c": 53.0, "mhz": 788, "cpu_pct": 3.3, "watts": 2.29, "rss_mb": 43} +{"t": 38, "temp_c": 53.0, "mhz": 776, "cpu_pct": 3.8, "watts": 2.92, "rss_mb": 43} +{"t": 39, "temp_c": 53.0, "mhz": 1483, "cpu_pct": 5.6, "watts": 2.94, "rss_mb": 43} +{"t": 40, "temp_c": 53.0, "mhz": 793, "cpu_pct": 4.0, "watts": 3.12, "rss_mb": 43} +{"t": 41, "temp_c": 52.0, "mhz": 802, "cpu_pct": 3.8, "watts": 2.78, "rss_mb": 43} +{"t": 42, "temp_c": 53.0, "mhz": 1379, "cpu_pct": 4.5, "watts": 2.97, "rss_mb": 43} +{"t": 43, "temp_c": 51.0, "mhz": 760, "cpu_pct": 2.8, "watts": 2.89, "rss_mb": 43} +{"t": 44, "temp_c": 51.0, "mhz": 791, "cpu_pct": 4.5, "watts": 2.28, "rss_mb": 43} +{"t": 45, "temp_c": 52.0, "mhz": 795, "cpu_pct": 5.5, "watts": 2.94, "rss_mb": 43} +{"t": 46, "temp_c": 52.0, "mhz": 773, "cpu_pct": 4.0, "watts": 2.89, "rss_mb": 43} +{"t": 47, "temp_c": 52.0, "mhz": 1401, "cpu_pct": 6.8, "watts": 2.91, "rss_mb": 43} +{"t": 48, "temp_c": 53.0, "mhz": 1531, "cpu_pct": 22.9, "watts": 4.61, "rss_mb": 43} +{"t": 49, "temp_c": 52.0, "mhz": 777, "cpu_pct": 3.0, "watts": 2.91, "rss_mb": 43} +{"t": 50, "temp_c": 51.0, "mhz": 797, "cpu_pct": 4.3, "watts": 2.95, "rss_mb": 43} +{"t": 51, "temp_c": 52.0, "mhz": 786, "cpu_pct": 4.8, "watts": 2.28, "rss_mb": 43} +{"t": 52, "temp_c": 51.0, "mhz": 804, "cpu_pct": 3.5, "watts": 2.83, "rss_mb": 43} +{"t": 53, "temp_c": 52.0, "mhz": 928, "cpu_pct": 4.3, "watts": 2.92, "rss_mb": 43} +{"t": 54, "temp_c": 52.0, "mhz": 2590, "cpu_pct": 4.5, "watts": 2.96, "rss_mb": 43} +{"t": 55, "temp_c": 51.0, "mhz": 791, "cpu_pct": 3.3, "watts": 2.98, "rss_mb": 43} +{"t": 56, "temp_c": 52.0, "mhz": 759, "cpu_pct": 4.5, "watts": 2.94, "rss_mb": 43} +{"t": 57, "temp_c": 52.0, "mhz": 801, "cpu_pct": 6.0, "watts": 3.13, "rss_mb": 43} +{"t": 58, "temp_c": 51.0, "mhz": 800, "cpu_pct": 3.8, "watts": 2.07, "rss_mb": 43} +{"t": 59, "temp_c": 52.0, "mhz": 797, "cpu_pct": 4.5, "watts": 2.92, "rss_mb": 43} +{"t": 60, "temp_c": 52.0, "mhz": 2423, "cpu_pct": 4.5, "watts": 2.97, "rss_mb": 43} +{"summary": "idle", "secs": 60, "temp_c_mean": 52.8, "temp_c_max": 55.0, "mhz_mean": 913, "cpu_pct_mean": 4.8, "watts_mean": 2.90} diff --git a/tools/camera-bench/results-2026-10-08-tower111-throttle/idle_after1200.jsonl b/tools/camera-bench/results-2026-10-08-tower111-throttle/idle_after1200.jsonl new file mode 100644 index 0000000..94e0a38 --- /dev/null +++ b/tools/camera-bench/results-2026-10-08-tower111-throttle/idle_after1200.jsonl @@ -0,0 +1,61 @@ +{"t": 1, "temp_c": 53.0, "mhz": 800, "cpu_pct": 5.5, "watts": 3.47, "rss_mb": 43} +{"t": 2, "temp_c": 52.0, "mhz": 787, "cpu_pct": 4.8, "watts": 3.01, "rss_mb": 43} +{"t": 3, "temp_c": 52.0, "mhz": 800, "cpu_pct": 4.0, "watts": 2.97, "rss_mb": 43} +{"t": 4, "temp_c": 54.0, "mhz": 1188, "cpu_pct": 4.5, "watts": 2.85, "rss_mb": 43} +{"t": 5, "temp_c": 51.0, "mhz": 878, "cpu_pct": 2.5, "watts": 2.87, "rss_mb": 43} +{"t": 6, "temp_c": 52.0, "mhz": 800, "cpu_pct": 4.5, "watts": 2.34, "rss_mb": 43} +{"t": 7, "temp_c": 51.0, "mhz": 791, "cpu_pct": 5.0, "watts": 3.03, "rss_mb": 43} +{"t": 8, "temp_c": 52.0, "mhz": 791, "cpu_pct": 4.3, "watts": 2.95, "rss_mb": 43} +{"t": 9, "temp_c": 52.0, "mhz": 835, "cpu_pct": 3.5, "watts": 2.76, "rss_mb": 43} +{"t": 10, "temp_c": 52.0, "mhz": 2785, "cpu_pct": 5.0, "watts": 2.96, "rss_mb": 43} +{"t": 11, "temp_c": 52.0, "mhz": 772, "cpu_pct": 2.3, "watts": 2.97, "rss_mb": 43} +{"t": 12, "temp_c": 52.0, "mhz": 757, "cpu_pct": 4.5, "watts": 2.93, "rss_mb": 43} +{"t": 13, "temp_c": 52.0, "mhz": 768, "cpu_pct": 5.8, "watts": 2.47, "rss_mb": 43} +{"t": 14, "temp_c": 52.0, "mhz": 797, "cpu_pct": 3.8, "watts": 2.86, "rss_mb": 43} +{"t": 15, "temp_c": 52.0, "mhz": 795, "cpu_pct": 3.0, "watts": 2.83, "rss_mb": 43} +{"t": 16, "temp_c": 52.0, "mhz": 1254, "cpu_pct": 5.5, "watts": 3.05, "rss_mb": 43} +{"t": 17, "temp_c": 51.0, "mhz": 794, "cpu_pct": 2.3, "watts": 2.94, "rss_mb": 43} +{"t": 18, "temp_c": 51.0, "mhz": 796, "cpu_pct": 4.3, "watts": 2.96, "rss_mb": 43} +{"t": 19, "temp_c": 52.0, "mhz": 761, "cpu_pct": 5.5, "watts": 3.04, "rss_mb": 43} +{"t": 20, "temp_c": 51.0, "mhz": 798, "cpu_pct": 3.8, "watts": 2.13, "rss_mb": 43} +{"t": 21, "temp_c": 52.0, "mhz": 800, "cpu_pct": 4.3, "watts": 2.92, "rss_mb": 43} +{"t": 22, "temp_c": 53.0, "mhz": 876, "cpu_pct": 4.5, "watts": 3.01, "rss_mb": 43} +{"t": 23, "temp_c": 54.0, "mhz": 900, "cpu_pct": 18.5, "watts": 3.94, "rss_mb": 43} +{"t": 24, "temp_c": 51.0, "mhz": 798, "cpu_pct": 7.3, "watts": 3.55, "rss_mb": 43} +{"t": 25, "temp_c": 51.0, "mhz": 800, "cpu_pct": 4.8, "watts": 2.98, "rss_mb": 43} +{"t": 26, "temp_c": 52.0, "mhz": 787, "cpu_pct": 3.5, "watts": 2.98, "rss_mb": 43} +{"t": 27, "temp_c": 51.0, "mhz": 794, "cpu_pct": 4.0, "watts": 2.12, "rss_mb": 43} +{"t": 28, "temp_c": 52.0, "mhz": 1107, "cpu_pct": 6.0, "watts": 3.08, "rss_mb": 43} +{"t": 29, "temp_c": 51.0, "mhz": 773, "cpu_pct": 2.8, "watts": 2.94, "rss_mb": 43} +{"t": 30, "temp_c": 51.0, "mhz": 800, "cpu_pct": 3.5, "watts": 2.95, "rss_mb": 43} +{"t": 31, "temp_c": 51.0, "mhz": 798, "cpu_pct": 5.0, "watts": 2.98, "rss_mb": 43} +{"t": 32, "temp_c": 51.0, "mhz": 790, "cpu_pct": 4.0, "watts": 2.93, "rss_mb": 43} +{"t": 33, "temp_c": 52.0, "mhz": 765, "cpu_pct": 4.0, "watts": 2.91, "rss_mb": 43} +{"t": 34, "temp_c": 52.0, "mhz": 800, "cpu_pct": 5.3, "watts": 2.19, "rss_mb": 43} +{"t": 35, "temp_c": 52.0, "mhz": 771, "cpu_pct": 3.0, "watts": 2.94, "rss_mb": 43} +{"t": 36, "temp_c": 51.0, "mhz": 800, "cpu_pct": 3.8, "watts": 2.91, "rss_mb": 43} +{"t": 37, "temp_c": 51.0, "mhz": 794, "cpu_pct": 5.0, "watts": 2.92, "rss_mb": 43} +{"t": 38, "temp_c": 51.0, "mhz": 754, "cpu_pct": 4.3, "watts": 2.94, "rss_mb": 43} +{"t": 39, "temp_c": 51.0, "mhz": 800, "cpu_pct": 4.3, "watts": 2.90, "rss_mb": 43} +{"t": 40, "temp_c": 52.0, "mhz": 1385, "cpu_pct": 4.8, "watts": 2.95, "rss_mb": 43} +{"t": 41, "temp_c": 51.0, "mhz": 778, "cpu_pct": 2.8, "watts": 2.07, "rss_mb": 43} +{"t": 42, "temp_c": 51.0, "mhz": 752, "cpu_pct": 4.3, "watts": 2.90, "rss_mb": 43} +{"t": 43, "temp_c": 51.0, "mhz": 787, "cpu_pct": 6.3, "watts": 3.10, "rss_mb": 43} +{"t": 44, "temp_c": 51.0, "mhz": 787, "cpu_pct": 4.5, "watts": 2.93, "rss_mb": 43} +{"t": 45, "temp_c": 52.0, "mhz": 765, "cpu_pct": 3.8, "watts": 2.91, "rss_mb": 43} +{"t": 46, "temp_c": 52.0, "mhz": 2499, "cpu_pct": 5.6, "watts": 2.86, "rss_mb": 43} +{"t": 47, "temp_c": 51.0, "mhz": 2820, "cpu_pct": 3.0, "watts": 2.90, "rss_mb": 43} +{"t": 48, "temp_c": 51.0, "mhz": 791, "cpu_pct": 3.8, "watts": 2.30, "rss_mb": 43} +{"t": 49, "temp_c": 51.0, "mhz": 796, "cpu_pct": 5.3, "watts": 2.96, "rss_mb": 43} +{"t": 50, "temp_c": 51.0, "mhz": 786, "cpu_pct": 3.8, "watts": 2.91, "rss_mb": 43} +{"t": 51, "temp_c": 52.0, "mhz": 801, "cpu_pct": 3.3, "watts": 2.75, "rss_mb": 43} +{"t": 52, "temp_c": 52.0, "mhz": 2800, "cpu_pct": 4.5, "watts": 2.99, "rss_mb": 43} +{"t": 53, "temp_c": 51.0, "mhz": 2414, "cpu_pct": 4.0, "watts": 2.91, "rss_mb": 43} +{"t": 54, "temp_c": 56.0, "mhz": 2800, "cpu_pct": 21.1, "watts": 4.36, "rss_mb": 43} +{"t": 55, "temp_c": 53.0, "mhz": 795, "cpu_pct": 6.5, "watts": 2.44, "rss_mb": 43} +{"t": 56, "temp_c": 51.0, "mhz": 794, "cpu_pct": 4.5, "watts": 2.87, "rss_mb": 43} +{"t": 57, "temp_c": 51.0, "mhz": 800, "cpu_pct": 3.5, "watts": 2.91, "rss_mb": 43} +{"t": 58, "temp_c": 51.0, "mhz": 800, "cpu_pct": 5.3, "watts": 3.09, "rss_mb": 43} +{"t": 59, "temp_c": 51.0, "mhz": 657, "cpu_pct": 3.8, "watts": 2.95, "rss_mb": 43} +{"t": 60, "temp_c": 51.0, "mhz": 767, "cpu_pct": 4.0, "watts": 2.92, "rss_mb": 43} +{"summary": "idle", "secs": 60, "temp_c_mean": 51.7, "temp_c_max": 56.0, "mhz_mean": 1010, "cpu_pct_mean": 4.9, "watts_mean": 2.91} diff --git a/tools/camera-bench/results-2026-10-08-tower111-throttle/idle_after1600.jsonl b/tools/camera-bench/results-2026-10-08-tower111-throttle/idle_after1600.jsonl new file mode 100644 index 0000000..75f7e34 --- /dev/null +++ b/tools/camera-bench/results-2026-10-08-tower111-throttle/idle_after1600.jsonl @@ -0,0 +1,61 @@ +{"t": 1, "temp_c": 54.0, "mhz": 891, "cpu_pct": 3.0, "watts": 3.18, "rss_mb": 43} +{"t": 2, "temp_c": 53.0, "mhz": 800, "cpu_pct": 4.3, "watts": 2.30, "rss_mb": 43} +{"t": 3, "temp_c": 52.0, "mhz": 792, "cpu_pct": 6.3, "watts": 3.11, "rss_mb": 43} +{"t": 4, "temp_c": 54.0, "mhz": 800, "cpu_pct": 3.5, "watts": 2.85, "rss_mb": 43} +{"t": 5, "temp_c": 53.0, "mhz": 813, "cpu_pct": 3.8, "watts": 2.86, "rss_mb": 43} +{"t": 6, "temp_c": 54.0, "mhz": 2506, "cpu_pct": 4.5, "watts": 3.04, "rss_mb": 43} +{"t": 7, "temp_c": 53.0, "mhz": 795, "cpu_pct": 2.8, "watts": 2.94, "rss_mb": 43} +{"t": 8, "temp_c": 53.0, "mhz": 784, "cpu_pct": 4.5, "watts": 2.95, "rss_mb": 43} +{"t": 9, "temp_c": 53.0, "mhz": 784, "cpu_pct": 4.5, "watts": 2.33, "rss_mb": 43} +{"t": 10, "temp_c": 52.0, "mhz": 797, "cpu_pct": 3.0, "watts": 2.74, "rss_mb": 43} +{"t": 11, "temp_c": 53.0, "mhz": 790, "cpu_pct": 5.0, "watts": 2.93, "rss_mb": 43} +{"t": 12, "temp_c": 54.0, "mhz": 2840, "cpu_pct": 4.8, "watts": 3.02, "rss_mb": 43} +{"t": 13, "temp_c": 52.0, "mhz": 797, "cpu_pct": 2.8, "watts": 2.96, "rss_mb": 43} +{"t": 14, "temp_c": 53.0, "mhz": 791, "cpu_pct": 4.0, "watts": 2.94, "rss_mb": 43} +{"t": 15, "temp_c": 53.0, "mhz": 718, "cpu_pct": 4.5, "watts": 2.99, "rss_mb": 43} +{"t": 16, "temp_c": 52.0, "mhz": 756, "cpu_pct": 3.3, "watts": 2.08, "rss_mb": 43} +{"t": 17, "temp_c": 53.0, "mhz": 796, "cpu_pct": 4.3, "watts": 2.92, "rss_mb": 43} +{"t": 18, "temp_c": 53.0, "mhz": 1035, "cpu_pct": 4.8, "watts": 2.99, "rss_mb": 43} +{"t": 19, "temp_c": 52.0, "mhz": 800, "cpu_pct": 3.3, "watts": 2.93, "rss_mb": 43} +{"t": 20, "temp_c": 62.0, "mhz": 2800, "cpu_pct": 10.2, "watts": 3.15, "rss_mb": 43} +{"t": 21, "temp_c": 54.0, "mhz": 772, "cpu_pct": 18.3, "watts": 4.66, "rss_mb": 43} +{"t": 22, "temp_c": 53.0, "mhz": 762, "cpu_pct": 3.5, "watts": 2.93, "rss_mb": 43} +{"t": 23, "temp_c": 52.0, "mhz": 781, "cpu_pct": 4.8, "watts": 2.16, "rss_mb": 43} +{"t": 24, "temp_c": 53.0, "mhz": 1121, "cpu_pct": 5.5, "watts": 3.00, "rss_mb": 43} +{"t": 25, "temp_c": 51.0, "mhz": 711, "cpu_pct": 3.0, "watts": 2.91, "rss_mb": 43} +{"t": 26, "temp_c": 52.0, "mhz": 748, "cpu_pct": 3.5, "watts": 2.94, "rss_mb": 43} +{"t": 27, "temp_c": 52.0, "mhz": 773, "cpu_pct": 4.8, "watts": 2.99, "rss_mb": 43} +{"t": 28, "temp_c": 51.0, "mhz": 776, "cpu_pct": 4.5, "watts": 2.93, "rss_mb": 43} +{"t": 29, "temp_c": 52.0, "mhz": 731, "cpu_pct": 4.5, "watts": 2.98, "rss_mb": 43} +{"t": 30, "temp_c": 52.0, "mhz": 785, "cpu_pct": 4.0, "watts": 2.11, "rss_mb": 43} +{"t": 31, "temp_c": 52.0, "mhz": 2803, "cpu_pct": 3.0, "watts": 2.97, "rss_mb": 43} +{"t": 32, "temp_c": 51.0, "mhz": 798, "cpu_pct": 3.0, "watts": 2.97, "rss_mb": 43} +{"t": 33, "temp_c": 52.0, "mhz": 768, "cpu_pct": 4.8, "watts": 2.98, "rss_mb": 43} +{"t": 34, "temp_c": 52.0, "mhz": 776, "cpu_pct": 4.5, "watts": 2.97, "rss_mb": 43} +{"t": 35, "temp_c": 51.0, "mhz": 795, "cpu_pct": 4.3, "watts": 2.93, "rss_mb": 43} +{"t": 36, "temp_c": 52.0, "mhz": 800, "cpu_pct": 4.1, "watts": 2.99, "rss_mb": 43} +{"t": 37, "temp_c": 52.0, "mhz": 2808, "cpu_pct": 3.5, "watts": 2.12, "rss_mb": 43} +{"t": 38, "temp_c": 51.0, "mhz": 796, "cpu_pct": 2.8, "watts": 2.95, "rss_mb": 43} +{"t": 39, "temp_c": 51.0, "mhz": 766, "cpu_pct": 6.0, "watts": 3.06, "rss_mb": 43} +{"t": 40, "temp_c": 51.0, "mhz": 776, "cpu_pct": 4.3, "watts": 2.90, "rss_mb": 43} +{"t": 41, "temp_c": 51.0, "mhz": 751, "cpu_pct": 4.3, "watts": 2.97, "rss_mb": 43} +{"t": 42, "temp_c": 51.0, "mhz": 796, "cpu_pct": 4.3, "watts": 2.94, "rss_mb": 43} +{"t": 43, "temp_c": 53.0, "mhz": 2800, "cpu_pct": 4.3, "watts": 2.10, "rss_mb": 43} +{"t": 44, "temp_c": 51.0, "mhz": 793, "cpu_pct": 1.8, "watts": 2.89, "rss_mb": 43} +{"t": 45, "temp_c": 51.0, "mhz": 782, "cpu_pct": 5.8, "watts": 2.97, "rss_mb": 43} +{"t": 46, "temp_c": 51.0, "mhz": 751, "cpu_pct": 4.3, "watts": 2.93, "rss_mb": 43} +{"t": 47, "temp_c": 52.0, "mhz": 750, "cpu_pct": 3.8, "watts": 2.93, "rss_mb": 43} +{"t": 48, "temp_c": 51.0, "mhz": 796, "cpu_pct": 4.8, "watts": 2.99, "rss_mb": 43} +{"t": 49, "temp_c": 52.0, "mhz": 1222, "cpu_pct": 3.3, "watts": 2.82, "rss_mb": 43} +{"t": 50, "temp_c": 51.0, "mhz": 791, "cpu_pct": 2.3, "watts": 2.14, "rss_mb": 43} +{"t": 51, "temp_c": 54.0, "mhz": 820, "cpu_pct": 14.6, "watts": 3.35, "rss_mb": 43} +{"t": 52, "temp_c": 51.0, "mhz": 770, "cpu_pct": 12.2, "watts": 4.17, "rss_mb": 43} +{"t": 53, "temp_c": 51.0, "mhz": 765, "cpu_pct": 4.3, "watts": 2.95, "rss_mb": 43} +{"t": 54, "temp_c": 52.0, "mhz": 795, "cpu_pct": 4.0, "watts": 2.94, "rss_mb": 43} +{"t": 55, "temp_c": 52.0, "mhz": 834, "cpu_pct": 3.3, "watts": 2.81, "rss_mb": 43} +{"t": 56, "temp_c": 51.0, "mhz": 800, "cpu_pct": 3.0, "watts": 2.89, "rss_mb": 43} +{"t": 57, "temp_c": 51.0, "mhz": 800, "cpu_pct": 6.8, "watts": 3.13, "rss_mb": 43} +{"t": 58, "temp_c": 51.0, "mhz": 781, "cpu_pct": 3.3, "watts": 2.32, "rss_mb": 43} +{"t": 59, "temp_c": 51.0, "mhz": 790, "cpu_pct": 4.5, "watts": 2.95, "rss_mb": 43} +{"t": 60, "temp_c": 52.0, "mhz": 797, "cpu_pct": 4.0, "watts": 2.73, "rss_mb": 43} +{"summary": "idle", "secs": 60, "temp_c_mean": 52.2, "temp_c_max": 62.0, "mhz_mean": 999, "cpu_pct_mean": 4.7, "watts_mean": 2.89} diff --git a/tools/camera-bench/results-2026-10-08-tower111-throttle/idle_after800.jsonl b/tools/camera-bench/results-2026-10-08-tower111-throttle/idle_after800.jsonl new file mode 100644 index 0000000..a6b645d --- /dev/null +++ b/tools/camera-bench/results-2026-10-08-tower111-throttle/idle_after800.jsonl @@ -0,0 +1,61 @@ +{"t": 1, "temp_c": 52.0, "mhz": 779, "cpu_pct": 3.0, "watts": 3.15, "rss_mb": 42} +{"t": 2, "temp_c": 52.0, "mhz": 1084, "cpu_pct": 4.8, "watts": 3.04, "rss_mb": 43} +{"t": 3, "temp_c": 52.0, "mhz": 798, "cpu_pct": 3.5, "watts": 2.33, "rss_mb": 43} +{"t": 4, "temp_c": 51.0, "mhz": 790, "cpu_pct": 3.8, "watts": 2.98, "rss_mb": 43} +{"t": 5, "temp_c": 52.0, "mhz": 772, "cpu_pct": 5.3, "watts": 2.94, "rss_mb": 43} +{"t": 6, "temp_c": 52.0, "mhz": 801, "cpu_pct": 3.8, "watts": 2.87, "rss_mb": 43} +{"t": 7, "temp_c": 51.0, "mhz": 800, "cpu_pct": 3.0, "watts": 2.95, "rss_mb": 43} +{"t": 8, "temp_c": 52.0, "mhz": 799, "cpu_pct": 5.8, "watts": 3.13, "rss_mb": 43} +{"t": 9, "temp_c": 51.0, "mhz": 794, "cpu_pct": 4.0, "watts": 2.32, "rss_mb": 43} +{"t": 10, "temp_c": 51.0, "mhz": 800, "cpu_pct": 3.5, "watts": 2.91, "rss_mb": 43} +{"t": 11, "temp_c": 51.0, "mhz": 777, "cpu_pct": 5.5, "watts": 2.90, "rss_mb": 43} +{"t": 12, "temp_c": 51.0, "mhz": 862, "cpu_pct": 4.0, "watts": 2.90, "rss_mb": 43} +{"t": 13, "temp_c": 52.0, "mhz": 1300, "cpu_pct": 3.5, "watts": 2.96, "rss_mb": 43} +{"t": 14, "temp_c": 51.0, "mhz": 778, "cpu_pct": 4.8, "watts": 3.06, "rss_mb": 43} +{"t": 15, "temp_c": 51.0, "mhz": 775, "cpu_pct": 3.8, "watts": 2.97, "rss_mb": 43} +{"t": 16, "temp_c": 51.0, "mhz": 758, "cpu_pct": 3.8, "watts": 2.31, "rss_mb": 43} +{"t": 17, "temp_c": 51.0, "mhz": 795, "cpu_pct": 4.0, "watts": 2.86, "rss_mb": 43} +{"t": 18, "temp_c": 51.0, "mhz": 800, "cpu_pct": 4.0, "watts": 2.91, "rss_mb": 43} +{"t": 19, "temp_c": 51.0, "mhz": 1273, "cpu_pct": 3.8, "watts": 3.00, "rss_mb": 43} +{"t": 20, "temp_c": 52.0, "mhz": 771, "cpu_pct": 4.5, "watts": 3.02, "rss_mb": 43} +{"t": 21, "temp_c": 51.0, "mhz": 797, "cpu_pct": 4.3, "watts": 2.98, "rss_mb": 43} +{"t": 22, "temp_c": 52.0, "mhz": 787, "cpu_pct": 3.8, "watts": 2.89, "rss_mb": 43} +{"t": 23, "temp_c": 51.0, "mhz": 800, "cpu_pct": 5.1, "watts": 2.19, "rss_mb": 43} +{"t": 24, "temp_c": 51.0, "mhz": 856, "cpu_pct": 5.3, "watts": 2.93, "rss_mb": 43} +{"t": 25, "temp_c": 52.0, "mhz": 799, "cpu_pct": 20.9, "watts": 4.73, "rss_mb": 43} +{"t": 26, "temp_c": 51.0, "mhz": 780, "cpu_pct": 5.6, "watts": 3.11, "rss_mb": 43} +{"t": 27, "temp_c": 51.0, "mhz": 800, "cpu_pct": 4.0, "watts": 2.87, "rss_mb": 43} +{"t": 28, "temp_c": 51.0, "mhz": 757, "cpu_pct": 4.0, "watts": 2.95, "rss_mb": 43} +{"t": 29, "temp_c": 51.0, "mhz": 800, "cpu_pct": 5.3, "watts": 2.22, "rss_mb": 43} +{"t": 30, "temp_c": 52.0, "mhz": 2051, "cpu_pct": 4.5, "watts": 2.98, "rss_mb": 43} +{"t": 31, "temp_c": 51.0, "mhz": 796, "cpu_pct": 2.5, "watts": 2.98, "rss_mb": 43} +{"t": 32, "temp_c": 51.0, "mhz": 782, "cpu_pct": 3.8, "watts": 2.94, "rss_mb": 43} +{"t": 33, "temp_c": 51.0, "mhz": 796, "cpu_pct": 3.3, "watts": 2.90, "rss_mb": 43} +{"t": 34, "temp_c": 51.0, "mhz": 790, "cpu_pct": 4.5, "watts": 2.92, "rss_mb": 43} +{"t": 35, "temp_c": 51.0, "mhz": 763, "cpu_pct": 6.3, "watts": 3.06, "rss_mb": 43} +{"t": 36, "temp_c": 52.0, "mhz": 866, "cpu_pct": 4.0, "watts": 2.14, "rss_mb": 43} +{"t": 37, "temp_c": 51.0, "mhz": 797, "cpu_pct": 2.0, "watts": 2.94, "rss_mb": 43} +{"t": 38, "temp_c": 51.0, "mhz": 787, "cpu_pct": 3.8, "watts": 2.98, "rss_mb": 43} +{"t": 39, "temp_c": 51.0, "mhz": 800, "cpu_pct": 4.8, "watts": 2.96, "rss_mb": 43} +{"t": 40, "temp_c": 51.0, "mhz": 735, "cpu_pct": 4.5, "watts": 2.93, "rss_mb": 43} +{"t": 41, "temp_c": 51.0, "mhz": 792, "cpu_pct": 6.5, "watts": 3.10, "rss_mb": 43} +{"t": 42, "temp_c": 52.0, "mhz": 1260, "cpu_pct": 3.3, "watts": 2.89, "rss_mb": 43} +{"t": 43, "temp_c": 51.0, "mhz": 800, "cpu_pct": 2.0, "watts": 2.13, "rss_mb": 43} +{"t": 44, "temp_c": 51.0, "mhz": 789, "cpu_pct": 5.0, "watts": 3.00, "rss_mb": 43} +{"t": 45, "temp_c": 51.0, "mhz": 796, "cpu_pct": 4.3, "watts": 2.93, "rss_mb": 43} +{"t": 46, "temp_c": 51.0, "mhz": 783, "cpu_pct": 4.3, "watts": 2.97, "rss_mb": 43} +{"t": 47, "temp_c": 51.0, "mhz": 777, "cpu_pct": 5.0, "watts": 2.98, "rss_mb": 43} +{"t": 48, "temp_c": 52.0, "mhz": 861, "cpu_pct": 3.3, "watts": 2.75, "rss_mb": 43} +{"t": 49, "temp_c": 51.0, "mhz": 779, "cpu_pct": 3.0, "watts": 2.90, "rss_mb": 43} +{"t": 50, "temp_c": 51.0, "mhz": 794, "cpu_pct": 4.3, "watts": 2.28, "rss_mb": 43} +{"t": 51, "temp_c": 51.0, "mhz": 778, "cpu_pct": 4.0, "watts": 2.95, "rss_mb": 43} +{"t": 52, "temp_c": 51.0, "mhz": 774, "cpu_pct": 4.8, "watts": 2.95, "rss_mb": 43} +{"t": 53, "temp_c": 51.0, "mhz": 790, "cpu_pct": 4.5, "watts": 2.84, "rss_mb": 43} +{"t": 54, "temp_c": 52.0, "mhz": 1624, "cpu_pct": 4.5, "watts": 2.92, "rss_mb": 43} +{"t": 55, "temp_c": 51.0, "mhz": 800, "cpu_pct": 5.0, "watts": 3.13, "rss_mb": 43} +{"t": 56, "temp_c": 51.0, "mhz": 764, "cpu_pct": 24.8, "watts": 4.61, "rss_mb": 43} +{"t": 57, "temp_c": 51.0, "mhz": 800, "cpu_pct": 3.8, "watts": 2.91, "rss_mb": 43} +{"t": 58, "temp_c": 51.0, "mhz": 797, "cpu_pct": 4.3, "watts": 2.12, "rss_mb": 43} +{"t": 59, "temp_c": 51.0, "mhz": 1127, "cpu_pct": 5.6, "watts": 3.01, "rss_mb": 43} +{"t": 60, "temp_c": 51.0, "mhz": 786, "cpu_pct": 3.3, "watts": 2.94, "rss_mb": 43} +{"summary": "idle", "secs": 60, "temp_c_mean": 51.2, "temp_c_max": 52.0, "mhz_mean": 862, "cpu_pct_mean": 4.8, "watts_mean": 2.91} diff --git a/tools/camera-bench/results-2026-10-08-tower111-throttle/idle_afterload.jsonl b/tools/camera-bench/results-2026-10-08-tower111-throttle/idle_afterload.jsonl new file mode 100644 index 0000000..3ea3b8e --- /dev/null +++ b/tools/camera-bench/results-2026-10-08-tower111-throttle/idle_afterload.jsonl @@ -0,0 +1,61 @@ +{"t": 1, "temp_c": 49.0, "mhz": 800, "cpu_pct": 7.6, "watts": -1.00, "rss_mb": 43} +{"t": 2, "temp_c": 48.0, "mhz": 800, "cpu_pct": 2.5, "watts": -1.00, "rss_mb": 43} +{"t": 3, "temp_c": 49.0, "mhz": 780, "cpu_pct": 2.5, "watts": -1.00, "rss_mb": 43} +{"t": 4, "temp_c": 49.0, "mhz": 2456, "cpu_pct": 4.5, "watts": -1.00, "rss_mb": 43} +{"t": 5, "temp_c": 48.0, "mhz": 794, "cpu_pct": 3.8, "watts": -1.00, "rss_mb": 43} +{"t": 6, "temp_c": 50.0, "mhz": 781, "cpu_pct": 22.0, "watts": -1.00, "rss_mb": 43} +{"t": 7, "temp_c": 48.0, "mhz": 786, "cpu_pct": 3.5, "watts": -1.00, "rss_mb": 43} +{"t": 8, "temp_c": 48.0, "mhz": 794, "cpu_pct": 2.8, "watts": -1.00, "rss_mb": 43} +{"t": 9, "temp_c": 48.0, "mhz": 793, "cpu_pct": 3.0, "watts": -1.00, "rss_mb": 43} +{"t": 10, "temp_c": 49.0, "mhz": 1249, "cpu_pct": 4.0, "watts": -1.00, "rss_mb": 43} +{"t": 11, "temp_c": 48.0, "mhz": 761, "cpu_pct": 1.8, "watts": -1.00, "rss_mb": 43} +{"t": 12, "temp_c": 48.0, "mhz": 798, "cpu_pct": 2.8, "watts": -1.00, "rss_mb": 43} +{"t": 13, "temp_c": 47.0, "mhz": 794, "cpu_pct": 3.5, "watts": -1.00, "rss_mb": 43} +{"t": 14, "temp_c": 48.0, "mhz": 767, "cpu_pct": 2.5, "watts": -1.00, "rss_mb": 43} +{"t": 15, "temp_c": 48.0, "mhz": 690, "cpu_pct": 2.8, "watts": -1.00, "rss_mb": 43} +{"t": 16, "temp_c": 55.0, "mhz": 2800, "cpu_pct": 12.8, "watts": -1.00, "rss_mb": 43} +{"t": 17, "temp_c": 52.0, "mhz": 800, "cpu_pct": 27.6, "watts": -1.00, "rss_mb": 43} +{"t": 18, "temp_c": 48.0, "mhz": 794, "cpu_pct": 9.4, "watts": -1.00, "rss_mb": 43} +{"t": 19, "temp_c": 48.0, "mhz": 769, "cpu_pct": 3.5, "watts": -1.00, "rss_mb": 43} +{"t": 20, "temp_c": 48.0, "mhz": 800, "cpu_pct": 2.3, "watts": -1.00, "rss_mb": 43} +{"t": 21, "temp_c": 48.0, "mhz": 812, "cpu_pct": 3.0, "watts": -1.00, "rss_mb": 43} +{"t": 22, "temp_c": 49.0, "mhz": 873, "cpu_pct": 5.3, "watts": -1.00, "rss_mb": 43} +{"t": 23, "temp_c": 48.0, "mhz": 1691, "cpu_pct": 1.8, "watts": -1.00, "rss_mb": 43} +{"t": 24, "temp_c": 48.0, "mhz": 790, "cpu_pct": 2.5, "watts": -1.00, "rss_mb": 43} +{"t": 25, "temp_c": 47.0, "mhz": 762, "cpu_pct": 3.3, "watts": -1.00, "rss_mb": 43} +{"t": 26, "temp_c": 48.0, "mhz": 800, "cpu_pct": 2.8, "watts": -1.00, "rss_mb": 43} +{"t": 27, "temp_c": 48.0, "mhz": 793, "cpu_pct": 3.3, "watts": -1.00, "rss_mb": 43} +{"t": 28, "temp_c": 48.0, "mhz": 791, "cpu_pct": 3.8, "watts": -1.00, "rss_mb": 43} +{"t": 29, "temp_c": 48.0, "mhz": 1217, "cpu_pct": 3.3, "watts": -1.00, "rss_mb": 43} +{"t": 30, "temp_c": 47.0, "mhz": 800, "cpu_pct": 1.3, "watts": -1.00, "rss_mb": 43} +{"t": 31, "temp_c": 49.0, "mhz": 794, "cpu_pct": 3.5, "watts": -1.00, "rss_mb": 43} +{"t": 32, "temp_c": 48.0, "mhz": 777, "cpu_pct": 3.3, "watts": -1.00, "rss_mb": 43} +{"t": 33, "temp_c": 48.0, "mhz": 800, "cpu_pct": 2.8, "watts": -1.00, "rss_mb": 43} +{"t": 34, "temp_c": 47.0, "mhz": 800, "cpu_pct": 3.8, "watts": -1.00, "rss_mb": 43} +{"t": 35, "temp_c": 48.0, "mhz": 764, "cpu_pct": 3.0, "watts": -1.00, "rss_mb": 43} +{"t": 36, "temp_c": 48.0, "mhz": 2800, "cpu_pct": 1.5, "watts": -1.00, "rss_mb": 43} +{"t": 37, "temp_c": 48.0, "mhz": 801, "cpu_pct": 3.8, "watts": -1.00, "rss_mb": 43} +{"t": 38, "temp_c": 48.0, "mhz": 794, "cpu_pct": 2.5, "watts": -1.00, "rss_mb": 43} +{"t": 39, "temp_c": 48.0, "mhz": 798, "cpu_pct": 2.5, "watts": -1.00, "rss_mb": 43} +{"t": 40, "temp_c": 48.0, "mhz": 790, "cpu_pct": 4.3, "watts": -1.00, "rss_mb": 43} +{"t": 41, "temp_c": 49.0, "mhz": 805, "cpu_pct": 2.3, "watts": -1.00, "rss_mb": 43} +{"t": 42, "temp_c": 49.0, "mhz": 777, "cpu_pct": 3.3, "watts": -1.00, "rss_mb": 43} +{"t": 43, "temp_c": 48.0, "mhz": 793, "cpu_pct": 2.5, "watts": -1.00, "rss_mb": 43} +{"t": 44, "temp_c": 47.0, "mhz": 791, "cpu_pct": 2.8, "watts": -1.00, "rss_mb": 43} +{"t": 45, "temp_c": 48.0, "mhz": 800, "cpu_pct": 3.0, "watts": -1.00, "rss_mb": 43} +{"t": 46, "temp_c": 48.0, "mhz": 800, "cpu_pct": 3.5, "watts": -1.00, "rss_mb": 43} +{"t": 47, "temp_c": 50.0, "mhz": 800, "cpu_pct": 17.0, "watts": -1.00, "rss_mb": 43} +{"t": 48, "temp_c": 48.0, "mhz": 779, "cpu_pct": 10.8, "watts": -1.00, "rss_mb": 43} +{"t": 49, "temp_c": 48.0, "mhz": 800, "cpu_pct": 3.3, "watts": -1.00, "rss_mb": 43} +{"t": 50, "temp_c": 48.0, "mhz": 754, "cpu_pct": 2.8, "watts": -1.00, "rss_mb": 43} +{"t": 51, "temp_c": 48.0, "mhz": 741, "cpu_pct": 2.8, "watts": -1.00, "rss_mb": 43} +{"t": 52, "temp_c": 48.0, "mhz": 800, "cpu_pct": 3.0, "watts": -1.00, "rss_mb": 43} +{"t": 53, "temp_c": 48.0, "mhz": 800, "cpu_pct": 2.5, "watts": -1.00, "rss_mb": 43} +{"t": 54, "temp_c": 49.0, "mhz": 798, "cpu_pct": 2.5, "watts": -1.00, "rss_mb": 43} +{"t": 55, "temp_c": 50.0, "mhz": 1573, "cpu_pct": 5.8, "watts": -1.00, "rss_mb": 43} +{"t": 56, "temp_c": 48.0, "mhz": 2828, "cpu_pct": 2.0, "watts": -1.00, "rss_mb": 43} +{"t": 57, "temp_c": 48.0, "mhz": 759, "cpu_pct": 3.0, "watts": -1.00, "rss_mb": 43} +{"t": 58, "temp_c": 49.0, "mhz": 790, "cpu_pct": 3.8, "watts": -1.00, "rss_mb": 43} +{"t": 59, "temp_c": 48.0, "mhz": 789, "cpu_pct": 2.8, "watts": -1.00, "rss_mb": 43} +{"t": 60, "temp_c": 48.0, "mhz": 796, "cpu_pct": 2.5, "watts": -1.00, "rss_mb": 43} +{"summary": "idle", "secs": 60, "temp_c_mean": 48.4, "temp_c_max": 55.0, "mhz_mean": 960, "cpu_pct_mean": 4.5, "watts_mean": -1.00} diff --git a/tools/camera-bench/results-2026-10-08-tower111-throttle/idle_afterpower.jsonl b/tools/camera-bench/results-2026-10-08-tower111-throttle/idle_afterpower.jsonl new file mode 100644 index 0000000..7aa80a3 --- /dev/null +++ b/tools/camera-bench/results-2026-10-08-tower111-throttle/idle_afterpower.jsonl @@ -0,0 +1,61 @@ +{"t": 1, "temp_c": 55.0, "mhz": 885, "cpu_pct": 4.5, "watts": 3.53, "rss_mb": 43} +{"t": 2, "temp_c": 54.0, "mhz": 1289, "cpu_pct": 3.8, "watts": 2.96, "rss_mb": 43} +{"t": 3, "temp_c": 53.0, "mhz": 800, "cpu_pct": 4.3, "watts": 3.07, "rss_mb": 43} +{"t": 4, "temp_c": 53.0, "mhz": 762, "cpu_pct": 3.5, "watts": 2.95, "rss_mb": 43} +{"t": 5, "temp_c": 54.0, "mhz": 1200, "cpu_pct": 5.1, "watts": 2.96, "rss_mb": 43} +{"t": 6, "temp_c": 54.0, "mhz": 707, "cpu_pct": 6.0, "watts": 3.12, "rss_mb": 43} +{"t": 7, "temp_c": 54.0, "mhz": 985, "cpu_pct": 4.0, "watts": 2.13, "rss_mb": 43} +{"t": 8, "temp_c": 54.0, "mhz": 801, "cpu_pct": 4.0, "watts": 3.00, "rss_mb": 43} +{"t": 9, "temp_c": 54.0, "mhz": 756, "cpu_pct": 4.0, "watts": 3.02, "rss_mb": 43} +{"t": 10, "temp_c": 53.0, "mhz": 798, "cpu_pct": 4.3, "watts": 2.98, "rss_mb": 43} +{"t": 11, "temp_c": 54.0, "mhz": 763, "cpu_pct": 4.8, "watts": 2.96, "rss_mb": 43} +{"t": 12, "temp_c": 53.0, "mhz": 800, "cpu_pct": 4.3, "watts": 3.04, "rss_mb": 43} +{"t": 13, "temp_c": 54.0, "mhz": 1561, "cpu_pct": 3.5, "watts": 2.91, "rss_mb": 43} +{"t": 14, "temp_c": 53.0, "mhz": 784, "cpu_pct": 3.3, "watts": 2.14, "rss_mb": 43} +{"t": 15, "temp_c": 52.0, "mhz": 793, "cpu_pct": 3.5, "watts": 3.03, "rss_mb": 43} +{"t": 16, "temp_c": 53.0, "mhz": 790, "cpu_pct": 5.0, "watts": 2.98, "rss_mb": 43} +{"t": 17, "temp_c": 54.0, "mhz": 788, "cpu_pct": 5.3, "watts": 2.91, "rss_mb": 43} +{"t": 18, "temp_c": 54.0, "mhz": 797, "cpu_pct": 5.0, "watts": 3.01, "rss_mb": 43} +{"t": 19, "temp_c": 54.0, "mhz": 1884, "cpu_pct": 3.5, "watts": 2.77, "rss_mb": 43} +{"t": 20, "temp_c": 53.0, "mhz": 860, "cpu_pct": 4.0, "watts": 3.00, "rss_mb": 43} +{"t": 21, "temp_c": 52.0, "mhz": 800, "cpu_pct": 4.3, "watts": 2.28, "rss_mb": 43} +{"t": 22, "temp_c": 52.0, "mhz": 774, "cpu_pct": 3.5, "watts": 2.94, "rss_mb": 43} +{"t": 23, "temp_c": 53.0, "mhz": 935, "cpu_pct": 5.5, "watts": 2.94, "rss_mb": 43} +{"t": 24, "temp_c": 53.0, "mhz": 779, "cpu_pct": 4.5, "watts": 2.93, "rss_mb": 43} +{"t": 25, "temp_c": 54.0, "mhz": 1192, "cpu_pct": 4.5, "watts": 2.91, "rss_mb": 43} +{"t": 26, "temp_c": 54.0, "mhz": 792, "cpu_pct": 4.0, "watts": 2.93, "rss_mb": 43} +{"t": 27, "temp_c": 52.0, "mhz": 798, "cpu_pct": 3.8, "watts": 2.97, "rss_mb": 43} +{"t": 28, "temp_c": 54.0, "mhz": 800, "cpu_pct": 12.9, "watts": 2.48, "rss_mb": 43} +{"t": 29, "temp_c": 52.0, "mhz": 798, "cpu_pct": 15.4, "watts": 4.12, "rss_mb": 43} +{"t": 30, "temp_c": 53.0, "mhz": 982, "cpu_pct": 4.5, "watts": 2.91, "rss_mb": 43} +{"t": 31, "temp_c": 52.0, "mhz": 792, "cpu_pct": 2.3, "watts": 2.92, "rss_mb": 43} +{"t": 32, "temp_c": 52.0, "mhz": 800, "cpu_pct": 5.3, "watts": 2.92, "rss_mb": 43} +{"t": 33, "temp_c": 52.0, "mhz": 754, "cpu_pct": 4.5, "watts": 2.97, "rss_mb": 43} +{"t": 34, "temp_c": 52.0, "mhz": 763, "cpu_pct": 4.8, "watts": 2.92, "rss_mb": 43} +{"t": 35, "temp_c": 53.0, "mhz": 758, "cpu_pct": 4.8, "watts": 2.09, "rss_mb": 43} +{"t": 36, "temp_c": 53.0, "mhz": 2677, "cpu_pct": 4.8, "watts": 3.08, "rss_mb": 43} +{"t": 37, "temp_c": 52.0, "mhz": 799, "cpu_pct": 2.5, "watts": 2.92, "rss_mb": 43} +{"t": 38, "temp_c": 52.0, "mhz": 795, "cpu_pct": 4.0, "watts": 2.89, "rss_mb": 43} +{"t": 39, "temp_c": 52.0, "mhz": 794, "cpu_pct": 5.0, "watts": 3.02, "rss_mb": 43} +{"t": 40, "temp_c": 52.0, "mhz": 778, "cpu_pct": 4.3, "watts": 2.95, "rss_mb": 43} +{"t": 41, "temp_c": 52.0, "mhz": 800, "cpu_pct": 5.0, "watts": 3.04, "rss_mb": 43} +{"t": 42, "temp_c": 54.0, "mhz": 1473, "cpu_pct": 4.5, "watts": 2.17, "rss_mb": 43} +{"t": 43, "temp_c": 52.0, "mhz": 789, "cpu_pct": 3.0, "watts": 2.92, "rss_mb": 43} +{"t": 44, "temp_c": 52.0, "mhz": 796, "cpu_pct": 4.0, "watts": 2.88, "rss_mb": 43} +{"t": 45, "temp_c": 52.0, "mhz": 762, "cpu_pct": 4.5, "watts": 2.99, "rss_mb": 43} +{"t": 46, "temp_c": 51.0, "mhz": 698, "cpu_pct": 4.6, "watts": 2.93, "rss_mb": 43} +{"t": 47, "temp_c": 53.0, "mhz": 792, "cpu_pct": 4.8, "watts": 3.01, "rss_mb": 43} +{"t": 48, "temp_c": 54.0, "mhz": 2799, "cpu_pct": 4.0, "watts": 2.92, "rss_mb": 43} +{"t": 49, "temp_c": 51.0, "mhz": 785, "cpu_pct": 3.0, "watts": 2.14, "rss_mb": 43} +{"t": 50, "temp_c": 52.0, "mhz": 801, "cpu_pct": 5.0, "watts": 2.91, "rss_mb": 43} +{"t": 51, "temp_c": 51.0, "mhz": 796, "cpu_pct": 3.8, "watts": 3.00, "rss_mb": 43} +{"t": 52, "temp_c": 51.0, "mhz": 800, "cpu_pct": 4.5, "watts": 2.95, "rss_mb": 43} +{"t": 53, "temp_c": 52.0, "mhz": 777, "cpu_pct": 4.3, "watts": 2.93, "rss_mb": 43} +{"t": 54, "temp_c": 52.0, "mhz": 2678, "cpu_pct": 3.8, "watts": 2.75, "rss_mb": 43} +{"t": 55, "temp_c": 51.0, "mhz": 800, "cpu_pct": 2.8, "watts": 2.25, "rss_mb": 43} +{"t": 56, "temp_c": 52.0, "mhz": 801, "cpu_pct": 5.5, "watts": 2.91, "rss_mb": 43} +{"t": 57, "temp_c": 51.0, "mhz": 764, "cpu_pct": 5.0, "watts": 3.11, "rss_mb": 43} +{"t": 58, "temp_c": 52.0, "mhz": 800, "cpu_pct": 4.3, "watts": 2.95, "rss_mb": 43} +{"t": 59, "temp_c": 59.0, "mhz": 2800, "cpu_pct": 21.9, "watts": 3.74, "rss_mb": 43} +{"t": 60, "temp_c": 51.0, "mhz": 782, "cpu_pct": 6.3, "watts": 3.67, "rss_mb": 43} +{"summary": "idle", "secs": 60, "temp_c_mean": 52.8, "temp_c_max": 59.0, "mhz_mean": 991, "cpu_pct_mean": 4.9, "watts_mean": 2.91} diff --git a/tools/camera-bench/results-2026-10-08-tower111-throttle/idle_start.jsonl b/tools/camera-bench/results-2026-10-08-tower111-throttle/idle_start.jsonl new file mode 100644 index 0000000..52164e3 --- /dev/null +++ b/tools/camera-bench/results-2026-10-08-tower111-throttle/idle_start.jsonl @@ -0,0 +1,61 @@ +{"t": 1, "temp_c": 53.0, "mhz": 776, "cpu_pct": 4.5, "watts": 3.53, "rss_mb": 42} +{"t": 2, "temp_c": 54.0, "mhz": 794, "cpu_pct": 5.5, "watts": 2.97, "rss_mb": 43} +{"t": 3, "temp_c": 55.0, "mhz": 2773, "cpu_pct": 4.0, "watts": 2.82, "rss_mb": 43} +{"t": 4, "temp_c": 61.0, "mhz": 2800, "cpu_pct": 7.8, "watts": 3.25, "rss_mb": 43} +{"t": 5, "temp_c": 52.0, "mhz": 800, "cpu_pct": 6.6, "watts": 3.21, "rss_mb": 43} +{"t": 6, "temp_c": 52.0, "mhz": 791, "cpu_pct": 3.8, "watts": 2.18, "rss_mb": 43} +{"t": 7, "temp_c": 53.0, "mhz": 790, "cpu_pct": 3.5, "watts": 2.86, "rss_mb": 43} +{"t": 8, "temp_c": 53.0, "mhz": 878, "cpu_pct": 4.8, "watts": 3.03, "rss_mb": 43} +{"t": 9, "temp_c": 53.0, "mhz": 780, "cpu_pct": 3.5, "watts": 2.97, "rss_mb": 43} +{"t": 10, "temp_c": 53.0, "mhz": 774, "cpu_pct": 4.5, "watts": 2.97, "rss_mb": 43} +{"t": 11, "temp_c": 53.0, "mhz": 772, "cpu_pct": 4.8, "watts": 2.97, "rss_mb": 43} +{"t": 12, "temp_c": 52.0, "mhz": 614, "cpu_pct": 4.0, "watts": 2.93, "rss_mb": 43} +{"t": 13, "temp_c": 53.0, "mhz": 798, "cpu_pct": 4.0, "watts": 2.16, "rss_mb": 43} +{"t": 14, "temp_c": 54.0, "mhz": 2847, "cpu_pct": 5.1, "watts": 2.94, "rss_mb": 43} +{"t": 15, "temp_c": 52.0, "mhz": 784, "cpu_pct": 3.0, "watts": 2.94, "rss_mb": 43} +{"t": 16, "temp_c": 52.0, "mhz": 787, "cpu_pct": 3.5, "watts": 2.93, "rss_mb": 43} +{"t": 17, "temp_c": 52.0, "mhz": 786, "cpu_pct": 5.6, "watts": 3.09, "rss_mb": 43} +{"t": 18, "temp_c": 53.0, "mhz": 767, "cpu_pct": 4.5, "watts": 2.92, "rss_mb": 43} +{"t": 19, "temp_c": 52.0, "mhz": 800, "cpu_pct": 3.8, "watts": 2.14, "rss_mb": 43} +{"t": 20, "temp_c": 53.0, "mhz": 2853, "cpu_pct": 3.8, "watts": 3.02, "rss_mb": 43} +{"t": 21, "temp_c": 51.0, "mhz": 792, "cpu_pct": 3.0, "watts": 2.92, "rss_mb": 43} +{"t": 22, "temp_c": 52.0, "mhz": 800, "cpu_pct": 4.0, "watts": 2.93, "rss_mb": 43} +{"t": 23, "temp_c": 51.0, "mhz": 784, "cpu_pct": 5.5, "watts": 3.00, "rss_mb": 43} +{"t": 24, "temp_c": 52.0, "mhz": 793, "cpu_pct": 4.0, "watts": 2.96, "rss_mb": 43} +{"t": 25, "temp_c": 52.0, "mhz": 758, "cpu_pct": 4.0, "watts": 2.94, "rss_mb": 43} +{"t": 26, "temp_c": 54.0, "mhz": 2843, "cpu_pct": 4.8, "watts": 2.20, "rss_mb": 43} +{"t": 27, "temp_c": 51.0, "mhz": 796, "cpu_pct": 2.5, "watts": 2.92, "rss_mb": 43} +{"t": 28, "temp_c": 52.0, "mhz": 830, "cpu_pct": 4.5, "watts": 2.89, "rss_mb": 43} +{"t": 29, "temp_c": 54.0, "mhz": 737, "cpu_pct": 25.5, "watts": 4.67, "rss_mb": 43} +{"t": 30, "temp_c": 52.0, "mhz": 787, "cpu_pct": 4.3, "watts": 3.12, "rss_mb": 43} +{"t": 31, "temp_c": 52.0, "mhz": 891, "cpu_pct": 3.5, "watts": 2.85, "rss_mb": 43} +{"t": 32, "temp_c": 52.0, "mhz": 900, "cpu_pct": 4.1, "watts": 2.89, "rss_mb": 43} +{"t": 33, "temp_c": 51.0, "mhz": 799, "cpu_pct": 3.8, "watts": 2.27, "rss_mb": 43} +{"t": 34, "temp_c": 51.0, "mhz": 792, "cpu_pct": 3.8, "watts": 2.96, "rss_mb": 43} +{"t": 35, "temp_c": 52.0, "mhz": 791, "cpu_pct": 6.6, "watts": 3.07, "rss_mb": 43} +{"t": 36, "temp_c": 52.0, "mhz": 790, "cpu_pct": 4.5, "watts": 2.83, "rss_mb": 43} +{"t": 37, "temp_c": 52.0, "mhz": 800, "cpu_pct": 4.0, "watts": 2.80, "rss_mb": 43} +{"t": 38, "temp_c": 51.0, "mhz": 797, "cpu_pct": 3.3, "watts": 2.98, "rss_mb": 43} +{"t": 39, "temp_c": 52.0, "mhz": 767, "cpu_pct": 4.8, "watts": 2.97, "rss_mb": 43} +{"t": 40, "temp_c": 52.0, "mhz": 800, "cpu_pct": 4.0, "watts": 2.30, "rss_mb": 43} +{"t": 41, "temp_c": 51.0, "mhz": 784, "cpu_pct": 5.3, "watts": 2.92, "rss_mb": 43} +{"t": 42, "temp_c": 52.0, "mhz": 703, "cpu_pct": 3.8, "watts": 2.83, "rss_mb": 43} +{"t": 43, "temp_c": 52.0, "mhz": 754, "cpu_pct": 3.8, "watts": 2.90, "rss_mb": 43} +{"t": 44, "temp_c": 51.0, "mhz": 775, "cpu_pct": 3.5, "watts": 3.01, "rss_mb": 43} +{"t": 45, "temp_c": 51.0, "mhz": 791, "cpu_pct": 4.5, "watts": 2.94, "rss_mb": 43} +{"t": 46, "temp_c": 51.0, "mhz": 770, "cpu_pct": 3.8, "watts": 2.95, "rss_mb": 43} +{"t": 47, "temp_c": 51.0, "mhz": 800, "cpu_pct": 5.5, "watts": 2.17, "rss_mb": 43} +{"t": 48, "temp_c": 51.0, "mhz": 797, "cpu_pct": 4.0, "watts": 2.91, "rss_mb": 43} +{"t": 49, "temp_c": 52.0, "mhz": 1821, "cpu_pct": 4.3, "watts": 2.90, "rss_mb": 43} +{"t": 50, "temp_c": 51.0, "mhz": 750, "cpu_pct": 3.8, "watts": 2.97, "rss_mb": 43} +{"t": 51, "temp_c": 52.0, "mhz": 786, "cpu_pct": 3.8, "watts": 2.90, "rss_mb": 43} +{"t": 52, "temp_c": 51.0, "mhz": 800, "cpu_pct": 3.3, "watts": 2.92, "rss_mb": 43} +{"t": 53, "temp_c": 51.0, "mhz": 718, "cpu_pct": 6.3, "watts": 3.06, "rss_mb": 43} +{"t": 54, "temp_c": 52.0, "mhz": 758, "cpu_pct": 4.3, "watts": 2.13, "rss_mb": 43} +{"t": 55, "temp_c": 61.0, "mhz": 2800, "cpu_pct": 20.6, "watts": 3.91, "rss_mb": 43} +{"t": 56, "temp_c": 51.0, "mhz": 792, "cpu_pct": 5.0, "watts": 3.58, "rss_mb": 43} +{"t": 57, "temp_c": 51.0, "mhz": 757, "cpu_pct": 3.8, "watts": 2.91, "rss_mb": 43} +{"t": 58, "temp_c": 51.0, "mhz": 800, "cpu_pct": 3.8, "watts": 2.90, "rss_mb": 43} +{"t": 59, "temp_c": 52.0, "mhz": 794, "cpu_pct": 5.3, "watts": 2.97, "rss_mb": 43} +{"t": 60, "temp_c": 52.0, "mhz": 1399, "cpu_pct": 6.8, "watts": 3.10, "rss_mb": 43} +{"summary": "idle", "secs": 60, "temp_c_mean": 52.3, "temp_c_max": 61.0, "mhz_mean": 1016, "cpu_pct_mean": 5.0, "watts_mean": 2.92} diff --git a/tools/camera-bench/results-2026-10-08-tower111-throttle/load2_paced.jsonl b/tools/camera-bench/results-2026-10-08-tower111-throttle/load2_paced.jsonl new file mode 100644 index 0000000..4d9a4d6 --- /dev/null +++ b/tools/camera-bench/results-2026-10-08-tower111-throttle/load2_paced.jsonl @@ -0,0 +1,121 @@ +{"t": 1, "temp_c": 78.0, "mhz": 2700, "cpu_pct": 91.1, "watts": 9.43, "rss_mb": 543, "done": 0, "queue": 0} +{"t": 2, "temp_c": 68.0, "mhz": 2800, "cpu_pct": 70.2, "watts": 8.25, "rss_mb": 259, "done": 1, "queue": 0} +{"t": 3, "temp_c": 79.0, "mhz": 2507, "cpu_pct": 91.5, "watts": 7.15, "rss_mb": 622, "done": 1, "queue": 0} +{"t": 4, "temp_c": 72.0, "mhz": 2800, "cpu_pct": 69.2, "watts": 8.38, "rss_mb": 292, "done": 2, "queue": 0} +{"t": 5, "temp_c": 78.0, "mhz": 2600, "cpu_pct": 90.7, "watts": 9.03, "rss_mb": 643, "done": 2, "queue": 0} +{"t": 6, "temp_c": 68.0, "mhz": 2800, "cpu_pct": 66.5, "watts": 8.15, "rss_mb": 312, "done": 3, "queue": 0} +{"t": 7, "temp_c": 76.0, "mhz": 2700, "cpu_pct": 91.5, "watts": 9.05, "rss_mb": 579, "done": 3, "queue": 0} +{"t": 8, "temp_c": 71.0, "mhz": 2800, "cpu_pct": 68.1, "watts": 8.22, "rss_mb": 401, "done": 4, "queue": 0} +{"t": 9, "temp_c": 78.0, "mhz": 2500, "cpu_pct": 91.2, "watts": 9.22, "rss_mb": 699, "done": 4, "queue": 0} +{"t": 10, "temp_c": 74.0, "mhz": 2800, "cpu_pct": 67.8, "watts": 7.97, "rss_mb": 466, "done": 5, "queue": 0} +{"t": 11, "temp_c": 78.0, "mhz": 2500, "cpu_pct": 92.0, "watts": 6.97, "rss_mb": 707, "done": 5, "queue": 0} +{"t": 12, "temp_c": 77.0, "mhz": 2800, "cpu_pct": 66.3, "watts": 8.36, "rss_mb": 474, "done": 6, "queue": 0} +{"t": 13, "temp_c": 77.0, "mhz": 2200, "cpu_pct": 92.0, "watts": 8.96, "rss_mb": 698, "done": 6, "queue": 0} +{"t": 14, "temp_c": 73.0, "mhz": 2799, "cpu_pct": 64.8, "watts": 8.08, "rss_mb": 480, "done": 7, "queue": 0} +{"t": 15, "temp_c": 78.0, "mhz": 2600, "cpu_pct": 92.5, "watts": 9.09, "rss_mb": 701, "done": 7, "queue": 0} +{"t": 16, "temp_c": 76.0, "mhz": 2800, "cpu_pct": 67.7, "watts": 8.08, "rss_mb": 484, "done": 8, "queue": 0} +{"t": 17, "temp_c": 79.0, "mhz": 2436, "cpu_pct": 92.5, "watts": 9.03, "rss_mb": 705, "done": 8, "queue": 0} +{"t": 18, "temp_c": 76.0, "mhz": 2800, "cpu_pct": 66.4, "watts": 7.98, "rss_mb": 488, "done": 9, "queue": 0} +{"t": 19, "temp_c": 79.0, "mhz": 2500, "cpu_pct": 92.3, "watts": 9.24, "rss_mb": 712, "done": 9, "queue": 0} +{"t": 20, "temp_c": 80.0, "mhz": 2800, "cpu_pct": 68.6, "watts": 6.06, "rss_mb": 518, "done": 10, "queue": 0} +{"t": 21, "temp_c": 77.0, "mhz": 2600, "cpu_pct": 92.0, "watts": 9.08, "rss_mb": 714, "done": 10, "queue": 0} +{"t": 22, "temp_c": 79.0, "mhz": 2800, "cpu_pct": 67.2, "watts": 8.11, "rss_mb": 521, "done": 11, "queue": 0} +{"t": 23, "temp_c": 77.0, "mhz": 2756, "cpu_pct": 93.0, "watts": 8.98, "rss_mb": 715, "done": 11, "queue": 0} +{"t": 24, "temp_c": 80.0, "mhz": 2800, "cpu_pct": 67.2, "watts": 8.06, "rss_mb": 530, "done": 12, "queue": 0} +{"t": 25, "temp_c": 77.0, "mhz": 2700, "cpu_pct": 91.5, "watts": 9.08, "rss_mb": 724, "done": 12, "queue": 0} +{"t": 26, "temp_c": 81.0, "mhz": 2800, "cpu_pct": 67.2, "watts": 7.91, "rss_mb": 541, "done": 13, "queue": 0} +{"t": 27, "temp_c": 78.0, "mhz": 2600, "cpu_pct": 95.2, "watts": 6.97, "rss_mb": 725, "done": 13, "queue": 0} +{"t": 28, "temp_c": 80.0, "mhz": 2800, "cpu_pct": 74.8, "watts": 8.50, "rss_mb": 544, "done": 14, "queue": 0} +{"t": 29, "temp_c": 76.0, "mhz": 2701, "cpu_pct": 92.5, "watts": 8.90, "rss_mb": 726, "done": 14, "queue": 0} +{"t": 30, "temp_c": 83.0, "mhz": 2800, "cpu_pct": 68.0, "watts": 8.05, "rss_mb": 563, "done": 15, "queue": 0} +{"t": 31, "temp_c": 78.0, "mhz": 2700, "cpu_pct": 92.1, "watts": 8.87, "rss_mb": 727, "done": 15, "queue": 0} +{"t": 32, "temp_c": 84.0, "mhz": 2800, "cpu_pct": 66.1, "watts": 7.93, "rss_mb": 602, "done": 16, "queue": 0} +{"t": 33, "temp_c": 79.0, "mhz": 2400, "cpu_pct": 92.2, "watts": 9.04, "rss_mb": 727, "done": 16, "queue": 0} +{"t": 34, "temp_c": 85.0, "mhz": 2800, "cpu_pct": 66.7, "watts": 7.80, "rss_mb": 574, "done": 17, "queue": 0} +{"t": 35, "temp_c": 77.0, "mhz": 2586, "cpu_pct": 91.5, "watts": 9.18, "rss_mb": 745, "done": 17, "queue": 0} +{"t": 36, "temp_c": 86.0, "mhz": 2800, "cpu_pct": 69.3, "watts": 6.03, "rss_mb": 603, "done": 18, "queue": 0} +{"t": 37, "temp_c": 76.0, "mhz": 2800, "cpu_pct": 90.8, "watts": 8.86, "rss_mb": 731, "done": 18, "queue": 0} +{"t": 38, "temp_c": 85.0, "mhz": 2800, "cpu_pct": 67.2, "watts": 8.03, "rss_mb": 584, "done": 19, "queue": 0} +{"t": 39, "temp_c": 77.0, "mhz": 2550, "cpu_pct": 91.8, "watts": 8.73, "rss_mb": 747, "done": 19, "queue": 0} +{"t": 40, "temp_c": 82.0, "mhz": 2799, "cpu_pct": 67.2, "watts": 7.98, "rss_mb": 614, "done": 20, "queue": 0} +{"t": 41, "temp_c": 77.0, "mhz": 2600, "cpu_pct": 91.2, "watts": 8.95, "rss_mb": 748, "done": 20, "queue": 0} +{"t": 42, "temp_c": 86.0, "mhz": 2800, "cpu_pct": 67.2, "watts": 7.87, "rss_mb": 618, "done": 21, "queue": 0} +{"t": 43, "temp_c": 78.0, "mhz": 2600, "cpu_pct": 91.0, "watts": 8.96, "rss_mb": 753, "done": 21, "queue": 0} +{"t": 44, "temp_c": 85.0, "mhz": 2800, "cpu_pct": 67.6, "watts": 7.96, "rss_mb": 586, "done": 22, "queue": 0} +{"t": 45, "temp_c": 76.0, "mhz": 2464, "cpu_pct": 91.5, "watts": 6.90, "rss_mb": 759, "done": 22, "queue": 0} +{"t": 46, "temp_c": 86.0, "mhz": 2800, "cpu_pct": 68.6, "watts": 8.08, "rss_mb": 605, "done": 23, "queue": 0} +{"t": 47, "temp_c": 78.0, "mhz": 2700, "cpu_pct": 91.0, "watts": 8.67, "rss_mb": 743, "done": 23, "queue": 0} +{"t": 48, "temp_c": 85.0, "mhz": 2800, "cpu_pct": 67.8, "watts": 8.02, "rss_mb": 628, "done": 24, "queue": 0} +{"t": 49, "temp_c": 80.0, "mhz": 2787, "cpu_pct": 91.3, "watts": 8.87, "rss_mb": 743, "done": 24, "queue": 0} +{"t": 50, "temp_c": 88.0, "mhz": 2800, "cpu_pct": 67.9, "watts": 7.83, "rss_mb": 598, "done": 25, "queue": 0} +{"t": 51, "temp_c": 78.0, "mhz": 2642, "cpu_pct": 92.0, "watts": 8.89, "rss_mb": 762, "done": 25, "queue": 0} +{"t": 52, "temp_c": 84.0, "mhz": 2800, "cpu_pct": 69.5, "watts": 7.97, "rss_mb": 637, "done": 26, "queue": 0} +{"t": 53, "temp_c": 77.0, "mhz": 2716, "cpu_pct": 91.5, "watts": 6.79, "rss_mb": 746, "done": 26, "queue": 0} +{"t": 54, "temp_c": 81.0, "mhz": 2800, "cpu_pct": 68.5, "watts": 8.08, "rss_mb": 649, "done": 27, "queue": 0} +{"t": 55, "temp_c": 78.0, "mhz": 2800, "cpu_pct": 93.0, "watts": 8.69, "rss_mb": 751, "done": 27, "queue": 0} +{"t": 56, "temp_c": 87.0, "mhz": 2800, "cpu_pct": 68.5, "watts": 8.04, "rss_mb": 632, "done": 28, "queue": 0} +{"t": 57, "temp_c": 77.0, "mhz": 2701, "cpu_pct": 91.5, "watts": 8.92, "rss_mb": 751, "done": 28, "queue": 0} +{"t": 58, "temp_c": 83.0, "mhz": 2800, "cpu_pct": 69.1, "watts": 7.85, "rss_mb": 654, "done": 29, "queue": 0} +{"t": 59, "temp_c": 77.0, "mhz": 2470, "cpu_pct": 96.2, "watts": 9.05, "rss_mb": 751, "done": 29, "queue": 0} +{"t": 60, "temp_c": 84.0, "mhz": 2800, "cpu_pct": 70.9, "watts": 8.10, "rss_mb": 634, "done": 30, "queue": 0} +{"t": 61, "temp_c": 78.0, "mhz": 2701, "cpu_pct": 91.0, "watts": 6.80, "rss_mb": 752, "done": 30, "queue": 0} +{"t": 62, "temp_c": 79.0, "mhz": 2800, "cpu_pct": 68.4, "watts": 8.13, "rss_mb": 660, "done": 31, "queue": 0} +{"t": 63, "temp_c": 77.0, "mhz": 2500, "cpu_pct": 91.5, "watts": 8.87, "rss_mb": 754, "done": 31, "queue": 0} +{"t": 64, "temp_c": 86.0, "mhz": 2800, "cpu_pct": 67.5, "watts": 7.89, "rss_mb": 661, "done": 32, "queue": 0} +{"t": 65, "temp_c": 78.0, "mhz": 2585, "cpu_pct": 92.2, "watts": 8.98, "rss_mb": 755, "done": 32, "queue": 0} +{"t": 66, "temp_c": 80.0, "mhz": 2800, "cpu_pct": 67.4, "watts": 8.05, "rss_mb": 672, "done": 33, "queue": 0} +{"t": 67, "temp_c": 77.0, "mhz": 2600, "cpu_pct": 91.7, "watts": 9.02, "rss_mb": 757, "done": 33, "queue": 0} +{"t": 68, "temp_c": 80.0, "mhz": 2800, "cpu_pct": 66.9, "watts": 5.91, "rss_mb": 682, "done": 34, "queue": 0} +{"t": 69, "temp_c": 79.0, "mhz": 2778, "cpu_pct": 92.0, "watts": 8.82, "rss_mb": 773, "done": 34, "queue": 0} +{"t": 70, "temp_c": 84.0, "mhz": 2800, "cpu_pct": 68.5, "watts": 7.94, "rss_mb": 641, "done": 35, "queue": 0} +{"t": 71, "temp_c": 78.0, "mhz": 2544, "cpu_pct": 91.0, "watts": 8.87, "rss_mb": 759, "done": 35, "queue": 0} +{"t": 72, "temp_c": 78.0, "mhz": 2300, "cpu_pct": 68.2, "watts": 7.88, "rss_mb": 693, "done": 36, "queue": 0} +{"t": 73, "temp_c": 79.0, "mhz": 2304, "cpu_pct": 92.1, "watts": 8.81, "rss_mb": 759, "done": 36, "queue": 0} +{"t": 74, "temp_c": 79.0, "mhz": 2801, "cpu_pct": 69.2, "watts": 8.11, "rss_mb": 671, "done": 37, "queue": 0} +{"t": 75, "temp_c": 78.0, "mhz": 2600, "cpu_pct": 92.7, "watts": 8.81, "rss_mb": 761, "done": 37, "queue": 0} +{"t": 76, "temp_c": 79.0, "mhz": 2800, "cpu_pct": 69.2, "watts": 6.00, "rss_mb": 683, "done": 38, "queue": 0} +{"t": 77, "temp_c": 78.0, "mhz": 2500, "cpu_pct": 91.7, "watts": 8.64, "rss_mb": 761, "done": 38, "queue": 0} +{"t": 78, "temp_c": 80.0, "mhz": 2800, "cpu_pct": 68.4, "watts": 7.99, "rss_mb": 679, "done": 39, "queue": 0} +{"t": 79, "temp_c": 78.0, "mhz": 2473, "cpu_pct": 91.5, "watts": 8.81, "rss_mb": 761, "done": 39, "queue": 0} +{"t": 80, "temp_c": 79.0, "mhz": 2799, "cpu_pct": 67.4, "watts": 7.79, "rss_mb": 684, "done": 40, "queue": 0} +{"t": 81, "temp_c": 77.0, "mhz": 2500, "cpu_pct": 93.2, "watts": 8.92, "rss_mb": 761, "done": 40, "queue": 0} +{"t": 82, "temp_c": 78.0, "mhz": 2800, "cpu_pct": 69.0, "watts": 8.03, "rss_mb": 687, "done": 41, "queue": 0} +{"t": 83, "temp_c": 80.0, "mhz": 2400, "cpu_pct": 92.2, "watts": 8.87, "rss_mb": 761, "done": 41, "queue": 0} +{"t": 84, "temp_c": 79.0, "mhz": 2800, "cpu_pct": 67.5, "watts": 5.93, "rss_mb": 692, "done": 42, "queue": 0} +{"t": 85, "temp_c": 76.0, "mhz": 2700, "cpu_pct": 93.0, "watts": 8.62, "rss_mb": 761, "done": 42, "queue": 0} +{"t": 86, "temp_c": 82.0, "mhz": 2300, "cpu_pct": 67.9, "watts": 7.85, "rss_mb": 695, "done": 43, "queue": 0} +{"t": 87, "temp_c": 79.0, "mhz": 2500, "cpu_pct": 91.3, "watts": 8.62, "rss_mb": 761, "done": 43, "queue": 0} +{"t": 88, "temp_c": 83.0, "mhz": 2800, "cpu_pct": 68.9, "watts": 7.88, "rss_mb": 729, "done": 44, "queue": 0} +{"t": 89, "temp_c": 78.0, "mhz": 2400, "cpu_pct": 93.0, "watts": 8.75, "rss_mb": 779, "done": 44, "queue": 0} +{"t": 90, "temp_c": 80.0, "mhz": 2600, "cpu_pct": 82.7, "watts": 8.46, "rss_mb": 716, "done": 45, "queue": 0} +{"t": 91, "temp_c": 78.0, "mhz": 2500, "cpu_pct": 92.8, "watts": 8.71, "rss_mb": 779, "done": 45, "queue": 0} +{"t": 92, "temp_c": 79.0, "mhz": 2800, "cpu_pct": 67.8, "watts": 5.88, "rss_mb": 709, "done": 46, "queue": 0} +{"t": 93, "temp_c": 78.0, "mhz": 2478, "cpu_pct": 92.2, "watts": 8.65, "rss_mb": 779, "done": 46, "queue": 0} +{"t": 94, "temp_c": 81.0, "mhz": 2800, "cpu_pct": 69.4, "watts": 7.89, "rss_mb": 730, "done": 47, "queue": 0} +{"t": 95, "temp_c": 78.0, "mhz": 2500, "cpu_pct": 92.5, "watts": 8.80, "rss_mb": 762, "done": 47, "queue": 0} +{"t": 96, "temp_c": 86.0, "mhz": 2700, "cpu_pct": 67.1, "watts": 7.85, "rss_mb": 730, "done": 48, "queue": 0} +{"t": 97, "temp_c": 78.0, "mhz": 2600, "cpu_pct": 93.2, "watts": 8.69, "rss_mb": 779, "done": 48, "queue": 0} +{"t": 98, "temp_c": 86.0, "mhz": 2800, "cpu_pct": 66.8, "watts": 7.91, "rss_mb": 730, "done": 49, "queue": 0} +{"t": 99, "temp_c": 77.0, "mhz": 2400, "cpu_pct": 92.5, "watts": 8.69, "rss_mb": 779, "done": 49, "queue": 0} +{"t": 100, "temp_c": 81.0, "mhz": 2800, "cpu_pct": 68.2, "watts": 8.11, "rss_mb": 723, "done": 50, "queue": 0} +{"t": 101, "temp_c": 79.0, "mhz": 2319, "cpu_pct": 92.2, "watts": 6.39, "rss_mb": 779, "done": 50, "queue": 0} +{"t": 102, "temp_c": 82.0, "mhz": 2799, "cpu_pct": 68.9, "watts": 7.91, "rss_mb": 719, "done": 51, "queue": 0} +{"t": 103, "temp_c": 78.0, "mhz": 2582, "cpu_pct": 92.3, "watts": 8.73, "rss_mb": 779, "done": 51, "queue": 0} +{"t": 104, "temp_c": 86.0, "mhz": 2800, "cpu_pct": 68.5, "watts": 7.98, "rss_mb": 731, "done": 52, "queue": 0} +{"t": 105, "temp_c": 79.0, "mhz": 2400, "cpu_pct": 92.3, "watts": 8.83, "rss_mb": 779, "done": 52, "queue": 0} +{"t": 106, "temp_c": 80.0, "mhz": 2600, "cpu_pct": 66.5, "watts": 7.91, "rss_mb": 731, "done": 53, "queue": 0} +{"t": 107, "temp_c": 78.0, "mhz": 2700, "cpu_pct": 92.0, "watts": 8.43, "rss_mb": 763, "done": 53, "queue": 0} +{"t": 108, "temp_c": 84.0, "mhz": 2800, "cpu_pct": 67.9, "watts": 5.85, "rss_mb": 731, "done": 54, "queue": 0} +{"t": 109, "temp_c": 78.0, "mhz": 2500, "cpu_pct": 92.2, "watts": 8.63, "rss_mb": 779, "done": 54, "queue": 0} +{"t": 110, "temp_c": 83.0, "mhz": 2800, "cpu_pct": 68.8, "watts": 7.87, "rss_mb": 731, "done": 55, "queue": 0} +{"t": 111, "temp_c": 78.0, "mhz": 2700, "cpu_pct": 92.2, "watts": 8.77, "rss_mb": 763, "done": 55, "queue": 0} +{"t": 112, "temp_c": 86.0, "mhz": 2700, "cpu_pct": 68.9, "watts": 7.82, "rss_mb": 731, "done": 56, "queue": 0} +{"t": 113, "temp_c": 78.0, "mhz": 2600, "cpu_pct": 92.5, "watts": 8.60, "rss_mb": 763, "done": 56, "queue": 0} +{"t": 114, "temp_c": 84.0, "mhz": 2800, "cpu_pct": 67.3, "watts": 8.08, "rss_mb": 731, "done": 57, "queue": 0} +{"t": 115, "temp_c": 77.0, "mhz": 2325, "cpu_pct": 92.5, "watts": 8.57, "rss_mb": 763, "done": 57, "queue": 0} +{"t": 116, "temp_c": 80.0, "mhz": 2500, "cpu_pct": 68.6, "watts": 7.92, "rss_mb": 731, "done": 58, "queue": 0} +{"t": 117, "temp_c": 77.0, "mhz": 2500, "cpu_pct": 93.0, "watts": 6.32, "rss_mb": 763, "done": 58, "queue": 0} +{"t": 118, "temp_c": 85.0, "mhz": 2600, "cpu_pct": 71.0, "watts": 7.98, "rss_mb": 731, "done": 59, "queue": 0} +{"t": 119, "temp_c": 79.0, "mhz": 2400, "cpu_pct": 93.8, "watts": 8.70, "rss_mb": 781, "done": 59, "queue": 0} +{"t": 120, "temp_c": 73.0, "mhz": 2800, "cpu_pct": 64.8, "watts": 7.78, "rss_mb": 385, "done": 60, "queue": 0} +{"summary": "pipeline", "kind": "c12_ea", "workers": 1, "threads": 4, "effort": 3, "distance": 1.00, "period_ms": 2000, "secs": 120.0, "frames": 60, "frames_per_s": 0.500, "job_ms_p50": 1440, "latency_ms_p50": 1447, "latency_ms_p95": 1505, "latency_ms_max": 1563, "max_queue": 1, "backlog_at_end": 0, "cpu_s_per_frame": 2.302, "peak_rss_mb": 781, "temp_c_mean": 79.3, "temp_c_max": 88.0, "mhz_mean": 2663, "mhz_min": 2200, "cpu_pct_mean": 80.2, "watts_mean": 8.18} diff --git a/tools/camera-bench/results-2026-10-08-tower111-throttle/matrix.log b/tools/camera-bench/results-2026-10-08-tower111-throttle/matrix.log new file mode 100644 index 0000000..9f71667 --- /dev/null +++ b/tools/camera-bench/results-2026-10-08-tower111-throttle/matrix.log @@ -0,0 +1,53 @@ +16:07:41 cool-down 60 s +16:08:41 none: flat out 60 s +16:08:41 throttle counts: package 0 cores 0 0 0 0 temp 60000 +16:09:42 none: paced 2000 ms 120 s +16:09:42 throttle counts: package 0 cores 0 0 0 0 temp 63000 +16:11:43 throttle counts: package 0 cores 0 0 0 0 temp 59000 +16:11:43 cool-down 60 s +every core capped at 1600 MHz ('/home/ggs/thermal-test/software/tools/thermal-sim/cap-cpu.sh restore' to undo) +16:12:43 cap1600: flat out 60 s +16:12:43 throttle counts: package 0 cores 0 0 0 0 temp 55000 +16:13:45 cap1600: paced 2000 ms 120 s +16:13:45 throttle counts: package 0 cores 0 0 0 0 temp 54000 +16:15:46 throttle counts: package 0 cores 0 0 0 0 temp 55000 +CPU clock limits restored +16:15:46 cool-down 60 s +every core capped at 1200 MHz ('/home/ggs/thermal-test/software/tools/thermal-sim/cap-cpu.sh restore' to undo) +16:16:46 cap1200: flat out 60 s +16:16:46 throttle counts: package 0 cores 0 0 0 0 temp 51000 +16:17:49 cap1200: paced 2000 ms 120 s +16:17:49 throttle counts: package 0 cores 0 0 0 0 temp 54000 +16:19:51 throttle counts: package 0 cores 0 0 0 0 temp 54000 +CPU clock limits restored +16:19:51 cool-down 60 s +every core capped at 800 MHz ('/home/ggs/thermal-test/software/tools/thermal-sim/cap-cpu.sh restore' to undo) +16:20:51 cap800: flat out 60 s +16:20:51 throttle counts: package 0 cores 0 0 0 0 temp 52000 +16:21:54 cap800: paced 2000 ms 120 s +16:21:54 throttle counts: package 0 cores 0 0 0 0 temp 52000 +16:23:59 throttle counts: package 0 cores 0 0 0 0 temp 53000 +CPU clock limits restored +16:23:59 cool-down 60 s +package power capped at 6 W (was 10 W; '/home/ggs/thermal-test/software/tools/thermal-sim/cap-power.sh restore' to undo) +16:24:59 power6w: flat out 60 s +16:24:59 throttle counts: package 0 cores 0 0 0 0 temp 51000 +16:26:01 power6w: paced 2000 ms 120 s +16:26:01 throttle counts: package 0 cores 0 0 0 0 temp 56000 +16:28:02 throttle counts: package 0 cores 0 0 0 0 temp 58000 +power limit restored to 10 W +16:28:02 cool-down 60 s +16:29:02 load2: 2 busy cores, paced 2000 ms 120 s +16:29:02 throttle counts: package 0 cores 0 0 0 0 temp 52000 +nothing to restore (no cap set since boot) +nothing to restore (no cap set since boot) +16:51:52 throttle counts: package 0 cores 0 0 0 0 temp 54000 +16:51:52 cool-down 60 s +every core capped at 800 MHz ('/home/ggs/thermal-test/software/tools/thermal-sim/cap-cpu.sh restore' to undo) +16:52:52 cap800+load2: paced 2000 ms 120 s +16:52:52 throttle counts: package 0 cores 0 0 0 0 temp 49000 +16:54:57 throttle counts: package 0 cores 0 0 0 0 temp 52000 +CPU clock limits restored +16:54:57 done +nothing to restore (no cap set since boot) +nothing to restore (no cap set since boot) diff --git a/tools/camera-bench/results-2026-10-08-tower111-throttle/matrix.sh b/tools/camera-bench/results-2026-10-08-tower111-throttle/matrix.sh new file mode 100644 index 0000000..aeecaea --- /dev/null +++ b/tools/camera-bench/results-2026-10-08-tower111-throttle/matrix.sh @@ -0,0 +1,78 @@ +#!/bin/sh +# Throttling matrix on tower 111 (3 Delta, N5105): the 12-bit save pipeline under +# clock caps, a power cap and competing load. Tower program stopped. ~25 min. +# nohup sh ~/thermal-test/matrix.sh > ~/thermal-test/matrix.log 2>&1 & +set -u +cd ~/fgc-bench +R=~/thermal-test/results-tower111 +O=/tmp/thermal-bench-out +B=./raw12_bench +SIM=~/thermal-test/software/tools/thermal-sim +mkdir -p "$R" + +restore() { + sudo -n "$SIM/cap-cpu.sh" restore + sudo -n "$SIM/cap-power.sh" restore + pkill -f "stress-ng --cpu" 2>/dev/null + rm -f /dev/shm/fgc_rapl_run + rm -rf "$O" +} +trap restore EXIT INT TERM + +touch /dev/shm/fgc_rapl_run +sudo -n sh -c 'while [ -e /dev/shm/fgc_rapl_run ]; do + cat /sys/class/powercap/intel-rapl:0/energy_uj > /dev/shm/fgc_rapl.tmp && + chmod 644 /dev/shm/fgc_rapl.tmp && mv /dev/shm/fgc_rapl.tmp /dev/shm/fgc_rapl + sleep 0.25; done; rm -f /dev/shm/fgc_rapl' & +export FGC_RAPL=/dev/shm/fgc_rapl +sleep 1 + +step() { echo "$(date +%T) $*"; } +counts() { + echo "$(date +%T) throttle counts: package $(cat /sys/devices/system/cpu/cpu0/thermal_throttle/package_throttle_count)" \ + "cores $(cat /sys/devices/system/cpu/cpu*/thermal_throttle/core_throttle_count | tr '\n' ' ')" \ + "temp $(cat /sys/class/thermal/thermal_zone2/temp)" +} +# label: flat-out 60 s, then one frame per 2 s for 120 s +run() { + step "$1: flat out 60 s"; counts + $B pipeline synth $O c12_ea 1 4 60 0 3 1.0 > "$R/$1_flat.jsonl" + step "$1: paced 2000 ms 120 s"; counts + $B pipeline synth $O c12_ea 1 4 120 2000 3 1.0 > "$R/$1_paced.jsonl" + counts +} +cool() { step "cool-down $1 s"; $B idle synth $O "$1" > "$R/idle_$2.jsonl"; } + +cool 60 start +run none +cool 60 a +for mhz in 1600 1200 800; do + sudo -n "$SIM/cap-cpu.sh" "$mhz" + run "cap${mhz}" + sudo -n "$SIM/cap-cpu.sh" restore + cool 60 "after${mhz}" +done +sudo -n "$SIM/cap-power.sh" 6 +run power6w +sudo -n "$SIM/cap-power.sh" restore +cool 60 afterpower + +step "load2: 2 busy cores, paced 2000 ms 120 s"; counts +stress-ng --cpu 2 --timeout 125s --quiet & +sp=$! # only this: a bare `wait` also waits for the power-reading loop, forever +sleep 2 +$B pipeline synth $O c12_ea 1 4 120 2000 3 1.0 > "$R/load2_paced.jsonl" +wait "$sp" +counts +cool 60 afterload + +sudo -n "$SIM/cap-cpu.sh" 800 +step "cap800+load2: paced 2000 ms 120 s"; counts +stress-ng --cpu 2 --timeout 125s --quiet & +sp=$! # only this: a bare `wait` also waits for the power-reading loop, forever +sleep 2 +$B pipeline synth $O c12_ea 1 4 120 2000 3 1.0 > "$R/cap800load2_paced.jsonl" +wait "$sp" +counts +sudo -n "$SIM/cap-cpu.sh" restore +step done diff --git a/tools/camera-bench/results-2026-10-08-tower111-throttle/none_flat.jsonl b/tools/camera-bench/results-2026-10-08-tower111-throttle/none_flat.jsonl new file mode 100644 index 0000000..31dafbe --- /dev/null +++ b/tools/camera-bench/results-2026-10-08-tower111-throttle/none_flat.jsonl @@ -0,0 +1,61 @@ +{"t": 1, "temp_c": 76.0, "mhz": 2848, "cpu_pct": 62.6, "watts": 8.44, "rss_mb": 535, "done": 0, "queue": 0} +{"t": 2, "temp_c": 74.0, "mhz": 2800, "cpu_pct": 61.8, "watts": 8.75, "rss_mb": 617, "done": 1, "queue": 0} +{"t": 3, "temp_c": 75.0, "mhz": 2800, "cpu_pct": 57.6, "watts": 8.43, "rss_mb": 639, "done": 2, "queue": 0} +{"t": 4, "temp_c": 76.0, "mhz": 2821, "cpu_pct": 59.0, "watts": 8.51, "rss_mb": 581, "done": 3, "queue": 0} +{"t": 5, "temp_c": 74.0, "mhz": 2800, "cpu_pct": 59.7, "watts": 8.71, "rss_mb": 686, "done": 4, "queue": 0} +{"t": 6, "temp_c": 82.0, "mhz": 2800, "cpu_pct": 68.8, "watts": 8.89, "rss_mb": 689, "done": 5, "queue": 0} +{"t": 7, "temp_c": 76.0, "mhz": 2874, "cpu_pct": 57.8, "watts": 6.11, "rss_mb": 681, "done": 6, "queue": 0} +{"t": 8, "temp_c": 70.0, "mhz": 2853, "cpu_pct": 60.6, "watts": 8.69, "rss_mb": 684, "done": 7, "queue": 0} +{"t": 9, "temp_c": 74.0, "mhz": 2831, "cpu_pct": 60.2, "watts": 8.79, "rss_mb": 688, "done": 8, "queue": 0} +{"t": 10, "temp_c": 72.0, "mhz": 2355, "cpu_pct": 60.1, "watts": 8.78, "rss_mb": 695, "done": 9, "queue": 0} +{"t": 11, "temp_c": 71.0, "mhz": 2801, "cpu_pct": 60.8, "watts": 8.76, "rss_mb": 697, "done": 10, "queue": 0} +{"t": 12, "temp_c": 75.0, "mhz": 2851, "cpu_pct": 60.5, "watts": 8.69, "rss_mb": 699, "done": 11, "queue": 0} +{"t": 13, "temp_c": 79.0, "mhz": 2500, "cpu_pct": 70.9, "watts": 9.14, "rss_mb": 701, "done": 12, "queue": 0} +{"t": 14, "temp_c": 66.0, "mhz": 1848, "cpu_pct": 59.6, "watts": 8.53, "rss_mb": 702, "done": 13, "queue": 0} +{"t": 15, "temp_c": 73.0, "mhz": 2800, "cpu_pct": 61.5, "watts": 8.77, "rss_mb": 704, "done": 14, "queue": 0} +{"t": 16, "temp_c": 79.0, "mhz": 2800, "cpu_pct": 61.6, "watts": 8.77, "rss_mb": 706, "done": 15, "queue": 0} +{"t": 17, "temp_c": 80.0, "mhz": 2800, "cpu_pct": 60.1, "watts": 8.63, "rss_mb": 706, "done": 16, "queue": 0} +{"t": 18, "temp_c": 79.0, "mhz": 2800, "cpu_pct": 61.0, "watts": 6.58, "rss_mb": 723, "done": 17, "queue": 0} +{"t": 19, "temp_c": 79.0, "mhz": 2800, "cpu_pct": 62.0, "watts": 8.73, "rss_mb": 708, "done": 18, "queue": 0} +{"t": 20, "temp_c": 77.0, "mhz": 2800, "cpu_pct": 59.8, "watts": 8.67, "rss_mb": 725, "done": 19, "queue": 0} +{"t": 21, "temp_c": 68.0, "mhz": 2346, "cpu_pct": 60.4, "watts": 8.69, "rss_mb": 727, "done": 20, "queue": 0} +{"t": 22, "temp_c": 72.0, "mhz": 2856, "cpu_pct": 59.9, "watts": 8.57, "rss_mb": 728, "done": 21, "queue": 0} +{"t": 23, "temp_c": 83.0, "mhz": 2800, "cpu_pct": 58.5, "watts": 8.44, "rss_mb": 729, "done": 22, "queue": 0} +{"t": 24, "temp_c": 84.0, "mhz": 2800, "cpu_pct": 61.8, "watts": 8.87, "rss_mb": 713, "done": 23, "queue": 0} +{"t": 25, "temp_c": 84.0, "mhz": 2800, "cpu_pct": 60.5, "watts": 8.65, "rss_mb": 713, "done": 24, "queue": 0} +{"t": 26, "temp_c": 83.0, "mhz": 2800, "cpu_pct": 60.8, "watts": 8.83, "rss_mb": 730, "done": 25, "queue": 0} +{"t": 27, "temp_c": 84.0, "mhz": 2800, "cpu_pct": 60.6, "watts": 8.71, "rss_mb": 715, "done": 26, "queue": 0} +{"t": 28, "temp_c": 86.0, "mhz": 2800, "cpu_pct": 60.7, "watts": 8.74, "rss_mb": 715, "done": 27, "queue": 0} +{"t": 29, "temp_c": 82.0, "mhz": 2800, "cpu_pct": 59.1, "watts": 8.59, "rss_mb": 715, "done": 28, "queue": 0} +{"t": 30, "temp_c": 85.0, "mhz": 2800, "cpu_pct": 62.7, "watts": 6.71, "rss_mb": 716, "done": 29, "queue": 0} +{"t": 31, "temp_c": 75.0, "mhz": 2850, "cpu_pct": 63.4, "watts": 9.06, "rss_mb": 716, "done": 30, "queue": 0} +{"t": 32, "temp_c": 84.0, "mhz": 2800, "cpu_pct": 60.5, "watts": 8.63, "rss_mb": 716, "done": 31, "queue": 0} +{"t": 33, "temp_c": 84.0, "mhz": 2800, "cpu_pct": 60.2, "watts": 8.64, "rss_mb": 716, "done": 32, "queue": 0} +{"t": 34, "temp_c": 84.0, "mhz": 2800, "cpu_pct": 60.2, "watts": 8.60, "rss_mb": 716, "done": 33, "queue": 0} +{"t": 35, "temp_c": 82.0, "mhz": 2854, "cpu_pct": 63.1, "watts": 8.86, "rss_mb": 732, "done": 34, "queue": 0} +{"t": 36, "temp_c": 79.0, "mhz": 2800, "cpu_pct": 60.2, "watts": 8.73, "rss_mb": 717, "done": 35, "queue": 0} +{"t": 37, "temp_c": 73.0, "mhz": 2804, "cpu_pct": 61.4, "watts": 8.82, "rss_mb": 717, "done": 36, "queue": 0} +{"t": 38, "temp_c": 75.0, "mhz": 2359, "cpu_pct": 60.7, "watts": 8.81, "rss_mb": 717, "done": 37, "queue": 0} +{"t": 39, "temp_c": 75.0, "mhz": 2876, "cpu_pct": 60.7, "watts": 8.87, "rss_mb": 717, "done": 38, "queue": 0} +{"t": 40, "temp_c": 76.0, "mhz": 2874, "cpu_pct": 61.1, "watts": 8.77, "rss_mb": 717, "done": 39, "queue": 0} +{"t": 41, "temp_c": 80.0, "mhz": 1841, "cpu_pct": 60.8, "watts": 6.49, "rss_mb": 717, "done": 40, "queue": 0} +{"t": 42, "temp_c": 78.0, "mhz": 2876, "cpu_pct": 60.4, "watts": 8.64, "rss_mb": 717, "done": 41, "queue": 0} +{"t": 43, "temp_c": 75.0, "mhz": 2850, "cpu_pct": 61.2, "watts": 8.78, "rss_mb": 718, "done": 42, "queue": 0} +{"t": 44, "temp_c": 80.0, "mhz": 2800, "cpu_pct": 72.2, "watts": 8.98, "rss_mb": 718, "done": 43, "queue": 0} +{"t": 45, "temp_c": 76.0, "mhz": 2800, "cpu_pct": 60.1, "watts": 8.55, "rss_mb": 734, "done": 44, "queue": 0} +{"t": 46, "temp_c": 72.0, "mhz": 2862, "cpu_pct": 61.7, "watts": 8.82, "rss_mb": 735, "done": 45, "queue": 0} +{"t": 47, "temp_c": 83.0, "mhz": 2800, "cpu_pct": 56.9, "watts": 8.28, "rss_mb": 735, "done": 46, "queue": 0} +{"t": 48, "temp_c": 84.0, "mhz": 2818, "cpu_pct": 60.3, "watts": 8.64, "rss_mb": 719, "done": 47, "queue": 0} +{"t": 49, "temp_c": 85.0, "mhz": 2800, "cpu_pct": 60.8, "watts": 8.68, "rss_mb": 717, "done": 48, "queue": 0} +{"t": 50, "temp_c": 85.0, "mhz": 2833, "cpu_pct": 60.9, "watts": 8.86, "rss_mb": 736, "done": 49, "queue": 0} +{"t": 51, "temp_c": 85.0, "mhz": 2800, "cpu_pct": 61.1, "watts": 8.66, "rss_mb": 723, "done": 50, "queue": 0} +{"t": 52, "temp_c": 80.0, "mhz": 2800, "cpu_pct": 60.1, "watts": 8.77, "rss_mb": 703, "done": 51, "queue": 0} +{"t": 53, "temp_c": 78.0, "mhz": 2800, "cpu_pct": 60.1, "watts": 6.81, "rss_mb": 691, "done": 52, "queue": 0} +{"t": 54, "temp_c": 78.0, "mhz": 2823, "cpu_pct": 61.7, "watts": 8.68, "rss_mb": 688, "done": 53, "queue": 0} +{"t": 55, "temp_c": 80.0, "mhz": 2800, "cpu_pct": 56.5, "watts": 8.64, "rss_mb": 589, "done": 54, "queue": 0} +{"t": 56, "temp_c": 80.0, "mhz": 2830, "cpu_pct": 59.9, "watts": 8.16, "rss_mb": 610, "done": 55, "queue": 0} +{"t": 57, "temp_c": 78.0, "mhz": 2872, "cpu_pct": 60.2, "watts": 8.80, "rss_mb": 608, "done": 56, "queue": 0} +{"t": 58, "temp_c": 75.0, "mhz": 2850, "cpu_pct": 60.8, "watts": 8.67, "rss_mb": 614, "done": 57, "queue": 0} +{"t": 59, "temp_c": 83.0, "mhz": 2800, "cpu_pct": 60.4, "watts": 8.71, "rss_mb": 592, "done": 58, "queue": 0} +{"t": 60, "temp_c": 61.0, "mhz": 757, "cpu_pct": 57.6, "watts": 8.60, "rss_mb": 392, "done": 59, "queue": 0} +{"summary": "pipeline", "kind": "c12_ea", "workers": 1, "threads": 4, "effort": 3, "distance": 1.00, "period_ms": 0, "secs": 60.0, "frames": 59, "frames_per_s": 0.983, "job_ms_p50": 994, "latency_ms_p50": 1009, "latency_ms_p95": 1094, "latency_ms_max": 1126, "max_queue": 0, "backlog_at_end": 0, "cpu_s_per_frame": 2.331, "peak_rss_mb": 736, "temp_c_mean": 78.0, "temp_c_max": 86.0, "mhz_mean": 2724, "mhz_min": 757, "cpu_pct_mean": 60.9, "watts_mean": 8.52} diff --git a/tools/camera-bench/results-2026-10-08-tower111-throttle/none_paced.jsonl b/tools/camera-bench/results-2026-10-08-tower111-throttle/none_paced.jsonl new file mode 100644 index 0000000..b6caa13 --- /dev/null +++ b/tools/camera-bench/results-2026-10-08-tower111-throttle/none_paced.jsonl @@ -0,0 +1,121 @@ +{"t": 1, "temp_c": 80.0, "mhz": 2824, "cpu_pct": 62.4, "watts": 8.60, "rss_mb": 535, "done": 0, "queue": 0} +{"t": 2, "temp_c": 57.0, "mhz": 2805, "cpu_pct": 7.0, "watts": 3.96, "rss_mb": 247, "done": 1, "queue": 0} +{"t": 3, "temp_c": 66.0, "mhz": 2398, "cpu_pct": 61.0, "watts": 8.51, "rss_mb": 376, "done": 1, "queue": 0} +{"t": 4, "temp_c": 56.0, "mhz": 800, "cpu_pct": 6.1, "watts": 3.49, "rss_mb": 283, "done": 2, "queue": 0} +{"t": 5, "temp_c": 71.0, "mhz": 2801, "cpu_pct": 61.5, "watts": 8.65, "rss_mb": 397, "done": 2, "queue": 0} +{"t": 6, "temp_c": 56.0, "mhz": 800, "cpu_pct": 4.8, "watts": 2.27, "rss_mb": 304, "done": 3, "queue": 0} +{"t": 7, "temp_c": 77.0, "mhz": 2828, "cpu_pct": 61.6, "watts": 7.53, "rss_mb": 580, "done": 3, "queue": 0} +{"t": 8, "temp_c": 56.0, "mhz": 2700, "cpu_pct": 5.8, "watts": 4.39, "rss_mb": 395, "done": 4, "queue": 0} +{"t": 9, "temp_c": 63.0, "mhz": 1427, "cpu_pct": 60.5, "watts": 7.96, "rss_mb": 543, "done": 4, "queue": 0} +{"t": 10, "temp_c": 56.0, "mhz": 1135, "cpu_pct": 4.3, "watts": 3.73, "rss_mb": 450, "done": 5, "queue": 0} +{"t": 11, "temp_c": 63.0, "mhz": 961, "cpu_pct": 61.0, "watts": 8.18, "rss_mb": 517, "done": 5, "queue": 0} +{"t": 12, "temp_c": 55.0, "mhz": 782, "cpu_pct": 5.3, "watts": 3.37, "rss_mb": 462, "done": 6, "queue": 0} +{"t": 13, "temp_c": 65.0, "mhz": 2225, "cpu_pct": 61.7, "watts": 8.44, "rss_mb": 560, "done": 6, "queue": 0} +{"t": 14, "temp_c": 57.0, "mhz": 800, "cpu_pct": 21.2, "watts": 4.37, "rss_mb": 467, "done": 7, "queue": 0} +{"t": 15, "temp_c": 65.0, "mhz": 2900, "cpu_pct": 62.1, "watts": 6.43, "rss_mb": 562, "done": 7, "queue": 0} +{"t": 16, "temp_c": 56.0, "mhz": 2350, "cpu_pct": 6.0, "watts": 4.45, "rss_mb": 470, "done": 8, "queue": 0} +{"t": 17, "temp_c": 63.0, "mhz": 1287, "cpu_pct": 60.7, "watts": 7.77, "rss_mb": 473, "done": 9, "queue": 0} +{"t": 18, "temp_c": 56.0, "mhz": 800, "cpu_pct": 4.3, "watts": 3.61, "rss_mb": 473, "done": 9, "queue": 0} +{"t": 19, "temp_c": 64.0, "mhz": 1260, "cpu_pct": 60.6, "watts": 8.03, "rss_mb": 478, "done": 9, "queue": 0} +{"t": 20, "temp_c": 55.0, "mhz": 820, "cpu_pct": 3.5, "watts": 3.44, "rss_mb": 478, "done": 10, "queue": 0} +{"t": 21, "temp_c": 63.0, "mhz": 964, "cpu_pct": 60.9, "watts": 8.29, "rss_mb": 561, "done": 10, "queue": 0} +{"t": 22, "temp_c": 58.0, "mhz": 2025, "cpu_pct": 7.3, "watts": 3.33, "rss_mb": 479, "done": 11, "queue": 0} +{"t": 23, "temp_c": 63.0, "mhz": 1983, "cpu_pct": 60.6, "watts": 8.44, "rss_mb": 480, "done": 12, "queue": 0} +{"t": 24, "temp_c": 57.0, "mhz": 2801, "cpu_pct": 5.0, "watts": 3.12, "rss_mb": 480, "done": 12, "queue": 0} +{"t": 25, "temp_c": 63.0, "mhz": 1899, "cpu_pct": 60.3, "watts": 6.72, "rss_mb": 481, "done": 13, "queue": 0} +{"t": 26, "temp_c": 57.0, "mhz": 2800, "cpu_pct": 3.8, "watts": 3.97, "rss_mb": 490, "done": 13, "queue": 0} +{"t": 27, "temp_c": 61.0, "mhz": 988, "cpu_pct": 59.7, "watts": 7.83, "rss_mb": 484, "done": 14, "queue": 0} +{"t": 28, "temp_c": 55.0, "mhz": 1757, "cpu_pct": 5.1, "watts": 3.63, "rss_mb": 484, "done": 14, "queue": 0} +{"t": 29, "temp_c": 63.0, "mhz": 1505, "cpu_pct": 60.5, "watts": 8.21, "rss_mb": 485, "done": 15, "queue": 0} +{"t": 30, "temp_c": 56.0, "mhz": 1597, "cpu_pct": 4.8, "watts": 3.31, "rss_mb": 485, "done": 15, "queue": 0} +{"t": 31, "temp_c": 62.0, "mhz": 895, "cpu_pct": 60.2, "watts": 8.52, "rss_mb": 541, "done": 15, "queue": 0} +{"t": 32, "temp_c": 68.0, "mhz": 2800, "cpu_pct": 5.3, "watts": 2.99, "rss_mb": 509, "done": 16, "queue": 0} +{"t": 33, "temp_c": 60.0, "mhz": 1018, "cpu_pct": 58.5, "watts": 6.76, "rss_mb": 487, "done": 17, "queue": 0} +{"t": 34, "temp_c": 65.0, "mhz": 2800, "cpu_pct": 6.1, "watts": 4.11, "rss_mb": 519, "done": 17, "queue": 0} +{"t": 35, "temp_c": 61.0, "mhz": 1461, "cpu_pct": 59.6, "watts": 7.82, "rss_mb": 489, "done": 18, "queue": 0} +{"t": 36, "temp_c": 58.0, "mhz": 2800, "cpu_pct": 5.5, "watts": 3.75, "rss_mb": 503, "done": 18, "queue": 0} +{"t": 37, "temp_c": 61.0, "mhz": 1150, "cpu_pct": 59.9, "watts": 8.21, "rss_mb": 490, "done": 19, "queue": 0} +{"t": 38, "temp_c": 71.0, "mhz": 2800, "cpu_pct": 6.3, "watts": 3.14, "rss_mb": 541, "done": 19, "queue": 0} +{"t": 39, "temp_c": 61.0, "mhz": 770, "cpu_pct": 57.7, "watts": 8.57, "rss_mb": 490, "done": 20, "queue": 0} +{"t": 40, "temp_c": 62.0, "mhz": 2800, "cpu_pct": 7.3, "watts": 3.22, "rss_mb": 515, "done": 20, "queue": 0} +{"t": 41, "temp_c": 61.0, "mhz": 911, "cpu_pct": 59.5, "watts": 6.74, "rss_mb": 490, "done": 21, "queue": 0} +{"t": 42, "temp_c": 64.0, "mhz": 2800, "cpu_pct": 5.8, "watts": 4.02, "rss_mb": 523, "done": 21, "queue": 0} +{"t": 43, "temp_c": 62.0, "mhz": 1504, "cpu_pct": 60.1, "watts": 7.78, "rss_mb": 490, "done": 22, "queue": 0} +{"t": 44, "temp_c": 74.0, "mhz": 2800, "cpu_pct": 9.6, "watts": 3.88, "rss_mb": 568, "done": 22, "queue": 0} +{"t": 45, "temp_c": 75.0, "mhz": 2851, "cpu_pct": 73.9, "watts": 8.89, "rss_mb": 725, "done": 22, "queue": 0} +{"t": 46, "temp_c": 68.0, "mhz": 2800, "cpu_pct": 8.5, "watts": 4.03, "rss_mb": 542, "done": 23, "queue": 0} +{"t": 47, "temp_c": 61.0, "mhz": 897, "cpu_pct": 58.5, "watts": 8.44, "rss_mb": 492, "done": 24, "queue": 0} +{"t": 48, "temp_c": 70.0, "mhz": 2800, "cpu_pct": 6.8, "watts": 3.07, "rss_mb": 554, "done": 24, "queue": 0} +{"t": 49, "temp_c": 61.0, "mhz": 801, "cpu_pct": 58.5, "watts": 8.55, "rss_mb": 492, "done": 25, "queue": 0} +{"t": 50, "temp_c": 71.0, "mhz": 2823, "cpu_pct": 8.0, "watts": 2.18, "rss_mb": 592, "done": 25, "queue": 0} +{"t": 51, "temp_c": 61.0, "mhz": 859, "cpu_pct": 56.5, "watts": 7.96, "rss_mb": 493, "done": 26, "queue": 0} +{"t": 52, "temp_c": 71.0, "mhz": 2800, "cpu_pct": 7.0, "watts": 3.76, "rss_mb": 568, "done": 26, "queue": 0} +{"t": 53, "temp_c": 60.0, "mhz": 822, "cpu_pct": 57.4, "watts": 8.01, "rss_mb": 587, "done": 26, "queue": 0} +{"t": 54, "temp_c": 72.0, "mhz": 2800, "cpu_pct": 8.3, "watts": 3.49, "rss_mb": 582, "done": 27, "queue": 0} +{"t": 55, "temp_c": 60.0, "mhz": 797, "cpu_pct": 58.4, "watts": 8.50, "rss_mb": 587, "done": 27, "queue": 0} +{"t": 56, "temp_c": 72.0, "mhz": 2846, "cpu_pct": 8.6, "watts": 3.12, "rss_mb": 597, "done": 28, "queue": 0} +{"t": 57, "temp_c": 60.0, "mhz": 2797, "cpu_pct": 57.9, "watts": 8.48, "rss_mb": 495, "done": 29, "queue": 0} +{"t": 58, "temp_c": 66.0, "mhz": 2801, "cpu_pct": 8.3, "watts": 2.35, "rss_mb": 606, "done": 29, "queue": 0} +{"t": 59, "temp_c": 59.0, "mhz": 926, "cpu_pct": 56.4, "watts": 7.81, "rss_mb": 495, "done": 30, "queue": 0} +{"t": 60, "temp_c": 73.0, "mhz": 2825, "cpu_pct": 8.3, "watts": 3.74, "rss_mb": 591, "done": 30, "queue": 0} +{"t": 61, "temp_c": 60.0, "mhz": 791, "cpu_pct": 57.4, "watts": 8.06, "rss_mb": 496, "done": 31, "queue": 0} +{"t": 62, "temp_c": 70.0, "mhz": 2875, "cpu_pct": 7.8, "watts": 3.28, "rss_mb": 596, "done": 31, "queue": 0} +{"t": 63, "temp_c": 61.0, "mhz": 1015, "cpu_pct": 57.2, "watts": 8.50, "rss_mb": 496, "done": 32, "queue": 0} +{"t": 64, "temp_c": 70.0, "mhz": 2850, "cpu_pct": 8.0, "watts": 3.16, "rss_mb": 604, "done": 32, "queue": 0} +{"t": 65, "temp_c": 60.0, "mhz": 763, "cpu_pct": 57.2, "watts": 8.50, "rss_mb": 496, "done": 33, "queue": 0} +{"t": 66, "temp_c": 69.0, "mhz": 2832, "cpu_pct": 8.1, "watts": 3.24, "rss_mb": 598, "done": 33, "queue": 0} +{"t": 67, "temp_c": 60.0, "mhz": 781, "cpu_pct": 57.0, "watts": 6.83, "rss_mb": 496, "done": 34, "queue": 0} +{"t": 68, "temp_c": 70.0, "mhz": 2868, "cpu_pct": 8.3, "watts": 3.68, "rss_mb": 603, "done": 34, "queue": 0} +{"t": 69, "temp_c": 60.0, "mhz": 801, "cpu_pct": 56.8, "watts": 8.06, "rss_mb": 496, "done": 35, "queue": 0} +{"t": 70, "temp_c": 71.0, "mhz": 2846, "cpu_pct": 9.8, "watts": 3.73, "rss_mb": 596, "done": 35, "queue": 0} +{"t": 71, "temp_c": 59.0, "mhz": 870, "cpu_pct": 56.6, "watts": 8.29, "rss_mb": 496, "done": 36, "queue": 0} +{"t": 72, "temp_c": 66.0, "mhz": 2833, "cpu_pct": 7.8, "watts": 3.19, "rss_mb": 608, "done": 36, "queue": 0} +{"t": 73, "temp_c": 60.0, "mhz": 819, "cpu_pct": 57.3, "watts": 8.50, "rss_mb": 497, "done": 37, "queue": 0} +{"t": 74, "temp_c": 69.0, "mhz": 2868, "cpu_pct": 8.6, "watts": 3.09, "rss_mb": 610, "done": 37, "queue": 0} +{"t": 75, "temp_c": 66.0, "mhz": 2819, "cpu_pct": 64.2, "watts": 9.07, "rss_mb": 497, "done": 38, "queue": 0} +{"t": 76, "temp_c": 71.0, "mhz": 2800, "cpu_pct": 15.7, "watts": 3.92, "rss_mb": 609, "done": 38, "queue": 0} +{"t": 77, "temp_c": 59.0, "mhz": 744, "cpu_pct": 56.8, "watts": 6.68, "rss_mb": 497, "done": 39, "queue": 0} +{"t": 78, "temp_c": 66.0, "mhz": 2852, "cpu_pct": 8.8, "watts": 3.74, "rss_mb": 614, "done": 39, "queue": 0} +{"t": 79, "temp_c": 60.0, "mhz": 827, "cpu_pct": 57.5, "watts": 8.15, "rss_mb": 497, "done": 40, "queue": 0} +{"t": 80, "temp_c": 64.0, "mhz": 2850, "cpu_pct": 8.1, "watts": 3.40, "rss_mb": 619, "done": 40, "queue": 0} +{"t": 81, "temp_c": 59.0, "mhz": 813, "cpu_pct": 56.8, "watts": 8.41, "rss_mb": 497, "done": 41, "queue": 0} +{"t": 82, "temp_c": 69.0, "mhz": 2800, "cpu_pct": 9.1, "watts": 3.12, "rss_mb": 634, "done": 41, "queue": 0} +{"t": 83, "temp_c": 59.0, "mhz": 758, "cpu_pct": 55.6, "watts": 8.52, "rss_mb": 497, "done": 42, "queue": 0} +{"t": 84, "temp_c": 63.0, "mhz": 2847, "cpu_pct": 8.5, "watts": 3.40, "rss_mb": 642, "done": 42, "queue": 0} +{"t": 85, "temp_c": 60.0, "mhz": 778, "cpu_pct": 57.0, "watts": 6.78, "rss_mb": 497, "done": 43, "queue": 0} +{"t": 86, "temp_c": 63.0, "mhz": 2841, "cpu_pct": 9.0, "watts": 3.58, "rss_mb": 627, "done": 43, "queue": 0} +{"t": 87, "temp_c": 59.0, "mhz": 859, "cpu_pct": 57.6, "watts": 7.97, "rss_mb": 498, "done": 44, "queue": 0} +{"t": 88, "temp_c": 70.0, "mhz": 2849, "cpu_pct": 10.4, "watts": 3.53, "rss_mb": 620, "done": 44, "queue": 0} +{"t": 89, "temp_c": 60.0, "mhz": 927, "cpu_pct": 56.4, "watts": 8.37, "rss_mb": 499, "done": 45, "queue": 0} +{"t": 90, "temp_c": 62.0, "mhz": 2375, "cpu_pct": 8.8, "watts": 3.21, "rss_mb": 637, "done": 45, "queue": 0} +{"t": 91, "temp_c": 60.0, "mhz": 795, "cpu_pct": 57.9, "watts": 8.64, "rss_mb": 499, "done": 46, "queue": 0} +{"t": 92, "temp_c": 63.0, "mhz": 2375, "cpu_pct": 8.6, "watts": 3.36, "rss_mb": 633, "done": 46, "queue": 0} +{"t": 93, "temp_c": 60.0, "mhz": 800, "cpu_pct": 57.9, "watts": 6.65, "rss_mb": 500, "done": 47, "queue": 0} +{"t": 94, "temp_c": 64.0, "mhz": 2864, "cpu_pct": 9.1, "watts": 3.88, "rss_mb": 616, "done": 47, "queue": 0} +{"t": 95, "temp_c": 59.0, "mhz": 952, "cpu_pct": 56.4, "watts": 7.98, "rss_mb": 500, "done": 48, "queue": 0} +{"t": 96, "temp_c": 65.0, "mhz": 1850, "cpu_pct": 8.6, "watts": 3.57, "rss_mb": 641, "done": 48, "queue": 0} +{"t": 97, "temp_c": 59.0, "mhz": 836, "cpu_pct": 56.9, "watts": 8.34, "rss_mb": 501, "done": 49, "queue": 0} +{"t": 98, "temp_c": 63.0, "mhz": 1850, "cpu_pct": 9.6, "watts": 3.17, "rss_mb": 652, "done": 49, "queue": 0} +{"t": 99, "temp_c": 59.0, "mhz": 800, "cpu_pct": 56.4, "watts": 8.57, "rss_mb": 501, "done": 50, "queue": 0} +{"t": 100, "temp_c": 65.0, "mhz": 2824, "cpu_pct": 10.1, "watts": 3.21, "rss_mb": 653, "done": 50, "queue": 0} +{"t": 101, "temp_c": 59.0, "mhz": 800, "cpu_pct": 55.9, "watts": 8.28, "rss_mb": 501, "done": 51, "queue": 0} +{"t": 102, "temp_c": 61.0, "mhz": 2366, "cpu_pct": 9.1, "watts": 2.33, "rss_mb": 649, "done": 51, "queue": 0} +{"t": 103, "temp_c": 59.0, "mhz": 780, "cpu_pct": 56.8, "watts": 7.96, "rss_mb": 501, "done": 52, "queue": 0} +{"t": 104, "temp_c": 66.0, "mhz": 2367, "cpu_pct": 9.3, "watts": 3.59, "rss_mb": 653, "done": 52, "queue": 0} +{"t": 105, "temp_c": 59.0, "mhz": 768, "cpu_pct": 56.4, "watts": 8.32, "rss_mb": 501, "done": 53, "queue": 0} +{"t": 106, "temp_c": 76.0, "mhz": 2800, "cpu_pct": 28.1, "watts": 4.55, "rss_mb": 650, "done": 53, "queue": 0} +{"t": 107, "temp_c": 59.0, "mhz": 800, "cpu_pct": 56.3, "watts": 8.80, "rss_mb": 501, "done": 54, "queue": 0} +{"t": 108, "temp_c": 65.0, "mhz": 2301, "cpu_pct": 8.8, "watts": 3.16, "rss_mb": 657, "done": 54, "queue": 0} +{"t": 109, "temp_c": 60.0, "mhz": 780, "cpu_pct": 56.5, "watts": 8.40, "rss_mb": 502, "done": 55, "queue": 0} +{"t": 110, "temp_c": 62.0, "mhz": 2833, "cpu_pct": 8.6, "watts": 3.42, "rss_mb": 661, "done": 55, "queue": 0} +{"t": 111, "temp_c": 59.0, "mhz": 793, "cpu_pct": 56.0, "watts": 6.64, "rss_mb": 502, "done": 56, "queue": 0} +{"t": 112, "temp_c": 64.0, "mhz": 2324, "cpu_pct": 10.4, "watts": 3.61, "rss_mb": 652, "done": 56, "queue": 0} +{"t": 113, "temp_c": 59.0, "mhz": 776, "cpu_pct": 55.6, "watts": 8.26, "rss_mb": 502, "done": 57, "queue": 0} +{"t": 114, "temp_c": 63.0, "mhz": 2343, "cpu_pct": 8.8, "watts": 3.27, "rss_mb": 670, "done": 57, "queue": 0} +{"t": 115, "temp_c": 59.0, "mhz": 800, "cpu_pct": 56.6, "watts": 8.56, "rss_mb": 502, "done": 58, "queue": 0} +{"t": 116, "temp_c": 65.0, "mhz": 2859, "cpu_pct": 9.3, "watts": 3.13, "rss_mb": 669, "done": 58, "queue": 0} +{"t": 117, "temp_c": 59.0, "mhz": 875, "cpu_pct": 57.0, "watts": 8.51, "rss_mb": 502, "done": 59, "queue": 0} +{"t": 118, "temp_c": 62.0, "mhz": 2879, "cpu_pct": 10.1, "watts": 3.42, "rss_mb": 663, "done": 59, "queue": 0} +{"t": 119, "temp_c": 59.0, "mhz": 800, "cpu_pct": 56.2, "watts": 6.63, "rss_mb": 502, "done": 60, "queue": 0} +{"t": 120, "temp_c": 55.0, "mhz": 800, "cpu_pct": 4.5, "watts": 3.68, "rss_mb": 390, "done": 60, "queue": 0} +{"summary": "pipeline", "kind": "c12_ea", "workers": 1, "threads": 4, "effort": 3, "distance": 1.00, "period_ms": 2000, "secs": 120.0, "frames": 60, "frames_per_s": 0.500, "job_ms_p50": 1008, "latency_ms_p50": 1021, "latency_ms_p95": 1094, "latency_ms_max": 1140, "max_queue": 1, "backlog_at_end": 0, "cpu_s_per_frame": 2.343, "peak_rss_mb": 735, "temp_c_mean": 62.9, "temp_c_max": 80.0, "mhz_mean": 1816, "mhz_min": 744, "cpu_pct_mean": 33.4, "watts_mean": 5.75} diff --git a/tools/camera-bench/results-2026-10-08-tower111-throttle/power6w_flat.jsonl b/tools/camera-bench/results-2026-10-08-tower111-throttle/power6w_flat.jsonl new file mode 100644 index 0000000..78aa7f9 --- /dev/null +++ b/tools/camera-bench/results-2026-10-08-tower111-throttle/power6w_flat.jsonl @@ -0,0 +1,62 @@ +{"t": 1, "temp_c": 70.0, "mhz": 2803, "cpu_pct": 62.3, "watts": 8.25, "rss_mb": 536, "done": 0, "queue": 0} +{"t": 2, "temp_c": 72.0, "mhz": 2835, "cpu_pct": 60.7, "watts": 8.51, "rss_mb": 620, "done": 1, "queue": 0} +{"t": 3, "temp_c": 65.0, "mhz": 2376, "cpu_pct": 59.8, "watts": 8.64, "rss_mb": 633, "done": 2, "queue": 0} +{"t": 4, "temp_c": 76.0, "mhz": 2800, "cpu_pct": 59.4, "watts": 8.45, "rss_mb": 575, "done": 3, "queue": 0} +{"t": 5, "temp_c": 76.0, "mhz": 2800, "cpu_pct": 59.8, "watts": 8.59, "rss_mb": 681, "done": 4, "queue": 0} +{"t": 6, "temp_c": 76.0, "mhz": 2800, "cpu_pct": 60.7, "watts": 8.72, "rss_mb": 689, "done": 5, "queue": 0} +{"t": 7, "temp_c": 71.0, "mhz": 2800, "cpu_pct": 60.0, "watts": 8.68, "rss_mb": 681, "done": 6, "queue": 0} +{"t": 8, "temp_c": 69.0, "mhz": 2369, "cpu_pct": 60.2, "watts": 8.58, "rss_mb": 689, "done": 7, "queue": 0} +{"t": 9, "temp_c": 72.0, "mhz": 2874, "cpu_pct": 60.3, "watts": 8.77, "rss_mb": 690, "done": 8, "queue": 0} +{"t": 10, "temp_c": 77.0, "mhz": 2800, "cpu_pct": 61.7, "watts": 8.66, "rss_mb": 692, "done": 9, "queue": 0} +{"t": 11, "temp_c": 73.0, "mhz": 2798, "cpu_pct": 61.4, "watts": 6.51, "rss_mb": 697, "done": 10, "queue": 0} +{"t": 12, "temp_c": 77.0, "mhz": 2848, "cpu_pct": 61.1, "watts": 8.87, "rss_mb": 698, "done": 11, "queue": 0} +{"t": 13, "temp_c": 80.0, "mhz": 2800, "cpu_pct": 61.1, "watts": 8.74, "rss_mb": 700, "done": 12, "queue": 0} +{"t": 14, "temp_c": 69.0, "mhz": 2400, "cpu_pct": 59.6, "watts": 8.67, "rss_mb": 702, "done": 13, "queue": 0} +{"t": 15, "temp_c": 74.0, "mhz": 2800, "cpu_pct": 60.5, "watts": 8.54, "rss_mb": 703, "done": 14, "queue": 0} +{"t": 16, "temp_c": 78.0, "mhz": 2800, "cpu_pct": 61.2, "watts": 8.76, "rss_mb": 704, "done": 15, "queue": 0} +{"t": 17, "temp_c": 66.0, "mhz": 1325, "cpu_pct": 57.4, "watts": 8.54, "rss_mb": 704, "done": 16, "queue": 0} +{"t": 18, "temp_c": 70.0, "mhz": 2807, "cpu_pct": 60.7, "watts": 8.80, "rss_mb": 721, "done": 17, "queue": 0} +{"t": 19, "temp_c": 71.0, "mhz": 2867, "cpu_pct": 60.7, "watts": 8.75, "rss_mb": 705, "done": 18, "queue": 0} +{"t": 20, "temp_c": 76.0, "mhz": 2800, "cpu_pct": 60.2, "watts": 8.59, "rss_mb": 722, "done": 19, "queue": 0} +{"t": 21, "temp_c": 71.0, "mhz": 2867, "cpu_pct": 61.0, "watts": 8.77, "rss_mb": 724, "done": 20, "queue": 0} +{"t": 22, "temp_c": 71.0, "mhz": 2837, "cpu_pct": 60.5, "watts": 8.74, "rss_mb": 725, "done": 21, "queue": 0} +{"t": 23, "temp_c": 77.0, "mhz": 2800, "cpu_pct": 61.7, "watts": 6.56, "rss_mb": 725, "done": 22, "queue": 0} +{"t": 24, "temp_c": 76.0, "mhz": 2800, "cpu_pct": 60.9, "watts": 8.86, "rss_mb": 710, "done": 23, "queue": 0} +{"t": 25, "temp_c": 75.0, "mhz": 2800, "cpu_pct": 61.0, "watts": 8.67, "rss_mb": 710, "done": 24, "queue": 0} +{"t": 26, "temp_c": 78.0, "mhz": 2800, "cpu_pct": 70.9, "watts": 9.28, "rss_mb": 728, "done": 25, "queue": 0} +{"t": 27, "temp_c": 78.0, "mhz": 2800, "cpu_pct": 60.5, "watts": 8.68, "rss_mb": 712, "done": 26, "queue": 0} +{"t": 28, "temp_c": 77.0, "mhz": 2800, "cpu_pct": 60.3, "watts": 8.72, "rss_mb": 714, "done": 27, "queue": 0} +{"t": 29, "temp_c": 77.0, "mhz": 2800, "cpu_pct": 61.5, "watts": 8.90, "rss_mb": 714, "done": 28, "queue": 0} +{"t": 30, "temp_c": 77.0, "mhz": 2800, "cpu_pct": 60.4, "watts": 8.83, "rss_mb": 714, "done": 29, "queue": 0} +{"t": 31, "temp_c": 80.0, "mhz": 2812, "cpu_pct": 59.9, "watts": 8.63, "rss_mb": 715, "done": 30, "queue": 0} +{"t": 32, "temp_c": 78.0, "mhz": 2800, "cpu_pct": 61.8, "watts": 8.76, "rss_mb": 715, "done": 31, "queue": 0} +{"t": 33, "temp_c": 73.0, "mhz": 2825, "cpu_pct": 58.3, "watts": 8.44, "rss_mb": 715, "done": 32, "queue": 0} +{"t": 34, "temp_c": 74.0, "mhz": 2800, "cpu_pct": 60.4, "watts": 8.55, "rss_mb": 716, "done": 33, "queue": 0} +{"t": 35, "temp_c": 71.0, "mhz": 2894, "cpu_pct": 61.5, "watts": 6.49, "rss_mb": 732, "done": 34, "queue": 0} +{"t": 36, "temp_c": 81.0, "mhz": 2874, "cpu_pct": 60.2, "watts": 8.64, "rss_mb": 716, "done": 35, "queue": 0} +{"t": 37, "temp_c": 82.0, "mhz": 2868, "cpu_pct": 60.9, "watts": 8.85, "rss_mb": 717, "done": 36, "queue": 0} +{"t": 38, "temp_c": 81.0, "mhz": 2800, "cpu_pct": 61.0, "watts": 8.88, "rss_mb": 717, "done": 37, "queue": 0} +{"t": 39, "temp_c": 60.0, "mhz": 1700, "cpu_pct": 61.2, "watts": 7.48, "rss_mb": 717, "done": 38, "queue": 0} +{"t": 40, "temp_c": 59.0, "mhz": 1800, "cpu_pct": 58.8, "watts": 6.06, "rss_mb": 685, "done": 39, "queue": 0} +{"t": 41, "temp_c": 59.0, "mhz": 1800, "cpu_pct": 66.5, "watts": 5.80, "rss_mb": 717, "done": 39, "queue": 0} +{"t": 42, "temp_c": 59.0, "mhz": 1500, "cpu_pct": 60.5, "watts": 5.78, "rss_mb": 718, "done": 40, "queue": 0} +{"t": 43, "temp_c": 58.0, "mhz": 1800, "cpu_pct": 60.2, "watts": 5.61, "rss_mb": 686, "done": 41, "queue": 0} +{"t": 44, "temp_c": 59.0, "mhz": 1800, "cpu_pct": 67.2, "watts": 5.78, "rss_mb": 718, "done": 41, "queue": 0} +{"t": 45, "temp_c": 59.0, "mhz": 1800, "cpu_pct": 60.8, "watts": 4.23, "rss_mb": 718, "done": 42, "queue": 0} +{"t": 46, "temp_c": 59.0, "mhz": 1800, "cpu_pct": 59.6, "watts": 5.78, "rss_mb": 686, "done": 43, "queue": 0} +{"t": 47, "temp_c": 59.0, "mhz": 1900, "cpu_pct": 67.1, "watts": 6.01, "rss_mb": 718, "done": 43, "queue": 0} +{"t": 48, "temp_c": 58.0, "mhz": 1800, "cpu_pct": 59.9, "watts": 6.00, "rss_mb": 734, "done": 44, "queue": 0} +{"t": 49, "temp_c": 59.0, "mhz": 1800, "cpu_pct": 60.3, "watts": 5.83, "rss_mb": 688, "done": 45, "queue": 0} +{"t": 50, "temp_c": 59.0, "mhz": 1900, "cpu_pct": 65.8, "watts": 5.98, "rss_mb": 735, "done": 45, "queue": 0} +{"t": 51, "temp_c": 58.0, "mhz": 1624, "cpu_pct": 57.7, "watts": 5.98, "rss_mb": 736, "done": 46, "queue": 0} +{"t": 52, "temp_c": 59.0, "mhz": 1785, "cpu_pct": 62.8, "watts": 6.06, "rss_mb": 720, "done": 47, "queue": 0} +{"t": 53, "temp_c": 58.0, "mhz": 1900, "cpu_pct": 64.3, "watts": 4.53, "rss_mb": 720, "done": 47, "queue": 0} +{"t": 54, "temp_c": 59.0, "mhz": 1900, "cpu_pct": 60.4, "watts": 6.05, "rss_mb": 736, "done": 48, "queue": 0} +{"t": 55, "temp_c": 59.0, "mhz": 1800, "cpu_pct": 61.7, "watts": 6.02, "rss_mb": 736, "done": 49, "queue": 0} +{"t": 56, "temp_c": 60.0, "mhz": 1800, "cpu_pct": 69.3, "watts": 6.12, "rss_mb": 736, "done": 49, "queue": 0} +{"t": 57, "temp_c": 58.0, "mhz": 1025, "cpu_pct": 76.2, "watts": 6.44, "rss_mb": 737, "done": 50, "queue": 0} +{"t": 58, "temp_c": 58.0, "mhz": 1900, "cpu_pct": 59.0, "watts": 5.69, "rss_mb": 689, "done": 51, "queue": 0} +{"t": 59, "temp_c": 59.0, "mhz": 1800, "cpu_pct": 68.7, "watts": 6.05, "rss_mb": 737, "done": 51, "queue": 0} +{"t": 60, "temp_c": 58.0, "mhz": 1900, "cpu_pct": 58.4, "watts": 6.07, "rss_mb": 737, "done": 52, "queue": 0} +{"t": 61, "temp_c": 55.0, "mhz": 797, "cpu_pct": 48.0, "watts": 4.43, "rss_mb": 391, "done": 53, "queue": 0} +{"summary": "pipeline", "kind": "c12_ea", "workers": 1, "threads": 4, "effort": 3, "distance": 1.00, "period_ms": 0, "secs": 60.8, "frames": 53, "frames_per_s": 0.871, "job_ms_p50": 1006, "latency_ms_p50": 1019, "latency_ms_p95": 1519, "latency_ms_max": 1660, "max_queue": 0, "backlog_at_end": 0, "cpu_s_per_frame": 2.651, "peak_rss_mb": 737, "temp_c_mean": 68.6, "temp_c_max": 82.0, "mhz_mean": 2359, "mhz_min": 797, "cpu_pct_mean": 61.4, "watts_mean": 7.50} diff --git a/tools/camera-bench/results-2026-10-08-tower111-throttle/power6w_paced.jsonl b/tools/camera-bench/results-2026-10-08-tower111-throttle/power6w_paced.jsonl new file mode 100644 index 0000000..d1b15f0 --- /dev/null +++ b/tools/camera-bench/results-2026-10-08-tower111-throttle/power6w_paced.jsonl @@ -0,0 +1,121 @@ +{"t": 1, "temp_c": 60.0, "mhz": 2099, "cpu_pct": 61.9, "watts": 6.70, "rss_mb": 533, "done": 0, "queue": 0} +{"t": 2, "temp_c": 54.0, "mhz": 771, "cpu_pct": 23.0, "watts": 4.74, "rss_mb": 247, "done": 1, "queue": 0} +{"t": 3, "temp_c": 61.0, "mhz": 2300, "cpu_pct": 62.3, "watts": 7.22, "rss_mb": 613, "done": 1, "queue": 0} +{"t": 4, "temp_c": 54.0, "mhz": 800, "cpu_pct": 11.6, "watts": 3.98, "rss_mb": 282, "done": 2, "queue": 0} +{"t": 5, "temp_c": 67.0, "mhz": 2500, "cpu_pct": 62.6, "watts": 8.10, "rss_mb": 628, "done": 2, "queue": 0} +{"t": 6, "temp_c": 55.0, "mhz": 2847, "cpu_pct": 6.8, "watts": 3.31, "rss_mb": 297, "done": 3, "queue": 0} +{"t": 7, "temp_c": 71.0, "mhz": 2700, "cpu_pct": 60.6, "watts": 6.62, "rss_mb": 570, "done": 3, "queue": 0} +{"t": 8, "temp_c": 54.0, "mhz": 775, "cpu_pct": 5.3, "watts": 4.47, "rss_mb": 385, "done": 4, "queue": 0} +{"t": 9, "temp_c": 64.0, "mhz": 2017, "cpu_pct": 61.8, "watts": 7.52, "rss_mb": 534, "done": 4, "queue": 0} +{"t": 10, "temp_c": 54.0, "mhz": 795, "cpu_pct": 4.8, "watts": 4.12, "rss_mb": 442, "done": 5, "queue": 0} +{"t": 11, "temp_c": 63.0, "mhz": 1780, "cpu_pct": 61.2, "watts": 7.88, "rss_mb": 544, "done": 5, "queue": 0} +{"t": 12, "temp_c": 55.0, "mhz": 2800, "cpu_pct": 5.8, "watts": 3.56, "rss_mb": 452, "done": 6, "queue": 0} +{"t": 13, "temp_c": 61.0, "mhz": 1853, "cpu_pct": 59.2, "watts": 8.49, "rss_mb": 462, "done": 7, "queue": 0} +{"t": 14, "temp_c": 55.0, "mhz": 911, "cpu_pct": 4.5, "watts": 3.11, "rss_mb": 462, "done": 7, "queue": 0} +{"t": 15, "temp_c": 61.0, "mhz": 1000, "cpu_pct": 61.2, "watts": 8.52, "rss_mb": 465, "done": 8, "queue": 0} +{"t": 16, "temp_c": 55.0, "mhz": 799, "cpu_pct": 4.8, "watts": 2.21, "rss_mb": 465, "done": 8, "queue": 0} +{"t": 17, "temp_c": 61.0, "mhz": 1664, "cpu_pct": 60.5, "watts": 7.80, "rss_mb": 469, "done": 9, "queue": 0} +{"t": 18, "temp_c": 54.0, "mhz": 2847, "cpu_pct": 6.3, "watts": 3.86, "rss_mb": 469, "done": 9, "queue": 0} +{"t": 19, "temp_c": 61.0, "mhz": 1379, "cpu_pct": 59.9, "watts": 7.94, "rss_mb": 473, "done": 10, "queue": 0} +{"t": 20, "temp_c": 54.0, "mhz": 892, "cpu_pct": 4.5, "watts": 3.44, "rss_mb": 473, "done": 10, "queue": 0} +{"t": 21, "temp_c": 61.0, "mhz": 1106, "cpu_pct": 61.1, "watts": 8.26, "rss_mb": 518, "done": 10, "queue": 0} +{"t": 22, "temp_c": 55.0, "mhz": 940, "cpu_pct": 5.5, "watts": 3.27, "rss_mb": 474, "done": 11, "queue": 0} +{"t": 23, "temp_c": 61.0, "mhz": 1003, "cpu_pct": 60.2, "watts": 8.42, "rss_mb": 476, "done": 12, "queue": 0} +{"t": 24, "temp_c": 59.0, "mhz": 2800, "cpu_pct": 5.2, "watts": 2.19, "rss_mb": 488, "done": 12, "queue": 0} +{"t": 25, "temp_c": 74.0, "mhz": 2800, "cpu_pct": 64.7, "watts": 7.51, "rss_mb": 479, "done": 13, "queue": 0} +{"t": 26, "temp_c": 55.0, "mhz": 1949, "cpu_pct": 16.4, "watts": 5.30, "rss_mb": 479, "done": 13, "queue": 0} +{"t": 27, "temp_c": 61.0, "mhz": 1027, "cpu_pct": 60.4, "watts": 7.74, "rss_mb": 481, "done": 14, "queue": 0} +{"t": 28, "temp_c": 54.0, "mhz": 1430, "cpu_pct": 4.8, "watts": 3.59, "rss_mb": 481, "done": 14, "queue": 0} +{"t": 29, "temp_c": 61.0, "mhz": 1523, "cpu_pct": 59.8, "watts": 8.16, "rss_mb": 483, "done": 15, "queue": 0} +{"t": 30, "temp_c": 55.0, "mhz": 1811, "cpu_pct": 4.5, "watts": 3.40, "rss_mb": 483, "done": 15, "queue": 0} +{"t": 31, "temp_c": 60.0, "mhz": 1240, "cpu_pct": 59.9, "watts": 8.45, "rss_mb": 484, "done": 16, "queue": 0} +{"t": 32, "temp_c": 55.0, "mhz": 2798, "cpu_pct": 5.0, "watts": 3.06, "rss_mb": 484, "done": 16, "queue": 0} +{"t": 33, "temp_c": 60.0, "mhz": 1326, "cpu_pct": 60.3, "watts": 6.75, "rss_mb": 486, "done": 17, "queue": 0} +{"t": 34, "temp_c": 57.0, "mhz": 2800, "cpu_pct": 5.3, "watts": 3.94, "rss_mb": 499, "done": 17, "queue": 0} +{"t": 35, "temp_c": 61.0, "mhz": 951, "cpu_pct": 60.0, "watts": 7.83, "rss_mb": 489, "done": 18, "queue": 0} +{"t": 36, "temp_c": 67.0, "mhz": 2800, "cpu_pct": 8.3, "watts": 3.86, "rss_mb": 542, "done": 18, "queue": 0} +{"t": 37, "temp_c": 59.0, "mhz": 977, "cpu_pct": 58.4, "watts": 8.21, "rss_mb": 490, "done": 19, "queue": 0} +{"t": 38, "temp_c": 64.0, "mhz": 2800, "cpu_pct": 6.3, "watts": 3.20, "rss_mb": 525, "done": 19, "queue": 0} +{"t": 39, "temp_c": 60.0, "mhz": 1375, "cpu_pct": 58.6, "watts": 8.42, "rss_mb": 491, "done": 20, "queue": 0} +{"t": 40, "temp_c": 66.0, "mhz": 2800, "cpu_pct": 6.8, "watts": 3.10, "rss_mb": 535, "done": 20, "queue": 0} +{"t": 41, "temp_c": 60.0, "mhz": 800, "cpu_pct": 58.8, "watts": 8.57, "rss_mb": 491, "done": 21, "queue": 0} +{"t": 42, "temp_c": 71.0, "mhz": 2800, "cpu_pct": 7.3, "watts": 2.24, "rss_mb": 568, "done": 21, "queue": 0} +{"t": 43, "temp_c": 59.0, "mhz": 752, "cpu_pct": 56.8, "watts": 7.77, "rss_mb": 492, "done": 22, "queue": 0} +{"t": 44, "temp_c": 68.0, "mhz": 2800, "cpu_pct": 6.8, "watts": 3.67, "rss_mb": 553, "done": 22, "queue": 0} +{"t": 45, "temp_c": 60.0, "mhz": 913, "cpu_pct": 58.3, "watts": 8.04, "rss_mb": 493, "done": 23, "queue": 0} +{"t": 46, "temp_c": 70.0, "mhz": 2800, "cpu_pct": 7.3, "watts": 3.44, "rss_mb": 569, "done": 23, "queue": 0} +{"t": 47, "temp_c": 59.0, "mhz": 807, "cpu_pct": 57.7, "watts": 8.44, "rss_mb": 493, "done": 24, "queue": 0} +{"t": 48, "temp_c": 73.0, "mhz": 2800, "cpu_pct": 8.6, "watts": 2.98, "rss_mb": 573, "done": 24, "queue": 0} +{"t": 49, "temp_c": 60.0, "mhz": 868, "cpu_pct": 56.8, "watts": 8.58, "rss_mb": 494, "done": 25, "queue": 0} +{"t": 50, "temp_c": 72.0, "mhz": 2800, "cpu_pct": 7.8, "watts": 2.32, "rss_mb": 579, "done": 25, "queue": 0} +{"t": 51, "temp_c": 59.0, "mhz": 791, "cpu_pct": 57.8, "watts": 7.74, "rss_mb": 494, "done": 26, "queue": 0} +{"t": 52, "temp_c": 73.0, "mhz": 2800, "cpu_pct": 7.8, "watts": 3.75, "rss_mb": 584, "done": 26, "queue": 0} +{"t": 53, "temp_c": 60.0, "mhz": 1035, "cpu_pct": 58.4, "watts": 8.00, "rss_mb": 494, "done": 27, "queue": 0} +{"t": 54, "temp_c": 68.0, "mhz": 2850, "cpu_pct": 10.1, "watts": 3.53, "rss_mb": 599, "done": 27, "queue": 0} +{"t": 55, "temp_c": 60.0, "mhz": 794, "cpu_pct": 56.3, "watts": 8.46, "rss_mb": 495, "done": 28, "queue": 0} +{"t": 56, "temp_c": 57.0, "mhz": 1400, "cpu_pct": 18.4, "watts": 4.00, "rss_mb": 503, "done": 28, "queue": 0} +{"t": 57, "temp_c": 64.0, "mhz": 2526, "cpu_pct": 65.7, "watts": 8.73, "rss_mb": 589, "done": 28, "queue": 0} +{"t": 58, "temp_c": 70.0, "mhz": 2835, "cpu_pct": 8.8, "watts": 3.55, "rss_mb": 597, "done": 29, "queue": 0} +{"t": 59, "temp_c": 59.0, "mhz": 822, "cpu_pct": 56.9, "watts": 6.63, "rss_mb": 496, "done": 30, "queue": 0} +{"t": 60, "temp_c": 73.0, "mhz": 2843, "cpu_pct": 8.9, "watts": 3.74, "rss_mb": 593, "done": 30, "queue": 0} +{"t": 61, "temp_c": 59.0, "mhz": 868, "cpu_pct": 55.6, "watts": 8.02, "rss_mb": 496, "done": 31, "queue": 0} +{"t": 62, "temp_c": 68.0, "mhz": 2834, "cpu_pct": 8.6, "watts": 3.48, "rss_mb": 606, "done": 31, "queue": 0} +{"t": 63, "temp_c": 59.0, "mhz": 765, "cpu_pct": 56.4, "watts": 8.40, "rss_mb": 497, "done": 32, "queue": 0} +{"t": 64, "temp_c": 64.0, "mhz": 2849, "cpu_pct": 8.8, "watts": 3.13, "rss_mb": 615, "done": 32, "queue": 0} +{"t": 65, "temp_c": 59.0, "mhz": 744, "cpu_pct": 56.8, "watts": 8.58, "rss_mb": 497, "done": 33, "queue": 0} +{"t": 66, "temp_c": 70.0, "mhz": 2817, "cpu_pct": 9.5, "watts": 3.22, "rss_mb": 603, "done": 33, "queue": 0} +{"t": 67, "temp_c": 59.0, "mhz": 798, "cpu_pct": 56.8, "watts": 6.71, "rss_mb": 497, "done": 34, "queue": 0} +{"t": 68, "temp_c": 64.0, "mhz": 2838, "cpu_pct": 9.1, "watts": 3.80, "rss_mb": 616, "done": 34, "queue": 0} +{"t": 69, "temp_c": 59.0, "mhz": 777, "cpu_pct": 56.8, "watts": 8.05, "rss_mb": 497, "done": 35, "queue": 0} +{"t": 70, "temp_c": 63.0, "mhz": 2851, "cpu_pct": 8.8, "watts": 3.52, "rss_mb": 619, "done": 35, "queue": 0} +{"t": 71, "temp_c": 59.0, "mhz": 801, "cpu_pct": 56.8, "watts": 8.37, "rss_mb": 498, "done": 36, "queue": 0} +{"t": 72, "temp_c": 67.0, "mhz": 2848, "cpu_pct": 10.3, "watts": 3.19, "rss_mb": 629, "done": 36, "queue": 0} +{"t": 73, "temp_c": 59.0, "mhz": 775, "cpu_pct": 55.3, "watts": 8.54, "rss_mb": 498, "done": 37, "queue": 0} +{"t": 74, "temp_c": 64.0, "mhz": 2849, "cpu_pct": 8.4, "watts": 3.39, "rss_mb": 626, "done": 37, "queue": 0} +{"t": 75, "temp_c": 59.0, "mhz": 815, "cpu_pct": 56.0, "watts": 6.70, "rss_mb": 498, "done": 38, "queue": 0} +{"t": 76, "temp_c": 61.0, "mhz": 2900, "cpu_pct": 8.6, "watts": 3.59, "rss_mb": 631, "done": 38, "queue": 0} +{"t": 77, "temp_c": 59.0, "mhz": 868, "cpu_pct": 56.2, "watts": 8.16, "rss_mb": 499, "done": 39, "queue": 0} +{"t": 78, "temp_c": 65.0, "mhz": 2844, "cpu_pct": 10.1, "watts": 3.41, "rss_mb": 619, "done": 39, "queue": 0} +{"t": 79, "temp_c": 59.0, "mhz": 779, "cpu_pct": 56.0, "watts": 8.34, "rss_mb": 499, "done": 40, "queue": 0} +{"t": 80, "temp_c": 61.0, "mhz": 1843, "cpu_pct": 8.6, "watts": 3.26, "rss_mb": 657, "done": 40, "queue": 0} +{"t": 81, "temp_c": 58.0, "mhz": 762, "cpu_pct": 56.0, "watts": 8.56, "rss_mb": 499, "done": 41, "queue": 0} +{"t": 82, "temp_c": 62.0, "mhz": 2859, "cpu_pct": 9.0, "watts": 3.43, "rss_mb": 659, "done": 41, "queue": 0} +{"t": 83, "temp_c": 59.0, "mhz": 800, "cpu_pct": 56.5, "watts": 8.15, "rss_mb": 499, "done": 42, "queue": 0} +{"t": 84, "temp_c": 63.0, "mhz": 2370, "cpu_pct": 9.1, "watts": 2.25, "rss_mb": 648, "done": 42, "queue": 0} +{"t": 85, "temp_c": 59.0, "mhz": 800, "cpu_pct": 55.5, "watts": 8.12, "rss_mb": 499, "done": 43, "queue": 0} +{"t": 86, "temp_c": 63.0, "mhz": 2358, "cpu_pct": 8.6, "watts": 3.38, "rss_mb": 648, "done": 43, "queue": 0} +{"t": 87, "temp_c": 74.0, "mhz": 2737, "cpu_pct": 75.9, "watts": 8.89, "rss_mb": 648, "done": 43, "queue": 0} +{"t": 88, "temp_c": 63.0, "mhz": 1850, "cpu_pct": 10.4, "watts": 3.95, "rss_mb": 650, "done": 44, "queue": 0} +{"t": 89, "temp_c": 59.0, "mhz": 800, "cpu_pct": 56.0, "watts": 8.58, "rss_mb": 501, "done": 45, "queue": 0} +{"t": 90, "temp_c": 68.0, "mhz": 2823, "cpu_pct": 10.1, "watts": 3.37, "rss_mb": 663, "done": 45, "queue": 0} +{"t": 91, "temp_c": 58.0, "mhz": 800, "cpu_pct": 55.9, "watts": 8.16, "rss_mb": 501, "done": 46, "queue": 0} +{"t": 92, "temp_c": 61.0, "mhz": 1849, "cpu_pct": 8.8, "watts": 2.33, "rss_mb": 653, "done": 46, "queue": 0} +{"t": 93, "temp_c": 59.0, "mhz": 779, "cpu_pct": 57.1, "watts": 8.05, "rss_mb": 501, "done": 47, "queue": 0} +{"t": 94, "temp_c": 62.0, "mhz": 2889, "cpu_pct": 8.9, "watts": 3.48, "rss_mb": 657, "done": 47, "queue": 0} +{"t": 95, "temp_c": 59.0, "mhz": 789, "cpu_pct": 56.5, "watts": 8.39, "rss_mb": 502, "done": 48, "queue": 0} +{"t": 96, "temp_c": 65.0, "mhz": 2830, "cpu_pct": 11.9, "watts": 3.27, "rss_mb": 652, "done": 48, "queue": 0} +{"t": 97, "temp_c": 59.0, "mhz": 768, "cpu_pct": 55.6, "watts": 8.59, "rss_mb": 502, "done": 49, "queue": 0} +{"t": 98, "temp_c": 65.0, "mhz": 2834, "cpu_pct": 9.1, "watts": 3.43, "rss_mb": 662, "done": 49, "queue": 0} +{"t": 99, "temp_c": 59.0, "mhz": 800, "cpu_pct": 56.5, "watts": 8.15, "rss_mb": 502, "done": 50, "queue": 0} +{"t": 100, "temp_c": 61.0, "mhz": 1849, "cpu_pct": 8.8, "watts": 2.16, "rss_mb": 666, "done": 50, "queue": 0} +{"t": 101, "temp_c": 59.0, "mhz": 763, "cpu_pct": 56.3, "watts": 8.20, "rss_mb": 502, "done": 51, "queue": 0} +{"t": 102, "temp_c": 62.0, "mhz": 2859, "cpu_pct": 10.3, "watts": 3.42, "rss_mb": 655, "done": 51, "queue": 0} +{"t": 103, "temp_c": 59.0, "mhz": 796, "cpu_pct": 55.3, "watts": 8.46, "rss_mb": 502, "done": 52, "queue": 0} +{"t": 104, "temp_c": 61.0, "mhz": 2349, "cpu_pct": 9.3, "watts": 3.08, "rss_mb": 688, "done": 52, "queue": 0} +{"t": 105, "temp_c": 58.0, "mhz": 796, "cpu_pct": 55.5, "watts": 8.55, "rss_mb": 503, "done": 53, "queue": 0} +{"t": 106, "temp_c": 64.0, "mhz": 2360, "cpu_pct": 9.3, "watts": 3.44, "rss_mb": 677, "done": 53, "queue": 0} +{"t": 107, "temp_c": 59.0, "mhz": 800, "cpu_pct": 56.7, "watts": 8.07, "rss_mb": 503, "done": 54, "queue": 0} +{"t": 108, "temp_c": 62.0, "mhz": 2875, "cpu_pct": 10.9, "watts": 2.33, "rss_mb": 661, "done": 54, "queue": 0} +{"t": 109, "temp_c": 59.0, "mhz": 800, "cpu_pct": 55.5, "watts": 8.23, "rss_mb": 503, "done": 55, "queue": 0} +{"t": 110, "temp_c": 61.0, "mhz": 2884, "cpu_pct": 9.0, "watts": 3.26, "rss_mb": 682, "done": 55, "queue": 0} +{"t": 111, "temp_c": 59.0, "mhz": 749, "cpu_pct": 56.0, "watts": 8.41, "rss_mb": 503, "done": 56, "queue": 0} +{"t": 112, "temp_c": 62.0, "mhz": 2784, "cpu_pct": 9.8, "watts": 3.13, "rss_mb": 689, "done": 56, "queue": 0} +{"t": 113, "temp_c": 58.0, "mhz": 798, "cpu_pct": 54.4, "watts": 8.50, "rss_mb": 504, "done": 57, "queue": 0} +{"t": 114, "temp_c": 61.0, "mhz": 2871, "cpu_pct": 10.3, "watts": 3.38, "rss_mb": 689, "done": 57, "queue": 0} +{"t": 115, "temp_c": 59.0, "mhz": 2825, "cpu_pct": 55.1, "watts": 7.96, "rss_mb": 504, "done": 58, "queue": 0} +{"t": 116, "temp_c": 60.0, "mhz": 2878, "cpu_pct": 8.3, "watts": 2.31, "rss_mb": 689, "done": 58, "queue": 0} +{"t": 117, "temp_c": 59.0, "mhz": 800, "cpu_pct": 57.4, "watts": 8.26, "rss_mb": 506, "done": 59, "queue": 0} +{"t": 118, "temp_c": 63.0, "mhz": 2726, "cpu_pct": 27.6, "watts": 4.88, "rss_mb": 692, "done": 59, "queue": 0} +{"t": 119, "temp_c": 59.0, "mhz": 800, "cpu_pct": 55.3, "watts": 8.37, "rss_mb": 506, "done": 60, "queue": 0} +{"t": 120, "temp_c": 55.0, "mhz": 903, "cpu_pct": 5.6, "watts": 3.07, "rss_mb": 394, "done": 60, "queue": 0} +{"summary": "pipeline", "kind": "c12_ea", "workers": 1, "threads": 4, "effort": 3, "distance": 1.00, "period_ms": 2000, "secs": 120.0, "frames": 60, "frames_per_s": 0.500, "job_ms_p50": 1000, "latency_ms_p50": 1013, "latency_ms_p95": 1109, "latency_ms_max": 1395, "max_queue": 1, "backlog_at_end": 0, "cpu_s_per_frame": 2.353, "peak_rss_mb": 739, "temp_c_mean": 61.2, "temp_c_max": 74.0, "mhz_mean": 1765, "mhz_min": 744, "cpu_pct_mean": 33.6, "watts_mean": 5.72} diff --git a/tools/camera-bench/results-2026-10-08-tower111-throttle/stream_cost.sh b/tools/camera-bench/results-2026-10-08-tower111-throttle/stream_cost.sh new file mode 100644 index 0000000..9b35c36 --- /dev/null +++ b/tools/camera-bench/results-2026-10-08-tower111-throttle/stream_cost.sh @@ -0,0 +1,34 @@ +#!/bin/sh +# CPU the program spends while the camera streams and nothing is saved: the +# tower's installed build against the branch (buffer reuse). Manual mode, one +# snapshot to start the stream, then 60 s measured. ~5 min. +set -u +export GENICAM_GENTL64_PATH=/opt/VimbaX/cti # as scripts/fgc does +T=~/thermal-test +CFG=$T/manual-config.ini +# The tower's config, but starting in manual (still homing: a snapshot needs it). +sed 's/^mode = auto/mode = manual/' ~/.config/fire_gimbal_control/config.ini > "$CFG" +ln -sf ~/.config/fire_gimbal_control/scan.csv "$T/scan.csv" +ln -sfn ~/.config/fire_gimbal_control/camera_profiles "$T/camera_profiles" +HZ=$(getconf CLK_TCK) + +measure() { # label binary + FIFO=$T/cmd2.fifo + rm -f "$FIFO"; mkfifo "$FIFO" + sleep 100000 > "$FIFO" & + holder=$! + "$2" -c "$CFG" --no-tui --no-mqtt --no-log-file < "$FIFO" > "$T/stream_$1.log" 2>&1 & + pid=$! + sleep 60 # start + homing + echo snapshot > "$FIFO" + sleep 15 # the snapshot saved; the stream keeps running + a=$(awk '{print $14 + $15}' /proc/$pid/stat) + sleep 60 + b=$(awk '{print $14 + $15}' /proc/$pid/stat) + echo "$1: $(echo "scale=1; 100 * ($b - $a) / $HZ / 60" | bc) % of one core while streaming" + kill -TERM "$pid"; sleep 10; kill "$holder" 2>/dev/null; sleep 3 + kill -0 "$pid" 2>/dev/null && kill -KILL "$pid" + rm -f "$FIFO" +} +measure installed ~/projects/fwt_2a/software/build/fire_gimbal_control +measure branch $T/software/build/fire_gimbal_control diff --git a/tools/thermal-sim/cap-cpu.sh b/tools/thermal-sim/cap-cpu.sh new file mode 100755 index 0000000..2159ef6 --- /dev/null +++ b/tools/thermal-sim/cap-cpu.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +# Cap the CPU clock, to see how the program copes with a CPU slowed to stay cool. +# +# sudo tools/thermal-sim/cap-cpu.sh 800 # every core at most 800 MHz +# sudo tools/thermal-sim/cap-cpu.sh restore # back to what it was +# +# Writes scaling_max_freq on every core. The values found the first time are +# kept in /run/fgc-cap-cpu.saved, so `restore` puts back the real ones however +# many caps were set in between. A reboot also clears the cap. +set -euo pipefail + +saved=/run/fgc-cap-cpu.saved +cpus=(/sys/devices/system/cpu/cpu[0-9]*/cpufreq) + +usage() { echo "usage: $0 | restore" >&2; exit 2; } +[[ $# -eq 1 ]] || usage +[[ $EUID -eq 0 ]] || { echo "$0: needs root (sudo)" >&2; exit 1; } +[[ -e ${cpus[0]}/scaling_max_freq ]] || { echo "$0: no cpufreq on this machine" >&2; exit 1; } + +if [[ $1 == restore ]]; then + [[ -f $saved ]] || { echo "nothing to restore (no cap set since boot)"; exit 0; } + while read -r dir khz; do echo "$khz" > "$dir/scaling_max_freq"; done < "$saved" + rm -f "$saved" + echo "CPU clock limits restored" + exit 0 +fi + +[[ $1 =~ ^[0-9]+$ ]] || usage +khz=$(( $1 * 1000 )) +if [[ ! -f $saved ]]; then + for d in "${cpus[@]}"; do echo "$d $(cat "$d/scaling_max_freq")"; done > "$saved" +fi +for d in "${cpus[@]}"; do + min=$(cat "$d/cpuinfo_min_freq") + (( khz < min )) && { echo "$0: $1 MHz is under this CPU's minimum ($((min / 1000)) MHz)" >&2; exit 1; } + echo "$khz" > "$d/scaling_max_freq" +done +echo "every core capped at $1 MHz ('$0 restore' to undo)" diff --git a/tools/thermal-sim/cap-power.sh b/tools/thermal-sim/cap-power.sh new file mode 100755 index 0000000..30728b6 --- /dev/null +++ b/tools/thermal-sim/cap-power.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# Cap the CPU package power, as a hot box or the 6 W TDP mode would. +# +# sudo tools/thermal-sim/cap-power.sh 6 # long-term limit 6 W +# sudo tools/thermal-sim/cap-power.sh restore +# +# Writes the RAPL long-term limit (constraint 0) of intel-rapl:0. The original is +# kept in /run/fgc-cap-power.saved. A reboot also clears it. Intel only. +set -euo pipefail + +saved=/run/fgc-cap-power.saved +rapl=/sys/class/powercap/intel-rapl:0 +limit=$rapl/constraint_0_power_limit_uw + +usage() { echo "usage: $0 | restore" >&2; exit 2; } +[[ $# -eq 1 ]] || usage +[[ $EUID -eq 0 ]] || { echo "$0: needs root (sudo)" >&2; exit 1; } +[[ -w $limit ]] || { echo "$0: no writable RAPL limit at $limit" >&2; exit 1; } + +if [[ $1 == restore ]]; then + [[ -f $saved ]] || { echo "nothing to restore (no cap set since boot)"; exit 0; } + cat "$saved" > "$limit" + rm -f "$saved" + echo "power limit restored to $(( $(cat "$limit") / 1000000 )) W" + exit 0 +fi + +[[ $1 =~ ^[0-9]+$ ]] || usage +[[ -f $saved ]] || cat "$limit" > "$saved" +echo $(( $1 * 1000000 )) > "$limit" +echo "package power capped at $1 W (was $(( $(cat "$saved") / 1000000 )) W; '$0 restore' to undo)" diff --git a/tools/thermal-sim/fake-temp.sh b/tools/thermal-sim/fake-temp.sh new file mode 100755 index 0000000..85ee097 --- /dev/null +++ b/tools/thermal-sim/fake-temp.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +# A fake /sys for the CPU guard, so heat can be simulated without heat. Point the +# program at it with [Thermal] sysfs_root = (config.ini or hardware.ini) +# and change the temperature while it runs. +# +# tools/thermal-sim/fake-temp.sh init # build the tree (4 cores, 3.6 GHz, 70 C) +# tools/thermal-sim/fake-temp.sh # set the CPU temperature +# tools/thermal-sim/fake-temp.sh throttle # ...and count one throttle event +# tools/thermal-sim/fake-temp.sh clock # set every core's clock +# tools/thermal-sim/fake-temp.sh ramp +# # walk the temperature, throttling +# # at 90 C and above +# +# The tree has no proc/stat, so the program reads the real /proc/stat: how busy +# the CPU is stays real (raise it with heat-load.sh). +set -euo pipefail + +usage() { sed -n '2,15p' "$0" >&2; exit 2; } +[[ $# -ge 2 ]] || usage +root=$1; shift +cpu=$root/sys/devices/system/cpu +zone=$root/sys/class/thermal/thermal_zone0 + +init() { + rm -f "$root/proc/stat" # an older tree's frozen copy would hide the real one + mkdir -p "$zone" + echo x86_pkg_temp > "$zone/type" + echo 70000 > "$zone/temp" + for c in 0 1 2 3; do + mkdir -p "$cpu/cpu$c/cpufreq" "$cpu/cpu$c/thermal_throttle" + echo 2800000 > "$cpu/cpu$c/cpufreq/scaling_cur_freq" + echo 3600000 > "$cpu/cpu$c/cpufreq/cpuinfo_max_freq" + echo 3600000 > "$cpu/cpu$c/cpufreq/scaling_max_freq" + echo 0 > "$cpu/cpu$c/thermal_throttle/package_throttle_count" + echo 0 > "$cpu/cpu$c/thermal_throttle/core_throttle_count" + done + echo "fake tree ready at $root: 70 C, 2.8 of 3.6 GHz" +} + +set_temp() { # [throttle] + echo $(( $1 * 1000 )) > "$zone/temp" + if [[ ${2:-} == throttle ]]; then + f=$cpu/cpu0/thermal_throttle/package_throttle_count + echo $(( $(cat "$f") + 1 )) > "$f" + fi +} + +[[ $1 == init ]] && { init; exit 0; } +[[ -d $zone ]] || { echo "$0: no tree at $root - run '$0 $root init' first" >&2; exit 1; } + +case $1 in + clock) + [[ $# -eq 2 ]] || usage + for c in "$cpu"/cpu[0-9]*; do echo $(( $2 * 1000 )) > "$c/cpufreq/scaling_cur_freq"; done + echo "clock $2 MHz" ;; + ramp) + [[ $# -eq 5 ]] || usage + from=$2 to=$3 step=$4 secs=$5 + (( from > to )) && step=$(( -step )) + t=$from + while :; do + echo "$(date +%T) $t C" + # Hold the step; at 90 C and up the CPU throttles, one counted + # event every 2 s. + for (( i = 0; i < secs; i += 2 )); do + if (( t >= 90 )); then set_temp "$t" throttle; else set_temp "$t"; fi + sleep 2 + done + (( t == to )) && break + t=$(( t + step )) + (( (step > 0 && t > to) || (step < 0 && t < to) )) && t=$to + done ;; + *) + [[ $1 =~ ^[0-9]+$ ]] || usage + set_temp "$1" "${2:-}" + echo "CPU $1 C${2:+, one throttle event}" ;; +esac diff --git a/tools/thermal-sim/heat-load.sh b/tools/thermal-sim/heat-load.sh new file mode 100755 index 0000000..d9ff02c --- /dev/null +++ b/tools/thermal-sim/heat-load.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# Keep some cores busy, standing in for heat and work competing with the program +# (a hot afternoon, the modem, something else running). +# +# tools/thermal-sim/heat-load.sh 2 600 # 2 cores flat out for 10 minutes +# +# Uses stress-ng when installed; otherwise one busy shell loop per core. Ctrl-C +# stops it early. Needs no root. +set -euo pipefail + +[[ $# -eq 2 && $1 =~ ^[0-9]+$ && $2 =~ ^[0-9]+$ ]] || { + echo "usage: $0 " >&2; exit 2; } +cores=$1 secs=$2 + +if command -v stress-ng >/dev/null; then + exec stress-ng --cpu "$cores" --timeout "${secs}s" --metrics-brief +fi + +echo "stress-ng not found; using $cores busy loop(s) for $secs s" +pids=() +trap 'kill "${pids[@]}" 2>/dev/null' EXIT INT TERM +for _ in $(seq "$cores"); do + ( end=$((SECONDS + secs)); while (( SECONDS < end )); do :; done ) & + pids+=($!) +done +wait