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  }