diff --git a/internal/tools/shared.go b/internal/tools/shared.go new file mode 100644 index 0000000..6c640ce --- /dev/null +++ b/internal/tools/shared.go @@ -0,0 +1,79 @@ +package tools + +import ( + "fmt" + "os" + "path/filepath" + "sort" + "strings" + + "forge.lclr.dev/AI/xdebug-mcp/internal/cache" + "forge.lclr.dev/AI/xdebug-mcp/internal/cachegrind" +) + +// loadProfile returns a parsed Profile from cache or by parsing the file. +func loadProfile(filePath string, c *cache.Cache) (*cachegrind.Profile, error) { + abs, err := filepath.Abs(filePath) + if err != nil { + return nil, fmt.Errorf("resolve path %s: %w", filePath, err) + } + info, err := os.Stat(abs) + if err != nil { + return nil, fmt.Errorf("file not found: %s", abs) + } + if p, ok := c.Get(abs, info.ModTime()); ok { + return p, nil + } + p, err := cachegrind.ParseFile(abs) + if err != nil { + return nil, err + } + c.Set(abs, p, info.ModTime()) + return p, nil +} + +// findFunctions returns functions matching name (exact first, then contains). +// Returns a non-empty errMsg if nothing is found. +func findFunctions(p *cachegrind.Profile, name string) ([]*cachegrind.Function, string) { + if fns, ok := p.ByName[name]; ok { + return fns, "" + } + var matches []*cachegrind.Function + for _, fn := range p.Functions { + if strings.Contains(fn.Name, name) { + matches = append(matches, fn) + } + } + if len(matches) == 0 { + return nil, fmt.Sprintf("function %q not found in profile (no exact or contains match)", name) + } + return matches, "" +} + +// sortedByTime returns a copy of fns sorted by Costs[0] descending. +func sortedByTime(fns []*cachegrind.Function) []*cachegrind.Function { + sorted := make([]*cachegrind.Function, len(fns)) + copy(sorted, fns) + sort.Slice(sorted, func(i, j int) bool { + ci, cj := int64(0), int64(0) + if len(sorted[i].Costs) > 0 { + ci = sorted[i].Costs[0] + } + if len(sorted[j].Costs) > 0 { + cj = sorted[j].Costs[0] + } + return ci > cj + }) + return sorted +} + +// formatCosts formats a cost slice as "Event=value Event=value". +func formatCosts(costs []int64, events []string) string { + parts := make([]string, 0, len(events)) + for i, ev := range events { + if i < len(costs) { + parts = append(parts, fmt.Sprintf("%s=%d", ev, costs[i])) + } + } + return strings.Join(parts, " ") +}