Skip to content

Commit 6220889

Browse files
Add benchmarking capabilities and refactor analysis for performance improvements
- Introduced a new project `CognitiveCodeAnalysis.Benchmarks` to facilitate performance benchmarking of the cognitive analysis and coupling analysis processes. - Implemented `CompiledSourceSet` to optimize source file reading, parsing, and compilation, reducing redundant operations. - Refactored `CognitiveCodeAnalyser` and `ClassCouplingAnalyser` to utilize the new compiled source set for improved efficiency. - Added benchmark tests for various analysis scenarios, including cognitive and coupling analysis, to measure performance under different file counts. - Enhanced the command-line interface and Makefile to support benchmark execution, providing users with tools to assess performance. - Aimed to improve the overall performance and responsiveness of the cognitive analysis framework.
1 parent 0b347be commit 6220889

13 files changed

Lines changed: 467 additions & 104 deletions

File tree

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/// <copyright company="Florian Krämer">
2+
/// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
/// </copyright>
4+
5+
using BenchmarkDotNet.Attributes;
6+
using BenchmarkDotNet.Jobs;
7+
8+
using CognitiveCodeAnalysis.CodeCoverage;
9+
using CognitiveCodeAnalysis.CognitiveAnalysis;
10+
using CognitiveCodeAnalysis.CognitiveAnalysis.Reports;
11+
using CognitiveCodeAnalysis.Common;
12+
using CognitiveCodeAnalysis.Configuration;
13+
using CognitiveCodeAnalysis.CouplingAnalysis;
14+
15+
namespace CognitiveCodeAnalysis.Benchmarks;
16+
17+
[ShortRunJob]
18+
[MemoryDiagnoser]
19+
public class AnalysisBenchmarks
20+
{
21+
[Params(100, 400, 800)]
22+
public int FileCount { get; set; }
23+
24+
private string _corpusDirectory = string.Empty;
25+
private List<string> _files = [];
26+
private CompiledSourceSet _compiledSources = null!;
27+
private CognitiveMetricsCollection _metrics = null!;
28+
private CognitiveConfiguration _configuration = null!;
29+
private CognitiveAnalysisFacade _facade = null!;
30+
private HtmlReport _htmlReport = null!;
31+
private string _reportOutputPath = string.Empty;
32+
33+
[GlobalSetup]
34+
public void GlobalSetup()
35+
{
36+
_corpusDirectory = BenchmarkCorpusGenerator.Generate(FileCount);
37+
_files = BenchmarkCorpusGenerator.ListSourceFiles(_corpusDirectory);
38+
_configuration = CreateBenchmarkConfiguration();
39+
_facade = CreateFacade();
40+
_htmlReport = new HtmlReport();
41+
_reportOutputPath = Path.Combine(_corpusDirectory, "benchmark-report.html");
42+
43+
_compiledSources = CompiledSourceSet.BuildAsync(_files).GetAwaiter().GetResult();
44+
_metrics = _facade.AnalyseSourceFiles(_files, _configuration);
45+
}
46+
47+
[GlobalCleanup]
48+
public void GlobalCleanup()
49+
{
50+
if (Directory.Exists(_corpusDirectory))
51+
{
52+
Directory.Delete(_corpusDirectory, recursive: true);
53+
}
54+
}
55+
56+
[Benchmark(Description = "01 Build compiled source set (read + parse + compile)")]
57+
public async Task BuildCompiledSourceSet()
58+
{
59+
await CompiledSourceSet.BuildAsync(_files);
60+
}
61+
62+
[Benchmark(Description = "02 Cognitive analysis only")]
63+
public CognitiveMetricsCollection CognitiveAnalysisOnly()
64+
{
65+
var analyser = new CognitiveCodeAnalyser();
66+
return analyser.AnalyseCompiled(_compiledSources, _configuration, progress: null);
67+
}
68+
69+
[Benchmark(Description = "03 Coupling analysis only")]
70+
public IReadOnlyList<ClassCouplingMetrics> CouplingAnalysisOnly()
71+
{
72+
var couplingAnalyser = new ClassCouplingAnalyser();
73+
return couplingAnalyser.AnalyseCompiled(_compiledSources);
74+
}
75+
76+
[Benchmark(Description = "04 Full analysis pipeline (compile + cognitive + coupling + scores)")]
77+
public CognitiveMetricsCollection FullAnalysisPipeline()
78+
{
79+
return _facade.AnalyseSourceFiles(_files, _configuration);
80+
}
81+
82+
[Benchmark(Description = "05 Html report generation")]
83+
public void HtmlReportGeneration()
84+
{
85+
_htmlReport.RenderMetrics(
86+
outputFile: _reportOutputPath,
87+
metricsCollection: _metrics,
88+
configuration: _configuration,
89+
baselineComparison: null,
90+
progress: null
91+
);
92+
}
93+
94+
private static CognitiveConfiguration CreateBenchmarkConfiguration()
95+
{
96+
return new CognitiveConfiguration
97+
{
98+
ScoreThreshold = -1,
99+
ShowOnlyMethodsExceedingThreshold = false,
100+
ShowHalsteadComplexity = true,
101+
ShowCyclomaticComplexity = true,
102+
ShowCouplingMetrics = true,
103+
GroupByClass = true,
104+
};
105+
}
106+
107+
private static CognitiveAnalysisFacade CreateFacade()
108+
{
109+
return new CognitiveAnalysisFacade(
110+
sourceFileFinder: new SourceFileFinder(),
111+
analyser: new CognitiveCodeAnalyser(),
112+
cognitiveConfiguration: new CognitiveConfiguration(),
113+
calculator: new ScoreCalculator(),
114+
coverageReader: new CoberturaReader(),
115+
classCouplingAnalyser: new ClassCouplingAnalyser()
116+
);
117+
}
118+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/// <copyright company="Florian Krämer">
2+
/// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
/// </copyright>
4+
5+
namespace CognitiveCodeAnalysis.Benchmarks;
6+
7+
/// <summary>
8+
/// Generates a deterministic synthetic C# corpus for repeatable benchmark runs.
9+
/// Each file contains one class with three methods and one interface for coupling edges.
10+
/// </summary>
11+
internal static class BenchmarkCorpusGenerator
12+
{
13+
internal static string Generate(int fileCount)
14+
{
15+
string directory = Path.Combine(
16+
Path.GetTempPath(),
17+
$"cca-benchmark-{fileCount}-{Guid.NewGuid():N}"
18+
);
19+
Directory.CreateDirectory(directory);
20+
21+
for (int index = 1; index <= fileCount; index++)
22+
{
23+
string path = Path.Combine(directory, $"Widget{index}.cs");
24+
File.WriteAllText(path, BuildSourceFile(index));
25+
}
26+
27+
return directory;
28+
}
29+
30+
internal static List<string> ListSourceFiles(string directory)
31+
{
32+
return Directory
33+
.GetFiles(directory, "*.cs", SearchOption.TopDirectoryOnly)
34+
.OrderBy(path => path, StringComparer.Ordinal)
35+
.ToList();
36+
}
37+
38+
private static string BuildSourceFile(int index)
39+
{
40+
return $$"""
41+
namespace Bench.N{{index}} {
42+
public class Widget{{index}} {
43+
private int _state;
44+
45+
public int Compute(int x, int y) {
46+
int result = 0;
47+
for (int i = 0; i < x; i++) {
48+
if (i % 2 == 0 && y > 0) {
49+
result += i;
50+
} else {
51+
result -= i;
52+
}
53+
54+
switch (i % 3) {
55+
case 0: result++; break;
56+
case 1: result--; break;
57+
default: break;
58+
}
59+
}
60+
61+
try {
62+
result = result / (x - x + 1);
63+
} catch (System.Exception) {
64+
result = -1;
65+
}
66+
67+
return result > 0 ? result : _state;
68+
}
69+
70+
public string Name() => "Widget{{index}}";
71+
72+
public void Touch() {
73+
_state++;
74+
}
75+
}
76+
77+
public interface IThing{{index}} {
78+
int Compute(int x, int y);
79+
}
80+
}
81+
""";
82+
}
83+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net8.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
<IsPackable>false</IsPackable>
9+
<RootNamespace>CognitiveCodeAnalysis.Benchmarks</RootNamespace>
10+
</PropertyGroup>
11+
12+
<ItemGroup>
13+
<PackageReference Include="BenchmarkDotNet" Version="0.14.0" />
14+
</ItemGroup>
15+
16+
<ItemGroup>
17+
<ProjectReference Include="..\CognitiveCodeAnalysis\CognitiveCodeAnalysis.csproj" />
18+
</ItemGroup>
19+
20+
</Project>
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/// <copyright company="Florian Krämer">
2+
/// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
/// </copyright>
4+
5+
using BenchmarkDotNet.Configs;
6+
using BenchmarkDotNet.Running;
7+
8+
namespace CognitiveCodeAnalysis.Benchmarks;
9+
10+
public static class Program
11+
{
12+
public static void Main(string[] args)
13+
{
14+
string artifactsPath = ResolveArtifactsPath();
15+
Directory.CreateDirectory(artifactsPath);
16+
17+
IConfig config = ManualConfig.Create(DefaultConfig.Instance)
18+
.WithArtifactsPath(artifactsPath);
19+
20+
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args, config);
21+
}
22+
23+
private static string ResolveArtifactsPath()
24+
{
25+
string? fromEnvironment = Environment.GetEnvironmentVariable("CCA_BENCHMARK_ARTIFACTS");
26+
if (!string.IsNullOrWhiteSpace(fromEnvironment))
27+
{
28+
return Path.GetFullPath(fromEnvironment);
29+
}
30+
31+
return Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), "artifacts", "benchmark"));
32+
}
33+
}

