-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Times built-in #351
Times built-in #351
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
// This file is part of yash, an extended POSIX shell. | ||
// Copyright (C) 2024 WATANABE Yuki | ||
// | ||
// This program is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU General Public License as published by | ||
// the Free Software Foundation, either version 3 of the License, or | ||
// (at your option) any later version. | ||
// | ||
// This program is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU General Public License for more details. | ||
// | ||
// You should have received a copy of the GNU General Public License | ||
// along with this program. If not, see <https://www.gnu.org/licenses/>. | ||
|
||
//! Times built-in | ||
//! | ||
//! The **`times`** built-in is used to display the accumulated user and system | ||
//! times for the shell and its children. | ||
//! | ||
//! # Synopsis | ||
//! | ||
//! ```sh | ||
//! times | ||
//! ``` | ||
//! | ||
//! # Description | ||
//! | ||
//! The built-in prints the accumulated user and system times for the shell and | ||
//! its children. | ||
//! | ||
//! # Options | ||
//! | ||
//! None. | ||
//! | ||
//! # Operands | ||
//! | ||
//! None. | ||
//! | ||
//! # Standard output | ||
//! | ||
//! Two lines are printed to the standard output, each in the following format: | ||
//! | ||
//! ```text | ||
//! 1m2.345678s 3m4.567890s | ||
//! ``` | ||
//! | ||
//! The first field of each line is the user time, and the second field is the | ||
//! system time. | ||
//! The first line shows the times consumed by the shell itself, and the | ||
//! second line shows the times consumed by its children. | ||
//! | ||
//! # Errors | ||
//! | ||
//! It is an error if the times cannot be obtained or the standard output is not | ||
//! writable. | ||
//! | ||
//! # Exit status | ||
//! | ||
//! Zero unless an error occurred. | ||
//! | ||
//! # Portability | ||
//! | ||
//! The `times` built-in is defined in POSIX. | ||
//! | ||
//! POSIX requires each field to be printed with six digits after the decimal | ||
//! point, but many implementations print less. Note that the number of digits | ||
//! does not necessarily indicate the precision of the times. | ||
|
||
use crate::common::output; | ||
use crate::common::report_error; | ||
use crate::common::report_simple_failure; | ||
use yash_env::semantics::Field; | ||
use yash_env::Env; | ||
use yash_env::System; | ||
|
||
mod format; | ||
mod syntax; | ||
|
||
/// Entry point of the `times` built-in | ||
pub async fn main(env: &mut Env, args: Vec<Field>) -> crate::Result { | ||
match syntax::parse(env, args) { | ||
Ok(()) => match env.system.times() { | ||
Ok(times) => { | ||
let result = format::format(×); | ||
output(env, &result).await | ||
} | ||
Err(error) => { | ||
report_simple_failure(env, &format!("cannot obtain times: {error}")).await | ||
} | ||
}, | ||
Err(error) => report_error(env, &error).await, | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
// This file is part of yash, an extended POSIX shell. | ||
// Copyright (C) 2024 WATANABE Yuki | ||
// | ||
// This program is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU General Public License as published by | ||
// the Free Software Foundation, either version 3 of the License, or | ||
// (at your option) any later version. | ||
// | ||
// This program is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU General Public License for more details. | ||
// | ||
// You should have received a copy of the GNU General Public License | ||
// along with this program. If not, see <https://www.gnu.org/licenses/>. | ||
|
||
//! Formatting the result of the times built-in | ||
|
||
use yash_env::system::Times; | ||
|
||
/// Formats a single time. | ||
/// | ||
/// This function panics if the `ticks_per_second` is zero. | ||
fn format_one_time<W>(ticks: u64, ticks_per_second: u64, result: &mut W) -> std::fmt::Result | ||
where | ||
W: std::fmt::Write, | ||
{ | ||
let seconds = ticks / ticks_per_second; | ||
let minutes = seconds / 60; | ||
let sub_minute_ticks = ticks - minutes * 60 * ticks_per_second; | ||
let seconds = sub_minute_ticks as f64 / ticks_per_second as f64; | ||
write!(result, "{minutes}m{seconds:.6}s") | ||
} | ||
|
||
/// Formats the result of the times built-in. | ||
/// | ||
/// This function takes a `Times` structure and returns a string that is to be | ||
/// printed to the standard output. See the | ||
/// [parent module documentation](crate::times) for the format. | ||
pub fn format(times: &Times) -> String { | ||
let mut result = String::with_capacity(64); | ||
|
||
format_one_time(times.self_user, times.ticks_per_second, &mut result).unwrap(); | ||
result.push(' '); | ||
format_one_time(times.self_system, times.ticks_per_second, &mut result).unwrap(); | ||
result.push('\n'); | ||
format_one_time(times.children_user, times.ticks_per_second, &mut result).unwrap(); | ||
result.push(' '); | ||
format_one_time(times.children_system, times.ticks_per_second, &mut result).unwrap(); | ||
result.push('\n'); | ||
|
||
result | ||
} | ||
magicant marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[test] | ||
fn format_one_time_zero() { | ||
let mut result = String::new(); | ||
format_one_time(0, 100, &mut result).unwrap(); | ||
assert_eq!(result, "0m0.000000s"); | ||
} | ||
|
||
#[test] | ||
fn format_one_time_less_than_one_second() { | ||
let mut result = String::new(); | ||
format_one_time(50, 100, &mut result).unwrap(); | ||
assert_eq!(result, "0m0.500000s"); | ||
} | ||
|
||
#[test] | ||
fn format_one_time_one_second() { | ||
let mut result = String::new(); | ||
format_one_time(1000, 1000, &mut result).unwrap(); | ||
assert_eq!(result, "0m1.000000s"); | ||
} | ||
|
||
#[test] | ||
fn format_one_time_more_than_one_second() { | ||
let mut result = String::new(); | ||
format_one_time(1225, 100, &mut result).unwrap(); | ||
assert_eq!(result, "0m12.250000s"); | ||
} | ||
|
||
#[test] | ||
fn format_one_time_more_than_one_minute() { | ||
let mut result = String::new(); | ||
format_one_time(123450, 100, &mut result).unwrap(); | ||
assert_eq!(result, "20m34.500000s"); | ||
} | ||
|
||
#[test] | ||
fn format_times() { | ||
let times = Times { | ||
self_user: 1250, | ||
self_system: 6525, | ||
children_user: 2475, | ||
children_system: 60000, | ||
ticks_per_second: 100, | ||
}; | ||
let result = format(×); | ||
assert_eq!( | ||
result, | ||
"0m12.500000s 1m5.250000s\n0m24.750000s 10m0.000000s\n" | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
// This file is part of yash, an extended POSIX shell. | ||
// Copyright (C) 2024 WATANABE Yuki | ||
// | ||
// This program is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU General Public License as published by | ||
// the Free Software Foundation, either version 3 of the License, or | ||
// (at your option) any later version. | ||
// | ||
// This program is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU General Public License for more details. | ||
// | ||
// You should have received a copy of the GNU General Public License | ||
// along with this program. If not, see <https://www.gnu.org/licenses/>. | ||
|
||
//! Command line syntax parsing for the times built-in | ||
|
||
use crate::common::syntax::{parse_arguments, Mode}; | ||
use std::borrow::Cow; | ||
use thiserror::Error; | ||
use yash_env::semantics::Field; | ||
use yash_env::Env; | ||
use yash_syntax::source::pretty::{Annotation, AnnotationType, MessageBase}; | ||
|
||
/// Error in parsing command line arguments | ||
#[derive(Clone, Debug, Eq, Error, PartialEq)] | ||
#[non_exhaustive] | ||
pub enum Error { | ||
/// An error occurred in the common parser. | ||
#[error(transparent)] | ||
CommonError(#[from] crate::common::syntax::ParseError<'static>), | ||
|
||
/// One or more operands are given. | ||
#[error("unexpected operand")] | ||
UnexpectedOperands(Vec<Field>), | ||
} | ||
|
||
impl MessageBase for Error { | ||
fn message_title(&self) -> Cow<str> { | ||
self.to_string().into() | ||
} | ||
|
||
fn main_annotation(&self) -> Annotation<'_> { | ||
use Error::*; | ||
match self { | ||
CommonError(e) => e.main_annotation(), | ||
UnexpectedOperands(operands) => Annotation::new( | ||
AnnotationType::Error, | ||
format!("{}: unexpected operand", operands[0].value).into(), | ||
&operands[0].origin, | ||
), | ||
} | ||
} | ||
} | ||
|
||
/// Parses command line arguments for the times built-in. | ||
pub fn parse(env: &Env, args: Vec<Field>) -> Result<(), Error> { | ||
let (options, operands) = parse_arguments(&[], Mode::with_env(env), args)?; | ||
debug_assert_eq!(options, []); | ||
|
||
if operands.is_empty() { | ||
Ok(()) | ||
} else { | ||
Err(Error::UnexpectedOperands(operands)) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -196,6 +196,9 @@ pub trait System: Debug { | |
#[must_use] | ||
fn now(&self) -> Instant; | ||
|
||
/// Returns consumed CPU times. | ||
fn times(&self) -> nix::Result<Times>; | ||
Comment on lines
+199
to
+200
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The addition of the Ensure that all concrete implementations of the |
||
|
||
/// Gets and/or sets the signal blocking mask. | ||
/// | ||
/// This is a low-level function used internally by | ||
|
@@ -397,6 +400,30 @@ pub trait System: Debug { | |
/// [`System::fstatat`]. | ||
pub const AT_FDCWD: Fd = Fd(nix::libc::AT_FDCWD); | ||
|
||
/// Set of consumed CPU time | ||
/// | ||
/// This structure contains four CPU time values, all in clock ticks. To convert | ||
/// them to seconds, divide each value by the number of clock ticks per second | ||
/// (`ticks_per_second`). | ||
/// | ||
/// This structure is returned by [`System::times`]. | ||
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] | ||
pub struct Times { | ||
/// User CPU time consumed by the current process | ||
pub self_user: u64, | ||
/// System CPU time consumed by the current process | ||
pub self_system: u64, | ||
/// User CPU time consumed by the children of the current process | ||
pub children_user: u64, | ||
/// System CPU time consumed by the children of the current process | ||
pub children_system: u64, | ||
|
||
/// Number of clock ticks per second | ||
/// | ||
/// This value is used to convert the consumed CPU time to seconds. | ||
pub ticks_per_second: u64, | ||
} | ||
|
||
/// How to handle a signal. | ||
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] | ||
pub enum SignalHandling { | ||
|
@@ -883,6 +910,9 @@ impl System for SharedSystem { | |
fn now(&self) -> Instant { | ||
self.0.borrow().now() | ||
} | ||
fn times(&self) -> nix::Result<Times> { | ||
self.0.borrow().times() | ||
} | ||
fn sigmask( | ||
&mut self, | ||
how: SigmaskHow, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
format_one_time
function correctly formats time values but panics ifticks_per_second
is zero. Consider handling this case more gracefully, perhaps by returning an error, to improve robustness.