|
@@ -20,6 +20,7 @@ import (
|
20
|
20
|
"math"
|
21
|
21
|
"path/filepath"
|
22
|
22
|
"sort"
|
|
23
|
+ "strconv"
|
23
|
24
|
"strings"
|
24
|
25
|
|
25
|
26
|
"github.com/google/pprof/profile"
|
|
@@ -69,9 +70,9 @@ type Node struct {
|
69
|
70
|
NumericTags map[string]TagMap
|
70
|
71
|
}
|
71
|
72
|
|
72
|
|
-// BumpWeight increases the weight of an edge between two nodes. If
|
|
73
|
+// AddToEdge increases the weight of an edge between two nodes. If
|
73
|
74
|
// there isn't such an edge one is created.
|
74
|
|
-func (n *Node) BumpWeight(to *Node, w int64, residual, inline bool) {
|
|
75
|
+func (n *Node) AddToEdge(to *Node, w int64, residual, inline bool) {
|
75
|
76
|
if n.Out[to] != to.In[n] {
|
76
|
77
|
panic(fmt.Errorf("asymmetric edges %v %v", *n, *to))
|
77
|
78
|
}
|
|
@@ -136,36 +137,24 @@ func (i *NodeInfo) NameComponents() []string {
|
136
|
137
|
return name
|
137
|
138
|
}
|
138
|
139
|
|
139
|
|
-// ExtendedNodeInfo extends the NodeInfo with a pointer to a parent node, to
|
140
|
|
-// identify nodes with identical information and different callers. This is
|
141
|
|
-// used when creating call trees.
|
142
|
|
-type ExtendedNodeInfo struct {
|
143
|
|
- NodeInfo
|
144
|
|
- parent *Node
|
145
|
|
-}
|
146
|
|
-
|
147
|
140
|
// NodeMap maps from a node info struct to a node. It is used to merge
|
148
|
141
|
// report entries with the same info.
|
149
|
|
-type NodeMap map[ExtendedNodeInfo]*Node
|
|
142
|
+type NodeMap map[NodeInfo]*Node
|
150
|
143
|
|
151
|
144
|
// NodeSet maps is a collection of node info structs.
|
152
|
145
|
type NodeSet map[NodeInfo]bool
|
153
|
146
|
|
154
|
147
|
// FindOrInsertNode takes the info for a node and either returns a matching node
|
155
|
148
|
// from the node map if one exists, or adds one to the map if one does not.
|
156
|
|
-// If parent is non-nil, return a match with the same parent.
|
157
|
149
|
// If kept is non-nil, nodes are only added if they can be located on it.
|
158
|
|
-func (m NodeMap) FindOrInsertNode(info NodeInfo, parent *Node, kept NodeSet) *Node {
|
159
|
|
- if kept != nil && !kept[info] {
|
160
|
|
- return nil
|
161
|
|
- }
|
162
|
|
-
|
163
|
|
- extendedInfo := ExtendedNodeInfo{
|
164
|
|
- info,
|
165
|
|
- parent,
|
|
150
|
+func (nm NodeMap) FindOrInsertNode(info NodeInfo, kept NodeSet) *Node {
|
|
151
|
+ if kept != nil {
|
|
152
|
+ if _, ok := kept[info]; !ok {
|
|
153
|
+ return nil
|
|
154
|
+ }
|
166
|
155
|
}
|
167
|
156
|
|
168
|
|
- if n := m[extendedInfo]; n != nil {
|
|
157
|
+ if n, ok := nm[info]; ok {
|
169
|
158
|
return n
|
170
|
159
|
}
|
171
|
160
|
|
|
@@ -176,7 +165,7 @@ func (m NodeMap) FindOrInsertNode(info NodeInfo, parent *Node, kept NodeSet) *No
|
176
|
165
|
LabelTags: make(TagMap),
|
177
|
166
|
NumericTags: make(map[string]TagMap),
|
178
|
167
|
}
|
179
|
|
- m[extendedInfo] = n
|
|
168
|
+ nm[info] = n
|
180
|
169
|
return n
|
181
|
170
|
}
|
182
|
171
|
|
|
@@ -216,76 +205,139 @@ func SortTags(t []*Tag, flat bool) []*Tag {
|
216
|
205
|
|
217
|
206
|
// New summarizes performance data from a profile into a graph.
|
218
|
207
|
func New(prof *profile.Profile, o *Options) (g *Graph) {
|
219
|
|
- locations := NewLocInfo(prof, o.ObjNames)
|
220
|
|
- nm := make(NodeMap)
|
|
208
|
+ if o.CallTree {
|
|
209
|
+ return newTree(prof, o)
|
|
210
|
+ }
|
|
211
|
+
|
|
212
|
+ nodes, locationMap := CreateNodes(prof, o.ObjNames, o.KeptNodes)
|
221
|
213
|
for _, sample := range prof.Sample {
|
222
|
|
- if sample.Location == nil {
|
|
214
|
+ weight := o.SampleValue(sample.Value)
|
|
215
|
+ if weight == 0 {
|
223
|
216
|
continue
|
224
|
217
|
}
|
|
218
|
+ seenNode := make(map[*Node]bool, len(sample.Location))
|
|
219
|
+ seenEdge := make(map[nodePair]bool, len(sample.Location))
|
|
220
|
+ var parent *Node
|
|
221
|
+ // A residual edge goes over one or more nodes that were not kept.
|
|
222
|
+ residual := false
|
225
|
223
|
|
226
|
|
- // Construct list of node names for sample.
|
227
|
|
- // Keep track of the index on the Sample for each frame,
|
228
|
|
- // to determine inlining status.
|
229
|
|
-
|
230
|
|
- var stack []NodeInfo
|
231
|
|
- var locIndex []int
|
232
|
|
- for i, loc := range sample.Location {
|
233
|
|
- id := loc.ID
|
234
|
|
- stack = append(stack, locations[id]...)
|
235
|
|
- for _ = range locations[id] {
|
236
|
|
- locIndex = append(locIndex, i)
|
|
224
|
+ labels := joinLabels(sample)
|
|
225
|
+ // Group the sample frames, based on a global map.
|
|
226
|
+ for i := len(sample.Location) - 1; i >= 0; i-- {
|
|
227
|
+ l := sample.Location[i]
|
|
228
|
+ locNodes := locationMap[l.ID]
|
|
229
|
+ for ni := len(locNodes) - 1; ni >= 0; ni-- {
|
|
230
|
+ n := locNodes[ni]
|
|
231
|
+ if n == nil {
|
|
232
|
+ residual = true
|
|
233
|
+ continue
|
|
234
|
+ }
|
|
235
|
+ // Add cum weight to all nodes in stack, avoiding double counting.
|
|
236
|
+ if _, ok := seenNode[n]; !ok {
|
|
237
|
+ seenNode[n] = true
|
|
238
|
+ n.addSample(weight, labels, sample.NumLabel, o.FormatTag, false)
|
|
239
|
+ }
|
|
240
|
+ // Update edge weights for all edges in stack, avoiding double counting.
|
|
241
|
+ if _, ok := seenEdge[nodePair{n, parent}]; !ok && parent != nil && n != parent {
|
|
242
|
+ seenEdge[nodePair{n, parent}] = true
|
|
243
|
+ parent.AddToEdge(n, weight, residual, ni != len(locNodes)-1)
|
|
244
|
+ }
|
|
245
|
+ parent = n
|
|
246
|
+ residual = false
|
237
|
247
|
}
|
238
|
248
|
}
|
|
249
|
+ if parent != nil && !residual {
|
|
250
|
+ // Add flat weight to leaf node.
|
|
251
|
+ parent.addSample(weight, labels, sample.NumLabel, o.FormatTag, true)
|
|
252
|
+ }
|
|
253
|
+ }
|
|
254
|
+
|
|
255
|
+ return selectNodesForGraph(nodes, o.DropNegative)
|
|
256
|
+}
|
|
257
|
+
|
|
258
|
+func selectNodesForGraph(nodes Nodes, dropNegative bool) *Graph {
|
|
259
|
+ // Collect nodes into a graph.
|
|
260
|
+ gNodes := make(Nodes, 0, len(nodes))
|
|
261
|
+ for _, n := range nodes {
|
|
262
|
+ if n == nil {
|
|
263
|
+ continue
|
|
264
|
+ }
|
|
265
|
+ if n.Cum == 0 && n.Flat == 0 {
|
|
266
|
+ continue
|
|
267
|
+ }
|
|
268
|
+ if dropNegative && isNegative(n) {
|
|
269
|
+ continue
|
|
270
|
+ }
|
|
271
|
+ gNodes = append(gNodes, n)
|
|
272
|
+ }
|
|
273
|
+ return &Graph{gNodes}
|
|
274
|
+}
|
239
|
275
|
|
|
276
|
+type nodePair struct {
|
|
277
|
+ src, dest *Node
|
|
278
|
+}
|
|
279
|
+
|
|
280
|
+func newTree(prof *profile.Profile, o *Options) (g *Graph) {
|
|
281
|
+ kept := o.KeptNodes
|
|
282
|
+ keepBinary := o.ObjNames
|
|
283
|
+ parentNodeMap := make(map[*Node]NodeMap, len(prof.Sample))
|
|
284
|
+ for _, sample := range prof.Sample {
|
240
|
285
|
weight := o.SampleValue(sample.Value)
|
241
|
|
- seenEdge := make(map[*Node]map[*Node]bool)
|
242
|
|
- var nn *Node
|
243
|
|
- nlocIndex := -1
|
|
286
|
+ if weight == 0 {
|
|
287
|
+ continue
|
|
288
|
+ }
|
|
289
|
+ var parent *Node
|
|
290
|
+ // A residual edge goes over one or more nodes that were not kept.
|
244
|
291
|
residual := false
|
245
|
|
- // Walk top-down over the frames in a sample, keeping track
|
246
|
|
- // of the current parent if we're building a tree.
|
247
|
|
- for i := len(stack); i > 0; i-- {
|
248
|
|
- var parent *Node
|
249
|
|
- if o.CallTree {
|
250
|
|
- parent = nn
|
251
|
|
- }
|
252
|
|
- n := nm.FindOrInsertNode(stack[i-1], parent, o.KeptNodes)
|
253
|
|
- if n == nil {
|
254
|
|
- residual = true
|
255
|
|
- continue
|
256
|
|
- }
|
257
|
|
- // Add flat weight to leaf node.
|
258
|
|
- if i == 1 {
|
259
|
|
- n.addSample(sample, weight, o.FormatTag, true)
|
260
|
|
- }
|
261
|
|
- // Add cum weight to all nodes in stack, avoiding double counting.
|
262
|
|
- if seenEdge[n] == nil {
|
263
|
|
- seenEdge[n] = make(map[*Node]bool)
|
264
|
|
- n.addSample(sample, weight, o.FormatTag, false)
|
|
292
|
+ labels := joinLabels(sample)
|
|
293
|
+ // Group the sample frames, based on a per-node map.
|
|
294
|
+ for i := len(sample.Location) - 1; i >= 0; i-- {
|
|
295
|
+ l := sample.Location[i]
|
|
296
|
+ nodeMap := parentNodeMap[parent]
|
|
297
|
+ if nodeMap == nil {
|
|
298
|
+ nodeMap = make(NodeMap)
|
|
299
|
+ parentNodeMap[parent] = nodeMap
|
265
|
300
|
}
|
266
|
|
- // Update edge weights for all edges in stack, avoiding double counting.
|
267
|
|
- if nn != nil && n != nn && !seenEdge[n][nn] {
|
268
|
|
- seenEdge[n][nn] = true
|
269
|
|
- // This is an inlined edge if the caller and the callee
|
270
|
|
- // correspond to the same entry in the sample.
|
271
|
|
- nn.BumpWeight(n, weight, residual, locIndex[i-1] == nlocIndex)
|
|
301
|
+ locNodes := nodeMap.findOrInsertLocation(l, keepBinary, kept)
|
|
302
|
+ for ni := len(locNodes) - 1; ni >= 0; ni-- {
|
|
303
|
+ n := locNodes[ni]
|
|
304
|
+ if n == nil {
|
|
305
|
+ residual = true
|
|
306
|
+ continue
|
|
307
|
+ }
|
|
308
|
+ n.addSample(weight, labels, sample.NumLabel, o.FormatTag, false)
|
|
309
|
+ if parent != nil {
|
|
310
|
+ parent.AddToEdge(n, weight, residual, ni != len(locNodes)-1)
|
|
311
|
+ }
|
|
312
|
+ parent = n
|
|
313
|
+ residual = false
|
272
|
314
|
}
|
273
|
|
- nn = n
|
274
|
|
- nlocIndex = locIndex[i-1]
|
275
|
|
- residual = false
|
|
315
|
+ }
|
|
316
|
+ if parent != nil && !residual {
|
|
317
|
+ parent.addSample(weight, labels, sample.NumLabel, o.FormatTag, true)
|
276
|
318
|
}
|
277
|
319
|
}
|
278
|
320
|
|
279
|
|
- // Collect nodes into a graph.
|
280
|
|
- ns := make(Nodes, 0, len(nm))
|
281
|
|
- for _, n := range nm {
|
282
|
|
- if o.DropNegative && isNegative(n) {
|
283
|
|
- continue
|
284
|
|
- }
|
285
|
|
- ns = append(ns, n)
|
|
321
|
+ nodes := make(Nodes, len(prof.Location))
|
|
322
|
+ for _, nm := range parentNodeMap {
|
|
323
|
+ nodes = append(nodes, nm.nodes()...)
|
286
|
324
|
}
|
|
325
|
+ return selectNodesForGraph(nodes, o.DropNegative)
|
|
326
|
+}
|
287
|
327
|
|
288
|
|
- return &Graph{ns}
|
|
328
|
+func joinLabels(s *profile.Sample) string {
|
|
329
|
+ if len(s.Label) == 0 {
|
|
330
|
+ return ""
|
|
331
|
+ }
|
|
332
|
+
|
|
333
|
+ var labels []string
|
|
334
|
+ for key, vals := range s.Label {
|
|
335
|
+ for _, v := range vals {
|
|
336
|
+ labels = append(labels, key+":"+v)
|
|
337
|
+ }
|
|
338
|
+ }
|
|
339
|
+ sort.Strings(labels)
|
|
340
|
+ return strings.Join(labels, `\n`)
|
289
|
341
|
}
|
290
|
342
|
|
291
|
343
|
// isNegative returns true if the node is considered as "negative" for the
|
|
@@ -301,51 +353,67 @@ func isNegative(n *Node) bool {
|
301
|
353
|
}
|
302
|
354
|
}
|
303
|
355
|
|
304
|
|
-// NewLocInfo creates a slice of formatted names for a location.
|
305
|
|
-func NewLocInfo(prof *profile.Profile, keepBinary bool) map[uint64][]NodeInfo {
|
306
|
|
- locations := make(map[uint64][]NodeInfo)
|
|
356
|
+// CreateNodes creates graph nodes for all locations in a profile. It
|
|
357
|
+// returns set of all nodes, plus a mapping of each location to the
|
|
358
|
+// set of corresponding nodes (one per location.Line). If kept is
|
|
359
|
+// non-nil, only nodes in that set are included; nodes that do not
|
|
360
|
+// match are represented as a nil.
|
|
361
|
+func CreateNodes(prof *profile.Profile, keepBinary bool, kept NodeSet) (Nodes, map[uint64]Nodes) {
|
|
362
|
+ locations := make(map[uint64]Nodes, len(prof.Location))
|
307
|
363
|
|
|
364
|
+ nm := make(NodeMap, len(prof.Location))
|
308
|
365
|
for _, l := range prof.Location {
|
309
|
|
- var objfile string
|
310
|
|
-
|
311
|
|
- if m := l.Mapping; m != nil {
|
312
|
|
- objfile = filepath.Base(m.File)
|
|
366
|
+ if nodes := nm.findOrInsertLocation(l, keepBinary, kept); nodes != nil {
|
|
367
|
+ locations[l.ID] = nodes
|
313
|
368
|
}
|
|
369
|
+ }
|
|
370
|
+ return nm.nodes(), locations
|
|
371
|
+}
|
314
|
372
|
|
315
|
|
- if len(l.Line) == 0 {
|
316
|
|
- locations[l.ID] = []NodeInfo{
|
317
|
|
- {
|
318
|
|
- Address: l.Address,
|
319
|
|
- Objfile: objfile,
|
320
|
|
- },
|
321
|
|
- }
|
322
|
|
- continue
|
|
373
|
+func (nm NodeMap) nodes() Nodes {
|
|
374
|
+ nodes := make(Nodes, 0, len(nm))
|
|
375
|
+ for _, n := range nm {
|
|
376
|
+ nodes = append(nodes, n)
|
|
377
|
+ }
|
|
378
|
+ return nodes
|
|
379
|
+}
|
|
380
|
+
|
|
381
|
+func (nm NodeMap) findOrInsertLocation(l *profile.Location, keepBinary bool, kept NodeSet) Nodes {
|
|
382
|
+ var objfile string
|
|
383
|
+ if m := l.Mapping; m != nil {
|
|
384
|
+ objfile = filepath.Base(m.File)
|
|
385
|
+ }
|
|
386
|
+
|
|
387
|
+ if len(l.Line) == 0 {
|
|
388
|
+ ni := NodeInfo{
|
|
389
|
+ Address: l.Address,
|
|
390
|
+ Objfile: objfile,
|
|
391
|
+ }
|
|
392
|
+ return Nodes{nm.FindOrInsertNode(ni, kept)}
|
|
393
|
+ }
|
|
394
|
+ var locNodes Nodes
|
|
395
|
+ for _, line := range l.Line {
|
|
396
|
+ ni := NodeInfo{
|
|
397
|
+ Address: l.Address,
|
|
398
|
+ Lineno: int(line.Line),
|
323
|
399
|
}
|
324
|
|
- var info []NodeInfo
|
325
|
|
- for _, line := range l.Line {
|
326
|
|
- ni := NodeInfo{
|
327
|
|
- Address: l.Address,
|
328
|
|
- Lineno: int(line.Line),
|
329
|
|
- }
|
330
|
400
|
|
331
|
|
- if line.Function != nil {
|
332
|
|
- ni.Name = line.Function.Name
|
333
|
|
- ni.OrigName = line.Function.SystemName
|
334
|
|
- if fname := line.Function.Filename; fname != "" {
|
335
|
|
- ni.File = filepath.Clean(fname)
|
336
|
|
- }
|
337
|
|
- if keepBinary {
|
338
|
|
- ni.StartLine = int(line.Function.StartLine)
|
339
|
|
- }
|
|
401
|
+ if line.Function != nil {
|
|
402
|
+ ni.Name = line.Function.Name
|
|
403
|
+ ni.OrigName = line.Function.SystemName
|
|
404
|
+ if fname := line.Function.Filename; fname != "" {
|
|
405
|
+ ni.File = filepath.Clean(fname)
|
340
|
406
|
}
|
341
|
|
- if keepBinary || line.Function == nil {
|
342
|
|
- ni.Objfile = objfile
|
|
407
|
+ if keepBinary {
|
|
408
|
+ ni.StartLine = int(line.Function.StartLine)
|
343
|
409
|
}
|
344
|
|
- info = append(info, ni)
|
345
|
410
|
}
|
346
|
|
- locations[l.ID] = info
|
|
411
|
+ if keepBinary || line.Function == nil {
|
|
412
|
+ ni.Objfile = objfile
|
|
413
|
+ }
|
|
414
|
+ locNodes = append(locNodes, nm.FindOrInsertNode(ni, kept))
|
347
|
415
|
}
|
348
|
|
- return locations
|
|
416
|
+ return locNodes
|
349
|
417
|
}
|
350
|
418
|
|
351
|
419
|
type tags struct {
|
|
@@ -376,7 +444,7 @@ func (ns Nodes) Sum() (flat int64, cum int64) {
|
376
|
444
|
return
|
377
|
445
|
}
|
378
|
446
|
|
379
|
|
-func (n *Node) addSample(s *profile.Sample, value int64, format func(int64, string) string, flat bool) {
|
|
447
|
+func (n *Node) addSample(value int64, labels string, numLabel map[string][]int64, format func(int64, string) string, flat bool) {
|
380
|
448
|
// Update sample value
|
381
|
449
|
if flat {
|
382
|
450
|
n.Flat += value
|
|
@@ -385,17 +453,8 @@ func (n *Node) addSample(s *profile.Sample, value int64, format func(int64, stri
|
385
|
453
|
}
|
386
|
454
|
|
387
|
455
|
// Add string tags
|
388
|
|
- var labels []string
|
389
|
|
- for key, vals := range s.Label {
|
390
|
|
- for _, v := range vals {
|
391
|
|
- labels = append(labels, key+":"+v)
|
392
|
|
- }
|
393
|
|
- }
|
394
|
|
- var joinedLabels string
|
395
|
|
- if len(labels) > 0 {
|
396
|
|
- sort.Strings(labels)
|
397
|
|
- joinedLabels = strings.Join(labels, `\n`)
|
398
|
|
- t := n.LabelTags.findOrAddTag(joinedLabels, "", 0)
|
|
456
|
+ if labels != "" {
|
|
457
|
+ t := n.LabelTags.findOrAddTag(labels, "", 0)
|
399
|
458
|
if flat {
|
400
|
459
|
t.Flat += value
|
401
|
460
|
} else {
|
|
@@ -403,21 +462,18 @@ func (n *Node) addSample(s *profile.Sample, value int64, format func(int64, stri
|
403
|
462
|
}
|
404
|
463
|
}
|
405
|
464
|
|
406
|
|
- numericTags := n.NumericTags[joinedLabels]
|
|
465
|
+ numericTags := n.NumericTags[labels]
|
407
|
466
|
if numericTags == nil {
|
408
|
467
|
numericTags = TagMap{}
|
409
|
|
- n.NumericTags[joinedLabels] = numericTags
|
|
468
|
+ n.NumericTags[labels] = numericTags
|
410
|
469
|
}
|
411
|
470
|
// Add numeric tags
|
412
|
|
- for key, nvals := range s.NumLabel {
|
|
471
|
+ if format == nil {
|
|
472
|
+ format = defaultLabelFormat
|
|
473
|
+ }
|
|
474
|
+ for key, nvals := range numLabel {
|
413
|
475
|
for _, v := range nvals {
|
414
|
|
- var label string
|
415
|
|
- if format != nil {
|
416
|
|
- label = format(v, key)
|
417
|
|
- } else {
|
418
|
|
- label = fmt.Sprintf("%d", v)
|
419
|
|
- }
|
420
|
|
- t := numericTags.findOrAddTag(label, key, v)
|
|
476
|
+ t := numericTags.findOrAddTag(format(v, key), key, v)
|
421
|
477
|
if flat {
|
422
|
478
|
t.Flat += value
|
423
|
479
|
} else {
|
|
@@ -427,6 +483,10 @@ func (n *Node) addSample(s *profile.Sample, value int64, format func(int64, stri
|
427
|
483
|
}
|
428
|
484
|
}
|
429
|
485
|
|
|
486
|
+func defaultLabelFormat(v int64, key string) string {
|
|
487
|
+ return strconv.FormatInt(v, 10)
|
|
488
|
+}
|
|
489
|
+
|
430
|
490
|
func (m TagMap) findOrAddTag(label, unit string, value int64) *Tag {
|
431
|
491
|
l := m[label]
|
432
|
492
|
if l == nil {
|
|
@@ -630,7 +690,7 @@ func isRedundant(e *Edge) bool {
|
630
|
690
|
// predecessors collects all the predecessors to node n, excluding edge e.
|
631
|
691
|
func predecessors(e *Edge, n *Node) map[*Node]bool {
|
632
|
692
|
seen := map[*Node]bool{n: true}
|
633
|
|
- queue := []*Node{n}
|
|
693
|
+ queue := Nodes{n}
|
634
|
694
|
for len(queue) > 0 {
|
635
|
695
|
n := queue[0]
|
636
|
696
|
queue = queue[1:]
|