github.com/jgbaldwinbrown/perf@v0.1.1/benchproc/extract.go (about)

     1  // Copyright 2022 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package benchproc
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"strings"
    11  
    12  	"golang.org/x/perf/benchfmt"
    13  )
    14  
    15  // An extractor returns some field of a benchmark result. The
    16  // result may be a view into a mutable []byte in *benchfmt.Result, so
    17  // it may change if the Result is modified.
    18  type extractor func(*benchfmt.Result) []byte
    19  
    20  // newExtractor returns a function that extracts some field of a
    21  // benchmark result.
    22  //
    23  // The key must be one of the following:
    24  //
    25  // - ".name" for the benchmark name (excluding per-benchmark
    26  // configuration).
    27  //
    28  // - ".fullname" for the full benchmark name (including per-benchmark
    29  // configuration).
    30  //
    31  // - "/{key}" for a benchmark sub-name key. This may be "/gomaxprocs"
    32  // and the extractor will normalize the name as needed.
    33  //
    34  // - Any other string is a file configuration key.
    35  func newExtractor(key string) (extractor, error) {
    36  	if len(key) == 0 {
    37  		return nil, fmt.Errorf("key must not be empty")
    38  	}
    39  
    40  	switch {
    41  	case key == ".config", key == ".unit":
    42  		// The caller should already have handled this more gracefully.
    43  		panic(key + " is not an extractor")
    44  
    45  	case key == ".name":
    46  		return extractName, nil
    47  
    48  	case key == ".fullname":
    49  		return extractFull, nil
    50  
    51  	case strings.HasPrefix(key, "/"):
    52  		// Construct the byte prefix to search for.
    53  		prefix := make([]byte, len(key)+1)
    54  		copy(prefix, key)
    55  		prefix[len(prefix)-1] = '='
    56  		isGomaxprocs := key == "/gomaxprocs"
    57  		return func(res *benchfmt.Result) []byte {
    58  			return extractNamePart(res, prefix, isGomaxprocs)
    59  		}, nil
    60  	}
    61  
    62  	return func(res *benchfmt.Result) []byte {
    63  		return extractConfig(res, key)
    64  	}, nil
    65  }
    66  
    67  // newExtractorFullName returns an extractor for the full name of a
    68  // benchmark, but optionally with the base name or sub-name
    69  // configuration keys excluded. Any excluded sub-name keys will be
    70  // deleted from the name. If ".name" is excluded, the name will be
    71  // normalized to "*". This will ignore anything in the exclude list that
    72  // isn't in the form of a /-prefixed sub-name key or ".name".
    73  func newExtractorFullName(exclude []string) extractor {
    74  	// Extract the sub-name keys, turn them into substrings and
    75  	// construct their normalized replacement.
    76  	//
    77  	// It's important that we fully delete excluded sub-name keys rather
    78  	// than, say, normalizing them to "*". Simply normalizing them will
    79  	// leak whether or not they are present into the result, resulting
    80  	// in different strings. This is most noticeable when excluding
    81  	// /gomaxprocs: since this is already omitted if it's 1, benchmarks
    82  	// running at /gomaxprocs of 1 won't merge with benchmarks at higher
    83  	// /gomaxprocs.
    84  	var delete [][]byte
    85  	excName := false
    86  	excGomaxprocs := false
    87  	for _, k := range exclude {
    88  		if k == ".name" {
    89  			excName = true
    90  		}
    91  		if !strings.HasPrefix(k, "/") {
    92  			continue
    93  		}
    94  		delete = append(delete, append([]byte(k), '='))
    95  		if k == "/gomaxprocs" {
    96  			excGomaxprocs = true
    97  		}
    98  	}
    99  	if len(delete) == 0 && !excName && !excGomaxprocs {
   100  		return extractFull
   101  	}
   102  	return func(res *benchfmt.Result) []byte {
   103  		return extractFullExcluded(res, delete, excName, excGomaxprocs)
   104  	}
   105  }
   106  
   107  func extractName(res *benchfmt.Result) []byte {
   108  	return res.Name.Base()
   109  }
   110  
   111  func extractFull(res *benchfmt.Result) []byte {
   112  	return res.Name.Full()
   113  }
   114  
   115  func extractFullExcluded(res *benchfmt.Result, delete [][]byte, excName, excGomaxprocs bool) []byte {
   116  	name := res.Name.Full()
   117  	found := false
   118  	if excName {
   119  		found = true
   120  	}
   121  	if !found {
   122  		for _, k := range delete {
   123  			if bytes.Contains(name, k) {
   124  				found = true
   125  				break
   126  			}
   127  		}
   128  	}
   129  	if !found && excGomaxprocs && bytes.IndexByte(name, '-') >= 0 {
   130  		found = true
   131  	}
   132  	if !found {
   133  		// No need to transform name.
   134  		return name
   135  	}
   136  
   137  	// Delete excluded keys from the name.
   138  	base, parts := res.Name.Parts()
   139  	var newName []byte
   140  	if excName {
   141  		newName = append(newName, '*')
   142  	} else {
   143  		newName = append(newName, base...)
   144  	}
   145  outer:
   146  	for _, part := range parts {
   147  		for _, k := range delete {
   148  			if bytes.HasPrefix(part, k) {
   149  				// Skip this part.
   150  				continue outer
   151  			}
   152  		}
   153  		if excGomaxprocs && part[0] == '-' {
   154  			// Skip gomaxprocs.
   155  			continue outer
   156  		}
   157  		newName = append(newName, part...)
   158  	}
   159  	return newName
   160  }
   161  
   162  func extractNamePart(res *benchfmt.Result, prefix []byte, isGomaxprocs bool) []byte {
   163  	_, parts := res.Name.Parts()
   164  	if isGomaxprocs && len(parts) > 0 {
   165  		last := parts[len(parts)-1]
   166  		if last[0] == '-' {
   167  			// GOMAXPROCS specified as "-N" suffix.
   168  			return last[1:]
   169  		}
   170  	}
   171  	// Search for the prefix.
   172  	for _, part := range parts {
   173  		if bytes.HasPrefix(part, prefix) {
   174  			return part[len(prefix):]
   175  		}
   176  	}
   177  	// Not found.
   178  	return nil
   179  }
   180  
   181  func extractConfig(res *benchfmt.Result, key string) []byte {
   182  	pos, ok := res.ConfigIndex(key)
   183  	if !ok {
   184  		return nil
   185  	}
   186  	return res.Config[pos].Value
   187  }