Source file src/cmd/vendor/golang.org/x/telemetry/internal/counter/stackcounter.go

     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  }
   213  

View as plain text