gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/tools/parsers/go_parser.go (about) 1 // Copyright 2020 The gVisor 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 // http://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 parsers holds parsers to parse Benchmark test output. 16 // 17 // Parsers parse Benchmark test output and place it in BigQuery 18 // structs for sending to BigQuery databases. 19 package parsers 20 21 import ( 22 "fmt" 23 "strconv" 24 "strings" 25 26 "gvisor.dev/gvisor/test/benchmarks/tools" 27 "gvisor.dev/gvisor/tools/bigquery" 28 ) 29 30 // ParseOutput expects golang benchmark output and returns a struct formatted 31 // for BigQuery. 32 func ParseOutput(output string, name string, official bool) (*bigquery.Suite, error) { 33 suite := bigquery.NewSuite(name, official) 34 lines := strings.Split(output, "\n") 35 for _, line := range lines { 36 bm, err := parseLine(line) 37 if err != nil { 38 return nil, fmt.Errorf("failed to parse line '%s': %v", line, err) 39 } 40 if bm != nil { 41 suite.Benchmarks = append(suite.Benchmarks, bm) 42 } 43 } 44 return suite, nil 45 } 46 47 // parseLine handles parsing a benchmark line into a bigquery.Benchmark. 48 // 49 // Example: "BenchmarkRuby/server_threads.1-6 1 1397875880 ns/op 140 requests_per_second.QPS" 50 // 51 // This function will return the following benchmark: 52 // 53 // *bigquery.Benchmark{ 54 // Name: BenchmarkRuby 55 // []*bigquery.Condition{ 56 // {Name: GOMAXPROCS, 6} 57 // {Name: server_threads, 1} 58 // } 59 // []*bigquery.Metric{ 60 // {Name: ns/op, Unit: ns/op, Sample: 1397875880} 61 // {Name: requests_per_second, Unit: QPS, Sample: 140 } 62 // } 63 // } 64 func parseLine(line string) (*bigquery.Benchmark, error) { 65 fields := strings.Fields(line) 66 67 // Check if this line is a Benchmark line. Otherwise ignore the line. 68 if len(fields) < 2 || !strings.HasPrefix(fields[0], "Benchmark") { 69 return nil, nil 70 } 71 72 iters, err := strconv.Atoi(fields[1]) 73 if err != nil { 74 return nil, fmt.Errorf("expecting number of runs, got %s: %v", fields[1], err) 75 } 76 77 name, params, err := parseNameParams(fields[0]) 78 if err != nil { 79 return nil, fmt.Errorf("parse name/params: %v", err) 80 } 81 82 bm := bigquery.NewBenchmark(name, iters) 83 for _, p := range params { 84 bm.AddCondition(p.Name, p.Value) 85 } 86 87 for i := 1; i < len(fields)/2; i++ { 88 value := fields[2*i] 89 metric := fields[2*i+1] 90 if err := makeMetric(bm, value, metric); err != nil { 91 return nil, fmt.Errorf("makeMetric on metric %q value: %s: %v", metric, value, err) 92 } 93 } 94 return bm, nil 95 } 96 97 // parseNameParams parses the Name, GOMAXPROCS, and Params from the test. 98 // Field here should be of the format TESTNAME/PARAMS-GOMAXPROCS. 99 // Parameters will be separated by a "/" with individual params being 100 // "name.value". 101 func parseNameParams(field string) (string, []*tools.Parameter, error) { 102 var params []*tools.Parameter 103 // Remove GOMAXPROCS from end. 104 maxIndex := strings.LastIndex(field, "-") 105 if maxIndex < 0 { 106 return "", nil, fmt.Errorf("GOMAXPROCS not found: %s", field) 107 } 108 maxProcs := field[maxIndex+1:] 109 params = append(params, &tools.Parameter{ 110 Name: "GOMAXPROCS", 111 Value: maxProcs, 112 }) 113 114 remainder := field[0:maxIndex] 115 index := strings.Index(remainder, "/") 116 if index == -1 { 117 return remainder, params, nil 118 } 119 120 name := remainder[0:index] 121 p := remainder[index+1:] 122 123 ps, err := tools.NameToParameters(p) 124 if err != nil { 125 return "", nil, fmt.Errorf("NameToParameters %s: %v", field, err) 126 } 127 params = append(params, ps...) 128 return name, params, nil 129 } 130 131 // makeMetric parses metrics and adds them to the passed Benchmark. 132 func makeMetric(bm *bigquery.Benchmark, value, metric string) error { 133 switch metric { 134 // Ignore most output from golang benchmarks. 135 case "MB/s", "B/op", "allocs/op": 136 return nil 137 case "ns/op": 138 val, err := strconv.ParseFloat(value, 64) 139 if err != nil { 140 return fmt.Errorf("ParseFloat %s: %v", value, err) 141 } 142 bm.AddMetric(metric /*metric name*/, metric /*unit*/, val /*sample*/) 143 default: 144 m, err := tools.ParseCustomMetric(value, metric) 145 if err != nil { 146 return fmt.Errorf("ParseCustomMetric %s: %v ", metric, err) 147 } 148 bm.AddMetric(m.Name, m.Unit, m.Sample) 149 } 150 return nil 151 }