Merge pull request #3608 from thaJeztah/formatstats_optimize

Small performance optimizations for formatting stats
This commit is contained in:
Sebastiaan van Stijn 2022-05-17 10:10:14 +02:00 committed by GitHub
commit 53f8ed4bec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 56 additions and 17 deletions

View File

@ -1,7 +1,7 @@
package container package container
import ( import (
"fmt" "strconv"
"sync" "sync"
"github.com/docker/cli/cli/command/formatter" "github.com/docker/cli/cli/command/formatter"
@ -43,7 +43,7 @@ type StatsEntry struct {
// Stats represents an entity to store containers statistics synchronously // Stats represents an entity to store containers statistics synchronously
type Stats struct { type Stats struct {
mutex sync.Mutex mutex sync.RWMutex
StatsEntry StatsEntry
err error err error
} }
@ -51,8 +51,8 @@ type Stats struct {
// GetError returns the container statistics error. // GetError returns the container statistics error.
// This is used to determine whether the statistics are valid or not // This is used to determine whether the statistics are valid or not
func (cs *Stats) GetError() error { func (cs *Stats) GetError() error {
cs.mutex.Lock() cs.mutex.RLock()
defer cs.mutex.Unlock() defer cs.mutex.RUnlock()
return cs.err 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 // GetStatistics returns container statistics with other meta data such as the container name
func (cs *Stats) GetStatistics() StatsEntry { func (cs *Stats) GetStatistics() StatsEntry {
cs.mutex.Lock() cs.mutex.RLock()
defer cs.mutex.Unlock() defer cs.mutex.RUnlock()
return cs.StatsEntry return cs.StatsEntry
} }
@ -183,7 +183,7 @@ func (c *statsContext) CPUPerc() string {
if c.s.IsInvalid { if c.s.IsInvalid {
return "--" return "--"
} }
return fmt.Sprintf("%.2f%%", c.s.CPUPercentage) return formatPercentage(c.s.CPUPercentage)
} }
func (c *statsContext) MemUsage() string { func (c *statsContext) MemUsage() string {
@ -193,33 +193,37 @@ func (c *statsContext) MemUsage() string {
if c.os == winOSType { if c.os == winOSType {
return units.BytesSize(c.s.Memory) 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 { func (c *statsContext) MemPerc() string {
if c.s.IsInvalid || c.os == winOSType { if c.s.IsInvalid || c.os == winOSType {
return "--" return "--"
} }
return fmt.Sprintf("%.2f%%", c.s.MemoryPercentage) return formatPercentage(c.s.MemoryPercentage)
} }
func (c *statsContext) NetIO() string { func (c *statsContext) NetIO() string {
if c.s.IsInvalid { if c.s.IsInvalid {
return "--" 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 { func (c *statsContext) BlockIO() string {
if c.s.IsInvalid { if c.s.IsInvalid {
return "--" 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 { func (c *statsContext) PIDs() string {
if c.s.IsInvalid || c.os == winOSType { if c.s.IsInvalid || c.os == winOSType {
return "--" 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) + "%"
} }

View File

@ -308,3 +308,38 @@ func TestContainerStatsContextWriteTrunc(t *testing.T) {
out.Reset() 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
}

View File

@ -184,13 +184,13 @@ func runStats(dockerCli command.Cli, opts *statsOptions) error {
waitFirst.Wait() waitFirst.Wait()
var errs []string var errs []string
cStats.mu.Lock() cStats.mu.RLock()
for _, c := range cStats.cs { for _, c := range cStats.cs {
if err := c.GetError(); err != nil { if err := c.GetError(); err != nil {
errs = append(errs, err.Error()) errs = append(errs, err.Error())
} }
} }
cStats.mu.Unlock() cStats.mu.RUnlock()
if len(errs) > 0 { if len(errs) > 0 {
return errors.New(strings.Join(errs, "\n")) return errors.New(strings.Join(errs, "\n"))
} }
@ -221,11 +221,11 @@ func runStats(dockerCli command.Cli, opts *statsOptions) error {
for range ticker.C { for range ticker.C {
cleanScreen() cleanScreen()
ccstats := []StatsEntry{} ccstats := []StatsEntry{}
cStats.mu.Lock() cStats.mu.RLock()
for _, c := range cStats.cs { for _, c := range cStats.cs {
ccstats = append(ccstats, c.GetStatistics()) ccstats = append(ccstats, c.GetStatistics())
} }
cStats.mu.Unlock() cStats.mu.RUnlock()
if err = statsFormatWrite(statsCtx, ccstats, daemonOSType, !opts.noTrunc); err != nil { if err = statsFormatWrite(statsCtx, ccstats, daemonOSType, !opts.noTrunc); err != nil {
break break
} }

View File

@ -14,7 +14,7 @@ import (
) )
type stats struct { type stats struct {
mu sync.Mutex mu sync.RWMutex
cs []*Stats cs []*Stats
} }