github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/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 "github.com/SagerNet/gvisor/test/benchmarks/tools" 27 "github.com/SagerNet/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 // *bigquery.Benchmark{ 53 // Name: BenchmarkRuby 54 // []*bigquery.Condition{ 55 // {Name: GOMAXPROCS, 6} 56 // {Name: server_threads, 1} 57 // } 58 // []*bigquery.Metric{ 59 // {Name: ns/op, Unit: ns/op, Sample: 1397875880} 60 // {Name: requests_per_second, Unit: QPS, Sample: 140 } 61 // } 62 //} 63 func parseLine(line string) (*bigquery.Benchmark, error) { 64 fields := strings.Fields(line) 65 66 // Check if this line is a Benchmark line. Otherwise ignore the line. 67 if len(fields) < 2 || !strings.HasPrefix(fields[0], "Benchmark") { 68 return nil, nil 69 } 70 71 iters, err := strconv.Atoi(fields[1]) 72 if err != nil { 73 return nil, fmt.Errorf("expecting number of runs, got %s: %v", fields[1], err) 74 } 75 76 name, params, err := parseNameParams(fields[0]) 77 if err != nil { 78 return nil, fmt.Errorf("parse name/params: %v", err) 79 } 80 81 bm := bigquery.NewBenchmark(name, iters) 82 for _, p := range params { 83 bm.AddCondition(p.Name, p.Value) 84 } 85 86 for i := 1; i < len(fields)/2; i++ { 87 value := fields[2*i] 88 metric := fields[2*i+1] 89 if err := makeMetric(bm, value, metric); err != nil { 90 return nil, fmt.Errorf("makeMetric on metric %q value: %s: %v", metric, value, err) 91 } 92 } 93 return bm, nil 94 } 95 96 // parseNameParams parses the Name, GOMAXPROCS, and Params from the test. 97 // Field here should be of the format TESTNAME/PARAMS-GOMAXPROCS. 98 // Parameters will be separated by a "/" with individual params being 99 // "name.value". 100 func parseNameParams(field string) (string, []*tools.Parameter, error) { 101 var params []*tools.Parameter 102 // Remove GOMAXPROCS from end. 103 maxIndex := strings.LastIndex(field, "-") 104 if maxIndex < 0 { 105 return "", nil, fmt.Errorf("GOMAXPROCS not found: %s", field) 106 } 107 maxProcs := field[maxIndex+1:] 108 params = append(params, &tools.Parameter{ 109 Name: "GOMAXPROCS", 110 Value: maxProcs, 111 }) 112 113 remainder := field[0:maxIndex] 114 index := strings.Index(remainder, "/") 115 if index == -1 { 116 return remainder, params, nil 117 } 118 119 name := remainder[0:index] 120 p := remainder[index+1:] 121 122 ps, err := tools.NameToParameters(p) 123 if err != nil { 124 return "", nil, fmt.Errorf("NameToParameters %s: %v", field, err) 125 } 126 params = append(params, ps...) 127 return name, params, nil 128 } 129 130 // makeMetric parses metrics and adds them to the passed Benchmark. 131 func makeMetric(bm *bigquery.Benchmark, value, metric string) error { 132 switch metric { 133 // Ignore most output from golang benchmarks. 134 case "MB/s", "B/op", "allocs/op": 135 return nil 136 case "ns/op": 137 val, err := strconv.ParseFloat(value, 64) 138 if err != nil { 139 return fmt.Errorf("ParseFloat %s: %v", value, err) 140 } 141 bm.AddMetric(metric /*metric name*/, metric /*unit*/, val /*sample*/) 142 default: 143 m, err := tools.ParseCustomMetric(value, metric) 144 if err != nil { 145 return fmt.Errorf("ParseCustomMetric %s: %v ", metric, err) 146 } 147 bm.AddMetric(m.Name, m.Unit, m.Sample) 148 } 149 return nil 150 }