Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

{
"dotnet.unitTests.runSettingsPath": "TestExplorer.runsettings",
}
11 changes: 11 additions & 0 deletions TestExplorer.runsettings
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<RunSettings>
<RunConfiguration>
<EnvironmentVariables>
<DOCKER_HOST>tcp://localhost:2375</DOCKER_HOST>
<DOCKER_TLS_VERIFY>""</DOCKER_TLS_VERIFY>
<DOCKER_CERT_PATH>""</DOCKER_CERT_PATH>
<DOCKER_DOTNET_NATIVE_HTTP_ENABLED>1</DOCKER_DOTNET_NATIVE_HTTP_ENABLED>
</EnvironmentVariables>
</RunConfiguration>
</RunSettings>
4 changes: 3 additions & 1 deletion test/Docker.DotNet.Tests/CommonCommands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@ public static class CommonCommands
{
public static readonly string[] SleepInfinity = ["/bin/sh", "-c", "trap \"exit 0\" TERM INT; sleep infinity"];

public static readonly string[] EchoToStdoutAndStderr = ["/bin/sh", "-c", "trap \"exit 0\" TERM INT; while true; do echo \"stdout message\"; echo \"stderr message\" >&2; sleep 1; done"];
public static readonly string[] EchoToStdoutAndStderr = ["/bin/sh", "-c", "trap \"exit 0\" TERM INT; RND=$RANDOM; while true; do echo \"stdout message $RND\"; echo \"stderr message $RND\" >&2; sleep 1; done"];

public static readonly string[] EchoToStdoutAndStderrFast = ["/bin/sh", "-c", "trap \"exit 0\" TERM INT; RND=$RANDOM; while true; do echo \"stdout message $RND\"; echo \"stderr message $RND\" >&2; done"];
}
2 changes: 2 additions & 0 deletions test/Docker.DotNet.Tests/Docker.DotNet.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@
<ItemGroup>
<Using Include="System" />
<Using Include="System.Collections.Generic" />
<Using Include="System.Collections.Concurrent" />
<Using Include="System.Diagnostics" />
<Using Include="System.IO" />
<Using Include="System.Linq" />
<Using Include="System.Net.Security" />
<Using Include="System.Net.NetworkInformation" />
<Using Include="System.Reflection" />
<Using Include="System.Security.Cryptography.X509Certificates" />
<Using Include="System.Text" />
Expand Down
188 changes: 188 additions & 0 deletions test/Docker.DotNet.Tests/IContainerOperationsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,194 @@ await _testFixture.DockerClient.Containers.StopContainerAsync(
Assert.NotEmpty(logList);
}

