LCOV - code coverage report
Current view: top level - lib/src/metrics/instruments - observable_gauge.dart (source / functions) Coverage Total Hit
Test: lcov.info Lines: 95.7 % 69 66
Test Date: 2026-08-27 23:42:02 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>, SDKInstrument {
      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              :   /// Creates a new ObservableGauge instance.
      23            7 :   ObservableGauge({
      24              :     required APIObservableGauge<T> apiGauge,
      25              :     required Meter meter,
      26              :   })  : _apiGaugeDelegate = apiGauge,
      27              :         _meter = meter,
      28            7 :         _storage = GaugeStorage<T>(
      29           14 :           exemplarFilter: meter.provider.exemplarFilter,
      30              :         );
      31              : 
      32            7 :   @override
      33           14 :   String get name => _apiGaugeDelegate.name;
      34              : 
      35            5 :   @override
      36           10 :   String? get unit => _apiGaugeDelegate.unit;
      37              : 
      38            5 :   @override
      39           10 :   String? get description => _apiGaugeDelegate.description;
      40              : 
      41            5 :   @override
      42              :   bool isEnabled() {
      43           15 :     return _meter.provider.enabled;
      44              :   }
      45              : 
      46            7 :   @override
      47            7 :   APIMeter get meter => _meter;
      48              : 
      49            5 :   @override
      50           10 :   List<ObservableCallback<T>> get callbacks => _apiGaugeDelegate.callbacks;
      51              : 
      52            3 :   @override
      53              :   APICallbackRegistration<T> addCallback(ObservableCallback<T> callback) {
      54              :     // Register with the API implementation first
      55            6 :     final registration = _apiGaugeDelegate.addCallback(callback);
      56              : 
      57              :     // Return a registration that handles unregistering properly
      58            3 :     return _ObservableGaugeCallbackRegistration<T>(
      59              :       apiRegistration: registration,
      60              :       gauge: this,
      61              :       callback: callback,
      62              :     );
      63              :   }
      64              : 
      65            2 :   @override
      66              :   void removeCallback(ObservableCallback<T> callback) {
      67            4 :     _apiGaugeDelegate.removeCallback(callback);
      68              :   }
      69              : 
      70              :   /// Gets the current value of the gauge for a specific set of attributes.
      71              :   /// If no attributes are provided, returns the average of all recorded values.
      72            1 :   T getValue([Attributes? attributes]) {
      73              :     final num value;
      74              : 
      75              :     if (attributes == null) {
      76              :       // For gauges without attributes, we return the average of all values
      77            2 :       final points = _storage.collectPoints();
      78            1 :       if (points.isEmpty) {
      79              :         value = 0;
      80              :       } else {
      81              :         value =
      82            5 :             points.fold<num>(0, (sum, point) => sum + (point.value as num)) /
      83            1 :                 points.length;
      84              :       }
      85              :     } else {
      86              :       // For specific attributes, get that value
      87            2 :       value = _storage.getValue(attributes);
      88              :     }
      89              : 
      90              :     // Handle the cast to the generic type
      91            2 :     if (T == int) return value.toInt() as T;
      92            2 :     if (T == double) return value.toDouble() as T;
      93              :     return value as T;
      94              :   }
      95              : 
      96              :   /// Collects measurements from all registered callbacks.
      97            5 :   @override
      98              :   List<Measurement<T>> collect() {
      99            5 :     if (!isEnabled()) {
     100            2 :       return [];
     101              :     }
     102              : 
     103            5 :     final result = <Measurement<T>>[];
     104              : 
     105              :     // Get a snapshot of callbacks to avoid concurrent modification issues
     106           10 :     final callbacksSnapshot = List<ObservableCallback<T>>.from(callbacks);
     107              : 
     108              :     // Call all callbacks
     109           10 :     for (final callback in callbacksSnapshot) {
     110              :       try {
     111              :         // Create a new observable result for each callback
     112            5 :         final observableResult = ObservableResult<T>();
     113              : 
     114              :         // Call the callback with the observable result
     115              :         // Cast the parameter to ensure type safety
     116              :         try {
     117            5 :           callback(observableResult as APIObservableResult<T>);
     118              :         } catch (e) {
     119            4 :           print('Type error in callback: $e');
     120              :           continue;
     121              :         }
     122              : 
     123              :         // Process the measurements from the observable result
     124           10 :         for (final measurement in observableResult.measurements) {
     125              :           // Type checking for the generic parameter
     126            5 :           final value = measurement.value;
     127              : 
     128              :           final num numValue;
     129              :           numValue = value;
     130              : 
     131              :           // For observable gauges, we just record the latest value
     132              :           // For SDK storage, convert the num to the appropriate T type
     133              :           final attributes =
     134           15 :               measurement.attributes ?? OTelFactory.otelFactory!.attributes();
     135            5 :           if (T == int) {
     136           12 :             _storage.record(numValue.toInt() as T, attributes, Context.current);
     137            4 :           } else if (T == double) {
     138            8 :             _storage.record(
     139            8 :                 numValue.toDouble() as T, attributes, Context.current);
     140              :           } else {
     141            0 :             _storage.record(numValue as T, attributes, Context.current);
     142              :           }
     143              : 
     144            5 :           result.add(measurement);
     145              :         }
     146              :       } catch (e) {
     147            0 :         print(
     148            0 :           'Error collecting measurements from ObservableGauge callback: $e',
     149              :         );
     150              :       }
     151              :     }
     152              : 
     153              :     return result;
     154              :   }
     155              : 
     156              :   /// Collects metrics for the SDK metric export.
     157              :   ///
     158              :   /// This is called by the MeterProvider during metric collection.
     159              :   /// Per the OTel spec, observable instruments must invoke their
     160              :   /// registered callbacks on every collection cycle and report the
     161              :   /// values the callback observes — that's the whole point of being
     162              :   /// "observable" vs sync. Drive [collect] first so the callback
     163              :   /// runs and storage is fresh; discard the returned measurements
     164              :   /// (collect already pushed them into [_storage]).
     165            5 :   @override
     166              :   List<Metric> collectMetrics() {
     167            5 :     if (!isEnabled()) {
     168            3 :       return [];
     169              :     }
     170              : 
     171            5 :     collect();
     172              : 
     173              :     // Get the points from storage
     174            5 :     final points = collectPoints();
     175            5 :     if (points.isEmpty) {
     176            1 :       return [];
     177              :     }
     178              : 
     179              :     // Create the metric to export
     180            5 :     return [
     181            5 :       Metric.gauge(
     182            5 :         name: name,
     183            5 :         description: description,
     184            5 :         unit: unit,
     185              :         points: points,
     186              :       ),
     187              :     ];
     188              :   }
     189              : 
     190              :   /// Gets the current points for this gauge.
     191              :   /// This is used by the SDK to collect metrics.
     192            5 :   List<MetricPoint<T>> collectPoints() {
     193            5 :     if (!isEnabled()) {
     194            1 :       return [];
     195              :     }
     196              : 
     197              :     // Return points from storage
     198           10 :     return _storage.collectPoints();
     199              :   }
     200              : }
     201              : 
     202              : /// Wrapper for APICallbackRegistration that also handles our internal state.
     203              : class _ObservableGaugeCallbackRegistration<T extends num>
     204              :     implements APICallbackRegistration<T> {
     205              :   /// The API registration.
     206              :   final APICallbackRegistration<T> apiRegistration;
     207              : 
     208              :   /// The gauge this registration is for.
     209              :   final ObservableGauge<T> gauge;
     210              : 
     211              :   /// The callback that was registered.
     212              :   final ObservableCallback<T> callback;
     213              : 
     214            3 :   _ObservableGaugeCallbackRegistration({
     215              :     required this.apiRegistration,
     216              :     required this.gauge,
     217              :     required this.callback,
     218              :   });
     219              : 
     220            2 :   @override
     221              :   void unregister() {
     222              :     // Unregister from the API implementation
     223            4 :     apiRegistration.unregister();
     224              : 
     225              :     // Also remove from our gauge directly for redundancy
     226            6 :     gauge.removeCallback(callback);
     227              :   }
     228              : }
        

Generated by: LCOV version 2.0-1