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 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 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]
133fn 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}