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