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 */