|
| 1 | +package queue |
| 2 | + |
| 3 | +import ( |
| 4 | + "bytes" |
| 5 | + "fmt" |
| 6 | + "os" |
| 7 | + "runtime" |
| 8 | + "strings" |
| 9 | + "sync" |
| 10 | +) |
| 11 | + |
| 12 | +const ( |
| 13 | + dunno = "???" |
| 14 | + centerDot = "·" |
| 15 | + dot = "." |
| 16 | + slash = "/" |
| 17 | +) |
| 18 | + |
| 19 | +// bufferPool is a pool of byte buffers that can be reused to reduce the number |
| 20 | +// of allocations and improve performance. It uses sync.Pool to manage a pool |
| 21 | +// of reusable *bytes.Buffer objects. When a buffer is requested from the pool, |
| 22 | +// if one is available, it is returned; otherwise, a new buffer is created. |
| 23 | +// When a buffer is no longer needed, it should be put back into the pool to be |
| 24 | +// reused. |
| 25 | +var bufferPool = sync.Pool{ |
| 26 | + New: func() interface{} { |
| 27 | + return new(bytes.Buffer) |
| 28 | + }, |
| 29 | +} |
| 30 | + |
| 31 | +// stack captures and returns the current stack trace, skipping the specified number of frames. |
| 32 | +// It retrieves the stack trace information, including the file name, line number, and function name, |
| 33 | +// and formats it into a byte slice. The function uses a buffer pool to manage memory efficiently. |
| 34 | +// |
| 35 | +// Parameters: |
| 36 | +// - skip: The number of stack frames to skip before recording the trace. |
| 37 | +// |
| 38 | +// Returns: |
| 39 | +// - A byte slice containing the formatted stack trace. |
| 40 | +func stack(skip int) []byte { |
| 41 | + buf := bufferPool.Get().(*bytes.Buffer) |
| 42 | + defer bufferPool.Put(buf) |
| 43 | + buf.Reset() |
| 44 | + |
| 45 | + var lines [][]byte |
| 46 | + var lastFile string |
| 47 | + for i := skip; ; i++ { |
| 48 | + pc, file, line, ok := runtime.Caller(i) |
| 49 | + if !ok { |
| 50 | + break |
| 51 | + } |
| 52 | + fmt.Fprintf(buf, "%s:%d (0x%x)\n", file, line, pc) |
| 53 | + if file != lastFile { |
| 54 | + data, err := os.ReadFile(file) |
| 55 | + if err != nil { |
| 56 | + continue |
| 57 | + } |
| 58 | + lines = bytes.Split(data, []byte{'\n'}) |
| 59 | + lastFile = file |
| 60 | + } |
| 61 | + fmt.Fprintf(buf, "\t%s: %s\n", function(pc), source(lines, line)) |
| 62 | + } |
| 63 | + return buf.Bytes() |
| 64 | +} |
| 65 | + |
| 66 | +// source retrieves the n-th line from the provided slice of byte slices, |
| 67 | +// trims any leading and trailing whitespace, and returns it as a byte slice. |
| 68 | +// If n is out of range, it returns a default "dunno" byte slice. |
| 69 | +// |
| 70 | +// Parameters: |
| 71 | +// |
| 72 | +// lines - a slice of byte slices representing lines of text |
| 73 | +// n - the 1-based index of the line to retrieve |
| 74 | +// |
| 75 | +// Returns: |
| 76 | +// |
| 77 | +// A byte slice containing the trimmed n-th line, or a default "dunno" byte slice if n is out of range. |
| 78 | +func source(lines [][]byte, n int) []byte { |
| 79 | + n-- |
| 80 | + if n < 0 || n >= len(lines) { |
| 81 | + return []byte(dunno) |
| 82 | + } |
| 83 | + return bytes.TrimSpace(lines[n]) |
| 84 | +} |
| 85 | + |
| 86 | +// function takes a program counter (pc) value and returns the name of the function |
| 87 | +// corresponding to that program counter as a byte slice. It uses runtime.FuncForPC |
| 88 | +// to retrieve the function information and processes the function name to remove |
| 89 | +// any path and package information, returning only the base function name. |
| 90 | +func function(pc uintptr) []byte { |
| 91 | + fn := runtime.FuncForPC(pc) |
| 92 | + if fn == nil { |
| 93 | + return []byte(dunno) |
| 94 | + } |
| 95 | + name := fn.Name() |
| 96 | + if lastSlash := strings.LastIndex(name, slash); lastSlash >= 0 { |
| 97 | + name = name[lastSlash+1:] |
| 98 | + } |
| 99 | + if period := strings.Index(name, dot); period >= 0 { |
| 100 | + name = name[period+1:] |
| 101 | + } |
| 102 | + name = strings.ReplaceAll(name, centerDot, dot) |
| 103 | + return []byte(name) |
| 104 | +} |
0 commit comments