Skip to main content
Glama

mcp-pprof-anaylzer

cpu.go7.05 kB
package analyzer import ( "encoding/json" "fmt" "log" "sort" "strings" "time" "github.com/google/pprof/profile" ) // AnalyzeCPUProfile 分析 CPU profile 文件并返回格式化结果。 func AnalyzeCPUProfile(p *profile.Profile, topN int, format string) (string, error) { log.Printf("Analyzing CPU profile (Top %d, Format: %s)", topN, format) // --- 1. 确定用于分析的值的索引 (通常是 CPU 时间) --- valueIndex := -1 // CPU 时间样本值的索引 (通常是 1, 'samples/count' 是 0) for i, st := range p.SampleType { // 查找 'cpu' 和 'nanoseconds' 或类似的样本类型 if (st.Type == "cpu" || st.Type == "samples") && (st.Unit == "nanoseconds" || st.Unit == "count") { // 优先选择 'cpu'/'nanoseconds',否则选择 'samples'/'count' if valueIndex == -1 || st.Type == "cpu" { valueIndex = i } } } if valueIndex == -1 { if len(p.SampleType) > 1 { valueIndex = 1 // 如果未找到特定类型,则默认为第二个值类型 log.Printf("Warning: Could not definitively identify CPU time value type, defaulting to index 1: %s/%s", p.SampleType[valueIndex].Type, p.SampleType[valueIndex].Unit) } else if len(p.SampleType) == 1 { valueIndex = 0 // 使用唯一可用的类型 log.Printf("Warning: Only one sample type found, using index 0: %s/%s", p.SampleType[valueIndex].Type, p.SampleType[valueIndex].Unit) } else { return "", fmt.Errorf("无法从 profile 样本类型中确定值类型 (例如 cpu nanoseconds)") } } valueUnit := p.SampleType[valueIndex].Unit log.Printf("使用索引 %d (%s/%s) 进行 CPU 分析", valueIndex, p.SampleType[valueIndex].Type, valueUnit) // --- 2. 按函数聚合 Flat 时间 --- flatTime := make(map[string]int64) totalValue := int64(0) for _, s := range p.Sample { if len(s.Location) > 0 && len(s.Value) > valueIndex { v := s.Value[valueIndex] totalValue += v // Flat 时间归因于堆栈中最顶层的函数 loc := s.Location[0] for _, line := range loc.Line { if line.Function != nil { flatTime[line.Function.Name] += v // 每个样本的顶层框架只计算一次函数 break } } } } if totalValue == 0 { log.Printf("Warning: Total value for the selected sample type (%s/%s) is zero.", p.SampleType[valueIndex].Type, valueUnit) // 继续处理,可能只是一个空的 profile 或选择了错误的样本类型 } // --- 3. 按 Flat 时间对函数进行排序 --- stats := make([]functionStat, 0, len(flatTime)) for name, flat := range flatTime { stats = append(stats, functionStat{Name: name, Flat: flat}) } sort.Slice(stats, func(i, j int) bool { return stats[i].Flat > stats[j].Flat // 降序排列 }) // --- 4. 格式化输出 --- var b strings.Builder limit := topN if limit > len(stats) { limit = len(stats) } // 获取总持续时间 (用于计算百分比) totalDuration := time.Duration(p.DurationNanos) * time.Nanosecond if totalDuration == 0 && totalValue > 0 && valueUnit == "nanoseconds" { // 如果 DurationNanos 为零,则从样本总值估算持续时间 totalDuration = time.Duration(totalValue) * time.Nanosecond log.Printf("Profile DurationNanos is 0, estimated total duration from samples: %s", totalDuration) } switch format { case "text", "markdown": // 目前两者使用相似格式 if format == "markdown" { b.WriteString("```text\n") // 使用文本块以获得更好的对齐效果 } b.WriteString(fmt.Sprintf("CPU Profile Analysis (Top %d Functions by Flat Time)\n", topN)) b.WriteString(fmt.Sprintf("Total Samples/Time (%s): %s\n", valueUnit, FormatSampleValue(totalValue, valueUnit))) // 使用导出的 FormatSampleValue if totalDuration > 0 { b.WriteString(fmt.Sprintf("Total Duration: %s\n", totalDuration)) } b.WriteString("--------------------------------------------------\n") b.WriteString(fmt.Sprintf("%-15s %-15s %s\n", "Flat Time", "%", "Function Name")) b.WriteString("--------------------------------------------------\n") for i := 0; i < limit; i++ { stat := stats[i] percent := 0.0 // 如果 totalValue 不为零,则计算百分比 if totalValue != 0 { percent = (float64(stat.Flat) / float64(totalValue)) * 100 } b.WriteString(fmt.Sprintf("%-15s %-15.2f %s\n", FormatSampleValue(stat.Flat, valueUnit), percent, stat.Name)) // 使用导出的 FormatSampleValue } if format == "markdown" { b.WriteString("```\n") } case "json": result := CPUAnalysisResult{ // 使用 types.go 中的结构体 ProfileType: "cpu", ValueType: p.SampleType[valueIndex].Type, ValueUnit: valueUnit, TotalValue: totalValue, TotalValueFormatted: FormatSampleValue(totalValue, valueUnit), // 使用导出的 FormatSampleValue TopN: limit, Functions: make([]CPUFunctionStat, 0, limit), // 使用 types.go 中的结构体 } if totalDuration > 0 { result.TotalDurationNanos = totalDuration.Nanoseconds() } for i := 0; i < limit; i++ { stat := stats[i] percent := 0.0 if totalValue != 0 { percent = (float64(stat.Flat) / float64(totalValue)) * 100 } result.Functions = append(result.Functions, CPUFunctionStat{ // 使用 types.go 中的结构体 FunctionName: stat.Name, FlatValue: stat.Flat, FlatValueFormatted: FormatSampleValue(stat.Flat, valueUnit), // 使用导出的 FormatSampleValue Percentage: percent, }) } jsonBytes, err := json.MarshalIndent(result, "", " ") // 使用缩进美化输出 if err != nil { log.Printf("Error marshaling CPU analysis to JSON: %v", err) // 返回一个简单的 JSON 错误 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 CPU profile using value index %d", valueIndex) flameGraphRoot, err := BuildFlameGraphTree(p, valueIndex) // 调用新函数 if err != nil { log.Printf("Error building flame graph tree: %v", err) errorResult := ErrorResult{Error: fmt.Sprintf("Failed to build flame graph tree: %v", err)} errJsonBytes, _ := json.Marshal(errorResult) return string(errJsonBytes), nil // 返回错误信息,但不标记为分析错误 } jsonBytes, err := json.Marshal(flameGraphRoot) // 使用 Marshal 生成紧凑 JSON if err != nil { log.Printf("Error marshaling flame graph tree to JSON: %v", err) errorResult := ErrorResult{Error: fmt.Sprintf("Failed to marshal 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