1use crate::filter::{FILTER_MAP_SIZE, NUM_KEY_STATES};
4
5use crate::filter::keynames::{get_key_name, get_value_name};
6use crate::logger::EventInfo;
7use crate::util;
8use serde::Serialize;
9use std::collections::VecDeque;
10use std::io::Write;
11use std::time::Duration;
12
13pub const HISTOGRAM_BUCKET_BOUNDARIES_MS: &[u64] = &[1, 2, 4, 8, 16, 32, 64, 128];
17pub const NUM_HISTOGRAM_BUCKETS: usize = HISTOGRAM_BUCKET_BOUNDARIES_MS.len() + 1;
18
19pub const MAX_BOUNCE_TIMING_SAMPLES: usize = 512;
20pub const MAX_NEAR_MISS_TIMING_SAMPLES: usize = 512;
21
22#[derive(Debug, Clone)]
23pub struct TimingSamples {
24 data: VecDeque<u64>,
25 capacity: usize,
26}
27
28impl TimingSamples {
29 pub fn with_capacity(capacity: usize) -> Self {
30 let data = VecDeque::new();
31 Self { data, capacity }
32 }
33
34 pub fn push(&mut self, value: u64) {
35 if self.capacity == 0 {
36 return;
37 }
38 if self.data.len() == self.capacity {
39 self.data.pop_front();
40 }
41 self.data.push_back(value);
42 }
43
44 pub fn is_empty(&self) -> bool {
45 self.data.is_empty()
46 }
47
48 pub fn len(&self) -> usize {
49 self.data.len()
50 }
51
52 pub fn to_vec(&self) -> Vec<u64> {
53 self.data.iter().copied().collect()
54 }
55}
56
57impl Default for TimingSamples {
58 fn default() -> Self {
59 Self::with_capacity(MAX_BOUNCE_TIMING_SAMPLES)
60 }
61}
62
63#[derive(Debug, Clone, Default)]
64pub struct TimingSummary {
65 count: u64,
66 sum_us: u128,
67 min_us: Option<u64>,
68 max_us: Option<u64>,
69}
70
71impl TimingSummary {
72 pub fn record(&mut self, value: u64) {
73 self.count = self.count.saturating_add(1);
74 self.sum_us = self.sum_us.saturating_add(value as u128);
75 self.min_us = Some(match self.min_us {
76 Some(current) => current.min(value),
77 None => value,
78 });
79 self.max_us = Some(match self.max_us {
80 Some(current) => current.max(value),
81 None => value,
82 });
83 }
84
85 pub fn count(&self) -> u64 {
86 self.count
87 }
88
89 pub fn min_us(&self) -> Option<u64> {
90 self.min_us
91 }
92
93 pub fn max_us(&self) -> Option<u64> {
94 self.max_us
95 }
96
97 pub fn average_us(&self) -> Option<u64> {
98 if self.count == 0 {
99 return None;
100 }
101 let avg = self.sum_us / u128::from(self.count);
102 Some(avg.min(u128::from(u64::MAX)) as u64)
103 }
104}
105
106#[derive(Debug, Serialize, Clone)]
108pub struct TimingHistogram {
109 pub buckets: [u64; NUM_HISTOGRAM_BUCKETS],
111 pub count: u64,
113 pub sum_us: u64,
115 }
119
120impl Default for TimingHistogram {
121 fn default() -> Self {
122 Self {
123 buckets: [0; NUM_HISTOGRAM_BUCKETS],
124 count: 0,
125 sum_us: 0,
126 }
127 }
128}
129
130impl TimingHistogram {
131 #[inline]
133 pub fn record(&mut self, timing_us: u64) {
134 let timing_ms = timing_us / 1000; let mut bucket_index = NUM_HISTOGRAM_BUCKETS - 1; for (i, &boundary_ms) in HISTOGRAM_BUCKET_BOUNDARIES_MS.iter().enumerate() {
138 if timing_ms < boundary_ms {
139 bucket_index = i;
140 break;
141 }
142 }
143
144 self.buckets[bucket_index] += 1;
145 self.count += 1;
146 self.sum_us = self.sum_us.saturating_add(timing_us); }
151
152 pub fn average_us(&self) -> u64 {
154 if self.count > 0 {
155 self.sum_us / self.count
156 } else {
157 0
158 }
159 }
160
161 }
163
164#[derive(Serialize, Clone, Debug)]
166pub struct Meta {
167 pub debounce_time_us: u64,
168 pub near_miss_threshold_us: u64,
169 pub log_all_events: bool,
170 pub log_bounces: bool,
171 pub log_interval_us: u64,
172}
173
174#[derive(Debug, Clone)]
177pub struct KeyValueStats {
178 pub total_processed: u64,
180 pub passed_count: u64,
182 pub dropped_count: u64,
184 pub bounce_histogram: TimingHistogram,
186 pub bounce_summary: TimingSummary,
188 pub bounce_samples: TimingSamples,
190}
191
192impl Default for KeyValueStats {
193 fn default() -> Self {
194 Self {
195 total_processed: 0,
196 passed_count: 0,
197 dropped_count: 0,
198 bounce_histogram: TimingHistogram::default(),
199 bounce_summary: TimingSummary::default(),
200 bounce_samples: TimingSamples::with_capacity(MAX_BOUNCE_TIMING_SAMPLES),
201 }
202 }
203}
204
205impl KeyValueStats {
206 #[inline]
208 pub fn record_bounce_timing(&mut self, value: u64) {
209 self.bounce_summary.record(value);
210 self.bounce_histogram.record(value);
211 self.bounce_samples.push(value);
212 }
213}
214
215#[derive(Debug, Clone)]
217pub struct NearMissStats {
218 pub summary: TimingSummary,
220 pub histogram: TimingHistogram,
222 pub samples: TimingSamples,
224}
225
226impl Default for NearMissStats {
227 fn default() -> Self {
228 Self {
229 summary: TimingSummary::default(),
230 histogram: TimingHistogram::default(),
231 samples: TimingSamples::with_capacity(MAX_NEAR_MISS_TIMING_SAMPLES),
232 }
233 }
234}
235
236impl NearMissStats {
237 #[inline]
239 pub fn record_timing(&mut self, value: u64) {
240 self.summary.record(value);
241 self.histogram.record(value);
242 self.samples.push(value);
243 }
244}
245
246#[derive(Debug, Clone, Default)]
248pub struct KeyStats {
249 pub press: KeyValueStats,
250 pub release: KeyValueStats,
251 pub repeat: KeyValueStats,
252}
253
254#[derive(Serialize, Debug)]
256struct PerKeyStatsJson {
257 key_code: u16,
258 key_name: &'static str,
259 total_processed: u64,
260 total_dropped: u64,
261 drop_percentage: f64,
262 stats: KeyStatsJson, }
264
265#[derive(Serialize, Debug)]
267struct KeyValueStatsJson {
268 total_processed: u64,
269 passed_count: u64,
270 dropped_count: u64,
271 drop_rate: f64,
272 timings_us: Vec<u64>, bounce_histogram: TimingHistogramJson,
274 #[serde(skip_serializing_if = "Option::is_none")]
275 min_us: Option<u64>,
276 #[serde(skip_serializing_if = "Option::is_none")]
277 max_us: Option<u64>,
278 #[serde(skip_serializing_if = "Option::is_none")]
279 avg_us: Option<u64>,
280}
281
282#[derive(Serialize, Debug)]
284struct KeyStatsJson {
285 press: KeyValueStatsJson,
286 release: KeyValueStatsJson,
287 repeat: KeyValueStatsJson, }
289
290#[derive(Serialize, Debug)]
292struct TimingHistogramJson {
293 buckets: Vec<HistogramBucketJson>,
294 count: u64,
295 avg_us: u64,
296 }
299
300#[derive(Serialize, Debug)]
302struct HistogramBucketJson {
303 min_ms: u64,
304 max_ms: Option<u64>, count: u64,
306}
307
308#[derive(Serialize, Debug)]
310struct NearMissStatsJson {
311 key_code: u16,
312 key_value: i32,
313 key_name: &'static str,
314 value_name: &'static str,
315 count: usize,
316 timings_us: Vec<u64>, near_miss_histogram: TimingHistogramJson,
318 #[serde(skip_serializing_if = "Option::is_none")]
319 min_us: Option<u64>,
320 #[serde(skip_serializing_if = "Option::is_none")]
321 max_us: Option<u64>,
322 #[serde(skip_serializing_if = "Option::is_none")]
323 avg_us: Option<u64>,
324}
325
326#[derive(Debug, Clone)]
329pub struct StatsCollector {
330 pub key_events_processed: u64,
332 pub key_events_passed: u64,
334 pub key_events_dropped: u64,
336 pub per_key_stats: Vec<KeyStats>,
338 pub per_key_near_miss_stats: Vec<NearMissStats>,
340 pub overall_bounce_histogram: TimingHistogram,
342 pub overall_near_miss_histogram: TimingHistogram,
344}
345
346impl Default for StatsCollector {
348 fn default() -> Self {
349 StatsCollector::with_capacity()
350 }
351}
352
353impl StatsCollector {
354 #[must_use]
356 pub fn with_capacity() -> Self {
357 let per_key_stats = vec![KeyStats::default(); FILTER_MAP_SIZE];
359 let per_key_near_miss_stats =
360 vec![NearMissStats::default(); FILTER_MAP_SIZE * NUM_KEY_STATES];
361
362 StatsCollector {
363 key_events_processed: 0,
364 key_events_passed: 0,
365 key_events_dropped: 0,
366 per_key_stats,
367 per_key_near_miss_stats,
368 overall_bounce_histogram: TimingHistogram::default(),
369 overall_near_miss_histogram: TimingHistogram::default(),
370 }
371 }
372
373 pub fn record_event_info_with_config(
377 &mut self,
378 info: &EventInfo,
379 config: &crate::config::Config,
380 ) {
381 use crate::event::is_key_event;
382
383 if !is_key_event(&info.event) {
385 return;
386 }
387
388 self.key_events_processed += 1;
389
390 let key_code_idx = info.event.code as usize;
392 let key_value_idx = info.event.value as usize;
393
394 if key_code_idx >= FILTER_MAP_SIZE || key_value_idx >= NUM_KEY_STATES {
396 return;
398 }
399
400 let value_stats = match info.event.value {
401 1 => &mut self.per_key_stats[key_code_idx].press,
402 0 => &mut self.per_key_stats[key_code_idx].release,
403 _ => &mut self.per_key_stats[key_code_idx].repeat,
404 };
405
406 value_stats.total_processed += 1;
408
409 if info.is_bounce {
411 self.key_events_dropped += 1;
412 value_stats.dropped_count += 1; if let Some(diff) = info.diff_us {
415 value_stats.record_bounce_timing(diff); }
417 } else {
418 self.key_events_passed += 1;
420 value_stats.passed_count += 1;
422
423 if let Some(last_us) = info.last_passed_us {
425 if let Some(diff) = info.event_us.checked_sub(last_us) {
426 if diff <= config.near_miss_threshold_us() {
430 let idx = key_code_idx * NUM_KEY_STATES + key_value_idx;
432 self.per_key_near_miss_stats[idx].record_timing(diff); }
435 }
436 }
437 }
438 }
439
440 pub fn aggregate_histograms(&mut self) {
443 self.overall_bounce_histogram = TimingHistogram::default();
445 self.overall_near_miss_histogram = TimingHistogram::default();
446
447 for key_stats in self.per_key_stats.iter() {
448 Self::accumulate_histogram(
450 &mut self.overall_bounce_histogram,
451 &key_stats.press.bounce_histogram,
452 );
453 Self::accumulate_histogram(
454 &mut self.overall_bounce_histogram,
455 &key_stats.release.bounce_histogram,
456 );
457 }
459
460 for near_miss_stats in self.per_key_near_miss_stats.iter() {
461 Self::accumulate_histogram(
463 &mut self.overall_near_miss_histogram,
464 &near_miss_stats.histogram,
465 );
466 }
467 }
468
469 #[inline]
471 fn accumulate_histogram(dest: &mut TimingHistogram, source: &TimingHistogram) {
472 if source.count > 0 {
473 dest.count += source.count;
474 dest.sum_us = dest.sum_us.saturating_add(source.sum_us);
475 for i in 0..NUM_HISTOGRAM_BUCKETS {
476 dest.buckets[i] += source.buckets[i];
477 }
478 }
482 }
483
484 fn format_histogram_human(histogram: &TimingHistogram) -> String {
486 if histogram.count == 0 {
487 return "No data".to_string();
488 }
489
490 let mut output = String::new();
491 let total_count = histogram.count;
492
493 let max_bucket_count = histogram.buckets.iter().copied().max().unwrap_or(0);
495 let bar_scale = if max_bucket_count > 0 {
496 50.0 / max_bucket_count as f64
497 } else {
498 0.0
499 }; for i in 0..NUM_HISTOGRAM_BUCKETS {
502 let bucket_count = histogram.buckets[i];
503 let percentage = if total_count > 0 {
504 (bucket_count as f64 / total_count as f64) * 100.0
505 } else {
506 0.0
507 };
508
509 let label = if i == 0 {
510 format!("< {}ms", HISTOGRAM_BUCKET_BOUNDARIES_MS[0])
511 } else if i == NUM_HISTOGRAM_BUCKETS - 1 {
512 format!(
513 ">= {}ms",
514 HISTOGRAM_BUCKET_BOUNDARIES_MS[NUM_HISTOGRAM_BUCKETS - 2]
515 )
516 } else {
517 format!(
518 "{}-{}ms",
519 HISTOGRAM_BUCKET_BOUNDARIES_MS[i - 1],
520 HISTOGRAM_BUCKET_BOUNDARIES_MS[i]
521 )
522 };
523
524 let bar_width = (bucket_count as f64 * bar_scale).round() as usize;
525 let bar = "#".repeat(bar_width);
526
527 output.push_str(&format!(
528 " {label:<10}: {bucket_count:<5} ({percentage:>5.1}%) [{bar}]\n"
529 ));
530 }
531
532 let avg_us = histogram.average_us();
533 output.push_str(&format!(
534 " Total: {}, Avg: {}\n",
535 total_count,
536 util::format_us(avg_us)
537 ));
538
539 output
540 }
541
542 pub fn format_stats_human_readable(
545 &mut self, config: &crate::config::Config,
547 report_type: &str,
548 mut writer: impl Write, ) -> std::io::Result<()> {
550 self.aggregate_histograms();
552
553 writeln!(writer, "\n--- Overall Statistics ({report_type}) ---")?;
554 writeln!(
555 writer,
556 "Key Events Processed: {}",
557 self.key_events_processed
558 )?;
559 writeln!(writer, "Key Events Passed: {}", self.key_events_passed)?;
560 writeln!(writer, "Key Events Dropped: {}", self.key_events_dropped)?;
561 let percentage = if self.key_events_processed > 0 {
562 (self.key_events_dropped as f64 / self.key_events_processed as f64) * 100.0
563 } else {
564 0.0
565 };
566 writeln!(writer, "Percentage Dropped: {percentage:.2}%")?;
567
568 writeln!(writer, "\n--- Overall Bounce Timing Histogram ---")?;
570 write!(
571 writer,
572 "{}",
573 Self::format_histogram_human(&self.overall_bounce_histogram)
574 )?;
575
576 writeln!(
578 writer,
579 "\n--- Overall Near-Miss Timing Histogram (Passed within {}) ---",
580 util::format_duration(config.near_miss_threshold())
581 )?;
582 write!(
583 writer,
584 "{}",
585 Self::format_histogram_human(&self.overall_near_miss_histogram)
586 )?;
587
588 let mut any_drops = false;
589 for key_code in 0..self.per_key_stats.len() {
590 let stats = &self.per_key_stats[key_code];
591 let total_drops_for_key = stats.press.dropped_count
592 + stats.release.dropped_count
593 + stats.repeat.dropped_count;
594
595 if total_drops_for_key > 0
596 || stats.press.total_processed > 0
597 || stats.release.total_processed > 0
598 || stats.repeat.total_processed > 0
599 {
600 if !any_drops {
602 writeln!(writer, "\n--- Dropped Event Statistics Per Key ---")?;
603 writeln!(writer, "Format: Key [Name] (Code):")?;
604 writeln!(
605 writer,
606 " State (Value): Processed: <count>, Passed: <count>, Dropped: <count> (<rate>%) (Bounce Time: Min / Avg / Max)"
607 )?;
608 any_drops = true;
609 }
610
611 let key_name = get_key_name(key_code as u16);
612 writeln!(writer, "\nKey [{key_name}] ({key_code}):")?;
613 let total_processed_for_key = stats.press.total_processed
615 + stats.release.total_processed
616 + stats.repeat.total_processed;
617 let total_passed_for_key = stats.press.passed_count
619 + stats.release.passed_count
620 + stats.repeat.passed_count;
621 let key_drop_percentage = if total_processed_for_key > 0 {
623 (total_drops_for_key as f64 / total_processed_for_key as f64) * 100.0
625 } else {
626 0.0
627 };
628 writeln!(
629 writer, " Total Processed: {total_processed_for_key}, Passed: {total_passed_for_key}, Dropped: {total_drops_for_key} ({key_drop_percentage:.2}%)"
631 )?;
632
633 let mut print_value_stats = |value_name: &str,
635 value_code: i32,
636 value_stats: &KeyValueStats|
637 -> std::io::Result<()> {
638 if value_stats.total_processed > 0 {
639 let drop_rate = if value_stats.total_processed > 0 {
641 (value_stats.dropped_count as f64 / value_stats.total_processed as f64)
642 * 100.0
643 } else {
644 0.0
645 };
646 write!(
647 writer,
648 " {:<7} ({}): Processed: {}, Passed: {}, Dropped: {} ({:.2}%)",
649 value_name,
650 value_code,
651 value_stats.total_processed,
652 value_stats.passed_count,
653 value_stats.dropped_count,
654 drop_rate
655 )?;
656 if let Some(min) = value_stats.bounce_summary.min_us() {
657 let max = value_stats.bounce_summary.max_us().unwrap_or(min);
658 let avg = value_stats.bounce_summary.average_us().unwrap_or(min);
659 writeln!(
660 writer,
661 " (Bounce Time: {} / {} / {})",
662 util::format_us(min),
663 util::format_us(avg),
664 util::format_us(max)
665 )?;
666 } else {
667 writeln!(writer)?;
668 }
669 }
670 Ok(())
671 };
672
673 print_value_stats("Press", 1, &stats.press)?;
674 print_value_stats("Release", 0, &stats.release)?;
675 print_value_stats("Repeat", 2, &stats.repeat)?; }
677 }
678 if !any_drops {
679 writeln!(writer, "\n--- No key events dropped ---")?;
680 }
681
682 let mut any_near_miss = false;
683 for idx in 0..self.per_key_near_miss_stats.len() {
684 let near_miss_stats = &self.per_key_near_miss_stats[idx];
685 if near_miss_stats.summary.count() > 0 {
686 if !any_near_miss {
687 writeln!(
688 writer,
689 "\n--- Passed Event Near-Miss Statistics (Passed within {}) ---",
690 util::format_duration(config.near_miss_threshold())
691 )?;
692 writeln!(
693 writer,
694 "Format: Key [Name] (Code, Value): Count (Near-Miss Time: Min / Avg / Max)"
695 )?;
696 any_near_miss = true;
697 }
698
699 let key_code = (idx / NUM_KEY_STATES) as u16;
700 let key_value = (idx % NUM_KEY_STATES) as i32;
701 let key_name = get_key_name(key_code);
702
703 let min = near_miss_stats.summary.min_us().unwrap_or(0);
704 let max = near_miss_stats.summary.max_us().unwrap_or(min);
705 let avg = near_miss_stats.summary.average_us().unwrap_or(min);
706 let count = near_miss_stats.summary.count();
707
708 writeln!(
709 writer,
710 " Key [{}] ({}, {}): {} (Near-Miss Time: {} / {} / {})",
711 key_name,
712 key_code,
713 key_value,
714 count,
715 util::format_us(min),
716 util::format_us(avg),
717 util::format_us(max)
718 )?;
719 }
720 }
721 if !any_near_miss {
722 writeln!(
723 writer,
724 "\n--- No near-miss events recorded (< {}) ---",
725 util::format_duration(config.near_miss_threshold())
726 )?;
727 }
728
729 writeln!(
730 writer,
731 "----------------------------------------------------------"
732 )?;
733 Ok(()) }
735
736 pub fn print_stats_to_stderr(&mut self, config: &crate::config::Config, report_type: &str) {
738 let _ =
740 self.format_stats_human_readable(config, report_type, &mut std::io::stderr().lock());
741 }
742
743 fn create_histogram_json(histogram: &TimingHistogram) -> TimingHistogramJson {
745 let mut buckets_json = Vec::with_capacity(NUM_HISTOGRAM_BUCKETS);
746 for i in 0..NUM_HISTOGRAM_BUCKETS {
747 let min_ms = if i == 0 {
748 0
749 } else {
750 HISTOGRAM_BUCKET_BOUNDARIES_MS[i - 1]
751 };
752 let max_ms = if i == NUM_HISTOGRAM_BUCKETS - 1 {
753 None
754 } else {
755 Some(HISTOGRAM_BUCKET_BOUNDARIES_MS[i])
756 };
757 buckets_json.push(HistogramBucketJson {
758 min_ms,
759 max_ms,
760 count: histogram.buckets[i],
761 });
762 }
763 TimingHistogramJson {
764 buckets: buckets_json,
765 count: histogram.count,
766 avg_us: histogram.average_us(),
767 }
770 }
771
772 pub fn print_stats_json(
775 &mut self,
776 config: &crate::config::Config,
777 runtime_us: Option<u64>,
778 report_type: &str,
779 mut writer: impl Write,
780 ) {
781 self.aggregate_histograms();
783
784 let mut per_key_stats_json_vec = Vec::new();
786 for (key_code_usize, stats) in self.per_key_stats.iter().enumerate() {
787 let total_processed_for_key = stats.press.total_processed
788 + stats.release.total_processed
789 + stats.repeat.total_processed;
790 let total_dropped_for_key = stats.press.dropped_count
791 + stats.release.dropped_count
792 + stats.repeat.dropped_count;
793
794 if total_processed_for_key > 0 {
795 let key_code = key_code_usize as u16;
797 let key_name = get_key_name(key_code);
798 let drop_percentage = if total_processed_for_key > 0 {
799 (total_dropped_for_key as f64 / total_processed_for_key as f64) * 100.0
800 } else {
801 0.0
802 };
803
804 let create_kv_stats_json = |kv_stats: &KeyValueStats| -> KeyValueStatsJson {
806 let drop_rate = if kv_stats.total_processed > 0 {
807 (kv_stats.dropped_count as f64 / kv_stats.total_processed as f64) * 100.0
808 } else {
809 0.0
810 };
811 KeyValueStatsJson {
812 total_processed: kv_stats.total_processed,
813 passed_count: kv_stats.passed_count,
814 dropped_count: kv_stats.dropped_count,
815 drop_rate,
816 timings_us: kv_stats.bounce_samples.to_vec(),
817 bounce_histogram: Self::create_histogram_json(&kv_stats.bounce_histogram),
818 min_us: kv_stats.bounce_summary.min_us(),
819 max_us: kv_stats.bounce_summary.max_us(),
820 avg_us: kv_stats.bounce_summary.average_us(),
821 }
822 };
823
824 let detailed_stats_json = KeyStatsJson {
826 press: create_kv_stats_json(&stats.press),
828 release: create_kv_stats_json(&stats.release),
829 repeat: create_kv_stats_json(&stats.repeat),
831 };
832
833 per_key_stats_json_vec.push(PerKeyStatsJson {
834 key_code,
835 key_name,
836 total_processed: total_processed_for_key,
837 total_dropped: total_dropped_for_key,
838 drop_percentage,
839 stats: detailed_stats_json, });
841 }
842 }
843
844 let mut near_miss_json_vec = Vec::new();
846 for (idx, near_miss_stats) in self.per_key_near_miss_stats.iter().enumerate() {
847 if near_miss_stats.summary.count() > 0 {
848 let key_code = (idx / NUM_KEY_STATES) as u16;
849 let key_value = (idx % NUM_KEY_STATES) as i32;
850 let key_name = get_key_name(key_code);
851 let value_name = get_value_name(key_value);
852
853 near_miss_json_vec.push(NearMissStatsJson {
854 key_code,
855 key_value,
856 key_name,
857 value_name,
858 count: near_miss_stats.summary.count() as usize,
859 timings_us: near_miss_stats.samples.to_vec(),
860 near_miss_histogram: Self::create_histogram_json(&near_miss_stats.histogram),
861 min_us: near_miss_stats.summary.min_us(),
862 max_us: near_miss_stats.summary.max_us(),
863 avg_us: near_miss_stats.summary.average_us(),
864 });
865 }
866 }
867
868 #[derive(Serialize)]
869 struct ReportData<'a> {
870 report_type: &'a str,
871 #[serde(skip_serializing_if = "Option::is_none")]
872 runtime_us: Option<u64>,
873 #[serde(skip_serializing_if = "Option::is_none")]
874 runtime_human: Option<String>,
875 debounce_time_us: u64,
877 near_miss_threshold_us: u64,
878 log_interval_us: u64,
879 debounce_time_human: String,
880 near_miss_threshold_human: String,
881 log_interval_human: String,
882 key_events_processed: u64,
883 key_events_passed: u64,
884 key_events_dropped: u64,
885 overall_bounce_histogram: TimingHistogramJson,
887 overall_near_miss_histogram: TimingHistogramJson,
888 per_key_stats: Vec<PerKeyStatsJson>,
890 per_key_near_miss_stats: Vec<NearMissStatsJson>,
891 }
892
893 let runtime_human = runtime_us.map(|us| util::format_duration(Duration::from_micros(us)));
894 let debounce_human = util::format_duration(config.debounce_time());
895 let near_miss_human = util::format_duration(config.near_miss_threshold());
896 let log_interval_human = util::format_duration(config.log_interval());
897
898 let report = ReportData {
899 report_type,
900 runtime_us, runtime_human,
902 debounce_time_us: config.debounce_us(), near_miss_threshold_us: config.near_miss_threshold_us(), log_interval_us: config.log_interval_us(), debounce_time_human: debounce_human,
906 near_miss_threshold_human: near_miss_human,
907 log_interval_human,
908 key_events_processed: self.key_events_processed,
909 key_events_passed: self.key_events_passed,
910 key_events_dropped: self.key_events_dropped,
911 overall_bounce_histogram: Self::create_histogram_json(&self.overall_bounce_histogram),
912 overall_near_miss_histogram: Self::create_histogram_json(
913 &self.overall_near_miss_histogram,
914 ),
915 per_key_stats: per_key_stats_json_vec, per_key_near_miss_stats: near_miss_json_vec, };
918
919 let _ = serde_json::to_writer_pretty(&mut writer, &report);
923 let _ = writeln!(writer);
924 }
925}