Browse Source

Merge pull request #59 from JackKCWong/main

add percentiles to summary output
pull/60/head
juanleon lahoz 2 years ago committed by GitHub
parent
commit
f78c799ba1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 60
      Cargo.lock
  2. 1
      Cargo.toml
  3. 2
      src/lib.rs
  4. 4
      src/main.rs
  5. 18
      src/plot/histogram.rs
  6. 14
      src/plot/xy.rs
  7. 57
      src/stats/mod.rs

60
Cargo.lock generated

@ -428,6 +428,17 @@ dependencies = [
"slab",
]
[[package]]
name = "getrandom"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
dependencies = [
"cfg-if",
"libc",
"wasi 0.11.0+wasi-snapshot-preview1",
]
[[package]]
name = "hashbrown"
version = "0.12.3"
@ -521,9 +532,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.147"
version = "0.2.153"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
[[package]]
name = "link-cplusplus"
@ -569,6 +580,7 @@ dependencies = [
"humantime",
"log",
"predicates",
"rand",
"regex",
"serial_test",
"simplelog",
@ -653,6 +665,12 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "ppv-lite86"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "predicates"
version = "3.0.3"
@ -702,6 +720,36 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]]
name = "redox_syscall"
version = "0.2.16"
@ -906,7 +954,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
dependencies = [
"libc",
"wasi",
"wasi 0.10.0+wasi-snapshot-preview1",
"winapi",
]
@ -966,6 +1014,12 @@ version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
version = "0.2.83"

1
Cargo.toml

@ -41,3 +41,4 @@ tempfile = "3"
assert_cmd = "^2"
predicates = "^3"
serial_test = "2"
rand = "0.8.5"

2
src/lib.rs

@ -8,7 +8,7 @@
//! ```rust,no_run
//! use lowcharts::plot;
//!
//! let vec = &[-1.0, -1.1, 2.0, 2.0, 2.1, -0.9, 11.0, 11.2, 1.9, 1.99];
//! let vec = &mut [-1.0, -1.1, 2.0, 2.0, 2.1, -0.9, 11.0, 11.2, 1.9, 1.99];
//! // Plot a histogram of the above vector, with 4 buckets and a precision
//! // chosen by library
//! let options = plot::HistogramOptions { intervals: 4, ..Default::default() };

4
src/main.rs

@ -105,7 +105,7 @@ fn histogram(matches: &ArgMatches) -> i32 {
Ok(r) => r,
_ => return 2,
};
let vec = reader.read(matches.value_of("input").unwrap());
let mut vec = reader.read(matches.value_of("input").unwrap());
if !assert_data(&vec, 1) {
return 1;
}
@ -117,7 +117,7 @@ fn histogram(matches: &ArgMatches) -> i32 {
options.log_scale = matches.is_present("log-scale");
options.intervals = matches.value_of_t("intervals").unwrap();
let width = matches.value_of_t("width").unwrap();
let histogram = plot::Histogram::new(&vec, options);
let histogram = plot::Histogram::new(&mut vec, options);
print!("{histogram:width$}");
0
}

18
src/plot/histogram.rs

