Skip to main content

nu_command/debug/
timeit.rs

1use 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        // reset outdest, so the command can write to stdout and stderr.
67        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        // Get the start time after all other computation has been done.
74        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}