@@ -6,12 +6,15 @@ package testing
6
6
7
7
import (
8
8
"context"
9
+ "crypto/rand"
10
+ "encoding/hex"
11
+ "encoding/json"
9
12
"flag"
10
13
"fmt"
11
- "github.com/AvalancheHQ/codspeed-go/testing/internal/sysinfo"
12
14
"io"
13
15
"math"
14
16
"os"
17
+ "path/filepath"
15
18
"runtime"
16
19
"slices"
17
20
"strconv"
@@ -20,6 +23,8 @@ import (
20
23
"sync/atomic"
21
24
"time"
22
25
"unicode"
26
+
27
+ "github.com/AvalancheHQ/codspeed-go/testing/internal/sysinfo"
23
28
)
24
29
25
30
func initBenchmarkFlags () {
@@ -137,26 +142,27 @@ type B struct {
137
142
// a call to [B.StopTimer].
138
143
func (b * B ) StartTimer () {
139
144
if ! b .timerOn {
140
- runtime .ReadMemStats (& memStats )
141
- b .startAllocs = memStats .Mallocs
142
- b .startBytes = memStats .TotalAlloc
145
+ // runtime.ReadMemStats(&memStats)
146
+ // b.startAllocs = memStats.Mallocs
147
+ // b.startBytes = memStats.TotalAlloc
143
148
b .start = highPrecisionTimeNow ()
144
149
b .timerOn = true
145
- b .loop .i &^= loopPoisonTimer
150
+ // b.loop.i &^= loopPoisonTimer
146
151
}
147
152
}
148
153
149
154
// StopTimer stops timing a test. This can be used to pause the timer
150
155
// while performing steps that you don't want to measure.
151
156
func (b * B ) StopTimer () {
152
157
if b .timerOn {
158
+ b .codspeedTimePerRoundNs = append (b .codspeedTimePerRoundNs , highPrecisionTimeSince (b .start ))
153
159
b .duration += highPrecisionTimeSince (b .start )
154
- runtime .ReadMemStats (& memStats )
155
- b .netAllocs += memStats .Mallocs - b .startAllocs
156
- b .netBytes += memStats .TotalAlloc - b .startBytes
160
+ // runtime.ReadMemStats(&memStats)
161
+ // b.netAllocs += memStats.Mallocs - b.startAllocs
162
+ // b.netBytes += memStats.TotalAlloc - b.startBytes
157
163
b .timerOn = false
158
164
// If we hit B.Loop with the timer stopped, fail.
159
- b .loop .i |= loopPoisonTimer
165
+ // b.loop.i |= loopPoisonTimer
160
166
}
161
167
}
162
168
@@ -344,18 +350,35 @@ func (b *B) launch() {
344
350
b .runN (b .benchTime .n )
345
351
}
346
352
} else {
347
- d := b .benchTime .d
348
- for n := int64 (1 ); ! b .failed && b .duration < d && n < 1e9 ; {
353
+ warmupD := time .Millisecond * 500
354
+ warmupN := int64 (1 )
355
+ for n := int64 (1 ); ! b .failed && b .duration < warmupD && n < 1e9 ; {
349
356
last := n
350
357
// Predict required iterations.
351
- goalns := d .Nanoseconds ()
358
+ goalns := warmupD .Nanoseconds ()
352
359
prevIters := int64 (b .N )
353
360
n = int64 (predictN (goalns , prevIters , b .duration .Nanoseconds (), last ))
354
361
b .runN (int (n ))
362
+ warmupN = n
363
+ }
364
+
365
+ // Reset the fields from the warmup run
366
+ b .codspeedItersPerRound = make ([]int64 , 0 )
367
+ b .codspeedTimePerRoundNs = make ([]time.Duration , 0 )
368
+
369
+ // Final run:
370
+ benchD := time .Second * 5
371
+ benchN := predictN (benchD .Nanoseconds (), int64 (b .N ), b .duration .Nanoseconds (), warmupN )
372
+ rounds := 100 // TODO: Compute the rounds in a better way
373
+ roundN := benchN / int (rounds )
374
+
375
+ for range rounds {
376
+ b .runN (int (roundN ))
377
+ b .codspeedItersPerRound = append (b .codspeedItersPerRound , int64 (roundN ))
355
378
}
356
379
}
357
380
}
358
- b .result = BenchmarkResult {b .N , b .duration , b .bytes , b .netAllocs , b .netBytes , b .extra }
381
+ b .result = BenchmarkResult {b .N , b .duration , b .bytes , b .netAllocs , b .netBytes , b .codspeedTimePerRoundNs , b . codspeedItersPerRound , b . extra }
359
382
}
360
383
361
384
// Elapsed returns the measured elapsed time of the benchmark.
@@ -408,14 +431,16 @@ func (b *B) stopOrScaleBLoop() bool {
408
431
panic ("loop iteration target overflow" )
409
432
}
410
433
b .loop .i ++
434
+
435
+ b .StartTimer ()
411
436
return true
412
437
}
413
438
414
439
func (b * B ) loopSlowPath () bool {
415
440
// Consistency checks
416
- if ! b .timerOn {
417
- b .Fatal ("B.Loop called with timer stopped" )
418
- }
441
+ // if !b.timerOn {
442
+ // b.Fatal("B.Loop called with timer stopped")
443
+ // }
419
444
if b .loop .i & loopPoisonMask != 0 {
420
445
panic (fmt .Sprintf ("unknown loop stop condition: %#x" , b .loop .i ))
421
446
}
@@ -428,21 +453,17 @@ func (b *B) loopSlowPath() bool {
428
453
// Within a b.Loop loop, we don't use b.N (to avoid confusion).
429
454
b .N = 0
430
455
b .loop .i ++
456
+
457
+ b .codspeedItersPerRound = make ([]int64 , 0 )
458
+ b .codspeedTimePerRoundNs = make ([]time.Duration , 0 )
459
+
431
460
b .ResetTimer ()
461
+ b .StartTimer ()
432
462
return true
433
463
}
434
464
// Handles fixed iterations case
435
465
if b .benchTime .n > 0 {
436
- if b .loop .n < uint64 (b .benchTime .n ) {
437
- b .loop .n = uint64 (b .benchTime .n )
438
- b .loop .i ++
439
- return true
440
- }
441
- b .StopTimer ()
442
- // Commit iteration count
443
- b .N = int (b .loop .n )
444
- b .loop .done = true
445
- return false
466
+ panic ("not supported with codspeed" )
446
467
}
447
468
// Handles fixed time case
448
469
return b .stopOrScaleBLoop ()
@@ -482,6 +503,7 @@ func (b *B) loopSlowPath() bool {
482
503
// whereas b.N-based benchmarks must run the benchmark function (and any
483
504
// associated setup and cleanup) several times.
484
505
func (b * B ) Loop () bool {
506
+ b .StopTimer ()
485
507
// This is written such that the fast path is as fast as possible and can be
486
508
// inlined.
487
509
//
@@ -496,6 +518,7 @@ func (b *B) Loop() bool {
496
518
// path can do consistency checks and fail.
497
519
if b .loop .i < b .loop .n {
498
520
b .loop .i ++
521
+ b .StartTimer ()
499
522
return true
500
523
}
501
524
return b .loopSlowPath ()
@@ -522,6 +545,9 @@ type BenchmarkResult struct {
522
545
MemAllocs uint64 // The total number of memory allocations.
523
546
MemBytes uint64 // The total number of bytes allocated.
524
547
548
+ CodspeedTimePerRoundNs []time.Duration
549
+ CodspeedItersPerRound []int64
550
+
525
551
// Extra records additional metrics reported by ReportMetric.
526
552
Extra map [string ]float64
527
553
}
@@ -753,6 +779,58 @@ func (s *benchState) processBench(b *B) {
753
779
continue
754
780
}
755
781
results := r .String ()
782
+
783
+ // ############################################################################################
784
+ // START CODSPEED
785
+ type RawResults struct {
786
+ BenchmarkName string `json:"benchmark_name"`
787
+ Pid int `json:"pid"`
788
+ CodspeedTimePerRoundNs []time.Duration `json:"codspeed_time_per_round_ns"`
789
+ CodspeedItersPerRound []int64 `json:"codspeed_iters_per_round"`
790
+ }
791
+ rawResults := RawResults {
792
+ BenchmarkName : benchName ,
793
+ Pid : os .Getpid (),
794
+ CodspeedTimePerRoundNs : r .CodspeedTimePerRoundNs ,
795
+ CodspeedItersPerRound : r .CodspeedItersPerRound ,
796
+ }
797
+
798
+ codspeedProfileFolder := os .Getenv ("CODSPEED_PROFILE_FOLDER" )
799
+ if codspeedProfileFolder == "" {
800
+ panic ("CODSPEED_PROFILE_FOLDER environment variable is not set" )
801
+ }
802
+ if err := os .MkdirAll (filepath .Join (codspeedProfileFolder , "raw_results" ), 0755 ); err != nil {
803
+ fmt .Fprintf (os .Stderr , "failed to create raw results directory: %v\n " , err )
804
+ continue
805
+ }
806
+ // Generate random filename to avoid any overwrites
807
+ randomBytes := make ([]byte , 16 )
808
+ if _ , err := rand .Read (randomBytes ); err != nil {
809
+ fmt .Fprintf (os .Stderr , "failed to generate random filename: %v\n " , err )
810
+ continue
811
+ }
812
+ rawResultsFile := filepath .Join (codspeedProfileFolder , "raw_results" , fmt .Sprintf ("%s.json" , hex .EncodeToString (randomBytes )))
813
+ file , err := os .Create (rawResultsFile )
814
+ if err != nil {
815
+ fmt .Fprintf (os .Stderr , "failed to create raw results file: %v\n " , err )
816
+ continue
817
+ }
818
+ output , err := json .MarshalIndent (rawResults , "" , " " )
819
+ if err != nil {
820
+ fmt .Fprintf (os .Stderr , "failed to marshal raw results: %v\n " , err )
821
+ file .Close ()
822
+ continue
823
+ }
824
+ // FIXME: Don't overwrite the file if it already exists
825
+ if _ , err := file .Write (output ); err != nil {
826
+ fmt .Fprintf (os .Stderr , "failed to write raw results: %v\n " , err )
827
+ file .Close ()
828
+ continue
829
+ }
830
+ defer file .Close ()
831
+ // END CODSPEED
832
+ // ############################################################################################
833
+
756
834
if b .chatty != nil {
757
835
fmt .Fprintf (b .w , "%-*s\t " , s .maxLen , benchName )
758
836
}
0 commit comments