1
0
Fork 0
mirror of git://git.code.sf.net/p/zsh/code synced 2024-12-28 16:15:02 +01:00

53257: use monotonic clock where appropriate

update the following features to use the monotonic clock for calculating time
deltas and intervals:

* MAILCHECK parameter
* PERIOD parameter
* SECONDS parameter
* %(nS.t.f) prompt-expansion sequence
* time built-in's elapsed time and cpu % values
* zsh/zftp ZFTP_TMOUT parameter
* zsh/zprof timings

also use CLOCK_MONOTONIC_RAW instead of CLOCK_MONOTONIC on macOS
This commit is contained in:
dana 2024-12-26 09:36:45 -06:00
parent 7798fd88ac
commit 6bb792dba8
18 changed files with 225 additions and 92 deletions

View file

@ -1,3 +1,12 @@
2024-12-26 dana <dana@dana.is>
* 53257: Doc/Zsh/params.yo, Doc/Zsh/prompt.yo,
Src/Modules/zftp.c, Src/Modules/zprof.c, Src/compat.c,
Src/exec.c, Src/hist.c, Src/init.c, Src/jobs.c, Src/params.c,
Src/prompt.c, Src/signals.c, Src/utils.c, Src/zsh.h,
Test/A08time.ztst, Test/D01prompt.ztst, Test/D04parameter.ztst:
use monotonic clock where appropriate
2024-12-16 dana <dana@dana.is> 2024-12-16 dana <dana@dana.is>
* 53251: Completion/Unix/Command/_man: fix page completion on * 53251: Completion/Unix/Command/_man: fix page completion on

View file

