Line data Source code
1 : import 'dart:collection';
2 :
3 : /// Performance monitoring utilities for native_workmanager.
4 : ///
5 : /// Provides tools to track and analyze:
6 : /// - Task execution time
7 : /// - Memory usage
8 : /// - Event dispatch latency
9 : /// - Task throughput
10 : /// - Chain execution performance
11 : ///
12 : /// Example usage:
13 : /// ```dart
14 : /// final monitor = PerformanceMonitor.instance;
15 : /// monitor.enable();
16 : ///
17 : /// // Tasks are automatically tracked when they run
18 : /// await NativeWorkManager.enqueue(...);
19 : ///
20 : /// // Get performance statistics
21 : /// final stats = monitor.getStatistics();
22 : /// print('Average task duration: ${stats.averageTaskDuration}ms');
23 : /// ```
24 : class PerformanceMonitor {
25 1 : PerformanceMonitor._();
26 :
27 : /// Singleton instance.
28 3 : static final PerformanceMonitor instance = PerformanceMonitor._();
29 :
30 : bool _enabled = false;
31 : final _taskMetrics = <String, TaskMetrics>{};
32 : final _recentEvents = Queue<PerformanceEvent>();
33 : final _maxRecentEvents = 100;
34 :
35 : DateTime? _monitoringStartTime;
36 :
37 : /// Enable performance monitoring.
38 1 : void enable() {
39 1 : _enabled = true;
40 2 : _monitoringStartTime = DateTime.now();
41 : }
42 :
43 : /// Disable performance monitoring.
44 1 : void disable() {
45 1 : _enabled = false;
46 : }
47 :
48 : /// Check if monitoring is enabled.
49 2 : bool get isEnabled => _enabled;
50 :
51 : /// Record task start.
52 1 : void recordTaskStart(String taskId, String workerType) {
53 1 : if (!_enabled) return;
54 :
55 3 : _taskMetrics[taskId] = TaskMetrics(
56 : taskId: taskId,
57 : workerType: workerType,
58 1 : startTime: DateTime.now(),
59 : );
60 :
61 2 : _addEvent(PerformanceEvent(
62 : type: PerformanceEventType.taskStarted,
63 : taskId: taskId,
64 1 : timestamp: DateTime.now(),
65 1 : metadata: {'workerType': workerType},
66 : ));
67 : }
68 :
69 : /// Record task completion.
70 1 : void recordTaskComplete(String taskId, bool success,
71 : {Map<String, dynamic>? resultData}) {
72 1 : if (!_enabled) return;
73 :
74 2 : final metrics = _taskMetrics[taskId];
75 : if (metrics == null) return;
76 :
77 2 : metrics.endTime = DateTime.now();
78 1 : metrics.success = success;
79 1 : metrics.resultData = resultData;
80 :
81 2 : _addEvent(PerformanceEvent(
82 : type: success
83 : ? PerformanceEventType.taskCompleted
84 : : PerformanceEventType.taskFailed,
85 : taskId: taskId,
86 1 : timestamp: DateTime.now(),
87 1 : metadata: {
88 2 : 'duration': metrics.duration.inMilliseconds,
89 : 'success': success,
90 : },
91 : ));
92 : }
93 :
94 : /// Record event dispatch latency.
95 1 : void recordEventDispatch(String taskId, Duration latency) {
96 1 : if (!_enabled) return;
97 :
98 2 : _addEvent(PerformanceEvent(
99 : type: PerformanceEventType.eventDispatched,
100 : taskId: taskId,
101 1 : timestamp: DateTime.now(),
102 2 : metadata: {'latency': latency.inMilliseconds},
103 : ));
104 : }
105 :
106 : /// Record chain start.
107 1 : void recordChainStart(String chainId, int stepCount) {
108 1 : if (!_enabled) return;
109 :
110 2 : _addEvent(PerformanceEvent(
111 : type: PerformanceEventType.chainStarted,
112 : taskId: chainId,
113 1 : timestamp: DateTime.now(),
114 1 : metadata: {'stepCount': stepCount},
115 : ));
116 : }
117 :
118 : /// Record chain completion.
119 1 : void recordChainComplete(String chainId, bool success, Duration duration) {
120 1 : if (!_enabled) return;
121 :
122 2 : _addEvent(PerformanceEvent(
123 : type: success
124 : ? PerformanceEventType.chainCompleted
125 : : PerformanceEventType.chainFailed,
126 : taskId: chainId,
127 1 : timestamp: DateTime.now(),
128 1 : metadata: {
129 1 : 'duration': duration.inMilliseconds,
130 : 'success': success,
131 : },
132 : ));
133 : }
134 :
135 : /// Get performance statistics.
136 1 : PerformanceStatistics getStatistics() {
137 1 : if (!_enabled) {
138 0 : return PerformanceStatistics.empty();
139 : }
140 :
141 : final completedTasks =
142 6 : _taskMetrics.values.where((m) => m.endTime != null).toList();
143 : final successfulTasks =
144 5 : completedTasks.where((m) => m.success == true).toList();
145 : final failedTasks =
146 5 : completedTasks.where((m) => m.success == false).toList();
147 :
148 : final durations =
149 5 : completedTasks.map((m) => m.duration.inMilliseconds).toList();
150 1 : final avgDuration = durations.isEmpty
151 : ? 0.0
152 5 : : durations.reduce((a, b) => a + b) / durations.length;
153 : final minDuration =
154 4 : durations.isEmpty ? 0 : durations.reduce((a, b) => a < b ? a : b);
155 : final maxDuration =
156 4 : durations.isEmpty ? 0 : durations.reduce((a, b) => a > b ? a : b);
157 :
158 : // Calculate event dispatch latencies
159 1 : final eventLatencies = _recentEvents
160 4 : .where((e) => e.type == PerformanceEventType.eventDispatched)
161 4 : .map((e) => e.metadata['latency'] as int? ?? 0)
162 1 : .toList();
163 1 : final avgEventLatency = eventLatencies.isEmpty
164 : ? 0.0
165 3 : : eventLatencies.reduce((a, b) => a + b) / eventLatencies.length;
166 :
167 : // Calculate throughput
168 1 : final monitoringDuration = _monitoringStartTime == null
169 : ? Duration.zero
170 3 : : DateTime.now().difference(_monitoringStartTime!);
171 2 : final tasksPerMinute = monitoringDuration.inMinutes == 0
172 : ? 0.0
173 0 : : completedTasks.length / monitoringDuration.inMinutes;
174 :
175 : // Group by worker type
176 1 : final byWorkerType = <String, List<TaskMetrics>>{};
177 2 : for (final metrics in completedTasks) {
178 5 : byWorkerType.putIfAbsent(metrics.workerType, () => []).add(metrics);
179 : }
180 :
181 2 : final workerTypeStats = byWorkerType.map((type, tasks) {
182 : final taskDurations =
183 5 : tasks.map((t) => t.duration.inMilliseconds).toList();
184 1 : final avgTaskDuration = taskDurations.isEmpty
185 : ? 0.0
186 5 : : taskDurations.reduce((a, b) => a + b) / taskDurations.length;
187 1 : return MapEntry(
188 : type,
189 1 : WorkerTypeStatistics(
190 : workerType: type,
191 1 : totalTasks: tasks.length,
192 : averageDuration: avgTaskDuration,
193 : successRate:
194 7 : tasks.where((t) => t.success == true).length / tasks.length,
195 : ));
196 : });
197 :
198 1 : return PerformanceStatistics(
199 2 : totalTasksScheduled: _taskMetrics.length,
200 1 : totalTasksCompleted: completedTasks.length,
201 1 : totalTasksSuccessful: successfulTasks.length,
202 1 : totalTasksFailed: failedTasks.length,
203 : averageTaskDuration: avgDuration,
204 : minTaskDuration: minDuration,
205 : maxTaskDuration: maxDuration,
206 : averageEventDispatchLatency: avgEventLatency,
207 : tasksPerMinute: tasksPerMinute,
208 : monitoringDuration: monitoringDuration,
209 : workerTypeStatistics: workerTypeStats,
210 2 : recentEvents: List.from(_recentEvents),
211 : );
212 : }
213 :
214 : /// Get metrics for a specific task.
215 1 : TaskMetrics? getTaskMetrics(String taskId) {
216 2 : return _taskMetrics[taskId];
217 : }
218 :
219 : /// Get all task metrics.
220 1 : List<TaskMetrics> getAllTaskMetrics() {
221 3 : return List.from(_taskMetrics.values);
222 : }
223 :
224 : /// Clear all recorded data.
225 1 : void clear() {
226 2 : _taskMetrics.clear();
227 2 : _recentEvents.clear();
228 2 : _monitoringStartTime = DateTime.now();
229 : }
230 :
231 1 : void _addEvent(PerformanceEvent event) {
232 2 : _recentEvents.add(event);
233 4 : if (_recentEvents.length > _maxRecentEvents) {
234 0 : _recentEvents.removeFirst();
235 : }
236 : }
237 : }
238 :
239 : /// Metrics for a single task execution.
240 : class TaskMetrics {
241 1 : TaskMetrics({
242 : required this.taskId,
243 : required this.workerType,
244 : required this.startTime,
245 : this.endTime,
246 : this.success,
247 : this.resultData,
248 : });
249 :
250 : final String taskId;
251 : final String workerType;
252 : final DateTime startTime;
253 : DateTime? endTime;
254 : bool? success;
255 : Map<String, dynamic>? resultData;
256 :
257 : /// Duration of task execution.
258 1 : Duration get duration =>
259 4 : endTime == null ? Duration.zero : endTime!.difference(startTime);
260 :
261 : /// Whether the task is still running.
262 2 : bool get isRunning => endTime == null;
263 :
264 1 : @override
265 : String toString() {
266 3 : return 'TaskMetrics(taskId: $taskId, workerType: $workerType, '
267 3 : 'duration: ${duration.inMilliseconds}ms, success: $success)';
268 : }
269 : }
270 :
271 : /// Performance event types.
272 : enum PerformanceEventType {
273 : taskStarted,
274 : taskCompleted,
275 : taskFailed,
276 : chainStarted,
277 : chainCompleted,
278 : chainFailed,
279 : eventDispatched,
280 : }
281 :
282 : /// A performance event record.
283 : class PerformanceEvent {
284 1 : PerformanceEvent({
285 : required this.type,
286 : required this.taskId,
287 : required this.timestamp,
288 : this.metadata = const {},
289 : });
290 :
291 : final PerformanceEventType type;
292 : final String taskId;
293 : final DateTime timestamp;
294 : final Map<String, dynamic> metadata;
295 :
296 1 : @override
297 : String toString() {
298 3 : return 'PerformanceEvent(type: $type, taskId: $taskId, '
299 2 : 'timestamp: $timestamp, metadata: $metadata)';
300 : }
301 : }
302 :
303 : /// Performance statistics summary.
304 : class PerformanceStatistics {
305 1 : PerformanceStatistics({
306 : required this.totalTasksScheduled,
307 : required this.totalTasksCompleted,
308 : required this.totalTasksSuccessful,
309 : required this.totalTasksFailed,
310 : required this.averageTaskDuration,
311 : required this.minTaskDuration,
312 : required this.maxTaskDuration,
313 : required this.averageEventDispatchLatency,
314 : required this.tasksPerMinute,
315 : required this.monitoringDuration,
316 : required this.workerTypeStatistics,
317 : required this.recentEvents,
318 : });
319 :
320 1 : factory PerformanceStatistics.empty() {
321 1 : return PerformanceStatistics(
322 : totalTasksScheduled: 0,
323 : totalTasksCompleted: 0,
324 : totalTasksSuccessful: 0,
325 : totalTasksFailed: 0,
326 : averageTaskDuration: 0,
327 : minTaskDuration: 0,
328 : maxTaskDuration: 0,
329 : averageEventDispatchLatency: 0,
330 : tasksPerMinute: 0,
331 : monitoringDuration: Duration.zero,
332 1 : workerTypeStatistics: {},
333 1 : recentEvents: [],
334 : );
335 : }
336 :
337 : final int totalTasksScheduled;
338 : final int totalTasksCompleted;
339 : final int totalTasksSuccessful;
340 : final int totalTasksFailed;
341 : final double averageTaskDuration;
342 : final int minTaskDuration;
343 : final int maxTaskDuration;
344 : final double averageEventDispatchLatency;
345 : final double tasksPerMinute;
346 : final Duration monitoringDuration;
347 : final Map<String, WorkerTypeStatistics> workerTypeStatistics;
348 : final List<PerformanceEvent> recentEvents;
349 :
350 : /// Success rate (0.0 - 1.0).
351 1 : double get successRate =>
352 5 : totalTasksCompleted == 0 ? 0 : totalTasksSuccessful / totalTasksCompleted;
353 :
354 1 : @override
355 : String toString() {
356 1 : return 'PerformanceStatistics(\n'
357 1 : ' Total tasks: $totalTasksScheduled\n'
358 1 : ' Completed: $totalTasksCompleted\n'
359 1 : ' Successful: $totalTasksSuccessful\n'
360 1 : ' Failed: $totalTasksFailed\n'
361 3 : ' Success rate: ${(successRate * 100).toStringAsFixed(1)}%\n'
362 2 : ' Avg duration: ${averageTaskDuration.toStringAsFixed(1)}ms\n'
363 1 : ' Min duration: ${minTaskDuration}ms\n'
364 1 : ' Max duration: ${maxTaskDuration}ms\n'
365 2 : ' Avg event latency: ${averageEventDispatchLatency.toStringAsFixed(1)}ms\n'
366 2 : ' Throughput: ${tasksPerMinute.toStringAsFixed(2)} tasks/min\n'
367 2 : ' Monitoring duration: ${monitoringDuration.inSeconds}s\n'
368 : ')';
369 : }
370 : }
371 :
372 : /// Statistics for a specific worker type.
373 : class WorkerTypeStatistics {
374 1 : WorkerTypeStatistics({
375 : required this.workerType,
376 : required this.totalTasks,
377 : required this.averageDuration,
378 : required this.successRate,
379 : });
380 :
381 : final String workerType;
382 : final int totalTasks;
383 : final double averageDuration;
384 : final double successRate;
385 :
386 1 : @override
387 : String toString() {
388 1 : return 'WorkerTypeStatistics(\n'
389 1 : ' Type: $workerType\n'
390 1 : ' Tasks: $totalTasks\n'
391 2 : ' Avg duration: ${averageDuration.toStringAsFixed(1)}ms\n'
392 3 : ' Success rate: ${(successRate * 100).toStringAsFixed(1)}%\n'
393 : ')';
394 : }
395 : }
|