summaryrefslogtreecommitdiff
path: root/source/util/StressTest.mc
diff options
context:
space:
mode:
Diffstat (limited to 'source/util/StressTest.mc')
-rw-r--r--source/util/StressTest.mc215
1 files changed, 215 insertions, 0 deletions
diff --git a/source/util/StressTest.mc b/source/util/StressTest.mc
new file mode 100644
index 0000000..d1f99ea
--- /dev/null
+++ b/source/util/StressTest.mc
@@ -0,0 +1,215 @@
+// Author: cirakg & claude ai
+// Summary: Watchdog stress build: the largest code of every 2D symbology, encoded, packed, turned into runs and drawn, at twice the normal steps per event.
+//
+// Two versions of this module exist; each build keeps exactly one:
+// * (:no_stress), in every normal build: ON is false.
+// * (:stress), only with stress.jungle (`make watchdog`): the app skips
+// the vault and runs StressTest.Job instead of the list.
+// Connect IQ stops code that runs too long in one event ("Watchdog
+// Tripped"), but only in the simulator's app runs, never in unit tests. So
+// this build runs every 2D encoder, the cache packer and the run finder on
+// the biggest inputs the app accepts, with TWO job steps per runner step:
+// a pass shows that each step uses at most half of the watchdog budget
+// (120,000 bytecodes per event on the 512 KB watches, 240,000 elsewhere,
+// where the runner already does two steps per tick). Codes that fit the
+// screen are then drawn the way CodeView draws them (RunsPainter: straight
+// from the runs, or from a bitmap painted in steps), in one onUpdate.
+// docker/scripts/app-diag.sh watchdog reads the DIAG stress lines.
+
+import Toybox.Graphics;
+import Toybox.Lang;
+import Toybox.System;
+import Toybox.WatchUi;
+
+(:stress)
+module StressTest {
+ const ON = true;
+ // Steps per runner step: the safety margin being tested.
+ const MARGIN = 2;
+ // The last code's text, which stays on the screen to be decoded.
+ const SCREEN_TEXT = "OTPW stress";
+
+ // One item: an encoder for the largest input of its kind, through
+ // Codes.DisplayJob (encode, pack, runs) like CodeView, MARGIN steps at a time.
+ class Item extends Jobs.Job {
+ var name as String;
+ var steps as Number = 0;
+ var job as Codes.DisplayJob;
+ var symbology as Number;
+ var painter as RunsPainter? = null;
+ // Highest memory use seen between steps.
+ var peak as Number = 0;
+
+ function initialize(label as String, type as Number, encoder as Codes.MatrixJob) {
+ Job.initialize();
+ name = label;
+ symbology = type;
+ job = new Codes.DisplayJob(encoder, null);
+ }
+
+ // The display job, then (when there are runs) the painter.
+ function step(budget as Number) as Boolean {
+ for (var i = 0; i < MARGIN; i++) {
+ steps++;
+ var used = System.getSystemStats().usedMemory;
+ peak = used > peak ? used : peak;
+ var p = painter;
+ if (p != null) {
+ if (p.step(budget)) {
+ return true;
+ }
+ } else if (job.step(budget)) {
+ var runs = job.runs;
+ if (runs == null) {
+ return true;
+ }
+ painter = new RunsPainter(runs, Codes.minModulePx(symbology));
+ }
+ }
+ return false;
+ }
+ }
+
+ // The screen: progress while encoding, the runs once an item is done.
+ class StressView extends WatchUi.View {
+ var title as String = "Stress test";
+ var painter as RunsPainter? = null;
+ private var _first as Boolean = true;
+
+ function initialize() {
+ View.initialize();
+ }
+
+ function onUpdate(dc as Graphics.Dc) as Void {
+ dc.setColor(Graphics.COLOR_BLACK, Graphics.COLOR_WHITE);
+ dc.clear();
+ var p = painter;
+ if (p != null) {
+ var drawn = p.draw(dc);
+ Diag.data("stress", "drawn name=" + title + " runs=" + p.runs.runs.size() + " direct=" + p.direct()
+ + " drawn=" + drawn);
+ painter = null;
+ } else {
+ dc.drawText(dc.getWidth() / 2, dc.getHeight() / 2, Graphics.FONT_XTINY, title, Graphics.TEXT_JUSTIFY_CENTER);
+ }
+ if (_first) {
+ _first = false;
+ Diag.firstFrame();
+ start();
+ }
+ }
+ }
+
+ var view as StressView? = null;
+ var items as Array<Item> = [] as Array<Item>;
+ var current as Item? = null;
+ var runner as Jobs.JobRunner? = null;
+
+ function initialView() as [WatchUi.Views] or [WatchUi.Views, WatchUi.InputDelegates] {
+ var v = new StressView();
+ view = v;
+ return [v];
+ }
+
+ // The largest inputs: QR version 40 (bytes) and a long digit string at
+ // level H, Data Matrix 144 x 144 (ASCII) and Base256, Aztec 32 layers
+ // (text and binary), PDF417 as big as this screen takes; then the
+ // largest Data Matrix and QR that fit the screen, to be drawn (on most
+ // watches the QR is drawn from a bitmap, RunsPainter's other path).
+ function start() as Void {
+ var letters = repeat("abcdefghij", 290);
+ var digits = repeat("0123456789", 80);
+ var binary = repeat("é€", 120);
+ var screen = System.getDeviceSettings();
+ var w = screen.screenWidth;
+ var h = screen.screenHeight;
+ var round = screen.screenShape == System.SCREEN_SHAPE_ROUND;
+ items = [
+ new Item("qr_v40", Codes.QR, new Qr.QrJob(letters, 0, 0, -1)),
+ new Item("qr_digits_h", Codes.QR, new Qr.QrJob(digits, 3, 0, -1)),
+ new Item("dm_144", Codes.DATAMATRIX, new DataMatrix.DataMatrixJob(letters.substring(0, 1500) as String)),
+ new Item("dm_base256", Codes.DATAMATRIX, new DataMatrix.DataMatrixJob(binary)),
+ new Item("aztec_text", Codes.AZTEC, new Aztec.AztecJob(letters.substring(0, 1400) as String, 0)),
+ new Item("aztec_binary", Codes.AZTEC, new Aztec.AztecJob(binary.substring(0, 160) as String, 3)),
+ new Item("pdf417_text", Codes.PDF417, new Pdf417.Pdf417Job(letters.substring(0, w > 300 ? 900 : 250) as String,
+ 1, w, h, round)),
+ new Item("pdf417_digits", Codes.PDF417, new Pdf417.Pdf417Job(digits.substring(0, 400) as String, 1, w, h, round)),
+ new Item("dm_fit", Codes.DATAMATRIX, new DataMatrix.DataMatrixJob(letters.substring(0, largestDataMatrix(w, h, round))
+ as String)),
+ // Last, so it stays on the screen: app-diag.sh decodes it.
+ new Item("qr_fit", Codes.QR, new Qr.QrJob(SCREEN_TEXT, 1, largestQrVersion(w, h, round), -1))
+ ] as Array<Item>;
+ Diag.data("stress", "start used_kb=" + System.getSystemStats().usedMemory / 1024);
+ next();
+ }
+
+ function next() as Void {
+ if (items.size() == 0) {
+ Diag.data("stress", "all done");
+ return;
+ }
+ var item = items[0];
+ items = items.slice(1, null) as Array<Item>;
+ current = item;
+ (view as StressView).title = item.name;
+ WatchUi.requestUpdate();
+ var r = new Jobs.JobRunner(item, 1, new Lang.Method(StressTest, :onItemDone));
+ runner = r;
+ r.start();
+ }
+
+ function onItemDone() as Void {
+ var item = current as Item;
+ var runs = item.job.runs;
+ Diag.data("stress", "done name=" + item.name + " steps=" + item.steps + " peak_kb=" + item.peak / 1024
+ + " total_kb=" + System.getSystemStats().totalMemory / 1024 + " error=" + item.job.error
+ + " size=" + (runs != null ? runs.width + "x" + runs.height : "-"));
+ (view as StressView).painter = item.painter;
+ WatchUi.requestUpdate();
+ // The next item starts in its own event, after the draw.
+ var r = new Jobs.JobRunner(new Jobs.Job(), 1, new Lang.Method(StressTest, :next));
+ runner = r;
+ r.start();
+ }
+
+ function repeat(text as String, times as Number) as String {
+ var out = "";
+ for (var i = 0; i < times; i++) {
+ out += text;
+ }
+ return out;
+ }
+
+ // The largest QR version that fits this screen at 2 px per module.
+ function largestQrVersion(w as Number, h as Number, round as Boolean) as Number {
+ for (var v = 40; v > 1; v--) {
+ var total = 17 + 4 * v + 8;
+ if (Codes.fits(total, total, 2, w, h, round)) {
+ return v;
+ }
+ }
+ return 1;
+ }
+
+ // An ASCII payload length that fills the largest Data Matrix fitting
+ // this screen at 2 px per module.
+ function largestDataMatrix(w as Number, h as Number, round as Boolean) as Number {
+ var best = 1;
+ for (var i = 0; i < DataMatrix.SIZES.size(); i++) {
+ var s = DataMatrix.SIZES[i] as Array<Number>;
+ if (Codes.fits(s[0] + 2, s[0] + 2, 2, w, h, round)) {
+ best = s[3];
+ }
+ }
+ return best;
+ }
+}
+
+(:no_stress)
+module StressTest {
+ const ON = false;
+
+ function initialView() as [WatchUi.Views] or [WatchUi.Views, WatchUi.InputDelegates] {
+ return [new WatchUi.View()];
+ }
+}