Skip to content

Commit bc0e112

Browse files
authored
Add support for time (#254)
* initial commit for time support * update dependencies on windows * try another dependencies on windows. * run fmt
1 parent 175858c commit bc0e112

File tree

5 files changed

+201
-11
lines changed

5 files changed

+201
-11
lines changed

Cargo.lock

Lines changed: 17 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/deno_task_shell/src/grammar.pest

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,6 @@ Case = { "case" }
168168
Esac = { "esac" }
169169
While = _{ "while" }
170170
Until = _{ "until" }
171-
Time = { "time" }
172171
For = _{ "for" }
173172
Lbrace = { "{" }
174173
Rbrace = { "}" }
@@ -181,7 +180,7 @@ RESERVED_WORD = _{
181180
If | Then | Else | Elif | Fi | Do | Done |
182181
Case | Esac | While | Until | For |
183182
Lbrace | Rbrace | Bang | In |
184-
StdoutStderr | Stdout | Time
183+
StdoutStderr | Stdout
185184
}
186185

187186
// Main grammar rules

crates/shell/Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,11 @@ uu_date = "0.0.29"
3737
miette = { version = "7.5.0", features = ["fancy"] }
3838
filetime = "0.2.25"
3939
chrono = "0.4.39"
40-
parse_datetime = "0.7.0"
40+
parse_datetime = "0.8.0"
4141
dtparse = "2.0.1"
42-
windows-sys = "0.59.0"
42+
windows-sys = { version = "0.59.0", features = ["Win32_Foundation", "Win32_System_Threading"] }
4343
ctrlc = "3.4.5"
44+
libc = "0.2.170"
4445

4546
[package.metadata.release]
4647
# Dont publish the binary

crates/shell/src/commands/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,15 @@ use crate::execute;
1010
pub mod date;
1111
pub mod printenv;
1212
pub mod set;
13+
pub mod time;
1314
pub mod touch;
1415
pub mod uname;
1516
pub mod which;
1617

1718
pub use date::DateCommand;
1819
pub use printenv::PrintEnvCommand;
1920
pub use set::SetCommand;
21+
pub use time::TimeCommand;
2022
pub use touch::TouchCommand;
2123
pub use uname::UnameCommand;
2224
pub use which::WhichCommand;
@@ -76,6 +78,10 @@ pub fn get_commands() -> HashMap<String, Rc<dyn ShellCommand>> {
7678
"clear".to_string(),
7779
Rc::new(ClearCommand) as Rc<dyn ShellCommand>,
7880
),
81+
(
82+
"time".to_string(),
83+
Rc::new(TimeCommand) as Rc<dyn ShellCommand>,
84+
),
7985
])
8086
}
8187

