1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
//! Utilities to easily count events for debuging puposes
//!
//! This can be useful to gather statistics about how often different code
//! paths are run.
//!
//! # Overhead
//!
//! Counters are relatively low overhead but not free (Cost of looking up a FxHashMap
//! using static string slices as key). Using counters may affect perfomance measurements.
//!
//! # Optimizing out
//!
//! When using the types `DebugCounters` and `DebugTable` instead of `Counters` and
//! `Table`, the implementation is empty unless the `debug_counters` feature flag is
//! enabled.
//! This way the code for counting events can be kept while opting out of its overhead
//! in shipping and profiling build configurations.
//!
//! # Dummy trait implementations
//!
//! In order to be embedded in structures that implement `Serialize` and `Deserialize`,
//! `Counters` and `DebugCounters` have dummy implementations of the traits that can be
//! emabled with the `dummy_serialization` feature flag.
//!
//! Similarly, the following traits have dummy implementations:
//! - `Eq`, `PartialEq`: Always true.
//! - `Hash`: Does not contribute to the hash. 
//!
//! These dummy implementations are meant to not change the behavior of the embedding
//! structures.
//!
//! # Example
//!
//! In the example below we have a function `do_the_thing` which we determined to
//! be expensive (using a profiler). We would like to get some insight into how
//! often the function is run and how often we take the slow and fast paths.
//!
//! ```rust
//! use counters::Counters;
//! use counters::filters::*;
//!
//! struct Foo {
//!     counters: Counters,
//! }
//!
//! impl Foo {
//!     // This method is not mutable (&self), however we can still update
//!     // the counters because they use internal mutability.
//!     fn do_the_thing(&self, n: u32) -> u32 {
//!         self.counters.event("do_the_thing");
//!         if n % 17 == 0 {
//!             self.counters.event("fast path A");
//!             return self.foo();
//!         }
//!
//!         if n % 56 == 0 {
//!             self.counters.event("fast path B");
//!             return self.bar();
//!         }
//!
//!         self.counters.event("slow path");
//!         return self.baz();
//!     }
//!
//!     fn do_all_of_the_things(&mut self) {
//!         self.counters.reset_all();
//!
//!         for i in 0..100 {
//!             self.do_the_thing(i);
//!         }
//!
//!         // We can use filters to accumulate the values of several counters.
//!         let total_fast_path = self.counters.accumulate(Contains("fast path"));
//!         let slow_path = self.counters.get("do_the_thing") - total_fast_path;
//!
//!         // Set the value of a counter.
//!         self.counters.set("slow path", slow_path);
//!
//!         // This prints the following to stdout:
//!         // slow path: 93
//!         // fast path A: 6
//!         // fast path B: 1
//!         // do_the_thing: 100
//!         self.counters.print_to_stdout(All);
//!     }
//!
//!     // Let's pretend the methods below do interesting things...
//!     fn foo(&self) -> u32 { 0 }
//!     fn bar(&self) -> u32 { 0 }
//!     fn baz(&self) -> u32 { 0 }
//! }
//!
//! ```

#[macro_use]
#[cfg(feature = "dummy_serialization")]
extern crate serde;

pub mod filters;
mod counters;
mod table;
pub use crate::counters::*;
pub use crate::table::*;

mod noop;

#[cfg(feature="debug_counters")]
pub type DebugCounters = Counters;
#[cfg(not(feature="debug_counters"))]
pub type DebugCounters = crate::noop::Counters;

#[cfg(feature="debug_counters")]
pub type DebugTable = Table;
#[cfg(not(feature="debug_counters"))]
pub type DebugTable = crate::noop::Table;

#[cfg(feature = "dummy_serialization")]
#[cfg_attr(feature = "dummy_serialization", derive(Serialize, Deserialize))]
struct Dummy;

#[test]
fn it_works() {
    use crate::filters::*;

    let counters = Counters::new();

    counters.event("foo::bar");
    counters.event("foo::bar");

    counters.event("foo::baz");

    counters.event("meh");
    counters.event("fooo");

    assert_eq!(counters.get("foo::bar"), 2);
    assert_eq!(counters.get("foo::baz"), 1);
    assert_eq!(counters.accumulate("foo::"), 3);

    counters.apply(Contains("foo::bar"), |_, val| val * 2);
    assert_eq!(counters.get("foo::bar"), 4);

    counters.reset_events(EndsWith("bar"));

    assert_eq!(counters.get("foo::bar"), 0);
    assert_eq!(counters.get("foo::baz"), 1);
    assert_eq!(counters.accumulate("foo::"), 1);

    counters.retain(Select(|key, _| key == "meh"));

    counters.reset_all();

    assert_eq!(counters.get("foo::bar"), 0);
    assert_eq!(counters.get("foo::baz"), 0);
    assert_eq!(counters.accumulate("foo::"), 0);
}

#[test]
fn noop() {
    use crate::filters::*;

    let counters = DebugCounters::new();

    counters.event("foo::bar");
    counters.event("foo::bar");

    counters.event("foo::baz");

    counters.event("meh");
    counters.event("fooo");

    assert_eq!(counters.get("foo::bar"), 0);
    assert_eq!(counters.get("foo::baz"), 0);
    assert_eq!(counters.accumulate("foo::"), 0);

    counters.apply(Contains("foo::bar"), |_, val| val * 2);
    assert_eq!(counters.get("foo::bar"), 0);

    counters.reset_events(EndsWith("bar"));

    assert_eq!(counters.get("foo::bar"), 0);
    assert_eq!(counters.get("foo::baz"), 0);
    assert_eq!(counters.accumulate("foo::"), 0);

    counters.reset_all();

    assert_eq!(counters.get("foo::bar"), 0);
    assert_eq!(counters.get("foo::baz"), 0);
    assert_eq!(counters.accumulate("foo::"), 0);    
}