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