loading up the forgejo repo on tangled to test page performance
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

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).

Gusted a2e0dd82 a2eb2497

+100 -8
+7 -8
modules/process/manager_stacktraces.go
··· 4 4 package process 5 5 6 6 import ( 7 + "bytes" 7 8 "fmt" 8 - "io" 9 9 "runtime/pprof" 10 10 "sort" 11 11 "time" ··· 175 175 // Now from within the lock we need to get the goroutines. 176 176 // Why? If we release the lock then between between filling the above map and getting 177 177 // the stacktraces another process could be created which would then look like a dead process below 178 - reader, writer := io.Pipe() 179 - defer reader.Close() 180 - go func() { 181 - err := pprof.Lookup("goroutine").WriteTo(writer, 0) 182 - _ = writer.CloseWithError(err) 183 - }() 184 - stacks, err = profile.Parse(reader) 178 + var buf bytes.Buffer 179 + if err := pprof.Lookup("goroutine").WriteTo(&buf, 0); err != nil { 180 + return nil, 0, 0, err 181 + } 182 + 183 + stacks, err = profile.ParseData(buf.Bytes()) 185 184 if err != nil { 186 185 return nil, 0, 0, err 187 186 }
+93
modules/process/manager_stacktraces_test.go
··· 1 + // Copyright 2025 The Forgejo Authors. All rights reserved. 2 + // SPDX-License-Identifier: GPL-3.0-or-later 3 + 4 + package process 5 + 6 + import ( 7 + "context" 8 + "testing" 9 + 10 + "github.com/stretchr/testify/assert" 11 + "github.com/stretchr/testify/require" 12 + ) 13 + 14 + func TestProcessStacktraces(t *testing.T) { 15 + _, _, finish := GetManager().AddContext(context.Background(), "Normal process") 16 + defer finish() 17 + parentCtx, _, finish := GetManager().AddContext(context.Background(), "Children normal process") 18 + defer finish() 19 + _, _, finish = GetManager().AddContext(parentCtx, "Children process") 20 + defer finish() 21 + _, _, finish = GetManager().AddTypedContext(context.Background(), "System process", SystemProcessType, true) 22 + defer finish() 23 + 24 + t.Run("No flat with no system process", func(t *testing.T) { 25 + processes, processCount, _, err := GetManager().ProcessStacktraces(false, true) 26 + require.NoError(t, err) 27 + assert.EqualValues(t, 4, processCount) 28 + assert.Len(t, processes, 2) 29 + 30 + assert.EqualValues(t, "Children normal process", processes[0].Description) 31 + assert.EqualValues(t, NormalProcessType, processes[0].Type) 32 + assert.Empty(t, processes[0].ParentPID) 33 + assert.Len(t, processes[0].Children, 1) 34 + 35 + assert.EqualValues(t, "Children process", processes[0].Children[0].Description) 36 + assert.EqualValues(t, processes[0].PID, processes[0].Children[0].ParentPID) 37 + 38 + assert.EqualValues(t, "Normal process", processes[1].Description) 39 + assert.EqualValues(t, NormalProcessType, processes[1].Type) 40 + assert.Empty(t, processes[1].ParentPID) 41 + assert.Empty(t, processes[1].Children) 42 + }) 43 + 44 + t.Run("Flat with no system process", func(t *testing.T) { 45 + processes, processCount, _, err := GetManager().ProcessStacktraces(true, true) 46 + require.NoError(t, err) 47 + assert.EqualValues(t, 4, processCount) 48 + assert.Len(t, processes, 3) 49 + 50 + assert.EqualValues(t, "Children process", processes[0].Description) 51 + assert.EqualValues(t, NormalProcessType, processes[0].Type) 52 + assert.EqualValues(t, processes[1].PID, processes[0].ParentPID) 53 + assert.Empty(t, processes[0].Children) 54 + 55 + assert.EqualValues(t, "Children normal process", processes[1].Description) 56 + assert.EqualValues(t, NormalProcessType, processes[1].Type) 57 + assert.Empty(t, processes[1].ParentPID) 58 + assert.Empty(t, processes[1].Children) 59 + 60 + assert.EqualValues(t, "Normal process", processes[2].Description) 61 + assert.EqualValues(t, NormalProcessType, processes[2].Type) 62 + assert.Empty(t, processes[2].ParentPID) 63 + assert.Empty(t, processes[2].Children) 64 + }) 65 + 66 + t.Run("System process", func(t *testing.T) { 67 + processes, processCount, _, err := GetManager().ProcessStacktraces(false, false) 68 + require.NoError(t, err) 69 + assert.EqualValues(t, 4, processCount) 70 + assert.Len(t, processes, 4) 71 + 72 + assert.EqualValues(t, "System process", processes[0].Description) 73 + assert.EqualValues(t, SystemProcessType, processes[0].Type) 74 + assert.Empty(t, processes[0].ParentPID) 75 + assert.Empty(t, processes[0].Children) 76 + 77 + assert.EqualValues(t, "Children normal process", processes[1].Description) 78 + assert.EqualValues(t, NormalProcessType, processes[1].Type) 79 + assert.Empty(t, processes[1].ParentPID) 80 + assert.Len(t, processes[1].Children, 1) 81 + 82 + assert.EqualValues(t, "Normal process", processes[2].Description) 83 + assert.EqualValues(t, NormalProcessType, processes[2].Type) 84 + assert.Empty(t, processes[2].ParentPID) 85 + assert.Empty(t, processes[2].Children) 86 + 87 + // This is the "main" pid, testing code always runs in a goroutine. 88 + assert.EqualValues(t, "(unassociated)", processes[3].Description) 89 + assert.EqualValues(t, NoneProcessType, processes[3].Type) 90 + assert.Empty(t, processes[3].ParentPID) 91 + assert.Empty(t, processes[3].Children) 92 + }) 93 + }