Add image processing benchmarks and UI folder open buttons

- Added MaddoShared.Benchmarks project with BenchmarkDotNet for comprehensive image processing performance tests (parallel, chunk, size, stress).
- Included helper for generating test images and custom configs to ensure InProcess toolchain for .NET Windows compatibility.
- Added cross-platform scripts to run benchmarks easily.
- Updated .gitignore for benchmark artifacts and temp files.
- Exposed GetFilesToProcessPublic in ImageCreationStuff for testability.
- Added file name sanitization in ImageCreatorSharp to prevent IO errors.
- Enhanced WinForms UI: added "Open" buttons for source/destination folders, handled folder opening in Explorer, and improved user messaging and layout.
- Updated solution file to include new benchmark project.
This commit is contained in:
MaddoScientisto 2026-02-14 19:20:25 +01:00
commit c2fd4bf780
17 changed files with 1608 additions and 301 deletions

25
MaddoShared.Benchmarks/.gitignore vendored Normal file
View file

@ -0,0 +1,25 @@
# BenchmarkDotNet artifacts
BenchmarkDotNet.Artifacts/
# Test images generated during benchmarks
TestImages/
# Build outputs
bin/
obj/
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# Results and logs
*.log
*.html
*.csv
results/
# Temporary files
*.tmp
*.temp

View file

@ -0,0 +1,91 @@
using BenchmarkDotNet.Columns;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Engines;
using BenchmarkDotNet.Exporters;
using BenchmarkDotNet.Exporters.Csv;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Toolchains.InProcess.Emit;
namespace MaddoShared.Benchmarks;
/// <summary>
/// InProcess configuration for benchmarks requiring Windows-specific APIs
/// This avoids the net10.0-windows vs net10.0 compatibility issue
/// </summary>
public class InProcessConfig : ManualConfig
{
public InProcessConfig()
{
AddLogger(ConsoleLogger.Default);
AddExporter(HtmlExporter.Default);
AddExporter(MarkdownExporter.GitHub);
AddExporter(CsvExporter.Default);
AddDiagnoser(MemoryDiagnoser.Default);
// Add job with InProcess toolchain
AddJob(Job.Default
.WithToolchain(InProcessEmitToolchain.Instance)
.WithWarmupCount(1)
.WithIterationCount(3));
// Configuration options
WithOptions(ConfigOptions.DisableOptimizationsValidator);
WithOptions(ConfigOptions.KeepBenchmarkFiles);
}
}
/// <summary>
/// Custom configuration for image processing benchmarks
/// Uses InProcess toolchain to avoid net10.0-windows compatibility issues
/// </summary>
public class BenchmarkConfig : ManualConfig
{
public BenchmarkConfig()
{
// Add console logger
AddLogger(ConsoleLogger.Default);
// Add exporters for different formats
AddExporter(HtmlExporter.Default);
AddExporter(MarkdownExporter.GitHub);
AddExporter(CsvExporter.Default);
AddExporter(RPlotExporter.Default);
// Add diagnosers
AddDiagnoser(MemoryDiagnoser.Default);
AddDiagnoser(ThreadingDiagnoser.Default);
// Add columns
AddColumn(StatisticColumn.Mean);
AddColumn(StatisticColumn.StdDev);
AddColumn(StatisticColumn.Error);
AddColumn(StatisticColumn.Min);
AddColumn(StatisticColumn.Max);
AddColumn(StatisticColumn.Median);
AddColumn(BaselineRatioColumn.RatioMean);
// Customize jobs with InProcess toolchain for Windows compatibility
AddJob(Job.Default
.WithToolchain(InProcessEmitToolchain.Instance)
.WithWarmupCount(1)
.WithIterationCount(3)
.WithId("Quick"));
AddJob(Job.Default
.WithToolchain(InProcessEmitToolchain.Instance)
.WithWarmupCount(2)
.WithIterationCount(5)
.WithId("Standard"));
}
/// <summary>
/// Fast configuration for development and quick tests
/// </summary>
public static IConfig Fast => new ManualConfig()
.AddLogger(ConsoleLogger.Default)
.AddExporter(MarkdownExporter.GitHub)
.AddDiagnoser(MemoryDiagnoser.Default)
.AddJob(Job.Dry.WithToolchain(InProcessEmitToolchain.Instance)); // Very fast, but less accurate
}

