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