golang.org/toolchain@v0.0.1-go1.9rc2.windows-amd64/src/cmd/vendor/github.com/google/pprof/internal/driver/driver_test.go (about) 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 driver 16 17 import ( 18 "bytes" 19 "fmt" 20 "io/ioutil" 21 "os" 22 "regexp" 23 "runtime" 24 "strconv" 25 "strings" 26 "testing" 27 "time" 28 29 "github.com/google/pprof/internal/plugin" 30 "github.com/google/pprof/internal/proftest" 31 "github.com/google/pprof/internal/symbolz" 32 "github.com/google/pprof/profile" 33 ) 34 35 func TestParse(t *testing.T) { 36 // Override weblist command to collect output in buffer 37 pprofCommands["weblist"].postProcess = nil 38 39 // Our mockObjTool.Open will always return success, causing 40 // driver.locateBinaries to "find" the binaries below in a non-existant 41 // directory. As a workaround, point the search path to the fake 42 // directory containing out fake binaries. 43 savePath := os.Getenv("PPROF_BINARY_PATH") 44 os.Setenv("PPROF_BINARY_PATH", "/path/to") 45 defer os.Setenv("PPROF_BINARY_PATH", savePath) 46 47 testcase := []struct { 48 flags, source string 49 }{ 50 {"text,functions,flat", "cpu"}, 51 {"tree,addresses,flat,nodecount=4", "cpusmall"}, 52 {"text,functions,flat", "unknown"}, 53 {"text,alloc_objects,flat", "heap_alloc"}, 54 {"text,files,flat", "heap"}, 55 {"text,inuse_objects,flat", "heap"}, 56 {"text,lines,cum,hide=line[X3]0", "cpu"}, 57 {"text,lines,cum,show=[12]00", "cpu"}, 58 {"topproto,lines,cum,hide=mangled[X3]0", "cpu"}, 59 {"tree,lines,cum,focus=[24]00", "heap"}, 60 {"tree,relative_percentages,cum,focus=[24]00", "heap"}, 61 {"callgrind", "cpu"}, 62 {"callgrind", "heap"}, 63 {"dot,functions,flat", "cpu"}, 64 {"dot,lines,flat,focus=[12]00", "heap"}, 65 {"dot,addresses,flat,ignore=[X3]002,focus=[X1]000", "contention"}, 66 {"dot,files,cum", "contention"}, 67 {"comments", "cpu"}, 68 {"comments", "heap"}, 69 {"tags", "cpu"}, 70 {"tags,tagignore=tag[13],tagfocus=key[12]", "cpu"}, 71 {"tags", "heap"}, 72 {"tags,unit=bytes", "heap"}, 73 {"traces", "cpu"}, 74 {"dot,alloc_space,flat,focus=[234]00", "heap_alloc"}, 75 {"dot,alloc_space,flat,hide=line.*1?23?", "heap_alloc"}, 76 {"dot,inuse_space,flat,tagfocus=1mb:2gb", "heap"}, 77 {"dot,inuse_space,flat,tagfocus=30kb:,tagignore=1mb:2mb", "heap"}, 78 {"disasm=line[13],addresses,flat", "cpu"}, 79 {"peek=line.*01", "cpu"}, 80 {"weblist=line[13],addresses,flat", "cpu"}, 81 } 82 83 baseVars := pprofVariables 84 defer func() { pprofVariables = baseVars }() 85 for _, tc := range testcase { 86 // Reset the pprof variables before processing 87 pprofVariables = baseVars.makeCopy() 88 89 f := baseFlags() 90 f.args = []string{tc.source} 91 92 flags := strings.Split(tc.flags, ",") 93 94 // Skip the output format in the first flag, to output to a proto 95 addFlags(&f, flags[1:]) 96 97 // Encode profile into a protobuf and decode it again. 98 protoTempFile, err := ioutil.TempFile("", "profile_proto") 99 if err != nil { 100 t.Errorf("cannot create tempfile: %v", err) 101 } 102 defer protoTempFile.Close() 103 f.strings["output"] = protoTempFile.Name() 104 105 if flags[0] == "topproto" { 106 f.bools["proto"] = false 107 f.bools["topproto"] = true 108 } 109 110 // First pprof invocation to save the profile into a profile.proto. 111 o1 := setDefaults(nil) 112 o1.Flagset = f 113 o1.Fetch = testFetcher{} 114 o1.Sym = testSymbolizer{} 115 if err := PProf(o1); err != nil { 116 t.Errorf("%s %q: %v", tc.source, tc.flags, err) 117 continue 118 } 119 // Reset the pprof variables after the proto invocation 120 pprofVariables = baseVars.makeCopy() 121 122 // Read the profile from the encoded protobuf 123 outputTempFile, err := ioutil.TempFile("", "profile_output") 124 if err != nil { 125 t.Errorf("cannot create tempfile: %v", err) 126 } 127 defer outputTempFile.Close() 128 f.strings["output"] = outputTempFile.Name() 129 f.args = []string{protoTempFile.Name()} 130 131 var solution string 132 // Apply the flags for the second pprof run, and identify name of 133 // the file containing expected results 134 if flags[0] == "topproto" { 135 solution = solutionFilename(tc.source, &f) 136 delete(f.bools, "topproto") 137 f.bools["text"] = true 138 } else { 139 delete(f.bools, "proto") 140 addFlags(&f, flags[:1]) 141 solution = solutionFilename(tc.source, &f) 142 } 143 144 // Second pprof invocation to read the profile from profile.proto 145 // and generate a report. 146 o2 := setDefaults(nil) 147 o2.Flagset = f 148 o2.Sym = testSymbolizeDemangler{} 149 o2.Obj = new(mockObjTool) 150 151 if err := PProf(o2); err != nil { 152 t.Errorf("%s: %v", tc.source, err) 153 } 154 b, err := ioutil.ReadFile(outputTempFile.Name()) 155 if err != nil { 156 t.Errorf("Failed to read profile %s: %v", outputTempFile.Name(), err) 157 } 158 159 // Read data file with expected solution 160 solution = "testdata/" + solution 161 sbuf, err := ioutil.ReadFile(solution) 162 if err != nil { 163 t.Errorf("reading solution file %s: %v", solution, err) 164 continue 165 } 166 if runtime.GOOS == "windows" { 167 sbuf = bytes.Replace(sbuf, []byte("testdata/"), []byte("testdata\\"), -1) 168 sbuf = bytes.Replace(sbuf, []byte("/path/to/"), []byte("\\path\\to\\"), -1) 169 } 170 171 if flags[0] == "svg" { 172 b = removeScripts(b) 173 sbuf = removeScripts(sbuf) 174 } 175 176 if string(b) != string(sbuf) { 177 t.Errorf("diff %s %s", solution, tc.source) 178 d, err := proftest.Diff(sbuf, b) 179 if err != nil { 180 t.Fatalf("diff %s %v", solution, err) 181 } 182 t.Errorf("%s\n%s\n", solution, d) 183 } 184 } 185 } 186 187 // removeScripts removes <script > .. </script> pairs from its input 188 func removeScripts(in []byte) []byte { 189 beginMarker := []byte("<script") 190 endMarker := []byte("</script>") 191 192 if begin := bytes.Index(in, beginMarker); begin > 0 { 193 if end := bytes.Index(in[begin:], endMarker); end > 0 { 194 in = append(in[:begin], removeScripts(in[begin+end+len(endMarker):])...) 195 } 196 } 197 return in 198 } 199 200 // addFlags parses flag descriptions and adds them to the testFlags 201 func addFlags(f *testFlags, flags []string) { 202 for _, flag := range flags { 203 fields := strings.SplitN(flag, "=", 2) 204 switch len(fields) { 205 case 1: 206 f.bools[fields[0]] = true 207 case 2: 208 if i, err := strconv.Atoi(fields[1]); err == nil { 209 f.ints[fields[0]] = i 210 } else { 211 f.strings[fields[0]] = fields[1] 212 } 213 } 214 } 215 } 216 217 // solutionFilename returns the name of the solution file for the test 218 func solutionFilename(source string, f *testFlags) string { 219 name := []string{"pprof", strings.TrimPrefix(source, "http://host:8000/")} 220 name = addString(name, f, []string{"flat", "cum"}) 221 name = addString(name, f, []string{"functions", "files", "lines", "addresses"}) 222 name = addString(name, f, []string{"inuse_space", "inuse_objects", "alloc_space", "alloc_objects"}) 223 name = addString(name, f, []string{"relative_percentages"}) 224 name = addString(name, f, []string{"seconds"}) 225 name = addString(name, f, []string{"text", "tree", "callgrind", "dot", "svg", "tags", "dot", "traces", "disasm", "peek", "weblist", "topproto", "comments"}) 226 if f.strings["focus"] != "" || f.strings["tagfocus"] != "" { 227 name = append(name, "focus") 228 } 229 if f.strings["ignore"] != "" || f.strings["tagignore"] != "" { 230 name = append(name, "ignore") 231 } 232 name = addString(name, f, []string{"hide", "show"}) 233 if f.strings["unit"] != "minimum" { 234 name = addString(name, f, []string{"unit"}) 235 } 236 return strings.Join(name, ".") 237 } 238 239 func addString(name []string, f *testFlags, components []string) []string { 240 for _, c := range components { 241 if f.bools[c] || f.strings[c] != "" || f.ints[c] != 0 { 242 return append(name, c) 243 } 244 } 245 return name 246 } 247 248 // testFlags implements the plugin.FlagSet interface. 249 type testFlags struct { 250 bools map[string]bool 251 ints map[string]int 252 floats map[string]float64 253 strings map[string]string 254 args []string 255 } 256 257 func (testFlags) ExtraUsage() string { return "" } 258 259 func (f testFlags) Bool(s string, d bool, c string) *bool { 260 if b, ok := f.bools[s]; ok { 261 return &b 262 } 263 return &d 264 } 265 266 func (f testFlags) Int(s string, d int, c string) *int { 267 if i, ok := f.ints[s]; ok { 268 return &i 269 } 270 return &d 271 } 272 273 func (f testFlags) Float64(s string, d float64, c string) *float64 { 274 if g, ok := f.floats[s]; ok { 275 return &g 276 } 277 return &d 278 } 279 280 func (f testFlags) String(s, d, c string) *string { 281 if t, ok := f.strings[s]; ok { 282 return &t 283 } 284 return &d 285 } 286 287 func (f testFlags) BoolVar(p *bool, s string, d bool, c string) { 288 if b, ok := f.bools[s]; ok { 289 *p = b 290 } else { 291 *p = d 292 } 293 } 294 295 func (f testFlags) IntVar(p *int, s string, d int, c string) { 296 if i, ok := f.ints[s]; ok { 297 *p = i 298 } else { 299 *p = d 300 } 301 } 302 303 func (f testFlags) Float64Var(p *float64, s string, d float64, c string) { 304 if g, ok := f.floats[s]; ok { 305 *p = g 306 } else { 307 *p = d 308 } 309 } 310 311 func (f testFlags) StringVar(p *string, s, d, c string) { 312 if t, ok := f.strings[s]; ok { 313 *p = t 314 } else { 315 *p = d 316 } 317 } 318 319 func (f testFlags) StringList(s, d, c string) *[]*string { 320 return &[]*string{} 321 } 322 323 func (f testFlags) Parse(func()) []string { 324 return f.args 325 } 326 327 func baseFlags() testFlags { 328 return testFlags{ 329 bools: map[string]bool{ 330 "proto": true, 331 "trim": true, 332 "compact_labels": true, 333 }, 334 ints: map[string]int{ 335 "nodecount": 20, 336 }, 337 floats: map[string]float64{ 338 "nodefraction": 0.05, 339 "edgefraction": 0.01, 340 "divide_by": 1.0, 341 }, 342 strings: map[string]string{ 343 "unit": "minimum", 344 }, 345 } 346 } 347 348 type testProfile struct { 349 } 350 351 const testStart = 0x1000 352 const testOffset = 0x5000 353 354 type testFetcher struct{} 355 356 func (testFetcher) Fetch(s string, d, t time.Duration) (*profile.Profile, string, error) { 357 var p *profile.Profile 358 s = strings.TrimPrefix(s, "http://host:8000/") 359 switch s { 360 case "cpu", "unknown": 361 p = cpuProfile() 362 case "cpusmall": 363 p = cpuProfileSmall() 364 case "heap": 365 p = heapProfile() 366 case "heap_alloc": 367 p = heapProfile() 368 p.SampleType = []*profile.ValueType{ 369 {Type: "alloc_objects", Unit: "count"}, 370 {Type: "alloc_space", Unit: "bytes"}, 371 } 372 case "contention": 373 p = contentionProfile() 374 case "symbolz": 375 p = symzProfile() 376 case "http://host2/symbolz": 377 p = symzProfile() 378 p.Mapping[0].Start += testOffset 379 p.Mapping[0].Limit += testOffset 380 for i := range p.Location { 381 p.Location[i].Address += testOffset 382 } 383 default: 384 return nil, "", fmt.Errorf("unexpected source: %s", s) 385 } 386 return p, s, nil 387 } 388 389 type testSymbolizer struct{} 390 391 func (testSymbolizer) Symbolize(_ string, _ plugin.MappingSources, _ *profile.Profile) error { 392 return nil 393 } 394 395 type testSymbolizeDemangler struct{} 396 397 func (testSymbolizeDemangler) Symbolize(_ string, _ plugin.MappingSources, p *profile.Profile) error { 398 for _, fn := range p.Function { 399 if fn.Name == "" || fn.SystemName == fn.Name { 400 fn.Name = fakeDemangler(fn.SystemName) 401 } 402 } 403 return nil 404 } 405 406 func testFetchSymbols(source, post string) ([]byte, error) { 407 var buf bytes.Buffer 408 409 if source == "http://host2/symbolz" { 410 for _, address := range strings.Split(post, "+") { 411 a, _ := strconv.ParseInt(address, 0, 64) 412 fmt.Fprintf(&buf, "%v\t", address) 413 if a-testStart < testOffset { 414 fmt.Fprintf(&buf, "wrong_source_%v_", address) 415 continue 416 } 417 fmt.Fprintf(&buf, "%#x\n", a-testStart-testOffset) 418 } 419 return buf.Bytes(), nil 420 } 421 for _, address := range strings.Split(post, "+") { 422 a, _ := strconv.ParseInt(address, 0, 64) 423 fmt.Fprintf(&buf, "%v\t", address) 424 if a-testStart > testOffset { 425 fmt.Fprintf(&buf, "wrong_source_%v_", address) 426 continue 427 } 428 fmt.Fprintf(&buf, "%#x\n", a-testStart) 429 } 430 return buf.Bytes(), nil 431 } 432 433 type testSymbolzSymbolizer struct{} 434 435 func (testSymbolzSymbolizer) Symbolize(variables string, sources plugin.MappingSources, p *profile.Profile) error { 436 return symbolz.Symbolize(sources, testFetchSymbols, p, nil) 437 } 438 439 func fakeDemangler(name string) string { 440 switch name { 441 case "mangled1000": 442 return "line1000" 443 case "mangled2000": 444 return "line2000" 445 case "mangled2001": 446 return "line2001" 447 case "mangled3000": 448 return "line3000" 449 case "mangled3001": 450 return "line3001" 451 case "mangled3002": 452 return "line3002" 453 case "mangledNEW": 454 return "operator new" 455 case "mangledMALLOC": 456 return "malloc" 457 default: 458 return name 459 } 460 } 461 462 func cpuProfile() *profile.Profile { 463 var cpuM = []*profile.Mapping{ 464 { 465 ID: 1, 466 Start: 0x1000, 467 Limit: 0x4000, 468 File: "/path/to/testbinary", 469 HasFunctions: true, 470 HasFilenames: true, 471 HasLineNumbers: true, 472 HasInlineFrames: true, 473 }, 474 } 475 476 var cpuF = []*profile.Function{ 477 {ID: 1, Name: "mangled1000", SystemName: "mangled1000", Filename: "testdata/file1000.src"}, 478 {ID: 2, Name: "mangled2000", SystemName: "mangled2000", Filename: "testdata/file2000.src"}, 479 {ID: 3, Name: "mangled2001", SystemName: "mangled2001", Filename: "testdata/file2000.src"}, 480 {ID: 4, Name: "mangled3000", SystemName: "mangled3000", Filename: "testdata/file3000.src"}, 481 {ID: 5, Name: "mangled3001", SystemName: "mangled3001", Filename: "testdata/file3000.src"}, 482 {ID: 6, Name: "mangled3002", SystemName: "mangled3002", Filename: "testdata/file3000.src"}, 483 } 484 485 var cpuL = []*profile.Location{ 486 { 487 ID: 1000, 488 Mapping: cpuM[0], 489 Address: 0x1000, 490 Line: []profile.Line{ 491 {Function: cpuF[0], Line: 1}, 492 }, 493 }, 494 { 495 ID: 2000, 496 Mapping: cpuM[0], 497 Address: 0x2000, 498 Line: []profile.Line{ 499 {Function: cpuF[2], Line: 9}, 500 {Function: cpuF[1], Line: 4}, 501 }, 502 }, 503 { 504 ID: 3000, 505 Mapping: cpuM[0], 506 Address: 0x3000, 507 Line: []profile.Line{ 508 {Function: cpuF[5], Line: 2}, 509 {Function: cpuF[4], Line: 5}, 510 {Function: cpuF[3], Line: 6}, 511 }, 512 }, 513 { 514 ID: 3001, 515 Mapping: cpuM[0], 516 Address: 0x3001, 517 Line: []profile.Line{ 518 {Function: cpuF[4], Line: 8}, 519 {Function: cpuF[3], Line: 9}, 520 }, 521 }, 522 { 523 ID: 3002, 524 Mapping: cpuM[0], 525 Address: 0x3002, 526 Line: []profile.Line{ 527 {Function: cpuF[5], Line: 5}, 528 {Function: cpuF[3], Line: 9}, 529 }, 530 }, 531 } 532 533 return &profile.Profile{ 534 PeriodType: &profile.ValueType{Type: "cpu", Unit: "milliseconds"}, 535 Period: 1, 536 DurationNanos: 10e9, 537 SampleType: []*profile.ValueType{ 538 {Type: "samples", Unit: "count"}, 539 {Type: "cpu", Unit: "milliseconds"}, 540 }, 541 Sample: []*profile.Sample{ 542 { 543 Location: []*profile.Location{cpuL[0], cpuL[1], cpuL[2]}, 544 Value: []int64{1000, 1000}, 545 Label: map[string][]string{ 546 "key1": []string{"tag1"}, 547 "key2": []string{"tag1"}, 548 }, 549 }, 550 { 551 Location: []*profile.Location{cpuL[0], cpuL[3]}, 552 Value: []int64{100, 100}, 553 Label: map[string][]string{ 554 "key1": []string{"tag2"}, 555 "key3": []string{"tag2"}, 556 }, 557 }, 558 { 559 Location: []*profile.Location{cpuL[1], cpuL[4]}, 560 Value: []int64{10, 10}, 561 Label: map[string][]string{ 562 "key1": []string{"tag3"}, 563 "key2": []string{"tag2"}, 564 }, 565 }, 566 { 567 Location: []*profile.Location{cpuL[2]}, 568 Value: []int64{10, 10}, 569 Label: map[string][]string{ 570 "key1": []string{"tag4"}, 571 "key2": []string{"tag1"}, 572 }, 573 }, 574 }, 575 Location: cpuL, 576 Function: cpuF, 577 Mapping: cpuM, 578 } 579 } 580 581 func cpuProfileSmall() *profile.Profile { 582 var cpuM = []*profile.Mapping{ 583 { 584 ID: 1, 585 Start: 0x1000, 586 Limit: 0x4000, 587 File: "/path/to/testbinary", 588 HasFunctions: true, 589 HasFilenames: true, 590 HasLineNumbers: true, 591 HasInlineFrames: true, 592 }, 593 } 594 595 var cpuL = []*profile.Location{ 596 { 597 ID: 1000, 598 Mapping: cpuM[0], 599 Address: 0x1000, 600 }, 601 { 602 ID: 2000, 603 Mapping: cpuM[0], 604 Address: 0x2000, 605 }, 606 { 607 ID: 3000, 608 Mapping: cpuM[0], 609 Address: 0x3000, 610 }, 611 { 612 ID: 4000, 613 Mapping: cpuM[0], 614 Address: 0x4000, 615 }, 616 { 617 ID: 5000, 618 Mapping: cpuM[0], 619 Address: 0x5000, 620 }, 621 } 622 623 return &profile.Profile{ 624 PeriodType: &profile.ValueType{Type: "cpu", Unit: "milliseconds"}, 625 Period: 1, 626 DurationNanos: 10e9, 627 SampleType: []*profile.ValueType{ 628 {Type: "samples", Unit: "count"}, 629 {Type: "cpu", Unit: "milliseconds"}, 630 }, 631 Sample: []*profile.Sample{ 632 { 633 Location: []*profile.Location{cpuL[0], cpuL[1], cpuL[2]}, 634 Value: []int64{1000, 1000}, 635 }, 636 { 637 Location: []*profile.Location{cpuL[3], cpuL[1], cpuL[4]}, 638 Value: []int64{1000, 1000}, 639 }, 640 { 641 Location: []*profile.Location{cpuL[2]}, 642 Value: []int64{1000, 1000}, 643 }, 644 { 645 Location: []*profile.Location{cpuL[4]}, 646 Value: []int64{1000, 1000}, 647 }, 648 }, 649 Location: cpuL, 650 Function: nil, 651 Mapping: cpuM, 652 } 653 } 654 655 func heapProfile() *profile.Profile { 656 var heapM = []*profile.Mapping{ 657 { 658 ID: 1, 659 BuildID: "buildid", 660 Start: 0x1000, 661 Limit: 0x4000, 662 HasFunctions: true, 663 HasFilenames: true, 664 HasLineNumbers: true, 665 HasInlineFrames: true, 666 }, 667 } 668 669 var heapF = []*profile.Function{ 670 {ID: 1, Name: "pruneme", SystemName: "pruneme", Filename: "prune.h"}, 671 {ID: 2, Name: "mangled1000", SystemName: "mangled1000", Filename: "testdata/file1000.src"}, 672 {ID: 3, Name: "mangled2000", SystemName: "mangled2000", Filename: "testdata/file2000.src"}, 673 {ID: 4, Name: "mangled2001", SystemName: "mangled2001", Filename: "testdata/file2000.src"}, 674 {ID: 5, Name: "mangled3000", SystemName: "mangled3000", Filename: "testdata/file3000.src"}, 675 {ID: 6, Name: "mangled3001", SystemName: "mangled3001", Filename: "testdata/file3000.src"}, 676 {ID: 7, Name: "mangled3002", SystemName: "mangled3002", Filename: "testdata/file3000.src"}, 677 {ID: 8, Name: "mangledMALLOC", SystemName: "mangledMALLOC", Filename: "malloc.h"}, 678 {ID: 9, Name: "mangledNEW", SystemName: "mangledNEW", Filename: "new.h"}, 679 } 680 681 var heapL = []*profile.Location{ 682 { 683 ID: 1000, 684 Mapping: heapM[0], 685 Address: 0x1000, 686 Line: []profile.Line{ 687 {Function: heapF[0], Line: 100}, 688 {Function: heapF[7], Line: 100}, 689 {Function: heapF[1], Line: 1}, 690 }, 691 }, 692 { 693 ID: 2000, 694 Mapping: heapM[0], 695 Address: 0x2000, 696 Line: []profile.Line{ 697 {Function: heapF[8], Line: 100}, 698 {Function: heapF[3], Line: 2}, 699 {Function: heapF[2], Line: 3}, 700 }, 701 }, 702 { 703 ID: 3000, 704 Mapping: heapM[0], 705 Address: 0x3000, 706 Line: []profile.Line{ 707 {Function: heapF[8], Line: 100}, 708 {Function: heapF[6], Line: 3}, 709 {Function: heapF[5], Line: 2}, 710 {Function: heapF[4], Line: 4}, 711 }, 712 }, 713 { 714 ID: 3001, 715 Mapping: heapM[0], 716 Address: 0x3001, 717 Line: []profile.Line{ 718 {Function: heapF[0], Line: 100}, 719 {Function: heapF[8], Line: 100}, 720 {Function: heapF[5], Line: 2}, 721 {Function: heapF[4], Line: 4}, 722 }, 723 }, 724 { 725 ID: 3002, 726 Mapping: heapM[0], 727 Address: 0x3002, 728 Line: []profile.Line{ 729 {Function: heapF[6], Line: 3}, 730 {Function: heapF[4], Line: 4}, 731 }, 732 }, 733 } 734 735 return &profile.Profile{ 736 Comments: []string{"comment", "#hidden comment"}, 737 PeriodType: &profile.ValueType{Type: "allocations", Unit: "bytes"}, 738 Period: 524288, 739 SampleType: []*profile.ValueType{ 740 {Type: "inuse_objects", Unit: "count"}, 741 {Type: "inuse_space", Unit: "bytes"}, 742 }, 743 Sample: []*profile.Sample{ 744 { 745 Location: []*profile.Location{heapL[0], heapL[1], heapL[2]}, 746 Value: []int64{10, 1024000}, 747 NumLabel: map[string][]int64{ 748 "bytes": []int64{102400}, 749 }, 750 }, 751 { 752 Location: []*profile.Location{heapL[0], heapL[3]}, 753 Value: []int64{20, 4096000}, 754 NumLabel: map[string][]int64{ 755 "bytes": []int64{204800}, 756 }, 757 }, 758 { 759 Location: []*profile.Location{heapL[1], heapL[4]}, 760 Value: []int64{40, 65536000}, 761 NumLabel: map[string][]int64{ 762 "bytes": []int64{1638400}, 763 }, 764 }, 765 { 766 Location: []*profile.Location{heapL[2]}, 767 Value: []int64{80, 32768000}, 768 NumLabel: map[string][]int64{ 769 "bytes": []int64{409600}, 770 }, 771 }, 772 }, 773 DropFrames: ".*operator new.*|malloc", 774 Location: heapL, 775 Function: heapF, 776 Mapping: heapM, 777 } 778 } 779 780 func contentionProfile() *profile.Profile { 781 var contentionM = []*profile.Mapping{ 782 { 783 ID: 1, 784 BuildID: "buildid-contention", 785 Start: 0x1000, 786 Limit: 0x4000, 787 HasFunctions: true, 788 HasFilenames: true, 789 HasLineNumbers: true, 790 HasInlineFrames: true, 791 }, 792 } 793 794 var contentionF = []*profile.Function{ 795 {ID: 1, Name: "mangled1000", SystemName: "mangled1000", Filename: "testdata/file1000.src"}, 796 {ID: 2, Name: "mangled2000", SystemName: "mangled2000", Filename: "testdata/file2000.src"}, 797 {ID: 3, Name: "mangled2001", SystemName: "mangled2001", Filename: "testdata/file2000.src"}, 798 {ID: 4, Name: "mangled3000", SystemName: "mangled3000", Filename: "testdata/file3000.src"}, 799 {ID: 5, Name: "mangled3001", SystemName: "mangled3001", Filename: "testdata/file3000.src"}, 800 {ID: 6, Name: "mangled3002", SystemName: "mangled3002", Filename: "testdata/file3000.src"}, 801 } 802 803 var contentionL = []*profile.Location{ 804 { 805 ID: 1000, 806 Mapping: contentionM[0], 807 Address: 0x1000, 808 Line: []profile.Line{ 809 {Function: contentionF[0], Line: 1}, 810 }, 811 }, 812 { 813 ID: 2000, 814 Mapping: contentionM[0], 815 Address: 0x2000, 816 Line: []profile.Line{ 817 {Function: contentionF[2], Line: 2}, 818 {Function: contentionF[1], Line: 3}, 819 }, 820 }, 821 { 822 ID: 3000, 823 Mapping: contentionM[0], 824 Address: 0x3000, 825 Line: []profile.Line{ 826 {Function: contentionF[5], Line: 2}, 827 {Function: contentionF[4], Line: 3}, 828 {Function: contentionF[3], Line: 5}, 829 }, 830 }, 831 { 832 ID: 3001, 833 Mapping: contentionM[0], 834 Address: 0x3001, 835 Line: []profile.Line{ 836 {Function: contentionF[4], Line: 3}, 837 {Function: contentionF[3], Line: 5}, 838 }, 839 }, 840 { 841 ID: 3002, 842 Mapping: contentionM[0], 843 Address: 0x3002, 844 Line: []profile.Line{ 845 {Function: contentionF[5], Line: 4}, 846 {Function: contentionF[3], Line: 3}, 847 }, 848 }, 849 } 850 851 return &profile.Profile{ 852 PeriodType: &profile.ValueType{Type: "contentions", Unit: "count"}, 853 Period: 524288, 854 SampleType: []*profile.ValueType{ 855 {Type: "contentions", Unit: "count"}, 856 {Type: "delay", Unit: "nanoseconds"}, 857 }, 858 Sample: []*profile.Sample{ 859 { 860 Location: []*profile.Location{contentionL[0], contentionL[1], contentionL[2]}, 861 Value: []int64{10, 10240000}, 862 }, 863 { 864 Location: []*profile.Location{contentionL[0], contentionL[3]}, 865 Value: []int64{20, 40960000}, 866 }, 867 { 868 Location: []*profile.Location{contentionL[1], contentionL[4]}, 869 Value: []int64{40, 65536000}, 870 }, 871 { 872 Location: []*profile.Location{contentionL[2]}, 873 Value: []int64{80, 32768000}, 874 }, 875 }, 876 Location: contentionL, 877 Function: contentionF, 878 Mapping: contentionM, 879 Comments: []string{"Comment #1", "Comment #2"}, 880 } 881 } 882 883 func symzProfile() *profile.Profile { 884 var symzM = []*profile.Mapping{ 885 { 886 ID: 1, 887 Start: testStart, 888 Limit: 0x4000, 889 File: "/path/to/testbinary", 890 }, 891 } 892 893 var symzL = []*profile.Location{ 894 {ID: 1, Mapping: symzM[0], Address: testStart}, 895 {ID: 2, Mapping: symzM[0], Address: testStart + 0x1000}, 896 {ID: 3, Mapping: symzM[0], Address: testStart + 0x2000}, 897 } 898 899 return &profile.Profile{ 900 PeriodType: &profile.ValueType{Type: "cpu", Unit: "milliseconds"}, 901 Period: 1, 902 DurationNanos: 10e9, 903 SampleType: []*profile.ValueType{ 904 {Type: "samples", Unit: "count"}, 905 {Type: "cpu", Unit: "milliseconds"}, 906 }, 907 Sample: []*profile.Sample{ 908 { 909 Location: []*profile.Location{symzL[0], symzL[1], symzL[2]}, 910 Value: []int64{1, 1}, 911 }, 912 }, 913 Location: symzL, 914 Mapping: symzM, 915 } 916 } 917 918 var autoCompleteTests = []struct { 919 in string 920 out string 921 }{ 922 {"", ""}, 923 {"xyz", "xyz"}, // no match 924 {"dis", "disasm"}, // single match 925 {"t", "t"}, // many matches 926 {"top abc", "top abc"}, // no function name match 927 {"top mangledM", "top mangledMALLOC"}, // single function name match 928 {"top cmd cmd mangledM", "top cmd cmd mangledMALLOC"}, 929 {"top mangled", "top mangled"}, // many function name matches 930 {"cmd mangledM", "cmd mangledM"}, // invalid command 931 {"top mangledM cmd", "top mangledM cmd"}, // cursor misplaced 932 {"top edMA", "top mangledMALLOC"}, // single infix function name match 933 {"top -mangledM", "top -mangledMALLOC"}, // ignore sign handled 934 {"lin", "lines"}, // single variable match 935 {"EdGeF", "edgefraction"}, // single capitalized match 936 {"help dis", "help disasm"}, // help command match 937 {"help relative_perc", "help relative_percentages"}, // help variable match 938 {"help coMpa", "help compact_labels"}, // help variable capitalized match 939 } 940 941 func TestAutoComplete(t *testing.T) { 942 complete := newCompleter(functionNames(heapProfile())) 943 944 for _, test := range autoCompleteTests { 945 if out := complete(test.in); out != test.out { 946 t.Errorf("autoComplete(%s) = %s; want %s", test.in, out, test.out) 947 } 948 } 949 } 950 951 func TestTagFilter(t *testing.T) { 952 var tagFilterTests = []struct { 953 name, value string 954 tags map[string][]string 955 want bool 956 }{ 957 {"test1", "tag2", map[string][]string{"value1": {"tag1", "tag2"}}, true}, 958 {"test2", "tag3", map[string][]string{"value1": {"tag1", "tag2"}}, false}, 959 {"test3", "tag1,tag3", map[string][]string{"value1": {"tag1", "tag2"}, "value2": {"tag3"}}, true}, 960 {"test4", "t..[12],t..3", map[string][]string{"value1": {"tag1", "tag2"}, "value2": {"tag3"}}, true}, 961 {"test5", "tag2,tag3", map[string][]string{"value1": {"tag1", "tag2"}}, false}, 962 } 963 964 for _, test := range tagFilterTests { 965 filter, err := compileTagFilter(test.name, test.value, &proftest.TestUI{T: t}, nil) 966 if err != nil { 967 t.Errorf("tagFilter %s:%v", test.name, err) 968 continue 969 } 970 s := profile.Sample{ 971 Label: test.tags, 972 } 973 974 if got := filter(&s); got != test.want { 975 t.Errorf("tagFilter %s: got %v, want %v", test.name, got, test.want) 976 } 977 } 978 } 979 980 func TestSymbolzAfterMerge(t *testing.T) { 981 baseVars := pprofVariables 982 pprofVariables = baseVars.makeCopy() 983 defer func() { pprofVariables = baseVars }() 984 985 f := baseFlags() 986 f.args = []string{"symbolz", "http://host2/symbolz"} 987 988 o := setDefaults(nil) 989 o.Flagset = f 990 o.Obj = new(mockObjTool) 991 src, cmd, err := parseFlags(o) 992 if err != nil { 993 t.Fatalf("parseFlags: %v", err) 994 } 995 996 if len(cmd) != 1 || cmd[0] != "proto" { 997 t.Fatalf("parseFlags returned command %v, want [proto]", cmd) 998 } 999 1000 o.Fetch = testFetcher{} 1001 o.Sym = testSymbolzSymbolizer{} 1002 p, err := fetchProfiles(src, o) 1003 if err != nil { 1004 t.Fatalf("fetchProfiles: %v", err) 1005 } 1006 if len(p.Location) != 3 { 1007 t.Errorf("Got %d locations after merge, want %d", len(p.Location), 3) 1008 } 1009 for i, l := range p.Location { 1010 if len(l.Line) != 1 { 1011 t.Errorf("Number of lines for symbolz %#x in iteration %d, got %d, want %d", l.Address, i, len(l.Line), 1) 1012 continue 1013 } 1014 address := l.Address - l.Mapping.Start 1015 if got, want := l.Line[0].Function.Name, fmt.Sprintf("%#x", address); got != want { 1016 t.Errorf("symbolz %#x, got %s, want %s", address, got, want) 1017 } 1018 } 1019 } 1020 1021 type mockObjTool struct{} 1022 1023 func (*mockObjTool) Open(file string, start, limit, offset uint64) (plugin.ObjFile, error) { 1024 return &mockFile{file, "abcdef", 0}, nil 1025 } 1026 1027 func (m *mockObjTool) Disasm(file string, start, end uint64) ([]plugin.Inst, error) { 1028 switch start { 1029 case 0x1000: 1030 return []plugin.Inst{ 1031 {Addr: 0x1000, Text: "instruction one"}, 1032 {Addr: 0x1001, Text: "instruction two"}, 1033 {Addr: 0x1002, Text: "instruction three"}, 1034 {Addr: 0x1003, Text: "instruction four"}, 1035 }, nil 1036 case 0x3000: 1037 return []plugin.Inst{ 1038 {Addr: 0x3000, Text: "instruction one"}, 1039 {Addr: 0x3001, Text: "instruction two"}, 1040 {Addr: 0x3002, Text: "instruction three"}, 1041 {Addr: 0x3003, Text: "instruction four"}, 1042 {Addr: 0x3004, Text: "instruction five"}, 1043 }, nil 1044 } 1045 return nil, fmt.Errorf("unimplemented") 1046 } 1047 1048 type mockFile struct { 1049 name, buildId string 1050 base uint64 1051 } 1052 1053 // Name returns the underlyinf file name, if available 1054 func (m *mockFile) Name() string { 1055 return m.name 1056 } 1057 1058 // Base returns the base address to use when looking up symbols in the file. 1059 func (m *mockFile) Base() uint64 { 1060 return m.base 1061 } 1062 1063 // BuildID returns the GNU build ID of the file, or an empty string. 1064 func (m *mockFile) BuildID() string { 1065 return m.buildId 1066 } 1067 1068 // SourceLine reports the source line information for a given 1069 // address in the file. Due to inlining, the source line information 1070 // is in general a list of positions representing a call stack, 1071 // with the leaf function first. 1072 func (*mockFile) SourceLine(addr uint64) ([]plugin.Frame, error) { 1073 return nil, fmt.Errorf("unimplemented") 1074 } 1075 1076 // Symbols returns a list of symbols in the object file. 1077 // If r is not nil, Symbols restricts the list to symbols 1078 // with names matching the regular expression. 1079 // If addr is not zero, Symbols restricts the list to symbols 1080 // containing that address. 1081 func (m *mockFile) Symbols(r *regexp.Regexp, addr uint64) ([]*plugin.Sym, error) { 1082 switch r.String() { 1083 case "line[13]": 1084 return []*plugin.Sym{ 1085 {[]string{"line1000"}, m.name, 0x1000, 0x1003}, 1086 {[]string{"line3000"}, m.name, 0x3000, 0x3004}, 1087 }, nil 1088 } 1089 return nil, fmt.Errorf("unimplemented") 1090 } 1091 1092 // Close closes the file, releasing associated resources. 1093 func (*mockFile) Close() error { 1094 return nil 1095 }