Line data Source code
1 : // Copyright The OpenTelemetry Authors
2 : // SPDX-License-Identifier: Apache-2.0
3 :
4 : import 'package:dartastic_opentelemetry_api/dartastic_opentelemetry_api.dart';
5 :
6 : import '../data/metric.dart';
7 : import '../data/metric_point.dart';
8 : import '../meter.dart';
9 : import '../storage/sum_storage.dart';
10 : import 'base_instrument.dart';
11 :
12 : /// A synchronous instrument that records monotonically increasing values.
13 : ///
14 : /// A Counter is used to measure a non-negative, monotonically increasing value.
15 : /// Counters only allow positive increments and are appropriate for values that
16 : /// never decrease, such as:
17 : /// - Request count
18 : /// - Completed operations
19 : /// - Error count
20 : /// - CPU time used
21 : /// - Bytes sent/received
22 : ///
23 : /// If the value can decrease, use an UpDownCounter instead.
24 : ///
25 : /// More information:
26 : /// https://opentelemetry.io/docs/specs/otel/metrics/api/#counter
27 : class Counter<T extends num> implements APICounter<T>, SDKInstrument {
28 : /// The underlying API Counter.
29 : final APICounter<T> _apiCounter;
30 :
31 : /// The Meter that created this Counter.
32 : final Meter _meter;
33 :
34 : /// Storage for accumulating counter measurements.
35 : final SumStorage<T> _storage = SumStorage<T>(isMonotonic: true);
36 :
37 : /// Creates a new Counter instance.
38 : ///
39 : /// @param apiCounter The API Counter to delegate API calls to
40 : /// @param meter The Meter that created this Counter
41 12 : Counter({required APICounter<T> apiCounter, required Meter meter})
42 : : _apiCounter = apiCounter,
43 : _meter = meter {
44 60 : _meter.provider.registerInstrument(_meter.name, this);
45 : }
46 :
47 : /// Gets the name of this counter.
48 12 : @override
49 24 : String get name => _apiCounter.name;
50 :
51 : /// Gets the unit of measurement for this counter.
52 8 : @override
53 16 : String? get unit => _apiCounter.unit;
54 :
55 : /// Gets the description of this counter.
56 8 : @override
57 16 : String? get description => _apiCounter.description;
58 :
59 : /// Checks if this counter is enabled.
60 : ///
61 : /// If false, measurements will be dropped and not recorded.
62 11 : @override
63 22 : bool get enabled => _meter.enabled;
64 :
65 : /// Gets the meter that created this counter.
66 12 : @override
67 12 : APIMeter get meter => _meter;
68 :
69 : /// Always true for Counter instruments.
70 1 : @override
71 : bool get isCounter => true;
72 :
73 : /// Always false for Counter instruments.
74 1 : @override
75 : bool get isUpDownCounter => false;
76 :
77 : /// Always false for Counter instruments.
78 1 : @override
79 : bool get isGauge => false;
80 :
81 : /// Always false for Counter instruments.
82 1 : @override
83 : bool get isHistogram => false;
84 :
85 : /// Records a measurement with this counter.
86 : ///
87 : /// This method increments the counter by the given value. The value must be
88 : /// non-negative, or an ArgumentError will be thrown.
89 : ///
90 : /// @param value The amount to increment the counter by (must be non-negative)
91 : /// @param attributes Optional attributes to associate with this measurement
92 : /// @throws ArgumentError if value is negative
93 11 : @override
94 : void add(T value, [Attributes? attributes]) {
95 : // First use the API implementation (no-op by default)
96 22 : _apiCounter.add(value, attributes);
97 :
98 : // Check for negative values
99 11 : if (value < 0) {
100 1 : throw ArgumentError('Counter value must be non-negative');
101 : }
102 :
103 : // Only record if enabled
104 11 : if (!enabled) return;
105 :
106 : // Record the measurement in our storage
107 22 : _storage.record(value, attributes);
108 : }
109 :
110 : /// Records a measurement with attributes specified as a map.
111 : ///
112 : /// This is a convenience method that converts the map to Attributes
113 : /// and calls add().
114 : ///
115 : /// @param value The amount to increment the counter by (must be non-negative)
116 : /// @param attributeMap Map of attribute names to values
117 1 : @override
118 : void addWithMap(T value, Map<String, Object> attributeMap) {
119 : // Just convert to Attributes and call add
120 : final attributes =
121 2 : attributeMap.isEmpty ? null : attributeMap.toAttributes();
122 1 : add(value, attributes);
123 : }
124 :
125 : /// Gets the current value of the counter for a specific set of attributes.
126 : ///
127 : /// If no attributes are provided, returns the sum of all values across all attributes.
128 : ///
129 : /// @param attributes Optional attributes to filter by
130 : /// @return The current value of the counter
131 4 : T getValue([Attributes? attributes]) {
132 8 : return _storage.getValue(attributes);
133 : }
134 :
135 : /// Gets the current points for this counter.
136 : ///
137 : /// This is used by the SDK to collect metrics for export.
138 : ///
139 : /// @return A list of metric points containing the current counter values
140 7 : List<MetricPoint<T>> collectPoints() {
141 14 : return _storage.collectPoints();
142 : }
143 :
144 : /// Collects metrics for this counter.
145 : ///
146 : /// This method is called by the SDK to collect metrics for export.
147 : ///
148 : /// @return A list of metrics containing the current counter values
149 7 : @override
150 : List<Metric> collectMetrics() {
151 8 : if (!enabled) return [];
152 :
153 : // Get the points from storage
154 7 : final points = collectPoints();
155 :
156 7 : if (points.isEmpty) return [];
157 :
158 7 : final metric = Metric(
159 7 : name: name,
160 7 : description: description,
161 7 : unit: unit,
162 : type: MetricType.sum,
163 : points: points,
164 : );
165 :
166 7 : return [metric];
167 : }
168 :
169 : /// Resets the counter.
170 : ///
171 : /// This is only used for Delta temporality and should not be called
172 : /// by application code.
173 2 : void reset() {
174 4 : _storage.reset();
175 : }
176 : }
|