github.com/waldiirawan/apm-agent-go/v2@v2.2.2/stacktrace/stacktrace.go (about)

     1  // Licensed to Elasticsearch B.V. under one or more contributor
     2  // license agreements. See the NOTICE file distributed with
     3  // this work for additional information regarding copyright
     4  // ownership. Elasticsearch B.V. licenses this file to you under
     5  // the Apache License, Version 2.0 (the "License"); you may
     6  // not use this file except in compliance with the License.
     7  // You may obtain a copy of the License at
     8  //
     9  //     http://www.apache.org/licenses/LICENSE-2.0
    10  //
    11  // Unless required by applicable law or agreed to in writing,
    12  // software distributed under the License is distributed on an
    13  // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    14  // KIND, either express or implied.  See the License for the
    15  // specific language governing permissions and limitations
    16  // under the License.
    17  
    18  package stacktrace // import "github.com/waldiirawan/apm-agent-go/v2/stacktrace"
    19  
    20  import (
    21  	"runtime"
    22  	"strings"
    23  )
    24  
    25  //go:generate /bin/bash generate_library.bash std ..
    26  
    27  // AppendStacktrace appends at most n entries to frames,
    28  // skipping skip frames starting with AppendStacktrace,
    29  // and returns the extended slice. If n is negative, then
    30  // all stack frames will be appended.
    31  //
    32  // See RuntimeFrame for information on what details are included.
    33  func AppendStacktrace(frames []Frame, skip, n int) []Frame {
    34  	if n == 0 {
    35  		return frames
    36  	}
    37  	var pc []uintptr
    38  	if n > 0 && n <= 10 {
    39  		pc = make([]uintptr, n)
    40  		pc = pc[:runtime.Callers(skip+1, pc)]
    41  	} else {
    42  		// n is negative or > 10, allocate space for 10
    43  		// and make repeated calls to runtime.Callers
    44  		// until we've got all the frames or reached n.
    45  		pc = make([]uintptr, 10)
    46  		m := 0
    47  		for {
    48  			m += runtime.Callers(skip+m+1, pc[m:])
    49  			if m < len(pc) || m == n {
    50  				pc = pc[:m]
    51  				break
    52  			}
    53  			// Extend pc's length, ensuring its length
    54  			// extends to its new capacity to minimise
    55  			// the number of calls to runtime.Callers.
    56  			pc = append(pc, 0)
    57  			for len(pc) < cap(pc) {
    58  				pc = append(pc, 0)
    59  			}
    60  		}
    61  	}
    62  	return AppendCallerFrames(frames, pc, n)
    63  }
    64  
    65  // AppendCallerFrames appends to n frames for the PCs in callers,
    66  // and returns the extended slice. If n is negative, all available
    67  // frames will be added. Multiple frames may exist for the same
    68  // caller/PC in the case of function call inlining.
    69  //
    70  // See RuntimeFrame for information on what details are included.
    71  func AppendCallerFrames(frames []Frame, callers []uintptr, n int) []Frame {
    72  	if len(callers) == 0 {
    73  		return frames
    74  	}
    75  	runtimeFrames := runtime.CallersFrames(callers)
    76  	for i := 0; n < 0 || i < n; i++ {
    77  		runtimeFrame, more := runtimeFrames.Next()
    78  		frames = append(frames, RuntimeFrame(runtimeFrame))
    79  		if !more {
    80  			break
    81  		}
    82  	}
    83  	return frames
    84  }
    85  
    86  // RuntimeFrame returns a Frame based on the given runtime.Frame.
    87  //
    88  // The resulting Frame will have the file path, package-qualified
    89  // function name, and line number set. The function name can be
    90  // split using SplitFunctionName, and the absolute path of the
    91  // file and its base name can be determined using standard filepath
    92  // functions.
    93  func RuntimeFrame(in runtime.Frame) Frame {
    94  	return Frame{
    95  		File:     in.File,
    96  		Function: in.Function,
    97  		Line:     in.Line,
    98  	}
    99  }
   100  
   101  // SplitFunctionName splits the function name as formatted in
   102  // runtime.Frame.Function, and returns the package path and
   103  // function name components.
   104  func SplitFunctionName(in string) (packagePath, function string) {
   105  	function = in
   106  	if function == "" {
   107  		return "", ""
   108  	}
   109  	// The last part of a package path will always have "."
   110  	// encoded as "%2e", so we can pick off the package path
   111  	// by finding the last part of the package path, and then
   112  	// the proceeding ".".
   113  	//
   114  	// Unexported method names may contain the package path.
   115  	// In these cases, the method receiver will be enclosed
   116  	// in parentheses, so we can treat that as the start of
   117  	// the function name.
   118  	sep := strings.Index(function, ".(")
   119  	if sep >= 0 {
   120  		packagePath = unescape(function[:sep])
   121  		function = function[sep+1:]
   122  	} else {
   123  		offset := 0
   124  		if sep := strings.LastIndex(function, "/"); sep >= 0 {
   125  			offset = sep
   126  		}
   127  		if sep := strings.IndexRune(function[offset+1:], '.'); sep >= 0 {
   128  			packagePath = unescape(function[:offset+1+sep])
   129  			function = function[offset+1+sep+1:]
   130  		}
   131  	}
   132  	return packagePath, function
   133  }
   134  
   135  func unescape(s string) string {
   136  	var n int
   137  	for i := 0; i < len(s); i++ {
   138  		if s[i] == '%' {
   139  			n++
   140  		}
   141  	}
   142  	if n == 0 {
   143  		return s
   144  	}
   145  	bytes := make([]byte, 0, len(s)-2*n)
   146  	for i := 0; i < len(s); i++ {
   147  		b := s[i]
   148  		if b == '%' && i+2 < len(s) {
   149  			b = fromhex(s[i+1])<<4 | fromhex(s[i+2])
   150  			i += 2
   151  		}
   152  		bytes = append(bytes, b)
   153  	}
   154  	return string(bytes)
   155  }
   156  
   157  func fromhex(b byte) byte {
   158  	if b >= 'a' {
   159  		return 10 + b - 'a'
   160  	}
   161  	return b - '0'
   162  }