golang.org/toolchain@v0.0.1-go1.9rc2.windows-amd64/src/cmd/vendor/github.com/google/pprof/profile/profile.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 provides a representation of profile.proto and 16 // methods to encode/decode profiles in this format. 17 package profile 18 19 import ( 20 "bytes" 21 "compress/gzip" 22 "fmt" 23 "io" 24 "io/ioutil" 25 "path/filepath" 26 "regexp" 27 "sort" 28 "strings" 29 "time" 30 ) 31 32 // Profile is an in-memory representation of profile.proto. 33 type Profile struct { 34 SampleType []*ValueType 35 DefaultSampleType string 36 Sample []*Sample 37 Mapping []*Mapping 38 Location []*Location 39 Function []*Function 40 Comments []string 41 42 DropFrames string 43 KeepFrames string 44 45 TimeNanos int64 46 DurationNanos int64 47 PeriodType *ValueType 48 Period int64 49 50 commentX []int64 51 dropFramesX int64 52 keepFramesX int64 53 stringTable []string 54 defaultSampleTypeX int64 55 } 56 57 // ValueType corresponds to Profile.ValueType 58 type ValueType struct { 59 Type string // cpu, wall, inuse_space, etc 60 Unit string // seconds, nanoseconds, bytes, etc 61 62 typeX int64 63 unitX int64 64 } 65 66 // Sample corresponds to Profile.Sample 67 type Sample struct { 68 Location []*Location 69 Value []int64 70 Label map[string][]string 71 NumLabel map[string][]int64 72 73 locationIDX []uint64 74 labelX []label 75 } 76 77 // label corresponds to Profile.Label 78 type label struct { 79 keyX int64 80 // Exactly one of the two following values must be set 81 strX int64 82 numX int64 // Integer value for this label 83 } 84 85 // Mapping corresponds to Profile.Mapping 86 type Mapping struct { 87 ID uint64 88 Start uint64 89 Limit uint64 90 Offset uint64 91 File string 92 BuildID string 93 HasFunctions bool 94 HasFilenames bool 95 HasLineNumbers bool 96 HasInlineFrames bool 97 98 fileX int64 99 buildIDX int64 100 } 101 102 // Location corresponds to Profile.Location 103 type Location struct { 104 ID uint64 105 Mapping *Mapping 106 Address uint64 107 Line []Line 108 109 mappingIDX uint64 110 } 111 112 // Line corresponds to Profile.Line 113 type Line struct { 114 Function *Function 115 Line int64 116 117 functionIDX uint64 118 } 119 120 // Function corresponds to Profile.Function 121 type Function struct { 122 ID uint64 123 Name string 124 SystemName string 125 Filename string 126 StartLine int64 127 128 nameX int64 129 systemNameX int64 130 filenameX int64 131 } 132 133 // Parse parses a profile and checks for its validity. The input 134 // may be a gzip-compressed encoded protobuf or one of many legacy 135 // profile formats which may be unsupported in the future. 136 func Parse(r io.Reader) (*Profile, error) { 137 data, err := ioutil.ReadAll(r) 138 if err != nil { 139 return nil, err 140 } 141 return ParseData(data) 142 } 143 144 // ParseData parses a profile from a buffer and checks for its 145 // validity. 146 func ParseData(data []byte) (*Profile, error) { 147 var p *Profile 148 var err error 149 if len(data) >= 2 && data[0] == 0x1f && data[1] == 0x8b { 150 gz, err := gzip.NewReader(bytes.NewBuffer(data)) 151 if err == nil { 152 data, err = ioutil.ReadAll(gz) 153 } 154 if err != nil { 155 return nil, fmt.Errorf("decompressing profile: %v", err) 156 } 157 } 158 if p, err = ParseUncompressed(data); err != nil && err != errNoData { 159 p, err = parseLegacy(data) 160 } 161 162 if err != nil { 163 return nil, fmt.Errorf("parsing profile: %v", err) 164 } 165 166 if err := p.CheckValid(); err != nil { 167 return nil, fmt.Errorf("malformed profile: %v", err) 168 } 169 return p, nil 170 } 171 172 var errUnrecognized = fmt.Errorf("unrecognized profile format") 173 var errMalformed = fmt.Errorf("malformed profile format") 174 var errNoData = fmt.Errorf("empty input file") 175 176 func parseLegacy(data []byte) (*Profile, error) { 177 parsers := []func([]byte) (*Profile, error){ 178 parseCPU, 179 parseHeap, 180 parseGoCount, // goroutine, threadcreate 181 parseThread, 182 parseContention, 183 parseJavaProfile, 184 } 185 186 for _, parser := range parsers { 187 p, err := parser(data) 188 if err == nil { 189 p.addLegacyFrameInfo() 190 return p, nil 191 } 192 if err != errUnrecognized { 193 return nil, err 194 } 195 } 196 return nil, errUnrecognized 197 } 198 199 // ParseUncompressed parses an uncompressed protobuf into a profile. 200 func ParseUncompressed(data []byte) (*Profile, error) { 201 if len(data) == 0 { 202 return nil, errNoData 203 } 204 p := &Profile{} 205 if err := unmarshal(data, p); err != nil { 206 return nil, err 207 } 208 209 if err := p.postDecode(); err != nil { 210 return nil, err 211 } 212 213 return p, nil 214 } 215 216 var libRx = regexp.MustCompile(`([.]so$|[.]so[._][0-9]+)`) 217 218 // massageMappings applies heuristic-based changes to the profile 219 // mappings to account for quirks of some environments. 220 func (p *Profile) massageMappings() { 221 // Merge adjacent regions with matching names, checking that the offsets match 222 if len(p.Mapping) > 1 { 223 mappings := []*Mapping{p.Mapping[0]} 224 for _, m := range p.Mapping[1:] { 225 lm := mappings[len(mappings)-1] 226 if adjacent(lm, m) { 227 lm.Limit = m.Limit 228 if m.File != "" { 229 lm.File = m.File 230 } 231 if m.BuildID != "" { 232 lm.BuildID = m.BuildID 233 } 234 p.updateLocationMapping(m, lm) 235 continue 236 } 237 mappings = append(mappings, m) 238 } 239 p.Mapping = mappings 240 } 241 242 // Use heuristics to identify main binary and move it to the top of the list of mappings 243 for i, m := range p.Mapping { 244 file := strings.TrimSpace(strings.Replace(m.File, "(deleted)", "", -1)) 245 if len(file) == 0 { 246 continue 247 } 248 if len(libRx.FindStringSubmatch(file)) > 0 { 249 continue 250 } 251 if file[0] == '[' { 252 continue 253 } 254 // Swap what we guess is main to position 0. 255 p.Mapping[0], p.Mapping[i] = p.Mapping[i], p.Mapping[0] 256 break 257 } 258 259 // Keep the mapping IDs neatly sorted 260 for i, m := range p.Mapping { 261 m.ID = uint64(i + 1) 262 } 263 } 264 265 // adjacent returns whether two mapping entries represent the same 266 // mapping that has been split into two. Check that their addresses are adjacent, 267 // and if the offsets match, if they are available. 268 func adjacent(m1, m2 *Mapping) bool { 269 if m1.File != "" && m2.File != "" { 270 if m1.File != m2.File { 271 return false 272 } 273 } 274 if m1.BuildID != "" && m2.BuildID != "" { 275 if m1.BuildID != m2.BuildID { 276 return false 277 } 278 } 279 if m1.Limit != m2.Start { 280 return false 281 } 282 if m1.Offset != 0 && m2.Offset != 0 { 283 offset := m1.Offset + (m1.Limit - m1.Start) 284 if offset != m2.Offset { 285 return false 286 } 287 } 288 return true 289 } 290 291 func (p *Profile) updateLocationMapping(from, to *Mapping) { 292 for _, l := range p.Location { 293 if l.Mapping == from { 294 l.Mapping = to 295 } 296 } 297 } 298 299 // Write writes the profile as a gzip-compressed marshaled protobuf. 300 func (p *Profile) Write(w io.Writer) error { 301 p.preEncode() 302 b := marshal(p) 303 zw := gzip.NewWriter(w) 304 defer zw.Close() 305 _, err := zw.Write(b) 306 return err 307 } 308 309 // WriteUncompressed writes the profile as a marshaled protobuf. 310 func (p *Profile) WriteUncompressed(w io.Writer) error { 311 p.preEncode() 312 b := marshal(p) 313 _, err := w.Write(b) 314 return err 315 } 316 317 // CheckValid tests whether the profile is valid. Checks include, but are 318 // not limited to: 319 // - len(Profile.Sample[n].value) == len(Profile.value_unit) 320 // - Sample.id has a corresponding Profile.Location 321 func (p *Profile) CheckValid() error { 322 // Check that sample values are consistent 323 sampleLen := len(p.SampleType) 324 if sampleLen == 0 && len(p.Sample) != 0 { 325 return fmt.Errorf("missing sample type information") 326 } 327 for _, s := range p.Sample { 328 if len(s.Value) != sampleLen { 329 return fmt.Errorf("mismatch: sample has: %d values vs. %d types", len(s.Value), len(p.SampleType)) 330 } 331 for _, l := range s.Location { 332 if l == nil { 333 return fmt.Errorf("sample has nil location") 334 } 335 } 336 } 337 338 // Check that all mappings/locations/functions are in the tables 339 // Check that there are no duplicate ids 340 mappings := make(map[uint64]*Mapping, len(p.Mapping)) 341 for _, m := range p.Mapping { 342 if m.ID == 0 { 343 return fmt.Errorf("found mapping with reserved ID=0") 344 } 345 if mappings[m.ID] != nil { 346 return fmt.Errorf("multiple mappings with same id: %d", m.ID) 347 } 348 mappings[m.ID] = m 349 } 350 functions := make(map[uint64]*Function, len(p.Function)) 351 for _, f := range p.Function { 352 if f.ID == 0 { 353 return fmt.Errorf("found function with reserved ID=0") 354 } 355 if functions[f.ID] != nil { 356 return fmt.Errorf("multiple functions with same id: %d", f.ID) 357 } 358 functions[f.ID] = f 359 } 360 locations := make(map[uint64]*Location, len(p.Location)) 361 for _, l := range p.Location { 362 if l.ID == 0 { 363 return fmt.Errorf("found location with reserved id=0") 364 } 365 if locations[l.ID] != nil { 366 return fmt.Errorf("multiple locations with same id: %d", l.ID) 367 } 368 locations[l.ID] = l 369 if m := l.Mapping; m != nil { 370 if m.ID == 0 || mappings[m.ID] != m { 371 return fmt.Errorf("inconsistent mapping %p: %d", m, m.ID) 372 } 373 } 374 for _, ln := range l.Line { 375 if f := ln.Function; f != nil { 376 if f.ID == 0 || functions[f.ID] != f { 377 return fmt.Errorf("inconsistent function %p: %d", f, f.ID) 378 } 379 } 380 } 381 } 382 return nil 383 } 384 385 // Aggregate merges the locations in the profile into equivalence 386 // classes preserving the request attributes. It also updates the 387 // samples to point to the merged locations. 388 func (p *Profile) Aggregate(inlineFrame, function, filename, linenumber, address bool) error { 389 for _, m := range p.Mapping { 390 m.HasInlineFrames = m.HasInlineFrames && inlineFrame 391 m.HasFunctions = m.HasFunctions && function 392 m.HasFilenames = m.HasFilenames && filename 393 m.HasLineNumbers = m.HasLineNumbers && linenumber 394 } 395 396 // Aggregate functions 397 if !function || !filename { 398 for _, f := range p.Function { 399 if !function { 400 f.Name = "" 401 f.SystemName = "" 402 } 403 if !filename { 404 f.Filename = "" 405 } 406 } 407 } 408 409 // Aggregate locations 410 if !inlineFrame || !address || !linenumber { 411 for _, l := range p.Location { 412 if !inlineFrame && len(l.Line) > 1 { 413 l.Line = l.Line[len(l.Line)-1:] 414 } 415 if !linenumber { 416 for i := range l.Line { 417 l.Line[i].Line = 0 418 } 419 } 420 if !address { 421 l.Address = 0 422 } 423 } 424 } 425 426 return p.CheckValid() 427 } 428 429 // String dumps a text representation of a profile. Intended mainly 430 // for debugging purposes. 431 func (p *Profile) String() string { 432 ss := make([]string, 0, len(p.Comments)+len(p.Sample)+len(p.Mapping)+len(p.Location)) 433 for _, c := range p.Comments { 434 ss = append(ss, "Comment: "+c) 435 } 436 if pt := p.PeriodType; pt != nil { 437 ss = append(ss, fmt.Sprintf("PeriodType: %s %s", pt.Type, pt.Unit)) 438 } 439 ss = append(ss, fmt.Sprintf("Period: %d", p.Period)) 440 if p.TimeNanos != 0 { 441 ss = append(ss, fmt.Sprintf("Time: %v", time.Unix(0, p.TimeNanos))) 442 } 443 if p.DurationNanos != 0 { 444 ss = append(ss, fmt.Sprintf("Duration: %.4v", time.Duration(p.DurationNanos))) 445 } 446 447 ss = append(ss, "Samples:") 448 var sh1 string 449 for _, s := range p.SampleType { 450 dflt := "" 451 if s.Type == p.DefaultSampleType { 452 dflt = "[dflt]" 453 } 454 sh1 = sh1 + fmt.Sprintf("%s/%s%s ", s.Type, s.Unit, dflt) 455 } 456 ss = append(ss, strings.TrimSpace(sh1)) 457 for _, s := range p.Sample { 458 var sv string 459 for _, v := range s.Value { 460 sv = fmt.Sprintf("%s %10d", sv, v) 461 } 462 sv = sv + ": " 463 for _, l := range s.Location { 464 sv = sv + fmt.Sprintf("%d ", l.ID) 465 } 466 ss = append(ss, sv) 467 const labelHeader = " " 468 if len(s.Label) > 0 { 469 ls := []string{} 470 for k, v := range s.Label { 471 ls = append(ls, fmt.Sprintf("%s:%v", k, v)) 472 } 473 sort.Strings(ls) 474 ss = append(ss, labelHeader+strings.Join(ls, " ")) 475 } 476 if len(s.NumLabel) > 0 { 477 ls := []string{} 478 for k, v := range s.NumLabel { 479 ls = append(ls, fmt.Sprintf("%s:%v", k, v)) 480 } 481 sort.Strings(ls) 482 ss = append(ss, labelHeader+strings.Join(ls, " ")) 483 } 484 } 485 486 ss = append(ss, "Locations") 487 for _, l := range p.Location { 488 locStr := fmt.Sprintf("%6d: %#x ", l.ID, l.Address) 489 if m := l.Mapping; m != nil { 490 locStr = locStr + fmt.Sprintf("M=%d ", m.ID) 491 } 492 if len(l.Line) == 0 { 493 ss = append(ss, locStr) 494 } 495 for li := range l.Line { 496 lnStr := "??" 497 if fn := l.Line[li].Function; fn != nil { 498 lnStr = fmt.Sprintf("%s %s:%d s=%d", 499 fn.Name, 500 fn.Filename, 501 l.Line[li].Line, 502 fn.StartLine) 503 if fn.Name != fn.SystemName { 504 lnStr = lnStr + "(" + fn.SystemName + ")" 505 } 506 } 507 ss = append(ss, locStr+lnStr) 508 // Do not print location details past the first line 509 locStr = " " 510 } 511 } 512 513 ss = append(ss, "Mappings") 514 for _, m := range p.Mapping { 515 bits := "" 516 if m.HasFunctions { 517 bits = bits + "[FN]" 518 } 519 if m.HasFilenames { 520 bits = bits + "[FL]" 521 } 522 if m.HasLineNumbers { 523 bits = bits + "[LN]" 524 } 525 if m.HasInlineFrames { 526 bits = bits + "[IN]" 527 } 528 ss = append(ss, fmt.Sprintf("%d: %#x/%#x/%#x %s %s %s", 529 m.ID, 530 m.Start, m.Limit, m.Offset, 531 m.File, 532 m.BuildID, 533 bits)) 534 } 535 536 return strings.Join(ss, "\n") + "\n" 537 } 538 539 // Scale multiplies all sample values in a profile by a constant. 540 func (p *Profile) Scale(ratio float64) { 541 if ratio == 1 { 542 return 543 } 544 ratios := make([]float64, len(p.SampleType)) 545 for i := range p.SampleType { 546 ratios[i] = ratio 547 } 548 p.ScaleN(ratios) 549 } 550 551 // ScaleN multiplies each sample values in a sample by a different amount. 552 func (p *Profile) ScaleN(ratios []float64) error { 553 if len(p.SampleType) != len(ratios) { 554 return fmt.Errorf("mismatched scale ratios, got %d, want %d", len(ratios), len(p.SampleType)) 555 } 556 allOnes := true 557 for _, r := range ratios { 558 if r != 1 { 559 allOnes = false 560 break 561 } 562 } 563 if allOnes { 564 return nil 565 } 566 for _, s := range p.Sample { 567 for i, v := range s.Value { 568 if ratios[i] != 1 { 569 s.Value[i] = int64(float64(v) * ratios[i]) 570 } 571 } 572 } 573 return nil 574 } 575 576 // HasFunctions determines if all locations in this profile have 577 // symbolized function information. 578 func (p *Profile) HasFunctions() bool { 579 for _, l := range p.Location { 580 if l.Mapping != nil && !l.Mapping.HasFunctions { 581 return false 582 } 583 } 584 return true 585 } 586 587 // HasFileLines determines if all locations in this profile have 588 // symbolized file and line number information. 589 func (p *Profile) HasFileLines() bool { 590 for _, l := range p.Location { 591 if l.Mapping != nil && (!l.Mapping.HasFilenames || !l.Mapping.HasLineNumbers) { 592 return false 593 } 594 } 595 return true 596 } 597 598 // Unsymbolizable returns true if a mapping points to a binary for which 599 // locations can't be symbolized in principle, at least now. 600 func (m *Mapping) Unsymbolizable() bool { 601 name := filepath.Base(m.File) 602 return name == "[vdso]" || strings.HasPrefix(name, "linux-vdso") || name == "[heap]" || strings.HasPrefix(m.File, "/dev/dri/") 603 } 604 605 // Copy makes a fully independent copy of a profile. 606 func (p *Profile) Copy() *Profile { 607 p.preEncode() 608 b := marshal(p) 609 610 pp := &Profile{} 611 if err := unmarshal(b, pp); err != nil { 612 panic(err) 613 } 614 if err := pp.postDecode(); err != nil { 615 panic(err) 616 } 617 618 return pp 619 }