From 9b001c4f5ff6530719c421f66b09598925c2bf2e Mon Sep 17 00:00:00 2001 From: Ian Campbell Date: Mon, 6 Mar 2017 17:29:09 +0000 Subject: [PATCH] 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 (cherry picked from commit 115f91d7575d6de6c7781a96a082f144fd17e400) Signed-off-by: Victor Vieux --- api/common.go | 2 +- api/swagger.yaml | 19 ++++++++++++++----- api/types/stats.go | 3 +++ cli/command/container/stats_helpers.go | 6 +++++- client/client.go | 2 +- daemon/stats_collector.go | 7 +++++++ daemon/stats_collector_unix.go | 13 +++++++++++++ daemon/stats_collector_windows.go | 4 ++++ docs/api/version-history.md | 6 ++++++ 9 files changed, 54 insertions(+), 8 deletions(-) diff --git a/api/common.go b/api/common.go index fd065d5abe..cde250ff93 100644 --- a/api/common.go +++ b/api/common.go @@ -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. diff --git a/api/swagger.yaml b/api/swagger.yaml index 4399dd688a..8ebc72aa52 100644 --- a/api/swagger.yaml +++ b/api/swagger.yaml @@ -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 container’s 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 diff --git a/api/types/stats.go b/api/types/stats.go index 9bf1928b8c..7ca76a5b63 100644 --- a/api/types/stats.go +++ b/api/types/stats.go @@ -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"` } diff --git a/cli/command/container/stats_helpers.go b/cli/command/container/stats_helpers.go index 4b57e3fe05..b1a7740486 100644 --- a/cli/command/container/stats_helpers.go +++ b/cli/command/container/stats_helpers.go @@ -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 } diff --git a/client/client.go b/client/client.go index c3e85d455a..50f9fe119d 100644 --- a/client/client.go +++ b/client/client.go @@ -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. diff --git a/daemon/stats_collector.go b/daemon/stats_collector.go index 291933b7ac..6b2bf1a65d 100644 --- a/daemon/stats_collector.go +++ b/daemon/stats_collector.go @@ -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) } diff --git a/daemon/stats_collector_unix.go b/daemon/stats_collector_unix.go index 0fcc9c5828..0a81cb8b68 100644 --- a/daemon/stats_collector_unix.go +++ b/daemon/stats_collector_unix.go @@ -12,6 +12,11 @@ import ( "github.com/opencontainers/runc/libcontainer/system" ) +/* +#include +*/ +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 +} diff --git a/daemon/stats_collector_windows.go b/daemon/stats_collector_windows.go index 41731b9c14..435bc4b59c 100644 --- a/daemon/stats_collector_windows.go +++ b/daemon/stats_collector_windows.go @@ -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 +} diff --git a/docs/api/version-history.md b/docs/api/version-history.md index a9cd07c1f1..5f3a006264 100644 --- a/docs/api/version-history.md +++ b/docs/api/version-history.md @@ -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