Skip to content

Commit 9b4378f

Browse files
authored
Merge pull request moby#5180 from tonistiigi/stack-compress
stack: compress shared stacks for clearer output
2 parents b60d621 + d6b158d commit 9b4378f

File tree

3 files changed

+125
-0
lines changed

3 files changed

+125
-0
lines changed

util/stack/compress.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package stack
2+
3+
import (
4+
"slices"
5+
)
6+
7+
func compressStacks(st []*Stack) []*Stack {
8+
if len(st) == 0 {
9+
return nil
10+
}
11+
12+
slices.SortFunc(st, func(a, b *Stack) int {
13+
return len(b.Frames) - len(a.Frames)
14+
})
15+
16+
out := []*Stack{st[0]}
17+
18+
loop0:
19+
for _, st := range st[1:] {
20+
maxIdx := -1
21+
for _, prev := range out {
22+
idx := subFrames(st.Frames, prev.Frames)
23+
if idx == -1 {
24+
continue
25+
}
26+
// full match, potentially skip all
27+
if idx == len(st.Frames)-1 {
28+
if st.Pid == prev.Pid && st.Version == prev.Version && slices.Compare(st.Cmdline, st.Cmdline) == 0 {
29+
continue loop0
30+
}
31+
}
32+
if idx > maxIdx {
33+
maxIdx = idx
34+
}
35+
}
36+
37+
if maxIdx > 0 {
38+
st.Frames = st.Frames[:len(st.Frames)-maxIdx]
39+
}
40+
out = append(out, st)
41+
}
42+
43+
return out
44+
}
45+
46+
func subFrames(a, b []*Frame) int {
47+
idx := -1
48+
i := len(a) - 1
49+
j := len(b) - 1
50+
for i >= 0 {
51+
if j < 0 {
52+
break
53+
}
54+
if a[i].Equal(b[j]) {
55+
idx++
56+
i--
57+
j--
58+
} else {
59+
break
60+
}
61+
}
62+
return idx
63+
}
64+
65+
func (a *Frame) Equal(b *Frame) bool {
66+
return a.File == b.File && a.Line == b.Line && a.Name == b.Name
67+
}

util/stack/compress_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package stack
2+
3+
import (
4+
"testing"
5+
6+
"github.com/pkg/errors"
7+
"github.com/stretchr/testify/require"
8+
)
9+
10+
func testcall1() error {
11+
return errors.Errorf("error1")
12+
}
13+
14+
func testcall2() error {
15+
return errors.WithStack(testcall1())
16+
}
17+
18+
func testcall3() error {
19+
err := testcall2()
20+
// this is from a different line
21+
return errors.WithStack(err)
22+
}
23+
24+
func TestCompressStacks(t *testing.T) {
25+
err := testcall2()
26+
st := Traces(err)
27+
28+
// full trace match, shorter is removed
29+
require.Len(t, st, 1)
30+
require.GreaterOrEqual(t, len(st[0].Frames), 2)
31+
32+
f := st[0].Frames
33+
require.Contains(t, f[0].Name, "testcall1")
34+
require.Contains(t, f[1].Name, "testcall2")
35+
}
36+
37+
func TestCompressMultiStacks(t *testing.T) {
38+
err := testcall3()
39+
st := Traces(err)
40+
41+
require.Len(t, st, 2)
42+
require.GreaterOrEqual(t, len(st[0].Frames), 4)
43+
44+
f1 := st[0].Frames
45+
require.Contains(t, f1[0].Name, "testcall1")
46+
require.Contains(t, f1[1].Name, "testcall2")
47+
require.Contains(t, f1[2].Name, "testcall3")
48+
49+
f2 := st[1].Frames
50+
require.Contains(t, f2[0].Name, "testcall3")
51+
// next line is shared and everything after is removed
52+
require.Len(t, f2, 2)
53+
require.Equal(t, f1[3], f2[1])
54+
}

util/stack/stack.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ func Helper() {
4444
}
4545

4646
func Traces(err error) []*Stack {
47+
return compressStacks(traces(err))
48+
}
49+
50+
func traces(err error) []*Stack {
4751
var st []*Stack
4852

4953
switch e := err.(type) {

0 commit comments

Comments
 (0)