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;
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 15 : Counter({required APICounter<T> apiCounter, required Meter meter})
42 : : _apiCounter = apiCounter,
43 : _meter = meter,
44 15 : _storage = SumStorage<T>(
45 : isMonotonic: true,
46 30 : exemplarFilter: meter.provider.exemplarFilter,
47 : ) {
48 75 : _meter.provider.registerInstrument(_meter.name, this);
49 : }
50 :
51 : /// Gets the name of this counter.
52 15 : @override
53 30 : String get name => _apiCounter.name;
54 :
55 : /// Gets the unit of measurement for this counter.
56 11 : @override
57 22 : String? get unit => _apiCounter.unit;
58 :
59 : /// Gets the description of this counter.
60 11 : @override
61 22 : String? get description => _apiCounter.description;
62 :
63 : /// Checks if this counter is enabled.
64 : ///
65 : /// If false, measurements will be dropped and not recorded.
66 14 : @override
67 28 : bool isEnabled() => _meter.isEnabled();
68 :
69 : /// Gets the meter that created this counter.
70 15 : @override
71 15 : APIMeter get meter => _meter;
72 :
73 : /// Always true for Counter instruments.
74 1 : @override
75 : bool get isCounter => true;
76 :
77 : /// Always false for Counter instruments.
78 1 : @override
79 : bool get isUpDownCounter => false;
80 :
81 : /// Always false for Counter instruments.
82 1 : @override
83 : bool get isGauge => false;
84 :
85 : /// Always false for Counter instruments.
86 1 : @override
87 : bool get isHistogram => false;
88 :
89 : /// Records a measurement with this counter.
90 : ///
91 : /// This method increments the counter by the given value. The value must be
92 : /// non-negative, or an ArgumentError will be thrown.
93 : ///
94 : /// @param value The amount to increment the counter by (must be non-negative)
95 : /// @param attributes Optional attributes to associate with this measurement
96 : /// @throws ArgumentError if value is negative
97 14 : @override
98 : void add(T value, [Attributes? attributes]) {
99 : // First use the API implementation (no-op by default)
100 28 : _apiCounter.add(value, attributes);
101 :
102 : // Check for negative values
103 14 : if (value < 0) {
104 1 : throw ArgumentError('Counter value must be non-negative');
105 : }
106 :
107 : // Only record if enabled
108 14 : if (!isEnabled()) return;
109 :
110 : // Record the measurement in our storage
111 42 : _storage.record(value, attributes, Context.current);
112 : }
113 :
114 : /// Records a measurement with attributes specified as a map.
115 : ///
116 : /// This is a convenience method that converts the map to Attributes
117 : /// and calls add().
118 : ///
119 : /// @param value The amount to increment the counter by (must be non-negative)
120 : /// @param attributeMap Map of attribute names to values
121 1 : @override
122 : void addWithMap(T value, Map<String, Object> attributeMap) {
123 : // Just convert to Attributes and call add
124 : final attributes =
125 2 : attributeMap.isEmpty ? null : attributeMap.toAttributes();
126 1 : add(value, attributes);
127 : }
128 :
129 : /// Gets the current value of the counter for a specific set of attributes.
130 : ///
131 : /// If no attributes are provided, returns the sum of all values across all attributes.
132 : ///
133 : /// @param attributes Optional attributes to filter by
134 : /// @return The current value of the counter
135 4 : T getValue([Attributes? attributes]) {
136 8 : return _storage.getValue(attributes);
137 : }
138 :
139 : /// Gets the current points for this counter.
140 : ///
141 : /// This is used by the SDK to collect metrics for export.
142 : ///
143 : /// @return A list of metric points containing the current counter values
144 10 : List<MetricPoint<T>> collectPoints() {
145 20 : return _storage.collectPoints();
146 : }
147 :
148 : /// Collects metrics for this counter.
149 : ///
150 : /// This method is called by the SDK to collect metrics for export.
151 : ///
152 : /// @return A list of metrics containing the current counter values
153 10 : @override
154 : List<Metric> collectMetrics() {
155 11 : if (!isEnabled()) return [];
156 :
157 : // Get the points from storage
158 10 : final points = collectPoints();
159 :
160 10 : if (points.isEmpty) return [];
161 :
162 10 : final metric = Metric(
163 10 : name: name,
164 10 : description: description,
165 10 : unit: unit,
166 : type: MetricType.sum,
167 : points: points,
168 : );
169 :
170 10 : return [metric];
171 : }
172 :
173 : /// Resets the counter.
174 : ///
175 : /// This is only used for Delta temporality and should not be called
176 : /// by application code.
177 2 : void reset() {
178 4 : _storage.reset();
179 : }
180 : }
|