Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions src/Fable.Transforms/Python/Replacements.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) =
Expand Down
80 changes: 48 additions & 32 deletions src/fable-library-py/fable_library/diagnostics.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
]
43 changes: 42 additions & 1 deletion tests/Python/TestMisc.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1288,7 +1288,7 @@
| 2 -> x <- "2"
| 3 | 4 -> x <- "3" // Multiple cases are allowed
// | 5 | 6 as j -> x <- string j // This prevents the optimization
| 4 -> x <- "4" // Unreachable cases are removed

Check warning on line 1291 in tests/Python/TestMisc.fs

View workflow job for this annotation

GitHub Actions / build-python (ubuntu-latest, 3.13)

This rule will never be matched

Check warning on line 1291 in tests/Python/TestMisc.fs

View workflow job for this annotation

GitHub Actions / build-python (ubuntu-latest, 3.14)

This rule will never be matched

Check warning on line 1291 in tests/Python/TestMisc.fs

View workflow job for this annotation

GitHub Actions / build-python (ubuntu-latest, 3.15)

This rule will never be matched

Check warning on line 1291 in tests/Python/TestMisc.fs

View workflow job for this annotation

GitHub Actions / build-python (windows-latest, 3.14)

This rule will never be matched

Check warning on line 1291 in tests/Python/TestMisc.fs

View workflow job for this annotation

GitHub Actions / build-python (windows-latest, 3.13)

This rule will never be matched

Check warning on line 1291 in tests/Python/TestMisc.fs

View workflow job for this annotation

GitHub Actions / build-python (windows-latest, 3.15)

This rule will never be matched
| _ -> x <- "?"
equal "3" x

Expand Down Expand Up @@ -1562,7 +1562,7 @@
let x = System.Diagnostics.Stopwatch.Frequency
x > 1000000L |> equal true


[<Fact>]
let ``test System.Diagnostics.Stopwatch.GetTimestamp works`` () =
let freq = double System.Diagnostics.Stopwatch.Frequency
let start = System.Diagnostics.Stopwatch.GetTimestamp()
Expand All @@ -1571,6 +1571,47 @@
let elapsedMs = (double (stop - start)) * 1000.0 / freq
elapsedMs > 0 |> equal true

[<Fact>]
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

[<Fact>]
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

[<Fact>]
let ``test Module mutable values work`` () =
Util.mutableValue <- 3
Expand Down
Loading