...

Source file src/k8s.io/kubernetes/pkg/kubelet/stats/cri_stats_provider_windows.go

Documentation: k8s.io/kubernetes/pkg/kubelet/stats

     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  }
   283  

View as plain text