Skip to content

Commit 8ee14f6

Browse files
committed
feat(testing): add benchmark markers
1 parent 944050f commit 8ee14f6

File tree

2 files changed

+88
-14
lines changed

2 files changed

+88
-14
lines changed

testing/capi/instrument-hooks.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,18 @@ typedef struct instruments_root_InstrumentHooks__547 InstrumentHooks;
77
*/
88
import "C"
99
import (
10+
"os"
1011
"runtime"
1112
"unsafe"
1213
)
1314

15+
const (
16+
MarkerTypeSampleStart = 0
17+
MarkerTypeSampleEnd = 1
18+
MarkerTypeBenchmarkStart = 2
19+
MarkerTypeBenchmarkEnd = 3
20+
)
21+
1422
// This will be set in the go-runner
1523
var integrationVersion = "dev"
1624

@@ -79,3 +87,16 @@ func (i *InstrumentHooks) IsInstrumented() bool {
7987
}
8088
return bool(C.instrument_hooks_is_instrumented(i.hooks))
8189
}
90+
91+
func CurrentTimestamp() uint64 {
92+
return uint64(C.instrument_hooks_current_timestamp())
93+
}
94+
95+
func (i *InstrumentHooks) AddBenchmarkTimestamps(startTimestamp, endTimestamp uint64) {
96+
if i.hooks == nil {
97+
return
98+
}
99+
pid := uint32(os.Getpid())
100+
C.instrument_hooks_add_marker(i.hooks, C.uint32_t(pid), C.uint8_t(MarkerTypeBenchmarkStart), C.uint64_t(startTimestamp))
101+
C.instrument_hooks_add_marker(i.hooks, C.uint32_t(pid), C.uint8_t(MarkerTypeBenchmarkEnd), C.uint64_t(endTimestamp))
102+
}

testing/testing/benchmark.go

Lines changed: 67 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,10 @@ type codspeed struct {
9090

9191
codspeedTimePerRoundNs []time.Duration
9292
codspeedItersPerRound []int64
93+
94+
startTimestamp uint64
95+
startTimestamps []uint64
96+
stopTimestamps []uint64
9397
}
9498