@ -926,7 +926,9 @@ referenced or seeded in the parent shell in between subshell invocations.
) )
vindex(SECONDS) vindex(SECONDS)
item(tt(SECONDS) <S>)( item(tt(SECONDS) <S>)(
The number of seconds since shell invocation. If this parameter The number of seconds since shell invocation. On most platforms, this
is a monotonic value, so it is not affected by NTP time jumps or other
clock changes (though it may be affected by slewing). If this parameter
is assigned a value, then the value returned upon reference is assigned a value, then the value returned upon reference
will be the value that was assigned plus the number of seconds will be the value that was assigned plus the number of seconds
since the assignment. since the assignment.
@ -936,8 +938,10 @@ be changed using the tt(typeset) command. The type may be changed only
to one of the floating point types or back to integer. For example, to one of the floating point types or back to integer. For example,
`tt(typeset -F SECONDS)' `tt(typeset -F SECONDS)'
causes the value to be reported as a floating point number. The causes the value to be reported as a floating point number. The
value is available to microsecond accuracy, although the shell may value is nominally available to nanosecond precision, although this
show more or fewer digits depending on the use of tt(typeset). See varies by platform (and probably isn't accurate to 1 ns regardless),
and the shell may show more or fewer digits depending on the
use of tt(typeset). See
the documentation for the builtin tt(typeset) in the documentation for the builtin tt(typeset) in
ifzman(zmanref(zshbuiltins))\ ifzman(zmanref(zshbuiltins))\
ifnzman(noderef(Shell Builtin Commands)) for more details. ifnzman(noderef(Shell Builtin Commands)) for more details.
@ -1735,8 +1739,12 @@ A star may be inserted between the percent sign and flags printing time
(e.g., `tt(%*E)'); this causes the time to be printed in (e.g., `tt(%*E)'); this causes the time to be printed in
`var(hh)tt(:)var(mm)tt(:)var(ss)tt(.)var(ttt)' `var(hh)tt(:)var(mm)tt(:)var(ss)tt(.)var(ttt)'
format (hours and minutes are only printed if they are not zero). format (hours and minutes are only printed if they are not zero).
Alternatively, `tt(m)' or `tt(u)' may be used (e.g., `tt(%mE)') to produce Alternatively, `tt(m)', `tt(u)', or `tt(n)' may be used (e.g.,
time output in milliseconds or microseconds, respectively. `tt(%mE)') to produce time output in milliseconds, microseconds, or
nanoseconds, respectively. Note that some timings on some platforms
are not actually nanosecond-precise (nor accurate to 1 ns when
they are); in fact on many systems user and kernel times are not
even microsecond-precise.
) )
vindex(TMOUT) vindex(TMOUT)
item(tt(TMOUT))( item(tt(TMOUT))(

View file

@ -195,7 +195,8 @@ sitem(tt(%K))(the hour of the day on the 24-hour clock)
sitem(tt(%L))(the hour of the day on the 12-hour clock) sitem(tt(%L))(the hour of the day on the 12-hour clock)
endsitem() endsitem()
In addition, if the system supports the POSIX tt(gettimeofday) system In addition, if the system supports the POSIX tt(clock_gettime)
or tt(gettimeofday) system
call, tt(%.) provides decimal fractions of a second since the epoch with call, tt(%.) provides decimal fractions of a second since the epoch with
leading zeroes. By default three decimal places are provided, but a leading zeroes. By default three decimal places are provided, but a
number of digits up to 9 may be given following the tt(%); hence tt(%6.) number of digits up to 9 may be given following the tt(%); hence tt(%6.)

View file

@ -397,7 +397,7 @@ zfalarm(int tmout)
signal(SIGALRM, zfhandler); signal(SIGALRM, zfhandler);
oalremain = alarm(tmout); oalremain = alarm(tmout);
if (oalremain) if (oalremain)
oaltime = time(NULL); oaltime = zmonotime(NULL);
/* /*
* We'll leave sigtrapped, sigfuncs and TRAPXXX as they are; since the * We'll leave sigtrapped, sigfuncs and TRAPXXX as they are; since the
* shell's handler doesn't get the signal, they don't matter. * shell's handler doesn't get the signal, they don't matter.
@ -431,7 +431,7 @@ zfunalarm(void)
* I love the way alarm() uses unsigned int while time_t * I love the way alarm() uses unsigned int while time_t
* is probably something completely different. * is probably something completely different.
*/ */
unsigned int tdiff = time(NULL) - oaltime; time_t tdiff = zmonotime(NULL) - oaltime;
alarm(oalremain < tdiff ? 1 : oalremain - tdiff); alarm(oalremain < tdiff ? 1 : oalremain - tdiff);
} else } else
alarm(0); alarm(0);

View file

@ -239,8 +239,7 @@ zprof_wrapper(Eprog prog, FuncWrap w, char *name)
struct sfunc sf, *sp; struct sfunc sf, *sp;
Pfunc f = NULL; Pfunc f = NULL;
Parc a = NULL; Parc a = NULL;
struct timeval tv; struct timespec ts;
struct timezone dummy;
double prev = 0, now; double prev = 0, now;
char *name_for_lookups; char *name_for_lookups;
@ -278,19 +277,19 @@ zprof_wrapper(Eprog prog, FuncWrap w, char *name)
stack = &sf; stack = &sf;
f->calls++; f->calls++;
tv.tv_sec = tv.tv_usec = 0; ts.tv_sec = ts.tv_nsec = 0;
gettimeofday(&tv, &dummy); zgettime_monotonic_if_available(&ts);
sf.beg = prev = ((((double) tv.tv_sec) * 1000.0) + sf.beg = prev = ((((double) ts.tv_sec) * 1000.0) +
(((double) tv.tv_usec) / 1000.0)); (((double) ts.tv_nsec) / 1000000.0));
} }
runshfunc(prog, w, name); runshfunc(prog, w, name);
if (active) { if (active) {
if (zprof_module && !(zprof_module->node.flags & MOD_UNLOAD)) { if (zprof_module && !(zprof_module->node.flags & MOD_UNLOAD)) {
tv.tv_sec = tv.tv_usec = 0; ts.tv_sec = ts.tv_nsec = 0;
gettimeofday(&tv, &dummy); zgettime_monotonic_if_available(&ts);
now = ((((double) tv.tv_sec) * 1000.0) + now = ((((double) ts.tv_sec) * 1000.0) +
(((double) tv.tv_usec) / 1000.0)); (((double) ts.tv_nsec) / 1000000.0));
f->self += now - sf.beg; f->self += now - sf.beg;
for (sp = sf.prev; sp && sp->p != f; sp = sp->prev); for (sp = sf.prev; sp && sp->p != f; sp = sp->prev);
if (!sp) if (!sp)

View file

@ -136,7 +136,19 @@ zgettime_monotonic_if_available(struct timespec *ts)
#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC) #if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
struct timespec dts; struct timespec dts;
/*
* On at least some versions of macOS it appears that CLOCK_MONOTONIC is not
* actually monotonic -- there are reports that it can go backwards.
* CLOCK_MONOTONIC_RAW does not have this problem. On top of that, it is faster
* to read and it has nanosecond precision. We could use it on other systems
* too, but on Linux at least it seems that CLOCK_MONOTONIC is preferred
*/
#if defined(__APPLE__) && defined(CLOCK_MONOTONIC_RAW)
if (clock_gettime(CLOCK_MONOTONIC_RAW, &dts) < 0) {
#else
if (clock_gettime(CLOCK_MONOTONIC, &dts) < 0) { if (clock_gettime(CLOCK_MONOTONIC, &dts) < 0) {
#endif
zwarn("unable to retrieve CLOCK_MONOTONIC time: %e", errno); zwarn("unable to retrieve CLOCK_MONOTONIC time: %e", errno);
ret--; ret--;
} else { } else {

View file

@ -348,10 +348,9 @@ setlimits(char *nam)
/**/ /**/
static pid_t static pid_t
zfork(struct timeval *tv) zfork(struct timespec *ts)
{ {
pid_t pid; pid_t pid;
struct timezone dummy_tz;
/* /*
* Is anybody willing to explain this test? * Is anybody willing to explain this test?
@ -360,8 +359,8 @@ zfork(struct timeval *tv)
zerr("job table full"); zerr("job table full");
return -1; return -1;
} }
if (tv) if (ts)
gettimeofday(tv, &dummy_tz); zgettime_monotonic_if_available(ts);
/* /*
* Queueing signals is necessary on Linux because fork() * Queueing signals is necessary on Linux because fork()
* manipulates mutexes, leading to deadlock in memory * manipulates mutexes, leading to deadlock in memory
@ -460,7 +459,7 @@ zfork(struct timeval *tv)
int list_pipe = 0, simple_pline = 0; int list_pipe = 0, simple_pline = 0;
static pid_t list_pipe_pid; static pid_t list_pipe_pid;
static struct timeval list_pipe_start; static struct timespec list_pipe_start;
static int nowait, pline_level = 0; static int nowait, pline_level = 0;
static int list_pipe_child = 0, list_pipe_job; static int list_pipe_child = 0, list_pipe_job;
static char list_pipe_text[JOBTEXTSIZE]; static char list_pipe_text[JOBTEXTSIZE];
@ -1863,7 +1862,7 @@ execpline(Estate state, wordcode slcode, int how, int last1)
(jobtab[list_pipe_job].stat & STAT_STOPPED)))) { (jobtab[list_pipe_job].stat & STAT_STOPPED)))) {
pid_t pid = 0; pid_t pid = 0;
int synch[2]; int synch[2];
struct timeval bgtime; struct timespec bgtime;
/* /*
* A pipeline with the shell handling the right * A pipeline with the shell handling the right
@ -2284,7 +2283,7 @@ closemn(struct multio **mfds, int fd, int type)
char buf[TCBUFSIZE]; char buf[TCBUFSIZE];
int len, i; int len, i;
pid_t pid; pid_t pid;
struct timeval bgtime; struct timespec bgtime;
/* /*
* We need to block SIGCHLD in case the process * We need to block SIGCHLD in case the process
@ -2829,7 +2828,7 @@ execcmd_fork(Estate state, int how, int type, Wordcode varspc,
pid_t pid; pid_t pid;
int synch[2], flags; int synch[2], flags;
struct entersubsh_ret esret; struct entersubsh_ret esret;
struct timeval bgtime; struct timespec bgtime;
child_block(); child_block();
esret.gleader = -1; esret.gleader = -1;
@ -2947,7 +2946,7 @@ execcmd_exec(Estate state, Execcmd_params eparams,
* for the "time" keyword * for the "time" keyword
*/ */
child_times_t shti, chti; child_times_t shti, chti;
struct timeval then; struct timespec then;
if (how & Z_TIMED) if (how & Z_TIMED)
shelltime(&shti, &chti, &then, 0); shelltime(&shti, &chti, &then, 0);
@ -5060,7 +5059,7 @@ getproc(char *cmd, char **eptr)
int out = *cmd == Inang; int out = *cmd == Inang;
char *pnam; char *pnam;
pid_t pid; pid_t pid;
struct timeval bgtime; struct timespec bgtime;
#ifndef PATH_DEV_FD #ifndef PATH_DEV_FD
int fd; int fd;
@ -5149,7 +5148,7 @@ getpipe(char *cmd, int nullexec)
Eprog prog; Eprog prog;
int pipes[2], out = *cmd == Inang; int pipes[2], out = *cmd == Inang;
pid_t pid; pid_t pid;
struct timeval bgtime; struct timespec bgtime;
char *ends; char *ends;
if (!(prog = parsecmd(cmd, &ends))) if (!(prog = parsecmd(cmd, &ends)))

View file

@ -2891,9 +2891,9 @@ flockhistfile(char *fn, int keep_trying)
/* /*
* Timeout is ten seconds. * Timeout is ten seconds.
*/ */
end_time = time(NULL) + (time_t)10; end_time = zmonotime(NULL) + (time_t)10;
while (fcntl(flock_fd, F_SETLKW, &lck) == -1) { while (fcntl(flock_fd, F_SETLKW, &lck) == -1) {
if (!keep_trying || time(NULL) >= end_time || if (!keep_trying || zmonotime(NULL) >= end_time ||
/* /*
* Randomise wait to minimise clashes with shells exiting at * Randomise wait to minimise clashes with shells exiting at
* the same time. * the same time.
@ -3137,7 +3137,7 @@ static int lockhistct;
static int static int
checklocktime(char *lockfile, long *sleep_usp, time_t then) checklocktime(char *lockfile, long *sleep_usp, time_t then)
{ {
time_t now = time(NULL); time_t now = zmonotime(NULL);
if (now + 10 < then) { if (now + 10 < then) {
/* File is more than 10 seconds in the future? */ /* File is more than 10 seconds in the future? */

View file

@ -1022,7 +1022,6 @@ setupvals(char *cmd, char *runscript, char *zsh_name)
#ifdef USE_GETPWUID #ifdef USE_GETPWUID
struct passwd *pswd; struct passwd *pswd;
#endif #endif
struct timezone dummy_tz;
char *ptr; char *ptr;
int i, j; int i, j;
#if defined(SITEFPATH_DIR) || defined(FPATH_DIR) || defined (ADDITIONAL_FPATH) || defined(FIXED_FPATH_DIR) #if defined(SITEFPATH_DIR) || defined(FPATH_DIR) || defined (ADDITIONAL_FPATH) || defined(FIXED_FPATH_DIR)
@ -1109,8 +1108,8 @@ setupvals(char *cmd, char *runscript, char *zsh_name)
hatchar = '^'; hatchar = '^';
termflags = TERM_UNKNOWN; termflags = TERM_UNKNOWN;
curjob = prevjob = coprocin = coprocout = -1; curjob = prevjob = coprocin = coprocout = -1;
gettimeofday(&shtimer, &dummy_tz); /* init $SECONDS */ zgettime_monotonic_if_available(&shtimer); /* init $SECONDS */
srand((unsigned int)(shtimer.tv_sec + shtimer.tv_usec)); /* seed $RANDOM */ srand((unsigned int)(shtimer.tv_sec + shtimer.tv_nsec)); /* seed $RANDOM */
/* Set default path */ /* Set default path */
path = (char **) zalloc(sizeof(*path) * 5); path = (char **) zalloc(sizeof(*path) * 5);
@ -1297,7 +1296,7 @@ setupvals(char *cmd, char *runscript, char *zsh_name)
#endif #endif
breaks = loops = 0; breaks = loops = 0;
lastmailcheck = time(NULL); lastmailcheck = zmonotime(NULL);
locallevel = sourcelevel = 0; locallevel = sourcelevel = 0;
sfcontext = SFC_NONE; sfcontext = SFC_NONE;
trap_return = 0; trap_return = 0;

View file

@ -136,7 +136,7 @@ int numpipestats, pipestats[MAX_PIPESTATS];
/**/ /**/
static struct timeval * static struct timeval *
dtime(struct timeval *dt, struct timeval *t1, struct timeval *t2) dtime_tv(struct timeval *dt, struct timeval *t1, struct timeval *t2)
{ {
dt->tv_sec = t2->tv_sec - t1->tv_sec; dt->tv_sec = t2->tv_sec - t1->tv_sec;
dt->tv_usec = t2->tv_usec - t1->tv_usec; dt->tv_usec = t2->tv_usec - t1->tv_usec;
@ -147,6 +147,21 @@ dtime(struct timeval *dt, struct timeval *t1, struct timeval *t2)
return dt; return dt;
} }
/* As above, but with timespecs */
/**/
static struct timespec *
dtime_ts(struct timespec *dt, struct timespec *t1, struct timespec *t2)
{
dt->tv_sec = t2->tv_sec - t1->tv_sec;
dt->tv_nsec = t2->tv_nsec - t1->tv_nsec;
if (dt->tv_nsec < 0) {
dt->tv_nsec += 1000000000.0;
dt->tv_sec -= 1.0;
}
return dt;
}
/* change job table entry from stopped to running */ /* change job table entry from stopped to running */
/**/ /**/
@ -349,7 +364,6 @@ get_usage(void)
void void
update_process(Process pn, int status) update_process(Process pn, int status)
{ {
struct timezone dummy_tz;
#ifdef HAVE_GETRUSAGE #ifdef HAVE_GETRUSAGE
struct timeval childs = child_usage.ru_stime; struct timeval childs = child_usage.ru_stime;
struct timeval childu = child_usage.ru_utime; struct timeval childu = child_usage.ru_utime;
@ -360,12 +374,12 @@ update_process(Process pn, int status)
/* get time-accounting info */ /* get time-accounting info */
get_usage(); get_usage();
gettimeofday(&pn->endtime, &dummy_tz); /* record time process exited */ zgettime_monotonic_if_available(&pn->endtime); /* record time process exited */
pn->status = status; /* save the status returned by WAIT */ pn->status = status; /* save the status returned by WAIT */
#ifdef HAVE_GETRUSAGE #ifdef HAVE_GETRUSAGE
dtime(&pn->ti.ru_stime, &childs, &child_usage.ru_stime); dtime_tv(&pn->ti.ru_stime, &childs, &child_usage.ru_stime);
dtime(&pn->ti.ru_utime, &childu, &child_usage.ru_utime); dtime_tv(&pn->ti.ru_utime, &childu, &child_usage.ru_utime);
#else #else
pn->ti.st = shtms.tms_cstime - childs; /* compute process system space time */ pn->ti.st = shtms.tms_cstime - childs; /* compute process system space time */
pn->ti.ut = shtms.tms_cutime - childu; /* compute process user space time */ pn->ti.ut = shtms.tms_cutime - childu; /* compute process user space time */
@ -753,7 +767,7 @@ printhhmmss(double secs)
} }
static void static void
printtime(struct timeval *real, child_times_t *ti, char *desc) printtime(struct timespec *real, child_times_t *ti, char *desc)
{ {
char *s; char *s;
double elapsed_time, user_time, system_time; double elapsed_time, user_time, system_time;
@ -774,21 +788,21 @@ printtime(struct timeval *real, child_times_t *ti, char *desc)
} }
/* go ahead and compute these, since almost every TIMEFMT will have them */ /* go ahead and compute these, since almost every TIMEFMT will have them */
elapsed_time = real->tv_sec + real->tv_usec / 1000000.0; elapsed_time = real->tv_sec + real->tv_nsec / 1000000000.0;
#ifdef HAVE_GETRUSAGE #ifdef HAVE_GETRUSAGE
user_time = ti->ru_utime.tv_sec + ti->ru_utime.tv_usec / 1000000.0; user_time = ti->ru_utime.tv_sec + ti->ru_utime.tv_usec / 1000000.0;
system_time = ti->ru_stime.tv_sec + ti->ru_stime.tv_usec / 1000000.0; system_time = ti->ru_stime.tv_sec + ti->ru_stime.tv_usec / 1000000.0;
total_time = user_time + system_time; total_time = user_time + system_time;
percent = 100.0 * total_time percent = 100.0 * total_time
/ (real->tv_sec + real->tv_usec / 1000000.0); / (real->tv_sec + real->tv_nsec / 1000000000.0);
#else #else
{ {
long clktck = get_clktck(); long clktck = get_clktck();
user_time = ti->ut / (double) clktck; user_time = ti->ut / (double) clktck;
system_time = ti->st / (double) clktck; system_time = ti->st / (double) clktck;
percent = 100.0 * (ti->ut + ti->st) percent = 100.0 * (ti->ut + ti->st)
/ (clktck * real->tv_sec + clktck * real->tv_usec / 1000000.0); / (clktck * real->tv_sec + clktck * real->tv_nsec / 1000000000.0);
} }
#endif #endif
@ -844,6 +858,23 @@ printtime(struct timeval *real, child_times_t *ti, char *desc)
break; break;
} }
break; break;
case 'n':
switch (*++s) {
case 'E':
fprintf(stderr, "%0.fns", elapsed_time * 1000000000.0);
break;
case 'U':
fprintf(stderr, "%0.fns", user_time * 1000000000.0);
break;
case 'S':
fprintf(stderr, "%0.fns", system_time * 1000000000.0);
break;
default:
fprintf(stderr, "%%n");
s--;
break;
}
break;
case '*': case '*':
switch (*++s) { switch (*++s) {
case 'E': case 'E':
@ -991,12 +1022,12 @@ static void
dumptime(Job jn) dumptime(Job jn)
{ {
Process pn; Process pn;
struct timeval dtimeval; struct timespec dtimespec;
if (!jn->procs) if (!jn->procs)
return; return;
for (pn = jn->procs; pn; pn = pn->next) for (pn = jn->procs; pn; pn = pn->next)
printtime(dtime(&dtimeval, &pn->bgtime, &pn->endtime), &pn->ti, printtime(dtime_ts(&dtimespec, &pn->bgtime, &pn->endtime), &pn->ti,
pn->text); pn->text);
} }
@ -1506,7 +1537,7 @@ deletejob(Job jn, int disowning)
/**/ /**/
void void
addproc(pid_t pid, char *text, int aux, struct timeval *bgtime, addproc(pid_t pid, char *text, int aux, struct timespec *bgtime,
int gleader, int list_pipe_job_used) int gleader, int list_pipe_job_used)
{ {
Process pn, *pnlist; Process pn, *pnlist;
@ -1894,16 +1925,15 @@ spawnjob(void)
/**/ /**/
void void
shelltime(child_times_t *shell, child_times_t *kids, struct timeval *then, int delta) shelltime(child_times_t *shell, child_times_t *kids, struct timespec *then, int delta)
{ {
struct timezone dummy_tz; struct timespec dtimespec, now;
struct timeval dtimeval, now;
child_times_t ti; child_times_t ti;
#ifndef HAVE_GETRUSAGE #ifndef HAVE_GETRUSAGE
struct tms buf; struct tms buf;
#endif #endif
gettimeofday(&now, &dummy_tz); zgettime_monotonic_if_available(&now);
#ifdef HAVE_GETRUSAGE #ifdef HAVE_GETRUSAGE
getrusage(RUSAGE_SELF, &ti); getrusage(RUSAGE_SELF, &ti);
@ -1916,8 +1946,8 @@ shelltime(child_times_t *shell, child_times_t *kids, struct timeval *then, int d
if (shell) { if (shell) {
if (delta) { if (delta) {
#ifdef HAVE_GETRUSAGE #ifdef HAVE_GETRUSAGE
dtime(&ti.ru_utime, &shell->ru_utime, &ti.ru_utime); dtime_tv(&ti.ru_utime, &shell->ru_utime, &ti.ru_utime);
dtime(&ti.ru_stime, &shell->ru_stime, &ti.ru_stime); dtime_tv(&ti.ru_stime, &shell->ru_stime, &ti.ru_stime);
#else #else
ti.ut -= shell->ut; ti.ut -= shell->ut;
ti.st -= shell->st; ti.st -= shell->st;
@ -1926,15 +1956,15 @@ shelltime(child_times_t *shell, child_times_t *kids, struct timeval *then, int d
*shell = ti; *shell = ti;
} }
if (delta) if (delta)
dtime(&dtimeval, then, &now); dtime_ts(&dtimespec, then, &now);
else { else {
if (then) if (then)
*then = now; *then = now;
dtime(&dtimeval, &shtimer, &now); dtime_ts(&dtimespec, &shtimer, &now);
} }
if (!delta == !shell) if (!delta == !shell)
printtime(&dtimeval, &ti, "shell"); printtime(&dtimespec, &ti, "shell");
#ifdef HAVE_GETRUSAGE #ifdef HAVE_GETRUSAGE
getrusage(RUSAGE_CHILDREN, &ti); getrusage(RUSAGE_CHILDREN, &ti);
@ -1945,8 +1975,8 @@ shelltime(child_times_t *shell, child_times_t *kids, struct timeval *then, int d
if (kids) { if (kids) {
if (delta) { if (delta) {
#ifdef HAVE_GETRUSAGE #ifdef HAVE_GETRUSAGE
dtime(&ti.ru_utime, &kids->ru_utime, &ti.ru_utime); dtime_tv(&ti.ru_utime, &kids->ru_utime, &ti.ru_utime);
dtime(&ti.ru_stime, &kids->ru_stime, &ti.ru_stime); dtime_tv(&ti.ru_stime, &kids->ru_stime, &ti.ru_stime);
#else #else
ti.ut -= shell->ut; ti.ut -= shell->ut;
ti.st -= shell->st; ti.st -= shell->st;
@ -1955,7 +1985,7 @@ shelltime(child_times_t *shell, child_times_t *kids, struct timeval *then, int d
*kids = ti; *kids = ti;
} }
if (!delta == !kids) if (!delta == !kids)
printtime(&dtimeval, &ti, "children"); printtime(&dtimespec, &ti, "children");
} }
/* see if jobs need printing */ /* see if jobs need printing */

View file

@ -137,11 +137,11 @@ unsigned char hatchar, hashchar;
unsigned char keyboardhackchar = '\0'; unsigned char keyboardhackchar = '\0';
/* $SECONDS = now.tv_sec - shtimer.tv_sec /* $SECONDS = now.tv_sec - shtimer.tv_sec
* + (now.tv_usec - shtimer.tv_usec) / 1000000.0 * + (now.tv_nsec - shtimer.tv_nsec) / 1000000000.0
* (rounded to an integer if the parameter is not set to float) */ * (rounded to an integer if the parameter is not set to float) */
/**/ /**/
struct timeval shtimer; struct timespec shtimer;
/* 0 if this $TERM setup is usable, otherwise it contains TERM_* flags */ /* 0 if this $TERM setup is usable, otherwise it contains TERM_* flags */
@ -4496,13 +4496,12 @@ randomsetfn(UNUSED(Param pm), zlong v)
zlong zlong
intsecondsgetfn(UNUSED(Param pm)) intsecondsgetfn(UNUSED(Param pm))
{ {
struct timeval now; struct timespec now;
struct timezone dummy_tz;
gettimeofday(&now, &dummy_tz); zgettime_monotonic_if_available(&now);
return (zlong)(now.tv_sec - shtimer.tv_sec - return (zlong)(now.tv_sec - shtimer.tv_sec -
(now.tv_usec < shtimer.tv_usec ? 1 : 0)); (now.tv_nsec < shtimer.tv_nsec ? 1 : 0));
} }
/* Function to set value of special parameter `SECONDS' */ /* Function to set value of special parameter `SECONDS' */
@ -4511,48 +4510,47 @@ intsecondsgetfn(UNUSED(Param pm))
void void
intsecondssetfn(UNUSED(Param pm), zlong x) intsecondssetfn(UNUSED(Param pm), zlong x)
{ {
struct timeval now; struct timespec now;
struct timezone dummy_tz;
zlong diff; zlong diff;
gettimeofday(&now, &dummy_tz); zgettime_monotonic_if_available(&now);
diff = (zlong)now.tv_sec - x; diff = (zlong)now.tv_sec - x;
shtimer.tv_sec = diff; shtimer.tv_sec = diff;
if ((zlong)shtimer.tv_sec != diff) if ((zlong)shtimer.tv_sec != diff)
zwarn("SECONDS truncated on assignment"); zwarn("SECONDS truncated on assignment");
shtimer.tv_usec = now.tv_usec; shtimer.tv_nsec = now.tv_nsec;
} }
/**/ /**/
double double
floatsecondsgetfn(UNUSED(Param pm)) floatsecondsgetfn(UNUSED(Param pm))
{ {
struct timeval now; struct timespec now;
struct timezone dummy_tz;
gettimeofday(&now, &dummy_tz); zgettime_monotonic_if_available(&now);
return (double)(now.tv_sec - shtimer.tv_sec) + return (double)(now.tv_sec - shtimer.tv_sec) +
(double)(now.tv_usec - shtimer.tv_usec) / 1000000.0; (double)(now.tv_nsec - shtimer.tv_nsec) / 1000000000.0;
} }
/**/ /**/
void void
floatsecondssetfn(UNUSED(Param pm), double x) floatsecondssetfn(UNUSED(Param pm), double x)
{ {
struct timeval now; struct timespec now;
struct timezone dummy_tz;
zgettime_monotonic_if_available(&now);
gettimeofday(&now, &dummy_tz);
shtimer.tv_sec = now.tv_sec - (zlong)x; shtimer.tv_sec = now.tv_sec - (zlong)x;
shtimer.tv_usec = now.tv_usec - (zlong)((x - (zlong)x) * 1000000.0); shtimer.tv_nsec = now.tv_nsec - (zlong)((x - (zlong)x) * 1000000000.0);
} }
/**/ /**/
double double
getrawseconds(void) getrawseconds(void)
{ {
return (double)shtimer.tv_sec + (double)shtimer.tv_usec / 1000000.0; return (double)shtimer.tv_sec + (double)shtimer.tv_nsec / 1000000000.0;
} }
/**/ /**/
@ -4560,7 +4558,7 @@ void
setrawseconds(double x) setrawseconds(double x)
{ {
shtimer.tv_sec = (zlong)x; shtimer.tv_sec = (zlong)x;
shtimer.tv_usec = (zlong)((x - (zlong)x) * 1000000.0); shtimer.tv_nsec = (zlong)((x - (zlong)x) * 1000000000.0);
} }
/**/ /**/

View file

@ -469,7 +469,7 @@ putpromptchar(int doprint, int endchar)
test = 1; test = 1;
break; break;
case 'S': case 'S':
if (time(NULL) - shtimer.tv_sec >= arg) if (zmonotime(NULL) - shtimer.tv_sec >= arg)
test = 1; test = 1;
break; break;
case 'v': case 'v':

View file

@ -342,8 +342,7 @@ wait_for_processes(void)
zwarn("job can't be suspended"); zwarn("job can't be suspended");
} else { } else {
#if defined(HAVE_WAIT3) && defined(HAVE_GETRUSAGE) #if defined(HAVE_WAIT3) && defined(HAVE_GETRUSAGE)
struct timezone dummy_tz; zgettime_monotonic_if_available(&pn->endtime);
gettimeofday(&pn->endtime, &dummy_tz);
#ifdef WIFCONTINUED #ifdef WIFCONTINUED
if (WIFCONTINUED(status)) if (WIFCONTINUED(status))
pn->status = SP_RUNNING; pn->status = SP_RUNNING;

View file

@ -1570,14 +1570,14 @@ preprompt(void)
/* If 1) the parameter PERIOD exists, 2) a hook function for * /* If 1) the parameter PERIOD exists, 2) a hook function for *
* "periodic" exists, 3) it's been greater than PERIOD since we * * "periodic" exists, 3) it's been greater than PERIOD since we *
* executed any such hook, then execute it now. */ * executed any such hook, then execute it now. */
if (period && ((zlong)time(NULL) > (zlong)lastperiodic + period) && if (period && ((zlong)zmonotime(NULL) > (zlong)lastperiodic + period) &&
!callhookfunc("periodic", NULL, 1, NULL)) !callhookfunc("periodic", NULL, 1, NULL))
lastperiodic = time(NULL); lastperiodic = zmonotime(NULL);
if (errflag) if (errflag)
return; return;
/* Check mail */ /* Check mail */
currentmailcheck = time(NULL); currentmailcheck = zmonotime(NULL);
if (mailcheck && if (mailcheck &&
(zlong) difftime(currentmailcheck, lastmailcheck) > mailcheck) { (zlong) difftime(currentmailcheck, lastmailcheck) > mailcheck) {
char *mailfile; char *mailfile;
@ -2761,6 +2761,19 @@ timespec_diff_us(const struct timespec *t1, const struct timespec *t2)
return (reverse ? LONG_MIN : LONG_MAX); return (reverse ? LONG_MIN : LONG_MAX);
} }
/* Like time(), but uses the monotonic clock */
/**/
mod_export int
zmonotime(time_t *tloc)
{
struct timespec ts;
zgettime_monotonic_if_available(&ts);
if (tloc)
*tloc = ts.tv_sec;
return ts.tv_sec;
}
/* /*
* Sleep for the given number of microseconds --- must be within * Sleep for the given number of microseconds --- must be within
* range of a long at the moment, but this is only used for * range of a long at the moment, but this is only used for
@ -2794,7 +2807,9 @@ zsleep(long us)
/** /**
* Sleep for time (fairly) randomly up to max_us microseconds. * Sleep for time (fairly) randomly up to max_us microseconds.
* Don't let the wallclock time extend beyond end_time. * Don't let the time extend beyond end_time. end_time is compared to
* the current *monotonic* clock time, so do NOT base it on e.g. time(2);
* use zmonotime() or zgettime_monotonic_if_available().
* Return 1 if that seemed to work, else 0. * Return 1 if that seemed to work, else 0.
* *
* For best results max_us should be a multiple of 2**16 or large * For best results max_us should be a multiple of 2**16 or large
@ -2806,7 +2821,7 @@ int
zsleep_random(long max_us, time_t end_time) zsleep_random(long max_us, time_t end_time)
{ {
long r; long r;
time_t now = time(NULL); time_t now = zmonotime(NULL);
/* /*
* Randomish backoff. Doesn't need to be fundamentally * Randomish backoff. Doesn't need to be fundamentally

View file

@ -1115,8 +1115,8 @@ struct process {
char text[JOBTEXTSIZE]; /* text to print when 'jobs' is run */ char text[JOBTEXTSIZE]; /* text to print when 'jobs' is run */
int status; /* return code from waitpid/wait3() */ int status; /* return code from waitpid/wait3() */
child_times_t ti; child_times_t ti;
struct timeval bgtime; /* time job was spawned */ struct timespec bgtime; /* time job was spawned */
struct timeval endtime; /* time job exited */ struct timespec endtime; /* time job exited */
}; };
struct execstack { struct execstack {

View file

@ -11,9 +11,44 @@
(time cat) >&/dev/null (time cat) >&/dev/null
0:`time' keyword (status only) 0:`time' keyword (status only)
( TIMEFMT='%E %mE %uE %* %m%mm %u%uu'; time (:) ) ( TIMEFMT='%E %mE %uE %nE %* %m%mm %u%uu %n%nn'; time (:) )
0:`time' keyword with custom TIMEFMT 0:`time' keyword with custom TIMEFMT
*?[0-9]##.[0-9](#c2)s [0-9]##ms [0-9]##us %\* %m%mm %u%uu *?[0-9]##.[0-9](#c2)s [0-9]##ms [0-9]##us [0-9]##ns %\* %m%mm %u%uu %n%nn
( TIMEFMT='x %U %S %E'; time (:) )
0:TIMEFMT %[USE] use centisecond precision
*?x( <0-9>.<00-99>s)(#c3)
( TIMEFMT='x %*U %*S %*E'; time (:) )
0:TIMEFMT %*[USE] use millisecond precision
*?x( <0-9>.<000-999>)(#c3)
( TIMEFMT='%nU %nS'; time (read -k3 -t0.1) )
1:TIMEFMT %nU and %nS are limited to microsecond precision
*?[1-9][0-9]#000ns [1-9][0-9]#000ns
# SECONDS (after - before) must be greater than the elapsed time, but not much
# greater. 25% was picked arbitrarily as something that hopefully will prevent
# the test from failing on slow machines
(
typeset -F SECONDS
TIMEFMT=%nE
a=$SECONDS
t=$( (time (read -k3 -t0.1)) 2>&1 )
b=$SECONDS
s=$(( b - a ))
t=$(( ${t%ns}.0 / 10**9 ))
echo $s $t $(( s > t )) $(( t > s - (s * 0.25) ))
)
0:`time' elapsed time matches SECONDS
*>[0-9.]## [0-9.]## 1 1
# Again, the wide range here is an attempt to prevent this test from failing on
# slow machines. We don't care about the exact time, just that it's vaguely sane
# and that each representation has the same basis
( TIMEFMT='%E %mE %uE %nE %*E'; time (read -k3 -t0.1) )
1:TIMEFMT elapsed time values
*?0.<10-50>s <10-500>ms <100000-500000>us <100000000-500000000>ns 0.<100-500>
time x=1 time x=1
0:`time' simple assignment 0:`time' simple assignment

View file

@ -68,6 +68,13 @@
print -P '%(?.true.false)' print -P '%(?.true.false)'
0:ternary prompt escapes 0:ternary prompt escapes
>true >true
>false
sec=$SECONDS
eval "print -P '%(${sec}S.true.false)'"
eval "print -P '%($((sec+30))S.true.false)'"
0:ternary prompt escape with test character S
>true
>false >false
print -P 'start %10<...<truncated at 10%<< Not truncated%3< ...<Not shown' print -P 'start %10<...<truncated at 10%<< Not truncated%3< ...<Not shown'

View file

@ -1550,6 +1550,28 @@
>1 >1
>1 >1
# Integer
a=$SECONDS
sleep 1
b=$SECONDS
print -r - $a $b $(( b > a ))
# Float
typeset -F SECONDS
a=$SECONDS
repeat 10 :
b=$SECONDS
print -r - $a $b $(( b > a ))
# Assignment
a=$SECONDS
SECONDS=8888
repeat 10 :
b=$SECONDS
print -r - $(( a < 8888 )) $(( b > 8888 ))
0:SECONDS
*>[0-9]## [0-9]## 1
*>[0-9]##.[0-9]## [0-9]##.[0-9]## 1
*>1 1
foo=("|" "?") foo=("|" "?")
[[ "|" = ${(j.|.)foo} ]] && print yes || print no [[ "|" = ${(j.|.)foo} ]] && print yes || print no
[[ "|" = ${(j.|.)~foo} ]] && print yes || print no [[ "|" = ${(j.|.)~foo} ]] && print yes || print no