Spaces:
Sleeping
Sleeping
#!/usr/bin/env python3 | |
# -*- coding: utf-8 -*- | |
""" | |
Performance Analyzer Service | |
This module provides functionality for analyzing code performance across different languages. | |
""" | |
import os | |
import re | |
import logging | |
from collections import defaultdict | |
logger = logging.getLogger(__name__) | |
class PerformanceAnalyzer: | |
""" | |
Service for analyzing code performance across different languages. | |
""" | |
def __init__(self): | |
""" | |
Initialize the PerformanceAnalyzer. | |
""" | |
logger.info("Initialized PerformanceAnalyzer") | |
self.analyzers = { | |
'Python': self._analyze_python_performance, | |
'JavaScript': self._analyze_javascript_performance, | |
'TypeScript': self._analyze_typescript_performance, | |
'Java': self._analyze_java_performance, | |
'Go': self._analyze_go_performance, | |
'Rust': self._analyze_rust_performance, | |
} | |
# Initialize performance patterns for different languages | |
self._init_performance_patterns() | |
def _init_performance_patterns(self): | |
""" | |
Initialize performance patterns for different languages. | |
""" | |
# Python performance patterns | |
self.python_patterns = [ | |
{ | |
'name': 'Inefficient list comprehension', | |
'pattern': r'\[.*?for.*?in.*?for.*?in.*?\]', | |
'severity': 'medium', | |
'description': 'Nested list comprehensions can be inefficient for large datasets.', | |
'suggestion': 'Consider using itertools or breaking into separate operations.', | |
}, | |
{ | |
'name': 'String concatenation in loop', | |
'pattern': r'for.*?\+\=\s*[\'\"](.*?)[\'\"]', | |
'severity': 'medium', | |
'description': 'String concatenation in loops is inefficient in Python.', | |
'suggestion': 'Use string join() or a list of strings with join() at the end.', | |
}, | |
{ | |
'name': 'Global variable in loop', | |
'pattern': r'global\s+\w+.*?for\s+\w+\s+in', | |
'severity': 'medium', | |
'description': 'Modifying global variables in loops can be inefficient.', | |
'suggestion': 'Use local variables and return values instead.', | |
}, | |
{ | |
'name': 'Inefficient dict/list access in loop', | |
'pattern': r'for.*?in.*?:\s*.*?\[.*?\]\s*=', | |
'severity': 'medium', | |
'description': 'Repeatedly accessing dictionary or list elements in a loop can be inefficient.', | |
'suggestion': 'Consider using a local variable to store the accessed element.', | |
}, | |
{ | |
'name': 'Using range(len())', | |
'pattern': r'for\s+\w+\s+in\s+range\(len\(', | |
'severity': 'low', | |
'description': 'Using range(len()) is less readable than using enumerate().', | |
'suggestion': 'Use enumerate() instead of range(len()).', | |
}, | |
{ | |
'name': 'Inefficient regular expression', | |
'pattern': r're\.compile\([\'\"].*?[\+\*].*?[\'\"]\)', | |
'severity': 'medium', | |
'description': 'Complex regular expressions can be inefficient.', | |
'suggestion': 'Simplify the regular expression or use more specific patterns.', | |
}, | |
{ | |
'name': 'Large memory allocation', | |
'pattern': r'\[.*?for.*?in\s+range\(\d{7,}\)\]', | |
'severity': 'high', | |
'description': 'Creating large lists in memory can cause performance issues.', | |
'suggestion': 'Use generators or iterators instead of creating large lists.', | |
}, | |
{ | |
'name': 'Inefficient database query in loop', | |
'pattern': r'for.*?in.*?:\s*.*?\.execute\(', | |
'severity': 'high', | |
'description': 'Executing database queries in a loop can be very inefficient.', | |
'suggestion': 'Use batch operations or join queries instead of querying in a loop.', | |
}, | |
] | |
# JavaScript performance patterns | |
self.javascript_patterns = [ | |
{ | |
'name': 'DOM manipulation in loop', | |
'pattern': r'for\s*\(.*?\)\s*\{.*?document\..*?\}', | |
'severity': 'high', | |
'description': 'Manipulating the DOM inside loops can cause performance issues.', | |
'suggestion': 'Batch DOM updates or use DocumentFragment.', | |
}, | |
{ | |
'name': 'Inefficient array manipulation', | |
'pattern': r'for\s*\(.*?\)\s*\{.*?splice\(.*?\}', | |
'severity': 'medium', | |
'description': 'Using splice() in loops can be inefficient for large arrays.', | |
'suggestion': 'Consider using filter() or other array methods.', | |
}, | |
{ | |
'name': 'Creating functions in loops', | |
'pattern': r'for\s*\(.*?\)\s*\{.*?function\s*\(.*?\)\s*\{.*?\}.*?\}', | |
'severity': 'medium', | |
'description': 'Creating functions inside loops can lead to performance issues.', | |
'suggestion': 'Define the function outside the loop and reference it.', | |
}, | |
{ | |
'name': 'Inefficient string concatenation', | |
'pattern': r'for\s*\(.*?\)\s*\{.*?\+\=\s*[\'\"](.*?)[\'\"].*?\}', | |
'severity': 'medium', | |
'description': 'String concatenation in loops can be inefficient.', | |
'suggestion': 'Use array join() or template literals.', | |
}, | |
{ | |
'name': 'Using eval()', | |
'pattern': r'eval\(', | |
'severity': 'high', | |
'description': 'Using eval() is slow and can introduce security vulnerabilities.', | |
'suggestion': 'Avoid using eval() and use safer alternatives.', | |
}, | |
{ | |
'name': 'Inefficient event handling', | |
'pattern': r'addEventListener\([\'\"].*?[\'\"],\s*function', | |
'severity': 'medium', | |
'description': 'Anonymous functions in event listeners can lead to memory leaks.', | |
'suggestion': 'Use named functions for event handlers to allow proper cleanup.', | |
}, | |
] | |
# TypeScript performance patterns (extends JavaScript patterns) | |
self.typescript_patterns = self.javascript_patterns + [ | |
{ | |
'name': 'Inefficient type assertion', | |
'pattern': r'<.*?>\s*\(.*?\)', | |
'severity': 'low', | |
'description': 'Excessive type assertions can impact runtime performance.', | |
'suggestion': 'Use proper typing and interfaces instead of frequent type assertions.', | |
}, | |
{ | |
'name': 'Complex type definitions', | |
'pattern': r'type\s+\w+\s*=\s*\{[^\}]{500,}\}', | |
'severity': 'medium', | |
'description': 'Overly complex type definitions can slow down the TypeScript compiler.', | |
'suggestion': 'Break complex types into smaller, reusable interfaces.', | |
}, | |
] | |
# Java performance patterns | |
self.java_patterns = [ | |
{ | |
'name': 'Inefficient string concatenation', | |
'pattern': r'for\s*\(.*?\)\s*\{.*?\+\=\s*[\'\"](.*?)[\'\"].*?\}', | |
'severity': 'medium', | |
'description': 'String concatenation in loops is inefficient in Java.', | |
'suggestion': 'Use StringBuilder or StringBuffer instead.', | |
}, | |
{ | |
'name': 'Creating objects in loops', | |
'pattern': r'for\s*\(.*?\)\s*\{.*?new\s+\w+\(.*?\).*?\}', | |
'severity': 'medium', | |
'description': 'Creating objects inside loops can lead to excessive garbage collection.', | |
'suggestion': 'Create objects outside the loop or use object pooling.', | |
}, | |
{ | |
'name': 'Inefficient collection iteration', | |
'pattern': r'for\s*\(int\s+i\s*=\s*0.*?i\s*<\s*\w+\.size\(\).*?\)', | |
'severity': 'low', | |
'description': 'Calling size() in each iteration can be inefficient for some collections.', | |
'suggestion': 'Store the size in a variable before the loop.', | |
}, | |
{ | |
'name': 'Using boxed primitives in performance-critical code', | |
'pattern': r'(Integer|Boolean|Double|Float|Long)\s+\w+\s*=', | |
'severity': 'low', | |
'description': 'Using boxed primitives can be less efficient than primitive types.', | |
'suggestion': 'Use primitive types (int, boolean, etc.) in performance-critical code.', | |
}, | |
{ | |
'name': 'Inefficient exception handling', | |
'pattern': r'try\s*\{.*?\}\s*catch\s*\(Exception\s+\w+\)\s*\{', | |
'severity': 'medium', | |
'description': 'Catching generic exceptions can hide issues and impact performance.', | |
'suggestion': 'Catch specific exceptions and handle them appropriately.', | |
}, | |
] | |
# Go performance patterns | |
self.go_patterns = [ | |
{ | |
'name': 'Inefficient string concatenation', | |
'pattern': r'for\s+.*?\{.*?\+\=\s*[\'\"](.*?)[\'\"].*?\}', | |
'severity': 'medium', | |
'description': 'String concatenation in loops can be inefficient.', | |
'suggestion': 'Use strings.Builder for string concatenation in loops.', | |
}, | |
{ | |
'name': 'Inefficient slice operations', | |
'pattern': r'for\s+.*?\{.*?append\(.*?\}', | |
'severity': 'medium', | |
'description': 'Repeatedly appending to a slice can cause multiple allocations.', | |
'suggestion': 'Pre-allocate slices with make() when the size is known.', | |
}, | |
{ | |
'name': 'Mutex in hot path', | |
'pattern': r'func\s+\(.*?\)\s+\w+\(.*?\)\s+\{.*?Lock\(\).*?Unlock\(\)', | |
'severity': 'medium', | |
'description': 'Using mutexes in frequently called functions can impact performance.', | |
'suggestion': 'Consider using atomic operations or redesigning for less contention.', | |
}, | |
{ | |
'name': 'Inefficient map iteration', | |
'pattern': r'for\s+\w+,\s*_\s*:=\s*range', | |
'severity': 'low', | |
'description': 'Iterating over maps when only keys are needed can be inefficient.', | |
'suggestion': 'Use a slice for ordered data when possible.', | |
}, | |
] | |
# Rust performance patterns | |
self.rust_patterns = [ | |
{ | |
'name': 'Inefficient string operations', | |
'pattern': r'for\s+.*?\{.*?\.push_str\(.*?\}', | |
'severity': 'medium', | |
'description': 'Repeatedly pushing to strings can be inefficient.', | |
'suggestion': 'Use string concatenation with the format! macro or String::with_capacity().', | |
}, | |
{ | |
'name': 'Excessive cloning', | |
'pattern': r'\.clone\(\)', | |
'severity': 'medium', | |
'description': 'Excessive cloning can impact performance.', | |
'suggestion': 'Use references or ownership transfer where possible.', | |
}, | |
{ | |
'name': 'Inefficient vector operations', | |
'pattern': r'for\s+.*?\{.*?\.push\(.*?\}', | |
'severity': 'medium', | |
'description': 'Repeatedly pushing to vectors can cause multiple allocations.', | |
'suggestion': 'Pre-allocate vectors with Vec::with_capacity() when the size is known.', | |
}, | |
{ | |
'name': 'Box allocation in loops', | |
'pattern': r'for\s+.*?\{.*?Box::new\(.*?\}', | |
'severity': 'medium', | |
'description': 'Allocating boxes in loops can be inefficient.', | |
'suggestion': 'Allocate memory outside the loop when possible.', | |
}, | |
] | |
def analyze_repository(self, repo_path, languages): | |
""" | |
Analyze code performance in a repository for the specified languages. | |
Args: | |
repo_path (str): The path to the repository. | |
languages (list): A list of programming languages to analyze. | |
Returns: | |
dict: A dictionary containing performance analysis results for each language. | |
""" | |
logger.info(f"Analyzing performance in repository at {repo_path} for languages: {languages}") | |
results = {} | |
for language in languages: | |
if language in self.analyzers: | |
try: | |
logger.info(f"Analyzing {language} code performance in {repo_path}") | |
results[language] = self.analyzers[language](repo_path) | |
except Exception as e: | |
logger.error(f"Error analyzing {language} code performance: {e}") | |
results[language] = { | |
'status': 'error', | |
'error': str(e), | |
'issues': [], | |
} | |
else: | |
logger.warning(f"No performance analyzer available for {language}") | |
results[language] = { | |
'status': 'not_supported', | |
'message': f"Performance analysis for {language} is not supported yet.", | |
'issues': [], | |
} | |
# Identify hotspots (files with multiple performance issues) | |
hotspots = self._identify_hotspots(results) | |
return { | |
'language_results': results, | |
'hotspots': hotspots, | |
} | |
def _identify_hotspots(self, results): | |
""" | |
Identify performance hotspots across all languages. | |
Args: | |
results (dict): Performance analysis results for each language. | |
Returns: | |
list: A list of hotspot files with multiple performance issues. | |
""" | |
# Count issues per file across all languages | |
file_issue_count = defaultdict(int) | |
file_issues = defaultdict(list) | |
for language, language_result in results.items(): | |
for issue in language_result.get('issues', []): | |
file_path = issue.get('file', '') | |
if file_path: | |
file_issue_count[file_path] += 1 | |
file_issues[file_path].append(issue) | |
# Identify hotspots (files with multiple issues) | |
hotspots = [] | |
for file_path, count in sorted(file_issue_count.items(), key=lambda x: x[1], reverse=True): | |
if count >= 2: # Files with at least 2 issues are considered hotspots | |
hotspots.append({ | |
'file': file_path, | |
'issue_count': count, | |
'issues': file_issues[file_path], | |
}) | |
return hotspots[:10] # Return top 10 hotspots | |
def _analyze_python_performance(self, repo_path): | |
""" | |
Analyze Python code for performance issues. | |
Args: | |
repo_path (str): The path to the repository. | |
Returns: | |
dict: Performance analysis results for Python code. | |
""" | |
logger.info(f"Analyzing Python code performance in {repo_path}") | |
# Find Python files | |
python_files = [] | |
for root, _, files in os.walk(repo_path): | |
for file in files: | |
if file.endswith('.py'): | |
python_files.append(os.path.join(root, file)) | |
if not python_files: | |
return { | |
'status': 'no_files', | |
'message': 'No Python files found in the repository.', | |
'issues': [], | |
} | |
# Analyze each Python file | |
issues = [] | |
for file_path in python_files: | |
try: | |
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f: | |
content = f.read() | |
# Check for performance patterns | |
for pattern in self.python_patterns: | |
matches = re.finditer(pattern['pattern'], content) | |
for match in matches: | |
line_number = content[:match.start()].count('\n') + 1 | |
code_snippet = match.group(0) | |
issues.append({ | |
'file': file_path, | |
'line': line_number, | |
'code': code_snippet, | |
'issue': pattern['name'], | |
'description': pattern['description'], | |
'suggestion': pattern['suggestion'], | |
'severity': pattern['severity'], | |
'language': 'Python', | |
}) | |
except Exception as e: | |
logger.error(f"Error analyzing Python file {file_path}: {e}") | |
# Group issues by severity | |
issues_by_severity = defaultdict(list) | |
for issue in issues: | |
severity = issue.get('severity', 'unknown') | |
issues_by_severity[severity].append(issue) | |
return { | |
'status': 'success', | |
'issues': issues, | |
'issues_by_severity': dict(issues_by_severity), | |
'issue_count': len(issues), | |
'files_analyzed': len(python_files), | |
} | |
def _analyze_javascript_performance(self, repo_path): | |
""" | |
Analyze JavaScript code for performance issues. | |
Args: | |
repo_path (str): The path to the repository. | |
Returns: | |
dict: Performance analysis results for JavaScript code. | |
""" | |
logger.info(f"Analyzing JavaScript code performance in {repo_path}") | |
# Find JavaScript files | |
js_files = [] | |
for root, _, files in os.walk(repo_path): | |
if 'node_modules' in root: | |
continue | |
for file in files: | |
if file.endswith(('.js', '.jsx')): | |
js_files.append(os.path.join(root, file)) | |
if not js_files: | |
return { | |
'status': 'no_files', | |
'message': 'No JavaScript files found in the repository.', | |
'issues': [], | |
} | |
# Analyze each JavaScript file | |
issues = [] | |
for file_path in js_files: | |
try: | |
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f: | |
content = f.read() | |
# Check for performance patterns | |
for pattern in self.javascript_patterns: | |
matches = re.finditer(pattern['pattern'], content) | |
for match in matches: | |
line_number = content[:match.start()].count('\n') + 1 | |
code_snippet = match.group(0) | |
issues.append({ | |
'file': file_path, | |
'line': line_number, | |
'code': code_snippet, | |
'issue': pattern['name'], | |
'description': pattern['description'], | |
'suggestion': pattern['suggestion'], | |
'severity': pattern['severity'], | |
'language': 'JavaScript', | |
}) | |
except Exception as e: | |
logger.error(f"Error analyzing JavaScript file {file_path}: {e}") | |
# Group issues by severity | |
issues_by_severity = defaultdict(list) | |
for issue in issues: | |
severity = issue.get('severity', 'unknown') | |
issues_by_severity[severity].append(issue) | |
return { | |
'status': 'success', | |
'issues': issues, | |
'issues_by_severity': dict(issues_by_severity), | |
'issue_count': len(issues), | |
'files_analyzed': len(js_files), | |
} | |
def _analyze_typescript_performance(self, repo_path): | |
""" | |
Analyze TypeScript code for performance issues. | |
Args: | |
repo_path (str): The path to the repository. | |
Returns: | |
dict: Performance analysis results for TypeScript code. | |
""" | |
logger.info(f"Analyzing TypeScript code performance in {repo_path}") | |
# Find TypeScript files | |
ts_files = [] | |
for root, _, files in os.walk(repo_path): | |
if 'node_modules' in root: | |
continue | |
for file in files: | |
if file.endswith(('.ts', '.tsx')): | |
ts_files.append(os.path.join(root, file)) | |
if not ts_files: | |
return { | |
'status': 'no_files', | |
'message': 'No TypeScript files found in the repository.', | |
'issues': [], | |
} | |
# Analyze each TypeScript file | |
issues = [] | |
for file_path in ts_files: | |
try: | |
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f: | |
content = f.read() | |
# Check for performance patterns | |
for pattern in self.typescript_patterns: | |
matches = re.finditer(pattern['pattern'], content) | |
for match in matches: | |
line_number = content[:match.start()].count('\n') + 1 | |
code_snippet = match.group(0) | |
issues.append({ | |
'file': file_path, | |
'line': line_number, | |
'code': code_snippet, | |
'issue': pattern['name'], | |
'description': pattern['description'], | |
'suggestion': pattern['suggestion'], | |
'severity': pattern['severity'], | |
'language': 'TypeScript', | |
}) | |
except Exception as e: | |
logger.error(f"Error analyzing TypeScript file {file_path}: {e}") | |
# Group issues by severity | |
issues_by_severity = defaultdict(list) | |
for issue in issues: | |
severity = issue.get('severity', 'unknown') | |
issues_by_severity[severity].append(issue) | |
return { | |
'status': 'success', | |
'issues': issues, | |
'issues_by_severity': dict(issues_by_severity), | |
'issue_count': len(issues), | |
'files_analyzed': len(ts_files), | |
} | |
def _analyze_java_performance(self, repo_path): | |
""" | |
Analyze Java code for performance issues. | |
Args: | |
repo_path (str): The path to the repository. | |
Returns: | |
dict: Performance analysis results for Java code. | |
""" | |
logger.info(f"Analyzing Java code performance in {repo_path}") | |
# Find Java files | |
java_files = [] | |
for root, _, files in os.walk(repo_path): | |
for file in files: | |
if file.endswith('.java'): | |
java_files.append(os.path.join(root, file)) | |
if not java_files: | |
return { | |
'status': 'no_files', | |
'message': 'No Java files found in the repository.', | |
'issues': [], | |
} | |
# Analyze each Java file | |
issues = [] | |
for file_path in java_files: | |
try: | |
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f: | |
content = f.read() | |
# Check for performance patterns | |
for pattern in self.java_patterns: | |
matches = re.finditer(pattern['pattern'], content) | |
for match in matches: | |
line_number = content[:match.start()].count('\n') + 1 | |
code_snippet = match.group(0) | |
issues.append({ | |
'file': file_path, | |
'line': line_number, | |
'code': code_snippet, | |
'issue': pattern['name'], | |
'description': pattern['description'], | |
'suggestion': pattern['suggestion'], | |
'severity': pattern['severity'], | |
'language': 'Java', | |
}) | |
except Exception as e: | |
logger.error(f"Error analyzing Java file {file_path}: {e}") | |
# Group issues by severity | |
issues_by_severity = defaultdict(list) | |
for issue in issues: | |
severity = issue.get('severity', 'unknown') | |
issues_by_severity[severity].append(issue) | |
return { | |
'status': 'success', | |
'issues': issues, | |
'issues_by_severity': dict(issues_by_severity), | |
'issue_count': len(issues), | |
'files_analyzed': len(java_files), | |
} | |
def _analyze_go_performance(self, repo_path): | |
""" | |
Analyze Go code for performance issues. | |
Args: | |
repo_path (str): The path to the repository. | |
Returns: | |
dict: Performance analysis results for Go code. | |
""" | |
logger.info(f"Analyzing Go code performance in {repo_path}") | |
# Find Go files | |
go_files = [] | |
for root, _, files in os.walk(repo_path): | |
for file in files: | |
if file.endswith('.go'): | |
go_files.append(os.path.join(root, file)) | |
if not go_files: | |
return { | |
'status': 'no_files', | |
'message': 'No Go files found in the repository.', | |
'issues': [], | |
} | |
# Analyze each Go file | |
issues = [] | |
for file_path in go_files: | |
try: | |
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f: | |
content = f.read() | |
# Check for performance patterns | |
for pattern in self.go_patterns: | |
matches = re.finditer(pattern['pattern'], content) | |
for match in matches: | |
line_number = content[:match.start()].count('\n') + 1 | |
code_snippet = match.group(0) | |
issues.append({ | |
'file': file_path, | |
'line': line_number, | |
'code': code_snippet, | |
'issue': pattern['name'], | |
'description': pattern['description'], | |
'suggestion': pattern['suggestion'], | |
'severity': pattern['severity'], | |
'language': 'Go', | |
}) | |
except Exception as e: | |
logger.error(f"Error analyzing Go file {file_path}: {e}") | |
# Group issues by severity | |
issues_by_severity = defaultdict(list) | |
for issue in issues: | |
severity = issue.get('severity', 'unknown') | |
issues_by_severity[severity].append(issue) | |
return { | |
'status': 'success', | |
'issues': issues, | |
'issues_by_severity': dict(issues_by_severity), | |
'issue_count': len(issues), | |
'files_analyzed': len(go_files), | |
} | |
def _analyze_rust_performance(self, repo_path): | |
""" | |
Analyze Rust code for performance issues. | |
Args: | |
repo_path (str): The path to the repository. | |
Returns: | |
dict: Performance analysis results for Rust code. | |
""" | |
logger.info(f"Analyzing Rust code performance in {repo_path}") | |
# Find Rust files | |
rust_files = [] | |
for root, _, files in os.walk(repo_path): | |
for file in files: | |
if file.endswith('.rs'): | |
rust_files.append(os.path.join(root, file)) | |
if not rust_files: | |
return { | |
'status': 'no_files', | |
'message': 'No Rust files found in the repository.', | |
'issues': [], | |
} | |
# Analyze each Rust file | |
issues = [] | |
for file_path in rust_files: | |
try: | |
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f: | |
content = f.read() | |
# Check for performance patterns | |
for pattern in self.rust_patterns: | |
matches = re.finditer(pattern['pattern'], content) | |
for match in matches: | |
line_number = content[:match.start()].count('\n') + 1 | |
code_snippet = match.group(0) | |
issues.append({ | |
'file': file_path, | |
'line': line_number, | |
'code': code_snippet, | |
'issue': pattern['name'], | |
'description': pattern['description'], | |
'suggestion': pattern['suggestion'], | |
'severity': pattern['severity'], | |
'language': 'Rust', | |
}) | |
except Exception as e: | |
logger.error(f"Error analyzing Rust file {file_path}: {e}") | |
# Group issues by severity | |
issues_by_severity = defaultdict(list) | |
for issue in issues: | |
severity = issue.get('severity', 'unknown') | |
issues_by_severity[severity].append(issue) | |
return { | |
'status': 'success', | |
'issues': issues, | |
'issues_by_severity': dict(issues_by_severity), | |
'issue_count': len(issues), | |
'files_analyzed': len(rust_files), | |
} |