github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/libkb/log_profile.go (about)

     1  package libkb
     2  
     3  import (
     4  	"bufio"
     5  	"fmt"
     6  	"os"
     7  	"regexp"
     8  	"strings"
     9  	"time"
    10  
    11  	"golang.org/x/net/context"
    12  )
    13  
    14  // LogProfileContext for LogProfile
    15  type LogProfileContext struct {
    16  	Contextified
    17  	Path string
    18  }
    19  
    20  func (l *LogProfileContext) maxDuration(durations []time.Duration) time.Duration {
    21  	max := time.Duration(0)
    22  	for _, d := range durations {
    23  		if d > max {
    24  			max = d
    25  		}
    26  	}
    27  	return max
    28  }
    29  
    30  func (l *LogProfileContext) minDuration(durations []time.Duration) time.Duration {
    31  	if len(durations) == 0 {
    32  		return 0
    33  	}
    34  	min := durations[0]
    35  	for _, d := range durations {
    36  		if d < min {
    37  			min = d
    38  		}
    39  	}
    40  	return min
    41  }
    42  
    43  func (l *LogProfileContext) avgDuration(durations []time.Duration) time.Duration {
    44  	if len(durations) == 0 {
    45  		return 0
    46  	}
    47  	var total int64
    48  	for _, d := range durations {
    49  		total += d.Nanoseconds()
    50  	}
    51  	return time.Duration(total / int64(len(durations)))
    52  }
    53  
    54  func (l *LogProfileContext) format(fn string, durations []time.Duration) string {
    55  	return fmt.Sprintf(`
    56  		%v:
    57  			max: %v
    58  			avg: %v
    59  			min: %v
    60  			len: %v`,
    61  		fn, l.maxDuration(durations), l.avgDuration(durations), l.minDuration(durations), len(durations))
    62  }
    63  
    64  func (l *LogProfileContext) parseMatch(matches []string) (filename, fnName string, d time.Duration) {
    65  	if len(matches) != 4 {
    66  		return "", "", 0
    67  	}
    68  	filename = matches[1]
    69  	fnName = matches[2]
    70  	// Some log calls have fnName: args so we want to strip that.
    71  	fnName = strings.Split(fnName, ":")[0]
    72  	// Some log calls have fnName(args) so we want to strip that.
    73  	fnName = strings.Split(fnName, "(")[0]
    74  	d, err := time.ParseDuration(matches[3])
    75  	if err != nil {
    76  		l.G().Log.CDebugf(context.TODO(), "Unable to parse duration: %s", err)
    77  		return "", "", 0
    78  	}
    79  	return filename, fnName, d
    80  }
    81  
    82  func (l *LogProfileContext) LogProfile(path string) ([]string, error) {
    83  	f, err := os.Open(path)
    84  	if err != nil {
    85  		return nil, err
    86  	}
    87  	defer f.Close()
    88  
    89  	re := regexp.MustCompile(`keybase (\w*\.go)\:\d+.*- (.*) -> .* \[time=(\d+\.\w+)\]`)
    90  	// filename -> functionName -> [durations...]
    91  	profiles := map[string]map[string][]time.Duration{}
    92  	scanner := bufio.NewScanner(f)
    93  	scanner.Split(bufio.ScanLines)
    94  	for scanner.Scan() {
    95  		// We expect two groups, the function name and a duration
    96  		matches := re.FindAllStringSubmatch(scanner.Text(), -1)
    97  		if len(matches) == 0 {
    98  			continue
    99  		}
   100  		filename, fnName, d := l.parseMatch(matches[0])
   101  		if fnName == "" {
   102  			continue
   103  		}
   104  
   105  		data, ok := profiles[filename]
   106  		if !ok {
   107  			data = make(map[string][]time.Duration)
   108  		}
   109  		durations, ok := data[fnName]
   110  		if ok {
   111  			durations = append(durations, d)
   112  		} else {
   113  			durations = []time.Duration{d}
   114  		}
   115  		data[fnName] = durations
   116  		profiles[filename] = data
   117  	}
   118  	res := []string{}
   119  	for filename, data := range profiles {
   120  		res = append(res, filename)
   121  		for fnName, durations := range data {
   122  			res = append(res, l.format(fnName, durations))
   123  		}
   124  	}
   125  	return res, nil
   126  }