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"));
}
}