github.com/razvanm/vanadium-go-1.3@v0.0.0-20160721203343-4a65068e5915/src/cmd/pprof/internal/profile/profile.go (about) 1 // Copyright 2014 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Package profile provides a representation of profile.proto and 6 // methods to encode/decode profiles in this format. 7 package profile 8 9 import ( 10 "bytes" 11 "compress/gzip" 12 "fmt" 13 "io" 14 "io/ioutil" 15 "regexp" 16 "strings" 17 "time" 18 ) 19 20 // Profile is an in-memory representation of profile.proto. 21 type Profile struct { 22 SampleType []*ValueType 23 Sample []*Sample 24 Mapping []*Mapping 25 Location []*Location 26 Function []*Function 27 28 DropFrames string 29 KeepFrames string 30 31 TimeNanos int64 32 DurationNanos int64 33 PeriodType *ValueType 34 Period int64 35 36 dropFramesX int64 37 keepFramesX int64 38 stringTable []string 39 } 40 41 // ValueType corresponds to Profile.ValueType 42 type ValueType struct { 43 Type string // cpu, wall, inuse_space, etc 44 Unit string // seconds, nanoseconds, bytes, etc 45 46 typeX int64 47 unitX int64 48 } 49 50 // Sample corresponds to Profile.Sample 51 type Sample struct { 52 Location []*Location 53 Value []int64 54 Label map[string][]string 55 NumLabel map[string][]int64 56 57 locationIDX []uint64 58 labelX []Label 59 } 60 61 // Label corresponds to Profile.Label 62 type Label struct { 63 keyX int64 64 // Exactly one of the two following values must be set 65 strX int64 66 numX int64 // Integer value for this label 67 } 68 69 // Mapping corresponds to Profile.Mapping 70 type Mapping struct { 71 ID uint64 72 Start uint64 73 Limit uint64 74 Offset uint64 75 File string 76 BuildID string 77 HasFunctions bool 78 HasFilenames bool 79 HasLineNumbers bool 80 HasInlineFrames bool 81 82 fileX int64 83 buildIDX int64 84 } 85 86 // Location corresponds to Profile.Location 87 type Location struct { 88 ID uint64 89 Mapping *Mapping 90 Address uint64 91 Line []Line 92 93 mappingIDX uint64 94 } 95 96 // Line corresponds to Profile.Line 97 type Line struct { 98 Function *Function 99 Line int64 100 101 functionIDX uint64 102 } 103 104 // Function corresponds to Profile.Function 105 type Function struct { 106 ID uint64 107 Name string 108 SystemName string 109 Filename string 110 StartLine int64 111 112 nameX int64 113 systemNameX int64 114 filenameX int64 115 } 116 117 // Parse parses a profile and checks for its validity. The input 118 // may be a gzip-compressed encoded protobuf or one of many legacy 119 // profile formats which may be unsupported in the future. 120 func Parse(r io.Reader) (*Profile, error) { 121 orig, err := ioutil.ReadAll(r) 122 if err != nil { 123 return nil, err 124 } 125 126 var p *Profile 127 if len(orig) >= 2 && orig[0] == 0x1f && orig[1] == 0x8b { 128 var data []byte 129 130 if gz, err := gzip.NewReader(bytes.NewBuffer(orig)); err == nil { 131 data, err = ioutil.ReadAll(gz) 132 } 133 if err != nil { 134 return nil, fmt.Errorf("decompressing profile: %v", err) 135 } 136 orig = data 137 } 138 if p, err = parseUncompressed(orig); err != nil { 139 if p, err = parseLegacy(orig); err != nil { 140 return nil, fmt.Errorf("parsing profile: %v", err) 141 } 142 } 143 144 if err := p.CheckValid(); err != nil { 145 return nil, fmt.Errorf("malformed profile: %v", err) 146 } 147 return p, nil 148 } 149 150 var errUnrecognized = fmt.Errorf("unrecognized profile format") 151 var errMalformed = fmt.Errorf("malformed profile format") 152 153 func parseLegacy(data []byte) (*Profile, error) { 154 parsers := []func([]byte) (*Profile, error){ 155 parseCPU, 156 parseHeap, 157 parseGoCount, // goroutine, threadcreate 158 parseThread, 159 parseContention, 160 } 161 162 for _, parser := range parsers { 163 p, err := parser(data) 164 if err == nil { 165 p.setMain() 166 p.addLegacyFrameInfo() 167 return p, nil 168 } 169 if err != errUnrecognized { 170 return nil, err 171 } 172 } 173 return nil, errUnrecognized 174 } 175 176 func parseUncompressed(data []byte) (*Profile, error) { 177 p := &Profile{} 178 if err := unmarshal(data, p); err != nil { 179 return nil, err 180 } 181 182 if err := p.postDecode(); err != nil { 183 return nil, err 184 } 185 186 return p, nil 187 } 188 189 var libRx = regexp.MustCompile(`([.]so$|[.]so[._][0-9]+)`) 190 191 // setMain scans Mapping entries and guesses which entry is main 192 // because legacy profiles don't obey the convention of putting main 193 // first. 194 func (p *Profile) setMain() { 195 for i := 0; i < len(p.Mapping); i++ { 196 file := strings.TrimSpace(strings.Replace(p.Mapping[i].File, "(deleted)", "", -1)) 197 if len(file) == 0 { 198 continue 199 } 200 if len(libRx.FindStringSubmatch(file)) > 0 { 201 continue 202 } 203 if strings.HasPrefix(file, "[") { 204 continue 205 } 206 // Swap what we guess is main to position 0. 207 tmp := p.Mapping[i] 208 p.Mapping[i] = p.Mapping[0] 209 p.Mapping[0] = tmp 210 break 211 } 212 } 213 214 // Write writes the profile as a gzip-compressed marshaled protobuf. 215 func (p *Profile) Write(w io.Writer) error { 216 p.preEncode() 217 b := marshal(p) 218 zw := gzip.NewWriter(w) 219 defer zw.Close() 220 _, err := zw.Write(b) 221 return err 222 } 223 224 // CheckValid tests whether the profile is valid. Checks include, but are 225 // not limited to: 226 // - len(Profile.Sample[n].value) == len(Profile.value_unit) 227 // - Sample.id has a corresponding Profile.Location 228 func (p *Profile) CheckValid() error { 229 // Check that sample values are consistent 230 sampleLen := len(p.SampleType) 231 if sampleLen == 0 && len(p.Sample) != 0 { 232 return fmt.Errorf("missing sample type information") 233 } 234 for _, s := range p.Sample { 235 if len(s.Value) != sampleLen { 236 return fmt.Errorf("mismatch: sample has: %d values vs. %d types", len(s.Value), len(p.SampleType)) 237 } 238 } 239 240 // Check that all mappings/locations/functions are in the tables 241 // Check that there are no duplicate ids 242 mappings := make(map[uint64]*Mapping, len(p.Mapping)) 243 for _, m := range p.Mapping { 244 if m.ID == 0 { 245 return fmt.Errorf("found mapping with reserved ID=0") 246 } 247 if mappings[m.ID] != nil { 248 return fmt.Errorf("multiple mappings with same id: %d", m.ID) 249 } 250 mappings[m.ID] = m 251 } 252 functions := make(map[uint64]*Function, len(p.Function)) 253 for _, f := range p.Function { 254 if f.ID == 0 { 255 return fmt.Errorf("found function with reserved ID=0") 256 } 257 if functions[f.ID] != nil { 258 return fmt.Errorf("multiple functions with same id: %d", f.ID) 259 } 260 functions[f.ID] = f 261 } 262 locations := make(map[uint64]*Location, len(p.Location)) 263 for _, l := range p.Location { 264 if l.ID == 0 { 265 return fmt.Errorf("found location with reserved id=0") 266 } 267 if locations[l.ID] != nil { 268 return fmt.Errorf("multiple locations with same id: %d", l.ID) 269 } 270 locations[l.ID] = l 271 if m := l.Mapping; m != nil { 272 if m.ID == 0 || mappings[m.ID] != m { 273 return fmt.Errorf("inconsistent mapping %p: %d", m, m.ID) 274 } 275 } 276 for _, ln := range l.Line { 277 if f := ln.Function; f != nil { 278 if f.ID == 0 || functions[f.ID] != f { 279 return fmt.Errorf("inconsistent function %p: %d", f, f.ID) 280 } 281 } 282 } 283 } 284 return nil 285 } 286 287 // Aggregate merges the locations in the profile into equivalence 288 // classes preserving the request attributes. It also updates the 289 // samples to point to the merged locations. 290 func (p *Profile) Aggregate(inlineFrame, function, filename, linenumber, address bool) error { 291 for _, m := range p.Mapping { 292 m.HasInlineFrames = m.HasInlineFrames && inlineFrame 293 m.HasFunctions = m.HasFunctions && function 294 m.HasFilenames = m.HasFilenames && filename 295 m.HasLineNumbers = m.HasLineNumbers && linenumber 296 } 297 298 // Aggregate functions 299 if !function || !filename { 300 for _, f := range p.Function { 301 if !function { 302 f.Name = "" 303 f.SystemName = "" 304 } 305 if !filename { 306 f.Filename = "" 307 } 308 } 309 } 310 311 // Aggregate locations 312 if !inlineFrame || !address || !linenumber { 313 for _, l := range p.Location { 314 if !inlineFrame && len(l.Line) > 1 { 315 l.Line = l.Line[len(l.Line)-1:] 316 } 317 if !linenumber { 318 for i := range l.Line { 319 l.Line[i].Line = 0 320 } 321 } 322 if !address { 323 l.Address = 0 324 } 325 } 326 } 327 328 return p.CheckValid() 329 } 330 331 // Print dumps a text representation of a profile. Intended mainly 332 // for debugging purposes. 333 func (p *Profile) String() string { 334 335 ss := make([]string, 0, len(p.Sample)+len(p.Mapping)+len(p.Location)) 336 if pt := p.PeriodType; pt != nil { 337 ss = append(ss, fmt.Sprintf("PeriodType: %s %s", pt.Type, pt.Unit)) 338 } 339 ss = append(ss, fmt.Sprintf("Period: %d", p.Period)) 340 if p.TimeNanos != 0 { 341 ss = append(ss, fmt.Sprintf("Time: %v", time.Unix(0, p.TimeNanos))) 342 } 343 if p.DurationNanos != 0 { 344 ss = append(ss, fmt.Sprintf("Duration: %v", time.Duration(p.DurationNanos))) 345 } 346 347 ss = append(ss, "Samples:") 348 var sh1 string 349 for _, s := range p.SampleType { 350 sh1 = sh1 + fmt.Sprintf("%s/%s ", s.Type, s.Unit) 351 } 352 ss = append(ss, strings.TrimSpace(sh1)) 353 for _, s := range p.Sample { 354 var sv string 355 for _, v := range s.Value { 356 sv = fmt.Sprintf("%s %10d", sv, v) 357 } 358 sv = sv + ": " 359 for _, l := range s.Location { 360 sv = sv + fmt.Sprintf("%d ", l.ID) 361 } 362 ss = append(ss, sv) 363 const labelHeader = " " 364 if len(s.Label) > 0 { 365 ls := labelHeader 366 for k, v := range s.Label { 367 ls = ls + fmt.Sprintf("%s:%v ", k, v) 368 } 369 ss = append(ss, ls) 370 } 371 if len(s.NumLabel) > 0 { 372 ls := labelHeader 373 for k, v := range s.NumLabel { 374 ls = ls + fmt.Sprintf("%s:%v ", k, v) 375 } 376 ss = append(ss, ls) 377 } 378 } 379 380 ss = append(ss, "Locations") 381 for _, l := range p.Location { 382 locStr := fmt.Sprintf("%6d: %#x ", l.ID, l.Address) 383 if m := l.Mapping; m != nil { 384 locStr = locStr + fmt.Sprintf("M=%d ", m.ID) 385 } 386 if len(l.Line) == 0 { 387 ss = append(ss, locStr) 388 } 389 for li := range l.Line { 390 lnStr := "??" 391 if fn := l.Line[li].Function; fn != nil { 392 lnStr = fmt.Sprintf("%s %s:%d s=%d", 393 fn.Name, 394 fn.Filename, 395 l.Line[li].Line, 396 fn.StartLine) 397 if fn.Name != fn.SystemName { 398 lnStr = lnStr + "(" + fn.SystemName + ")" 399 } 400 } 401 ss = append(ss, locStr+lnStr) 402 // Do not print location details past the first line 403 locStr = " " 404 } 405 } 406 407 ss = append(ss, "Mappings") 408 for _, m := range p.Mapping { 409 bits := "" 410 if m.HasFunctions { 411 bits = bits + "[FN]" 412 } 413 if m.HasFilenames { 414 bits = bits + "[FL]" 415 } 416 if m.HasLineNumbers { 417 bits = bits + "[LN]" 418 } 419 if m.HasInlineFrames { 420 bits = bits + "[IN]" 421 } 422 ss = append(ss, fmt.Sprintf("%d: %#x/%#x/%#x %s %s %s", 423 m.ID, 424 m.Start, m.Limit, m.Offset, 425 m.File, 426 m.BuildID, 427 bits)) 428 } 429 430 return strings.Join(ss, "\n") + "\n" 431 } 432 433 // Merge adds profile p adjusted by ratio r into profile p. Profiles 434 // must be compatible (same Type and SampleType). 435 // TODO(rsilvera): consider normalizing the profiles based on the 436 // total samples collected. 437 func (p *Profile) Merge(pb *Profile, r float64) error { 438 if err := p.Compatible(pb); err != nil { 439 return err 440 } 441 442 pb = pb.Copy() 443 444 // Keep the largest of the two periods. 445 if pb.Period > p.Period { 446 p.Period = pb.Period 447 } 448 449 p.DurationNanos += pb.DurationNanos 450 451 p.Mapping = append(p.Mapping, pb.Mapping...) 452 for i, m := range p.Mapping { 453 m.ID = uint64(i + 1) 454 } 455 p.Location = append(p.Location, pb.Location...) 456 for i, l := range p.Location { 457 l.ID = uint64(i + 1) 458 } 459 p.Function = append(p.Function, pb.Function...) 460 for i, f := range p.Function { 461 f.ID = uint64(i + 1) 462 } 463 464 if r != 1.0 { 465 for _, s := range pb.Sample { 466 for i, v := range s.Value { 467 s.Value[i] = int64((float64(v) * r)) 468 } 469 } 470 } 471 p.Sample = append(p.Sample, pb.Sample...) 472 return p.CheckValid() 473 } 474 475 // Compatible determines if two profiles can be compared/merged. 476 // returns nil if the profiles are compatible; otherwise an error with 477 // details on the incompatibility. 478 func (p *Profile) Compatible(pb *Profile) error { 479 if !compatibleValueTypes(p.PeriodType, pb.PeriodType) { 480 return fmt.Errorf("incompatible period types %v and %v", p.PeriodType, pb.PeriodType) 481 } 482 483 if len(p.SampleType) != len(pb.SampleType) { 484 return fmt.Errorf("incompatible sample types %v and %v", p.SampleType, pb.SampleType) 485 } 486 487 for i := range p.SampleType { 488 if !compatibleValueTypes(p.SampleType[i], pb.SampleType[i]) { 489 return fmt.Errorf("incompatible sample types %v and %v", p.SampleType, pb.SampleType) 490 } 491 } 492 493 return nil 494 } 495 496 // HasFunctions determines if all locations in this profile have 497 // symbolized function information. 498 func (p *Profile) HasFunctions() bool { 499 for _, l := range p.Location { 500 if l.Mapping == nil || !l.Mapping.HasFunctions { 501 return false 502 } 503 } 504 return true 505 } 506 507 // HasFileLines determines if all locations in this profile have 508 // symbolized file and line number information. 509 func (p *Profile) HasFileLines() bool { 510 for _, l := range p.Location { 511 if l.Mapping == nil || (!l.Mapping.HasFilenames || !l.Mapping.HasLineNumbers) { 512 return false 513 } 514 } 515 return true 516 } 517 518 func compatibleValueTypes(v1, v2 *ValueType) bool { 519 if v1 == nil || v2 == nil { 520 return true // No grounds to disqualify. 521 } 522 return v1.Type == v2.Type && v1.Unit == v2.Unit 523 } 524 525 // Copy makes a fully independent copy of a profile. 526 func (p *Profile) Copy() *Profile { 527 p.preEncode() 528 b := marshal(p) 529 530 pp := &Profile{} 531 if err := unmarshal(b, pp); err != nil { 532 panic(err) 533 } 534 if err := pp.postDecode(); err != nil { 535 panic(err) 536 } 537 538 return pp 539 } 540 541 // Demangler maps symbol names to a human-readable form. This may 542 // include C++ demangling and additional simplification. Names that 543 // are not demangled may be missing from the resulting map. 544 type Demangler func(name []string) (map[string]string, error) 545 546 // Demangle attempts to demangle and optionally simplify any function 547 // names referenced in the profile. It works on a best-effort basis: 548 // it will silently preserve the original names in case of any errors. 549 func (p *Profile) Demangle(d Demangler) error { 550 // Collect names to demangle. 551 var names []string 552 for _, fn := range p.Function { 553 names = append(names, fn.SystemName) 554 } 555 556 // Update profile with demangled names. 557 demangled, err := d(names) 558 if err != nil { 559 return err 560 } 561 for _, fn := range p.Function { 562 if dd, ok := demangled[fn.SystemName]; ok { 563 fn.Name = dd 564 } 565 } 566 return nil 567 }