diet-guard/app/lib/services/frame_stats.dart
Krzysztof kuhy Rudnicki c12faa3920
Some checks are pending
pre-commit / pre-commit (push) Waiting to run
Tests / test (3.10) (push) Waiting to run
Tests / test (3.11) (push) Waiting to run
Tests / test (3.12) (push) Waiting to run
app: package the desktop app as the web build, and delete the Linux embedder
Completes the move: `linux/` is gone, replaced by the web build served by the
wrapper in a Chrome `--app` window. The icons and the .desktop installer move
out of `linux/` first -- they are not Flutter scaffolding and are still
needed; the entry now points at the packaged launcher and matches the
`--class=diet_guard_app` the wrapper passes, so the taskbar shows the app's
icon rather than a browser's.

install_arch.sh carries over the three traps each of which produced an
install that looked fine until launched: `dart build cli` rather than
`dart compile exe` (which refuses any package whose dependency graph has
native build hooks), `options=('!strip')` (makepkg would otherwise discard
the AOT snapshot, leaving a binary that runs as the bare Dart VM printing a
usage message), and no browser in `depends` (this machine's policy uninstalls
`chromium` seconds later, failing dependency resolution).

Also adds the frame-cadence instrument the performance claim is measured
with, armed by `--dart-define=DIET_GUARD_FRAME_STATS=1` and read with
`String.fromEnvironment` -- `bool.fromEnvironment` only accepts the literal
`true`, so `=1` would evaluate to false and a whole run would silently
produce no data. It reports frame-to-frame cadence rather than build/raster
durations, since work that fits the budget says nothing about the frame rate
actually achieved.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 16:32:24 +02:00

76 lines
2.5 KiB
Dart

/// Optional frame-cadence instrument, armed with
/// `--dart-define=DIET_GUARD_FRAME_STATS=1`.
///
/// Reports **frame-to-frame cadence**, not build/raster durations: work that
/// fits the budget says nothing about the frame rate actually achieved, which
/// is the number the desktop migration exists to move.
///
/// Read with `String.fromEnvironment`, never `bool.fromEnvironment` -- the
/// latter only accepts the literal `true`, so `=1` would silently evaluate to
/// false and a whole measurement run would produce no data while looking like
/// a broken callback.
library;
import 'dart:async';
import 'dart:convert';
import 'dart:ui' show FramePhase;
import 'package:diet_guard_app/services/desktop_wrapper.dart';
import 'package:flutter/scheduler.dart';
import 'package:http/http.dart' as http;
/// Whether the instrument is armed for this build.
const frameStatsEnabled =
String.fromEnvironment('DIET_GUARD_FRAME_STATS') == '1';
/// Starts collecting frame-to-frame intervals and periodically posts a
/// summary to the desktop wrapper, which writes it to disk.
///
/// The wrapper is used as the sink because a browser cannot write a file and
/// reading its console from outside needs a debugger attached; a POST lands
/// somewhere a measurement run can simply read.
// coverage:ignore-start
void startFrameStats({Duration reportEvery = const Duration(seconds: 2)}) {
final intervalsMs = <double>[];
int? previousMicros;
SchedulerBinding.instance.addTimingsCallback((timings) {
for (final timing in timings) {
final micros = timing.timestampInMicroseconds(FramePhase.rasterFinish);
if (previousMicros != null) {
intervalsMs.add((micros - previousMicros!) / 1000);
}
previousMicros = micros;
}
});
Timer.periodic(reportEvery, (_) {
if (intervalsMs.isEmpty) return;
final sample = [...intervalsMs]..sort();
intervalsMs.clear();
double percentile(double fraction) =>
sample[(sample.length * fraction).floor().clamp(
0,
sample.length - 1,
)];
unawaited(
http.post(
Uri.parse(
'$desktopWrapperOrigin${WrapperPaths.documents}'
'frame_stats.json',
),
body: jsonEncode({
'frames': sample.length,
'p50_ms': percentile(0.50),
'p95_ms': percentile(0.95),
'max_ms': sample.last,
'fps_at_p50': 1000 / percentile(0.50),
'fps_at_p95': 1000 / percentile(0.95),
}),
),
);
});
}
// coverage:ignore-end