View file

@ -0,0 +1,130 @@
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Engines;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Toolchains.InProcess.Emit;
using MaddoShared.Benchmarks.Helpers;
using Microsoft.Extensions.Logging;
namespace MaddoShared.Benchmarks;
/// <summary>
/// Benchmarks focused on different chunk sizes for parallel processing
/// </summary>
[MemoryDiagnoser]
[Config(typeof(InProcessConfig))]
public class ChunkSizeBenchmarks
{
private string _sourceDirectory;
private string _destinationDirectory;
private ImageCreationStuff _imageCreationStuff;
private PicSettings _picSettings;
[Params(100)]
public int ImageCount { get; set; }
[Params(0, 5, 10, 20, 50)]
public int ChunkSize { get; set; }
[GlobalSetup]
public void Setup()
{
var tempBase = Path.Combine(Path.GetTempPath(), "ChunkBenchmarks", Guid.NewGuid().ToString());
_sourceDirectory = Path.Combine(tempBase, "Source");
_destinationDirectory = Path.Combine(tempBase, "Destination");
Directory.CreateDirectory(_sourceDirectory);
Directory.CreateDirectory(_destinationDirectory);
Console.WriteLine($"Generating {ImageCount} test images for chunk size testing...");
TestImageGenerator.GenerateTestImages(_sourceDirectory, ImageCount, width: 2000, height: 1500);
var loggerFactory = LoggerFactory.Create(builder =>
{
builder.SetMinimumLevel(LogLevel.Warning);
});
var logger = loggerFactory.CreateLogger<ImageCreationStuff>();
var imageCreatorLogger = loggerFactory.CreateLogger<ImageCreatorSharp>();
_picSettings = new PicSettings
{
DirectorySorgente = _sourceDirectory,
DirectoryDestinazione = _destinationDirectory,
DimStandard = 800,
DimStandardMiniatura = 200,
LarghezzaBig = 1024,
AltezzaBig = 768,
LarghezzaSmall = 200,
AltezzaSmall = 150,
CreaMiniature = true,
AggiungiScritteMiniature = false,
UsaForzaJpg = true,
UsaRotazioneAutomatica = true,
LogoAggiungi = false,
FotoGrandeDimOrigina = false,
TestoNome = false,
NomeData = false,
Suffisso = "_small",
Margine = 10,
Trasparenza = 100
};
var imageCreatorService = new ImageCreatorSharp(_picSettings, imageCreatorLogger);
_imageCreationStuff = new ImageCreationStuff(logger, _picSettings, imageCreatorService);
}
[GlobalCleanup]
public void Cleanup()
{
try
{
var tempBase = Path.GetDirectoryName(_sourceDirectory);
if (Directory.Exists(tempBase))
{
Directory.Delete(tempBase, recursive: true);
}
}
catch (Exception ex)
{
Console.WriteLine($"Cleanup error: {ex.Message}");
}
}
[IterationSetup]
public void IterationSetup()
{
if (Directory.Exists(_destinationDirectory))
{
Directory.Delete(_destinationDirectory, recursive: true);
}
Directory.CreateDirectory(_destinationDirectory);
}
[Benchmark]
public async Task ProcessWithVariableChunkSize()
{
var options = new ImageCreationStuff.Options
{
SourcePath = _sourceDirectory,
DestinationPath = _destinationDirectory,
MaxThreads = Environment.ProcessorCount,
ChunksSize = ChunkSize,
LinearExecution = false,
AggiornaSottodirectory = false,
CreaSottocartelle = false,
FilePerCartella = 100,
SuffissoCartelle = "",
CifreContatore = 4,
NumerazioneType = NumerazioneType.Progressiva
};
var results = new ConcurrentBag<string>();
await _imageCreationStuff.ProcessImagesParallel(options, results, null, CancellationToken.None);
}
}

