1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
use nu_engine::{eval_block, eval_expression_with_input};
use nu_protocol::{
    ast::Call,
    engine::{Command, EngineState, Stack},
    Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Type,
    Value,
};
use std::time::Instant;

#[derive(Clone)]
pub struct TimeIt;

impl Command for TimeIt {
    fn name(&self) -> &str {
        "timeit"
    }

    fn usage(&self) -> &str {
        "Time the running time of a block."
    }

    fn signature(&self) -> nu_protocol::Signature {
        Signature::build("timeit")
            .required(
                "command",
                SyntaxShape::OneOf(vec![SyntaxShape::Block, SyntaxShape::Expression]),
                "The command or block to run.",
            )
            .input_output_types(vec![
                (Type::Any, Type::Duration),
                (Type::Nothing, Type::Duration),
            ])
            .allow_variants_without_examples(true)
            .category(Category::Debug)
    }

    fn search_terms(&self) -> Vec<&str> {
        vec!["timing", "timer", "benchmark", "measure"]
    }

    fn run(
        &self,
        engine_state: &EngineState,
        stack: &mut Stack,
        call: &Call,
        input: PipelineData,
    ) -> Result<PipelineData, ShellError> {
        let command_to_run = call.positional_nth(0);

        // Get the start time after all other computation has been done.
        let start_time = Instant::now();

        if let Some(command_to_run) = command_to_run {
            if let Some(block_id) = command_to_run.as_block() {
                let block = engine_state.get_block(block_id);
                eval_block(
                    engine_state,
                    stack,
                    block,
                    input,
                    call.redirect_stdout,
                    call.redirect_stderr,
                )?
            } else {
                eval_expression_with_input(
                    engine_state,
                    stack,
                    command_to_run,
                    input,
                    call.redirect_stdout,
                    call.redirect_stderr,
                )
                .map(|res| res.0)?
            }
        } else {
            PipelineData::empty()
        }
        .into_value(call.head);

        let end_time = Instant::now();

        let output = Value::duration((end_time - start_time).as_nanos() as i64, call.head);

        Ok(output.into_pipeline_data())
    }

    fn examples(&self) -> Vec<Example> {
        vec![
            Example {
                description: "Times a command within a closure",
                example: "timeit { sleep 500ms }",
                result: None,
            },
            Example {
                description: "Times a command using an existing input",
                example: "http get https://www.nushell.sh/book/ | timeit { split chars }",
                result: None,
            },
            Example {
                description: "Times a command invocation",
                example: "timeit ls -la",
                result: None,
            },
        ]
    }
}

#[test]
// Due to difficulty in observing side-effects from time closures,
// checks that the closures have run correctly must use the filesystem.
fn test_time_block() {
    use nu_test_support::{nu, nu_repl_code, playground::Playground};
    Playground::setup("test_time_block", |dirs, _| {
        let inp = [
            r#"[2 3 4] | timeit {to nuon | save foo.txt }"#,
            "open foo.txt",
        ];
        let actual_repl = nu!(cwd: dirs.test(), nu_repl_code(&inp));
        assert_eq!(actual_repl.err, "");
        assert_eq!(actual_repl.out, "[2, 3, 4]");
    });
}

#[test]
fn test_time_block_2() {
    use nu_test_support::{nu, nu_repl_code, playground::Playground};
    Playground::setup("test_time_block", |dirs, _| {
        let inp = [
            r#"[2 3 4] | timeit {{result: $in} | to nuon | save foo.txt }"#,
            "open foo.txt",
        ];
        let actual_repl = nu!(cwd: dirs.test(), nu_repl_code(&inp));
        assert_eq!(actual_repl.err, "");
        assert_eq!(actual_repl.out, "{result: [2, 3, 4]}");
    });
}