cadvisor_stats_provider.go 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. // Copyright 2019 Yunion
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package stats
  15. import (
  16. "fmt"
  17. "path"
  18. "sort"
  19. "strings"
  20. cadvisorapiv2 "github.com/google/cadvisor/info/v2"
  21. "k8s.io/apimachinery/pkg/types"
  22. "k8s.io/klog/v2"
  23. "yunion.io/x/log"
  24. "yunion.io/x/onecloud/pkg/util/pod/cadvisor"
  25. )
  26. // containerID is the identity of a container in a pod.
  27. type containerID struct {
  28. podRef PodReference
  29. containerName string
  30. }
  31. // buildPodRef returns a PodReference that identifies the Pod managing cinfo
  32. func buildPodRef(containerLabels map[string]string) PodReference {
  33. podName := GetPodName(containerLabels)
  34. podNamespace := GetPodNamespace(containerLabels)
  35. podUID := GetPodUID(containerLabels)
  36. return PodReference{Name: podName, Namespace: podNamespace, UID: podUID}
  37. }
  38. func getCadvisorContainerInfo(ca cadvisor.Interface) (map[string]cadvisorapiv2.ContainerInfo, error) {
  39. infos, err := ca.ContainerInfoV2("/", cadvisorapiv2.RequestOptions{
  40. IdType: cadvisorapiv2.TypeName,
  41. Count: 2, // 2 samples are needed to compute "instantaneous" CPU
  42. Recursive: true,
  43. })
  44. if err != nil {
  45. if _, ok := infos["/"]; ok {
  46. // If the failure is partial, log it and return a best-effort
  47. // response.
  48. log.Errorf("Partial failure issuing cadvisor.ContainerInfoV2: %v", err)
  49. } else {
  50. return nil, fmt.Errorf("failed to get root cgroup stats: %v", err)
  51. }
  52. }
  53. return infos, nil
  54. }
  55. // libcontainerCgroupManagerType defines how to interface with libcontainer
  56. type libcontainerCgroupManagerType string
  57. const (
  58. // libcontainerCgroupfs means use libcontainer with cgroupfs
  59. libcontainerCgroupfs libcontainerCgroupManagerType = "cgroupfs"
  60. // libcontainerSystemd means use libcontainer with systemd
  61. libcontainerSystemd libcontainerCgroupManagerType = "systemd"
  62. // systemdSuffix is the cgroup name suffix for systemd
  63. systemdSuffix string = ".slice"
  64. )
  65. func IsSystemdStyleName(name string) bool {
  66. return strings.HasSuffix(name, systemdSuffix)
  67. }
  68. // CgroupName is the abstract name of a cgroup prior to any driver specific conversion.
  69. // It is specified as a list of strings from its individual components, such as:
  70. // {"kubepods", "burstable", "pod1234-abcd-5678-efgh"}
  71. type CgroupName []string
  72. func ParseCgroupfsToCgroupName(name string) CgroupName {
  73. components := strings.Split(strings.TrimPrefix(name, "/"), "/")
  74. if len(components) == 1 && components[0] == "" {
  75. components = []string{}
  76. }
  77. return CgroupName(components)
  78. }
  79. func unescapeSystemdCgroupName(part string) string {
  80. return strings.Replace(part, "_", "-", -1)
  81. }
  82. func ParseSystemdToCgroupName(name string) CgroupName {
  83. driverName := path.Base(name)
  84. driverName = strings.TrimSuffix(driverName, systemdSuffix)
  85. parts := strings.Split(driverName, "-")
  86. result := []string{}
  87. for _, part := range parts {
  88. result = append(result, unescapeSystemdCgroupName(part))
  89. }
  90. return CgroupName(result)
  91. }
  92. const (
  93. podCgroupNamePrefix = "pod"
  94. )
  95. // GetPodCgroupNameSuffix returns the last element of the pod CgroupName identifier
  96. func GetPodCgroupNameSuffix(podUID types.UID) string {
  97. return podCgroupNamePrefix + string(podUID)
  98. }
  99. func getContainerInfoById(id string, infos map[string]cadvisorapiv2.ContainerInfo) *cadvisorapiv2.ContainerInfo {
  100. for key, info := range infos {
  101. if IsSystemdStyleName(key) {
  102. // Convert to internal cgroup name and take the last component only.
  103. internalCgroupName := ParseSystemdToCgroupName(key)
  104. key = internalCgroupName[len(internalCgroupName)-1]
  105. } else {
  106. // Take last component only.
  107. key = path.Base(key)
  108. }
  109. //if GetPodCgroupNameSuffix(podUID) == key {
  110. if id == key {
  111. return &info
  112. }
  113. }
  114. return nil
  115. }
  116. func getLatestContainerStatsById(id string, infos map[string]cadvisorapiv2.ContainerInfo) *cadvisorapiv2.ContainerStats {
  117. info := getContainerInfoById(id, infos)
  118. if info == nil {
  119. return nil
  120. }
  121. cstat, found := latestContainerStats(info)
  122. if !found {
  123. return nil
  124. }
  125. return cstat
  126. }
  127. // getCadvisorPodInfoFromPodUID returns a pod cgroup information by matching the podUID with its CgroupName identifier base name
  128. func getCadvisorPodInfoFromPodUID(podUID types.UID, infos map[string]cadvisorapiv2.ContainerInfo) *cadvisorapiv2.ContainerInfo {
  129. return getContainerInfoById(string(podUID), infos)
  130. }
  131. // containerInfoWithCgroup contains the ContainerInfo and its cgroup name.
  132. type containerInfoWithCgroup struct {
  133. cinfo cadvisorapiv2.ContainerInfo
  134. cgroup string
  135. }
  136. // removeTerminatedContainerInfo returns the specified containerInfo but with
  137. // the stats of the terminated containers removed.
  138. //
  139. // A ContainerInfo is considered to be of a terminated container if it has an
  140. // older CreationTime and zero CPU instantaneous and memory RSS usage.
  141. func removeTerminatedContainerInfo(containerInfo map[string]cadvisorapiv2.ContainerInfo) map[string]cadvisorapiv2.ContainerInfo {
  142. cinfoMap := make(map[containerID][]containerInfoWithCgroup)
  143. for key, cinfo := range containerInfo {
  144. if !isPodManagedContainer(&cinfo) {
  145. continue
  146. }
  147. cinfoID := containerID{
  148. podRef: buildPodRef(cinfo.Spec.Labels),
  149. containerName: GetContainerName(cinfo.Spec.Labels),
  150. }
  151. cinfoMap[cinfoID] = append(cinfoMap[cinfoID], containerInfoWithCgroup{
  152. cinfo: cinfo,
  153. cgroup: key,
  154. })
  155. }
  156. result := make(map[string]cadvisorapiv2.ContainerInfo)
  157. for _, refs := range cinfoMap {
  158. if len(refs) == 1 {
  159. result[refs[0].cgroup] = refs[0].cinfo
  160. continue
  161. }
  162. sort.Sort(ByCreationTime(refs))
  163. for i := len(refs) - 1; i >= 0; i-- {
  164. if hasMemoryAndCPUInstUsage(&refs[i].cinfo) {
  165. result[refs[i].cgroup] = refs[i].cinfo
  166. break
  167. }
  168. }
  169. }
  170. return result
  171. }
  172. // hasMemoryAndCPUInstUsage returns true if the specified container info has
  173. // both non-zero CPU instantaneous usage and non-zero memory RSS usage, and
  174. // false otherwise.
  175. func hasMemoryAndCPUInstUsage(info *cadvisorapiv2.ContainerInfo) bool {
  176. if !info.Spec.HasCpu || !info.Spec.HasMemory {
  177. return false
  178. }
  179. cstat, found := latestContainerStats(info)
  180. if !found {
  181. return false
  182. }
  183. if cstat.CpuInst == nil {
  184. return false
  185. }
  186. return cstat.CpuInst.Usage.Total != 0 && cstat.Memory.RSS != 0
  187. }
  188. // ByCreationTime implements sort.Interface for []containerInfoWithCgroup based
  189. // on the cinfo.Spec.CreationTime field.
  190. type ByCreationTime []containerInfoWithCgroup
  191. func (a ByCreationTime) Len() int { return len(a) }
  192. func (a ByCreationTime) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
  193. func (a ByCreationTime) Less(i, j int) bool {
  194. if a[i].cinfo.Spec.CreationTime.Equal(a[j].cinfo.Spec.CreationTime) {
  195. // There shouldn't be two containers with the same name and/or the same
  196. // creation time. However, to make the logic here robust, we break the
  197. // tie by moving the one without CPU instantaneous or memory RSS usage
  198. // to the beginning.
  199. return hasMemoryAndCPUInstUsage(&a[j].cinfo)
  200. }
  201. return a[i].cinfo.Spec.CreationTime.Before(a[j].cinfo.Spec.CreationTime)
  202. }
  203. // isPodManagedContainer returns true if the cinfo container is managed by a Pod
  204. func isPodManagedContainer(cinfo *cadvisorapiv2.ContainerInfo) bool {
  205. podName := GetPodName(cinfo.Spec.Labels)
  206. podNamespace := GetPodNamespace(cinfo.Spec.Labels)
  207. managed := podName != "" && podNamespace != ""
  208. if !managed && podName != podNamespace {
  209. klog.Warningf(
  210. "Expect container to have either both podName (%s) and podNamespace (%s) labels, or neither.",
  211. podName, podNamespace)
  212. }
  213. return managed
  214. }