// Author: cirakg & claude ai // Summary: Debug-only diagnostics: start-up time, memory, screen markers for UI scripts, and the performance overlay. // // Debug builds print one machine-readable line per event to the simulator // console: "DIAG", the event name, then key=value pairs, for example // DIAG first_frame startup_ms=1 mem_used=6984 mem_total=781888 // tools/check_budgets.py reads these lines and compares them with // tools/budgets.json. Plain key=value pairs are used instead of JSON because // the simulator console escapes quotes. Release builds compile the empty // (:release) versions instead, so a real watch running a release build never // logs anything. // // UI scripts (docker/scripts/screenshots.sh) wait for "DIAG shown " // lines before taking a screenshot, and read the PIN keypad's key positions // from "DIAG pin_keys" to tap them. // // Security rule: only timings and memory counters ever go through here, never // secrets, codes or entry names. This is the only file allowed to call // System.println outside tests (enforced by tools/lint_project.py). import Toybox.Graphics; import Toybox.Lang; import Toybox.System; // (:glance): the app object calls markStart() before the non-glance code is // loaded (apps with a glance load the rest only for the full app). (:glance) module Diag { // System.getTimer() value when the app object was created. (:debug) var startMs as Number = 0; // Start the start-up timer. Called from OtpWalletApp.initialize(). (:debug) function markStart() as Void { startMs = System.getTimer(); } (:release) function markStart() as Void { } // Report start-up time and memory use after the first complete frame. // startup_ms covers app object creation to the end of the first draw. The // time the system needs to load the program before that is invisible to // the app; app-diag.sh measures it from outside (README section 18). (:debug) function firstFrame() as Void { var stats = System.getSystemStats(); System.println("DIAG first_frame startup_ms=" + (System.getTimer() - startMs) + " mem_used=" + stats.usedMemory + " mem_total=" + stats.totalMemory); } (:release) function firstFrame() as Void { } // The glance's memory after its first draw (README 18: at most 70 % of // the glance's 64 KB; `make memtest` checks it). (:debug) function glanceMemory() as Void { var stats = System.getSystemStats(); System.println("DIAG glance mem_used=" + stats.usedMemory + " mem_total=" + stats.totalMemory); } (:release) function glanceMemory() as Void { } // A screen finished its first draw since it was shown. (:debug) function shown(screen as String) as Void { System.println("DIAG shown " + screen); } (:release) function shown(screen as String) as Void { } // Free-form key=value data for UI scripts (never secrets). (:debug) function data(event as String, values as String) as Void { System.println("DIAG " + event + " " + values); } (:release) function data(event as String, values as String) as Void { } // The performance overlay (toggled in the debug menu): draw time of the // frame that started at startMs, and memory use. (:debug) var overlayOn as Boolean = false; (:debug) function overlay(dc as Graphics.Dc, frameStartMs as Number) as Void { if (!overlayOn) { return; } var stats = System.getSystemStats(); var text = (System.getTimer() - frameStartMs) + " ms " + (stats.usedMemory * 100 / stats.totalMemory) + "% mem"; dc.setColor(Graphics.COLOR_YELLOW, Graphics.COLOR_BLACK); dc.drawText(dc.getWidth() / 2, dc.getHeight() - dc.getFontHeight(Graphics.FONT_XTINY) - 2, Graphics.FONT_XTINY, text, Graphics.TEXT_JUSTIFY_CENTER); } (:release) function overlay(dc as Graphics.Dc, frameStartMs as Number) as Void { } // Peak memory while long work runs (sampled by the import job). (:debug) var peakMemory as Number = 0; (:debug) function sampleMemory() as Void { var used = System.getSystemStats().usedMemory; if (used > peakMemory) { peakMemory = used; } } (:release) function sampleMemory() as Void { } // Reports an import's size, duration and peak memory for UI scripts. (:debug) function importDone(entries as Number, wallMs as Number) as Void { System.println("DIAG import entries=" + entries + " wall_ms=" + wallMs + " mem_peak=" + peakMemory + " mem_total=" + System.getSystemStats().totalMemory); } (:release) function importDone(entries as Number, wallMs as Number) as Void { } }