(ns emmy.util.stopwatch
(:require [emmy.util :as u]
[stopwatch.core :as sw]))
(defprotocol IStopwatch
(running? [this])
(start [this])
(stop [this])
(reset [this])
(-elapsed [this unit] "Displays the current elapsed time in the supplied units.")
(repr [this] "Prints a string representation of the stopwatch."))
{:method-builders {#'emmy.util.stopwatch/running? #object[emmy.util.stopwatch$eval78752$fn__78753 0x4dddea7 "
emmy.util.stopwatch$eval78752$fn__78753@4dddea7"
]
#'emmy.util.stopwatch/reset #object[emmy.util.stopwatch$eval78752$fn__78764 0x663779ee "
emmy.util.stopwatch$eval78752$fn__78764@663779ee"
]
#'emmy.util.stopwatch/start #object[emmy.util.stopwatch$eval78752$fn__78775 0x6ce64d5c "
emmy.util.stopwatch$eval78752$fn__78775@6ce64d5c"
]
#'emmy.util.stopwatch/-elapsed #object[emmy.util.stopwatch$eval78752$fn__78786 0x28d037f3 "
emmy.util.stopwatch$eval78752$fn__78786@28d037f3"
]
#'emmy.util.stopwatch/repr #object[emmy.util.stopwatch$eval78752$fn__78799 0x4292fca8 "
emmy.util.stopwatch$eval78752$fn__78799@4292fca8"
]
#'emmy.util.stopwatch/stop #object[emmy.util.stopwatch$eval78752$fn__78810 0x1fb7051d "
emmy.util.stopwatch$eval78752$fn__78810@1fb7051d"
]
}
:method-map {:-elapsed :-elapsed :repr :repr :reset :reset :running? :running? :start :start :stop :stop} :on emmy.util.stopwatch.IStopwatch :on-interface emmy.util.stopwatch.IStopwatch :sigs {:-elapsed {:arglists ([this unit]) :col 4 :doc "
Displays the current elapsed time in the supplied units."
:end-col 12 :end-row 6 :name -elapsed :row 6 :tag nil}
:repr {:arglists ([this]) :col 4 :doc "
Prints a string representation of the stopwatch."
:end-col 8 :end-row 7 :name repr :row 7 :tag nil}
:reset {:arglists ([this]) :col 4 :doc nil :end-col 9 :end-row 5 :name reset :row 5 :tag nil} :running? {:arglists ([this]) :col 4 :doc nil :end-col 12 :end-row 2 :name running? :row 2 :tag nil} :start {:arglists ([this]) :col 4 :doc nil :end-col 9 :end-row 3 :name start :row 3 :tag nil} :stop {8 more elided}}
:var #'emmy.util.stopwatch/IStopwatch}
(defn elapsed
"Wrapper that handles a default implementation."
([sw] (-elapsed sw :nanos))
([sw unit] (-elapsed sw unit)))
#object[emmy.util.stopwatch$elapsed 0x4165371a "
emmy.util.stopwatch$elapsed@4165371a"
]
(def units
"Allowed units of time, ordered from most precise to least."
[:nanos :micros :millis :seconds :minutes :hours :days])
[:nanos :micros :millis :seconds :minutes :hours :days]
(def ^:private abbreviate
{:nanos "ns"
:micros "\u03bcs"
:millis "ms"
:seconds "s"
:minutes "min"
:hours "h"
:days "d"})
{:days "
d"
:hours "
h"
:micros "
μs"
:millis "
ms"
:minutes "
min"
:nanos "
ns"
:seconds "
s"}

Native Stopwatch Implementation

Conversions from nanoseconds. The stopwatch library stores nanos, so we only need to convert FROM nanos, ever, not between other units.

(let [->micros 1e3
->ms (* ->micros 1e3)
->s (* ->ms 1e3)
->m (* ->s 60)
->h (* ->m 60)
->d (* ->h 24)]
(defn- from-nanos
[ns unit]
(/ ns
(case unit
:nanos 1
:micros ->micros
:millis ->ms
:seconds ->s
:minutes ->m
:hours ->h
:days ->d
(u/illegal (str "Unknown unit: " unit))))))
#'emmy.util.stopwatch/from-nanos
(defn- choose-unit
"Returns a pair of [value, unit]."
[ns]
(or (->> (reverse units)
(map (juxt #(from-nanos ns %) identity))
(filter (comp #(> % 1) first))
first)
[0 :nanos]))
#object[emmy.util.stopwatch$choose_unit 0x68a3ef6a "
emmy.util.stopwatch$choose_unit@68a3ef6a"
]

Implementation of an "immutable" stopwatch (minus the must-be-mutable elapsed-fn, of course). The final stopwatch will wrap this in an atom.

(deftype Stopwatch [elapsed-fn offset is-running?]
IStopwatch
(running? [_] is-running?)
(start [this]
(if is-running?
this
(Stopwatch. (sw/start) offset true)))
(stop [this]
(if is-running?
(let [offset' (elapsed this :nanos)]
(Stopwatch. (constantly offset') offset' false))
this))
(reset [_] (Stopwatch. nil 0 false))
(-elapsed [_ unit]
(-> (if is-running?
(+ (elapsed-fn)
offset)
offset)
(from-nanos unit)))
(repr [this]
(let [[x unit] (choose-unit (elapsed this :nanos))]
(str x " " (abbreviate unit)))))
#object[emmy.util.stopwatch$eval78838$__GT_Stopwatch__78843 0x3d01296a "
emmy.util.stopwatch$eval78838$__GT_Stopwatch__78843@3d01296a"
]
(defn- wrapped
"Accepts some object implementing `IStopWatch` and returns a mutable
implementation that wraps an immutable stopwatch in an atom."
[stopwatch]
(let [sw (atom stopwatch)]
(reify IStopwatch
(running? [_] (running? @sw))
(start [this] (swap! sw start) this)
(stop [this] (swap! sw stop) this)
(reset [this] (swap! sw reset) this)
(-elapsed [_ unit] (-elapsed @sw unit))
(repr [_] (repr @sw))
Object
(toString [_] (repr @sw)))))
#object[emmy.util.stopwatch$wrapped 0x1ab94c3a "
emmy.util.stopwatch$wrapped@1ab94c3a"
]
(defn stopwatch
"Returns an implementation of [[IStopwatch]]."
[& {:keys [started?] :or {started? true}}]
(let [watch (Stopwatch. nil 0 false)]
(wrapped
(if started?
(start watch)
watch))))
#object[emmy.util.stopwatch$stopwatch 0x728c6590 "
emmy.util.stopwatch$stopwatch@728c6590"
]