Add continous JSON Logging monitor (#738)

* Add simple JSON Monitor

* Add documentation

* Log global state

* Fix formatting

* Save state depending on closure outcome, have file opened all the time

* Make OnDiskJSONMonitor cloneable

* Switch to FnMut to allow stateful closures

* Use &mut M: Monitor for the closure
This commit is contained in:
Sönke 2022-08-27 16:05:38 +02:00 committed by GitHub
parent 2389f677f4
commit eb7c8a1174
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 82 additions and 3 deletions

View File

@ -2,7 +2,13 @@
use alloc::{string::String, vec::Vec};
use core::time::Duration;
use std::{fs::File, io::Write, path::PathBuf};
use std::{
fs::{File, OpenOptions},
io::Write,
path::PathBuf,
};
use serde_json::json;
use crate::{
bolts::{current_time, format_duration_hms},
@ -133,3 +139,76 @@ impl OnDiskTOMLMonitor<NopMonitor> {
Self::new(filename, NopMonitor::new())
}
}
#[derive(Debug, Clone)]
/// Wraps a base monitor and continuously appends the current statistics to a JSON lines file.
pub struct OnDiskJSONMonitor<F, M>
where
F: FnMut(&mut M) -> bool,
M: Monitor,
{
base: M,
path: PathBuf,
/// A function that has the current runtime as argument and decides, whether a record should be logged
log_record: F,
}
impl<F, M> OnDiskJSONMonitor<F, M>
where
F: FnMut(&mut M) -> bool,
M: Monitor,
{
/// Create a new [`OnDiskJSONMonitor`]
pub fn new<P>(filename: P, base: M, log_record: F) -> Self
where
P: Into<PathBuf>,
{
let path = filename.into();
Self {
base,
path,
log_record,
}
}
}
impl<F, M> Monitor for OnDiskJSONMonitor<F, M>
where
F: FnMut(&mut M) -> bool,
M: Monitor,
{
fn client_stats_mut(&mut self) -> &mut Vec<ClientStats> {
self.base.client_stats_mut()
}
fn client_stats(&self) -> &[ClientStats] {
self.base.client_stats()
}
fn start_time(&mut self) -> Duration {
self.base.start_time()
}
fn display(&mut self, event_msg: String, sender_id: u32) {
if (self.log_record)(&mut self.base) {
let file = OpenOptions::new()
.append(true)
.create(true)
.open(&self.path)
.expect("Failed to open logging file");
let line = json!({
"run_time": current_time() - self.base.start_time(),
"clients": self.base.client_stats().len(),
"corpus": self.base.corpus_size(),
"objectives": self.base.objective_size(),
"executions": self.base.total_execs(),
"exec_sec": self.base.execs_per_sec(),
"clients": &self.client_stats()[1..]
});
writeln!(&file, "{}", line).expect("Unable to write JSON to file");
}
self.base.display(event_msg, sender_id);
}
}

View File

@ -15,7 +15,7 @@ use alloc::{fmt::Debug, string::String, vec::Vec};
use core::{fmt, time::Duration};
#[cfg(feature = "std")]
pub use disk::OnDiskTOMLMonitor;
pub use disk::{OnDiskJSONMonitor, OnDiskTOMLMonitor};
use hashbrown::HashMap;
use serde::{Deserialize, Serialize};
@ -56,7 +56,7 @@ impl fmt::Display for UserStats {
}
/// A simple struct to keep track of client monitor
#[derive(Debug, Clone, Default)]
#[derive(Debug, Clone, Default, Serialize)]
pub struct ClientStats {
// monitor (maybe we need a separated struct?)
/// The corpus size for this client