1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00

Correct CPU usage calculation in presence of offline CPUs and newer Linux

In https://github.com/torvalds/linux/commit/5ca3726 (released in v4.7-rc1) the
content of the `cpuacct.usage_percpu` file in sysfs was changed to include both
online and offline cpus. This broke the arithmetic in the stats helpers used by
`docker stats`, since it was using the length of the PerCPUUsage array as a
proxy for the number of online CPUs.

Add current number of online CPUs to types.StatsJSON and use it in the
calculation.

Keep a fallback to `len(v.CPUStats.CPUUsage.PercpuUsage)` so this code
continues to work when talking to an older daemon. An old client talking to a
new daemon will ignore the new field and behave as before.

Fixes #28941.

Signed-off-by: Ian Campbell <ian.campbell@docker.com>
(cherry picked from commit 115f91d757)
Signed-off-by: Victor Vieux <victorvieux@gmail.com>
This commit is contained in:
Ian Campbell 2017-03-06 17:29:09 +00:00 committed by Victor Vieux
parent 3c3f3204bf
commit 9b001c4f5f
9 changed files with 54 additions and 8 deletions

View file

@ -21,7 +21,7 @@ import (
// Common constants for daemon and client.
const (
// DefaultVersion of Current REST API
DefaultVersion string = "1.26"
DefaultVersion string = "1.27"
// NoBaseImageSpecifier is the symbol used by the FROM
// command to specify that no base image is to be used.

View file

@ -19,10 +19,10 @@ produces:
consumes:
- "application/json"
- "text/plain"
basePath: "/v1.26"
basePath: "/v1.27"
info:
title: "Docker Engine API"
version: "1.26"
version: "1.27"
x-logo:
url: "https://docs.docker.com/images/logo-docker-main.png"
description: |
@ -44,7 +44,7 @@ info:
The API is usually changed in each release of Docker, so API calls are versioned to ensure that clients don't break.
For Docker Engine >= 1.13.1, the API version is 1.26. To lock to this version, you prefix the URL with `/v1.26`. For example, calling `/info` is the same as calling `/v1.26/info`.
For Docker Engine >= 17.03.1, the API version is 1.27. To lock to this version, you prefix the URL with `/v1.27`. For example, calling `/info` is the same as calling `/v1.27/info`.
Engine releases in the near future should support this version of the API, so your client will continue to work even if it is talking to a newer Engine.
@ -52,10 +52,11 @@ info:
The API uses an open schema model, which means server may add extra properties to responses. Likewise, the server will ignore any extra query parameters and request body properties. When you write clients, you need to ignore additional properties in responses to ensure they do not break when talking to newer Docker daemons.
This documentation is for version 1.26 of the API, which was introduced with Docker 1.13.1. Use this table to find documentation for previous versions of the API:
This documentation is for version 1.27 of the API, which was introduced with Docker 17.03.1. Use this table to find documentation for previous versions of the API:
Docker version | API version | Changes
----------------|-------------|---------
1.13.1 & 17.03.0 | [1.26](https://docs.docker.com/engine/api/v1.26/) | [API changes](https://docs.docker.com/engine/api/version-history/#v1-26-api-changes)
1.13.0 | [1.25](https://docs.docker.com/engine/api/v1.25/) | [API changes](https://docs.docker.com/engine/api/version-history/#v1-25-api-changes)
1.12.x | [1.24](https://docs.docker.com/engine/api/v1.24/) | [API changes](https://docs.docker.com/engine/api/version-history/#v1-24-api-changes)
1.11.x | [1.23](https://docs.docker.com/engine/api/v1.23/) | [API changes](https://docs.docker.com/engine/api/version-history/#v1-23-api-changes)
@ -3378,7 +3379,13 @@ paths:
description: |
This endpoint returns a live stream of a containers resource usage statistics.
The `precpu_stats` is the CPU statistic of last read, which is used for calculating the CPU usage percentage. It is not the same as the `cpu_stats` field.
The `precpu_stats` is the CPU statistic of last read, which is used
for calculating the CPU usage percentage. It is not the same as the
`cpu_stats` field.
If either `precpu_stats.online_cpus` or `cpu_stats.online_cpus` is
nil then for compatibility with older daemons the length of the
corresponding `cpu_usage.percpu_usage` array should be used.
operationId: "ContainerStats"
produces:
- "application/json"
@ -3458,6 +3465,7 @@ paths:
total_usage: 100215355
usage_in_kernelmode: 30000000
system_cpu_usage: 739306590000000
online_cpus: 4
throttling_data:
periods: 0
throttled_periods: 0
@ -3473,6 +3481,7 @@ paths:
total_usage: 100093996
usage_in_kernelmode: 30000000
system_cpu_usage: 9492140000000
online_cpus: 4
throttling_data:
periods: 0
throttled_periods: 0

View file

@ -47,6 +47,9 @@ type CPUStats struct {
// System Usage. Linux only.
SystemUsage uint64 `json:"system_cpu_usage,omitempty"`
// Online CPUs. Linux only.
OnlineCPUs uint32 `json:"online_cpus,omitempty"`
// Throttling Data. Linux only.
ThrottlingData ThrottlingData `json:"throttling_data,omitempty"`
}

View file

@ -179,10 +179,14 @@ func calculateCPUPercentUnix(previousCPU, previousSystem uint64, v *types.StatsJ
cpuDelta = float64(v.CPUStats.CPUUsage.TotalUsage) - float64(previousCPU)
// calculate the change for the entire system between readings
systemDelta = float64(v.CPUStats.SystemUsage) - float64(previousSystem)
onlineCPUs = float64(v.CPUStats.OnlineCPUs)
)
if onlineCPUs == 0.0 {
onlineCPUs = float64(len(v.CPUStats.CPUUsage.PercpuUsage))
}
if systemDelta > 0.0 && cpuDelta > 0.0 {
cpuPercent = (cpuDelta / systemDelta) * float64(len(v.CPUStats.CPUUsage.PercpuUsage)) * 100.0
cpuPercent = (cpuDelta / systemDelta) * onlineCPUs * 100.0
}
return cpuPercent
}

View file

@ -58,7 +58,7 @@ import (
)
// DefaultVersion is the version of the current stable API
const DefaultVersion string = "1.26"
const DefaultVersion string = "1.27"
// Client is the API client that performs all operations
// against a docker server.

View file

@ -115,6 +115,12 @@ func (s *statsCollector) run() {
continue
}
onlineCPUs, err := s.getNumberOnlineCPUs()
if err != nil {
logrus.Errorf("collecting system online cpu count: %v", err)
continue
}
for _, pair := range pairs {
stats, err := s.supervisor.GetContainerStats(pair.container)
if err != nil {
@ -132,6 +138,7 @@ func (s *statsCollector) run() {
}
// FIXME: move to containerd on Linux (not Windows)
stats.CPUStats.SystemUsage = systemUsage
stats.CPUStats.OnlineCPUs = onlineCPUs
pair.publisher.Publish(*stats)
}

View file

@ -12,6 +12,11 @@ import (
"github.com/opencontainers/runc/libcontainer/system"
)
/*
#include <unistd.h>
*/
import "C"
// platformNewStatsCollector performs platform specific initialisation of the
// statsCollector structure.
func platformNewStatsCollector(s *statsCollector) {
@ -69,3 +74,11 @@ func (s *statsCollector) getSystemCPUUsage() (uint64, error) {
}
return 0, fmt.Errorf("invalid stat format. Error trying to parse the '/proc/stat' file")
}
func (s *statsCollector) getNumberOnlineCPUs() (uint32, error) {
i, err := C.sysconf(C._SC_NPROCESSORS_ONLN)
if err != nil {
return 0, err
}
return uint32(i), nil
}

View file

@ -13,3 +13,7 @@ func platformNewStatsCollector(s *statsCollector) {
func (s *statsCollector) getSystemCPUUsage() (uint64, error) {
return 0, nil
}
func (s *statsCollector) getNumberOnlineCPUs() (uint32, error) {
return 0, nil
}

View file

@ -13,6 +13,12 @@ keywords: "API, Docker, rcli, REST, documentation"
will be rejected.
-->
## v1.27 API changes
[Docker Engine API v1.27](https://docs.docker.com/engine/api/v1.27/) documentation
* `GET /containers/(id or name)/stats` now includes an `online_cpus` field in both `precpu_stats` and `cpu_stats`. If this field is `nil` then for compatibility with older daemons the length of the corresponding `cpu_usage.percpu_usage` array should be used.
## v1.26 API changes
[Docker Engine API v1.26](https://docs.docker.com/engine/api/v1.26/) documentation