Skip to content

Commit

Permalink
Merge #141
Browse files Browse the repository at this point in the history
141: Implement `time` r=GrayJack a=envp

This PR will track building `time`

Partially fixes #53

Co-authored-by: Vaibhav Yenamandra <3663231+envp@users.noreply.github.com>
  • Loading branch information
bors[bot] and envp authored Mar 10, 2021
2 parents 9f502f0 + 46529e0 commit a6dc22f
Show file tree
Hide file tree
Showing 20 changed files with 354 additions and 0 deletions.
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

0 comments on commit a6dc22f

Please sign in to comment.