99 lines
3.2 KiB
C#
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" }
|
|
}
|
|
}
|
|
""";
|
|
}
|