github.com/haraldrudell/parl@v0.4.176/ptesting/sub-benchmark-logger.go (about) 1 /* 2 © 2023–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/) 3 ISC License 4 */ 5 6 package ptesting 7 8 import ( 9 "fmt" 10 "os" 11 "strconv" 12 "strings" 13 "testing" 14 "time" 15 16 "github.com/haraldrudell/parl" 17 "github.com/haraldrudell/parl/ptime" 18 ) 19 20 const ( 21 OperationsPerSecond = "op/s" 22 NanosecondsPerOp = "ns/op" 23 SecondsPerOp = "sec/op" 24 ) 25 26 type SubBenchLogger struct { 27 b *testing.B 28 m map[string]*SubBench 29 LastSubBench *SubBench 30 T0 time.Time 31 } 32 33 // NewSubBenchLogger returns an object to measure latency of each sub-benchmark b.N invocation 34 // 35 // Usage: 36 // 37 // func BenchmarkXxx(b *testing.B) { 38 // benchLogger := NewSubBenchLogger(b) 39 // defer benchLogger.Log() 40 // b.Run(…, func(b *testing.B) { 41 // defer benchLogger.Invo(b) 42 func NewSubBenchLogger(b *testing.B) (sbl *SubBenchLogger) { 43 return &SubBenchLogger{ 44 b: b, 45 m: make(map[string]*SubBench), 46 } 47 } 48 49 func (s *SubBenchLogger) Invo(b *testing.B) { 50 var subBench *SubBench 51 if subBench = s.m[b.Name()]; subBench == nil { 52 subBench = &SubBench{Name: b.Name()} 53 s.m[subBench.Name] = subBench 54 55 t1 := time.Now() 56 if s.LastSubBench != nil { 57 s.LastSubBench.D = t1.Sub(s.T0) 58 } 59 s.LastSubBench = subBench 60 s.T0 = t1 61 } 62 subBench.Runs = append(subBench.Runs, SubBenchRun{BN: b.N, Latency: b.Elapsed()}) 63 } 64 65 func (s *SubBenchLogger) Log() { 66 if len(s.m) == 0 { 67 fmt.Fprintf(os.Stderr, "%s NO sub-benchmark INVOCATIONS\n", s.b.Name()) 68 return 69 } 70 if s.LastSubBench != nil { 71 t1 := time.Now() 72 s.LastSubBench.D = t1.Sub(s.T0) 73 } 74 for _, subBench := range s.m { 75 var maxBN, secondBN string 76 length := len(subBench.Runs) 77 if length > 0 { 78 run := subBench.Runs[length-1] 79 maxBN = parl.Sprintf(" max b.N: %s %s", 80 BNString(run.BN), ptime.Duration(run.Latency), 81 ) 82 } 83 if length > 1 { 84 run := subBench.Runs[length-2] 85 secondBN = parl.Sprintf(" second b.N: %s %s", 86 BNString(run.BN), ptime.Duration(run.Latency), 87 ) 88 } 89 bNs := make([]string, length) 90 for i, run := range subBench.Runs { 91 bNs[i] = BNString(run.BN) 92 } 93 fmt.Fprintf(os.Stderr, "%s %s %s%s all b.Ns: %s\n", 94 subBench.Name, ptime.Duration(subBench.D), 95 maxBN, secondBN, strings.Join(bNs, "\x20"), 96 ) 97 } 98 } 99 100 type SubBench struct { 101 Name string 102 D time.Duration 103 Runs []SubBenchRun 104 } 105 106 type SubBenchRun struct { 107 BN int 108 Latency time.Duration 109 } 110 111 // BNString makes a b.N number readable 112 // - 1000000 → 1e6 113 // - 121293782 → 121,293,782 114 func BNString(bN int) (s string) { 115 s = strconv.Itoa(bN) 116 117 // check if form 1e9 should be used 118 if !strings.HasSuffix(s, "00") { 119 s = parl.Sprintf("%d", bN) // get string with comma-separators 120 return 121 } 122 123 exponent := 2 124 index := len(s) - exponent - 1 125 for index > 0 { 126 if s[index] != '0' { 127 break 128 } 129 exponent++ 130 index-- 131 } 132 133 s = s[:index+1] + "e" + strconv.Itoa(exponent) 134 return 135 }