Browse Source

Fix unstable nodefraction handling. (#131)

There is an issue where decreasing nodefraction (which should produce
more nodes) ended up reducing the number of displayed nodes.  This is
because we have an upper limit on number of nodes to display and the
number of node-lets counts against that limit.  This can cause a problem
if a node with large number of node-lets shows up near the front of
the list of nodes (e.g., std::allocator_traits::allocate in a heapz
profile had 102 node-lets, which caused all subsequent nodes to be
dropped).

Fixed by limiting the count of charged node-lets per node to
maxNodelets (4), which is the maximum number of node-lets we
display per node.
Alexey Alexandrov 8 years ago
parent
commit
e48932ed15
3 changed files with 62 additions and 5 deletions
  1. 4
    4
      internal/graph/dotgraph.go
  2. 53
    0
      internal/graph/dotgraph_test.go
  3. 5
    1
      internal/graph/graph.go

+ 4
- 4
internal/graph/dotgraph.go View File

@@ -50,6 +50,8 @@ type DotConfig struct {
50 50
 	Total       int64                      // The total weight of the graph, used to compute percentages
51 51
 }
52 52
 
53
+const maxNodelets = 4 // Number of nodelets for labels (both numeric and non)
54
+
53 55
 // ComposeDot creates and writes a in the DOT format to the writer, using
54 56
 // the configurations given.
55 57
 func ComposeDot(w io.Writer, g *Graph, a *DotAttributes, c *DotConfig) {
@@ -204,8 +206,6 @@ func (b *builder) addNode(node *Node, nodeID int, maxFlat float64) {
204 206
 
205 207
 // addNodelets generates the DOT boxes for the node tags if they exist.
206 208
 func (b *builder) addNodelets(node *Node, nodeID int) bool {
207
-	const maxNodelets = 4    // Number of nodelets for alphanumeric labels
208
-	const maxNumNodelets = 4 // Number of nodelets for numeric labels
209 209
 	var nodelets string
210 210
 
211 211
 	// Populate two Tag slices, one for LabelTags and one for NumericTags.
@@ -242,12 +242,12 @@ func (b *builder) addNodelets(node *Node, nodeID int) bool {
242 242
 		nodelets += fmt.Sprintf(`N%d_%d [label = "%s" fontsize=8 shape=box3d tooltip="%s"]`+"\n", nodeID, i, t.Name, weight)
243 243
 		nodelets += fmt.Sprintf(`N%d -> N%d_%d [label=" %s" weight=100 tooltip="%s" labeltooltip="%s"]`+"\n", nodeID, nodeID, i, weight, weight, weight)
244 244
 		if nts := lnts[t.Name]; nts != nil {
245
-			nodelets += b.numericNodelets(nts, maxNumNodelets, flatTags, fmt.Sprintf(`N%d_%d`, nodeID, i))
245
+			nodelets += b.numericNodelets(nts, maxNodelets, flatTags, fmt.Sprintf(`N%d_%d`, nodeID, i))
246 246
 		}
247 247
 	}
248 248
 
249 249
 	if nts := lnts[""]; nts != nil {
250
-		nodelets += b.numericNodelets(nts, maxNumNodelets, flatTags, fmt.Sprintf(`N%d`, nodeID))
250
+		nodelets += b.numericNodelets(nts, maxNodelets, flatTags, fmt.Sprintf(`N%d`, nodeID))
251 251
 	}
252 252
 
253 253
 	fmt.Fprint(b, nodelets)

+ 53
- 0
internal/graph/dotgraph_test.go View File

@@ -209,6 +209,59 @@ func compareGraphs(t *testing.T, got, want []byte) {
209 209
 	}
210 210
 }
211 211
 
212
+func TestNodeletCountCapping(t *testing.T) {
213
+	labelTags := make(TagMap)
214
+	for i := 0; i < 10; i++ {
215
+		name := fmt.Sprintf("tag-%d", i)
216
+		labelTags[name] = &Tag{
217
+			Name: name,
218
+			Flat: 10,
219
+			Cum:  10,
220
+		}
221
+	}
222
+	numTags := make(TagMap)
223
+	for i := 0; i < 10; i++ {
224
+		name := fmt.Sprintf("num-tag-%d", i)
225
+		numTags[name] = &Tag{
226
+			Name:  name,
227
+			Unit:  "mb",
228
+			Value: 16,
229
+			Flat:  10,
230
+			Cum:   10,
231
+		}
232
+	}
233
+	node1 := &Node{
234
+		Info:        NodeInfo{Name: "node1-with-tags"},
235
+		Flat:        10,
236
+		Cum:         10,
237
+		NumericTags: map[string]TagMap{"": numTags},
238
+		LabelTags:   labelTags,
239
+	}
240
+	node2 := &Node{
241
+		Info: NodeInfo{Name: "node2"},
242
+		Flat: 15,
243
+		Cum:  15,
244
+	}
245
+	node3 := &Node{
246
+		Info: NodeInfo{Name: "node3"},
247
+		Flat: 15,
248
+		Cum:  15,
249
+	}
250
+	g := &Graph{
251
+		Nodes: Nodes{
252
+			node1,
253
+			node2,
254
+			node3,
255
+		},
256
+	}
257
+	for n := 1; n <= 3; n++ {
258
+		input := maxNodelets + n
259
+		if got, want := len(g.SelectTopNodes(input, true)), n; got != want {
260
+			t.Errorf("SelectTopNodes(%d): got %d nodes, want %d", input, got, want)
261
+		}
262
+	}
263
+}
264
+
212 265
 func TestMultilinePrintableName(t *testing.T) {
213 266
 	ni := &NodeInfo{
214 267
 		Name:    "test1.test2::test3",

+ 5
- 1
internal/graph/graph.go View File

@@ -802,7 +802,11 @@ func (g *Graph) selectTopNodes(maxNodes int, visualMode bool) Nodes {
802 802
 			// If generating a visual graph, count tags as nodes. Update
803 803
 			// maxNodes to account for them.
804 804
 			for i, n := range g.Nodes {
805
-				if count += countTags(n) + 1; count >= maxNodes {
805
+				tags := countTags(n)
806
+				if tags > maxNodelets {
807
+					tags = maxNodelets
808
+				}
809
+				if count += tags + 1; count >= maxNodes {
806 810
 					maxNodes = i + 1
807 811
 					break
808 812
 				}