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::stdout;
8use std::io::Write;
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>,
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>,
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    if let Some(statistic_options_lock) = STATISTIC_OPTIONS.get() {
68        if let Ok(mut statistic_options) = statistic_options_lock.write() {
69            let name = if let Some(casing) = &statistic_options.statistics_casing {
70                name.to_string().to_case(*casing)
71            } else {
72                name.to_string()
73            };
74            let prefix = statistic_options.statistic_prefix;
75            let _ = writeln!(
76                statistic_options.statistics_writer,
77                "{prefix} {name}={value}"
78            );
79        }
80    }
81}
82
83/// Logs the postfix of the statistics (if it has been set).
84///
85/// Certain formats (e.g. the [MiniZinc](https://www.minizinc.org/doc-2.7.6/en/fzn-spec.html#statistics-output)
86/// output format) require that a block of statistics is followed by a closing line; this
87/// function outputs this closing line **if** it is configued.
88pub fn log_statistic_postfix() {
89    if let Some(statistic_options_lock) = STATISTIC_OPTIONS.get() {
90        if let Ok(mut statistic_options) = statistic_options_lock.write() {
91            if let Some(post_fix) = statistic_options.after_statistics {
92                let _ = writeln!(statistic_options.statistics_writer, "{post_fix}");
93            }
94        }
95    }
96}
97
98/// Returns whether or not statistics should be logged by determining whether the
99/// [`StatisticOptions`] have been configured.
100pub fn should_log_statistics() -> bool {
101    STATISTIC_OPTIONS.get().is_some()
102}