mirror of
https://github.com/moby/moby.git
synced 2026-01-11 18:51:37 +00:00
Make one-shot stats faster
This commit moves one-shot stats processing out of the publishing channels, i.e. collect stats directly. Also changes the method of getSystemCPUUsage() on Linux to return number of online CPUs also. Signed-off-by: Xinfeng Liu <XinfengLiu@icloud.com>
This commit is contained in:
@@ -1,15 +1,11 @@
|
||||
package stats // import "github.com/docker/docker/daemon/stats"
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/containerd/log"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/container"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/moby/pubsub"
|
||||
)
|
||||
|
||||
@@ -20,7 +16,6 @@ type Collector struct {
|
||||
supervisor supervisor
|
||||
interval time.Duration
|
||||
publishers map[*container.Container]*pubsub.Publisher
|
||||
bufReader *bufio.Reader
|
||||
}
|
||||
|
||||
// NewCollector creates a stats collector that will poll the supervisor with the specified interval
|
||||
@@ -29,7 +24,6 @@ func NewCollector(supervisor supervisor, interval time.Duration) *Collector {
|
||||
interval: interval,
|
||||
supervisor: supervisor,
|
||||
publishers: make(map[*container.Container]*pubsub.Publisher),
|
||||
bufReader: bufio.NewReaderSize(nil, 128),
|
||||
}
|
||||
s.cond = sync.NewCond(&s.m)
|
||||
return s
|
||||
@@ -108,45 +102,15 @@ func (s *Collector) Run() {
|
||||
|
||||
s.cond.L.Unlock()
|
||||
|
||||
onlineCPUs, err := s.getNumberOnlineCPUs()
|
||||
if err != nil {
|
||||
log.G(context.TODO()).Errorf("collecting system online cpu count: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, pair := range pairs {
|
||||
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")
|
||||
continue
|
||||
if err != nil {
|
||||
stats = &types.StatsJSON{
|
||||
Name: pair.container.Name,
|
||||
ID: pair.container.ID,
|
||||
}
|
||||
|
||||
// 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,
|
||||
})
|
||||
}
|
||||
pair.publisher.Publish(*stats)
|
||||
}
|
||||
|
||||
time.Sleep(s.interval)
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
//go:build !windows
|
||||
|
||||
package stats // import "github.com/docker/docker/daemon/stats"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
// The value comes from `C.sysconf(C._SC_CLK_TCK)`, and
|
||||
// on Linux it's a constant which is safe to be hard coded,
|
||||
// so we can avoid using cgo here. For details, see:
|
||||
// https://github.com/containerd/cgroups/pull/12
|
||||
clockTicksPerSecond = 100
|
||||
nanoSecondsPerSecond = 1e9
|
||||
)
|
||||
|
||||
// getSystemCPUUsage returns the host system's cpu usage in
|
||||
// nanoseconds. An error is returned if the format of the underlying
|
||||
// file does not match.
|
||||
//
|
||||
// Uses /proc/stat defined by POSIX. Looks for the cpu
|
||||
// statistics line and then sums up the first seven fields
|
||||
// provided. See `man 5 proc` for details on specific field
|
||||
// information.
|
||||
func (s *Collector) getSystemCPUUsage() (uint64, error) {
|
||||
f, err := os.Open("/proc/stat")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer func() {
|
||||
s.bufReader.Reset(nil)
|
||||
f.Close()
|
||||
}()
|
||||
s.bufReader.Reset(f)
|
||||
|
||||
for {
|
||||
line, err := s.bufReader.ReadString('\n')
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
parts := strings.Fields(line)
|
||||
switch parts[0] {
|
||||
case "cpu":
|
||||
if len(parts) < 8 {
|
||||
return 0, fmt.Errorf("invalid number of cpu fields")
|
||||
}
|
||||
var totalClockTicks uint64
|
||||
for _, i := range parts[1:8] {
|
||||
v, err := strconv.ParseUint(i, 10, 64)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("Unable to convert value %s to int: %s", i, err)
|
||||
}
|
||||
totalClockTicks += v
|
||||
}
|
||||
return (totalClockTicks * nanoSecondsPerSecond) /
|
||||
clockTicksPerSecond, nil
|
||||
}
|
||||
}
|
||||
return 0, fmt.Errorf("invalid stat format. Error trying to parse the '/proc/stat' file")
|
||||
}
|
||||
|
||||
func (s *Collector) getNumberOnlineCPUs() (uint32, error) {
|
||||
var cpuset unix.CPUSet
|
||||
err := unix.SchedGetaffinity(0, &cpuset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return uint32(cpuset.Count()), nil
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package stats // import "github.com/docker/docker/daemon/stats"
|
||||
|
||||
// getSystemCPUUsage returns the host system's cpu usage in
|
||||
// nanoseconds. An error is returned if the format of the underlying
|
||||
// file does not match. This is a no-op on Windows.
|
||||
func (s *Collector) getSystemCPUUsage() (uint64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (s *Collector) getNumberOnlineCPUs() (uint32, error) {
|
||||
return 0, nil
|
||||
}
|
||||
Reference in New Issue
Block a user