crates/shell/src/commands/time.rs

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
use std::time::Instant;
2+
3+
use deno_task_shell::{ExecuteResult, ShellCommand, ShellCommandContext};
4+
use futures::future::LocalBoxFuture;
5+
6+
#[cfg(unix)]
7+
use libc::{rusage, timeval, RUSAGE_CHILDREN};
8+
9+
#[cfg(windows)]
10+
use windows_sys::Win32::Foundation::{FILETIME, HANDLE};
11+
#[cfg(windows)]
12+
use windows_sys::Win32::System::Threading::GetProcessTimes;
13+
14+
pub struct TimeCommand;
15+
16+
impl ShellCommand for TimeCommand {
17+
fn execute(&self, mut context: ShellCommandContext) -> LocalBoxFuture<'static, ExecuteResult> {
18+
Box::pin(async move {
19+
match execute_time(&mut context).await {
20+
Ok(_) => ExecuteResult::from_exit_code(0),
21+
Err(exit_code) => ExecuteResult::from_exit_code(exit_code),
22+
}
23+
})
24+
}
25+
}
26+
27+
#[cfg(unix)]
28+
fn timeval_to_seconds(tv: timeval) -> f64 {
29+
tv.tv_sec as f64 + (tv.tv_usec as f64 / 1_000_000.0)
30+
}
31+
32+
#[cfg(unix)]
33+
fn get_resource_usage() -> rusage {
34+
let mut usage = unsafe { std::mem::zeroed::<rusage>() };
35+
unsafe {
36+
libc::getrusage(RUSAGE_CHILDREN, &mut usage);
37+
}
38+
usage
39+
}
40+
41+
#[cfg(windows)]
42+
fn filetime_to_seconds(ft: FILETIME) -> f64 {
43+
// Convert FILETIME to 100-nanosecond intervals, then to seconds
44+
let time_value = ((ft.dwHighDateTime as u64) << 32) | (ft.dwLowDateTime as u64);
45+
time_value as f64 / 10_000_000.0
46+
}
47+
48+
#[cfg(windows)]
49+
fn get_process_times(handle: HANDLE) -> (f64, f64) {
50+
// Initialize FILETIME structures
51+
let mut creation_time = FILETIME {
52+
dwLowDateTime: 0,
53+
dwHighDateTime: 0,
54+
};
55+
let mut exit_time = FILETIME {
56+
dwLowDateTime: 0,
57+
dwHighDateTime: 0,
58+
};
59+
let mut kernel_time = FILETIME {
60+
dwLowDateTime: 0,
61+
dwHighDateTime: 0,
62+
};
63+
let mut user_time = FILETIME {
64+
dwLowDateTime: 0,
65+
dwHighDateTime: 0,
66+
};
67+
68+
unsafe {
69+
GetProcessTimes(
70+
handle,
71+
&mut creation_time,
72+
&mut exit_time,
73+
&mut kernel_time,
74+
&mut user_time,
75+
);
76+
}
77+
78+
// Convert to seconds
79+
let kernel_seconds = filetime_to_seconds(kernel_time);
80+
let user_seconds = filetime_to_seconds(user_time);
81+
82+
(user_seconds, kernel_seconds)
83+
}
84+
85+
#[cfg(windows)]
86+
fn get_current_process_handle() -> HANDLE {
87+
use windows_sys::Win32::System::Threading::GetCurrentProcess;
88+
unsafe { GetCurrentProcess() }
89+
}
90+
91+
async fn execute_time(context: &mut ShellCommandContext) -> Result<(), i32> {
92+
if context.args.is_empty() {
93+
context
94+
.stderr
95+
.write_line("Usage: time COMMAND [ARGS...]")
96+
.ok();
97+
return Err(1);
98+
}
99+
100+
let command_line = context.args.join(" ");
101+
102+
#[cfg(unix)]
103+
let before_usage = get_resource_usage();
104+
105+
#[cfg(windows)]
106+
let process_handle = get_current_process_handle();
107+
#[cfg(windows)]
108+
let (before_user, before_kernel) = get_process_times(process_handle);
109+
110+
let start = Instant::now();
111+
112+
let result = crate::execute::execute(&command_line, None, &mut context.state).await;
113+
114+
let duration = start.elapsed();
115+
116+
#[cfg(unix)]
117+
let after_usage = get_resource_usage();
118+
119+
#[cfg(windows)]
120+
let (after_user, after_kernel) = get_process_times(process_handle);
121+
122+
#[cfg(unix)]
123+
let user_time =
124+
timeval_to_seconds(after_usage.ru_utime) - timeval_to_seconds(before_usage.ru_utime);
125+
#[cfg(unix)]
126+
let sys_time =
127+
timeval_to_seconds(after_usage.ru_stime) - timeval_to_seconds(before_usage.ru_stime);
128+
129+
#[cfg(windows)]
130+
let user_time = after_user - before_user;
131+
#[cfg(windows)]
132+
let sys_time = after_kernel - before_kernel;
133+
134+
#[cfg(not(any(unix, windows)))]
135+
let user_time = 0.0;
136+
#[cfg(not(any(unix, windows)))]
137+
let sys_time = 0.0;
138+
139+
let real_time = duration.as_secs_f64();
140+
let cpu_time = user_time + sys_time;
141+
let cpu_usage = if real_time > 0.0 {
142+
(cpu_time / real_time) * 100.0
143+
} else {
144+
0.0
145+
};
146+
147+
context
148+
.stderr
149+
.write_line(&format!("\nreal\t{:.3}s", real_time))
150+
.ok();
151+
context
152+
.stderr
153+
.write_line(&format!("user\t{:.3}s", user_time))
154+
.ok();
155+
context
156+
.stderr
157+
.write_line(&format!("sys\t{:.3}s", sys_time))
158+
.ok();
159+
context
160+
.stderr
161+
.write_line(&format!("cpu\t{:.1}%", cpu_usage))
162+
.ok();
163+
164+
match result {
165+
Ok(execute_result) => match execute_result.exit_code() {
166+
0 => Ok(()),
167+
code => Err(code),
168+
},
169+
Err(err) => {
170+
context.stderr.write_line(&format!("Error: {}", err)).ok();
171+
Err(1)
172+
}
173+
}
174+
}

0 commit comments

Comments
 (0)