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