Skip to main content

fraiseql_wire/metrics/
counters.rs

1//! Counter metrics for fraiseql-wire
2//!
3//! Counters track counts of events that only increase over time:
4//! - Queries submitted, completed, failed
5//! - Errors by category
6//! - Rows processed, filtered, deserialized
7//! - Authentication attempts and successes
8
9use crate::metrics::labels;
10use metrics::counter;
11
12/// Record a query submission
13pub fn query_submitted(
14    entity: &str,
15    has_where_sql: bool,
16    has_where_rust: bool,
17    has_order_by: bool,
18) {
19    counter!(
20        "fraiseql_queries_total",
21        labels::ENTITY => entity.to_string(),
22        "has_where_sql" => has_where_sql.to_string(),
23        "has_where_rust" => has_where_rust.to_string(),
24        "has_order_by" => has_order_by.to_string(),
25    )
26    .increment(1);
27}
28
29/// Record a successful query completion
30pub fn query_success(entity: &str) {
31    counter!(
32        "fraiseql_query_success_total",
33        labels::ENTITY => entity.to_string(),
34    )
35    .increment(1);
36}
37
38/// Record a failed query
39pub fn query_error(entity: &str, error_category: &str) {
40    counter!(
41        "fraiseql_query_error_total",
42        labels::ENTITY => entity.to_string(),
43        labels::ERROR_CATEGORY => error_category.to_string(),
44    )
45    .increment(1);
46}
47
48/// Record a cancelled query
49pub fn query_cancelled(entity: &str) {
50    counter!(
51        "fraiseql_query_cancelled_total",
52        labels::ENTITY => entity.to_string(),
53    )
54    .increment(1);
55}
56
57/// Record query completion with status (success, error, cancelled)
58pub fn query_completed(status: &str, entity: &str) {
59    counter!(
60        "fraiseql_query_completed_total",
61        labels::ENTITY => entity.to_string(),
62        labels::STATUS => status.to_string(),
63    )
64    .increment(1);
65}
66
67/// Record rows processed from the database
68pub fn rows_processed(entity: &str, count: u64, status: &str) {
69    counter!(
70        "fraiseql_rows_processed_total",
71        labels::ENTITY => entity.to_string(),
72        labels::STATUS => status.to_string(),
73    )
74    .increment(count);
75}
76
77/// Record rows filtered by Rust predicates
78pub fn rows_filtered(entity: &str, count: u64) {
79    counter!(
80        "fraiseql_rows_filtered_total",
81        labels::ENTITY => entity.to_string(),
82    )
83    .increment(count);
84}
85
86/// Record successful deserialization
87pub fn deserialization_success(entity: &str, type_name: &str) {
88    counter!(
89        "fraiseql_rows_deserialized_total",
90        labels::ENTITY => entity.to_string(),
91        labels::TYPE_NAME => type_name.to_string(),
92    )
93    .increment(1);
94}
95
96/// Record deserialization failure
97pub fn deserialization_failure(entity: &str, type_name: &str, reason: &str) {
98    counter!(
99        "fraiseql_rows_deserialization_failed_total",
100        labels::ENTITY => entity.to_string(),
101        labels::TYPE_NAME => type_name.to_string(),
102        labels::REASON => reason.to_string(),
103    )
104    .increment(1);
105}
106
107/// Record a generic error
108pub fn error_occurred(category: &str, phase: &str) {
109    counter!(
110        "fraiseql_errors_total",
111        labels::ERROR_CATEGORY => category.to_string(),
112        labels::PHASE => phase.to_string(),
113    )
114    .increment(1);
115}
116
117/// Record a protocol error
118pub fn protocol_error(message_type: &str) {
119    counter!(
120        "fraiseql_protocol_errors_total",
121        "message_type" => message_type.to_string(),
122    )
123    .increment(1);
124}
125
126/// Record a JSON parsing error by entity
127pub fn json_parse_error(entity: &str) {
128    counter!(
129        "fraiseql_json_parse_errors_total",
130        labels::ENTITY => entity.to_string(),
131    )
132    .increment(1);
133}
134
135/// Record connection creation
136pub fn connection_created(transport: &str) {
137    counter!(
138        "fraiseql_connections_created_total",
139        labels::TRANSPORT => transport.to_string(),
140    )
141    .increment(1);
142}
143
144/// Record connection failure
145pub fn connection_failed(phase: &str, error_category: &str) {
146    counter!(
147        "fraiseql_connections_failed_total",
148        labels::PHASE => phase.to_string(),
149        labels::ERROR_CATEGORY => error_category.to_string(),
150    )
151    .increment(1);
152}
153
154/// Record authentication attempt
155pub fn auth_attempted(mechanism: &str) {
156    counter!(
157        "fraiseql_authentications_total",
158        labels::MECHANISM => mechanism.to_string(),
159    )
160    .increment(1);
161}
162
163/// Record successful authentication
164pub fn auth_successful(mechanism: &str) {
165    counter!(
166        "fraiseql_authentications_successful_total",
167        labels::MECHANISM => mechanism.to_string(),
168    )
169    .increment(1);
170}
171
172/// Record failed authentication
173pub fn auth_failed(mechanism: &str, reason: &str) {
174    counter!(
175        "fraiseql_authentications_failed_total",
176        labels::MECHANISM => mechanism.to_string(),
177        labels::REASON => reason.to_string(),
178    )
179    .increment(1);
180}
181
182/// Record memory limit exceeded event
183pub fn memory_limit_exceeded(entity: &str) {
184    counter!(
185        "fraiseql_memory_limit_exceeded_total",
186        labels::ENTITY => entity.to_string(),
187    )
188    .increment(1);
189}
190
191/// Record an adaptive chunk size adjustment
192///
193/// Called when adaptive chunking decides to increase or decrease `chunk_size`
194/// based on channel occupancy observations.
195///
196/// # Labels
197/// - `entity`: The query entity (project, etc.)
198/// - `direction`: Either "increase" or "decrease"
199/// - `old_size`: The previous chunk size (e.g., "256")
200/// - `new_size`: The new chunk size after adjustment (e.g., "384")
201///
202/// # Example
203/// ```rust
204/// fraiseql_wire::metrics::counters::adaptive_chunk_adjusted("projects", 256, 384);
205/// ```
206pub fn adaptive_chunk_adjusted(entity: &str, old_size: usize, new_size: usize) {
207    let direction = if new_size > old_size {
208        "increase"
209    } else {
210        "decrease"
211    };
212
213    counter!(
214        "fraiseql_adaptive_chunk_adjusted_total",
215        labels::ENTITY => entity.to_string(),
216        "direction" => direction.to_string(),
217        "old_size" => old_size.to_string(),
218        "new_size" => new_size.to_string(),
219    )
220    .increment(1);
221}
222
223/// Record a stream pause event
224///
225/// Called when a stream is paused by the user.
226///
227/// # Labels
228/// - `entity`: The query entity (project, etc.)
229///
230/// # Example
231/// ```rust
232/// fraiseql_wire::metrics::counters::stream_paused("projects");
233/// ```
234pub fn stream_paused(entity: &str) {
235    counter!(
236        "fraiseql_stream_paused_total",
237        labels::ENTITY => entity.to_string(),
238    )
239    .increment(1);
240}
241
242/// Record a stream resume event
243///
244/// Called when a paused stream is resumed by the user.
245///
246/// # Labels
247/// - `entity`: The query entity (project, etc.)
248///
249/// # Example
250/// ```rust
251/// fraiseql_wire::metrics::counters::stream_resumed("projects");
252/// ```
253pub fn stream_resumed(entity: &str) {
254    counter!(
255        "fraiseql_stream_resumed_total",
256        labels::ENTITY => entity.to_string(),
257    )
258    .increment(1);
259}
260
261/// Record a stream pause timeout expiry (auto-resume)
262pub fn stream_pause_timeout_expired(entity: &str) {
263    counter!(
264        "fraiseql_stream_pause_timeout_expired_total",
265        labels::ENTITY => entity.to_string(),
266    )
267    .increment(1);
268}
269
270#[cfg(test)]
271mod tests {
272    use super::*;
273
274    #[test]
275    fn test_query_submitted() {
276        // Should not panic when called
277        query_submitted("test_entity", true, false, true);
278    }
279
280    #[test]
281    fn test_query_success() {
282        query_success("test_entity");
283    }
284
285    #[test]
286    fn test_query_error() {
287        query_error("test_entity", "connection");
288    }
289
290    #[test]
291    fn test_rows_processed() {
292        rows_processed("test_entity", 100, "ok");
293        rows_processed("test_entity", 5, "error");
294    }
295
296    #[test]
297    fn test_error_occurred() {
298        error_occurred("protocol", labels::PHASE_QUERY);
299    }
300
301    #[test]
302    fn test_auth_attempted() {
303        auth_attempted(labels::MECHANISM_SCRAM);
304    }
305
306    #[test]
307    fn test_memory_limit_exceeded() {
308        memory_limit_exceeded("test_entity");
309    }
310
311    #[test]
312    fn test_adaptive_chunk_adjusted_increase() {
313        adaptive_chunk_adjusted("test_entity", 256, 384);
314    }
315
316    #[test]
317    fn test_adaptive_chunk_adjusted_decrease() {
318        adaptive_chunk_adjusted("test_entity", 256, 170);
319    }
320
321    #[test]
322    fn test_stream_paused() {
323        stream_paused("test_entity");
324    }
325
326    #[test]
327    fn test_stream_resumed() {
328        stream_resumed("test_entity");
329    }
330}