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

     1  package parsing
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"sort"
     7  	"strconv"
     8  	"strings"
     9  
    10  	"github.com/GuanceCloud/cliutils/pprofparser/domain/languages"
    11  	"github.com/GuanceCloud/cliutils/pprofparser/domain/pprof"
    12  	"github.com/GuanceCloud/cliutils/pprofparser/domain/quantity"
    13  	"github.com/GuanceCloud/cliutils/pprofparser/tools/filepathtoolkit"
    14  	"github.com/GuanceCloud/cliutils/pprofparser/tools/logtoolkit"
    15  	"github.com/GuanceCloud/cliutils/pprofparser/tools/parsetoolkit"
    16  	"github.com/google/pprof/profile"
    17  )
    18  
    19  // [|lm:System.Private.CoreLib;|ns:System.Diagnostics.Tracing;|ct:EventSource;|fn:DebugCheckEvent]
    20  
    21  type DDFieldTag string
    22  
    23  const UnknownInfo = "<unknown>"
    24  
    25  const (
    26  	AssemblyTag  DDFieldTag = "|lm:"
    27  	NamespaceTag DDFieldTag = "|ns:"
    28  	ClassTag     DDFieldTag = "|ct:"
    29  	MethodTag    DDFieldTag = "|fn:"
    30  )
    31  
    32  var ddDotnetFieldIdx = map[DDFieldTag]int{
    33  	AssemblyTag:  0,
    34  	NamespaceTag: 1,
    35  	ClassTag:     2,
    36  	MethodTag:    3,
    37  }
    38  
    39  type GetPropertyByLine func(lang languages.Lang, line profile.Line) string
    40  
    41  var (
    42  	getFuncName       = getPropertyCallable(getFuncNameByLine)
    43  	getMethod         = getPropertyCallable(getMethodByLine)
    44  	getClass          = getPropertyCallable(getClassByLine)
    45  	getNamespace      = getPropertyCallable(getNamespaceByLine)
    46  	getAssembly       = getPropertyCallable(getAssemblyByLine)
    47  	getFuncDisplayStr = getPropertyCallable(GetPrintStrByLine)
    48  	getDirectory      = getPropertyCallable(getDirectoryByLine)
    49  	getFile           = getPropertyCallable(getFileByLine)
    50  	getPackageName    = getPropertyCallable(getPackageNameByLine)
    51  	getLine           = getPropertyCallable(func(lang languages.Lang, line profile.Line) string {
    52  		return strconv.FormatInt(getLineByLine(lang, line), 10)
    53  	})
    54  )
    55  
    56  func getFuncNameByLine(lang languages.Lang, line profile.Line) string {
    57  	switch lang {
    58  	case languages.NodeJS:
    59  		segments := strings.Split(line.Function.Name, ":")
    60  		if len(segments) > 1 {
    61  			return segments[len(segments)-2]
    62  		}
    63  		return UnknownInfo
    64  	case languages.DotNet:
    65  		return getDDDotnetMethodName(line.Function.Name)
    66  	case languages.PHP:
    67  		return getPHPBaseFuncName(line.Function.Name)
    68  	}
    69  	return line.Function.Name
    70  }
    71  
    72  func getDDDotnetMethodName(funcName string) string {
    73  	pieces := strings.Split(funcName, " ")
    74  
    75  	var className, methodName string
    76  
    77  	classIdx, fnIdx := ddDotnetFieldIdx[ClassTag], ddDotnetFieldIdx[MethodTag]
    78  	if classIdx < len(pieces) && strings.HasPrefix(pieces[classIdx], string(ClassTag)) {
    79  		className = strings.TrimPrefix(pieces[classIdx], string(ClassTag))
    80  	}
    81  	if fnIdx < len(pieces) && strings.HasPrefix(pieces[fnIdx], string(MethodTag)) {
    82  		methodName = strings.TrimPrefix(pieces[fnIdx], string(MethodTag))
    83  	}
    84  
    85  	if className == "" || methodName == "" {
    86  		for _, piece := range pieces {
    87  			piece = strings.TrimSpace(piece)
    88  			if className == "" && strings.HasPrefix(piece, string(ClassTag)) {
    89  				className = strings.TrimPrefix(piece, string(ClassTag))
    90  			}
    91  			if methodName == "" && strings.HasPrefix(piece, string(MethodTag)) {
    92  				methodName = strings.TrimPrefix(piece, string(MethodTag))
    93  			}
    94  		}
    95  	}
    96  	if methodName == "" {
    97  		return "unknown"
    98  	}
    99  
   100  	if className != "" {
   101  		return className + "." + methodName
   102  	}
   103  
   104  	return methodName
   105  }
   106  
   107  func getDDDotnetField(funcName string, tag DDFieldTag) string {
   108  	pieces := strings.Split(funcName, " ")
   109  	idx := ddDotnetFieldIdx[tag]
   110  
   111  	tagStr := string(tag)
   112  	if idx < len(pieces) && strings.HasPrefix(pieces[idx], tagStr) {
   113  		return strings.TrimPrefix(pieces[idx], tagStr)
   114  	}
   115  	for _, piece := range pieces {
   116  		piece = strings.TrimSpace(piece)
   117  		if strings.HasPrefix(piece, tagStr) {
   118  			return strings.TrimPrefix(piece, tagStr)
   119  		}
   120  	}
   121  	return "<unknown>"
   122  }
   123  
   124  func getFuncIdentifier(lang languages.Lang, smp *profile.Sample, reverse bool) string {
   125  	i := 0
   126  	if reverse {
   127  		i = len(smp.Location) - 1
   128  	}
   129  	if len(smp.Location) > 0 {
   130  		loc := smp.Location[i]
   131  		if len(loc.Line) > 0 {
   132  			return strconv.FormatUint(loc.Line[len(loc.Line)-1].Function.ID, 10)
   133  		}
   134  	}
   135  	return UnknownInfo
   136  }
   137  
   138  func getMethodByLine(lang languages.Lang, line profile.Line) string {
   139  	return getDDDotnetMethodName(line.Function.Name)
   140  }
   141  
   142  func getPropertyCallable(getPropertyByLine GetPropertyByLine) GetPropertyFunc {
   143  	return func(lang languages.Lang, sample *profile.Sample, reverse bool) string {
   144  		i := 0
   145  		if reverse {
   146  			i = len(sample.Location) - 1
   147  		}
   148  
   149  		if len(sample.Location) > 0 {
   150  			loc := sample.Location[i]
   151  			if len(loc.Line) > 0 {
   152  				return getPropertyByLine(lang, loc.Line[len(loc.Line)-1])
   153  			}
   154  		}
   155  		return UnknownInfo
   156  	}
   157  }
   158  
   159  func getClassByLine(lang languages.Lang, line profile.Line) string {
   160  	funcName := line.Function.Name
   161  	switch lang {
   162  	case languages.DotNet:
   163  		return getDDDotnetField(funcName, ClassTag)
   164  	case languages.PHP:
   165  		funcName = getPHPBaseFuncName(funcName)
   166  		if pos := strings.LastIndex(funcName, "::"); pos >= 0 {
   167  			return funcName[:pos]
   168  		}
   169  		if pos := strings.Index(funcName, "|"); pos >= 0 {
   170  			return funcName[:pos]
   171  		}
   172  		filename := strings.ReplaceAll(line.Function.Filename, "\\", "/")
   173  
   174  		if pos := strings.Index(filename, "/vendor/"); pos >= 0 {
   175  			filename = filename[pos+len("/vendor/"):]
   176  			if idx := strings.Index(filename, "/src/"); idx >= 0 {
   177  				filename = filename[:idx]
   178  			} else if idx := strings.LastIndexByte(filename, '/'); idx >= 0 {
   179  				filename = filename[:idx]
   180  			}
   181  			return filename
   182  		}
   183  
   184  		return "standard"
   185  	}
   186  
   187  	return UnknownInfo
   188  }
   189  
   190  func getNamespaceByLine(lang languages.Lang, line profile.Line) string {
   191  	switch lang {
   192  	case languages.DotNet:
   193  		if namespace := getDDDotnetField(line.Function.Name, NamespaceTag); namespace != "" {
   194  			return namespace
   195  		}
   196  	}
   197  	return UnknownInfo
   198  }
   199  
   200  func getAssemblyByLine(lang languages.Lang, line profile.Line) string {
   201  	switch lang {
   202  	case languages.DotNet:
   203  		if assembly := getDDDotnetField(line.Function.Name, AssemblyTag); assembly != "" {
   204  			return assembly
   205  		}
   206  	}
   207  	return UnknownInfo
   208  }
   209  
   210  func getLineByLine(lang languages.Lang, line profile.Line) int64 {
   211  	switch lang {
   212  	case languages.NodeJS:
   213  		segments := strings.Split(line.Function.Name, ":")
   214  		if len(segments) > 0 {
   215  			lineNo := segments[len(segments)-1]
   216  			if lineNoRegExp.MatchString(lineNo) {
   217  				lineNum, _ := strconv.ParseInt(lineNo, 10, 64)
   218  				return lineNum
   219  			}
   220  		}
   221  	}
   222  	return line.Line
   223  }
   224  
   225  func getFileByLine(lang languages.Lang, line profile.Line) string {
   226  	switch lang {
   227  	case languages.NodeJS:
   228  		funcName := line.Function.Name
   229  		segments := strings.Split(funcName, ":")
   230  		if len(segments) >= 3 {
   231  			filename := strings.TrimSpace(strings.Join(segments[:len(segments)-2], ":"))
   232  			if filename != "" {
   233  				return filename
   234  			}
   235  		}
   236  		return UnknownInfo
   237  	case languages.PHP:
   238  		filename := strings.TrimSpace(line.Function.Filename)
   239  		if filename == "" {
   240  			filename = "standard"
   241  		}
   242  		return filename
   243  	}
   244  	return line.Function.Filename
   245  }
   246  
   247  func getDirectoryByLine(lang languages.Lang, line profile.Line) string {
   248  	return filepathtoolkit.DirName(getFileByLine(lang, line))
   249  }
   250  
   251  func getThreadID(lang languages.Lang, smp *profile.Sample, reverse bool) string {
   252  	return getThreadIDBySample(smp)
   253  }
   254  
   255  func getThreadIDBySample(smp *profile.Sample) string {
   256  	if tid := parsetoolkit.GetLabel(smp, LabelThreadID); tid != "" {
   257  		return tid
   258  	}
   259  	return UnknownInfo
   260  }
   261  
   262  func getThreadName(lang languages.Lang, smp *profile.Sample, reverse bool) string {
   263  	return getThreadNameBySample(smp)
   264  }
   265  
   266  func getThreadNameBySample(smp *profile.Sample) string {
   267  	if tName := parsetoolkit.GetLabel(smp, LabelThreadName); tName != "" {
   268  		return tName
   269  	}
   270  	return UnknownInfo
   271  }
   272  
   273  func getPackageNameByLine(lang languages.Lang, line profile.Line) string {
   274  	switch lang {
   275  	case languages.GoLang:
   276  		packageName, _ := cutGoFuncName(line.Function.Name)
   277  		return packageName
   278  	}
   279  	return UnknownInfo
   280  }
   281  
   282  // cutGoFuncName 切割pprof go func 为 package 和 func name
   283  // return package name 和 func name
   284  func cutGoFuncName(funcName string) (string, string) {
   285  	pos := strings.LastIndexByte(funcName, '/')
   286  	packageName := ""
   287  	if pos > -1 {
   288  		packageName, funcName = funcName[:pos+1], funcName[pos+1:]
   289  	}
   290  	cuts := strings.SplitN(funcName, ".", 2)
   291  	if len(cuts) < 2 {
   292  		logtoolkit.Errorf(`func name not contains ".": %s`, funcName)
   293  		return packageName, cuts[0]
   294  	}
   295  	return packageName + cuts[0], cuts[1]
   296  }
   297  
   298  func GetPrintStrByLine(lang languages.Lang, line profile.Line) string {
   299  	switch lang {
   300  	case languages.GoLang:
   301  		_, funcName := cutGoFuncName(line.Function.Name)
   302  		return fmt.Sprintf("%s(%s)", funcName, filepathtoolkit.BaseName(line.Function.Filename))
   303  	case languages.NodeJS:
   304  		// node:internal/timers:listOnTimeout:569
   305  		// ./node_modules/@pyroscope/nodejs/dist/cjs/index.js:(anonymous):313
   306  		// :(idle):0
   307  		segments := strings.Split(line.Function.Name, ":")
   308  		funcName := "<unknown>"
   309  		filename := ""
   310  		if len(segments) == 1 {
   311  			funcName = segments[0]
   312  		} else if len(segments) > 1 {
   313  			funcName = segments[len(segments)-2]
   314  			filename = strings.TrimSpace(strings.Join(segments[:len(segments)-2], ":"))
   315  		}
   316  		baseName := filepathtoolkit.BaseName(filename)
   317  		if baseName == "" || baseName == "." {
   318  			return funcName
   319  		}
   320  		return fmt.Sprintf("%s(%s)", funcName, baseName)
   321  
   322  	case languages.DotNet:
   323  		return getDDDotnetMethodName(line.Function.Name)
   324  	case languages.PHP:
   325  		filename := line.Function.Filename
   326  		if filename != "" {
   327  			filename = filepathtoolkit.BaseName(filename)
   328  		}
   329  		funcName := getPHPBaseFuncName(line.Function.Name)
   330  		if filename != "" {
   331  			return fmt.Sprintf("%s(%s)", funcName, filename)
   332  		}
   333  		return funcName
   334  	default:
   335  		return fmt.Sprintf("%s(%s)", line.Function.Name, filepathtoolkit.BaseName(line.Function.Filename))
   336  	}
   337  }
   338  
   339  func getPHPBaseFuncName(funcName string) string {
   340  	if funcName == "" {
   341  		return UnknownInfo
   342  	}
   343  	pos := strings.LastIndexByte(funcName, '\\')
   344  	if pos >= 0 && pos < len(funcName)-1 {
   345  		return funcName[pos+1:]
   346  	}
   347  	return funcName
   348  }
   349  
   350  func GetSpyPrintStr(funcName, fileName string) string {
   351  	return fmt.Sprintf("%s(%s)", funcName, filepathtoolkit.BaseName(fileName))
   352  }
   353  
   354  func GetFuncAndLineDisplay(lang languages.Lang, smp *profile.Sample, reverse bool) string {
   355  	i := 0
   356  	if reverse {
   357  		i = len(smp.Location) - 1
   358  	}
   359  	if len(smp.Location) > 0 {
   360  		loc := smp.Location[i]
   361  		if len(loc.Line) > 0 {
   362  			line := loc.Line[len(loc.Line)-1]
   363  			switch lang {
   364  			case languages.PHP:
   365  				funcName := getPHPBaseFuncName(line.Function.Name)
   366  				filename := line.Function.Filename
   367  				if filename != "" {
   368  					filename = filepathtoolkit.BaseName(filename)
   369  				}
   370  				if filename != "" {
   371  					return fmt.Sprintf("%s(%s:L#%d)", funcName, filename, line.Line)
   372  				}
   373  				return funcName
   374  			case languages.GoLang:
   375  				_, funcName := cutGoFuncName(line.Function.Name)
   376  				return fmt.Sprintf("%s(%s:L#%d)",
   377  					funcName, filepathtoolkit.BaseName(line.Function.Filename), line.Line)
   378  			default:
   379  				return fmt.Sprintf("%s(%s:L#%d)",
   380  					line.Function.Name, filepathtoolkit.BaseName(line.Function.Filename), line.Line)
   381  			}
   382  		}
   383  	}
   384  	return "<unknown>"
   385  }
   386  
   387  var (
   388  	Function = &Aggregator{
   389  		Name:            "Function",
   390  		Mapping:         []string{pprof.FieldFunctionName},
   391  		ShowLanguages:   languages.PythonID | languages.GolangID,
   392  		GetIdentifier:   getFuncIdentifier,
   393  		GetDisplayStr:   getFuncDisplayStr,
   394  		GetMappingFuncs: []GetPropertyFunc{getFuncName},
   395  	}
   396  
   397  	PHPFunction = &Aggregator{
   398  		Name:            "Function",
   399  		Mapping:         []string{pprof.FieldFunctionName},
   400  		GetIdentifier:   getFuncIdentifier,
   401  		GetDisplayStr:   getFuncDisplayStr,
   402  		GetMappingFuncs: []GetPropertyFunc{getFuncName},
   403  	}
   404  
   405  	Method = &Aggregator{
   406  		Name:            "Method",
   407  		Mapping:         []string{pprof.FieldFunctionName},
   408  		ShowLanguages:   languages.JavaID | languages.DotNetID,
   409  		GetIdentifier:   getMethod,
   410  		GetDisplayStr:   getMethod,
   411  		GetMappingFuncs: []GetPropertyFunc{getMethod},
   412  	}
   413  
   414  	Class = &Aggregator{
   415  		Name:            "Class",
   416  		Mapping:         []string{pprof.FieldClass},
   417  		ShowLanguages:   languages.DotNetID,
   418  		GetIdentifier:   getClass,
   419  		GetDisplayStr:   getClass,
   420  		GetMappingFuncs: []GetPropertyFunc{getClass},
   421  	}
   422  
   423  	Namespace = &Aggregator{
   424  		Name:            "Namespace",
   425  		Mapping:         []string{pprof.FieldNamespace},
   426  		ShowLanguages:   languages.DotNetID,
   427  		GetIdentifier:   getNamespace,
   428  		GetDisplayStr:   getNamespace,
   429  		GetMappingFuncs: []GetPropertyFunc{getNamespace},
   430  	}
   431  
   432  	Assembly = &Aggregator{
   433  		Name:            "Assembly",
   434  		Mapping:         []string{pprof.FieldAssembly},
   435  		ShowLanguages:   languages.DotNetID,
   436  		GetIdentifier:   getAssembly,
   437  		GetDisplayStr:   getAssembly,
   438  		GetMappingFuncs: []GetPropertyFunc{getAssembly},
   439  	}
   440  
   441  	PyroNodeFunction = &Aggregator{
   442  		Name:            "Function",
   443  		Mapping:         []string{pprof.FieldFunctionName},
   444  		ShowLanguages:   languages.NodeJSID,
   445  		GetIdentifier:   getFuncIdentifier,
   446  		GetDisplayStr:   getFuncDisplayStr,
   447  		GetMappingFuncs: []GetPropertyFunc{getFuncName},
   448  	}
   449  
   450  	FunctionLine = &Aggregator{
   451  		Name:          "Function + Line",
   452  		Mapping:       []string{pprof.FieldFunctionName, pprof.FieldLine},
   453  		ShowLanguages: languages.PythonID | languages.GolangID,
   454  		GetIdentifier: func(lang languages.Lang, smp *profile.Sample, reverse bool) string {
   455  			i := 0
   456  			if reverse {
   457  				i = len(smp.Location) - 1
   458  			}
   459  			if len(smp.Location) > 0 {
   460  				loc := smp.Location[i]
   461  				if len(loc.Line) > 0 {
   462  					return fmt.Sprintf("%s###%d###%d",
   463  						loc.Line[len(loc.Line)-1].Function.Filename, loc.Line[len(loc.Line)-1].Function.ID, loc.Line[len(loc.Line)-1].Line)
   464  				}
   465  			}
   466  			return "<unknown>"
   467  		},
   468  		GetDisplayStr:   GetFuncAndLineDisplay,
   469  		GetMappingFuncs: []GetPropertyFunc{getFuncName, getLine},
   470  	}
   471  
   472  	Directory = &Aggregator{
   473  		Name:            "Directory",
   474  		Mapping:         []string{pprof.FieldDirectory},
   475  		ShowLanguages:   languages.PythonID | languages.GolangID,
   476  		GetIdentifier:   getDirectory,
   477  		GetDisplayStr:   getDirectory,
   478  		GetMappingFuncs: []GetPropertyFunc{getDirectory},
   479  	}
   480  
   481  	File = &Aggregator{
   482  		Name:            "File",
   483  		Mapping:         []string{pprof.FieldFile},
   484  		ShowLanguages:   languages.PythonID | languages.GolangID,
   485  		GetIdentifier:   getFile,
   486  		GetDisplayStr:   getFile,
   487  		GetMappingFuncs: []GetPropertyFunc{getFile},
   488  	}
   489  
   490  	PyroNodeFile = &Aggregator{
   491  		Name:            "File",
   492  		Mapping:         []string{pprof.FieldFile},
   493  		ShowLanguages:   languages.NodeJSID,
   494  		GetIdentifier:   getFile,
   495  		GetDisplayStr:   getFile,
   496  		GetMappingFuncs: []GetPropertyFunc{getFile},
   497  	}
   498  
   499  	ThreadID = &Aggregator{
   500  		Name:            "Thread ID",
   501  		Mapping:         []string{pprof.FieldThreadID},
   502  		ShowLanguages:   languages.PythonID | languages.DotNetID,
   503  		GetIdentifier:   getThreadID,
   504  		GetDisplayStr:   getThreadID,
   505  		GetMappingFuncs: []GetPropertyFunc{getThreadID},
   506  	}
   507  
   508  	ThreadName = &Aggregator{
   509  		Name:            "Thread Name",
   510  		Mapping:         []string{pprof.FieldThreadName},
   511  		ShowLanguages:   languages.PythonID | languages.DotNetID,
   512  		GetIdentifier:   getThreadName,
   513  		GetDisplayStr:   getThreadName,
   514  		GetMappingFuncs: []GetPropertyFunc{getThreadName},
   515  	}
   516  
   517  	Package = &Aggregator{
   518  		Name:            "Package",
   519  		Mapping:         []string{pprof.FieldPackage},
   520  		ShowLanguages:   languages.GolangID,
   521  		GetIdentifier:   getPackageName,
   522  		GetDisplayStr:   getPackageName,
   523  		GetMappingFuncs: []GetPropertyFunc{getPackageName},
   524  	}
   525  )
   526  
   527  type GetPropertyFunc func(lang languages.Lang, smp *profile.Sample, reverse bool) string
   528  
   529  type Aggregator struct {
   530  	Name    string
   531  	Mapping []string
   532  
   533  	ShowLanguages languages.LangID
   534  
   535  	// GetIdentifier 获取维度的唯一标识
   536  	GetIdentifier GetPropertyFunc
   537  
   538  	// GetDisplayStr 获取维度的显示字符
   539  	GetDisplayStr GetPropertyFunc
   540  
   541  	// GetMappingFuncs, 获取与Mapping字段对应值Func
   542  	GetMappingFuncs []GetPropertyFunc
   543  }
   544  
   545  type AggregatorSelectSlice []*AggregatorSelect
   546  
   547  func (asm AggregatorSelectSlice) CalcPercentAndQuantity(total int64) {
   548  	for _, aggregatorSelect := range asm {
   549  		for _, opt := range aggregatorSelect.Options {
   550  			opt.CalcPercentAndQuantity(total)
   551  		}
   552  	}
   553  }
   554  
   555  func (asm AggregatorSelectSlice) MarshalJSON() ([]byte, error) {
   556  
   557  	JSONMap := make([]*AggregatorSelectForJSON, 0, len(asm))
   558  
   559  	for _, aggregatorSelect := range asm {
   560  
   561  		selectForJSON := &AggregatorSelectForJSON{
   562  			Dimension: aggregatorSelect.Aggregator.Name,
   563  			Mapping:   aggregatorSelect.Mapping,
   564  		}
   565  
   566  		for _, opt := range aggregatorSelect.Options {
   567  			selectForJSON.Options = append(selectForJSON.Options, opt)
   568  		}
   569  
   570  		sort.Sort(selectForJSON.Options)
   571  
   572  		JSONMap = append(JSONMap, selectForJSON)
   573  	}
   574  	return json.Marshal(JSONMap)
   575  }
   576  
   577  type OptionSlice []*AggregatorOption
   578  
   579  func (os OptionSlice) Len() int {
   580  	return len(os)
   581  }
   582  
   583  func (os OptionSlice) Less(i, j int) bool {
   584  	return os[i].Value > os[j].Value
   585  }
   586  
   587  func (os OptionSlice) Swap(i, j int) {
   588  	os[i], os[j] = os[j], os[i]
   589  }
   590  
   591  type OptionMap map[string]*AggregatorOption
   592  
   593  type AggregatorSelect struct {
   594  	Aggregator *Aggregator
   595  	Mapping    []string
   596  	Options    OptionMap
   597  }
   598  
   599  type AggregatorSelectForJSON struct {
   600  	Dimension string      `json:"dimension"`
   601  	Mapping   []string    `json:"mapping"`
   602  	Options   OptionSlice `json:"data"`
   603  }
   604  
   605  type AggregatorOption struct {
   606  	Title         string             `json:"title"`
   607  	Quantity      *quantity.Quantity `json:"quantity"`
   608  	Value         int64              `json:"value"`
   609  	Unit          *quantity.Unit     `json:"unit"`
   610  	Percent       string             `json:"percent"`
   611  	MappingValues []string           `json:"mappingValues"`
   612  }
   613  
   614  func (ao *AggregatorOption) CalcPercentAndQuantity(total int64) {
   615  
   616  	if total <= 0 {
   617  		ao.Percent = "100"
   618  	} else {
   619  		ao.Percent = fmt.Sprintf("%.2f", float64(ao.Value)/float64(total)*100)
   620  	}
   621  
   622  	if ao.Unit != nil {
   623  		ao.Quantity = ao.Unit.Quantity(ao.Value)
   624  
   625  		// 转成默认单位
   626  		ao.Quantity.SwitchToDefaultUnit()
   627  		ao.Value = ao.Quantity.Value
   628  		ao.Unit = ao.Quantity.Unit
   629  	}
   630  }
   631  
   632  var PythonAggregatorList = []*Aggregator{
   633  	Function,
   634  	FunctionLine,
   635  	Directory,
   636  	File,
   637  	ThreadID,
   638  	ThreadName,
   639  }
   640  
   641  var GoAggregatorList = []*Aggregator{
   642  	Function,
   643  	FunctionLine,
   644  	Directory,
   645  	File,
   646  	Package,
   647  }
   648  
   649  var SpyAggregatorList = []*Aggregator{
   650  	Function,
   651  	FunctionLine,
   652  	Directory,
   653  	File,
   654  	ThreadName,
   655  }
   656  
   657  var PyroscopeNodeJSAggregatorList = []*Aggregator{
   658  	PyroNodeFunction,
   659  	PyroNodeFile,
   660  }
   661  
   662  var DDTraceDotnetAggregatorList = []*Aggregator{
   663  	Method,
   664  	Class,
   665  	Namespace,
   666  	Assembly,
   667  	ThreadID,
   668  	ThreadName,
   669  }
   670  
   671  var DDTracePHPAggregatorList = []*Aggregator{
   672  	PHPFunction,
   673  	FunctionLine,
   674  	Class,
   675  	File,
   676  	Directory,
   677  }