// Copyright (c) 2023 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

// Package stacktrace provides support for gathering stack traces // efficiently.
package stacktrace import ( ) var _stackPool = pool.New(func() *Stack { return &Stack{ storage: make([]uintptr, 64), } }) // Stack is a captured stack trace. type Stack struct { pcs []uintptr // program counters; always a subslice of storage frames *runtime.Frames // The size of pcs varies depending on requirements: // it will be one if the only the first frame was requested, // and otherwise it will reflect the depth of the call stack. // // storage decouples the slice we need (pcs) from the slice we pool. // We will always allocate a reasonably large storage, but we'll use // only as much of it as we need. storage []uintptr } // Depth specifies how deep of a stack trace should be captured. type Depth int const ( // First captures only the first frame. First Depth = iota // Full captures the entire call stack, allocating more // storage for it if needed. Full ) // Capture captures a stack trace of the specified depth, skipping // the provided number of frames. skip=0 identifies the caller of // Capture. // // The caller must call Free on the returned stacktrace after using it. func ( int, Depth) *Stack { := _stackPool.Get() switch { case First: .pcs = .storage[:1] case Full: .pcs = .storage } // Unlike other "skip"-based APIs, skip=0 identifies runtime.Callers // itself. +2 to skip captureStacktrace and runtime.Callers. := runtime.Callers( +2, .pcs, ) // runtime.Callers truncates the recorded stacktrace if there is no // room in the provided slice. For the full stack trace, keep expanding // storage until there are fewer frames than there is room. if == Full { := .pcs for == len() { = make([]uintptr, len()*2) = runtime.Callers(+2, ) } // Discard old storage instead of returning it to the pool. // This will adjust the pool size over time if stack traces are // consistently very deep. .storage = .pcs = [:] } else { .pcs = .pcs[:] } .frames = runtime.CallersFrames(.pcs) return } // Free releases resources associated with this stacktrace // and returns it back to the pool. func ( *Stack) () { .frames = nil .pcs = nil _stackPool.Put() } // Count reports the total number of frames in this stacktrace. // Count DOES NOT change as Next is called. func ( *Stack) () int { return len(.pcs) } // Next returns the next frame in the stack trace, // and a boolean indicating whether there are more after it. func ( *Stack) () ( runtime.Frame, bool) { return .frames.Next() } // Take returns a string representation of the current stacktrace. // // skip is the number of frames to skip before recording the stack trace. // skip=0 identifies the caller of Take. func ( int) string { := Capture(+1, Full) defer .Free() := bufferpool.Get() defer .Free() := NewFormatter() .FormatStack() return .String() } // Formatter formats a stack trace into a readable string representation. type Formatter struct { b *buffer.Buffer nonEmpty bool // whehther we've written at least one frame already } // NewFormatter builds a new Formatter. func ( *buffer.Buffer) Formatter { return Formatter{b: } } // FormatStack formats all remaining frames in the provided stacktrace -- minus // the final runtime.main/runtime.goexit frame. func ( *Formatter) ( *Stack) { // Note: On the last iteration, frames.Next() returns false, with a valid // frame, but we ignore this frame. The last frame is a runtime frame which // adds noise, since it's only either runtime.main or runtime.goexit. for , := .Next(); ; , = .Next() { .FormatFrame() } } // FormatFrame formats the given frame. func ( *Formatter) ( runtime.Frame) { if .nonEmpty { .b.AppendByte('\n') } .nonEmpty = true .b.AppendString(.Function) .b.AppendByte('\n') .b.AppendByte('\t') .b.AppendString(.File) .b.AppendByte(':') .b.AppendInt(int64(.Line)) }