LCOV - code coverage report
Current view: top level - lib/src/metrics/instruments - observable_gauge.dart (source / functions) Coverage Total Hit
Test: lcov.info Lines: 97.4 % 76 74
Test Date: 2026-10-11 16:35:14 Functions: - 0 0

            Line data    Source code
       1              : // Copyright The OpenTelemetry Authors
       2              : // SPDX-License-Identifier: Apache-2.0
       3              : 
       4              : import '../../../dartastic_opentelemetry.dart';
       5              : 
       6              : /// ObservableGauge is an asynchronous instrument which reports non-additive value(s)
       7              : /// when the instrument is being observed.
       8              : ///
       9              : /// An ObservableGauge is used to asynchronously measure a non-additive current value
      10              : /// that cannot be calculated synchronously.
      11              : class ObservableGauge<T extends num>
      12              :     implements APIObservableGauge<T>, SDKObservableInstrument {
      13              :   /// The underlying API ObservableGauge.
      14              :   final APIObservableGauge<T> _apiGaugeDelegate;
      15              : 
      16              :   /// The Meter that created this ObservableGauge.
      17              :   final Meter _meter;
      18              : 
      19              :   /// Storage for gauge measurements.
      20              :   final GaugeStorage<T> _storage;
      21              : 
      22              :   /// Observations queued by batch callbacks, drained on the next [collect].
      23              :   final List<Measurement<T>> _batchObservations = [];
      24              : 
      25              :   /// Creates a new ObservableGauge instance.
      26            8 :   ObservableGauge({
      27              :     required APIObservableGauge<T> apiGauge,
      28              :     required Meter meter,
      29              :   })  : _apiGaugeDelegate = apiGauge,
      30              :         _meter = meter,
      31            8 :         _storage = GaugeStorage<T>(
      32           16 :           exemplarFilter: meter.provider.exemplarFilter,
      33              :         );
      34              : 
      35            8 :   @override
      36           16 :   String get name => _apiGaugeDelegate.name;
      37              : 
      38            6 :   @override
      39           12 :   String? get unit => _apiGaugeDelegate.unit;
      40              : 
      41            6 :   @override
      42           12 :   String? get description => _apiGaugeDelegate.description;
      43              : 
      44            1 :   @override
      45            2 :   InstrumentAdvisory? get advisory => _apiGaugeDelegate.advisory;
      46              : 
      47            6 :   @override
      48              :   bool isEnabled() {
      49           18 :     return _meter.provider.enabled;
      50              :   }
      51              : 
      52            8 :   @override
      53            8 :   APIMeter get meter => _meter;
      54              : 
      55            6 :   @override
      56           12 :   List<ObservableCallback<T>> get callbacks => _apiGaugeDelegate.callbacks;
      57              : 
      58            3 :   @override
      59              :   APICallbackRegistration<T> addCallback(ObservableCallback<T> callback) {
      60              :     // Register with the API implementation first
      61            6 :     final registration = _apiGaugeDelegate.addCallback(callback);
      62              : 
      63              :     // Return a registration that handles unregistering properly
      64            3 :     return _ObservableGaugeCallbackRegistration<T>(
      65              :       apiRegistration: registration,
      66              :       gauge: this,
      67              :       callback: callback,
      68              :     );
      69              :   }
      70              : 
      71            2 :   @override
      72              :   void removeCallback(ObservableCallback<T> callback) {
      73            4 :     _apiGaugeDelegate.removeCallback(callback);
      74              :   }
      75              : 
      76              :   /// Gets the current value of the gauge for a specific set of attributes.
      77              :   /// If no attributes are provided, returns the average of all recorded values.
      78            1 :   T getValue([Attributes? attributes]) {
      79              :     final num value;
      80              : 
      81              :     if (attributes == null) {
      82              :       // For gauges without attributes, we return the average of all values
      83            2 :       final points = _storage.collectPoints();
      84            1 :       if (points.isEmpty) {
      85              :         value = 0;
      86              :       } else {
      87              :         value =
      88            5 :             points.fold<num>(0, (sum, point) => sum + (point.value as num)) /
      89            1 :                 points.length;
      90              :       }
      91              :     } else {
      92              :       // For specific attributes, get that value
      93            2 :       value = _storage.getValue(attributes);
      94              :     }
      95              : 
      96              :     // Handle the cast to the generic type
      97            2 :     if (T == int) return value.toInt() as T;
      98            2 :     if (T == double) return value.toDouble() as T;
      99              :     return value as T;
     100              :   }
     101              : 
     102            1 :   @override
     103              :   void observeFromBatch(num value, Attributes? attributes) {
     104            2 :     _batchObservations.add(
     105            1 :       OTelFactory.otelFactory!
     106            2 :           .createMeasurement<T>(castNum<T>(value), attributes),
     107              :     );
     108              :   }
     109              : 
     110              :   /// Collects measurements from all registered callbacks and from any
     111              :   /// batch callbacks that observed this instrument since the last collection.
     112            6 :   @override
     113              :   List<Measurement<T>> collect() {
     114            6 :     if (!isEnabled()) {
     115            4 :       _batchObservations.clear();
     116            2 :       return [];
     117              :     }
     118              : 
     119            6 :     final result = <Measurement<T>>[];
     120              : 
     121              :     // Get a snapshot of callbacks to avoid concurrent modification issues
     122           12 :     final callbacksSnapshot = List<ObservableCallback<T>>.from(callbacks);
     123              : 
     124              :     // Call all callbacks
     125           12 :     for (final callback in callbacksSnapshot) {
     126              :       try {
     127              :         // Create a new observable result for each callback
     128            6 :         final observableResult = ObservableResult<T>();
     129              : 
     130              :         // Call the callback with the observable result
     131              :         // Cast the parameter to ensure type safety
     132              :         try {
     133            6 :           callback(observableResult as APIObservableResult<T>);
     134              :         } catch (e) {
     135            4 :           print('Type error in callback: $e');
     136              :           continue;
     137              :         }
     138              : 
     139              :         // Process the measurements from the observable result
     140           12 :         for (final measurement in observableResult.measurements) {
     141            6 :           _record(measurement);
     142            6 :           result.add(measurement);
     143              :         }
     144              :       } catch (e) {
     145            0 :         print(
     146            0 :           'Error collecting measurements from ObservableGauge callback: $e',
     147              :         );
     148              :       }
     149              :     }
     150              : 
     151              :     // Batch callbacks ran before this instrument collected; drain what they
     152              :     // observed into the same storage.
     153            7 :     for (final measurement in _batchObservations) {
     154            1 :       _record(measurement);
     155            1 :       result.add(measurement);
     156              :     }
     157           12 :     _batchObservations.clear();
     158              : 
     159              :     return result;
     160              :   }
     161              : 
     162              :   /// Records one observed value; a gauge keeps only the latest per attributes.
     163            6 :   void _record(Measurement<T> measurement) {
     164              :     final attributes =
     165           18 :         measurement.attributes ?? OTelFactory.otelFactory!.attributes();
     166           30 :     _storage.record(castNum<T>(measurement.value), attributes, Context.current);
     167              :   }
     168              : 
     169              :   /// Collects metrics for the SDK metric export.
     170              :   ///
     171              :   /// This is called by the MeterProvider during metric collection.
     172              :   /// Per the OTel spec, observable instruments must invoke their
     173              :   /// registered callbacks on every collection cycle and report the
     174              :   /// values the callback observes — that's the whole point of being
     175              :   /// "observable" vs sync. Drive [collect] first so the callback
     176              :   /// runs and storage is fresh; discard the returned measurements
     177              :   /// (collect already pushed them into [_storage]).
     178            6 :   @override
     179              :   List<Metric> collectMetrics() {
     180            6 :     if (!isEnabled()) {
     181            3 :       return [];
     182              :     }
     183              : 
     184            6 :     collect();
     185              : 
     186              :     // Get the points from storage
     187            6 :     final points = collectPoints();
     188            6 :     if (points.isEmpty) {
     189            2 :       return [];
     190              :     }
     191              : 
     192              :     // Create the metric to export
     193            6 :     return [
     194            6 :       Metric.gauge(
     195            6 :         name: name,
     196            6 :         description: description,
     197            6 :         unit: unit,
     198              :         points: points,
     199              :       ),
     200              :     ];
     201              :   }
     202              : 
     203              :   /// Gets the current points for this gauge.
     204              :   /// This is used by the SDK to collect metrics.
     205            6 :   List<MetricPoint<T>> collectPoints() {
     206            6 :     if (!isEnabled()) {
     207            1 :       return [];
     208              :     }
     209              : 
     210              :     // Return points from storage
     211           12 :     return _storage.collectPoints();
     212              :   }
     213              : }
     214              : 
     215              : /// Wrapper for APICallbackRegistration that also handles our internal state.
     216              : class _ObservableGaugeCallbackRegistration<T extends num>
     217              :     implements APICallbackRegistration<T> {
     218              :   /// The API registration.
     219              :   final APICallbackRegistration<T> apiRegistration;
     220              : 
     221              :   /// The gauge this registration is for.
     222              :   final ObservableGauge<T> gauge;
     223              : 
     224              :   /// The callback that was registered.
     225              :   final ObservableCallback<T> callback;
     226              : 
     227            3 :   _ObservableGaugeCallbackRegistration({
     228              :     required this.apiRegistration,
     229              :     required this.gauge,
     230              :     required this.callback,
     231              :   });
     232              : 
     233            2 :   @override
     234              :   void unregister() {
     235              :     // Unregister from the API implementation
     236            4 :     apiRegistration.unregister();
     237              : 
     238              :     // Also remove from our gauge directly for redundancy
     239            6 :     gauge.removeCallback(callback);
     240              :   }
     241              : }
        

Generated by: LCOV version 2.0-1