Skip to content

Commit b5402cd

Browse files
committed
add speed and parallel tests
1 parent af238fd commit b5402cd

6 files changed

Lines changed: 212 additions & 4 deletions

File tree

.github/workflows/ci.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
name: CI
22

33
on:
4-
pull_request:
5-
branches:
6-
- main
4+
workflow_dispatch:
5+
#pull_request:
6+
# branches:
7+
# - main
78

89
jobs:
910
build:

.vscode/settings.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
2+
{
3+
"dotnet.unitTests.runSettingsPath": "TestExplorer.runsettings",
4+
}

TestExplorer.runsettings

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<RunSettings>
3+
<RunConfiguration>
4+
<EnvironmentVariables>
5+
<DOCKER_HOST>tcp://localhost:2375</DOCKER_HOST>
6+
<DOCKER_TLS_VERIFY>""</DOCKER_TLS_VERIFY>
7+
<DOCKER_CERT_PATH>""</DOCKER_CERT_PATH>
8+
<DOCKER_DOTNET_NATIVE_HTTP_ENABLED>1</DOCKER_DOTNET_NATIVE_HTTP_ENABLED>
9+
</EnvironmentVariables>
10+
</RunConfiguration>
11+
</RunSettings>

test/Docker.DotNet.Tests/CommonCommands.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,7 @@ public static class CommonCommands
44
{
55
public static readonly string[] SleepInfinity = ["/bin/sh", "-c", "trap \"exit 0\" TERM INT; sleep infinity"];
66

7-
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"];
7+
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"];
8+
9+
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"];
810
}

test/Docker.DotNet.Tests/Docker.DotNet.Tests.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,12 @@
2020
<ItemGroup>
2121
<Using Include="System" />
2222
<Using Include="System.Collections.Generic" />
23+
<Using Include="System.Collections.Concurrent" />
2324
<Using Include="System.Diagnostics" />
2425
<Using Include="System.IO" />
2526
<Using Include="System.Linq" />
2627
<Using Include="System.Net.Security" />
28+
<Using Include="System.Net.NetworkInformation" />
2729
<Using Include="System.Reflection" />
2830
<Using Include="System.Security.Cryptography.X509Certificates" />
2931
<Using Include="System.Text" />

test/Docker.DotNet.Tests/IContainerOperationsTests.cs

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,194 @@ await _testFixture.DockerClient.Containers.StopContainerAsync(
167167
Assert.NotEmpty(logList);
168168
}
169169

