| // Copyright 2020 The Marl Authors | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // https://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| // Package bench provides types and methods for parsing Google benchmark results. | |
| package bench | |
| import ( | |
| "encoding/json" | |
| "errors" | |
| "fmt" | |
| "regexp" | |
| "strconv" | |
| "strings" | |
| "time" | |
| ) | |
| // Test holds the results of a single benchmark test. | |
| type Test struct { | |
| Name string | |
| NumTasks uint | |
| NumThreads uint | |
| Duration time.Duration | |
| Iterations uint | |
| } | |
| var testVarRE = regexp.MustCompile(`([\w])+:([0-9]+)`) | |
| func (t *Test) parseName() { | |
| for _, match := range testVarRE.FindAllStringSubmatch(t.Name, -1) { | |
| if len(match) != 3 { | |
| continue | |
| } | |
| n, err := strconv.Atoi(match[2]) | |
| if err != nil { | |
| continue | |
| } | |
| switch match[1] { | |
| case "threads": | |
| t.NumThreads = uint(n) | |
| case "tasks": | |
| t.NumTasks = uint(n) | |
| } | |
| } | |
| } | |
| // Benchmark holds a set of benchmark test results. | |
| type Benchmark struct { | |
| Tests []Test | |
| } | |
| // Parse parses the benchmark results from the string s. | |
| // Parse will handle the json and 'console' formats. | |
| func Parse(s string) (Benchmark, error) { | |
| type Parser = func(s string) (Benchmark, error) | |
| for _, parser := range []Parser{parseConsole, parseJSON} { | |
| b, err := parser(s) | |
| switch err { | |
| case nil: | |
| return b, nil | |
| case errWrongFormat: | |
| default: | |
| return Benchmark{}, err | |
| } | |
| } | |
| return Benchmark{}, errors.New("Unrecognised file format") | |
| } | |
| var errWrongFormat = errors.New("Wrong format") | |
| var consoleLineRE = regexp.MustCompile(`([\w/:]+)\s+([0-9]+(?:.[0-9e+]+)?) ns\s+[0-9]+(?:.[0-9e+]+) ns\s+([0-9]+)`) | |
| func parseConsole(s string) (Benchmark, error) { | |
| blocks := strings.Split(s, "--------------------------------------------------------------------------------------------------------") | |
| if len(blocks) != 3 { | |
| return Benchmark{}, errWrongFormat | |
| } | |
| lines := strings.Split(blocks[2], "\n") | |
| b := Benchmark{ | |
| Tests: make([]Test, 0, len(lines)), | |
| } | |
| for _, line := range lines { | |
| if len(line) == 0 { | |
| continue | |
| } | |
| matches := consoleLineRE.FindStringSubmatch(line) | |
| if len(matches) != 4 { | |
| return Benchmark{}, fmt.Errorf("Unable to parse the line:\n" + line) | |
| } | |
| ns, err := strconv.ParseFloat(matches[2], 64) | |
| if err != nil { | |
| return Benchmark{}, fmt.Errorf("Unable to parse the duration: " + matches[2]) | |
| } | |
| iterations, err := strconv.Atoi(matches[3]) | |
| if err != nil { | |
| return Benchmark{}, fmt.Errorf("Unable to parse the number of iterations: " + matches[3]) | |
| } | |
| t := Test{ | |
| Name: matches[1], | |
| Duration: time.Nanosecond * time.Duration(ns), | |
| Iterations: uint(iterations), | |
| } | |
| t.parseName() | |
| b.Tests = append(b.Tests, t) | |
| } | |
| return b, nil | |
| } | |
| func parseJSON(s string) (Benchmark, error) { | |
| type T struct { | |
| Name string `json:"name"` | |
| Iterations uint `json:"iterations"` | |
| Time float64 `json:"real_time"` | |
| } | |
| type B struct { | |
| Tests []T `json:"benchmarks"` | |
| } | |
| b := B{} | |
| d := json.NewDecoder(strings.NewReader(s)) | |
| if err := d.Decode(&b); err != nil { | |
| return Benchmark{}, err | |
| } | |
| out := Benchmark{ | |
| Tests: make([]Test, len(b.Tests)), | |
| } | |
| for i, test := range b.Tests { | |
| t := Test{ | |
| Name: test.Name, | |
| Duration: time.Nanosecond * time.Duration(int64(test.Time)), | |
| Iterations: test.Iterations, | |
| } | |
| t.parseName() | |
| out.Tests[i] = t | |
| } | |
| return out, nil | |
| } |