Zsh: automatically show the duration of long-running commands
Originally published
Last modified
Sometimes I expect a command to be quick, but I type it and wait minutes or hours, then I wish I had recorded the start date of the command to know how long the command took.
Zsh has a hook that runs before a command is executed, and a hook that runs before the prompt is displayed. Using these hooks, we can determine if a command took a long time to display information about the execution duration.
I have chosen 5 seconds as the amount of time I start caring about the execution duration.
print_long_command_duration_preexec() {
_date_start=${(%):-%D{%s}}
}
[[ -z "$preexec_functions" ]] && preexec_functions=()
preexec_functions+=(print_long_command_duration_preexec)
print_long_command_duration_precmd() {
_date_end=${(%):-%D{%s}}
"$_date_end" "${_date_start:-$_date_end}"
pretty_print_date_difference unset _date_start
unset _date_end
}
[[ -z "$precmd_functions" ]] && precmd_functions=()
precmd_functions+=(print_long_command_duration_precmd)
pretty_print_date_difference() {
setopt LOCAL_OPTIONS NO_FORCE_FLOAT NO_C_PRECEDENCES &>/dev/null # Old zsh does not have FORCE_FLOAT
local date_end="$1"
local date_start="$2"
(( date_end - date_start < 5 )) && return
if ( date --version 2>&1; true; ) | grep -q -e GNU -e BusyBox; then
local date_start_iso="$(date -u -d @"$date_start" +%FT%TZ)"
local date_end_iso="$(date -u -d @"$date_end" +%FT%TZ)"
local date_start_local="$(date -d @"$date_start" '+%F %T')"
local date_end_local="$(date -d @"$date_end" '+%F %T')"
else
local date_start_iso="$(date -u -r "$date_start" +%FT%TZ)"
local date_end_iso="$(date -u -r "$date_end" +%FT%TZ)"
local date_start_local="$(date -r "$date_start" '+%F %T')"
local date_end_local="$(date -r "$date_end" '+%F %T')"
fi
local wall_time
local d=$(( (date_end - date_start) / 86400 ))
local h=$(( (date_end - date_start) % 86400 / 3600 ))
local m=$(( (date_end - date_start) % 86400 % 3600 / 60 ))
local s=$(( (date_end - date_start) % 86400 % 3600 % 60 ))
if [[ "$d" -gt 0 ]]; then
wall_time="$(printf '%dd %.02d:%.02d:%.02d' "$d" "$h" "$m" "$s")"
else
wall_time="$(printf '%.02d:%.02d:%.02d' "$h" "$m" "$s")"
fi
{
local local_time_zone="$(date +%Z)"
if [[ "$local_time_zone" = UTC ]]; then
echo "Wall time: $wall_time\tStart: $date_start_iso\tStop: $date_end_iso"
else
local wall_time_prefix="Wall time: $wall_time"
local local_time_prefix="${(l(${#wall_time_prefix})( )):-Local time ($local_time_zone)}"
echo "$wall_time_prefix\tStart: $date_start_iso\tStop: $date_end_iso"
echo "$local_time_prefix\t : $date_start_local\t : $date_end_local"
fi
} | if command -v column &>/dev/null; then column -t -s $'\t'; else cat; fi
}