// Author: cirakg & claude ai // Summary: Debug-only menu items: load demo entries, the performance overlay and the on-watch benchmark. // // Release builds compile the empty (:release) module instead, so none of this // reaches the Store build. The benchmark is how the watch's real speed gets // measured (docs/PERFORMANCE.md): sideload a debug build and run it. import Toybox.Lang; import Toybox.System; import Toybox.Timer; import Toybox.WatchUi; (:debug) module DebugMenu { function addItems(menu as WatchUi.Menu2) as Void { menu.addItem(new WatchUi.MenuItem("Debug: demo entries", "Adds fake entries", :debugDemo, null)); menu.addItem(new WatchUi.MenuItem("Debug: overlay", Diag.overlayOn ? "On" : "Off", :debugOverlay, null)); menu.addItem(new WatchUi.MenuItem("Debug: benchmark", "Takes a few seconds", :debugBench, null)); } function handle(id as Object?) as Void { if (id == :debugDemo) { try { Fixtures.load(AppState.theVault()); } catch (e instanceof VaultError) { // Entry cap or full store: load what fits. } AppState.theCodes().clear(); WatchUi.popView(WatchUi.SLIDE_DOWN); } else if (id == :debugOverlay) { Diag.overlayOn = !Diag.overlayOn; WatchUi.popView(WatchUi.SLIDE_DOWN); } else if (id == :debugBench) { var runner = new BenchRunner(); bench = runner; WatchUi.switchToView(new InfoView("Benchmark", ["Measuring…"], "bench_running"), new InfoDelegate(), WatchUi.SLIDE_LEFT); runner.start(); } } const BENCH_HEADERS = 20; // The running benchmark (kept so its timer isn't collected). var bench as BenchRunner? = null; // Times the operations the budgets depend on, one per timer event, so no // event comes near the watchdog limit (it is enforced on the watch and in // normal simulator runs). class BenchRunner { private var _lines as Array = [] as Array; private var _step as Number = 0; private var _timer as Timer.Timer? = null; function initialize() { } function start() as Void { var t = new Timer.Timer(); t.start(method(:tick), 200, true); _timer = t; } function tick() as Void { var t = System.getTimer(); if (_step == 0) { for (var i = 0; i < 5; i++) { Otp.totp(Hashes.SHA1, new [20]b, 1800000000l, 30, 6); } _lines.add("TOTP SHA-1: " + (System.getTimer() - t) / 5 + " ms"); } else if (_step == 1) { Otp.totp(Hashes.SHA512, new [64]b, 1800000000l, 30, 6); _lines.add("TOTP SHA-512: " + (System.getTimer() - t) + " ms"); } else if (_step == 2) { _lines.add("PIN derivation: " + Kdf.calibrate(1000, Kdf.ITERATIONS_PER_STEP) + " iterations per second"); } else if (_step == 3) { // At most BENCH_HEADERS: all 110 of the UI test's entries in // one event tripped the watchdog. var vault = AppState.theVault(); var ids = vault.ids(); var n = ids.size() < BENCH_HEADERS ? ids.size() : BENCH_HEADERS; for (var i = 0; i < n; i++) { vault.header(ids[i]); } _lines.add("Headers: " + n + " in " + (System.getTimer() - t) + " ms"); } else if (_step == 4) { SelfTest.run(); _lines.add("Self-test: " + (System.getTimer() - t) + " ms"); } else { var stats = System.getSystemStats(); _lines.add("Memory: " + stats.usedMemory / 1024 + " of " + stats.totalMemory / 1024 + " KB"); var timer = _timer; if (timer != null) { timer.stop(); } bench = null; WatchUi.switchToView(new InfoView("Benchmark", _lines, "bench"), new InfoDelegate(), WatchUi.SLIDE_IMMEDIATE); return; } _step++; } } } (:release) module DebugMenu { function addItems(menu as WatchUi.Menu2) as Void { } function handle(id as Object?) as Void { } }