Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,5 @@ windows-sys = {version = "0.52.0", features = ["Win32_Foundation", "Win32_System
[dev-dependencies]
env_logger = "0.11.3"
rand = "0.10.0"
test-case = "3.3.1"
tiny_http = "0.12.0"
67 changes: 66 additions & 1 deletion src/cmd/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use log::{error, info};
use process_lines_actions::InnerState;
use std::env::consts::EXE_SUFFIX;
use std::ffi::{OsStr, OsString};
use std::mem;
use std::path::{Path, PathBuf};
use std::process::{ExitStatus, Stdio};
use std::time::{Duration, Instant};
Expand Down Expand Up @@ -557,14 +558,38 @@ impl From<InnerProcessOutput> for ProcessOutput {
}

/// collected statistics about the process execution.
#[derive(Default)]
#[derive(Debug, Default, Clone)]
#[cfg_attr(test, derive(PartialEq, Eq))]
pub struct ProcessStatistics {
/// peak memory usage in bytes.
/// This is populated for sandboxed commands on systems
/// with cgroups v1/v2.
pub memory_peak: Option<u64>,
}

impl ProcessStatistics {
/// Merge two `ProcessStatistics` into one, following a fixed set of aggregation rules:
///
/// - `memory_peak`: the maximum of the two values is kept, since a merged peak
/// should reflect the highest peak observed across all runs. If only one side
/// has a value and the other is `None`, that value is used as-is.
pub fn merge(self, other: Self) -> Self {
Self {
memory_peak: match (self.memory_peak, other.memory_peak) {
(Some(a), Some(b)) => Some(a.max(b)),
(a, b) => a.or(b),
},
}
}

/// Merge another `ProcessStatistics` into `self` in place.
///
/// See [`merge`](Self::merge) for the aggregation rules.
pub fn merge_mut(&mut self, other: Self) {
*self = mem::take(self).merge(other);
}
}

/// Output of a [`Command`](struct.Command.html) when it was executed with the
/// [`run_capture`](struct.Command.html#method.run_capture) method.
pub struct ProcessOutput {
Expand Down Expand Up @@ -732,3 +757,43 @@ fn exe_suffix(file: &OsStr) -> OsString {
path.push(EXE_SUFFIX);
path
}

#[cfg(test)]
mod tests {
use super::ProcessStatistics;
use test_case::test_case;

const fn stats(peak: Option<u64>) -> ProcessStatistics {
ProcessStatistics { memory_peak: peak }
}

#[test_case(stats(None), stats(None), stats(None))]
#[test_case(stats(Some(100)), stats(None), stats(Some(100)))]
#[test_case(stats(None), stats(Some(100)), stats(Some(100)))]
#[test_case(stats(Some(300)), stats(Some(100)), stats(Some(300)))]
#[test_case(stats(Some(100)), stats(Some(300)), stats(Some(300)))]
#[test_case(stats(Some(42)), stats(Some(42)), stats(Some(42)))]
fn test_merge(lhs: ProcessStatistics, rhs: ProcessStatistics, expected: ProcessStatistics) {
{
let lhs = lhs.clone();
let rhs = rhs.clone();
assert_eq!(lhs.merge(rhs), expected);
}

{
let mut lhs = lhs.clone();
lhs.merge_mut(rhs);
assert_eq!(lhs, expected);
}
}

#[test]
fn merge_mut_accumulate_over_multiple() {
let mut s = stats(None);
s.merge_mut(stats(Some(50)));
s.merge_mut(stats(Some(200)));
s.merge_mut(stats(None));
s.merge_mut(stats(Some(150)));
assert_eq!(s.memory_peak, Some(200));
}
}
Loading