Skip to content

Commit

Permalink
Get container stats directly for oneshot
Browse files Browse the repository at this point in the history
Signed-off-by: Xinfeng Liu <XinfengLiu@icloud.com>
  • Loading branch information
xinfengliu committed Sep 13, 2023
1 parent ca57e45 commit b54bac6
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 31 deletions.
9 changes: 9 additions & 0 deletions daemon/stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,15 @@ func (daemon *Daemon) ContainerStats(ctx context.Context, prefixOrName string, c
})
}

// Get container stats directly if OneShot is set
if config.OneShot {
stats, err := daemon.statsCollector.CollectOneShot(ctr)
if err != nil {
return err
}
return json.NewEncoder(config.OutStream).Encode(stats)
}

outStream := config.OutStream
if config.Stream {
wf := ioutils.NewWriteFlusher(outStream)
Expand Down
81 changes: 50 additions & 31 deletions daemon/stats/collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ type Collector struct {
supervisor supervisor
interval time.Duration
publishers map[*container.Container]*pubsub.Publisher
sync.RWMutex
cache cache
}

type cache struct {
onlineCPUs uint32
}

// NewCollector creates a stats collector that will poll the supervisor with the specified interval
Expand Down Expand Up @@ -56,6 +62,11 @@ func (s *Collector) Collect(c *container.Container) chan interface{} {
return publisher.Subscribe()
}

// CollectOneShot collect one shot container stats
func (s *Collector) CollectOneShot(c *container.Container) (*types.StatsJSON, error) {
return s.getContainerStats(c)
}

// StopCollection closes the channels for all subscribers and removes
// the container from metrics collection.
func (s *Collector) StopCollection(c *container.Container) {
Expand Down Expand Up @@ -114,6 +125,9 @@ func (s *Collector) Run() {
time.Sleep(s.interval)
continue
}
s.Lock()
s.cache.onlineCPUs = onlineCPUs
s.Unlock()

g, ctx := errgroup.WithContext(context.Background())
pairCh := make(chan publishersPair)
Expand All @@ -138,38 +152,11 @@ func (s *Collector) Run() {
for i := 0; i < numWorkers; i++ {
g.Go(func() error {
for pair := range pairCh {
stats, err := s.supervisor.GetContainerStats(pair.container)

switch err.(type) {
case nil:
// Sample system CPU usage close to container usage to avoid
// noise in metric calculations.
systemUsage, err := s.getSystemCPUUsage()
if err != nil {
log.G(context.TODO()).WithError(err).WithField("container_id", pair.container.ID).Errorf("collecting system cpu usage")
return err
}

// FIXME: move to containerd on Linux (not Windows)
stats.CPUStats.SystemUsage = systemUsage
stats.CPUStats.OnlineCPUs = onlineCPUs

pair.publisher.Publish(*stats)

case errdefs.ErrConflict, errdefs.ErrNotFound:
// publish empty stats containing only name and ID if not running or not found
pair.publisher.Publish(types.StatsJSON{
Name: pair.container.Name,
ID: pair.container.ID,
})

default:
log.G(context.TODO()).Errorf("collecting stats for %s: %v", pair.container.ID, err)
pair.publisher.Publish(types.StatsJSON{
Name: pair.container.Name,
ID: pair.container.ID,
})
stats, err := s.getContainerStats(pair.container)
if err != nil {
return err
}
pair.publisher.Publish(*stats)

select {
case <-ctx.Done():
Expand All @@ -184,3 +171,35 @@ func (s *Collector) Run() {
time.Sleep(s.interval)
}
}

func (s *Collector) getContainerStats(c *container.Container) (*types.StatsJSON, error) {
stats, err := s.supervisor.GetContainerStats(c)
switch err.(type) {
case nil:
// Sample system CPU usage close to container usage to avoid
// noise in metric calculations.
systemUsage, err := s.getSystemCPUUsage()
if err != nil {
log.G(context.TODO()).WithError(err).WithField("container_id", c.ID).Errorf("collecting system cpu usage")
return nil, err
}
// FIXME: move to containerd on Linux (not Windows)
stats.CPUStats.SystemUsage = systemUsage
s.RLock()
stats.CPUStats.OnlineCPUs = s.cache.onlineCPUs
s.RUnlock()
return stats, nil
case errdefs.ErrConflict, errdefs.ErrNotFound:
// publish empty stats containing only name and ID if not running or not found
return &types.StatsJSON{
Name: c.Name,
ID: c.ID,
}, nil
default:
log.G(context.TODO()).Errorf("collecting stats for %s: %v", c.ID, err)
return &types.StatsJSON{
Name: c.Name,
ID: c.ID,
}, nil
}
}

0 comments on commit b54bac6

Please sign in to comment.