123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394 |
- /*
- Copyright 2017 The Kubernetes Authors.
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- package stats
- import (
- "fmt"
- "time"
- cadvisorapiv1 "github.com/google/cadvisor/info/v1"
- cadvisorapiv2 "github.com/google/cadvisor/info/v2"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/klog"
- statsapi "k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1"
- "k8s.io/kubernetes/pkg/kubelet/cadvisor"
- )
- // defaultNetworkInterfaceName is used for collectng network stats.
- // This logic relies on knowledge of the container runtime implementation and
- // is not reliable.
- const defaultNetworkInterfaceName = "eth0"
- func cadvisorInfoToCPUandMemoryStats(info *cadvisorapiv2.ContainerInfo) (*statsapi.CPUStats, *statsapi.MemoryStats) {
- cstat, found := latestContainerStats(info)
- if !found {
- return nil, nil
- }
- var cpuStats *statsapi.CPUStats
- var memoryStats *statsapi.MemoryStats
- if info.Spec.HasCpu {
- cpuStats = &statsapi.CPUStats{
- Time: metav1.NewTime(cstat.Timestamp),
- }
- if cstat.CpuInst != nil {
- cpuStats.UsageNanoCores = &cstat.CpuInst.Usage.Total
- }
- if cstat.Cpu != nil {
- cpuStats.UsageCoreNanoSeconds = &cstat.Cpu.Usage.Total
- }
- }
- if info.Spec.HasMemory && cstat.Memory != nil {
- pageFaults := cstat.Memory.ContainerData.Pgfault
- majorPageFaults := cstat.Memory.ContainerData.Pgmajfault
- memoryStats = &statsapi.MemoryStats{
- Time: metav1.NewTime(cstat.Timestamp),
- UsageBytes: &cstat.Memory.Usage,
- WorkingSetBytes: &cstat.Memory.WorkingSet,
- RSSBytes: &cstat.Memory.RSS,
- PageFaults: &pageFaults,
- MajorPageFaults: &majorPageFaults,
- }
- // availableBytes = memory limit (if known) - workingset
- if !isMemoryUnlimited(info.Spec.Memory.Limit) {
- availableBytes := info.Spec.Memory.Limit - cstat.Memory.WorkingSet
- memoryStats.AvailableBytes = &availableBytes
- }
- }
- return cpuStats, memoryStats
- }
- // cadvisorInfoToContainerStats returns the statsapi.ContainerStats converted
- // from the container and filesystem info.
- func cadvisorInfoToContainerStats(name string, info *cadvisorapiv2.ContainerInfo, rootFs, imageFs *cadvisorapiv2.FsInfo) *statsapi.ContainerStats {
- result := &statsapi.ContainerStats{
- StartTime: metav1.NewTime(info.Spec.CreationTime),
- Name: name,
- }
- cstat, found := latestContainerStats(info)
- if !found {
- return result
- }
- cpu, memory := cadvisorInfoToCPUandMemoryStats(info)
- result.CPU = cpu
- result.Memory = memory
- if rootFs != nil {
- // The container logs live on the node rootfs device
- result.Logs = buildLogsStats(cstat, rootFs)
- }
- if imageFs != nil {
- // The container rootFs lives on the imageFs devices (which may not be the node root fs)
- result.Rootfs = buildRootfsStats(cstat, imageFs)
- }
- cfs := cstat.Filesystem
- if cfs != nil {
- if cfs.BaseUsageBytes != nil {
- if result.Rootfs != nil {
- rootfsUsage := *cfs.BaseUsageBytes
- result.Rootfs.UsedBytes = &rootfsUsage
- }
- if cfs.TotalUsageBytes != nil && result.Logs != nil {
- logsUsage := *cfs.TotalUsageBytes - *cfs.BaseUsageBytes
- result.Logs.UsedBytes = &logsUsage
- }
- }
- if cfs.InodeUsage != nil && result.Rootfs != nil {
- rootInodes := *cfs.InodeUsage
- result.Rootfs.InodesUsed = &rootInodes
- }
- }
- for _, acc := range cstat.Accelerators {
- result.Accelerators = append(result.Accelerators, statsapi.AcceleratorStats{
- Make: acc.Make,
- Model: acc.Model,
- ID: acc.ID,
- MemoryTotal: acc.MemoryTotal,
- MemoryUsed: acc.MemoryUsed,
- DutyCycle: acc.DutyCycle,
- })
- }
- result.UserDefinedMetrics = cadvisorInfoToUserDefinedMetrics(info)
- return result
- }
- // cadvisorInfoToContainerCPUAndMemoryStats returns the statsapi.ContainerStats converted
- // from the container and filesystem info.
- func cadvisorInfoToContainerCPUAndMemoryStats(name string, info *cadvisorapiv2.ContainerInfo) *statsapi.ContainerStats {
- result := &statsapi.ContainerStats{
- StartTime: metav1.NewTime(info.Spec.CreationTime),
- Name: name,
- }
- cpu, memory := cadvisorInfoToCPUandMemoryStats(info)
- result.CPU = cpu
- result.Memory = memory
- return result
- }
- // cadvisorInfoToNetworkStats returns the statsapi.NetworkStats converted from
- // the container info from cadvisor.
- func cadvisorInfoToNetworkStats(name string, info *cadvisorapiv2.ContainerInfo) *statsapi.NetworkStats {
- if !info.Spec.HasNetwork {
- return nil
- }
- cstat, found := latestContainerStats(info)
- if !found {
- return nil
- }
- if cstat.Network == nil {
- return nil
- }
- iStats := statsapi.NetworkStats{
- Time: metav1.NewTime(cstat.Timestamp),
- }
- for i := range cstat.Network.Interfaces {
- inter := cstat.Network.Interfaces[i]
- iStat := statsapi.InterfaceStats{
- Name: inter.Name,
- RxBytes: &inter.RxBytes,
- RxErrors: &inter.RxErrors,
- TxBytes: &inter.TxBytes,
- TxErrors: &inter.TxErrors,
- }
- if inter.Name == defaultNetworkInterfaceName {
- iStats.InterfaceStats = iStat
- }
- iStats.Interfaces = append(iStats.Interfaces, iStat)
- }
- return &iStats
- }
- // cadvisorInfoToUserDefinedMetrics returns the statsapi.UserDefinedMetric
- // converted from the container info from cadvisor.
- func cadvisorInfoToUserDefinedMetrics(info *cadvisorapiv2.ContainerInfo) []statsapi.UserDefinedMetric {
- type specVal struct {
- ref statsapi.UserDefinedMetricDescriptor
- valType cadvisorapiv1.DataType
- time time.Time
- value float64
- }
- udmMap := map[string]*specVal{}
- for _, spec := range info.Spec.CustomMetrics {
- udmMap[spec.Name] = &specVal{
- ref: statsapi.UserDefinedMetricDescriptor{
- Name: spec.Name,
- Type: statsapi.UserDefinedMetricType(spec.Type),
- Units: spec.Units,
- },
- valType: spec.Format,
- }
- }
- for _, stat := range info.Stats {
- for name, values := range stat.CustomMetrics {
- specVal, ok := udmMap[name]
- if !ok {
- klog.Warningf("spec for custom metric %q is missing from cAdvisor output. Spec: %+v, Metrics: %+v", name, info.Spec, stat.CustomMetrics)
- continue
- }
- for _, value := range values {
- // Pick the most recent value
- if value.Timestamp.Before(specVal.time) {
- continue
- }
- specVal.time = value.Timestamp
- specVal.value = value.FloatValue
- if specVal.valType == cadvisorapiv1.IntType {
- specVal.value = float64(value.IntValue)
- }
- }
- }
- }
- var udm []statsapi.UserDefinedMetric
- for _, specVal := range udmMap {
- udm = append(udm, statsapi.UserDefinedMetric{
- UserDefinedMetricDescriptor: specVal.ref,
- Time: metav1.NewTime(specVal.time),
- Value: specVal.value,
- })
- }
- return udm
- }
- // latestContainerStats returns the latest container stats from cadvisor, or nil if none exist
- func latestContainerStats(info *cadvisorapiv2.ContainerInfo) (*cadvisorapiv2.ContainerStats, bool) {
- stats := info.Stats
- if len(stats) < 1 {
- return nil, false
- }
- latest := stats[len(stats)-1]
- if latest == nil {
- return nil, false
- }
- return latest, true
- }
- func isMemoryUnlimited(v uint64) bool {
- // Size after which we consider memory to be "unlimited". This is not
- // MaxInt64 due to rounding by the kernel.
- // TODO: cadvisor should export this https://github.com/google/cadvisor/blob/master/metrics/prometheus.go#L596
- const maxMemorySize = uint64(1 << 62)
- return v > maxMemorySize
- }
- // getCgroupInfo returns the information of the container with the specified
- // containerName from cadvisor.
- func getCgroupInfo(cadvisor cadvisor.Interface, containerName string, updateStats bool) (*cadvisorapiv2.ContainerInfo, error) {
- var maxAge *time.Duration
- if updateStats {
- age := 0 * time.Second
- maxAge = &age
- }
- infoMap, err := cadvisor.ContainerInfoV2(containerName, cadvisorapiv2.RequestOptions{
- IdType: cadvisorapiv2.TypeName,
- Count: 2, // 2 samples are needed to compute "instantaneous" CPU
- Recursive: false,
- MaxAge: maxAge,
- })
- if err != nil {
- return nil, fmt.Errorf("failed to get container info for %q: %v", containerName, err)
- }
- if len(infoMap) != 1 {
- return nil, fmt.Errorf("unexpected number of containers: %v", len(infoMap))
- }
- info := infoMap[containerName]
- return &info, nil
- }
- // getCgroupStats returns the latest stats of the container having the
- // specified containerName from cadvisor.
- func getCgroupStats(cadvisor cadvisor.Interface, containerName string, updateStats bool) (*cadvisorapiv2.ContainerStats, error) {
- info, err := getCgroupInfo(cadvisor, containerName, updateStats)
- if err != nil {
- return nil, err
- }
- stats, found := latestContainerStats(info)
- if !found {
- return nil, fmt.Errorf("failed to get latest stats from container info for %q", containerName)
- }
- return stats, nil
- }
- func buildLogsStats(cstat *cadvisorapiv2.ContainerStats, rootFs *cadvisorapiv2.FsInfo) *statsapi.FsStats {
- fsStats := &statsapi.FsStats{
- Time: metav1.NewTime(cstat.Timestamp),
- AvailableBytes: &rootFs.Available,
- CapacityBytes: &rootFs.Capacity,
- InodesFree: rootFs.InodesFree,
- Inodes: rootFs.Inodes,
- }
- if rootFs.Inodes != nil && rootFs.InodesFree != nil {
- logsInodesUsed := *rootFs.Inodes - *rootFs.InodesFree
- fsStats.InodesUsed = &logsInodesUsed
- }
- return fsStats
- }
- func buildRootfsStats(cstat *cadvisorapiv2.ContainerStats, imageFs *cadvisorapiv2.FsInfo) *statsapi.FsStats {
- return &statsapi.FsStats{
- Time: metav1.NewTime(cstat.Timestamp),
- AvailableBytes: &imageFs.Available,
- CapacityBytes: &imageFs.Capacity,
- InodesFree: imageFs.InodesFree,
- Inodes: imageFs.Inodes,
- }
- }
- func getUint64Value(value *uint64) uint64 {
- if value == nil {
- return 0
- }
- return *value
- }
- func uint64Ptr(i uint64) *uint64 {
- return &i
- }
- func calcEphemeralStorage(containers []statsapi.ContainerStats, volumes []statsapi.VolumeStats, rootFsInfo *cadvisorapiv2.FsInfo,
- podLogStats *statsapi.FsStats, isCRIStatsProvider bool) *statsapi.FsStats {
- result := &statsapi.FsStats{
- Time: metav1.NewTime(rootFsInfo.Timestamp),
- AvailableBytes: &rootFsInfo.Available,
- CapacityBytes: &rootFsInfo.Capacity,
- InodesFree: rootFsInfo.InodesFree,
- Inodes: rootFsInfo.Inodes,
- }
- for _, container := range containers {
- addContainerUsage(result, &container, isCRIStatsProvider)
- }
- for _, volume := range volumes {
- result.UsedBytes = addUsage(result.UsedBytes, volume.FsStats.UsedBytes)
- result.InodesUsed = addUsage(result.InodesUsed, volume.InodesUsed)
- result.Time = maxUpdateTime(&result.Time, &volume.FsStats.Time)
- }
- if podLogStats != nil {
- result.UsedBytes = addUsage(result.UsedBytes, podLogStats.UsedBytes)
- result.InodesUsed = addUsage(result.InodesUsed, podLogStats.InodesUsed)
- result.Time = maxUpdateTime(&result.Time, &podLogStats.Time)
- }
- return result
- }
- func addContainerUsage(stat *statsapi.FsStats, container *statsapi.ContainerStats, isCRIStatsProvider bool) {
- if rootFs := container.Rootfs; rootFs != nil {
- stat.Time = maxUpdateTime(&stat.Time, &rootFs.Time)
- stat.InodesUsed = addUsage(stat.InodesUsed, rootFs.InodesUsed)
- stat.UsedBytes = addUsage(stat.UsedBytes, rootFs.UsedBytes)
- if logs := container.Logs; logs != nil {
- stat.UsedBytes = addUsage(stat.UsedBytes, logs.UsedBytes)
- // We have accurate container log inode usage for CRI stats provider.
- if isCRIStatsProvider {
- stat.InodesUsed = addUsage(stat.InodesUsed, logs.InodesUsed)
- }
- stat.Time = maxUpdateTime(&stat.Time, &logs.Time)
- }
- }
- }
- func maxUpdateTime(first, second *metav1.Time) metav1.Time {
- if first.Before(second) {
- return *second
- }
- return *first
- }
- func addUsage(first, second *uint64) *uint64 {
- if first == nil {
- return second
- } else if second == nil {
- return first
- }
- total := *first + *second
- return &total
- }
|