diff --git a/cli/command/container/formatter_stats.go b/cli/command/container/formatter_stats.go index 4909405cf3..c079c913ff 100644 --- a/cli/command/container/formatter_stats.go +++ b/cli/command/container/formatter_stats.go @@ -1,7 +1,7 @@ package container import ( - "fmt" + "strconv" "sync" "github.com/docker/cli/cli/command/formatter" @@ -43,7 +43,7 @@ type StatsEntry struct { // Stats represents an entity to store containers statistics synchronously type Stats struct { - mutex sync.Mutex + mutex sync.RWMutex StatsEntry err error } @@ -51,8 +51,8 @@ type Stats struct { // GetError returns the container statistics error. // This is used to determine whether the statistics are valid or not func (cs *Stats) GetError() error { - cs.mutex.Lock() - defer cs.mutex.Unlock() + cs.mutex.RLock() + defer cs.mutex.RUnlock() return cs.err } @@ -94,8 +94,8 @@ func (cs *Stats) SetStatistics(s StatsEntry) { // GetStatistics returns container statistics with other meta data such as the container name func (cs *Stats) GetStatistics() StatsEntry { - cs.mutex.Lock() - defer cs.mutex.Unlock() + cs.mutex.RLock() + defer cs.mutex.RUnlock() return cs.StatsEntry } @@ -183,7 +183,7 @@ func (c *statsContext) CPUPerc() string { if c.s.IsInvalid { return "--" } - return fmt.Sprintf("%.2f%%", c.s.CPUPercentage) + return formatPercentage(c.s.CPUPercentage) } func (c *statsContext) MemUsage() string { @@ -193,33 +193,37 @@ func (c *statsContext) MemUsage() string { if c.os == winOSType { return units.BytesSize(c.s.Memory) } - return fmt.Sprintf("%s / %s", units.BytesSize(c.s.Memory), units.BytesSize(c.s.MemoryLimit)) + return units.BytesSize(c.s.Memory) + " / " + units.BytesSize(c.s.MemoryLimit) } func (c *statsContext) MemPerc() string { if c.s.IsInvalid || c.os == winOSType { return "--" } - return fmt.Sprintf("%.2f%%", c.s.MemoryPercentage) + return formatPercentage(c.s.MemoryPercentage) } func (c *statsContext) NetIO() string { if c.s.IsInvalid { return "--" } - return fmt.Sprintf("%s / %s", units.HumanSizeWithPrecision(c.s.NetworkRx, 3), units.HumanSizeWithPrecision(c.s.NetworkTx, 3)) + return units.HumanSizeWithPrecision(c.s.NetworkRx, 3) + " / " + units.HumanSizeWithPrecision(c.s.NetworkTx, 3) } func (c *statsContext) BlockIO() string { if c.s.IsInvalid { return "--" } - return fmt.Sprintf("%s / %s", units.HumanSizeWithPrecision(c.s.BlockRead, 3), units.HumanSizeWithPrecision(c.s.BlockWrite, 3)) + return units.HumanSizeWithPrecision(c.s.BlockRead, 3) + " / " + units.HumanSizeWithPrecision(c.s.BlockWrite, 3) } func (c *statsContext) PIDs() string { if c.s.IsInvalid || c.os == winOSType { return "--" } - return fmt.Sprintf("%d", c.s.PidsCurrent) + return strconv.FormatUint(c.s.PidsCurrent, 10) +} + +func formatPercentage(val float64) string { + return strconv.FormatFloat(val, 'f', 2, 64) + "%" } diff --git a/cli/command/container/formatter_stats_test.go b/cli/command/container/formatter_stats_test.go index 844679654b..ad395e6f74 100644 --- a/cli/command/container/formatter_stats_test.go +++ b/cli/command/container/formatter_stats_test.go @@ -308,3 +308,38 @@ func TestContainerStatsContextWriteTrunc(t *testing.T) { out.Reset() } } + +func BenchmarkStatsFormat(b *testing.B) { + b.ReportAllocs() + stats := genStats() + + for i := 0; i < b.N; i++ { + for _, s := range stats { + _ = s.CPUPerc() + _ = s.MemUsage() + _ = s.MemPerc() + _ = s.NetIO() + _ = s.BlockIO() + _ = s.PIDs() + } + } +} + +func genStats() []statsContext { + entry := statsContext{s: StatsEntry{ + CPUPercentage: 12.3456789, + Memory: 123.456789, + MemoryLimit: 987.654321, + MemoryPercentage: 12.3456789, + BlockRead: 123.456789, + BlockWrite: 987.654321, + NetworkRx: 123.456789, + NetworkTx: 987.654321, + PidsCurrent: 123456789, + }} + stats := make([]statsContext, 100) + for i := 0; i < 100; i++ { + stats = append(stats, entry) + } + return stats +} diff --git a/cli/command/container/stats.go b/cli/command/container/stats.go index 38d89450c4..0e938f3fad 100644 --- a/cli/command/container/stats.go +++ b/cli/command/container/stats.go @@ -184,13 +184,13 @@ func runStats(dockerCli command.Cli, opts *statsOptions) error { waitFirst.Wait() var errs []string - cStats.mu.Lock() + cStats.mu.RLock() for _, c := range cStats.cs { if err := c.GetError(); err != nil { errs = append(errs, err.Error()) } } - cStats.mu.Unlock() + cStats.mu.RUnlock() if len(errs) > 0 { return errors.New(strings.Join(errs, "\n")) } @@ -221,11 +221,11 @@ func runStats(dockerCli command.Cli, opts *statsOptions) error { for range ticker.C { cleanScreen() ccstats := []StatsEntry{} - cStats.mu.Lock() + cStats.mu.RLock() for _, c := range cStats.cs { ccstats = append(ccstats, c.GetStatistics()) } - cStats.mu.Unlock() + cStats.mu.RUnlock() if err = statsFormatWrite(statsCtx, ccstats, daemonOSType, !opts.noTrunc); err != nil { break } diff --git a/cli/command/container/stats_helpers.go b/cli/command/container/stats_helpers.go index 299bd26baf..5ea7f665dc 100644 --- a/cli/command/container/stats_helpers.go +++ b/cli/command/container/stats_helpers.go @@ -14,7 +14,7 @@ import ( ) type stats struct { - mu sync.Mutex + mu sync.RWMutex cs []*Stats }