src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

stackcounter.go (5293B)


      1 // Copyright 2023 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 counter
      6 
      7 import (
      8 	"fmt"
      9 	"runtime"
     10 	"strings"
     11 	"sync"
     12 )
     13 
     14 // On the disk, and upstream, stack counters look like sets of
     15 // regular counters with names that include newlines.
     16 
     17 // a StackCounter is the in-memory knowledge about a stack counter.
     18 // StackCounters are more expensive to use than regular Counters,
     19 // requiring, at a minimum, a call to runtime.Callers.
     20 type StackCounter struct {
     21 	name  string
     22 	depth int
     23 	file  *file
     24 
     25 	mu sync.Mutex
     26 	// as this is a detail of the implementation, it could be replaced
     27 	// by a more efficient mechanism
     28 	stacks []stack
     29 }
     30 
     31 type stack struct {
     32 	pcs     []uintptr
     33 	counter *Counter
     34 }
     35 
     36 func NewStack(name string, depth int) *StackCounter {
     37 	return &StackCounter{name: name, depth: depth, file: &defaultFile}
     38 }
     39 
     40 // Inc increments a stack counter. It computes the caller's stack and
     41 // looks up the corresponding counter. It then increments that counter,
     42 // creating it if necessary.
     43 func (c *StackCounter) Inc() {
     44 	pcs := make([]uintptr, c.depth)
     45 	n := runtime.Callers(2, pcs) // caller of Inc
     46 	pcs = pcs[:n]
     47 
     48 	c.mu.Lock()
     49 	defer c.mu.Unlock()
     50 
     51 	// Existing counter?
     52 	var ctr *Counter
     53 	for _, s := range c.stacks {
     54 		if eq(s.pcs, pcs) {
     55 			if s.counter != nil {
     56 				ctr = s.counter
     57 				break
     58 			}
     59 		}
     60 	}
     61 
     62 	if ctr == nil {
     63 		// Create new counter.
     64 		ctr = &Counter{
     65 			name: EncodeStack(pcs, c.name),
     66 			file: c.file,
     67 		}
     68 		c.stacks = append(c.stacks, stack{pcs: pcs, counter: ctr})
     69 	}
     70 
     71 	ctr.Inc()
     72 }
     73 
     74 // EncodeStack returns the name of the counter to
     75 // use for the given stack of program counters.
     76 // The name encodes the stack.
     77 func EncodeStack(pcs []uintptr, prefix string) string {
     78 	var locs []string
     79 	lastImport := ""
     80 	frs := runtime.CallersFrames(pcs)
     81 	for {
     82 		fr, more := frs.Next()
     83 		// TODO(adonovan): this CutLast(".") operation isn't
     84 		// appropriate for generic function symbols.
     85 		path, fname := cutLastDot(fr.Function)
     86 		if path == lastImport {
     87 			path = `"` // (a ditto mark)
     88 		} else {
     89 			lastImport = path
     90 		}
     91 		var loc string
     92 		if fr.Func != nil {
     93 			// Use function-relative line numbering.
     94 			// f:+2 means two lines into function f.
     95 			// f:-1 should never happen, but be conservative.
     96 			//
     97 			// An inlined call is replaced by a NOP instruction
     98 			// with the correct pclntab information.
     99 			_, entryLine := fr.Func.FileLine(fr.Entry)
    100 			loc = fmt.Sprintf("%s.%s:%+d,+0x%x", path, fname, fr.Line-entryLine, fr.PC-fr.Entry)
    101 		} else {
    102 			// The function is non-Go code or is fully inlined:
    103 			// use absolute line number within enclosing file.
    104 			//
    105 			// For inlined calls, the PC and Entry values
    106 			// both refer to the enclosing combined function.
    107 			// For example, both these PCs are relative to "caller":
    108 			//
    109 			//   callee:=1,+0x12        ('=' means inlined)
    110 			//   caller:+2,+0x34
    111 			loc = fmt.Sprintf("%s.%s:=%d,+0x%x", path, fname, fr.Line, fr.PC-fr.Entry)
    112 		}
    113 		locs = append(locs, loc)
    114 		if !more {
    115 			break
    116 		}
    117 	}
    118 
    119 	name := prefix + "\n" + strings.Join(locs, "\n")
    120 	if len(name) > maxNameLen {
    121 		const bad = "\ntruncated\n"
    122 		name = name[:maxNameLen-len(bad)] + bad
    123 	}
    124 	return name
    125 }
    126 
    127 // DecodeStack expands the (compressed) stack encoded in the counter name.
    128 func DecodeStack(ename string) string {
    129 	if !strings.Contains(ename, "\n") {
    130 		return ename // not a stack counter
    131 	}
    132 	lines := strings.Split(ename, "\n")
    133 	var lastPath string // empty or ends with .
    134 	for i, line := range lines {
    135 		path, rest := cutLastDot(line)
    136 		if len(path) == 0 {
    137 			continue // unchanged
    138 		}
    139 		if len(path) == 1 && path[0] == '"' {
    140 			lines[i] = lastPath + rest
    141 		} else {
    142 			lastPath = path + "."
    143 			// line unchanged
    144 		}
    145 	}
    146 	return strings.Join(lines, "\n") // trailing \n?
    147 }
    148 
    149 // input is <import path>.<function name>
    150 // output is (import path, function name)
    151 func cutLastDot(x string) (before, after string) {
    152 	i := strings.LastIndex(x, ".")
    153 	if i < 0 {
    154 		return "", x
    155 	}
    156 	return x[:i], x[i+1:]
    157 }
    158 
    159 // Names reports all the counter names associated with a StackCounter.
    160 func (c *StackCounter) Names() []string {
    161 	c.mu.Lock()
    162 	defer c.mu.Unlock()
    163 	names := make([]string, len(c.stacks))
    164 	for i, s := range c.stacks {
    165 		names[i] = s.counter.Name()
    166 	}
    167 	return names
    168 }
    169 
    170 // Counters returns the known Counters for a StackCounter.
    171 // There may be more in the count file.
    172 func (c *StackCounter) Counters() []*Counter {
    173 	c.mu.Lock()
    174 	defer c.mu.Unlock()
    175 	counters := make([]*Counter, len(c.stacks))
    176 	for i, s := range c.stacks {
    177 		counters[i] = s.counter
    178 	}
    179 	return counters
    180 }
    181 
    182 func eq(a, b []uintptr) bool {
    183 	if len(a) != len(b) {
    184 		return false
    185 	}
    186 	for i := range a {
    187 		if a[i] != b[i] {
    188 			return false
    189 		}
    190 	}
    191 	return true
    192 }
    193 
    194 // ReadStack reads the given stack counter.
    195 // This is the implementation of
    196 // golang.org/x/telemetry/counter/countertest.ReadStackCounter.
    197 func ReadStack(c *StackCounter) (map[string]uint64, error) {
    198 	ret := map[string]uint64{}
    199 	for _, ctr := range c.Counters() {
    200 		v, err := Read(ctr)
    201 		if err != nil {
    202 			return nil, err
    203 		}
    204 		ret[DecodeStack(ctr.Name())] = v
    205 	}
    206 	return ret, nil
    207 }
    208 
    209 // IsStackCounter reports whether the counter name is for a stack counter.
    210 func IsStackCounter(name string) bool {
    211 	return strings.Contains(name, "\n")
    212 }