// Copyright 2023 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package counter import ( "fmt" "runtime" "strings" "sync" ) // On the disk, and upstream, stack counters look like sets of // regular counters with names that include newlines. // a StackCounter is the in-memory knowledge about a stack counter. // StackCounters are more expensive to use than regular Counters, // requiring, at a minimum, a call to runtime.Callers. type StackCounter struct { name string depth int file *file mu sync.Mutex // as this is a detail of the implementation, it could be replaced // by a more efficient mechanism stacks []stack } type stack struct { pcs []uintptr counter *Counter } func NewStack(name string, depth int) *StackCounter { return &StackCounter{name: name, depth: depth, file: &defaultFile} } // Inc increments a stack counter. It computes the caller's stack and // looks up the corresponding counter. It then increments that counter, // creating it if necessary. func (c *StackCounter) Inc() { pcs := make([]uintptr, c.depth) n := runtime.Callers(2, pcs) // caller of Inc pcs = pcs[:n] c.mu.Lock() defer c.mu.Unlock() // Existing counter? var ctr *Counter for _, s := range c.stacks { if eq(s.pcs, pcs) { if s.counter != nil { ctr = s.counter break } } } if ctr == nil { // Create new counter. ctr = &Counter{ name: EncodeStack(pcs, c.name), file: c.file, } c.stacks = append(c.stacks, stack{pcs: pcs, counter: ctr}) } ctr.Inc() } // EncodeStack returns the name of the counter to // use for the given stack of program counters. // The name encodes the stack. func EncodeStack(pcs []uintptr, prefix string) string { var locs []string lastImport := "" frs := runtime.CallersFrames(pcs) for { fr, more := frs.Next() // TODO(adonovan): this CutLast(".") operation isn't // appropriate for generic function symbols. path, fname := cutLastDot(fr.Function) if path == lastImport { path = `"` // (a ditto mark) } else { lastImport = path } var loc string if fr.Func != nil { // Use function-relative line numbering. // f:+2 means two lines into function f. // f:-1 should never happen, but be conservative. _, entryLine := fr.Func.FileLine(fr.Entry) loc = fmt.Sprintf("%s.%s:%+d", path, fname, fr.Line-entryLine) } else { // The function is non-Go code or is fully inlined: // use absolute line number within enclosing file. loc = fmt.Sprintf("%s.%s:=%d", path, fname, fr.Line) } locs = append(locs, loc) if !more { break } } name := prefix + "\n" + strings.Join(locs, "\n") if len(name) > maxNameLen { const bad = "\ntruncated\n" name = name[:maxNameLen-len(bad)] + bad } return name } // DecodeStack expands the (compressed) stack encoded in the counter name. func DecodeStack(ename string) string { if !strings.Contains(ename, "\n") { return ename // not a stack counter } lines := strings.Split(ename, "\n") var lastPath string // empty or ends with . for i, line := range lines { path, rest := cutLastDot(line) if len(path) == 0 { continue // unchanged } if len(path) == 1 && path[0] == '"' { lines[i] = lastPath + rest } else { lastPath = path + "." // line unchanged } } return strings.Join(lines, "\n") // trailing \n? } // input is . // output is (import path, function name) func cutLastDot(x string) (before, after string) { i := strings.LastIndex(x, ".") if i < 0 { return "", x } return x[:i], x[i+1:] } // Names reports all the counter names associated with a StackCounter. func (c *StackCounter) Names() []string { c.mu.Lock() defer c.mu.Unlock() names := make([]string, len(c.stacks)) for i, s := range c.stacks { names[i] = s.counter.Name() } return names } // Counters returns the known Counters for a StackCounter. // There may be more in the count file. func (c *StackCounter) Counters() []*Counter { c.mu.Lock() defer c.mu.Unlock() counters := make([]*Counter, len(c.stacks)) for i, s := range c.stacks { counters[i] = s.counter } return counters } func eq(a, b []uintptr) bool { if len(a) != len(b) { return false } for i := range a { if a[i] != b[i] { return false } } return true } // ReadStack reads the given stack counter. // This is the implementation of // golang.org/x/telemetry/counter/countertest.ReadStackCounter. func ReadStack(c *StackCounter) (map[string]uint64, error) { ret := map[string]uint64{} for _, ctr := range c.Counters() { v, err := Read(ctr) if err != nil { return nil, err } ret[DecodeStack(ctr.Name())] = v } return ret, nil } // IsStackCounter reports whether the counter name is for a stack counter. func IsStackCounter(name string) bool { return strings.Contains(name, "\n") }