From 7b2421ffa50f35c662487ef5047edb388bdbe1de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Geisend=C3=B6rfer?= Date: Wed, 21 Sep 2022 10:01:08 +0200 Subject: [PATCH] fix: support windows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Seemss like this also made the code a little faster :) name old time/op new time/op delta Gostackparse-12 26.9µs ± 6% 25.3µs ± 1% -5.97% (p=0.000 n=10+10) name old MiB/s new MiB/s delta Gostackparse-12 295 ± 5% 314 ± 1% +6.27% (p=0.000 n=10+10) name old alloc/op new alloc/op delta Gostackparse-12 17.2kB ± 0% 17.2kB ± 0% -0.19% (p=0.000 n=10+10) name old allocs/op new allocs/op delta Gostackparse-12 306 ± 0% 248 ± 0% -18.95% (p=0.000 n=10+10) --- gostackparse.go | 55 +++++++++++++++-------- gostackparse_test.go | 72 +++++++++++++++++++++++++++++++ test-fixtures/windows.golden.json | 20 +++++++++ test-fixtures/windows.txt | 3 ++ 4 files changed, 131 insertions(+), 19 deletions(-) create mode 100644 test-fixtures/windows.golden.json create mode 100644 test-fixtures/windows.txt diff --git a/gostackparse.go b/gostackparse.go index 0d5bd1d..4b2adb7 100644 --- a/gostackparse.go +++ b/gostackparse.go @@ -253,35 +253,52 @@ func parseFunc(line []byte, state parserState) *Frame { // // Example Update: // &Frame{File: "/root/go1.15.6.linux.amd64/src/net/http/server.go", Line: 2969} -func parseFile(line []byte, f *Frame) bool { - if len(line) == 0 || line[0] != '\t' { +// +// Note: The +0x36c is the relative pc offset from the function entry pc. It's +// omitted if it's 0. +func parseFile(line []byte, f *Frame) (ret bool) { + if len(line) < 2 || line[0] != '\t' { return false } - line = line[1:] + + const ( + stateFilename = iota + stateColon + stateLine + ) + + var state = stateFilename for i, c := range line { - if c == ':' { - if f.File != "" { - return false + switch state { + case stateFilename: + if c == ':' { + state = stateColon } - f.File = string(line[0:i]) - } else if c == ' ' || i+1 == len(line) { - if f.File == "" { - return false + case stateColon: + if isDigit(c) { + f.File = string(line[0 : i-1]) + f.Line = int(c - '0') + state = stateLine + ret = true + } else { + state = stateFilename } - var end int + case stateLine: if c == ' ' { - end = i - } else { - end = i + 1 + return true + } else if !isDigit(c) { + return false } - - var err error - f.Line, err = strconv.Atoi(string(line[len(f.File)+1 : end])) - return err == nil + f.Line = f.Line*10 + int(c-'0') } + } - return false + return +} + +func isDigit(c byte) bool { + return c >= '0' && c <= '9' } // Goroutine represents a single goroutine and its stack after extracting it diff --git a/gostackparse_test.go b/gostackparse_test.go index 2fccad1..bb910a0 100644 --- a/gostackparse_test.go +++ b/gostackparse_test.go @@ -374,3 +374,75 @@ func TestFuzzCorupus(t *testing.T) { require.NoError(t, ioutil.WriteFile(name, []byte(dump.String()), 0666)) } } + +func Test_parseFile(t *testing.T) { + tests := []struct { + name string + line string + wantFrame Frame + wantReturn bool + }{ + { + name: "empty", + line: "", + wantFrame: Frame{}, + wantReturn: false, + }, + { + name: "simple", + line: "\t/root/go1.15.6.linux.amd64/src/net/http/server.go:2969 +0x36c", + wantFrame: Frame{ + File: "/root/go1.15.6.linux.amd64/src/net/http/server.go", + Line: 2969, + }, + wantReturn: true, + }, + { + name: "simple+space", + line: "\t/root/go1.15.6.linux.amd64/src/net/http/cool server.go:2969 +0x36c", + wantFrame: Frame{ + File: "/root/go1.15.6.linux.amd64/src/net/http/cool server.go", + Line: 2969, + }, + wantReturn: true, + }, + { + name: "no-relative-pc", + line: "\t/root/go1.15.6.linux.amd64/src/net/http/server.go:2969", + wantFrame: Frame{ + File: "/root/go1.15.6.linux.amd64/src/net/http/server.go", + Line: 2969, + }, + wantReturn: true, + }, + { + name: "no-relative-pc+space", + line: "\t/root/go1.15.6.linux.amd64/src/net/http/cool server.go:2969", + wantFrame: Frame{ + File: "/root/go1.15.6.linux.amd64/src/net/http/cool server.go", + Line: 2969, + }, + wantReturn: true, + }, + } + for _, tt := range tests { + if len(tt.line) > 1 { + tt.name = "windows+" + tt.name + tt.line = "\tC:" + tt.line[1:] + tt.wantFrame.File = "C:" + tt.wantFrame.File + tests = append(tests, tt) + } + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var f Frame + got := parseFile([]byte(tt.line), &f) + if got != tt.wantReturn { + t.Fatalf("got=%v want=%v", got, tt.wantReturn) + } else if f != tt.wantFrame { + t.Fatalf("got=%+v want=%+v", f, tt.wantFrame) + } + }) + } +} diff --git a/test-fixtures/windows.golden.json b/test-fixtures/windows.golden.json new file mode 100644 index 0000000..b8e573e --- /dev/null +++ b/test-fixtures/windows.golden.json @@ -0,0 +1,20 @@ +{ + "Errors": null, + "Goroutines": [ + { + "ID": 1, + "State": "running", + "Wait": 0, + "LockedToThread": false, + "Stack": [ + { + "Func": "runtime/pprof.writeGoroutineStacks", + "File": "C:/Users/felixge/pprof.go", + "Line": 693 + } + ], + "FramesElided": false, + "CreatedBy": null + } + ] +} diff --git a/test-fixtures/windows.txt b/test-fixtures/windows.txt new file mode 100644 index 0000000..ff9534b --- /dev/null +++ b/test-fixtures/windows.txt @@ -0,0 +1,3 @@ +goroutine 1 [running]: +runtime/pprof.writeGoroutineStacks(0x14e5940, 0xc0000abc50, 0x101b8a5, 0xc000333d20) + C:/Users/felixge/pprof.go:693 +0x9f