1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
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()];
}
}
|