1use nu_engine::{ClosureEvalOnce, command_prelude::*};
2use nu_protocol::engine::Closure;
3use web_time::Instant;
4
5#[derive(Clone)]
6pub struct TimeIt;
7
8impl Command for TimeIt {
9 fn name(&self) -> &str {
10 "timeit"
11 }
12
13 fn description(&self) -> &str {
14 "Time how long it takes a closure to run."
15 }
16
17 fn extra_description(&self) -> &str {
18 "Any pipeline input given to this command is passed to the closure. Note that streaming inputs may affect timing results, and it is recommended to add a `collect` command before this if the input is a stream.
19
20This command will bubble up any errors encountered when running the closure. The return pipeline of the closure is collected into a value and then discarded if `--output` is not set."
21 }
22
23 fn signature(&self) -> nu_protocol::Signature {
24 Signature::build("timeit")
25 .required("command", SyntaxShape::Closure(None), "The closure to run.")
26 .switch("output", "Include the closure output.", Some('o'))
27 .input_output_types(vec![
28 (Type::Any, Type::Duration),
29 (Type::Nothing, Type::Duration),
30 (
31 Type::Any,
32 Type::Record(Box::new([
33 ("time".into(), Type::Duration),
34 ("output".into(), Type::Any),
35 ])),
36 ),
37 (
38 Type::Nothing,
39 Type::Record(Box::new([
40 ("time".into(), Type::Duration),
41 ("output".into(), Type::Any),
42 ])),
43 ),
44 ])
45 .allow_variants_without_examples(true)
46 .category(Category::Debug)
47 }
48
49 fn search_terms(&self) -> Vec<&str> {
50 vec!["timing", "timer", "benchmark", "measure"]
51 }
52
53 fn requires_ast_for_arguments(&self) -> bool {
54 true
55 }
56
57 fn run(
58 &self,
59 engine_state: &EngineState,
60 stack: &mut Stack,
61 call: &Call,
62 input: PipelineData,
63 ) -> Result<PipelineData, ShellError> {
64 let stack = &mut stack.push_redirection(None, None);
66
67 let include_output = call.has_flag(engine_state, stack, "output")?;
68 let closure: Closure = call.req(engine_state, stack, 0)?;
69 let closure = ClosureEvalOnce::new_preserve_out_dest(engine_state, stack, closure);
70
71 let start_time = Instant::now();
73 let closure_output = closure.run_with_input(input)?.into_value(call.head)?;
74 let time = Value::duration(start_time.elapsed().as_nanos() as i64, call.head);
75
76 let output = if include_output {
77 Value::record(
78 record! {
79 "time" => time,
80 "output" => closure_output
81 },
82 call.head,
83 )
84 } else {
85 time
86 };
87
88 Ok(output.into_pipeline_data())
89 }
90
91 fn examples(&self) -> Vec<Example<'_>> {
92 vec![
93 #[cfg(not(test))]
94 Example {
95 description: "Time a closure containing one command",
96 example: "timeit { sleep 500ms }",
97 result: Some(Value::test_duration(500_631_800)),
98 },
99 Example {
100 description: "Time a closure with an input value",
101 example: "'A really long string' | timeit { split chars }",
102 result: None,
103 },
104 Example {
105 description: "Time a closure with an input stream",
106 example: "open some_file.txt | collect | timeit { split chars }",
107 result: None,
108 },
109 Example {
110 description: "Time a closure containing a pipeline",
111 example: "timeit { open some_file.txt | split chars }",
112 result: None,
113 },
114 #[cfg(not(test))]
115 Example {
116 description: "Time a closure and also return the output",
117 example: "timeit --output { 'example text' }",
118 result: Some(Value::test_record(record! {
119 "time" => Value::test_duration(14328),
120 "output" => Value::test_string("example text")})),
121 },
122 ]
123 }
124}
125
126#[test]
127fn test_time_block() {
130 use nu_test_support::{nu, nu_repl_code, playground::Playground};
131 Playground::setup("test_time_block", |dirs, _| {
132 let inp = [
133 r#"[2 3 4] | timeit {to nuon | save foo.txt }"#,
134 "open foo.txt",
135 ];
136 let actual_repl = nu!(cwd: dirs.test(), nu_repl_code(&inp));
137 assert_eq!(actual_repl.err, "");
138 assert_eq!(actual_repl.out, "[2, 3, 4]");
139 });
140}
141
142#[test]
143fn test_time_block_2() {
144 use nu_test_support::{nu, nu_repl_code, playground::Playground};
145 Playground::setup("test_time_block", |dirs, _| {
146 let inp = [
147 r#"[2 3 4] | timeit {{result: $in} | to nuon | save foo.txt }"#,
148 "open foo.txt",
149 ];
150 let actual_repl = nu!(cwd: dirs.test(), nu_repl_code(&inp));
151 assert_eq!(actual_repl.err, "");
152 assert_eq!(actual_repl.out, "{result: [2, 3, 4]}");
153 });
154}