#!/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), }