
Imported golang.org/x/telemetry@9c0d19e to avoid go version requirement change. For #3815 For golang/go#70056
203 lines
4.8 KiB
Go
203 lines
4.8 KiB
Go
// 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 <import path>.<function name>
|
|
// 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")
|
|
}
|