LCOV - code coverage report
Current view: top level - src/performance - performance_monitor.dart Coverage Total Hit
Test: lcov.info Lines: 97.7 % 129 126
Test Date: 2026-04-30 18:23:23 Functions: - 0 0

            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              : }
        

Generated by: LCOV version 2.4-0