Skip to content

Commit

Permalink
Increase parse header performance
Browse files Browse the repository at this point in the history
  • Loading branch information
becheran committed Mar 13, 2021
1 parent f1bb708 commit 3d55c81
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 20 deletions.
47 changes: 27 additions & 20 deletions internal/model/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,49 +88,56 @@ func parseStackPos(scanner *bufio.Scanner) (fileName string, line int32, pos *in
return
}

// parseHeader of stack trace. See: https://golang.org/src/runtime/traceback.go?s=30186:30213#L869
func parseHeader(header string) (routine Goroutine, err error) {
routineHeader := strings.Split(header, " ")
if len(routineHeader) < 3 {
err = fmt.Errorf("Expected header with len >= 3, but got: %s", routineHeader)
// ParseHeader of stack trace. See: https://golang.org/src/runtime/traceback.go?s=30186:30213#L869
func ParseHeader(header string) (routine Goroutine, err error) {
if len(header) < 10 {
err = fmt.Errorf("Expected header to begin with \"goroutine \" but len was < 10")
return
}
if routineHeader[0] != "goroutine" {
err = fmt.Errorf("Expected goroutine header, but got: %s", routineHeader)
if header[0:10] != "goroutine " {
err = fmt.Errorf("Expected goroutine header, but got: %s", header[0:10])
return
}
id, parseErr := strconv.ParseInt(routineHeader[1], 10, 64)
seperator := strings.Index(header[10:], " ")

id, parseErr := strconv.ParseInt(header[10:10+seperator], 10, 64)
if parseErr != nil {
err = fmt.Errorf("Could not parse ID. Err: %s", parseErr.Error())
return
}

stateStartIdx := strings.Index(header, "[")
// Remove []:
fullState := header[stateStartIdx+1 : len(header)-2]
stateParts := strings.Split(fullState, ",")
fullState := header[12+seperator : len(header)-1]
firstComma := strings.Index(fullState, ",")
var status string
lockedToThread := false
waitTimeMin := int64(0)
if len(stateParts) == 1 {
if firstComma < 0 {
status = fullState
} else {
status = stateParts[0]
for idx := 1; idx < len(stateParts); idx++ {
part := strings.Trim(stateParts[idx], " ")
if strings.HasSuffix(part, "minutes") {
status = fullState[:firstComma]

parseWaitBlock := func(part string) {
if part == "locked to thread" {
lockedToThread = true
} else {
minUnitSep := strings.Index(part, " ")
waitTimeMin, parseErr = strconv.ParseInt(part[:minUnitSep], 10, 64)
if parseErr != nil {
err = fmt.Errorf("Failed to parse minutes. Err: %s", parseErr.Error())
return
}
} else if part == "locked to thread" {
lockedToThread = true
}
}
}

sndComma := strings.Index(fullState[firstComma+1:], ",")
if sndComma > 0 {
parseWaitBlock(fullState[firstComma+2 : firstComma+sndComma+1])
parseWaitBlock(fullState[firstComma+sndComma+3:])
} else {
parseWaitBlock(fullState[firstComma+2:])
}
}
routine = Goroutine{
Status: status,
ID: id,
Expand All @@ -146,7 +153,7 @@ func ParseStackFrame(reader io.Reader) (routines []Goroutine, err error) {
for scanner.Scan() {
line := scanner.Text()

routine, err := parseHeader(line)
routine, err := ParseHeader(line)
if err != nil {
log.Printf("Failed to parse routine header. Err: %s", err.Error())
continue
Expand Down
43 changes: 43 additions & 0 deletions internal/model/model_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,12 @@ func TestParseTrace(t *testing.T) {
assert.False(t, r3.LockedToThread)
}

func Benchmark_ParseTrace(b *testing.B) {
for n := 0; n < b.N; n++ {
model.ParseStackFrame(strings.NewReader(trace_1))
}
}

var trace_2 = `goroutine 268 [runnable, locked to thread]:
syscall.Syscall9(0x7ff9af9b0500, 0x7, 0x1f4, 0xc0000902d8, 0x1, 0xc0000902c8, 0xc000090348, 0xc000090298, 0x0, 0x0, ...)
C:/Program Files/Go/src/runtime/syscall_windows.go:356 +0xf2
Expand All @@ -133,3 +139,40 @@ func TestParseLockedToThread(t *testing.T) {
assert.Equal(t, int64(0), r0.WaitSinceMin)
assert.True(t, r0.LockedToThread)
}

func Benchmark_ParseHeader(b *testing.B) {
for n := 0; n < b.N; n++ {
model.ParseHeader("goroutine 268 [runnable, locked to thread]")
}
}

func Test_ParseHeader_Invalid(t *testing.T) {
_, err := model.ParseHeader("")
assert.NotNil(t, err)
_, err = model.ParseHeader("gogogogogoroutines")
assert.NotNil(t, err)
_, err = model.ParseHeader("goroutine0fd")
assert.NotNil(t, err)
}

func Test_ParseHeader_Valid(t *testing.T) {
result, err := model.ParseHeader("goroutine 268 [runnable, locked to thread]")
assert.Nil(t, err)
assert.Equal(t, int64(268), result.ID)
assert.Equal(t, "runnable", result.Status)
assert.Equal(t, true, result.LockedToThread)

result, err = model.ParseHeader("goroutine 1 [chan receive, 16 minutes]")
assert.Nil(t, err)
assert.Equal(t, int64(1), result.ID)
assert.Equal(t, "chan receive", result.Status)
assert.Equal(t, int64(16), result.WaitSinceMin)
assert.Equal(t, false, result.LockedToThread)

result, err = model.ParseHeader("goroutine 1 [chan receive, 16 minutes, locked to thread]")
assert.Nil(t, err)
assert.Equal(t, int64(1), result.ID)
assert.Equal(t, "chan receive", result.Status)
assert.Equal(t, int64(16), result.WaitSinceMin)
assert.Equal(t, true, result.LockedToThread)
}

0 comments on commit 3d55c81

Please sign in to comment.