k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/kubelet/stats/cri_stats_provider_windows.go (about) 1 //go:build windows 2 // +build windows 3 4 /* 5 Copyright 2019 The Kubernetes Authors. 6 7 Licensed under the Apache License, Version 2.0 (the "License"); 8 you may not use this file except in compliance with the License. 9 You may obtain a copy of the License at 10 11 http://www.apache.org/licenses/LICENSE-2.0 12 13 Unless required by applicable law or agreed to in writing, software 14 distributed under the License is distributed on an "AS IS" BASIS, 15 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 See the License for the specific language governing permissions and 17 limitations under the License. 18 */ 19 20 package stats 21 22 import ( 23 "fmt" 24 "time" 25 26 "github.com/Microsoft/hcsshim" 27 cadvisorapiv2 "github.com/google/cadvisor/info/v2" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 "k8s.io/apimachinery/pkg/types" 30 runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1" 31 "k8s.io/klog/v2" 32 statsapi "k8s.io/kubelet/pkg/apis/stats/v1alpha1" 33 ) 34 35 // windowsNetworkStatsProvider creates an interface that allows for testing the logic without needing to create a container 36 type windowsNetworkStatsProvider interface { 37 HNSListEndpointRequest() ([]hcsshim.HNSEndpoint, error) 38 GetHNSEndpointStats(endpointName string) (*hcsshim.HNSEndpointStats, error) 39 } 40 41 // networkStats exposes the required functionality for hcsshim in this scenario 42 type networkStats struct{} 43 44 func (s networkStats) HNSListEndpointRequest() ([]hcsshim.HNSEndpoint, error) { 45 return hcsshim.HNSListEndpointRequest() 46 } 47 48 func (s networkStats) GetHNSEndpointStats(endpointName string) (*hcsshim.HNSEndpointStats, error) { 49 return hcsshim.GetHNSEndpointStats(endpointName) 50 } 51 52 // listContainerNetworkStats returns the network stats of all the running containers. 53 func (p *criStatsProvider) listContainerNetworkStats() (map[string]*statsapi.NetworkStats, error) { 54 networkStatsProvider := newNetworkStatsProvider(p) 55 56 endpoints, err := networkStatsProvider.HNSListEndpointRequest() 57 if err != nil { 58 klog.ErrorS(err, "Failed to fetch current HNS endpoints") 59 return nil, err 60 } 61 62 networkStats := make(map[string]*statsapi.NetworkStats) 63 for _, endpoint := range endpoints { 64 endpointStats, err := networkStatsProvider.GetHNSEndpointStats(endpoint.Id) 65 if err != nil { 66 klog.V(2).InfoS("Failed to fetch statistics for endpoint, continue to get stats for other endpoints", "endpointId", endpoint.Id, "containers", endpoint.SharedContainers) 67 continue 68 } 69 70 // only add the interface for each container if not already in the list 71 for _, cId := range endpoint.SharedContainers { 72 networkStat, found := networkStats[cId] 73 if found && networkStat.Name != endpoint.Name { 74 iStat := hcsStatToInterfaceStat(endpointStats, endpoint.Name) 75 networkStat.Interfaces = append(networkStat.Interfaces, iStat) 76 continue 77 } 78 networkStats[cId] = hcsStatsToNetworkStats(p.clock.Now(), endpointStats, endpoint.Name) 79 } 80 } 81 82 return networkStats, nil 83 } 84 85 func (p *criStatsProvider) addCRIPodContainerStats(criSandboxStat *runtimeapi.PodSandboxStats, 86 ps *statsapi.PodStats, fsIDtoInfo map[runtimeapi.FilesystemIdentifier]*cadvisorapiv2.FsInfo, 87 containerMap map[string]*runtimeapi.Container, 88 podSandbox *runtimeapi.PodSandbox, 89 rootFsInfo *cadvisorapiv2.FsInfo, 90 updateCPUNanoCoreUsage bool) error { 91 for _, criContainerStat := range criSandboxStat.GetWindows().GetContainers() { 92 container, found := containerMap[criContainerStat.Attributes.Id] 93 if !found { 94 continue 95 } 96 // Fill available stats for full set of required pod stats 97 cs, err := p.makeWinContainerStats(criContainerStat, container, rootFsInfo, fsIDtoInfo, podSandbox.GetMetadata()) 98 if err != nil { 99 return fmt.Errorf("make container stats: %w", err) 100 101 } 102 ps.Containers = append(ps.Containers, *cs) 103 } 104 105 return nil 106 } 107 108 func (p *criStatsProvider) makeWinContainerStats( 109 stats *runtimeapi.WindowsContainerStats, 110 container *runtimeapi.Container, 111 rootFsInfo *cadvisorapiv2.FsInfo, 112 fsIDtoInfo map[runtimeapi.FilesystemIdentifier]*cadvisorapiv2.FsInfo, 113 meta *runtimeapi.PodSandboxMetadata) (*statsapi.ContainerStats, error) { 114 result := &statsapi.ContainerStats{ 115 Name: stats.Attributes.Metadata.Name, 116 // The StartTime in the summary API is the container creation time. 117 StartTime: metav1.NewTime(time.Unix(0, container.CreatedAt)), 118 CPU: &statsapi.CPUStats{}, 119 Memory: &statsapi.MemoryStats{}, 120 Rootfs: &statsapi.FsStats{}, 121 // UserDefinedMetrics is not supported by CRI. 122 } 123 if stats.Cpu != nil { 124 result.CPU.Time = metav1.NewTime(time.Unix(0, stats.Cpu.Timestamp)) 125 if stats.Cpu.UsageCoreNanoSeconds != nil { 126 result.CPU.UsageCoreNanoSeconds = &stats.Cpu.UsageCoreNanoSeconds.Value 127 } 128 if stats.Cpu.UsageNanoCores != nil { 129 result.CPU.UsageNanoCores = &stats.Cpu.UsageNanoCores.Value 130 } 131 } else { 132 result.CPU.Time = metav1.NewTime(time.Unix(0, time.Now().UnixNano())) 133 result.CPU.UsageCoreNanoSeconds = uint64Ptr(0) 134 result.CPU.UsageNanoCores = uint64Ptr(0) 135 } 136 if stats.Memory != nil { 137 result.Memory.Time = metav1.NewTime(time.Unix(0, stats.Memory.Timestamp)) 138 if stats.Memory.WorkingSetBytes != nil { 139 result.Memory.WorkingSetBytes = &stats.Memory.WorkingSetBytes.Value 140 } 141 if stats.Memory.AvailableBytes != nil { 142 result.Memory.AvailableBytes = &stats.Memory.AvailableBytes.Value 143 } 144 if stats.Memory.PageFaults != nil { 145 result.Memory.PageFaults = &stats.Memory.PageFaults.Value 146 } 147 } else { 148 result.Memory.Time = metav1.NewTime(time.Unix(0, time.Now().UnixNano())) 149 result.Memory.WorkingSetBytes = uint64Ptr(0) 150 result.Memory.AvailableBytes = uint64Ptr(0) 151 result.Memory.PageFaults = uint64Ptr(0) 152 } 153 if stats.WritableLayer != nil { 154 result.Rootfs.Time = metav1.NewTime(time.Unix(0, stats.WritableLayer.Timestamp)) 155 if stats.WritableLayer.UsedBytes != nil { 156 result.Rootfs.UsedBytes = &stats.WritableLayer.UsedBytes.Value 157 } 158 } 159 var err error 160 fsID := stats.GetWritableLayer().GetFsId() 161 if fsID != nil { 162 imageFsInfo, found := fsIDtoInfo[*fsID] 163 if !found { 164 imageFsInfo, err = p.getFsInfo(fsID) 165 if err != nil { 166 return nil, fmt.Errorf("get filesystem info: %w", err) 167 } 168 fsIDtoInfo[*fsID] = imageFsInfo 169 } 170 if imageFsInfo != nil { 171 // The image filesystem id is unknown to the local node or there's 172 // an error on retrieving the stats. In these cases, we omit those stats 173 // and return the best-effort partial result. See 174 // https://github.com/kubernetes/heapster/issues/1793. 175 result.Rootfs.AvailableBytes = &imageFsInfo.Available 176 result.Rootfs.CapacityBytes = &imageFsInfo.Capacity 177 } 178 } 179 // NOTE: This doesn't support the old pod log path, `/var/log/pods/UID`. For containers 180 // using old log path, empty log stats are returned. This is fine, because we don't 181 // officially support in-place upgrade anyway. 182 result.Logs, err = p.hostStatsProvider.getPodContainerLogStats(meta.GetNamespace(), meta.GetName(), types.UID(meta.GetUid()), container.GetMetadata().GetName(), rootFsInfo) 183 if err != nil { 184 klog.ErrorS(err, "Unable to fetch container log stats", "containerName", container.GetMetadata().GetName()) 185 } 186 return result, nil 187 } 188 189 // hcsStatsToNetworkStats converts hcsshim.Statistics.Network to statsapi.NetworkStats 190 func hcsStatsToNetworkStats(timestamp time.Time, hcsStats *hcsshim.HNSEndpointStats, endpointName string) *statsapi.NetworkStats { 191 result := &statsapi.NetworkStats{ 192 Time: metav1.NewTime(timestamp), 193 Interfaces: make([]statsapi.InterfaceStats, 0), 194 } 195 196 iStat := hcsStatToInterfaceStat(hcsStats, endpointName) 197 198 // TODO: add support of multiple interfaces for getting default interface. 199 result.Interfaces = append(result.Interfaces, iStat) 200 result.InterfaceStats = iStat 201 202 return result 203 } 204 205 func hcsStatToInterfaceStat(hcsStats *hcsshim.HNSEndpointStats, endpointName string) statsapi.InterfaceStats { 206 iStat := statsapi.InterfaceStats{ 207 Name: endpointName, 208 RxBytes: &hcsStats.BytesReceived, 209 TxBytes: &hcsStats.BytesSent, 210 } 211 return iStat 212 } 213 214 func addCRIPodCPUStats(ps *statsapi.PodStats, criPodStat *runtimeapi.PodSandboxStats) { 215 if criPodStat == nil || criPodStat.Windows == nil || criPodStat.Windows.Cpu == nil { 216 return 217 } 218 criCPU := criPodStat.Windows.Cpu 219 ps.CPU = &statsapi.CPUStats{ 220 Time: metav1.NewTime(time.Unix(0, criCPU.Timestamp)), 221 UsageNanoCores: valueOfUInt64Value(criCPU.UsageNanoCores), 222 UsageCoreNanoSeconds: valueOfUInt64Value(criCPU.UsageCoreNanoSeconds), 223 } 224 } 225 226 func addCRIPodMemoryStats(ps *statsapi.PodStats, criPodStat *runtimeapi.PodSandboxStats) { 227 if criPodStat == nil || criPodStat.Windows == nil || criPodStat.Windows.Memory == nil { 228 return 229 } 230 criMemory := criPodStat.Windows.Memory 231 ps.Memory = &statsapi.MemoryStats{ 232 Time: metav1.NewTime(time.Unix(0, criMemory.Timestamp)), 233 AvailableBytes: valueOfUInt64Value(criMemory.AvailableBytes), 234 WorkingSetBytes: valueOfUInt64Value(criMemory.WorkingSetBytes), 235 PageFaults: valueOfUInt64Value(criMemory.PageFaults), 236 } 237 } 238 239 func addCRIPodProcessStats(ps *statsapi.PodStats, criPodStat *runtimeapi.PodSandboxStats) { 240 if criPodStat == nil || criPodStat.Windows == nil || criPodStat.Windows.Process == nil { 241 return 242 } 243 ps.ProcessStats = &statsapi.ProcessStats{ 244 ProcessCount: valueOfUInt64Value(criPodStat.Windows.Process.ProcessCount), 245 } 246 } 247 248 func addCRIPodNetworkStats(ps *statsapi.PodStats, criPodStat *runtimeapi.PodSandboxStats) { 249 if criPodStat == nil || criPodStat.Windows == nil || criPodStat.Windows.Network == nil { 250 return 251 } 252 criNetwork := criPodStat.Windows.Network 253 iStats := statsapi.NetworkStats{ 254 Time: metav1.NewTime(time.Unix(0, criNetwork.Timestamp)), 255 InterfaceStats: criInterfaceToWinSummary(criNetwork.DefaultInterface), 256 Interfaces: make([]statsapi.InterfaceStats, 0, len(criNetwork.Interfaces)), 257 } 258 for _, iface := range criNetwork.Interfaces { 259 iStats.Interfaces = append(iStats.Interfaces, criInterfaceToWinSummary(iface)) 260 } 261 ps.Network = &iStats 262 } 263 264 func criInterfaceToWinSummary(criIface *runtimeapi.WindowsNetworkInterfaceUsage) statsapi.InterfaceStats { 265 return statsapi.InterfaceStats{ 266 Name: criIface.Name, 267 RxBytes: valueOfUInt64Value(criIface.RxBytes), 268 TxBytes: valueOfUInt64Value(criIface.TxBytes), 269 } 270 } 271 272 // newNetworkStatsProvider uses the real windows hcsshim if not provided otherwise if the interface is provided 273 // by the cristatsprovider in testing scenarios it uses that one 274 func newNetworkStatsProvider(p *criStatsProvider) windowsNetworkStatsProvider { 275 var statsProvider windowsNetworkStatsProvider 276 if p.windowsNetworkStatsProvider == nil { 277 statsProvider = networkStats{} 278 } else { 279 statsProvider = p.windowsNetworkStatsProvider.(windowsNetworkStatsProvider) 280 } 281 return statsProvider 282 }