Baseline implementation for gs-timing
Some checks failed
/ Build and Release Library (push) Failing after 31s
Some checks failed
/ Build and Release Library (push) Failing after 31s
Includes: - Complete implementation - Most code documentation Does not include: - Tests - Complete documentation
This commit is contained in:
commit
ab8ee3b24d
14 changed files with 588 additions and 0 deletions
68
.forgejo/workflows/pull_request.yaml
Normal file
68
.forgejo/workflows/pull_request.yaml
Normal file
|
@ -0,0 +1,68 @@
|
|||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
library_snapshot:
|
||||
runs-on: docker
|
||||
container:
|
||||
image: registry.garrity.co:8443/gs/ci-scala:latest
|
||||
name: 'Build and Test Library Snapshot'
|
||||
env:
|
||||
GS_MAVEN_USER: ${{ vars.GS_MAVEN_USER }}
|
||||
GS_MAVEN_TOKEN: ${{ secrets.GS_MAVEN_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
name: 'Checkout Repository'
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: 'Pre-Commit'
|
||||
run: |
|
||||
pre-commit install
|
||||
pre-commit run --all-files
|
||||
- name: 'Prepare Versioned Build'
|
||||
run: |
|
||||
latest_git_tag="$(git describe --tags --abbrev=0 || echo 'No Tags')"
|
||||
latest_commit_message="$(git show -s --format=%s HEAD)"
|
||||
if [[ "$latest_commit_message" == *"(major)"* ]]; then
|
||||
export GS_RELEASE_TYPE="major"
|
||||
elif [[ "$latest_commit_message" == *"(minor)"* ]]; then
|
||||
export GS_RELEASE_TYPE="minor"
|
||||
elif [[ "$latest_commit_message" == *"(patch)"* ]]; then
|
||||
export GS_RELEASE_TYPE="patch"
|
||||
elif [[ "$latest_commit_message" == *"(docs)"* ]]; then
|
||||
export GS_RELEASE_TYPE="norelease"
|
||||
elif [[ "$latest_commit_message" == *"(norelease)"* ]]; then
|
||||
export GS_RELEASE_TYPE="norelease"
|
||||
else
|
||||
export GS_RELEASE_TYPE="norelease"
|
||||
fi
|
||||
echo "GS_RELEASE_TYPE=$GS_RELEASE_TYPE" >> $GITHUB_ENV
|
||||
echo "Previous Git Tag: $latest_git_tag"
|
||||
echo "Latest Commit: $latest_commit_message ($GS_RELEASE_TYPE) (SNAPSHOT)"
|
||||
if [ "$GS_RELEASE_TYPE" = "norelease" ]; then
|
||||
sbtn -Dsnapshot=true -Drelease="patch" semVerInfo
|
||||
else
|
||||
sbtn -Dsnapshot=true -Drelease="$GS_RELEASE_TYPE" semVerInfo
|
||||
fi
|
||||
- name: 'Unit Tests and Code Coverage'
|
||||
run: |
|
||||
sbtn clean
|
||||
sbtn coverage
|
||||
sbtn test
|
||||
sbtn coverageReport
|
||||
- name: 'Publish Snapshot'
|
||||
run: |
|
||||
echo "Testing env var propagation = ${{ env.GS_RELEASE_TYPE }}"
|
||||
if [ "${{ env.GS_RELEASE_TYPE }}" = "norelease" ]; then
|
||||
echo "Skipping publish due to GS_RELEASE_TYPE=norelease"
|
||||
else
|
||||
sbtn coverageOff
|
||||
sbtn clean
|
||||
sbtn compile
|
||||
sbtn publish
|
||||
fi
|
84
.forgejo/workflows/release.yaml
Normal file
84
.forgejo/workflows/release.yaml
Normal file
|
@ -0,0 +1,84 @@
|
|||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
library_release:
|
||||
runs-on: docker
|
||||
container:
|
||||
image: registry.garrity.co:8443/gs/ci-scala:latest
|
||||
name: 'Build and Release Library'
|
||||
env:
|
||||
GS_MAVEN_USER: ${{ vars.GS_MAVEN_USER }}
|
||||
GS_MAVEN_TOKEN: ${{ secrets.GS_MAVEN_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
name: 'Checkout Repository'
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: 'Pre-Commit'
|
||||
run: |
|
||||
pre-commit install
|
||||
pre-commit run --all-files
|
||||
- name: 'Prepare Versioned Build'
|
||||
run: |
|
||||
latest_git_tag="$(git describe --tags --abbrev=0 || echo 'No Tags')"
|
||||
latest_commit_message="$(git show -s --format=%s HEAD)"
|
||||
if [[ "$latest_commit_message" == *"(major)"* ]]; then
|
||||
export GS_RELEASE_TYPE="major"
|
||||
elif [[ "$latest_commit_message" == *"(minor)"* ]]; then
|
||||
export GS_RELEASE_TYPE="minor"
|
||||
elif [[ "$latest_commit_message" == *"(patch)"* ]]; then
|
||||
export GS_RELEASE_TYPE="patch"
|
||||
elif [[ "$latest_commit_message" == *"(docs)"* ]]; then
|
||||
export GS_RELEASE_TYPE="norelease"
|
||||
elif [[ "$latest_commit_message" == *"(norelease)"* ]]; then
|
||||
export GS_RELEASE_TYPE="norelease"
|
||||
else
|
||||
export GS_RELEASE_TYPE="norelease"
|
||||
fi
|
||||
|
||||
echo "GS_RELEASE_TYPE=$GS_RELEASE_TYPE" >> $GITHUB_ENV
|
||||
echo "Previous Git Tag: $latest_git_tag"
|
||||
echo "Latest Commit: $latest_commit_message"
|
||||
echo "Selected Release Type: '$GS_RELEASE_TYPE'"
|
||||
|
||||
if [ "$GS_RELEASE_TYPE" = "norelease" ]; then
|
||||
echo "Skipping all versioning for 'norelease' commit."
|
||||
else
|
||||
sbtn -Drelease="$GS_RELEASE_TYPE" semVerInfo
|
||||
fi
|
||||
- name: 'Unit Tests and Code Coverage'
|
||||
run: |
|
||||
if [ "${{ env.GS_RELEASE_TYPE }}" = "norelease" ]; then
|
||||
echo "Skipping build/test for 'norelease' commit."
|
||||
else
|
||||
sbtn clean
|
||||
sbtn coverage
|
||||
sbtn test
|
||||
sbtn coverageReport
|
||||
fi
|
||||
- name: 'Publish Release'
|
||||
run: |
|
||||
if [ "${{ env.GS_RELEASE_TYPE }}" = "norelease" ]; then
|
||||
echo "Skipping publish for 'norelease' commit."
|
||||
else
|
||||
sbtn coverageOff
|
||||
sbtn clean
|
||||
sbtn semVerWriteVersionToFile
|
||||
sbtn publish
|
||||
fi
|
||||
- name: 'Create Git Tag'
|
||||
run: |
|
||||
if [ "${{ env.GS_RELEASE_TYPE }}" = "norelease" ]; then
|
||||
echo "Skipping Git tag for 'norelease' commit."
|
||||
else
|
||||
selected_version="$(cat .version)"
|
||||
git tag "$selected_version"
|
||||
git push origin "$selected_version"
|
||||
fi
|
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
target/
|
||||
project/target/
|
||||
project/project/
|
||||
modules/core/target/
|
16
.pre-commit-config.yaml
Normal file
16
.pre-commit-config.yaml
Normal file
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.5.0
|
||||
hooks:
|
||||
- id: end-of-file-fixer
|
||||
- id: trailing-whitespace
|
||||
- id: fix-byte-order-marker
|
||||
- id: mixed-line-ending
|
||||
args: ['--fix=lf']
|
||||
description: Enforces using only 'LF' line endings.
|
||||
- id: trailing-whitespace
|
||||
- repo: https://git.garrity.co/garrity-software/gs-pre-commit-scala
|
||||
rev: v1.0.1
|
||||
hooks:
|
||||
- id: scalafmt
|
72
.scalafmt.conf
Normal file
72
.scalafmt.conf
Normal file
|
@ -0,0 +1,72 @@
|
|||
// See: https://github.com/scalameta/scalafmt/tags for the latest tags.
|
||||
version = 3.8.1
|
||||
runner.dialect = scala3
|
||||
maxColumn = 80
|
||||
|
||||
rewrite {
|
||||
rules = [RedundantBraces, RedundantParens, Imports, SortModifiers]
|
||||
imports.expand = true
|
||||
imports.sort = scalastyle
|
||||
redundantBraces.ifElseExpressions = true
|
||||
redundantBraces.stringInterpolation = true
|
||||
}
|
||||
|
||||
indent {
|
||||
main = 2
|
||||
callSite = 2
|
||||
defnSite = 2
|
||||
extendSite = 4
|
||||
withSiteRelativeToExtends = 2
|
||||
commaSiteRelativeToExtends = 2
|
||||
}
|
||||
|
||||
align {
|
||||
preset = more
|
||||
openParenCallSite = false
|
||||
openParenDefnSite = false
|
||||
}
|
||||
|
||||
newlines {
|
||||
implicitParamListModifierForce = [before,after]
|
||||
topLevelStatementBlankLines = [
|
||||
{
|
||||
blanks = 1
|
||||
}
|
||||
]
|
||||
afterCurlyLambdaParams = squash
|
||||
}
|
||||
|
||||
danglingParentheses {
|
||||
defnSite = true
|
||||
callSite = true
|
||||
ctrlSite = true
|
||||
exclude = []
|
||||
}
|
||||
|
||||
verticalMultiline {
|
||||
atDefnSite = true
|
||||
arityThreshold = 2
|
||||
newlineAfterOpenParen = true
|
||||
}
|
||||
|
||||
comments {
|
||||
wrap = standalone
|
||||
}
|
||||
|
||||
docstrings {
|
||||
style = "SpaceAsterisk"
|
||||
oneline = unfold
|
||||
wrap = yes
|
||||
forceBlankLineBefore = true
|
||||
}
|
||||
|
||||
project {
|
||||
excludePaths = [
|
||||
"glob:**target/**",
|
||||
"glob:**.metals/**",
|
||||
"glob:**.bloop/**",
|
||||
"glob:**.bsp/**",
|
||||
"glob:**metals.sbt",
|
||||
"glob:**.git/**"
|
||||
]
|
||||
}
|
9
LICENSE
Normal file
9
LICENSE
Normal file
|
@ -0,0 +1,9 @@
|
|||
MIT License
|
||||
|
||||
Copyright Patrick Garrity
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
28
README.md
Normal file
28
README.md
Normal file
|
@ -0,0 +1,28 @@
|
|||
# gs-timing
|
||||
|
||||
[GS Open Source](https://garrity.co/oss.html) |
|
||||
[License (MIT)](./LICENSE)
|
||||
|
||||
Timing library for Cats Effect and Scala 3.
|
||||
|
||||
- [Usage](#usage)
|
||||
- [Donate](#donate)
|
||||
|
||||
## Usage
|
||||
|
||||
### Dependency
|
||||
|
||||
This artifact is available in the Garrity Software Maven repository.
|
||||
|
||||
```scala
|
||||
externalResolvers +=
|
||||
"Garrity Software Releases" at "https://maven.garrity.co/gs"
|
||||
|
||||
val Gstiming: ModuleID =
|
||||
"gs" %% "gs-timing-v0" % "$VERSION"
|
||||
```
|
||||
|
||||
## Donate
|
||||
|
||||
Enjoy this project or want to help me achieve my [goals](https://garrity.co)?
|
||||
Consider [Donating to Pat on Ko-fi](https://ko-fi.com/gspfm).
|
32
build.sbt
Normal file
32
build.sbt
Normal file
|
@ -0,0 +1,32 @@
|
|||
val scala3: String = "3.5.0"
|
||||
|
||||
externalResolvers := Seq(
|
||||
"Garrity Software Mirror" at "https://maven.garrity.co/releases",
|
||||
"Garrity Software Releases" at "https://maven.garrity.co/gs"
|
||||
)
|
||||
|
||||
ThisBuild / scalaVersion := scala3
|
||||
ThisBuild / versionScheme := Some("semver-spec")
|
||||
ThisBuild / gsProjectName := "gs-timing"
|
||||
|
||||
lazy val sharedSettings = Seq(
|
||||
scalaVersion := scala3,
|
||||
version := semVerSelected.value
|
||||
)
|
||||
|
||||
lazy val testSettings = Seq(
|
||||
libraryDependencies ++= Seq(
|
||||
"org.scalameta" %% "munit" % "1.0.1" % Test
|
||||
)
|
||||
)
|
||||
|
||||
lazy val `gs-timing` = project
|
||||
.in(file("."))
|
||||
.settings(sharedSettings)
|
||||
.settings(testSettings)
|
||||
.settings(name := s"${gsProjectName.value}-v${semVerMajor.value}")
|
||||
.settings(
|
||||
libraryDependencies ++= Seq(
|
||||
"org.typelevel" %% "cats-effect" % "3.5.4"
|
||||
)
|
||||
)
|
1
project/build.properties
Normal file
1
project/build.properties
Normal file
|
@ -0,0 +1 @@
|
|||
sbt.version=1.10.1
|
33
project/plugins.sbt
Normal file
33
project/plugins.sbt
Normal file
|
@ -0,0 +1,33 @@
|
|||
def selectCredentials(): Credentials =
|
||||
if ((Path.userHome / ".sbt" / ".credentials").exists())
|
||||
Credentials(Path.userHome / ".sbt" / ".credentials")
|
||||
else
|
||||
Credentials.apply(
|
||||
realm = "Reposilite",
|
||||
host = "maven.garrity.co",
|
||||
userName = sys.env
|
||||
.get("GS_MAVEN_USER")
|
||||
.getOrElse(
|
||||
throw new RuntimeException(
|
||||
"You must either provide ~/.sbt/.credentials or specify the GS_MAVEN_USER environment variable."
|
||||
)
|
||||
),
|
||||
passwd = sys.env
|
||||
.get("GS_MAVEN_TOKEN")
|
||||
.getOrElse(
|
||||
throw new RuntimeException(
|
||||
"You must either provide ~/.sbt/.credentials or specify the GS_MAVEN_TOKEN environment variable."
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
credentials += selectCredentials()
|
||||
|
||||
externalResolvers := Seq(
|
||||
"Garrity Software Mirror" at "https://maven.garrity.co/releases",
|
||||
"Garrity Software Releases" at "https://maven.garrity.co/gs"
|
||||
)
|
||||
|
||||
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.1.0")
|
||||
addSbtPlugin("gs" % "sbt-garrity-software" % "0.4.0")
|
||||
addSbtPlugin("gs" % "sbt-gs-semver" % "0.3.0")
|
32
src/main/scala/gs/timing/v0/ElapsedTime.scala
Normal file
32
src/main/scala/gs/timing/v0/ElapsedTime.scala
Normal file
|
@ -0,0 +1,32 @@
|
|||
package gs.timing.v0
|
||||
|
||||
import scala.concurrent.duration.FiniteDuration
|
||||
|
||||
/** Represents elapsed, monotonic time, expressed as nanoseconds.
|
||||
*
|
||||
* @param start
|
||||
* The start time of this duration (an absolute monotonic value).
|
||||
* @param end
|
||||
* The end time of this duration (an absolute monotonic value).
|
||||
* @param duration
|
||||
* The duration that elapsed, expressed as nanoseconds.
|
||||
*/
|
||||
case class ElapsedTime(
|
||||
start: Long,
|
||||
end: Long,
|
||||
duration: FiniteDuration
|
||||
):
|
||||
/** @return
|
||||
* The elapsed duration, expressed as nanoseconds.
|
||||
*/
|
||||
def toNanoseconds(): Long = duration.toNanos
|
||||
|
||||
/** @return
|
||||
* The elapsed duration, expressed as milliseconds.
|
||||
*/
|
||||
def toMilliseconds(): Long = duration.toMillis
|
||||
|
||||
/** @return
|
||||
* The elapsed duration, expressed as seconds.
|
||||
*/
|
||||
def toSeconds(): Long = duration.toSeconds
|
96
src/main/scala/gs/timing/v0/MonotonicProvider.scala
Normal file
96
src/main/scala/gs/timing/v0/MonotonicProvider.scala
Normal file
|
@ -0,0 +1,96 @@
|
|||
package gs.timing.v0
|
||||
|
||||
import cats.effect.Ref
|
||||
import cats.effect.Sync
|
||||
import cats.syntax.all.*
|
||||
|
||||
/** Provider of monotonic time - a monotonic clock is a clock that will never
|
||||
* adjust or jump forwards or backwards, and represents the amount of time
|
||||
* elapsed since some arbitrary point in time in the past.
|
||||
*
|
||||
* This is useful for calculating _elapsed time_ -- the exact duration elapsed
|
||||
* between two relative events. This is _not useful_ for calculating anything
|
||||
* related to real dates and times.
|
||||
*
|
||||
* ## System Provider
|
||||
*
|
||||
* For most real use cases, the _system_ provider is appropriate. This
|
||||
* delegates to `System.nanoTime()` under the covers, which leverages the
|
||||
* monotonic clock of the system where the application is running.
|
||||
*
|
||||
* {{{
|
||||
* import cats.effect.IO
|
||||
* import gs.timing.v0.MonotonicProvider
|
||||
*
|
||||
* val provider = MonotonicProvider.system[IO]
|
||||
*
|
||||
* val time: IO[Long] = provider.monotonic()
|
||||
* }}}
|
||||
*
|
||||
* ## Manual Provider
|
||||
*
|
||||
* For testing purposes, a manual provider is supported. This provider allows
|
||||
* the user to manually control a tick count and assume the role of the clock.
|
||||
* This allows for deterministic clock values.
|
||||
*
|
||||
* {{{
|
||||
* import cats.effect.IO
|
||||
* import gs.timing.v0.MonotonicProvider
|
||||
*
|
||||
* for
|
||||
* provider <- MonotonicProvider.manual[IO]
|
||||
* t1 <- provider.monotonic() // 0
|
||||
* _ <- provider.tick()
|
||||
* t2 <- provider.monotonic() // 1
|
||||
* _ <- provider.set(10)
|
||||
* t3 <- provider.monotonic() // 10
|
||||
* _ <- provider.reset()
|
||||
* t4 <- provider.monotonic() // 0
|
||||
* yield
|
||||
* ()
|
||||
* }}}
|
||||
*/
|
||||
trait MonotonicProvider[F[_]]:
|
||||
/** @return
|
||||
* The current value of the underlying monotonic clock.
|
||||
*/
|
||||
def monotonic(): F[Long]
|
||||
|
||||
object MonotonicProvider:
|
||||
|
||||
/** @return
|
||||
* A new provider based on the system's underlying monotonic clock. This
|
||||
* implementation delegates to `System.nanoTime()`.
|
||||
*/
|
||||
def system[F[_]: Sync]: SystemProvider[F] =
|
||||
new SystemProvider[F]
|
||||
|
||||
/** @return
|
||||
* A new provider, always initialized to 0, that is manually controlled by
|
||||
* the user.
|
||||
*/
|
||||
def manual[F[_]: Sync]: F[ManualTickProvider[F]] =
|
||||
ManualTickProvider.initialize[F]
|
||||
|
||||
final class SystemProvider[F[_]: Sync] extends MonotonicProvider[F]:
|
||||
override def monotonic(): F[Long] = Sync[F].delay(System.nanoTime())
|
||||
|
||||
final class ManualTickProvider[F[_]: Sync] private (
|
||||
ticks: Ref[F, Long]
|
||||
) extends MonotonicProvider[F]:
|
||||
override def monotonic(): F[Long] = ticks.get
|
||||
|
||||
def tick(): F[Unit] = ticks.update(_ + 1)
|
||||
|
||||
def set(newTickCount: Long): F[Unit] = ticks.set(newTickCount)
|
||||
|
||||
def reset(): F[Unit] = ticks.set(0)
|
||||
|
||||
object ManualTickProvider:
|
||||
|
||||
def initialize[F[_]: Sync]: F[ManualTickProvider[F]] =
|
||||
Ref.of[F, Long](0).map(ticks => new ManualTickProvider[F](ticks))
|
||||
|
||||
end ManualTickProvider
|
||||
|
||||
end MonotonicProvider
|
54
src/main/scala/gs/timing/v0/MonotonicTimer.scala
Normal file
54
src/main/scala/gs/timing/v0/MonotonicTimer.scala
Normal file
|
@ -0,0 +1,54 @@
|
|||
package gs.timing.v0
|
||||
|
||||
import cats.Functor
|
||||
import cats.syntax.all.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
import scala.concurrent.duration.FiniteDuration
|
||||
|
||||
/** Timer based on monotonic time expressed as nanoseconds. Each timer is based
|
||||
* on a fixed start point, and calculates [[ElapsedTime]] based on that point.
|
||||
*
|
||||
* This class should be instantiated by [[Timing]], which will inject an
|
||||
* appropriate function for calculating elapsed time.
|
||||
*
|
||||
* ## Usage
|
||||
*
|
||||
* {{{
|
||||
* import gs.timing.v0.Timing
|
||||
* import cats.effect.IO
|
||||
*
|
||||
* val timing = Timing.system[IO]
|
||||
*
|
||||
* val program: IO[List[ElapsedTime]] =
|
||||
* for
|
||||
* timer <- timing.start()
|
||||
* elapsed1 <- timing.checkpoint()
|
||||
* elapsed2 <- timing.checkpoint()
|
||||
* elapsed3 <- timing.checkpoint()
|
||||
* yield
|
||||
* List(elapsed1, elapsed2, elapsed3)
|
||||
* }}}
|
||||
*
|
||||
* @param start
|
||||
* The fixed start point, expressed as monotonic time (nanoseconds, tick
|
||||
* count).
|
||||
* @param checkpointFunction
|
||||
* The checkpoint function used to evaluate the current time.
|
||||
*/
|
||||
final class MonotonicTimer[F[_]: Functor](
|
||||
val start: Long,
|
||||
private val checkpointFunction: () => F[Long]
|
||||
):
|
||||
|
||||
/** @return
|
||||
* The [[ElapsedTime]] (in nanoseconds) based on the fixed start point and
|
||||
* checking the current monotonic time.
|
||||
*/
|
||||
def checkpoint(): F[ElapsedTime] =
|
||||
checkpointFunction().map(end =>
|
||||
ElapsedTime(
|
||||
start = start,
|
||||
end = end,
|
||||
duration = new FiniteDuration(end - start, TimeUnit.NANOSECONDS)
|
||||
)
|
||||
)
|
59
src/main/scala/gs/timing/v0/Timing.scala
Normal file
59
src/main/scala/gs/timing/v0/Timing.scala
Normal file
|
@ -0,0 +1,59 @@
|
|||
package gs.timing.v0
|
||||
|
||||
import cats.effect.Sync
|
||||
import cats.syntax.all.*
|
||||
|
||||
/** Primary entrypoint for the `gs-timing` library. This class is an interface
|
||||
* for working with monotonic time:
|
||||
*
|
||||
* - Getting the current value of some monotonic clock.
|
||||
* - Calculating the elapsed time between two events.
|
||||
*
|
||||
* This class/library is not useful for anything related to human date/time
|
||||
* operations, and is focused on precise machine-level timing.
|
||||
*
|
||||
* ## Sharing Instances
|
||||
*
|
||||
* System instances of this class can be shared - one instance can be used
|
||||
* across an application.
|
||||
*
|
||||
* Manual instances of this class are user-controlled and should be shared with
|
||||
* caution, and will not necessarily give consistent results across concurrent
|
||||
* calls (depending on what each call does). Since these are typically reserved
|
||||
* for testing purposes, creating an instance per test is advised.
|
||||
*
|
||||
* @param monotonicProvider
|
||||
* The [[MonotonicProvider]], which determines how nanoseconds are
|
||||
* calculated.
|
||||
*/
|
||||
final class Timing[F[_]: Sync](
|
||||
monotonicProvider: MonotonicProvider[F]
|
||||
):
|
||||
/** @return
|
||||
* The current monotonic time (expressed as nanoseconds).
|
||||
*/
|
||||
def monotonic(): F[Long] = monotonicProvider.monotonic()
|
||||
|
||||
/** @return
|
||||
* A new [[MonotonicTimer]] based on the current monotonic time.
|
||||
*/
|
||||
def start(): F[MonotonicTimer[F]] =
|
||||
monotonic().map(start => new MonotonicTimer[F](start, monotonic))
|
||||
|
||||
object Timing:
|
||||
|
||||
/** @return
|
||||
* A new [[Timing]] instance based on the system's underlying monotonic
|
||||
* clock. This implementation delegates to `System.nanoTime()`.
|
||||
*/
|
||||
def system[F[_]: Sync]: Timing[F] =
|
||||
new Timing[F](MonotonicProvider.system[F])
|
||||
|
||||
/** @return
|
||||
* A new [[Timing]] instance, always initialized to 0, that is manually
|
||||
* controlled by the user.
|
||||
*/
|
||||
def manual[F[_]: Sync]: F[Timing[F]] =
|
||||
MonotonicProvider.manual[F].map(new Timing[F](_))
|
||||
|
||||
end Timing
|
Loading…
Add table
Reference in a new issue