-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
stackutil improvements: (un-aggregated) snapshots, add gids to text o…
…utput
- Loading branch information
Showing
6 changed files
with
289 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
package stackutil | ||
|
||
import ( | ||
"bytes" | ||
"io" | ||
"sort" | ||
"strings" | ||
|
||
"github.com/maruel/panicparse/v2/stack" | ||
|
||
"github.com/eluv-io/common-go/util/numberutil" | ||
"github.com/eluv-io/common-go/util/sliceutil" | ||
"github.com/eluv-io/errors-go" | ||
"github.com/eluv-io/utc-go" | ||
) | ||
|
||
// NewSnapshot creates a snapshot by parsing stacktrace information from the given string. Returns an error if no | ||
// stacktraces are found. | ||
func NewSnapshot(trace string) (*Snapshot, error) { | ||
in := bytes.NewBuffer([]byte(trace)) | ||
snapshot, _, err := stack.ScanSnapshot(in, io.Discard, stack.DefaultOpts()) | ||
if snapshot == nil { | ||
return nil, errors.E("stack.CreateSnapshot", errors.K.NotExist, "error", err, "reason", "no stacktrace found") | ||
} | ||
s := &Snapshot{snapshot, utc.Now()} | ||
s.SortByGID(true) | ||
return s, nil | ||
} | ||
|
||
// ExtractSnapshots extracts all snapshot found in the given reader. In the case of an error, the snapshots found up to | ||
// that point are returned. | ||
func ExtractSnapshots(reader io.Reader) ([]*Snapshot, error) { | ||
res := make([]*Snapshot, 0, 10) | ||
for { | ||
snapshot, _, err := stack.ScanSnapshot(reader, io.Discard, stack.DefaultOpts()) | ||
if snapshot == nil { | ||
if err != io.EOF { | ||
return res, errors.E("stack.ExtractSnapshots", errors.K.IO, err) | ||
} | ||
break | ||
} | ||
s := &Snapshot{snapshot, utc.Now()} | ||
s.SortByGID(true) | ||
res = append(res, s) | ||
} | ||
return res, nil | ||
} | ||
|
||
// Snapshot is a wrapper around github.com/maruel/panicparse/v2/stack.Snapshot and offers sorting, filtering | ||
// and custom text marshalling. | ||
type Snapshot struct { | ||
*stack.Snapshot | ||
Timestamp utc.UTC | ||
} | ||
|
||
// SortByGID sorts the goroutines by goroutine ID. | ||
func (s *Snapshot) SortByGID(ascending bool) { | ||
goroutines := s.Goroutines | ||
sort.SliceStable(goroutines, func(i, j int) bool { | ||
return numberutil.LessInt(ascending, goroutines[i].ID, goroutines[j].ID) | ||
}) | ||
} | ||
|
||
// AsText converts this stack to a text based form. | ||
func (s *Snapshot) String() string { | ||
res, _ := s.AsText() | ||
return res | ||
} | ||
|
||
// AsText converts this stack to a text based form. | ||
func (s *Snapshot) AsText() (string, error) { | ||
out := &bytes.Buffer{} | ||
err := s.writeAsText(out) | ||
if err != nil { | ||
return "", err | ||
} | ||
return out.String(), nil | ||
} | ||
|
||
// Filter filters the Snapshot by retaining the Goroutines that match the given "keep" function and removing all | ||
// others. It returns the number of removed Goroutines. | ||
func (s *Snapshot) Filter(keep func(goroutine *stack.Goroutine) bool) (removed int) { | ||
remove := func(e *stack.Goroutine) bool { | ||
return !keep(e) | ||
} | ||
s.Goroutines, removed = sliceutil.RemoveMatch(s.Goroutines, remove) | ||
return removed | ||
} | ||
|
||
// FilterText filters the Snapshot by retaining (keep=true) or removing (keep=false) the Goroutines that match the given "match" | ||
// strings. A Goroutine matches if at least one of its function calls or corresponding source | ||
// filenames contains at least one of the "match" strings. The return value is the number of removed Goroutines. | ||
func (s *Snapshot) FilterText(keep bool, match ...string) (removed int) { | ||
s.Goroutines, removed = sliceutil.RemoveMatch(s.Goroutines, func(e *stack.Goroutine) bool { | ||
for _, call := range e.Stack.Calls { | ||
for _, s := range match { | ||
if strings.Contains(call.SrcName, s) || strings.Contains(call.Func.Complete, s) { | ||
return !keep | ||
} | ||
} | ||
} | ||
return keep | ||
}) | ||
return removed | ||
} | ||
|
||
// Aggregate creates an aggregated stack dump from this snapshot. The aggregation is more or less aggressive based on | ||
// the provided similarity parameter. | ||
func (s *Snapshot) Aggregate(sim stack.Similarity) *AggregateStack { | ||
agg := s.Snapshot.Aggregate(sim) | ||
return &AggregateStack{ | ||
Agg: agg, | ||
Similarity: sim, | ||
Timestamp: s.Timestamp, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package stackutil_test | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
|
||
"github.com/eluv-io/common-go/util/stackutil" | ||
) | ||
|
||
func TestSnapshot(t *testing.T) { | ||
stackb, err := os.ReadFile("testdata/stacktrace1.txt") | ||
require.NoError(t, err) | ||
stack := string(stackb) | ||
|
||
snapshot, err := stackutil.NewSnapshot(stack) | ||
require.NoError(t, err) | ||
|
||
require.Equal(t, 587, len(snapshot.Goroutines)) | ||
|
||
removed := snapshot.FilterText(true, "content-fabric") | ||
require.Equal(t, 157, removed) | ||
require.Equal(t, 430, len(snapshot.Goroutines)) | ||
|
||
removed = snapshot.FilterText(false, "qparts.") | ||
require.Equal(t, 423, removed) | ||
require.Equal(t, 7, len(snapshot.Goroutines)) | ||
|
||
text, err := snapshot.AsText() | ||
require.NoError(t, err) | ||
fmt.Println(text) | ||
} |
Oops, something went wrong.