Giter Club home page Giter Club logo

panicparse's Introduction

panicparse

Parses panic stack traces, densifies and deduplicates goroutines with similar stack traces. Helps debugging crashes and deadlocks in heavily parallelized process.

PkgGoDev codecov go-recipes

panicparse helps make sense of Go crash dumps:

Screencast

Features

  • Race detector support, e.g. it can parse output produced by go test -race
  • HTML export.
  • Easy to use as an HTTP Handler middleware.
  • High performance parsing.
  • HTTP web server that serves a very tight and swell snapshot of your goroutines, much more readable than net/http/pprof.
  • >50% more compact output than original stack dump yet more readable.
  • 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.
  • Parses the source files if available to augment the output.
  • Works on any platform supported by Go, including Windows, macOS, linux.
  • Full go module support.
  • Requires >=go1.17. Use v2.3.1 for older Go versions.

Installation

go install github.com/maruel/panicparse/v2/cmd/pp@latest

Usage

Piping a stack trace from another process

TL;DR

  • Ubuntu (bash v4 or zsh): |&
  • macOS, install bash 4+, then: |&
  • Windows or macOS with stock bash v3: 2>&1 |
  • Fish 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 via the native print() function.

Bash v4 or zsh: |& tells the shell to redirect stderr to stdout, it's an alias for 2>&1 | (bash v4, zsh):

go test -v |&pp

Windows or macOS native bash (which is 3.2.57): They don't have this shortcut, so use the long form:

go test -v 2>&1 | pp

Fish: &| redirects stderr and stdout. It's an alias for 2>&1 | (fish piping):

go test -v &| pp

PowerShell: It has broken 2>&1 redirection. 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

Disable inlining

The Go toolchain inlines functions when it can. This causes traces to be less informative. Optimization also interfere with traces. You can use the following to help diagnosing issues:

go install -gcflags '-N -l' path/to/foo
foo |& pp

or

go test -gcflags '-N -l' ./... |& pp

Run go tool compile -help to get the full list of valid values for -gcflags.

GOTRACEBACK

By default, GOTRACEBACK defaults to single, which means that a panic will only return the current goroutine trace alone. 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 macOS

Install bash v4+ on macOS via homebrew or macports. Your future self will appreciate having done that.

If you have /usr/bin/pp installed

If you try pp for the first time and you get:

Creating tables and indexes...
Done.

and/or

/usr/bin/pp5.18: No input files specified

you may be running the Perl PAR Packager instead of panicparse.

You have two choices, either you put $GOPATH/bin at the beginning of $PATH or use long name panicparse with:

go install github.com/maruel/panicparse/v2@latest

then using panicparse instead of pp:

go test 2> panicparse

Hint: You may also use shell aliases

alias gp=panicparse
go test 2> gp

alias p=panicparse
go test 2> p

webstack in action

The webstack.SnapshotHandler http.Handler enables glancing at at a snapshot of your process trivially:

Screencast

Authors

panicparse was created with ❤️️ and passion by Marc-Antoine Ruel and friends.

panicparse's People

Contributors

aleksi avatar dallbee avatar derekperkins avatar gwik avatar jtagcat avatar kashav avatar maruel avatar mattn avatar mordfustang21 avatar nikolaydubina avatar nvanbenschoten avatar oneofone avatar tbg avatar themartorana avatar xyproto avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

panicparse's Issues

Create a single GOROOT / GOPATH guesser with a flag to turn that off.

Use case

For each of GOROOT and GOPATH:

  • Build Go executable on CI at path /x
  • Runs Go executable on prod at path /y
  • Devs checkout sources at path /z

Current state

There's currently an hard coded variable of 'well known goroots' in stack/stack.go#L63 to help disambiguation for GOROOT. It's a cheap effort at best. There's no such guessing for GOPATH.

Design

Be more 'smarty pants' about guessing both GOROOT and GOPATH. The way to achieve this is to spot check files in the stack trace, and try to match the two roots by finding the exact match of files. If an exact match is found, use this to rebase the paths.

That said, this kind of 'smart' behaviour can occasionally be counter productive, so a single boolean flag has be provided to disable this completely, i.e. -norebase

Rejected alternatives

A naive implementation would be to add two flags to handle absolute path rebasing of GOROOT and GOPATH, e.g. -goroot <path> and -gopath <path>. Doing so will be error prone for users, and most users wouldn't realize they need to calculate the values, leading to no source parsing, which is currently the case for the use case above. That's silent failure and nobody likes this.

panicparse mangles cgo stack traces.

We have a cgo call in our log that gets slightly mangled by panic parse:
From the raw log:

goroutine 56 [runnable, locked to thread]: 
github.com/cockroachdb/cockroach/storage/engine._Cfunc_DBIterSeek(0x7f147f5fc2c0, 0xc82996fd80, 0x10, 0x0, 0x0, 0xc820024801, 0x7f14089fbc88, 0xc800000010, 0x0, 0xc800000000, ...)
  ??:0 +0x6d
github.com/cockroachdb/cockroach/storage/engine.(*rocksDBIterator).Seek(0xc8465534a0, 0xc82996fd80, 0x10, 0x20, 0x0, 0xc800000000)
  /go/src/github.com/cockroachdb/cockroach/storage/engine/rocksdb.go:634 +0x421
github.com/cockroachdb/cockroach/storage/engine.MVCCIterate(0x7f15818a6400, 0xc82028bd40, 0xc82996fd80, 0x10, 0x20, 0xc82996fda0, 0x10, 0x20, 0x0, 0x0, ...)
  /go/src/github.com/cockroachdb/cockroach/storage/engine/mvcc.go:1266 +0x131d
github.com/cockroachdb/cockroach/storage.(*Replica).entries(0xc82053aa80, 0x7f15818a6400, 0xc82028bd40, 0xe2ff0, 0xe2ff1, 0xffffffffffffffff, 0x0, 0x0, 0x0, 0x0, ...)
  /go/src/github.com/cockroachdb/cockroach/storage/replica_raftstorage.go:120 +0x436
github.com/cockroachdb/cockroach/storage.(*Replica).Entries(0xc82053aa80, 0xe2ff0, 0xe2ff1, 0xffffffffffffffff, 0x0, 0x0, 0x0, 0x0, 0x0)
  /go/src/github.com/cockroachdb/cockroach/storage/replica_raftstorage.go:86 +0xa7
github.com/coreos/etcd/raft.(*raftLog).slice(0xc820e6f880, 0xe2ff0, 0xe2ff1, 0xffffffffffffffff, 0x0, 0x0, 0x0, 0x0, 0x0)
  /go/src/github.com/coreos/etcd/raft/log.go:307 +0x195
github.com/coreos/etcd/raft.(*raftLog).nextEnts(0xc820e6f880, 0x0, 0x0, 0x0)
  /go/src/github.com/coreos/etcd/raft/log.go:142 +0x9f
