github.com/stackdocker/rkt@v0.10.1-0.20151109095037-1aa827478248/Godeps/_workspace/src/google.golang.org/grpc/benchmark/stats/util.go (about) 1 package stats 2 3 import ( 4 "bufio" 5 "bytes" 6 "fmt" 7 "os" 8 "runtime" 9 "sort" 10 "strings" 11 "sync" 12 "testing" 13 ) 14 15 var ( 16 curB *testing.B 17 curBenchName string 18 curStats map[string]*Stats 19 20 orgStdout *os.File 21 nextOutPos int 22 23 injectCond *sync.Cond 24 injectDone chan struct{} 25 ) 26 27 // AddStats adds a new unnamed Stats instance to the current benchmark. You need 28 // to run benchmarks by calling RunTestMain() to inject the stats to the 29 // benchmark results. If numBuckets is not positive, the default value (16) will 30 // be used. Please note that this calls b.ResetTimer() since it may be blocked 31 // until the previous benchmark stats is printed out. So AddStats() should 32 // typically be called at the very beginning of each benchmark function. 33 func AddStats(b *testing.B, numBuckets int) *Stats { 34 return AddStatsWithName(b, "", numBuckets) 35 } 36 37 // AddStatsWithName adds a new named Stats instance to the current benchmark. 38 // With this, you can add multiple stats in a single benchmark. You need 39 // to run benchmarks by calling RunTestMain() to inject the stats to the 40 // benchmark results. If numBuckets is not positive, the default value (16) will 41 // be used. Please note that this calls b.ResetTimer() since it may be blocked 42 // until the previous benchmark stats is printed out. So AddStatsWithName() 43 // should typically be called at the very beginning of each benchmark function. 44 func AddStatsWithName(b *testing.B, name string, numBuckets int) *Stats { 45 var benchName string 46 for i := 1; ; i++ { 47 pc, _, _, ok := runtime.Caller(i) 48 if !ok { 49 panic("benchmark function not found") 50 } 51 p := strings.Split(runtime.FuncForPC(pc).Name(), ".") 52 benchName = p[len(p)-1] 53 if strings.HasPrefix(benchName, "Benchmark") { 54 break 55 } 56 } 57 procs := runtime.GOMAXPROCS(-1) 58 if procs != 1 { 59 benchName = fmt.Sprintf("%s-%d", benchName, procs) 60 } 61 62 stats := NewStats(numBuckets) 63 64 if injectCond != nil { 65 // We need to wait until the previous benchmark stats is printed out. 66 injectCond.L.Lock() 67 for curB != nil && curBenchName != benchName { 68 injectCond.Wait() 69 } 70 71 curB = b 72 curBenchName = benchName 73 curStats[name] = stats 74 75 injectCond.L.Unlock() 76 } 77 78 b.ResetTimer() 79 return stats 80 } 81 82 // RunTestMain runs the tests with enabling injection of benchmark stats. It 83 // returns an exit code to pass to os.Exit. 84 func RunTestMain(m *testing.M) int { 85 startStatsInjector() 86 defer stopStatsInjector() 87 return m.Run() 88 } 89 90 // startStatsInjector starts stats injection to benchmark results. 91 func startStatsInjector() { 92 orgStdout = os.Stdout 93 r, w, _ := os.Pipe() 94 os.Stdout = w 95 nextOutPos = 0 96 97 resetCurBenchStats() 98 99 injectCond = sync.NewCond(&sync.Mutex{}) 100 injectDone = make(chan struct{}) 101 go func() { 102 defer close(injectDone) 103 104 scanner := bufio.NewScanner(r) 105 scanner.Split(splitLines) 106 for scanner.Scan() { 107 injectStatsIfFinished(scanner.Text()) 108 } 109 if err := scanner.Err(); err != nil { 110 panic(err) 111 } 112 }() 113 } 114 115 // stopStatsInjector stops stats injection and restores os.Stdout. 116 func stopStatsInjector() { 117 os.Stdout.Close() 118 <-injectDone 119 injectCond = nil 120 os.Stdout = orgStdout 121 } 122 123 // splitLines is a split function for a bufio.Scanner that returns each line 124 // of text, teeing texts to the original stdout even before each line ends. 125 func splitLines(data []byte, eof bool) (advance int, token []byte, err error) { 126 if eof && len(data) == 0 { 127 return 0, nil, nil 128 } 129 130 if i := bytes.IndexByte(data, '\n'); i >= 0 { 131 orgStdout.Write(data[nextOutPos : i+1]) 132 nextOutPos = 0 133 return i + 1, data[0:i], nil 134 } 135 136 orgStdout.Write(data[nextOutPos:]) 137 nextOutPos = len(data) 138 139 if eof { 140 // This is a final, non-terminated line. Return it. 141 return len(data), data, nil 142 } 143 144 return 0, nil, nil 145 } 146 147 // injectStatsIfFinished prints out the stats if the current benchmark finishes. 148 func injectStatsIfFinished(line string) { 149 injectCond.L.Lock() 150 defer injectCond.L.Unlock() 151 152 // We assume that the benchmark results start with the benchmark name. 153 if curB == nil || !strings.HasPrefix(line, curBenchName) { 154 return 155 } 156 157 if !curB.Failed() { 158 // Output all stats in alphabetical order. 159 names := make([]string, 0, len(curStats)) 160 for name := range curStats { 161 names = append(names, name) 162 } 163 sort.Strings(names) 164 for _, name := range names { 165 stats := curStats[name] 166 // The output of stats starts with a header like "Histogram (unit: ms)" 167 // followed by statistical properties and the buckets. Add the stats name 168 // if it is a named stats and indent them as Go testing outputs. 169 lines := strings.Split(stats.String(), "\n") 170 if n := len(lines); n > 0 { 171 if name != "" { 172 name = ": " + name 173 } 174 fmt.Fprintf(orgStdout, "--- %s%s\n", lines[0], name) 175 for _, line := range lines[1 : n-1] { 176 fmt.Fprintf(orgStdout, "\t%s\n", line) 177 } 178 } 179 } 180 } 181 182 resetCurBenchStats() 183 injectCond.Signal() 184 } 185 186 // resetCurBenchStats resets the current benchmark stats. 187 func resetCurBenchStats() { 188 curB = nil 189 curBenchName = "" 190 curStats = make(map[string]*Stats) 191 }