diff --git a/services/process/server/process.go b/services/process/server/process.go index ed11b265..a7e800e3 100644 --- a/services/process/server/process.go +++ b/services/process/server/process.go @@ -230,7 +230,7 @@ func (s *server) GetStacks(ctx context.Context, req *pb.GetStacksRequest) (*pb.G // New thread so append last entry and start over. if fields[0] == "Thread" { // Depending on wrapper/gdb this may have additional fields but we don't need them. - if len(fields) < 6 { + if len(fields) < 4 { recorder.CounterOrLog(ctx, processGetStacksFailureCounter, 1, attribute.String("reason", "parse_err")) return nil, status.Errorf(codes.Internal, "unparsable pstack output for new thread: %s", text) } @@ -244,13 +244,21 @@ func (s *server) GetStacks(ctx context.Context, req *pb.GetStacksRequest) (*pb.G recorder.CounterOrLog(ctx, processGetStacksFailureCounter, 1, attribute.String("reason", "parse_err")) return nil, status.Errorf(codes.Internal, "can't parse thread number: %s : %v", text, err) } - if n, err := fmt.Sscanf(fields[3], "0x%x", &stack.ThreadId); n != 1 || err != nil { - recorder.CounterOrLog(ctx, processGetStacksFailureCounter, 1, attribute.String("reason", "parse_err")) - return nil, status.Errorf(codes.Internal, "can't parse thread id: %s : %v", text, err) + if fields[2] == "(Thread" && len(fields) >= 6 { + if n, err := fmt.Sscanf(fields[3], "0x%x", &stack.ThreadId); n != 1 || err != nil { + recorder.CounterOrLog(ctx, processGetStacksFailureCounter, 1, attribute.String("reason", "parse_err")) + return nil, status.Errorf(codes.Internal, "can't parse thread id: %s : %v", text, err) + } + if n, err := fmt.Sscanf(fields[5], "%d", &stack.Lwp); n != 1 || err != nil { + recorder.CounterOrLog(ctx, processGetStacksFailureCounter, 1, attribute.String("reason", "parse_err")) + return nil, status.Errorf(codes.Internal, "can't parse lwp: %s : %v", text, err) + } } - if n, err := fmt.Sscanf(fields[5], "%d", &stack.Lwp); n != 1 || err != nil { - recorder.CounterOrLog(ctx, processGetStacksFailureCounter, 1, attribute.String("reason", "parse_err")) - return nil, status.Errorf(codes.Internal, "can't parse lwp: %s : %v", text, err) + if fields[2] == "(LWP" { + if n, err := fmt.Sscanf(fields[3], "%d", &stack.Lwp); n != 1 || err != nil { + recorder.CounterOrLog(ctx, processGetStacksFailureCounter, 1, attribute.String("reason", "parse_err")) + return nil, status.Errorf(codes.Internal, "can't parse lwp: %s : %v", text, err) + } } numEntries++ continue diff --git a/services/process/server/process_linux_test.go b/services/process/server/process_linux_test.go index e9463400..864e44b4 100644 --- a/services/process/server/process_linux_test.go +++ b/services/process/server/process_linux_test.go @@ -30,12 +30,16 @@ var ( "./testdata/linux_bad3.ps", // bad nice value } - testdataPstackNoThreads = "./testdata/linux_pstack_no_threads.txt" - testdataPstackNoThreadsTextProto = "./testdata/linux_pstack_no_threads.textproto" - testdataPstackThreads = "./testdata/linux_pstack_threads.txt" - testdataPstackThreadsTextProto = "./testdata/linux_pstack_threads.textproto" - testdataPstackThreadsBadThread = "./testdata/linux_pstack_threads_bad_thread.txt" - testdataPstackThreadsBadThreadNumber = "./testdata/linux_pstack_threads_bad_thread_number.txt" - testdataPstackThreadsBadThreadID = "./testdata/linux_pstack_threads_bad_thread_id.txt" - testdataPstackThreadsBadLwp = "./testdata/linux_pstack_threads_bad_lwp.txt" + testdataPstackNoThreads = "./testdata/linux_pstack_no_threads.txt" + testdataPstackNoThreadsTextProto = "./testdata/linux_pstack_no_threads.textproto" + testdataPstackThreads = "./testdata/linux_pstack_threads.txt" + testdataPstackThreadsTextProto = "./testdata/linux_pstack_threads.textproto" + testdataPstackSingleThread = "./testdata/linux_pstack_single_thread.txt" + testdataPstackSingleThreadTextProto = "./testdata/linux_pstack_single_thread.textproto" + testdataPstackSingleThreadWithLWP = "./testdata/linux_pstack_single_thread_with_lwp.txt" + testdataPstackSingleThreadWithLWPTextProto = "./testdata/linux_pstack_single_thread_with_lwp.textproto" + testdataPstackThreadsBadThread = "./testdata/linux_pstack_threads_bad_thread.txt" + testdataPstackThreadsBadThreadNumber = "./testdata/linux_pstack_threads_bad_thread_number.txt" + testdataPstackThreadsBadThreadID = "./testdata/linux_pstack_threads_bad_thread_id.txt" + testdataPstackThreadsBadLwp = "./testdata/linux_pstack_threads_bad_lwp.txt" ) diff --git a/services/process/server/process_test.go b/services/process/server/process_test.go index a77b8ce5..caaf99ca 100644 --- a/services/process/server/process_test.go +++ b/services/process/server/process_test.go @@ -294,8 +294,8 @@ func TestPstackNative(t *testing.T) { }) testutil.FatalOnErr("can't get native pstack", err, t) - // We're a go program. We have multiple threads. - if len(resp.Stacks) <= 1 { + // We're a go program. We have one or more threads. + if len(resp.Stacks) < 1 { t.Fatalf("Not enough threads in native response. Response: %+v", prototext.Format(resp)) } @@ -372,6 +372,20 @@ func TestPstack(t *testing.T) { validate: testdataPstackThreadsTextProto, pid: 1, }, + { + name: "A program with a single thread", + command: testutil.ResolvePath(t, "cat"), + input: testdataPstackSingleThread, + validate: testdataPstackSingleThreadTextProto, + pid: 1, + }, + { + name: "A program with a single thread with an lwp", + command: testutil.ResolvePath(t, "cat"), + input: testdataPstackSingleThreadWithLWP, + validate: testdataPstackSingleThreadWithLWPTextProto, + pid: 1, + }, { name: "bad pid - zero", command: testutil.ResolvePath(t, "cat"), diff --git a/services/process/server/testdata/linux_pstack_single_thread.textproto b/services/process/server/testdata/linux_pstack_single_thread.textproto new file mode 100644 index 00000000..7e5a28ea --- /dev/null +++ b/services/process/server/testdata/linux_pstack_single_thread.textproto @@ -0,0 +1,5 @@ +stacks : < + thread_number : 1 + stacks : "#0 0x00000000000807ec in runtime.futex.abi0 ()" + stacks : "#1 0x000000000004556c in runtime.futexsleep ()" +> diff --git a/services/process/server/testdata/linux_pstack_single_thread.txt b/services/process/server/testdata/linux_pstack_single_thread.txt new file mode 100644 index 00000000..74ce4a7e --- /dev/null +++ b/services/process/server/testdata/linux_pstack_single_thread.txt @@ -0,0 +1,3 @@ +Thread 1 (process 2947021): +#0 0x00000000000807ec in runtime.futex.abi0 () +#1 0x000000000004556c in runtime.futexsleep () diff --git a/services/process/server/testdata/linux_pstack_single_thread_with_lwp.textproto b/services/process/server/testdata/linux_pstack_single_thread_with_lwp.textproto new file mode 100644 index 00000000..4f225d57 --- /dev/null +++ b/services/process/server/testdata/linux_pstack_single_thread_with_lwp.textproto @@ -0,0 +1,6 @@ +stacks : < + thread_number : 9 + lwp : 19049 + stacks : "#0 0x00000000000807ec in runtime.futex.abi0 ()" + stacks : "#1 0x000000000004556c in runtime.futexsleep ()" +> diff --git a/services/process/server/testdata/linux_pstack_single_thread_with_lwp.txt b/services/process/server/testdata/linux_pstack_single_thread_with_lwp.txt new file mode 100644 index 00000000..c58b4172 --- /dev/null +++ b/services/process/server/testdata/linux_pstack_single_thread_with_lwp.txt @@ -0,0 +1,3 @@ +Thread 9 (LWP 19049 "go"): +#0 0x00000000000807ec in runtime.futex.abi0 () +#1 0x000000000004556c in runtime.futexsleep ()