rustics/
time_hier.rs

1//
2//  Copyright 2024 Jonathan L Bertoni
3//
4//  This code is available under the Berkeley 2-Clause, Berkeley 3-clause,
5//  and MIT licenses.
6//
7
8//!
9//! ## Type
10//!
11//! * TimeHier
12//!   * This module provides a bridge between the Hier code and the RunningTime
13//!     type.  See the running_time module for details on RunningTime.
14//!
15//!   * See the library comments (lib.rs) for an overview of how hierarchical
16//!     statistics work.
17//!
18//!   * The function TimeHier::new_hier is a wrapper for the Hier constructor
19//!     and does the initialization specific to the TimeHier type.  It is the
20//!     preferred interface for creating a Hier instance that uses RunningTime
21//!     instances.
22//!
23//! ## Example
24//!```
25//!     // This example is based on the code in IntegerHier.
26//!
27//!     use rustics::Rustics;
28//!     use rustics::timer_mut;
29//!     use rustics::hier::Hier;
30//!     use rustics::hier::HierDescriptor;
31//!     use rustics::hier::HierDimension;
32//!     use rustics::hier::HierIndex;
33//!     use rustics::hier::HierSet;
34//!     use rustics::time_hier::TimeHier;
35//!     use rustics::time_hier::TimeHierConfig;
36//!     use rustics::time::Timer;
37//!     use rustics::time::DurationTimer;
38//!
39//!     // Make a descriptor of the first level.  We have chosen to sum
40//!     // 1000 level 0 RunningTime instances into one level 1 RunningTime
41//!     // instance.  This level is large, so we will keep only 1000
42//!     // level 0 instances in the window.
43//!
44//!     let dimension_0 = HierDimension::new(1000, 1000);
45//!
46//!     // At level 1, we want to sum 100 level 1 instances into one level
47//!     // 2 instance.  This level is smaller, so let's retain 200
48//!     // RunningTime instances here.
49//!
50//!     let dimension_1 = HierDimension::new(100, 200);
51//!
52//!     // Level two isn't summed, so the period isn't used.  Let's
53//!     // pretend this level isn't used much, so retain only 100
54//!     // instances in it.
55//!
56//!     let dimension_2 = HierDimension::new(0, 100);
57//!
58//!     // Now create the Vec of the dimensions.
59//!
60//!     let dimensions =
61//!         vec![ dimension_0, dimension_1, dimension_2 ];
62//!
63//!     // Now create the entire descriptor for the hier instance.  Let's
64//!     // record 2000 time samples into each level 0 RunningTime instance.
65//!
66//!     let auto_advance = Some(2000);
67//!     let descriptor   = HierDescriptor::new(dimensions, auto_advance);
68//!
69//!     // Use DurationTimer for the clock.
70//!
71//!     let timer = DurationTimer::new_box();
72//!
73//!     // Now specify some parameters used by Hier to do printing.  The
74//!     // defaults for the title and printer are fine, so just pass None
75//!     // for print_opts.
76//!     //
77//!     // The title defaults to the name and output will go to stdout.
78//!
79//!     let name        = "Hierarchical Time".to_string();
80//!     let print_opts  = None;
81//!     let window_size = None;
82//!
83//!     // Finally, create the configuration description for the
84//!     // constructor.
85//!
86//!     let configuration =
87//!         TimeHierConfig { descriptor, name, window_size, timer, print_opts };
88//!
89//!     // Now make the Hier instance.
90//!
91//!     let mut time_hier = TimeHier::new_hier(configuration);
92//!
93//!     // Now record some events with boring data.
94//!
95//!     let mut events   = 0;
96//!     let auto_advance = auto_advance.unwrap();
97//!
98//!     for i in  0..auto_advance {
99//!         events += 1;
100//!         time_hier.record_event();
101//!     }
102//!
103//!     // Print our data.
104//!
105//!     time_hier.print();
106//!
107//!     // We have just completed the first level 0 instance, but the
108//!     // implementation creates the next instance only when it has data
109//!     // to record, so there should be only one level zero instance,
110//!     // and nothing at level 1 or level 2.
111//!
112//!     assert!(time_hier.event_count() == events);
113//!     assert!(time_hier.count()       == events as u64);
114//!     assert!(time_hier.live_len(0)   == 1     );
115//!     assert!(time_hier.live_len(1)   == 0     );
116//!     assert!(time_hier.live_len(2)   == 0     );
117//!
118//!     // Now record a sample to force the creation of the second level
119//!     // 1 instance.
120//!
121//!     events += 1;
122//!     time_hier.record_time(10);
123//!
124//!     // The new level 0 instance should have only one event recorded.
125//!     // The Rustics implementation for Hier returns the data in the
126//!     // current level 0 instance, so check it.
127//!
128//!     assert!(time_hier.count()       == 1     );
129//!     assert!(time_hier.event_count() == events);
130//!     assert!(time_hier.live_len(0)   == 2     );
131//!     assert!(time_hier.live_len(1)   == 0     );
132//!     assert!(time_hier.live_len(2)   == 0     );
133//!
134//!     // Record enough events to fill a level 1 summary.  It will not
135//!     // be created yet, though.  That occurs when we start the next
136//!     // level 0 batch, i.e., retire the current level 0 instance.
137//!     //
138//!     // Use the finish() method this time.  It uses the clock
139//!     // directly.  This approach works if multiple threads are using
140//!     // the Hier instance.
141//!
142//!     let events_per_level_1 =
143//!         auto_advance * dimension_0.period() as i64;
144//!
145//!     let timer = DurationTimer::new_box();
146//!
147//!     for _i in events..events_per_level_1 {
148//!         time_hier.record_time(timer_mut!(timer).finish());
149//!         events += 1;
150//!     }
151//!
152//!     // Check the state again.  We need to record one more event to
153//!     // cause the summation at level 0 into level 1.
154//!
155//!     let expected_live  = dimension_0.period();
156//!     let expected_count = auto_advance as u64;
157//!
158//!     assert!(time_hier.event_count() == events        );
159//!     assert!(time_hier.count()       == expected_count);
160//!     assert!(time_hier.live_len(0)   == expected_live );
161//!     assert!(time_hier.live_len(1)   == 0             );
162//!     assert!(time_hier.live_len(2)   == 0             );
163//!
164//!     time_hier.record_time(42);
165//!     events += 1;
166//!
167//!     assert!(time_hier.live_len(1)   == 1     );
168//!     assert!(time_hier.event_count() == events);
169//!
170//!     // Now print an instance from the hierarchy.  In this case, we
171//!     // will index into level 1, and print the third instance of the
172//!     // vector (index 2).  We use the set All to look at all the
173//!     // instances in the window, not just the live instances.
174//!
175//!     let index = HierIndex::new(HierSet::All, 1, 2);
176//!
177//!     // The default printer and default title are fine for our
178//!     // example, so pass None for the printer and title options.
179//!
180//!     time_hier.print_index_opts(index, None, None);
181//!```
182
183// This module provides the interface between RunningTime and the Hier
184// code.
185
186use std::any::Any;
187use std::cell::RefCell;
188use std::rc::Rc;
189
190use super::Rustics;
191use super::Histogram;
192use super::TimerBox;
193use super::PrintOption;
194use super::timer;
195use super::hier_box;
196use super::hier_item;
197use super::running_time::RunningTime;
198use super::time_window::TimeWindow;
199use crate::running_integer::IntegerExporter;
200
201use crate::Hier;
202use crate::HierDescriptor;
203use crate::HierConfig;
204use crate::HierGenerator;
205use crate::HierMember;
206use crate::HierExporter;
207use crate::ExporterRc;
208use crate::MemberRc;
209
210// Provide for downcasting from a Hier member to a Rustics
211// type or "dyn Any" to get to the RunningTime code.
212
213impl HierMember for RunningTime {
214    fn to_rustics(&self) -> &dyn Rustics {
215        self
216    }
217
218    fn to_rustics_mut(&mut self) -> &mut dyn Rustics {
219        self
220    }
221
222    fn as_any(&self) -> &dyn Any {
223        self
224    }
225
226    fn as_any_mut(&mut self) -> &mut dyn Any {
227        self
228    }
229
230    fn to_histogram(&self) -> &dyn Histogram {
231        self
232    }
233}
234
235/// TimeHier provides an interface from the Hier code to
236/// the RunningTime code.
237///
238/// See the module comments for sample code.
239
240#[derive(Clone)]
241pub struct TimeHier {
242    timer:  TimerBox,
243    hz:     u128,
244}
245
246/// TimeHierConfig is used to pass the configuration parameters
247/// for a TimeHier instance.  The window_size parameter can be
248/// set to cause the Hier instance to maintain a window of the
249/// last n events to be used for its Rustics reporting.
250
251pub struct TimeHierConfig {
252    pub name:        String,
253    pub descriptor:  HierDescriptor,
254    pub window_size: Option<usize>,
255    pub timer:       TimerBox,
256    pub print_opts:  PrintOption,
257}
258
259impl TimeHier {
260    /// The new() function constructs a TimeHier instance, which is an
261    /// implementation of HierGenerator for the RunningTime type.  Most
262    /// users should just invoke new_hier() or use one of the set interfaces.
263
264    pub fn new(timer: TimerBox) -> TimeHier  {
265        let hz = timer!(timer).hz();
266
267        TimeHier { timer, hz }
268    }
269
270    /// new_hier() constructs a new Hier instance from the given
271    /// configuration.  It does the grunt work specific to the
272    /// RunningTime type.
273
274    pub fn new_hier(configuration: TimeHierConfig) -> Hier {
275        let generator   = TimeHier::new(configuration.timer);
276        let generator   = Rc::from(RefCell::new(generator));
277        let class       = "time".to_string();
278
279        let descriptor  = configuration.descriptor;
280        let name        = configuration.name;
281        let print_opts  = configuration.print_opts;
282        let window_size = configuration.window_size;
283
284        let config =
285            HierConfig { descriptor, generator, name, window_size, class, print_opts };
286
287        Hier::new(config)
288    }
289}
290
291// This trait provides the thus bridge between "impl RunningTime"
292// and the Hier implementation.  This code is of interest mainly to
293// developers who are creating a custom Rustics type and need examples.
294
295impl HierGenerator for TimeHier {
296    // Creates a member with the given name and printer.
297
298    fn make_member(&self, name: &str, print_opts: &PrintOption) -> MemberRc {
299        let member = RunningTime::new(name, self.timer.clone(), print_opts);
300
301        hier_box!(member)
302    }
303
304    fn make_window(&self, name: &str, window_size: usize, print_opts: &PrintOption)
305            -> Box<dyn Rustics> {
306        let window = TimeWindow::new(name, window_size, self.timer.clone(), print_opts);
307
308        Box::new(window)
309    }
310
311    // Makes a member from a complete list of exported instances.
312
313    fn make_from_exporter(&self, name: &str, print_opts: &PrintOption, exporter: ExporterRc)
314            -> MemberRc {
315        let mut exporter_borrow = exporter        .borrow_mut();
316        let     exporter_any    = exporter_borrow .as_any_mut();
317        let     exporter_impl   = exporter_any    .downcast_mut::<IntegerExporter>().unwrap();
318        let     member          = exporter_impl   .make_member(name, print_opts);
319        let     timer           = self.timer.clone();
320        let     member          = RunningTime::from_integer(timer, print_opts, member);
321
322        hier_box!(member)
323    }
324
325    // Makes a new exporter so the Hier code can sum some RunningTime
326    // instances.
327
328    fn make_exporter(&self) -> ExporterRc {
329        let exporter = IntegerExporter::new();
330
331        Rc::from(RefCell::new(exporter))
332    }
333
334    // Pushes another instance onto the export list.  We will sum all of
335    // them at some point.
336
337    fn push(&self, exporter: &mut dyn HierExporter, member_rc: MemberRc) {
338        let exporter_any  = exporter     .as_any_mut();
339        let exporter_impl = exporter_any .downcast_mut::<IntegerExporter>().unwrap();
340
341        let member_borrow = hier_item!(member_rc);
342        let member_any    = member_borrow .as_any();
343        let member_impl   = member_any    .downcast_ref::<RunningTime>().unwrap();
344
345        exporter_impl.push(member_impl.export());
346    }
347
348    fn hz(&self) -> u128 {
349        self.hz
350    }
351}
352
353#[cfg(test)]
354mod tests {
355    use std::sync::Arc;
356    use std::sync::Mutex;
357    use super::*;
358    use crate::PrintOpts;
359    use crate::arc_box;
360    use crate::hier::HierDescriptor;
361    use crate::hier::HierDimension;
362    use crate::hier::GeneratorRc;
363    use crate::tests::continuing_box;
364    use crate::tests::continuing_timer_increment;
365    use crate::tests::check_printer_box;
366
367    fn level_0_period() -> usize {
368        8
369    }
370
371    fn level_0_retain() -> usize {
372        3 * level_0_period()
373    }
374
375    fn make_descriptor(auto_next: i64) -> HierDescriptor {
376        let     levels         = 4;
377        let     level_0_period = level_0_period();
378        let     dimension      = HierDimension::new(level_0_period, level_0_retain());
379        let mut dimensions     = Vec::<HierDimension>::with_capacity(levels);
380
381        // Push the level 0 descriptor.
382
383        dimensions.push(dimension);
384
385        // Create a hierarchy.
386
387        let mut period = 4;
388
389        for _i in 1..levels {
390            let dimension = HierDimension::new(period, 3 * period);
391
392            dimensions.push(dimension);
393
394            period += 2;
395        }
396
397        HierDescriptor::new(dimensions, Some(auto_next))
398    }
399
400    fn make_time_hier(generator:  GeneratorRc, auto_next: i64, window_size: Option<usize>)
401            -> Hier {
402        let descriptor = make_descriptor(auto_next);
403        let class      = "time".to_string();
404        let name       = "test hier".to_string();
405        let print_opts = None;
406
407        let configuration =
408            HierConfig { descriptor, generator, class, name, window_size, print_opts };
409
410        Hier::new(configuration)
411    }
412
413    fn test_new_hier_arc() {
414        let     auto_next     = 200;
415        let     descriptor    = make_descriptor(auto_next);
416        let     name          = "test hier".to_string();
417        let     print_opts    = None;
418        let     timer         = continuing_box();
419        let     window_size   = None;
420        let     configuration = TimeHierConfig { descriptor, name, window_size, timer, print_opts };
421
422        let     hier          = TimeHier::new_hier(configuration);
423        let     hier          = arc_box!(hier);
424        let mut hier_impl     = hier.lock().unwrap();
425
426        // Now just record a few events.
427
428        let mut events = 0;
429        let mut sum    = 0;
430
431        for i in 0..auto_next / 2 {
432            hier_impl.record_event();
433
434            // Make sure that this event was recorded properly.
435
436            let expected = (i + 1) * continuing_timer_increment();
437
438            assert!(hier_impl.max_i64() == expected);
439            sum += expected;
440
441            // Now try record_time()
442
443            hier_impl.record_time(i);
444
445            sum    += i;
446            events += 2;
447        }
448
449        let mean = sum as f64 / events as f64;
450
451        // Check that the event count and mean match our
452        // expectations.
453
454        assert!(hier_impl.event_count() == events);
455        assert!(hier_impl.mean()        == mean  );
456        assert!(hier_impl.class()       == "time");
457
458        hier_impl.print();
459    }
460
461    // Do a minimal liveness test of the generic hier implementation.
462
463    fn test_simple_stat() {
464        // First, just make a generator and a member, then record one event.
465
466        let     timer        = continuing_box();
467        let     generator    = TimeHier::new(timer);
468        let     member_rc    = generator.make_member("test member", &None);
469        let     member_clone = member_rc.clone();
470        let mut member       = member_clone.borrow_mut();
471        let     value        = 42;
472
473        member.to_rustics_mut().record_time(value);
474
475        assert!(member.to_rustics().count() == 1);
476        assert!(member.to_rustics().mean()  == value as f64);
477
478        // Drop the lock on the member.
479
480        drop(member);
481
482        // Now try making an exporter and check basic sanity of as_any_mut.
483
484        let exporter_rc = generator.make_exporter();
485
486        // Push the member's numbers onto the exporter.
487
488        generator.push(&mut *exporter_rc.borrow_mut(), member_rc);
489
490        let new_member_rc = generator.make_from_exporter("member export", &None, exporter_rc);
491
492        // See that the new member matches expectations.
493
494        let new_member = hier_item!(new_member_rc);
495
496        assert!(new_member.to_rustics().count() == 1);
497        assert!(new_member.to_rustics().mean()  == value as f64);
498        assert!(new_member.to_rustics().class() == "time");
499
500        // Now make an actual hier instance.
501
502        let     generator = Rc::from(RefCell::new(generator));
503        let mut hier      = make_time_hier(generator, 4, None);
504
505        let mut events = 0;
506
507        for i in 0..100 {
508            hier.record_time(i + 1);
509
510            events += 1;
511        }
512
513        assert!(hier.event_count() == events);
514        hier.print();
515    }
516
517    fn test_window() {
518        let     auto_next   = 100;
519        let     window_size = Some(1000);
520        let     timer       = continuing_box();
521        let     generator   = TimeHier::new(timer);
522        let     generator   = Rc::from(RefCell::new(generator));
523        let mut hier        = make_time_hier(generator, auto_next, window_size);
524        let     period      = level_0_period();
525        let     window_size = window_size.unwrap() as i64;
526        let mut events      = 0 as i64;
527
528        // Check time_window *_extremes.
529
530        assert!( hier.int_extremes  ());
531        assert!(!hier.float_extremes());
532
533        for i in 0..window_size {
534            let sample = i + 1;
535
536            hier.record_time(sample);
537            events += 1;
538            assert!(hier.count()   == events as u64);
539            assert!(hier.max_i64() == sample       );
540
541            let level_0_pushes = (events + auto_next - 1) / auto_next;
542            let level_0_all    = std::cmp::min(level_0_pushes, level_0_retain() as i64);
543            let level_0_live   = std::cmp::min(level_0_pushes, level_0_period() as i64);
544
545            assert!(hier.all_len (0) == level_0_all  as usize);
546            assert!(hier.live_len(0) == level_0_live as usize);
547
548            if hier.all_len(0) > period {
549                assert!(hier.all_len(1) > 0);
550            }
551
552            assert!(hier.count() == events as u64);
553        }
554
555        // Compute the expected mean of the window.
556
557        let sum   = (window_size * (window_size + 1)) / 2;
558        let sum   = sum as f64;
559        let count = events as f64;
560        let mean  = sum / count;
561
562        // Check the mean and event count from the Rustics interface.
563
564        assert!(hier.count()       == events as u64);
565        assert!(hier.mean()        == mean         );
566        assert!(hier.event_count() == events       );
567
568        // Make sure that count() matches the window_size.
569
570        hier.record_time(window_size + 1);
571
572        assert!(hier.count() == window_size as u64);
573
574        // Start again and test record_event().
575
576        let     timer           = continuing_box();
577        let     generator       = TimeHier::new(timer);
578        let     generator       = Rc::from(RefCell::new(generator));
579        let mut hier            = make_time_hier(generator, auto_next, Some(window_size as usize));
580        let     timer_increment = continuing_timer_increment();
581        let mut timer_interval  = timer_increment;
582        let mut total_time      = 0;
583        let mut events          = 0;
584
585        // Check the clock.
586
587        {
588            let timer     = continuing_box();
589            let generator = TimeHier::new(timer);
590            let timer     = continuing_box();
591            let timer     = timer!(timer);
592
593            assert!(generator.hz() == timer.hz());
594        }
595
596        for i in 1..=window_size {
597            hier.record_event();
598            assert!(hier.max_i64() == timer_interval );
599            assert!(hier.min_i64() == timer_increment);
600            assert!(hier.count()   == i as u64       );
601
602            total_time     += timer_interval;
603            timer_interval += timer_increment;
604            events         += 1;
605        }
606
607        let mean = total_time as f64 / events as f64;
608
609        assert!(hier.mean()  == mean  );
610        assert!(hier.class() == "time");
611
612        {
613            let histogram = hier.to_log_histogram().unwrap();
614            let histogram = histogram.borrow();
615
616            let mut sum = 0;
617
618            for sample in histogram.positive.iter() {
619                sum += *sample;
620            }
621
622            assert!(sum == events);
623        }
624
625        hier.clear_all();
626        assert!(hier.mean() == 0.0);
627
628        hier.record_interval(&mut continuing_box());
629        events = 1;
630
631        assert!(hier.mean()  == continuing_timer_increment() as f64);
632        assert!(hier.count() == events);
633
634    }
635
636    fn test_print_output() {
637        let expected =
638            [
639                "Test Statistics",
640                "    Count               1,000 ",
641                "    Minimum             1.000 microsecond",
642                "    Maximum             1.000 millisecond",
643                "    Log Mode               20 ",
644                "    Mode Value        786.432 microseconds",
645                "    Mean              500.500 microseconds",
646                "    Std Dev           288.819 microseconds",
647                "    Variance         +8.34166 e+10 ",
648                "    Skewness         -4.16336 e-11 ",
649                "    Kurtosis         -1.19999 e+0  ",
650                "  Log Histogram",
651                "  -----------------------",
652                "    0:                 0                 0                 0                 0",
653                "    4:                 0                 0                 0                 0",
654                "    8:                 0                 0                 1                 1",
655                "   12:                 2                 4                 8                16",
656                "   16:                33                66               131               262",
657                "   20:               476                 0                 0                 0",
658                ""
659            ];
660
661        let     timer      = continuing_box();
662        let     printer    = Some(check_printer_box(&expected, true, false));
663        let     title      = None;
664        let     units      = None;
665        let     histo_opts = None;
666        let     print_opts = Some(PrintOpts { printer, title, units, histo_opts });
667
668        let     name       = "Test Statistics";
669        let mut stats      = RunningTime::new(&name, timer, &print_opts);
670        let     samples    = 1000;
671
672        for _i in 1..=samples {
673            stats.record_event();
674        }
675
676        stats.print();
677    }
678
679    #[test]
680    fn run_tests() {
681        test_simple_stat ();
682        test_new_hier_arc();
683        test_window      ();
684        test_print_output();
685    }
686}