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 "flag" 20 "fmt" 21 "io/ioutil" 22 "net" 23 _ "net/http/pprof" 24 "os" 25 "reflect" 26 "regexp" 27 "runtime" 28 "strconv" 29 "strings" 30 "testing" 31 "time" 32 33 "github.com/google/pprof/internal/plugin" 34 "github.com/google/pprof/internal/proftest" 35 "github.com/google/pprof/internal/symbolz" 36 "github.com/google/pprof/profile" 37 ) 38 39 var updateFlag = flag.Bool("update", false, "Update the golden files") 40 41 func TestParse(t *testing.T) { 42 // Override weblist command to collect output in buffer 43 pprofCommands["weblist"].postProcess = nil 44 45 // Our mockObjTool.Open will always return success, causing 46 // driver.locateBinaries to "find" the binaries below in a non-existent 47 // directory. As a workaround, point the search path to the fake 48 // directory containing out fake binaries. 49 savePath := os.Getenv("PPROF_BINARY_PATH") 50 os.Setenv("PPROF_BINARY_PATH", "/path/to") 51 defer os.Setenv("PPROF_BINARY_PATH", savePath) 52 testcase := []struct { 53 flags, source string 54 }{ 55 {"text,functions,flat", "cpu"}, 56 {"tree,addresses,flat,nodecount=4", "cpusmall"}, 57 {"text,functions,flat,nodecount=5,call_tree", "unknown"}, 58 {"text,alloc_objects,flat", "heap_alloc"}, 59 {"text,files,flat", "heap"}, 60 {"text,files,flat,focus=[12]00,taghide=[X3]00", "heap"}, 61 {"text,inuse_objects,flat", "heap"}, 62 {"text,lines,cum,hide=line[X3]0", "cpu"}, 63 {"text,lines,cum,show=[12]00", "cpu"}, 64 {"text,lines,cum,hide=line[X3]0,focus=[12]00", "cpu"}, 65 {"topproto,lines,cum,hide=mangled[X3]0", "cpu"}, 66 {"tree,lines,cum,focus=[24]00", "heap"}, 67 {"tree,relative_percentages,cum,focus=[24]00", "heap"}, 68 {"callgrind", "cpu"}, 69 {"callgrind,call_tree", "cpu"}, 70 {"callgrind", "heap"}, 71 {"dot,functions,flat", "cpu"}, 72 {"dot,functions,flat,call_tree", "cpu"}, 73 {"dot,lines,flat,focus=[12]00", "heap"}, 74 {"dot,unit=minimum", "heap_sizetags"}, 75 {"dot,addresses,flat,ignore=[X3]002,focus=[X1]000", "contention"}, 76 {"dot,files,cum", "contention"}, 77 {"comments,add_comment=some-comment", "cpu"}, 78 {"comments", "heap"}, 79 {"tags", "cpu"}, 80 {"tags,tagignore=tag[13],tagfocus=key[12]", "cpu"}, 81 {"tags", "heap"}, 82 {"tags,unit=bytes", "heap"}, 83 {"traces", "cpu"}, 84 {"traces", "heap_tags"}, 85 {"dot,alloc_space,flat,focus=[234]00", "heap_alloc"}, 86 {"dot,alloc_space,flat,tagshow=[2]00", "heap_alloc"}, 87 {"dot,alloc_space,flat,hide=line.*1?23?", "heap_alloc"}, 88 {"dot,inuse_space,flat,tagfocus=1mb:2gb", "heap"}, 89 {"dot,inuse_space,flat,tagfocus=30kb:,tagignore=1mb:2mb", "heap"}, 90 {"disasm=line[13],addresses,flat", "cpu"}, 91 {"peek=line.*01", "cpu"}, 92 {"weblist=line[13],addresses,flat", "cpu"}, 93 {"tags,tagfocus=400kb:", "heap_request"}, 94 } 95 96 baseVars := pprofVariables 97 defer func() { pprofVariables = baseVars }() 98 for _, tc := range testcase { 99 // Reset the pprof variables before processing 100 pprofVariables = baseVars.makeCopy() 101 102 f := baseFlags() 103 f.args = []string{tc.source} 104 105 flags := strings.Split(tc.flags, ",") 106 107 // Skip the output format in the first flag, to output to a proto 108 addFlags(&f, flags[1:]) 109 110 // Encode profile into a protobuf and decode it again. 111 protoTempFile, err := ioutil.TempFile("", "profile_proto") 112 if err != nil { 113 t.Errorf("cannot create tempfile: %v", err) 114 } 115 defer os.Remove(protoTempFile.Name()) 116 defer protoTempFile.Close() 117 f.strings["output"] = protoTempFile.Name() 118 119 if flags[0] == "topproto" { 120 f.bools["proto"] = false 121 f.bools["topproto"] = true 122 } 123 124 // First pprof invocation to save the profile into a profile.proto. 125 o1 := setDefaults(nil) 126 o1.Flagset = f 127 o1.Fetch = testFetcher{} 128 o1.Sym = testSymbolizer{} 129 if err := PProf(o1); err != nil { 130 t.Errorf("%s %q: %v", tc.source, tc.flags, err) 131 continue 132 } 133 // Reset the pprof variables after the proto invocation 134 pprofVariables = baseVars.makeCopy() 135 136 // Read the profile from the encoded protobuf 137 outputTempFile, err := ioutil.TempFile("", "profile_output") 138 if err != nil { 139 t.Errorf("cannot create tempfile: %v", err) 140 } 141 defer os.Remove(outputTempFile.Name()) 142 defer outputTempFile.Close() 143 f.strings["output"] = outputTempFile.Name() 144 f.args = []string{protoTempFile.Name()} 145 146 var solution string 147 // Apply the flags for the second pprof run, and identify name of 148 // the file containing expected results 149 if flags[0] == "topproto" { 150 solution = solutionFilename(tc.source, &f) 151 delete(f.bools, "topproto") 152 f.bools["text"] = true 153 } else { 154 delete(f.bools, "proto") 155 addFlags(&f, flags[:1]) 156 solution = solutionFilename(tc.source, &f) 157 } 158 // The add_comment flag is not idempotent so only apply it on the first run. 159 delete(f.strings, "add_comment") 160 161 // Second pprof invocation to read the profile from profile.proto 162 // and generate a report. 163 o2 := setDefaults(nil) 164 o2.Flagset = f 165 o2.Sym = testSymbolizeDemangler{} 166 o2.Obj = new(mockObjTool) 167 168 if err := PProf(o2); err != nil { 169 t.Errorf("%s: %v", tc.source, err) 170 } 171 b, err := ioutil.ReadFile(outputTempFile.Name()) 172 if err != nil { 173 t.Errorf("Failed to read profile %s: %v", outputTempFile.Name(), err) 174 } 175 176 // Read data file with expected solution 177 solution = "testdata/" + solution 178 sbuf, err := ioutil.ReadFile(solution) 179 if err != nil { 180 t.Errorf("reading solution file %s: %v", solution, err) 181 continue 182 } 183 if runtime.GOOS == "windows" { 184 sbuf = bytes.Replace(sbuf, []byte("testdata/"), []byte("testdata\\"), -1) 185 sbuf = bytes.Replace(sbuf, []byte("/path/to/"), []byte("\\path\\to\\"), -1) 186 } 187 188 if flags[0] == "svg" { 189 b = removeScripts(b) 190 sbuf = removeScripts(sbuf) 191 } 192 193 if string(b) != string(sbuf) { 194 t.Errorf("diff %s %s", solution, tc.source) 195 d, err := proftest.Diff(sbuf, b) 196 if err != nil { 197 t.Fatalf("diff %s %v", solution, err) 198 } 199 t.Errorf("%s\n%s\n", solution, d) 200 if *updateFlag { 201 err := ioutil.WriteFile(solution, b, 0644) 202 if err != nil { 203 t.Errorf("failed to update the solution file %q: %v", solution, err) 204 } 205 } 206 } 207 } 208 } 209 210 // removeScripts removes <script > .. </script> pairs from its input 211 func removeScripts(in []byte) []byte { 212 beginMarker := []byte("<script") 213 endMarker := []byte("</script>") 214 215 if begin := bytes.Index(in, beginMarker); begin > 0 { 216 if end := bytes.Index(in[begin:], endMarker); end > 0 { 217 in = append(in[:begin], removeScripts(in[begin+end+len(endMarker):])...) 218 } 219 } 220 return in 221 } 222 223 // addFlags parses flag descriptions and adds them to the testFlags 224 func addFlags(f *testFlags, flags []string) { 225 for _, flag := range flags { 226 fields := strings.SplitN(flag, "=", 2) 227 switch len(fields) { 228 case 1: 229 f.bools[fields[0]] = true 230 case 2: 231 if i, err := strconv.Atoi(fields[1]); err == nil { 232 f.ints[fields[0]] = i 233 } else { 234 f.strings[fields[0]] = fields[1] 235 } 236 } 237 } 238 } 239 240 func testSourceURL(port int) string { 241 return fmt.Sprintf("http://%s/", net.JoinHostPort(testSourceAddress, strconv.Itoa(port))) 242 } 243 244 // solutionFilename returns the name of the solution file for the test 245 func solutionFilename(source string, f *testFlags) string { 246 name := []string{"pprof", strings.TrimPrefix(source, testSourceURL(8000))} 247 name = addString(name, f, []string{"flat", "cum"}) 248 name = addString(name, f, []string{"functions", "files", "lines", "addresses"}) 249 name = addString(name, f, []string{"inuse_space", "inuse_objects", "alloc_space", "alloc_objects"}) 250 name = addString(name, f, []string{"relative_percentages"}) 251 name = addString(name, f, []string{"seconds"}) 252 name = addString(name, f, []string{"call_tree"}) 253 name = addString(name, f, []string{"text", "tree", "callgrind", "dot", "svg", "tags", "dot", "traces", "disasm", "peek", "weblist", "topproto", "comments"}) 254 if f.strings["focus"] != "" || f.strings["tagfocus"] != "" { 255 name = append(name, "focus") 256 } 257 if f.strings["ignore"] != "" || f.strings["tagignore"] != "" { 258 name = append(name, "ignore") 259 } 260 name = addString(name, f, []string{"hide", "show"}) 261 if f.strings["unit"] != "minimum" { 262 name = addString(name, f, []string{"unit"}) 263 } 264 return strings.Join(name, ".") 265 } 266 267 func addString(name []string, f *testFlags, components []string) []string { 268 for _, c := range components { 269 if f.bools[c] || f.strings[c] != "" || f.ints[c] != 0 { 270 return append(name, c) 271 } 272 } 273 return name 274 } 275 276 // testFlags implements the plugin.FlagSet interface. 277 type testFlags struct { 278 bools map[string]bool 279 ints map[string]int 280 floats map[string]float64 281 strings map[string]string 282 args []string 283 stringLists map[string][]*string 284 } 285 286 func (testFlags) ExtraUsage() string { return "" } 287 288 func (f testFlags) Bool(s string, d bool, c string) *bool { 289 if b, ok := f.bools[s]; ok { 290 return &b 291 } 292 return &d 293 } 294 295 func (f testFlags) Int(s string, d int, c string) *int { 296 if i, ok := f.ints[s]; ok { 297 return &i 298 } 299 return &d 300 } 301 302 func (f testFlags) Float64(s string, d float64, c string) *float64 { 303 if g, ok := f.floats[s]; ok { 304 return &g 305 } 306 return &d 307 } 308 309 func (f testFlags) String(s, d, c string) *string { 310 if t, ok := f.strings[s]; ok { 311 return &t 312 } 313 return &d 314 } 315 316 func (f testFlags) BoolVar(p *bool, s string, d bool, c string) { 317 if b, ok := f.bools[s]; ok { 318 *p = b 319 } else { 320 *p = d 321 } 322 } 323 324 func (f testFlags) IntVar(p *int, s string, d int, c string) { 325 if i, ok := f.ints[s]; ok { 326 *p = i 327 } else { 328 *p = d 329 } 330 } 331 332 func (f testFlags) Float64Var(p *float64, s string, d float64, c string) { 333 if g, ok := f.floats[s]; ok { 334 *p = g 335 } else { 336 *p = d 337 } 338 } 339 340 func (f testFlags) StringVar(p *string, s, d, c string) { 341 if t, ok := f.strings[s]; ok { 342 *p = t 343 } else { 344 *p = d 345 } 346 } 347 348 func (f testFlags) StringList(s, d, c string) *[]*string { 349 if t, ok := f.stringLists[s]; ok { 350 return &t 351 } 352 return &[]*string{} 353 } 354 355 func (f testFlags) Parse(func()) []string { 356 return f.args 357 } 358 359 func baseFlags() testFlags { 360 return testFlags{ 361 bools: map[string]bool{ 362 "proto": true, 363 "trim": true, 364 "compact_labels": true, 365 }, 366 ints: map[string]int{ 367 "nodecount": 20, 368 }, 369 floats: map[string]float64{ 370 "nodefraction": 0.05, 371 "edgefraction": 0.01, 372 "divide_by": 1.0, 373 }, 374 strings: map[string]string{ 375 "unit": "minimum", 376 }, 377 } 378 } 379 380 const testStart = 0x1000 381 const testOffset = 0x5000 382 383 type testFetcher struct{} 384 385 func (testFetcher) Fetch(s string, d, t time.Duration) (*profile.Profile, string, error) { 386 var p *profile.Profile 387 switch s { 388 case "cpu", "unknown": 389 p = cpuProfile() 390 case "cpusmall": 391 p = cpuProfileSmall() 392 case "heap": 393 p = heapProfile() 394 case "heap_alloc": 395 p = heapProfile() 396 p.SampleType = []*profile.ValueType{ 397 {Type: "alloc_objects", Unit: "count"}, 398 {Type: "alloc_space", Unit: "bytes"}, 399 } 400 case "heap_request": 401 p = heapProfile() 402 for _, s := range p.Sample { 403 s.NumLabel["request"] = s.NumLabel["bytes"] 404 } 405 case "heap_sizetags": 406 p = heapProfile() 407 tags := []int64{2, 4, 8, 16, 32, 64, 128, 256} 408 for _, s := range p.Sample { 409 numValues := append(s.NumLabel["bytes"], tags...) 410 s.NumLabel["bytes"] = numValues 411 } 412 case "heap_tags": 413 p = heapProfile() 414 for i := 0; i < len(p.Sample); i += 2 { 415 s := p.Sample[i] 416 if s.Label == nil { 417 s.Label = make(map[string][]string) 418 } 419 s.NumLabel["request"] = s.NumLabel["bytes"] 420 s.Label["key1"] = []string{"tag"} 421 } 422 case "contention": 423 p = contentionProfile() 424 case "symbolz": 425 p = symzProfile() 426 default: 427 return nil, "", fmt.Errorf("unexpected source: %s", s) 428 } 429 return p, testSourceURL(8000) + s, nil 430 } 431 432 type testSymbolizer struct{} 433 434 func (testSymbolizer) Symbolize(_ string, _ plugin.MappingSources, _ *profile.Profile) error { 435 return nil 436 } 437 438 type testSymbolizeDemangler struct{} 439 440 func (testSymbolizeDemangler) Symbolize(_ string, _ plugin.MappingSources, p *profile.Profile) error { 441 for _, fn := range p.Function { 442 if fn.Name == "" || fn.SystemName == fn.Name { 443 fn.Name = fakeDemangler(fn.SystemName) 444 } 445 } 446 return nil 447 } 448 449 func testFetchSymbols(source, post string) ([]byte, error) { 450 var buf bytes.Buffer 451 452 switch source { 453 case testSourceURL(8000) + "symbolz": 454 for _, address := range strings.Split(post, "+") { 455 a, _ := strconv.ParseInt(address, 0, 64) 456 fmt.Fprintf(&buf, "%v\t", address) 457 if a-testStart > testOffset { 458 fmt.Fprintf(&buf, "wrong_source_%v_", address) 459 continue 460 } 461 fmt.Fprintf(&buf, "%#x\n", a-testStart) 462 } 463 return buf.Bytes(), nil 464 case testSourceURL(8001) + "symbolz": 465 for _, address := range strings.Split(post, "+") { 466 a, _ := strconv.ParseInt(address, 0, 64) 467 fmt.Fprintf(&buf, "%v\t", address) 468 if a-testStart < testOffset { 469 fmt.Fprintf(&buf, "wrong_source_%v_", address) 470 continue 471 } 472 fmt.Fprintf(&buf, "%#x\n", a-testStart-testOffset) 473 } 474 return buf.Bytes(), nil 475 default: 476 return nil, fmt.Errorf("unexpected source: %s", source) 477 } 478 } 479 480 type testSymbolzSymbolizer struct{} 481 482 func (testSymbolzSymbolizer) Symbolize(variables string, sources plugin.MappingSources, p *profile.Profile) error { 483 return symbolz.Symbolize(p, false, sources, testFetchSymbols, nil) 484 } 485 486 func fakeDemangler(name string) string { 487 switch name { 488 case "mangled1000": 489 return "line1000" 490 case "mangled2000": 491 return "line2000" 492 case "mangled2001": 493 return "line2001" 494 case "mangled3000": 495 return "line3000" 496 case "mangled3001": 497 return "line3001" 498 case "mangled3002": 499 return "line3002" 500 case "mangledNEW": 501 return "operator new" 502 case "mangledMALLOC": 503 return "malloc" 504 default: 505 return name 506 } 507 } 508 509 func cpuProfile() *profile.Profile { 510 var cpuM = []*profile.Mapping{ 511 { 512 ID: 1, 513 Start: 0x1000, 514 Limit: 0x4000, 515 File: "/path/to/testbinary", 516 HasFunctions: true, 517 HasFilenames: true, 518 HasLineNumbers: true, 519 HasInlineFrames: true, 520 }, 521 } 522 523 var cpuF = []*profile.Function{ 524 {ID: 1, Name: "mangled1000", SystemName: "mangled1000", Filename: "testdata/file1000.src"}, 525 {ID: 2, Name: "mangled2000", SystemName: "mangled2000", Filename: "testdata/file2000.src"}, 526 {ID: 3, Name: "mangled2001", SystemName: "mangled2001", Filename: "testdata/file2000.src"}, 527 {ID: 4, Name: "mangled3000", SystemName: "mangled3000", Filename: "testdata/file3000.src"}, 528 {ID: 5, Name: "mangled3001", SystemName: "mangled3001", Filename: "testdata/file3000.src"}, 529 {ID: 6, Name: "mangled3002", SystemName: "mangled3002", Filename: "testdata/file3000.src"}, 530 } 531 532 var cpuL = []*profile.Location{ 533 { 534 ID: 1000, 535 Mapping: cpuM[0], 536 Address: 0x1000, 537 Line: []profile.Line{ 538 {Function: cpuF[0], Line: 1}, 539 }, 540 }, 541 { 542 ID: 2000, 543 Mapping: cpuM[0], 544 Address: 0x2000, 545 Line: []profile.Line{ 546 {Function: cpuF[2], Line: 9}, 547 {Function: cpuF[1], Line: 4}, 548 }, 549 }, 550 { 551 ID: 3000, 552 Mapping: cpuM[0], 553 Address: 0x3000, 554 Line: []profile.Line{ 555 {Function: cpuF[5], Line: 2}, 556 {Function: cpuF[4], Line: 5}, 557 {Function: cpuF[3], Line: 6}, 558 }, 559 }, 560 { 561 ID: 3001, 562 Mapping: cpuM[0], 563 Address: 0x3001, 564 Line: []profile.Line{ 565 {Function: cpuF[4], Line: 8}, 566 {Function: cpuF[3], Line: 9}, 567 }, 568 }, 569 { 570 ID: 3002, 571 Mapping: cpuM[0], 572 Address: 0x3002, 573 Line: []profile.Line{ 574 {Function: cpuF[5], Line: 5}, 575 {Function: cpuF[3], Line: 9}, 576 }, 577 }, 578 } 579 580 return &profile.Profile{ 581 PeriodType: &profile.ValueType{Type: "cpu", Unit: "milliseconds"}, 582 Period: 1, 583 DurationNanos: 10e9, 584 SampleType: []*profile.ValueType{ 585 {Type: "samples", Unit: "count"}, 586 {Type: "cpu", Unit: "milliseconds"}, 587 }, 588 Sample: []*profile.Sample{ 589 { 590 Location: []*profile.Location{cpuL[0], cpuL[1], cpuL[2]}, 591 Value: []int64{1000, 1000}, 592 Label: map[string][]string{ 593 "key1": {"tag1"}, 594 "key2": {"tag1"}, 595 }, 596 }, 597 { 598 Location: []*profile.Location{cpuL[0], cpuL[3]}, 599 Value: []int64{100, 100}, 600 Label: map[string][]string{ 601 "key1": {"tag2"}, 602 "key3": {"tag2"}, 603 }, 604 }, 605 { 606 Location: []*profile.Location{cpuL[1], cpuL[4]}, 607 Value: []int64{10, 10}, 608 Label: map[string][]string{ 609 "key1": {"tag3"}, 610 "key2": {"tag2"}, 611 }, 612 }, 613 { 614 Location: []*profile.Location{cpuL[2]}, 615 Value: []int64{10, 10}, 616 Label: map[string][]string{ 617 "key1": {"tag4"}, 618 "key2": {"tag1"}, 619 }, 620 }, 621 }, 622 Location: cpuL, 623 Function: cpuF, 624 Mapping: cpuM, 625 } 626 } 627 628 func cpuProfileSmall() *profile.Profile { 629 var cpuM = []*profile.Mapping{ 630 { 631 ID: 1, 632 Start: 0x1000, 633 Limit: 0x4000, 634 File: "/path/to/testbinary", 635 HasFunctions: true, 636 HasFilenames: true, 637 HasLineNumbers: true, 638 HasInlineFrames: true, 639 }, 640 } 641 642 var cpuL = []*profile.Location{ 643 { 644 ID: 1000, 645 Mapping: cpuM[0], 646 Address: 0x1000, 647 }, 648 { 649 ID: 2000, 650 Mapping: cpuM[0], 651 Address: 0x2000, 652 }, 653 { 654 ID: 3000, 655 Mapping: cpuM[0], 656 Address: 0x3000, 657 }, 658 { 659 ID: 4000, 660 Mapping: cpuM[0], 661 Address: 0x4000, 662 }, 663 { 664 ID: 5000, 665 Mapping: cpuM[0], 666 Address: 0x5000, 667 }, 668 } 669 670 return &profile.Profile{ 671 PeriodType: &profile.ValueType{Type: "cpu", Unit: "milliseconds"}, 672 Period: 1, 673 DurationNanos: 10e9, 674 SampleType: []*profile.ValueType{ 675 {Type: "samples", Unit: "count"}, 676 {Type: "cpu", Unit: "milliseconds"}, 677 }, 678 Sample: []*profile.Sample{ 679 { 680 Location: []*profile.Location{cpuL[0], cpuL[1], cpuL[2]}, 681 Value: []int64{1000, 1000}, 682 }, 683 { 684 Location: []*profile.Location{cpuL[3], cpuL[1], cpuL[4]}, 685 Value: []int64{1000, 1000}, 686 }, 687 { 688 Location: []*profile.Location{cpuL[2]}, 689 Value: []int64{1000, 1000}, 690 }, 691 { 692 Location: []*profile.Location{cpuL[4]}, 693 Value: []int64{1000, 1000}, 694 }, 695 }, 696 Location: cpuL, 697 Function: nil, 698 Mapping: cpuM, 699 } 700 } 701 702 func heapProfile() *profile.Profile { 703 var heapM = []*profile.Mapping{ 704 { 705 ID: 1, 706 BuildID: "buildid", 707 Start: 0x1000, 708 Limit: 0x4000, 709 HasFunctions: true, 710 HasFilenames: true, 711 HasLineNumbers: true, 712 HasInlineFrames: true, 713 }, 714 } 715 716 var heapF = []*profile.Function{ 717 {ID: 1, Name: "pruneme", SystemName: "pruneme", Filename: "prune.h"}, 718 {ID: 2, Name: "mangled1000", SystemName: "mangled1000", Filename: "testdata/file1000.src"}, 719 {ID: 3, Name: "mangled2000", SystemName: "mangled2000", Filename: "testdata/file2000.src"}, 720 {ID: 4, Name: "mangled2001", SystemName: "mangled2001", Filename: "testdata/file2000.src"}, 721 {ID: 5, Name: "mangled3000", SystemName: "mangled3000", Filename: "testdata/file3000.src"}, 722 {ID: 6, Name: "mangled3001", SystemName: "mangled3001", Filename: "testdata/file3000.src"}, 723 {ID: 7, Name: "mangled3002", SystemName: "mangled3002", Filename: "testdata/file3000.src"}, 724 {ID: 8, Name: "mangledMALLOC", SystemName: "mangledMALLOC", Filename: "malloc.h"}, 725 {ID: 9, Name: "mangledNEW", SystemName: "mangledNEW", Filename: "new.h"}, 726 } 727 728 var heapL = []*profile.Location{ 729 { 730 ID: 1000, 731 Mapping: heapM[0], 732 Address: 0x1000, 733 Line: []profile.Line{ 734 {Function: heapF[0], Line: 100}, 735 {Function: heapF[7], Line: 100}, 736 {Function: heapF[1], Line: 1}, 737 }, 738 }, 739 { 740 ID: 2000, 741 Mapping: heapM[0], 742 Address: 0x2000, 743 Line: []profile.Line{ 744 {Function: heapF[8], Line: 100}, 745 {Function: heapF[3], Line: 2}, 746 {Function: heapF[2], Line: 3}, 747 }, 748 }, 749 { 750 ID: 3000, 751 Mapping: heapM[0], 752 Address: 0x3000, 753 Line: []profile.Line{ 754 {Function: heapF[8], Line: 100}, 755 {Function: heapF[6], Line: 3}, 756 {Function: heapF[5], Line: 2}, 757 {Function: heapF[4], Line: 4}, 758 }, 759 }, 760 { 761 ID: 3001, 762 Mapping: heapM[0], 763 Address: 0x3001, 764 Line: []profile.Line{ 765 {Function: heapF[0], Line: 100}, 766 {Function: heapF[8], Line: 100}, 767 {Function: heapF[5], Line: 2}, 768 {Function: heapF[4], Line: 4}, 769 }, 770 }, 771 { 772 ID: 3002, 773 Mapping: heapM[0], 774 Address: 0x3002, 775 Line: []profile.Line{ 776 {Function: heapF[6], Line: 3}, 777 {Function: heapF[4], Line: 4}, 778 }, 779 }, 780 } 781 782 return &profile.Profile{ 783 Comments: []string{"comment", "#hidden comment"}, 784 PeriodType: &profile.ValueType{Type: "allocations", Unit: "bytes"}, 785 Period: 524288, 786 SampleType: []*profile.ValueType{ 787 {Type: "inuse_objects", Unit: "count"}, 788 {Type: "inuse_space", Unit: "bytes"}, 789 }, 790 Sample: []*profile.Sample{ 791 { 792 Location: []*profile.Location{heapL[0], heapL[1], heapL[2]}, 793 Value: []int64{10, 1024000}, 794 NumLabel: map[string][]int64{"bytes": {102400}}, 795 }, 796 { 797 Location: []*profile.Location{heapL[0], heapL[3]}, 798 Value: []int64{20, 4096000}, 799 NumLabel: map[string][]int64{"bytes": {204800}}, 800 }, 801 { 802 Location: []*profile.Location{heapL[1], heapL[4]}, 803 Value: []int64{40, 65536000}, 804 NumLabel: map[string][]int64{"bytes": {1638400}}, 805 }, 806 { 807 Location: []*profile.Location{heapL[2]}, 808 Value: []int64{80, 32768000}, 809 NumLabel: map[string][]int64{"bytes": {409600}}, 810 }, 811 }, 812 DropFrames: ".*operator new.*|malloc", 813 Location: heapL, 814 Function: heapF, 815 Mapping: heapM, 816 } 817 } 818 819 func contentionProfile() *profile.Profile { 820 var contentionM = []*profile.Mapping{ 821 { 822 ID: 1, 823 BuildID: "buildid-contention", 824 Start: 0x1000, 825 Limit: 0x4000, 826 HasFunctions: true, 827 HasFilenames: true, 828 HasLineNumbers: true, 829 HasInlineFrames: true, 830 }, 831 } 832 833 var contentionF = []*profile.Function{ 834 {ID: 1, Name: "mangled1000", SystemName: "mangled1000", Filename: "testdata/file1000.src"}, 835 {ID: 2, Name: "mangled2000", SystemName: "mangled2000", Filename: "testdata/file2000.src"}, 836 {ID: 3, Name: "mangled2001", SystemName: "mangled2001", Filename: "testdata/file2000.src"}, 837 {ID: 4, Name: "mangled3000", SystemName: "mangled3000", Filename: "testdata/file3000.src"}, 838 {ID: 5, Name: "mangled3001", SystemName: "mangled3001", Filename: "testdata/file3000.src"}, 839 {ID: 6, Name: "mangled3002", SystemName: "mangled3002", Filename: "testdata/file3000.src"}, 840 } 841 842 var contentionL = []*profile.Location{ 843 { 844 ID: 1000, 845 Mapping: contentionM[0], 846 Address: 0x1000, 847 Line: []profile.Line{ 848 {Function: contentionF[0], Line: 1}, 849 }, 850 }, 851 { 852 ID: 2000, 853 Mapping: contentionM[0], 854 Address: 0x2000, 855 Line: []profile.Line{ 856 {Function: contentionF[2], Line: 2}, 857 {Function: contentionF[1], Line: 3}, 858 }, 859 }, 860 { 861 ID: 3000, 862 Mapping: contentionM[0], 863 Address: 0x3000, 864 Line: []profile.Line{ 865 {Function: contentionF[5], Line: 2}, 866 {Function: contentionF[4], Line: 3}, 867 {Function: contentionF[3], Line: 5}, 868 }, 869 }, 870 { 871 ID: 3001, 872 Mapping: contentionM[0], 873 Address: 0x3001, 874 Line: []profile.Line{ 875 {Function: contentionF[4], Line: 3}, 876 {Function: contentionF[3], Line: 5}, 877 }, 878 }, 879 { 880 ID: 3002, 881 Mapping: contentionM[0], 882 Address: 0x3002, 883 Line: []profile.Line{ 884 {Function: contentionF[5], Line: 4}, 885 {Function: contentionF[3], Line: 3}, 886 }, 887 }, 888 } 889 890 return &profile.Profile{ 891 PeriodType: &profile.ValueType{Type: "contentions", Unit: "count"}, 892 Period: 524288, 893 SampleType: []*profile.ValueType{ 894 {Type: "contentions", Unit: "count"}, 895 {Type: "delay", Unit: "nanoseconds"}, 896 }, 897 Sample: []*profile.Sample{ 898 { 899 Location: []*profile.Location{contentionL[0], contentionL[1], contentionL[2]}, 900 Value: []int64{10, 10240000}, 901 }, 902 { 903 Location: []*profile.Location{contentionL[0], contentionL[3]}, 904 Value: []int64{20, 40960000}, 905 }, 906 { 907 Location: []*profile.Location{contentionL[1], contentionL[4]}, 908 Value: []int64{40, 65536000}, 909 }, 910 { 911 Location: []*profile.Location{contentionL[2]}, 912 Value: []int64{80, 32768000}, 913 }, 914 }, 915 Location: contentionL, 916 Function: contentionF, 917 Mapping: contentionM, 918 Comments: []string{"Comment #1", "Comment #2"}, 919 } 920 } 921 922 func symzProfile() *profile.Profile { 923 var symzM = []*profile.Mapping{ 924 { 925 ID: 1, 926 Start: testStart, 927 Limit: 0x4000, 928 File: "/path/to/testbinary", 929 }, 930 } 931 932 var symzL = []*profile.Location{ 933 {ID: 1, Mapping: symzM[0], Address: testStart}, 934 {ID: 2, Mapping: symzM[0], Address: testStart + 0x1000}, 935 {ID: 3, Mapping: symzM[0], Address: testStart + 0x2000}, 936 } 937 938 return &profile.Profile{ 939 PeriodType: &profile.ValueType{Type: "cpu", Unit: "milliseconds"}, 940 Period: 1, 941 DurationNanos: 10e9, 942 SampleType: []*profile.ValueType{ 943 {Type: "samples", Unit: "count"}, 944 {Type: "cpu", Unit: "milliseconds"}, 945 }, 946 Sample: []*profile.Sample{ 947 { 948 Location: []*profile.Location{symzL[0], symzL[1], symzL[2]}, 949 Value: []int64{1, 1}, 950 }, 951 }, 952 Location: symzL, 953 Mapping: symzM, 954 } 955 } 956 957 var autoCompleteTests = []struct { 958 in string 959 out string 960 }{ 961 {"", ""}, 962 {"xyz", "xyz"}, // no match 963 {"dis", "disasm"}, // single match 964 {"t", "t"}, // many matches 965 {"top abc", "top abc"}, // no function name match 966 {"top mangledM", "top mangledMALLOC"}, // single function name match 967 {"top cmd cmd mangledM", "top cmd cmd mangledMALLOC"}, 968 {"top mangled", "top mangled"}, // many function name matches 969 {"cmd mangledM", "cmd mangledM"}, // invalid command 970 {"top mangledM cmd", "top mangledM cmd"}, // cursor misplaced 971 {"top edMA", "top mangledMALLOC"}, // single infix function name match 972 {"top -mangledM", "top -mangledMALLOC"}, // ignore sign handled 973 {"lin", "lines"}, // single variable match 974 {"EdGeF", "edgefraction"}, // single capitalized match 975 {"help dis", "help disasm"}, // help command match 976 {"help relative_perc", "help relative_percentages"}, // help variable match 977 {"help coMpa", "help compact_labels"}, // help variable capitalized match 978 } 979 980 func TestAutoComplete(t *testing.T) { 981 complete := newCompleter(functionNames(heapProfile())) 982 983 for _, test := range autoCompleteTests { 984 if out := complete(test.in); out != test.out { 985 t.Errorf("autoComplete(%s) = %s; want %s", test.in, out, test.out) 986 } 987 } 988 } 989 990 func TestTagFilter(t *testing.T) { 991 var tagFilterTests = []struct { 992 desc, value string 993 tags map[string][]string 994 want bool 995 }{ 996 { 997 "1 key with 1 matching value", 998 "tag2", 999 map[string][]string{"value1": {"tag1", "tag2"}}, 1000 true, 1001 }, 1002 { 1003 "1 key with no matching values", 1004 "tag3", 1005 map[string][]string{"value1": {"tag1", "tag2"}}, 1006 false, 1007 }, 1008 { 1009 "two keys, each with value matching different one value in list", 1010 "tag1,tag3", 1011 map[string][]string{"value1": {"tag1", "tag2"}, "value2": {"tag3"}}, 1012 true, 1013 }, 1014 {"two keys, all value matching different regex value in list", 1015 "t..[12],t..3", 1016 map[string][]string{"value1": {"tag1", "tag2"}, "value2": {"tag3"}}, 1017 true, 1018 }, 1019 { 1020 "one key, not all values in list matched", 1021 "tag2,tag3", 1022 map[string][]string{"value1": {"tag1", "tag2"}}, 1023 false, 1024 }, 1025 { 1026 "key specified, list of tags where all tags in list matched", 1027 "key1=tag1,tag2", 1028 map[string][]string{"key1": {"tag1", "tag2"}}, 1029 true, 1030 }, 1031 {"key specified, list of tag values where not all are matched", 1032 "key1=tag1,tag2", 1033 map[string][]string{"key1": {"tag1"}}, 1034 true, 1035 }, 1036 { 1037 "key included for regex matching, list of values where all values in list matched", 1038 "key1:tag1,tag2", 1039 map[string][]string{"key1": {"tag1", "tag2"}}, 1040 true, 1041 }, 1042 { 1043 "key included for regex matching, list of values where not only second value matched", 1044 "key1:tag1,tag2", 1045 map[string][]string{"key1": {"tag2"}}, 1046 false, 1047 }, 1048 { 1049 "key included for regex matching, list of values where not only first value matched", 1050 "key1:tag1,tag2", 1051 map[string][]string{"key1": {"tag1"}}, 1052 false, 1053 }, 1054 } 1055 for _, test := range tagFilterTests { 1056 t.Run(test.desc, func(*testing.T) { 1057 filter, err := compileTagFilter(test.desc, test.value, nil, &proftest.TestUI{T: t}, nil) 1058 if err != nil { 1059 t.Fatalf("tagFilter %s:%v", test.desc, err) 1060 } 1061 s := profile.Sample{ 1062 Label: test.tags, 1063 } 1064 if got := filter(&s); got != test.want { 1065 t.Errorf("tagFilter %s: got %v, want %v", test.desc, got, test.want) 1066 } 1067 }) 1068 } 1069 } 1070 1071 func TestIdentifyNumLabelUnits(t *testing.T) { 1072 var tagFilterTests = []struct { 1073 desc string 1074 tagVals []map[string][]int64 1075 tagUnits []map[string][]string 1076 wantUnits map[string]string 1077 allowedRx string 1078 wantIgnoreErrCount int 1079 }{ 1080 { 1081 "Multiple keys, no units for all keys", 1082 []map[string][]int64{{"keyA": {131072}, "keyB": {128}}}, 1083 []map[string][]string{{"keyA": {}, "keyB": {""}}}, 1084 map[string]string{"keyA": "keyA", "keyB": "keyB"}, 1085 "", 1086 0, 1087 }, 1088 { 1089 "Multiple keys, different units for each key", 1090 []map[string][]int64{{"keyA": {131072}, "keyB": {128}}}, 1091 []map[string][]string{{"keyA": {"bytes"}, "keyB": {"kilobytes"}}}, 1092 map[string]string{"keyA": "bytes", "keyB": "kilobytes"}, 1093 "", 1094 0, 1095 }, 1096 { 1097 "Multiple keys with multiple values, different units for each key", 1098 []map[string][]int64{{"keyC": {131072, 1}, "keyD": {128, 252}}}, 1099 []map[string][]string{{"keyC": {"bytes", "bytes"}, "keyD": {"kilobytes", "kilobytes"}}}, 1100 map[string]string{"keyC": "bytes", "keyD": "kilobytes"}, 1101 "", 1102 0, 1103 }, 1104 { 1105 "Multiple keys with multiple values, some units missing", 1106 []map[string][]int64{{"key1": {131072, 1}, "A": {128, 252}, "key3": {128}, "key4": {1}}, {"key3": {128}, "key4": {1}}}, 1107 []map[string][]string{{"key1": {"", "bytes"}, "A": {"kilobytes", ""}, "key3": {""}, "key4": {"hour"}}, {"key3": {"seconds"}, "key4": {""}}}, 1108 map[string]string{"key1": "bytes", "A": "kilobytes", "key3": "seconds", "key4": "hour"}, 1109 "", 1110 0, 1111 }, 1112 { 1113 "One key with three units in same sample", 1114 []map[string][]int64{{"key": {8, 8, 16}}}, 1115 []map[string][]string{{"key": {"bytes", "megabytes", "kilobytes"}}}, 1116 map[string]string{"key": "bytes"}, 1117 `(For tag key used unit bytes, also encountered unit\(s\) kilobytes, megabytes)`, 1118 1, 1119 }, 1120 { 1121 "One key with four units in same sample", 1122 []map[string][]int64{{"key": {8, 8, 16, 32}}}, 1123 []map[string][]string{{"key": {"bytes", "kilobytes", "a", "megabytes"}}}, 1124 map[string]string{"key": "bytes"}, 1125 `(For tag key used unit bytes, also encountered unit\(s\) a, kilobytes, megabytes)`, 1126 1, 1127 }, 1128 { 1129 "One key with two units in same sample", 1130 []map[string][]int64{{"key": {8, 8}}}, 1131 []map[string][]string{{"key": {"bytes", "seconds"}}}, 1132 map[string]string{"key": "bytes"}, 1133 `(For tag key used unit bytes, also encountered unit\(s\) seconds)`, 1134 1, 1135 }, 1136 { 1137 "One key with different units in different samples", 1138 []map[string][]int64{{"key1": {8}}, {"key1": {8}}, {"key1": {8}}}, 1139 []map[string][]string{{"key1": {"bytes"}}, {"key1": {"kilobytes"}}, {"key1": {"megabytes"}}}, 1140 map[string]string{"key1": "bytes"}, 1141 `(For tag key1 used unit bytes, also encountered unit\(s\) kilobytes, megabytes)`, 1142 1, 1143 }, 1144 { 1145 "Key alignment, unit not specified", 1146 []map[string][]int64{{"alignment": {8}}}, 1147 []map[string][]string{nil}, 1148 map[string]string{"alignment": "bytes"}, 1149 "", 1150 0, 1151 }, 1152 { 1153 "Key request, unit not specified", 1154 []map[string][]int64{{"request": {8}}, {"request": {8, 8}}}, 1155 []map[string][]string{nil, nil}, 1156 map[string]string{"request": "bytes"}, 1157 "", 1158 0, 1159 }, 1160 { 1161 "Check units not over-written for keys with default units", 1162 []map[string][]int64{{ 1163 "alignment": {8}, 1164 "request": {8}, 1165 "bytes": {8}, 1166 }}, 1167 []map[string][]string{{ 1168 "alignment": {"seconds"}, 1169 "request": {"minutes"}, 1170 "bytes": {"hours"}, 1171 }}, 1172 map[string]string{ 1173 "alignment": "seconds", 1174 "request": "minutes", 1175 "bytes": "hours", 1176 }, 1177 "", 1178 0, 1179 }, 1180 } 1181 for _, test := range tagFilterTests { 1182 t.Run(test.desc, func(*testing.T) { 1183 p := profile.Profile{Sample: make([]*profile.Sample, len(test.tagVals))} 1184 for i, numLabel := range test.tagVals { 1185 s := profile.Sample{ 1186 NumLabel: numLabel, 1187 NumUnit: test.tagUnits[i], 1188 } 1189 p.Sample[i] = &s 1190 } 1191 testUI := &proftest.TestUI{T: t, AllowRx: test.allowedRx} 1192 units := identifyNumLabelUnits(&p, testUI) 1193 if !reflect.DeepEqual(test.wantUnits, units) { 1194 t.Errorf("got %v units, want %v", units, test.wantUnits) 1195 } 1196 if got, want := testUI.NumAllowRxMatches, test.wantIgnoreErrCount; want != got { 1197 t.Errorf("got %d errors logged, want %d errors logged", got, want) 1198 } 1199 }) 1200 } 1201 } 1202 1203 func TestNumericTagFilter(t *testing.T) { 1204 var tagFilterTests = []struct { 1205 desc, value string 1206 tags map[string][]int64 1207 identifiedUnits map[string]string 1208 want bool 1209 }{ 1210 { 1211 "Match when unit conversion required", 1212 "128kb", 1213 map[string][]int64{"key1": {131072}, "key2": {128}}, 1214 map[string]string{"key1": "bytes", "key2": "kilobytes"}, 1215 true, 1216 }, 1217 { 1218 "Match only when values equal after unit conversion", 1219 "512kb", 1220 map[string][]int64{"key1": {512}, "key2": {128}}, 1221 map[string]string{"key1": "bytes", "key2": "kilobytes"}, 1222 false, 1223 }, 1224 { 1225 "Match when values and units initially equal", 1226 "10bytes", 1227 map[string][]int64{"key1": {10}, "key2": {128}}, 1228 map[string]string{"key1": "bytes", "key2": "kilobytes"}, 1229 true, 1230 }, 1231 { 1232 "Match range without lower bound, no unit conversion required", 1233 ":10bytes", 1234 map[string][]int64{"key1": {8}}, 1235 map[string]string{"key1": "bytes"}, 1236 true, 1237 }, 1238 { 1239 "Match range without lower bound, unit conversion required", 1240 ":10kb", 1241 map[string][]int64{"key1": {8}}, 1242 map[string]string{"key1": "bytes"}, 1243 true, 1244 }, 1245 { 1246 "Match range without upper bound, unit conversion required", 1247 "10b:", 1248 map[string][]int64{"key1": {8}}, 1249 map[string]string{"key1": "kilobytes"}, 1250 true, 1251 }, 1252 { 1253 "Match range without upper bound, no unit conversion required", 1254 "10b:", 1255 map[string][]int64{"key1": {12}}, 1256 map[string]string{"key1": "bytes"}, 1257 true, 1258 }, 1259 { 1260 "Don't match range without upper bound, no unit conversion required", 1261 "10b:", 1262 map[string][]int64{"key1": {8}}, 1263 map[string]string{"key1": "bytes"}, 1264 false, 1265 }, 1266 { 1267 "Multiple keys with different units, don't match range without upper bound", 1268 "10kb:", 1269 map[string][]int64{"key1": {8}}, 1270 map[string]string{"key1": "bytes", "key2": "kilobytes"}, 1271 false, 1272 }, 1273 { 1274 "Match range without upper bound, unit conversion required", 1275 "10b:", 1276 map[string][]int64{"key1": {8}}, 1277 map[string]string{"key1": "kilobytes"}, 1278 true, 1279 }, 1280 { 1281 "Don't match range without lower bound, no unit conversion required", 1282 ":10b", 1283 map[string][]int64{"key1": {12}}, 1284 map[string]string{"key1": "bytes"}, 1285 false, 1286 }, 1287 { 1288 "Match specific key, key present, one of two values match", 1289 "bytes=5b", 1290 map[string][]int64{"bytes": {10, 5}}, 1291 map[string]string{"bytes": "bytes"}, 1292 true, 1293 }, 1294 { 1295 "Match specific key, key present and value matches", 1296 "bytes=1024b", 1297 map[string][]int64{"bytes": {1024}}, 1298 map[string]string{"bytes": "kilobytes"}, 1299 false, 1300 }, 1301 { 1302 "Match specific key, matching key present and value matches, also non-matching key", 1303 "bytes=1024b", 1304 map[string][]int64{"bytes": {1024}, "key2": {5}}, 1305 map[string]string{"bytes": "bytes", "key2": "bytes"}, 1306 true, 1307 }, 1308 { 1309 "Match specific key and range of values, value matches", 1310 "bytes=512b:1024b", 1311 map[string][]int64{"bytes": {780}}, 1312 map[string]string{"bytes": "bytes"}, 1313 true, 1314 }, 1315 { 1316 "Match specific key and range of values, value too large", 1317 "key1=1kb:2kb", 1318 map[string][]int64{"key1": {4096}}, 1319 map[string]string{"key1": "bytes"}, 1320 false, 1321 }, 1322 { 1323 "Match specific key and range of values, value too small", 1324 "key1=1kb:2kb", 1325 map[string][]int64{"key1": {256}}, 1326 map[string]string{"key1": "bytes"}, 1327 false, 1328 }, 1329 { 1330 "Match specific key and value, unit conversion required", 1331 "bytes=1024b", 1332 map[string][]int64{"bytes": {1}}, 1333 map[string]string{"bytes": "kilobytes"}, 1334 true, 1335 }, 1336 { 1337 "Match specific key and value, key does not appear", 1338 "key2=256bytes", 1339 map[string][]int64{"key1": {256}}, 1340 map[string]string{"key1": "bytes"}, 1341 false, 1342 }, 1343 } 1344 for _, test := range tagFilterTests { 1345 t.Run(test.desc, func(*testing.T) { 1346 wantErrMsg := strings.Join([]string{"(", test.desc, ":Interpreted '", test.value[strings.Index(test.value, "=")+1:], "' as range, not regexp", ")"}, "") 1347 filter, err := compileTagFilter(test.desc, test.value, test.identifiedUnits, &proftest.TestUI{T: t, 1348 AllowRx: wantErrMsg}, nil) 1349 if err != nil { 1350 t.Fatalf("%v", err) 1351 } 1352 s := profile.Sample{ 1353 NumLabel: test.tags, 1354 } 1355 if got := filter(&s); got != test.want { 1356 t.Fatalf("got %v, want %v", got, test.want) 1357 } 1358 }) 1359 } 1360 } 1361 1362 type testSymbolzMergeFetcher struct{} 1363 1364 func (testSymbolzMergeFetcher) Fetch(s string, d, t time.Duration) (*profile.Profile, string, error) { 1365 var p *profile.Profile 1366 switch s { 1367 case testSourceURL(8000) + "symbolz": 1368 p = symzProfile() 1369 case testSourceURL(8001) + "symbolz": 1370 p = symzProfile() 1371 p.Mapping[0].Start += testOffset 1372 p.Mapping[0].Limit += testOffset 1373 for i := range p.Location { 1374 p.Location[i].Address += testOffset 1375 } 1376 default: 1377 return nil, "", fmt.Errorf("unexpected source: %s", s) 1378 } 1379 return p, s, nil 1380 } 1381 1382 func TestSymbolzAfterMerge(t *testing.T) { 1383 baseVars := pprofVariables 1384 pprofVariables = baseVars.makeCopy() 1385 defer func() { pprofVariables = baseVars }() 1386 1387 f := baseFlags() 1388 f.args = []string{ 1389 testSourceURL(8000) + "symbolz", 1390 testSourceURL(8001) + "symbolz", 1391 } 1392 1393 o := setDefaults(nil) 1394 o.Flagset = f 1395 o.Obj = new(mockObjTool) 1396 src, cmd, err := parseFlags(o) 1397 if err != nil { 1398 t.Fatalf("parseFlags: %v", err) 1399 } 1400 1401 if len(cmd) != 1 || cmd[0] != "proto" { 1402 t.Fatalf("parseFlags returned command %v, want [proto]", cmd) 1403 } 1404 1405 o.Fetch = testSymbolzMergeFetcher{} 1406 o.Sym = testSymbolzSymbolizer{} 1407 p, err := fetchProfiles(src, o) 1408 if err != nil { 1409 t.Fatalf("fetchProfiles: %v", err) 1410 } 1411 if len(p.Location) != 3 { 1412 t.Errorf("Got %d locations after merge, want %d", len(p.Location), 3) 1413 } 1414 for i, l := range p.Location { 1415 if len(l.Line) != 1 { 1416 t.Errorf("Number of lines for symbolz %#x in iteration %d, got %d, want %d", l.Address, i, len(l.Line), 1) 1417 continue 1418 } 1419 address := l.Address - l.Mapping.Start 1420 if got, want := l.Line[0].Function.Name, fmt.Sprintf("%#x", address); got != want { 1421 t.Errorf("symbolz %#x, got %s, want %s", address, got, want) 1422 } 1423 } 1424 } 1425 1426 type mockObjTool struct{} 1427 1428 func (*mockObjTool) Open(file string, start, limit, offset uint64) (plugin.ObjFile, error) { 1429 return &mockFile{file, "abcdef", 0}, nil 1430 } 1431 1432 func (m *mockObjTool) Disasm(file string, start, end uint64) ([]plugin.Inst, error) { 1433 switch start { 1434 case 0x1000: 1435 return []plugin.Inst{ 1436 {Addr: 0x1000, Text: "instruction one", File: "file1000.src", Line: 1}, 1437 {Addr: 0x1001, Text: "instruction two", File: "file1000.src", Line: 1}, 1438 {Addr: 0x1002, Text: "instruction three", File: "file1000.src", Line: 2}, 1439 {Addr: 0x1003, Text: "instruction four", File: "file1000.src", Line: 1}, 1440 }, nil 1441 case 0x3000: 1442 return []plugin.Inst{ 1443 {Addr: 0x3000, Text: "instruction one"}, 1444 {Addr: 0x3001, Text: "instruction two"}, 1445 {Addr: 0x3002, Text: "instruction three"}, 1446 {Addr: 0x3003, Text: "instruction four"}, 1447 {Addr: 0x3004, Text: "instruction five"}, 1448 }, nil 1449 } 1450 return nil, fmt.Errorf("unimplemented") 1451 } 1452 1453 type mockFile struct { 1454 name, buildID string 1455 base uint64 1456 } 1457 1458 // Name returns the underlyinf file name, if available 1459 func (m *mockFile) Name() string { 1460 return m.name 1461 } 1462 1463 // Base returns the base address to use when looking up symbols in the file. 1464 func (m *mockFile) Base() uint64 { 1465 return m.base 1466 } 1467 1468 // BuildID returns the GNU build ID of the file, or an empty string. 1469 func (m *mockFile) BuildID() string { 1470 return m.buildID 1471 } 1472 1473 // SourceLine reports the source line information for a given 1474 // address in the file. Due to inlining, the source line information 1475 // is in general a list of positions representing a call stack, 1476 // with the leaf function first. 1477 func (*mockFile) SourceLine(addr uint64) ([]plugin.Frame, error) { 1478 return nil, fmt.Errorf("unimplemented") 1479 } 1480 1481 // Symbols returns a list of symbols in the object file. 1482 // If r is not nil, Symbols restricts the list to symbols 1483 // with names matching the regular expression. 1484 // If addr is not zero, Symbols restricts the list to symbols 1485 // containing that address. 1486 func (m *mockFile) Symbols(r *regexp.Regexp, addr uint64) ([]*plugin.Sym, error) { 1487 switch r.String() { 1488 case "line[13]": 1489 return []*plugin.Sym{ 1490 {[]string{"line1000"}, m.name, 0x1000, 0x1003}, 1491 {[]string{"line3000"}, m.name, 0x3000, 0x3004}, 1492 }, nil 1493 } 1494 return nil, fmt.Errorf("unimplemented") 1495 } 1496 1497 // Close closes the file, releasing associated resources. 1498 func (*mockFile) Close() error { 1499 return nil 1500 } 1501