-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #9 from savi-lang/add/time-measure
Add `Time.Measure` functions leveraging system monotonic clocks.
- Loading branch information
Showing
5 changed files
with
214 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
:class Time.Measure.Spec | ||
:is Spec | ||
:const describes: "Time.Measure" | ||
|
||
:it "measures nanoseconds spent in the given block" | ||
run_count = 0 | ||
prior = Time.Measure.current_nanoseconds | ||
measured = Time.Measure.nanoseconds -> ( | ||
run_count += 1 | ||
_FFI.TestHacks.sleep(1) | ||
) | ||
after = Time.Measure.current_nanoseconds | ||
|
||
assert: run_count == 1 | ||
assert: measured >= 1000000000 // at least 1 second (due to the sleep) | ||
assert: measured <= 10000000000 // it should never take more than 10 seconds | ||
assert: measured <= (after - prior) | ||
|
||
:it "measures microseconds spent in the given block" | ||
run_count = 0 | ||
prior = Time.Measure.current_microseconds | ||
measured = Time.Measure.microseconds -> ( | ||
run_count += 1 | ||
_FFI.TestHacks.sleep(1) | ||
) | ||
after = Time.Measure.current_microseconds | ||
|
||
assert: run_count == 1 | ||
assert: measured >= 1000000 // at least 1 second (due to the sleep) | ||
assert: measured <= 10000000 // it should never take more than 10 seconds | ||
assert: measured <= (after - prior) | ||
|
||
:it "measures milliseconds spent in the given block" | ||
run_count = 0 | ||
prior = Time.Measure.current_milliseconds | ||
measured = Time.Measure.milliseconds -> ( | ||
run_count += 1 | ||
_FFI.TestHacks.sleep(1) | ||
) | ||
after = Time.Measure.current_milliseconds | ||
|
||
assert: run_count == 1 | ||
assert: measured > 0 | ||
assert: measured >= 1000 // at least 1 second (due to the sleep) | ||
assert: measured <= 10000 // it should never take more than 10 seconds | ||
assert: measured <= (after - prior) | ||
|
||
// It's never advisable to use `sleep` in Savi, given that it blocks the | ||
// entire scheduler thread from making progress. However, as a test hack | ||
// it's reasonable because it helps us test a specific non-zero amount of | ||
// time passing inside the block we are measuring. | ||
:module _FFI.TestHacks | ||
:ffi sleep(seconds I32) I32 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
:: A collection of functions relating to measuring intervals of time elapsed | ||
:: during some portion of the program's execution. | ||
:module Time.Measure | ||
:: Measure the interval in nanoseconds that elapse in the given yield block. | ||
:fun nanoseconds | ||
:yields None for None | ||
prior = @current_nanoseconds | ||
yield None | ||
@current_nanoseconds - prior | ||
|
||
:: Measure the interval in microseconds that elapse in the given yield block. | ||
:fun microseconds | ||
:yields None for None | ||
prior = @current_microseconds | ||
yield None | ||
@current_microseconds - prior | ||
|
||
:: Measure the interval in milliseconds that elapse in the given yield block. | ||
:fun milliseconds | ||
:yields None for None | ||
prior = @current_milliseconds | ||
yield None | ||
@current_milliseconds - prior | ||
|
||
:: Get the current monotonic non-adjusted nanosecond count. | ||
:: | ||
:: This number is not guaranteed to have any particular relationship to the | ||
:: current wall clock time - it is more likely to be related to system uptime, | ||
:: although there is no particular guarantee of that relationship either. | ||
:: For wall clock time, call `Time.now` instead. | ||
:: | ||
:: Being monotonic and non-adjusted, this is suitable for calculating | ||
:: time intervals using subtraction with wrap-around underflow semantics. | ||
:: That is, because the underlying count will wrap around on overflow, | ||
:: subtraction of two values across time should wrap around on underflow | ||
:: to properly account for the case of two values that straddle an overflow. | ||
:: See `nanoseconds` function for an example of how to measure time. | ||
:: | ||
:: This function is intended to be used for asynchronous timing measurements. | ||
:: For synchronous timing measurements, use the `nanoseconds` function, | ||
:: which handles the timing measurement for you around the given yield block. | ||
:fun current_nanoseconds U64 | ||
case ( | ||
| Platform.is_windows | | ||
0 // TODO: Windows support | ||
| Platform.is_macos | | ||
// MacOS gives us a count of total uptime "ticks" since the OS booted. | ||
// We don't know how long a "tick" is without asking the operating system, | ||
// so we ask it for the timebase and multiple/divide to get nanoseconds. | ||
timebase = Pair(U32, U32).new(0, 0) | ||
timebase_res = _FFI.mach_timebase_info(stack_address_of_variable timebase) | ||
return 0 if (timebase_res != 0) | ||
_FFI.mach_absolute_time | ||
* timebase.first.u64 | ||
/ timebase.last.u64 | ||
| | ||
// Other POSIX platforms give us the seconds since unix epoch and | ||
// nanoseconds as a pair, which we combine. We choose the monotonic clock. | ||
pair = Pair(USize, USize).new(0, 0) | ||
_FFI.clock_gettime( | ||
_FFI.ClockType.monotonic | ||
stack_address_of_variable pair | ||
) | ||
pair.first.u64 * 1000000000 + pair.last.u64 | ||
) | ||
|
||
:: Get the current monotonic non-adjusted microsecond count. | ||
:: | ||
:: This number is not guaranteed to have any particular relationship to the | ||
:: current wall clock time - it is more likely to be related to system uptime, | ||
:: although there is no particular guarantee of that relationship either. | ||
:: For wall clock time, call `Time.now` instead. | ||
:: | ||
:: Being monotonic and non-adjusted, this is suitable for calculating | ||
:: time intervals using subtraction with wrap-around underflow semantics. | ||
:: That is, because the underlying count will wrap around on overflow, | ||
:: subtraction of two values across time should wrap around on underflow | ||
:: to properly account for the case of two values that straddle an overflow. | ||
:: See `microseconds` function for an example of how to measure time. | ||
:: | ||
:: This function is intended to be used for asynchronous timing measurements. | ||
:: For synchronous timing measurements, use the `microseconds` function, | ||
:: which handles the timing measurement for you around the given yield block. | ||
:fun current_microseconds U64 | ||
case ( | ||
| Platform.is_windows | | ||
0 // TODO: Windows support | ||
| Platform.is_macos | | ||
// MacOS gives us a count of total uptime "ticks" since the OS booted. | ||
// We don't know how long a "tick" is without asking the operating system, | ||
// so we ask it for the timebase and multiple/divide to get microseconds. | ||
timebase = Pair(U32, U32).new(0, 0) | ||
timebase_res = _FFI.mach_timebase_info(stack_address_of_variable timebase) | ||
return 0 if (timebase_res != 0) | ||
_FFI.mach_absolute_time | ||
* timebase.first.u64 | ||
/ (timebase.last.u64 * 1000) | ||
| | ||
// Other POSIX platforms give us the seconds since unix epoch and | ||
// nanoseconds as a pair, which we combine. We choose the monotonic clock. | ||
pair = Pair(USize, USize).new(0, 0) | ||
_FFI.clock_gettime( | ||
_FFI.ClockType.monotonic | ||
stack_address_of_variable pair | ||
) | ||
pair.first.u64 * 1000000 + pair.last.u64 / 1000 | ||
) | ||
|
||
:: Get the current monotonic non-adjusted millisecond count. | ||
:: | ||
:: This number is not guaranteed to have any particular relationship to the | ||
:: current wall clock time - it is more likely to be related to system uptime, | ||
:: although there is no particular guarantee of that relationship either. | ||
:: For wall clock time, call `Time.now` instead. | ||
:: | ||
:: Being monotonic and non-adjusted, this is suitable for calculating | ||
:: time intervals using subtraction with wrap-around underflow semantics. | ||
:: That is, because the underlying count will wrap around on overflow, | ||
:: subtraction of two values across time should wrap around on underflow | ||
:: to properly account for the case of two values that straddle an overflow. | ||
:: See `milliseconds` function for an example of how to measure time. | ||
:: | ||
:: This function is intended to be used for asynchronous timing measurements. | ||
:: For synchronous timing measurements, use the `milliseconds` function, | ||
:: which handles the timing measurement for you around the given yield block. | ||
:fun current_milliseconds U64 | ||
case ( | ||
| Platform.is_windows | | ||
0 // TODO: Windows support | ||
| Platform.is_macos | | ||
// MacOS gives us a count of total uptime "ticks" since the OS booted. | ||
// We don't know how long a "tick" is without asking the operating system, | ||
// so we ask it for the timebase and multiple/divide to get milliseconds. | ||
timebase = Pair(U32, U32).new(0, 0) | ||
timebase_res = _FFI.mach_timebase_info(stack_address_of_variable timebase) | ||
return 0 if (timebase_res != 0) | ||
_FFI.mach_absolute_time | ||
* timebase.first.u64 | ||
/ (timebase.last.u64 * 1000000) | ||
| | ||
// Other POSIX platforms give us the seconds since unix epoch and | ||
// nanoseconds as a pair, which we combine. We choose the monotonic clock. | ||
pair = Pair(USize, USize).new(0, 0) | ||
_FFI.clock_gettime( | ||
_FFI.ClockType.monotonic | ||
stack_address_of_variable pair | ||
) | ||
pair.first.u64 * 1000 + pair.last.u64 / 1000000 | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters