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 default: 73 return Benchmark{}, err 74 } 75 } 76 77 return Benchmark{}, errors.New("Unrecognised file format") 78} 79 80var errWrongFormat = errors.New("Wrong format") 81var consoleLineRE = regexp.MustCompile(`([\w/:]+)\s+([0-9]+(?:.[0-9e+]+)?) ns\s+[0-9]+(?:.[0-9e+]+) ns\s+([0-9]+)`) 82 83func parseConsole(s string) (Benchmark, error) { 84 blocks := strings.Split(s, "--------------------------------------------------------------------------------------------------------") 85 if len(blocks) != 3 { 86 return Benchmark{}, errWrongFormat 87 } 88 89 lines := strings.Split(blocks[2], "\n") 90 b := Benchmark{ 91 Tests: make([]Test, 0, len(lines)), 92 } 93 for _, line := range lines { 94 if len(line) == 0 { 95 continue 96 } 97 matches := consoleLineRE.FindStringSubmatch(line) 98 if len(matches) != 4 { 99 return Benchmark{}, fmt.Errorf("Unable to parse the line:\n" + line) 100 } 101 ns, err := strconv.ParseFloat(matches[2], 64) 102 if err != nil { 103 return Benchmark{}, fmt.Errorf("Unable to parse the duration: " + matches[2]) 104 } 105 iterations, err := strconv.Atoi(matches[3]) 106 if err != nil { 107 return Benchmark{}, fmt.Errorf("Unable to parse the number of iterations: " + matches[3]) 108 } 109 110 t := Test{ 111 Name: matches[1], 112 Duration: time.Nanosecond * time.Duration(ns), 113 Iterations: uint(iterations), 114 } 115 t.parseName() 116 b.Tests = append(b.Tests, t) 117 } 118 return b, nil 119} 120 121func parseJSON(s string) (Benchmark, error) { 122 type T struct { 123 Name string `json:"name"` 124 Iterations uint `json:"iterations"` 125 Time float64 `json:"real_time"` 126 } 127 type B struct { 128 Tests []T `json:"benchmarks"` 129 } 130 b := B{} 131 d := json.NewDecoder(strings.NewReader(s)) 132 if err := d.Decode(&b); err != nil { 133 return Benchmark{}, err 134 } 135 136 out := Benchmark{ 137 Tests: make([]Test, len(b.Tests)), 138 } 139 for i, test := range b.Tests { 140 t := Test{ 141 Name: test.Name, 142 Duration: time.Nanosecond * time.Duration(int64(test.Time)), 143 Iterations: test.Iterations, 144 } 145 t.parseName() 146 out.Tests[i] = t 147 } 148 149 return out, nil 150} 151