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}