forked from BasedHardware/omi
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbackground_resource_telemetry.dart
More file actions
138 lines (123 loc) · 5.24 KB
/
Copy pathbackground_resource_telemetry.dart
File metadata and controls
138 lines (123 loc) · 5.24 KB
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
import 'dart:math' as math;
typedef BackgroundResourceEventEmitter = void Function(String eventName, Map<String, dynamic> properties);
typedef BackgroundResourceSnapshotLoader = Future<BackgroundResourceSnapshot> Function(
DateTime backgroundStartedAt,
BackgroundResourceSnapshot startSnapshot,
);
class BackgroundResourceSnapshot {
const BackgroundResourceSnapshot({
required this.bleBytesReceived,
required this.websocketBytesSent,
required this.recordingState,
required this.deviceConnected,
required this.deviceType,
required this.batchModeEnabled,
this.foregroundTaskRunning = false,
this.backgroundDisconnectCount = 0,
this.connectionTimeoutCount = 0,
this.failToConnectCount = 0,
this.reconnectCount = 0,
this.maxReconnectDurationMs = 0,
this.reconnectionCountTotal = 0,
this.failToConnectCountTotal = 0,
this.bleHistorySaturated = false,
this.nativeBackgroundBytesConsumed = 0,
this.nativeBackgroundPacketsConsumed = 0,
this.diagnosticsDeviceId,
});
final int bleBytesReceived;
final int websocketBytesSent;
final String recordingState;
final bool deviceConnected;
final String deviceType;
final bool batchModeEnabled;
final bool foregroundTaskRunning;
final int backgroundDisconnectCount;
final int connectionTimeoutCount;
final int failToConnectCount;
final int reconnectCount;
final int maxReconnectDurationMs;
final int reconnectionCountTotal;
final int failToConnectCountTotal;
final bool bleHistorySaturated;
final int nativeBackgroundBytesConsumed;
final int nativeBackgroundPacketsConsumed;
/// Used only to query the same device at resume; never emitted to analytics.
final String? diagnosticsDeviceId;
}
/// Emits one privacy-safe summary when a real background session ends.
///
/// Audio, transcript content, device identifiers, and exact locations are
/// intentionally excluded. Analytics failures never affect lifecycle work.
class BackgroundResourceTelemetry {
BackgroundResourceTelemetry({
required BackgroundResourceEventEmitter emit,
DateTime Function()? now,
String Function()? sessionIdFactory,
this.minimumDuration = const Duration(minutes: 1),
}) : _emit = emit,
_now = now ?? DateTime.now,
_sessionIdFactory = sessionIdFactory ?? (() => DateTime.now().microsecondsSinceEpoch.toString());
static const eventName = 'Mobile Background Resource Session';
final BackgroundResourceEventEmitter _emit;
final DateTime Function() _now;
final String Function() _sessionIdFactory;
final Duration minimumDuration;
_BackgroundSessionStart? _activeSession;
void onPaused(BackgroundResourceSnapshot snapshot) {
if (_activeSession != null) return;
_activeSession = _BackgroundSessionStart(
id: _sessionIdFactory(),
startedAt: _now(),
snapshot: snapshot,
);
}
Future<void> onResumed(BackgroundResourceSnapshotLoader loadSnapshot) async {
final session = _activeSession;
_activeSession = null;
if (session == null) return;
final endedAt = _now();
final duration = endedAt.difference(session.startedAt);
if (duration < minimumDuration) return;
try {
final end = await loadSnapshot(session.startedAt, session.snapshot);
final durationSeconds = math.max(1, duration.inSeconds);
final bleBytes = math.max(0, end.bleBytesReceived - session.snapshot.bleBytesReceived);
final websocketBytes = math.max(0, end.websocketBytesSent - session.snapshot.websocketBytesSent);
_emit(eventName, {
'background_session_id': session.id,
'background_duration_seconds': durationSeconds,
'start_recording_state': session.snapshot.recordingState,
'end_recording_state': end.recordingState,
'start_device_connected': session.snapshot.deviceConnected,
'end_device_connected': end.deviceConnected,
'start_device_type': session.snapshot.deviceType,
'end_device_type': end.deviceType,
'batch_mode_enabled': end.batchModeEnabled,
'ble_bytes_received': bleBytes,
'websocket_bytes_sent': websocketBytes,
'ble_receive_bytes_per_second': bleBytes ~/ durationSeconds,
'websocket_send_bytes_per_second': websocketBytes ~/ durationSeconds,
'foreground_task_running_on_resume': end.foregroundTaskRunning,
'background_disconnect_count': end.backgroundDisconnectCount,
'connection_timeout_count': end.connectionTimeoutCount,
'fail_to_connect_count': end.failToConnectCount,
'reconnect_count': end.reconnectCount,
'max_reconnect_duration_ms': end.maxReconnectDurationMs,
'reconnection_count_total': end.reconnectionCountTotal,
'fail_to_connect_count_total': end.failToConnectCountTotal,
'ble_history_saturated': end.bleHistorySaturated,
'native_background_bytes_consumed': end.nativeBackgroundBytesConsumed,
'native_background_packets_consumed': end.nativeBackgroundPacketsConsumed,
});
} catch (_) {
// Telemetry is strictly fail-open. The app resume path must continue.
}
}
}
class _BackgroundSessionStart {
const _BackgroundSessionStart({required this.id, required this.startedAt, required this.snapshot});
final String id;
final DateTime startedAt;
final BackgroundResourceSnapshot snapshot;
}