bucket.go 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. // Copyright 2015 Marc-Antoine Ruel. All rights reserved.
  2. // Use of this source code is governed under the Apache License, Version 2.0
  3. // that can be found in the LICENSE file.
  4. package stack
  5. import (
  6. "sort"
  7. )
  8. // Similarity is the level at which two call lines arguments must match to be
  9. // considered similar enough to coalesce them.
  10. type Similarity int
  11. const (
  12. // ExactFlags requires same bits (e.g. Locked).
  13. ExactFlags Similarity = iota
  14. // ExactLines requests the exact same arguments on the call line.
  15. ExactLines
  16. // AnyPointer considers different pointers a similar call line.
  17. AnyPointer
  18. // AnyValue accepts any value as similar call line.
  19. AnyValue
  20. )
  21. // Aggregate merges similar goroutines into buckets.
  22. //
  23. // The buckets are ordered in library provided order of relevancy. You can
  24. // reorder at your chosing.
  25. func Aggregate(goroutines []*Goroutine, similar Similarity) []*Bucket {
  26. type count struct {
  27. ids []int
  28. first bool
  29. }
  30. b := map[*Signature]*count{}
  31. // O(n²). Fix eventually.
  32. for _, routine := range goroutines {
  33. found := false
  34. for key, c := range b {
  35. // When a match is found, this effectively drops the other goroutine ID.
  36. if key.similar(&routine.Signature, similar) {
  37. found = true
  38. c.ids = append(c.ids, routine.ID)
  39. c.first = c.first || routine.First
  40. if !key.equal(&routine.Signature) {
  41. // Almost but not quite equal. There's different pointers passed
  42. // around but the same values. Zap out the different values.
  43. newKey := key.merge(&routine.Signature)
  44. b[newKey] = c
  45. delete(b, key)
  46. }
  47. break
  48. }
  49. }
  50. if !found {
  51. // Create a copy of the Signature, since it will be mutated.
  52. key := &Signature{}
  53. *key = routine.Signature
  54. b[key] = &count{ids: []int{routine.ID}, first: routine.First}
  55. }
  56. }
  57. out := make(buckets, 0, len(b))
  58. for signature, c := range b {
  59. sort.Ints(c.ids)
  60. out = append(out, &Bucket{Signature: *signature, IDs: c.ids, First: c.first})
  61. }
  62. sort.Sort(out)
  63. return out
  64. }
  65. // Bucket is a stack trace signature and the list of goroutines that fits this
  66. // signature.
  67. type Bucket struct {
  68. Signature
  69. // IDs is the ID of each Goroutine with this Signature.
  70. IDs []int
  71. // First is true if this Bucket contains the first goroutine, e.g. the one
  72. // Signature that likely generated the panic() call, if any.
  73. First bool
  74. }
  75. // less does reverse sort.
  76. func (b *Bucket) less(r *Bucket) bool {
  77. if b.First || r.First {
  78. return b.First
  79. }
  80. return b.Signature.less(&r.Signature)
  81. }
  82. //
  83. // buckets is a list of Bucket sorted by repeation count.
  84. type buckets []*Bucket
  85. func (b buckets) Len() int {
  86. return len(b)
  87. }
  88. func (b buckets) Less(i, j int) bool {
  89. return b[i].less(b[j])
  90. }
  91. func (b buckets) Swap(i, j int) {
  92. b[j], b[i] = b[i], b[j]
  93. }