170+
[Fact]
171+
public async Task GetContainerLogs_Parallel_Tty_False_Follow_False_ReadsLogs()
172+
{
173+
using var containerLogsCts = new CancellationTokenSource(TimeSpan.FromSeconds(60));
174+
175+
var parallelContainerCount = 3;
176+
var parallelThreadCount = 100;
177+
var runtimeInSeconds = 9;
178+
179+
var containerIds = new string[parallelContainerCount];
180+
181+
long memoryUsageBefore = GC.GetTotalAllocatedBytes(true);
182+
183+
long socketsBefore = IPGlobalProperties.GetIPGlobalProperties()
184+
.GetTcpIPv4Statistics()
185+
.CurrentConnections;
186+
187+
Process process = Process.GetCurrentProcess();
188+
TimeSpan cpuTimeBefore = process.TotalProcessorTime;
189+
190+
ParallelOptions parallelOptions = new ParallelOptions
191+
{
192+
MaxDegreeOfParallelism = parallelContainerCount,
193+
CancellationToken = _testFixture.Cts.Token
194+
};
195+
196+
await Parallel.ForEachAsync(Enumerable.Range(0, parallelContainerCount), parallelOptions, async (parallel, ct) =>
197+
{
198+
var createContainerResponse = await _testFixture.DockerClient.Containers.CreateContainerAsync(
199+
new CreateContainerParameters
200+
{
201+
Image = _testFixture.Image.ID,
202+
Entrypoint = CommonCommands.EchoToStdoutAndStderr,
203+
Tty = false
204+
},
205+
_testFixture.Cts.Token
206+
);
207+
208+
await _testFixture.DockerClient.Containers.StartContainerAsync(
209+
createContainerResponse.ID,
210+
new ContainerStartParameters(),
211+
_testFixture.Cts.Token
212+
);
213+
containerIds[parallel] = createContainerResponse.ID;
214+
});
215+
216+
await Task.Delay(TimeSpan.FromSeconds(runtimeInSeconds));
217+
218+
await Parallel.ForEachAsync(Enumerable.Range(0, parallelContainerCount), parallelOptions, async (parallel, ct) =>
219+
{
220+
await _testFixture.DockerClient.Containers.StopContainerAsync(
221+
containerIds[parallel],
222+
new ContainerStopParameters(),
223+
_testFixture.Cts.Token
224+
);
225+
});
226+
227+
containerLogsCts.CancelAfter(TimeSpan.FromSeconds(1));
228+
229+
var logLists = new ConcurrentDictionary<int, string>();
230+
var threads = new List<Thread>();
231+
232+
for (int parallel = 0; parallel < parallelContainerCount * parallelThreadCount; parallel++)
233+
{
234+
int index = parallel;
235+
string containerId = containerIds[parallel % parallelContainerCount];
236+
CancellationToken ct = containerLogsCts.Token;
237+
238+
var thread = new Thread(() =>
239+
{
240+
var logList = new StringBuilder(2000);
241+
try
242+
{
243+
var task = _testFixture.DockerClient.Containers.GetContainerLogsAsync(
244+
containerId,
245+
new ContainerLogsParameters
246+
{
247+
ShowStderr = true,
248+
ShowStdout = true,
249+
Timestamps = true,
250+
Follow = false
251+
},
252+
new Progress<string>(m => logList.AppendLine(m)),
253+
ct
254+
);
255+
256+
task.GetAwaiter().GetResult();
257+
}
258+
catch (OperationCanceledException)
259+
{
260+
}
261+
262+
Thread.Sleep(100);
263+
264+
logLists.TryAdd(index, logList.ToString());
265+
logList.Clear();
266+
});
267+
268+
threads.Add(thread);
269+
thread.Start();
270+
}
271+
272+
foreach (var thread in threads)
273+
{
274+
thread.Join();
275+
}
276+
277+
TimeSpan cpuTimeAfter = process.TotalProcessorTime;
278+
279+
long socketsAfter = IPGlobalProperties.GetIPGlobalProperties()
280+
.GetTcpIPv4Statistics()
281+
.CurrentConnections;
282+
283+
long memoryUsageAfter = GC.GetTotalAllocatedBytes(true);
284+
285+
var averageLineCount = logLists.Values.Average(logs => logs.Split('\n').Count());
286+
287+
_testOutputHelper.WriteLine($"avg. Line count: {averageLineCount:N1}, cpu ticks: {cpuTimeAfter.Ticks - cpuTimeBefore.Ticks:N0}, mem usage: {memoryUsageAfter - memoryUsageBefore:N0}, sockets: {socketsAfter - socketsBefore:N0}");
288+
_testOutputHelper.WriteLine($"FirstLine: {logLists.Values.FirstOrDefault()}");
289+
290+
// one container should produce 2 lines per second (stdout + stderr) plus 1 for last empty line of split
291+
Assert.True(averageLineCount > (runtimeInSeconds + 1) * 2, $"Average line count {averageLineCount:N1} is less than expected {(runtimeInSeconds + 1) * 2}");
292+
GC.Collect();
293+
}
294+
295+
[Fact]
296+
public async Task GetContainerLogs_SpeedTest_Tty_False_Follow_True_Requires_Task_To_Be_Cancelled()
297+
{
298+
using var containerLogsCts = new CancellationTokenSource(TimeSpan.FromSeconds(60));
299+
300+
var runtimeInSeconds = 15;
301+
302+
var createContainerResponse = await _testFixture.DockerClient.Containers.CreateContainerAsync(
303+
new CreateContainerParameters
304+
{
305+
Image = _testFixture.Image.ID,
306+
Entrypoint = CommonCommands.EchoToStdoutAndStderrFast,
307+
Tty = false
308+
},
309+
_testFixture.Cts.Token
310+
);
311+
312+
await _testFixture.DockerClient.Containers.StartContainerAsync(
313+
createContainerResponse.ID,
314+
new ContainerStartParameters(),
315+
_testFixture.Cts.Token
316+
);
317+
318+
containerLogsCts.CancelAfter(TimeSpan.FromSeconds(runtimeInSeconds));
319+
320+
long memoryUsageBefore = GC.GetTotalAllocatedBytes(true);
321+
322+
var counter = 0;
323+
try
324+
{
325+
await _testFixture.DockerClient.Containers.GetContainerLogsAsync(
326+
createContainerResponse.ID,
327+
new ContainerLogsParameters
328+
{
329+
ShowStderr = true,
330+
ShowStdout = true,
331+
Timestamps = true,
332+
Follow = true
333+
},
334+
new Progress<string>(m => counter++),
335+
containerLogsCts.Token);
336+
}
337+
catch (OperationCanceledException)
338+
{
339+
340+
}
341+
342+
343+
long memoryUsageAfter = GC.GetTotalAllocatedBytes(true);
344+
345+
await _testFixture.DockerClient.Containers.StopContainerAsync(
346+
createContainerResponse.ID,
347+
new ContainerStopParameters(),
348+
_testFixture.Cts.Token
349+
);
350+
351+
_testOutputHelper.WriteLine($"Line count: {counter}, mem usage: {memoryUsageAfter - memoryUsageBefore:N0}");
352+
353+
Assert.True(counter > runtimeInSeconds * 25000, $"Line count {counter} is less than expected {runtimeInSeconds * 25000}");
354+
355+
GC.Collect();
356+
}
357+
170358
[Fact]
171359
public async Task GetContainerLogs_Tty_False_Follow_True_Requires_Task_To_Be_Cancelled()
172360
{

0 commit comments

Comments
 (0)