110 lines
2.7 KiB
Go
110 lines
2.7 KiB
Go
// Copyright 2015 Marc-Antoine Ruel. All rights reserved.
|
|
// Use of this source code is governed under the Apache License, Version 2.0
|
|
// that can be found in the LICENSE file.
|
|
|
|
package stack
|
|
|
|
import (
|
|
"sort"
|
|
)
|
|
|
|
// Similarity is the level at which two call lines arguments must match to be
|
|
// considered similar enough to coalesce them.
|
|
type Similarity int
|
|
|
|
const (
|
|
// ExactFlags requires same bits (e.g. Locked).
|
|
ExactFlags Similarity = iota
|
|
// ExactLines requests the exact same arguments on the call line.
|
|
ExactLines
|
|
// AnyPointer considers different pointers a similar call line.
|
|
AnyPointer
|
|
// AnyValue accepts any value as similar call line.
|
|
AnyValue
|
|
)
|
|
|
|
// Bucketize returns the number of similar goroutines.
|
|
func Bucketize(goroutines []Goroutine, similar Similarity) map[*Signature][]Goroutine {
|
|
out := map[*Signature][]Goroutine{}
|
|
// O(n²). Fix eventually.
|
|
for _, routine := range goroutines {
|
|
found := false
|
|
for key := range out {
|
|
// When a match is found, this effectively drops the other goroutine ID.
|
|
if key.Similar(&routine.Signature, similar) {
|
|
found = true
|
|
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)
|
|
out[newKey] = append(out[key], routine)
|
|
delete(out, key)
|
|
} else {
|
|
out[key] = append(out[key], routine)
|
|
}
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
key := &Signature{}
|
|
*key = routine.Signature
|
|
out[key] = []Goroutine{routine}
|
|
}
|
|
}
|
|
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
|
|
}
|
|
|
|
// Less does reverse sort.
|
|
func (b *Bucket) Less(r *Bucket) bool {
|
|
if b.First() {
|
|
return true
|
|
}
|
|
if r.First() {
|
|
return false
|
|
}
|
|
return b.Signature.Less(&r.Signature)
|
|
}
|
|
|
|
// Buckets is a list of Bucket sorted by repeation count.
|
|
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])
|
|
}
|
|
|
|
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(buckets map[*Signature][]Goroutine) Buckets {
|
|
out := make(Buckets, 0, len(buckets))
|
|
for signature, count := range buckets {
|
|
out = append(out, Bucket{*signature, count})
|
|
}
|
|
sort.Sort(out)
|
|
return out
|
|
}
|