|
2 | 2 | from itertools import islice |
3 | 3 | import shutil |
4 | 4 | import subprocess |
5 | | -from typing import Any, Optional |
6 | | -from agents import RunContextWrapper, function_tool |
| 5 | +from typing import Optional |
| 6 | +from agents import RunContextWrapper, ToolOutputImage, function_tool |
7 | 7 | from llm_utils import number_group_of_lines |
8 | 8 | from perfparser import LineLoc |
9 | 9 |
|
10 | 10 | from accelerant.chat_interface import CodeSuggestion |
| 11 | +from accelerant.flamegraph import make_flamegraph_png, png_to_data_url |
11 | 12 | from accelerant.lsp import TOP_LEVEL_SYMBOL_KINDS, uri_to_relpath |
12 | 13 | from accelerant.patch import apply_simultaneous_suggestions |
| 14 | +from accelerant.perf import PerfData |
13 | 15 | from accelerant.util import find_symbol, truncate_for_llm |
14 | 16 | from accelerant.project import Project |
15 | 17 |
|
@@ -56,50 +58,65 @@ def check_codebase_for_errors( |
56 | 58 | return "OK: Codebase has no errors!" |
57 | 59 |
|
58 | 60 |
|
| 61 | +def _shared_build_and_run_perf(project: Project) -> PerfData: |
| 62 | + version = project.fs_sandbox().version() |
| 63 | + perf_data = project.perf_data(version) |
| 64 | + if perf_data is None: |
| 65 | + project.build_for_profiling() |
| 66 | + project.run_profiler() |
| 67 | + perf_data = project.perf_data(version) |
| 68 | + assert perf_data is not None, "perf data should be available after profiling" |
| 69 | + return perf_data |
| 70 | + |
| 71 | + |
59 | 72 | @function_tool |
60 | 73 | def run_perf_profiler( |
61 | 74 | ctx: RunContextWrapper[AgentContext], |
62 | | -) -> list[dict[str, Any]]: |
| 75 | +) -> list[dict]: |
63 | 76 | """Run a performance profiler on the target binary and return the top hotspots.""" |
64 | | - try: |
65 | | - project = ctx.context.project |
66 | | - version = project.fs_sandbox().version() |
67 | | - perf_data = project.perf_data(version) |
68 | | - if perf_data is None: |
69 | | - project.build_for_profiling() |
70 | | - project.run_profiler() |
71 | | - perf_data = project.perf_data(version) |
72 | | - assert perf_data is not None, "perf data should be available after profiling" |
73 | | - perf_tabulated = perf_data.tabulate() |
74 | | - NUM_HOTSPOTS = 5 |
75 | | - |
76 | | - def get_parent_region(loc: LineLoc) -> Optional[str]: |
77 | | - parent_sym = project.lsp().syncexec( |
78 | | - project.lsp().request_nearest_parent_symbol( |
79 | | - loc.path, loc.line - 1, TOP_LEVEL_SYMBOL_KINDS |
80 | | - ), |
81 | | - ) |
82 | | - if parent_sym is None: |
83 | | - return None |
84 | | - return parent_sym["name"] |
85 | | - |
86 | | - hotspots = list( |
87 | | - islice( |
88 | | - map( |
89 | | - lambda x: { |
90 | | - "parent_region": get_parent_region(x[0]) or "<unknown>", |
91 | | - "loc": x[0], |
92 | | - "pct_time": x[1] * 100, |
93 | | - }, |
94 | | - filter(lambda x: x[0].line > 0, perf_tabulated), |
95 | | - ), |
96 | | - NUM_HOTSPOTS, |
97 | | - ) |
| 77 | + project = ctx.context.project |
| 78 | + perf_data = _shared_build_and_run_perf(project) |
| 79 | + perf_tabulated = perf_data.tabulate() |
| 80 | + NUM_HOTSPOTS = 5 |
| 81 | + |
| 82 | + def get_parent_region(loc: LineLoc) -> Optional[str]: |
| 83 | + parent_sym = project.lsp().syncexec( |
| 84 | + project.lsp().request_nearest_parent_symbol( |
| 85 | + loc.path, loc.line - 1, TOP_LEVEL_SYMBOL_KINDS |
| 86 | + ), |
98 | 87 | ) |
99 | | - return hotspots |
100 | | - except Exception as e: |
101 | | - print("ERROR", e) |
102 | | - raise e |
| 88 | + if parent_sym is None: |
| 89 | + return None |
| 90 | + return parent_sym["name"] |
| 91 | + |
| 92 | + hotspots = list( |
| 93 | + islice( |
| 94 | + map( |
| 95 | + lambda x: { |
| 96 | + "parent_region": get_parent_region(x[0]) or "<unknown>", |
| 97 | + "loc": x[0], |
| 98 | + "pct_time": x[1] * 100, |
| 99 | + }, |
| 100 | + filter(lambda x: x[0].line > 0, perf_tabulated), |
| 101 | + ), |
| 102 | + NUM_HOTSPOTS, |
| 103 | + ) |
| 104 | + ) |
| 105 | + return hotspots |
| 106 | + |
| 107 | + |
| 108 | +@function_tool |
| 109 | +def generate_flamegraph( |
| 110 | + ctx: RunContextWrapper[AgentContext], |
| 111 | +) -> ToolOutputImage: |
| 112 | + """Generate a flamegraph PNG image from the performance data, building the project and running the profiler if necessary.""" |
| 113 | + project = ctx.context.project |
| 114 | + perf_data = _shared_build_and_run_perf(project) |
| 115 | + |
| 116 | + flamegraph_data = make_flamegraph_png(perf_data.data_path()) |
| 117 | + flamegraph_data_url = png_to_data_url(flamegraph_data) |
| 118 | + flamegraph_output = ToolOutputImage(image_url=flamegraph_data_url, detail="high") |
| 119 | + return flamegraph_output |
103 | 120 |
|
104 | 121 |
|
105 | 122 | @function_tool |
|
0 commit comments