golang.org/toolchain@v0.0.1-go1.9rc2.windows-amd64/src/cmd/vendor/github.com/google/pprof/profile/profile_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 profile 16 17 import ( 18 "bytes" 19 "fmt" 20 "io/ioutil" 21 "path/filepath" 22 "regexp" 23 "strings" 24 "testing" 25 26 "github.com/google/pprof/internal/proftest" 27 ) 28 29 func TestParse(t *testing.T) { 30 const path = "testdata/" 31 32 for _, source := range []string{ 33 "go.crc32.cpu", 34 "go.godoc.thread", 35 "gobench.cpu", 36 "gobench.heap", 37 "cppbench.cpu", 38 "cppbench.heap", 39 "cppbench.contention", 40 "cppbench.growth", 41 "cppbench.thread", 42 "cppbench.thread.all", 43 "cppbench.thread.none", 44 "java.cpu", 45 "java.heap", 46 "java.contention", 47 } { 48 inbytes, err := ioutil.ReadFile(filepath.Join(path, source)) 49 if err != nil { 50 t.Fatal(err) 51 } 52 p, err := Parse(bytes.NewBuffer(inbytes)) 53 if err != nil { 54 t.Fatalf("%s: %s", source, err) 55 } 56 57 js := p.String() 58 goldFilename := path + source + ".string" 59 gold, err := ioutil.ReadFile(goldFilename) 60 if err != nil { 61 t.Fatalf("%s: %v", source, err) 62 } 63 64 if js != string(gold) { 65 t.Errorf("diff %s %s", source, goldFilename) 66 d, err := proftest.Diff(gold, []byte(js)) 67 if err != nil { 68 t.Fatalf("%s: %v", source, err) 69 } 70 t.Error(source + "\n" + string(d) + "\n" + "new profile at:\n" + leaveTempfile([]byte(js))) 71 } 72 73 // Reencode and decode. 74 bw := bytes.NewBuffer(nil) 75 if err := p.Write(bw); err != nil { 76 t.Fatalf("%s: %v", source, err) 77 } 78 if p, err = Parse(bw); err != nil { 79 t.Fatalf("%s: %v", source, err) 80 } 81 js2 := p.String() 82 if js2 != string(gold) { 83 d, err := proftest.Diff(gold, []byte(js2)) 84 if err != nil { 85 t.Fatalf("%s: %v", source, err) 86 } 87 t.Error(source + "\n" + string(d) + "\n" + "gold:\n" + goldFilename + 88 "\nnew profile at:\n" + leaveTempfile([]byte(js))) 89 } 90 } 91 } 92 93 func TestParseError(t *testing.T) { 94 95 testcases := []string{ 96 "", 97 "garbage text", 98 "\x1f\x8b", // truncated gzip header 99 "\x1f\x8b\x08\x08\xbe\xe9\x20\x58\x00\x03\x65\x6d\x70\x74\x79\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // empty gzipped file 100 } 101 102 for i, input := range testcases { 103 _, err := Parse(strings.NewReader(input)) 104 if err == nil { 105 t.Errorf("got nil, want error for input #%d", i) 106 } 107 } 108 } 109 110 // leaveTempfile leaves |b| in a temporary file on disk and returns the 111 // temp filename. This is useful to recover a profile when the test 112 // fails. 113 func leaveTempfile(b []byte) string { 114 f1, err := ioutil.TempFile("", "profile_test") 115 if err != nil { 116 panic(err) 117 } 118 if _, err := f1.Write(b); err != nil { 119 panic(err) 120 } 121 return f1.Name() 122 } 123 124 const mainBinary = "/bin/main" 125 126 var cpuM = []*Mapping{ 127 { 128 ID: 1, 129 Start: 0x10000, 130 Limit: 0x40000, 131 File: mainBinary, 132 HasFunctions: true, 133 HasFilenames: true, 134 HasLineNumbers: true, 135 HasInlineFrames: true, 136 }, 137 { 138 ID: 2, 139 Start: 0x1000, 140 Limit: 0x4000, 141 File: "/lib/lib.so", 142 HasFunctions: true, 143 HasFilenames: true, 144 HasLineNumbers: true, 145 HasInlineFrames: true, 146 }, 147 { 148 ID: 3, 149 Start: 0x4000, 150 Limit: 0x5000, 151 File: "/lib/lib2_c.so.6", 152 HasFunctions: true, 153 HasFilenames: true, 154 HasLineNumbers: true, 155 HasInlineFrames: true, 156 }, 157 { 158 ID: 4, 159 Start: 0x5000, 160 Limit: 0x9000, 161 File: "/lib/lib.so_6 (deleted)", 162 HasFunctions: true, 163 HasFilenames: true, 164 HasLineNumbers: true, 165 HasInlineFrames: true, 166 }, 167 } 168 169 var cpuF = []*Function{ 170 {ID: 1, Name: "main", SystemName: "main", Filename: "main.c"}, 171 {ID: 2, Name: "foo", SystemName: "foo", Filename: "foo.c"}, 172 {ID: 3, Name: "foo_caller", SystemName: "foo_caller", Filename: "foo.c"}, 173 } 174 175 var cpuL = []*Location{ 176 { 177 ID: 1000, 178 Mapping: cpuM[1], 179 Address: 0x1000, 180 Line: []Line{ 181 {Function: cpuF[0], Line: 1}, 182 }, 183 }, 184 { 185 ID: 2000, 186 Mapping: cpuM[0], 187 Address: 0x2000, 188 Line: []Line{ 189 {Function: cpuF[1], Line: 2}, 190 {Function: cpuF[2], Line: 1}, 191 }, 192 }, 193 { 194 ID: 3000, 195 Mapping: cpuM[0], 196 Address: 0x3000, 197 Line: []Line{ 198 {Function: cpuF[1], Line: 2}, 199 {Function: cpuF[2], Line: 1}, 200 }, 201 }, 202 { 203 ID: 3001, 204 Mapping: cpuM[0], 205 Address: 0x3001, 206 Line: []Line{ 207 {Function: cpuF[2], Line: 2}, 208 }, 209 }, 210 { 211 ID: 3002, 212 Mapping: cpuM[0], 213 Address: 0x3002, 214 Line: []Line{ 215 {Function: cpuF[2], Line: 3}, 216 }, 217 }, 218 } 219 220 var testProfile = &Profile{ 221 PeriodType: &ValueType{Type: "cpu", Unit: "milliseconds"}, 222 Period: 1, 223 DurationNanos: 10e9, 224 SampleType: []*ValueType{ 225 {Type: "samples", Unit: "count"}, 226 {Type: "cpu", Unit: "milliseconds"}, 227 }, 228 Sample: []*Sample{ 229 { 230 Location: []*Location{cpuL[0]}, 231 Value: []int64{1000, 1000}, 232 Label: map[string][]string{ 233 "key1": []string{"tag1"}, 234 "key2": []string{"tag1"}, 235 }, 236 }, 237 { 238 Location: []*Location{cpuL[1], cpuL[0]}, 239 Value: []int64{100, 100}, 240 Label: map[string][]string{ 241 "key1": []string{"tag2"}, 242 "key3": []string{"tag2"}, 243 }, 244 }, 245 { 246 Location: []*Location{cpuL[2], cpuL[0]}, 247 Value: []int64{10, 10}, 248 Label: map[string][]string{ 249 "key1": []string{"tag3"}, 250 "key2": []string{"tag2"}, 251 }, 252 }, 253 { 254 Location: []*Location{cpuL[3], cpuL[0]}, 255 Value: []int64{10000, 10000}, 256 Label: map[string][]string{ 257 "key1": []string{"tag4"}, 258 "key2": []string{"tag1"}, 259 }, 260 }, 261 { 262 Location: []*Location{cpuL[4], cpuL[0]}, 263 Value: []int64{1, 1}, 264 Label: map[string][]string{ 265 "key1": []string{"tag4"}, 266 "key2": []string{"tag1"}, 267 }, 268 }, 269 }, 270 Location: cpuL, 271 Function: cpuF, 272 Mapping: cpuM, 273 } 274 275 var aggTests = map[string]aggTest{ 276 "precise": aggTest{true, true, true, true, 5}, 277 "fileline": aggTest{false, true, true, true, 4}, 278 "inline_function": aggTest{false, true, false, true, 3}, 279 "function": aggTest{false, true, false, false, 2}, 280 } 281 282 type aggTest struct { 283 precise, function, fileline, inlineFrame bool 284 rows int 285 } 286 287 const totalSamples = int64(11111) 288 289 func TestAggregation(t *testing.T) { 290 prof := testProfile.Copy() 291 for _, resolution := range []string{"precise", "fileline", "inline_function", "function"} { 292 a := aggTests[resolution] 293 if !a.precise { 294 if err := prof.Aggregate(a.inlineFrame, a.function, a.fileline, a.fileline, false); err != nil { 295 t.Error("aggregating to " + resolution + ":" + err.Error()) 296 } 297 } 298 if err := checkAggregation(prof, &a); err != nil { 299 t.Error("failed aggregation to " + resolution + ": " + err.Error()) 300 } 301 } 302 } 303 304 // checkAggregation verifies that the profile remained consistent 305 // with its aggregation. 306 func checkAggregation(prof *Profile, a *aggTest) error { 307 // Check that the total number of samples for the rows was preserved. 308 total := int64(0) 309 310 samples := make(map[string]bool) 311 for _, sample := range prof.Sample { 312 tb := locationHash(sample) 313 samples[tb] = true 314 total += sample.Value[0] 315 } 316 317 if total != totalSamples { 318 return fmt.Errorf("sample total %d, want %d", total, totalSamples) 319 } 320 321 // Check the number of unique sample locations 322 if a.rows != len(samples) { 323 return fmt.Errorf("number of samples %d, want %d", len(samples), a.rows) 324 } 325 326 // Check that all mappings have the right detail flags. 327 for _, m := range prof.Mapping { 328 if m.HasFunctions != a.function { 329 return fmt.Errorf("unexpected mapping.HasFunctions %v, want %v", m.HasFunctions, a.function) 330 } 331 if m.HasFilenames != a.fileline { 332 return fmt.Errorf("unexpected mapping.HasFilenames %v, want %v", m.HasFilenames, a.fileline) 333 } 334 if m.HasLineNumbers != a.fileline { 335 return fmt.Errorf("unexpected mapping.HasLineNumbers %v, want %v", m.HasLineNumbers, a.fileline) 336 } 337 if m.HasInlineFrames != a.inlineFrame { 338 return fmt.Errorf("unexpected mapping.HasInlineFrames %v, want %v", m.HasInlineFrames, a.inlineFrame) 339 } 340 } 341 342 // Check that aggregation has removed finer resolution data. 343 for _, l := range prof.Location { 344 if !a.inlineFrame && len(l.Line) > 1 { 345 return fmt.Errorf("found %d lines on location %d, want 1", len(l.Line), l.ID) 346 } 347 348 for _, ln := range l.Line { 349 if !a.fileline && (ln.Function.Filename != "" || ln.Line != 0) { 350 return fmt.Errorf("found line %s:%d on location %d, want :0", 351 ln.Function.Filename, ln.Line, l.ID) 352 } 353 if !a.function && (ln.Function.Name != "") { 354 return fmt.Errorf(`found file %s location %d, want ""`, 355 ln.Function.Name, l.ID) 356 } 357 } 358 } 359 360 return nil 361 } 362 363 // Test merge leaves the main binary in place. 364 func TestMergeMain(t *testing.T) { 365 prof := testProfile.Copy() 366 p1, err := Merge([]*Profile{prof}) 367 if err != nil { 368 t.Fatalf("merge error: %v", err) 369 } 370 if cpuM[0].File != p1.Mapping[0].File { 371 t.Errorf("want Mapping[0]=%s got %s", cpuM[0].File, p1.Mapping[0].File) 372 } 373 } 374 375 func TestMerge(t *testing.T) { 376 // Aggregate a profile with itself and once again with a factor of 377 // -2. Should end up with an empty profile (all samples for a 378 // location should add up to 0). 379 380 prof := testProfile.Copy() 381 p1, err := Merge([]*Profile{prof, prof}) 382 if err != nil { 383 t.Errorf("merge error: %v", err) 384 } 385 prof.Scale(-2) 386 prof, err = Merge([]*Profile{p1, prof}) 387 if err != nil { 388 t.Errorf("merge error: %v", err) 389 } 390 391 // Use aggregation to merge locations at function granularity. 392 if err := prof.Aggregate(false, true, false, false, false); err != nil { 393 t.Errorf("aggregating after merge: %v", err) 394 } 395 396 samples := make(map[string]int64) 397 for _, s := range prof.Sample { 398 tb := locationHash(s) 399 samples[tb] = samples[tb] + s.Value[0] 400 } 401 for s, v := range samples { 402 if v != 0 { 403 t.Errorf("nonzero value for sample %s: %d", s, v) 404 } 405 } 406 } 407 408 func TestMergeAll(t *testing.T) { 409 // Aggregate 10 copies of the profile. 410 profs := make([]*Profile, 10) 411 for i := 0; i < 10; i++ { 412 profs[i] = testProfile.Copy() 413 } 414 prof, err := Merge(profs) 415 if err != nil { 416 t.Errorf("merge error: %v", err) 417 } 418 samples := make(map[string]int64) 419 for _, s := range prof.Sample { 420 tb := locationHash(s) 421 samples[tb] = samples[tb] + s.Value[0] 422 } 423 for _, s := range testProfile.Sample { 424 tb := locationHash(s) 425 if samples[tb] != s.Value[0]*10 { 426 t.Errorf("merge got wrong value at %s : %d instead of %d", tb, samples[tb], s.Value[0]*10) 427 } 428 } 429 } 430 431 func TestFilter(t *testing.T) { 432 // Perform several forms of filtering on the test profile. 433 434 type filterTestcase struct { 435 focus, ignore, hide, show *regexp.Regexp 436 fm, im, hm, hnm bool 437 } 438 439 for tx, tc := range []filterTestcase{ 440 {nil, nil, nil, nil, true, false, false, false}, 441 {regexp.MustCompile("notfound"), nil, nil, nil, false, false, false, false}, 442 {nil, regexp.MustCompile("foo.c"), nil, nil, true, true, false, false}, 443 {nil, nil, regexp.MustCompile("lib.so"), nil, true, false, true, false}, 444 } { 445 prof := *testProfile.Copy() 446 gf, gi, gh, gnh := prof.FilterSamplesByName(tc.focus, tc.ignore, tc.hide, tc.show) 447 if gf != tc.fm { 448 t.Errorf("Filter #%d, got fm=%v, want %v", tx, gf, tc.fm) 449 } 450 if gi != tc.im { 451 t.Errorf("Filter #%d, got im=%v, want %v", tx, gi, tc.im) 452 } 453 if gh != tc.hm { 454 t.Errorf("Filter #%d, got hm=%v, want %v", tx, gh, tc.hm) 455 } 456 if gnh != tc.hnm { 457 t.Errorf("Filter #%d, got hnm=%v, want %v", tx, gnh, tc.hnm) 458 } 459 } 460 } 461 462 func TestTagFilter(t *testing.T) { 463 // Perform several forms of tag filtering on the test profile. 464 465 type filterTestcase struct { 466 include, exclude *regexp.Regexp 467 im, em bool 468 count int 469 } 470 471 countTags := func(p *Profile) map[string]bool { 472 tags := make(map[string]bool) 473 474 for _, s := range p.Sample { 475 for l := range s.Label { 476 tags[l] = true 477 } 478 for l := range s.NumLabel { 479 tags[l] = true 480 } 481 } 482 return tags 483 } 484 485 for tx, tc := range []filterTestcase{ 486 {nil, nil, true, false, 3}, 487 {regexp.MustCompile("notfound"), nil, false, false, 0}, 488 {regexp.MustCompile("key1"), nil, true, false, 1}, 489 {nil, regexp.MustCompile("key[12]"), true, true, 1}, 490 } { 491 prof := testProfile.Copy() 492 gim, gem := prof.FilterTagsByName(tc.include, tc.exclude) 493 if gim != tc.im { 494 t.Errorf("Filter #%d, got include match=%v, want %v", tx, gim, tc.im) 495 } 496 if gem != tc.em { 497 t.Errorf("Filter #%d, got exclude match=%v, want %v", tx, gem, tc.em) 498 } 499 if tags := countTags(prof); len(tags) != tc.count { 500 t.Errorf("Filter #%d, got %d tags[%v], want %d", tx, len(tags), tags, tc.count) 501 } 502 } 503 } 504 505 // locationHash constructs a string to use as a hashkey for a sample, based on its locations 506 func locationHash(s *Sample) string { 507 var tb string 508 for _, l := range s.Location { 509 for _, ln := range l.Line { 510 tb = tb + fmt.Sprintf("%s:%d@%d ", ln.Function.Name, ln.Line, l.Address) 511 } 512 } 513 return tb 514 } 515 516 func TestSetMain(t *testing.T) { 517 testProfile.massageMappings() 518 if testProfile.Mapping[0].File != mainBinary { 519 t.Errorf("got %s for main", testProfile.Mapping[0].File) 520 } 521 }