-
Notifications
You must be signed in to change notification settings - Fork 22
Integrate Line Profiler in Codeflash CF-470 #35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 47 commits
Commits
Show all changes
53 commits
Select commit
Hold shift + click to select a range
0e75144
todo
aseembits93 a9f6196
boilerplate ready, need to fill in the gaps
aseembits93 9ebbd61
wip
aseembits93 371ae4c
Merge remote-tracking branch 'origin/main' into line-profiler
aseembits93 f493836
avoid instrument_codeflash_capture
aseembits93 0220ee6
working mvp, need to parse though
aseembits93 339362f
undo coverage util modification
aseembits93 2f5baa9
cleaning up
aseembits93 de43f6b
Merge remote-tracking branch 'origin/main' into line-profiler
aseembits93 d171ba7
adding some runconfigs
aseembits93 8646196
line profiler results are saved in a temp file, need to pass to ai se…
aseembits93 51b8e27
Merge remote-tracking branch 'origin/main' into line-profiler
aseembits93 4247780
working demo of new opt candidates with lineprof info
aseembits93 52715ba
Merge remote-tracking branch 'origin/main' into line-profiler
aseembits93 ca0540e
removing env files
aseembits93 0a78e7e
concurrent execution of new optim candidates, readonly context testing
aseembits93 93623cb
still debugging readonly context code
aseembits93 102de11
works, need to follow type hints
aseembits93 e292831
Merge remote-tracking branch 'origin/main' into line-profiler
aseembits93 040a747
exception handling when lprof fails
aseembits93 7a413f6
feedback, cleanup
aseembits93 127f466
todo, improve testing
aseembits93 e224234
cleaning
aseembits93 3b9bee0
redo newline
aseembits93 e6a9066
better test, improve testing
aseembits93 c805624
Merge remote-tracking branch 'origin/main' into line-profiler
aseembits93 0b77d2a
wip concurrent optimization loop
aseembits93 81a6b78
Merge remote-tracking branch 'origin/main' into line-profiler
aseembits93 89e72b7
refactored to make a new category for line profiler tests
aseembits93 c00e324
works for any level of nested function
aseembits93 7ffe25f
works, optimization list length will be incorrectly displayed as we a…
aseembits93 d24d24c
fix for indentation in line profiler output
aseembits93 82f4138
cleaning up
aseembits93 29f4c1a
wrote some tests for instrumentation+testrun+parsing, todo, write som…
aseembits93 862eae8
Merge branch 'main' into line-profiler
aseembits93 ee90a08
putting file restore in the finally block
aseembits93 e17106f
putting file restore in the finally block and also handling failures …
aseembits93 b3e38f2
quick fix for merge with main
aseembits93 4af8442
minor fixes
aseembits93 d8246fc
pathlib for r/w
aseembits93 97c3cac
set up empty line profile results if there's an error
aseembits93 6418e7d
better exception handling for line profiler
aseembits93 275b88b
better type hinting
aseembits93 4b20465
add line_profiler as a requirement in pyproject toml
aseembits93 00abefb
more tests
aseembits93 0ab9dbf
import order issue
aseembits93 7e764ec
Merge branch 'main' into line-profiler
misrasaurabh1 a628dae
formatting changes
aseembits93 2abbfba
moving line profiler instrument tests in a separate file
aseembits93 b8614e0
tests galore
aseembits93 ab06721
Merge remote-tracking branch 'origin/main' into line-profiler
aseembits93 b269f87
nested classes handled now
aseembits93 3cab8ea
change max workers to one
aseembits93 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| from code_to_optimize.bubble_sort_in_class import BubbleSortClass | ||
|
|
||
|
|
||
| def sort_classmethod(x): | ||
| y = BubbleSortClass() | ||
| return y.sorter(x) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| from code_to_optimize.bubble_sort_in_nested_class import WrapperClass | ||
|
|
||
|
|
||
| def sort_classmethod(x): | ||
| y = WrapperClass.BubbleSortClass() | ||
| return y.sorter(x) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,234 @@ | ||
| """Adapted from line_profiler (https://github.com/pyutils/line_profiler) written by Enthought, Inc. (BSD License)""" | ||
| from collections import defaultdict | ||
|
|
||
| import isort | ||
| import libcst as cst | ||
| from pathlib import Path | ||
| from typing import Union, List | ||
| from libcst import ImportFrom, ImportAlias, Name | ||
|
|
||
| from codeflash.code_utils.code_utils import get_run_tmp_file | ||
|
|
||
|
|
||
| class LineProfilerDecoratorAdder(cst.CSTTransformer): | ||
| """Transformer that adds a decorator to a function with a specific qualified name.""" | ||
| #Todo we don't support nested functions yet so they can only be inside classes, dont use qualified names, instead use the structure | ||
| def __init__(self, qualified_name: str, decorator_name: str): | ||
| """ | ||
| Initialize the transformer. | ||
|
|
||
| Args: | ||
| qualified_name: The fully qualified name of the function to add the decorator to (e.g., "MyClass.nested_func.target_func"). | ||
| decorator_name: The name of the decorator to add. | ||
| """ | ||
| super().__init__() | ||
| self.qualified_name_parts = qualified_name.split(".") | ||
| self.decorator_name = decorator_name | ||
|
|
||
| # Track our current context path, only add when we encounter a class | ||
| self.context_stack = [] | ||
|
|
||
| def visit_ClassDef(self, node: cst.ClassDef) -> None: | ||
| # Track when we enter a class | ||
| self.context_stack.append(node.name.value) | ||
|
|
||
| def leave_ClassDef(self, original_node: cst.ClassDef, updated_node: cst.ClassDef) -> cst.ClassDef: | ||
| # Pop the context when we leave a class | ||
| self.context_stack.pop() | ||
| return updated_node | ||
|
|
||
| def visit_FunctionDef(self, node: cst.FunctionDef) -> None: | ||
| # Track when we enter a function | ||
| self.context_stack.append(node.name.value) | ||
|
|
||
| def leave_FunctionDef(self, original_node: cst.FunctionDef, updated_node: cst.FunctionDef) -> cst.FunctionDef: | ||
| function_name = original_node.name.value | ||
|
|
||
| # Check if the current context path matches our target qualified name | ||
| if self._matches_qualified_path(): | ||
| # Check if the decorator is already present | ||
| has_decorator = any( | ||
| self._is_target_decorator(decorator.decorator) | ||
| for decorator in original_node.decorators | ||
| ) | ||
|
|
||
| # Only add the decorator if it's not already there | ||
| if not has_decorator: | ||
| new_decorator = cst.Decorator( | ||
| decorator=cst.Name(value=self.decorator_name) | ||
| ) | ||
|
|
||
| # Add our new decorator to the existing decorators | ||
| updated_decorators = [new_decorator] + list(updated_node.decorators) | ||
| updated_node = updated_node.with_changes( | ||
| decorators=tuple(updated_decorators) | ||
| ) | ||
|
|
||
| # Pop the context when we leave a function | ||
| self.context_stack.pop() | ||
| return updated_node | ||
|
|
||
| def _matches_qualified_path(self) -> bool: | ||
| """Check if the current context stack matches the qualified name.""" | ||
| if len(self.context_stack) != len(self.qualified_name_parts): | ||
| return False | ||
|
|
||
| for i, name in enumerate(self.qualified_name_parts): | ||
| if self.context_stack[i] != name: | ||
| return False | ||
|
|
||
| return True | ||
|
|
||
| def _is_target_decorator(self, decorator_node: Union[cst.Name, cst.Attribute, cst.Call]) -> bool: | ||
| """Check if a decorator matches our target decorator name.""" | ||
| if isinstance(decorator_node, cst.Name): | ||
| return decorator_node.value == self.decorator_name | ||
| elif isinstance(decorator_node, cst.Call) and isinstance(decorator_node.func, cst.Name): | ||
| return decorator_node.func.value == self.decorator_name | ||
| return False | ||
|
|
||
| class ProfileEnableTransformer(cst.CSTTransformer): | ||
| def __init__(self,filename): | ||
| # Flag to track if we found the import statement | ||
| self.found_import = False | ||
| # Track indentation of the import statement | ||
| self.import_indentation = None | ||
| self.filename = filename | ||
|
|
||
| def leave_ImportFrom(self, original_node: cst.ImportFrom, updated_node: cst.ImportFrom) -> cst.ImportFrom: | ||
| # Check if this is the line profiler import statement | ||
| if (isinstance(original_node.module, cst.Name) and | ||
| original_node.module.value == "line_profiler" and | ||
| any(name.name.value == "profile" and | ||
| (not name.asname or name.asname.name.value == "codeflash_line_profile") | ||
| for name in original_node.names)): | ||
|
|
||
| self.found_import = True | ||
| # Get the indentation from the original node | ||
| if hasattr(original_node, "leading_lines"): | ||
| leading_whitespace = original_node.leading_lines[-1].whitespace if original_node.leading_lines else "" | ||
| self.import_indentation = leading_whitespace | ||
|
|
||
| return updated_node | ||
|
|
||
| def leave_Module(self, original_node: cst.Module, updated_node: cst.Module) -> cst.Module: | ||
| if not self.found_import: | ||
| return updated_node | ||
|
|
||
| # Create a list of statements from the original module | ||
| new_body = list(updated_node.body) | ||
|
|
||
| # Find the index of the import statement | ||
| import_index = None | ||
| for i, stmt in enumerate(new_body): | ||
| if isinstance(stmt, cst.SimpleStatementLine): | ||
| for small_stmt in stmt.body: | ||
| if isinstance(small_stmt, cst.ImportFrom): | ||
| if (isinstance(small_stmt.module, cst.Name) and | ||
| small_stmt.module.value == "line_profiler" and | ||
| any(name.name.value == "profile" and | ||
| (not name.asname or name.asname.name.value == "codeflash_line_profile") | ||
| for name in small_stmt.names)): | ||
| import_index = i | ||
| break | ||
| if import_index is not None: | ||
| break | ||
|
|
||
| if import_index is not None: | ||
| # Create the new enable statement to insert after the import | ||
| enable_statement = cst.parse_statement( | ||
| f"codeflash_line_profile.enable(output_prefix='{self.filename}')" | ||
| ) | ||
|
|
||
| # Insert the new statement after the import statement | ||
| new_body.insert(import_index + 1, enable_statement) | ||
|
|
||
| # Create a new module with the updated body | ||
| return updated_node.with_changes(body=new_body) | ||
|
|
||
| def add_decorator_to_qualified_function(module, qualified_name, decorator_name): | ||
| """ | ||
| Add a decorator to a function with the exact qualified name in the source code. | ||
|
|
||
| Args: | ||
| module: The Python source code as a string. | ||
| qualified_name: The fully qualified name of the function to add the decorator to (e.g., "MyClass.nested_func.target_func"). | ||
| decorator_name: The name of the decorator to add. | ||
|
|
||
| Returns: | ||
| The modified source code as a string. | ||
| """ | ||
| # Parse the source code into a CST | ||
|
|
||
| # Apply our transformer | ||
| transformer = LineProfilerDecoratorAdder(qualified_name, decorator_name) | ||
| modified_module = module.visit(transformer) | ||
|
|
||
| # Convert the modified CST back to source code | ||
| return modified_module | ||
|
|
||
| def add_profile_enable(original_code: str, line_profile_output_file: str) -> str: | ||
| # todo modify by using a libcst transformer | ||
| module = cst.parse_module(original_code) | ||
| transformer = ProfileEnableTransformer(line_profile_output_file) | ||
| modified_module = module.visit(transformer) | ||
| return modified_module.code | ||
|
|
||
|
|
||
| class ImportAdder(cst.CSTTransformer): | ||
| def __init__(self, import_statement): | ||
| self.import_statement = import_statement | ||
| self.has_import = False | ||
|
|
||
| def leave_Module(self, original_node, updated_node): | ||
| # If the import is already there, don't add it again | ||
| if self.has_import: | ||
| return updated_node | ||
|
|
||
| # Parse the import statement into a CST node | ||
| import_node = cst.parse_statement(self.import_statement) | ||
|
|
||
| # Add the import to the module's body | ||
| return updated_node.with_changes( | ||
| body=[import_node] + list(updated_node.body) | ||
| ) | ||
|
|
||
| def visit_ImportFrom(self, node): | ||
| # Check if the profile is already imported from line_profiler | ||
| if node.module and node.module.value == "line_profiler": | ||
| for import_alias in node.names: | ||
| if import_alias.name.value == "profile": | ||
| self.has_import = True | ||
|
|
||
|
|
||
| def add_decorator_imports(function_to_optimize, code_context): | ||
| """Adds a profile decorator to a function in a Python file and all its helper functions.""" | ||
| #self.function_to_optimize, file_path_to_helper_classes, self.test_cfg.tests_root | ||
| #grouped iteration, file to fns to optimize, from line_profiler import profile as codeflash_line_profile | ||
| file_paths = defaultdict(list) | ||
| line_profile_output_file = get_run_tmp_file(Path("baseline_lprof")) | ||
| file_paths[function_to_optimize.file_path].append(function_to_optimize.qualified_name) | ||
| for elem in code_context.helper_functions: | ||
| file_paths[elem.file_path].append(elem.qualified_name) | ||
| for file_path,fns_present in file_paths.items(): | ||
| #open file | ||
| file_contents = file_path.read_text("utf-8") | ||
| # parse to cst | ||
| module_node = cst.parse_module(file_contents) | ||
| for fn_name in fns_present: | ||
| # add decorator | ||
| module_node = add_decorator_to_qualified_function(module_node, fn_name, 'codeflash_line_profile') | ||
| # add imports | ||
| # Create a transformer to add the import | ||
| transformer = ImportAdder("from line_profiler import profile as codeflash_line_profile") | ||
| # Apply the transformer to add the import | ||
| module_node = module_node.visit(transformer) | ||
| modified_code = isort.code(module_node.code, float_to_top=True) | ||
| # write to file | ||
| with open(file_path, "w", encoding="utf-8") as file: | ||
| file.write(modified_code) | ||
| #Adding profile.enable line for changing the savepath of the data, do this only for the main file and not the helper files | ||
| file_contents = function_to_optimize.file_path.read_text("utf-8") | ||
| modified_code = add_profile_enable(file_contents,str(line_profile_output_file)) | ||
| function_to_optimize.file_path.write_text(modified_code,"utf-8") | ||
| return line_profile_output_file | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.