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); }