Add stats for fuzzing
This commit is contained in:
@@ -11,6 +11,7 @@ cargo-fuzz = true
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
libfuzzer-sys = "0.3"
|
libfuzzer-sys = "0.3"
|
||||||
persistent_store = { path = "..", features = ["std"] }
|
persistent_store = { path = "..", features = ["std"] }
|
||||||
|
strum = { version = "0.19", features = ["derive"] }
|
||||||
|
|
||||||
# Prevent this from interfering with workspaces
|
# Prevent this from interfering with workspaces
|
||||||
[workspace]
|
[workspace]
|
||||||
|
|||||||
@@ -29,6 +29,9 @@
|
|||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
mod histogram;
|
mod histogram;
|
||||||
|
mod stats;
|
||||||
|
|
||||||
|
pub use stats::{StatKey, Stats};
|
||||||
|
|
||||||
/// Bit-level entropy source based on a byte slice shared reference.
|
/// Bit-level entropy source based on a byte slice shared reference.
|
||||||
///
|
///
|
||||||
|
|||||||
187
libraries/persistent_store/fuzz/src/stats.rs
Normal file
187
libraries/persistent_store/fuzz/src/stats.rs
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
// Copyright 2019-2020 Google LLC
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
//! Helpers to compute and display fuzzing coverage statistics.
|
||||||
|
//!
|
||||||
|
//! This is not used during actual fuzzing, only when replaying a corpus to compute statistics.
|
||||||
|
|
||||||
|
use crate::histogram::{bucket_from_width, Histogram};
|
||||||
|
use crate::num_bits;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use strum::{Display, EnumIter, EnumString, IntoEnumIterator};
|
||||||
|
|
||||||
|
/// Statistics for each fuzzing run.
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, EnumIter, EnumString, Display)]
|
||||||
|
pub enum StatKey {
|
||||||
|
/// The available entropy in bytes.
|
||||||
|
Entropy,
|
||||||
|
|
||||||
|
/// The size of a page in bytes.
|
||||||
|
PageSize,
|
||||||
|
|
||||||
|
/// The number of pages.
|
||||||
|
NumPages,
|
||||||
|
|
||||||
|
/// The maximum number times a page can be erased.
|
||||||
|
MaxPageErases,
|
||||||
|
|
||||||
|
/// The dirty length of the initial storage in bytes.
|
||||||
|
///
|
||||||
|
/// This is the length of the prefix of the storage that is written using entropy before the
|
||||||
|
/// store is initialized. This permits to check the store against an invalid storage: it should
|
||||||
|
/// not crash but may misbehave.
|
||||||
|
DirtyLength,
|
||||||
|
|
||||||
|
/// The number of used erase cycles of the initial storage.
|
||||||
|
///
|
||||||
|
/// This permits to check the store as if it already consumed lifetime. In particular it permits
|
||||||
|
/// to check the store when lifetime is almost out.
|
||||||
|
InitCycles,
|
||||||
|
|
||||||
|
/// The number of words written during fuzzing.
|
||||||
|
///
|
||||||
|
/// This permits to get an idea of how much lifetime was exercised during fuzzing.
|
||||||
|
UsedLifetime,
|
||||||
|
|
||||||
|
/// Whether the store reached the end of the lifetime during fuzzing.
|
||||||
|
FinishedLifetime,
|
||||||
|
|
||||||
|
/// The number of times the store was fully compacted.
|
||||||
|
///
|
||||||
|
/// The store is considered fully compacted when all pages have been compacted once. So each
|
||||||
|
/// page has been compacted at least that number of times.
|
||||||
|
NumCompactions,
|
||||||
|
|
||||||
|
/// The number of times the store was powered on.
|
||||||
|
PowerOnCount,
|
||||||
|
|
||||||
|
/// The number of times a transaction was applied.
|
||||||
|
TransactionCount,
|
||||||
|
|
||||||
|
/// The number of times a clear operation was applied.
|
||||||
|
ClearCount,
|
||||||
|
|
||||||
|
/// The number of times a prepare operation was applied.
|
||||||
|
PrepareCount,
|
||||||
|
|
||||||
|
/// The number of times an insert update was applied.
|
||||||
|
InsertCount,
|
||||||
|
|
||||||
|
/// The number of times a remove update was applied.
|
||||||
|
RemoveCount,
|
||||||
|
|
||||||
|
/// The number of times a store operation was interrupted.
|
||||||
|
InterruptionCount,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Statistics about multiple fuzzing runs.
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Stats {
|
||||||
|
/// Maps each statistics to its histogram.
|
||||||
|
stats: HashMap<StatKey, Histogram>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Stats {
|
||||||
|
/// Adds a measure for a statistics.
|
||||||
|
pub fn add(&mut self, key: StatKey, value: usize) {
|
||||||
|
self.stats.entry(key).or_default().add(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Merges another statistics into this one.
|
||||||
|
pub fn merge(&mut self, other: &Stats) {
|
||||||
|
for (&key, other) in &other.stats {
|
||||||
|
self.stats.entry(key).or_default().merge(other);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the count of a bucket for a given key.
|
||||||
|
pub fn get_count(&self, key: StatKey, bucket: usize) -> Option<usize> {
|
||||||
|
self.stats.get(&key).and_then(|h| h.get(bucket))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns one past the highest non-empty bucket.
|
||||||
|
///
|
||||||
|
/// In other words, all non-empty buckets of the histogram are smaller than the returned bucket.
|
||||||
|
fn bucket_lim(&self) -> usize {
|
||||||
|
self.stats
|
||||||
|
.values()
|
||||||
|
.map(|h| h.bucket_lim())
|
||||||
|
.max()
|
||||||
|
.unwrap_or(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Stats {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
|
||||||
|
let mut matrix: Vec<Vec<String>> = Vec::new();
|
||||||
|
|
||||||
|
let mut header = Vec::new();
|
||||||
|
header.push(String::new());
|
||||||
|
let bits = num_bits(self.bucket_lim());
|
||||||
|
for width in 0..bits {
|
||||||
|
header.push(format!(" {}", bucket_from_width(width)));
|
||||||
|
}
|
||||||
|
header.push(" count".into());
|
||||||
|
matrix.push(header);
|
||||||
|
|
||||||
|
for key in StatKey::iter() {
|
||||||
|
let mut row = Vec::new();
|
||||||
|
row.push(format!("{}:", key));
|
||||||
|
for width in 0..bits {
|
||||||
|
row.push(match self.get_count(key, bucket_from_width(width)) {
|
||||||
|
None => String::new(),
|
||||||
|
Some(x) => format!(" {}", x),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let count = self.stats.get(&key).map_or(0, |h| h.count());
|
||||||
|
row.push(format!(" {}", count));
|
||||||
|
matrix.push(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
write_matrix(f, matrix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prints a string aligned to the right for a given width.
|
||||||
|
fn align(f: &mut std::fmt::Formatter, x: &str, n: usize) -> Result<(), std::fmt::Error> {
|
||||||
|
for _ in 0..n.saturating_sub(x.len()) {
|
||||||
|
write!(f, " ")?;
|
||||||
|
}
|
||||||
|
write!(f, "{}", x)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prints a matrix with columns of minimal width to fit all elements.
|
||||||
|
fn write_matrix(
|
||||||
|
f: &mut std::fmt::Formatter,
|
||||||
|
mut m: Vec<Vec<String>>,
|
||||||
|
) -> Result<(), std::fmt::Error> {
|
||||||
|
if m.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let num_cols = m.iter().map(|r| r.len()).max().unwrap();
|
||||||
|
let mut col_len = vec![0; num_cols];
|
||||||
|
for row in &mut m {
|
||||||
|
row.resize(num_cols, String::new());
|
||||||
|
for col in 0..num_cols {
|
||||||
|
col_len[col] = std::cmp::max(col_len[col], row[col].len());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for row in m {
|
||||||
|
for col in 0..num_cols {
|
||||||
|
align(f, &row[col], col_len[col])?;
|
||||||
|
}
|
||||||
|
writeln!(f)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user