Skip to content

Commit

Permalink
stack: Rename Bucketize to Aggregate, simplify Bucket
Browse files Browse the repository at this point in the history
Change the returned slice to be a slice of pointers, which makes sorting
more efficient.

Simplify tests in bucket_test.go to be more focused.
  • Loading branch information
maruel committed Mar 18, 2018
1 parent f6d6c6c commit 8fe0a69
Show file tree
Hide file tree
Showing 7 changed files with 78 additions and 164 deletions.
6 changes: 3 additions & 3 deletions internal/html.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
"github.com/maruel/panicparse/stack"
)

func writeToHTML(html string, buckets []stack.Bucket, needsEnv bool) error {
func writeToHTML(html string, buckets []*stack.Bucket, needsEnv bool) error {
m := template.FuncMap{
"funcClass": funcClass,
"notoColorEmoji1F4A3": notoColorEmoji1F4A3,
Expand All @@ -27,7 +27,7 @@ func writeToHTML(html string, buckets []stack.Bucket, needsEnv bool) error {
return err
}
data := struct {
Buckets []stack.Bucket
Buckets []*stack.Bucket
Now time.Time
NeedsEnv bool
}{buckets, time.Now().Truncate(time.Second), needsEnv}
Expand Down Expand Up @@ -59,7 +59,7 @@ func funcClass(line *stack.Call) template.HTML {
}

func routineClass(bucket *stack.Bucket) template.HTML {
if bucket.First() {
if bucket.First {
return "RoutineFirst"
}
return "Routine"
Expand Down
6 changes: 3 additions & 3 deletions internal/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,13 @@ var defaultPalette = Palette{
Arguments: resetFG,
}

func writeToConsole(out io.Writer, p *Palette, buckets []stack.Bucket, fullPath, needsEnv bool, filter, match *regexp.Regexp) error {
func writeToConsole(out io.Writer, p *Palette, buckets []*stack.Bucket, fullPath, needsEnv bool, filter, match *regexp.Regexp) error {
if needsEnv {
_, _ = io.WriteString(out, "\nTo see all goroutines, visit https://github.com/maruel/panicparse#gotraceback\n\n")
}
srcLen, pkgLen := CalcLengths(buckets, fullPath)
for _, bucket := range buckets {
header := p.BucketHeader(&bucket, fullPath, len(buckets) > 1)
header := p.BucketHeader(bucket, fullPath, len(buckets) > 1)
if filter != nil && filter.MatchString(header) {
continue
}
Expand Down Expand Up @@ -90,7 +90,7 @@ func process(in io.Reader, out io.Writer, p *Palette, s stack.Similarity, fullPa
if parse {
stack.Augment(c.Goroutines)
}
buckets := stack.Bucketize(c.Goroutines, s)
buckets := stack.Aggregate(c.Goroutines, s)
if html == "" {
return writeToConsole(out, p, buckets, fullPath, needsEnv, filter, match)
}
Expand Down
6 changes: 3 additions & 3 deletions internal/ui.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ type Palette struct {
}

// CalcLengths returns the maximum length of the source lines and package names.
func CalcLengths(buckets []stack.Bucket, fullPath bool) (int, int) {
func CalcLengths(buckets []*stack.Bucket, fullPath bool) (int, int) {
srcLen := 0
pkgLen := 0
for _, bucket := range buckets {
Expand Down Expand Up @@ -75,7 +75,7 @@ func (p *Palette) functionColor(line *stack.Call) string {

// routineColor returns the color for the header of the goroutines bucket.
func (p *Palette) routineColor(bucket *stack.Bucket, multipleBuckets bool) string {
if bucket.First() && multipleBuckets {
if bucket.First && multipleBuckets {
return p.RoutineFirst
}
return p.Routine
Expand All @@ -95,7 +95,7 @@ func (p *Palette) BucketHeader(bucket *stack.Bucket, fullPath, multipleBuckets b
}
return fmt.Sprintf(
"%s%d: %s%s%s\n",
p.routineColor(bucket, multipleBuckets), len(bucket.Routines),
p.routineColor(bucket, multipleBuckets), len(bucket.IDs),
bucket.State, extra,
p.EOLReset)
}
Expand Down
16 changes: 7 additions & 9 deletions internal/ui_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ var testPalette = &Palette{
}

func TestCalcLengths(t *testing.T) {
b := []stack.Bucket{
b := []*stack.Bucket{
{
Signature: stack.Signature{
Stack: stack.Stack{
Expand All @@ -43,7 +43,8 @@ func TestCalcLengths(t *testing.T) {
},
},
},
Routines: nil,
IDs: []int{},
First: true,
},
}
srcLen, pkgLen := CalcLengths(b, true)
Expand Down Expand Up @@ -72,12 +73,8 @@ func TestBucketHeader(t *testing.T) {
SleepMax: 6,
SleepMin: 2,
},
Routines: []stack.Goroutine{
{
First: true,
},
{},
},
IDs: []int{1, 2},
First: true,
}
// When printing, it prints the remote path, not the transposed local path.
compareString(t, "B2: chan receive [2~6 minutes]D [Created by main.mainImpl @ /gopath/src/github.com/foo/bar/baz.go:74]A\n", testPalette.BucketHeader(b, true, true))
Expand All @@ -92,7 +89,8 @@ func TestBucketHeader(t *testing.T) {
SleepMin: 6,
Locked: true,
},
Routines: nil,
IDs: []int{},
First: true,
}
compareString(t, "C0: b0rked [6 minutes] [locked]A\n", testPalette.BucketHeader(b, false, false))
}
Expand Down
70 changes: 31 additions & 39 deletions stack/bucket.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,91 +23,83 @@ const (
AnyValue
)

// Bucketize merges similar goroutines into buckets.
// Aggregate merges similar goroutines into buckets.
//
// The buckets are ordering in order of relevancy.
func Bucketize(goroutines []Goroutine, similar Similarity) []Bucket {
b := map[*Signature][]Goroutine{}
// The buckets are ordered in library provided order of relevancy. You can
// reorder at your chosing.
func Aggregate(goroutines []Goroutine, similar Similarity) []*Bucket {
type count struct {
ids []int
first bool
}
b := map[*Signature]*count{}
// O(n²). Fix eventually.
for _, routine := range goroutines {
found := false
for key := range b {
for key, c := range b {
// When a match is found, this effectively drops the other goroutine ID.
if key.similar(&routine.Signature, similar) {
found = true
c.ids = append(c.ids, routine.ID)
c.first = c.first || routine.First
if !key.equal(&routine.Signature) {
// Almost but not quite equal. There's different pointers passed
// around but the same values. Zap out the different values.
newKey := key.merge(&routine.Signature)
b[newKey] = append(b[key], routine)
b[newKey] = c
delete(b, key)
} else {
b[key] = append(b[key], routine)
}
break
}
}
if !found {
// Create a copy of the Signature, since it will be mutated.
key := &Signature{}
*key = routine.Signature
b[key] = []Goroutine{routine}
b[key] = &count{ids: []int{routine.ID}, first: routine.First}
}
}
return sortBuckets(b)
out := make(buckets, 0, len(b))
for signature, c := range b {
sort.Ints(c.ids)
out = append(out, &Bucket{Signature: *signature, IDs: c.ids, First: c.first})
}
sort.Sort(out)
return out
}

// Bucket is a stack trace signature and the list of goroutines that fits this
// signature.
type Bucket struct {
Signature
Routines []Goroutine
}

// First returns true if it contains the first goroutine, e.g. the ones that
// likely generated the panic() call, if any.
func (b *Bucket) First() bool {
for _, r := range b.Routines {
if r.First {
return true
}
}
return false
// IDs is the ID of each Goroutine with this Signature.
IDs []int
// First is true if this Bucket contains the first goroutine, e.g. the one
// Signature that likely generated the panic() call, if any.
First bool
}

// less does reverse sort.
func (b *Bucket) less(r *Bucket) bool {
if b.First() {
return true
}
if r.First() {
return false
if b.First || r.First {
return b.First
}
return b.Signature.less(&r.Signature)
}

//

// buckets is a list of Bucket sorted by repeation count.
type buckets []Bucket
type buckets []*Bucket

func (b buckets) Len() int {
return len(b)
}

func (b buckets) Less(i, j int) bool {
return b[i].less(&b[j])
return b[i].less(b[j])
}

func (b buckets) Swap(i, j int) {
b[j], b[i] = b[i], b[j]
}

// sortBuckets creates a list of Bucket from each goroutine stack trace count.
func sortBuckets(b map[*Signature][]Goroutine) []Bucket {
out := make(buckets, 0, len(b))
for signature, count := range b {
out = append(out, Bucket{*signature, count})
}
sort.Sort(out)
return out
}
Loading

0 comments on commit 8fe0a69

Please sign in to comment.