github.com/coreos/etcd/raft.newReady(0xc827880300, 0xc8462a1070, 0xb, 0x4, 0xe2fef, 0x0, 0x0, 0x0, 0x0, 0x0, ...)
  /go/src/github.com/coreos/etcd/raft/node.go:475 +0x70
github.com/coreos/etcd/raft.(*RawNode).newReady(0xc833b899c0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, ...)
  /go/src/github.com/coreos/etcd/raft/rawnode.go:40 +0x60
github.com/coreos/etcd/raft.(*RawNode).Ready(0xc833b899c0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, ...)
  /go/src/github.com/coreos/etcd/raft/rawnode.go:182 +0x43
github.com/cockroachdb/cockroach/storage.(*Replica).handleRaftReady(0xc82053aa80, 0x0, 0x0)
  /go/src/github.com/cockroachdb/cockroach/storage/replica.go:1167 +0x10e
github.com/cockroachdb/cockroach/storage.(*Store).processRaft.func1()
  /go/src/github.com/cockroachdb/cockroach/storage/store.go:1802 +0x35a
github.com/cockroachdb/cockroach/util/stop.(*Stopper).RunWorker.func1(0xc8202b0e00, 0xc820209300)
  /go/src/github.com/cockroachdb/cockroach/util/stop/stopper.go:139 +0x52
created by github.com/cockroachdb/cockroach/util/stop.(*Stopper).RunWorker
  /go/src/github.com/cockroachdb/cockroach/util/stop/stopper.go:140 +0x62

