99 lines
3.2 KiB
C#

using System.Runtime.CompilerServices;
using FluentAssertions;
using Workflow.Application.BuildFlow.Common.Services;
using Xunit;
using Xunit.Abstractions;
namespace BuildFlow.Workflow.Tests.Performance;
/// <summary>OpenXML 顺序导出的托管内存峰值门禁。</summary>
public sealed class OpenXmlMemoryProfileTests(ITestOutputHelper output)
{
private const int ExportRows = 50_000;
private const long MaxPeakDeltaBytes = 128L * 1024 * 1024;
[Fact]
public async Task LargeAsyncExportKeepsManagedMemoryBounded()
{
var filePath = Path.Combine(
Path.GetTempPath(),
$"buildflow-openxml-memory-{Guid.NewGuid():N}.xlsx");
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
var baselineBytes = GC.GetTotalMemory(true);
var peakBytes = baselineBytes;
void SampleManagedMemory()
{
peakBytes = Math.Max(peakBytes, GC.GetTotalMemory(false));
}
try
{
var service = new FormSchemaExcelService();
await using var file = new FileStream(
filePath,
FileMode.CreateNew,
FileAccess.ReadWrite,
FileShare.None,
81_920,
FileOptions.Asynchronous | FileOptions.SequentialScan);
await service.WriteExportAsync(
SchemaJson,
"内存门禁",
CreateRowsAsync(ExportRows, SampleManagedMemory),
file,
CancellationToken.None);
await file.FlushAsync(CancellationToken.None);
SampleManagedMemory();
new FileInfo(filePath).Length.Should().BeGreaterThan(0);
var peakDeltaBytes = Math.Max(0, peakBytes - baselineBytes);
peakDeltaBytes.Should().BeLessThan(
MaxPeakDeltaBytes,
"OpenXML 导出应按行顺序写入,不能把全部 JSON 行或工作表加载到内存");
output.WriteLine($"SOP_METRIC excel_export_peak_managed_bytes={peakDeltaBytes}");
output.WriteLine($"SOP_METRIC excel_export_rows={ExportRows}");
}
finally
{
System.IO.File.Delete(filePath);
}
}
private static async IAsyncEnumerable<string> CreateRowsAsync(
int count,
Action sampleManagedMemory,
[EnumeratorCancellation] CancellationToken ct = default)
{
for (var index = 0; index < count; index++)
{
ct.ThrowIfCancellationRequested();
if (index % 128 == 0)
{
sampleManagedMemory();
}
yield return $$"""{"id":{{index}},"name":"row-{{index}}","active":{{(index % 2 == 0).ToString().ToLowerInvariant()}}}""";
if (index % 1024 == 0)
{
await Task.Yield();
}
}
sampleManagedMemory();
}
private const string SchemaJson = """
{
"type": "object",
"properties": {
"id": { "type": "number", "title": "编号", "x-component": "InputNumber" },
"name": { "type": "string", "title": "名称", "x-component": "Input" },
"active": { "type": "boolean", "title": "启用", "x-component": "Switch" }
}
}
""";
}