1
16
17 package stats
18
19 import (
20 "context"
21 "fmt"
22 "path/filepath"
23 "sort"
24 "strings"
25
26 cadvisorapiv2 "github.com/google/cadvisor/info/v2"
27 "k8s.io/klog/v2"
28
29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
30 "k8s.io/apimachinery/pkg/types"
31 utilfeature "k8s.io/apiserver/pkg/util/feature"
32 statsapi "k8s.io/kubelet/pkg/apis/stats/v1alpha1"
33 kubetypes "k8s.io/kubelet/pkg/types"
34 "k8s.io/kubernetes/pkg/features"
35 "k8s.io/kubernetes/pkg/kubelet/cadvisor"
36 "k8s.io/kubernetes/pkg/kubelet/cm"
37 kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
38 "k8s.io/kubernetes/pkg/kubelet/server/stats"
39 "k8s.io/kubernetes/pkg/kubelet/status"
40 )
41
42
43
44
45
46
47 type cadvisorStatsProvider struct {
48
49
50 cadvisor cadvisor.Interface
51
52 resourceAnalyzer stats.ResourceAnalyzer
53
54 imageService kubecontainer.ImageService
55
56 statusProvider status.PodStatusProvider
57
58 hostStatsProvider HostStatsProvider
59 }
60
61
62
63 func newCadvisorStatsProvider(
64 cadvisor cadvisor.Interface,
65 resourceAnalyzer stats.ResourceAnalyzer,
66 imageService kubecontainer.ImageService,
67 statusProvider status.PodStatusProvider,
68 hostStatsProvider HostStatsProvider,
69 ) containerStatsProvider {
70 return &cadvisorStatsProvider{
71 cadvisor: cadvisor,
72 resourceAnalyzer: resourceAnalyzer,
73 imageService: imageService,
74 statusProvider: statusProvider,
75 hostStatsProvider: hostStatsProvider,
76 }
77 }
78
79
80 func (p *cadvisorStatsProvider) ListPodStats(_ context.Context) ([]statsapi.PodStats, error) {
81
82
83
84 rootFsInfo, err := p.cadvisor.RootFsInfo()
85 if err != nil {
86 return nil, fmt.Errorf("failed to get rootFs info: %v", err)
87 }
88 imageFsInfo, err := p.cadvisor.ImagesFsInfo()
89 if err != nil {
90 return nil, fmt.Errorf("failed to get imageFs info: %v", err)
91 }
92 infos, err := getCadvisorContainerInfo(p.cadvisor)
93 if err != nil {
94 return nil, fmt.Errorf("failed to get container info from cadvisor: %v", err)
95 }
96
97 filteredInfos, allInfos := filterTerminatedContainerInfoAndAssembleByPodCgroupKey(infos)
98
99 podToStats := map[statsapi.PodReference]*statsapi.PodStats{}
100 for key, cinfo := range filteredInfos {
101
102
103
104
105 if strings.HasSuffix(key, ".mount") {
106 continue
107 }
108
109 if !isPodManagedContainer(&cinfo) {
110 continue
111 }
112 ref := buildPodRef(cinfo.Spec.Labels)
113
114
115
116 podStats, found := podToStats[ref]
117 if !found {
118 podStats = &statsapi.PodStats{PodRef: ref}
119 podToStats[ref] = podStats
120 }
121
122
123
124 containerName := kubetypes.GetContainerName(cinfo.Spec.Labels)
125 if containerName == kubetypes.PodInfraContainerName {
126
127
128 podStats.Network = cadvisorInfoToNetworkStats(&cinfo)
129 } else {
130 containerStat := cadvisorInfoToContainerStats(containerName, &cinfo, &rootFsInfo, &imageFsInfo)
131
132
133 podUID := types.UID(podStats.PodRef.UID)
134 logs, err := p.hostStatsProvider.getPodContainerLogStats(podStats.PodRef.Namespace, podStats.PodRef.Name, podUID, containerName, &rootFsInfo)
135 if err != nil {
136 klog.ErrorS(err, "Unable to fetch container log stats", "containerName", containerName)
137 } else {
138 containerStat.Logs = logs
139 }
140 podStats.Containers = append(podStats.Containers, *containerStat)
141 }
142 }
143
144
145 result := make([]statsapi.PodStats, 0, len(podToStats))
146 for _, podStats := range podToStats {
147 makePodStorageStats(podStats, &rootFsInfo, p.resourceAnalyzer, p.hostStatsProvider, false)
148
149 podUID := types.UID(podStats.PodRef.UID)
150
151 podInfo := getCadvisorPodInfoFromPodUID(podUID, allInfos)
152 if podInfo != nil {
153 cpu, memory := cadvisorInfoToCPUandMemoryStats(podInfo)
154 podStats.CPU = cpu
155 podStats.Memory = memory
156 podStats.Swap = cadvisorInfoToSwapStats(podInfo)
157 podStats.ProcessStats = cadvisorInfoToProcessStats(podInfo)
158 }
159
160 status, found := p.statusProvider.GetPodStatus(podUID)
161 if found && status.StartTime != nil && !status.StartTime.IsZero() {
162 podStats.StartTime = *status.StartTime
163
164 result = append(result, *podStats)
165 }
166 }
167
168 return result, nil
169 }
170
171
172
173
174
175 func (p *cadvisorStatsProvider) ListPodStatsAndUpdateCPUNanoCoreUsage(ctx context.Context) ([]statsapi.PodStats, error) {
176 return p.ListPodStats(ctx)
177 }
178
179
180 func (p *cadvisorStatsProvider) ListPodCPUAndMemoryStats(_ context.Context) ([]statsapi.PodStats, error) {
181 infos, err := getCadvisorContainerInfo(p.cadvisor)
182 if err != nil {
183 return nil, fmt.Errorf("failed to get container info from cadvisor: %v", err)
184 }
185 filteredInfos, allInfos := filterTerminatedContainerInfoAndAssembleByPodCgroupKey(infos)
186
187 podToStats := map[statsapi.PodReference]*statsapi.PodStats{}
188 for key, cinfo := range filteredInfos {
189
190
191
192
193 if strings.HasSuffix(key, ".mount") {
194 continue
195 }
196
197 if !isPodManagedContainer(&cinfo) {
198 continue
199 }
200 ref := buildPodRef(cinfo.Spec.Labels)
201
202
203
204 podStats, found := podToStats[ref]
205 if !found {
206 podStats = &statsapi.PodStats{PodRef: ref}
207 podToStats[ref] = podStats
208 }
209
210
211
212 containerName := kubetypes.GetContainerName(cinfo.Spec.Labels)
213 if containerName == kubetypes.PodInfraContainerName {
214
215
216 podStats.StartTime = metav1.NewTime(cinfo.Spec.CreationTime)
217 } else {
218 podStats.Containers = append(podStats.Containers, *cadvisorInfoToContainerCPUAndMemoryStats(containerName, &cinfo))
219 }
220 }
221
222
223 result := make([]statsapi.PodStats, 0, len(podToStats))
224 for _, podStats := range podToStats {
225 podUID := types.UID(podStats.PodRef.UID)
226
227 podInfo := getCadvisorPodInfoFromPodUID(podUID, allInfos)
228 if podInfo != nil {
229 cpu, memory := cadvisorInfoToCPUandMemoryStats(podInfo)
230 podStats.CPU = cpu
231 podStats.Memory = memory
232 podStats.Swap = cadvisorInfoToSwapStats(podInfo)
233 }
234 result = append(result, *podStats)
235 }
236
237 return result, nil
238 }
239
240
241 func (p *cadvisorStatsProvider) ImageFsStats(ctx context.Context) (imageFsRet *statsapi.FsStats, containerFsRet *statsapi.FsStats, errCall error) {
242 imageFsInfo, err := p.cadvisor.ImagesFsInfo()
243 if err != nil {
244 return nil, nil, fmt.Errorf("failed to get imageFs info: %v", err)
245 }
246
247 if !utilfeature.DefaultFeatureGate.Enabled(features.KubeletSeparateDiskGC) {
248 imageStats, err := p.imageService.ImageStats(ctx)
249 if err != nil || imageStats == nil {
250 return nil, nil, fmt.Errorf("failed to get image stats: %v", err)
251 }
252
253 var imageFsInodesUsed *uint64
254 if imageFsInfo.Inodes != nil && imageFsInfo.InodesFree != nil {
255 imageFsIU := *imageFsInfo.Inodes - *imageFsInfo.InodesFree
256 imageFsInodesUsed = &imageFsIU
257 }
258
259 imageFs := &statsapi.FsStats{
260 Time: metav1.NewTime(imageFsInfo.Timestamp),
261 AvailableBytes: &imageFsInfo.Available,
262 CapacityBytes: &imageFsInfo.Capacity,
263 UsedBytes: &imageStats.TotalStorageBytes,
264 InodesFree: imageFsInfo.InodesFree,
265 Inodes: imageFsInfo.Inodes,
266 InodesUsed: imageFsInodesUsed,
267 }
268 return imageFs, imageFs, nil
269 }
270 containerFsInfo, err := p.cadvisor.ContainerFsInfo()
271 if err != nil {
272 return nil, nil, fmt.Errorf("failed to get container fs info: %v", err)
273 }
274 imageStats, err := p.imageService.ImageFsInfo(ctx)
275 if err != nil || imageStats == nil {
276 return nil, nil, fmt.Errorf("failed to get image stats: %v", err)
277 }
278 splitFileSystem := false
279 if imageStats.ImageFilesystems[0].FsId.Mountpoint != imageStats.ContainerFilesystems[0].FsId.Mountpoint {
280 klog.InfoS("Detect Split Filesystem", "ImageFilesystems", imageStats.ImageFilesystems[0], "ContainerFilesystems", imageStats.ContainerFilesystems[0])
281 splitFileSystem = true
282 }
283 imageFs := imageStats.ImageFilesystems[0]
284 var imageFsInodesUsed *uint64
285 if imageFsInfo.Inodes != nil && imageFsInfo.InodesFree != nil {
286 imageFsIU := *imageFsInfo.Inodes - *imageFsInfo.InodesFree
287 imageFsInodesUsed = &imageFsIU
288 }
289
290 fsStats := &statsapi.FsStats{
291 Time: metav1.NewTime(imageFsInfo.Timestamp),
292 AvailableBytes: &imageFsInfo.Available,
293 CapacityBytes: &imageFsInfo.Capacity,
294 UsedBytes: &imageFs.UsedBytes.Value,
295 InodesFree: imageFsInfo.InodesFree,
296 Inodes: imageFsInfo.Inodes,
297 InodesUsed: imageFsInodesUsed,
298 }
299 if !splitFileSystem {
300 return fsStats, fsStats, nil
301 }
302
303 containerFs := imageStats.ContainerFilesystems[0]
304 var containerFsInodesUsed *uint64
305 if containerFsInfo.Inodes != nil && containerFsInfo.InodesFree != nil {
306 containerFsIU := *containerFsInfo.Inodes - *containerFsInfo.InodesFree
307 containerFsInodesUsed = &containerFsIU
308 }
309
310 fsContainerStats := &statsapi.FsStats{
311 Time: metav1.NewTime(containerFsInfo.Timestamp),
312 AvailableBytes: &containerFsInfo.Available,
313 CapacityBytes: &containerFsInfo.Capacity,
314 UsedBytes: &containerFs.UsedBytes.Value,
315 InodesFree: containerFsInfo.InodesFree,
316 Inodes: containerFsInfo.Inodes,
317 InodesUsed: containerFsInodesUsed,
318 }
319
320 return fsStats, fsContainerStats, nil
321 }
322
323
324
325 func (p *cadvisorStatsProvider) ImageFsDevice(_ context.Context) (string, error) {
326 imageFsInfo, err := p.cadvisor.ImagesFsInfo()
327 if err != nil {
328 return "", err
329 }
330 return imageFsInfo.Device, nil
331 }
332
333
334 func buildPodRef(containerLabels map[string]string) statsapi.PodReference {
335 podName := kubetypes.GetPodName(containerLabels)
336 podNamespace := kubetypes.GetPodNamespace(containerLabels)
337 podUID := kubetypes.GetPodUID(containerLabels)
338 return statsapi.PodReference{Name: podName, Namespace: podNamespace, UID: podUID}
339 }
340
341
342 func isPodManagedContainer(cinfo *cadvisorapiv2.ContainerInfo) bool {
343 podName := kubetypes.GetPodName(cinfo.Spec.Labels)
344 podNamespace := kubetypes.GetPodNamespace(cinfo.Spec.Labels)
345 managed := podName != "" && podNamespace != ""
346 if !managed && podName != podNamespace {
347 klog.InfoS(
348 "Expect container to have either both podName and podNamespace labels, or neither",
349 "podNameLabel", podName, "podNamespaceLabel", podNamespace)
350 }
351 return managed
352 }
353
354
355 func getCadvisorPodInfoFromPodUID(podUID types.UID, infos map[string]cadvisorapiv2.ContainerInfo) *cadvisorapiv2.ContainerInfo {
356 if info, found := infos[cm.GetPodCgroupNameSuffix(podUID)]; found {
357 return &info
358 }
359 return nil
360 }
361
362
363
364
365
366
367
368 func filterTerminatedContainerInfoAndAssembleByPodCgroupKey(containerInfo map[string]cadvisorapiv2.ContainerInfo) (map[string]cadvisorapiv2.ContainerInfo, map[string]cadvisorapiv2.ContainerInfo) {
369 cinfoMap := make(map[containerID][]containerInfoWithCgroup)
370 cinfosByPodCgroupKey := make(map[string]cadvisorapiv2.ContainerInfo)
371 for key, cinfo := range containerInfo {
372 var podCgroupKey string
373 if cm.IsSystemdStyleName(key) {
374
375 internalCgroupName := cm.ParseSystemdToCgroupName(key)
376 podCgroupKey = internalCgroupName[len(internalCgroupName)-1]
377 } else {
378
379 podCgroupKey = filepath.Base(key)
380 }
381 cinfosByPodCgroupKey[podCgroupKey] = cinfo
382 if !isPodManagedContainer(&cinfo) {
383 continue
384 }
385 cinfoID := containerID{
386 podRef: buildPodRef(cinfo.Spec.Labels),
387 containerName: kubetypes.GetContainerName(cinfo.Spec.Labels),
388 }
389 cinfoMap[cinfoID] = append(cinfoMap[cinfoID], containerInfoWithCgroup{
390 cinfo: cinfo,
391 cgroup: key,
392 })
393 }
394 result := make(map[string]cadvisorapiv2.ContainerInfo)
395 for _, refs := range cinfoMap {
396 if len(refs) == 1 {
397
398
399 if !isContainerTerminated(&refs[0].cinfo) {
400 result[refs[0].cgroup] = refs[0].cinfo
401 }
402 continue
403 }
404 sort.Sort(ByCreationTime(refs))
405 for i := len(refs) - 1; i >= 0; i-- {
406 if hasMemoryAndCPUInstUsage(&refs[i].cinfo) {
407 result[refs[i].cgroup] = refs[i].cinfo
408 break
409 }
410 }
411 }
412 return result, cinfosByPodCgroupKey
413 }
414
415
416
417 type ByCreationTime []containerInfoWithCgroup
418
419 func (a ByCreationTime) Len() int { return len(a) }
420 func (a ByCreationTime) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
421 func (a ByCreationTime) Less(i, j int) bool {
422 if a[i].cinfo.Spec.CreationTime.Equal(a[j].cinfo.Spec.CreationTime) {
423
424
425
426
427 return hasMemoryAndCPUInstUsage(&a[j].cinfo)
428 }
429 return a[i].cinfo.Spec.CreationTime.Before(a[j].cinfo.Spec.CreationTime)
430 }
431
432
433 type containerID struct {
434 podRef statsapi.PodReference
435 containerName string
436 }
437
438
439 type containerInfoWithCgroup struct {
440 cinfo cadvisorapiv2.ContainerInfo
441 cgroup string
442 }
443
444
445
446
447 func hasMemoryAndCPUInstUsage(info *cadvisorapiv2.ContainerInfo) bool {
448 if !info.Spec.HasCpu || !info.Spec.HasMemory {
449 return false
450 }
451 cstat, found := latestContainerStats(info)
452 if !found {
453 return false
454 }
455 if cstat.CpuInst == nil {
456 return false
457 }
458 return cstat.CpuInst.Usage.Total != 0 && cstat.Memory.RSS != 0
459 }
460
461
462
463
464
465
466 func isContainerTerminated(info *cadvisorapiv2.ContainerInfo) bool {
467 if !info.Spec.HasCpu && !info.Spec.HasMemory && !info.Spec.HasNetwork {
468 return true
469 }
470 cstat, found := latestContainerStats(info)
471 if !found {
472 return true
473 }
474 if cstat.Network != nil {
475 iStats := cadvisorInfoToNetworkStats(info)
476 if iStats != nil {
477 for _, iStat := range iStats.Interfaces {
478 if *iStat.RxErrors != 0 || *iStat.TxErrors != 0 || *iStat.RxBytes != 0 || *iStat.TxBytes != 0 {
479 return false
480 }
481 }
482 }
483 }
484 if cstat.CpuInst == nil || cstat.Memory == nil {
485 return true
486 }
487 return cstat.CpuInst.Usage.Total == 0 && cstat.Memory.RSS == 0
488 }
489
490 func getCadvisorContainerInfo(ca cadvisor.Interface) (map[string]cadvisorapiv2.ContainerInfo, error) {
491 infos, err := ca.ContainerInfoV2("/", cadvisorapiv2.RequestOptions{
492 IdType: cadvisorapiv2.TypeName,
493 Count: 2,
494 Recursive: true,
495 })
496 if err != nil {
497 if _, ok := infos["/"]; ok {
498
499
500 klog.ErrorS(err, "Partial failure issuing cadvisor.ContainerInfoV2")
501 } else {
502 return nil, fmt.Errorf("failed to get root cgroup stats: %v", err)
503 }
504 }
505 return infos, nil
506 }
507
View as plain text