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 requires_ast_for_arguments(&self) -> bool {
60        true
61    }
62
63    fn run(
64        &self,
65        engine_state: &EngineState,
66        stack: &mut Stack,
67        call: &Call,
68        input: PipelineData,
69    ) -> Result<PipelineData, ShellError> {
70        // reset outdest, so the command can write to stdout and stderr.
71        let stack = &mut stack.push_redirection(None, None);
72
73        let include_output = call.has_flag(engine_state, stack, "output")?;
74        let closure: Closure = call.req(engine_state, stack, 0)?;
75        let closure = ClosureEvalOnce::new_preserve_out_dest(engine_state, stack, closure);
76
77        // Get the start time after all other computation has been done.
78        let start_time = Instant::now();
79        let closure_output = closure.run_with_input(input)?.into_value(call.head)?;
80        let time = Value::duration(start_time.elapsed().as_nanos() as i64, call.head);
81
82        let output = if include_output {
83            Value::record(
84                record! {
85                "time" => time,
86                "output" => closure_output
87                },
88                call.head,
89            )
90        } else {
91            time
92        };
93
94        Ok(output.into_pipeline_data())
95    }
96
97    fn examples(&self) -> Vec<Example<'_>> {
98        vec![
99            #[cfg(not(test))]
100            Example {
101                description: "Time a closure containing one command.",
102                example: "timeit { sleep 500ms }",
103                result: Some(Value::test_duration(500_631_800)),
104            },
105            Example {
106                description: "Time a closure with an input value.",
107                example: "'A really long string' | timeit { split chars }",
108                result: None,
109            },
110            Example {
111                description: "Time a closure with an input stream.",
112                example: "open some_file.txt | collect | timeit { split chars }",
113                result: None,
114            },
115            Example {
116                description: "Time a closure containing a pipeline.",
117                example: "timeit { open some_file.txt | split chars }",
118                result: None,
119            },
120            #[cfg(not(test))]
121            Example {
122                description: "Time a closure and also return the output.",
123                example: "timeit --output { 'example text' }",
124                result: Some(Value::test_record(record! {
125                "time" => Value::test_duration(14328),
126                "output" => Value::test_string("example text")})),
127            },
128        ]
129    }
130}
131
132#[test]
133// Due to difficulty in observing side-effects from time closures,
134// checks that the closures have run correctly must use the filesystem.
135fn test_time_block() {
136    use nu_test_support::{nu, nu_repl_code, playground::Playground};
137    Playground::setup("test_time_block", |dirs, _| {
138        let inp = ["[2 3 4] | timeit {to nuon | save foo.txt }", "open foo.txt"];
139        let actual_repl = nu!(cwd: dirs.test(), nu_repl_code(&inp));
140        assert_eq!(actual_repl.err, "");
141        assert_eq!(actual_repl.out, "[2, 3, 4]");
142    });
143}
144
145#[test]
146fn test_time_block_2() {
147    use nu_test_support::{nu, nu_repl_code, playground::Playground};
148    Playground::setup("test_time_block", |dirs, _| {
149        let inp = [
150            "[2 3 4] | timeit {{result: $in} | to nuon | save foo.txt }",
151            "open foo.txt",
152        ];
153        let actual_repl = nu!(cwd: dirs.test(), nu_repl_code(&inp));
154        assert_eq!(actual_repl.err, "");
155        assert_eq!(actual_repl.out, "{result: [2, 3, 4]}");
156    });
157}