helper.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  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. "fmt"
  16. "time"
  17. cadvisorapiv1 "github.com/google/cadvisor/info/v1"
  18. cadvisorapiv2 "github.com/google/cadvisor/info/v2"
  19. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  20. "k8s.io/klog"
  21. statsapi "k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1"
  22. "k8s.io/kubernetes/pkg/kubelet/cadvisor"
  23. )
  24. // defaultNetworkInterfaceName is used for collectng network stats.
  25. // This logic relies on knowledge of the container runtime implementation and
  26. // is not reliable.
  27. const defaultNetworkInterfaceName = "eth0"
  28. func cadvisorInfoToCPUandMemoryStats(info *cadvisorapiv2.ContainerInfo) (*statsapi.CPUStats, *statsapi.MemoryStats) {
  29. cstat, found := latestContainerStats(info)
  30. if !found {
  31. return nil, nil
  32. }
  33. var cpuStats *statsapi.CPUStats
  34. var memoryStats *statsapi.MemoryStats
  35. if info.Spec.HasCpu {
  36. cpuStats = &statsapi.CPUStats{
  37. Time: metav1.NewTime(cstat.Timestamp),
  38. }
  39. if cstat.CpuInst != nil {
  40. cpuStats.UsageNanoCores = &cstat.CpuInst.Usage.Total
  41. }
  42. if cstat.Cpu != nil {
  43. cpuStats.UsageCoreNanoSeconds = &cstat.Cpu.Usage.Total
  44. }
  45. }
  46. if info.Spec.HasMemory && cstat.Memory != nil {
  47. pageFaults := cstat.Memory.ContainerData.Pgfault
  48. majorPageFaults := cstat.Memory.ContainerData.Pgmajfault
  49. memoryStats = &statsapi.MemoryStats{
  50. Time: metav1.NewTime(cstat.Timestamp),
  51. UsageBytes: &cstat.Memory.Usage,
  52. WorkingSetBytes: &cstat.Memory.WorkingSet,
  53. RSSBytes: &cstat.Memory.RSS,
  54. PageFaults: &pageFaults,
  55. MajorPageFaults: &majorPageFaults,
  56. }
  57. // availableBytes = memory limit (if known) - workingset
  58. if !isMemoryUnlimited(info.Spec.Memory.Limit) {
  59. availableBytes := info.Spec.Memory.Limit - cstat.Memory.WorkingSet
  60. memoryStats.AvailableBytes = &availableBytes
  61. }
  62. }
  63. return cpuStats, memoryStats
  64. }
  65. // cadvisorInfoToContainerStats returns the statsapi.ContainerStats converted
  66. // from the container and filesystem info.
  67. func cadvisorInfoToContainerStats(name string, info *cadvisorapiv2.ContainerInfo, rootFs, imageFs *cadvisorapiv2.FsInfo) *statsapi.ContainerStats {
  68. result := &statsapi.ContainerStats{
  69. StartTime: metav1.NewTime(info.Spec.CreationTime),
  70. Name: name,
  71. }
  72. cstat, found := latestContainerStats(info)
  73. if !found {
  74. return result
  75. }
  76. cpu, memory := cadvisorInfoToCPUandMemoryStats(info)
  77. result.CPU = cpu
  78. result.Memory = memory
  79. if rootFs != nil {
  80. // The container logs live on the node rootfs device
  81. result.Logs = buildLogsStats(cstat, rootFs)
  82. }
  83. if imageFs != nil {
  84. // The container rootFs lives on the imageFs devices (which may not be the node root fs)
  85. result.Rootfs = buildRootfsStats(cstat, imageFs)
  86. }
  87. cfs := cstat.Filesystem
  88. if cfs != nil {
  89. if cfs.BaseUsageBytes != nil {
  90. if result.Rootfs != nil {
  91. rootfsUsage := *cfs.BaseUsageBytes
  92. result.Rootfs.UsedBytes = &rootfsUsage
  93. }
  94. if cfs.TotalUsageBytes != nil && result.Logs != nil {
  95. logsUsage := *cfs.TotalUsageBytes - *cfs.BaseUsageBytes
  96. result.Logs.UsedBytes = &logsUsage
  97. }
  98. }
  99. if cfs.InodeUsage != nil && result.Rootfs != nil {
  100. rootInodes := *cfs.InodeUsage
  101. result.Rootfs.InodesUsed = &rootInodes
  102. }
  103. }
  104. for _, acc := range cstat.Accelerators {
  105. result.Accelerators = append(result.Accelerators, statsapi.AcceleratorStats{
  106. Make: acc.Make,
  107. Model: acc.Model,
  108. ID: acc.ID,
  109. MemoryTotal: acc.MemoryTotal,
  110. MemoryUsed: acc.MemoryUsed,
  111. DutyCycle: acc.DutyCycle,
  112. })
  113. }
  114. result.UserDefinedMetrics = cadvisorInfoToUserDefinedMetrics(info)
  115. return result
  116. }
  117. // cadvisorInfoToContainerCPUAndMemoryStats returns the statsapi.ContainerStats converted
  118. // from the container and filesystem info.
  119. func cadvisorInfoToContainerCPUAndMemoryStats(name string, info *cadvisorapiv2.ContainerInfo) *statsapi.ContainerStats {
  120. result := &statsapi.ContainerStats{
  121. StartTime: metav1.NewTime(info.Spec.CreationTime),
  122. Name: name,
  123. }
  124. cpu, memory := cadvisorInfoToCPUandMemoryStats(info)
  125. result.CPU = cpu
  126. result.Memory = memory
  127. return result
  128. }
  129. // cadvisorInfoToNetworkStats returns the statsapi.NetworkStats converted from
  130. // the container info from cadvisor.
  131. func cadvisorInfoToNetworkStats(name string, info *cadvisorapiv2.ContainerInfo) *statsapi.NetworkStats {
  132. if !info.Spec.HasNetwork {
  133. return nil
  134. }
  135. cstat, found := latestContainerStats(info)
  136. if !found {
  137. return nil
  138. }
  139. if cstat.Network == nil {
  140. return nil
  141. }
  142. iStats := statsapi.NetworkStats{
  143. Time: metav1.NewTime(cstat.Timestamp),
  144. }
  145. for i := range cstat.Network.Interfaces {
  146. inter := cstat.Network.Interfaces[i]
  147. iStat := statsapi.InterfaceStats{
  148. Name: inter.Name,
  149. RxBytes: &inter.RxBytes,
  150. RxErrors: &inter.RxErrors,
  151. TxBytes: &inter.TxBytes,
  152. TxErrors: &inter.TxErrors,
  153. }
  154. if inter.Name == defaultNetworkInterfaceName {
  155. iStats.InterfaceStats = iStat
  156. }
  157. iStats.Interfaces = append(iStats.Interfaces, iStat)
  158. }
  159. return &iStats
  160. }
  161. // cadvisorInfoToUserDefinedMetrics returns the statsapi.UserDefinedMetric
  162. // converted from the container info from cadvisor.
  163. func cadvisorInfoToUserDefinedMetrics(info *cadvisorapiv2.ContainerInfo) []statsapi.UserDefinedMetric {
  164. type specVal struct {
  165. ref statsapi.UserDefinedMetricDescriptor
  166. valType cadvisorapiv1.DataType
  167. time time.Time
  168. value float64
  169. }
  170. udmMap := map[string]*specVal{}
  171. for _, spec := range info.Spec.CustomMetrics {
  172. udmMap[spec.Name] = &specVal{
  173. ref: statsapi.UserDefinedMetricDescriptor{
  174. Name: spec.Name,
  175. Type: statsapi.UserDefinedMetricType(spec.Type),
  176. Units: spec.Units,
  177. },
  178. valType: spec.Format,
  179. }
  180. }
  181. for _, stat := range info.Stats {
  182. for name, values := range stat.CustomMetrics {
  183. specVal, ok := udmMap[name]
  184. if !ok {
  185. klog.Warningf("spec for custom metric %q is missing from cAdvisor output. Spec: %+v, Metrics: %+v", name, info.Spec, stat.CustomMetrics)
  186. continue
  187. }
  188. for _, value := range values {
  189. // Pick the most recent value
  190. if value.Timestamp.Before(specVal.time) {
  191. continue
  192. }
  193. specVal.time = value.Timestamp
  194. specVal.value = value.FloatValue
  195. if specVal.valType == cadvisorapiv1.IntType {
  196. specVal.value = float64(value.IntValue)
  197. }
  198. }
  199. }
  200. }
  201. var udm []statsapi.UserDefinedMetric
  202. for _, specVal := range udmMap {
  203. udm = append(udm, statsapi.UserDefinedMetric{
  204. UserDefinedMetricDescriptor: specVal.ref,
  205. Time: metav1.NewTime(specVal.time),
  206. Value: specVal.value,
  207. })
  208. }
  209. return udm
  210. }
  211. // latestContainerStats returns the latest container stats from cadvisor, or nil if none exist
  212. func latestContainerStats(info *cadvisorapiv2.ContainerInfo) (*cadvisorapiv2.ContainerStats, bool) {
  213. stats := info.Stats
  214. if len(stats) < 1 {
  215. return nil, false
  216. }
  217. latest := stats[len(stats)-1]
  218. if latest == nil {
  219. return nil, false
  220. }
  221. return latest, true
  222. }
  223. func isMemoryUnlimited(v uint64) bool {
  224. // Size after which we consider memory to be "unlimited". This is not
  225. // MaxInt64 due to rounding by the kernel.
  226. // TODO: cadvisor should export this https://github.com/google/cadvisor/blob/master/metrics/prometheus.go#L596
  227. const maxMemorySize = uint64(1 << 62)
  228. return v > maxMemorySize
  229. }
  230. // getCgroupInfo returns the information of the container with the specified
  231. // containerName from cadvisor.
  232. func getCgroupInfo(cadvisor cadvisor.Interface, containerName string, updateStats bool) (*cadvisorapiv2.ContainerInfo, error) {
  233. var maxAge *time.Duration
  234. if updateStats {
  235. age := 0 * time.Second
  236. maxAge = &age
  237. }
  238. infoMap, err := cadvisor.ContainerInfoV2(containerName, cadvisorapiv2.RequestOptions{
  239. IdType: cadvisorapiv2.TypeName,
  240. Count: 2, // 2 samples are needed to compute "instantaneous" CPU
  241. Recursive: false,
  242. MaxAge: maxAge,
  243. })
  244. if err != nil {
  245. return nil, fmt.Errorf("failed to get container info for %q: %v", containerName, err)
  246. }
  247. if len(infoMap) != 1 {
  248. return nil, fmt.Errorf("unexpected number of containers: %v", len(infoMap))
  249. }
  250. info := infoMap[containerName]
  251. return &info, nil
  252. }
  253. // getCgroupStats returns the latest stats of the container having the
  254. // specified containerName from cadvisor.
  255. func getCgroupStats(cadvisor cadvisor.Interface, containerName string, updateStats bool) (*cadvisorapiv2.ContainerStats, error) {
  256. info, err := getCgroupInfo(cadvisor, containerName, updateStats)
  257. if err != nil {
  258. return nil, err
  259. }
  260. stats, found := latestContainerStats(info)
  261. if !found {
  262. return nil, fmt.Errorf("failed to get latest stats from container info for %q", containerName)
  263. }
  264. return stats, nil
  265. }
  266. func buildLogsStats(cstat *cadvisorapiv2.ContainerStats, rootFs *cadvisorapiv2.FsInfo) *statsapi.FsStats {
  267. fsStats := &statsapi.FsStats{
  268. Time: metav1.NewTime(cstat.Timestamp),
  269. AvailableBytes: &rootFs.Available,
  270. CapacityBytes: &rootFs.Capacity,
  271. InodesFree: rootFs.InodesFree,
  272. Inodes: rootFs.Inodes,
  273. }
  274. if rootFs.Inodes != nil && rootFs.InodesFree != nil {
  275. logsInodesUsed := *rootFs.Inodes - *rootFs.InodesFree
  276. fsStats.InodesUsed = &logsInodesUsed
  277. }
  278. return fsStats
  279. }
  280. func buildRootfsStats(cstat *cadvisorapiv2.ContainerStats, imageFs *cadvisorapiv2.FsInfo) *statsapi.FsStats {
  281. return &statsapi.FsStats{
  282. Time: metav1.NewTime(cstat.Timestamp),
  283. AvailableBytes: &imageFs.Available,
  284. CapacityBytes: &imageFs.Capacity,
  285. InodesFree: imageFs.InodesFree,
  286. Inodes: imageFs.Inodes,
  287. }
  288. }
  289. func getUint64Value(value *uint64) uint64 {
  290. if value == nil {
  291. return 0
  292. }
  293. return *value
  294. }
  295. func uint64Ptr(i uint64) *uint64 {
  296. return &i
  297. }
  298. func calcEphemeralStorage(containers []statsapi.ContainerStats, volumes []statsapi.VolumeStats, rootFsInfo *cadvisorapiv2.FsInfo,
  299. podLogStats *statsapi.FsStats, isCRIStatsProvider bool) *statsapi.FsStats {
  300. result := &statsapi.FsStats{
  301. Time: metav1.NewTime(rootFsInfo.Timestamp),
  302. AvailableBytes: &rootFsInfo.Available,
  303. CapacityBytes: &rootFsInfo.Capacity,
  304. InodesFree: rootFsInfo.InodesFree,
  305. Inodes: rootFsInfo.Inodes,
  306. }
  307. for _, container := range containers {
  308. addContainerUsage(result, &container, isCRIStatsProvider)
  309. }
  310. for _, volume := range volumes {
  311. result.UsedBytes = addUsage(result.UsedBytes, volume.FsStats.UsedBytes)
  312. result.InodesUsed = addUsage(result.InodesUsed, volume.InodesUsed)
  313. result.Time = maxUpdateTime(&result.Time, &volume.FsStats.Time)
  314. }
  315. if podLogStats != nil {
  316. result.UsedBytes = addUsage(result.UsedBytes, podLogStats.UsedBytes)
  317. result.InodesUsed = addUsage(result.InodesUsed, podLogStats.InodesUsed)
  318. result.Time = maxUpdateTime(&result.Time, &podLogStats.Time)
  319. }
  320. return result
  321. }
  322. func addContainerUsage(stat *statsapi.FsStats, container *statsapi.ContainerStats, isCRIStatsProvider bool) {
  323. if rootFs := container.Rootfs; rootFs != nil {
  324. stat.Time = maxUpdateTime(&stat.Time, &rootFs.Time)
  325. stat.InodesUsed = addUsage(stat.InodesUsed, rootFs.InodesUsed)
  326. stat.UsedBytes = addUsage(stat.UsedBytes, rootFs.UsedBytes)
  327. if logs := container.Logs; logs != nil {
  328. stat.UsedBytes = addUsage(stat.UsedBytes, logs.UsedBytes)
  329. // We have accurate container log inode usage for CRI stats provider.
  330. if isCRIStatsProvider {
  331. stat.InodesUsed = addUsage(stat.InodesUsed, logs.InodesUsed)
  332. }
  333. stat.Time = maxUpdateTime(&stat.Time, &logs.Time)
  334. }
  335. }
  336. }
  337. func maxUpdateTime(first, second *metav1.Time) metav1.Time {
  338. if first.Before(second) {
  339. return *second
  340. }
  341. return *first
  342. }
  343. func addUsage(first, second *uint64) *uint64 {
  344. if first == nil {
  345. return second
  346. } else if second == nil {
  347. return first
  348. }
  349. total := *first + *second
  350. return &total
  351. }