1use std::sync::LazyLock;
7use std::sync::atomic::{AtomicUsize, Ordering};
8
9use crate::logpatterns::Printable;
10
11#[derive(Debug, Default)]
13pub struct VariantCounters {
14 pub canonical: AtomicUsize,
15 pub java: AtomicUsize,
16 pub message: AtomicUsize,
17 pub time_only: AtomicUsize,
18 pub json: AtomicUsize,
19 pub logfmt: AtomicUsize,
20 pub text: AtomicUsize,
21 pub parse_errors: AtomicUsize,
22}
23
24static COUNTERS: LazyLock<VariantCounters> = LazyLock::new(Default::default);
25
26impl VariantCounters {
27 pub fn record_variant(&self, variant: &Printable<'_>) {
29 match variant {
30 Printable::Canonical(_) => self.canonical.fetch_add(1, Ordering::Relaxed),
31 Printable::Java(_) => self.java.fetch_add(1, Ordering::Relaxed),
32 Printable::Message(_) => self.message.fetch_add(1, Ordering::Relaxed),
33 Printable::TimeOnly(_) => self.time_only.fetch_add(1, Ordering::Relaxed),
34 Printable::Json(_) => self.json.fetch_add(1, Ordering::Relaxed),
35 Printable::Logfmt(_) => self.logfmt.fetch_add(1, Ordering::Relaxed),
36 Printable::Text(_) => self.text.fetch_add(1, Ordering::Relaxed),
37 };
38 }
39
40 pub fn record_parse_error(&self) {
42 self.parse_errors.fetch_add(1, Ordering::Relaxed);
43 }
44
45 pub fn get_counts(&self) -> VariantCounts {
47 VariantCounts {
48 canonical: self.canonical.load(Ordering::Relaxed),
49 java: self.java.load(Ordering::Relaxed),
50 message: self.message.load(Ordering::Relaxed),
51 time_only: self.time_only.load(Ordering::Relaxed),
52 json: self.json.load(Ordering::Relaxed),
53 logfmt: self.logfmt.load(Ordering::Relaxed),
54 text: self.text.load(Ordering::Relaxed),
55 parse_errors: self.parse_errors.load(Ordering::Relaxed),
56 }
57 }
58
59 pub fn reset(&self) {
61 self.canonical.store(0, Ordering::Relaxed);
62 self.java.store(0, Ordering::Relaxed);
63 self.message.store(0, Ordering::Relaxed);
64 self.time_only.store(0, Ordering::Relaxed);
65 self.json.store(0, Ordering::Relaxed);
66 self.logfmt.store(0, Ordering::Relaxed);
67 self.text.store(0, Ordering::Relaxed);
68 self.parse_errors.store(0, Ordering::Relaxed);
69 }
70}
71
72#[derive(Debug, Clone)]
74pub struct VariantCounts {
75 pub canonical: usize,
76 pub java: usize,
77 pub message: usize,
78 pub time_only: usize,
79 pub json: usize,
80 pub logfmt: usize,
81 pub text: usize,
82 pub parse_errors: usize,
83}
84
85impl VariantCounts {
86 pub fn total(&self) -> usize {
87 self.canonical + self.java + self.message + self.time_only + self.json + self.logfmt + self.text
88 }
89
90 pub fn successful_parses(&self) -> usize {
91 self.total() - self.text }
93
94 pub fn json_parses(&self) -> usize {
95 self.canonical + self.java + self.message + self.time_only + self.json
96 }
97
98 pub fn percentages(&self) -> VariantPercentages {
100 let total = self.total() as f64;
101 if total == 0.0 {
102 return VariantPercentages::default();
103 }
104
105 VariantPercentages {
106 canonical: (self.canonical as f64 / total) * 100.0,
107 java: (self.java as f64 / total) * 100.0,
108 message: (self.message as f64 / total) * 100.0,
109 time_only: (self.time_only as f64 / total) * 100.0,
110 json: (self.json as f64 / total) * 100.0,
111 logfmt: (self.logfmt as f64 / total) * 100.0,
112 text: (self.text as f64 / total) * 100.0,
113 parse_errors: (self.parse_errors as f64 / total) * 100.0,
114 }
115 }
116
117 pub fn print_report(&self) {
119 let percentages = self.percentages();
120 let total = self.total();
121
122 println!("JSON Parsing Profile Report:");
123 println!("============================");
124 println!("Total lines processed: {}", total);
125 println!(
126 "JSON lines: {} ({:.1}%)",
127 self.json_parses(),
128 (self.json_parses() as f64 / total as f64) * 100.0
129 );
130 println!("Plain text: {} ({:.1}%)", self.text, percentages.text);
131 println!("Parse errors: {} ({:.1}%)", self.parse_errors, percentages.parse_errors);
132 println!();
133 println!("JSON Variant Breakdown:");
134 println!(
135 " Canonical: {} ({:.1}%) - FASTEST",
136 self.canonical, percentages.canonical
137 );
138 println!(" Java: {} ({:.1}%)", self.java, percentages.java);
139 println!(
140 " Message: {} ({:.1}%) - SUPERSET (includes GCP, Logstash, etc.)",
141 self.message, percentages.message
142 );
143 println!(" TimeOnly: {} ({:.1}%)", self.time_only, percentages.time_only);
144 println!(" Generic: {} ({:.1}%) - FALLBACK", self.json, percentages.json);
145 println!(" Logfmt: {} ({:.1}%)", self.logfmt, percentages.logfmt);
146 println!();
147
148 if percentages.canonical > 50.0 {
149 println!(
150 "✅ Good: {}% of logs use the fast Canonical path",
151 percentages.canonical
152 );
153 } else if percentages.canonical > 25.0 {
154 println!(
155 "⚠️ Moderate: {}% of logs use the fast Canonical path",
156 percentages.canonical
157 );
158 } else {
159 println!(
160 "❌ Poor: Only {}% of logs use the fast Canonical path",
161 percentages.canonical
162 );
163 println!(" Consider adding more fields to Canonical or optimizing Message parsing");
164 }
165 }
166}
167
168#[derive(Debug, Clone, Default)]
169pub struct VariantPercentages {
170 pub canonical: f64,
171 pub java: f64,
172 pub message: f64,
173 pub time_only: f64,
174 pub json: f64,
175 pub logfmt: f64,
176 pub text: f64,
177 pub parse_errors: f64,
178}
179
180pub fn record_variant(variant: &Printable<'_>) {
182 COUNTERS.record_variant(variant);
183}
184
185pub fn record_parse_error() {
186 COUNTERS.record_parse_error();
187}
188
189pub fn get_counts() -> VariantCounts {
190 COUNTERS.get_counts()
191}
192
193pub fn reset_counters() {
194 COUNTERS.reset();
195}
196
197pub fn print_report() {
198 get_counts().print_report();
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204
205 #[test]
206 fn can_count_variants() {
207 reset_counters();
208
209 let counts = VariantCounts {
212 canonical: 50,
213 message: 30,
214 json: 15,
215 text: 5,
216 java: 0,
217 logfmt: 0,
218 time_only: 0,
219 parse_errors: 0,
220 };
221
222 let percentages = counts.percentages();
223 assert_eq!(counts.total(), 100);
224 assert!((percentages.canonical - 50.0).abs() < 0.1);
225 assert!((percentages.message - 30.0).abs() < 0.1);
226 assert!((percentages.json - 15.0).abs() < 0.1);
227 assert!((percentages.text - 5.0).abs() < 0.1);
228 }
229}