3 * Copyright 2017 gRPC authors.
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
36 curStats map[string]*Stats
42 injectDone chan struct{}
45 // AddStats adds a new unnamed Stats instance to the current benchmark. You need
46 // to run benchmarks by calling RunTestMain() to inject the stats to the
47 // benchmark results. If numBuckets is not positive, the default value (16) will
48 // be used. Please note that this calls b.ResetTimer() since it may be blocked
49 // until the previous benchmark stats is printed out. So AddStats() should
50 // typically be called at the very beginning of each benchmark function.
51 func AddStats(b *testing.B, numBuckets int) *Stats {
52 return AddStatsWithName(b, "", numBuckets)
55 // AddStatsWithName adds a new named Stats instance to the current benchmark.
56 // With this, you can add multiple stats in a single benchmark. You need
57 // to run benchmarks by calling RunTestMain() to inject the stats to the
58 // benchmark results. If numBuckets is not positive, the default value (16) will
59 // be used. Please note that this calls b.ResetTimer() since it may be blocked
60 // until the previous benchmark stats is printed out. So AddStatsWithName()
61 // should typically be called at the very beginning of each benchmark function.
62 func AddStatsWithName(b *testing.B, name string, numBuckets int) *Stats {
65 pc, _, _, ok := runtime.Caller(i)
67 panic("benchmark function not found")
69 p := strings.Split(runtime.FuncForPC(pc).Name(), ".")
70 benchName = p[len(p)-1]
71 if strings.HasPrefix(benchName, "run") {
75 procs := runtime.GOMAXPROCS(-1)
77 benchName = fmt.Sprintf("%s-%d", benchName, procs)
80 stats := NewStats(numBuckets)
82 if injectCond != nil {
83 // We need to wait until the previous benchmark stats is printed out.
85 for curB != nil && curBenchName != benchName {
90 curBenchName = benchName
91 curStats[name] = stats
100 // RunTestMain runs the tests with enabling injection of benchmark stats. It
101 // returns an exit code to pass to os.Exit.
102 func RunTestMain(m *testing.M) int {
104 defer stopStatsInjector()
108 // startStatsInjector starts stats injection to benchmark results.
109 func startStatsInjector() {
110 orgStdout = os.Stdout
117 injectCond = sync.NewCond(&sync.Mutex{})
118 injectDone = make(chan struct{})
120 defer close(injectDone)
122 scanner := bufio.NewScanner(r)
123 scanner.Split(splitLines)
125 injectStatsIfFinished(scanner.Text())
127 if err := scanner.Err(); err != nil {
133 // stopStatsInjector stops stats injection and restores os.Stdout.
134 func stopStatsInjector() {
138 os.Stdout = orgStdout
141 // splitLines is a split function for a bufio.Scanner that returns each line
142 // of text, teeing texts to the original stdout even before each line ends.
143 func splitLines(data []byte, eof bool) (advance int, token []byte, err error) {
144 if eof && len(data) == 0 {
148 if i := bytes.IndexByte(data, '\n'); i >= 0 {
149 orgStdout.Write(data[nextOutPos : i+1])
151 return i + 1, data[0:i], nil
154 orgStdout.Write(data[nextOutPos:])
155 nextOutPos = len(data)
158 // This is a final, non-terminated line. Return it.
159 return len(data), data, nil
165 // injectStatsIfFinished prints out the stats if the current benchmark finishes.
166 func injectStatsIfFinished(line string) {
168 defer injectCond.L.Unlock()
169 // We assume that the benchmark results start with "Benchmark".
170 if curB == nil || !strings.HasPrefix(line, "Benchmark") {
175 // Output all stats in alphabetical order.
176 names := make([]string, 0, len(curStats))
177 for name := range curStats {
178 names = append(names, name)
181 for _, name := range names {
182 stats := curStats[name]
183 // The output of stats starts with a header like "Histogram (unit: ms)"
184 // followed by statistical properties and the buckets. Add the stats name
185 // if it is a named stats and indent them as Go testing outputs.
186 lines := strings.Split(stats.String(), "\n")
187 if n := len(lines); n > 0 {
191 fmt.Fprintf(orgStdout, "--- %s%s\n", lines[0], name)
192 for _, line := range lines[1 : n-1] {
193 fmt.Fprintf(orgStdout, "\t%s\n", line)
203 // resetCurBenchStats resets the current benchmark stats.
204 func resetCurBenchStats() {
207 curStats = make(map[string]*Stats)