1 // Copyright 2014 Google Inc. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package graph 16 17 import ( 18 "bytes" 19 "flag" 20 "fmt" 21 "io/ioutil" 22 "path/filepath" 23 "reflect" 24 "strconv" 25 "strings" 26 "testing" 27 28 "github.com/google/pprof/internal/proftest" 29 ) 30 31 var updateFlag = flag.Bool("update", false, "Update the golden files") 32 33 func TestComposeWithStandardGraph(t *testing.T) { 34 g := baseGraph() 35 a, c := baseAttrsAndConfig() 36 37 var buf bytes.Buffer 38 ComposeDot(&buf, g, a, c) 39 40 compareGraphs(t, buf.Bytes(), "compose1.dot") 41 } 42 43 func TestComposeWithNodeAttributesAndZeroFlat(t *testing.T) { 44 g := baseGraph() 45 a, c := baseAttrsAndConfig() 46 47 // Set NodeAttributes for Node 1. 48 a.Nodes[g.Nodes[0]] = &DotNodeAttributes{ 49 Shape: "folder", 50 Bold: true, 51 Peripheries: 2, 52 URL: "www.google.com", 53 Formatter: func(ni *NodeInfo) string { 54 return strings.ToUpper(ni.Name) 55 }, 56 } 57 58 // Set Flat value to zero on Node 2. 59 g.Nodes[1].Flat = 0 60 61 var buf bytes.Buffer 62 ComposeDot(&buf, g, a, c) 63 64 compareGraphs(t, buf.Bytes(), "compose2.dot") 65 } 66 67 func TestComposeWithTagsAndResidualEdge(t *testing.T) { 68 g := baseGraph() 69 a, c := baseAttrsAndConfig() 70 71 // Add tags to Node 1. 72 g.Nodes[0].LabelTags["a"] = &Tag{ 73 Name: "tag1", 74 Cum: 10, 75 Flat: 10, 76 } 77 g.Nodes[0].NumericTags[""] = TagMap{ 78 "b": &Tag{ 79 Name: "tag2", 80 Cum: 20, 81 Flat: 20, 82 Unit: "ms", 83 }, 84 } 85 86 // Set edge to be Residual. 87 g.Nodes[0].Out[g.Nodes[1]].Residual = true 88 89 var buf bytes.Buffer 90 ComposeDot(&buf, g, a, c) 91 92 compareGraphs(t, buf.Bytes(), "compose3.dot") 93 } 94 95 func TestComposeWithNestedTags(t *testing.T) { 96 g := baseGraph() 97 a, c := baseAttrsAndConfig() 98 99 // Add tags to Node 1. 100 g.Nodes[0].LabelTags["tag1"] = &Tag{ 101 Name: "tag1", 102 Cum: 10, 103 Flat: 10, 104 } 105 g.Nodes[0].NumericTags["tag1"] = TagMap{ 106 "tag2": &Tag{ 107 Name: "tag2", 108 Cum: 20, 109 Flat: 20, 110 Unit: "ms", 111 }, 112 } 113 114 var buf bytes.Buffer 115 ComposeDot(&buf, g, a, c) 116 117 compareGraphs(t, buf.Bytes(), "compose5.dot") 118 } 119 120 func TestComposeWithEmptyGraph(t *testing.T) { 121 g := &Graph{} 122 a, c := baseAttrsAndConfig() 123 124 var buf bytes.Buffer 125 ComposeDot(&buf, g, a, c) 126 127 compareGraphs(t, buf.Bytes(), "compose4.dot") 128 } 129 130 func TestComposeWithStandardGraphAndURL(t *testing.T) { 131 g := baseGraph() 132 a, c := baseAttrsAndConfig() 133 c.LegendURL = "http://example.com" 134 135 var buf bytes.Buffer 136 ComposeDot(&buf, g, a, c) 137 138 compareGraphs(t, buf.Bytes(), "compose6.dot") 139 } 140 141 func baseGraph() *Graph { 142 src := &Node{ 143 Info: NodeInfo{Name: "src"}, 144 Flat: 10, 145 Cum: 25, 146 In: make(EdgeMap), 147 Out: make(EdgeMap), 148 LabelTags: make(TagMap), 149 NumericTags: make(map[string]TagMap), 150 } 151 dest := &Node{ 152 Info: NodeInfo{Name: "dest"}, 153 Flat: 15, 154 Cum: 25, 155 In: make(EdgeMap), 156 Out: make(EdgeMap), 157 LabelTags: make(TagMap), 158 NumericTags: make(map[string]TagMap), 159 } 160 edge := &Edge{ 161 Src: src, 162 Dest: dest, 163 Weight: 10, 164 } 165 src.Out[dest] = edge 166 src.In[src] = edge 167 return &Graph{ 168 Nodes: Nodes{ 169 src, 170 dest, 171 }, 172 } 173 } 174 175 func baseAttrsAndConfig() (*DotAttributes, *DotConfig) { 176 a := &DotAttributes{ 177 Nodes: make(map[*Node]*DotNodeAttributes), 178 } 179 c := &DotConfig{ 180 Title: "testtitle", 181 Labels: []string{"label1", "label2"}, 182 Total: 100, 183 FormatValue: func(v int64) string { 184 return strconv.FormatInt(v, 10) 185 }, 186 } 187 return a, c 188 } 189 190 func compareGraphs(t *testing.T, got []byte, wantFile string) { 191 wantFile = filepath.Join("testdata", wantFile) 192 want, err := ioutil.ReadFile(wantFile) 193 if err != nil { 194 t.Fatalf("error reading test file %s: %v", wantFile, err) 195 } 196 197 if string(got) != string(want) { 198 d, err := proftest.Diff(got, want) 199 if err != nil { 200 t.Fatalf("error finding diff: %v", err) 201 } 202 t.Errorf("Compose incorrectly wrote %s", string(d)) 203 if *updateFlag { 204 err := ioutil.WriteFile(wantFile, got, 0644) 205 if err != nil { 206 t.Errorf("failed to update the golden file %q: %v", wantFile, err) 207 } 208 } 209 } 210 } 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 265 func TestMultilinePrintableName(t *testing.T) { 266 ni := &NodeInfo{ 267 Name: "test1.test2::test3", 268 File: "src/file.cc", 269 Address: 123, 270 Lineno: 999, 271 } 272 273 want := fmt.Sprintf(`%016x\ntest1\ntest2\ntest3\nfile.cc:999\n`, 123) 274 if got := multilinePrintableName(ni); got != want { 275 t.Errorf("multilinePrintableName(%#v) == %q, want %q", ni, got, want) 276 } 277 } 278 279 func TestTagCollapse(t *testing.T) { 280 281 makeTag := func(name, unit string, value, flat, cum int64) *Tag { 282 return &Tag{name, unit, value, flat, 0, cum, 0} 283 } 284 285 tagSource := []*Tag{ 286 makeTag("12mb", "mb", 12, 100, 100), 287 makeTag("1kb", "kb", 1, 1, 1), 288 makeTag("1mb", "mb", 1, 1000, 1000), 289 makeTag("2048mb", "mb", 2048, 1000, 1000), 290 makeTag("1b", "b", 1, 100, 100), 291 makeTag("2b", "b", 2, 100, 100), 292 makeTag("7b", "b", 7, 100, 100), 293 } 294 295 tagWant := [][]*Tag{ 296 { 297 makeTag("1B..2GB", "", 0, 2401, 2401), 298 }, 299 { 300 makeTag("2GB", "", 0, 1000, 1000), 301 makeTag("1B..12MB", "", 0, 1401, 1401), 302 }, 303 { 304 makeTag("2GB", "", 0, 1000, 1000), 305 makeTag("12MB", "", 0, 100, 100), 306 makeTag("1B..1MB", "", 0, 1301, 1301), 307 }, 308 { 309 makeTag("2GB", "", 0, 1000, 1000), 310 makeTag("1MB", "", 0, 1000, 1000), 311 makeTag("2B..1kB", "", 0, 201, 201), 312 makeTag("1B", "", 0, 100, 100), 313 makeTag("12MB", "", 0, 100, 100), 314 }, 315 } 316 317 for _, tc := range tagWant { 318 var got, want []*Tag 319 b := builder{nil, &DotAttributes{}, &DotConfig{}} 320 got = b.collapsedTags(tagSource, len(tc), true) 321 want = SortTags(tc, true) 322 323 if !reflect.DeepEqual(got, want) { 324 t.Errorf("collapse to %d, got:\n%v\nwant:\n%v", len(tc), tagString(got), tagString(want)) 325 } 326 } 327 } 328 329 func tagString(t []*Tag) string { 330 var ret []string 331 for _, s := range t { 332 ret = append(ret, fmt.Sprintln(s)) 333 } 334 return strings.Join(ret, ":") 335 } 336