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 }