diff --git a/src/Fable.Transforms/Python/Replacements.fs b/src/Fable.Transforms/Python/Replacements.fs index 5ef2df57fe..a66cc11ab5 100644 --- a/src/Fable.Transforms/Python/Replacements.fs +++ b/src/Fable.Transforms/Python/Replacements.fs @@ -784,6 +784,7 @@ let tryEntityIdent (com: Compiler) entFullName = | Types.exception_ -> makeIdentExpr "Exception" |> Some | "System.Threading.Tasks.TaskCanceledException" -> makeImportLib com Any "TaskCanceledError" "async_builder" |> Some + | "System.Diagnostics.Stopwatch" -> makeImportLib com Any "StopWatch" "diagnostics" |> Some // | "System.Collections.Generic.KeyNotFoundException" -> // makeImportLib com Any "KeyNotFoundException" "System.Collections.Generic" |> Some | BuiltinSystemException entName -> makeImportLib com Any entName "System" |> Some @@ -2990,15 +2991,20 @@ let stopwatch (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr o | ".ctor", _ -> Helper.LibCall(com, "diagnostics", "StopWatch", t, args, i.SignatureArgTypes, isConstructor = true, ?loc = r) |> Some + | "StartNew", None -> Helper.LibCall(com, "diagnostics", "start_new", t, [], ?loc = r) |> Some + | "GetTimestamp", None -> Helper.LibCall(com, "diagnostics", "get_timestamp", t, [], ?loc = r) |> Some + | "GetElapsedTime", None -> + Helper.LibCall(com, "diagnostics", "get_elapsed_time", t, args, i.SignatureArgTypes, ?loc = r) + |> Some + | "get_IsRunning", Some x -> Helper.InstanceCall(x, "is_running", t, []) |> Some + | "get_Elapsed", Some x -> Helper.InstanceCall(x, "elapsed", t, []) |> Some | "get_ElapsedMilliseconds", Some x -> Helper.InstanceCall(x, "elapsed_milliseconds", t, []) |> Some | "get_ElapsedTicks", Some x -> Helper.InstanceCall(x, "elapsed_ticks", t, []) |> Some | "Start", Some x - | "Stop", Some x -> Helper.InstanceCall(x, i.CompiledName.ToLower(), t, []) |> Some - | _ -> - let memberName = Naming.lowerFirst i.CompiledName - - Helper.LibCall(com, "diagnostics", memberName, Boolean, args, i.SignatureArgTypes, ?thisArg = thisArg, ?loc = r) - |> Some + | "Stop", Some x + | "Reset", Some x + | "Restart", Some x -> Helper.InstanceCall(x, i.CompiledName.ToLower(), t, []) |> Some + | _ -> None let debug (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr option) (args: Expr list) = diff --git a/src/fable-library-py/fable_library/diagnostics.py b/src/fable-library-py/fable_library/diagnostics.py index fbe00a6a4e..7b302b9f1f 100644 --- a/src/fable-library-py/fable_library/diagnostics.py +++ b/src/fable-library-py/fable_library/diagnostics.py @@ -2,63 +2,79 @@ import time -from .core import int32, int64 - - -class TimerError(Exception): - """A custom exception used to report errors in use of Timer class""" +from .core import int64 +from .time_span import TimeSpan, from_ticks class StopWatch: def __init__(self) -> None: - self._start_time: float | None = None - self._elapsed_time: float | None = None + self._started_at: int | None = None + self._elapsed_ns = 0 def is_running(self) -> bool: - """Return True if timer is running""" - return self._start_time is not None + return self._started_at is not None def start(self) -> None: - """Start a new timer""" - if self._start_time is not None: - raise TimerError("Timer is running. Use .stop() to stop it") - - self._start_time = time.perf_counter() + if self._started_at is None: + self._started_at = time.perf_counter_ns() def stop(self) -> None: - """Stop the timer, and report the elapsed time""" - if self._start_time is None: - raise TimerError("Timer is not running. Use .start() to start it") + if self._started_at is not None: + self._elapsed_ns += time.perf_counter_ns() - self._started_at + self._started_at = None + + def reset(self) -> None: + self._started_at = None + self._elapsed_ns = 0 + + def restart(self) -> None: + self._elapsed_ns = 0 + self._started_at = time.perf_counter_ns() - self._elapsed_time = time.perf_counter() - self._start_time - self._start_time = None + def _elapsed_nanoseconds(self) -> int: + if self._started_at is None: + return self._elapsed_ns + return self._elapsed_ns + time.perf_counter_ns() - self._started_at - def elapsed_milliseconds(self) -> int: - """Return the elapsed time in milliseconds""" - if self._elapsed_time is None: - raise TimerError("Timer is not running. Use .start() to start it") + def elapsed_milliseconds(self) -> int64: + return int64(self._elapsed_nanoseconds() // 1_000_000) - return int32(self._elapsed_time * 1000) + def elapsed_ticks(self) -> int64: + # invariant: ElapsedTicks and GetTimestamp use the units declared by Frequency. + return int64(self._elapsed_nanoseconds()) - def elapsed_ticks(self) -> int: - """Return the elapsed time in ticks""" - if self._elapsed_time is None: - raise TimerError("Timer is not running. Use .start() to start it") + def elapsed(self) -> TimeSpan: + return from_ticks(self._elapsed_nanoseconds() // 100) - return int32(self._elapsed_time * 10000000) +def start_new() -> StopWatch: + stopwatch = StopWatch() + stopwatch.start() + return stopwatch -def frequency() -> int: - return 1000000000 + +def frequency() -> int64: + return int64(1_000_000_000) + + +def is_high_resolution() -> bool: + return True def get_timestamp() -> int64: return int64(time.perf_counter_ns()) +def get_elapsed_time(starting_timestamp: int64, ending_timestamp: int64 | None = None) -> TimeSpan: + end = get_timestamp() if ending_timestamp is None else ending_timestamp + return from_ticks((end - starting_timestamp) // 100) + + __all__ = [ "StopWatch", - "TimerError", "frequency", + "get_elapsed_time", "get_timestamp", + "is_high_resolution", + "start_new", ] diff --git a/tests/Python/TestMisc.fs b/tests/Python/TestMisc.fs index 44eca8a4cc..3dfec7142e 100644 --- a/tests/Python/TestMisc.fs +++ b/tests/Python/TestMisc.fs @@ -1562,7 +1562,7 @@ let ``test System.Diagnostics.Stopwatch.Frequency works`` () = let x = System.Diagnostics.Stopwatch.Frequency x > 1000000L |> equal true - +[] let ``test System.Diagnostics.Stopwatch.GetTimestamp works`` () = let freq = double System.Diagnostics.Stopwatch.Frequency let start = System.Diagnostics.Stopwatch.GetTimestamp() @@ -1571,6 +1571,47 @@ let ``test System.Diagnostics.Stopwatch.GetTimestamp works`` () = let elapsedMs = (double (stop - start)) * 1000.0 / freq elapsedMs > 0 |> equal true +[] +let ``test Stopwatch accumulates while running and after restart`` () = + let sw = System.Diagnostics.Stopwatch() + box sw :? System.Diagnostics.Stopwatch |> equal true + sw.IsRunning |> equal false + sw.ElapsedMilliseconds |> equal 0L + sw.Start() + sw.Start() + System.Threading.Thread.Sleep(20) + (sw.ElapsedMilliseconds > 0L) |> equal true + (sw.Elapsed.TotalMilliseconds > 0.0) |> equal true + let elapsedTicks = sw.ElapsedTicks + (elapsedTicks > 0L) |> equal true + sw.Stop() + sw.Stop() + let stopped = sw.ElapsedTicks + (stopped >= elapsedTicks) |> equal true + sw.Start() + System.Threading.Thread.Sleep(20) + sw.Stop() + (sw.ElapsedTicks > stopped) |> equal true + sw.Restart() + sw.IsRunning |> equal true + (sw.ElapsedTicks < stopped) |> equal true + sw.Reset() + sw.IsRunning |> equal false + sw.ElapsedTicks |> equal 0L + +[] +let ``test Stopwatch timestamp units and elapsed time agree`` () = + System.Diagnostics.Stopwatch.IsHighResolution |> equal true + let frequency = System.Diagnostics.Stopwatch.Frequency + let start = System.Diagnostics.Stopwatch.GetTimestamp() + System.Threading.Thread.Sleep(20) + let stop = System.Diagnostics.Stopwatch.GetTimestamp() + let elapsed = System.Diagnostics.Stopwatch.GetElapsedTime(start, stop) + (elapsed.TotalMilliseconds >= 10.0) |> equal true + (System.Diagnostics.Stopwatch.GetElapsedTime(start).Ticks >= elapsed.Ticks) |> equal true + let calculatedMilliseconds = float (stop - start) * 1000.0 / float frequency + (abs (calculatedMilliseconds - elapsed.TotalMilliseconds) < 1.0) |> equal true + [] let ``test Module mutable values work`` () = Util.mutableValue <- 3