View file

@ -0,0 +1,107 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
namespace MaddoShared.Benchmarks.Helpers;
/// <summary>
/// Helper class to generate test images for benchmarking
/// </summary>
[SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")]
public static class TestImageGenerator
{
/// <summary>
/// Generates a set of test JPEG images in the specified directory
/// </summary>
/// <param name="outputDirectory">Directory where images will be created</param>
/// <param name="imageCount">Number of images to generate</param>
/// <param name="width">Width of each image</param>
/// <param name="height">Height of each image</param>
/// <param name="includeSubfolders">Whether to create images in subfolders</param>
public static void GenerateTestImages(
string outputDirectory,
int imageCount,
int width = 4000,
int height = 3000,
bool includeSubfolders = false)
{
Directory.CreateDirectory(outputDirectory);
var random = new Random(42); // Fixed seed for reproducibility
for (int i = 0; i < imageCount; i++)
{
var targetDir = outputDirectory;
if (includeSubfolders && i % 10 == 0)
{
targetDir = Path.Combine(outputDirectory, $"Subfolder_{i / 10}");
Directory.CreateDirectory(targetDir);
}
var filePath = Path.Combine(targetDir, $"test_image_{i:D5}.jpg");
// Skip if already exists
if (File.Exists(filePath))
continue;
using var bitmap = new Bitmap(width, height);
using var graphics = Graphics.FromImage(bitmap);
// Fill with a random color background
var bgColor = Color.FromArgb(random.Next(256), random.Next(256), random.Next(256));
graphics.Clear(bgColor);
// Draw some random shapes to make it more realistic
for (int j = 0; j < 20; j++)
{
var color = Color.FromArgb(random.Next(256), random.Next(256), random.Next(256));
var brush = new SolidBrush(color);
var x = random.Next(width);
var y = random.Next(height);
var w = random.Next(200, 800);
var h = random.Next(200, 800);
graphics.FillEllipse(brush, x, y, w, h);
}
// Add some text
using var font = new Font("Arial", 48, FontStyle.Bold);
var text = $"Test Image {i}";
var textBrush = new SolidBrush(Color.White);
graphics.DrawString(text, font, textBrush, new PointF(100, 100));
// Save as JPEG with standard quality
var encoder = GetEncoder(ImageFormat.Jpeg);
var encoderParameters = new EncoderParameters(1);
encoderParameters.Param[0] = new EncoderParameter(Encoder.Quality, 85L);
bitmap.Save(filePath, encoder, encoderParameters);
}
}
/// <summary>
/// Cleans up generated test images
/// </summary>
public static void CleanupTestImages(string directory)
{
if (Directory.Exists(directory))
{
Directory.Delete(directory, recursive: true);
}
}
private static ImageCodecInfo GetEncoder(ImageFormat format)
{
var codecs = ImageCodecInfo.GetImageEncoders();
foreach (var codec in codecs)
{
if (codec.FormatID == format.Guid)
{
return codec;
}
}
return null;
}
}

View file

@ -0,0 +1,182 @@
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Engines;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Toolchains.InProcess.Emit;
using MaddoShared.Benchmarks.Helpers;
using Microsoft.Extensions.Logging;
namespace MaddoShared.Benchmarks;
/// <summary>
/// Benchmarks for image processing with various configurations
/// </summary>
[MemoryDiagnoser]
[Config(typeof(InProcessConfig))]
public class ImageProcessingBenchmarks
{
private string _sourceDirectory;
private string _destinationDirectory;
private ImageCreationStuff _imageCreationStuff;
private PicSettings _picSettings;
private ILogger<ImageCreationStuff> _logger;
private ILogger<ImageCreatorSharp> _imageCreatorLogger;
[Params(10, 50, 100)]
public int ImageCount { get; set; }
[Params(1, 2, 4, 8)]
public int MaxThreads { get; set; }
[GlobalSetup]
public void Setup()
{
// Create temp directories
var tempBase = Path.Combine(Path.GetTempPath(), "ImageBenchmarks", Guid.NewGuid().ToString());
_sourceDirectory = Path.Combine(tempBase, "Source");
_destinationDirectory = Path.Combine(tempBase, "Destination");
Directory.CreateDirectory(_sourceDirectory);
Directory.CreateDirectory(_destinationDirectory);
// Generate test images
Console.WriteLine($"Generating {ImageCount} test images...");
TestImageGenerator.GenerateTestImages(_sourceDirectory, ImageCount, width: 2000, height: 1500);
// Setup logging
var loggerFactory = LoggerFactory.Create(builder =>
{
builder.SetMinimumLevel(LogLevel.Warning); // Reduce noise during benchmarks
});
_logger = loggerFactory.CreateLogger<ImageCreationStuff>();
_imageCreatorLogger = loggerFactory.CreateLogger<ImageCreatorSharp>();
// Setup PicSettings with default values
_picSettings = new PicSettings
{
DirectorySorgente = _sourceDirectory,
DirectoryDestinazione = _destinationDirectory,
DimStandard = 800,
DimStandardMiniatura = 200,
LarghezzaBig = 1024,
AltezzaBig = 768,
LarghezzaSmall = 200,
AltezzaSmall = 150,
CreaMiniature = true,
AggiungiScritteMiniature = false,
UsaForzaJpg = true,
UsaRotazioneAutomatica = true,
LogoAggiungi = false,
FotoGrandeDimOrigina = false,
TestoNome = false,
NomeData = false,
Suffisso = "_small",
Margine = 10,
Trasparenza = 100
};
var imageCreatorService = new ImageCreatorSharp(_picSettings, _imageCreatorLogger);
_imageCreationStuff = new ImageCreationStuff(_logger, _picSettings, imageCreatorService);
}
[GlobalCleanup]
public void Cleanup()
{
// Clean up temp directories
try
{
var tempBase = Path.GetDirectoryName(_sourceDirectory);
if (Directory.Exists(tempBase))
{
Directory.Delete(tempBase, recursive: true);
}
}
catch (Exception ex)
{
Console.WriteLine($"Cleanup error: {ex.Message}");
}
}
[IterationSetup]
public void IterationSetup()
{
// Clean destination directory before each iteration
if (Directory.Exists(_destinationDirectory))
{
Directory.Delete(_destinationDirectory, recursive: true);
}
Directory.CreateDirectory(_destinationDirectory);
}
[Benchmark(Description = "Process images in parallel with chunking")]
public async Task ProcessImagesParallelWithChunks()
{
var options = new ImageCreationStuff.Options
{
SourcePath = _sourceDirectory,
DestinationPath = _destinationDirectory,
MaxThreads = MaxThreads,
ChunksSize = 10,
LinearExecution = false,
AggiornaSottodirectory = false,
CreaSottocartelle = false,
FilePerCartella = 100,
SuffissoCartelle = "",
CifreContatore = 4,
NumerazioneType = NumerazioneType.Progressiva
};
var results = new ConcurrentBag<string>();
await _imageCreationStuff.ProcessImagesParallel(options, results, null, CancellationToken.None);
}
[Benchmark(Description = "Process images in parallel without chunking")]
public async Task ProcessImagesParallelWithoutChunks()
{
var options = new ImageCreationStuff.Options
{
SourcePath = _sourceDirectory,
DestinationPath = _destinationDirectory,
MaxThreads = MaxThreads,
ChunksSize = 0, // No chunking
LinearExecution = false,
AggiornaSottodirectory = false,
CreaSottocartelle = false,
FilePerCartella = 100,
SuffissoCartelle = "",
CifreContatore = 4,
NumerazioneType = NumerazioneType.Progressiva
};
var results = new ConcurrentBag<string>();
await _imageCreationStuff.ProcessImagesParallel(options, results, null, CancellationToken.None);
}
[Benchmark(Description = "Process images linearly")]
public async Task ProcessImagesLinear()
{
var options = new ImageCreationStuff.Options
{
SourcePath = _sourceDirectory,
DestinationPath = _destinationDirectory,
MaxThreads = MaxThreads,
ChunksSize = 0,
LinearExecution = true,
AggiornaSottodirectory = false,
CreaSottocartelle = false,
FilePerCartella = 100,
SuffissoCartelle = "",
CifreContatore = 4,
NumerazioneType = NumerazioneType.Progressiva
};
var results = new ConcurrentBag<string>();
await _imageCreationStuff.ProcessImagesParallel(options, results, null, CancellationToken.None);
}
}

View file

@ -0,0 +1,151 @@
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Engines;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Toolchains.InProcess.Emit;
using MaddoShared.Benchmarks.Helpers;
using Microsoft.Extensions.Logging;
namespace MaddoShared.Benchmarks;
/// <summary>
/// Benchmarks for comparing performance with different image sizes
/// </summary>
[MemoryDiagnoser]
[Config(typeof(InProcessConfig))]
public class ImageSizeBenchmarks
{
private string _sourceDirectory;
private string _destinationDirectory;
private ImageCreationStuff _imageCreationStuff;
private PicSettings _picSettings;
[Params(50)]
public int ImageCount { get; set; }
public enum ImageSize
{
Small, // 1280x960
Medium, // 2560x1920
Large, // 4000x3000
ExtraLarge // 6000x4000
}
[ParamsAllValues]
public ImageSize Size { get; set; }
[GlobalSetup]
public void Setup()
{
var tempBase = Path.Combine(Path.GetTempPath(), "SizeBenchmarks", Guid.NewGuid().ToString());
_sourceDirectory = Path.Combine(tempBase, "Source");
_destinationDirectory = Path.Combine(tempBase, "Destination");
Directory.CreateDirectory(_sourceDirectory);
Directory.CreateDirectory(_destinationDirectory);
var (width, height) = GetDimensions(Size);
Console.WriteLine($"Generating {ImageCount} test images at {width}x{height}...");
TestImageGenerator.GenerateTestImages(_sourceDirectory, ImageCount, width, height);
var loggerFactory = LoggerFactory.Create(builder =>
{
builder.SetMinimumLevel(LogLevel.Warning);
});
var logger = loggerFactory.CreateLogger<ImageCreationStuff>();
var imageCreatorLogger = loggerFactory.CreateLogger<ImageCreatorSharp>();
_picSettings = new PicSettings
{
DirectorySorgente = _sourceDirectory,
DirectoryDestinazione = _destinationDirectory,
DimStandard = 800,
DimStandardMiniatura = 200,
LarghezzaBig = 1024,
AltezzaBig = 768,
LarghezzaSmall = 200,
AltezzaSmall = 150,
CreaMiniature = true,
AggiungiScritteMiniature = false,
UsaForzaJpg = true,
UsaRotazioneAutomatica = true,
LogoAggiungi = false,
FotoGrandeDimOrigina = false,
TestoNome = false,
NomeData = false,
Suffisso = "_small",
Margine = 10,
Trasparenza = 100
};
var imageCreatorService = new ImageCreatorSharp(_picSettings, imageCreatorLogger);
_imageCreationStuff = new ImageCreationStuff(logger, _picSettings, imageCreatorService);
}
private static (int width, int height) GetDimensions(ImageSize size)
{
return size switch
{
ImageSize.Small => (1280, 960),
ImageSize.Medium => (2560, 1920),
ImageSize.Large => (4000, 3000),
ImageSize.ExtraLarge => (6000, 4000),
_ => throw new ArgumentException($"Unknown size: {size}")
};
}
[GlobalCleanup]
public void Cleanup()
{
try
{
var tempBase = Path.GetDirectoryName(_sourceDirectory);
if (Directory.Exists(tempBase))
{
Directory.Delete(tempBase, recursive: true);
}
}
catch (Exception ex)
{
Console.WriteLine($"Cleanup error: {ex.Message}");
}
}
[IterationSetup]
public void IterationSetup()
{
if (Directory.Exists(_destinationDirectory))
{
Directory.Delete(_destinationDirectory, recursive: true);
}
Directory.CreateDirectory(_destinationDirectory);
}
[Benchmark]
public async Task ProcessDifferentImageSizes()
{
var options = new ImageCreationStuff.Options
{
SourcePath = _sourceDirectory,
DestinationPath = _destinationDirectory,
MaxThreads = Environment.ProcessorCount,
ChunksSize = 10,
LinearExecution = false,
AggiornaSottodirectory = false,
CreaSottocartelle = false,
FilePerCartella = 100,
SuffissoCartelle = "",
CifreContatore = 4,
NumerazioneType = NumerazioneType.Progressiva
};
var results = new ConcurrentBag<string>();
await _imageCreationStuff.ProcessImagesParallel(options, results, null, CancellationToken.None);
}
}

View file

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0-windows</TargetFramework>
<PlatformTarget>x64</PlatformTarget>
<UseWindowsForms>true</UseWindowsForms>
<ImportWindowsDesktopTargets>true</ImportWindowsDesktopTargets>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.14.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MaddoShared\MaddoShared.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,35 @@
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Running;
using BenchmarkDotNet.Toolchains.InProcess.Emit;
using System;
using System.Linq;
namespace MaddoShared.Benchmarks;
internal class Program
{
static void Main(string[] args)
{
// Check if --job argument is provided
bool hasJobArg = args.Any(a => a.Contains("--job"));
if (hasJobArg)
{
Console.WriteLine("Note: Overriding --job argument to use InProcess toolchain");
Console.WriteLine("This is required to avoid net10.0 vs net10.0-windows compatibility issues.");
Console.WriteLine();
// Remove --job arguments and add our own InProcess config
args = args.Where(a => !a.StartsWith("--job") && a != "dry" && a != "short").ToArray();
}
// Create configuration that always uses InProcess toolchain
var config = DefaultConfig.Instance
.WithOptions(ConfigOptions.DisableOptimizationsValidator)
.WithOptions(ConfigOptions.KeepBenchmarkFiles);
// Run benchmarks - each class has [Config(typeof(InProcessConfig))] which provides InProcess toolchain
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args, config);
}
}

