From b9ab7a509544ef968e094dac79a0733da9d56095 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Mon, 23 Aug 2021 18:59:46 +0200 Subject: [PATCH] Shell: Support `time -n ` You can now specify a number of iterations when timing a command. The default value is 1 and behaves exactly as before. If the iteration count is greater than 1, the command will be executed that many times, and then you get a little timing report afterwards with the average runtime per iteration, and also the average runtime excluding the very first iteration. (Excluding the first iteration is useful when it's slowed down by cold caches, etc.) This is something I've been doing manually forever (running `time foo` and then eyeballing the results to headmath an average) and this makes that whole process so much nicer. :^) --- Userland/Shell/Builtin.cpp | 46 +++++++++++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/Userland/Shell/Builtin.cpp b/Userland/Shell/Builtin.cpp index bb290e79620..d0e7417769c 100644 --- a/Userland/Shell/Builtin.cpp +++ b/Userland/Shell/Builtin.cpp @@ -906,27 +906,61 @@ int Shell::builtin_time(int argc, const char** argv) { Vector args; + int number_of_iterations = 1; + Core::ArgsParser parser; + parser.add_option(number_of_iterations, "Number of iterations", "iterations", 'n', "iterations"); parser.set_stop_on_first_non_option(true); parser.add_positional_argument(args, "Command to execute with arguments", "command", Core::ArgsParser::Required::Yes); if (!parser.parse(argc, const_cast(argv), Core::ArgsParser::FailureBehavior::PrintUsage)) return 1; + if (number_of_iterations < 1) + return 1; + AST::Command command; for (auto& arg : args) command.argv.append(arg); auto commands = expand_aliases({ move(command) }); - Core::ElapsedTimer timer; + Vector iteration_times; + int exit_code = 1; - timer.start(); - for (auto& job : run_commands(commands)) { - block_on_job(job); - exit_code = job.exit_code(); + for (int i = 0; i < number_of_iterations; ++i) { + Core::ElapsedTimer timer; + timer.start(); + for (auto& job : run_commands(commands)) { + block_on_job(job); + exit_code = job.exit_code(); + } + iteration_times.append(timer.elapsed()); } - warnln("Time: {} ms", timer.elapsed()); + + if (number_of_iterations == 1) { + warnln("Time: {} ms", iteration_times.first()); + } else { + float total_time = 0; + for (auto time : iteration_times) + total_time += static_cast(time); + float average = total_time / iteration_times.size(); + + float total_time_excluding_first = 0; + for (size_t i = 1; i < iteration_times.size(); ++i) + total_time_excluding_first += static_cast(iteration_times[i]); + float average_excluding_first = total_time_excluding_first / (iteration_times.size() - 1); + + warnln("Timing report:"); + warnln("=============="); + warn("Command: "); + for (auto& string : args) + warn("{} ", string); + warnln(""); + warnln("Average time: {} ms", average); + warnln("Excluding first: {} ms", average_excluding_first); + } + return exit_code; }