@ -54,7 +54,7 @@ impl Histogram {
///
/// `options` is a `HistogramOptions` struct with the preferences to create
/// histogram.
pub fn new(vec: &[f64], mut options: HistogramOptions) -> Self {
pub fn new(vec: &mut [f64], mut options: HistogramOptions) -> Self {
let mut stats = Stats::new(vec, options.precision);
if options.log_scale {
stats.min = 0.0; // We will silently discard negative values
@ -222,7 +222,7 @@ mod tests {
#[test]
fn test_buckets() {
let stats = Stats::new(&[-2.0, 14.0], None);
let stats = Stats::new(&mut [-2.0, 14.0], None);
let options = HistogramOptions {
intervals: 8,
..Default::default()
@ -247,14 +247,14 @@ mod tests {
intervals: 6,
..Default::default()
};
let mut hist = Histogram::new_with_stats(Stats::new(&[-2.0, 4.0], None), &options);
let mut hist = Histogram::new_with_stats(Stats::new(&mut [-2.0, 4.0], None), &options);
hist.load(&[-1.0, 2.0, -1.0, 2.0, 10.0, 10.0, 10.0, -10.0]);
assert_eq!(hist.top, 2);
}
#[test]
fn display_test() {
let stats = Stats::new(&[-2.0, 14.0], None);
let stats = Stats::new(&mut [-2.0, 14.0], None);
let options = HistogramOptions {
intervals: 8,
precision: Some(3),
@ -280,7 +280,7 @@ mod tests {
precision: Some(3),
..Default::default()
};
let mut hist = Histogram::new_with_stats(Stats::new(&[-2.0, 14.0], None), &options);
let mut hist = Histogram::new_with_stats(Stats::new(&mut [-2.0, 14.0], None), &options);
hist.load(&[
-1.0, -1.1, 2.0, 2.0, 2.1, -0.9, 11.0, 11.2, 1.9, 1.99, 1.98, 1.97, 1.96,
]);
@ -291,7 +291,7 @@ mod tests {
#[test]
fn display_test_human_units() {
let vector = &[
let vector = &mut [
-1.0,
-12000000.0,
-12000001.0,
@ -320,7 +320,7 @@ mod tests {
#[test]
fn display_test_log_scale() {
let hist = Histogram::new(
&[0.4, 0.4, 0.4, 0.4, 255.0, 0.2, 1.2, 128.0, 126.0, -7.0],
&mut [0.4, 0.4, 0.4, 0.4, 255.0, 0.2, 1.2, 128.0, 126.0, -7.0],
HistogramOptions {
intervals: 8,
log_scale: true,
@ -402,7 +402,7 @@ mod tests {
intervals: 8,
..Default::default()
};
let hist = Histogram::new_with_stats(Stats::new(&[-12.0, 4.0], None), &options);
let hist = Histogram::new_with_stats(Stats::new(&mut [-12.0, 4.0], None), &options);
assert!(hist.find_slot(-13.0) == None);
assert!(hist.find_slot(13.0) == None);
assert!(hist.find_slot(-12.0) == Some(0));
@ -416,7 +416,7 @@ mod tests {
fn find_slot_logarithmic() {
let hist = Histogram::new(
// More than 8 values to avoid interval truncation
&[255.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, -2000.0],
&mut [255.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, -2000.0],
HistogramOptions {
intervals: 8,
log_scale: true,

14
src/plot/xy.rs

@ -1,3 +1,4 @@
use std::borrow::BorrowMut;
use std::fmt;
use std::ops::Range;
@ -32,7 +33,12 @@ impl XyPlot {
/// "None" is used, human units will be used, with an heuristic based on the
/// input data for deciding the units and the decimal places.
pub fn new(vec: &[f64], width: usize, height: usize, precision: Option<usize>) -> Self {
let mut plot = Self::new_with_stats(width, height, Stats::new(vec, precision), precision);
let mut plot = Self::new_with_stats(
width,
height,
Stats::new(vec.to_vec().borrow_mut(), precision),
precision,
);
plot.load(vec);
plot
}
@ -132,7 +138,7 @@ mod tests {
#[test]
fn basic_test() {
let stats = Stats::new(&[-1.0, 4.0], None);
let stats = Stats::new(&mut [-1.0, 4.0], None);
let mut plot = XyPlot::new_with_stats(3, 5, stats, Some(3));
plot.load(&[-1.0, 0.0, 1.0, 2.0, 3.0, 4.0, -1.0]);
assert_float_eq!(plot.x_axis[0], -0.5, rmax <= f64::EPSILON);
@ -146,7 +152,7 @@ mod tests {
#[test]
fn display_test() {
let stats = Stats::new(&[-1.0, 4.0], None);
let stats = Stats::new(&mut [-1.0, 4.0], None);
let mut plot = XyPlot::new_with_stats(3, 5, stats, Some(3));
plot.load(&[-1.0, 0.0, 1.0, 2.0, 3.0, 4.0, -1.0]);
Paint::disable();
@ -159,7 +165,7 @@ mod tests {
#[test]
fn display_test_human_units() {
let vector = &[1000000.0, -1000000.0, -2000000.0, -4000000.0];
let vector = &mut [1000000.0, -1000000.0, -2000000.0, -4000000.0];
let plot = XyPlot::new(vector, 3, 5, None);
Paint::disable();
let display = format!("{plot}");

57
src/stats/mod.rs

@ -21,6 +21,27 @@ pub struct Stats {
/// Number of samples of the input values.
pub samples: usize,
precision: Option<usize>, // If None, then human friendly display will be used
/// 50 percentile
pub p50: f64,
/// 90 percentile
pub p90: f64,
/// 95 percentile
pub p95: f64,
/// 99 percentile
pub p99: f64,
}
fn percentiles(vec: &mut [f64]) -> (f64, f64, f64, f64) {
vec.sort_by(|a, b| a.partial_cmp(b).unwrap());
let len = vec.len();
let p50 = vec[len / 2];
let p90 = vec[(len * 9) / 10];
let p95 = vec[(len * 95) / 100];
let p99 = vec[(len * 99) / 100];
(p50, p90, p95, p99)
}
impl Stats {
@ -29,7 +50,7 @@ impl Stats {
/// `precision` is an Option with the number of decimals to display. If
/// "None" is used, human units will be used, with an heuristic based on the
/// input data for deciding the units and the decimal places.
pub fn new(vec: &[f64], precision: Option<usize>) -> Self {
pub fn new(vec: &mut [f64], precision: Option<usize>) -> Self {
let mut max = vec[0];
let mut min = max;
let mut temp: f64 = 0.0;
@ -42,6 +63,7 @@ impl Stats {
}
let var = temp / vec.len() as f64;
let std = var.sqrt();
let (p50, p90, p95, p99) = percentiles(vec);
Self {
min,
max,
@ -50,6 +72,10 @@ impl Stats {
var,
samples: vec.len(),
precision,
p50,
p90,
p95,
p99,
}
}
}
@ -73,6 +99,14 @@ impl fmt::Display for Stats {
avg = Blue.paint(formatter.format(self.avg)),
var = Blue.paint(format!("{:.3}", self.var)),
std = Blue.paint(format!("{:.3}", self.std)),
)?;
writeln!(
f,
"p50 = {p50}; p90 = {p90}; p95 = {p95}; p99 = {p99}",
p50 = Blue.paint(formatter.format(self.p50)),
p90 = Blue.paint(formatter.format(self.p90)),
p95 = Blue.paint(formatter.format(self.p95)),
p99 = Blue.paint(formatter.format(self.p99)),
)
}
}
@ -81,11 +115,12 @@ impl fmt::Display for Stats {
mod tests {
use super::*;
use float_eq::assert_float_eq;
use rand::{seq::SliceRandom, thread_rng};
use yansi::Paint;
#[test]
fn basic_test() {
let stats = Stats::new(&[1.1, 3.3, 2.2], Some(3));
let stats = Stats::new(&mut [1.1, 3.3, 2.2], Some(3));
assert_eq!(3_usize, stats.samples);
assert_float_eq!(stats.avg, 2.2, rmax <= f64::EPSILON);
assert_float_eq!(stats.min, 1.1, rmax <= f64::EPSILON);
@ -96,7 +131,7 @@ mod tests {
#[test]
fn test_display() {
let stats = Stats::new(&[1.1, 3.3, 2.2], Some(3));
let stats = Stats::new(&mut [1.1, 3.3, 2.2], Some(3));
Paint::disable();
let display = format!("{stats}");
assert!(display.contains("Samples = 3"));
@ -107,11 +142,25 @@ mod tests {
#[test]
fn test_big_num() {
let stats = Stats::new(&[123456789.1234, 123456788.1234], None);
let stats = Stats::new(&mut [123456789.1234, 123456788.1234], None);
Paint::disable();
let display = format!("{stats}");
assert!(display.contains("Samples = 2"));
assert!(display.contains("Min = 123456788.123"));
assert!(display.contains("Max = 123456789.123"));
}
#[test]
fn test_percentile() {
let mut vec: Vec<f64> = (0..100).map(|i| i as f64).collect();
vec.shuffle(&mut thread_rng());
let stats = Stats::new(&mut vec, Some(1));
Paint::disable();
let display = format!("{stats}");
println!("{}", display);
assert!(display.contains("p50 = 50.0"));
assert!(display.contains("p90 = 90.0"));
assert!(display.contains("p95 = 95.0"));
assert!(display.contains("p99 = 99.0"));
}
}

Loading…
Cancel
Save