github.com/facebookincubator/go-belt@v0.0.0-20230703220935-39cd348f1a38/pkg/runtime/stack_trace.go (about)

     1  // Copyright 2022 Meta Platforms, Inc. and affiliates.
     2  //
     3  // Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
     4  //
     5  // 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
     6  //
     7  // 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
     8  //
     9  // 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
    10  //
    11  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    12  
    13  package runtime
    14  
    15  import (
    16  	"fmt"
    17  	"runtime"
    18  	"strings"
    19  )
    20  
    21  type FramesIterator interface {
    22  	// Next works the same as standard (*runtime.Frames).Next().
    23  	Next() (runtime.Frame, bool)
    24  }
    25  
    26  type StackTrace interface {
    27  	// ProgramCounters copies a slice of program counters to the argument.
    28  	// The program counters which begins with the final caller and ends with the initial/root caller.
    29  	ProgramCounters(PCs) int
    30  
    31  	// Frames is an analog of runtime.CallerFrames.
    32  	Frames() FramesIterator
    33  
    34  	// String implements fmt.Stringer.
    35  	String() string
    36  
    37  	// Len returns the amount of frames in the stack trace
    38  	Len() int
    39  }
    40  
    41  // PCs is a goroutine execution frames stack trace.
    42  type PCs []PC
    43  
    44  var _ StackTrace = (PCs)(nil)
    45  
    46  // Len implements interface StackTrace.
    47  func (s PCs) Len() int {
    48  	return len(s)
    49  }
    50  
    51  // ProgramCounters implements interface StackTrace.
    52  func (s PCs) ProgramCounters(out PCs) int {
    53  	return copy(out, s)
    54  }
    55  
    56  // CallersFrames is an equivalent of standard runtime.CallerFrames.
    57  func (s PCs) Frames() FramesIterator {
    58  	pcs := make([]uintptr, len(s))
    59  	for idx, pc := range s {
    60  		pcs[idx] = uintptr(pc)
    61  	}
    62  	return runtime.CallersFrames(pcs)
    63  }
    64  
    65  // String implements fmt.Stringer.
    66  func (s PCs) String() string {
    67  	var result strings.Builder
    68  	frames := s.Frames()
    69  	if frames == nil {
    70  		return "<invalid stack trace>"
    71  	}
    72  	frameDepth := 1
    73  	for {
    74  		frame, haveMore := frames.Next()
    75  		result.WriteString(fmt.Sprintf("%d. %s:%d: %s\n", frameDepth, frame.File, frame.Line, frame.Function))
    76  		frameDepth++
    77  		if !haveMore {
    78  			break
    79  		}
    80  	}
    81  	return result.String()
    82  }
    83  
    84  // CallerStackTrace returns the StackTrace of the current Caller (in current goroutine).
    85  func CallerStackTrace(callerPCFilter PCFilter) PCs {
    86  	if callerPCFilter == nil {
    87  		callerPCFilter = DefaultCallerPCFilter
    88  	}
    89  
    90  	pcs := pcsPool.Get().(*[]uintptr)
    91  	defer pcsPool.Put(pcs)
    92  
    93  	startIdx := 0
    94  	n := runtime.Callers(1, *pcs)
    95  	for i := 0; i < n; i++ {
    96  		pc := (*pcs)[i]
    97  		if callerPCFilter(pc) {
    98  			startIdx = i
    99  			break
   100  		}
   101  	}
   102  
   103  	result := make(PCs, n-startIdx)
   104  	for idx, pc := range (*pcs)[startIdx:n] {
   105  		result[idx] = PC(pc)
   106  	}
   107  	return result
   108  }
   109  
   110  // Frames is a slice of runtime.Frame
   111  type Frames []runtime.Frame
   112  
   113  var _ StackTrace = (Frames)(nil)
   114  
   115  // Len implements interface StackTrace.
   116  func (s Frames) Len() int {
   117  	return len(s)
   118  }
   119  
   120  // ProgramCounters implements interface StackTrace.
   121  func (s Frames) ProgramCounters(out PCs) int {
   122  	endIdx := len(s)
   123  	if len(out) < endIdx {
   124  		endIdx = len(out)
   125  	}
   126  	for idx, frame := range s[:endIdx] {
   127  		out[idx] = PC(frame.PC)
   128  	}
   129  	return endIdx
   130  }
   131  
   132  // CallersFrames is an equivalent of standard runtime.CallerFrames.
   133  func (s Frames) Frames() FramesIterator {
   134  	if len(s) == 0 {
   135  		return nil
   136  	}
   137  	return newFramesIterator(s)
   138  }
   139  
   140  type framesIterator struct {
   141  	Frames      Frames
   142  	CurPosition int
   143  }
   144  
   145  func newFramesIterator(s Frames) *framesIterator {
   146  	return &framesIterator{Frames: s}
   147  }
   148  
   149  var _ FramesIterator = (*framesIterator)(nil)
   150  
   151  // Next implements FramesIterator.
   152  func (i *framesIterator) Next() (runtime.Frame, bool) {
   153  	if len(i.Frames) <= i.CurPosition {
   154  		return runtime.Frame{}, false
   155  	}
   156  
   157  	frame := i.Frames[i.CurPosition]
   158  	i.CurPosition++
   159  	return frame, len(i.Frames) > i.CurPosition
   160  }
   161  
   162  // String implements fmt.Stringer.
   163  func (s Frames) String() string {
   164  	var result strings.Builder
   165  	for idx, frame := range s {
   166  		result.WriteString(fmt.Sprintf("%d. %s:%d: %s\n", idx+1, frame.File, frame.Line, frame.Function))
   167  	}
   168  	return result.String()
   169  }