github.com/GuanceCloud/cliutils@v1.1.21/pprofparser/service/parsing/pprof.go (about) 1 package parsing 2 3 import ( 4 "bufio" 5 "bytes" 6 "errors" 7 "fmt" 8 "io" 9 "io/fs" 10 "path/filepath" 11 "regexp" 12 "strconv" 13 "strings" 14 15 "github.com/GuanceCloud/cliutils/pprofparser/domain/events" 16 "github.com/GuanceCloud/cliutils/pprofparser/domain/languages" 17 "github.com/GuanceCloud/cliutils/pprofparser/domain/parameter" 18 "github.com/GuanceCloud/cliutils/pprofparser/domain/pprof" 19 "github.com/GuanceCloud/cliutils/pprofparser/domain/quantity" 20 "github.com/GuanceCloud/cliutils/pprofparser/domain/tracing" 21 "github.com/GuanceCloud/cliutils/pprofparser/service/storage" 22 "github.com/GuanceCloud/cliutils/pprofparser/tools/logtoolkit" 23 "github.com/GuanceCloud/cliutils/pprofparser/tools/parsetoolkit" 24 "github.com/google/pprof/profile" 25 "github.com/pierrec/lz4/v4" 26 ) 27 28 const ( 29 LabelExceptionType = "exception type" 30 LabelThreadID = "thread id" 31 LabelThreadNativeID = "thread native id" 32 LabelThreadName = "thread name" 33 LabelSpanID = "span id" 34 LabelLocalRootSpanID = "local root span id" 35 ) 36 37 var ( 38 ZIPMagic = []byte{0x50, 0x4b, 3, 4} 39 LZ4Magic = []byte{4, 34, 77, 24} 40 GZIPMagic = []byte{31, 139} 41 ) 42 43 var lineNoRegExp = regexp.MustCompile(`^\d+$`) 44 45 type PProf struct { 46 from string 47 workspaceUUID string 48 profiles []*parameter.Profile 49 filterBySpan bool 50 span *parameter.Span 51 spanIDSet *tracing.SpanIDSet 52 DisplayCtl 53 } 54 55 type Decompressor struct { 56 io.Reader 57 r io.Reader 58 } 59 60 func NewDecompressor(r io.Reader) io.ReadCloser { 61 bufReader := bufio.NewReader(r) 62 63 magics, err := bufReader.Peek(4) 64 if err != nil { 65 return &Decompressor{ 66 r: r, 67 Reader: bufReader, 68 } 69 } 70 71 if bytes.Compare(LZ4Magic, magics) == 0 { 72 return &Decompressor{ 73 r: r, 74 Reader: lz4.NewReader(bufReader), 75 } 76 } 77 78 return &Decompressor{ 79 r: r, 80 Reader: bufReader, 81 } 82 } 83 84 func (d *Decompressor) Close() error { 85 var err error 86 if rc, ok := d.Reader.(io.Closer); ok { 87 if e := rc.Close(); e != nil { 88 err = e 89 } 90 } 91 92 if rc, ok := d.r.(io.Closer); ok { 93 if e := rc.Close(); e != nil { 94 err = e 95 } 96 } 97 return err 98 } 99 100 func NewPProfParser( 101 from string, 102 workspaceUUID string, 103 profiles []*parameter.Profile, 104 filterBySpan bool, 105 span *parameter.Span, 106 spanIDSet *tracing.SpanIDSet, 107 ctl DisplayCtl, 108 ) *PProf { 109 return &PProf{ 110 from: from, 111 workspaceUUID: workspaceUUID, 112 profiles: profiles, 113 filterBySpan: filterBySpan, 114 span: span, 115 spanIDSet: spanIDSet, 116 DisplayCtl: ctl, 117 } 118 } 119 120 func isGlobPattern(pattern string) bool { 121 return strings.ContainsAny(pattern, "?*") 122 } 123 124 func (p *PProf) mergePProf(filename string) (*profile.Profile, error) { 125 if len(p.profiles) == 0 { 126 return nil, fmt.Errorf("empty profiles") 127 } 128 129 client, err := storage.GetStorage(storage.LocalDisk) 130 if err != nil { 131 return nil, fmt.Errorf("init oss client err: %w", err) 132 } 133 134 filenames := []string{filename} 135 136 if strings.ContainsRune(filename, '|') { 137 filenames = strings.Split(filename, "|") 138 } 139 140 profSrc := make([]*profile.Profile, 0, len(p.profiles)) 141 142 for _, prof := range p.profiles { 143 startTime, err := prof.StartTime() 144 if err != nil { 145 return nil, fmt.Errorf("cast ProfileStart to int64 fail: %w", err) 146 } 147 148 profilePath := "" 149 150 FilenameLoop: 151 for _, name := range filenames { 152 if name == "" { 153 continue 154 } 155 156 pattern := client.GetProfilePath(p.workspaceUUID, prof.ProfileID, startTime, name) 157 if ok, err := client.IsFileExists(pattern); ok && err == nil { 158 profilePath = pattern 159 break 160 } 161 162 // check whether the filename is a glob pattern 163 if isGlobPattern(name) { 164 matches, err := filepath.Glob(pattern) 165 if err != nil { 166 return nil, fmt.Errorf("illegal glob pattern [%s]; %w", pattern, err) 167 } 168 169 for _, match := range matches { 170 baseName := filepath.Base(match) 171 if baseName != events.DefaultMetaFileName && baseName != events.DefaultMetaFileNameWithExt { 172 profilePath = match 173 break FilenameLoop 174 } 175 } 176 } 177 } 178 179 if profilePath == "" { 180 return nil, fmt.Errorf("no available profile file found: [%s]: %w", filename, fs.ErrNotExist) 181 } 182 183 reader, err := client.ReadFile(profilePath) 184 185 if err != nil { 186 if errors.Is(err, fs.ErrNotExist) { 187 return nil, fmt.Errorf("profile file [%s] not exists: %w", profilePath, err) 188 } 189 if ok, err := client.IsFileExists(profilePath); err == nil && !ok { 190 return nil, fmt.Errorf("profile file [%s] not exists:%w", profilePath, fs.ErrNotExist) 191 } 192 return nil, fmt.Errorf("unable to read profile file [%s]: %w", profilePath, err) 193 } 194 195 parsedPProf, err := parseAndClose(NewDecompressor(reader)) 196 if err != nil { 197 logtoolkit.Errorf("parse profile [path:%s] fail: %s", profilePath, err) 198 continue 199 } 200 201 profSrc = append(profSrc, parsedPProf) 202 } 203 204 if len(profSrc) == 0 { 205 return nil, fmt.Errorf("no available profile") 206 } 207 208 mergedPProf, err := profile.Merge(profSrc) 209 if err != nil { 210 return nil, fmt.Errorf("merge profile fail: %w", err) 211 } 212 if err := mergedPProf.CheckValid(); err != nil { 213 return nil, fmt.Errorf("invalid merged profile file: %w", err) 214 } 215 return mergedPProf, nil 216 } 217 218 func (p *PProf) Summary() (map[events.Type]*EventSummary, int64, error) { 219 lang, err := parameter.VerifyLanguage(p.profiles) 220 if err != nil { 221 return nil, 0, fmt.Errorf("unable to resolve language: %w", err) 222 //======= 223 // return nil, 0, fmt.Errorf("GetSummary VerifyLanguage err: %w", err) 224 // } 225 // 226 // ok, err := IsPySpyProfile(param.Profiles, param.WorkspaceUUID) 227 // if ok && err == nil { 228 // prof := param.Profiles[0] 229 // startNanos, err := jsontoolkit.IFaceCast2Int64(prof.ProfileStart) 230 // if err != nil { 231 // return nil, 0, fmt.Errorf("resolve Profile start timestamp fail: %w", err) 232 // } 233 // endNanos, err := jsontoolkit.IFaceCast2Int64(prof.ProfileEnd) 234 // if err != nil { 235 // return nil, 0, fmt.Errorf("resolve Profile end timestamp fail: %w", err) 236 // } 237 // profileFile := storage.DefaultDiskStorage.GetProfilePath(param.WorkspaceUUID, prof.ProfileID, startNanos, events.DefaultProfileFilename) 238 // 239 // summaries, err := GetPySpySummary(profileFile) 240 // if err != nil { 241 // return nil, 0, fmt.Errorf("get py-spy profile summary fail: %w", err) 242 // } 243 // return summaries, endNanos - startNanos, nil 244 // } else if err != nil { 245 // logtoolkit.Warnf("judge if profile is from py-spy err: %s", err) 246 //>>>>>>> 66994b8f59cd601e6e7fbac181122f708d77ef3a:service/parsing/multiparser.go 247 } 248 249 fileSampleTypes := getFileSampleTypes(lang) 250 if len(fileSampleTypes) == 0 { 251 return nil, 0, fmt.Errorf("getFileSampleTypes: not supported language [%s]", lang) 252 } 253 254 summariesTypedMap := make(map[events.Type]*EventSummary) 255 var totalDurationNanos int64 = 0 256 257 filesCount := 0 258 for filename, sampleTypes := range fileSampleTypes { 259 260 mergedPProf, err := p.mergePProf(filename) 261 if err != nil { 262 if errors.Is(err, fs.ErrNotExist) { 263 continue 264 } 265 return nil, 0, fmt.Errorf("merge pprof: %w", err) 266 } 267 filesCount++ 268 269 if mergedPProf.DurationNanos > totalDurationNanos { 270 totalDurationNanos = mergedPProf.DurationNanos 271 } 272 273 // pprof.SampleType 和 pprof.Sample[xx].Value 一一对应 274 summaryMap := make(map[int]*EventSummary) 275 276 for i, st := range mergedPProf.SampleType { 277 278 if et, ok := sampleTypes[st.Type]; ok { 279 280 if p.from == parameter.FromTrace && !p.ShowInTrace(et) { 281 continue 282 } 283 284 if p.from == parameter.FromProfile && !p.ShowInProfile(et) { 285 continue 286 } 287 288 unit, err := quantity.ParseUnit(et.GetQuantityKind(), st.Unit) 289 if err != nil { 290 return nil, 0, fmt.Errorf("parseUnit error: %w", err) 291 } 292 293 summaryMap[i] = &EventSummary{ 294 SummaryValueType: &SummaryValueType{ 295 Type: et, 296 Unit: unit, 297 }, 298 Value: 0, 299 } 300 } 301 } 302 303 for _, sample := range mergedPProf.Sample { 304 // 需要进行span过滤 305 if p.filterBySpan { 306 spanID := parsetoolkit.GetStringLabel(sample, LabelSpanID) 307 rootSpanId := parsetoolkit.GetStringLabel(sample, LabelLocalRootSpanID) 308 // 没有spanID的数据去掉 309 if spanID == "" { 310 continue 311 } 312 if p.spanIDSet != nil { 313 if p.spanIDSet == tracing.AllTraceSpanSet { 314 if rootSpanId != p.span.SpanID { 315 continue 316 } 317 } else if !p.spanIDSet.Contains(spanID) { 318 continue 319 } 320 } 321 } 322 for i, v := range sample.Value { 323 if _, ok := summaryMap[i]; ok { 324 summaryMap[i].Value += v 325 } 326 } 327 } 328 329 for _, summary := range summaryMap { 330 summariesTypedMap[summary.Type] = summary 331 } 332 } 333 334 if filesCount == 0 { 335 sb := &strings.Builder{} 336 for i, pro := range p.profiles { 337 if i > 0 { 338 sb.WriteByte(';') 339 } 340 sb.WriteString(pro.ProfileID) 341 } 342 return nil, 0, fmt.Errorf("no corresponding profiling file exists, workspaceUUID [%s], profileID [%s]", p.workspaceUUID, sb.String()) 343 } 344 345 return summariesTypedMap, totalDurationNanos, nil 346 } 347 348 // parseAndClose parse profile from a readable stream, and try to close the reader when end 349 func parseAndClose(r io.Reader) (*profile.Profile, error) { 350 if r == nil { 351 return nil, fmt.Errorf("nil reader") 352 } 353 354 if closable, ok := r.(io.Closer); ok { 355 defer closable.Close() 356 } 357 358 goPprof, err := profile.Parse(r) 359 360 if err != nil { 361 return nil, fmt.Errorf("parse pprof err: %w", err) 362 } 363 364 return goPprof, nil 365 } 366 367 // ResolveFlameGraph (lang languages.Lang, eType events.Type, pprofSampleType string, filterBySpan bool, span *parameter.Span, spanIDSet *dql.SpanIDSet) 368 func (p *PProf) ResolveFlameGraph(eventType events.Type) (*pprof.Frame, AggregatorSelectSlice, error) { 369 370 lang, err := parameter.VerifyLanguage(p.profiles) 371 if err != nil { 372 return nil, nil, fmt.Errorf("VerifyLanguage fail: %s", err) 373 } 374 375 sampleFile, err := GetFileByEvent(lang, eventType) 376 if err != nil { 377 return nil, nil, fmt.Errorf("GetFileByEvent: %s", err) 378 } 379 380 mergedPProf, err := p.mergePProf(sampleFile.Filename) 381 if err != nil { 382 return nil, nil, fmt.Errorf("merge pprof: %w", err) 383 } 384 385 valueIdx, valueUnit, err := p.getIdxOfTypeAndUnit(sampleFile.SampleType, mergedPProf) 386 if err != nil { 387 return nil, nil, fmt.Errorf("render frame: %w", err) 388 } 389 390 unit, err := quantity.ParseUnit(eventType.GetQuantityKind(), valueUnit) 391 if err != nil { 392 return nil, nil, fmt.Errorf("ParseUnit fail: %w", err) 393 } 394 395 rootFrame := &pprof.Frame{ 396 SubFrames: make(pprof.SubFrames), 397 } 398 399 aggregatorList := PythonAggregatorList 400 switch lang { 401 case languages.GoLang: 402 aggregatorList = GoAggregatorList 403 case languages.NodeJS: 404 aggregatorList = PyroscopeNodeJSAggregatorList 405 case languages.DotNet: 406 aggregatorList = DDTraceDotnetAggregatorList 407 case languages.PHP: 408 aggregatorList = DDTracePHPAggregatorList 409 } 410 411 aggregatorSelectMap := make(AggregatorSelectSlice, 0, len(aggregatorList)) 412 413 for _, aggregator := range aggregatorList { 414 aggregatorSelectMap = append(aggregatorSelectMap, &AggregatorSelect{ 415 Aggregator: aggregator, 416 Mapping: aggregator.Mapping, 417 Options: make(map[string]*AggregatorOption), 418 }) 419 } 420 421 totalValue := int64(0) 422 for _, smp := range mergedPProf.Sample { 423 if smp.Value[valueIdx] == 0 { 424 // 过滤值为0的采样数据 425 continue 426 } 427 428 // span 过滤,必须有spanID的才显示 429 if p.filterBySpan { 430 spanID := parsetoolkit.GetStringLabel(smp, LabelSpanID) 431 rootSpanId := parsetoolkit.GetStringLabel(smp, LabelLocalRootSpanID) 432 if spanID == "" { 433 continue 434 } 435 if p.spanIDSet == tracing.AllTraceSpanSet { 436 if rootSpanId != p.span.SpanID { 437 continue 438 } 439 } else if p.spanIDSet != nil { 440 if !p.spanIDSet.Contains(spanID) { 441 continue 442 } 443 } 444 } 445 446 currentFrame := rootFrame 447 448 totalValue += smp.Value[valueIdx] 449 450 for _, aggregatorSelect := range aggregatorSelectMap { 451 aggregator := aggregatorSelect.Aggregator 452 identifier := aggregator.GetIdentifier(lang, smp, false) 453 454 if _, ok := aggregatorSelect.Options[identifier]; ok { 455 aggregatorSelect.Options[identifier].Value += smp.Value[valueIdx] 456 } else { 457 mappingValues := make([]string, 0, len(aggregator.GetMappingFuncs)) 458 for _, mFunc := range aggregator.GetMappingFuncs { 459 mappingValues = append(mappingValues, mFunc(lang, smp, false)) 460 } 461 aggregatorSelect.Options[identifier] = &AggregatorOption{ 462 Title: aggregator.GetDisplayStr(lang, smp, false), 463 Value: smp.Value[valueIdx], 464 Unit: unit, 465 MappingValues: mappingValues, 466 } 467 } 468 } 469 470 for i := len(smp.Location) - 1; i >= 0; i-- { 471 location := smp.Location[i] 472 line := location.Line[len(location.Line)-1] 473 474 var funcIdentifier string 475 //if i == 0 { 476 // 最后一层必须严格相同, 不是最后一层行号不相同也允许合并 477 //funcIdentifier = fmt.Sprintf("%s###%s###%d", line.Function.Filename, line.Function.Name, line.Line) 478 funcIdentifier = strconv.FormatUint(location.ID, 10) 479 //} else { 480 // funcIdentifier = fmt.Sprintf("%s###%s###%s", parsetoolkit.GetLabel(smp, LabelThreadID), line.Function.Filename, line.Function.Name) 481 //} 482 483 subFrame, ok := currentFrame.SubFrames[funcIdentifier] 484 485 if ok { 486 subFrame.Value += smp.Value[valueIdx] 487 } else { 488 subFrame = &pprof.Frame{ 489 Value: smp.Value[valueIdx], 490 Unit: unit, 491 Function: getFuncNameByLine(lang, line), 492 Line: getLineByLine(lang, line), 493 File: getFileByLine(lang, line), 494 Directory: getDirectoryByLine(lang, line), 495 ThreadID: getThreadIDBySample(smp), 496 ThreadName: getThreadNameBySample(smp), 497 Class: getClassByLine(lang, line), 498 Namespace: getNamespaceByLine(lang, line), 499 Assembly: getAssemblyByLine(lang, line), 500 Package: getPackageNameByLine(lang, line), 501 PrintString: GetPrintStrByLine(lang, line), 502 SubFrames: make(pprof.SubFrames), 503 } 504 currentFrame.SubFrames[funcIdentifier] = subFrame 505 } 506 507 currentFrame = subFrame 508 } 509 } 510 511 rootFrame.Value = totalValue 512 rootFrame.Unit = unit 513 514 parsetoolkit.CalcPercentAndQuantity(rootFrame, totalValue) 515 aggregatorSelectMap.CalcPercentAndQuantity(totalValue) 516 517 return rootFrame, aggregatorSelectMap, nil 518 } 519 520 func (p *PProf) getIdxOfTypeAndUnit(typeName string, pprof *profile.Profile) (int, string, error) { 521 for idx, st := range pprof.SampleType { 522 if st.Type == typeName { 523 return idx, st.Unit, nil 524 } 525 } 526 return 0, "", fmt.Errorf("the pprof does not contain the event type: %s", typeName) 527 }