Skip to content
Merged
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
28 changes: 28 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: CI

on:
push:
branches: [master, main]
pull_request:

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
java: ['17', '21']
steps:
- uses: actions/checkout@v4

- name: Set up JDK ${{ matrix.java }}
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: ${{ matrix.java }}
cache: sbt

- name: Set up sbt
uses: sbt/setup-sbt@v1

- name: Compile and test
run: sbt -v "compile; test"
25 changes: 18 additions & 7 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
.project
.classpath
# sbt
target/
project/target/
project/project/
project/build/
project/boot/
lib_managed/
src_managed/
project/boot/
project/build/target/
project/plugins/target/
project/plugins/lib_managed/
project/plugins/src_managed/b_managed/

# IDE
.idea/
.idea_modules/
.bsp/
.metals/
.bloop/
.vscode/
.project
.classpath
.settings/

# OS
.DS_Store
10 changes: 10 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
ThisBuild / scalaVersion := "3.3.4"
ThisBuild / organization := "com.programmera"
ThisBuild / version := "1.1.0"

lazy val root = (project in file("."))
.settings(
name := "simpletimer",
libraryDependencies += "org.scalameta" %% "munit" % "1.0.2" % Test,
scalacOptions ++= Seq("-deprecation", "-feature", "-Wunused:all")
)
9 changes: 1 addition & 8 deletions project/build.properties
Original file line number Diff line number Diff line change
@@ -1,8 +1 @@
#Project properties
#Sun Sep 26 22:47:02 CEST 2010
project.organization=myself
project.name=simpletimer
sbt.version=0.7.4
project.version=1.0
build.scala.versions=2.8.1
project.initialize=false
sbt.version=1.10.5
113 changes: 31 additions & 82 deletions src/main/scala/Timer.scala
Original file line number Diff line number Diff line change
@@ -1,101 +1,50 @@
package com.programmera.timer

import collection.immutable.HashMap
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.AtomicLong


trait UsingTimer {
def withTimer[T](name: String)(f: => T): T = {
trait UsingTimer:
def withTimer[T](name: String)(f: => T): T =
Timer(name).invoke(f)
}
}

object Timer {
/**
* timers
*/
private var timerMap = HashMap[String, Timer]()

/**
* factory method
* creates a new timer with a given name
*
* @param name id of the new timer
*/
def addTimer(name: String): Unit = {
timerMap.get(name) match {
case None => timerMap += ((name, new TimerImpl()))
case Some(x) => throw new IllegalArgumentException("Timer " + name + " already created")
}
}
object Timer:
private val timerMap = ConcurrentHashMap[String, Timer]()

def consumedTime(name: String): Long = {
timerMap.get(name) match {
case Some(x) => x.consumedTime
case None => throw new IllegalArgumentException("Timer " + name + " not avaliable")
}
}
/** Register a new timer under `name`. Throws if one already exists. */
def addTimer(name: String): Unit =
val previous = timerMap.putIfAbsent(name, new TimerImpl())
if previous != null then
throw new IllegalArgumentException(s"Timer $name already created")

/**
* Retrieve specific timer via apply method
*
* @param name id of the Timer
* @return Timer with id
*/
private[timer] def apply(name: String): Timer = {
timerMap.get(name) match {
case Some(x) => x
case None => throw new java.lang.IllegalArgumentException("Timer " + name + " not avaliable")
}
}
/** Nanoseconds consumed by the most recent invocation of `name`. */
def consumedTime(name: String): Long =
lookup(name).consumedTime

}
private[timer] def apply(name: String): Timer = lookup(name)

private[timer] trait Timer {
private[timer] def reset(): Unit = timerMap.clear()

/**
* @return Long nanoseconds timer
*/
def consumedTime: Long
private def lookup(name: String): Timer =
timerMap.get(name) match
case null => throw new IllegalArgumentException(s"Timer $name not available")
case t => t

/**
* function scope
*/
def invoke[T](f: => T): T
}
private[timer] trait Timer:
def consumedTime: Long
def invoke[T](f: => T): T

/**
* Timer implementation class
*
*/
private[timer] class TimerImpl extends Timer
{
private var _consumedTime = new AtomicLong
private[timer] class TimerImpl extends Timer:
private val _consumedTime = new AtomicLong

private def consumedTime_=(l: Long) {
_consumedTime.set(l)
}
def consumedTime = _consumedTime.get
def consumedTime: Long = _consumedTime.get

def invoke[T](f: => T): T = {
def invoke[T](f: => T): T =
val start = System.nanoTime()

def calcConsumedTime: Unit = {
val end = System.nanoTime()
consumedTime = end - start
}

try {
try
val ret = f
calcConsumedTime
_consumedTime.set(System.nanoTime() - start)
ret
}
catch {
case e: Throwable => {
calcConsumedTime
catch
case e: Throwable =>
_consumedTime.set(System.nanoTime() - start)
throw e
}
}
}
}

43 changes: 0 additions & 43 deletions src/main/scala/timerTest.scala

This file was deleted.

55 changes: 55 additions & 0 deletions src/test/scala/com/programmera/timer/TimerSuite.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.programmera.timer

class TimerSuite extends munit.FunSuite:

override def beforeEach(context: BeforeEach): Unit =
Timer.reset()

test("addTimer registers a new timer with zero consumed time") {
Timer.addTimer("a")
assertEquals(Timer.consumedTime("a"), 0L)
}

test("addTimer twice with the same name throws") {
Timer.addTimer("dup")
intercept[IllegalArgumentException](Timer.addTimer("dup"))
}

test("consumedTime on an unknown timer throws") {
intercept[IllegalArgumentException](Timer.consumedTime("missing"))
}

test("withTimer returns the block's value and records elapsed time") {
Timer.addTimer("sleep")
val timing = new UsingTimer {}
val result = timing.withTimer("sleep") {
Thread.sleep(20)
42
}
assertEquals(result, 42)
assert(
Timer.consumedTime("sleep") >= 15_000_000L,
s"expected >= 15ms, got ${Timer.consumedTime("sleep")} ns"
)
}

test("withTimer records elapsed time even when the block throws") {
Timer.addTimer("boom")
val timing = new UsingTimer {}
intercept[RuntimeException] {
timing.withTimer("boom") {
Thread.sleep(5)
throw new RuntimeException("nope")
}
}
assert(Timer.consumedTime("boom") > 0L)
}

test("independent timers track their own elapsed times") {
Timer.addTimer("one")
Timer.addTimer("two")
val timing = new UsingTimer {}
timing.withTimer("one")(Thread.sleep(5))
timing.withTimer("two")(Thread.sleep(15))
assert(Timer.consumedTime("two") > Timer.consumedTime("one"))
}
Loading