code-review-agent / src /services /performance_analyzer.py
c1r3x's picture
Review Agent: Uploaded remaining files
aa300a4 verified
#!/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),
}