1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
// Copyright 2023 Martin Pool

//! Track counters of the number of files, bytes, blocks, etc, processed.
//!
//! Library code sets counters through the [Monitor] interface.

#![warn(missing_docs)]

use std::fmt::{self, Debug};
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering::Relaxed;

use itertools::Itertools;
use serde::ser::{Serialize, SerializeStruct};
use strum::{EnumCount, IntoEnumIterator};
use strum_macros::{EnumCount, EnumIter, IntoStaticStr};

/// Counters of events or bytes.
#[derive(Debug, Clone, Copy, Eq, PartialEq, EnumCount, EnumIter, IntoStaticStr)]
pub enum Counter {
    /// Number of files processed (restored, backed up, etc).
    ///
    /// Includes files that are unchanged, but not files that are excluded.
    Files,
    /// Total bytes in files processed.
    FileBytes,
    /// Number of directories processed.
    Dirs,
    /// Number of symlinks processed.
    Symlinks,
    /// Number of entries (files etc) that are unchanged from the basis backup.
    EntriesUnchanged,
    /// Number of entries changed since the basis backup.
    EntriesChanged,
    /// Number of entries added since the basis backup.
    EntriesAdded,
    /// Number of entries deleted relative to the basis backup.
    EntriesDeleted,
    /// Number of files with length zero.
    EmptyFiles,
    /// Number of small files packed into combined blocks.
    SmallFiles,
    /// Number of files that used a single block: not combined but not broken into multiple blocks.
    SingleBlockFiles,
    /// Number of files broken into multiple blocks.
    MultiBlockFiles,
    /// Number of blocks that matched a hash-addressed block that's already present.
    DeduplicatedBlocks,
    /// Total bytes in deduplicated blocks.
    DeduplicatedBlockBytes,
    /// Blocks written.
    BlockWrites,
    /// Total uncompressed bytes in blocks written out.
    BlockWriteUncompressedBytes,
    /// Total compressed bytes in blocks written out.
    BlockWriteCompressedBytes,
    /// Number of blocks read
    BlockReads,
    /// Total uncompressed bytes read from blocks.
    BlockReadUncompressedBytes,
    /// Total compressed bytes read from blocks.
    BlockReadCompressedBytes,
    /// Found the content of a block in memory.
    BlockContentCacheHit,
    /// Failed to find a block in memory.
    BlockContentCacheMiss,
    /// Cache knows that this block exists.
    BlockExistenceCacheHit,
    /// Cache did not know whether this block exists.
    BlockExistenceCacheMiss,
    /// Number of index hunks written.
    IndexWrites,
    /// Total uncompressed bytes in index hunks written.
    IndexWriteUncompressedBytes,
    /// Total compressed bytes in index hunks written.
    IndexWriteCompressedBytes,
}

/// Counter values, identified by a [Counter].
#[derive(Default)]
pub struct Counters {
    counters: [AtomicUsize; Counter::COUNT],
}

impl Counters {
    /// Increase the value for a given counter by an amount.
    pub fn count(&self, counter: Counter, increment: usize) {
        self.counters[counter as usize].fetch_add(increment, Relaxed);
    }

    /// Set the absolute value of a counter.
    pub fn set(&self, counter: Counter, value: usize) {
        self.counters[counter as usize].store(value, Relaxed);
    }

    /// Get the current value of a counter.
    pub fn get(&self, counter: Counter) -> usize {
        self.counters[counter as usize].load(Relaxed)
    }

    /// Return an iterator over counter, value pairs.
    pub fn iter(&self) -> impl Iterator<Item = (Counter, usize)> {
        Counter::iter()
            .map(move |c| (c, self.counters[c as usize].load(Relaxed)))
            .collect_vec()
            .into_iter()
    }
}

impl Debug for Counters {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let mut s = f.debug_struct("Counters");
        for i in Counter::iter() {
            s.field(
                &format!("{:?}", i),
                &self.counters[i as usize].load(Relaxed),
            );
        }
        s.finish()
    }
}

impl Serialize for Counters {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        let mut s = serializer.serialize_struct("Counters", self.counters.len())?;
        for (c, v) in self.iter() {
            s.serialize_field(c.into(), &v)?;
        }
        s.end()
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn simple_counts() {
        let counters = Counters::default();
        counters.count(Counter::Files, 1);
        counters.count(Counter::Files, 2);
        counters.set(Counter::FileBytes, 100);
        assert_eq!(counters.get(Counter::Files), 3);
        assert_eq!(counters.get(Counter::Dirs), 0);
        assert_eq!(counters.get(Counter::FileBytes), 100);
    }

    #[test]
    fn iter_counters() {
        let counters = Counters::default();
        counters.count(Counter::Files, 2);
        dbg!(&counters);

        counters.iter().for_each(|(c, v)| {
            assert_eq!(counters.get(c), v);
        });
        assert_eq!(counters.iter().count(), Counter::COUNT);
        assert!(counters
            .iter()
            .all(|(c, v)| (c == Counter::Files) == (v == 2)));
    }

    #[test]
    fn debug_form() {
        let counters = Counters::default();
        counters.count(Counter::Files, 2);
        let d = format!("{counters:#?}");
        println!("{}", d);
        assert!(d.contains("Files: 2"));
        assert!(d.contains("Dirs: 0"));
    }
}