github.com/GuanceCloud/cliutils@v1.1.21/pprofparser/service/parsing/collapse.go (about)

     1  package parsing
     2  
     3  import (
     4  	"bufio"
     5  	"fmt"
     6  	"github.com/GuanceCloud/cliutils/pprofparser/domain/tracing"
     7  	"os"
     8  	"regexp"
     9  	"strconv"
    10  	"strings"
    11  
    12  	"github.com/GuanceCloud/cliutils/pprofparser/domain/events"
    13  	"github.com/GuanceCloud/cliutils/pprofparser/domain/parameter"
    14  	"github.com/GuanceCloud/cliutils/pprofparser/domain/pprof"
    15  	"github.com/GuanceCloud/cliutils/pprofparser/domain/quantity"
    16  	"github.com/GuanceCloud/cliutils/pprofparser/service/storage"
    17  	"github.com/GuanceCloud/cliutils/pprofparser/tools/filepathtoolkit"
    18  	"github.com/GuanceCloud/cliutils/pprofparser/tools/logtoolkit"
    19  	"github.com/GuanceCloud/cliutils/pprofparser/tools/parsetoolkit"
    20  )
    21  
    22  /*
    23  py-spy profiler output is as below:
    24  
    25  process 95768:"/opt/homebrew/Cellar/python@3.10/3.10.5/Frameworks/Python.framework/Versions/3.10/Resources/Python.app/Contents/MacOS/Python fibobacci.py";thread (0x100850580);<module> (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:14);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:5) 1
    26  process 95768:"/opt/homebrew/Cellar/python@3.10/3.10.5/Frameworks/Python.framework/Versions/3.10/Resources/Python.app/Contents/MacOS/Python fibobacci.py";thread (0x100850580);<module> (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:14);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:7) 1
    27  process 95768:"/opt/homebrew/Cellar/python@3.10/3.10.5/Frameworks/Python.framework/Versions/3.10/Resources/Python.app/Contents/MacOS/Python fibobacci.py";thread (0x100850580);<module> (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:14);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:5) 1
    28  process 95768:"/opt/homebrew/Cellar/python@3.10/3.10.5/Frameworks/Python.framework/Versions/3.10/Resources/Python.app/Contents/MacOS/Python fibobacci.py";thread (0x100850580);<module> (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:14);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:8);fibonacci (/Users/zy/PycharmProjects/pyroscope-demo/fibobacci.py:5) 1
    29  
    30  nginx;/usr/sbin/nginx+0x24678;__libc_start_main;main;ngx_master_process_cycle;/usr/sbin/nginx+0x4f0d8;ngx_spawn_process;/usr/sbin/nginx+0x4fa44;ngx_process_events_and_timers;/usr/sbin/nginx+0x51f54;epoll_pwait 1
    31  
    32  */
    33  
    34  var processRegExp = regexp.MustCompile(`^process\s+\d+:"`)
    35  var threadRexExp = regexp.MustCompile(`^thread\s+\([a-zA-Z\d]+\)$`)
    36  var stackTraceRegExp = regexp.MustCompile(`^(\S+)(?: +\(([^:]+):(\d+)\))?`)
    37  
    38  type Collapse struct {
    39  	workspaceUUID string
    40  	profiles      []*parameter.Profile
    41  	filterBySpan  bool
    42  	spanIDSet     *tracing.SpanIDSet
    43  }
    44  
    45  func NewCollapse(workspaceUUID string, profiles []*parameter.Profile,
    46  	filterBySpan bool, spanIDSet *tracing.SpanIDSet) *Collapse {
    47  	return &Collapse{
    48  		workspaceUUID: workspaceUUID,
    49  		profiles:      profiles,
    50  		filterBySpan:  filterBySpan,
    51  		spanIDSet:     spanIDSet,
    52  	}
    53  }
    54  
    55  func summary(filename string) (map[events.Type]*EventSummary, error) {
    56  	f, err := os.Open(filename)
    57  	if err != nil {
    58  		return nil, fmt.Errorf("open profile file [%s] fail: %w", filename, err)
    59  	}
    60  	defer f.Close()
    61  
    62  	sampleSummary := &EventSummary{
    63  		SummaryValueType: &SummaryValueType{
    64  			Type: events.CpuSamples,
    65  			Unit: quantity.CountUnit,
    66  		},
    67  		Value: 0,
    68  	}
    69  
    70  	spySummaries := map[events.Type]*EventSummary{
    71  		events.CpuSamples: sampleSummary,
    72  	}
    73  
    74  	scanner := bufio.NewScanner(f)
    75  	for scanner.Scan() {
    76  		line := strings.TrimSpace(scanner.Text())
    77  		if len(line) < 2 {
    78  			continue
    79  		}
    80  
    81  		blankIdx := strings.LastIndexByte(line, ' ')
    82  		if blankIdx < 0 {
    83  			logtoolkit.Errorf("py-spy profile doesn't contain any blank [line: %s]", line)
    84  			continue
    85  		}
    86  		n, err := strconv.ParseInt(strings.TrimSpace(line[blankIdx+1:]), 10, 64)
    87  		if err != nil {
    88  			logtoolkit.Errorf("resolve sample count fail [line: %s]: %w", line, err)
    89  			continue
    90  		}
    91  		sampleSummary.Value += n
    92  	}
    93  	return spySummaries, nil
    94  }
    95  
    96  func (p *Collapse) Summary() (map[events.Type]*EventSummary, int64, error) {
    97  
    98  	prof := p.profiles[0]
    99  
   100  	startNanos, err := prof.StartTime()
   101  	if err != nil {
   102  		return nil, 0, fmt.Errorf("resolve Profile start timestamp fail: %w", err)
   103  	}
   104  	endNanos, err := prof.EndTime()
   105  	if err != nil {
   106  		return nil, 0, fmt.Errorf("resolve Profile end timestamp fail: %w", err)
   107  	}
   108  
   109  	filename := storage.DefaultDiskStorage.GetProfilePath(p.workspaceUUID, prof.ProfileID, startNanos, events.DefaultProfileFilename)
   110  
   111  	summaries, err := summary(filename)
   112  	if err != nil {
   113  		return nil, 0, fmt.Errorf("resolve collapse summary fail: %w", err)
   114  	}
   115  
   116  	return summaries, endNanos - startNanos, nil
   117  }
   118  
   119  func IsCollapseProfile(profiles []*parameter.Profile, workspaceUUID string) (bool, error) {
   120  	// 当前 py-spy 一次只有一条profile数据
   121  	if len(profiles) > 1 {
   122  		return false, nil
   123  	}
   124  
   125  	metadata, err := ReadMetaData(profiles[0], workspaceUUID)
   126  	if err != nil {
   127  		return false, fmt.Errorf("read py-spy metadata file fail: %w", err)
   128  	}
   129  
   130  	return metadata.Format == RawFlameGraph || metadata.Format == Collapsed, nil
   131  }
   132  
   133  func (p *Collapse) ResolveFlameGraph(_ events.Type) (*pprof.Frame, AggregatorSelectSlice, error) {
   134  
   135  	prof := p.profiles[0]
   136  
   137  	startNanos, err := prof.StartTime()
   138  	if err != nil {
   139  		return nil, nil, fmt.Errorf("invalid profile start: %w", err)
   140  	}
   141  	file := storage.DefaultDiskStorage.GetProfilePath(p.workspaceUUID, prof.ProfileID, startNanos, events.DefaultProfileFilename)
   142  
   143  	f, err := os.Open(file)
   144  	if err != nil {
   145  		return nil, nil, fmt.Errorf("open py-spy profile file fail: %w", err)
   146  	}
   147  	defer f.Close()
   148  
   149  	scanner := bufio.NewScanner(f)
   150  
   151  	rootFrame := &pprof.Frame{
   152  		SubFrames: make(pprof.SubFrames),
   153  	}
   154  	totalValue := int64(0)
   155  
   156  	aggregatorSelects := make(AggregatorSelectSlice, 0, len(SpyAggregatorList))
   157  
   158  	for _, aggregator := range SpyAggregatorList {
   159  		aggregatorSelects = append(aggregatorSelects, &AggregatorSelect{
   160  			Aggregator: aggregator,
   161  			Mapping:    aggregator.Mapping,
   162  			Options:    make(map[string]*AggregatorOption),
   163  		})
   164  	}
   165  
   166  	for scanner.Scan() {
   167  
   168  		line := strings.TrimSpace(scanner.Text())
   169  		if len(line) == 0 {
   170  			continue
   171  		}
   172  
   173  		stacks := strings.Split(line, ";")
   174  		if len(stacks) == 0 {
   175  			continue
   176  		}
   177  
   178  		lastStack := strings.TrimSpace(stacks[len(stacks)-1])
   179  		if !stackTraceRegExp.MatchString(lastStack) {
   180  			logtoolkit.Warnf("The last stacktrace not match with the regexp [%s], the stacktrace [%s]", stackTraceRegExp.String(), lastStack)
   181  			continue
   182  		}
   183  		blankIdx := strings.LastIndexByte(lastStack, ' ')
   184  		if blankIdx < 0 {
   185  			logtoolkit.Warnf("Can not find any blank from [%s]", lastStack)
   186  			continue
   187  		}
   188  
   189  		sampleCount, err := strconv.ParseInt(lastStack[blankIdx+1:], 10, 64)
   190  		if err != nil {
   191  			logtoolkit.Warnf("Can not resolve sample count from [%s]", lastStack)
   192  			continue
   193  		}
   194  		totalValue += sampleCount
   195  
   196  		currentFrame := rootFrame
   197  		threadName := "<unknown>"
   198  
   199  		for idx, stack := range stacks {
   200  			stack = strings.TrimSpace(stack)
   201  			matches := stackTraceRegExp.FindStringSubmatch(stack)
   202  			if len(matches) != 4 {
   203  				if processRegExp.MatchString(stack) {
   204  					continue
   205  				} else if threadRexExp.MatchString(stack) {
   206  					threadName = stack
   207  					continue
   208  				} else {
   209  					return nil, nil, fmt.Errorf("resolve stacktrace from profiling file fail")
   210  				}
   211  			}
   212  
   213  			funcName, codeFile, lineNoStr := matches[1], matches[2], matches[3]
   214  
   215  			if codeFile == "" {
   216  				codeFile = "<unknown>"
   217  			}
   218  
   219  			var lineNo int64 = -1
   220  			if lineNoStr != "" {
   221  				lineNo, _ = strconv.ParseInt(lineNoStr, 10, 64)
   222  			}
   223  
   224  			funcIdentifier := fmt.Sprintf("%s###%s###%s###%d", threadName, codeFile, funcName, lineNo)
   225  
   226  			if idx == len(stacks)-1 {
   227  
   228  				for _, aggregatorSelect := range aggregatorSelects {
   229  
   230  					var identifier string
   231  					var displayStr string
   232  					var mappingValues []string
   233  
   234  					switch aggregatorSelect.Aggregator {
   235  					case Function:
   236  						identifier = fmt.Sprintf("%s###%s", codeFile, funcName)
   237  						displayStr = GetSpyPrintStr(funcName, codeFile)
   238  						mappingValues = []string{funcName}
   239  					case FunctionLine:
   240  						identifier = fmt.Sprintf("%s###%s###%d", codeFile, funcName, lineNo)
   241  						displayStr = fmt.Sprintf("%s(%s:L#%d)", funcName, filepathtoolkit.BaseName(codeFile), lineNo)
   242  						mappingValues = []string{funcName, fmt.Sprintf("%d", lineNo)}
   243  					case Directory:
   244  						identifier = filepathtoolkit.DirName(codeFile)
   245  						displayStr = identifier
   246  						mappingValues = []string{identifier}
   247  					case File:
   248  						identifier = codeFile
   249  						displayStr = codeFile
   250  						mappingValues = []string{codeFile}
   251  					case ThreadName:
   252  						identifier = threadName
   253  						displayStr = threadName
   254  						mappingValues = []string{threadName}
   255  					}
   256  
   257  					if _, ok := aggregatorSelect.Options[identifier]; ok {
   258  						aggregatorSelect.Options[identifier].Value += sampleCount
   259  					} else {
   260  						aggregatorSelect.Options[identifier] = &AggregatorOption{
   261  							Title:         displayStr,
   262  							Value:         sampleCount,
   263  							Unit:          quantity.CountUnit,
   264  							MappingValues: mappingValues,
   265  						}
   266  					}
   267  				}
   268  			}
   269  
   270  			subFrame, ok := currentFrame.SubFrames[funcIdentifier]
   271  
   272  			if ok {
   273  				subFrame.Value += sampleCount
   274  			} else {
   275  				subFrame = &pprof.Frame{
   276  					Value:       sampleCount,
   277  					Unit:        quantity.CountUnit,
   278  					Function:    funcName,
   279  					Line:        lineNo,
   280  					File:        codeFile,
   281  					Directory:   filepathtoolkit.DirName(codeFile),
   282  					ThreadID:    "",
   283  					ThreadName:  threadName,
   284  					Package:     "",
   285  					PrintString: GetSpyPrintStr(funcName, codeFile),
   286  					SubFrames:   make(pprof.SubFrames),
   287  				}
   288  				currentFrame.SubFrames[funcIdentifier] = subFrame
   289  			}
   290  
   291  			currentFrame = subFrame
   292  		}
   293  	}
   294  
   295  	rootFrame.Value = totalValue
   296  	rootFrame.Unit = quantity.CountUnit
   297  
   298  	parsetoolkit.CalcPercentAndQuantity(rootFrame, totalValue)
   299  	aggregatorSelects.CalcPercentAndQuantity(totalValue)
   300  	return rootFrame, aggregatorSelects, nil
   301  }
   302  
   303  func ParseRawFlameGraph(filename string) (*pprof.Frame, AggregatorSelectSlice, error) {
   304  	f, err := os.Open(filename)
   305  	if err != nil {
   306  		return nil, nil, fmt.Errorf("open py-spy profile file fail: %w", err)
   307  	}
   308  	defer f.Close()
   309  
   310  	scanner := bufio.NewScanner(f)
   311  
   312  	rootFrame := &pprof.Frame{
   313  		SubFrames: make(pprof.SubFrames),
   314  	}
   315  	totalValue := int64(0)
   316  
   317  	aggregatorSelects := make(AggregatorSelectSlice, 0, len(SpyAggregatorList))
   318  
   319  	for _, aggregator := range SpyAggregatorList {
   320  		aggregatorSelects = append(aggregatorSelects, &AggregatorSelect{
   321  			Aggregator: aggregator,
   322  			Mapping:    aggregator.Mapping,
   323  			Options:    make(map[string]*AggregatorOption),
   324  		})
   325  	}
   326  
   327  	for scanner.Scan() {
   328  
   329  		line := strings.TrimSpace(scanner.Text())
   330  		if len(line) == 0 {
   331  			continue
   332  		}
   333  
   334  		stacks := strings.Split(line, ";")
   335  		if len(stacks) == 0 {
   336  			continue
   337  		}
   338  
   339  		lastStack := strings.TrimSpace(stacks[len(stacks)-1])
   340  		if !stackTraceRegExp.MatchString(lastStack) {
   341  			logtoolkit.Warnf("The last stacktrace not match with the regexp [%s], the stacktrace [%s]", stackTraceRegExp.String(), lastStack)
   342  			continue
   343  		}
   344  		blankIdx := strings.LastIndexByte(lastStack, ' ')
   345  		if blankIdx < 0 {
   346  			logtoolkit.Warnf("Can not find any blank from [%s]", lastStack)
   347  			continue
   348  		}
   349  
   350  		sampleCount, err := strconv.ParseInt(lastStack[blankIdx+1:], 10, 64)
   351  		if err != nil {
   352  			logtoolkit.Warnf("Can not resolve sample count from [%s]", lastStack)
   353  			continue
   354  		}
   355  		totalValue += sampleCount
   356  
   357  		currentFrame := rootFrame
   358  		threadName := "<unknown>"
   359  
   360  		for idx, stack := range stacks {
   361  			stack = strings.TrimSpace(stack)
   362  			matches := stackTraceRegExp.FindStringSubmatch(stack)
   363  			if len(matches) != 4 {
   364  				if processRegExp.MatchString(stack) {
   365  					continue
   366  				} else if threadRexExp.MatchString(stack) {
   367  					threadName = stack
   368  					continue
   369  				} else {
   370  					return nil, nil, fmt.Errorf("resolve stacktrace from profiling file fail")
   371  				}
   372  			}
   373  
   374  			funcName, codeFile, lineNoStr := matches[1], matches[2], matches[3]
   375  
   376  			if codeFile == "" {
   377  				codeFile = "<unknown>"
   378  			}
   379  
   380  			var lineNo int64 = -1
   381  			if lineNoStr != "" {
   382  				lineNo, _ = strconv.ParseInt(lineNoStr, 10, 64)
   383  			}
   384  
   385  			funcIdentifier := fmt.Sprintf("%s###%s###%s###%d", threadName, codeFile, funcName, lineNo)
   386  
   387  			if idx == len(stacks)-1 {
   388  
   389  				for _, aggregatorSelect := range aggregatorSelects {
   390  
   391  					var identifier string
   392  					var displayStr string
   393  					var mappingValues []string
   394  
   395  					switch aggregatorSelect.Aggregator {
   396  					case Function:
   397  						identifier = fmt.Sprintf("%s###%s", codeFile, funcName)
   398  						displayStr = GetSpyPrintStr(funcName, codeFile)
   399  						mappingValues = []string{funcName}
   400  					case FunctionLine:
   401  						identifier = fmt.Sprintf("%s###%s###%d", codeFile, funcName, lineNo)
   402  						displayStr = fmt.Sprintf("%s(%s:L#%d)", funcName, filepathtoolkit.BaseName(codeFile), lineNo)
   403  						mappingValues = []string{funcName, fmt.Sprintf("%d", lineNo)}
   404  					case Directory:
   405  						identifier = filepathtoolkit.DirName(codeFile)
   406  						displayStr = identifier
   407  						mappingValues = []string{identifier}
   408  					case File:
   409  						identifier = codeFile
   410  						displayStr = codeFile
   411  						mappingValues = []string{codeFile}
   412  					case ThreadName:
   413  						identifier = threadName
   414  						displayStr = threadName
   415  						mappingValues = []string{threadName}
   416  					}
   417  
   418  					if _, ok := aggregatorSelect.Options[identifier]; ok {
   419  						aggregatorSelect.Options[identifier].Value += sampleCount
   420  					} else {
   421  						aggregatorSelect.Options[identifier] = &AggregatorOption{
   422  							Title:         displayStr,
   423  							Value:         sampleCount,
   424  							Unit:          quantity.CountUnit,
   425  							MappingValues: mappingValues,
   426  						}
   427  					}
   428  				}
   429  			}
   430  
   431  			subFrame, ok := currentFrame.SubFrames[funcIdentifier]
   432  
   433  			if ok {
   434  				subFrame.Value += sampleCount
   435  			} else {
   436  				subFrame = &pprof.Frame{
   437  					Value:       sampleCount,
   438  					Unit:        quantity.CountUnit,
   439  					Function:    funcName,
   440  					Line:        lineNo,
   441  					File:        codeFile,
   442  					Directory:   filepathtoolkit.DirName(codeFile),
   443  					ThreadID:    "",
   444  					ThreadName:  threadName,
   445  					Package:     "",
   446  					PrintString: GetSpyPrintStr(funcName, codeFile),
   447  					SubFrames:   make(pprof.SubFrames),
   448  				}
   449  				currentFrame.SubFrames[funcIdentifier] = subFrame
   450  			}
   451  
   452  			currentFrame = subFrame
   453  		}
   454  	}
   455  
   456  	rootFrame.Value = totalValue
   457  	rootFrame.Unit = quantity.CountUnit
   458  
   459  	parsetoolkit.CalcPercentAndQuantity(rootFrame, totalValue)
   460  	aggregatorSelects.CalcPercentAndQuantity(totalValue)
   461  	return rootFrame, aggregatorSelects, nil
   462  }