summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGusted <postmaster@gusted.xyz>2025-01-02 02:52:08 +0100
committerGusted <postmaster@gusted.xyz>2025-01-05 04:07:49 +0100
commita2e0dd829c55887201ed2e38808b85a9cf2163f6 (patch)
tree88d22bd17fdda139d183e7a0b1dcc2a83eb8b782
parentfeat: add files to compare (#6461) (diff)
downloadforgejo-a2e0dd829c55887201ed2e38808b85a9cf2163f6.tar.xz
forgejo-a2e0dd829c55887201ed2e38808b85a9cf2163f6.zip
chore: avoid trying to stream data
`profile.Parse` always call `io.ReadAll` so avoid the trouble and a goroutine and do it ourselves. Add some limited testing (testing the parsed stack is volatile and not really feasible).
-rw-r--r--modules/process/manager_stacktraces.go15
-rw-r--r--modules/process/manager_stacktraces_test.go93
2 files changed, 100 insertions, 8 deletions
diff --git a/modules/process/manager_stacktraces.go b/modules/process/manager_stacktraces.go
index e260893113..a603e6a9f2 100644
--- a/modules/process/manager_stacktraces.go
+++ b/modules/process/manager_stacktraces.go
@@ -4,8 +4,8 @@
package process
import (
+ "bytes"
"fmt"
- "io"
"runtime/pprof"
"sort"
"time"
@@ -175,13 +175,12 @@ func (pm *Manager) ProcessStacktraces(flat, noSystem bool) ([]*Process, int, int
// Now from within the lock we need to get the goroutines.
// Why? If we release the lock then between between filling the above map and getting
// the stacktraces another process could be created which would then look like a dead process below
- reader, writer := io.Pipe()
- defer reader.Close()
- go func() {
- err := pprof.Lookup("goroutine").WriteTo(writer, 0)
- _ = writer.CloseWithError(err)
- }()
- stacks, err = profile.Parse(reader)
+ var buf bytes.Buffer
+ if err := pprof.Lookup("goroutine").WriteTo(&buf, 0); err != nil {
+ return nil, 0, 0, err
+ }
+
+ stacks, err = profile.ParseData(buf.Bytes())
if err != nil {
return nil, 0, 0, err
}
diff --git a/modules/process/manager_stacktraces_test.go b/modules/process/manager_stacktraces_test.go
new file mode 100644
index 0000000000..d00fbc5ec8
--- /dev/null
+++ b/modules/process/manager_stacktraces_test.go
@@ -0,0 +1,93 @@
+// Copyright 2025 The Forgejo Authors. All rights reserved.
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package process
+
+import (
+ "context"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestProcessStacktraces(t *testing.T) {
+ _, _, finish := GetManager().AddContext(context.Background(), "Normal process")
+ defer finish()
+ parentCtx, _, finish := GetManager().AddContext(context.Background(), "Children normal process")
+ defer finish()
+ _, _, finish = GetManager().AddContext(parentCtx, "Children process")
+ defer finish()
+ _, _, finish = GetManager().AddTypedContext(context.Background(), "System process", SystemProcessType, true)
+ defer finish()
+
+ t.Run("No flat with no system process", func(t *testing.T) {
+ processes, processCount, _, err := GetManager().ProcessStacktraces(false, true)
+ require.NoError(t, err)
+ assert.EqualValues(t, 4, processCount)
+ assert.Len(t, processes, 2)
+
+ assert.EqualValues(t, "Children normal process", processes[0].Description)
+ assert.EqualValues(t, NormalProcessType, processes[0].Type)
+ assert.Empty(t, processes[0].ParentPID)
+ assert.Len(t, processes[0].Children, 1)
+
+ assert.EqualValues(t, "Children process", processes[0].Children[0].Description)
+ assert.EqualValues(t, processes[0].PID, processes[0].Children[0].ParentPID)
+
+ assert.EqualValues(t, "Normal process", processes[1].Description)
+ assert.EqualValues(t, NormalProcessType, processes[1].Type)
+ assert.Empty(t, processes[1].ParentPID)
+ assert.Empty(t, processes[1].Children)
+ })
+
+ t.Run("Flat with no system process", func(t *testing.T) {
+ processes, processCount, _, err := GetManager().ProcessStacktraces(true, true)
+ require.NoError(t, err)
+ assert.EqualValues(t, 4, processCount)
+ assert.Len(t, processes, 3)
+
+ assert.EqualValues(t, "Children process", processes[0].Description)
+ assert.EqualValues(t, NormalProcessType, processes[0].Type)
+ assert.EqualValues(t, processes[1].PID, processes[0].ParentPID)
+ assert.Empty(t, processes[0].Children)
+
+ assert.EqualValues(t, "Children normal process", processes[1].Description)
+ assert.EqualValues(t, NormalProcessType, processes[1].Type)
+ assert.Empty(t, processes[1].ParentPID)
+ assert.Empty(t, processes[1].Children)
+
+ assert.EqualValues(t, "Normal process", processes[2].Description)
+ assert.EqualValues(t, NormalProcessType, processes[2].Type)
+ assert.Empty(t, processes[2].ParentPID)
+ assert.Empty(t, processes[2].Children)
+ })
+
+ t.Run("System process", func(t *testing.T) {
+ processes, processCount, _, err := GetManager().ProcessStacktraces(false, false)
+ require.NoError(t, err)
+ assert.EqualValues(t, 4, processCount)
+ assert.Len(t, processes, 4)
+
+ assert.EqualValues(t, "System process", processes[0].Description)
+ assert.EqualValues(t, SystemProcessType, processes[0].Type)
+ assert.Empty(t, processes[0].ParentPID)
+ assert.Empty(t, processes[0].Children)
+
+ assert.EqualValues(t, "Children normal process", processes[1].Description)
+ assert.EqualValues(t, NormalProcessType, processes[1].Type)
+ assert.Empty(t, processes[1].ParentPID)
+ assert.Len(t, processes[1].Children, 1)
+
+ assert.EqualValues(t, "Normal process", processes[2].Description)
+ assert.EqualValues(t, NormalProcessType, processes[2].Type)
+ assert.Empty(t, processes[2].ParentPID)
+ assert.Empty(t, processes[2].Children)
+
+ // This is the "main" pid, testing code always runs in a goroutine.
+ assert.EqualValues(t, "(unassociated)", processes[3].Description)
+ assert.EqualValues(t, NoneProcessType, processes[3].Type)
+ assert.Empty(t, processes[3].ParentPID)
+ assert.Empty(t, processes[3].Children)
+ })
+}