1// Copyright 2020 The Marl Authors 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// https://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15// Package bench provides types and methods for parsing Google benchmark results. 16package bench 17 18import ( 19 "encoding/json" 20 "errors" 21 "fmt" 22 "regexp" 23 "strconv" 24 "strings" 25 "time" 26) 27 28// Test holds the results of a single benchmark test. 29type Test struct { 30 Name string 31 NumTasks uint 32 NumThreads uint 33 Duration time.Duration 34 Iterations uint 35} 36 37var testVarRE = regexp.MustCompile(`([\w])+:([0-9]+)`) 38 39func (t *Test) parseName() { 40 for _, match := range testVarRE.FindAllStringSubmatch(t.Name, -1) { 41 if len(match) != 3 { 42 continue 43 } 44 n, err := strconv.Atoi(match[2]) 45 if err != nil { 46 continue 47 } 48 switch match[1] { 49 case "threads": 50 t.NumThreads = uint(n) 51 case "tasks": 52 t.NumTasks = uint(n) 53 } 54 } 55} 56 57// Benchmark holds a set of benchmark test results. 58type Benchmark struct { 59 Tests []Test 60} 61 62// Parse parses the benchmark results from the string s. 63// Parse will handle the json and 'console' formats. 64func Parse(s string) (Benchmark, error) { 65 type Parser = func(s string) (Benchmark, error) 66 for _, parser := range []Parser{parseConsole, parseJSON} { 67 b, err := parser(s) 68 switch err { 69 case nil: 70 return b, nil 71 case errWrongFormat: 72 break 73 default: 74 return Benchmark{}, err 75 } 76 } 77 78 return Benchmark{}, errors.New("Unrecognised file format") 79} 80 81var errWrongFormat = errors.New("Wrong format") 82var consoleLineRE = regexp.MustCompile(`([\w/:]+)\s+([0-9]+(?:.[0-9]+)?) ns\s+[0-9]+(?:.[0-9]+) ns\s+([0-9]+)`) 83 84func parseConsole(s string) (Benchmark, error) { 85 blocks := strings.Split(s, "------------------------------------------------------------------------------------------") 86 if len(blocks) != 3 { 87 return Benchmark{}, errWrongFormat 88 } 89 90 lines := strings.Split(blocks[2], "\n") 91 b := Benchmark{ 92 Tests: make([]Test, 0, len(lines)), 93 } 94 for _, line := range lines { 95 if len(line) == 0 { 96 continue 97 } 98 matches := consoleLineRE.FindStringSubmatch(line) 99 if len(matches) != 4 { 100 return Benchmark{}, fmt.Errorf("Unable to parse the line:\n" + line) 101 } 102 ns, err := strconv.ParseFloat(matches[2], 64) 103 if err != nil { 104 return Benchmark{}, fmt.Errorf("Unable to parse the duration: " + matches[2]) 105 } 106 iterations, err := strconv.Atoi(matches[3]) 107 if err != nil { 108 return Benchmark{}, fmt.Errorf("Unable to parse the number of iterations: " + matches[3]) 109 } 110 111 t := Test{ 112 Name: matches[1], 113 Duration: time.Nanosecond * time.Duration(ns), 114 Iterations: uint(iterations), 115 } 116 t.parseName() 117 b.Tests = append(b.Tests, t) 118 } 119 return b, nil 120} 121 122func parseJSON(s string) (Benchmark, error) { 123 type T struct { 124 Name string `json:"name"` 125 Iterations uint `json:"iterations"` 126 Time float64 `json:"real_time"` 127 } 128 type B struct { 129 Tests []T `json:"benchmarks"` 130 } 131 b := B{} 132 d := json.NewDecoder(strings.NewReader(s)) 133 if err := d.Decode(&b); err != nil { 134 return Benchmark{}, err 135 } 136 137 out := Benchmark{ 138 Tests: make([]Test, len(b.Tests)), 139 } 140 for i, test := range b.Tests { 141 t := Test{ 142 Name: test.Name, 143 Duration: time.Nanosecond * time.Duration(int64(test.Time)), 144 Iterations: test.Iterations, 145 } 146 t.parseName() 147 out.Tests[i] = t 148 } 149 150 return out, nil 151} 152