Paul King created GROOVY-11947:
----------------------------------

             Summary: Here you go:  Title: Add timing utility methods (timed, 
timedNanos) and Timed record to groovy-jdk
                 Key: GROOVY-11947
                 URL: https://issues.apache.org/jira/browse/GROOVY-11947
             Project: Groovy
          Issue Type: New Feature
            Reporter: Paul King



h3. Summary
Add a small set of timing utilities to the groovy-jdk as extension methods, 
providing a convenient way to measure the elapsed time of a block of code 
without the caller having to manually record start/end times. Include a 
{{Timed}} record type for returning a result together with its elapsed duration.
h3. Motivation
Timing a block of code is one of the most common ad-hoc measurements developers 
perform. The current idiom in Groovy is verbose and error-prone:
{code}
long start = System.nanoTime()
def result = computeSomething()
long elapsedNanos = System.nanoTime() - start
{code}
Developers frequently reach for {{System.currentTimeMillis()}} here, which is 
the wrong tool - wall-clock time is not guaranteed to be monotonic and can 
produce negative or wildly incorrect durations when the system clock is 
adjusted (NTP sync, DST, manual change). A built-in utility that uses 
{{System.nanoTime()}} correctly by default steers users toward the right 
primitive.
h3. Proposed API
Three additions to the groovy-jdk:

{{long timedNanos(Closure c)}} - runs the closure and returns the elapsed time 
in nanoseconds. Does not return the closure's result.
{{long timedMillis(Closure c)}} - same as above but returns milliseconds 
(computed from nanoTime, not currentTimeMillis, so still monotonic).
{{<T> Timed<T> timed(Closure<T> c)}} - runs the closure and returns a {{Timed}} 
record containing both the result and the elapsed nanoseconds.

The {{Timed<T>}} type would be a record with:

{{T result}} - the value returned by the closure
{{long nanos}} - elapsed time in nanoseconds
{{Duration getDuration()}} - convenience accessor returning 
{{Duration.ofNanos(nanos)}}
{{long getMillis()}} - convenience accessor returning {{nanos / 1_000_000}}

h3. Example Usage
{code}
// Just time it
long elapsed = timedNanos { expensiveOperation() }
println "took ${elapsed}ns"
// Time it and keep the result
def t = timed { computeReport() }
println "report generated in ${t.millis}ms"
processReport(t.result)
// Use the Duration accessor for human-friendly output
println "took ${timed { loadData() }.duration}"
{code}
h3. Implementation Notes

All three methods must use {{System.nanoTime()}}, not 
{{System.currentTimeMillis()}}. This is the whole point of the utility.
Exceptions thrown by the closure should propagate unchanged - the utility does 
not swallow or transform them. Timing information is lost in the exception 
case; if users need timing on failure they can use a try/finally around 
{{timedNanos}} themselves.
The closure runs synchronously on the calling thread. If the closure submits 
asynchronous work without waiting for it, only the submission time is measured. 
This should be documented clearly.
{{long}} subtraction of two {{nanoTime}} readings handles wraparound correctly 
as long as the interval is under ~292 years, so no special handling is needed.

h3. Location
These are best placed as extension methods in {{DefaultGroovyMethods}} or a 
dedicated timing extension module. The {{Timed}} record would live alongside 
them.
h3. Out of Scope

Benchmarking utilities (warmup, statistical measurement, JIT handling) - users 
who need this should use JMH.
Async / reactive timing helpers - a separate concern, possibly a future 
enhancement.
CPU time measurement - {{ThreadMXBean.getCurrentThreadCpuTime()}} has different 
semantics and should not be conflated with wall-clock elapsed time.

h3. Related
Complements the existing time-related sugar in groovy-jdk (e.g. 
{{use(TimeCategory)}}, {{Date}} extensions) by adding a primitive for the 
measurement case specifically.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to