Created
January 31, 2026 15:21
-
-
Save defufna/264317929df2562a3ba9e9bc5690c929 to your computer and use it in GitHub Desktop.
Test disk speed script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #:property Configuration=Release | |
| using System.Diagnostics; | |
| using System.Text; | |
| var sizes = new[] { 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576, 2097152, 4194304, 8388608, 16777216, 33554432, 67108864, 134217728, 268435456 }; | |
| var configurations = new List<TestRun>(); | |
| long defaultTotalData = 256L * 1024 * 1024; // 1 GB | |
| foreach (var size in sizes) | |
| { | |
| // Add default configuration: 1 worker, Buffered (WriteThrough=false) | |
| configurations.Add(new TestRun | |
| { | |
| ChunkSize = size, | |
| TotalFileSize = defaultTotalData, | |
| NumWorkers = 1, | |
| WriteThrough = false | |
| }); | |
| } | |
| foreach (var size in sizes) | |
| { | |
| // Add default configuration: 1 worker, Buffered (WriteThrough=false) | |
| configurations.Add(new TestRun | |
| { | |
| ChunkSize = size, | |
| TotalFileSize = defaultTotalData, | |
| NumWorkers = 1, | |
| WriteThrough = true | |
| }); | |
| } | |
| var html = new StringBuilder(); | |
| html.AppendLine("<html><head><script src='https://cdn.jsdelivr.net/npm/chart.js'></script></head><body>"); | |
| html.AppendLine("<h1>Disk Write Performance Test</h1>"); | |
| html.AppendLine("<div style='width: 800px;'><canvas id='perfChart'></canvas></div>"); | |
| html.AppendLine("<table border='1' style='margin-top: 20px; border-collapse: collapse; width: 800px; text-align: center;'>"); | |
| html.AppendLine("<tr><th>Chunk Size</th><th>Workers</th><th>Mode</th><th>Avg Time (s)</th><th>Avg Speed (MB/s)</th></tr>"); | |
| // Data structure for chart: SeriesName -> List of (ChunkSizeLabel, Speed) | |
| // We assume we want to plot Speed vs ChunkSize for different configurations (Workers/Mode) | |
| var chartData = new Dictionary<string, List<(string Label, double Speed)>>(); | |
| foreach (var config in configurations) | |
| { | |
| string seriesName = config.GetSeriesName(); | |
| if (!chartData.ContainsKey(seriesName)) | |
| { | |
| chartData[seriesName] = new List<(string, double)>(); | |
| } | |
| string sizeLabel = config.GetSizeLabel(); | |
| Console.WriteLine($"Testing: {sizeLabel}, {config.NumWorkers} Workers, WriteThrough={config.WriteThrough}..."); | |
| var runs = new List<(double Time, double Speed)>(); | |
| for (int run = 0; run < 5; run++) | |
| { | |
| // Setup | |
| byte[] data = new byte[config.ChunkSize]; | |
| new Random().NextBytes(data); | |
| long dataPerWorker = config.TotalFileSize / config.NumWorkers; | |
| string filename = "test.bin"; | |
| // Preallocate file | |
| FileOptions fileOptions = FileOptions.WriteThrough; | |
| if (!config.WriteThrough) fileOptions = FileOptions.None; | |
| using (var fs = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)) | |
| { | |
| fs.SetLength(config.TotalFileSize); | |
| } | |
| // Measure | |
| Stopwatch sw = Stopwatch.StartNew(); | |
| Parallel.For(0, config.NumWorkers, i => | |
| { | |
| FileOptions workerOptions = config.WriteThrough ? FileOptions.WriteThrough : FileOptions.None; | |
| // Each worker opens the same file | |
| using var fs = new FileStream(filename, FileMode.Open, FileAccess.Write, FileShare.ReadWrite, 4096, workerOptions); | |
| fs.Seek(i * dataPerWorker, SeekOrigin.Begin); | |
| Stopwatch sw2 = Stopwatch.StartNew(); | |
| long written = 0; | |
| while (written < dataPerWorker) | |
| { | |
| int toWrite = (int)Math.Min(config.ChunkSize, dataPerWorker - written); | |
| fs.Write(data, 0, toWrite); | |
| written += toWrite; | |
| if(sw2.ElapsedMilliseconds > 5000) | |
| { | |
| Console.WriteLine($" Worker {i} written {written} bytes..."); | |
| sw2.Restart(); | |
| } | |
| } | |
| }); | |
| sw.Stop(); | |
| // Calculate | |
| double sec = sw.Elapsed.TotalSeconds; | |
| double mb = config.TotalFileSize / (1024.0 * 1024.0); | |
| double speed = mb / sec; | |
| runs.Add((sec, speed)); | |
| Console.Write("."); | |
| // Cleanup | |
| if (File.Exists(filename)) File.Delete(filename); | |
| } | |
| // Filter: Sort by Speed. Remove Lowest (slowest) and Highest (fastest). | |
| runs.Sort((a, b) => a.Speed.CompareTo(b.Speed)); | |
| // Keep runs[1, 2, 3] | |
| var validRuns = runs.Skip(1).Take(3).ToList(); | |
| double avgTime = validRuns.Average(r => r.Time); | |
| double avgSpeed = validRuns.Average(r => r.Speed); | |
| // Save for Chart | |
| chartData[seriesName].Add((sizeLabel, avgSpeed)); | |
| Console.WriteLine($" Done. Avg: {avgSpeed:F2} MB/s"); | |
| string modeStr = config.WriteThrough ? "WriteThrough" : "Buffered"; | |
| html.AppendLine($"<tr><td>{sizeLabel}</td><td>{config.NumWorkers}</td><td>{modeStr}</td><td>{avgTime:F2}</td><td>{avgSpeed:F2}</td></tr>"); | |
| } | |
| html.AppendLine("</table>"); | |
| // Generate Chart.js script | |
| var allLabels = chartData.Values.SelectMany(v => v.Select(x => x.Label)).Distinct().ToList(); | |
| html.AppendLine("<script>"); | |
| html.AppendLine("const ctx = document.getElementById('perfChart').getContext('2d');"); | |
| html.AppendLine("new Chart(ctx, {"); | |
| html.AppendLine(" type: 'line',"); | |
| html.AppendLine(" data: {"); | |
| html.AppendLine($" labels: [{string.Join(",", allLabels.Select(s => $"'{s}'"))}],"); | |
| html.AppendLine(" datasets: ["); | |
| string[] colors = { "red", "blue", "green", "orange", "purple", "cyan", "magenta" }; | |
| int ci = 0; | |
| foreach (var kvp in chartData) | |
| { | |
| var seriesName = kvp.Key; | |
| var dataPoints = kvp.Value; | |
| var speeds = dataPoints.Select(dp => dp.Speed).ToList(); | |
| html.AppendLine(" {"); | |
| html.AppendLine($" label: '{seriesName}',"); | |
| html.AppendLine($" data: [{string.Join(",", speeds.Select(v => v.ToString("F2")))}],"); | |
| html.AppendLine($" borderColor: '{colors[ci % colors.Length]}',"); | |
| html.AppendLine(" fill: false,"); | |
| html.AppendLine(" tension: 0.1"); | |
| html.AppendLine(" },"); | |
| ci++; | |
| } | |
| html.AppendLine(" ]"); | |
| html.AppendLine(" },"); | |
| html.AppendLine(" options: {"); | |
| html.AppendLine(" scales: {"); | |
| html.AppendLine(" y: { beginAtZero: true, title: { display: true, text: 'Speed (MB/s)' } },"); | |
| html.AppendLine(" x: { title: { display: true, text: 'Chunk Size' } }"); | |
| html.AppendLine(" }"); | |
| html.AppendLine(" }"); | |
| html.AppendLine("});"); | |
| html.AppendLine("</script>"); | |
| html.AppendLine("</body></html>"); | |
| File.WriteAllText("report.html", html.ToString()); | |
| Console.WriteLine("Report generated: report.html"); | |
| class TestRun | |
| { | |
| public int ChunkSize { get; set; } | |
| public long TotalFileSize { get; set; } | |
| public int NumWorkers { get; set; } | |
| public bool WriteThrough { get; set; } | |
| public string GetSeriesName() | |
| { | |
| string wt = WriteThrough ? "WriteThrough" : "Buffered"; | |
| return $"{NumWorkers} Workers ({wt})"; | |
| } | |
| public string GetSizeLabel() | |
| { | |
| return ChunkSize < 1048576 ? $"{ChunkSize / 1024.0:F0} KB" : $"{ChunkSize / 1048576.0:F0} MB"; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment