79 lines
2 KiB
Go
79 lines
2 KiB
Go
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, " ")
|
|
}
|