LCOV - code coverage report
Current view: top level - src/metrics/storage - sum_storage.dart (source / functions) Coverage Total Hit
Test: lcov.info Lines: 81.2 % 48 39
Test Date: 2025-11-15 13:23:01 Functions: - 0 0

            Line data    Source code
       1              : // Licensed under the Apache License, Version 2.0
       2              : // Copyright 2025, Michael Bushe, All rights reserved.
       3              : 
       4              : import 'package:dartastic_opentelemetry_api/dartastic_opentelemetry_api.dart';
       5              : 
       6              : import '../data/exemplar.dart';
       7              : import '../data/metric_point.dart';
       8              : import 'metric_storage.dart';
       9              : 
      10              : /// Storage implementation for sum-based metrics like Counter and UpDownCounter.
      11              : ///
      12              : /// SumStorage accumulates measurements for sum-based instruments. It maintains
      13              : /// separate accumulated values for each unique set of attributes, and provides
      14              : /// methods to collect the current state as metric points.
      15              : ///
      16              : /// This storage implementation supports both monotonic sums (like Counter)
      17              : /// and non-monotonic sums (like UpDownCounter).
      18              : ///
      19              : /// More information:
      20              : /// https://opentelemetry.io/docs/specs/otel/metrics/sdk/#the-temporality-of-instruments
      21              : class SumStorage<T extends num> extends NumericStorage<T> {
      22              :   /// Map of attribute sets to accumulated values.
      23              :   final Map<Attributes?, _SumPointData<T>> _points = {};
      24              : 
      25              :   /// Whether the sum is monotonic (only increases).
      26              :   ///
      27              :   /// Monotonic sums only accept positive increments and are
      28              :   /// appropriate for counters that never decrease.
      29              :   final bool isMonotonic;
      30              : 
      31              :   /// The start time for all points.
      32              :   ///
      33              :   /// This is used for cumulative temporality reporting.
      34              :   final DateTime _startTime = DateTime.now();
      35              : 
      36              :   /// Creates a new SumStorage instance.
      37              :   ///
      38              :   /// @param isMonotonic Whether this storage is for a monotonic sum
      39           15 :   SumStorage({
      40              :     required this.isMonotonic,
      41              :   });
      42              : 
      43              :   /// Records a measurement with the given attributes.
      44              :   ///
      45              :   /// For synchronous instruments, this is a delta that gets added to the existing value.
      46              :   /// For asynchronous instruments, this should be the absolute value.
      47              :   ///
      48              :   /// @param value The value to record
      49              :   /// @param attributes Optional attributes to associate with this measurement
      50           14 :   @override
      51              :   void record(T value, [Attributes? attributes]) {
      52              :     // Check constraints for monotonic counters
      53           25 :     if (isMonotonic && value < 0) {
      54            0 :       print('Warning: Negative value $value provided to monotonic sum storage. '
      55              :           'This will be ignored.');
      56              :       return;
      57              :     }
      58              : 
      59              :     // Check if we already have an entry for these attributes
      60           28 :     if (_points.containsKey(attributes)) {
      61              :       // Add to existing data point
      62           24 :       _points[attributes]!.add(value);
      63              :     } else {
      64              :       // Create new data point
      65           42 :       _points[attributes] = _SumPointData<T>(
      66              :         value: value,
      67           14 :         lastUpdateTime: DateTime.now(),
      68              :       );
      69              :     }
      70              :   }
      71              : 
      72              :   /// Gets the current value for the given attributes.
      73              :   ///
      74              :   /// If no attributes are provided, returns the sum across all attribute sets.
      75              :   ///
      76              :   /// @param attributes Optional attributes to filter by
      77              :   /// @return The current accumulated value
      78            6 :   @override
      79              :   T getValue([Attributes? attributes]) {
      80              :     num result;
      81              : 
      82              :     if (attributes == null) {
      83              :       // Sum of all values across all attribute sets
      84           36 :       result = _points.values.fold<num>(0, (sum, data) => sum + data.value);
      85           12 :     } else if (_points.containsKey(attributes)) {
      86              :       // Return the value for the specific attributes
      87           18 :       result = _points[attributes]!.value;
      88              :     } else {
      89              :       // No entry for these attributes
      90              :       result = 0;
      91              :     }
      92              : 
      93              :     // Convert to the appropriate generic type
      94            6 :     if (T == int) {
      95            5 :       return result.toInt() as T;
      96            3 :     } else if (T == double) {
      97            2 :       return result.toDouble() as T;
      98              :     } else {
      99              :       return result as T;
     100              :     }
     101              :   }
     102              : 
     103              :   /// Collects the current set of metric points.
     104              :   ///
     105              :   /// This method is used by the instrument to collect all current
     106              :   /// sum values as metric points for export.
     107              :   ///
     108              :   /// @return A list of metric points containing the current values
     109           10 :   @override
     110              :   List<MetricPoint<T>> collectPoints() {
     111           10 :     final now = DateTime.now();
     112              : 
     113           40 :     return _points.entries.map((entry) {
     114              :       // Convert null attributes to empty attributes for MetricPoint
     115           18 :       final attributes = entry.key ?? OTelFactory.otelFactory!.attributes();
     116              : 
     117              :       // Convert numeric value to the specific generic type T
     118              :       final T typedValue;
     119           10 :       if (T == int) {
     120           27 :         typedValue = entry.value.value.toInt() as T;
     121            3 :       } else if (T == double) {
     122            6 :         typedValue = entry.value.value.toDouble() as T;
     123              :       } else {
     124            2 :         typedValue = entry.value.value;
     125              :       }
     126              : 
     127           10 :       return MetricPoint<T>.sum(
     128              :         attributes: attributes,
     129           10 :         startTime: _startTime,
     130              :         time: now,
     131              :         value: typedValue,
     132           10 :         isMonotonic: isMonotonic,
     133           20 :         exemplars: entry.value.exemplars,
     134              :       );
     135           10 :     }).toList();
     136              :   }
     137              : 
     138              :   /// Resets all points (for delta temporality).
     139              :   ///
     140              :   /// This method clears all accumulated values. It is used when
     141              :   /// reporting with delta temporality to reset the accumulation
     142              :   /// after each export.
     143            5 :   @override
     144              :   void reset() {
     145           10 :     _points.clear();
     146              :   }
     147              : 
     148              :   /// Adds an exemplar to a specific point.
     149              :   ///
     150              :   /// Exemplars are example measurements that provide additional
     151              :   /// context about specific observations.
     152              :   ///
     153              :   /// @param exemplar The exemplar to add
     154              :   /// @param attributes The attributes identifying the point to add the exemplar to
     155            0 :   @override
     156              :   void addExemplar(Exemplar exemplar, [Attributes? attributes]) {
     157            0 :     if (_points.containsKey(attributes)) {
     158            0 :       _points[attributes]!.exemplars.add(exemplar);
     159              :     }
     160              :   }
     161              : }
     162              : 
     163              : /// Internal class representing data for a single sum point.
     164              : ///
     165              : /// This class tracks the accumulated value, last update time,
     166              : /// and exemplars for a specific combination of attributes.
     167              : class _SumPointData<T extends num> {
     168              :   /// The accumulated value.
     169              :   T value;
     170              : 
     171              :   /// The time this point was last updated.
     172              :   DateTime lastUpdateTime;
     173              : 
     174              :   /// Exemplars for this point.
     175              :   final List<Exemplar> exemplars = [];
     176              : 
     177              :   /// Creates a new _SumPointData instance.
     178              :   ///
     179              :   /// @param value The initial value
     180              :   /// @param lastUpdateTime The time of the initial value
     181           14 :   _SumPointData({
     182              :     required this.value,
     183              :     required this.lastUpdateTime,
     184              :   });
     185              : 
     186              :   /// Adds a value to this point (for synchronous counters).
     187              :   ///
     188              :   /// @param delta The value to add to the accumulated value
     189            8 :   void add(T delta) {
     190              :     // Handle the addition with proper type conversion
     191            8 :     if (T == int) {
     192           28 :       value = (value + delta).toInt() as T;
     193            3 :     } else if (T == double) {
     194            8 :       value = (value + delta).toDouble() as T;
     195              :     } else {
     196            3 :       value = (value + delta) as T;
     197              :     }
     198              : 
     199           16 :     lastUpdateTime = DateTime.now();
     200              :   }
     201              : 
     202              :   /// Sets the value directly (for asynchronous counters).
     203              :   ///
     204              :   /// @param newValue The new absolute value to set
     205            0 :   void setValue(T newValue) {
     206            0 :     value = newValue;
     207            0 :     lastUpdateTime = DateTime.now();
     208              :   }
     209              : 
     210            0 :   @override
     211            0 :   String toString() => 'SumPointData(value: $value)';
     212              : }
        

Generated by: LCOV version 2.0-1