@@ -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