// Copyright 2026 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
///|
fn iter_n_microseconds(inner : () -> Unit, k : Int) -> Double {
let ts = monotonic_clock_start()
for _ in 0.. Unit, count : UInt) -> Summary {
let count = count.land(0x7FFFFFFF).reinterpret_as_int()
let threshold = 100000.0 // 100 ms
let target_batch_size = for trial = 1 {
let single_us = iter_n_microseconds(inner, trial) / trial.to_double()
let target_batch_size = threshold /
(if single_us < 1.0 { 1.0 } else { single_us })
if trial.to_double() * single_us > threshold {
break target_batch_size
}
continue trial * 2
}
let batch_size = if target_batch_size < 1.0 {
1
} else {
target_batch_size.ceil().to_int()
}
let samples = Array::makei(count, _ => {
iter_n_microseconds(inner, batch_size) / batch_size.to_double()
})
samples.sort()
winsorize(sorted_data=samples, 5.0)
Summary::new(name?, sorted_data=samples, batch_size)
}
///|
/// Run a benchmark in batch mode
pub fn Bench::bench(
self : Bench,
name? : String,
f : () -> Unit,
count? : UInt = 10,
) -> Unit {
let summary = iter_count(name?, f, count)
if !self.buffer.is_empty() {
self.buffer.write_char(',')
}
self.buffer.write_string(summary.to_json().stringify(escape_slash=true))
self.summaries.push(summary)
}
///|
/// Run a benchmark and return its summary
pub fn single_bench(
name? : String,
f : () -> Unit,
count? : UInt = 10,
) -> Summary {
iter_count(name?, f, count)
}
// NOTE: use a type parameter `Any` to avoid exposing `trait OpaqueValue` to users
///|
/// Keep a value to prevent the optimizer from removing
/// the calculation that produces it.
pub fn[Any] Bench::keep(self : Bench, value : Any) -> Unit {
let trait_object : &OpaqueValue = @ref.new(value)
self._storage = trait_object
}
///|
/// Returns a JSON array string containing all collected benchmark summaries.
/// This helper is mainly used internally for tests and diagnostics.
#coverage.skip
pub fn Bench::dump_summaries(self : Bench) -> String {
"[\{self.buffer.to_string()}]"
}
///|
#skip("slow benchmarking test")
test "bench - single_bench basic functionality" {
let bench_result = single_bench(
() => {
// Simple operation
let x = 1 + 1
ignore(x)
},
count=2,
)
// Check that summary fields are properly populated
inspect(bench_result.runs, content="2")
inspect(bench_result.batch_size > 0, content="true")
inspect(bench_result.sum > 0.0, content="true")
inspect(bench_result.min <= bench_result.max, content="true")
}
///|
#skip("slow benchmarking test")
test "bench - Summary struct properties" {
// Create a very simple bench and extract the summary
let bench_result = single_bench(
() => {
// Simple operation
let x = 1
ignore(x)
},
count=5,
)
// Test that all the properties are accessible
inspect(bench_result.mean >= 0, content="true")
inspect(bench_result.median >= 0, content="true")
inspect(bench_result.std_dev >= 0, content="true")
inspect(bench_result.std_dev_pct >= 0, content="true")
inspect(bench_result.median_abs_dev >= 0, content="true")
inspect(bench_result.median_abs_dev_pct >= 0, content="true")
// The quartiles should be in ascending order
let (q1, q2, q3) = bench_result.quartiles
inspect(q1 <= q2, content="true")
inspect(q2 <= q3, content="true")
// IQR should be q3 - q1
inspect((q3 - q1 - bench_result.iqr).abs() < 0.001, content="true")
}
///|
#skip("slow benchmarking test")
test "bench - T struct and bench method" {
let bench_obj = Bench()
// Ensure the buffer is initially empty
inspect(bench_obj.buffer.is_empty(), content="true")
inspect(bench_obj.summaries.is_empty(), content="true")
// Run a benchmark
bench_obj.bench(
() => {
// Simple operation
let x = 20 * 20
ignore(x)
},
count=2,
)
// After running, the buffer should not be empty and there should be one summary
inspect(bench_obj.buffer.is_empty(), content="false")
inspect(bench_obj.summaries.length(), content="1")
// Run another benchmark to ensure we can add multiple
bench_obj.bench(
() => {
// Another operation
let y = 30 * 30
ignore(y)
},
count=2,
)
// Should now have two summaries
inspect(bench_obj.summaries.length(), content="2")
}
///|
#skip("slow benchmarking test")
test "bench - monotonic clock functionality" {
// Test that the clock functions work properly
let start = monotonic_clock_start()
// Do some simple work
let result = for _ in 0..<1000; result = 0 {
continue result + 1
} nobreak {
result
}
ignore(result)
let elapsed = monotonic_clock_end(start)
// Elapsed time should be non-negative
inspect(elapsed >= 0.0, content="true")
}
///|
test "bench - keep stores value" {
let b = Bench()
@test.assert_eq(b.summaries.length(), 0)
b.keep(123)
@test.assert_eq(b.summaries.length(), 0)
b.keep(456)
@test.assert_eq(b.summaries.length(), 0)
}