stats_unix.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. //go:build !windows
  2. package daemon // import "github.com/docker/docker/daemon"
  3. import (
  4. "bufio"
  5. "context"
  6. "fmt"
  7. "os"
  8. "strconv"
  9. "strings"
  10. statsV1 "github.com/containerd/cgroups/v3/cgroup1/stats"
  11. statsV2 "github.com/containerd/cgroups/v3/cgroup2/stats"
  12. "github.com/docker/docker/api/types"
  13. "github.com/docker/docker/container"
  14. "github.com/pkg/errors"
  15. )
  16. func copyBlkioEntry(entries []*statsV1.BlkIOEntry) []types.BlkioStatEntry {
  17. out := make([]types.BlkioStatEntry, len(entries))
  18. for i, re := range entries {
  19. out[i] = types.BlkioStatEntry{
  20. Major: re.Major,
  21. Minor: re.Minor,
  22. Op: re.Op,
  23. Value: re.Value,
  24. }
  25. }
  26. return out
  27. }
  28. func (daemon *Daemon) stats(c *container.Container) (*types.StatsJSON, error) {
  29. c.Lock()
  30. task, err := c.GetRunningTask()
  31. c.Unlock()
  32. if err != nil {
  33. return nil, err
  34. }
  35. cs, err := task.Stats(context.Background())
  36. if err != nil {
  37. if strings.Contains(err.Error(), "container not found") {
  38. return nil, containerNotFound(c.ID)
  39. }
  40. return nil, err
  41. }
  42. s := &types.StatsJSON{}
  43. s.Read = cs.Read
  44. stats := cs.Metrics
  45. switch t := stats.(type) {
  46. case *statsV1.Metrics:
  47. return daemon.statsV1(s, t)
  48. case *statsV2.Metrics:
  49. return daemon.statsV2(s, t)
  50. default:
  51. return nil, errors.Errorf("unexpected type of metrics %+v", t)
  52. }
  53. }
  54. func (daemon *Daemon) statsV1(s *types.StatsJSON, stats *statsV1.Metrics) (*types.StatsJSON, error) {
  55. if stats.Blkio != nil {
  56. s.BlkioStats = types.BlkioStats{
  57. IoServiceBytesRecursive: copyBlkioEntry(stats.Blkio.IoServiceBytesRecursive),
  58. IoServicedRecursive: copyBlkioEntry(stats.Blkio.IoServicedRecursive),
  59. IoQueuedRecursive: copyBlkioEntry(stats.Blkio.IoQueuedRecursive),
  60. IoServiceTimeRecursive: copyBlkioEntry(stats.Blkio.IoServiceTimeRecursive),
  61. IoWaitTimeRecursive: copyBlkioEntry(stats.Blkio.IoWaitTimeRecursive),
  62. IoMergedRecursive: copyBlkioEntry(stats.Blkio.IoMergedRecursive),
  63. IoTimeRecursive: copyBlkioEntry(stats.Blkio.IoTimeRecursive),
  64. SectorsRecursive: copyBlkioEntry(stats.Blkio.SectorsRecursive),
  65. }
  66. }
  67. if stats.CPU != nil {
  68. s.CPUStats = types.CPUStats{
  69. CPUUsage: types.CPUUsage{
  70. TotalUsage: stats.CPU.Usage.Total,
  71. PercpuUsage: stats.CPU.Usage.PerCPU,
  72. UsageInKernelmode: stats.CPU.Usage.Kernel,
  73. UsageInUsermode: stats.CPU.Usage.User,
  74. },
  75. ThrottlingData: types.ThrottlingData{
  76. Periods: stats.CPU.Throttling.Periods,
  77. ThrottledPeriods: stats.CPU.Throttling.ThrottledPeriods,
  78. ThrottledTime: stats.CPU.Throttling.ThrottledTime,
  79. },
  80. }
  81. }
  82. if stats.Memory != nil {
  83. raw := map[string]uint64{
  84. "cache": stats.Memory.Cache,
  85. "rss": stats.Memory.RSS,
  86. "rss_huge": stats.Memory.RSSHuge,
  87. "mapped_file": stats.Memory.MappedFile,
  88. "dirty": stats.Memory.Dirty,
  89. "writeback": stats.Memory.Writeback,
  90. "pgpgin": stats.Memory.PgPgIn,
  91. "pgpgout": stats.Memory.PgPgOut,
  92. "pgfault": stats.Memory.PgFault,
  93. "pgmajfault": stats.Memory.PgMajFault,
  94. "inactive_anon": stats.Memory.InactiveAnon,
  95. "active_anon": stats.Memory.ActiveAnon,
  96. "inactive_file": stats.Memory.InactiveFile,
  97. "active_file": stats.Memory.ActiveFile,
  98. "unevictable": stats.Memory.Unevictable,
  99. "hierarchical_memory_limit": stats.Memory.HierarchicalMemoryLimit,
  100. "hierarchical_memsw_limit": stats.Memory.HierarchicalSwapLimit,
  101. "total_cache": stats.Memory.TotalCache,
  102. "total_rss": stats.Memory.TotalRSS,
  103. "total_rss_huge": stats.Memory.TotalRSSHuge,
  104. "total_mapped_file": stats.Memory.TotalMappedFile,
  105. "total_dirty": stats.Memory.TotalDirty,
  106. "total_writeback": stats.Memory.TotalWriteback,
  107. "total_pgpgin": stats.Memory.TotalPgPgIn,
  108. "total_pgpgout": stats.Memory.TotalPgPgOut,
  109. "total_pgfault": stats.Memory.TotalPgFault,
  110. "total_pgmajfault": stats.Memory.TotalPgMajFault,
  111. "total_inactive_anon": stats.Memory.TotalInactiveAnon,
  112. "total_active_anon": stats.Memory.TotalActiveAnon,
  113. "total_inactive_file": stats.Memory.TotalInactiveFile,
  114. "total_active_file": stats.Memory.TotalActiveFile,
  115. "total_unevictable": stats.Memory.TotalUnevictable,
  116. }
  117. if stats.Memory.Usage != nil {
  118. s.MemoryStats = types.MemoryStats{
  119. Stats: raw,
  120. Usage: stats.Memory.Usage.Usage,
  121. MaxUsage: stats.Memory.Usage.Max,
  122. Limit: stats.Memory.Usage.Limit,
  123. Failcnt: stats.Memory.Usage.Failcnt,
  124. }
  125. } else {
  126. s.MemoryStats = types.MemoryStats{
  127. Stats: raw,
  128. }
  129. }
  130. // if the container does not set memory limit, use the machineMemory
  131. if s.MemoryStats.Limit > daemon.machineMemory && daemon.machineMemory > 0 {
  132. s.MemoryStats.Limit = daemon.machineMemory
  133. }
  134. }
  135. if stats.Pids != nil {
  136. s.PidsStats = types.PidsStats{
  137. Current: stats.Pids.Current,
  138. Limit: stats.Pids.Limit,
  139. }
  140. }
  141. return s, nil
  142. }
  143. func (daemon *Daemon) statsV2(s *types.StatsJSON, stats *statsV2.Metrics) (*types.StatsJSON, error) {
  144. if stats.Io != nil {
  145. var isbr []types.BlkioStatEntry
  146. for _, re := range stats.Io.Usage {
  147. isbr = append(isbr,
  148. types.BlkioStatEntry{
  149. Major: re.Major,
  150. Minor: re.Minor,
  151. Op: "read",
  152. Value: re.Rbytes,
  153. },
  154. types.BlkioStatEntry{
  155. Major: re.Major,
  156. Minor: re.Minor,
  157. Op: "write",
  158. Value: re.Wbytes,
  159. },
  160. )
  161. }
  162. s.BlkioStats = types.BlkioStats{
  163. IoServiceBytesRecursive: isbr,
  164. // Other fields are unsupported
  165. }
  166. }
  167. if stats.CPU != nil {
  168. s.CPUStats = types.CPUStats{
  169. CPUUsage: types.CPUUsage{
  170. TotalUsage: stats.CPU.UsageUsec * 1000,
  171. // PercpuUsage is not supported
  172. UsageInKernelmode: stats.CPU.SystemUsec * 1000,
  173. UsageInUsermode: stats.CPU.UserUsec * 1000,
  174. },
  175. ThrottlingData: types.ThrottlingData{
  176. Periods: stats.CPU.NrPeriods,
  177. ThrottledPeriods: stats.CPU.NrThrottled,
  178. ThrottledTime: stats.CPU.ThrottledUsec * 1000,
  179. },
  180. }
  181. }
  182. if stats.Memory != nil {
  183. s.MemoryStats = types.MemoryStats{
  184. // Stats is not compatible with v1
  185. Stats: map[string]uint64{
  186. "anon": stats.Memory.Anon,
  187. "file": stats.Memory.File,
  188. "kernel_stack": stats.Memory.KernelStack,
  189. "slab": stats.Memory.Slab,
  190. "sock": stats.Memory.Sock,
  191. "shmem": stats.Memory.Shmem,
  192. "file_mapped": stats.Memory.FileMapped,
  193. "file_dirty": stats.Memory.FileDirty,
  194. "file_writeback": stats.Memory.FileWriteback,
  195. "anon_thp": stats.Memory.AnonThp,
  196. "inactive_anon": stats.Memory.InactiveAnon,
  197. "active_anon": stats.Memory.ActiveAnon,
  198. "inactive_file": stats.Memory.InactiveFile,
  199. "active_file": stats.Memory.ActiveFile,
  200. "unevictable": stats.Memory.Unevictable,
  201. "slab_reclaimable": stats.Memory.SlabReclaimable,
  202. "slab_unreclaimable": stats.Memory.SlabUnreclaimable,
  203. "pgfault": stats.Memory.Pgfault,
  204. "pgmajfault": stats.Memory.Pgmajfault,
  205. "workingset_refault": stats.Memory.WorkingsetRefault,
  206. "workingset_activate": stats.Memory.WorkingsetActivate,
  207. "workingset_nodereclaim": stats.Memory.WorkingsetNodereclaim,
  208. "pgrefill": stats.Memory.Pgrefill,
  209. "pgscan": stats.Memory.Pgscan,
  210. "pgsteal": stats.Memory.Pgsteal,
  211. "pgactivate": stats.Memory.Pgactivate,
  212. "pgdeactivate": stats.Memory.Pgdeactivate,
  213. "pglazyfree": stats.Memory.Pglazyfree,
  214. "pglazyfreed": stats.Memory.Pglazyfreed,
  215. "thp_fault_alloc": stats.Memory.ThpFaultAlloc,
  216. "thp_collapse_alloc": stats.Memory.ThpCollapseAlloc,
  217. },
  218. Usage: stats.Memory.Usage,
  219. // MaxUsage is not supported
  220. Limit: stats.Memory.UsageLimit,
  221. }
  222. // if the container does not set memory limit, use the machineMemory
  223. if s.MemoryStats.Limit > daemon.machineMemory && daemon.machineMemory > 0 {
  224. s.MemoryStats.Limit = daemon.machineMemory
  225. }
  226. if stats.MemoryEvents != nil {
  227. // Failcnt is set to the "oom" field of the "memory.events" file.
  228. // See https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html
  229. s.MemoryStats.Failcnt = stats.MemoryEvents.Oom
  230. }
  231. }
  232. if stats.Pids != nil {
  233. s.PidsStats = types.PidsStats{
  234. Current: stats.Pids.Current,
  235. Limit: stats.Pids.Limit,
  236. }
  237. }
  238. return s, nil
  239. }
  240. // Resolve Network SandboxID in case the container reuse another container's network stack
  241. func (daemon *Daemon) getNetworkSandboxID(c *container.Container) (string, error) {
  242. curr := c
  243. for curr.HostConfig.NetworkMode.IsContainer() {
  244. containerID := curr.HostConfig.NetworkMode.ConnectedContainer()
  245. connected, err := daemon.GetContainer(containerID)
  246. if err != nil {
  247. return "", errors.Wrapf(err, "Could not get container for %s", containerID)
  248. }
  249. curr = connected
  250. }
  251. return curr.NetworkSettings.SandboxID, nil
  252. }
  253. func (daemon *Daemon) getNetworkStats(c *container.Container) (map[string]types.NetworkStats, error) {
  254. sandboxID, err := daemon.getNetworkSandboxID(c)
  255. if err != nil {
  256. return nil, err
  257. }
  258. sb, err := daemon.netController.SandboxByID(sandboxID)
  259. if err != nil {
  260. return nil, err
  261. }
  262. lnstats, err := sb.Statistics()
  263. if err != nil {
  264. return nil, err
  265. }
  266. stats := make(map[string]types.NetworkStats)
  267. // Convert libnetwork nw stats into api stats
  268. for ifName, ifStats := range lnstats {
  269. stats[ifName] = types.NetworkStats{
  270. RxBytes: ifStats.RxBytes,
  271. RxPackets: ifStats.RxPackets,
  272. RxErrors: ifStats.RxErrors,
  273. RxDropped: ifStats.RxDropped,
  274. TxBytes: ifStats.TxBytes,
  275. TxPackets: ifStats.TxPackets,
  276. TxErrors: ifStats.TxErrors,
  277. TxDropped: ifStats.TxDropped,
  278. }
  279. }
  280. return stats, nil
  281. }
  282. const (
  283. // The value comes from `C.sysconf(C._SC_CLK_TCK)`, and
  284. // on Linux it's a constant which is safe to be hard coded,
  285. // so we can avoid using cgo here. For details, see:
  286. // https://github.com/containerd/cgroups/pull/12
  287. clockTicksPerSecond = 100
  288. nanoSecondsPerSecond = 1e9
  289. )
  290. // getSystemCPUUsage returns the host system's cpu usage in
  291. // nanoseconds and number of online CPUs. An error is returned
  292. // if the format of the underlying file does not match.
  293. //
  294. // Uses /proc/stat defined by POSIX. Looks for the cpu
  295. // statistics line and then sums up the first seven fields
  296. // provided. See `man 5 proc` for details on specific field
  297. // information.
  298. func getSystemCPUUsage() (cpuUsage uint64, cpuNum uint32, err error) {
  299. f, err := os.Open("/proc/stat")
  300. if err != nil {
  301. return 0, 0, err
  302. }
  303. defer f.Close()
  304. scanner := bufio.NewScanner(f)
  305. for scanner.Scan() {
  306. line := scanner.Text()
  307. if len(line) < 4 || line[:3] != "cpu" {
  308. break // Assume all cpu* records are at the front, like glibc https://github.com/bminor/glibc/blob/5d00c201b9a2da768a79ea8d5311f257871c0b43/sysdeps/unix/sysv/linux/getsysstats.c#L108-L135
  309. }
  310. if line[3] == ' ' {
  311. parts := strings.Fields(line)
  312. if len(parts) < 8 {
  313. return 0, 0, fmt.Errorf("invalid number of cpu fields")
  314. }
  315. var totalClockTicks uint64
  316. for _, i := range parts[1:8] {
  317. v, err := strconv.ParseUint(i, 10, 64)
  318. if err != nil {
  319. return 0, 0, fmt.Errorf("Unable to convert value %s to int: %w", i, err)
  320. }
  321. totalClockTicks += v
  322. }
  323. cpuUsage = (totalClockTicks * nanoSecondsPerSecond) /
  324. clockTicksPerSecond
  325. }
  326. if '0' <= line[3] && line[3] <= '9' {
  327. cpuNum++
  328. }
  329. }
  330. if err := scanner.Err(); err != nil {
  331. return 0, 0, fmt.Errorf("error scanning '/proc/stat' file: %w", err)
  332. }
  333. return
  334. }