Implement passthrough for benchmark (#2580)

* Implement passthrough for benchmark

Add a new option -p,--passthrough to benchmark.
With this option, the benchmark command prints its results to stdout and passes the block's output to the next command in the pipeline.

* Add execution block for benchmark -p

`benchmark --passthrough` now takes a block where the benchmark output is sent.
Example:
`benchmark -p {save bench.toml} {ls}`
This commit is contained in:
Radek Vít 2020-09-21 19:30:16 +02:00 committed by GitHub
parent 9b577b8679
commit 09429d08aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -4,7 +4,10 @@ use crate::prelude::*;
#[cfg(feature = "rich-benchmark")] #[cfg(feature = "rich-benchmark")]
use heim::cpu::time; use heim::cpu::time;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{hir::Block, Dictionary, Signature, SyntaxShape, UntaggedValue, Value}; use nu_protocol::{
hir::{Block, ClassifiedCommand, Commands, InternalCommand},
Dictionary, Scope, Signature, SyntaxShape, UntaggedValue, Value,
};
use std::convert::TryInto; use std::convert::TryInto;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
@ -13,6 +16,7 @@ pub struct Benchmark;
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
struct BenchmarkArgs { struct BenchmarkArgs {
block: Block, block: Block,
passthrough: Option<Block>,
} }
#[async_trait] #[async_trait]
@ -22,11 +26,18 @@ impl WholeStreamCommand for Benchmark {
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("benchmark").required( Signature::build("benchmark")
.required(
"block", "block",
SyntaxShape::Block, SyntaxShape::Block,
"the block to run and benchmark", "the block to run and benchmark",
) )
.named(
"passthrough",
SyntaxShape::Block,
"Display the benchmark results and pass through the block's output",
Some('p'),
)
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
@ -42,15 +53,21 @@ impl WholeStreamCommand for Benchmark {
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![Example { vec![
Example {
description: "Benchmarks a command within a block", description: "Benchmarks a command within a block",
example: "benchmark { sleep 500ms }", example: "benchmark { sleep 500ms }",
result: None, result: None,
}] },
Example {
description: "Benchmarks a command within a block and passes its output through",
example: "echo 45 | benchmark { sleep 500ms } --passthrough {}",
result: Some(vec![UntaggedValue::int(45).into()]),
},
]
} }
} }
#[cfg(feature = "rich-benchmark")]
async fn benchmark( async fn benchmark(
raw_args: CommandArgs, raw_args: CommandArgs,
registry: &CommandRegistry, registry: &CommandRegistry,
@ -60,10 +77,11 @@ async fn benchmark(
let tag = raw_args.call_info.args.span; let tag = raw_args.call_info.args.span;
let mut context = EvaluationContext::from_raw(&raw_args, &registry); let mut context = EvaluationContext::from_raw(&raw_args, &registry);
let scope = raw_args.call_info.scope.clone(); let scope = raw_args.call_info.scope.clone();
let (BenchmarkArgs { block }, input) = raw_args.process(&registry).await?; let (BenchmarkArgs { block, passthrough }, input) = raw_args.process(&registry).await?;
let start_time = Instant::now(); let start_time = Instant::now();
#[cfg(feature = "rich-benchmark")]
let start = time().await; let start = time().await;
let result = run_block( let result = run_block(
@ -75,30 +93,41 @@ async fn benchmark(
&scope.env, &scope.env,
) )
.await; .await;
let _ = result?.drain_vec().await; let output = result?.into_vec().await;
#[cfg(feature = "rich-benchmark")]
let end = time().await; let end = time().await;
let end_time = Instant::now(); let end_time = Instant::now();
context.clear_errors(); context.clear_errors();
// return basic runtime
#[cfg(not(feature = "rich-benchmark"))]
{
let mut indexmap = IndexMap::with_capacity(1);
let real_time = into_big_int(end_time - start_time);
indexmap.insert("real time".to_string(), real_time);
benchmark_output(indexmap, output, passthrough, &tag, &mut context, &scope).await
}
// return advanced stats
#[cfg(feature = "rich-benchmark")]
if let (Ok(start), Ok(end)) = (start, end) { if let (Ok(start), Ok(end)) = (start, end) {
let mut indexmap = IndexMap::with_capacity(4); let mut indexmap = IndexMap::with_capacity(4);
let real_time = into_value(end_time - start_time, &tag); let real_time = into_big_int(end_time - start_time);
indexmap.insert("real time".to_string(), real_time); indexmap.insert("real time".to_string(), real_time);
let user_time = into_value(end.user() - start.user(), &tag); let user_time = into_big_int(end.user() - start.user());
indexmap.insert("user time".to_string(), user_time); indexmap.insert("user time".to_string(), user_time);
let system_time = into_value(end.system() - start.system(), &tag); let system_time = into_big_int(end.system() - start.system());
indexmap.insert("system time".to_string(), system_time); indexmap.insert("system time".to_string(), system_time);
let idle_time = into_value(end.idle() - start.idle(), &tag); let idle_time = into_big_int(end.idle() - start.idle());
indexmap.insert("idle time".to_string(), idle_time); indexmap.insert("idle time".to_string(), idle_time);
let value = UntaggedValue::Row(Dictionary::from(indexmap)).into_value(&tag); benchmark_output(indexmap, output, passthrough, &tag, &mut context, &scope).await
Ok(OutputStream::one(value))
} else { } else {
Err(ShellError::untagged_runtime_error( Err(ShellError::untagged_runtime_error(
"Could not retreive CPU time", "Could not retreive CPU time",
@ -106,49 +135,68 @@ async fn benchmark(
} }
} }
#[cfg(not(feature = "rich-benchmark"))] async fn benchmark_output<T, Output>(
async fn benchmark( indexmap: IndexMap<String, BigInt>,
raw_args: CommandArgs, block_output: Output,
registry: &CommandRegistry, passthrough: Option<Block>,
) -> Result<OutputStream, ShellError> { tag: T,
let registry = registry.clone(); context: &mut EvaluationContext,
scope: &Scope,
) -> Result<OutputStream, ShellError>
where
T: Into<Tag> + Copy,
Output: Into<OutputStream>,
{
let value = UntaggedValue::Row(Dictionary::from(
indexmap
.into_iter()
.map(|(k, v)| (k, UntaggedValue::duration(v).into_value(tag)))
.collect::<IndexMap<String, Value>>(),
))
.into_value(tag);
let tag = raw_args.call_info.args.span; if let Some(time_block) = passthrough {
let mut context = EvaluationContext::from_raw(&raw_args, &registry); let benchmark_output = InputStream::one(value);
let scope = raw_args.call_info.scope.clone();
let (BenchmarkArgs { block }, input) = raw_args.process(&registry).await?;
let start_time = Instant::now(); // add autoview for an empty block
let time_block = add_implicit_autoview(time_block);
let result = run_block( let _ = run_block(
&block, &time_block,
&mut context, context,
input, benchmark_output,
&scope.it, &scope.it,
&scope.vars, &scope.vars,
&scope.env, &scope.env,
) )
.await; .await?;
let _ = result?.drain_vec().await;
let end_time = Instant::now();
context.clear_errors(); context.clear_errors();
let mut indexmap = IndexMap::with_capacity(4); Ok(block_output.into())
} else {
let real_time = into_value(end_time - start_time, &tag); let benchmark_output = OutputStream::one(value);
indexmap.insert("real time".to_string(), real_time); Ok(benchmark_output)
}
let value = UntaggedValue::Row(Dictionary::from(indexmap)).into_value(&tag);
Ok(OutputStream::one(value))
} }
fn into_value<T: TryInto<Duration>>(time: T, tag: &Span) -> Value { fn add_implicit_autoview(mut block: Block) -> Block {
UntaggedValue::duration( if block.block.is_empty() {
block.push({
let mut commands = Commands::new(block.span);
commands.push(ClassifiedCommand::Internal(InternalCommand::new(
"autoview".to_string(),
block.span,
block.span,
)));
commands
});
}
block
}
fn into_big_int<T: TryInto<Duration>>(time: T) -> BigInt {
time.try_into() time.try_into()
.unwrap_or_else(|_| Duration::new(0, 0)) .unwrap_or_else(|_| Duration::new(0, 0))
.as_nanos() .as_nanos()
.into(), .into()
)
.into_value(tag)
} }