30
}
31
32
>
func newReport(objects []trackedObject, trackedRoots int, expected patterns, pruneTypes patterns) report {
report.go
33
>
report := report{
34
>
trackedRoots: trackedRoots,
35
>
}
36
>
37
>
// Matching mutates pattern.matched for stale-expected pattern detection.
38
>
activeExpected := slices.Clone(expected)
39
>
40
>
type groupKey struct {
41
>
path string
42
>
typeName string
43
>
expected bool
44
>
}
45
>
groupByKey := make(map[groupKey]*objectGroup)
46
>
retainedAddresses := make(map[uintptr]struct{})
47
>
expectedAddresses := make(map[uintptr]struct{})
48
>
unexpectedAddresses := make(map[uintptr]struct{})
49
>
50
>
// Classify each retained object and fold equivalent normalized paths into
51
>
// a single report row.
52
>
for _, obj := range objects {
53
>
if obj.collected.Load() {
54
continue
55
}
56
58
>
report.totalRetainedPaths++
59
>
retainedAddresses[obj.addr] = struct{}{}
60
>
expected := len(expectedBy) > 0
61
>
if expected {
62
report.expectedRetainedPaths++
63
expectedAddresses[obj.addr] = struct{}{}
66
>
unexpectedAddresses[obj.addr] = struct{}{}
67
>
}
68
70
>
path: obj.path.normalized(),
71
>
typeName: obj.typeName,
72
>
expected: expected,
73
>
}
74
>
group := groupByKey[key]
75
>
if group == nil {
76
>
group = &objectGroup{
77
>
path: key.path,
78
>
typeName: key.typeName,
79
>
addresses: make(map[uintptr]struct{}),
80
>
}
81
>
groupByKey[key] = group
82
>
}
83
>
group.paths++
84
>
group.addresses[obj.addr] = struct{}{}
85
}
87
>
report.expectedRetainedObjects = len(expectedAddresses)
88
>
report.unexpectedRetainedObjects = len(unexpectedAddresses)
89
>
90
>
// Expected patterns that never matched any tracked object are stale and
91
>
// should be removed with the fix that made them unnecessary.
92
>
report.unmatchedExpected = activeExpected.unmatched()
93
>
report.unmatchedPrunes = pruneTypes.unmatched()
94
>
95
>
// Keep report output stable across map iteration order and repeated runs.
96
>
for key, group := range groupByKey {
97
>
if key.expected {
98
report.expectedObjects = append(report.expectedObjects, *group)
101
>
}
102
}
104
>
slices.SortFunc(groups, func(a objectGroup, b objectGroup) int {
105
>
if c := cmp.Compare(b.objectCount(), a.objectCount()); c != 0 {
106
>
return c
107
>
}
109
return c
110
}
112
>
return c
113
>
}
114
return cmp.Compare(a.typeName, b.typeName)
115
})
116
}
118
>
sortGroups(report.expectedObjects)
119
>
slices.Sort(report.unmatchedExpected)
120
>
slices.Sort(report.unmatchedPrunes)
121
>
return report
122
}
123
125
>
var failures []error
126
>
if len(r.unexpectedObjects) > 0 {
128
>
}
130
failures = append(failures, errors.New("stale expected patterns"))
131
}
133
failures = append(failures, errors.New("stale prunes"))
134
}
136
}
137
139
>
return [3]int{
140
>
r.totalRetainedObjects,
141
>
r.unexpectedRetainedObjects,
142
>
len(r.unmatchedExpected) + len(r.unmatchedPrunes),
143
>
}
144
>
}
145
147
>
var out strings.Builder
148
>
r.writeSummary(&out)
149
>
150
>
writeGroups := func(title string, groups []objectGroup) {
151
>
fmt.Fprintf(&out, "%s:\n", title)
152
>
if len(groups) == 0 {
153
>
out.WriteString(" none\n")
154
>
return
155
>
}
156
>
for _, group := range groups {
157
>
fmt.Fprintf(&out, " %s: %s\n", group.counts(), group.name())
158
>
}
159
}
161
>
writeGroups("unexpected retained objects", r.unexpectedObjects)
162
>
out.WriteByte('\n')
163
>
writeGroups("expected retained objects", r.expectedObjects)
164
>
165
>
if len(r.unmatchedExpected) > 0 {
166
out.WriteString("\nstale expected patterns:\n")
167
}
169
fmt.Fprintf(&out, " %s\n", pattern)
170
}
171
173
out.WriteString("\nstale prunes:\n")
174
}
176
fmt.Fprintf(&out, " %s\n", pattern)
177
}
179
}
180
182
>
out.WriteString("object leak report\n\n")
183
>
fmt.Fprintf(out, "tracked root objects: %d\n", r.trackedRoots)
184
>
fmt.Fprintf(
185
>
out,
186
>
"retained paths: %d total, %d expected, %d unexpected\n",
187
>
r.totalRetainedPaths,
188
>
r.expectedRetainedPaths,
189
>
r.unexpectedRetainedPaths,
190
>
)
191
>
fmt.Fprintf(
192
>
out,
193
>
"retained objects: %d total, %d expected, %d unexpected\n",
194
>
r.totalRetainedObjects,
195
>
r.expectedRetainedObjects,
196
>
r.unexpectedRetainedObjects,
197
>
)
198
>
}
199
201
>
if g.path == "" {
202
>
return g.typeName
203
>
}
204
>
return fmt.Sprintf("%s (%s)", g.path, g.typeName)
205
}
206
208
>
return len(g.addresses)
209
>
}
210
212
>
objects := g.objectCount()
213
>
if g.paths == objects {
214
>
return formatCount(objects, "object")
215
>
}
216
>
return fmt.Sprintf("%s, %s", formatCount(g.paths, "path"), formatCount(objects, "object"))
217
}
218
220
>
if count == 1 {
221
>
return fmt.Sprintf("1 %s", label)
222
>
}
223
>
return fmt.Sprintf("%d %ss", count, label)
224
}