parseStack parses a single stack trace from the given scanner. line is the first line of the stack trace, which should look like: goroutine 123 [runnable]:
(line string)
| 128 | // |
| 129 | // goroutine 123 [runnable]: |
| 130 | func (p *stackParser) parseStack(line string) (Stack, error) { |
| 131 | id, state, err := parseGoStackHeader(line) |
| 132 | if err != nil { |
| 133 | return Stack{}, fmt.Errorf("parse header: %w", err) |
| 134 | } |
| 135 | |
| 136 | // Read the rest of the stack trace. |
| 137 | var ( |
| 138 | firstFunction string |
| 139 | fullStack bytes.Buffer |
| 140 | ) |
| 141 | funcs := make(map[string]struct{}) |
| 142 | for p.scan.Scan() { |
| 143 | line := p.scan.Text() |
| 144 | if strings.HasPrefix(line, "goroutine ") { |
| 145 | // If we see the goroutine header, |
| 146 | // it's the end of this stack. |
| 147 | // Unscan so the next Scan sees the same line. |
| 148 | p.scan.Unscan() |
| 149 | break |
| 150 | } |
| 151 | |
| 152 | fullStack.WriteString(line) |
| 153 | fullStack.WriteByte('\n') // scanner trims the newline |
| 154 | |
| 155 | if len(line) == 0 { |
| 156 | // Empty line usually marks the end of the stack |
| 157 | // but we don't want to have to rely on that. |
| 158 | // Just skip it. |
| 159 | continue |
| 160 | } |
| 161 | |
| 162 | funcName, creator, err := parseFuncName(line) |
| 163 | if err != nil { |
| 164 | return Stack{}, fmt.Errorf("parse function: %w", err) |
| 165 | } |
| 166 | if !creator { |
| 167 | // A function is part of a goroutine's stack |
| 168 | // only if it's not a "created by" function. |
| 169 | // |
| 170 | // The creator function is part of a different stack. |
| 171 | // We don't care about it right now. |
| 172 | funcs[funcName] = struct{}{} |
| 173 | if firstFunction == "" { |
| 174 | firstFunction = funcName |
| 175 | } |
| 176 | |
| 177 | } |
| 178 | |
| 179 | // The function name followed by a line in the form: |
| 180 | // |
| 181 | // <tab>example.com/path/to/package/file.go:123 +0x123 |
| 182 | // |
| 183 | // We don't care about the position so we can skip this line. |
| 184 | if p.scan.Scan() { |
| 185 | // Be defensive: |
| 186 | // Skip the line only if it starts with a tab. |
| 187 | bs := p.scan.Bytes() |
no test coverage detected