github.com/haraldrudell/parl@v0.4.176/pruntime/debug-stack.go (about)

     1  /*
     2  © 2022–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/)
     3  ISC License
     4  */
     5  
     6  package pruntime
     7  
     8  import (
     9  	"bytes"
    10  	"runtime"
    11  	"runtime/debug"
    12  )
    13  
    14  const (
    15  	// the lead-in line contains “goroutine 18 [running]:”
    16  	prLeadInLines = 1
    17  	// stack frame of pruntime.DebugStack function
    18  	prDebugStackStdFrame = 1
    19  	// stack frame of debuig.Stack function
    20  	prDebugStackFnFrame = 1
    21  	//	- first line of frame describes source file
    22  	//	- second line of frame describes package and function
    23  	prLinesPerFrame = 2
    24  	// creator lines are two lines that describe how a goroutine was launched:
    25  	//	- “created by…”
    26  	prCreatorLines = 2
    27  	// newline as a byte
    28  	byteNewline = byte('\n')
    29  	// tab as a byte
    30  	byteTab = byte('\t')
    31  	// space as a byte
    32  	byteSpace = byte('\x20')
    33  )
    34  
    35  // byte slice newline separator
    36  var byteSliceNewline = []byte{byteNewline}
    37  
    38  // byte slice tab
    39  var byteSliceTab = []byte{byteTab}
    40  
    41  // byte slice two spaces
    42  var byteSliceTwoSpaces = []byte{byteSpace, byteSpace}
    43  
    44  var _ = runtime.Stack
    45  
    46  // DebugStack returns a string stack trace intended to be printed or when a full printable trace is desired
    47  //   - top returned stack frame is caller of [pruntime.DebugStack]
    48  //   - skipFrames allows for removing additional frames.
    49  //   - differences from debug.Stack:
    50  //   - tabs are replaced with two spaces
    51  //   - Stack frames other than the callers of pruntime.DebugStack are removed
    52  func DebugStack(skipFrames int) (stack string) {
    53  	if skipFrames < 0 {
    54  		skipFrames = 0
    55  	}
    56  
    57  	/*
    58  		goroutine 18 [running]:
    59  		runtime/debug.Stack()
    60  			/opt/homebrew/Cellar/go/1.18/libexec/src/runtime/debug/stack.go:24 +0x68
    61  		github.com/haraldrudell/parl/pruntime.NewStack()
    62  		…
    63  		created by testing.(*T).Run
    64  			/opt/homebrew/Cellar/go/1.18/libexec/src/testing/testing.go:1486 +0x300
    65  	*/
    66  
    67  	// remove final newline, split into lines
    68  	//	- hold off on converting to string to reduce interning memory leak
    69  	//	- [][]byte
    70  	var stackTraceByteLines = bytes.Split(bytes.TrimSuffix(debug.Stack(), byteSliceNewline), byteSliceNewline)
    71  
    72  	// number of stack frames that are not header data or creator line
    73  	var frameCount = (len(stackTraceByteLines)-prLeadInLines-prCreatorLines)/prLinesPerFrame -
    74  		prDebugStackStdFrame - prDebugStackFnFrame
    75  
    76  	// check skipFrames maximum value
    77  	if skipFrames > frameCount {
    78  		skipFrames = frameCount
    79  	}
    80  
    81  	// undesirable stack frames: debug.Stack, pruntime.DebugStack and skipFrames
    82  	var skipLines = prLinesPerFrame * (prDebugStackStdFrame + prDebugStackFnFrame + skipFrames)
    83  
    84  	// remove lines
    85  	copy(stackTraceByteLines[prLeadInLines:], stackTraceByteLines[prLeadInLines+skipLines:])
    86  	stackTraceByteLines = stackTraceByteLines[:len(stackTraceByteLines)-skipLines]
    87  
    88  	// merge back together, replace tab with two spaces, make string
    89  	stack = string(bytes.ReplaceAll(
    90  		bytes.Join(stackTraceByteLines, byteSliceNewline),
    91  		byteSliceTab,
    92  		byteSliceTwoSpaces,
    93  	))
    94  
    95  	return
    96  }