metric_helper.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. // Copyright The OpenTelemetry Authors
  2. // SPDX-License-Identifier: Apache-2.0
  3. package dockerstatsreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/dockerstatsreceiver"
  4. import (
  5. "fmt"
  6. "strconv"
  7. "strings"
  8. dtypes "github.com/docker/docker/api/types"
  9. ctypes "github.com/docker/docker/api/types/container"
  10. )
  11. const nanosInASecond = 1e9
  12. // Following functions has been copied from: calculateCPUPercentUnix(), calculateMemUsageUnixNoCache(), calculateMemPercentUnixNoCache()
  13. // https://github.com/docker/cli/blob/a2e9ed3b874fccc177b9349f3b0277612403934f/cli/command/container/stats_helpers.go
  14. // Copyright 2012-2017 Docker, Inc.
  15. // This product includes software developed at Docker, Inc. (https://www.docker.com).
  16. // The following is courtesy of our legal counsel:
  17. // Use and transfer of Docker may be subject to certain restrictions by the
  18. // United States and other governments.
  19. // It is your responsibility to ensure that your use and/or transfer does not
  20. // violate applicable laws.
  21. // For more information, please see https://www.bis.doc.gov
  22. // See also https://www.apache.org/dev/crypto.html and/or seek legal counsel.
  23. func calculateCPUPercent(previous *dtypes.CPUStats, v *dtypes.CPUStats) float64 {
  24. var (
  25. cpuPercent = 0.0
  26. // calculate the change for the cpu usage of the container in between readings
  27. cpuDelta = float64(v.CPUUsage.TotalUsage) - float64(previous.CPUUsage.TotalUsage)
  28. // calculate the change for the entire system between readings
  29. systemDelta = float64(v.SystemUsage) - float64(previous.SystemUsage)
  30. onlineCPUs = float64(v.OnlineCPUs)
  31. )
  32. if onlineCPUs == 0.0 {
  33. onlineCPUs = float64(len(v.CPUUsage.PercpuUsage))
  34. }
  35. if systemDelta > 0.0 && cpuDelta > 0.0 {
  36. cpuPercent = (cpuDelta / systemDelta) * onlineCPUs * 100.0
  37. }
  38. return cpuPercent
  39. }
  40. // calculateMemUsageNoCache calculate memory usage of the container.
  41. // Cache is intentionally excluded to avoid misinterpretation of the output.
  42. //
  43. // On cgroup v1 host, the result is `mem.Usage - mem.Stats["total_inactive_file"]` .
  44. // On cgroup v2 host, the result is `mem.Usage - mem.Stats["inactive_file"] `.
  45. //
  46. // This definition is consistent with cadvisor and containerd/CRI.
  47. // * https://github.com/google/cadvisor/commit/307d1b1cb320fef66fab02db749f07a459245451
  48. // * https://github.com/containerd/cri/commit/6b8846cdf8b8c98c1d965313d66bc8489166059a
  49. //
  50. // On Docker 19.03 and older, the result was `mem.Usage - mem.Stats["cache"]`.
  51. // See https://github.com/moby/moby/issues/40727 for the background.
  52. func calculateMemUsageNoCache(memoryStats *dtypes.MemoryStats) uint64 {
  53. // cgroup v1
  54. if v, isCgroup1 := memoryStats.Stats["total_inactive_file"]; isCgroup1 && v < memoryStats.Usage {
  55. return memoryStats.Usage - v
  56. }
  57. // cgroup v2
  58. if v := memoryStats.Stats["inactive_file"]; v < memoryStats.Usage {
  59. return memoryStats.Usage - v
  60. }
  61. return memoryStats.Usage
  62. }
  63. func calculateMemoryPercent(limit uint64, usedNoCache uint64) float64 {
  64. // MemoryStats.Limit will never be 0 unless the container is not running and we haven't
  65. // got any data from cgroup
  66. if limit != 0 {
  67. return float64(usedNoCache) / float64(limit) * 100.0
  68. }
  69. return 0.0
  70. }
  71. // calculateCPULimit calculate the number of cpus assigned to a container.
  72. //
  73. // Calculation is based on 3 alternatives by the following order:
  74. // - nanocpus: if set by i.e docker run -cpus=2
  75. // - cpusetCpus: if set by i.e docker run -docker run -cpuset-cpus="0,2"
  76. // - cpuquota: if set by i.e docker run -cpu-quota=50000
  77. //
  78. // See https://docs.docker.com/config/containers/resource_constraints/#configure-the-default-cfs-scheduler for background.
  79. func calculateCPULimit(hostConfig *ctypes.HostConfig) (float64, error) {
  80. var cpuLimit float64
  81. var err error
  82. switch {
  83. case hostConfig.NanoCPUs > 0:
  84. cpuLimit = float64(hostConfig.NanoCPUs) / nanosInASecond
  85. case hostConfig.CpusetCpus != "":
  86. cpuLimit, err = parseCPUSet(hostConfig.CpusetCpus)
  87. if err != nil {
  88. return cpuLimit, err
  89. }
  90. case hostConfig.CPUQuota > 0:
  91. period := hostConfig.CPUPeriod
  92. if period == 0 {
  93. period = 100000 // Default CFS Period
  94. }
  95. cpuLimit = float64(hostConfig.CPUQuota) / float64(period)
  96. }
  97. return cpuLimit, nil
  98. }
  99. // parseCPUSet helper function to decompose -cpuset-cpus value into number os cpus.
  100. func parseCPUSet(line string) (float64, error) {
  101. var numCPUs uint64
  102. lineSlice := strings.Split(line, ",")
  103. for _, l := range lineSlice {
  104. lineParts := strings.Split(l, "-")
  105. if len(lineParts) == 2 {
  106. p0, err0 := strconv.Atoi(lineParts[0])
  107. if err0 != nil {
  108. return 0, fmt.Errorf("invalid -cpuset-cpus value: %w", err0)
  109. }
  110. p1, err1 := strconv.Atoi(lineParts[1])
  111. if err1 != nil {
  112. return 0, fmt.Errorf("invalid -cpuset-cpus value: %w", err1)
  113. }
  114. numCPUs += uint64(p1 - p0 + 1)
  115. } else if len(lineParts) == 1 {
  116. numCPUs++
  117. }
  118. }
  119. return float64(numCPUs), nil
  120. }