After running through the latest panicparse (no args), I get the following slightly-truncated trace at the very beginning of the generated output (eg: before the other goroutines in panicparse's output):

  ??:0 +0x6d
github.com/cockroachdb/cockroach/storage/engine.(*rocksDBIterator).Seek(0xc8465534a0, 0xc82996fd80, 0x10, 0x20, 0x0, 0xc800000000)
  /go/src/github.com/cockroachdb/cockroach/storage/engine/rocksdb.go:634 +0x421
github.com/cockroachdb/cockroach/storage/engine.MVCCIterate(0x7f15818a6400, 0xc82028bd40, 0xc82996fd80, 0x10, 0x20, 0xc82996fda0, 0x10, 0x20, 0x0, 0x0, ...)
  /go/src/github.com/cockroachdb/cockroach/storage/engine/mvcc.go:1266 +0x131d
github.com/cockroachdb/cockroach/storage.(*Replica).entries(0xc82053aa80, 0x7f15818a6400, 0xc82028bd40, 0xe2ff0, 0xe2ff1, 0xffffffffffffffff, 0x0, 0x0, 0x0, 0x0, ...)
  /go/src/github.com/cockroachdb/cockroach/storage/replica_raftstorage.go:120 +0x436
github.com/cockroachdb/cockroach/storage.(*Replica).Entries(0xc82053aa80, 0xe2ff0, 0xe2ff1, 0xffffffffffffffff, 0x0, 0x0, 0x0, 0x0, 0x0)
  /go/src/github.com/cockroachdb/cockroach/storage/replica_raftstorage.go:86 +0xa7
github.com/coreos/etcd/raft.(*raftLog).slice(0xc820e6f880, 0xe2ff0, 0xe2ff1, 0xffffffffffffffff, 0x0, 0x0, 0x0, 0x0, 0x0)
  /go/src/github.com/coreos/etcd/raft/log.go:307 +0x195
github.com/coreos/etcd/raft.(*raftLog).nextEnts(0xc820e6f880, 0x0, 0x0, 0x0)
  /go/src/github.com/coreos/etcd/raft/log.go:142 +0x9f
github.com/coreos/etcd/raft.newReady(0xc827880300, 0xc8462a1070, 0xb, 0x4, 0xe2fef, 0x0, 0x0, 0x0, 0x0, 0x0, ...)
  /go/src/github.com/coreos/etcd/raft/node.go:475 +0x70
github.com/coreos/etcd/raft.(*RawNode).newReady(0xc833b899c0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, ...)
  /go/src/github.com/coreos/etcd/raft/rawnode.go:40 +0x60
github.com/coreos/etcd/raft.(*RawNode).Ready(0xc833b899c0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, ...)
  /go/src/github.com/coreos/etcd/raft/rawnode.go:182 +0x43
github.com/cockroachdb/cockroach/storage.(*Replica).handleRaftReady(0xc82053aa80, 0x0, 0x0)
  /go/src/github.com/cockroachdb/cockroach/storage/replica.go:1167 +0x10e
github.com/cockroachdb/cockroach/storage.(*Store).processRaft.func1()
  /go/src/github.com/cockroachdb/cockroach/storage/store.go:1802 +0x35a
github.com/cockroachdb/cockroach/util/stop.(*Stopper).RunWorker.func1(0xc8202b0e00, 0xc820209300)
  /go/src/github.com/cockroachdb/cockroach/util/stop/stopper.go:139 +0x52
created by github.com/cockroachdb/cockroach/util/stop.(*Stopper).RunWorker
  /go/src/github.com/cockroachdb/cockroach/util/stop/stopper.go:140 +0x62

And the following entry corresponding to the beginning of the actual trace:

1: runnable [locked]
    engine         .:0                     _Cfunc_DBIterSeek(#470, #429, 0x10, 0, 0, #77, #469, #56, 0, #4, ...)

Please find attached the full logs. This is from https://github.com/cockroachdb/cockroach and covers the entire lifetime of that process.
Full log:
node0.log.txt

Output from cat node0.log.txt | pp > node0.parsed-plain.txt
node0.parsed-plain.txt

TestPanicweb fail with "expected 4 calls, got 5" on ppc64{,le}

As revealed by failed ppc64el build on https://buildd.debian.org/status/package.php?p=panicparse, and reproducible with GOARCH=ppc64le and GOARCH=ppc64 on my Debian amd64 machine:

$ GOARCH=ppc64le GOOS=linux go test ./...
?   	github.com/maruel/panicparse	[no test files]
ok  	github.com/maruel/panicparse/cmd/panic	(cached)
?   	github.com/maruel/panicparse/cmd/panic/internal	[no test files]
?   	github.com/maruel/panicparse/cmd/panic/internal/incorrect	[no test files]
?   	github.com/maruel/panicparse/cmd/panic/internal/utf8	[no test files]
?   	github.com/maruel/panicparse/cmd/panicweb	[no test files]
?   	github.com/maruel/panicparse/cmd/panicweb/internal	[no test files]
?   	github.com/maruel/panicparse/cmd/pp	[no test files]
ok  	github.com/maruel/panicparse/internal	(cached)
ok  	github.com/maruel/panicparse/internal/htmlstack	(cached)
?   	github.com/maruel/panicparse/internal/internaltest	[no test files]
compile failure: exit status 2
go build: -race requires cgo
--- FAIL: TestPanicweb (3.38s)
    context_test.go:1619: expected 4 calls, got 5
FAIL
FAIL	github.com/maruel/panicparse/stack	8.528s
ok  	github.com/maruel/panicparse/stack/webstack	(cached)
FAIL

Tested with Go 1.14.7, 1.15.15, 1.16.7 and 1.17.

Thanks in advance!

Create alternative HTML output format

Generating an HTML page instead of CLI ouput is a matter of processing the output of stack.ParseDump() which is a slice []stack.Goroutine and displaying it nicely.

There's two options; dump it to disk or create a server. The later is tricky because it means it has to outlive the calling process. I don't have personal opinion.

pp panics on accessing array index out of range

Running pp with output from my long running process panics here:

if int(n.Pos()) >= p.lineToByteOffset[l] {

$ curl https://gist.githubusercontent.com/TrueFurby/27e50782a159ee8191774b230bd377af/raw/1b043460b3333b2c5d125cf7e694927da44167c5/gistfile1.txt | pp
panic: runtime error: index out of range

goroutine 1 [running]:
github.com/maruel/panicparse/stack.(*parsedFile).getFuncAST.func1(0x5db100, 0xc420077c80, 0xc420418b00)
	/home/furby/go/src/github.com/maruel/panicparse/stack/source.go:139 +0x14c
go/ast.inspector.Visit(0xc42054c700, 0x5db100, 0xc420077c80, 0x0, 0x0)
	/usr/local/go/src/go/ast/walk.go:373 +0x3a
go/ast.Walk(0x5da4c0, 0xc42054c700, 0x5db100, 0xc420077c80)
	/usr/local/go/src/go/ast/walk.go:52 +0x66
go/ast.Inspect(0x5db100, 0xc420077c80, 0xc42054c700)
	/usr/local/go/src/go/ast/walk.go:385 +0x4b
github.com/maruel/panicparse/stack.(*parsedFile).getFuncAST(0xc42051ec60, 0xc42010d484, 0xf, 0x1ae, 0xc4200c5820)
	/home/furby/go/src/github.com/maruel/panicparse/stack/source.go:129 +0x103
github.com/maruel/panicparse/stack.(*cache).getFuncAST(0xc4200bfb18, 0xc42014c720, 0x0)
	/home/furby/go/src/github.com/maruel/panicparse/stack/source.go:105 +0xb2
github.com/maruel/panicparse/stack.(*cache).augmentGoroutine(0xc4200bfb18, 0xc42020a5c0)
	/home/furby/go/src/github.com/maruel/panicparse/stack/source.go:58 +0x107
github.com/maruel/panicparse/stack.Augment(0xc42020a000, 0xc0, 0x10b)
	/home/furby/go/src/github.com/maruel/panicparse/stack/source.go:34 +0x5a
github.com/maruel/panicparse/internal.process(0x5da380, 0xc42000c010, 0x5da3c0, 0xc42000c018, 0x5ee300, 0x2, 0x100, 0xc42003fef0, 0x4f5cef)
	/home/furby/go/src/github.com/maruel/panicparse/internal/main.go:67 +0x2ea
github.com/maruel/panicparse/internal.Main(0x0, 0x0)
	/home/furby/go/src/github.com/maruel/panicparse/internal/main.go:138 +0x3b5
main.main()
	/home/furby/go/src/github.com/maruel/panicparse/cmd/pp/main.go:27 +0x26

goroutine 5 [syscall]:
os/signal.signal_recv(0x0)
	/usr/local/go/src/runtime/sigqueue.go:131 +0xa6
os/signal.loop()
	/usr/local/go/src/os/signal/signal_unix.go:22 +0x22
created by os/signal.init.0
	/usr/local/go/src/os/signal/signal_unix.go:28 +0x41

goroutine 6 [chan receive]:
github.com/maruel/panicparse/internal.Main.func1(0xc42005e1e0)
	/home/furby/go/src/github.com/maruel/panicparse/internal/main.go:93 +0x34
created by github.com/maruel/panicparse/internal.Main
	/home/furby/go/src/github.com/maruel/panicparse/internal/main.go:91 +0xa3

goroutine 7 [select, locked to thread]:
runtime.gopark(0x549340, 0x0, 0x5409fc, 0x6, 0x18, 0x1)
	/usr/local/go/src/runtime/proc.go:277 +0x12c
runtime.selectgo(0xc420040f50, 0xc42005e2a0)
	/usr/local/go/src/runtime/select.go:395 +0x1138
runtime.ensureSigM.func1()
	/usr/local/go/src/runtime/signal_unix.go:511 +0x220
runtime.goexit()
	/usr/local/go/src/runtime/asm_amd64.s:2337 +0x1

"no input file specified" error

Hi,

Trying to debug one panicing test on Mac OS Sierra, Go 1.8, via Terminal 2 (also installed bash v4 - with the same result), but getting "/usr/bin/pp5.18: No input files specified" whatever I do.

myuser: ~/go/src/mydirectory $ go test --tags all --run TestExtractUserIDFromReqToken -v 2>&1 | pp
Creating tables and indexes...
Done.

TOTAL TIME: 0.83 sec.
=== RUN   TestExtractUserIDFromReqToken
--- FAIL: TestExtractUserIDFromReqToken (0.00s)
panic: runtime error: invalid memory address or nil pointer dereference [recovered]
	panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x38 pc=0x17c767c]

goroutine 21 [running]:
testing.tRunner.func1(0xc42031c340)
	/usr/local/go/src/testing/testing.go:622 +0x29d
panic(0x18dc760, 0x1ec7fe0)
	/usr/local/go/src/runtime/panic.go:489 +0x2cf
blah-blah/usersvc.TestExtractUserIDFromReqToken(0xc42031c340)
	/Users/myuser/go/src/blah-blah/testfile.go:20 +0x5c
testing.tRunner(0xc42031c340, 0x1a176c8)
	/usr/local/go/src/testing/testing.go:657 +0x96
created by testing.(*T).Run
	/usr/local/go/src/testing/testing.go:697 +0x2ca
exit status 2
FAIL	blah-blah	0.877s
**/usr/bin/pp5.18: No input files specified**

Please let me know if I'm using arguments correctly?
Thank you!!

Process the source files to deduce types and improve presentation

string

package main

func foo(a string) { panic(a) }
func main()        { foo("hi") }

Actual

1: running
    main    a.go:3           foo(0x4418a0, 0x2)
    main    a.go:4           main()

This is not optimal. Conceptually to the user, there was only one argument, not two. But the compiler sees only the low level arguments.

Expected

By having panicparse process the source files, it could instead print:

1: running
    main    a.go:7           foo(string len=2)
    main    a.go:4           main()

array and slice

Same for arrays&slices (3 values)

package main

func foo(b []byte) { panic(b) }
func main()        { foo(make([]byte, 5, 10)) }

Actual

1: running
    main    b.go:3           foo(0xc20800a080, 0x5, 0xa)
    main    b.go:4           main()

Expected

1: running
    main    b.go:3           foo([]byte len=5 cap=10)
    main    b.go:4           main()

Caveat

panicparse must fallback to the current behavior when source files are unavailable or line numbers do not match exactly with what is in the trace.

Support fish shell

Fish doesn't seem to like the | &pp syntax. Is there an equivalent means of using panicparse?

Opening "internal" to the world; export writer as an API

Hey, i'm building some golang app supervising software and found your stacktracing project interesting.
Is it possible to get rid of "internal" package (e.g. rename it to "reallyinternal") so I'll be able to import it and use its functions?

TestAugment/float64 fails on 32-bit architectures (v1.6.1)

The Debian build daemons for 32-bit architectures uncovered an error with TestAugment/float64,
see https://buildd.debian.org/status/package.php?p=panicparse

It was reproducible on my amd64 machine by setting the GOARCH environment variable.

Tested with Go 1.14.7, 1.15.15, 1.16.7, and 1.17.

386, arm, and mipsle (with GOMIPS=softfloat)

$ GOARCH=386 GOOS=linux go test ./...
?   	github.com/maruel/panicparse	[no test files]
ok  	github.com/maruel/panicparse/cmd/panic	(cached)
?   	github.com/maruel/panicparse/cmd/panic/internal	[no test files]
?   	github.com/maruel/panicparse/cmd/panic/internal/incorrect	[no test files]
?   	github.com/maruel/panicparse/cmd/panic/internal/utf8	[no test files]
?   	github.com/maruel/panicparse/cmd/panicweb	[no test files]
?   	github.com/maruel/panicparse/cmd/panicweb/internal	[no test files]
?   	github.com/maruel/panicparse/cmd/pp	[no test files]
ok  	github.com/maruel/panicparse/internal	(cached)
ok  	github.com/maruel/panicparse/internal/htmlstack	(cached)
?   	github.com/maruel/panicparse/internal/internaltest	[no test files]
compile failure: exit status 2
go build: -race is only supported on linux/amd64, linux/ppc64le, linux/arm64, freebsd/amd64, netbsd/amd64, darwin/amd64, darwin/arm64, and windows/amd64
--- FAIL: TestAugment (0.00s)
    --- FAIL: TestAugment/float64 (0.50s)
        source_test.go:468: Call 0, value 0, expected pointer, got 0
FAIL
FAIL	github.com/maruel/panicparse/stack	4.558s
ok  	github.com/maruel/panicparse/stack/webstack	(cached)
FAIL

mipsle (with GOMIPS=softfloat)

$ GOARCH=mips GOMIPS=softfloat go test -run TestAugment ./stack
--- FAIL: TestAugment (0.02s)
    --- FAIL: TestAugment/float64 (0.54s)
        source_test.go:472: Different (-want +got):
              stack.Stack{
              	Calls: []stack.Call{
              		{
              			... // 2 identical fields
              			Line: 6,
              			Func: {Raw: "main.f"},
              			Args: stack.Args{
              				Values: []stack.Arg{
              					{Value: 805306367},
            + 					{},
              				},
              				Processed: nil,
              				Elided:    false,
              			},
              			IsStdlib:   false,
              			RelSrcPath: "",
              		},
              		{SrcPath: "main.go", Line: 3, Func: {Raw: "main.main"}},
              	},
              	Elided: false,
              }
        source_test.go:473: Source code:
            package main
            func main() {
            	f(0.5)
            }
            func f(v float64) {
            	panic("ooh")
            }
        source_test.go:474: Output:
            panic: ooh
            
            goroutine 1 [running]:
            main.f(0x3fe00000, 0x0)
            	/tmp/panicparse297691722/main.go:6 +0x4c
            main.main()
            	/tmp/panicparse297691722/main.go:3 +0x40
            exit status 2
FAIL
FAIL	github.com/maruel/panicparse/stack	4.241s
FAIL

Thanks in advance!

panicparse fails on processing the new stack trace format in go1.17

Repro:

(Used to point to go1.17beta1, go1.17rc1, updated to go1.17)

go get golang.org/dl/go1.17
go1.17 download
go1.17 test ./stack

TestAugment, TestPanic and TestPanicWeb fail.

Manual compile:

git clone https://go.googlesource.com/go golang
cd golang/src
./make.bash
export PATH=$(pwd)/../bin:$PATH
cd /path/to/panicparse
go test ./stack

Improve color palette

Facts:

  • Some Go users use terminal with white background. I suspect this is mostly caused by OSX's Terminal defaults. I expect little to no Linux and Windows users to use white background.
  • Some people cannot distinguish green-red. This is a bigger non-self inflected problem.

Options:

  1. Make the color palette configurable via an environment variable.
  2. Force the background to be black. This would look like hell but would be readable yet very annoying.
  3. Try to read the background color and switch color table dynamically (no idea how to achieve this).
  4. Change the current colors to use a palette that is both readable with black and white backgrounds.

I think option 1 is preferable but it still has the drawback of not working out of the box, requiring reading the help page, then taking action to make the change permanent. This is clumsy and annoying.

So in the meantime, option 4 is more sensible. I need to play with both 256 and 16 colors terminals to ensure I do not make things worse.

Reference: https://news.ycombinator.com/item?id=10634503

Successor

Project continues here. Featuring :

  • Clear stack trace
  • Failing line recognition and highlighting

capture d ecran 2019-02-20 a 15 34 39

How to parse a stacktrace?

There's a beautiful looking Stack struct that I'm hoping to be able to use, but I can't figure out how to generate it from a library. https://pkg.go.dev/github.com/maruel/panicparse/v2/stack#Stack

This is what I was expecting to find, but I can't see anything public that returns a Stack

// Library function
func ParseStack(stackBytes []byte) *Stack

// Usage
myStack := stack.ParseStack(debug.Stack())

My use case is that we wrap errors as we move up the stack and attach contextual data to each frame. We have our own internal stacktrace parser, but are hoping to swap it out for panicparse. Is this something you would like to support?

Add bool option to keep full path

The idea is that pp -full-path (flag name to be determined) would print the full path of each file instead of the default (base name only).

Challenges:

  • Alignment: stdlib vs deep GOPATH package would cause poor alignment.
  • Column width: pp tries hard to make the line as short as possible, this wouldn't be possible with full path.

Equivalent for stack.Call.IsStdLib in v2?

I'm currently in the process of upgrading gobble to panicparse v2.

In panicpase v1, we had an easy way to determine whether a function call belongs to Go's standard library ((stack.Call).IsStdLib), however I don't see an equivalent field in panicparse v2.

Can I have that field back?

fatal error: float64nan (panic before malloc heap initialized) on mips{,le} (Go 1.15+)

go test ./... fails on mips and mipsle with the following fatal error on Go 1.15 and up.

fatal error: float64nan
runtime: panic before malloc heap initialized

No such error when tested with Go 1.14.7.

Workaround on Go 1.15 and above: set GOMIPS=softfloat

Apparently fails on real MIPS machine, and not just cross-compiled version, see https://buildd.debian.org/status/package.php?p=panicparse for 1.6.1-1. (Unfortunately, it failed without log...)

Full test log (where GOMIPS=hardfloat is the default):

$ GOARCH=mips go test ./...
?   	github.com/maruel/panicparse	[no test files]
fatal error: float64nan
runtime: panic before malloc heap initialized

runtime stack:
runtime.throw(0x16e786, 0xa)
	/usr/lib/go-1.16/src/runtime/panic.go:1117 +0x60 fp=0x40800140 sp=0x4080012c pc=0x4ec6c
runtime.check()
	/usr/lib/go-1.16/src/runtime/runtime1.go:252 +0x574 fp=0x40800170 sp=0x40800140 pc=0x650bc
runtime.rt0_go(0x40800303, 0x4080032b, 0x40800366, 0x40800379, 0x0, 0x4080038d, 0x4080039b, 0x408003d1, 0x408003e7, 0x40800463, ...)
	/usr/lib/go-1.16/src/runtime/asm_mipsx.s:57 +0x90 fp=0x40800180 sp=0x40800170 pc=0x896fc
FAIL	github.com/maruel/panicparse/cmd/panic	0.016s
?   	github.com/maruel/panicparse/cmd/panic/internal	[no test files]
?   	github.com/maruel/panicparse/cmd/panic/internal/incorrect	[no test files]
?   	github.com/maruel/panicparse/cmd/panic/internal/utf8	[no test files]
?   	github.com/maruel/panicparse/cmd/panicweb	[no test files]
?   	github.com/maruel/panicparse/cmd/panicweb/internal	[no test files]
?   	github.com/maruel/panicparse/cmd/pp	[no test files]
fatal error: float64nan
runtime: panic before malloc heap initialized

runtime stack:
runtime.throw(0x2baec0, 0xa)
	/usr/lib/go-1.16/src/runtime/panic.go:1117 +0x60 fp=0x40800130 sp=0x4080011c pc=0x50ce8
runtime.check()
	/usr/lib/go-1.16/src/runtime/runtime1.go:252 +0x574 fp=0x40800160 sp=0x40800130 pc=0x67604
runtime.rt0_go(0x408002fe, 0x40800329, 0x40800364, 0x40800377, 0x0, 0x4080038b, 0x40800399, 0x408003cf, 0x408003e5, 0x40800461, ...)
	/usr/lib/go-1.16/src/runtime/asm_mipsx.s:57 +0x90 fp=0x40800170 sp=0x40800160 pc=0x8f3b8
FAIL	github.com/maruel/panicparse/internal	0.024s
fatal error: float64nan
runtime: panic before malloc heap initialized

runtime stack:
runtime.throw(0x228f64, 0xa)
	/usr/lib/go-1.16/src/runtime/panic.go:1117 +0x60 fp=0x40800130 sp=0x4080011c pc=0x50b68
runtime.check()
	/usr/lib/go-1.16/src/runtime/runtime1.go:252 +0x574 fp=0x40800160 sp=0x40800130 pc=0x67234
runtime.rt0_go(0x408002f2, 0x4080031e, 0x40800359, 0x4080036c, 0x0, 0x40800380, 0x4080038e, 0x408003c4, 0x408003da, 0x40800456, ...)
	/usr/lib/go-1.16/src/runtime/asm_mipsx.s:57 +0x90 fp=0x40800170 sp=0x40800160 pc=0x8c52c
FAIL	github.com/maruel/panicparse/internal/htmlstack	0.014s
?   	github.com/maruel/panicparse/internal/internaltest	[no test files]
fatal error: float64nan
runtime: panic before malloc heap initialized

runtime stack:
runtime.throw(0x25a462, 0xa)
	/usr/lib/go-1.16/src/runtime/panic.go:1117 +0x60 fp=0x40800140 sp=0x4080012c pc=0x50c40
runtime.check()
	/usr/lib/go-1.16/src/runtime/runtime1.go:252 +0x574 fp=0x40800170 sp=0x40800140 pc=0x67460
runtime.rt0_go(0x40800307, 0x4080032f, 0x4080036a, 0x4080037d, 0x0, 0x40800391, 0x4080039f, 0x408003d5, 0x408003eb, 0x40800467, ...)
	/usr/lib/go-1.16/src/runtime/asm_mipsx.s:57 +0x90 fp=0x40800180 sp=0x40800170 pc=0x8e75c
FAIL	github.com/maruel/panicparse/stack	0.010s
fatal error: float64nan
runtime: panic before malloc heap initialized

runtime stack:
runtime.throw(0x372dda, 0xa)
	/usr/lib/go-1.16/src/runtime/panic.go:1117 +0x60 fp=0x40800130 sp=0x4080011c pc=0x51304
runtime.check()
	/usr/lib/go-1.16/src/runtime/runtime1.go:252 +0x574 fp=0x40800160 sp=0x40800130 pc=0x679d0
runtime.rt0_go(0x408002f8, 0x40800323, 0x4080035e, 0x40800371, 0x0, 0x40800385, 0x40800393, 0x408003c9, 0x408003df, 0x4080045b, ...)
	/usr/lib/go-1.16/src/runtime/asm_mipsx.s:57 +0x90 fp=0x40800170 sp=0x40800160 pc=0x8ec54
FAIL	github.com/maruel/panicparse/stack/webstack	0.008s
FAIL

Many thanks!

panicparse ignores control-C

This was puzzling to me when trying it out - if you don't supply a filename it just hangs and can't be interrupted.
The behaviour was added in a0eb0ce, but I don't follow the explanation.
I can understand ignoring Ctrl-\, which was added later.

Stack trace not printed

This utility has worked great for us so far, but we had an incident in which panicparse not only failed to format and color the error, it also completely swallowed it.

i.e, with panicparse, we saw this error:

2019/04/15 17:39:51 http: panic serving [::1]:50141: string length out of range: -58

We had to reproduce without panicparse to see the actual stack trace:

2019/04/15 17:44:16 http: panic serving [::1]:50622: string length out of range: -58
goroutine 52 [running]:
net/http.(*conn).serve.func1(0xc000198000)
        /usr/local/Cellar/go/1.12.1/libexec/src/net/http/server.go:1769 +0x139
panic(0x4750f80, 0xc000246260)
        /usr/local/Cellar/go/1.12.1/libexec/src/runtime/panic.go:522 +0x1b5
apollo/micro/internal/tradeconv.ConvAction(0xc0002f40a0, 0x99, 0x99, 0xc00039e640)
        /Users/tshabtay/git/fleet/src/apollo/micro/internal/tradeconv/tradeconv.go:24 +0x21d
apollo/micro/svc/recon/internal/dates.(*API).DatesGet(0xc0002d48d0, 0x495b820, 0xc0005c2150, 0xc000318400, 0x13414cf, 0x4713720, 0x495b820)
        /Users/tshabtay/git/fleet/src/apollo/micro/svc/recon/internal/dates/api.go:50 +0xc29
apollo/micro/svc/recon/internal/restapi.HandlerAPI.func2(0xc000318400, 0x13414cf, 0x457a301, 0xc0001320a0)
        /Users/tshabtay/git/fleet/src/apollo/micro/svc/recon/internal/restapi/configure_recon.go:80 +0x65
apollo/micro/svc/recon/internal/restapi/operations/dates.DatesGetHandlerFunc.Handle(0xc000075540, 0xc000318400, 0x13414cf, 0x4948f00, 0xc0001320a0)
        /Users/tshabtay/git/fleet/src/apollo/micro/svc/recon/internal/restapi/operations/dates/dates_get.go:19 +0x3a
apollo/micro/svc/recon/internal/restapi/operations/dates.(*DatesGet).ServeHTTP(0xc0002a20c0, 0x49594e0, 0xc00039c1c0, 0xc000318400)
        /Users/tshabtay/git/fleet/src/apollo/micro/svc/recon/internal/restapi/operations/dates/dates_get.go:54 +0x16e
apollo/vendor/github.com/go-openapi/runtime/middleware.NewOperationExecutor.func1(0x49594e0, 0xc00039c1c0, 0xc000318400)
        /Users/tshabtay/git/fleet/src/apollo/vendor/github.com/go-openapi/runtime/middleware/operation.go:28 +0x75
net/http.HandlerFunc.ServeHTTP(0xc0002d4150, 0x49594e0, 0xc00039c1c0, 0xc000318400)
        /usr/local/Cellar/go/1.12.1/libexec/src/net/http/server.go:1995 +0x44
apollo/vendor/github.com/go-openapi/runtime/middleware.NewRouter.func1(0x49594e0, 0xc00039c1c0, 0xc000318200)
        /Users/tshabtay/git/fleet/src/apollo/vendor/github.com/go-openapi/runtime/middleware/router.go:76 +0x358
net/http.HandlerFunc.ServeHTTP(0xc0002a31e0, 0x49594e0, 0xc00039c1c0, 0xc000318200)
        /usr/local/Cellar/go/1.12.1/libexec/src/net/http/server.go:1995 +0x44
apollo/vendor/github.com/go-openapi/runtime/middleware.Redoc.func1(0x49594e0, 0xc00039c1c0, 0xc000318200)
        /Users/tshabtay/git/fleet/src/apollo/vendor/github.com/go-openapi/runtime/middleware/redoc.go:72 +0x2a5
net/http.HandlerFunc.ServeHTTP(0xc000576a00, 0x49594e0, 0xc00039c1c0, 0xc000318200)
        /usr/local/Cellar/go/1.12.1/libexec/src/net/http/server.go:1995 +0x44
apollo/vendor/github.com/go-openapi/runtime/middleware.Spec.func1(0x49594e0, 0xc00039c1c0, 0xc000318200)
        /Users/tshabtay/git/fleet/src/apollo/vendor/github.com/go-openapi/runtime/middleware/spec.go:46 +0x1ad
net/http.HandlerFunc.ServeHTTP(0xc000576a40, 0x49594e0, 0xc00039c1c0, 0xc000318200)
        /usr/local/Cellar/go/1.12.1/libexec/src/net/http/server.go:1995 +0x44
apollo/vendor/github.com/rs/cors.(*Cors).Handler.func1(0x49594e0, 0xc00039c1c0, 0xc000318200)
        /Users/tshabtay/git/fleet/src/apollo/vendor/github.com/rs/cors/cors.go:207 +0x1af
net/http.HandlerFunc.ServeHTTP(0xc0002a3a00, 0x49594e0, 0xc00039c1c0, 0xc000318200)
        /usr/local/Cellar/go/1.12.1/libexec/src/net/http/server.go:1995 +0x44
net/http.serverHandler.ServeHTTP(0xc000345380, 0x49594e0, 0xc00039c1c0, 0xc000318200)
        /usr/local/Cellar/go/1.12.1/libexec/src/net/http/server.go:2774 +0xa8
net/http.(*conn).serve(0xc000198000, 0x495b760, 0xc0002381c0)
        /usr/local/Cellar/go/1.12.1/libexec/src/net/http/server.go:1878 +0x851
created by net/http.(*Server).Serve
        /usr/local/Cellar/go/1.12.1/libexec/src/net/http/server.go:2884 +0x2f4

index out of range [1] with length 1

When calling pp with the trace linked below, I get the following result:

panic: deadlock detected at fmut

panic: runtime error: index out of range [1] with length 1

goroutine 1 [running]:
github.com/maruel/panicparse/v2/stack.(*Stack).less(0xc00052d2d0, 0xc00052d480, 0xc000382300)
	/media/ext4_data/Coding/go/pkg/mod/github.com/maruel/panicparse/[email protected]/stack/stack.go:525 +0x5ef
github.com/maruel/panicparse/v2/stack.(*Signature).less(0xc00052d440, 0xc00052d290, 0x7f15c1c85101)
	/media/ext4_data/Coding/go/pkg/mod/github.com/maruel/panicparse/[email protected]/stack/stack.go:660 +0x72
github.com/maruel/panicparse/v2/stack.(*Snapshot).Aggregate.func1(0x33, 0x32, 0x1)
	/media/ext4_data/Coding/go/pkg/mod/github.com/maruel/panicparse/[email protected]/stack/bucket.go:86 +0x87
sort.insertionSort_func(0xc0000d9b00, 0xc00000e7a0, 0x28, 0x3c)
	/usr/lib/go/src/sort/zfuncversion.go:12 +0xb4
sort.stable_func(0xc0000d9b00, 0xc00000e7a0, 0xcc)
	/usr/lib/go/src/sort/zfuncversion.go:167 +0x51
sort.SliceStable(0x5c55a0, 0xc00000e780, 0xc0000d9b00)
	/usr/lib/go/src/sort/slice.go:27 +0xcd
github.com/maruel/panicparse/v2/stack.(*Snapshot).Aggregate(0xc00010d810, 0x2, 0xc0000d9ca8)
	/media/ext4_data/Coding/go/pkg/mod/github.com/maruel/panicparse/[email protected]/stack/bucket.go:80 +0x7a5
github.com/maruel/panicparse/v2/internal.processInner(0x64e000, 0xc0000b2008, 0xc0000dcf00, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, 0xc00010d810, ...)
	/media/ext4_data/Coding/go/pkg/mod/github.com/maruel/panicparse/[email protected]/internal/main.go:133 +0x212
github.com/maruel/panicparse/v2/internal.process(0x64dfe0, 0xc0000b2000, 0x64e000, 0xc0000b2008, 0xc0000dcf00, 0x2, 0x2, 0xc000090101, 0x0, 0x0, ...)
	/media/ext4_data/Coding/go/pkg/mod/github.com/maruel/panicparse/[email protected]/internal/main.go:162 +0x3b1
github.com/maruel/panicparse/v2/internal.Main(0x0, 0x0)
	/media/ext4_data/Coding/go/pkg/mod/github.com/maruel/panicparse/[email protected]/internal/main.go:314 +0x7f3
main.main()
	/media/ext4_data/Coding/go/pkg/mod/github.com/maruel/panicparse/[email protected]/cmd/pp/main.go:19 +0x26

trace-file: https://gist.github.com/imsodin/ece52ebaf298477e314fa1f0cbaf5f85

I get the same panic with v2.0.2, and v1.5.0 works.

Usage as a library?

The documentation states: "Usable as a library!" But I can't figure out how to use this to get a string representation of the parsed output (without needing to use the internal package).

Is there a standard method I'm missing to get in an io.Writer the contents that would normally be printed to stdout or do I need to duplicate internal methods in my own code?

Thanks!

[feature] rank "unique snowflakes" higher

after #3, it would be nice if goroutines with only 1 occurrence were ranked higher than merged (>1) ones. I think there's slightly higher probability that they may contain something "distinct" than the merged ones. Even if not, keeping some order in the traces could help grasp their nature. (Unless there's already some important ordering I didn't notice?)

Also, it would be nice if the "sections" in output could be somewhat "marked" then. I didn't realize the sorting is done with some meaningful order (i.e. #3) until I browsed through the issues. What I mean is for example something like:

---- SOURCE ---- [or: CULPRIT?]
1: running ...
  ...
---- USER, UNIQUE ----
1: ...
  ...
1: ...
  ...
---- USER, REPEATING ----
5: ...
  ...
22: ...
  ...
434: ...
  ...
---- STDLIB ----
1: ...
  ...
23: ...
 ...

Golang API to print from another applet?

I stitched together a little api to print from a go process (as part of error handlers in an application).

Source here: https://github.com/pnegahdar/ppstack

Is this the best way to do this? I basically took a bunch of your things from internal and wrapped it in a small lib. Maybe this sort of functionality should be supported by panicparse?

Happy to merge the work in and get rid of the project if you have a path forward.

Output modification fails on a Gin application

I tried to set this up with my company's Gin application and it...just sort of failed. All debug output is gone and the only message resulting from panic output is a
bufio.Scanner: token too long error. For context, here's (a shortened by ~15 frames and obfuscated version of) what normally goes to the console:

goroutine 55 [running]:
main.recovery(0xc8201f6310)
    /appdir/middleware.go:40 +0x165
main.(*DB).Update(0xc8202d1860, 0x78e0c0, 0xc82006cd00, 0xc820369440, 0xc8201e5680)
    /appdir/db.go:150 +0x1a19
main.UpdateAccountHandler(0xc8201d4430)
    /appdir/account_handlers.go:134 +0x326
main.InjectContext.func1(0xc8201664d0)
    /appdir/context.go:52 +0x61
created by net/http.(*Server).Serve
    /appdir/server.go:1910 +0x3f6
  type=stack-trace

Admittedly we've got our own output systems in place instead of just a standard panic call, but the output format doesn't seem different enough to cause a major issue...thoughts?

Mark $GOPATH/pkg/mod and vendor packages distinctively

Could the stdlib entries be marked visually in some way that would make it easy to clearly separate them from "my code" from the first glance? there's usually kinda... notable difference in quality/trust level between those groups :) I think either different color, or some marker chars at start of a line? personally, I'm also not quite sure if I see value in marking private vs. public identifiers using different color; but I assume you had a need for that.

Hm; truth said, it could also be nice if 3rd-party libs were marked with a distinct color/marker, too... but then detection can be difficult here: one has them in .../vendor/..., other in ../Godeps/..., other in "everything except $GOPATH/mycompany.com/... and $GOROOT/src/...". So maybe some kinda $HOME/.config/panicparse/colors.ini with regexp->color patterns?... [with some option to specify complex matches like "everything except this and that"?]

Poorly defined idea, I know, sorry...

panicparse as import

I would like to import panicparse , but couldn't find any way to use it except webstack for web.

It would be better if there is a Write() method similar to ToHTML() s.Aggregate(stack.AnyPointer).Write(w)

e.g.

func StacktraceHandler(e interface{}, w io.Writer) {
	buf := make([]byte, 1024)
	buf = buf[:runtime.Stack(buf, false)]
	opts := stack.DefaultOpts()
	stk, _, err := stack.ScanSnapshot(bytes.NewReader(buf), ioutil.Discard, opts)
	if err == io.EOF {
		err = nil
	}
	if err != nil {
		_, _ = w.Write([]byte(fmt.Sprintf("panic: %v\n%s\n", e, buf)))
	} else {
		_, _ = w.Write([]byte(fmt.Sprintf("panic: %v\n", e)))
		if stk != nil {
			_ = stk.Aggregate(stack.AnyPointer).Write(w)
		}
	}
}

Replace panic with panicparse within app?

Is there a way to bake panicparse into my app so that I just get the PP output, instead of having to run a command line tool every time I get a panic from one of my services?

Using panicparse within a recover()

Hi! Cool library 😄

To use pp as a library, I'd like to call it internally from within a recover block. The closest I found was func ParseDump(r io.Reader, out io.Writer) ([]Goroutine, error), which I guess I could pass nil parameters to? Is there a more elegant option?

Thanks, and sorry if I missed something obvious.

Backwards compatibility with tools that are unaware of go modules

Hi there,

thanks for this project, it seems to work very well : ).

Quick Question: Would you be interested in making the library backwards compatible so that it could be imported by go projects that haven't upgraded to go modules yet? This would mainly involve creating a v2 directory inside of the repository and moving the v2 code into a v2 directory as recommended here: https://blog.golang.org/v2-go-modules#TOC_3.


What I mean is that the code below works fine as long as you have go.mod file in your project.

package main

import (
	"fmt"
	"github.com/maruel/panicparse/v2/stack"
)

func main() {
	fmt.Printf("%#v\n", stack.Goroutine{})
}

However, if you don't have a go.mod file, you get an error like this:

main.go:6:2: cannot find package "github.com/maruel/panicparse/v2/stack" in any of:
	/usr/local/Cellar/go/1.15.6/libexec/src/github.com/maruel/panicparse/v2/stack (from $GOROOT)
	/Users/bob/go/src/github.com/maruel/panicparse/v2/stack (from $GOPATH)

If you're interested, I might be able to send you patch for this, but I figured I reach out first to see how you feel about it.

No worries if you decide you don't want this compatibility, your work on this is much appreciated either way : ).

-- Felix

[feature] "vim/compiler-like format" [-format=vim? =quickfix?]

It would be cool if the tool supported an output format similar to "typical compilers", so that it could be easily fetched into vim's "quickfix" window, for easy jumping through the lines shown in the trace. For example:

1: running...
/foo/bar/baz/tool.go:123: (*whatever) FlavorText()
/foo/bar/baz/tool.go:55: (*whatever) Something()
/foo/bar/baz/main.go:22: CREATED BY: main()
5: ...
  ...
7: ...
  ...

When imported to vim with :cex system('panicparse -format=vim -only < foobar.log') | copen, this should make it trivial to jump between tool.go l.123, main.go l.22, etc., using :cn and :cp commands. (With properly configured :set errorformat=... in vim, the lines like 1: running... would still get displayed in the quickfix window, clearly separating the goroutines.)

edit: the Go oracle tool has it as: -format=plain

Empty structs aren't decoded correctly in function arguments.

This is a pretty minor thing, but it confused me enough to make me want to fix it, unless its a harder problem than I'm realizing.

Empty structs are decoded as ..., `` or sometimes as unknown.

I'd be happy to take a stab at this tonight if this is indeed a bug.

Examples:

//go:noinline
func Foo(x struct{}) {
	panic("test")
}

func TestFoo(t *testing.T) {
	Foo(struct{}{})
}

Results in:

1: running [Created by testing.(*T).Run @ testing.go:1238]
    testing testing.go:1143    tRunner.func1.2(*T(#1), func(#2))
    testing testing.go:1146    tRunner.func1(*T(#3))
            panic.go:965       panic(interface{}(#1))
    pp ppl_test.go:16 Foo()
    pp pp_test.go:20 TestFoo(*T(#3))
    testing testing.go:1193    tRunner(*T(#3), func(0x8952b8))
exit status 2

And

//go:noinline
func Foo(x chan struct{}) {
	panic("test")
}

func TestFoo(t *testing.T) {
	Foo(nil)
}

Results in:

1: running [Created by testing.(*T).Run @ testing.go:1238]
    testing testing.go:1143    tRunner.func1.2(*T(#1), func(#2))
    testing testing.go:1146    tRunner.func1(*T(#3))
            panic.go:965       panic(interface{}(#1))
    pp ppl_test.go:16 Foo(chan <unknown>(0x0))
    pp pp_test.go:20 TestFoo(*T(#3))
    testing testing.go:1193    tRunner(*T(#3), func(0x8952b8))
exit status 2

goroutine with only stdlib code in it should be ranked down

Make sure user code is ranked at the top, so goroutine with generic code should be ranked down.

Examples

This one is interesting because it's main but it's a test from generated code, so the user do not care. Probably requires a one-off hack:

1: chan receive
    testing        testing.go:576        RunTests(0x8f5120, 0xa1a6c0, 0x3, 0x3, 0x1)
    testing        testing.go:485        (*M).Run(0xc20804e370, 0xa25bc0)
    main           _testmain.go:56       main()

These are just noise:

1: syscall [Created by signal.init·1 @ signal_unix.go:27]
    signal         signal_unix.go:21     loop()
1: IO wait [Created by httptest.(*Server).Start @ server.go:109]
    net            fd_poll_runtime.go:84 (*pollDesc).Wait(0xc208010f40, 0x72, 0x0, 0x0)
    net            fd_poll_runtime.go:89 (*pollDesc).WaitRead(0xc208010f40, 0x0, 0x0)
    net            fd_unix.go:419        (*netFD).accept(0xc208010ee0, 0x0, 0x7f29d4c1de20, 0xc208029668)
    net            tcpsock_posix.go:234  (*TCPListener).AcceptTCP(0xc20804c258, 0x0, 0x0, 0x0)
    net            tcpsock_posix.go:244  (*TCPListener).Accept(0xc20804c258, 0x0, 0x0, 0x0, 0x0)
    httptest       server.go:48          (*historyListener).Accept(0xc20842e1e0, 0x0, 0x0, 0x0, 0x0)
    http           server.go:1728        (*Server).Serve(0xc208050fc0, 0x7f29d4c1f368, 0xc20842e1e0, 0x0, 0x0)

More aggressive deduplication

Hi, thanks for the great tool!

I took a large (20k lines) goroutine dump from a server and I used panicparse to analyze it. Unfortunately panicparse still emits many thousands of lines because lots of goroutines that are the same for my purposes cannot be deduplicated.

I dug into it more and I found two specific issues:

  1. Panicparse compares argument values and if it doesn't think they're pointers, it wants the values to be the same. For my purposes, I really don't care about function args; I want two goroutines to be deduped if they have the same stack trace.

    In case it helps, here are two lines from two different goroutines that caused them to not be deduped (this is deep in net's inner workings, far away from my own code):

    io.copyBuffer(0x7fa5add90238, 0xc820106aa0, 0x7fa5addd01d8, 0xc820106a60, 0xc820166000, 0x8000, 0x8000, 0x38bd, 0x0, 0x0)
    io.copyBuffer(0x7fa5add90238, 0xc820028220, 0x7fa5addd01d8, 0xc820152148, 0xc820a80000, 0x8000, 0x8000, 0x98d, 0x0, 0x0)
    

    My quick hack was to modify my local copy of stack.go to consider two Args to be similar if they have the same number of args (without looking at the args):

    func (a *Args) Similar(r *Args) bool {
      return a.Elided == r.Elided && len(a.Values) == len(r.Values)
    }
  2. If a goroutine has been blocked for a long time on e..g a channel op or IO, it prints something like

    goroutine 134103 [chan send, 609 minutes]:
    

    or

    goroutine 134227 [IO wait, 747 minutes]:
    

    A difference in # of minutes causes two goroutines not to be deduped. My quick hack here was to modify my trace file with a quick s/\d+ minutes/123 minutes/. A more robust solution would be for panicparse to merge multiple values by replacing with * minutes (as with merged function params) or, if we want to be fancy, something like (12-49) minutes.

I wanted to bring these issues to your attention and see what you think a good solution might be. I'm not sure if, for other people in other contexts, having goroutines not be disambiguated for the above two reasons is useful. If so, perhaps there could be a flag for even more aggressive deduplication.

Failed to execute temporary parl

Hello!

$ uname -a
Darwin MacBook-Pro.local 20.4.0 Darwin Kernel Version 20.4.0: 
Thu Apr 22 21:46:41 PDT 2021; root:xnu-7195.101.2~1/RELEASE_ARM64_T8101 arm64

$ go version
go version go1.16.4 darwin/arm64
$ cat panic.txt
panic: sky is falling

goroutine 17 [running]:
main.foo()
	/Users/anthony/gopanic/main.go:14 +0x38
created by main.main
	/Users/anthony/gopanic/main.go:8 +0x4c
exit status 2
$ pp panic.txt
Failed to execute temporary parl (class PAR::StrippedPARL::Static) in file 
'/var/folders/6t/v80c8sfs5zqf38b2yhzq592h0000gn/T/parlblFF': Inappropriate ioctl for device at 
/System/Library/Perl/Extras/5.30/PAR/StrippedPARL/Base.pm line 77, <DATA> line 1.
/usr/bin/pp5.30: Failed to extract a parl from 'PAR::StrippedPARL::Static' to file 
'/var/folders/6t/v80c8sfs5zqf38b2yhzq592h0000gn/T/parlsUtSZkL' at 
/System/Library/Perl/Extras/5.30/PAR/Packer.pm line 1216, <DATA> line 1.

the rabbit

I want to know how your CLI has a rabbit. 😆

Name clash

pp clashes with Perl's /usr/bin/pp on macOS:

$ pp --help
PAR Packager, version 1.017 (PAR version 1.007)

Could you provide cmd/panicparse for convenience?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.