Skip to content

Commit

Permalink
Merge pull request #5180 from tonistiigi/stack-compress
Browse files Browse the repository at this point in the history
stack: compress shared stacks for clearer output
  • Loading branch information
crazy-max committed Sep 3, 2024
2 parents b60d621 + d6b158d commit 9b4378f
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 0 deletions.
67 changes: 67 additions & 0 deletions util/stack/compress.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package stack

import (
"slices"
)

func compressStacks(st []*Stack) []*Stack {
if len(st) == 0 {
return nil
}

slices.SortFunc(st, func(a, b *Stack) int {
return len(b.Frames) - len(a.Frames)
})

out := []*Stack{st[0]}

loop0:
for _, st := range st[1:] {
maxIdx := -1
for _, prev := range out {
idx := subFrames(st.Frames, prev.Frames)
if idx == -1 {
continue
}
// full match, potentially skip all
if idx == len(st.Frames)-1 {
if st.Pid == prev.Pid && st.Version == prev.Version && slices.Compare(st.Cmdline, st.Cmdline) == 0 {
continue loop0
}
}
if idx > maxIdx {
maxIdx = idx
}
}

if maxIdx > 0 {
st.Frames = st.Frames[:len(st.Frames)-maxIdx]
}
out = append(out, st)
}

return out
}

func subFrames(a, b []*Frame) int {
idx := -1
i := len(a) - 1
j := len(b) - 1
for i >= 0 {
if j < 0 {
break
}
if a[i].Equal(b[j]) {
idx++
i--
j--
} else {
break
}
}
return idx
}

func (a *Frame) Equal(b *Frame) bool {
return a.File == b.File && a.Line == b.Line && a.Name == b.Name
}
54 changes: 54 additions & 0 deletions util/stack/compress_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package stack

import (
"testing"

"github.com/pkg/errors"
"github.com/stretchr/testify/require"
)

func testcall1() error {
return errors.Errorf("error1")
}

func testcall2() error {
return errors.WithStack(testcall1())
}

func testcall3() error {
err := testcall2()
// this is from a different line
return errors.WithStack(err)
}

func TestCompressStacks(t *testing.T) {
err := testcall2()
st := Traces(err)

// full trace match, shorter is removed
require.Len(t, st, 1)
require.GreaterOrEqual(t, len(st[0].Frames), 2)

f := st[0].Frames
require.Contains(t, f[0].Name, "testcall1")
require.Contains(t, f[1].Name, "testcall2")
}

func TestCompressMultiStacks(t *testing.T) {
err := testcall3()
st := Traces(err)

require.Len(t, st, 2)
require.GreaterOrEqual(t, len(st[0].Frames), 4)

f1 := st[0].Frames
require.Contains(t, f1[0].Name, "testcall1")
require.Contains(t, f1[1].Name, "testcall2")
require.Contains(t, f1[2].Name, "testcall3")

f2 := st[1].Frames
require.Contains(t, f2[0].Name, "testcall3")
// next line is shared and everything after is removed
require.Len(t, f2, 2)
require.Equal(t, f1[3], f2[1])
}
4 changes: 4 additions & 0 deletions util/stack/stack.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ func Helper() {
}

func Traces(err error) []*Stack {
return compressStacks(traces(err))
}

func traces(err error) []*Stack {
var st []*Stack

switch e := err.(type) {
Expand Down

0 comments on commit 9b4378f

Please sign in to comment.