MarketAlly.AIPlugin.Extensions/MarketAlly.AIPlugin.Analysis/PerformanceAnalyzerPlugin.cs

1323 lines
45 KiB
C#
Executable File

using MarketAlly.AIPlugin;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace MarketAlly.AIPlugin.Analysis.Plugins
{
[AIPlugin("PerformanceAnalyzer", "Identifies performance bottlenecks and optimization opportunities in code")]
public class PerformanceAnalyzerPlugin : IAIPlugin
{
[AIParameter("Full path to the file or directory to analyze", required: true)]
public string Path { get; set; } = string.Empty;
[AIParameter("Analyze algorithm complexity", required: false)]
public bool AnalyzeComplexity { get; set; } = true;
[AIParameter("Check for inefficient loops and iterations", required: false)]
public bool CheckLoops { get; set; } = true;
[AIParameter("Analyze memory allocation patterns", required: false)]
public bool AnalyzeMemory { get; set; } = true;
[AIParameter("Check for database query optimization opportunities", required: false)]
public bool CheckDatabase { get; set; } = true;
[AIParameter("Suggest caching opportunities", required: false)]
public bool SuggestCaching { get; set; } = true;
[AIParameter("Performance analysis depth: basic, detailed, comprehensive", required: false)]
public string AnalysisDepth { get; set; } = "detailed";
public IReadOnlyDictionary<string, Type> SupportedParameters => new Dictionary<string, Type>
{
["path"] = typeof(string),
["analyzeComplexity"] = typeof(bool),
["checkLoops"] = typeof(bool),
["analyzeMemory"] = typeof(bool),
["checkDatabase"] = typeof(bool),
["suggestCaching"] = typeof(bool),
["analysisDepth"] = typeof(string)
};
public async Task<AIPluginResult> ExecuteAsync(IReadOnlyDictionary<string, object> parameters)
{
try
{
// Extract parameters
string path = parameters["path"]?.ToString() ?? string.Empty;
bool analyzeComplexity = GetBoolParameter(parameters, "analyzeComplexity", true);
bool checkLoops = GetBoolParameter(parameters, "checkLoops", true);
bool analyzeMemory = GetBoolParameter(parameters, "analyzeMemory", true);
bool checkDatabase = GetBoolParameter(parameters, "checkDatabase", true);
bool suggestCaching = GetBoolParameter(parameters, "suggestCaching", true);
string analysisDepth = parameters.TryGetValue("analysisDepth", out var depth)
? depth?.ToString() ?? "detailed"
: "detailed";
// Validate path
if (!File.Exists(path) && !Directory.Exists(path))
{
return new AIPluginResult(
new FileNotFoundException($"Path not found: {path}"),
"Path not found"
);
}
// Get files to analyze
var filesToAnalyze = GetFilesToAnalyze(path);
if (!filesToAnalyze.Any())
{
return new AIPluginResult(
new InvalidOperationException("No C# files found to analyze"),
"No files found"
);
}
// Initialize analyzers based on depth
var analyzers = GetAnalyzersForDepth(analysisDepth);
// Analyze performance for each file
var performanceIssues = new List<PerformanceIssue>();
var optimizationOpportunities = new List<OptimizationOpportunity>();
var cachingOpportunities = new List<CachingOpportunity>();
var complexityIssues = new List<ComplexityIssue>();
var loopOptimizations = new List<LoopOptimization>();
var memoryOptimizations = new List<MemoryOptimization>();
var databaseOptimizations = new List<DatabaseOptimization>();
foreach (string filePath in filesToAnalyze)
{
var fileResult = await AnalyzeFilePerformance(
filePath, analyzeComplexity, checkLoops, analyzeMemory,
checkDatabase, suggestCaching, analyzers);
performanceIssues.AddRange(fileResult.PerformanceIssues);
optimizationOpportunities.AddRange(fileResult.OptimizationOpportunities);
cachingOpportunities.AddRange(fileResult.CachingOpportunities);
complexityIssues.AddRange(fileResult.ComplexityIssues);
loopOptimizations.AddRange(fileResult.LoopOptimizations);
memoryOptimizations.AddRange(fileResult.MemoryOptimizations);
databaseOptimizations.AddRange(fileResult.DatabaseOptimizations);
}
// Calculate performance score (0-100, higher is better)
int performanceScore = CalculatePerformanceScore(
performanceIssues, optimizationOpportunities, filesToAnalyze.Count);
// Generate recommendations
var recommendations = GeneratePerformanceRecommendations(
performanceIssues, optimizationOpportunities, cachingOpportunities);
var result = new
{
Path = path,
FilesAnalyzed = filesToAnalyze.Count,
AnalysisDepth = analysisDepth,
PerformanceScore = performanceScore,
ComplexityIssues = analyzeComplexity ? complexityIssues.Select(i => new
{
i.MethodName,
i.ClassName,
i.FilePath,
i.LineNumber,
i.AlgorithmicComplexity,
i.Description,
i.Severity,
i.RecommendedAction
}).ToList() : null,
LoopOptimizations = checkLoops ? loopOptimizations.Select(l => new
{
l.MethodName,
l.ClassName,
l.FilePath,
l.LineNumber,
l.LoopType,
l.IssueType,
l.Description,
l.Severity,
l.Suggestion
}).ToList() : null,
MemoryOptimizations = analyzeMemory ? memoryOptimizations.Select(m => new
{
m.MethodName,
m.ClassName,
m.FilePath,
m.LineNumber,
m.IssueType,
m.Description,
m.EstimatedImpact,
m.Suggestion
}).ToList() : null,
DatabaseOptimizations = checkDatabase ? databaseOptimizations.Select(d => new
{
d.MethodName,
d.ClassName,
d.FilePath,
d.LineNumber,
d.QueryType,
d.IssueType,
d.Description,
d.Severity,
d.Suggestion
}).ToList() : null,
CachingOpportunities = suggestCaching ? cachingOpportunities.Select(c => new
{
c.MethodName,
c.ClassName,
c.FilePath,
c.LineNumber,
c.CacheType,
c.Rationale,
c.EstimatedBenefit,
c.Implementation
}).ToList() : null,
Recommendations = recommendations,
Summary = new
{
TotalIssues = performanceIssues.Count,
HighSeverityIssues = performanceIssues.Count(i => i.Severity == "High"),
OptimizationOpportunities = optimizationOpportunities.Count,
CachingOpportunities = cachingOpportunities.Count,
EstimatedPerformanceGain = CalculateEstimatedGain(optimizationOpportunities),
PriorityActions = GetPriorityActions(performanceIssues, optimizationOpportunities)
}
};
return new AIPluginResult(result,
$"Performance analysis completed for {filesToAnalyze.Count} files. " +
$"Found {performanceIssues.Count} issues and {optimizationOpportunities.Count} optimization opportunities.");
}
catch (Exception ex)
{
return new AIPluginResult(ex, "Failed to analyze performance");
}
}
private async Task<FilePerformanceResult> AnalyzeFilePerformance(
string filePath, bool analyzeComplexity, bool checkLoops, bool analyzeMemory,
bool checkDatabase, bool suggestCaching, List<IPerformanceAnalyzer> analyzers)
{
var sourceCode = await File.ReadAllTextAsync(filePath);
var syntaxTree = CSharpSyntaxTree.ParseText(sourceCode, path: filePath);
var root = await syntaxTree.GetRootAsync();
var result = new FilePerformanceResult
{
FilePath = filePath,
PerformanceIssues = new List<PerformanceIssue>(),
OptimizationOpportunities = new List<OptimizationOpportunity>(),
CachingOpportunities = new List<CachingOpportunity>(),
ComplexityIssues = new List<ComplexityIssue>(),
LoopOptimizations = new List<LoopOptimization>(),
MemoryOptimizations = new List<MemoryOptimization>(),
DatabaseOptimizations = new List<DatabaseOptimization>()
};
// Run enabled analyzers
foreach (var analyzer in analyzers)
{
var analysis = await analyzer.AnalyzeAsync(root, filePath, sourceCode);
result.PerformanceIssues.AddRange(analysis.PerformanceIssues);
result.OptimizationOpportunities.AddRange(analysis.OptimizationOpportunities);
if (analyzer is ComplexityAnalyzer && analyzeComplexity)
result.ComplexityIssues.AddRange(analysis.ComplexityIssues);
if (analyzer is LoopAnalyzer && checkLoops)
result.LoopOptimizations.AddRange(analysis.LoopOptimizations);
if (analyzer is MemoryAnalyzer && analyzeMemory)
result.MemoryOptimizations.AddRange(analysis.MemoryOptimizations);
if (analyzer is DatabaseAnalyzer && checkDatabase)
result.DatabaseOptimizations.AddRange(analysis.DatabaseOptimizations);
if (analyzer is CachingAnalyzer && suggestCaching)
result.CachingOpportunities.AddRange(analysis.CachingOpportunities);
}
return result;
}
private List<IPerformanceAnalyzer> GetAnalyzersForDepth(string depth)
{
var analyzers = new List<IPerformanceAnalyzer>();
switch (depth.ToLowerInvariant())
{
case "basic":
analyzers.Add(new LoopAnalyzer());
analyzers.Add(new BasicMemoryAnalyzer());
break;
case "detailed":
analyzers.Add(new ComplexityAnalyzer());
analyzers.Add(new LoopAnalyzer());
analyzers.Add(new MemoryAnalyzer());
analyzers.Add(new DatabaseAnalyzer());
break;
case "comprehensive":
analyzers.Add(new ComplexityAnalyzer());
analyzers.Add(new LoopAnalyzer());
analyzers.Add(new MemoryAnalyzer());
analyzers.Add(new DatabaseAnalyzer());
analyzers.Add(new CachingAnalyzer());
analyzers.Add(new AsyncAnalyzer());
analyzers.Add(new CollectionAnalyzer());
break;
default:
goto case "detailed";
}
return analyzers;
}
private List<string> GetFilesToAnalyze(string path)
{
var files = new List<string>();
if (File.Exists(path))
{
if (path.EndsWith(".cs", StringComparison.OrdinalIgnoreCase))
{
files.Add(path);
}
}
else if (Directory.Exists(path))
{
files.AddRange(Directory.GetFiles(path, "*.cs", SearchOption.AllDirectories)
.Where(f => !f.Contains("\\bin\\") && !f.Contains("\\obj\\") &&
!f.EndsWith(".Designer.cs") && !f.EndsWith(".g.cs")));
}
return files;
}
private int CalculatePerformanceScore(List<PerformanceIssue> issues,
List<OptimizationOpportunity> opportunities, int totalFiles)
{
if (totalFiles == 0) return 100;
// Base score
int score = 100;
// Deduct points for issues
var highSeverityIssues = issues.Count(i => i.Severity == "High");
var mediumSeverityIssues = issues.Count(i => i.Severity == "Medium");
var lowSeverityIssues = issues.Count(i => i.Severity == "Low");
score -= highSeverityIssues * 15; // -15 points per high severity
score -= mediumSeverityIssues * 8; // -8 points per medium severity
score -= lowSeverityIssues * 3; // -3 points per low severity
// Additional deduction for high opportunity count
if (opportunities.Count > totalFiles * 5)
{
score -= 20; // Penalty for many missed opportunities
}
return Math.Max(0, Math.Min(100, score));
}
private List<string> GeneratePerformanceRecommendations(
List<PerformanceIssue> issues,
List<OptimizationOpportunity> opportunities,
List<CachingOpportunity> cachingOpportunities)
{
var recommendations = new List<string>();
if (!issues.Any() && !opportunities.Any())
{
recommendations.Add("✅ No significant performance issues detected.");
return recommendations;
}
recommendations.Add("🚀 Performance Optimization Recommendations:");
// High priority issues
var highPriorityIssues = issues.Where(i => i.Severity == "High").ToList();
if (highPriorityIssues.Any())
{
recommendations.Add($"🔥 HIGH PRIORITY - Address {highPriorityIssues.Count} critical performance issue(s):");
foreach (var issue in highPriorityIssues.Take(5))
{
recommendations.Add($" • {issue.ClassName}.{issue.MethodName}: {issue.Description}");
}
}
// Memory optimizations
var memoryIssues = issues.Where(i => i.Category == "Memory").ToList();
if (memoryIssues.Any())
{
recommendations.Add($"💾 Memory Optimization ({memoryIssues.Count} opportunities):");
recommendations.Add(" • Use StringBuilder for string concatenations");
recommendations.Add(" • Implement IDisposable for resource management");
recommendations.Add(" • Consider object pooling for frequently allocated objects");
}
// Loop optimizations
var loopIssues = issues.Where(i => i.Category == "Loop").ToList();
if (loopIssues.Any())
{
recommendations.Add($"🔄 Loop Optimization ({loopIssues.Count} opportunities):");
recommendations.Add(" • Cache collection.Count in loop variables");
recommendations.Add(" • Use for loops instead of foreach where appropriate");
recommendations.Add(" • Consider LINQ optimizations or alternatives");
}
// Database optimizations
var dbIssues = issues.Where(i => i.Category == "Database").ToList();
if (dbIssues.Any())
{
recommendations.Add($"🗄️ Database Optimization ({dbIssues.Count} opportunities):");
recommendations.Add(" • Use async database operations");
recommendations.Add(" • Implement connection pooling");
recommendations.Add(" • Consider query optimization and indexing");
}
// Caching opportunities
if (cachingOpportunities.Any())
{
var highBenefitCaching = cachingOpportunities.Where(c => c.EstimatedBenefit == "High").ToList();
if (highBenefitCaching.Any())
{
recommendations.Add($"⚡ High-Impact Caching ({highBenefitCaching.Count} opportunities):");
foreach (var cache in highBenefitCaching.Take(3))
{
recommendations.Add($" • {cache.MethodName}: {cache.Rationale}");
}
}
}
// General best practices
recommendations.Add("📋 General Performance Best Practices:");
recommendations.Add(" • Use async/await for I/O operations");
recommendations.Add(" • Minimize allocations in hot paths");
recommendations.Add(" • Profile your application under realistic load");
recommendations.Add(" • Consider using Span<T> and Memory<T> for high-performance scenarios");
return recommendations;
}
private string CalculateEstimatedGain(List<OptimizationOpportunity> opportunities)
{
var highImpact = opportunities.Count(o => o.EstimatedImpact == "High");
var mediumImpact = opportunities.Count(o => o.EstimatedImpact == "Medium");
var lowImpact = opportunities.Count(o => o.EstimatedImpact == "Low");
if (highImpact > 0)
return $"Significant (>{highImpact} high-impact optimization{(highImpact > 1 ? "s" : "")})";
if (mediumImpact > 0)
return $"Moderate ({mediumImpact} medium-impact optimization{(mediumImpact > 1 ? "s" : "")})";
if (lowImpact > 0)
return $"Minor ({lowImpact} low-impact optimization{(lowImpact > 1 ? "s" : "")})";
return "Minimal";
}
private List<string> GetPriorityActions(List<PerformanceIssue> issues,
List<OptimizationOpportunity> opportunities)
{
var actions = new List<string>();
// Top 3 high severity issues
var topIssues = issues
.Where(i => i.Severity == "High")
.OrderBy(i => i.MethodName)
.Take(3);
foreach (var issue in topIssues)
{
actions.Add($"Fix {issue.Category.ToLower()} issue in {issue.ClassName}.{issue.MethodName}");
}
// Top 2 high impact opportunities
var topOpportunities = opportunities
.Where(o => o.EstimatedImpact == "High")
.Take(2);
foreach (var opp in topOpportunities)
{
actions.Add($"Implement {opp.OptimizationType} in {opp.ClassName}.{opp.MethodName}");
}
if (!actions.Any())
{
actions.Add("No critical performance issues identified");
}
return actions;
}
private bool GetBoolParameter(IReadOnlyDictionary<string, object> parameters, string key, bool defaultValue)
{
return parameters.TryGetValue(key, out var value) ? Convert.ToBoolean(value) : defaultValue;
}
}
// Supporting interfaces and classes
public interface IPerformanceAnalyzer
{
Task<PerformanceAnalysisResult> AnalyzeAsync(SyntaxNode root, string filePath, string sourceCode);
}
public class PerformanceAnalysisResult
{
public List<PerformanceIssue> PerformanceIssues { get; set; } = new();
public List<OptimizationOpportunity> OptimizationOpportunities { get; set; } = new();
public List<CachingOpportunity> CachingOpportunities { get; set; } = new();
public List<ComplexityIssue> ComplexityIssues { get; set; } = new();
public List<LoopOptimization> LoopOptimizations { get; set; } = new();
public List<MemoryOptimization> MemoryOptimizations { get; set; } = new();
public List<DatabaseOptimization> DatabaseOptimizations { get; set; } = new();
}
public class PerformanceIssue
{
public string MethodName { get; set; } = string.Empty;
public string ClassName { get; set; } = string.Empty;
public string FilePath { get; set; } = string.Empty;
public int LineNumber { get; set; }
public string Category { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public string Severity { get; set; } = string.Empty;
public string RecommendedAction { get; set; } = string.Empty;
}
public class OptimizationOpportunity
{
public string MethodName { get; set; } = string.Empty;
public string ClassName { get; set; } = string.Empty;
public string FilePath { get; set; } = string.Empty;
public int LineNumber { get; set; }
public string OptimizationType { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public string EstimatedImpact { get; set; } = string.Empty;
public string Suggestion { get; set; } = string.Empty;
}
public class ComplexityIssue
{
public string MethodName { get; set; } = string.Empty;
public string ClassName { get; set; } = string.Empty;
public string FilePath { get; set; } = string.Empty;
public int LineNumber { get; set; }
public string AlgorithmicComplexity { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public string Severity { get; set; } = string.Empty;
public string RecommendedAction { get; set; } = string.Empty;
}
public class LoopOptimization
{
public string MethodName { get; set; } = string.Empty;
public string ClassName { get; set; } = string.Empty;
public string FilePath { get; set; } = string.Empty;
public int LineNumber { get; set; }
public string LoopType { get; set; } = string.Empty;
public string IssueType { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public string Severity { get; set; } = string.Empty;
public string Suggestion { get; set; } = string.Empty;
}
public class MemoryOptimization
{
public string MethodName { get; set; } = string.Empty;
public string ClassName { get; set; } = string.Empty;
public string FilePath { get; set; } = string.Empty;
public int LineNumber { get; set; }
public string IssueType { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public string EstimatedImpact { get; set; } = string.Empty;
public string Suggestion { get; set; } = string.Empty;
}
public class DatabaseOptimization
{
public string MethodName { get; set; } = string.Empty;
public string ClassName { get; set; } = string.Empty;
public string FilePath { get; set; } = string.Empty;
public int LineNumber { get; set; }
public string QueryType { get; set; } = string.Empty;
public string IssueType { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public string Severity { get; set; } = string.Empty;
public string Suggestion { get; set; } = string.Empty;
}
public class CachingOpportunity
{
public string MethodName { get; set; } = string.Empty;
public string ClassName { get; set; } = string.Empty;
public string FilePath { get; set; } = string.Empty;
public int LineNumber { get; set; }
public string CacheType { get; set; } = string.Empty;
public string Rationale { get; set; } = string.Empty;
public string EstimatedBenefit { get; set; } = string.Empty;
public string Implementation { get; set; } = string.Empty;
}
public class FilePerformanceResult
{
public string FilePath { get; set; } = string.Empty;
public List<PerformanceIssue> PerformanceIssues { get; set; } = new();
public List<OptimizationOpportunity> OptimizationOpportunities { get; set; } = new();
public List<CachingOpportunity> CachingOpportunities { get; set; } = new();
public List<ComplexityIssue> ComplexityIssues { get; set; } = new();
public List<LoopOptimization> LoopOptimizations { get; set; } = new();
public List<MemoryOptimization> MemoryOptimizations { get; set; } = new();
public List<DatabaseOptimization> DatabaseOptimizations { get; set; } = new();
}
// Concrete analyzer implementations
public class LoopAnalyzer : IPerformanceAnalyzer
{
public Task<PerformanceAnalysisResult> AnalyzeAsync(SyntaxNode root, string filePath, string sourceCode)
{
var result = new PerformanceAnalysisResult();
var methods = root.DescendantNodes().OfType<MethodDeclarationSyntax>();
foreach (var method in methods)
{
var className = GetClassName(method);
var methodName = method.Identifier.ValueText;
// Analyze for loops
var forLoops = method.DescendantNodes().OfType<ForStatementSyntax>();
foreach (var forLoop in forLoops)
{
AnalyzeForLoop(forLoop, className, methodName, filePath, result);
}
// Analyze foreach loops
var foreachLoops = method.DescendantNodes().OfType<ForEachStatementSyntax>();
foreach (var foreachLoop in foreachLoops)
{
AnalyzeForeachLoop(foreachLoop, className, methodName, filePath, result);
}
// Analyze while loops
var whileLoops = method.DescendantNodes().OfType<WhileStatementSyntax>();
foreach (var whileLoop in whileLoops)
{
AnalyzeWhileLoop(whileLoop, className, methodName, filePath, result);
}
}
return Task.FromResult(result);
}
private void AnalyzeForLoop(ForStatementSyntax forLoop, string className, string methodName,
string filePath, PerformanceAnalysisResult result)
{
var lineNumber = forLoop.GetLocation().GetLineSpan().StartLinePosition.Line + 1;
// Check for inefficient condition evaluation
if (forLoop.Condition != null)
{
var conditionText = forLoop.Condition.ToString();
if (conditionText.Contains(".Count") || conditionText.Contains(".Length"))
{
result.LoopOptimizations.Add(new LoopOptimization
{
MethodName = methodName,
ClassName = className,
FilePath = filePath,
LineNumber = lineNumber,
LoopType = "for",
IssueType = "Repeated property access",
Description = "Loop condition evaluates collection.Count or array.Length on each iteration",
Severity = "Medium",
Suggestion = "Cache the collection size in a local variable before the loop"
});
}
}
}
private void AnalyzeForeachLoop(ForEachStatementSyntax foreachLoop, string className, string methodName,
string filePath, PerformanceAnalysisResult result)
{
var lineNumber = foreachLoop.GetLocation().GetLineSpan().StartLinePosition.Line + 1;
// Check if foreach is used with indexed access inside
var body = foreachLoop.Statement;
if (body != null)
{
var elementAccesses = body.DescendantNodes().OfType<ElementAccessExpressionSyntax>();
if (elementAccesses.Any())
{
result.LoopOptimizations.Add(new LoopOptimization
{
MethodName = methodName,
ClassName = className,
FilePath = filePath,
LineNumber = lineNumber,
LoopType = "foreach",
IssueType = "Indexed access in foreach",
Description = "Using indexed access inside foreach loop - consider using for loop instead",
Severity = "Low",
Suggestion = "Use for loop with index when you need indexed access"
});
}
}
}
private void AnalyzeWhileLoop(WhileStatementSyntax whileLoop, string className, string methodName,
string filePath, PerformanceAnalysisResult result)
{
var lineNumber = whileLoop.GetLocation().GetLineSpan().StartLinePosition.Line + 1;
// Basic analysis - could be expanded
var conditionText = whileLoop.Condition.ToString();
if (conditionText.Contains("true"))
{
result.PerformanceIssues.Add(new PerformanceIssue
{
MethodName = methodName,
ClassName = className,
FilePath = filePath,
LineNumber = lineNumber,
Category = "Loop",
Description = "Infinite loop detected - ensure proper exit condition",
Severity = "High",
RecommendedAction = "Add proper termination condition or use different loop construct"
});
}
}
private string GetClassName(SyntaxNode node)
{
return node.Ancestors().OfType<ClassDeclarationSyntax>().FirstOrDefault()?.Identifier.ValueText ?? "Unknown";
}
}
public class MemoryAnalyzer : IPerformanceAnalyzer
{
public Task<PerformanceAnalysisResult> AnalyzeAsync(SyntaxNode root, string filePath, string sourceCode)
{
var result = new PerformanceAnalysisResult();
var methods = root.DescendantNodes().OfType<MethodDeclarationSyntax>();
foreach (var method in methods)
{
var className = GetClassName(method);
var methodName = method.Identifier.ValueText;
AnalyzeStringConcatenation(method, className, methodName, filePath, result);
AnalyzeDisposablePatterns(method, className, methodName, filePath, result);
AnalyzeCollectionUsage(method, className, methodName, filePath, result);
}
return Task.FromResult(result);
}
private void AnalyzeStringConcatenation(MethodDeclarationSyntax method, string className,
string methodName, string filePath, PerformanceAnalysisResult result)
{
var lineNumber = method.GetLocation().GetLineSpan().StartLinePosition.Line + 1;
// Look for string concatenation in loops
var loops = method.DescendantNodes().Where(n =>
n is ForStatementSyntax || n is ForEachStatementSyntax || n is WhileStatementSyntax);
foreach (var loop in loops)
{
var assignments = loop.DescendantNodes().OfType<AssignmentExpressionSyntax>()
.Where(a => a.OperatorToken.IsKind(SyntaxKind.PlusEqualsToken));
foreach (var assignment in assignments)
{
if (IsStringType(assignment.Left))
{
result.MemoryOptimizations.Add(new MemoryOptimization
{
MethodName = methodName,
ClassName = className,
FilePath = filePath,
LineNumber = assignment.GetLocation().GetLineSpan().StartLinePosition.Line + 1,
IssueType = "String concatenation in loop",
Description = "String concatenation inside loop creates multiple temporary strings",
EstimatedImpact = "High",
Suggestion = "Use StringBuilder for multiple string concatenations"
});
}
}
}
}
private void AnalyzeDisposablePatterns(MethodDeclarationSyntax method, string className,
string methodName, string filePath, PerformanceAnalysisResult result)
{
var lineNumber = method.GetLocation().GetLineSpan().StartLinePosition.Line + 1;
// Look for new expressions that might create disposable objects
var objectCreations = method.DescendantNodes().OfType<ObjectCreationExpressionSyntax>();
foreach (var creation in objectCreations)
{
var typeName = creation.Type.ToString();
// Check for common disposable types not in using statements
if (IsDisposableType(typeName) && !IsInUsingStatement(creation))
{
result.MemoryOptimizations.Add(new MemoryOptimization
{
MethodName = methodName,
ClassName = className,
FilePath = filePath,
LineNumber = creation.GetLocation().GetLineSpan().StartLinePosition.Line + 1,
IssueType = "Disposable not in using statement",
Description = $"Creating {typeName} without proper disposal pattern",
EstimatedImpact = "Medium",
Suggestion = "Wrap disposable objects in using statements or implement try-finally"
});
}
}
}
private void AnalyzeCollectionUsage(MethodDeclarationSyntax method, string className,
string methodName, string filePath, PerformanceAnalysisResult result)
{
var lineNumber = method.GetLocation().GetLineSpan().StartLinePosition.Line + 1;
// Look for inefficient collection operations
var invocations = method.DescendantNodes().OfType<InvocationExpressionSyntax>();
foreach (var invocation in invocations)
{
var memberAccess = invocation.Expression as MemberAccessExpressionSyntax;
if (memberAccess != null)
{
var methodCall = memberAccess.Name.Identifier.ValueText;
// Check for inefficient LINQ operations
if (methodCall == "Count" && HasLinqWhere(invocation))
{
result.MemoryOptimizations.Add(new MemoryOptimization
{
MethodName = methodName,
ClassName = className,
FilePath = filePath,
LineNumber = invocation.GetLocation().GetLineSpan().StartLinePosition.Line + 1,
IssueType = "Inefficient LINQ usage",
Description = "Using Where().Count() instead of Count(predicate)",
EstimatedImpact = "Low",
Suggestion = "Use Count(predicate) instead of Where(predicate).Count()"
});
}
if (methodCall == "Any" && HasLinqWhere(invocation))
{
result.MemoryOptimizations.Add(new MemoryOptimization
{
MethodName = methodName,
ClassName = className,
FilePath = filePath,
LineNumber = invocation.GetLocation().GetLineSpan().StartLinePosition.Line + 1,
IssueType = "Inefficient LINQ usage",
Description = "Using Where().Any() instead of Any(predicate)",
EstimatedImpact = "Low",
Suggestion = "Use Any(predicate) instead of Where(predicate).Any()"
});
}
}
}
}
private bool IsStringType(SyntaxNode node)
{
// Simple heuristic - could be improved with semantic analysis
return node.ToString().Contains("string") || node.ToString().Contains("String");
}
private bool IsDisposableType(string typeName)
{
var disposableTypes = new[]
{
"FileStream", "StreamReader", "StreamWriter", "HttpClient", "SqlConnection",
"SqlCommand", "DbConnection", "DbCommand", "BinaryReader", "BinaryWriter",
"StringReader", "StringWriter", "MemoryStream", "NetworkStream"
};
return disposableTypes.Any(dt => typeName.Contains(dt));
}
private bool IsInUsingStatement(SyntaxNode node)
{
return node.Ancestors().OfType<UsingStatementSyntax>().Any();
}
private bool HasLinqWhere(InvocationExpressionSyntax invocation)
{
var memberAccess = invocation.Expression as MemberAccessExpressionSyntax;
if (memberAccess?.Expression is InvocationExpressionSyntax parentInvocation)
{
var parentMemberAccess = parentInvocation.Expression as MemberAccessExpressionSyntax;
return parentMemberAccess?.Name.Identifier.ValueText == "Where";
}
return false;
}
private string GetClassName(SyntaxNode node)
{
return node.Ancestors().OfType<ClassDeclarationSyntax>().FirstOrDefault()?.Identifier.ValueText ?? "Unknown";
}
}
public class BasicMemoryAnalyzer : MemoryAnalyzer
{
// Simplified version for basic analysis
}
public class DatabaseAnalyzer : IPerformanceAnalyzer
{
public Task<PerformanceAnalysisResult> AnalyzeAsync(SyntaxNode root, string filePath, string sourceCode)
{
var result = new PerformanceAnalysisResult();
var methods = root.DescendantNodes().OfType<MethodDeclarationSyntax>();
foreach (var method in methods)
{
var className = GetClassName(method);
var methodName = method.Identifier.ValueText;
AnalyzeDatabaseOperations(method, className, methodName, filePath, result);
AnalyzeAsyncPatterns(method, className, methodName, filePath, result);
}
return Task.FromResult(result);
}
private void AnalyzeDatabaseOperations(MethodDeclarationSyntax method, string className,
string methodName, string filePath, PerformanceAnalysisResult result)
{
var lineNumber = method.GetLocation().GetLineSpan().StartLinePosition.Line + 1;
// Look for database-related method calls
var invocations = method.DescendantNodes().OfType<InvocationExpressionSyntax>();
foreach (var invocation in invocations)
{
var memberAccess = invocation.Expression as MemberAccessExpressionSyntax;
if (memberAccess != null)
{
var methodCall = memberAccess.Name.Identifier.ValueText;
// Check for synchronous database operations
if (IsDatabaseMethod(methodCall) && !IsAsyncMethod(method))
{
result.DatabaseOptimizations.Add(new DatabaseOptimization
{
MethodName = methodName,
ClassName = className,
FilePath = filePath,
LineNumber = invocation.GetLocation().GetLineSpan().StartLinePosition.Line + 1,
QueryType = "Database Operation",
IssueType = "Synchronous database call",
Description = $"Synchronous {methodCall} call can block thread",
Severity = "Medium",
Suggestion = $"Use {methodCall}Async() and make method async"
});
}
// Check for potential N+1 queries
if (IsInLoop(invocation) && IsDatabaseMethod(methodCall))
{
result.DatabaseOptimizations.Add(new DatabaseOptimization
{
MethodName = methodName,
ClassName = className,
FilePath = filePath,
LineNumber = invocation.GetLocation().GetLineSpan().StartLinePosition.Line + 1,
QueryType = "Database Operation",
IssueType = "Potential N+1 query",
Description = "Database query inside loop may cause N+1 query problem",
Severity = "High",
Suggestion = "Consider batching queries or using joins/includes"
});
}
}
}
}
private void AnalyzeAsyncPatterns(MethodDeclarationSyntax method, string className,
string methodName, string filePath, PerformanceAnalysisResult result)
{
var lineNumber = method.GetLocation().GetLineSpan().StartLinePosition.Line + 1;
// Look for .Result or .Wait() calls on async operations
var memberAccesses = method.DescendantNodes().OfType<MemberAccessExpressionSyntax>();
foreach (var memberAccess in memberAccesses)
{
if (memberAccess.Name.Identifier.ValueText == "Result" ||
memberAccess.Name.Identifier.ValueText == "Wait")
{
result.DatabaseOptimizations.Add(new DatabaseOptimization
{
MethodName = methodName,
ClassName = className,
FilePath = filePath,
LineNumber = memberAccess.GetLocation().GetLineSpan().StartLinePosition.Line + 1,
QueryType = "Async Pattern",
IssueType = "Blocking async operation",
Description = "Using .Result or .Wait() can cause deadlocks",
Severity = "High",
Suggestion = "Use await instead of .Result/.Wait()"
});
}
}
}
private bool IsDatabaseMethod(string methodName)
{
var dbMethods = new[]
{
"ExecuteScalar", "ExecuteNonQuery", "ExecuteReader", "Query", "QueryFirst",
"QuerySingle", "Execute", "SaveChanges", "Add", "Update", "Remove", "Find"
};
return dbMethods.Contains(methodName);
}
private bool IsAsyncMethod(MethodDeclarationSyntax method)
{
return method.Modifiers.Any(m => m.IsKind(SyntaxKind.AsyncKeyword));
}
private bool IsInLoop(SyntaxNode node)
{
return node.Ancestors().Any(a =>
a is ForStatementSyntax || a is ForEachStatementSyntax || a is WhileStatementSyntax);
}
private string GetClassName(SyntaxNode node)
{
return node.Ancestors().OfType<ClassDeclarationSyntax>().FirstOrDefault()?.Identifier.ValueText ?? "Unknown";
}
}
public class CachingAnalyzer : IPerformanceAnalyzer
{
public Task<PerformanceAnalysisResult> AnalyzeAsync(SyntaxNode root, string filePath, string sourceCode)
{
var result = new PerformanceAnalysisResult();
var methods = root.DescendantNodes().OfType<MethodDeclarationSyntax>();
foreach (var method in methods)
{
var className = GetClassName(method);
var methodName = method.Identifier.ValueText;
AnalyzeCachingOpportunities(method, className, methodName, filePath, result);
}
return Task.FromResult(result);
}
private void AnalyzeCachingOpportunities(MethodDeclarationSyntax method, string className,
string methodName, string filePath, PerformanceAnalysisResult result)
{
var lineNumber = method.GetLocation().GetLineSpan().StartLinePosition.Line + 1;
// Look for expensive operations that could benefit from caching
var invocations = method.DescendantNodes().OfType<InvocationExpressionSyntax>();
foreach (var invocation in invocations)
{
var memberAccess = invocation.Expression as MemberAccessExpressionSyntax;
if (memberAccess != null)
{
var methodCall = memberAccess.Name.Identifier.ValueText;
// Database queries
if (IsDatabaseMethod(methodCall) && HasSimpleParameters(invocation))
{
result.CachingOpportunities.Add(new CachingOpportunity
{
MethodName = methodName,
ClassName = className,
FilePath = filePath,
LineNumber = invocation.GetLocation().GetLineSpan().StartLinePosition.Line + 1,
CacheType = "Query Result Cache",
Rationale = "Database query with deterministic parameters",
EstimatedBenefit = "High",
Implementation = "Consider using IMemoryCache or distributed cache"
});
}
// HTTP calls
if (IsHttpMethod(methodCall))
{
result.CachingOpportunities.Add(new CachingOpportunity
{
MethodName = methodName,
ClassName = className,
FilePath = filePath,
LineNumber = invocation.GetLocation().GetLineSpan().StartLinePosition.Line + 1,
CacheType = "HTTP Response Cache",
Rationale = "External HTTP call that could be cached",
EstimatedBenefit = "Medium",
Implementation = "Implement HTTP response caching with appropriate TTL"
});
}
// Expensive computations
if (IsExpensiveComputation(methodCall))
{
result.CachingOpportunities.Add(new CachingOpportunity
{
MethodName = methodName,
ClassName = className,
FilePath = filePath,
LineNumber = invocation.GetLocation().GetLineSpan().StartLinePosition.Line + 1,
CacheType = "Computation Cache",
Rationale = "Expensive computation that could be memoized",
EstimatedBenefit = "Medium",
Implementation = "Consider memoization pattern or result caching"
});
}
}
}
}
private bool IsDatabaseMethod(string methodName)
{
return new[] { "Query", "QueryFirst", "QuerySingle", "Find", "Where", "Select" }.Contains(methodName);
}
private bool IsHttpMethod(string methodName)
{
return new[] { "GetAsync", "PostAsync", "PutAsync", "DeleteAsync", "SendAsync" }.Contains(methodName);
}
private bool IsExpensiveComputation(string methodName)
{
return new[] { "Calculate", "Compute", "Process", "Transform", "Parse", "Serialize" }
.Any(keyword => methodName.Contains(keyword));
}
private bool HasSimpleParameters(InvocationExpressionSyntax invocation)
{
// Simple heuristic: check if parameters are likely to be cacheable
return invocation.ArgumentList.Arguments.Count <= 3;
}
private string GetClassName(SyntaxNode node)
{
return node.Ancestors().OfType<ClassDeclarationSyntax>().FirstOrDefault()?.Identifier.ValueText ?? "Unknown";
}
}
public class ComplexityAnalyzer : IPerformanceAnalyzer
{
public Task<PerformanceAnalysisResult> AnalyzeAsync(SyntaxNode root, string filePath, string sourceCode)
{
var result = new PerformanceAnalysisResult();
var methods = root.DescendantNodes().OfType<MethodDeclarationSyntax>();
foreach (var method in methods)
{
var className = GetClassName(method);
var methodName = method.Identifier.ValueText;
AnalyzeAlgorithmicComplexity(method, className, methodName, filePath, result);
}
return Task.FromResult(result);
}
private void AnalyzeAlgorithmicComplexity(MethodDeclarationSyntax method, string className,
string methodName, string filePath, PerformanceAnalysisResult result)
{
var lineNumber = method.GetLocation().GetLineSpan().StartLinePosition.Line + 1;
var complexity = EstimateComplexity(method);
if (complexity.StartsWith("O(n²)") || complexity.StartsWith("O(n³)"))
{
result.ComplexityIssues.Add(new ComplexityIssue
{
MethodName = methodName,
ClassName = className,
FilePath = filePath,
LineNumber = lineNumber,
AlgorithmicComplexity = complexity,
Description = $"Method has {complexity} algorithmic complexity",
Severity = complexity.StartsWith("O(n³)") ? "High" : "Medium",
RecommendedAction = "Consider optimizing algorithm or using more efficient data structures"
});
}
}
private string EstimateComplexity(MethodDeclarationSyntax method)
{
var nestedLoopDepth = CalculateNestedLoopDepth(method);
return nestedLoopDepth switch
{
0 => "O(1)",
1 => "O(n)",
2 => "O(n²)",
3 => "O(n³)",
_ => $"O(n^{nestedLoopDepth})"
};
}
private int CalculateNestedLoopDepth(SyntaxNode node)
{
int maxDepth = 0;
CalculateNestedDepth(node, 0, ref maxDepth);
return maxDepth;
}
private void CalculateNestedDepth(SyntaxNode node, int currentDepth, ref int maxDepth)
{
if (node is ForStatementSyntax || node is ForEachStatementSyntax || node is WhileStatementSyntax)
{
currentDepth++;
maxDepth = Math.Max(maxDepth, currentDepth);
}
foreach (var child in node.ChildNodes())
{
CalculateNestedDepth(child, currentDepth, ref maxDepth);
}
}
private string GetClassName(SyntaxNode node)
{
return node.Ancestors().OfType<ClassDeclarationSyntax>().FirstOrDefault()?.Identifier.ValueText ?? "Unknown";
}
}
public class AsyncAnalyzer : IPerformanceAnalyzer
{
public Task<PerformanceAnalysisResult> AnalyzeAsync(SyntaxNode root, string filePath, string sourceCode)
{
var result = new PerformanceAnalysisResult();
var methods = root.DescendantNodes().OfType<MethodDeclarationSyntax>();
foreach (var method in methods)
{
var className = GetClassName(method);
var methodName = method.Identifier.ValueText;
AnalyzeAsyncUsage(method, className, methodName, filePath, result);
}
return Task.FromResult(result);
}
private void AnalyzeAsyncUsage(MethodDeclarationSyntax method, string className,
string methodName, string filePath, PerformanceAnalysisResult result)
{
var lineNumber = method.GetLocation().GetLineSpan().StartLinePosition.Line + 1;
// Check for missing async/await patterns
var awaitExpressions = method.DescendantNodes().OfType<AwaitExpressionSyntax>();
var isAsync = method.Modifiers.Any(m => m.IsKind(SyntaxKind.AsyncKeyword));
if (awaitExpressions.Any() && !isAsync)
{
result.PerformanceIssues.Add(new PerformanceIssue
{
MethodName = methodName,
ClassName = className,
FilePath = filePath,
LineNumber = lineNumber,
Category = "Async",
Description = "Method uses await but is not marked as async",
Severity = "High",
RecommendedAction = "Add async modifier to method signature"
});
}
}
private string GetClassName(SyntaxNode node)
{
return node.Ancestors().OfType<ClassDeclarationSyntax>().FirstOrDefault()?.Identifier.ValueText ?? "Unknown";
}
}
public class CollectionAnalyzer : IPerformanceAnalyzer
{
public Task<PerformanceAnalysisResult> AnalyzeAsync(SyntaxNode root, string filePath, string sourceCode)
{
var result = new PerformanceAnalysisResult();
var methods = root.DescendantNodes().OfType<MethodDeclarationSyntax>();
foreach (var method in methods)
{
var className = GetClassName(method);
var methodName = method.Identifier.ValueText;
AnalyzeCollectionUsage(method, className, methodName, filePath, result);
}
return Task.FromResult(result);
}
private void AnalyzeCollectionUsage(MethodDeclarationSyntax method, string className,
string methodName, string filePath, PerformanceAnalysisResult result)
{
var lineNumber = method.GetLocation().GetLineSpan().StartLinePosition.Line + 1;
// Look for inefficient collection operations
var invocations = method.DescendantNodes().OfType<InvocationExpressionSyntax>();
foreach (var invocation in invocations)
{
var memberAccess = invocation.Expression as MemberAccessExpressionSyntax;
if (memberAccess != null)
{
var methodCall = memberAccess.Name.Identifier.ValueText;
// Multiple enumeration
if (methodCall == "ToList" && IsChainedWithLinq(invocation))
{
result.OptimizationOpportunities.Add(new OptimizationOpportunity
{
MethodName = methodName,
ClassName = className,
FilePath = filePath,
LineNumber = invocation.GetLocation().GetLineSpan().StartLinePosition.Line + 1,
OptimizationType = "Collection optimization",
Description = "Multiple LINQ operations - consider optimizing query",
EstimatedImpact = "Medium",
Suggestion = "Combine LINQ operations or use more efficient approach"
});
}
}
}
}
private bool IsChainedWithLinq(InvocationExpressionSyntax invocation)
{
var memberAccess = invocation.Expression as MemberAccessExpressionSyntax;
return memberAccess?.Expression is InvocationExpressionSyntax;
}
private string GetClassName(SyntaxNode node)
{
return node.Ancestors().OfType<ClassDeclarationSyntax>().FirstOrDefault()?.Identifier.ValueText ?? "Unknown";
}
}
}