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