• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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