Local telemetry for AMDGPU independent throttle flags, correlated with systemd journal entries and machine context (power, PMF, dGPU runtime). Built to produce shareable evidence for Framework Laptop 16 power-limit debugging.
Framework issue #146 tracks cases where the CPU stays capped (for example around 35W) while the dGPU is asleep, with throttle flags such as SPL or PROCHOT_CPU still asserted. Reproducing that needs a timeline that ties together:
- Which throttle flags were active, and when they changed
- APU power and temperature samples
- Charger / power-profile / sleep context
- AMD PMF limits from Linux (
pmf.*) when debugfs is readable - dGPU DRM runtime state (
gpu_power.*) - Nearby journal messages around each transition
framelog runs continuously on the laptop, stores everything in SQLite, and exposes a local web UI plus JSON exports so you (or Framework engineers) can inspect a session without re-running the repro on the same machine.
Collection is read-only: it does not change firmware, ACPI tables, or PMF settings.
See docs/prochot-status-investigation.md if PROCHOT_CPU / PROCHOT_GPU show 100% active on Radeon 780M — the raw metrics are often correct, but the meaning can be misleading on legacy gpu_metrics v2.1.
Contribute: Pull requests are welcome. Please report bugs, request features, or share capture feedback in the framelog issue tracker.
- Samples
gpu_metricsvialibamdgpu_top(noamdgpu_topbinary required) - Stores time series in SQLite
- Detects flag transitions (0→1 and 1→0)
- On each transition, queries journal logs in a configurable window (default 60s before, 30s after)
- Local web UI with uPlot graphs at
http://127.0.0.1:8787 - Modular context sources (power, battery, display, profile, sleep, PMF, dGPU power)
- Cached system inventory (CPU, GPU, memory, storage, BIOS, Linux) in UI and exports
- Guided capture CLI (
framelog capture) for clean, attachable evidence folders - Evidence Doctor — deterministic analysis in the UI (
/analyze), API, andframelog analyze
| Platform | Collector | Throttle flags | Package power | PMF / #146 rules |
|---|---|---|---|---|
| Framework 13/16 AMD (Radeon iGPU/dGPU) | lib-amdgpu |
Yes | Yes (APU package power) | Yes when debugfs PMF is readable |
| Framework 13 Intel | intel |
No | Yes (RAPL/hwmon) | Context only; AMD rules skipped |
| Other AMDGPU laptops | lib-amdgpu |
Usually | Usually | PMF only on AMD PMF firmware |
Check fit before a long capture:
framelog capabilities
framelog inspect --collector lib-amdgpu # AMD
framelog inspect --collector intel # Intel-onlyThe System page (/system) and GET /api/capabilities show detected DRM cards, recommended collector, and which Evidence Doctor rules apply.
- Linux with AMDGPU (
amdgpu) forlib-amdgpu/ default collector - Read access to
/sys/class/drm/card*/device/gpu_metrics(often requires root) - Optional: systemd journal for transition correlation
- Optional:
/sys/kernel/debug/amd_pmf/current_power_limitsfor PMF context (may require root)
cargo build --release
sudo install -m755 target/release/framelog /usr/local/bin/framelogInstall the release binary and register the systemd service:
sudo scripts/install-service.shUseful variants:
sudo scripts/install-service.sh --collector intel
sudo scripts/install-service.sh --bind 0.0.0.0:8787 --no-start
cargo build --release
sudo scripts/install-service.sh --no-buildThe installer writes /usr/local/bin/framelog, installs framelog.service,
runs systemctl daemon-reload, then enables and starts the service by default.
After install:
systemctl status framelog.service
journalctl -u framelog.service -f
xdg-open http://127.0.0.1:8787Continuous logging (typical on a Framework 16):
sudo scripts/install-service.sh
# open dashboard — live DB updates while the service runs
xdg-open http://127.0.0.1:8787One-shot evidence session for a bug report:
sudo systemctl stop framelog.service
framelog capture --duration 20m --issue framework-146
framelog serve
# dashboard → Load export → pick export.json from the capture folderSee in-app help for live vs export playback and the full capture workflow.
| Command | Purpose |
|---|---|
framelog run |
Collector + web UI (same as systemd service) |
framelog collect |
Collector only, no web server |
framelog serve |
Web UI only; reads existing SQLite DB |
framelog inspect |
One live sample to stdout (debug, not the web UI) |
framelog capture |
Guided session: backup, reset, collect, export folder |
framelog dump |
Write timestamped JSON export from the DB |
framelog analyze |
Deterministic findings from an export.json (Evidence Doctor) |
framelog reset --yes |
Clear telemetry (optional backup first) |
Global flags (all subcommands): --db, --interval-ms, --bind, --journal-before-secs, --journal-after-secs, --apu-only, --collector (lib-amdgpu, fake, amdgpu-top-json).
framelog run --db ~/.local/share/framelog/framelog.db
framelog serve
framelog inspect
framelog capture --duration 20m --issue framework-146
framelog dump --output-dir ~/exports
framelog analyze export.json
framelog analyze export.json --format json --ai-prompt
framelog reset --yes
framelog run --collector fake # no GPU required| Flag | Default | Description |
|---|---|---|
--db |
~/.local/share/framelog/framelog.db |
SQLite path (FRAMELOG_DB env) |
--interval-ms |
1000 |
Sample interval |
--bind |
127.0.0.1:8787 |
Web listen address |
--journal-before-secs |
60 |
Journal window before transitions |
--journal-after-secs |
30 |
Journal window after transitions |
--apu-only |
off | Only sample APU-like devices |
--collector |
lib-amdgpu |
lib-amdgpu, fake, amdgpu-top-json, or intel |
capture also accepts --duration (required), --issue (default capture), --output-dir, --device-pci, --skip-pre-backup.
dump accepts --output, --output-dir, --from-ms, --to-ms, --device-pci. CLI dumps include an analysis block (same rules as Evidence Doctor).
analyze accepts --format markdown|json, --output, --ai-prompt, --from-ms, --to-ms, --device-pci.
reset requires --yes. It writes a backup export first unless --no-backup is set; use --backup or --backup-dir to control the path.
| URL | Purpose |
|---|---|
| http://127.0.0.1:8787/ | Dashboard: charts, timeline, export, load export |
| http://127.0.0.1:8787/analyze | Evidence Doctor: findings for the selected time range |
| http://127.0.0.1:8787/help | Workflows: capture, live vs playback |
| http://127.0.0.1:8787/system | CPU/GPU inventory and issue #146 notes |
Live inspection — While framelog run or the systemd service is collecting, open the dashboard. It reads the growing database (use Tail (live) and Refresh). Logging continues in the background.
Export playback — Run framelog serve without a collector. Click Load export and choose export.json. Data stays in the browser; nothing is written to SQLite. Use Return to live to switch back to the database. There is no separate playback-only daemon.
Share a window — On the dashboard, set a time range and click Export window (JSON bundle). Recipients use Load export in their own UI.
Analyze a range — Set the dashboard time window and click Analyze range, or open the Analysis page after loading an export. Live DB mode uses GET /api/findings; loaded exports use POST /api/analyze/export in the browser only. Findings are deterministic rules (Framework #146 shape, thermal vs power cap, missing PMF, and similar), not cloud AI. Use Copy AI prompt on the analysis page if you want an external model to explain the report further.
Export window — Dashboard Export window is capped at 7 days and 500k samples (same limits as GET /api/export). Use framelog dump or capture for full-range bundles.
The UI includes flag presets (power / thermal / current), power/temperature overlays, journal snippets on transitions, and event timeline playback (Play / Prev / Next).
framelog capture produces a folder ready to attach to issue #146 or similar reports:
- Stop any collector using the same DB:
sudo systemctl stop framelog.service - Run capture while reproducing the issue:
framelog capture --duration 20m --issue framework-146 - Inspect offline:
framelog serve→ Load export →export.json - Attach
export.json,report.md, andsummary.txtto the GitHub issue - Restart logging when done:
sudo systemctl start framelog.service
framelog-capture-framework-146-<timestamp>/
export.json
analysis.json
manifest.json
system.json
report.md
summary.txt
pre_capture_backup.json # only if DB had older data
framelog capture --duration 1h --issue framework-146 --output-dir ~/captures/run-1
framelog capture --duration 300s --collector fake --skip-pre-backupThe collector prunes telemetry older than 24 hours by default (--retention-hours, set 0 to keep all data). Pruning runs at service start and hourly. New databases use WAL mode and incremental auto-vacuum; after upgrading from a large existing database, reclaim disk space once with:
sudo systemctl stop framelog.service
sqlite3 /var/lib/framelog/framelog.db 'VACUUM;'
sudo systemctl start framelog.serviceframelog dump exports the full stored range (CLI has no 7-day web limit). dump and reset backups embed an analysis block in the JSON.
framelog capture and framelog reset --yes refuse to run while the database is still receiving samples (for example when framelog.service is active). Stop the collector first:
sudo systemctl stop framelog.serviceIf a collector was just stopped, wait until no new samples arrive for about five seconds, or the command exits with database still receiving samples.
After a successful reset, start the service again if you want continuous logging:
framelog dump --output-dir ~/framelog-exports
framelog reset --yes --backup-dir ~/framelog-exports
sudo systemctl start framelog.serviceThe repository includes an example service unit at systemd/framelog.service.
It runs framelog run as root because the first install path usually needs
read access to AMDGPU gpu_metrics, debugfs PMF limits, and the journal.
Build and install the binary:
cargo build --release
sudo install -m755 target/release/framelog /usr/local/bin/framelogInstall the included unit:
sudo install -D -m644 systemd/framelog.service /etc/systemd/system/framelog.service
sudo systemctl daemon-reload
sudo systemctl enable --now framelog.serviceOr create/edit it manually:
sudoedit /etc/systemd/system/framelog.service[Unit]
Description=Framework firmware throttle flag logger (framelog)
After=network.target
[Service]
Type=simple
ExecStart=/usr/local/bin/framelog run --db /var/lib/framelog/framelog.db --bind 127.0.0.1:8787 --journal-before-secs 60 --journal-after-secs 30
Restart=on-failure
RestartSec=5
# Root keeps first install simple for gpu_metrics, debugfs PMF, and journal access.
User=root
Group=root
StateDirectory=framelog
LogsDirectory=framelog
[Install]
WantedBy=multi-user.targetValidate and operate the service:
sudo systemd-analyze verify /etc/systemd/system/framelog.service
sudo systemctl daemon-reload
sudo systemctl restart framelog.service
systemctl status framelog.service
journalctl -u framelog.service -fThe unit stores data in /var/lib/framelog/framelog.db and serves the UI on
http://127.0.0.1:8787. For a clean guided capture, stop the service first:
sudo systemctl stop framelog.service
framelog capture --duration 20m --issue framework-146
sudo systemctl start framelog.service| Source | Signals |
|---|---|
power |
AC connected, input watts, adapter watts reported |
battery |
Status, percent, power |
display |
External monitor count |
profile |
Active power profile (D-Bus) |
sleep |
Sleep/wake markers |
pmf |
spl_mw, sppt_mw, fppt_mw, STT skin temps (debugfs) |
gpu_power |
dGPU runtime status, suspend, D3cold |
With --collector fake, synthetic sources exercise the UI without hardware.
When filing or reviewing traces for issue #146, correlate:
- dGPU runtime (
gpu_power.dgpu_runtime_status,dgpu_runtime_suspended) - OS power profile (
profile.active) - Charger (
power.ac_connected,power.input_watts,power.adapter_watts_reported) - PMF limits (
pmf.*) when debugfs is readable - Throttle flags (
SPL,PROCHOT_CPU) and APU power/temperature
For the reported \_SB_.PCI0.GPP0.M241(1) / M241(0) race trigger, see
docs/framework-146-m241-trigger-capture.md.
For the 544/545 MHz (or ~1400 MHz) CPU frequency lock after long idle, see docs/framework-146-long-idle-capture.md. That workflow uses normal read-only collection; the explicit opt-in ACPI helper is only for the M241 trigger path.
PMF limits (live) come from /sys/kernel/debug/amd_pmf/current_power_limits. Full ACPI STTC tables live in firmware AML; see this discussion. framelog does not modify firmware or call ryzenadj from the hot path.
AMD SMU independent throttle status bits (same family as amdgpu_top):
- Power:
SPL,SPPT,FPPT,PPT0–PPT3 - Temperature:
PROCHOT_CPU,PROCHOT_GPU,TEMP_HOTSPOT, … - Current:
TDC_*,EDC_*
Harvested at service start and refreshed about daily. Shown in the UI and embedded in exports. Read-only from /proc, /sys, and /etc/os-release; storage serial numbers are omitted.
REST API on the same port as the UI (127.0.0.1:8787 by default).
Health and bounds
GET /api/healthGET /api/capabilities— detected hardware, signals, and Evidence Doctor rule availabilityGET /api/time-bounds,GET /api/summary
Telemetry series
GET /api/devices,GET /api/flagsGET /api/series/{flag},GET /api/metrics/{metric}GET /api/transitions,GET /api/transitions/journal?ids=GET /api/transitions/{id}/journal
Context
GET /api/context/keys,GET /api/context/healthGET /api/context/series/{key},GET /api/context/transitionsGET /api/context/transitions/journal?ids=GET /api/context/transitions/{id}/journal
Export and analysis
GET /api/export— JSON bundle for the requested window (7 days / 500k samples max)GET /api/findings— Evidence Doctor report for a window (live DB)POST /api/analyze/export— analyze a client-loaded export JSON (no upload to a server)
System inventory
GET /api/system,POST /api/system/refresh
Use framelog dump or capture for full-range export bundles without the web limits.
MIT