77 "bytes"
88 "context"
99 "errors"
10- "fmt"
1110 "io"
12- "strings"
1311 "sync"
1412 "time"
1513
@@ -287,30 +285,32 @@ func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions)
287285 }
288286 }
289287
290- // Buffer to store formatted stats text.
291- // Once formatted, it will be printed in one write to avoid screen flickering.
292- var statsTextBuffer bytes.Buffer
288+ // renderBuf holds the formatted stats output produced by statsFormatWrite.
289+ // It does not include any terminal control sequences.
290+ var renderBuf bytes.Buffer
291+
292+ // frameBuf holds the final terminal frame, including cursor movement and
293+ // line-clearing escape sequences, written in a single pass to avoid flicker.
294+ var frameBuf bytes.Buffer
293295
294296 statsCtx := formatter.Context {
295- Output : & statsTextBuffer ,
297+ Output : & renderBuf ,
296298 Format : NewStatsFormat (format , daemonOSType ),
297299 }
298300
299301 if options .NoStream {
300- cStats .mu .RLock ()
301- ccStats := make ([]StatsEntry , 0 , len (cStats .cs ))
302- for _ , c := range cStats .cs {
303- ccStats = append (ccStats , c .GetStatistics ())
304- }
305- cStats .mu .RUnlock ()
306-
307- if len (ccStats ) == 0 {
302+ statsList := cStats .snapshot ()
303+ if len (statsList ) == 0 {
308304 return nil
309305 }
306+ ccStats := make ([]StatsEntry , 0 , len (statsList ))
307+ for _ , c := range statsList {
308+ ccStats = append (ccStats , c .GetStatistics ())
309+ }
310310 if err := statsFormatWrite (statsCtx , ccStats , daemonOSType , ! options .NoTrunc ); err != nil {
311311 return err
312312 }
313- _ , _ = fmt . Fprint ( dockerCLI .Out (), statsTextBuffer . String ())
313+ _ , _ = dockerCLI .Out (). Write ( renderBuf . Bytes ())
314314 return nil
315315 }
316316
@@ -319,34 +319,38 @@ func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions)
319319 for {
320320 select {
321321 case <- ticker .C :
322- cStats .mu .RLock ()
323- ccStats := make ([]StatsEntry , 0 , len (cStats .cs ))
324- for _ , c := range cStats .cs {
322+ renderBuf .Reset ()
323+ frameBuf .Reset ()
324+ statsList := cStats .snapshot ()
325+ if len (statsList ) == 0 && ! showAll {
326+ // Clear screen
327+ _ , _ = io .WriteString (dockerCLI .Out (), "\033 [H\033 [J" )
328+ return nil
329+ }
330+ ccStats := make ([]StatsEntry , 0 , len (statsList ))
331+ for _ , c := range statsList {
325332 ccStats = append (ccStats , c .GetStatistics ())
326333 }
327- cStats .mu .RUnlock ()
328-
329- // Start by moving the cursor to the top-left
330- _ , _ = fmt .Fprint (& statsTextBuffer , "\033 [H" )
331334
332335 if err := statsFormatWrite (statsCtx , ccStats , daemonOSType , ! options .NoTrunc ); err != nil {
333336 return err
334337 }
335338
336- for line := range strings .SplitSeq (statsTextBuffer .String (), "\n " ) {
339+ // Start by moving the cursor to the top-left
340+ _ , _ = io .WriteString (& frameBuf , "\033 [H" )
341+
342+ // TODO(thaJeztah): consider wrapping the writer to inject ANSI (line-clearing) during formatting.
343+ // instead of post-processing the results.
344+ for line := range bytes .SplitSeq (renderBuf .Bytes (), []byte {'\n' }) {
337345 // In case the new text is shorter than the one we are writing over,
338346 // we'll append the "erase line" escape sequence to clear the remaining text.
339- _ , _ = fmt .Fprintln (& statsTextBuffer , line , "\033 [K" )
347+ _ , _ = frameBuf .Write (line )
348+ _ , _ = io .WriteString (& frameBuf , "\033 [K" )
349+ _ = frameBuf .WriteByte ('\n' )
340350 }
341351 // We might have fewer containers than before, so let's clear the remaining text
342- _ , _ = fmt .Fprint (& statsTextBuffer , "\033 [J" )
343-
344- _ , _ = fmt .Fprint (dockerCLI .Out (), statsTextBuffer .String ())
345- statsTextBuffer .Reset ()
346-
347- if len (ccStats ) == 0 && ! showAll {
348- return nil
349- }
352+ _ , _ = io .WriteString (& frameBuf , "\033 [J" )
353+ _ , _ = dockerCLI .Out ().Write (frameBuf .Bytes ())
350354 case err , ok := <- closeChan :
351355 if ! ok || err == nil || errors .Is (err , io .EOF ) || errors .Is (err , io .ErrUnexpectedEOF ) {
352356 // Suppress "unexpected EOF" errors in the CLI so that
0 commit comments