github.com/clubpay/ronykit/kit@v0.14.4-0.20240515065620-d0dace45cbc7/internal/stacktrace/stacktrace.go (about)

     1  // Copyright (c) 2016 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package stacktrace
    22  
    23  import (
    24  	"runtime"
    25  	"sync"
    26  
    27  	"github.com/clubpay/ronykit/kit/utils"
    28  	"github.com/clubpay/ronykit/kit/utils/buf"
    29  )
    30  
    31  const (
    32  	// stacktraceFirst captures only the first frame.
    33  	stacktraceFirst stacktraceDepth = iota
    34  
    35  	// stacktraceFull captures the entire call stack, allocating more
    36  	// storage for it if needed.
    37  	stacktraceFull
    38  )
    39  
    40  var stacktracePool = sync.Pool{
    41  	New: func() any {
    42  		return &stacktrace{
    43  			storage: make([]uintptr, 64),
    44  		}
    45  	},
    46  }
    47  
    48  type stacktrace struct {
    49  	pcs    []uintptr // program counters; always a subslice of storage
    50  	frames *runtime.Frames
    51  
    52  	// The size of pcs varies depending on requirements:
    53  	// it will be one, if only the first frame was requested,
    54  	// and otherwise it will reflect the depth of the call stack.
    55  	//
    56  	// storage decouples the slice we need (pcs) from the slice we pool.
    57  	// We will always allocate a reasonably large storage, but we'll use
    58  	// only as much of it as we need.
    59  	storage []uintptr
    60  }
    61  
    62  // stacktraceDepth specifies how deep of a stack trace should be captured.
    63  type stacktraceDepth int
    64  
    65  // captureStacktrace captures a stack trace of the specified depth, skipping
    66  // the provided number of frames. skip=0 identifies the caller of
    67  // captureStacktrace.
    68  //
    69  // The caller must call Free on the returned stacktrace after using it.
    70  func captureStacktrace(skip int, depth stacktraceDepth) *stacktrace {
    71  	stack := stacktracePool.Get().(*stacktrace) //nolint:forcetypeassert
    72  
    73  	switch depth {
    74  	case stacktraceFirst:
    75  		stack.pcs = stack.storage[:1]
    76  	case stacktraceFull:
    77  		stack.pcs = stack.storage
    78  	}
    79  
    80  	// Unlike other "skip"-based APIs, skip=0 identifies runtime.Callers
    81  	// itself. +2 to skip captureStacktrace and runtime.Callers.
    82  	numFrames := runtime.Callers(
    83  		skip+2,
    84  		stack.pcs,
    85  	)
    86  
    87  	// runtime.Callers truncate the recorded stacktrace if there is no
    88  	// room in the provided slice. For the full stack trace, keep expanding
    89  	// storage until there are fewer frames than there is room.
    90  	if depth == stacktraceFull {
    91  		pcs := stack.pcs
    92  		for numFrames == len(pcs) {
    93  			pcs = make([]uintptr, len(pcs)*2)
    94  			numFrames = runtime.Callers(skip+2, pcs)
    95  		}
    96  
    97  		// Discard old storage instead of returning it to the pool.
    98  		// This will adjust the pool size over time if stack traces are
    99  		// consistently very deep.
   100  		stack.storage = pcs
   101  		stack.pcs = pcs[:numFrames]
   102  	} else {
   103  		stack.pcs = stack.pcs[:numFrames]
   104  	}
   105  
   106  	stack.frames = runtime.CallersFrames(stack.pcs)
   107  
   108  	return stack
   109  }
   110  
   111  // Free releases resources associated with this stacktrace
   112  // and returns it back to the pool.
   113  func (st *stacktrace) Free() {
   114  	st.frames = nil
   115  	st.pcs = nil
   116  	stacktracePool.Put(st)
   117  }
   118  
   119  // Count reports the total number of frames in this stacktrace.
   120  // Count DOES NOT change as Next is called.
   121  func (st *stacktrace) Count() int {
   122  	return len(st.pcs)
   123  }
   124  
   125  // Next returns the next frame in the stack trace,
   126  // and a boolean indicating whether there are more after it.
   127  func (st *stacktrace) Next() (_ runtime.Frame, more bool) {
   128  	return st.frames.Next()
   129  }
   130  
   131  func TakeStacktrace(skip int) string {
   132  	stack := captureStacktrace(skip+1, stacktraceFull)
   133  	defer stack.Free()
   134  
   135  	buffer := buf.GetCap(1024)
   136  	defer buffer.Release()
   137  
   138  	stackfmt := newStackFormatter(buffer)
   139  	stackfmt.FormatStack(stack)
   140  
   141  	return string(*buffer.Bytes())
   142  }
   143  
   144  // stackFormatter formats a stack trace into a readable string representation.
   145  type stackFormatter struct {
   146  	b        *buf.Bytes
   147  	nonEmpty bool // whehther we've written at least one frame already
   148  }
   149  
   150  // newStackFormatter builds a new stackFormatter.
   151  func newStackFormatter(b *buf.Bytes) stackFormatter {
   152  	return stackFormatter{b: b}
   153  }
   154  
   155  // FormatStack formats all remaining frames in the provided stacktrace -- minus
   156  // the final runtime.main/runtime.goexit frame.
   157  func (sf *stackFormatter) FormatStack(stack *stacktrace) {
   158  	// Note: On the last iteration, frames.Next() returns false, with a valid
   159  	// frame, but we ignore this frame. The last frame is a runtime frame which
   160  	// adds noise, since it's only either runtime.main or runtime.goexit.
   161  	for frame, more := stack.Next(); more; frame, more = stack.Next() {
   162  		sf.FormatFrame(frame)
   163  	}
   164  }
   165  
   166  // FormatFrame formats the given frame.
   167  func (sf *stackFormatter) FormatFrame(frame runtime.Frame) {
   168  	if sf.nonEmpty {
   169  		sf.b.AppendByte('\n')
   170  	}
   171  	sf.nonEmpty = true
   172  	sf.b.AppendString(frame.Function)
   173  	sf.b.AppendByte('\n')
   174  	sf.b.AppendByte('\t')
   175  	sf.b.AppendString(frame.File)
   176  	sf.b.AppendByte(':')
   177  	sf.b.AppendString(utils.IntToStr(frame.Line))
   178  }