9599
// B is a type passed to [Benchmark] functions to manage benchmark
@@ -150,7 +154,7 @@ type B struct {
150154
// StartTimer starts timing a test. This function is called automatically
151155
// before a benchmark starts, but it can also be used to resume timing after
152156
// a call to [B.StopTimer].
153-
func (b *B) StartTimer() {
157+
func (b *B) StartTimerWithoutMarker() {
154158
if !b.timerOn {
155159
// runtime.ReadMemStats(&memStats)
156160
// b.startAllocs = memStats.Mallocs
@@ -161,9 +165,19 @@ func (b *B) StartTimer() {
161165
}
162166
}
163167

168+
func (b *B) StartTimer() {
169+
timerOn := b.timerOn
170+
171+
b.StartTimerWithoutMarker()
172+
173+
if !timerOn {
174+
b.startTimestamp = capi.CurrentTimestamp()
175+
}
176+
}
177+
164178
// StopTimer stops timing a test. This can be used to pause the timer
165179
// while performing steps that you don't want to measure.
166-
func (b *B) StopTimer() {
180+
func (b *B) StopTimerWithoutMarker() {
167181
if b.timerOn {
168182
timeSinceStart := highPrecisionTimeSince(b.start)
169183
b.duration += timeSinceStart
@@ -183,6 +197,25 @@ func (b *B) StopTimer() {
183197
}
184198
}
185199

200+
func (b *B) StopTimer() {
201+
endTimestamp := capi.CurrentTimestamp()
202+
timerOn := b.timerOn
203+
204+
b.StopTimerWithoutMarker()
205+
206+
if timerOn {
207+
if b.startTimestamp >= endTimestamp {
208+
// This should never happen, unless we have a bug in the timer logic.
209+
panic(fmt.Sprintf("Invalid benchmark timestamps: start timestamp (%d) is greater than or equal to end timestamp (%d)", b.startTimestamp, endTimestamp))
210+
}
211+
b.startTimestamps = append(b.startTimestamps, b.startTimestamp)
212+
b.stopTimestamps = append(b.stopTimestamps, endTimestamp)
213+
214+
// Reset to prevent accidental reuse
215+
b.startTimestamp = 0
216+
}
217+
}
218+
186219
// ResetTimer zeroes the elapsed benchmark time and memory allocation counters
187220
// and deletes user-reported metrics.
188221
// It does not affect whether the timer is running.
@@ -199,10 +232,29 @@ func (b *B) ResetTimer() {
199232
b.startAllocs = memStats.Mallocs
200233
b.startBytes = memStats.TotalAlloc
201234
b.start = highPrecisionTimeNow()
235+
236+
b.startTimestamp = capi.CurrentTimestamp()
202237
}
203238
b.duration = 0
204239
b.netAllocs = 0
205240
b.netBytes = 0
241+
242+
// Clear CodSpeed timestamp data
243+
b.codspeedItersPerRound = b.codspeedItersPerRound[:0]
244+
b.codspeedTimePerRoundNs = b.codspeedTimePerRoundNs[:0]
245+
b.startTimestamps = b.startTimestamps[:0]
246+
b.stopTimestamps = b.stopTimestamps[:0]
247+
}
248+
249+
func (b *B) sendAccumulatedTimestamps() {
250+
for i := 0; i < len(b.startTimestamps); i++ {
251+
b.instrument_hooks.AddBenchmarkTimestamps(
252+
b.startTimestamps[i],
253+
b.stopTimestamps[i],
254+
)
255+
}
256+
b.startTimestamps = b.startTimestamps[:0]
257+
b.stopTimestamps = b.stopTimestamps[:0]
206258
}
207259

208260
// SetBytes records the number of bytes processed in a single operation.
@@ -387,8 +439,7 @@ func (b *B) launch() {
387439
}
388440

389441
// Reset the fields from the warmup run
390-
b.codspeedItersPerRound = make([]int64, 0)
391-
b.codspeedTimePerRoundNs = make([]time.Duration, 0)
442+
b.ResetTimer()
392443

393444
// Final run:
394445
benchD := b.benchTime.d
@@ -413,6 +464,7 @@ func (b *B) launch() {
413464
b.runN(int(roundN))
414465
}
415466
b.codspeed.instrument_hooks.StopBenchmark()
467+
b.sendAccumulatedTimestamps()
416468
}
417469
}
418470
b.result = BenchmarkResult{b.N, b.duration, b.bytes, b.netAllocs, b.netBytes, b.codspeedTimePerRoundNs, b.codspeedItersPerRound, b.extra}
@@ -452,8 +504,10 @@ func (b *B) stopOrScaleBLoop() bool {
452504
t := b.Elapsed()
453505
if t >= b.benchTime.d {
454506
// Stop the timer so we don't count cleanup time
455-
b.StopTimer()
507+
b.StopTimerWithoutMarker()
456508
b.codspeed.instrument_hooks.StopBenchmark()
509+
b.sendAccumulatedTimestamps()
510+
457511
// Commit iteration count
458512
b.N = int(b.loop.n)
459513
b.loop.done = true
@@ -470,7 +524,7 @@ func (b *B) stopOrScaleBLoop() bool {
470524
}
471525
b.loop.i++
472526

473-
b.StartTimer()
527+
b.StartTimerWithoutMarker()
474528
return true
475529
}
476530

@@ -492,24 +546,23 @@ func (b *B) loopSlowPath() bool {
492546
b.N = 0
493547
b.loop.i++
494548

495-
b.codspeedItersPerRound = make([]int64, 0)
496-
b.codspeedTimePerRoundNs = make([]time.Duration, 0)
497-
498549
b.codspeed.instrument_hooks.StartBenchmark()
499550
b.ResetTimer()
500-
b.StartTimer()
551+
b.StartTimerWithoutMarker()
501552
return true
502553
}
503554
// Handles fixed iterations case
504555
if b.benchTime.n > 0 {
505556
if b.loop.n < uint64(b.benchTime.n) {
506557
b.loop.n = uint64(b.benchTime.n)
507558
b.loop.i++
508-
b.StartTimer()
559+
b.StartTimerWithoutMarker()
509560
return true
510561
}
511-
b.StopTimer()
562+
b.StopTimerWithoutMarker()
512563
b.codspeed.instrument_hooks.StopBenchmark()
564+
b.sendAccumulatedTimestamps()
565+
513566
// Commit iteration count
514567
b.N = int(b.loop.n)
515568
b.loop.done = true
@@ -553,7 +606,7 @@ func (b *B) loopSlowPath() bool {
553606
// whereas b.N-based benchmarks must run the benchmark function (and any
554607
// associated setup and cleanup) several times.
555608
func (b *B) Loop() bool {
556-
b.StopTimer()
609+
b.StopTimerWithoutMarker()
557610
// This is written such that the fast path is as fast as possible and can be
558611
// inlined.
559612
//
@@ -568,7 +621,7 @@ func (b *B) Loop() bool {
568621
// path can do consistency checks and fail.
569622
if b.loop.i < b.loop.n {
570623
b.loop.i++
571-
b.StartTimer()
624+
b.StartTimerWithoutMarker()
572625
return true
573626
}
574627
return b.loopSlowPath()

0 commit comments

Comments
 (0)