Skip to main content

pumpkin_core/statistics/
statistic_logging.rs

1//! Responsible for behaviour related to logging statistics with a specific pre-fix and closing
2//! lines.
3
4use std::fmt::Debug;
5use std::fmt::Display;
6use std::fmt::Formatter;
7use std::io::Write;
8use std::io::stdout;
9use std::sync::OnceLock;
10use std::sync::RwLock;
11
12use convert_case::Case;
13use convert_case::Casing;
14
15/// The options for statistic logging containing the statistic prefix, the (optional) line which is
16/// printed after the statistics, and the (optional) casing of the statistics.
17pub struct StatisticOptions<'a> {
18    // What is printed before a statistic is printed, the statistics will be printed in the
19    // form `{PREFIX} {VALUE}={NAME}`
20    statistic_prefix: &'a str,
21    // A closing line which is printed after all of the statistics have been printed
22    after_statistics: Option<&'a str>,
23    // The casing of the name of the statistic
24    statistics_casing: Option<Case<'static>>,
25    // The writer to which the statistics are written
26    statistics_writer: Box<dyn Write + Send + Sync>,
27}
28
29impl Debug for StatisticOptions<'_> {
30    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
31        f.debug_struct("StatisticOptions")
32            .field("statistic_prefix", &self.statistic_prefix)
33            .field("after_statistics", &self.after_statistics)
34            .field("statistics_casing", &self.statistics_casing)
35            .field("statistics_writer", &"<Writer>")
36            .finish()
37    }
38}
39
40static STATISTIC_OPTIONS: OnceLock<RwLock<StatisticOptions>> = OnceLock::new();
41
42/// Configures the logging of the statistics.
43///
44/// It specifies the (optional) prefix and a closing line (postfix) which
45/// can be written to the writer after all of the statistics have been logged.
46/// It also specifies the writer to be used for writing statistics. In case no writer is specified,
47/// stdout will be used. Statistics will only be written if `log_statistics` is true.
48pub fn configure_statistic_logging(
49    prefix: &'static str,
50    after: Option<&'static str>,
51    casing: Option<Case<'static>>,
52    writer: Option<Box<dyn Write + Send + Sync>>,
53) {
54    let _ = STATISTIC_OPTIONS.get_or_init(|| {
55        RwLock::from(StatisticOptions {
56            statistic_prefix: prefix,
57            after_statistics: after,
58            statistics_casing: casing,
59            statistics_writer: writer.unwrap_or(Box::new(stdout())),
60        })
61    });
62}
63
64/// Logs the provided statistic with name `name` and value `value`. At the moment it will log in
65/// the format `STATISTIC_PREFIX NAME=VALUE`.
66pub fn log_statistic(name: impl Display, value: impl Display) {
67    let Some(statistic_options_lock) = STATISTIC_OPTIONS.get() else {
68        return;
69    };
70
71    let Ok(mut statistic_options) = statistic_options_lock.write() else {
72        return;
73    };
74
75    let name = if let Some(casing) = &statistic_options.statistics_casing {
76        name.to_string().to_case(*casing)
77    } else {
78        name.to_string()
79    };
80    let prefix = statistic_options.statistic_prefix;
81    let _ = writeln!(
82        statistic_options.statistics_writer,
83        "{prefix} {name}={value}"
84    );
85}
86
87/// Logs the postfix of the statistics (if it has been set).
88///
89/// Certain formats (e.g. the [MiniZinc](https://www.minizinc.org/doc-2.7.6/en/fzn-spec.html#statistics-output)
90/// output format) require that a block of statistics is followed by a closing line; this
91/// function outputs this closing line **if** it is configued.
92pub fn log_statistic_postfix() {
93    let Some(statistic_options_lock) = STATISTIC_OPTIONS.get() else {
94        return;
95    };
96
97    let Ok(mut statistic_options) = statistic_options_lock.write() else {
98        return;
99    };
100
101    let Some(post_fix) = statistic_options.after_statistics else {
102        return;
103    };
104
105    let _ = writeln!(statistic_options.statistics_writer, "{post_fix}");
106}
107
108/// Returns whether or not statistics should be logged by determining whether the
109/// [`StatisticOptions`] have been configured.
110pub fn should_log_statistics() -> bool {
111    STATISTIC_OPTIONS.get().is_some()
112}