Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
b27496b
General: Add `time` binary to toml files of OSes
envp Feb 27, 2021
471414b
Time: Scaffold code from template
envp Feb 28, 2021
b9ad3d5
Time: cli: Add -p flag, and accept a command to run
envp Feb 28, 2021
a621450
Time: cli: Rename struct s/Args/TimeOpts
envp Feb 28, 2021
6229d6a
Core: Create, export module resource
envp Mar 1, 2021
2ffb334
Time: cli: Combine exec_args and executable into a single field
envp Mar 1, 2021
cd93133
Time: subprocess: Create subprocess module
envp Mar 1, 2021
4e6468f
Time: Parse CLI opts, run subprocess and extract resource usage
envp Mar 1, 2021
8caf5c9
Time: cli: Fix bug in argument specification
envp Mar 6, 2021
f0b97fd
Time: cli: Refactor and add test cases
envp Mar 6, 2021
6f3dd31
Time: Break up CLI into flags, output modules
envp Mar 6, 2021
003f012
Time: Implement `POSIX.2` output formatting and macos (FreeBSD) default
envp Mar 6, 2021
0bcb1f2
Time: Format code
envp Mar 6, 2021
54223a0
Time: output: Move platform specific details into their own modules
envp Mar 6, 2021
1f25d16
Time: subprocess: Follow `POSIX.2` spec for error codes
envp Mar 6, 2021
d8eba37
Time: cli: Drop non-standard `--posix`
envp Mar 6, 2021
31264e0
Time: drop macos specific code
envp Mar 6, 2021
c5b4b55
Time: Custom get_rusage() for FuchsiaOS
envp Mar 6, 2021
cfb73c7
Time: Format source code
envp Mar 6, 2021
3776a7c
Core: resource: Implement Conversion trait for rusage -> RUsage
envp Mar 7, 2021
aca20a8
Time: subprocess: Drop benchmark like test
envp Mar 8, 2021
46529e0
Core: resource: Drop import as std::convert::From is in the Prelude
envp Mar 8, 2021
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 @@ -41,6 +41,7 @@ members = [
"sort",
"tail",
"tee",
"time",
"touch",
"true",
"tty",
Expand Down
1 change: 1 addition & 0 deletions FreeBSD.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ members = [
"sort",
"tail",
"tee",
"time",
"touch",
"true",
"tty",
Expand Down
1 change: 1 addition & 0 deletions Fuchsia.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ members = [
"sort",
"tail",
"tee",
# "time",
"touch",
"true",
"tty",
Expand Down
1 change: 1 addition & 0 deletions Haiku.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ members = [
"sort",
"tail",
"tee",
"time",
"touch",
"true",
"tty",
Expand Down
1 change: 1 addition & 0 deletions Illumos.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ members = [
"sort",
"tail",
"tee",
"time",
"touch",
"true",
"tty",
Expand Down
1 change: 1 addition & 0 deletions Linux.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ members = [
"sort",
"tail",
"tee",
"time",
"touch",
"true",
"tty",
Expand Down
1 change: 1 addition & 0 deletions MacOS.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ members = [
"sort",
"tail",
"tee",
"time",
"touch",
"true",
"tty",
Expand Down
1 change: 1 addition & 0 deletions NetBSD.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ members = [
"sort",
"tail",
"tee",
"time",
"touch",
"true",
"tty",
Expand Down
1 change: 1 addition & 0 deletions OpenBSD.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ members = [
"sort",
"tail",
"tee",
"time",
"touch",
"true",
"tty",
Expand Down
1 change: 1 addition & 0 deletions Solaris.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ members = [
"sort",
"tail",
"tee",
"time",
"touch",
"true",
"tty",
Expand Down
1 change: 1 addition & 0 deletions Unix.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ members = [
"sort",
"tail",
"tee",
"time",
"touch",
"true",
"tty",
Expand Down
1 change: 1 addition & 0 deletions coreutils_core/src/os.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pub mod group;
pub mod login_name;
pub mod passwd;
pub mod process;
pub mod resource;
pub mod time;
pub mod tty;
pub mod utsname;
Expand Down
109 changes: 109 additions & 0 deletions coreutils_core/src/os/resource.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
//! Module abstracting interactions with getrusage(2)
//!
//! Also holds utility functions for summarizing the data returned by getrusage(2)
use super::TimeVal;
#[cfg(not(target_os = "fuchsia"))]
use libc::getrusage;
use libc::{c_int, rusage, RUSAGE_CHILDREN, RUSAGE_SELF};

/// Interface for `RUSAGE_*` constants from libc.
///
/// TODO This is an incomplete set of constants. It is currently missing
/// `libc::RUSAGE_THREAD` which requires the `_GNU_SOURCE` macro to be defined
/// at build time.
pub enum ResourceConsumer {
Caller = RUSAGE_SELF as isize,
Children = RUSAGE_CHILDREN as isize,
}

#[derive(Debug)]
pub struct RUsage {
pub timing: Timing,
pub mem: MemoryUsage,
pub io: IOUsage,
}

#[derive(Debug)]
pub struct Timing {
/// User CPU time used
pub user_time: TimeVal,
/// System CPU time used
pub sys_time: TimeVal,
}

#[derive(Debug)]
pub struct MemoryUsage {
/// Maximum resident set size
pub max_rss: u64,
/// Number of page reclaims (soft page faults)
pub num_minor_page_flt: u64,
/// Number of page faults (hard page faults)
pub num_major_page_flt: u64,
/// Number of voluntary context switches
pub num_vol_ctx_switch: u64,
/// Number of involuntary context switches
pub num_invol_ctx_switch: u64,
/// Unmaintained on linux: Integral shared memory size
pub shared_mem_size: u64,
/// Unmaintained on linux: Integral unshared data size
pub unshared_data_size: u64,
/// Unmaintained on linux: Integral unshared stack size
pub unshared_stack_size: u64,
/// Unmaintained on linux: Number of swaps
pub num_swaps: u64,
}

#[derive(Debug)]
pub struct IOUsage {
/// Number of block input operations
pub num_block_in: u64,
/// Number of block output operations
pub num_block_out: u64,
/// Unmaintained on linux: Number of IPC messages recieved
pub num_sock_recv: u64,
/// Unmaintained on linux: Number of IPC messages sent
pub num_sock_send: u64,
/// Unmaintained: Number of signals recieved
pub num_signals: u64,
}

impl From<rusage> for RUsage {
fn from(ru: rusage) -> Self {
RUsage {
timing: Timing { user_time: ru.ru_utime, sys_time: ru.ru_stime },
mem: MemoryUsage {
max_rss: ru.ru_maxrss as u64,
num_minor_page_flt: ru.ru_minflt as u64,
num_major_page_flt: ru.ru_majflt as u64,
num_vol_ctx_switch: ru.ru_nvcsw as u64,
num_invol_ctx_switch: ru.ru_nivcsw as u64,
shared_mem_size: ru.ru_ixrss as u64,
unshared_data_size: ru.ru_idrss as u64,
unshared_stack_size: ru.ru_isrss as u64,
num_swaps: ru.ru_nswap as u64,
},
io: IOUsage {
num_block_in: ru.ru_inblock as u64,
num_block_out: ru.ru_oublock as u64,
num_sock_recv: ru.ru_msgrcv as u64,
num_sock_send: ru.ru_msgsnd as u64,
num_signals: ru.ru_nsignals as u64,
},
}
}
}

/// Safely wrap `libc::getrusage`
pub fn get_rusage(target: ResourceConsumer) -> RUsage {
let mut usage: rusage = unsafe { std::mem::zeroed() };

#[cfg(not(target_os = "fuchsia"))]
// Fuchsia doesn't have a getrusage syscall, but provides the rusage struct.
// The default is to abort with an error message so that callers don't end
// up with invalid data.
unsafe {
getrusage(target as c_int, &mut usage);
}

RUsage::from(usage)
}
15 changes: 15 additions & 0 deletions time/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "time"
version = "0.1.0"
authors = ["Vaibhav Yenamandra <v@calloc.net>"]
license = "MPL-2.0-no-copyleft-exception"
build = "build.rs"
edition = "2018"
description = "time a simple command"

[dependencies]
clap = { version = "^2.33.0", features = ["wrap_help"] }
coreutils_core = { path = "../coreutils_core" }

[build-dependencies]
clap = { version = "^2.33.0" }
24 changes: 24 additions & 0 deletions time/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use std::env;

use clap::Shell;

#[path = "src/cli.rs"]
mod cli;

fn main() {
let mut app = cli::create_app();

let out_dir = match env::var("OUT_DIR") {
Ok(dir) => dir,
Err(err) => {
eprintln!("No OUT_DIR: {}", err);
return;
},
};

app.gen_completions("template", Shell::Zsh, out_dir.clone());
app.gen_completions("template", Shell::Fish, out_dir.clone());
app.gen_completions("template", Shell::Bash, out_dir.clone());
app.gen_completions("template", Shell::PowerShell, out_dir.clone());
app.gen_completions("template", Shell::Elvish, out_dir);
}
29 changes: 29 additions & 0 deletions time/src/cli.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use clap::{
crate_authors, crate_description, crate_name, crate_version, App, AppSettings::ColoredHelp, Arg,
};

pub(crate) fn create_app<'a, 'b>() -> App<'a, 'b> {
let app = App::new(crate_name!())
.version(crate_version!())
.author(crate_authors!())
.about(crate_description!())
.help_message("Display help information.")
.version_message("Display version information.")
.help_short("?")
.settings(&[ColoredHelp]);

let posix_fmt = Arg::with_name("posix")
.help(
"Display time output in POSIX specified format as:\n\treal %f\n\tuser %f\n\tsys \
%f\nTimer accuracy is arbitrary, but will always be counted in seconds.",
)
.short("p")
.takes_value(false);

let command = Arg::with_name("COMMAND").help("Command or utility to run.").required(true);

let arguments =
Arg::with_name("ARGUMENT").help("Optional arguments to pass to <COMMAND>.").multiple(true);

app.args(&[posix_fmt, command, arguments])
}
60 changes: 60 additions & 0 deletions time/src/flags.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//! Command line options that are supported by `time`

use crate::{cli::create_app, output::OutputFormatter};

// Condense CLI args as a struct
#[derive(Debug)]
pub struct TimeOpts {
/// Formatter to use when printing stats back to CLI
pub printer: OutputFormatter,
/// Command as seen on the CLI
pub command: Vec<String>,
}

impl TimeOpts {
pub fn from_matches() -> Self { Self::new(create_app().get_matches()) }

pub fn new(args: clap::ArgMatches) -> Self {
let command =
args.value_of("COMMAND").expect("`COMMAND` value cannot be `None`, it is required.");

TimeOpts {
printer: if args.is_present("posix") {
OutputFormatter::Posix
} else {
OutputFormatter::Default
},
command: match args.values_of("ARGUMENT") {
Some(vs) => {
let mut cmd = vec![command.to_owned()];
cmd.extend(vs.into_iter().map(|item| item.to_owned()));
cmd
},
None => vec![command.to_owned()],
},
}
}
}

#[cfg(test)]
mod tests {
use super::{create_app, OutputFormatter, TimeOpts};

#[test]
fn parsing_valid_command_with_args() {
let args = vec!["test-time", "cmd-to-run", "arg1", "arg2", "arg3"];
let opts = TimeOpts::new(create_app().get_matches_from(args));

assert_eq!(4, opts.command.len());
assert_eq!(vec!["cmd-to-run", "arg1", "arg2", "arg3"], opts.command);
assert_eq!(OutputFormatter::Default, opts.printer);
}

#[test]
fn parse_valid_command_with_posix_spec() {
let args = vec!["test-time", "cmd-to-run", "arg1", "arg2", "arg3", "-p"];
let opts = TimeOpts::new(create_app().get_matches_from(args));

assert_eq!(OutputFormatter::Posix, opts.printer);
}
}
19 changes: 19 additions & 0 deletions time/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
mod cli;
mod flags;
mod output;
mod subprocess;

use coreutils_core::os::resource::{get_rusage, ResourceConsumer};

fn main() {
let opts = flags::TimeOpts::from_matches();
let (exit_status, duration) = match subprocess::timed_run(&opts.command) {
Ok(rv) => rv,
Err(err) => subprocess::exit_with_msg(err),
};

let usage = get_rusage(ResourceConsumer::Children);

eprintln!("{}", opts.printer.format_stats(&usage, &duration));
std::process::exit(exit_status.code().unwrap_or(1));
}
30 changes: 30 additions & 0 deletions time/src/output.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//! Output interface for `time`

use coreutils_core::os::{resource::RUsage, TimeVal};

#[derive(Debug, PartialEq)]
pub enum OutputFormatter {
Default,
Posix,
}

/// Express `coreutils_core::os::TimeVal` into `f64` seconds
fn as_secs_f64(tv: TimeVal) -> f64 { tv.tv_sec as f64 + (tv.tv_usec as f64) / 1_000_000.0 }

impl OutputFormatter {
pub fn format_stats(self, rusage: &RUsage, duration: &std::time::Duration) -> String {
let wall_time = duration.as_secs_f64();
let user_time = as_secs_f64(rusage.timing.user_time);
let sys_time = as_secs_f64(rusage.timing.sys_time);
match self {
OutputFormatter::Default => default_formatter(rusage, wall_time, user_time, sys_time),
OutputFormatter::Posix => {
format!("real {:.2}\nuser {:.2}\nsys {:.2}", wall_time, user_time, sys_time)
},
}
}
}

pub fn default_formatter(_: &RUsage, wall_time: f64, user_time: f64, sys_time: f64) -> String {
format!("{:.2} real {:.2} user {:.2} sys", wall_time, user_time, sys_time)
}
Loading