Skip to main content
Glama

mcp-pprof-anaylzer

heap.go13.4 kB
package analyzer import ( "encoding/json" "fmt" "log" "sort" "strings" "github.com/google/pprof/profile" ) // AnalyzeHeapProfile 分析 Heap profile (主要关注 inuse_space) 并返回格式化结果。 func AnalyzeHeapProfile(p *profile.Profile, topN int, format string) (string, error) { log.Printf("Analyzing Heap profile (Top %d, Format: %s)", topN, format) // --- 1. 查找 'inuse_space' 的样本值索引 --- // 常见的索引:0:alloc_objects, 1:alloc_space, 2:inuse_objects, 3:inuse_space valueIndex := -1 objectsIndex := -1 // For tracking object counts for i, st := range p.SampleType { if st.Type == "inuse_space" && st.Unit == "bytes" { valueIndex = i } if st.Type == "inuse_objects" && st.Unit == "count" { objectsIndex = i } } // 回退方案:如果找不到 inuse_space,则尝试 alloc_space if valueIndex == -1 { for i, st := range p.SampleType { if st.Type == "alloc_space" && st.Unit == "bytes" { valueIndex = i log.Printf("Warning: 'inuse_space' not found, falling back to 'alloc_space'") break } } } // Fallback: If inuse_objects is not found, try alloc_objects if objectsIndex == -1 { for i, st := range p.SampleType { if st.Type == "alloc_objects" && st.Unit == "count" { objectsIndex = i log.Printf("Warning: 'inuse_objects' not found, falling back to 'alloc_objects'") break } } } // 回退方案:如果未找到特定类型,则尝试最后一个值 (通常是 inuse_space) if valueIndex == -1 && len(p.SampleType) > 0 { valueIndex = len(p.SampleType) - 1 log.Printf("Warning: Could not find 'inuse_space' or 'alloc_space', defaulting to last sample type index %d: %s/%s", valueIndex, p.SampleType[valueIndex].Type, p.SampleType[valueIndex].Unit) } if valueIndex == -1 { return "", fmt.Errorf("无法从 profile 样本类型中确定值类型 (例如 inuse_space bytes)") } valueUnit := p.SampleType[valueIndex].Unit valueType := p.SampleType[valueIndex].Type log.Printf("使用索引 %d (%s/%s) 进行 Heap 分析", valueIndex, valueType, valueUnit) if objectsIndex >= 0 { log.Printf("使用索引 %d (%s/%s) 进行对象计数", objectsIndex, p.SampleType[objectsIndex].Type, p.SampleType[objectsIndex].Unit) } // --- 2. Aggregate memory usage values by function and allocation site --- // Create two maps: one for aggregating by function, one for aggregating by allocation site funcValue := make(map[string]int64) // Aggregate by function name allocSiteValue := make(map[string]int64) // Aggregate by allocation site (function+file+line) funcObjects := make(map[string]int64) // Object count aggregated by function allocSiteObjects := make(map[string]int64) // Object count aggregated by allocation site // Maps for storing type information typeValue := make(map[string]int64) // Memory usage aggregated by type typeObjects := make(map[string]int64) // Object count aggregated by type totalValue := int64(0) totalObjects := int64(0) for _, s := range p.Sample { if len(s.Location) > 0 && len(s.Value) > valueIndex { v := s.Value[valueIndex] // Memory usage (bytes) totalValue += v // If object count information is available, collect it too var objCount int64 = 0 if objectsIndex >= 0 && len(s.Value) > objectsIndex { objCount = s.Value[objectsIndex] totalObjects += objCount } // Extract type information (if available) typeName := "unknown" if len(s.Label) > 0 { if typeLabels, ok := s.Label["type"]; ok && len(typeLabels) > 0 { typeName = typeLabels[0] } else if objLabels, ok := s.Label["object"]; ok && len(objLabels) > 0 { typeName = objLabels[0] } } // Aggregate by type typeValue[typeName] += v if objCount > 0 { typeObjects[typeName] += objCount } // Attribute memory to the topmost function in the allocation stack loc := s.Location[0] for _, line := range loc.Line { if line.Function != nil { funcName := line.Function.Name fileName := line.Function.Filename lineNum := line.Line // Aggregate by function funcValue[funcName] += v if objCount > 0 { funcObjects[funcName] += objCount } // Aggregate by allocation site (function+file+line) allocSiteKey := fmt.Sprintf("%s at %s:%d", funcName, fileName, lineNum) allocSiteValue[allocSiteKey] += v if objCount > 0 { allocSiteObjects[allocSiteKey] += objCount } break // Only attribute to the first function found in the top frame } } } } if totalValue == 0 { log.Printf("Warning: Total value for the selected sample type (%s/%s) is zero.", valueType, valueUnit) } // --- 3. Sort functions, allocation sites, and types by aggregated values --- // Sort by function funcStats := make([]functionStat, 0, len(funcValue)) for name, val := range funcValue { funcStats = append(funcStats, functionStat{Name: name, Flat: val}) } sort.Slice(funcStats, func(i, j int) bool { return funcStats[i].Flat > funcStats[j].Flat // Sort in descending order }) // Sort by allocation site type allocSiteStat struct { Site string Value int64 Count int64 } allocSiteStats := make([]allocSiteStat, 0, len(allocSiteValue)) for site, val := range allocSiteValue { count := allocSiteObjects[site] allocSiteStats = append(allocSiteStats, allocSiteStat{Site: site, Value: val, Count: count}) } sort.Slice(allocSiteStats, func(i, j int) bool { return allocSiteStats[i].Value > allocSiteStats[j].Value // Sort in descending order }) // Sort by type type typeStat struct { Type string Value int64 Count int64 } typeStats := make([]typeStat, 0, len(typeValue)) for typeName, val := range typeValue { count := typeObjects[typeName] typeStats = append(typeStats, typeStat{Type: typeName, Value: val, Count: count}) } sort.Slice(typeStats, func(i, j int) bool { return typeStats[i].Value > typeStats[j].Value // Sort in descending order }) // --- 4. Format output --- var b strings.Builder limit := topN if limit > len(funcStats) { limit = len(funcStats) } allocSiteLimit := limit if allocSiteLimit > len(allocSiteStats) { allocSiteLimit = len(allocSiteStats) } typeLimit := limit if typeLimit > len(typeStats) { typeLimit = len(typeStats) } switch format { case "text", "markdown": if format == "markdown" { b.WriteString("```text\n") } b.WriteString(fmt.Sprintf("Heap Profile Analysis (Top %d Functions by %s)\n", topN, valueType)) b.WriteString(fmt.Sprintf("Total %s (%s): %s\n", valueType, valueUnit, FormatBytes(totalValue))) if totalObjects > 0 { b.WriteString(fmt.Sprintf("Total Objects: %d\n", totalObjects)) } // Output by function b.WriteString("\n=== By Function ===\n") b.WriteString("--------------------------------------------------\n") b.WriteString(fmt.Sprintf("%-15s %-15s %s\n", valueType, "%", "Function Name")) b.WriteString("--------------------------------------------------\n") for i := 0; i < limit; i++ { stat := funcStats[i] percent := 0.0 if totalValue != 0 { percent = (float64(stat.Flat) / float64(totalValue)) * 100 } objStr := "" if count, ok := funcObjects[stat.Name]; ok && count > 0 { objStr = fmt.Sprintf(" (%d objects)", count) } b.WriteString(fmt.Sprintf("%-15s %-15.2f %s%s\n", FormatBytes(stat.Flat), percent, stat.Name, objStr)) } // Output by allocation site b.WriteString("\n=== By Allocation Site ===\n") b.WriteString("--------------------------------------------------\n") b.WriteString(fmt.Sprintf("%-15s %-15s %s\n", valueType, "%", "Allocation Site")) b.WriteString("--------------------------------------------------\n") for i := 0; i < allocSiteLimit; i++ { stat := allocSiteStats[i] percent := 0.0 if totalValue != 0 { percent = (float64(stat.Value) / float64(totalValue)) * 100 } objStr := "" if stat.Count > 0 { objStr = fmt.Sprintf(" (%d objects)", stat.Count) } b.WriteString(fmt.Sprintf("%-15s %-15.2f %s%s\n", FormatBytes(stat.Value), percent, stat.Site, objStr)) } if len(typeStats) > 0 && typeStats[0].Type != "unknown" { b.WriteString("\n=== By Type ===\n") b.WriteString("--------------------------------------------------\n") b.WriteString(fmt.Sprintf("%-15s %-15s %-15s %s\n", valueType, "%", "Avg Size", "Type")) b.WriteString("--------------------------------------------------\n") for i := 0; i < typeLimit; i++ { stat := typeStats[i] percent := 0.0 if totalValue != 0 { percent = (float64(stat.Value) / float64(totalValue)) * 100 } avgSize := int64(0) if stat.Count > 0 { avgSize = stat.Value / stat.Count } b.WriteString(fmt.Sprintf("%-15s %-15.2f %-15s %s (%d objects)\n", FormatBytes(stat.Value), percent, FormatBytes(avgSize), stat.Type, stat.Count)) } } if format == "markdown" { b.WriteString("```\n") } case "json": result := struct { ProfileType string `json:"profileType"` ValueType string `json:"valueType"` ValueUnit string `json:"valueUnit"` TotalValue int64 `json:"totalValue"` TotalValueFormatted string `json:"totalValueFormatted"` TotalObjects int64 `json:"totalObjects,omitempty"` TopN int `json:"topN"` Functions []HeapFunctionStat `json:"functions"` AllocationSites []AllocSiteStat `json:"allocationSites,omitempty"` Types []TypeStat `json:"types,omitempty"` }{ ProfileType: "heap", ValueType: valueType, ValueUnit: valueUnit, TotalValue: totalValue, TotalValueFormatted: FormatBytes(totalValue), // 使用导出的 FormatBytes TopN: limit, Functions: make([]HeapFunctionStat, 0, limit), } if totalObjects > 0 { result.TotalObjects = totalObjects } for i := 0; i < limit; i++ { stat := funcStats[i] percent := 0.0 if totalValue != 0 { percent = (float64(stat.Flat) / float64(totalValue)) * 100 } funcStat := HeapFunctionStat{ FunctionName: stat.Name, Value: stat.Flat, ValueFormatted: FormatBytes(stat.Flat), Percentage: percent, } result.Functions = append(result.Functions, funcStat) } if len(allocSiteStats) > 0 { result.AllocationSites = make([]AllocSiteStat, 0, allocSiteLimit) for i := 0; i < allocSiteLimit; i++ { stat := allocSiteStats[i] percent := 0.0 if totalValue != 0 { percent = (float64(stat.Value) / float64(totalValue)) * 100 } siteStat := AllocSiteStat{ Site: stat.Site, Value: stat.Value, ValueFormatted: FormatBytes(stat.Value), Percentage: percent, } if stat.Count > 0 { siteStat.ObjectCount = stat.Count avgSize := stat.Value / stat.Count siteStat.AvgSize = avgSize siteStat.AvgSizeFormatted = FormatBytes(avgSize) } result.AllocationSites = append(result.AllocationSites, siteStat) } } if len(typeStats) > 0 && typeStats[0].Type != "unknown" { result.Types = make([]TypeStat, 0, typeLimit) for i := 0; i < typeLimit; i++ { stat := typeStats[i] percent := 0.0 if totalValue != 0 { percent = (float64(stat.Value) / float64(totalValue)) * 100 } typeStat := TypeStat{ Type: stat.Type, Value: stat.Value, ValueFormatted: FormatBytes(stat.Value), Percentage: percent, } if stat.Count > 0 { typeStat.ObjectCount = stat.Count avgSize := stat.Value / stat.Count typeStat.AvgSize = avgSize typeStat.AvgSizeFormatted = FormatBytes(avgSize) } result.Types = append(result.Types, typeStat) } } jsonBytes, err := json.MarshalIndent(result, "", " ") if err != nil { log.Printf("Error marshaling Heap analysis to JSON: %v", err) errorResult := ErrorResult{Error: fmt.Sprintf("Failed to marshal result to JSON: %v", err)} // 使用 types.go 中的结构体 errJsonBytes, _ := json.Marshal(errorResult) return string(errJsonBytes), nil } return string(jsonBytes), nil case "flamegraph-json": log.Printf("Generating flame graph JSON for Heap profile (%s) using value index %d", valueType, valueIndex) // BuildFlameGraphTree will automatically detect this is a memory profile and find the objectsIndex // based on the valueType and valueUnit flameGraphRoot, err := BuildFlameGraphTree(p, valueIndex) if err != nil { log.Printf("Error building flame graph tree for heap: %v", err) errorResult := ErrorResult{Error: fmt.Sprintf("Failed to build flame graph tree for heap: %v", err)} errJsonBytes, _ := json.Marshal(errorResult) return string(errJsonBytes), nil } jsonBytes, err := json.Marshal(flameGraphRoot) // 使用 Marshal 生成紧凑 JSON if err != nil { log.Printf("Error marshaling heap flame graph tree to JSON: %v", err) errorResult := ErrorResult{Error: fmt.Sprintf("Failed to marshal heap flame graph tree to JSON: %v", err)} errJsonBytes, _ := json.Marshal(errorResult) return string(errJsonBytes), nil } return string(jsonBytes), nil default: return "", fmt.Errorf("unsupported output format: %s", format) } return b.String(), nil }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ZephyrDeng/pprof-analyzer-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server