diff --git a/crates/nu-command/src/debug/timeit.rs b/crates/nu-command/src/debug/timeit.rs index 7bb492769c..182cab87d7 100644 --- a/crates/nu-command/src/debug/timeit.rs +++ b/crates/nu-command/src/debug/timeit.rs @@ -1,4 +1,5 @@ -use nu_engine::{command_prelude::*, get_eval_block, get_eval_expression_with_input}; +use nu_engine::{command_prelude::*, ClosureEvalOnce}; +use nu_protocol::engine::Closure; use std::time::Instant; #[derive(Clone)] @@ -10,16 +11,18 @@ impl Command for TimeIt { } fn description(&self) -> &str { - "Time the running time of a block." + "Time how long it takes a closure to run." + } + + fn extra_description(&self) -> &str { + "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. + +This 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." } fn signature(&self) -> nu_protocol::Signature { Signature::build("timeit") - .required( - "command", - SyntaxShape::OneOf(vec![SyntaxShape::Block, SyntaxShape::Expression]), - "The command or block to run.", - ) + .required("command", SyntaxShape::Closure(None), "The closure to run.") .input_output_types(vec![ (Type::Any, Type::Duration), (Type::Nothing, Type::Duration), @@ -46,51 +49,38 @@ impl Command for TimeIt { // reset outdest, so the command can write to stdout and stderr. let stack = &mut stack.push_redirection(None, None); - let command_to_run = call.positional_nth(stack, 0); + let closure: Closure = call.req(engine_state, stack, 0)?; + let closure = ClosureEvalOnce::new_preserve_out_dest(engine_state, stack, closure); // Get the start time after all other computation has been done. let start_time = Instant::now(); + closure.run_with_input(input)?.into_value(call.head)?; + let time = start_time.elapsed(); - if let Some(command_to_run) = command_to_run { - if let Some(block_id) = command_to_run.as_block() { - let eval_block = get_eval_block(engine_state); - let block = engine_state.get_block(block_id); - eval_block(engine_state, stack, block, input)? - } else { - let eval_expression_with_input = get_eval_expression_with_input(engine_state); - let expression = &command_to_run.clone(); - eval_expression_with_input(engine_state, stack, expression, input)? - } - } else { - PipelineData::empty() - } - .into_value(call.head)?; - - let end_time = Instant::now(); - - let output = Value::duration( - end_time.saturating_duration_since(start_time).as_nanos() as i64, - call.head, - ); - + let output = Value::duration(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", + description: "Time a closure containing one command", 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 }", + description: "Time a closure with an input value", + example: "'A really long string' | timeit { split chars }", result: None, }, Example { - description: "Times a command invocation", - example: "timeit ls -la", + description: "Time a closure with an input stream", + example: "open some_file.txt | collect | timeit { split chars }", + result: None, + }, + Example { + description: "Time a closure containing a pipeline", + example: "timeit { open some_file.txt | split chars }", result: None, }, ] diff --git a/crates/nu-command/tests/commands/debug/timeit.rs b/crates/nu-command/tests/commands/debug/timeit.rs index a59f67d26a..509f5bc4e7 100644 --- a/crates/nu-command/tests/commands/debug/timeit.rs +++ b/crates/nu-command/tests/commands/debug/timeit.rs @@ -2,7 +2,7 @@ use nu_test_support::nu; #[test] fn timeit_show_stdout() { - let actual = nu!("let t = timeit nu --testbin cococo abcdefg"); + let actual = nu!("let t = timeit { nu --testbin cococo abcdefg }"); assert_eq!(actual.out, "abcdefg"); } diff --git a/crates/nu-engine/src/closure_eval.rs b/crates/nu-engine/src/closure_eval.rs index b271d90cbe..66c6287cca 100644 --- a/crates/nu-engine/src/closure_eval.rs +++ b/crates/nu-engine/src/closure_eval.rs @@ -88,6 +88,29 @@ impl ClosureEval { } } + pub fn new_preserve_out_dest( + engine_state: &EngineState, + stack: &Stack, + closure: Closure, + ) -> Self { + let engine_state = engine_state.clone(); + let stack = stack.captures_to_stack_preserve_out_dest(closure.captures); + let block = engine_state.get_block(closure.block_id).clone(); + let env_vars = stack.env_vars.clone(); + let env_hidden = stack.env_hidden.clone(); + let eval = get_eval_block_with_early_return(&engine_state); + + Self { + engine_state, + stack, + block, + arg_index: 0, + env_vars, + env_hidden, + eval, + } + } + /// Sets whether to enable debugging when evaluating the closure. /// /// By default, this is controlled by the [`EngineState`] used to create this [`ClosureEval`]. @@ -189,6 +212,22 @@ impl<'a> ClosureEvalOnce<'a> { } } + pub fn new_preserve_out_dest( + engine_state: &'a EngineState, + stack: &Stack, + closure: Closure, + ) -> Self { + let block = engine_state.get_block(closure.block_id); + let eval = get_eval_block_with_early_return(engine_state); + Self { + engine_state, + stack: stack.captures_to_stack_preserve_out_dest(closure.captures), + block, + arg_index: 0, + eval, + } + } + /// Sets whether to enable debugging when evaluating the closure. /// /// By default, this is controlled by the [`EngineState`] used to create this [`ClosureEvalOnce`].