diff --git a/clock.opam b/clock.opam index d3f7290a9b..a8e845192e 100644 --- a/clock.opam +++ b/clock.opam @@ -11,6 +11,7 @@ depends: [ "ocaml" {>= "4.12"} "alcotest" {with-test} "astring" + "mtime" "ptime" "odoc" {with-doc} ] diff --git a/dune-project b/dune-project index 801187517e..3db17adf76 100644 --- a/dune-project +++ b/dune-project @@ -24,6 +24,7 @@ (ocaml (>= 4.12)) (alcotest :with-test) astring + mtime ptime ) ) diff --git a/ocaml/libs/clock/date.ml b/ocaml/libs/clock/date.ml index f916a2a99c..d5efa2dfbf 100644 --- a/ocaml/libs/clock/date.ml +++ b/ocaml/libs/clock/date.ml @@ -8,7 +8,7 @@ but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - *) +*) let months = [| diff --git a/ocaml/libs/clock/dune b/ocaml/libs/clock/dune index 416ec3f586..60676e0a76 100644 --- a/ocaml/libs/clock/dune +++ b/ocaml/libs/clock/dune @@ -1,9 +1,11 @@ (library (name clock) (public_name clock) - (modules date) + (modules date timer) (libraries astring + mtime + mtime.clock.os ptime ptime.clock.os ) diff --git a/ocaml/libs/clock/timer.ml b/ocaml/libs/clock/timer.ml new file mode 100644 index 0000000000..5288ca3037 --- /dev/null +++ b/ocaml/libs/clock/timer.ml @@ -0,0 +1,53 @@ +type t = {start: Ptime.t; elapsed: Mtime_clock.counter; timeout: Mtime.Span.t} + +type remaining = Spare of Mtime.Span.t | Excess of Mtime.Span.t + +let is_shorter a ~than:b = Mtime.Span.compare a b < 0 + +let is_longer a ~than:b = Mtime.Span.compare a b > 0 + +let start ~timeout = + {start= Ptime_clock.now (); elapsed= Mtime_clock.counter (); timeout} + +let timeout {timeout; _} = timeout + +let elapsed t = Mtime_clock.count t.elapsed + +let remaining t = + let elapsed = Mtime_clock.count t.elapsed in + let difference = Mtime.Span.abs_diff t.timeout elapsed in + if Mtime.Span.compare t.timeout elapsed > 0 then + Spare difference + else + Excess difference + +let expired t = match remaining t with Spare _ -> false | Excess _ -> true + +let deadline_of t = + Mtime.Span.to_uint64_ns t.timeout + |> Int64.to_float + |> Ptime.Span.of_float_s + |> Option.get + |> Ptime.(Span.add Ptime.(to_span t.start)) + |> Ptime.Span.to_float_s + +let shorten_by dur t = + let timeout = + if is_longer dur ~than:t.timeout then + Mtime.Span.zero + else + Mtime.Span.abs_diff dur t.timeout + in + {t with timeout} + +let extend_by dur t = + let timeout = Mtime.Span.add dur t.timeout in + {t with timeout} + +(* Conversion functions *) +let span_to_s span = + Mtime.Span.to_uint64_ns span |> Int64.to_float |> fun ns -> ns /. 1e9 + +let s_to_span s = + let micros_of = Float.to_int (s *. 1_000_000.) in + Mtime.Span.(micros_of * us) diff --git a/ocaml/libs/clock/timer.mli b/ocaml/libs/clock/timer.mli new file mode 100644 index 0000000000..f442148270 --- /dev/null +++ b/ocaml/libs/clock/timer.mli @@ -0,0 +1,59 @@ +(** This module is useful for knowing that a set amount of time has passed + since a particular moment in time. For example, to know when pasta is + cooked al dente. *) +type t + +type remaining = Spare of Mtime.Span.t | Excess of Mtime.Span.t + +val start : timeout:Mtime.Span.t -> t +(** [start ~timeout] starts a timer that expires after [timeout] has passed. + The wait is done in monotonic time, not in POSIX time. *) + +val timeout : t -> Mtime.Span.t +(** [timeout timer] returns the amount of time after which the timer expires, + from the moment it was started. *) + +val expired : t -> bool +(** [expired timer] returns whether [timer] has reached the timeout it was + set to. *) + +val elapsed : t -> Mtime.Span.t +(** [elapsed timer] returns the amount of time elapsed since [timer] was + started. *) + +val remaining : t -> remaining +(** [remaining timer] returns the amount of spare time is left until it + expires, or the amount of excess time since it expired. *) + +val deadline_of : t -> float +(** [deadline_of timer] returns the posix timestamp when the timer expires. + This is an approximation as the timer doesn't take leap seconds into + account when waiting. The use of this function is discouraged and it's + only provided for backwards-compatible reasons. *) + +val shorten_by : Mtime.Span.t -> t -> t +(** [shorten_by amount timer] creates a new timer with the timeout of [timer] + shortened by [amount]. The starting time doesn't change. *) + +val extend_by : Mtime.Span.t -> t -> t +(** [extend_by amount timer] creates a new timer with the timeout of [timer] + delayed by [amount]. The starting time doesn't change. *) + +(** Mtime.Span helpers *) + +val is_shorter : Mtime.Span.t -> than:Mtime.Span.t -> bool +(** [is_shorter dur ~than] returns whether [dur] lasts less than [than]. *) + +val is_longer : Mtime.Span.t -> than:Mtime.Span.t -> bool +(** [is_longer dur ~than] returns whether [dur] lasts more than [than]. *) + +val span_to_s : Mtime.Span.t -> float +(** [span_to_s span] converts a time span into seconds, represented by a float. + When the span is longer than ~54 years it becomes unprecise, avoid whenever + possible, this is unavoidable when using Thread.wait functions and related. + *) + +val s_to_span : float -> Mtime.Span.t +(** [s_to_span] convert a float representing seconds to a timespan, retains + microsecond precision. Avoid whenever possible, some RPC function already + use this so it needs to be available. *)