190. Телеметрия и выпуск: кольцевой буфер, прореживание, хранение, сборка
Последний раздел части: прибор шлёт показания часами, приложение должно их показывать, хранить и переживать выпуск новой версии. Три вопроса, на которые тут есть числа: что держать в памяти, что рисовать на экране и в чём хранить журнал.
Числа получены пакетом _verify/dart190: пять проверок и ряд на
200 000 точек с одним выбросом.
Как это называется в документации
Оригинал Перевод ring / circular buffer кольцевой буфер downsampling, decimation прореживание min-max decimation прореживание по минимуму и максимуму retention срок хранения append-only log журнал только на дозапись gzip, deflate сжатие preferences, key-value store настройки: пары «ключ — значение» build number, version code номер сборки release build, obfuscation сборка для выпуска, запутывание кода
190.1 Кольцевой буфер: память не растёт
Приложение, которое держит всё пришедшее, падает через сутки. Экрану нужна только недавняя история, поэтому точки кладут в кольцо фиксированного размера:
| Что проверено | Значение |
|---|---|
| ёмкость буфера | 10 000 точек |
| подано точек | 25 000 |
| осталось в буфере | 10 000 |
| первая точка в буфере | 15 000-я |
Память не растёт вообще: список выделен один раз, новые точки затирают старые. Всё, что нужно сохранить дольше, уходит в журнал на диск — это следующий разговор.
190.2 Прореживание: экран уже, чем данные
На графике шириной 800 точек нет смысла рисовать 200 000 значений. Вопрос в том, как их сократить. Простое «каждая 250-я» быстрое и неправильное:
| Способ | Точек на экране | Выброс 3,3 В |
|---|---|---|
| каждая 250-я | 800 | потерян |
| минимум и максимум по корзине | 1600 | на месте |
Второй способ смотрит на все точки корзины и берёт из неё две — самую низкую и самую высокую:
for (int j = i; j < end; j++) {
if (src[j].volts < lo.volts) lo = src[j];
if (src[j].volts > hi.volts) hi = src[j];
}
Цена — вдвое больше точек на экране и полный проход по данным. Выгода — на графике видно то, ради чего его смотрят: короткий выброс, просадку питания, единичный сбой.
190.3 Хранение: двоичный формат, текст и сжатие
Журнал на 100 000 точек, где у каждой время в миллисекундах от 1970 года и напряжение:
| Формат | Размер, байт |
|---|---|
| двоичный, 12 байт на точку | 1 200 000 |
| текстовый CSV | 1 999 999 |
| двоичный после gzip | 503 969 |
| текстовый после gzip | 205 684 |
Вывод неожиданный ровно до того момента, как о нём подумаешь: текст больше, но сжимается лучше. Двоичное представление чисел с плавающей точкой почти случайно с точки зрения сжатия, а в тексте повторяются цифры, запятые и одинаковые старшие разряды времени. Для отправки на сервер текст в gzip оказывается в 2,5 раза меньше двоичного в gzip.
Но сравнение неравное. CSV хранит напряжение до милливольта, а число с плавающей точкой — все свои разряды, и у плавной волны младшие из них меняются от точки к точке. Двоичная запись той же точности — милливольты 16-разрядным целым, время приращением от прошлой точки, тоже 16 бит, — занимает 4 байта на точку: 400 000 байт, а после gzip — 3 662. Так мало выходит у гладкой волны без шума; с шумом ±2 мВ, как у настоящего АЦП, — 66 485 байт против 300 916 у текста в gzip, всё равно в 4,5 раза меньше. Выходит, «текст сжимается лучше» — свойство выбранной записи, а не правило: сжатию помогает не текст, а отсутствие лишней точности.
Практический выбор: на устройстве храним двоично (быстро писать, легко читать кусками) и не точнее, чем нужно; отправляем сжатым — текстом, когда на той стороне его должен прочитать любой инструмент, или той же компактной двоичной записью, когда дорог трафик.
190.4 Настройки: что хранить рядом с приложением
Адрес прибора, единицы измерения, выбранный канал, порог тревоги — это пары «ключ — значение», и к ним два требования:
- Значения по умолчанию заданы в коде. Приложение обязано запускаться на чистом устройстве без настройки.
- Ничего секретного. Хранилище настроек не защищено; пароли и ключи — в защищённом хранилище платформы или нигде.
Отдельный случай — адрес и имя прибора: их полезно помнить, но показывать пользователю и давать сменить, иначе смена платы превращается в переустановку приложения.
190.5 Выпуск: версия и что проверить перед сборкой
Версия приложения — одна на всё: номер в pubspec.yaml, тот же номер
на экране «о программе» и в журнале. Расходятся они мгновенно, если
ставить их руками, поэтому номер берут из одного места, а сборку делают
командой, а не мышью.
Перед выпуском полезно прогнать ровно то, что разобрано в этой части:
| Что | Откуда |
|---|---|
| проверки пакетов и экрана | 185–189, dart test и flutter test |
| сборка для выпуска и номер версии | этот раздел |
| конвейер: сборка на каждое изменение | раздел 110 |
| тест на каждую исправленную ошибку | раздел 184 |
И правило из раздела 182, которое дороже всех остальных: заморозить изменения перед выпуском. Правка «на всякий случай» в кандидате — это 7 % шанса новой ошибки без единых ворот впереди.
190.6 Грабли
Хранить все точки. Приложение съедает память за сутки работы и падает именно тогда, когда прибор что-то показал.
Рисовать «каждую N-ю». График красивый и врёт: выброс, ради которого его смотрели, пропал.
Журнал в текст без сжатия. Вдвое больше места и трафика, чем нужно.
Двоичный журнал без описания формата. Через полгода никто не вспомнит, где там время, а где напряжение; описание — в том же репозитории, рядом.
Секреты в настройках. Хранилище настроек читается любым, у кого есть устройство.
Версию ставят руками. На экране одна, в магазине другая, в журнале третья — и разбор случая из поля становится гаданием.
190.7 Итоги
- Кольцевой буфер держит последние 10 000 точек: после 25 000 поданных в нём ровно 10 000, начиная с 15 000-й, и память не растёт.
- Прореживание «каждая 250-я» даёт 800 точек и теряет выброс; прореживание по минимуму и максимуму даёт 1600 и сохраняет его.
- 100 000 точек занимают 1 200 000 байт двоично и 1 999 999 текстом, но после gzip — 503 969 и 205 684: текст сжимается в 2,5 раза лучше. Но двоичная запись до милливольта с приращением времени после gzip меньше текста: 3 662 байта, а с шумом ±2 мВ — 66 485 против 300 916.
- Настройки — с значениями по умолчанию и без секретов; версия — из одного места.
- Перед выпуском гоняют все проверки частей XXII и XXIII и замораживают изменения.
Дальше: раздел 191 «Встраиваемый Linux: когда он нужен и из чего собран образ».