helper.go 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. // Copyright 2019 Yunion
  2. //
  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. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package stats
  15. import (
  16. "fmt"
  17. "regexp"
  18. "strconv"
  19. "strings"
  20. "time"
  21. cadvisorapiv1 "github.com/google/cadvisor/info/v1"
  22. info "github.com/google/cadvisor/info/v1"
  23. cadvisorapiv2 "github.com/google/cadvisor/info/v2"
  24. "github.com/google/cadvisor/utils/sysfs"
  25. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  26. "k8s.io/klog/v2"
  27. "yunion.io/x/log"
  28. )
  29. // defaultNetworkInterfaceName is used for collectng network stats.
  30. // This logic relies on knowledge of the container runtime implementation and
  31. // is not reliable.
  32. const defaultNetworkInterfaceName = "eth0"
  33. func getUint64Value(value *uint64) uint64 {
  34. if value == nil {
  35. return 0
  36. }
  37. return *value
  38. }
  39. func uint64Ptr(i uint64) *uint64 {
  40. return &i
  41. }
  42. func cadvisorInfoToCPUandMemoryStats(info *cadvisorapiv2.ContainerInfo) (*CPUStats, *MemoryStats) {
  43. cstat, found := latestContainerStats(info)
  44. if !found {
  45. return nil, nil
  46. }
  47. var cpuStats *CPUStats
  48. var memoryStats *MemoryStats
  49. cpuStats = &CPUStats{
  50. Time: metav1.NewTime(cstat.Timestamp),
  51. UsageNanoCores: uint64Ptr(0),
  52. UsageCoreNanoSeconds: uint64Ptr(0),
  53. }
  54. if info.Spec.HasCpu {
  55. if cstat.CpuInst != nil {
  56. cpuStats.UsageNanoCores = &cstat.CpuInst.Usage.Total
  57. }
  58. if cstat.Cpu != nil {
  59. cpuStats.UsageCoreNanoSeconds = &cstat.Cpu.Usage.Total
  60. }
  61. }
  62. if info.Spec.HasMemory && cstat.Memory != nil {
  63. pageFaults := cstat.Memory.ContainerData.Pgfault
  64. majorPageFaults := cstat.Memory.ContainerData.Pgmajfault
  65. memoryStats = &MemoryStats{
  66. Time: metav1.NewTime(cstat.Timestamp),
  67. UsageBytes: &cstat.Memory.Usage,
  68. WorkingSetBytes: &cstat.Memory.WorkingSet,
  69. RSSBytes: &cstat.Memory.RSS,
  70. PageFaults: &pageFaults,
  71. MajorPageFaults: &majorPageFaults,
  72. }
  73. // availableBytes = memory limit (if known) - workingset
  74. if !isMemoryUnlimited(info.Spec.Memory.Limit) {
  75. availableBytes := info.Spec.Memory.Limit - cstat.Memory.WorkingSet
  76. memoryStats.AvailableBytes = &availableBytes
  77. }
  78. } else {
  79. memoryStats = &MemoryStats{
  80. Time: metav1.NewTime(cstat.Timestamp),
  81. WorkingSetBytes: uint64Ptr(0),
  82. }
  83. }
  84. return cpuStats, memoryStats
  85. }
  86. // latestContainerStats returns the latest container stats from cadvisor, or nil if none exist
  87. func latestContainerStats(info *cadvisorapiv2.ContainerInfo) (*cadvisorapiv2.ContainerStats, bool) {
  88. stats := info.Stats
  89. if len(stats) < 1 {
  90. return nil, false
  91. }
  92. latest := stats[len(stats)-1]
  93. if latest == nil {
  94. return nil, false
  95. }
  96. return latest, true
  97. }
  98. func isMemoryUnlimited(v uint64) bool {
  99. // Size after which we consider memory to be "unlimited". This is not
  100. // MaxInt64 due to rounding by the kernel.
  101. // TODO: cadvisor should export this https://github.com/google/cadvisor/blob/master/metrics/prometheus.go#L596
  102. const maxMemorySize = uint64(1 << 62)
  103. return v > maxMemorySize
  104. }
  105. // cadvisorInfoToNetworkStats returns the statsapi.NetworkStats converted from
  106. // the container info from cadvisor.
  107. func cadvisorInfoToNetworkStats(info *cadvisorapiv2.ContainerInfo) *NetworkStats {
  108. if !info.Spec.HasNetwork {
  109. return nil
  110. }
  111. cstat, found := latestContainerStats(info)
  112. if !found {
  113. return nil
  114. }
  115. if cstat.Network == nil {
  116. return nil
  117. }
  118. iStats := NetworkStats{
  119. Time: metav1.NewTime(cstat.Timestamp),
  120. }
  121. for i := range cstat.Network.Interfaces {
  122. inter := cstat.Network.Interfaces[i]
  123. iStat := InterfaceStats{
  124. Name: inter.Name,
  125. RxBytes: &inter.RxBytes,
  126. RxErrors: &inter.RxErrors,
  127. TxBytes: &inter.TxBytes,
  128. TxErrors: &inter.TxErrors,
  129. }
  130. if inter.Name == defaultNetworkInterfaceName {
  131. iStats.InterfaceStats = iStat
  132. }
  133. iStats.Interfaces = append(iStats.Interfaces, iStat)
  134. }
  135. return &iStats
  136. }
  137. // cadvisorInfoToUserDefinedMetrics returns the statsapi.UserDefinedMetric
  138. // converted from the container info from cadvisor.
  139. func cadvisorInfoToUserDefinedMetrics(info *cadvisorapiv2.ContainerInfo) []UserDefinedMetric {
  140. type specVal struct {
  141. ref UserDefinedMetricDescriptor
  142. valType cadvisorapiv1.DataType
  143. time time.Time
  144. value float64
  145. }
  146. udmMap := map[string]*specVal{}
  147. for _, spec := range info.Spec.CustomMetrics {
  148. udmMap[spec.Name] = &specVal{
  149. ref: UserDefinedMetricDescriptor{
  150. Name: spec.Name,
  151. Type: UserDefinedMetricType(spec.Type),
  152. Units: spec.Units,
  153. },
  154. valType: spec.Format,
  155. }
  156. }
  157. for _, stat := range info.Stats {
  158. for name, values := range stat.CustomMetrics {
  159. specVal, ok := udmMap[name]
  160. if !ok {
  161. klog.Warningf("spec for custom metric %q is missing from cAdvisor output. Spec: %+v, Metrics: %+v", name, info.Spec, stat.CustomMetrics)
  162. continue
  163. }
  164. for _, value := range values {
  165. // Pick the most recent value
  166. if value.Timestamp.Before(specVal.time) {
  167. continue
  168. }
  169. specVal.time = value.Timestamp
  170. specVal.value = value.FloatValue
  171. if specVal.valType == cadvisorapiv1.IntType {
  172. specVal.value = float64(value.IntValue)
  173. }
  174. }
  175. }
  176. }
  177. var udm []UserDefinedMetric
  178. for _, specVal := range udmMap {
  179. udm = append(udm, UserDefinedMetric{
  180. UserDefinedMetricDescriptor: specVal.ref,
  181. Time: metav1.NewTime(specVal.time),
  182. Value: specVal.value,
  183. })
  184. }
  185. return udm
  186. }
  187. func cadvisorInfoToProcessStats(info *cadvisorapiv2.ContainerInfo) *ProcessStats {
  188. cstat, found := latestContainerStats(info)
  189. if !found || cstat.Processes == nil {
  190. return nil
  191. }
  192. return &ProcessStats{
  193. ProcessCount: cstat.Processes.ProcessCount,
  194. FdCount: cstat.Processes.FdCount,
  195. SocketCount: cstat.Processes.SocketCount,
  196. ThreadsCurrent: cstat.Processes.ThreadsCurrent,
  197. ThreadsMax: cstat.Processes.ThreadsMax,
  198. }
  199. }
  200. func convertToDiskIoStats(cStats *cadvisorapiv1.DiskIoStats) DiskIoStats {
  201. if cStats == nil {
  202. return nil
  203. }
  204. diskInfos, err := GetBlockDeviceInfo(sysfs.NewRealSysFs())
  205. if err != nil {
  206. log.Warningf("get block device info: %v", err)
  207. return nil
  208. }
  209. result := make(map[string]*DiskIoStat)
  210. var (
  211. keyServiced = "service"
  212. keyServiceBytes = "serviceBytes"
  213. )
  214. infos := map[string][]cadvisorapiv1.PerDiskStats{
  215. keyServiced: cStats.IoServiced,
  216. keyServiceBytes: cStats.IoServiceBytes,
  217. }
  218. for key, info := range infos {
  219. isByte := key == keyServiceBytes
  220. for i := range info {
  221. svc := info[i]
  222. devName := svc.Device
  223. if devName == "" {
  224. // fill devName by major and minor number
  225. key := fmt.Sprintf("%d:%d", svc.Major, svc.Minor)
  226. disk, ok := diskInfos[key]
  227. if !ok {
  228. log.Warningf("not found disk by %s from %#v", key, diskInfos)
  229. continue
  230. }
  231. devName = fmt.Sprintf("/dev/%s", disk.Name)
  232. }
  233. diskResult, ok := result[devName]
  234. if !ok {
  235. diskResult = NewDiskIoStat(devName, svc.Stats, isByte)
  236. } else {
  237. diskResult.fillStats(svc.Stats, isByte)
  238. }
  239. result[devName] = diskResult
  240. }
  241. }
  242. return result
  243. }
  244. func cadvisorInfoToDiskIoStats(info *cadvisorapiv2.ContainerInfo) DiskIoStats {
  245. cstat, found := latestContainerStats(info)
  246. if !found || cstat.DiskIo == nil {
  247. return nil
  248. }
  249. return convertToDiskIoStats(cstat.DiskIo)
  250. }
  251. var schedulerRegExp = regexp.MustCompile(`.*\[(.*)\].*`)
  252. // Get information about block devices present on the system.
  253. // Uses the passed in system interface to retrieve the low level OS information.
  254. func GetBlockDeviceInfo(sysfs sysfs.SysFs) (map[string]info.DiskInfo, error) {
  255. disks, err := sysfs.GetBlockDevices()
  256. if err != nil {
  257. return nil, err
  258. }
  259. diskMap := make(map[string]info.DiskInfo)
  260. for _, disk := range disks {
  261. name := disk.Name()
  262. // Ignore non-disk devices.
  263. // TODO(rjnagal): Maybe just match hd, sd, loop, and dm prefixes.
  264. if strings.HasPrefix(name, "ram") || strings.HasPrefix(name, "sr") {
  265. continue
  266. }
  267. diskInfo := info.DiskInfo{
  268. Name: name,
  269. }
  270. dev, err := sysfs.GetBlockDeviceNumbers(name)
  271. if err != nil {
  272. return nil, err
  273. }
  274. n, err := fmt.Sscanf(dev, "%d:%d", &diskInfo.Major, &diskInfo.Minor)
  275. if err != nil || n != 2 {
  276. return nil, fmt.Errorf("could not parse device numbers from %s for device %s", dev, name)
  277. }
  278. out, err := sysfs.GetBlockDeviceSize(name)
  279. if err != nil {
  280. return nil, err
  281. }
  282. // Remove trailing newline before conversion.
  283. size, err := strconv.ParseUint(strings.TrimSpace(out), 10, 64)
  284. if err != nil {
  285. return nil, err
  286. }
  287. // size is in 512 bytes blocks.
  288. diskInfo.Size = size * 512
  289. diskInfo.Scheduler = "none"
  290. blkSched, err := sysfs.GetBlockDeviceScheduler(name)
  291. if err == nil {
  292. matches := schedulerRegExp.FindSubmatch([]byte(blkSched))
  293. if len(matches) >= 2 {
  294. diskInfo.Scheduler = string(matches[1])
  295. }
  296. }
  297. device := fmt.Sprintf("%d:%d", diskInfo.Major, diskInfo.Minor)
  298. diskMap[device] = diskInfo
  299. }
  300. return diskMap, nil
  301. }