]> CyberLeo.Net >> Repos - CDN/shlib.git/blob - lib/sh/stopwatch.sh
sh/stopwatch: Add 'time' verb to stopwatch, to retrieve just the times
[CDN/shlib.git] / lib / sh / stopwatch.sh
1 if [ -z "${__stopwatch_sh_loaded}" ]
2 then
3   __stopwatch_sh_loaded=yes
4
5   __stopwatch_sh_VERSION="Stopwatch v0.1"
6   __stopwatch_sh_DESCRIPTION="Stopwatch implementation supporting multiple tagged timers"
7   __stopwatch_sh_COPYRIGHT=$( cat <<"END_OF_COPYRIGHT"
8
9 Copyright (c) 2000-2012, CyberLeo
10 All rights reserved.
11
12 Redistribution and use in source and binary forms, with or without
13 modification, are permitted provided that the following conditions are met:
14
15     * Redistributions of the source code must retain the above copyright
16       notice, this list of conditions, and the following disclaimer.
17     * Redistributions in binary form must reproduce the above copyright
18       notice, this list of conditions, and the following disclaimer in the
19       documentation and/or other materials provided with the distribution.
20     * Neither the name of the organization nor the names of its contributors
21       may be used to endorse or promote products derived from this software
22       without specific prior written permission.
23
24 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
25 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
28 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
29 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
30 GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 HOWEVER CAUSED AND ON ANY THEORY OF LIABILTY, WHETHER IN CONTRACT, STRICT
32 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
33 OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34
35 END_OF_COPYRIGHT
36 )
37
38   [ "${kvs}" ] || {
39     kvs="$(mktemp -t ".stopwatch.XXXXXXXX")"
40     trap "rm -f '${kvs}'; exit" exit hup int term kill
41   }
42   want kvs
43
44   stopwatch_help() {
45     cat <<EOF >&2
46 ${__stopwatch_sh_VERSION}
47 ${__stopwatch_sh_DESCRIPTION}
48
49 ${__stopwatch_sh_COPYRIGHT}
50
51 Usage: stopwatch <tag> <start|lap|stop|reset>
52
53 Simple bourne shell based stopwatch
54 Requires 'kvs' and the id 'stopwatch' therein
55 so make sure you set 'kvs' before sourcing this script if necessary.
56
57 After loading, the following commands are available:
58
59 Start a stopwatch
60   stopwatch <tag> start
61
62 'Lap' a stopwatch (emit timer value without stopping)
63   stopwatch <tag> lap
64
65 Stop a stopwatch; can be resumed later with 'start'
66   stopwatch <tag> stop
67
68 Reset a stopwatch back to zero
69   stopwatch <tag> reset
70
71 Return the bare time, and delta (if any) since last start
72   stopwatch <tag> time
73
74 Internal functions available:
75
76 stopwatch_humany_duration <usec>
77   Transform an integer number of microseconds into a human-readable string
78   (Ex: 3d14h10m3.223s or 7m10.013s )
79
80 stopwatch_set_message_callback <command>
81   Register a callback to format event messages emitted when a stopwatch is
82   commanded. The callback will receive the following parameters:
83     <command> - one of: starting, started, lap, get, stopping, stopped, reset
84     <tag>     - stopwatch name
85     <time>    - the current time on the stopwatch, if any
86     <delta>   - the time since the last change or inquiry (for lap and stopping)
87   The commands are as follows:
88     starting  - called start while stopped
89     started   - called start while running
90     lap       - called lap while running
91     get       - called lap while stopped
92     stopping  - called stop while running
93     stopped   - called stop while stopped
94     reset     - called reset
95     time      - called to return only the formatted time strings
96
97 EOF
98     kill -ABRT $$
99   }
100
101   case "$(uname -s)" in
102   Linux)
103     _stopwatch_usec() {
104       echo $(( $(date +%s%N) / 1000 ))
105     }
106     ;;
107   FreeBSD)
108     _stopwatch_usec() {
109       "${_root}/lib/sh/usec" || echo $(( $(date +%s) * 1000000 ))
110     }
111     ;;
112   *)
113     echo "Unsupported platform: $(uname -s)" >&2
114     exit 1
115     ;;
116   esac
117
118   # Format stopwatch event messages (Can be overridden; see
119   # 'stopwatch_set_message_callback')
120   _stopwatch_message_callback() {
121     command="${1}"
122     tag="${2}"
123     time="${3:+$(stopwatch_humany_duration ${3})}"
124     delta="${4:+$(stopwatch_humany_duration ${4})}"
125     case "${command}" in
126     starting)  printf "Stopwatch: '%s' starts%s.\n" "${tag}" "${time:+ at ${time}}";;
127     started)   printf "Stopwatch: '%s' is already running.\n" "${tag}" ;;
128     lap)       printf "Stopwatch: '%s' is running: %s%s\n" "${tag}" "${time}" "${delta:+ (${delta} since last lap)}" ;;
129     get)       printf "Stopwatch: '%s' is stopped: %s\n" "${tag}" "${time}" ;;
130     stopping)  printf "Stopwatch: '%s' stops at %s%s.\n" "${tag}" "${time}" "${delta:+ (${delta} since last start)}" ;;
131     stopped)   printf "Stopwatch: '%s' is stopped%s.\n" "${tag}" "${time:+ at ${time}}" ;;
132     reset)     printf "Stopwatch: '%s' is reset to zero.\n" "${tag}" ;;
133     time)      printf "%s %s\n" "${time}" "${delta}" ;;
134     *)         printf "Stopwatch: No message for command %s\n" "${command}" ;;
135     esac
136   }
137
138   # Set the message callback
139   stopwatch_set_message_callback() {
140     _stopwatch_registered_message_callback="${1:-_stopwatch_message_callback}"
141     kvs_set stopwatch message_callback "${_stopwatch_registered_message_callback}"
142   }
143
144   # Fetch the message callback; default if unset
145   stopwatch_get_message_callback() {
146     [ "${_stopwatch_registered_message_callback}" ] ||
147       _stopwatch_registered_message_callback="$(kvs_get stopwatch messsage_callback)"
148     if [ "${_stopwatch_registered_message_callback}" ]
149     then
150       echo "${_stopwatch_registered_message_callback}"
151     else
152       _stopwatch_registered_message_callback=_stopwatch_message_callback
153       echo _stopwatch_message_callback
154     fi
155   }
156
157   # Try and format a passed integer number of seconds into something more
158   # easily parseable by a human (like 6m10s or something)
159   stopwatch_humany_duration() {
160     local usec secs mins hurs days
161     [ "${1}" -a "$(echo "${1}" | tr -Cd '0-9')" = "${1}" ] || return 255
162     usec=$(( ${1} % 1000000 ))
163     secs=$(( ${1} / 1000000 ))
164     mins=0
165     hurs=0
166     [ "${secs}" -lt 60 ] || {
167       mins=$(( ${secs} / 60 ))
168       secs=$(( ${secs} % 60 ))
169     }
170     [ "${mins}" -lt 60 ] || {
171       hurs=$(( ${mins} / 60 ))
172       mins=$(( ${mins} % 60 ))
173     }
174     [ "${mins}" -gt 0 ] || mins=""
175     [ "${hurs}" -lt 24 ] || {
176       days=$(( ${hurs} / 24 ))
177       hurs=$(( ${hurs} % 24 ))
178     }
179     [ "${hurs}" -gt 0 ] || hurs=""
180
181     printf "%s%s%s%s.%03ds" "${days:+${days}d}" "${hurs:+${hurs}h}" "${mins:+${mins}m}" "${secs}" "$(( ${usec} / 1000 ))"
182   }
183
184   # Start a named stopwatch, but do nothing if the named stopwatch is already
185   # running.
186   stopwatch_start() {
187     name="${1:-stopwatch}"
188     [ "${nao}" ] || nao="$(_stopwatch_usec)"
189     # Is the stopwatch running?
190     if kvs_has_key stopwatch "${name}"
191     then
192       eval "$(stopwatch_get_message_callback) started ${name}"
193     else
194       # start the stopwatch
195       accum="$(kvs_get stopwatch "${name}_accumulator")"
196       kvs_set stopwatch "${name}" "${nao}"
197       [ "${accum}" ] && kvs_set stopwatch "${name}_laptime" "${nao}"
198       eval "$(stopwatch_get_message_callback) starting ${name} ${accum}"
199     fi
200   }
201
202   # 'Lap' the named stopwatch: print out a line indicating how long the named
203   # stopwatch has been running, but do not stop nor reset the stopwatch.
204   stopwatch_lap() {
205     name="${1:-stopwatch}"
206     [ "${nao}" ] || nao="$(_stopwatch_usec)"
207     # Is the stopwatch running?
208     if kvs_has_key stopwatch "${name}"
209     then
210       # emit a line indicating how long it has been running
211       start="$(kvs_get stopwatch "${name}")"
212       accum="$(kvs_get stopwatch "${name}_accumulator")"
213       laptm="$(kvs_get stopwatch "${name}_laptime")"
214       delta="$(( ${nao} - ${start} ))"
215       [ "${laptm}" ] && laptm="$(( ${nao} - ${laptm} ))"
216       [ "${accum}" ] && delta="$(( ${delta} + ${accum} ))"
217       kvs_set stopwatch "${name}_laptime" "${nao}"
218       eval "$(stopwatch_get_message_callback) lap ${name} ${delta} ${laptm}"
219     else
220       accum="$(kvs_get stopwatch "${name}_accumulator")"
221       eval "$(stopwatch_get_message_callback) get ${name} ${accum}"
222     fi
223   }
224
225   # Stop the named stopwatch and print out a line indicating how long it had
226   # been running up until that point; do not reset the stopwatch.
227   stopwatch_stop() {
228     name="${1:-stopwatch}"
229     [ "${nao}" ] || nao="$(_stopwatch_usec)"
230     if kvs_has_key stopwatch "${name}"
231     then
232       start="$(kvs_get stopwatch "${name}")"
233       accum="$(kvs_get stopwatch "${name}_accumulator")"
234       delta="$(( ${nao} - ${start} ))"
235       diffs="${delta}"
236       [ "${accum}" ] && delta="$(( ${delta} + ${accum} ))"
237       kvs_set stopwatch "${name}_accumulator" "${delta}"
238       kvs_unset stopwatch "${name}"
239       kvs_unset stopwatch "${name}_laptime"
240       eval "$(stopwatch_get_message_callback) stopping ${name} ${delta} ${diffs}"
241     else
242       accum="$(kvs_get stopwatch "${name}_accumulator")"
243       eval "$(stopwatch_get_message_callback) stopped ${name} ${accum}"
244     fi
245   }
246
247   # Reset the named stopwatch back to zero.
248   stopwatch_reset() {
249     name="${1:-stopwatch}"
250     [ "${nao}" ] || nao="$(_stopwatch_usec)"
251     kvs_unset stopwatch "${name}"
252     kvs_unset stopwatch "${name}_accumulator"
253     kvs_unset stopwatch "${name}_laptime"
254     eval "$(stopwatch_get_message_callback) reset ${name}"
255   }
256
257   # Emit the current time and delta
258   stopwatch_time() {
259     name="${1:-stopwatch}"
260     [ "${nao}" ] || nao="$(_stopwatch_usec)"
261     if kvs_has_key stopwatch "${name}"
262     then
263       # It's running
264       start="$(kvs_get stopwatch "${name}")"
265       accum="$(kvs_get stopwatch "${name}_accumulator")"
266       delta="$(( ${nao} - ${start} ))"
267       diffs="${delta}"
268       [ "${accum}" ] && delta="$(( ${delta} + ${accum} ))"
269       eval "$(stopwatch_get_message_callback) time ${name} ${delta} ${diffs}"
270     elif kvs_has_key stopwatch "${name}_accumulator"
271     then
272       # It's stopped
273       accum="$(kvs_get stopwatch "${name}_accumulator")"
274       eval "$(stopwatch_get_message_callback) time ${name} ${accum} 0"
275     else
276       # It's nonexistent
277       eval "$(stopwatch_get_message_callback) time ${name} 0 0"
278     fi
279   }
280
281   stopwatch() {
282     [ "${1}" -a "${2}" ] || stopwatch_help;
283     nao="$(_stopwatch_usec)"
284     case "${2}" in
285     [Ss][Tt][Aa][Rr][Tt])  stopwatch_start "${1}"  ;;
286     [Ll][Aa][Pp])          stopwatch_lap "${1}"    ;;
287     [Ss][Tt][Oo][Pp])      stopwatch_stop "${1}"   ;;
288     [Rr][Ee][Ss][Ee][Tt])  stopwatch_reset "${1}"  ;;
289     [Tt][Ii][Mm][Ee])      stopwatch_time "${1}"   ;;
290     *)                     stopwatch_help          ;;
291     esac
292   }
293 fi