Hi, This patch adds tracking of total execution (real) time for all commands executed in an interactive ksh instance. I find it useful as an automatic, simple poor-man's means of profiling as-i-go (and just informative). I personally have a couple ksh functions to toggle showing this info in my prompt.
It works by calling clock_gettime(2) before/after each command line is executed (encompassing pipes/&&'s/etc), using a monotonic clock. This is for interactive shells only. See the first caveat below about the impact of this. The information collected is exposed in two ways: 1. A new parameter named $NANOSECONDS, exposing the number of nanoseconds for the previous commands (0 on startup). An interactive ksh can just display this, or format it as desired (in the prompt or otherwise). 2. A new escape character supported in prompts/PS1, "\x". This sequence is expanded to a human-readable form, showing the two most significant units measured (where the units are, in decreasing order of significance: minutes, seconds, milliseconds, microseconds, nanoseconds). See caveat 2 below for comments on this. I've tested by running on my laptop and a couple home machines (all amd64) for a couple weeks now, and tested batch scripts to ensure the behavior isn't present there (this only impacts interactive shells). Caveats: 1. This type of tracking can still delay commands roughly 10 msec according to documentation and some rudimentary tests. I'd prefer using getnanotime(9), since absolute accuracy isn't required for this case, but that's not exposed outside the kernel. None the less, the impact isn't noticeable on the machines I've tested, including a 15 year old laptop. 2. The current format of "\x" is debatable, and I'm open to suggestions. Or even removing it. Exposing this info via $NANOSECONDS is sufficient for most cases, allowing a user to customize it. Any interest in adopting this? Feedback welcome. -Ryan Index: ksh.1 =================================================================== RCS file: /cvs/src/bin/ksh/ksh.1,v retrieving revision 1.201 diff -u -p -u -p -r1.201 ksh.1 --- ksh.1 18 Jun 2018 17:03:58 -0000 1.201 +++ ksh.1 27 Nov 2018 05:20:00 -0000 @@ -1499,6 +1499,13 @@ The index of the next argument to be pro Assigning 1 to this parameter causes .Ic getopts to process arguments from the beginning the next time it is invoked. +.It Ev NANOSECONDS +The number of nanoseconds spent executing the previously executed command in +an interactive shell. +This time is measured by calling +.Xr clock_gettime 2 +with a monotonic clock, before and after each command execution, and +measuring the difference. .It Ev PATH A colon separated list of directories that are searched when looking for commands and files sourced using the @@ -1581,6 +1588,13 @@ Insert a carriage return character. The name of the shell. .It Li \et The current time, in 24-hour HH:MM:SS format. +.It Li \ex +The execution time of the previous command, including the 2 most significant +units of measurement (in order: minutes (m), seconds(s), milliseconds (ms), +microseconds (µs), and nanoseconds (ns). +The parameter +.Ev NANOSECONDS +can be leveraged to customize this display. .It Li \eT The current time, in 12-hour HH:MM:SS format. .It Li \e@ Index: lex.c =================================================================== RCS file: /cvs/src/bin/ksh/lex.c,v retrieving revision 1.78 diff -u -p -u -p -r1.78 lex.c --- lex.c 15 Jan 2018 14:58:05 -0000 1.78 +++ lex.c 27 Nov 2018 05:20:00 -0000 @@ -9,6 +9,7 @@ #include <libgen.h> #include <stdio.h> #include <string.h> +#include <time.h> #include <unistd.h> #include "sh.h" @@ -102,6 +103,8 @@ char **history; /* saved commands */ char **histptr; /* last history item */ uint32_t histsize; /* history size */ +extern struct timespec cmd_start_time, cmd_end_time, cmd_exec_time; + /* optimized getsc_bn() */ #define getsc() (*source->str != '\0' && *source->str != '\\' \ && !backslash_skip ? *source->str++ : getsc_bn()) @@ -1373,6 +1376,9 @@ dopprompt(const char *sp, int ntruncate, } else strlcpy(strbuf, basename(p), sizeof strbuf); break; + case 'x': /* '\' 'x' execution time */ + expand_execution_time(strbuf, sizeof strbuf); + break; case '!': /* '\' '!' history line number */ snprintf(strbuf, sizeof strbuf, "%d", source->line + 1); @@ -1665,4 +1671,25 @@ pop_state_(State_info *si, Lex_state *ol afree(old_base, ATEMP); return si->base + STATE_BSIZE - 1; +} + +void +expand_execution_time(char *buf, int bufsz) +{ + long nanos = cmd_exec_time.tv_nsec % 1000; + long micros = cmd_exec_time.tv_nsec / 1000 % 1000; + long millis = cmd_exec_time.tv_nsec / 1000000; + time_t seconds = cmd_exec_time.tv_sec % 60; + time_t minutes = cmd_exec_time.tv_sec / 60; + + if (minutes > 0) + snprintf(buf, bufsz, "%llum %llus", minutes, seconds); + else if (seconds > 0) + snprintf(buf, bufsz, "%llus %ldms", seconds, millis); + else if (millis > 0) + snprintf(buf, bufsz, "%ldms %ldµs", millis, micros); + else if (micros > 0) + snprintf(buf, bufsz, "%ldµs %ldns", micros, nanos); + else + snprintf(buf, bufsz, "%ldns", nanos); } Index: lex.h =================================================================== RCS file: /cvs/src/bin/ksh/lex.h,v retrieving revision 1.21 diff -u -p -u -p -r1.21 lex.h --- lex.h 15 Jan 2018 14:58:05 -0000 1.21 +++ lex.h 27 Nov 2018 05:20:00 -0000 @@ -117,3 +117,4 @@ void yyerror(const char *, ...) Source * pushs(int, Area *); void set_prompt(int); void pprompt(const char *, int); +void expand_execution_time(char *, int); Index: main.c =================================================================== RCS file: /cvs/src/bin/ksh/main.c,v retrieving revision 1.96 diff -u -p -u -p -r1.96 main.c --- main.c 20 Nov 2018 07:02:23 -0000 1.96 +++ main.c 27 Nov 2018 05:20:00 -0000 @@ -13,6 +13,7 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <time.h> #include <unistd.h> #include "sh.h" @@ -71,6 +72,8 @@ int current_wd_size; int x_cols = 80; +struct timespec cmd_start_time, cmd_end_time, cmd_exec_time; + /* * shell initialization */ @@ -548,8 +551,16 @@ shell(Source *volatile s, volatile int t int i; newenv(E_PARSE); - if (interactive) + if (interactive) { really_exit = 0; + + /* Initialize command execution timers to all zero */ + timespecclear(&cmd_start_time); + timespecclear(&cmd_end_time); + timespecclear(&cmd_exec_time); + setint(global("NANOSECONDS"), 0); + } + i = sigsetjmp(genv->jbuf, 0); if (i) { switch (i) { @@ -631,8 +642,27 @@ shell(Source *volatile s, volatile int t } } - if (t && (!Flag(FNOEXEC) || (s->flags & SF_TTY))) + if (t && (!Flag(FNOEXEC) || (s->flags & SF_TTY))) { + if (interactive) + clock_gettime(CLOCK_MONOTONIC, &cmd_start_time); + exstat = execute(t, 0, NULL); + + if (interactive) { + /* + * Calculate the time spent executing the + * command and updat ehte NANOSECONDS parameter + * accordingly. Note rollover could happen if + * the command took > 292 years to execute. + */ + clock_gettime(CLOCK_MONOTONIC, &cmd_end_time); + timespecsub(&cmd_end_time, &cmd_start_time, + &cmd_exec_time); + setint(global("NANOSECONDS"), + cmd_exec_time.tv_sec * 1e9 + + cmd_exec_time.tv_nsec); + } + } if (t != NULL && t->type != TEOF && interactive && really_exit) really_exit = 0; Index: table.h =================================================================== RCS file: /cvs/src/bin/ksh/table.h,v retrieving revision 1.15 diff -u -p -u -p -r1.15 table.h --- table.h 18 Jun 2018 17:03:58 -0000 1.15 +++ table.h 27 Nov 2018 05:20:00 -0000 @@ -171,6 +171,7 @@ extern const struct builtin shbuiltins [ #define V_TMPDIR 17 #define V_LINENO 18 #define V_TERM 19 +#define V_NANOSECONDS 20 /* values for set_prompt() */ #define PS1 0 /* command */