diff options
Diffstat (limited to 'vendor/github.com/maruel')
-rw-r--r-- | vendor/github.com/maruel/panicparse/.travis.yml | 15 | ||||
-rw-r--r-- | vendor/github.com/maruel/panicparse/LICENSE | 201 | ||||
-rw-r--r-- | vendor/github.com/maruel/panicparse/README.md | 123 | ||||
-rw-r--r-- | vendor/github.com/maruel/panicparse/stack/source.go | 291 | ||||
-rw-r--r-- | vendor/github.com/maruel/panicparse/stack/stack.go | 832 | ||||
-rw-r--r-- | vendor/github.com/maruel/panicparse/stack/ui.go | 139 | ||||
-rw-r--r-- | vendor/github.com/maruel/panicparse/vendor.yml | 17 |
7 files changed, 1618 insertions, 0 deletions
diff --git a/vendor/github.com/maruel/panicparse/.travis.yml b/vendor/github.com/maruel/panicparse/.travis.yml new file mode 100644 index 000000000..c6da8f0b8 --- /dev/null +++ b/vendor/github.com/maruel/panicparse/.travis.yml @@ -0,0 +1,15 @@ +# Copyright 2014 Marc-Antoine Ruel. All rights reserved. +# Use of this source code is governed under the Apache License, Version 2.0 +# that can be found in the LICENSE file. + +sudo: false +language: go + +go: +- tip + +before_install: + - go get github.com/maruel/pre-commit-go/cmd/pcg + +script: + - pcg diff --git a/vendor/github.com/maruel/panicparse/LICENSE b/vendor/github.com/maruel/panicparse/LICENSE new file mode 100644 index 000000000..b76840c25 --- /dev/null +++ b/vendor/github.com/maruel/panicparse/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2015 Marc-Antoine Ruel + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/maruel/panicparse/README.md b/vendor/github.com/maruel/panicparse/README.md new file mode 100644 index 000000000..9fc039cd9 --- /dev/null +++ b/vendor/github.com/maruel/panicparse/README.md @@ -0,0 +1,123 @@ +panicparse +========== + +Parses panic stack traces, densifies and deduplicates goroutines with similar +stack traces. Helps debugging crashes and deadlocks in heavily parallelized +process. + +[![Build Status](https://travis-ci.org/maruel/panicparse.svg?branch=master)](https://travis-ci.org/maruel/panicparse) + +panicparse helps make sense of Go crash dumps: + +![Screencast](https://raw.githubusercontent.com/wiki/maruel/panicparse/parse.gif "Screencast") + + +Features +-------- + + * >50% more compact output than original stack dump yet more readable. + * Exported symbols are bold, private symbols are darker. + * Stdlib is green, main is yellow, rest is red. + * Deduplicates redundant goroutine stacks. Useful for large server crashes. + * Arguments as pointer IDs instead of raw pointer values. + * Pushes stdlib-only stacks at the bottom to help focus on important code. + * Usable as a library! + [![GoDoc](https://godoc.org/github.com/maruel/panicparse/stack?status.svg)](https://godoc.org/github.com/maruel/panicparse/stack) + * Warning: please pin the version (e.g. vendor it). Breaking changes are + not planned but may happen. + * Parses the source files if available to augment the output. + * Works on Windows. + + +Installation +------------ + + go get github.com/maruel/panicparse/cmd/pp + + +Usage +----- + +### Piping a stack trace from another process + +#### TL;DR + + * Ubuntu (bash v4 or zsh): `|&` + * OSX, [install bash 4+](README.md#updating-bash-on-osx), then: `|&` + * Windows _or_ OSX with stock bash v3: `2>&1 |` + * [Fish](http://fishshell.com/) shell: `^|` + + +#### Longer version + +`pp` streams its stdin to stdout as long as it doesn't detect any panic. +`panic()` and Go's native deadlock detector [print to +stderr](https://golang.org/src/runtime/panic1.go) via the native [`print()` +function](https://golang.org/pkg/builtin/#print). + + +**Bash v4** or **zsh**: `|&` tells the shell to redirect stderr to stdout, +it's an alias for `2>&1 |` ([bash +v4](https://www.gnu.org/software/bash/manual/bash.html#Pipelines), +[zsh](http://zsh.sourceforge.net/Doc/Release/Shell-Grammar.html#Simple-Commands-_0026-Pipelines)): + + go test -v |&pp + + +**Windows or OSX native bash** [(which is +3.2.57)](http://meta.ath0.com/2012/02/05/apples-great-gpl-purge/): They don't +have this shortcut, so use the long form: + + go test -v 2>&1 | pp + + +**Fish**: It uses [^ for stderr +redirection](http://fishshell.com/docs/current/tutorial.html#tut_pipes_and_redirections) +so the shortcut is `^|`: + + go test -v ^|pp + + +**PowerShell**: [It has broken `2>&1` redirection](https://connect.microsoft.com/PowerShell/feedback/details/765551/in-powershell-v3-you-cant-redirect-stderr-to-stdout-without-generating-error-records). The workaround is to shell out to cmd.exe. :( + + +### Investigate deadlock + +On POSIX, use `Ctrl-\` to send SIGQUIT to your process, `pp` will ignore +the signal and will parse the stack trace. + + +### Parsing from a file + +To dump to a file then parse, pass the file path of a stack trace + + go test 2> stack.txt + pp stack.txt + + +Tips +---- + +### GOTRACEBACK + +Starting with Go 1.6, [`GOTRACEBACK`](https://golang.org/pkg/runtime/) defaults +to `single` instead of `all` / `1` that was used in 1.5 and before. To get all +goroutines trace and not just the crashing one, set the environment variable: + + export GOTRACEBACK=all + +or `set GOTRACEBACK=all` on Windows. Probably worth to put it in your `.bashrc`. + + +### Updating bash on OSX + +Install bash v4+ on OSX via [homebrew](http://brew.sh) or +[macports](https://www.macports.org/). Your future self will appreciate having +done that. + + +### If you have `/usr/bin/pp` installed + +You may have the Perl PAR Packager installed. Use long name `panicparse` then; + + go get github.com/maruel/panicparse diff --git a/vendor/github.com/maruel/panicparse/stack/source.go b/vendor/github.com/maruel/panicparse/stack/source.go new file mode 100644 index 000000000..f09e67336 --- /dev/null +++ b/vendor/github.com/maruel/panicparse/stack/source.go @@ -0,0 +1,291 @@ +// Copyright 2015 Marc-Antoine Ruel. All rights reserved. +// Use of this source code is governed under the Apache License, Version 2.0 +// that can be found in the LICENSE file. + +// This file contains the code to process sources, to be able to deduct the +// original types. + +package stack + +import ( + "bytes" + "fmt" + "go/ast" + "go/parser" + "go/token" + "io/ioutil" + "log" + "math" + "strings" +) + +// cache is a cache of sources on the file system. +type cache struct { + files map[string][]byte + parsed map[string]*parsedFile +} + +// Augment processes source files to improve calls to be more descriptive. +// +// It modifies goroutines in place. +func Augment(goroutines []Goroutine) { + c := &cache{} + for i := range goroutines { + c.augmentGoroutine(&goroutines[i]) + } +} + +// augmentGoroutine processes source files to improve call to be more +// descriptive. +// +// It modifies the routine. +func (c *cache) augmentGoroutine(goroutine *Goroutine) { + if c.files == nil { + c.files = map[string][]byte{} + } + if c.parsed == nil { + c.parsed = map[string]*parsedFile{} + } + // For each call site, look at the next call and populate it. Then we can + // walk back and reformat things. + for i := range goroutine.Stack.Calls { + c.load(goroutine.Stack.Calls[i].SourcePath) + } + + // Once all loaded, we can look at the next call when available. + for i := 1; i < len(goroutine.Stack.Calls); i++ { + // Get the AST from the previous call and process the call line with it. + if f := c.getFuncAST(&goroutine.Stack.Calls[i]); f != nil { + processCall(&goroutine.Stack.Calls[i], f) + } + } +} + +// Private stuff. + +// load loads a source file and parses the AST tree. Failures are ignored. +func (c *cache) load(fileName string) { + if _, ok := c.parsed[fileName]; ok { + return + } + c.parsed[fileName] = nil + if !strings.HasSuffix(fileName, ".go") { + // Ignore C and assembly. + c.files[fileName] = nil + return + } + log.Printf("load(%s)", fileName) + if _, ok := c.files[fileName]; !ok { + var err error + if c.files[fileName], err = ioutil.ReadFile(fileName); err != nil { + log.Printf("Failed to read %s: %s", fileName, err) + c.files[fileName] = nil + return + } + } + fset := token.NewFileSet() + src := c.files[fileName] + parsed, err := parser.ParseFile(fset, fileName, src, 0) + if err != nil { + log.Printf("Failed to parse %s: %s", fileName, err) + return + } + // Convert the line number into raw file offset. + offsets := []int{0, 0} + start := 0 + for l := 1; start < len(src); l++ { + start += bytes.IndexByte(src[start:], '\n') + 1 + offsets = append(offsets, start) + } + c.parsed[fileName] = &parsedFile{offsets, parsed} +} + +func (c *cache) getFuncAST(call *Call) *ast.FuncDecl { + if p := c.parsed[call.SourcePath]; p != nil { + return p.getFuncAST(call.Func.Name(), call.Line) + } + return nil +} + +type parsedFile struct { + lineToByteOffset []int + parsed *ast.File +} + +// getFuncAST gets the callee site function AST representation for the code +// inside the function f at line l. +func (p *parsedFile) getFuncAST(f string, l int) (d *ast.FuncDecl) { + // Walk the AST to find the lineToByteOffset that fits the line number. + var lastFunc *ast.FuncDecl + var found ast.Node + // Inspect() goes depth first. This means for example that a function like: + // func a() { + // b := func() {} + // c() + // } + // + // Were we are looking at the c() call can return confused values. It is + // important to look at the actual ast.Node hierarchy. + ast.Inspect(p.parsed, func(n ast.Node) bool { + if d != nil { + return false + } + if n == nil { + return true + } + if found != nil { + // We are walking up. + } + if int(n.Pos()) >= p.lineToByteOffset[l] { + // We are expecting a ast.CallExpr node. It can be harder to figure out + // when there are multiple calls on a single line, as the stack trace + // doesn't have file byte offset information, only line based. + // gofmt will always format to one function call per line but there can + // be edge cases, like: + // a = A{Foo(), Bar()} + d = lastFunc + //p.processNode(call, n) + return false + } else if f, ok := n.(*ast.FuncDecl); ok { + lastFunc = f + } + return true + }) + return +} + +func name(n ast.Node) string { + if _, ok := n.(*ast.InterfaceType); ok { + return "interface{}" + } + if i, ok := n.(*ast.Ident); ok { + return i.Name + } + if _, ok := n.(*ast.FuncType); ok { + return "func" + } + if s, ok := n.(*ast.SelectorExpr); ok { + return s.Sel.Name + } + // TODO(maruel): Implement anything missing. + return "<unknown>" +} + +// fieldToType returns the type name and whether if it's an ellipsis. +func fieldToType(f *ast.Field) (string, bool) { + switch arg := f.Type.(type) { + case *ast.ArrayType: + return "[]" + name(arg.Elt), false + case *ast.Ellipsis: + return name(arg.Elt), true + case *ast.FuncType: + // Do not print the function signature to not overload the trace. + return "func", false + case *ast.Ident: + return arg.Name, false + case *ast.InterfaceType: + return "interface{}", false + case *ast.SelectorExpr: + return arg.Sel.Name, false + case *ast.StarExpr: + return "*" + name(arg.X), false + default: + // TODO(maruel): Implement anything missing. + return "<unknown>", false + } +} + +// extractArgumentsType returns the name of the type of each input argument. +func extractArgumentsType(f *ast.FuncDecl) ([]string, bool) { + var fields []*ast.Field + if f.Recv != nil { + if len(f.Recv.List) != 1 { + panic("Expect only one receiver; please fix panicparse's code") + } + // If it is an object receiver (vs a pointer receiver), its address is not + // printed in the stack trace so it needs to be ignored. + if _, ok := f.Recv.List[0].Type.(*ast.StarExpr); ok { + fields = append(fields, f.Recv.List[0]) + } + } + var types []string + extra := false + for _, arg := range append(fields, f.Type.Params.List...) { + // Assert that extra is only set on the last item of fields? + var t string + t, extra = fieldToType(arg) + mult := len(arg.Names) + if mult == 0 { + mult = 1 + } + for i := 0; i < mult; i++ { + types = append(types, t) + } + } + return types, extra +} + +// processCall walks the function and populate call accordingly. +func processCall(call *Call, f *ast.FuncDecl) { + values := make([]uint64, len(call.Args.Values)) + for i := range call.Args.Values { + values[i] = call.Args.Values[i].Value + } + index := 0 + pop := func() uint64 { + if len(values) != 0 { + x := values[0] + values = values[1:] + index++ + return x + } + return 0 + } + popName := func() string { + n := call.Args.Values[index].Name + v := pop() + if len(n) == 0 { + return fmt.Sprintf("0x%x", v) + } + return n + } + + types, extra := extractArgumentsType(f) + for i := 0; len(values) != 0; i++ { + var t string + if i >= len(types) { + if !extra { + // These are unexpected value! Print them as hex. + call.Args.Processed = append(call.Args.Processed, popName()) + continue + } + t = types[len(types)-1] + } else { + t = types[i] + } + switch t { + case "float32": + call.Args.Processed = append(call.Args.Processed, fmt.Sprintf("%g", math.Float32frombits(uint32(pop())))) + case "float64": + call.Args.Processed = append(call.Args.Processed, fmt.Sprintf("%g", math.Float64frombits(pop()))) + case "int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64": + call.Args.Processed = append(call.Args.Processed, fmt.Sprintf("%d", pop())) + case "string": + call.Args.Processed = append(call.Args.Processed, fmt.Sprintf("%s(%s, len=%d)", t, popName(), pop())) + default: + if strings.HasPrefix(t, "*") { + call.Args.Processed = append(call.Args.Processed, fmt.Sprintf("%s(%s)", t, popName())) + } else if strings.HasPrefix(t, "[]") { + call.Args.Processed = append(call.Args.Processed, fmt.Sprintf("%s(%s len=%d cap=%d)", t, popName(), pop(), pop())) + } else { + // Assumes it's an interface. For now, discard the object value, which + // is probably not a good idea. + call.Args.Processed = append(call.Args.Processed, fmt.Sprintf("%s(%s)", t, popName())) + pop() + } + } + if len(values) == 0 && call.Args.Elided { + return + } + } +} diff --git a/vendor/github.com/maruel/panicparse/stack/stack.go b/vendor/github.com/maruel/panicparse/stack/stack.go new file mode 100644 index 000000000..cfb502e66 --- /dev/null +++ b/vendor/github.com/maruel/panicparse/stack/stack.go @@ -0,0 +1,832 @@ +// Copyright 2015 Marc-Antoine Ruel. All rights reserved. +// Use of this source code is governed under the Apache License, Version 2.0 +// that can be found in the LICENSE file. + +// Package stack analyzes stack dump of Go processes and simplifies it. +// +// It is mostly useful on servers will large number of identical goroutines, +// making the crash dump harder to read than strictly necesary. +package stack + +import ( + "bufio" + "bytes" + "errors" + "fmt" + "io" + "math" + "net/url" + "os" + "path/filepath" + "regexp" + "runtime" + "sort" + "strconv" + "strings" + "unicode" + "unicode/utf8" +) + +const lockedToThread = "locked to thread" + +var ( + // TODO(maruel): Handle corrupted stack cases: + // - missed stack barrier + // - found next stack barrier at 0x123; expected + // - runtime: unexpected return pc for FUNC_NAME called from 0x123 + + reRoutineHeader = regexp.MustCompile("^goroutine (\\d+) \\[([^\\]]+)\\]\\:\n$") + reMinutes = regexp.MustCompile("^(\\d+) minutes$") + reUnavail = regexp.MustCompile("^(?:\t| +)goroutine running on other thread; stack unavailable") + // See gentraceback() in src/runtime/traceback.go for more information. + // - Sometimes the source file comes up as "<autogenerated>". It is the + // compiler than generated these, not the runtime. + // - The tab may be replaced with spaces when a user copy-paste it, handle + // this transparently. + // - "runtime.gopanic" is explicitly replaced with "panic" by gentraceback(). + // - The +0x123 byte offset is printed when frame.pc > _func.entry. _func is + // generated by the linker. + // - The +0x123 byte offset is not included with generated code, e.g. unnamed + // functions "func·006()" which is generally go func() { ... }() + // statements. Since the _func is generated at runtime, it's probably why + // _func.entry is not set. + // - C calls may have fp=0x123 sp=0x123 appended. I think it normally happens + // when a signal is not correctly handled. It is printed with m.throwing>0. + // These are discarded. + // - For cgo, the source file may be "??". + reFile = regexp.MustCompile("^(?:\t| +)(\\?\\?|\\<autogenerated\\>|.+\\.(?:c|go|s))\\:(\\d+)(?:| \\+0x[0-9a-f]+)(?:| fp=0x[0-9a-f]+ sp=0x[0-9a-f]+)\n$") + // Sadly, it doesn't note the goroutine number so we could cascade them per + // parenthood. + reCreated = regexp.MustCompile("^created by (.+)\n$") + reFunc = regexp.MustCompile("^(.+)\\((.*)\\)\n$") + reElided = regexp.MustCompile("^\\.\\.\\.additional frames elided\\.\\.\\.\n$") + // Include frequent GOROOT value on Windows, distro provided and user + // installed path. This simplifies the user's life when processing a trace + // generated on another VM. + // TODO(maruel): Guess the path automatically via traces containing the + // 'runtime' package, which is very frequent. This would be "less bad" than + // throwing up random values at the parser. + goroots = []string{runtime.GOROOT(), "c:/go", "/usr/lib/go", "/usr/local/go"} +) + +// Similarity is the level at which two call lines arguments must match to be +// considered similar enough to coalesce them. +type Similarity int + +const ( + // ExactFlags requires same bits (e.g. Locked). + ExactFlags Similarity = iota + // ExactLines requests the exact same arguments on the call line. + ExactLines + // AnyPointer considers different pointers a similar call line. + AnyPointer + // AnyValue accepts any value as similar call line. + AnyValue +) + +// Function is a function call. +// +// Go stack traces print a mangled function call, this wrapper unmangle the +// string before printing and adds other filtering methods. +type Function struct { + Raw string +} + +// String is the fully qualified function name. +// +// Sadly Go is a bit confused when the package name doesn't match the directory +// containing the source file and will use the directory name instead of the +// real package name. +func (f Function) String() string { + s, _ := url.QueryUnescape(f.Raw) + return s +} + +// Name is the naked function name. +func (f Function) Name() string { + parts := strings.SplitN(filepath.Base(f.Raw), ".", 2) + if len(parts) == 1 { + return parts[0] + } + return parts[1] +} + +// PkgName is the package name for this function reference. +func (f Function) PkgName() string { + parts := strings.SplitN(filepath.Base(f.Raw), ".", 2) + if len(parts) == 1 { + return "" + } + s, _ := url.QueryUnescape(parts[0]) + return s +} + +// PkgDotName returns "<package>.<func>" format. +func (f Function) PkgDotName() string { + parts := strings.SplitN(filepath.Base(f.Raw), ".", 2) + s, _ := url.QueryUnescape(parts[0]) + if len(parts) == 1 { + return parts[0] + } + if s != "" || parts[1] != "" { + return s + "." + parts[1] + } + return "" +} + +// IsExported returns true if the function is exported. +func (f Function) IsExported() bool { + name := f.Name() + parts := strings.Split(name, ".") + r, _ := utf8.DecodeRuneInString(parts[len(parts)-1]) + if unicode.ToUpper(r) == r { + return true + } + return f.PkgName() == "main" && name == "main" +} + +// Arg is an argument on a Call. +type Arg struct { + Value uint64 // Value is the raw value as found in the stack trace + Name string // Name is a pseudo name given to the argument +} + +// IsPtr returns true if we guess it's a pointer. It's only a guess, it can be +// easily be confused by a bitmask. +func (a *Arg) IsPtr() bool { + // Assumes all pointers are above 16Mb and positive. + return a.Value > 16*1024*1024 && a.Value < math.MaxInt64 +} + +func (a Arg) String() string { + if a.Name != "" { + return a.Name + } + if a.Value == 0 { + return "0" + } + return fmt.Sprintf("0x%x", a.Value) +} + +// Args is a series of function call arguments. +type Args struct { + Values []Arg // Values is the arguments as shown on the stack trace. They are mangled via simplification. + Processed []string // Processed is the arguments generated from processing the source files. It can have a length lower than Values. + Elided bool // If set, it means there was a trailing ", ..." +} + +func (a Args) String() string { + var v []string + if len(a.Processed) != 0 { + v = make([]string, 0, len(a.Processed)) + for _, item := range a.Processed { + v = append(v, item) + } + } else { + v = make([]string, 0, len(a.Values)) + for _, item := range a.Values { + v = append(v, item.String()) + } + } + if a.Elided { + v = append(v, "...") + } + return strings.Join(v, ", ") +} + +// Equal returns true only if both arguments are exactly equal. +func (a *Args) Equal(r *Args) bool { + if a.Elided != r.Elided || len(a.Values) != len(r.Values) { + return false + } + for i, l := range a.Values { + if l != r.Values[i] { + return false + } + } + return true +} + +// Similar returns true if the two Args are equal or almost but not quite +// equal. +func (a *Args) Similar(r *Args, similar Similarity) bool { + if a.Elided != r.Elided || len(a.Values) != len(r.Values) { + return false + } + if similar == AnyValue { + return true + } + for i, l := range a.Values { + switch similar { + case ExactFlags, ExactLines: + if l != r.Values[i] { + return false + } + default: + if l.IsPtr() != r.Values[i].IsPtr() || (!l.IsPtr() && l != r.Values[i]) { + return false + } + } + } + return true +} + +// Merge merges two similar Args, zapping out differences. +func (a *Args) Merge(r *Args) Args { + out := Args{ + Values: make([]Arg, len(a.Values)), + Elided: a.Elided, + } + for i, l := range a.Values { + if l != r.Values[i] { + out.Values[i].Name = "*" + out.Values[i].Value = l.Value + } else { + out.Values[i] = l + } + } + return out +} + +// Call is an item in the stack trace. +type Call struct { + SourcePath string // Full path name of the source file + Line int // Line number + Func Function // Fully qualified function name (encoded). + Args Args // Call arguments +} + +// Equal returns true only if both calls are exactly equal. +func (c *Call) Equal(r *Call) bool { + return c.SourcePath == r.SourcePath && c.Line == r.Line && c.Func == r.Func && c.Args.Equal(&r.Args) +} + +// Similar returns true if the two Call are equal or almost but not quite +// equal. +func (c *Call) Similar(r *Call, similar Similarity) bool { + return c.SourcePath == r.SourcePath && c.Line == r.Line && c.Func == r.Func && c.Args.Similar(&r.Args, similar) +} + +// Merge merges two similar Call, zapping out differences. +func (c *Call) Merge(r *Call) Call { + return Call{ + SourcePath: c.SourcePath, + Line: c.Line, + Func: c.Func, + Args: c.Args.Merge(&r.Args), + } +} + +// SourceName returns the base file name of the source file. +func (c *Call) SourceName() string { + return filepath.Base(c.SourcePath) +} + +// SourceLine returns "source.go:line", including only the base file name. +func (c *Call) SourceLine() string { + return fmt.Sprintf("%s:%d", c.SourceName(), c.Line) +} + +// FullSourceLine returns "/path/to/source.go:line". +func (c *Call) FullSourceLine() string { + return fmt.Sprintf("%s:%d", c.SourcePath, c.Line) +} + +// PkgSource is one directory plus the file name of the source file. +func (c *Call) PkgSource() string { + return filepath.Join(filepath.Base(filepath.Dir(c.SourcePath)), c.SourceName()) +} + +const testMainSource = "_test" + string(os.PathSeparator) + "_testmain.go" + +// IsStdlib returns true if it is a Go standard library function. This includes +// the 'go test' generated main executable. +func (c *Call) IsStdlib() bool { + for _, goroot := range goroots { + if strings.HasPrefix(c.SourcePath, goroot) { + return true + } + } + // Consider _test/_testmain.go as stdlib since it's injected by "go test". + return c.PkgSource() == testMainSource +} + +// IsPkgMain returns true if it is in the main package. +func (c *Call) IsPkgMain() bool { + return c.Func.PkgName() == "main" +} + +// Stack is a call stack. +type Stack struct { + Calls []Call // Call stack. First is original function, last is leaf function. + Elided bool // Happens when there's >100 items in Stack, currently hardcoded in package runtime. +} + +// Equal returns true on if both call stacks are exactly equal. +func (s *Stack) Equal(r *Stack) bool { + if len(s.Calls) != len(r.Calls) || s.Elided != r.Elided { + return false + } + for i := range s.Calls { + if !s.Calls[i].Equal(&r.Calls[i]) { + return false + } + } + return true +} + +// Similar returns true if the two Stack are equal or almost but not quite +// equal. +func (s *Stack) Similar(r *Stack, similar Similarity) bool { + if len(s.Calls) != len(r.Calls) || s.Elided != r.Elided { + return false + } + for i := range s.Calls { + if !s.Calls[i].Similar(&r.Calls[i], similar) { + return false + } + } + return true +} + +// Merge merges two similar Stack, zapping out differences. +func (s *Stack) Merge(r *Stack) *Stack { + // Assumes similar stacks have the same length. + out := &Stack{ + Calls: make([]Call, len(s.Calls)), + Elided: s.Elided, + } + for i := range s.Calls { + out.Calls[i] = s.Calls[i].Merge(&r.Calls[i]) + } + return out +} + +// Less compares two Stack, where the ones that are less are more +// important, so they come up front. A Stack with more private functions is +// 'less' so it is at the top. Inversely, a Stack with only public +// functions is 'more' so it is at the bottom. +func (s *Stack) Less(r *Stack) bool { + lStdlib := 0 + lPrivate := 0 + for _, c := range s.Calls { + if c.IsStdlib() { + lStdlib++ + } else { + lPrivate++ + } + } + rStdlib := 0 + rPrivate := 0 + for _, s := range r.Calls { + if s.IsStdlib() { + rStdlib++ + } else { + rPrivate++ + } + } + if lPrivate > rPrivate { + return true + } + if lPrivate < rPrivate { + return false + } + if lStdlib > rStdlib { + return false + } + if lStdlib < rStdlib { + return true + } + + // Stack lengths are the same. + for x := range s.Calls { + if s.Calls[x].Func.Raw < r.Calls[x].Func.Raw { + return true + } + if s.Calls[x].Func.Raw > r.Calls[x].Func.Raw { + return true + } + if s.Calls[x].PkgSource() < r.Calls[x].PkgSource() { + return true + } + if s.Calls[x].PkgSource() > r.Calls[x].PkgSource() { + return true + } + if s.Calls[x].Line < r.Calls[x].Line { + return true + } + if s.Calls[x].Line > r.Calls[x].Line { + return true + } + } + return false +} + +// Signature represents the signature of one or multiple goroutines. +// +// It is effectively the stack trace plus the goroutine internal bits, like +// it's state, if it is thread locked, which call site created this goroutine, +// etc. +type Signature struct { + // Use git grep 'gopark(|unlock)\(' to find them all plus everything listed + // in runtime/traceback.go. Valid values includes: + // - chan send, chan receive, select + // - finalizer wait, mark wait (idle), + // - Concurrent GC wait, GC sweep wait, force gc (idle) + // - IO wait, panicwait + // - semacquire, semarelease + // - sleep, timer goroutine (idle) + // - trace reader (blocked) + // Stuck cases: + // - chan send (nil chan), chan receive (nil chan), select (no cases) + // Runnable states: + // - idle, runnable, running, syscall, waiting, dead, enqueue, copystack, + // Scan states: + // - scan, scanrunnable, scanrunning, scansyscall, scanwaiting, scandead, + // scanenqueue + State string + CreatedBy Call // Which other goroutine which created this one. + SleepMin int // Wait time in minutes, if applicable. + SleepMax int // Wait time in minutes, if applicable. + Stack Stack + Locked bool // Locked to an OS thread. +} + +// Equal returns true only if both signatures are exactly equal. +func (s *Signature) Equal(r *Signature) bool { + if s.State != r.State || !s.CreatedBy.Equal(&r.CreatedBy) || s.Locked != r.Locked || s.SleepMin != r.SleepMin || s.SleepMax != r.SleepMax { + return false + } + return s.Stack.Equal(&r.Stack) +} + +// Similar returns true if the two Signature are equal or almost but not quite +// equal. +func (s *Signature) Similar(r *Signature, similar Similarity) bool { + if s.State != r.State || !s.CreatedBy.Similar(&r.CreatedBy, similar) { + return false + } + if similar == ExactFlags && s.Locked != r.Locked { + return false + } + return s.Stack.Similar(&r.Stack, similar) +} + +// Merge merges two similar Signature, zapping out differences. +func (s *Signature) Merge(r *Signature) *Signature { + min := s.SleepMin + if r.SleepMin < min { + min = r.SleepMin + } + max := s.SleepMax + if r.SleepMax > max { + max = r.SleepMax + } + return &Signature{ + State: s.State, // Drop right side. + CreatedBy: s.CreatedBy, // Drop right side. + SleepMin: min, + SleepMax: max, + Stack: *s.Stack.Merge(&r.Stack), + Locked: s.Locked || r.Locked, // TODO(maruel): This is weirdo. + } +} + +// Less compares two Signature, where the ones that are less are more +// important, so they come up front. A Signature with more private functions is +// 'less' so it is at the top. Inversely, a Signature with only public +// functions is 'more' so it is at the bottom. +func (s *Signature) Less(r *Signature) bool { + if s.Stack.Less(&r.Stack) { + return true + } + if r.Stack.Less(&s.Stack) { + return false + } + if s.Locked && !r.Locked { + return true + } + if r.Locked && !s.Locked { + return false + } + if s.State < r.State { + return true + } + if s.State > r.State { + return false + } + return false +} + +// Goroutine represents the state of one goroutine, including the stack trace. +type Goroutine struct { + Signature // It's stack trace, internal bits, state, which call site created it, etc. + ID int // Goroutine ID. + First bool // First is the goroutine first printed, normally the one that crashed. +} + +// Bucketize returns the number of similar goroutines. +func Bucketize(goroutines []Goroutine, similar Similarity) map[*Signature][]Goroutine { + out := map[*Signature][]Goroutine{} + // O(n²). Fix eventually. + for _, routine := range goroutines { + found := false + for key := range out { + // When a match is found, this effectively drops the other goroutine ID. + if key.Similar(&routine.Signature, similar) { + found = true + if !key.Equal(&routine.Signature) { + // Almost but not quite equal. There's different pointers passed + // around but the same values. Zap out the different values. + newKey := key.Merge(&routine.Signature) + out[newKey] = append(out[key], routine) + delete(out, key) + } else { + out[key] = append(out[key], routine) + } + break + } + } + if !found { + key := &Signature{} + *key = routine.Signature + out[key] = []Goroutine{routine} + } + } + return out +} + +// Bucket is a stack trace signature and the list of goroutines that fits this +// signature. +type Bucket struct { + Signature + Routines []Goroutine +} + +// First returns true if it contains the first goroutine, e.g. the ones that +// likely generated the panic() call, if any. +func (b *Bucket) First() bool { + for _, r := range b.Routines { + if r.First { + return true + } + } + return false +} + +// Less does reverse sort. +func (b *Bucket) Less(r *Bucket) bool { + if b.First() { + return true + } + if r.First() { + return false + } + return b.Signature.Less(&r.Signature) +} + +// Buckets is a list of Bucket sorted by repeation count. +type Buckets []Bucket + +func (b Buckets) Len() int { + return len(b) +} + +func (b Buckets) Less(i, j int) bool { + return b[i].Less(&b[j]) +} + +func (b Buckets) Swap(i, j int) { + b[j], b[i] = b[i], b[j] +} + +// SortBuckets creates a list of Bucket from each goroutine stack trace count. +func SortBuckets(buckets map[*Signature][]Goroutine) Buckets { + out := make(Buckets, 0, len(buckets)) + for signature, count := range buckets { + out = append(out, Bucket{*signature, count}) + } + sort.Sort(out) + return out +} + +// scanLines is similar to bufio.ScanLines except that it: +// - doesn't drop '\n' +// - doesn't strip '\r' +// - returns when the data is bufio.MaxScanTokenSize bytes +func scanLines(data []byte, atEOF bool) (advance int, token []byte, err error) { + if atEOF && len(data) == 0 { + return 0, nil, nil + } + if i := bytes.IndexByte(data, '\n'); i >= 0 { + return i + 1, data[0 : i+1], nil + } + if atEOF { + return len(data), data, nil + } + if len(data) >= bufio.MaxScanTokenSize { + // Returns the line even if it is not at EOF nor has a '\n', otherwise the + // scanner will return bufio.ErrTooLong which is definitely not what we + // want. + return len(data), data, nil + } + return 0, nil, nil +} + +// ParseDump processes the output from runtime.Stack(). +// +// It supports piping from another command and assumes there is junk before the +// actual stack trace. The junk is streamed to out. +func ParseDump(r io.Reader, out io.Writer) ([]Goroutine, error) { + goroutines := make([]Goroutine, 0, 16) + var goroutine *Goroutine + scanner := bufio.NewScanner(r) + scanner.Split(scanLines) + // TODO(maruel): Use a formal state machine. Patterns follows: + // - reRoutineHeader + // Either: + // - reUnavail + // - reFunc + reFile in a loop + // - reElided + // Optionally ends with: + // - reCreated + reFile + // Between each goroutine stack dump: an empty line + created := false + // firstLine is the first line after the reRoutineHeader header line. + firstLine := false + for scanner.Scan() { + line := scanner.Text() + if line == "\n" { + if goroutine != nil { + goroutine = nil + continue + } + } else if line[len(line)-1] == '\n' { + if goroutine == nil { + if match := reRoutineHeader.FindStringSubmatch(line); match != nil { + if id, err := strconv.Atoi(match[1]); err == nil { + // See runtime/traceback.go. + // "<state>, \d+ minutes, locked to thread" + items := strings.Split(match[2], ", ") + sleep := 0 + locked := false + for i := 1; i < len(items); i++ { + if items[i] == lockedToThread { + locked = true + continue + } + // Look for duration, if any. + if match2 := reMinutes.FindStringSubmatch(items[i]); match2 != nil { + sleep, _ = strconv.Atoi(match2[1]) + } + } + goroutines = append(goroutines, Goroutine{ + Signature: Signature{ + State: items[0], + SleepMin: sleep, + SleepMax: sleep, + Locked: locked, + }, + ID: id, + First: len(goroutines) == 0, + }) + goroutine = &goroutines[len(goroutines)-1] + firstLine = true + continue + } + } + } else { + if firstLine { + firstLine = false + if match := reUnavail.FindStringSubmatch(line); match != nil { + // Generate a fake stack entry. + goroutine.Stack.Calls = []Call{{SourcePath: "<unavailable>"}} + continue + } + } + + if match := reFile.FindStringSubmatch(line); match != nil { + // Triggers after a reFunc or a reCreated. + num, err := strconv.Atoi(match[2]) + if err != nil { + return goroutines, fmt.Errorf("failed to parse int on line: \"%s\"", line) + } + if created { + created = false + goroutine.CreatedBy.SourcePath = match[1] + goroutine.CreatedBy.Line = num + } else { + i := len(goroutine.Stack.Calls) - 1 + if i < 0 { + return goroutines, errors.New("unexpected order") + } + goroutine.Stack.Calls[i].SourcePath = match[1] + goroutine.Stack.Calls[i].Line = num + } + continue + } + + if match := reCreated.FindStringSubmatch(line); match != nil { + created = true + goroutine.CreatedBy.Func.Raw = match[1] + continue + } + + if match := reFunc.FindStringSubmatch(line); match != nil { + args := Args{} + for _, a := range strings.Split(match[2], ", ") { + if a == "..." { + args.Elided = true + continue + } + if a == "" { + // Remaining values were dropped. + break + } + v, err := strconv.ParseUint(a, 0, 64) + if err != nil { + return goroutines, fmt.Errorf("failed to parse int on line: \"%s\"", line) + } + args.Values = append(args.Values, Arg{Value: v}) + } + goroutine.Stack.Calls = append(goroutine.Stack.Calls, Call{Func: Function{match[1]}, Args: args}) + continue + } + + if match := reElided.FindStringSubmatch(line); match != nil { + goroutine.Stack.Elided = true + continue + } + } + } + _, _ = io.WriteString(out, line) + goroutine = nil + } + nameArguments(goroutines) + return goroutines, scanner.Err() +} + +// Private stuff. + +func nameArguments(goroutines []Goroutine) { + // Set a name for any pointer occuring more than once. + type object struct { + args []*Arg + inPrimary bool + id int + } + objects := map[uint64]object{} + // Enumerate all the arguments. + for i := range goroutines { + for j := range goroutines[i].Stack.Calls { + for k := range goroutines[i].Stack.Calls[j].Args.Values { + arg := goroutines[i].Stack.Calls[j].Args.Values[k] + if arg.IsPtr() { + objects[arg.Value] = object{ + args: append(objects[arg.Value].args, &goroutines[i].Stack.Calls[j].Args.Values[k]), + inPrimary: objects[arg.Value].inPrimary || i == 0, + } + } + } + } + // CreatedBy.Args is never set. + } + order := uint64Slice{} + for k, obj := range objects { + if len(obj.args) > 1 && obj.inPrimary { + order = append(order, k) + } + } + sort.Sort(order) + nextID := 1 + for _, k := range order { + for _, arg := range objects[k].args { + arg.Name = fmt.Sprintf("#%d", nextID) + } + nextID++ + } + + // Now do the rest. This is done so the output is deterministic. + order = uint64Slice{} + for k := range objects { + order = append(order, k) + } + sort.Sort(order) + for _, k := range order { + // Process the remaining pointers, they were not referenced by primary + // thread so will have higher IDs. + if objects[k].inPrimary { + continue + } + for _, arg := range objects[k].args { + arg.Name = fmt.Sprintf("#%d", nextID) + } + nextID++ + } +} + +type uint64Slice []uint64 + +func (a uint64Slice) Len() int { return len(a) } +func (a uint64Slice) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a uint64Slice) Less(i, j int) bool { return a[i] < a[j] } diff --git a/vendor/github.com/maruel/panicparse/stack/ui.go b/vendor/github.com/maruel/panicparse/stack/ui.go new file mode 100644 index 000000000..b125fc940 --- /dev/null +++ b/vendor/github.com/maruel/panicparse/stack/ui.go @@ -0,0 +1,139 @@ +// Copyright 2016 Marc-Antoine Ruel. All rights reserved. +// Use of this source code is governed under the Apache License, Version 2.0 +// that can be found in the LICENSE file. + +package stack + +import ( + "fmt" + "strings" +) + +// Palette defines the color used. +// +// An empty object Palette{} can be used to disable coloring. +type Palette struct { + EOLReset string + + // Routine header. + RoutineFirst string // The first routine printed. + Routine string // Following routines. + CreatedBy string + + // Call line. + Package string + SourceFile string + FunctionStdLib string + FunctionStdLibExported string + FunctionMain string + FunctionOther string + FunctionOtherExported string + Arguments string +} + +// CalcLengths returns the maximum length of the source lines and package names. +func CalcLengths(buckets Buckets, fullPath bool) (int, int) { + srcLen := 0 + pkgLen := 0 + for _, bucket := range buckets { + for _, line := range bucket.Signature.Stack.Calls { + l := 0 + if fullPath { + l = len(line.FullSourceLine()) + } else { + l = len(line.SourceLine()) + } + if l > srcLen { + srcLen = l + } + l = len(line.Func.PkgName()) + if l > pkgLen { + pkgLen = l + } + } + } + return srcLen, pkgLen +} + +// functionColor returns the color to be used for the function name based on +// the type of package the function is in. +func (p *Palette) functionColor(line *Call) string { + if line.IsStdlib() { + if line.Func.IsExported() { + return p.FunctionStdLibExported + } + return p.FunctionStdLib + } else if line.IsPkgMain() { + return p.FunctionMain + } else if line.Func.IsExported() { + return p.FunctionOtherExported + } + return p.FunctionOther +} + +// routineColor returns the color for the header of the goroutines bucket. +func (p *Palette) routineColor(bucket *Bucket, multipleBuckets bool) string { + if bucket.First() && multipleBuckets { + return p.RoutineFirst + } + return p.Routine +} + +// BucketHeader prints the header of a goroutine signature. +func (p *Palette) BucketHeader(bucket *Bucket, fullPath, multipleBuckets bool) string { + extra := "" + if bucket.SleepMax != 0 { + if bucket.SleepMin != bucket.SleepMax { + extra += fmt.Sprintf(" [%d~%d minutes]", bucket.SleepMin, bucket.SleepMax) + } else { + extra += fmt.Sprintf(" [%d minutes]", bucket.SleepMax) + } + } + if bucket.Locked { + extra += " [locked]" + } + created := bucket.CreatedBy.Func.PkgDotName() + if created != "" { + created += " @ " + if fullPath { + created += bucket.CreatedBy.FullSourceLine() + } else { + created += bucket.CreatedBy.SourceLine() + } + extra += p.CreatedBy + " [Created by " + created + "]" + } + return fmt.Sprintf( + "%s%d: %s%s%s\n", + p.routineColor(bucket, multipleBuckets), len(bucket.Routines), + bucket.State, extra, + p.EOLReset) +} + +// callLine prints one stack line. +func (p *Palette) callLine(line *Call, srcLen, pkgLen int, fullPath bool) string { + src := "" + if fullPath { + src = line.FullSourceLine() + } else { + src = line.SourceLine() + } + return fmt.Sprintf( + " %s%-*s %s%-*s %s%s%s(%s)%s", + p.Package, pkgLen, line.Func.PkgName(), + p.SourceFile, srcLen, src, + p.functionColor(line), line.Func.Name(), + p.Arguments, line.Args, + p.EOLReset) +} + +// StackLines prints one complete stack trace, without the header. +func (p *Palette) StackLines(signature *Signature, srcLen, pkgLen int, fullPath bool) string { + out := make([]string, len(signature.Stack.Calls)) + for i := range signature.Stack.Calls { + out[i] = p.callLine(&signature.Stack.Calls[i], srcLen, pkgLen, fullPath) + } + if signature.Stack.Elided { + out = append(out, " (...)") + } + return strings.Join(out, "\n") + "\n" +} diff --git a/vendor/github.com/maruel/panicparse/vendor.yml b/vendor/github.com/maruel/panicparse/vendor.yml new file mode 100644 index 000000000..ff3d43f5f --- /dev/null +++ b/vendor/github.com/maruel/panicparse/vendor.yml @@ -0,0 +1,17 @@ +vendors: +- path: github.com/kr/pretty + rev: 737b74a46c4bf788349f72cb256fed10aea4d0ac +- path: github.com/kr/text + rev: 7cafcd837844e784b526369c9bce262804aebc60 +- path: github.com/maruel/ut + rev: a9c9f15ccfa6f8b90182a53df32f4745586fbae3 +- path: github.com/mattn/go-colorable + rev: 9056b7a9f2d1f2d96498d6d146acd1f9d5ed3d59 +- path: github.com/mattn/go-isatty + rev: 56b76bdf51f7708750eac80fa38b952bb9f32639 +- path: github.com/mgutz/ansi + rev: c286dcecd19ff979eeb73ea444e479b903f2cfcb +- path: github.com/pmezard/go-difflib + rev: 792786c7400a136282c1664665ae0a8db921c6c2 +- path: golang.org/x/sys + rev: a646d33e2ee3172a661fc09bca23bb4889a41bc8 |