CognitiveCodeAnalysis.sln

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CognitiveCodeAnalysisConsol
1414
EndProject
1515
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CognitiveCodeAnalysisConsoleApp.Tests", "CognitiveCodeAnalysisConsoleApp.Tests\CognitiveCodeAnalysisConsoleApp.Tests.csproj", "{8EB9755A-AE7D-4120-9473-B8FD8A1F9D30}"
1616
EndProject
17+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CognitiveCodeAnalysis.Benchmarks", "CognitiveCodeAnalysis.Benchmarks\CognitiveCodeAnalysis.Benchmarks.csproj", "{E482EABC-C006-4A19-9AFD-5E86FDDDDE0E}"
18+
EndProject
1719
Global
1820
GlobalSection(SolutionConfigurationPlatforms) = preSolution
1921
Debug|Any CPU = Debug|Any CPU
@@ -72,6 +74,18 @@ Global
7274
{8EB9755A-AE7D-4120-9473-B8FD8A1F9D30}.Release|x64.Build.0 = Release|Any CPU
7375
{8EB9755A-AE7D-4120-9473-B8FD8A1F9D30}.Release|x86.ActiveCfg = Release|Any CPU
7476
{8EB9755A-AE7D-4120-9473-B8FD8A1F9D30}.Release|x86.Build.0 = Release|Any CPU
77+
{E482EABC-C006-4A19-9AFD-5E86FDDDDE0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
78+
{E482EABC-C006-4A19-9AFD-5E86FDDDDE0E}.Debug|Any CPU.Build.0 = Debug|Any CPU
79+
{E482EABC-C006-4A19-9AFD-5E86FDDDDE0E}.Debug|x64.ActiveCfg = Debug|Any CPU
80+
{E482EABC-C006-4A19-9AFD-5E86FDDDDE0E}.Debug|x64.Build.0 = Debug|Any CPU
81+
{E482EABC-C006-4A19-9AFD-5E86FDDDDE0E}.Debug|x86.ActiveCfg = Debug|Any CPU
82+
{E482EABC-C006-4A19-9AFD-5E86FDDDDE0E}.Debug|x86.Build.0 = Debug|Any CPU
83+
{E482EABC-C006-4A19-9AFD-5E86FDDDDE0E}.Release|Any CPU.ActiveCfg = Release|Any CPU
84+
{E482EABC-C006-4A19-9AFD-5E86FDDDDE0E}.Release|Any CPU.Build.0 = Release|Any CPU
85+
{E482EABC-C006-4A19-9AFD-5E86FDDDDE0E}.Release|x64.ActiveCfg = Release|Any CPU
86+
{E482EABC-C006-4A19-9AFD-5E86FDDDDE0E}.Release|x64.Build.0 = Release|Any CPU
87+
{E482EABC-C006-4A19-9AFD-5E86FDDDDE0E}.Release|x86.ActiveCfg = Release|Any CPU
88+
{E482EABC-C006-4A19-9AFD-5E86FDDDDE0E}.Release|x86.Build.0 = Release|Any CPU
7589
EndGlobalSection
7690
GlobalSection(SolutionProperties) = preSolution
7791
HideSolutionNode = FALSE

CognitiveCodeAnalysis/src/CognitiveAnalysis/CognitiveAnalysisFacade.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
/// </copyright>
44

55
using CognitiveCodeAnalysis.CodeCoverage;
6+
using CognitiveCodeAnalysis.Common;
67
using CognitiveCodeAnalysis.Configuration;
78
using CognitiveCodeAnalysis.CouplingAnalysis;
89

@@ -47,8 +48,10 @@ private async Task<CognitiveMetricsCollection> AnalyseSourceFilesAsync(
4748
CognitiveConfiguration configuration,
4849
IProgress<AnalysisProgress>? progress
4950
) {
50-
var cognitiveTask = analyser.AnalyseFilesAsync(files, configuration, progress);
51-
var couplingTask = classCouplingAnalyser.AnalyseAsync(files);
51+
CompiledSourceSet sources = await CompiledSourceSet.BuildAsync(files);
52+
53+
var cognitiveTask = Task.Run(() => analyser.AnalyseCompiled(sources, configuration, progress));
54+
var couplingTask = Task.Run(() => classCouplingAnalyser.AnalyseCompiled(sources));
5255

5356
await Task.WhenAll(cognitiveTask, couplingTask);
5457

0 commit comments

Comments
 (0)