cri_stats_provider.go 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864
  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. for _, fs := range resp {
  279. s := &statsapi.FsStats{
  280. Time: metav1.NewTime(time.Unix(0, fs.Timestamp)),
  281. UsedBytes: &fs.UsedBytes.Value,
  282. }
  283. if fs.InodesUsed != nil {
  284. s.InodesUsed = &fs.InodesUsed.Value
  285. }
  286. imageFsInfo := p.getFsInfo(fs.GetFsId())
  287. if imageFsInfo != nil {
  288. // The image filesystem id is unknown to the local node or there's
  289. // an error on retrieving the stats. In these cases, we omit those
  290. // stats and return the best-effort partial result. See
  291. // https://github.com/kubernetes/heapster/issues/1793.
  292. s.AvailableBytes = &imageFsInfo.Available
  293. s.CapacityBytes = &imageFsInfo.Capacity
  294. s.InodesFree = imageFsInfo.InodesFree
  295. s.Inodes = imageFsInfo.Inodes
  296. }
  297. return s, nil
  298. }
  299. return nil, fmt.Errorf("imageFs information is unavailable")
  300. }
  301. // ImageFsDevice returns name of the device where the image filesystem locates,
  302. // e.g. /dev/sda1.
  303. func (p *criStatsProvider) ImageFsDevice() (string, error) {
  304. resp, err := p.imageService.ImageFsInfo()
  305. if err != nil {
  306. return "", err
  307. }
  308. for _, fs := range resp {
  309. fsInfo := p.getFsInfo(fs.GetFsId())
  310. if fsInfo != nil {
  311. return fsInfo.Device, nil
  312. }
  313. }
  314. return "", errors.New("imagefs device is not found")
  315. }
  316. // getFsInfo returns the information of the filesystem with the specified
  317. // fsID. If any error occurs, this function logs the error and returns
  318. // nil.
  319. func (p *criStatsProvider) getFsInfo(fsID *runtimeapi.FilesystemIdentifier) *cadvisorapiv2.FsInfo {
  320. if fsID == nil {
  321. klog.V(2).Infof("Failed to get filesystem info: fsID is nil.")
  322. return nil
  323. }
  324. mountpoint := fsID.GetMountpoint()
  325. fsInfo, err := p.cadvisor.GetDirFsInfo(mountpoint)
  326. if err != nil {
  327. msg := fmt.Sprintf("Failed to get the info of the filesystem with mountpoint %q: %v.", mountpoint, err)
  328. if err == cadvisorfs.ErrNoSuchDevice {
  329. klog.V(2).Info(msg)
  330. } else {
  331. klog.Error(msg)
  332. }
  333. return nil
  334. }
  335. return &fsInfo
  336. }
  337. // buildPodStats returns a PodStats that identifies the Pod managing cinfo
  338. func buildPodStats(podSandbox *runtimeapi.PodSandbox) *statsapi.PodStats {
  339. return &statsapi.PodStats{
  340. PodRef: statsapi.PodReference{
  341. Name: podSandbox.Metadata.Name,
  342. UID: podSandbox.Metadata.Uid,
  343. Namespace: podSandbox.Metadata.Namespace,
  344. },
  345. // The StartTime in the summary API is the pod creation time.
  346. StartTime: metav1.NewTime(time.Unix(0, podSandbox.CreatedAt)),
  347. }
  348. }
  349. func (p *criStatsProvider) makePodStorageStats(s *statsapi.PodStats, rootFsInfo *cadvisorapiv2.FsInfo) {
  350. podNs := s.PodRef.Namespace
  351. podName := s.PodRef.Name
  352. podUID := types.UID(s.PodRef.UID)
  353. vstats, found := p.resourceAnalyzer.GetPodVolumeStats(podUID)
  354. if !found {
  355. return
  356. }
  357. podLogDir := kuberuntime.BuildPodLogsDirectory(podNs, podName, podUID)
  358. logStats, err := p.getPodLogStats(podLogDir, rootFsInfo)
  359. if err != nil {
  360. klog.Errorf("Unable to fetch pod log stats for path %s: %v ", podLogDir, err)
  361. // If people do in-place upgrade, there might be pods still using
  362. // the old log path. For those pods, no pod log stats is returned.
  363. // We should continue generating other stats in that case.
  364. // calcEphemeralStorage tolerants logStats == nil.
  365. }
  366. ephemeralStats := make([]statsapi.VolumeStats, len(vstats.EphemeralVolumes))
  367. copy(ephemeralStats, vstats.EphemeralVolumes)
  368. s.VolumeStats = append(vstats.EphemeralVolumes, vstats.PersistentVolumes...)
  369. s.EphemeralStorage = calcEphemeralStorage(s.Containers, ephemeralStats, rootFsInfo, logStats, true)
  370. }
  371. func (p *criStatsProvider) addPodNetworkStats(
  372. ps *statsapi.PodStats,
  373. podSandboxID string,
  374. caInfos map[string]cadvisorapiv2.ContainerInfo,
  375. cs *statsapi.ContainerStats,
  376. netStats *statsapi.NetworkStats,
  377. ) {
  378. caPodSandbox, found := caInfos[podSandboxID]
  379. // try get network stats from cadvisor first.
  380. if found {
  381. networkStats := cadvisorInfoToNetworkStats(ps.PodRef.Name, &caPodSandbox)
  382. if networkStats != nil {
  383. ps.Network = networkStats
  384. return
  385. }
  386. }
  387. // Not found from cadvisor, get from netStats.
  388. if netStats != nil {
  389. ps.Network = netStats
  390. return
  391. }
  392. // TODO: sum Pod network stats from container stats.
  393. klog.V(4).Infof("Unable to find network stats for sandbox %q", podSandboxID)
  394. }
  395. func (p *criStatsProvider) addPodCPUMemoryStats(
  396. ps *statsapi.PodStats,
  397. podUID types.UID,
  398. allInfos map[string]cadvisorapiv2.ContainerInfo,
  399. cs *statsapi.ContainerStats,
  400. ) {
  401. // try get cpu and memory stats from cadvisor first.
  402. podCgroupInfo := getCadvisorPodInfoFromPodUID(podUID, allInfos)
  403. if podCgroupInfo != nil {
  404. cpu, memory := cadvisorInfoToCPUandMemoryStats(podCgroupInfo)
  405. ps.CPU = cpu
  406. ps.Memory = memory
  407. return
  408. }
  409. // Sum Pod cpu and memory stats from containers stats.
  410. if cs.CPU != nil {
  411. if ps.CPU == nil {
  412. ps.CPU = &statsapi.CPUStats{}
  413. }
  414. ps.CPU.Time = cs.StartTime
  415. usageCoreNanoSeconds := getUint64Value(cs.CPU.UsageCoreNanoSeconds) + getUint64Value(ps.CPU.UsageCoreNanoSeconds)
  416. usageNanoCores := getUint64Value(cs.CPU.UsageNanoCores) + getUint64Value(ps.CPU.UsageNanoCores)
  417. ps.CPU.UsageCoreNanoSeconds = &usageCoreNanoSeconds
  418. ps.CPU.UsageNanoCores = &usageNanoCores
  419. }
  420. if cs.Memory != nil {
  421. if ps.Memory == nil {
  422. ps.Memory = &statsapi.MemoryStats{}
  423. }
  424. ps.Memory.Time = cs.Memory.Time
  425. availableBytes := getUint64Value(cs.Memory.AvailableBytes) + getUint64Value(ps.Memory.AvailableBytes)
  426. usageBytes := getUint64Value(cs.Memory.UsageBytes) + getUint64Value(ps.Memory.UsageBytes)
  427. workingSetBytes := getUint64Value(cs.Memory.WorkingSetBytes) + getUint64Value(ps.Memory.WorkingSetBytes)
  428. rSSBytes := getUint64Value(cs.Memory.RSSBytes) + getUint64Value(ps.Memory.RSSBytes)
  429. pageFaults := getUint64Value(cs.Memory.PageFaults) + getUint64Value(ps.Memory.PageFaults)
  430. majorPageFaults := getUint64Value(cs.Memory.MajorPageFaults) + getUint64Value(ps.Memory.MajorPageFaults)
  431. ps.Memory.AvailableBytes = &availableBytes
  432. ps.Memory.UsageBytes = &usageBytes
  433. ps.Memory.WorkingSetBytes = &workingSetBytes
  434. ps.Memory.RSSBytes = &rSSBytes
  435. ps.Memory.PageFaults = &pageFaults
  436. ps.Memory.MajorPageFaults = &majorPageFaults
  437. }
  438. }
  439. func (p *criStatsProvider) makeContainerStats(
  440. stats *runtimeapi.ContainerStats,
  441. container *runtimeapi.Container,
  442. rootFsInfo *cadvisorapiv2.FsInfo,
  443. fsIDtoInfo map[runtimeapi.FilesystemIdentifier]*cadvisorapiv2.FsInfo,
  444. meta *runtimeapi.PodSandboxMetadata,
  445. updateCPUNanoCoreUsage bool,
  446. ) *statsapi.ContainerStats {
  447. result := &statsapi.ContainerStats{
  448. Name: stats.Attributes.Metadata.Name,
  449. // The StartTime in the summary API is the container creation time.
  450. StartTime: metav1.NewTime(time.Unix(0, container.CreatedAt)),
  451. CPU: &statsapi.CPUStats{},
  452. Memory: &statsapi.MemoryStats{},
  453. Rootfs: &statsapi.FsStats{},
  454. // UserDefinedMetrics is not supported by CRI.
  455. }
  456. if stats.Cpu != nil {
  457. result.CPU.Time = metav1.NewTime(time.Unix(0, stats.Cpu.Timestamp))
  458. if stats.Cpu.UsageCoreNanoSeconds != nil {
  459. result.CPU.UsageCoreNanoSeconds = &stats.Cpu.UsageCoreNanoSeconds.Value
  460. }
  461. var usageNanoCores *uint64
  462. if updateCPUNanoCoreUsage {
  463. usageNanoCores = p.getAndUpdateContainerUsageNanoCores(stats)
  464. } else {
  465. usageNanoCores = p.getContainerUsageNanoCores(stats)
  466. }
  467. if usageNanoCores != nil {
  468. result.CPU.UsageNanoCores = usageNanoCores
  469. }
  470. } else {
  471. result.CPU.Time = metav1.NewTime(time.Unix(0, time.Now().UnixNano()))
  472. result.CPU.UsageCoreNanoSeconds = uint64Ptr(0)
  473. result.CPU.UsageNanoCores = uint64Ptr(0)
  474. }
  475. if stats.Memory != nil {
  476. result.Memory.Time = metav1.NewTime(time.Unix(0, stats.Memory.Timestamp))
  477. if stats.Memory.WorkingSetBytes != nil {
  478. result.Memory.WorkingSetBytes = &stats.Memory.WorkingSetBytes.Value
  479. }
  480. } else {
  481. result.Memory.Time = metav1.NewTime(time.Unix(0, time.Now().UnixNano()))
  482. result.Memory.WorkingSetBytes = uint64Ptr(0)
  483. }
  484. if stats.WritableLayer != nil {
  485. result.Rootfs.Time = metav1.NewTime(time.Unix(0, stats.WritableLayer.Timestamp))
  486. if stats.WritableLayer.UsedBytes != nil {
  487. result.Rootfs.UsedBytes = &stats.WritableLayer.UsedBytes.Value
  488. }
  489. if stats.WritableLayer.InodesUsed != nil {
  490. result.Rootfs.InodesUsed = &stats.WritableLayer.InodesUsed.Value
  491. }
  492. }
  493. fsID := stats.GetWritableLayer().GetFsId()
  494. if fsID != nil {
  495. imageFsInfo, found := fsIDtoInfo[*fsID]
  496. if !found {
  497. imageFsInfo = p.getFsInfo(fsID)
  498. fsIDtoInfo[*fsID] = imageFsInfo
  499. }
  500. if imageFsInfo != nil {
  501. // The image filesystem id is unknown to the local node or there's
  502. // an error on retrieving the stats. In these cases, we omit those stats
  503. // and return the best-effort partial result. See
  504. // https://github.com/kubernetes/heapster/issues/1793.
  505. result.Rootfs.AvailableBytes = &imageFsInfo.Available
  506. result.Rootfs.CapacityBytes = &imageFsInfo.Capacity
  507. result.Rootfs.InodesFree = imageFsInfo.InodesFree
  508. result.Rootfs.Inodes = imageFsInfo.Inodes
  509. }
  510. }
  511. // NOTE: This doesn't support the old pod log path, `/var/log/pods/UID`. For containers
  512. // using old log path, empty log stats are returned. This is fine, because we don't
  513. // officially support in-place upgrade anyway.
  514. var (
  515. containerLogPath = kuberuntime.BuildContainerLogsDirectory(meta.GetNamespace(),
  516. meta.GetName(), types.UID(meta.GetUid()), container.GetMetadata().GetName())
  517. err error
  518. )
  519. result.Logs, err = p.getPathFsStats(containerLogPath, rootFsInfo)
  520. if err != nil {
  521. klog.Errorf("Unable to fetch container log stats for path %s: %v ", containerLogPath, err)
  522. }
  523. return result
  524. }
  525. func (p *criStatsProvider) makeContainerCPUAndMemoryStats(
  526. stats *runtimeapi.ContainerStats,
  527. container *runtimeapi.Container,
  528. ) *statsapi.ContainerStats {
  529. result := &statsapi.ContainerStats{
  530. Name: stats.Attributes.Metadata.Name,
  531. // The StartTime in the summary API is the container creation time.
  532. StartTime: metav1.NewTime(time.Unix(0, container.CreatedAt)),
  533. CPU: &statsapi.CPUStats{},
  534. Memory: &statsapi.MemoryStats{},
  535. // UserDefinedMetrics is not supported by CRI.
  536. }
  537. if stats.Cpu != nil {
  538. result.CPU.Time = metav1.NewTime(time.Unix(0, stats.Cpu.Timestamp))
  539. if stats.Cpu.UsageCoreNanoSeconds != nil {
  540. result.CPU.UsageCoreNanoSeconds = &stats.Cpu.UsageCoreNanoSeconds.Value
  541. }
  542. usageNanoCores := p.getContainerUsageNanoCores(stats)
  543. if usageNanoCores != nil {
  544. result.CPU.UsageNanoCores = usageNanoCores
  545. }
  546. } else {
  547. result.CPU.Time = metav1.NewTime(time.Unix(0, time.Now().UnixNano()))
  548. result.CPU.UsageCoreNanoSeconds = uint64Ptr(0)
  549. result.CPU.UsageNanoCores = uint64Ptr(0)
  550. }
  551. if stats.Memory != nil {
  552. result.Memory.Time = metav1.NewTime(time.Unix(0, stats.Memory.Timestamp))
  553. if stats.Memory.WorkingSetBytes != nil {
  554. result.Memory.WorkingSetBytes = &stats.Memory.WorkingSetBytes.Value
  555. }
  556. } else {
  557. result.Memory.Time = metav1.NewTime(time.Unix(0, time.Now().UnixNano()))
  558. result.Memory.WorkingSetBytes = uint64Ptr(0)
  559. }
  560. return result
  561. }
  562. // getContainerUsageNanoCores gets the cached usageNanoCores.
  563. func (p *criStatsProvider) getContainerUsageNanoCores(stats *runtimeapi.ContainerStats) *uint64 {
  564. if stats == nil || stats.Attributes == nil {
  565. return nil
  566. }
  567. p.mutex.RLock()
  568. defer p.mutex.RUnlock()
  569. cached, ok := p.cpuUsageCache[stats.Attributes.Id]
  570. if !ok || cached.usageNanoCores == nil {
  571. return nil
  572. }
  573. // return a copy of the usage
  574. latestUsage := *cached.usageNanoCores
  575. return &latestUsage
  576. }
  577. // getContainerUsageNanoCores computes usageNanoCores based on the given and
  578. // the cached usageCoreNanoSeconds, updates the cache with the computed
  579. // usageNanoCores, and returns the usageNanoCores.
  580. func (p *criStatsProvider) getAndUpdateContainerUsageNanoCores(stats *runtimeapi.ContainerStats) *uint64 {
  581. if stats == nil || stats.Attributes == nil || stats.Cpu == nil || stats.Cpu.UsageCoreNanoSeconds == nil {
  582. return nil
  583. }
  584. id := stats.Attributes.Id
  585. usage, err := func() (*uint64, error) {
  586. p.mutex.Lock()
  587. defer p.mutex.Unlock()
  588. cached, ok := p.cpuUsageCache[id]
  589. if !ok || cached.stats.UsageCoreNanoSeconds == nil {
  590. // Cannot compute the usage now, but update the cached stats anyway
  591. p.cpuUsageCache[id] = &cpuUsageRecord{stats: stats.Cpu, usageNanoCores: nil}
  592. return nil, nil
  593. }
  594. newStats := stats.Cpu
  595. cachedStats := cached.stats
  596. nanoSeconds := newStats.Timestamp - cachedStats.Timestamp
  597. if nanoSeconds <= 0 {
  598. return nil, fmt.Errorf("zero or negative interval (%v - %v)", newStats.Timestamp, cachedStats.Timestamp)
  599. }
  600. usageNanoCores := (newStats.UsageCoreNanoSeconds.Value - cachedStats.UsageCoreNanoSeconds.Value) * uint64(time.Second/time.Nanosecond) / uint64(nanoSeconds)
  601. // Update cache with new value.
  602. usageToUpdate := usageNanoCores
  603. p.cpuUsageCache[id] = &cpuUsageRecord{stats: newStats, usageNanoCores: &usageToUpdate}
  604. return &usageNanoCores, nil
  605. }()
  606. if err != nil {
  607. // This should not happen. Log now to raise visiblity
  608. klog.Errorf("failed updating cpu usage nano core: %v", err)
  609. }
  610. return usage
  611. }
  612. func (p *criStatsProvider) cleanupOutdatedCaches() {
  613. p.mutex.Lock()
  614. defer p.mutex.Unlock()
  615. for k, v := range p.cpuUsageCache {
  616. if v == nil {
  617. delete(p.cpuUsageCache, k)
  618. }
  619. if time.Since(time.Unix(0, v.stats.Timestamp)) > defaultCachePeriod {
  620. delete(p.cpuUsageCache, k)
  621. }
  622. }
  623. }
  624. // removeTerminatedPods returns pods with terminated ones removed.
  625. // It only removes a terminated pod when there is a running instance
  626. // of the pod with the same name and namespace.
  627. // This is needed because:
  628. // 1) PodSandbox may be recreated;
  629. // 2) Pod may be recreated with the same name and namespace.
  630. func removeTerminatedPods(pods []*runtimeapi.PodSandbox) []*runtimeapi.PodSandbox {
  631. podMap := make(map[statsapi.PodReference][]*runtimeapi.PodSandbox)
  632. // Sort order by create time
  633. sort.Slice(pods, func(i, j int) bool {
  634. return pods[i].CreatedAt < pods[j].CreatedAt
  635. })
  636. for _, pod := range pods {
  637. refID := statsapi.PodReference{
  638. Name: pod.GetMetadata().GetName(),
  639. Namespace: pod.GetMetadata().GetNamespace(),
  640. // UID is intentionally left empty.
  641. }
  642. podMap[refID] = append(podMap[refID], pod)
  643. }
  644. result := make([]*runtimeapi.PodSandbox, 0)
  645. for _, refs := range podMap {
  646. if len(refs) == 1 {
  647. result = append(result, refs[0])
  648. continue
  649. }
  650. found := false
  651. for i := 0; i < len(refs); i++ {
  652. if refs[i].State == runtimeapi.PodSandboxState_SANDBOX_READY {
  653. found = true
  654. result = append(result, refs[i])
  655. }
  656. }
  657. if !found {
  658. result = append(result, refs[len(refs)-1])
  659. }
  660. }
  661. return result
  662. }
  663. // removeTerminatedContainers returns containers with terminated ones.
  664. // It only removes a terminated container when there is a running instance
  665. // of the container.
  666. func removeTerminatedContainers(containers []*runtimeapi.Container) []*runtimeapi.Container {
  667. containerMap := make(map[containerID][]*runtimeapi.Container)
  668. // Sort order by create time
  669. sort.Slice(containers, func(i, j int) bool {
  670. return containers[i].CreatedAt < containers[j].CreatedAt
  671. })
  672. for _, container := range containers {
  673. refID := containerID{
  674. podRef: buildPodRef(container.Labels),
  675. containerName: kubetypes.GetContainerName(container.Labels),
  676. }
  677. containerMap[refID] = append(containerMap[refID], container)
  678. }
  679. result := make([]*runtimeapi.Container, 0)
  680. for _, refs := range containerMap {
  681. if len(refs) == 1 {
  682. result = append(result, refs[0])
  683. continue
  684. }
  685. found := false
  686. for i := 0; i < len(refs); i++ {
  687. if refs[i].State == runtimeapi.ContainerState_CONTAINER_RUNNING {
  688. found = true
  689. result = append(result, refs[i])
  690. }
  691. }
  692. if !found {
  693. result = append(result, refs[len(refs)-1])
  694. }
  695. }
  696. return result
  697. }
  698. func (p *criStatsProvider) addCadvisorContainerStats(
  699. cs *statsapi.ContainerStats,
  700. caPodStats *cadvisorapiv2.ContainerInfo,
  701. ) {
  702. if caPodStats.Spec.HasCustomMetrics {
  703. cs.UserDefinedMetrics = cadvisorInfoToUserDefinedMetrics(caPodStats)
  704. }
  705. cpu, memory := cadvisorInfoToCPUandMemoryStats(caPodStats)
  706. if cpu != nil {
  707. cs.CPU = cpu
  708. }
  709. if memory != nil {
  710. cs.Memory = memory
  711. }
  712. }
  713. func getCRICadvisorStats(infos map[string]cadvisorapiv2.ContainerInfo) map[string]cadvisorapiv2.ContainerInfo {
  714. stats := make(map[string]cadvisorapiv2.ContainerInfo)
  715. infos = removeTerminatedContainerInfo(infos)
  716. for key, info := range infos {
  717. // On systemd using devicemapper each mount into the container has an
  718. // associated cgroup. We ignore them to ensure we do not get duplicate
  719. // entries in our summary. For details on .mount units:
  720. // http://man7.org/linux/man-pages/man5/systemd.mount.5.html
  721. if strings.HasSuffix(key, ".mount") {
  722. continue
  723. }
  724. // Build the Pod key if this container is managed by a Pod
  725. if !isPodManagedContainer(&info) {
  726. continue
  727. }
  728. stats[path.Base(key)] = info
  729. }
  730. return stats
  731. }
  732. func (p *criStatsProvider) getPathFsStats(path string, rootFsInfo *cadvisorapiv2.FsInfo) (*statsapi.FsStats, error) {
  733. m := p.logMetricsService.createLogMetricsProvider(path)
  734. logMetrics, err := m.GetMetrics()
  735. if err != nil {
  736. return nil, err
  737. }
  738. result := &statsapi.FsStats{
  739. Time: metav1.NewTime(rootFsInfo.Timestamp),
  740. AvailableBytes: &rootFsInfo.Available,
  741. CapacityBytes: &rootFsInfo.Capacity,
  742. InodesFree: rootFsInfo.InodesFree,
  743. Inodes: rootFsInfo.Inodes,
  744. }
  745. usedbytes := uint64(logMetrics.Used.Value())
  746. result.UsedBytes = &usedbytes
  747. inodesUsed := uint64(logMetrics.InodesUsed.Value())
  748. result.InodesUsed = &inodesUsed
  749. result.Time = maxUpdateTime(&result.Time, &logMetrics.Time)
  750. return result, nil
  751. }
  752. // getPodLogStats gets stats for logs under the pod log directory. Container logs usually exist
  753. // under the container log directory. However, for some container runtimes, e.g. kata, gvisor,
  754. // they may want to keep some pod level logs, in that case they can put those logs directly under
  755. // the pod log directory. And kubelet will take those logs into account as part of pod ephemeral
  756. // storage.
  757. func (p *criStatsProvider) getPodLogStats(path string, rootFsInfo *cadvisorapiv2.FsInfo) (*statsapi.FsStats, error) {
  758. files, err := p.osInterface.ReadDir(path)
  759. if err != nil {
  760. return nil, err
  761. }
  762. result := &statsapi.FsStats{
  763. Time: metav1.NewTime(rootFsInfo.Timestamp),
  764. AvailableBytes: &rootFsInfo.Available,
  765. CapacityBytes: &rootFsInfo.Capacity,
  766. InodesFree: rootFsInfo.InodesFree,
  767. Inodes: rootFsInfo.Inodes,
  768. }
  769. for _, f := range files {
  770. if f.IsDir() {
  771. continue
  772. }
  773. // Only include *files* under pod log directory.
  774. fpath := filepath.Join(path, f.Name())
  775. fstats, err := p.getPathFsStats(fpath, rootFsInfo)
  776. if err != nil {
  777. return nil, fmt.Errorf("failed to get fsstats for %q: %v", fpath, err)
  778. }
  779. result.UsedBytes = addUsage(result.UsedBytes, fstats.UsedBytes)
  780. result.InodesUsed = addUsage(result.InodesUsed, fstats.InodesUsed)
  781. result.Time = maxUpdateTime(&result.Time, &fstats.Time)
  782. }
  783. return result, nil
  784. }