diff options
Diffstat (limited to 'docs/DEVELOPER_GUIDE.md')
| -rw-r--r-- | docs/DEVELOPER_GUIDE.md | 17 |
1 files changed, 13 insertions, 4 deletions
diff --git a/docs/DEVELOPER_GUIDE.md b/docs/DEVELOPER_GUIDE.md index 395cf00..77d2f02 100644 --- a/docs/DEVELOPER_GUIDE.md +++ b/docs/DEVELOPER_GUIDE.md @@ -23,7 +23,8 @@ Everything runs in Docker through the `Makefile`; the host needs only Docker and | All static checks | `make lint` | | Screenshots against the approved images | `make screenshots` | | Accept changed screens after reviewing build/screenshots/ | `make screenshots-approve` | -| Start-up time and memory budgets | `make perf`, `make memtest` | +| Start-up time and memory budgets (app and glance) | `make perf`, `make memtest` | +| Largest 2D codes at twice the steps per event (watchdog) | `make watchdog` | | Signed package for the Store beta | `make package` | | Regenerate the crypto/OTP test vectors | `make vectors` | | Build the M1 settings spike | `make spike` (instructions: [SETTINGS_SPIKE.md](SETTINGS_SPIKE.md)) | @@ -41,7 +42,7 @@ Use other devices with `TEST_DEVICES="fr70 venu3"` on any target that takes a de | `test/golden/` | Approved simulator screenshots: one folder per device, one image per screen of the UI flow | | `resources/` | Strings, the default launcher icon, the option properties and, from M7, the settings form | | `resources-icons/`, `icons.jungle` | Launcher icons at each size the products need, generated by `make icons` | -| `manifest.xml`, `monkey.jungle`, `test.jungle`, `uitest.jungle` | App manifest and build configuration | +| `manifest.xml`, `monkey.jungle`, `test.jungle`, `uitest.jungle`, `stress.jungle` | App manifest and build configuration | | `docker/` | Images (`Dockerfile`, `docs.Dockerfile`), hash-locked dependencies, and the container scripts | | `tools/` | Python tools (test-log judge, screenshot checker, linters, icon generator) and their tests | | `docs/` | This guide and the other documents | @@ -55,6 +56,7 @@ Use other devices with `TEST_DEVICES="fr70 venu3"` on any target that takes a de - `debug`: what `make build`, `make sim` and `make perf` use. It contains the `(:debug)` diagnostics, the debug menu items (demo entries, performance overlay, on-watch benchmark) and the fixtures. - `uitest`: a debug build plus `uitest.jungle`, for `make screenshots`. It keeps the `(:uitest)` half of `source/util/TestMode.mc`, so the app starts with a wiped store, fake entries, the PIN 123456, a frozen clock and a 10 s auto-lock. +- `stress`: a debug build plus `stress.jungle`, for `make watchdog`. It keeps the `(:stress)` half of `source/util/StressTest.mc`: the app starts straight into the watchdog stress test instead of the list. - `release`: `monkeyc -r` removes every `(:debug)` declaration; used by `make package`. - `test`: adds `test.jungle` and `--unit-test`. @@ -84,6 +86,9 @@ The simulator is a GTK program. The scripts start it on a virtual display (Xvfb - Anything long runs as a `Jobs.Job` with a bounded step (`Kdf.ITERATIONS_PER_STEP` = 100). - Screens compute at most one SHA-512 code per event, in a timer event separate from drawing. - The scripted UI flow runs on the 255S profile, so a regression trips there. + - **M8 found that step sizes guessed from "bytecodes per loop" were 2 to 3 times too large**: a simple loop iteration costs 50 to 70 counted bytecodes, not 10 to 20. Every step of the 2D encoders, the cache packer and the run finder is now sized by measurement in `make watchdog`, which runs the largest input of each with *two* steps per event: a pass means every step uses at most half the budget. Do the same for any new job: add its worst input to `StressTest.start()`. + - `Jobs.JobRunner` runs one step per 50 ms tick on the 512 KB watches and two on the others (`Jobs.stepsPerTick`, by app memory). `tools/gen_products.py --check` (part of `make lint`) fails if a product's `watchdogCount` doesn't match that rule. + - Drawing counts too: one `fillRectangle` per run in `onUpdate` is fine up to about 1,500 runs per 120,000 bytecodes. `RunsPainter` paints larger codes into a buffered bitmap in steps first (the Venu X1 fits a QR with 8,000 runs). - `StringUtil.convertEncodedString` stops the app with an uncatchable "System Error" on invalid UTF-8; `try` doesn't help. Decode untrusted bytes with `Bytes.toUtf8`, which validates first. - `Application.Storage` stores ByteArrays (its `ValueType` includes them, unlike `Application.PropertyValueType`), so records are stored as single ByteArrays of known size. Dictionaries passed to it must be typed `Dictionary<Application.Storage.KeyType, Application.Storage.ValueType>`; `Dictionary<String, ...>` doesn't type-check. - `Array.slice` returns `Array<Object or Null>`; cast it back (`as Array<String>`). @@ -97,6 +102,10 @@ The simulator is a GTK program. The scripts start it on a virtual display (Xvfb - `Attention has :vibrate` inside a class that has a private `vibrate` method warns (the symbol lookup finds the method); the helper is called `buzz`. - `has` and `hidden` are reserved words; they can't name a function or a variable. - Under strict typing, a property value (`Application.Properties.ValueType`) can't be compared with `==` to a Boolean or Number: narrow it with `instanceof` first (`Forms.flag`, `Forms.number`). +- **Memory of objects** (measured on the Forerunner 255S): an `Entry` takes about 490 bytes even when empty, an import item with its entry about 760, and an 80-byte ByteArray about 110. Hold many small records packed in ByteArrays (`ImportItem.pack`) rather than as objects, and grids in ByteArrays rather than `Array<Number>` (a 132 x 132 grid of Numbers took about 90 KB). +- The app's code and constant data are loaded whole at start: about 156 KB of the 255S's 512 KB before anything runs (release and debug builds alike). Only resources (`WatchUi.loadResource`) load on demand. +- `module` is a reserved word too. +- **The glance** loads every `(:glance)` module; the crypto it needs for the favourite code (`Hkdf`, `Hmac`, `Hashes`, `Sha512`, `Aes`, `SealedBox`, `Otp`, `Bytes`, `Clock`, `TestMode`) took it from 19 % to 37 % of its 64 KB. A glance module can't name app-only classes (the compiler checks the glance scope), hence `GlanceCode` (glance) and `GlanceStore` (app) in one file. In the simulator, an app that exits does not return to its glance; a new launch starts there (`screenshots.sh` launches again for `glance_code.png`). - Zeroing a ByteArray is a byte loop in the VM: zeroing a 10 KB import text in one event tripped the watchdog on the Forerunner 255S. The native HMAC takes data in pieces (`update`), so large content is hashed without copying it into one buffer. ### Screenshots and the UI flow @@ -112,7 +121,7 @@ Because the entry IDs, the clock and the codes are fixed in the UI-test build, e ### Performance and memory -Debug builds print `DIAG first_frame startup_ms=... mem_used=... mem_total=...` after their first frame (`source/util/Diag.mc`). `docker/scripts/app-diag.sh` adds a `HARNESS first_frame wall_ms=...` line, the wall-clock time from launching `monkeydo` until that first frame. `tools/check_budgets.py` compares both with `tools/budgets.json`. See [PERFORMANCE.md](PERFORMANCE.md) for the numbers and their limits. +Debug builds print `DIAG first_frame startup_ms=... mem_used=... mem_total=...` after their first frame, and the glance `DIAG glance mem_used=... mem_total=...` (`source/util/Diag.mc`). `docker/scripts/app-diag.sh` adds a `HARNESS first_frame wall_ms=...` line, the wall-clock time from launching `monkeydo` until that first frame. `tools/check_budgets.py` compares both with `tools/budgets.json`. See [PERFORMANCE.md](PERFORMANCE.md) for the numbers and their limits. ## Conventions @@ -126,7 +135,7 @@ Debug builds print `DIAG first_frame startup_ms=... mem_used=... mem_total=...` ## Adding things -- **A device**: add `<iq:product id="..."/>` to `manifest.xml`, then `make sdk` (downloads its files), `make icons` and `make products` (checks it meets the target rule: a watch, API 5.0 or newer, at least 512 KB). +- **A device**: add `<iq:product id="..."/>` to `manifest.xml`, then `make sdk` (downloads its files), `make icons` and `make products` (checks it meets the target rule: a watch, API 5.0 or newer, at least 512 KB, and the watchdog the job runner assumes for its memory). - **A test device**: add it to `TEST_DEVICES` (unit tests, perf and screenshots) or `SPOT_DEVICES` (screenshots only) in the `Makefile`. Then run `make screenshots`, review `build/screenshots/<device>/`, and run `make screenshots-approve`. - **A screen in the UI flow**: print `Diag.shown("<name>")` after its first complete draw, add the steps to `run_flow` in `tools/ui_flow.py`, and approve the new goldens after reviewing them. - **Import inputs**: new parser cases go into `test/import/` (fixtures are built in `ImportSupport.mc`, all fake). To widen the fuzz corpus, change the seeds or mutations in `tools/gen_fuzz_corpus.py` and run `make corpus`. The Python tests check that the committed corpus matches the generator. |