[Fact]
public async Task GetContainerLogs_Parallel_Tty_False_Follow_False_ReadsLogs()
{
using var containerLogsCts = new CancellationTokenSource(TimeSpan.FromSeconds(60));

var parallelContainerCount = 3;
var parallelThreadCount = 100;
var runtimeInSeconds = 9;

var containerIds = new string[parallelContainerCount];

long memoryUsageBefore = GC.GetTotalAllocatedBytes(true);

long socketsBefore = IPGlobalProperties.GetIPGlobalProperties()
.GetTcpIPv4Statistics()
.CurrentConnections;

Process process = Process.GetCurrentProcess();
TimeSpan cpuTimeBefore = process.TotalProcessorTime;

ParallelOptions parallelOptions = new ParallelOptions
{
MaxDegreeOfParallelism = parallelContainerCount,
CancellationToken = _testFixture.Cts.Token
};

await Parallel.ForEachAsync(Enumerable.Range(0, parallelContainerCount), parallelOptions, async (parallel, ct) =>
{
var createContainerResponse = await _testFixture.DockerClient.Containers.CreateContainerAsync(
new CreateContainerParameters
{
Image = _testFixture.Image.ID,
Entrypoint = CommonCommands.EchoToStdoutAndStderr,
Tty = false
},
_testFixture.Cts.Token
);

await _testFixture.DockerClient.Containers.StartContainerAsync(
createContainerResponse.ID,
new ContainerStartParameters(),
_testFixture.Cts.Token
);
containerIds[parallel] = createContainerResponse.ID;
});

await Task.Delay(TimeSpan.FromSeconds(runtimeInSeconds));

await Parallel.ForEachAsync(Enumerable.Range(0, parallelContainerCount), parallelOptions, async (parallel, ct) =>
{
await _testFixture.DockerClient.Containers.StopContainerAsync(
containerIds[parallel],
new ContainerStopParameters(),
_testFixture.Cts.Token
);
});

containerLogsCts.CancelAfter(TimeSpan.FromSeconds(1));

var logLists = new ConcurrentDictionary<int, string>();
var threads = new List<Thread>();

for (int parallel = 0; parallel < parallelContainerCount * parallelThreadCount; parallel++)
{
int index = parallel;
string containerId = containerIds[parallel % parallelContainerCount];
CancellationToken ct = containerLogsCts.Token;

var thread = new Thread(() =>
{
var logList = new StringBuilder(2000);
try
{
var task = _testFixture.DockerClient.Containers.GetContainerLogsAsync(
containerId,
new ContainerLogsParameters
{
ShowStderr = true,
ShowStdout = true,
Timestamps = true,
Follow = false
},
new Progress<string>(m => logList.AppendLine(m)),
ct
);

task.GetAwaiter().GetResult();
}
catch (OperationCanceledException)
{
}

Thread.Sleep(100);

logLists.TryAdd(index, logList.ToString());
logList.Clear();
});

threads.Add(thread);
thread.Start();
}

foreach (var thread in threads)
{
thread.Join();
}

TimeSpan cpuTimeAfter = process.TotalProcessorTime;

long socketsAfter = IPGlobalProperties.GetIPGlobalProperties()
.GetTcpIPv4Statistics()
.CurrentConnections;

long memoryUsageAfter = GC.GetTotalAllocatedBytes(true);

var averageLineCount = logLists.Values.Average(logs => logs.Split('\n').Count());

_testOutputHelper.WriteLine($"avg. Line count: {averageLineCount:N1}, cpu ticks: {cpuTimeAfter.Ticks - cpuTimeBefore.Ticks:N0}, mem usage: {memoryUsageAfter - memoryUsageBefore:N0}, sockets: {socketsAfter - socketsBefore:N0}");
_testOutputHelper.WriteLine($"FirstLine: {logLists.Values.FirstOrDefault()}");

// one container should produce 2 lines per second (stdout + stderr) plus 1 for last empty line of split
Assert.True(averageLineCount > (runtimeInSeconds + 1) * 2, $"Average line count {averageLineCount:N1} is less than expected {(runtimeInSeconds + 1) * 2}");
GC.Collect();
}

[Fact]
public async Task GetContainerLogs_SpeedTest_Tty_False_Follow_True_Requires_Task_To_Be_Cancelled()
{
using var containerLogsCts = new CancellationTokenSource(TimeSpan.FromSeconds(60));

var runtimeInSeconds = 15;

var createContainerResponse = await _testFixture.DockerClient.Containers.CreateContainerAsync(
new CreateContainerParameters
{
Image = _testFixture.Image.ID,
Entrypoint = CommonCommands.EchoToStdoutAndStderrFast,
Tty = false
},
_testFixture.Cts.Token
);

await _testFixture.DockerClient.Containers.StartContainerAsync(
createContainerResponse.ID,
new ContainerStartParameters(),
_testFixture.Cts.Token
);

containerLogsCts.CancelAfter(TimeSpan.FromSeconds(runtimeInSeconds));

long memoryUsageBefore = GC.GetTotalAllocatedBytes(true);

var counter = 0;
try
{
await _testFixture.DockerClient.Containers.GetContainerLogsAsync(
createContainerResponse.ID,
new ContainerLogsParameters
{
ShowStderr = true,
ShowStdout = true,
Timestamps = true,
Follow = true
},
new Progress<string>(m => counter++),
containerLogsCts.Token);
}
catch (OperationCanceledException)
{

}


long memoryUsageAfter = GC.GetTotalAllocatedBytes(true);

await _testFixture.DockerClient.Containers.StopContainerAsync(
createContainerResponse.ID,
new ContainerStopParameters(),
_testFixture.Cts.Token
);

_testOutputHelper.WriteLine($"Line count: {counter}, mem usage: {memoryUsageAfter - memoryUsageBefore:N0}");

Assert.True(counter > runtimeInSeconds * 25000, $"Line count {counter} is less than expected {runtimeInSeconds * 25000}");

GC.Collect();
}

[Fact]
public async Task GetContainerLogs_Tty_False_Follow_True_Requires_Task_To_Be_Cancelled()
{
Expand Down
Loading