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  }