diff --git a/src/config.rs b/src/config.rs index 9ffbee7..c58f749 100644 --- a/src/config.rs +++ b/src/config.rs @@ -408,7 +408,20 @@ pub struct RunCommand { #[clap(short('p'), long, default_value = "128", value_name = "COUNT")] pub concurrency: NonZeroUsize, - /// Throughput sampling period, in seconds. + /// Sampling period, in seconds. + /// + /// While running the workload, periodically takes a snapshot of the statistics + /// and records it as a separate data point in the log. At the end, the log gets saved to + /// the final report. The sampling log can be used later for generating plots + /// or HDR histogram logs for further detailed data analysis. + /// + /// Sampling period does not affect the value of the final statistics computed + /// for the whole run. You'll not get more accurate measurements by sampling more frequently + /// (assuming the same total length of the run). + /// + /// The sampling log is used for analyzing throughput fluctuations and the number of samples + /// does affect the accuracy of estimating the throughput error to some degree. + /// The throughput error estimate may be inaccurate if you collect less than 10 samples. #[clap( short('s'), long("sampling"), @@ -417,6 +430,17 @@ pub struct RunCommand { )] pub sampling_interval: Interval, + /// Doesn't keep the sampling log in the report + /// + /// Use this option when you want to run the workload for a very long time, to keep memory + /// usage low and steady. The sample log will still be printed while running, but its data won't + /// be kept in memory nor saved to the final report. + /// + /// Caution: you will not be able to later generate plots nor HDR histogram logs from the + /// report if you enable this option. + #[clap(long)] + pub drop_sampling_log: bool, + /// Label that will be added to the report to help identifying the test #[clap(long("tag"), value_delimiter = ',')] pub tags: Vec, diff --git a/src/exec.rs b/src/exec.rs index 1c10526..6f3f287 100644 --- a/src/exec.rs +++ b/src/exec.rs @@ -179,6 +179,7 @@ pub async fn par_execute( sampling: Interval, workload: Workload, show_progress: bool, + keep_log: bool, ) -> Result { if exec_options.cycle_range.1 <= exec_options.cycle_range.0 { return Err(LatteError::Configuration(format!( @@ -202,7 +203,7 @@ pub async fn par_execute( let progress = Arc::new(StatusLine::with_options(progress, progress_opts)); let deadline = BoundedCycleCounter::new(exec_options.duration, exec_options.cycle_range); let mut streams = Vec::with_capacity(thread_count); - let mut stats = Recorder::start(rate, concurrency); + let mut stats = Recorder::start(rate, concurrency, keep_log); for _ in 0..thread_count { let s = spawn_stream( diff --git a/src/main.rs b/src/main.rs index fe7da57..c5b4e34 100644 --- a/src/main.rs +++ b/src/main.rs @@ -203,6 +203,7 @@ async fn load(conf: LoadCommand) -> Result<()> { config::Interval::Unbounded, loader, !conf.quiet, + false, ) .await?; @@ -265,6 +266,7 @@ async fn run(conf: RunCommand) -> Result<()> { Interval::Unbounded, runner.clone()?, !conf.quiet, + false, ) .await?; } @@ -294,6 +296,7 @@ async fn run(conf: RunCommand) -> Result<()> { conf.sampling_interval, runner, !conf.quiet, + !conf.drop_sampling_log, ) .await { diff --git a/src/stats.rs b/src/stats.rs index 7529030..2c13716 100644 --- a/src/stats.rs +++ b/src/stats.rs @@ -294,13 +294,18 @@ pub struct Recorder { log: Vec, rate_limit: Option, concurrency_limit: NonZeroUsize, + keep_log: bool, } impl Recorder { /// Creates a new recorder. /// The `rate_limit` and `concurrency_limit` parameters are used only as the /// reference levels for relative throughput and relative parallelism. - pub fn start(rate_limit: Option, concurrency_limit: NonZeroUsize) -> Recorder { + pub fn start( + rate_limit: Option, + concurrency_limit: NonZeroUsize, + keep_log: bool, + ) -> Recorder { let start_time = SystemTime::now(); let start_instant = Instant::now(); Recorder { @@ -325,6 +330,7 @@ impl Recorder { request_latency: LatencyDistributionRecorder::default(), throughput_meter: ThroughputMeter::default(), concurrency_meter: TimeSeriesStats::default(), + keep_log, } } @@ -356,6 +362,9 @@ impl Recorder { if self.errors.len() < MAX_KEPT_ERRORS { self.errors.extend(sample.req_errors.iter().cloned()); } + if !self.keep_log { + self.log.clear(); + } self.log.push(sample); self.log.last().unwrap() } @@ -381,6 +390,10 @@ impl Recorder { let concurrency = self.concurrency_meter.mean(); let concurrency_ratio = 100.0 * concurrency.value / self.concurrency_limit.get() as f64; + if !self.keep_log { + self.log.clear(); + } + BenchmarkStats { start_time: self.start_time.into(), end_time: self.end_time.into(),