View file

@ -0,0 +1,172 @@
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Toolchains.InProcess.Emit;
using MaddoShared.Benchmarks.Helpers;
using Microsoft.Extensions.Logging;
namespace MaddoShared.Benchmarks;
/// <summary>
/// Stress test benchmark for large-scale image processing
/// WARNING: This will generate a large number of images and may take significant time and disk space
/// </summary>
[MemoryDiagnoser]
[Config(typeof(InProcessConfig))]
public class StressTestBenchmark
{
private string _sourceDirectory;
private string _destinationDirectory;
private ImageCreationStuff _imageCreationStuff;
private PicSettings _picSettings;
[Params(500, 1000)]
public int ImageCount { get; set; }
[GlobalSetup]
public void Setup()
{
var tempBase = Path.Combine(Path.GetTempPath(), "StressTestBenchmarks", Guid.NewGuid().ToString());
_sourceDirectory = Path.Combine(tempBase, "Source");
_destinationDirectory = Path.Combine(tempBase, "Destination");
Directory.CreateDirectory(_sourceDirectory);
Directory.CreateDirectory(_destinationDirectory);
Console.WriteLine($"[STRESS TEST] Generating {ImageCount} test images...");
Console.WriteLine("This may take several minutes depending on your hardware.");
// Use smaller images for stress test to save space and time
TestImageGenerator.GenerateTestImages(_sourceDirectory, ImageCount, width: 1920, height: 1080);
var loggerFactory = LoggerFactory.Create(builder =>
{
builder.SetMinimumLevel(LogLevel.Warning);
});
var logger = loggerFactory.CreateLogger<ImageCreationStuff>();
var imageCreatorLogger = loggerFactory.CreateLogger<ImageCreatorSharp>();
_picSettings = new PicSettings
{
DirectorySorgente = _sourceDirectory,
DirectoryDestinazione = _destinationDirectory,
DimStandard = 800,
DimStandardMiniatura = 200,
LarghezzaBig = 1024,
AltezzaBig = 768,
LarghezzaSmall = 200,
AltezzaSmall = 150,
CreaMiniature = true,
AggiungiScritteMiniature = false,
UsaForzaJpg = true,
UsaRotazioneAutomatica = true,
LogoAggiungi = false,
FotoGrandeDimOrigina = false,
TestoNome = false,
NomeData = false,
Suffisso = "_small",
Margine = 10,
Trasparenza = 100
};
var imageCreatorService = new ImageCreatorSharp(_picSettings, imageCreatorLogger);
_imageCreationStuff = new ImageCreationStuff(logger, _picSettings, imageCreatorService);
Console.WriteLine($"[STRESS TEST] Setup complete. Ready to process {ImageCount} images.");
}
[GlobalCleanup]
public void Cleanup()
{
Console.WriteLine("[STRESS TEST] Cleaning up test data...");
try
{
var tempBase = Path.GetDirectoryName(_sourceDirectory);
if (Directory.Exists(tempBase))
{
Directory.Delete(tempBase, recursive: true);
}
}
catch (Exception ex)
{
Console.WriteLine($"Cleanup error: {ex.Message}");
}
}
[IterationSetup]
public void IterationSetup()
{
if (Directory.Exists(_destinationDirectory))
{
Directory.Delete(_destinationDirectory, recursive: true);
}
Directory.CreateDirectory(_destinationDirectory);
GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, blocking: true, compacting: true);
}
[Benchmark(Description = "Stress test with optimal settings")]
public async Task StressTestOptimalSettings()
{
var options = new ImageCreationStuff.Options
{
SourcePath = _sourceDirectory,
DestinationPath = _destinationDirectory,
MaxThreads = Environment.ProcessorCount,
ChunksSize = 25, // Process in chunks to manage memory
LinearExecution = false,
AggiornaSottodirectory = false,
CreaSottocartelle = false,
FilePerCartella = 100,
SuffissoCartelle = "",
CifreContatore = 4,
NumerazioneType = NumerazioneType.Progressiva
};
var results = new ConcurrentBag<string>();
var startTime = DateTime.Now;
await _imageCreationStuff.ProcessImagesParallel(options, results, null, CancellationToken.None);
var duration = DateTime.Now - startTime;
var throughput = ImageCount / duration.TotalSeconds;
Console.WriteLine($"[STRESS TEST] Processed {results.Count}/{ImageCount} images in {duration.TotalSeconds:F2}s");
Console.WriteLine($"[STRESS TEST] Throughput: {throughput:F2} images/second");
}
[Benchmark(Description = "Stress test with aggressive memory management")]
public async Task StressTestAggressiveMemoryManagement()
{
var options = new ImageCreationStuff.Options
{
SourcePath = _sourceDirectory,
DestinationPath = _destinationDirectory,
MaxThreads = Environment.ProcessorCount / 2, // Reduce threads to save memory
ChunksSize = 10, // Smaller chunks for more frequent GC
LinearExecution = false,
AggiornaSottodirectory = false,
CreaSottocartelle = false,
FilePerCartella = 100,
SuffissoCartelle = "",
CifreContatore = 4,
NumerazioneType = NumerazioneType.Progressiva
};
var results = new ConcurrentBag<string>();
var startTime = DateTime.Now;
await _imageCreationStuff.ProcessImagesParallel(options, results, null, CancellationToken.None);
var duration = DateTime.Now - startTime;
var throughput = ImageCount / duration.TotalSeconds;
Console.WriteLine($"[STRESS TEST] Processed {results.Count}/{ImageCount} images in {duration.TotalSeconds:F2}s");
Console.WriteLine($"[STRESS TEST] Throughput: {throughput:F2} images/second");
}
}