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  }