|
1 | 1 | using System.Collections.Concurrent; |
| 2 | +using System.Diagnostics; |
2 | 3 | using System.Text.Json; |
3 | 4 | using Aspire.Hosting; |
4 | 5 | using Aspire.Hosting.ApplicationModel; |
@@ -34,7 +35,8 @@ public async Task AspireRunsIdenticalScenariosAgainstRealRf3AndExternalEngines() |
34 | 35 | $"--Benchmarks:Output={output}", $"--Benchmarks:Documents={options.Documents}", $"--Benchmarks:Operations={options.Operations}", $"--Benchmarks:Warmup={options.Warmup}", |
35 | 36 | $"--Benchmarks:Repetitions={options.Repetitions}", $"--Benchmarks:Concurrency={options.Concurrency}", $"--Benchmarks:Dimensions={options.Dimensions}", |
36 | 37 | $"--Benchmarks:TopK={options.TopK}", $"--Benchmarks:PayloadBytes={options.PayloadBytes}", $"--Benchmarks:Seed={options.Seed}", |
37 | | - $"--Benchmarks:TimeoutSeconds={options.TimeoutSeconds}"], timeout.Token); |
| 38 | + $"--Benchmarks:TimeoutSeconds={options.TimeoutSeconds}", |
| 39 | + $"--Benchmarks:SourceRevision={Environment.GetEnvironmentVariable("GITHUB_SHA") ?? "unrecorded"}"], timeout.Token); |
38 | 40 | builder.Services.AddLogging(logging => |
39 | 41 | { |
40 | 42 | logging.ClearProviders(); logging.AddConsole(); logging.SetMinimumLevel(LogLevel.Warning); |
@@ -105,8 +107,40 @@ await app.ResourceNotifications.WaitForResourceAsync("comparisons", |
105 | 107 | await captureLifetime.CancelAsync(); await capture; await Task.WhenAll(logCaptures.Values); |
106 | 108 | Directory.CreateDirectory(evidence); |
107 | 109 | await File.WriteAllLinesAsync(Path.Combine(evidence, "runner.log"), lines, CancellationToken.None); |
| 110 | + var redis = app.Services.GetRequiredService<DistributedApplicationModel>().Resources |
| 111 | + .OfType<ContainerResource>().Single(resource => resource.Name == "benchmark-redis"); |
108 | 112 | await app.StopAsync(CancellationToken.None); |
109 | | - if (Directory.Exists(root)) Directory.Delete(root, true); |
| 113 | + await DeleteDataAsync(root, redis); |
110 | 114 | } |
111 | 115 | } |
| 116 | + |
| 117 | + private static async Task DeleteDataAsync(string root, ContainerResource redis) |
| 118 | + { |
| 119 | + if (!Directory.Exists(root)) return; |
| 120 | + try { Directory.Delete(root, true); return; } |
| 121 | + catch (UnauthorizedAccessException) when (OperatingSystem.IsLinux()) { } |
| 122 | + |
| 123 | + // Native Linux bind mounts retain container UIDs. Use the already pinned image to remove |
| 124 | + // only this test's external data after all resources have stopped; never prune Docker data. |
| 125 | + var external = Path.Combine(root, "external"); |
| 126 | + if (!Directory.Exists(external) || !redis.TryGetContainerImageName(out var image)) |
| 127 | + throw new IOException("Cannot clean the comparison run's container-owned data."); |
| 128 | + var start = new ProcessStartInfo("docker") { RedirectStandardError = true, RedirectStandardOutput = true }; |
| 129 | + foreach (var argument in new[] |
| 130 | + { |
| 131 | + "run", "--rm", "--pull", "never", "--network", "none", "--read-only", "--user", "0:0", |
| 132 | + "--cap-drop", "ALL", "--cap-add", "DAC_OVERRIDE", "--entrypoint", "/bin/sh", |
| 133 | + "--mount", $"type=bind,source={external},target=/data", image!, "-c", "rm -rf /data/*" |
| 134 | + }) start.ArgumentList.Add(argument); |
| 135 | + using var process = Process.Start(start) ?? throw new IOException("Cannot start comparison data cleanup."); |
| 136 | + var error = process.StandardError.ReadToEndAsync(); |
| 137 | + var output = process.StandardOutput.ReadToEndAsync(); |
| 138 | + using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(30)); |
| 139 | + try { await process.WaitForExitAsync(timeout.Token); } |
| 140 | + catch (OperationCanceledException) { process.Kill(entireProcessTree: true); throw; } |
| 141 | + await output; |
| 142 | + if (process.ExitCode != 0) throw new IOException($"Comparison data cleanup failed: {await error}"); |
| 143 | + await error; |
| 144 | + Directory.Delete(root, true); |
| 145 | + } |
112 | 146 | } |
0 commit comments