cri_stats_provider.go 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855
  1. /*
  2. Copyright 2017 The Kubernetes Authors.
  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. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package stats
  14. import (
  15. "errors"
  16. "fmt"
  17. "path"
  18. "path/filepath"
  19. "sort"
  20. "strings"
  21. "sync"
  22. "time"
  23. cadvisorfs "github.com/google/cadvisor/fs"
  24. cadvisorapiv2 "github.com/google/cadvisor/info/v2"
  25. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  26. "k8s.io/apimachinery/pkg/types"
  27. internalapi "k8s.io/cri-api/pkg/apis"
  28. runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
  29. "k8s.io/klog"
  30. statsapi "k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1"
  31. "k8s.io/kubernetes/pkg/kubelet/cadvisor"
  32. kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
  33. "k8s.io/kubernetes/pkg/kubelet/kuberuntime"
  34. "k8s.io/kubernetes/pkg/kubelet/server/stats"
  35. kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
  36. )
  37. var (
  38. // defaultCachePeriod is the default cache period for each cpuUsage.
  39. defaultCachePeriod = 10 * time.Minute
  40. )
  41. // cpuUsageRecord holds the cpu usage stats and the calculated usageNanoCores.
  42. type cpuUsageRecord struct {
  43. stats *runtimeapi.CpuUsage
  44. usageNanoCores *uint64
  45. }
  46. // criStatsProvider implements the containerStatsProvider interface by getting
  47. // the container stats from CRI.
  48. type criStatsProvider struct {
  49. // cadvisor is used to get the node root filesystem's stats (such as the
  50. // capacity/available bytes/inodes) that will be populated in per container
  51. // filesystem stats.
  52. cadvisor cadvisor.Interface
  53. // resourceAnalyzer is used to get the volume stats of the pods.
  54. resourceAnalyzer stats.ResourceAnalyzer
  55. // runtimeService is used to get the status and stats of the pods and its
  56. // managed containers.
  57. runtimeService internalapi.RuntimeService
  58. // imageService is used to get the stats of the image filesystem.
  59. imageService internalapi.ImageManagerService
  60. // logMetrics provides the metrics for container logs
  61. logMetricsService LogMetricsService
  62. // osInterface is the interface for syscalls.
  63. osInterface kubecontainer.OSInterface
  64. // cpuUsageCache caches the cpu usage for containers.
  65. cpuUsageCache map[string]*cpuUsageRecord
  66. mutex sync.RWMutex
  67. }
  68. // newCRIStatsProvider returns a containerStatsProvider implementation that
  69. // provides container stats using CRI.
  70. func newCRIStatsProvider(
  71. cadvisor cadvisor.Interface,
  72. resourceAnalyzer stats.ResourceAnalyzer,
  73. runtimeService internalapi.RuntimeService,
  74. imageService internalapi.ImageManagerService,
  75. logMetricsService LogMetricsService,
  76. osInterface kubecontainer.OSInterface,
  77. ) containerStatsProvider {
  78. return &criStatsProvider{
  79. cadvisor: cadvisor,
  80. resourceAnalyzer: resourceAnalyzer,
  81. runtimeService: runtimeService,
  82. imageService: imageService,
  83. logMetricsService: logMetricsService,
  84. osInterface: osInterface,
  85. cpuUsageCache: make(map[string]*cpuUsageRecord),
  86. }
  87. }
  88. // ListPodStats returns the stats of all the pod-managed containers.
  89. func (p *criStatsProvider) ListPodStats() ([]statsapi.PodStats, error) {
  90. // Don't update CPU nano core usage.
  91. return p.listPodStats(false)
  92. }
  93. // ListPodStatsAndUpdateCPUNanoCoreUsage updates the cpu nano core usage for
  94. // the containers and returns the stats for all the pod-managed containers.
  95. // This is a workaround because CRI runtimes do not supply nano core usages,
  96. // so this function calculate the difference between the current and the last
  97. // (cached) cpu stats to calculate this metrics. The implementation assumes a
  98. // single caller to periodically invoke this function to update the metrics. If
  99. // there exist multiple callers, the period used to compute the cpu usage may
  100. // vary and the usage could be incoherent (e.g., spiky). If no caller calls
  101. // this function, the cpu usage will stay nil. Right now, eviction manager is
  102. // the only caller, and it calls this function every 10s.
  103. func (p *criStatsProvider) ListPodStatsAndUpdateCPUNanoCoreUsage() ([]statsapi.PodStats, error) {
  104. // Update CPU nano core usage.
  105. return p.listPodStats(true)
  106. }
  107. func (p *criStatsProvider) listPodStats(updateCPUNanoCoreUsage bool) ([]statsapi.PodStats, error) {
  108. // Gets node root filesystem information, which will be used to populate
  109. // the available and capacity bytes/inodes in container stats.
  110. rootFsInfo, err := p.cadvisor.RootFsInfo()
  111. if err != nil {
  112. return nil, fmt.Errorf("failed to get rootFs info: %v", err)
  113. }
  114. containers, err := p.runtimeService.ListContainers(&runtimeapi.ContainerFilter{})
  115. if err != nil {
  116. return nil, fmt.Errorf("failed to list all containers: %v", err)
  117. }
  118. // Creates pod sandbox map.
  119. podSandboxMap := make(map[string]*runtimeapi.PodSandbox)
  120. podSandboxes, err := p.runtimeService.ListPodSandbox(&runtimeapi.PodSandboxFilter{})
  121. if err != nil {
  122. return nil, fmt.Errorf("failed to list all pod sandboxes: %v", err)
  123. }
  124. podSandboxes = removeTerminatedPods(podSandboxes)
  125. for _, s := range podSandboxes {
  126. podSandboxMap[s.Id] = s
  127. }
  128. // fsIDtoInfo is a map from filesystem id to its stats. This will be used
  129. // as a cache to avoid querying cAdvisor for the filesystem stats with the
  130. // same filesystem id many times.
  131. fsIDtoInfo := make(map[runtimeapi.FilesystemIdentifier]*cadvisorapiv2.FsInfo)
  132. // sandboxIDToPodStats is a temporary map from sandbox ID to its pod stats.
  133. sandboxIDToPodStats := make(map[string]*statsapi.PodStats)
  134. resp, err := p.runtimeService.ListContainerStats(&runtimeapi.ContainerStatsFilter{})
  135. if err != nil {
  136. return nil, fmt.Errorf("failed to list all container stats: %v", err)
  137. }
  138. containers = removeTerminatedContainers(containers)
  139. // Creates container map.
  140. containerMap := make(map[string]*runtimeapi.Container)
  141. for _, c := range containers {
  142. containerMap[c.Id] = c
  143. }
  144. allInfos, err := getCadvisorContainerInfo(p.cadvisor)
  145. if err != nil {
  146. return nil, fmt.Errorf("failed to fetch cadvisor stats: %v", err)
  147. }
  148. caInfos := getCRICadvisorStats(allInfos)
  149. // get network stats for containers.
  150. // This is only used on Windows. For other platforms, (nil, nil) should be returned.
  151. containerNetworkStats, err := p.listContainerNetworkStats()
  152. if err != nil {
  153. return nil, fmt.Errorf("failed to list container network stats: %v", err)
  154. }
  155. for _, stats := range resp {
  156. containerID := stats.Attributes.Id
  157. container, found := containerMap[containerID]
  158. if !found {
  159. continue
  160. }
  161. podSandboxID := container.PodSandboxId
  162. podSandbox, found := podSandboxMap[podSandboxID]
  163. if !found {
  164. continue
  165. }
  166. // Creates the stats of the pod (if not created yet) which the
  167. // container belongs to.
  168. ps, found := sandboxIDToPodStats[podSandboxID]
  169. if !found {
  170. ps = buildPodStats(podSandbox)
  171. sandboxIDToPodStats[podSandboxID] = ps
  172. }
  173. // Fill available stats for full set of required pod stats
  174. cs := p.makeContainerStats(stats, container, &rootFsInfo, fsIDtoInfo, podSandbox.GetMetadata(), updateCPUNanoCoreUsage)
  175. p.addPodNetworkStats(ps, podSandboxID, caInfos, cs, containerNetworkStats[podSandboxID])
  176. p.addPodCPUMemoryStats(ps, types.UID(podSandbox.Metadata.Uid), allInfos, cs)
  177. // If cadvisor stats is available for the container, use it to populate
  178. // container stats
  179. caStats, caFound := caInfos[containerID]
  180. if !caFound {
  181. klog.V(5).Infof("Unable to find cadvisor stats for %q", containerID)
  182. } else {
  183. p.addCadvisorContainerStats(cs, &caStats)
  184. }
  185. ps.Containers = append(ps.Containers, *cs)
  186. }
  187. // cleanup outdated caches.
  188. p.cleanupOutdatedCaches()
  189. result := make([]statsapi.PodStats, 0, len(sandboxIDToPodStats))
  190. for _, s := range sandboxIDToPodStats {
  191. p.makePodStorageStats(s, &rootFsInfo)
  192. result = append(result, *s)
  193. }
  194. return result, nil
  195. }
  196. // ListPodCPUAndMemoryStats returns the CPU and Memory stats of all the pod-managed containers.
  197. func (p *criStatsProvider) ListPodCPUAndMemoryStats() ([]statsapi.PodStats, error) {
  198. containers, err := p.runtimeService.ListContainers(&runtimeapi.ContainerFilter{})
  199. if err != nil {
  200. return nil, fmt.Errorf("failed to list all containers: %v", err)
  201. }
  202. // Creates pod sandbox map.
  203. podSandboxMap := make(map[string]*runtimeapi.PodSandbox)
  204. podSandboxes, err := p.runtimeService.ListPodSandbox(&runtimeapi.PodSandboxFilter{})
  205. if err != nil {
  206. return nil, fmt.Errorf("failed to list all pod sandboxes: %v", err)
  207. }
  208. podSandboxes = removeTerminatedPods(podSandboxes)
  209. for _, s := range podSandboxes {
  210. podSandboxMap[s.Id] = s
  211. }
  212. // sandboxIDToPodStats is a temporary map from sandbox ID to its pod stats.
  213. sandboxIDToPodStats := make(map[string]*statsapi.PodStats)
  214. resp, err := p.runtimeService.ListContainerStats(&runtimeapi.ContainerStatsFilter{})
  215. if err != nil {
  216. return nil, fmt.Errorf("failed to list all container stats: %v", err)
  217. }
  218. containers = removeTerminatedContainers(containers)
  219. // Creates container map.
  220. containerMap := make(map[string]*runtimeapi.Container)
  221. for _, c := range containers {
  222. containerMap[c.Id] = c
  223. }
  224. allInfos, err := getCadvisorContainerInfo(p.cadvisor)
  225. if err != nil {
  226. return nil, fmt.Errorf("failed to fetch cadvisor stats: %v", err)
  227. }
  228. caInfos := getCRICadvisorStats(allInfos)
  229. for _, stats := range resp {
  230. containerID := stats.Attributes.Id
  231. container, found := containerMap[containerID]
  232. if !found {
  233. continue
  234. }
  235. podSandboxID := container.PodSandboxId
  236. podSandbox, found := podSandboxMap[podSandboxID]
  237. if !found {
  238. continue
  239. }
  240. // Creates the stats of the pod (if not created yet) which the
  241. // container belongs to.
  242. ps, found := sandboxIDToPodStats[podSandboxID]
  243. if !found {
  244. ps = buildPodStats(podSandbox)
  245. sandboxIDToPodStats[podSandboxID] = ps
  246. }
  247. // Fill available CPU and memory stats for full set of required pod stats
  248. cs := p.makeContainerCPUAndMemoryStats(stats, container)
  249. p.addPodCPUMemoryStats(ps, types.UID(podSandbox.Metadata.Uid), allInfos, cs)
  250. // If cadvisor stats is available for the container, use it to populate
  251. // container stats
  252. caStats, caFound := caInfos[containerID]
  253. if !caFound {
  254. klog.V(4).Infof("Unable to find cadvisor stats for %q", containerID)
  255. } else {
  256. p.addCadvisorContainerStats(cs, &caStats)
  257. }
  258. ps.Containers = append(ps.Containers, *cs)
  259. }
  260. // cleanup outdated caches.
  261. p.cleanupOutdatedCaches()
  262. result := make([]statsapi.PodStats, 0, len(sandboxIDToPodStats))
  263. for _, s := range sandboxIDToPodStats {
  264. result = append(result, *s)
  265. }
  266. return result, nil
  267. }
  268. // ImageFsStats returns the stats of the image filesystem.
  269. func (p *criStatsProvider) ImageFsStats() (*statsapi.FsStats, error) {
  270. resp, err := p.imageService.ImageFsInfo()
  271. if err != nil {
  272. return nil, err
  273. }
  274. // CRI may return the stats of multiple image filesystems but we only
  275. // return the first one.
  276. //
  277. // TODO(yguo0905): Support returning stats of multiple image filesystems.
  278. if len(resp) == 0 {
  279. return nil, fmt.Errorf("imageFs information is unavailable")
  280. }
  281. fs := resp[0]
  282. s := &statsapi.FsStats{
  283. Time: metav1.NewTime(time.Unix(0, fs.Timestamp)),
  284. UsedBytes: &fs.UsedBytes.Value,
  285. }
  286. if fs.InodesUsed != nil {
  287. s.InodesUsed = &fs.InodesUsed.Value
  288. }
  289. imageFsInfo := p.getFsInfo(fs.GetFsId())
  290. if imageFsInfo != nil {
  291. // The image filesystem id is unknown to the local node or there's
  292. // an error on retrieving the stats. In these cases, we omit those
  293. // stats and return the best-effort partial result. See
  294. // https://github.com/kubernetes/heapster/issues/1793.
  295. s.AvailableBytes = &imageFsInfo.Available
  296. s.CapacityBytes = &imageFsInfo.Capacity
  297. s.InodesFree = imageFsInfo.InodesFree
  298. s.Inodes = imageFsInfo.Inodes
  299. }
  300. return s, nil
  301. }
  302. // ImageFsDevice returns name of the device where the image filesystem locates,
  303. // e.g. /dev/sda1.
  304. func (p *criStatsProvider) ImageFsDevice() (string, error) {
  305. resp, err := p.imageService.ImageFsInfo()
  306. if err != nil {
  307. return "", err
  308. }
  309. for _, fs := range resp {
  310. fsInfo := p.getFsInfo(fs.GetFsId())
  311. if fsInfo != nil {
  312. return fsInfo.Device, nil
  313. }
  314. }
  315. return "", errors.New("imagefs device is not found")
  316. }
  317. // getFsInfo returns the information of the filesystem with the specified
  318. // fsID. If any error occurs, this function logs the error and returns
  319. // nil.
  320. func (p *criStatsProvider) getFsInfo(fsID *runtimeapi.FilesystemIdentifier) *cadvisorapiv2.FsInfo {
  321. if fsID == nil {
  322. klog.V(2).Infof("Failed to get filesystem info: fsID is nil.")
  323. return nil
  324. }
  325. mountpoint := fsID.GetMountpoint()
  326. fsInfo, err := p.cadvisor.GetDirFsInfo(mountpoint)
  327. if err != nil {
  328. msg := fmt.Sprintf("Failed to get the info of the filesystem with mountpoint %q: %v.", mountpoint, err)
  329. if err == cadvisorfs.ErrNoSuchDevice {
  330. klog.V(2).Info(msg)
  331. } else {
  332. klog.Error(msg)
  333. }
  334. return nil
  335. }
  336. return &fsInfo
  337. }
  338. // buildPodStats returns a PodStats that identifies the Pod managing cinfo
  339. func buildPodStats(podSandbox *runtimeapi.PodSandbox) *statsapi.PodStats {
  340. return &statsapi.PodStats{
  341. PodRef: statsapi.PodReference{
  342. Name: podSandbox.Metadata.Name,
  343. UID: podSandbox.Metadata.Uid,
  344. Namespace: podSandbox.Metadata.Namespace,
  345. },
  346. // The StartTime in the summary API is the pod creation time.
  347. StartTime: metav1.NewTime(time.Unix(0, podSandbox.CreatedAt)),
  348. }
  349. }
  350. func (p *criStatsProvider) makePodStorageStats(s *statsapi.PodStats, rootFsInfo *cadvisorapiv2.FsInfo) {
  351. podNs := s.PodRef.Namespace
  352. podName := s.PodRef.Name
  353. podUID := types.UID(s.PodRef.UID)
  354. vstats, found := p.resourceAnalyzer.GetPodVolumeStats(podUID)
  355. if !found {
  356. return
  357. }
  358. podLogDir := kuberuntime.BuildPodLogsDirectory(podNs, podName, podUID)
  359. logStats, err := p.getPodLogStats(podLogDir, rootFsInfo)
  360. if err != nil {
  361. klog.Errorf("Unable to fetch pod log stats for path %s: %v ", podLogDir, err)
  362. // If people do in-place upgrade, there might be pods still using
  363. // the old log path. For those pods, no pod log stats is returned.
  364. // We should continue generating other stats in that case.
  365. // calcEphemeralStorage tolerants logStats == nil.
  366. }
  367. ephemeralStats := make([]statsapi.VolumeStats, len(vstats.EphemeralVolumes))
  368. copy(ephemeralStats, vstats.EphemeralVolumes)
  369. s.VolumeStats = append(vstats.EphemeralVolumes, vstats.PersistentVolumes...)
  370. s.EphemeralStorage = calcEphemeralStorage(s.Containers, ephemeralStats, rootFsInfo, logStats, true)
  371. }
  372. func (p *criStatsProvider) addPodNetworkStats(
  373. ps *statsapi.PodStats,
  374. podSandboxID string,
  375. caInfos map[string]cadvisorapiv2.ContainerInfo,
  376. cs *statsapi.ContainerStats,
  377. netStats *statsapi.NetworkStats,
  378. ) {
  379. caPodSandbox, found := caInfos[podSandboxID]
  380. // try get network stats from cadvisor first.
  381. if found {
  382. networkStats := cadvisorInfoToNetworkStats(ps.PodRef.Name, &caPodSandbox)
  383. if networkStats != nil {
  384. ps.Network = networkStats
  385. return
  386. }
  387. }
  388. // Not found from cadvisor, get from netStats.
  389. if netStats != nil {
  390. ps.Network = netStats
  391. return
  392. }
  393. // TODO: sum Pod network stats from container stats.
  394. klog.V(4).Infof("Unable to find network stats for sandbox %q", podSandboxID)
  395. }
  396. func (p *criStatsProvider) addPodCPUMemoryStats(
  397. ps *statsapi.PodStats,
  398. podUID types.UID,
  399. allInfos map[string]cadvisorapiv2.ContainerInfo,
  400. cs *statsapi.ContainerStats,
  401. ) {
  402. // try get cpu and memory stats from cadvisor first.
  403. podCgroupInfo := getCadvisorPodInfoFromPodUID(podUID, allInfos)
  404. if podCgroupInfo != nil {
  405. cpu, memory := cadvisorInfoToCPUandMemoryStats(podCgroupInfo)
  406. ps.CPU = cpu
  407. ps.Memory = memory
  408. return
  409. }
  410. // Sum Pod cpu and memory stats from containers stats.
  411. if cs.CPU != nil {
  412. if ps.CPU == nil {
  413. ps.CPU = &statsapi.CPUStats{}
  414. }
  415. ps.CPU.Time = cs.StartTime
  416. usageCoreNanoSeconds := getUint64Value(cs.CPU.UsageCoreNanoSeconds) + getUint64Value(ps.CPU.UsageCoreNanoSeconds)
  417. usageNanoCores := getUint64Value(cs.CPU.UsageNanoCores) + getUint64Value(ps.CPU.UsageNanoCores)
  418. ps.CPU.UsageCoreNanoSeconds = &usageCoreNanoSeconds
  419. ps.CPU.UsageNanoCores = &usageNanoCores
  420. }
  421. if cs.Memory != nil {
  422. if ps.Memory == nil {
  423. ps.Memory = &statsapi.MemoryStats{}
  424. }
  425. ps.Memory.Time = cs.Memory.Time
  426. availableBytes := getUint64Value(cs.Memory.AvailableBytes) + getUint64Value(ps.Memory.AvailableBytes)
  427. usageBytes := getUint64Value(cs.Memory.UsageBytes) + getUint64Value(ps.Memory.UsageBytes)
  428. workingSetBytes := getUint64Value(cs.Memory.WorkingSetBytes) + getUint64Value(ps.Memory.WorkingSetBytes)
  429. rSSBytes := getUint64Value(cs.Memory.RSSBytes) + getUint64Value(ps.Memory.RSSBytes)
  430. pageFaults := getUint64Value(cs.Memory.PageFaults) + getUint64Value(ps.Memory.PageFaults)
  431. majorPageFaults := getUint64Value(cs.Memory.MajorPageFaults) + getUint64Value(ps.Memory.MajorPageFaults)
  432. ps.Memory.AvailableBytes = &availableBytes
  433. ps.Memory.UsageBytes = &usageBytes
  434. ps.Memory.WorkingSetBytes = &workingSetBytes
  435. ps.Memory.RSSBytes = &rSSBytes
  436. ps.Memory.PageFaults = &pageFaults
  437. ps.Memory.MajorPageFaults = &majorPageFaults
  438. }
  439. }
  440. func (p *criStatsProvider) makeContainerStats(
  441. stats *runtimeapi.ContainerStats,
  442. container *runtimeapi.Container,
  443. rootFsInfo *cadvisorapiv2.FsInfo,
  444. fsIDtoInfo map[runtimeapi.FilesystemIdentifier]*cadvisorapiv2.FsInfo,
  445. meta *runtimeapi.PodSandboxMetadata,
  446. updateCPUNanoCoreUsage bool,
  447. ) *statsapi.ContainerStats {
  448. result := &statsapi.ContainerStats{
  449. Name: stats.Attributes.Metadata.Name,
  450. // The StartTime in the summary API is the container creation time.
  451. StartTime: metav1.NewTime(time.Unix(0, container.CreatedAt)),
  452. CPU: &statsapi.CPUStats{},
  453. Memory: &statsapi.MemoryStats{},
  454. Rootfs: &statsapi.FsStats{},
  455. // UserDefinedMetrics is not supported by CRI.
  456. }
  457. if stats.Cpu != nil {
  458. result.CPU.Time = metav1.NewTime(time.Unix(0, stats.Cpu.Timestamp))
  459. if stats.Cpu.UsageCoreNanoSeconds != nil {
  460. result.CPU.UsageCoreNanoSeconds = &stats.Cpu.UsageCoreNanoSeconds.Value
  461. }
  462. var usageNanoCores *uint64
  463. if updateCPUNanoCoreUsage {
  464. usageNanoCores = p.getAndUpdateContainerUsageNanoCores(stats)
  465. } else {
  466. usageNanoCores = p.getContainerUsageNanoCores(stats)
  467. }
  468. if usageNanoCores != nil {
  469. result.CPU.UsageNanoCores = usageNanoCores
  470. }
  471. } else {
  472. result.CPU.Time = metav1.NewTime(time.Unix(0, time.Now().UnixNano()))
  473. result.CPU.UsageCoreNanoSeconds = uint64Ptr(0)
  474. result.CPU.UsageNanoCores = uint64Ptr(0)
  475. }
  476. if stats.Memory != nil {
  477. result.Memory.Time = metav1.NewTime(time.Unix(0, stats.Memory.Timestamp))
  478. if stats.Memory.WorkingSetBytes != nil {
  479. result.Memory.WorkingSetBytes = &stats.Memory.WorkingSetBytes.Value
  480. }
  481. } else {
  482. result.Memory.Time = metav1.NewTime(time.Unix(0, time.Now().UnixNano()))
  483. result.Memory.WorkingSetBytes = uint64Ptr(0)
  484. }
  485. if stats.WritableLayer != nil {
  486. result.Rootfs.Time = metav1.NewTime(time.Unix(0, stats.WritableLayer.Timestamp))
  487. if stats.WritableLayer.UsedBytes != nil {
  488. result.Rootfs.UsedBytes = &stats.WritableLayer.UsedBytes.Value
  489. }
  490. if stats.WritableLayer.InodesUsed != nil {
  491. result.Rootfs.InodesUsed = &stats.WritableLayer.InodesUsed.Value
  492. }
  493. }
  494. fsID := stats.GetWritableLayer().GetFsId()
  495. if fsID != nil {
  496. imageFsInfo, found := fsIDtoInfo[*fsID]
  497. if !found {
  498. imageFsInfo = p.getFsInfo(fsID)
  499. fsIDtoInfo[*fsID] = imageFsInfo
  500. }
  501. if imageFsInfo != nil {
  502. // The image filesystem id is unknown to the local node or there's
  503. // an error on retrieving the stats. In these cases, we omit those stats
  504. // and return the best-effort partial result. See
  505. // https://github.com/kubernetes/heapster/issues/1793.
  506. result.Rootfs.AvailableBytes = &imageFsInfo.Available
  507. result.Rootfs.CapacityBytes = &imageFsInfo.Capacity
  508. result.Rootfs.InodesFree = imageFsInfo.InodesFree
  509. result.Rootfs.Inodes = imageFsInfo.Inodes
  510. }
  511. }
  512. // NOTE: This doesn't support the old pod log path, `/var/log/pods/UID`. For containers
  513. // using old log path, empty log stats are returned. This is fine, because we don't
  514. // officially support in-place upgrade anyway.
  515. var (
  516. containerLogPath = kuberuntime.BuildContainerLogsDirectory(meta.GetNamespace(),
  517. meta.GetName(), types.UID(meta.GetUid()), container.GetMetadata().GetName())
  518. err error
  519. )
  520. result.Logs, err = p.getPathFsStats(containerLogPath, rootFsInfo)
  521. if err != nil {
  522. klog.Errorf("Unable to fetch container log stats for path %s: %v ", containerLogPath, err)
  523. }
  524. return result
  525. }
  526. func (p *criStatsProvider) makeContainerCPUAndMemoryStats(
  527. stats *runtimeapi.ContainerStats,
  528. container *runtimeapi.Container,
  529. ) *statsapi.ContainerStats {
  530. result := &statsapi.ContainerStats{
  531. Name: stats.Attributes.Metadata.Name,
  532. // The StartTime in the summary API is the container creation time.
  533. StartTime: metav1.NewTime(time.Unix(0, container.CreatedAt)),
  534. CPU: &statsapi.CPUStats{},
  535. Memory: &statsapi.MemoryStats{},
  536. // UserDefinedMetrics is not supported by CRI.
  537. }
  538. if stats.Cpu != nil {
  539. result.CPU.Time = metav1.NewTime(time.Unix(0, stats.Cpu.Timestamp))
  540. if stats.Cpu.UsageCoreNanoSeconds != nil {
  541. result.CPU.UsageCoreNanoSeconds = &stats.Cpu.UsageCoreNanoSeconds.Value
  542. }
  543. usageNanoCores := p.getContainerUsageNanoCores(stats)
  544. if usageNanoCores != nil {
  545. result.CPU.UsageNanoCores = usageNanoCores
  546. }
  547. } else {
  548. result.CPU.Time = metav1.NewTime(time.Unix(0, time.Now().UnixNano()))
  549. result.CPU.UsageCoreNanoSeconds = uint64Ptr(0)
  550. result.CPU.UsageNanoCores = uint64Ptr(0)
  551. }
  552. if stats.Memory != nil {
  553. result.Memory.Time = metav1.NewTime(time.Unix(0, stats.Memory.Timestamp))
  554. if stats.Memory.WorkingSetBytes != nil {
  555. result.Memory.WorkingSetBytes = &stats.Memory.WorkingSetBytes.Value
  556. }
  557. } else {
  558. result.Memory.Time = metav1.NewTime(time.Unix(0, time.Now().UnixNano()))
  559. result.Memory.WorkingSetBytes = uint64Ptr(0)
  560. }
  561. return result
  562. }
  563. // getContainerUsageNanoCores gets the cached usageNanoCores.
  564. func (p *criStatsProvider) getContainerUsageNanoCores(stats *runtimeapi.ContainerStats) *uint64 {
  565. if stats == nil || stats.Attributes == nil {
  566. return nil
  567. }
  568. p.mutex.RLock()
  569. defer p.mutex.RUnlock()
  570. cached, ok := p.cpuUsageCache[stats.Attributes.Id]
  571. if !ok || cached.usageNanoCores == nil {
  572. return nil
  573. }
  574. // return a copy of the usage
  575. latestUsage := *cached.usageNanoCores
  576. return &latestUsage
  577. }
  578. // getContainerUsageNanoCores computes usageNanoCores based on the given and
  579. // the cached usageCoreNanoSeconds, updates the cache with the computed
  580. // usageNanoCores, and returns the usageNanoCores.
  581. func (p *criStatsProvider) getAndUpdateContainerUsageNanoCores(stats *runtimeapi.ContainerStats) *uint64 {
  582. if stats == nil || stats.Attributes == nil || stats.Cpu == nil || stats.Cpu.UsageCoreNanoSeconds == nil {
  583. return nil
  584. }
  585. id := stats.Attributes.Id
  586. usage, err := func() (*uint64, error) {
  587. p.mutex.Lock()
  588. defer p.mutex.Unlock()
  589. cached, ok := p.cpuUsageCache[id]
  590. if !ok || cached.stats.UsageCoreNanoSeconds == nil || stats.Cpu.UsageCoreNanoSeconds.Value < cached.stats.UsageCoreNanoSeconds.Value {
  591. // Cannot compute the usage now, but update the cached stats anyway
  592. p.cpuUsageCache[id] = &cpuUsageRecord{stats: stats.Cpu, usageNanoCores: nil}
  593. return nil, nil
  594. }
  595. newStats := stats.Cpu
  596. cachedStats := cached.stats
  597. nanoSeconds := newStats.Timestamp - cachedStats.Timestamp
  598. if nanoSeconds <= 0 {
  599. return nil, fmt.Errorf("zero or negative interval (%v - %v)", newStats.Timestamp, cachedStats.Timestamp)
  600. }
  601. usageNanoCores := uint64(float64(newStats.UsageCoreNanoSeconds.Value-cachedStats.UsageCoreNanoSeconds.Value) /
  602. float64(nanoSeconds) * float64(time.Second/time.Nanosecond))
  603. // Update cache with new value.
  604. usageToUpdate := usageNanoCores
  605. p.cpuUsageCache[id] = &cpuUsageRecord{stats: newStats, usageNanoCores: &usageToUpdate}
  606. return &usageNanoCores, nil
  607. }()
  608. if err != nil {
  609. // This should not happen. Log now to raise visibility
  610. klog.Errorf("failed updating cpu usage nano core: %v", err)
  611. }
  612. return usage
  613. }
  614. func (p *criStatsProvider) cleanupOutdatedCaches() {
  615. p.mutex.Lock()
  616. defer p.mutex.Unlock()
  617. for k, v := range p.cpuUsageCache {
  618. if v == nil {
  619. delete(p.cpuUsageCache, k)
  620. }
  621. if time.Since(time.Unix(0, v.stats.Timestamp)) > defaultCachePeriod {
  622. delete(p.cpuUsageCache, k)
  623. }
  624. }
  625. }
  626. // removeTerminatedPods returns pods with terminated ones removed.
  627. // It only removes a terminated pod when there is a running instance
  628. // of the pod with the same name and namespace.
  629. // This is needed because:
  630. // 1) PodSandbox may be recreated;
  631. // 2) Pod may be recreated with the same name and namespace.
  632. func removeTerminatedPods(pods []*runtimeapi.PodSandbox) []*runtimeapi.PodSandbox {
  633. podMap := make(map[statsapi.PodReference][]*runtimeapi.PodSandbox)
  634. // Sort order by create time
  635. sort.Slice(pods, func(i, j int) bool {
  636. return pods[i].CreatedAt < pods[j].CreatedAt
  637. })
  638. for _, pod := range pods {
  639. refID := statsapi.PodReference{
  640. Name: pod.GetMetadata().GetName(),
  641. Namespace: pod.GetMetadata().GetNamespace(),
  642. // UID is intentionally left empty.
  643. }
  644. podMap[refID] = append(podMap[refID], pod)
  645. }
  646. result := make([]*runtimeapi.PodSandbox, 0)
  647. for _, refs := range podMap {
  648. if len(refs) == 1 {
  649. result = append(result, refs[0])
  650. continue
  651. }
  652. found := false
  653. for i := 0; i < len(refs); i++ {
  654. if refs[i].State == runtimeapi.PodSandboxState_SANDBOX_READY {
  655. found = true
  656. result = append(result, refs[i])
  657. }
  658. }
  659. if !found {
  660. result = append(result, refs[len(refs)-1])
  661. }
  662. }
  663. return result
  664. }
  665. // removeTerminatedContainers removes all terminated containers since they should
  666. // not be used for usage calculations.
  667. func removeTerminatedContainers(containers []*runtimeapi.Container) []*runtimeapi.Container {
  668. containerMap := make(map[containerID][]*runtimeapi.Container)
  669. // Sort order by create time
  670. sort.Slice(containers, func(i, j int) bool {
  671. return containers[i].CreatedAt < containers[j].CreatedAt
  672. })
  673. for _, container := range containers {
  674. refID := containerID{
  675. podRef: buildPodRef(container.Labels),
  676. containerName: kubetypes.GetContainerName(container.Labels),
  677. }
  678. containerMap[refID] = append(containerMap[refID], container)
  679. }
  680. result := make([]*runtimeapi.Container, 0)
  681. for _, refs := range containerMap {
  682. for i := 0; i < len(refs); i++ {
  683. if refs[i].State == runtimeapi.ContainerState_CONTAINER_RUNNING {
  684. result = append(result, refs[i])
  685. }
  686. }
  687. }
  688. return result
  689. }
  690. func (p *criStatsProvider) addCadvisorContainerStats(
  691. cs *statsapi.ContainerStats,
  692. caPodStats *cadvisorapiv2.ContainerInfo,
  693. ) {
  694. if caPodStats.Spec.HasCustomMetrics {
  695. cs.UserDefinedMetrics = cadvisorInfoToUserDefinedMetrics(caPodStats)
  696. }
  697. cpu, memory := cadvisorInfoToCPUandMemoryStats(caPodStats)
  698. if cpu != nil {
  699. cs.CPU = cpu
  700. }
  701. if memory != nil {
  702. cs.Memory = memory
  703. }
  704. }
  705. func getCRICadvisorStats(infos map[string]cadvisorapiv2.ContainerInfo) map[string]cadvisorapiv2.ContainerInfo {
  706. stats := make(map[string]cadvisorapiv2.ContainerInfo)
  707. infos = removeTerminatedContainerInfo(infos)
  708. for key, info := range infos {
  709. // On systemd using devicemapper each mount into the container has an
  710. // associated cgroup. We ignore them to ensure we do not get duplicate
  711. // entries in our summary. For details on .mount units:
  712. // http://man7.org/linux/man-pages/man5/systemd.mount.5.html
  713. if strings.HasSuffix(key, ".mount") {
  714. continue
  715. }
  716. // Build the Pod key if this container is managed by a Pod
  717. if !isPodManagedContainer(&info) {
  718. continue
  719. }
  720. stats[path.Base(key)] = info
  721. }
  722. return stats
  723. }
  724. func (p *criStatsProvider) getPathFsStats(path string, rootFsInfo *cadvisorapiv2.FsInfo) (*statsapi.FsStats, error) {
  725. m := p.logMetricsService.createLogMetricsProvider(path)
  726. logMetrics, err := m.GetMetrics()
  727. if err != nil {
  728. return nil, err
  729. }
  730. result := &statsapi.FsStats{
  731. Time: metav1.NewTime(rootFsInfo.Timestamp),
  732. AvailableBytes: &rootFsInfo.Available,
  733. CapacityBytes: &rootFsInfo.Capacity,
  734. InodesFree: rootFsInfo.InodesFree,
  735. Inodes: rootFsInfo.Inodes,
  736. }
  737. usedbytes := uint64(logMetrics.Used.Value())
  738. result.UsedBytes = &usedbytes
  739. inodesUsed := uint64(logMetrics.InodesUsed.Value())
  740. result.InodesUsed = &inodesUsed
  741. result.Time = maxUpdateTime(&result.Time, &logMetrics.Time)
  742. return result, nil
  743. }
  744. // getPodLogStats gets stats for logs under the pod log directory. Container logs usually exist
  745. // under the container log directory. However, for some container runtimes, e.g. kata, gvisor,
  746. // they may want to keep some pod level logs, in that case they can put those logs directly under
  747. // the pod log directory. And kubelet will take those logs into account as part of pod ephemeral
  748. // storage.
  749. func (p *criStatsProvider) getPodLogStats(path string, rootFsInfo *cadvisorapiv2.FsInfo) (*statsapi.FsStats, error) {
  750. files, err := p.osInterface.ReadDir(path)
  751. if err != nil {
  752. return nil, err
  753. }
  754. result := &statsapi.FsStats{
  755. Time: metav1.NewTime(rootFsInfo.Timestamp),
  756. AvailableBytes: &rootFsInfo.Available,
  757. CapacityBytes: &rootFsInfo.Capacity,
  758. InodesFree: rootFsInfo.InodesFree,
  759. Inodes: rootFsInfo.Inodes,
  760. }
  761. for _, f := range files {
  762. if f.IsDir() {
  763. continue
  764. }
  765. // Only include *files* under pod log directory.
  766. fpath := filepath.Join(path, f.Name())
  767. fstats, err := p.getPathFsStats(fpath, rootFsInfo)
  768. if err != nil {
  769. return nil, fmt.Errorf("failed to get fsstats for %q: %v", fpath, err)
  770. }
  771. result.UsedBytes = addUsage(result.UsedBytes, fstats.UsedBytes)
  772. result.InodesUsed = addUsage(result.InodesUsed, fstats.InodesUsed)
  773. result.Time = maxUpdateTime(&result.Time, &fstats.Time)
  774. }
  775. return result, nil
  776. }