LCOV - code coverage report
Current view: top level - lib/src/metrics - meter_provider.dart (source / functions) Coverage Total Hit
Test: lcov.info Lines: 96.1 % 77 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 'dart:async';
       5              : 
       6              : import 'package:meta/meta.dart';
       7              : 
       8              : import '../../dartastic_opentelemetry.dart';
       9              : import 'meter.dart';
      10              : 
      11              : part 'meter_provider_create.dart';
      12              : 
      13              : /// SDK implementation of the APIMeterProvider interface.
      14              : ///
      15              : /// The MeterProvider is the entry point to the metrics API. It is responsible
      16              : /// for creating and managing Meters, as well as configuring the metric pipeline
      17              : /// via MetricReaders and Views.
      18              : ///
      19              : /// This implementation delegates some functionality to the API MeterProvider
      20              : /// implementation while adding SDK-specific behaviors.
      21              : ///
      22              : /// More information:
      23              : /// https://opentelemetry.io/docs/specs/otel/metrics/sdk/
      24              : class MeterProvider implements APIMeterProvider {
      25              :   /// The underlying API MeterProvider implementation.
      26              :   final APIMeterProvider delegate;
      27              : 
      28              :   /// The resource associated with this MeterProvider.
      29              :   Resource? resource;
      30              : 
      31              :   /// List of metric readers associated with this MeterProvider.
      32              :   final List<MetricReader> _metricReaders = [];
      33              : 
      34              :   /// List of views for configuring metric collection.
      35              :   final List<View> _views = [];
      36              : 
      37              :   /// The ExemplarFilter used by this provider's meters.
      38              :   ExemplarFilter exemplarFilter = const TraceBasedExemplarFilter();
      39              : 
      40              :   /// The endpoint URL this provider was configured with.
      41              :   ///
      42              :   /// The API's [APIMeterProvider] holds no configuration or operational
      43              :   /// state (metrics/noop.md), so the SDK keeps it here.
      44              :   String endpoint;
      45              : 
      46              :   /// The name of the service being instrumented.
      47              :   String serviceName;
      48              : 
      49              :   /// The version of the service being instrumented.
      50              :   String? serviceVersion;
      51              : 
      52              :   /// Whether this provider records measurements.
      53              :   ///
      54              :   /// When false, every meter and instrument created by this provider reports
      55              :   /// `isEnabled() == false` and drops measurements.
      56              :   bool enabled = true;
      57              : 
      58              :   /// Whether [shutdown] has completed.
      59              :   bool isShutdown = false;
      60              : 
      61              :   /// Private constructor for creating MeterProvider instances.
      62              :   ///
      63              :   /// @param delegate The API MeterProvider implementation to delegate to
      64              :   /// @param resource Optional Resource describing the entity producing telemetry
      65          134 :   MeterProvider._({
      66              :     required this.delegate,
      67              :     required this.endpoint,
      68              :     required this.serviceName,
      69              :     this.serviceVersion,
      70              :     this.resource,
      71              :   }) {
      72          134 :     if (OTelLog.isDebug()) {
      73          375 :       OTelLog.debug('MeterProvider: Created with resource: $resource');
      74              :     }
      75              :   }
      76              : 
      77           33 :   @override
      78              :   APIMeter getMeter({
      79              :     required String name,
      80              :     String? version,
      81              :     String? schemaUrl,
      82              :     Attributes? attributes,
      83              :   }) {
      84              :     // Check if provider is shutdown
      85           33 :     if (isShutdown) {
      86              :       // Return a no-op meter instead of throwing
      87            1 :       if (OTelLog.isDebug()) {
      88            1 :         OTelLog.debug(
      89            1 :           'MeterProvider: Attempting to get meter "$name" after shutdown. Returning a no-op meter.',
      90              :         );
      91              :       }
      92            1 :       return NoopMeter(name: name, version: version, schemaUrl: schemaUrl);
      93              :     }
      94              : 
      95              :     // Create a unique key for this meter
      96           33 :     final meterKey = '$name:${version ?? ''}:${schemaUrl ?? ''}';
      97              : 
      98              :     // Return an existing meter if we already have one with this configuration
      99           66 :     if (_meters.containsKey(meterKey)) {
     100            6 :       return _meters[meterKey]!;
     101              :     }
     102              : 
     103              :     // Call the API implementation first
     104           66 :     final apiMeter = delegate.getMeter(
     105              :       name: name,
     106              :       version: version,
     107              :       schemaUrl: schemaUrl,
     108              :       attributes: attributes,
     109              :     );
     110              : 
     111              :     // Wrap it with our SDK implementation
     112           33 :     final meter = MeterCreate.create(delegate: apiMeter, provider: this);
     113              : 
     114              :     // Store the meter in the registry
     115           66 :     _meters[meterKey] = meter;
     116              : 
     117              :     // Initialize the instruments set for this meter
     118           66 :     _instruments[meterKey] = {};
     119              : 
     120           33 :     if (OTelLog.isLogMetrics()) {
     121           30 :       OTelLog.logMetric(
     122           30 :         'MeterProvider: Created meter "$name" (version: $version)',
     123              :       );
     124              :     }
     125              : 
     126              :     return meter;
     127              :   }
     128              : 
     129              :   /// Adds a MetricReader to this MeterProvider.
     130              :   ///
     131              :   /// MetricReaders are responsible for collecting and exporting metrics.
     132              :   /// They can be configured to collect metrics at different intervals and
     133              :   /// export them to different backends.
     134              :   ///
     135              :   /// @param reader The MetricReader to add
     136          132 :   void addMetricReader(MetricReader reader) {
     137          264 :     if (!_metricReaders.contains(reader)) {
     138          264 :       _metricReaders.add(reader);
     139          132 :       reader.registerMeterProvider(this);
     140              :     }
     141              :   }
     142              : 
     143              :   /// Adds a View to this MeterProvider.
     144              :   ///
     145              :   /// Views allow for customizing how metrics are collected and aggregated.
     146              :   /// They can be used to filter, transform, and aggregate metrics before
     147              :   /// they are exported.
     148              :   ///
     149              :   /// @param view The View to add
     150            2 :   void addView(View view) {
     151            4 :     _views.add(view);
     152              :   }
     153              : 
     154              :   /// Gets all views configured for this MeterProvider.
     155              :   ///
     156              :   /// @return An unmodifiable list of all views
     157            6 :   List<View> get views => List.unmodifiable(_views);
     158              : 
     159              :   /// Gets all metric readers associated with this MeterProvider.
     160              :   ///
     161              :   /// @return An unmodifiable list of all metric readers
     162           24 :   List<MetricReader> get metricReaders => List.unmodifiable(_metricReaders);
     163              : 
     164              :   /// Registry of all meters created by this provider
     165              :   final Map<String, Meter> _meters = {};
     166              : 
     167              :   /// Registry of active instruments across all meters
     168              :   final Map<String, Set<SDKInstrument>> _instruments = {};
     169              : 
     170              :   /// Registers an instrument with this provider.
     171              :   ///
     172              :   /// This allows the provider to track all active instruments for metrics collection.
     173              :   ///
     174              :   /// @param instrumentName The name of the instrument
     175              :   /// @param instrument The instrument to register
     176           31 :   void registerInstrument(String instrumentName, SDKInstrument instrument) {
     177           62 :     final meterKey = instrument.meter.name;
     178           62 :     if (!_instruments.containsKey(meterKey)) {
     179           62 :       _instruments[meterKey] = {};
     180              :     }
     181              : 
     182           93 :     _instruments[meterKey]!.add(instrument);
     183              : 
     184           31 :     if (OTelLog.isLogMetrics()) {
     185           28 :       OTelLog.logMetric(
     186          112 :         'MeterProvider: Registered instrument "${instrument.name}" for meter "${instrument.meter.name}"',
     187              :       );
     188              :     }
     189              :   }
     190              : 
     191              :   /// Collects all metrics from all instruments across all meters.
     192              :   ///
     193              :   /// This is called by metric readers to gather the current metrics.
     194              :   ///
     195              :   /// @return A list of all collected metrics
     196          128 :   Future<List<Metric>> collectAllMetrics() async {
     197          128 :     if (isShutdown) {
     198          117 :       return [];
     199              :     }
     200              : 
     201           19 :     final allMetrics = <Metric>[];
     202              : 
     203              :     // Batch callbacks observe several instruments at once, so they run
     204              :     // once per collection before any instrument collects; each instrument
     205              :     // then drains what was observed for it alongside its own callbacks.
     206           54 :     for (final meter in _meters.values) {
     207           16 :       meter.runBatchCallbacks();
     208              :     }
     209              : 
     210              :     // Collect from each meter's instruments
     211           54 :     for (final entry in _instruments.entries) {
     212           16 :       final meterName = entry.key;
     213           16 :       final instruments = entry.value;
     214              : 
     215           16 :       if (OTelLog.isLogMetrics()) {
     216           13 :         OTelLog.logMetric(
     217           26 :           'MeterProvider: Collecting metrics from ${instruments.length} instruments in meter "$meterName"',
     218              :         );
     219              :       }
     220              : 
     221              :       // Collect metrics from each instrument
     222           32 :       for (final instrument in instruments) {
     223              :         try {
     224           16 :           final metrics = instrument.collectMetrics();
     225           16 :           if (metrics.isNotEmpty) {
     226           16 :             allMetrics.addAll(metrics);
     227              : 
     228           16 :             if (OTelLog.isLogMetrics()) {
     229           13 :               OTelLog.logMetric(
     230           39 :                 'MeterProvider: Collected ${metrics.length} metrics from instrument "${instrument.name}"',
     231              :               );
     232              :             }
     233              :           }
     234              :         } catch (e) {
     235            0 :           if (OTelLog.isLogMetrics()) {
     236            0 :             OTelLog.logMetric(
     237            0 :               'MeterProvider: Error collecting metrics from instrument "${instrument.name}": $e',
     238              :             );
     239              :           }
     240              :         }
     241              :       }
     242              :     }
     243              : 
     244           19 :     if (OTelLog.isLogMetrics()) {
     245           16 :       OTelLog.logMetric(
     246           32 :         'MeterProvider: Collected ${allMetrics.length} total metrics',
     247              :       );
     248              :     }
     249              : 
     250              :     return allMetrics;
     251              :   }
     252              : 
     253              :   /// Force flushes metrics through all associated MetricReaders.
     254              :   ///
     255              :   /// This method forces an immediate collection and export of metrics
     256              :   /// through all registered metric readers.
     257              :   ///
     258              :   /// @return true if all flushes were successful, false otherwise
     259            4 :   @override
     260              :   Future<bool> forceFlush() async {
     261            4 :     if (isShutdown) {
     262            2 :       if (OTelLog.isLogExport()) {
     263            2 :         OTelLog.logExport('MeterProvider: Cannot flush after shutdown');
     264              :       }
     265              :       return false;
     266              :     }
     267              : 
     268            4 :     if (OTelLog.isLogExport()) {
     269            3 :       OTelLog.logExport(
     270            9 :         'MeterProvider: Force flushing metrics through ${_metricReaders.length} readers',
     271              :       );
     272              :     }
     273              : 
     274              :     var success = true;
     275            8 :     for (final reader in _metricReaders) {
     276            4 :       final result = await reader.forceFlush();
     277              :       success = success && result;
     278              :     }
     279              :     return success;
     280              :   }
     281              : 
     282              :   /// Shuts down this MeterProvider and all associated resources.
     283              :   ///
     284              :   /// This method shuts down all metric readers and prevents the creation
     285              :   /// of new meters. Any subsequent calls to getMeter() will return a no-op
     286              :   /// meter.
     287              :   ///
     288              :   /// @return true if shutdown was successful, false otherwise
     289          132 :   @override
     290              :   Future<bool> shutdown() async {
     291          132 :     if (isShutdown) {
     292              :       return true; // Already shut down
     293              :     }
     294              : 
     295              :     // Mark as shut down immediately to prevent new interactions
     296          132 :     isShutdown = true;
     297              : 
     298              :     var success = true;
     299              : 
     300              :     // Shutdown all metric readers
     301          262 :     for (final reader in _metricReaders) {
     302          130 :       final result = await reader.shutdown();
     303              :       success = success && result;
     304              :     }
     305              : 
     306              :     // Clear collections
     307          264 :     _metricReaders.clear();
     308          264 :     _views.clear();
     309              : 
     310              :     // Finally call the underlying API implementation
     311          264 :     await delegate.shutdown();
     312              : 
     313              :     return success;
     314              :   }
     315              : }
        

Generated by: LCOV version 2.0-1