nu_command/debug/
timeit.rs1use nu_engine::{ClosureEvalOnce, command_prelude::*};
2use nu_protocol::engine::Closure;
3use nu_utils::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(
33 vec![
34 ("time".into(), Type::Duration),
35 ("output".into(), Type::Any),
36 ]
37 .into(),
38 ),
39 ),
40 (
41 Type::Nothing,
42 Type::Record(
43 vec![
44 ("time".into(), Type::Duration),
45 ("output".into(), Type::Any),
46 ]
47 .into(),
48 ),
49 ),
50 ])
51 .allow_variants_without_examples(true)
52 .category(Category::Debug)
53 }
54
55 fn search_terms(&self) -> Vec<&str> {
56 vec!["timing", "timer", "benchmark", "measure"]
57 }
58
59 fn run(
60 &self,
61 engine_state: &EngineState,
62 stack: &mut Stack,
63 call: &Call,
64 input: PipelineData,
65 ) -> Result<PipelineData, ShellError> {
66 let stack = &mut stack.push_redirection(None, None);
68
69 let include_output = call.has_flag(engine_state, stack, "output")?;
70 let closure: Closure = call.req(engine_state, stack, 0)?;
71 let closure = ClosureEvalOnce::new_preserve_out_dest(engine_state, stack, closure);
72
73 let start_time = Instant::now();
75 let closure_output = closure.run_with_input(input)?.into_value(call.head)?;
76 let time = Value::duration(start_time.elapsed().as_nanos() as i64, call.head);
77
78 let output = if include_output {
79 Value::record(
80 record! {
81 "time" => time,
82 "output" => closure_output
83 },
84 call.head,
85 )
86 } else {
87 time
88 };
89
90 Ok(output.into_pipeline_data())
91 }
92
93 fn examples(&self) -> Vec<Example<'_>> {
94 vec![
95 #[cfg(not(test))]
96 Example {
97 description: "Time a closure containing one command.",
98 example: "timeit { sleep 500ms }",
99 result: Some(Value::test_duration(500_631_800)),
100 },
101 Example {
102 description: "Time a closure with an input value.",
103 example: "'A really long string' | timeit { split chars }",
104 result: None,
105 },
106 Example {
107 description: "Time a closure with an input stream.",
108 example: "open some_file.txt | collect | timeit { split chars }",
109 result: None,
110 },
111 Example {
112 description: "Time a closure containing a pipeline.",
113 example: "timeit { open some_file.txt | split chars }",
114 result: None,
115 },
116 #[cfg(not(test))]
117 Example {
118 description: "Time a closure and also return the output.",
119 example: "timeit --output { 'example text' }",
120 result: Some(Value::test_record(record! {
121 "time" => Value::test_duration(14328),
122 "output" => Value::test_string("example text")
123 })),
124 },
125 ]
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use nu_test_support::prelude::*;
132
133 #[test]
134 fn test_time_block() -> Result {
135 let code = "
136 [2 3 4] | timeit { job send 0 --tag 1001 }
137 job recv --tag 1001 --timeout 0sec
138 ";
139
140 test().run(code).expect_value_eq([2, 3, 4])
141 }
142
143 #[test]
144 fn test_time_block_2() -> Result {
145 let code = "
146 [2 3 4] | timeit { {result: $in} | job send 0 --tag 1002 }
147 job recv --tag 1002 --timeout 0sec
148 ";
149
150 test().run(code).expect_value_eq(test_record! {
151 "result" => test_value!([2, 3, 4]),
152 })
153 }
154}