summary_test.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455
  1. /*
  2. Copyright 2016 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. "testing"
  16. "time"
  17. "github.com/google/cadvisor/info/v1"
  18. "github.com/google/cadvisor/info/v2"
  19. fuzz "github.com/google/gofuzz"
  20. "github.com/stretchr/testify/assert"
  21. "k8s.io/kubernetes/pkg/api"
  22. "k8s.io/kubernetes/pkg/api/unversioned"
  23. kubestats "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/stats"
  24. "k8s.io/kubernetes/pkg/kubelet/cm"
  25. "k8s.io/kubernetes/pkg/kubelet/container"
  26. "k8s.io/kubernetes/pkg/kubelet/leaky"
  27. )
  28. const (
  29. // Offsets from seed value in generated container stats.
  30. offsetCPUUsageCores = iota
  31. offsetCPUUsageCoreSeconds
  32. offsetMemPageFaults
  33. offsetMemMajorPageFaults
  34. offsetMemUsageBytes
  35. offsetMemRSSBytes
  36. offsetMemWorkingSetBytes
  37. offsetNetRxBytes
  38. offsetNetRxErrors
  39. offsetNetTxBytes
  40. offsetNetTxErrors
  41. )
  42. var (
  43. timestamp = time.Now()
  44. creationTime = timestamp.Add(-5 * time.Minute)
  45. )
  46. func TestBuildSummary(t *testing.T) {
  47. node := api.Node{}
  48. node.Name = "FooNode"
  49. nodeConfig := cm.NodeConfig{
  50. RuntimeCgroupsName: "/docker-daemon",
  51. SystemCgroupsName: "/system",
  52. KubeletCgroupsName: "/kubelet",
  53. }
  54. const (
  55. namespace0 = "test0"
  56. namespace2 = "test2"
  57. )
  58. const (
  59. seedRoot = 0
  60. seedRuntime = 100
  61. seedKubelet = 200
  62. seedMisc = 300
  63. seedPod0Infra = 1000
  64. seedPod0Container0 = 2000
  65. seedPod0Container1 = 2001
  66. seedPod1Infra = 3000
  67. seedPod1Container = 4000
  68. seedPod2Infra = 5000
  69. seedPod2Container = 6000
  70. )
  71. const (
  72. pName0 = "pod0"
  73. pName1 = "pod1"
  74. pName2 = "pod0" // ensure pName2 conflicts with pName0, but is in a different namespace
  75. )
  76. const (
  77. cName00 = "c0"
  78. cName01 = "c1"
  79. cName10 = "c0" // ensure cName10 conflicts with cName02, but is in a different pod
  80. cName20 = "c1" // ensure cName20 conflicts with cName01, but is in a different pod + namespace
  81. )
  82. const (
  83. rootfsCapacity = uint64(10000000)
  84. rootfsAvailable = uint64(5000000)
  85. rootfsInodesFree = uint64(1000)
  86. rootfsInodes = uint64(2000)
  87. imagefsCapacity = uint64(20000000)
  88. imagefsAvailable = uint64(8000000)
  89. imagefsInodesFree = uint64(2000)
  90. imagefsInodes = uint64(4000)
  91. )
  92. prf0 := kubestats.PodReference{Name: pName0, Namespace: namespace0, UID: "UID" + pName0}
  93. prf1 := kubestats.PodReference{Name: pName1, Namespace: namespace0, UID: "UID" + pName1}
  94. prf2 := kubestats.PodReference{Name: pName2, Namespace: namespace2, UID: "UID" + pName2}
  95. infos := map[string]v2.ContainerInfo{
  96. "/": summaryTestContainerInfo(seedRoot, "", "", ""),
  97. "/docker-daemon": summaryTestContainerInfo(seedRuntime, "", "", ""),
  98. "/kubelet": summaryTestContainerInfo(seedKubelet, "", "", ""),
  99. "/system": summaryTestContainerInfo(seedMisc, "", "", ""),
  100. // Pod0 - Namespace0
  101. "/pod0-i": summaryTestContainerInfo(seedPod0Infra, pName0, namespace0, leaky.PodInfraContainerName),
  102. "/pod0-c0": summaryTestContainerInfo(seedPod0Container0, pName0, namespace0, cName00),
  103. "/pod0-c1": summaryTestContainerInfo(seedPod0Container1, pName0, namespace0, cName01),
  104. // Pod1 - Namespace0
  105. "/pod1-i": summaryTestContainerInfo(seedPod1Infra, pName1, namespace0, leaky.PodInfraContainerName),
  106. "/pod1-c0": summaryTestContainerInfo(seedPod1Container, pName1, namespace0, cName10),
  107. // Pod2 - Namespace2
  108. "/pod2-i": summaryTestContainerInfo(seedPod2Infra, pName2, namespace2, leaky.PodInfraContainerName),
  109. "/pod2-c0": summaryTestContainerInfo(seedPod2Container, pName2, namespace2, cName20),
  110. }
  111. freeRootfsInodes := rootfsInodesFree
  112. totalRootfsInodes := rootfsInodes
  113. rootfs := v2.FsInfo{
  114. Capacity: rootfsCapacity,
  115. Available: rootfsAvailable,
  116. InodesFree: &freeRootfsInodes,
  117. Inodes: &totalRootfsInodes,
  118. }
  119. freeImagefsInodes := imagefsInodesFree
  120. totalImagefsInodes := imagefsInodes
  121. imagefs := v2.FsInfo{
  122. Capacity: imagefsCapacity,
  123. Available: imagefsAvailable,
  124. InodesFree: &freeImagefsInodes,
  125. Inodes: &totalImagefsInodes,
  126. }
  127. // memory limit overrides for each container (used to test available bytes if a memory limit is known)
  128. memoryLimitOverrides := map[string]uint64{
  129. "/": uint64(1 << 30),
  130. "/pod2-c0": uint64(1 << 15),
  131. }
  132. for name, memoryLimitOverride := range memoryLimitOverrides {
  133. info, found := infos[name]
  134. if !found {
  135. t.Errorf("No container defined with name %v", name)
  136. }
  137. info.Spec.Memory.Limit = memoryLimitOverride
  138. infos[name] = info
  139. }
  140. sb := &summaryBuilder{
  141. newFsResourceAnalyzer(&MockStatsProvider{}, time.Minute*5), &node, nodeConfig, rootfs, imagefs, container.ImageStats{}, infos}
  142. summary, err := sb.build()
  143. assert.NoError(t, err)
  144. nodeStats := summary.Node
  145. assert.Equal(t, "FooNode", nodeStats.NodeName)
  146. assert.EqualValues(t, testTime(creationTime, seedRoot).Unix(), nodeStats.StartTime.Time.Unix())
  147. checkCPUStats(t, "Node", seedRoot, nodeStats.CPU)
  148. checkMemoryStats(t, "Node", seedRoot, infos["/"], nodeStats.Memory)
  149. checkNetworkStats(t, "Node", seedRoot, nodeStats.Network)
  150. systemSeeds := map[string]int{
  151. kubestats.SystemContainerRuntime: seedRuntime,
  152. kubestats.SystemContainerKubelet: seedKubelet,
  153. kubestats.SystemContainerMisc: seedMisc,
  154. }
  155. systemContainerToNodeCgroup := map[string]string{
  156. kubestats.SystemContainerRuntime: nodeConfig.RuntimeCgroupsName,
  157. kubestats.SystemContainerKubelet: nodeConfig.KubeletCgroupsName,
  158. kubestats.SystemContainerMisc: nodeConfig.SystemCgroupsName,
  159. }
  160. for _, sys := range nodeStats.SystemContainers {
  161. name := sys.Name
  162. info := infos[systemContainerToNodeCgroup[name]]
  163. seed, found := systemSeeds[name]
  164. if !found {
  165. t.Errorf("Unknown SystemContainer: %q", name)
  166. }
  167. assert.EqualValues(t, testTime(creationTime, seed).Unix(), sys.StartTime.Time.Unix(), name+".StartTime")
  168. checkCPUStats(t, name, seed, sys.CPU)
  169. checkMemoryStats(t, name, seed, info, sys.Memory)
  170. checkFsStats(t, rootfsCapacity, rootfsAvailable, totalRootfsInodes, rootfsInodesFree, sys.Logs)
  171. checkFsStats(t, imagefsCapacity, imagefsAvailable, totalImagefsInodes, imagefsInodesFree, sys.Rootfs)
  172. }
  173. assert.Equal(t, 3, len(summary.Pods))
  174. indexPods := make(map[kubestats.PodReference]kubestats.PodStats, len(summary.Pods))
  175. for _, pod := range summary.Pods {
  176. indexPods[pod.PodRef] = pod
  177. }
  178. // Validate Pod0 Results
  179. ps, found := indexPods[prf0]
  180. assert.True(t, found)
  181. assert.Len(t, ps.Containers, 2)
  182. indexCon := make(map[string]kubestats.ContainerStats, len(ps.Containers))
  183. for _, con := range ps.Containers {
  184. indexCon[con.Name] = con
  185. }
  186. con := indexCon[cName00]
  187. assert.EqualValues(t, testTime(creationTime, seedPod0Container0).Unix(), con.StartTime.Time.Unix())
  188. checkCPUStats(t, "Pod0Container0", seedPod0Container0, con.CPU)
  189. checkMemoryStats(t, "Pod0Conainer0", seedPod0Container0, infos["/pod0-c0"], con.Memory)
  190. con = indexCon[cName01]
  191. assert.EqualValues(t, testTime(creationTime, seedPod0Container1).Unix(), con.StartTime.Time.Unix())
  192. checkCPUStats(t, "Pod0Container1", seedPod0Container1, con.CPU)
  193. checkMemoryStats(t, "Pod0Container1", seedPod0Container1, infos["/pod0-c1"], con.Memory)
  194. assert.EqualValues(t, testTime(creationTime, seedPod0Infra).Unix(), ps.StartTime.Time.Unix())
  195. checkNetworkStats(t, "Pod0", seedPod0Infra, ps.Network)
  196. // Validate Pod1 Results
  197. ps, found = indexPods[prf1]
  198. assert.True(t, found)
  199. assert.Len(t, ps.Containers, 1)
  200. con = ps.Containers[0]
  201. assert.Equal(t, cName10, con.Name)
  202. checkCPUStats(t, "Pod1Container0", seedPod1Container, con.CPU)
  203. checkMemoryStats(t, "Pod1Container0", seedPod1Container, infos["/pod1-c0"], con.Memory)
  204. checkNetworkStats(t, "Pod1", seedPod1Infra, ps.Network)
  205. // Validate Pod2 Results
  206. ps, found = indexPods[prf2]
  207. assert.True(t, found)
  208. assert.Len(t, ps.Containers, 1)
  209. con = ps.Containers[0]
  210. assert.Equal(t, cName20, con.Name)
  211. checkCPUStats(t, "Pod2Container0", seedPod2Container, con.CPU)
  212. checkMemoryStats(t, "Pod2Container0", seedPod2Container, infos["/pod2-c0"], con.Memory)
  213. checkNetworkStats(t, "Pod2", seedPod2Infra, ps.Network)
  214. }
  215. func generateCustomMetricSpec() []v1.MetricSpec {
  216. f := fuzz.New().NilChance(0).Funcs(
  217. func(e *v1.MetricSpec, c fuzz.Continue) {
  218. c.Fuzz(&e.Name)
  219. switch c.Intn(3) {
  220. case 0:
  221. e.Type = v1.MetricGauge
  222. case 1:
  223. e.Type = v1.MetricCumulative
  224. case 2:
  225. e.Type = v1.MetricDelta
  226. }
  227. switch c.Intn(2) {
  228. case 0:
  229. e.Format = v1.IntType
  230. case 1:
  231. e.Format = v1.FloatType
  232. }
  233. c.Fuzz(&e.Units)
  234. })
  235. var ret []v1.MetricSpec
  236. f.Fuzz(&ret)
  237. return ret
  238. }
  239. func generateCustomMetrics(spec []v1.MetricSpec) map[string][]v1.MetricVal {
  240. ret := map[string][]v1.MetricVal{}
  241. for _, metricSpec := range spec {
  242. f := fuzz.New().NilChance(0).Funcs(
  243. func(e *v1.MetricVal, c fuzz.Continue) {
  244. switch metricSpec.Format {
  245. case v1.IntType:
  246. c.Fuzz(&e.IntValue)
  247. case v1.FloatType:
  248. c.Fuzz(&e.FloatValue)
  249. }
  250. })
  251. var metrics []v1.MetricVal
  252. f.Fuzz(&metrics)
  253. ret[metricSpec.Name] = metrics
  254. }
  255. return ret
  256. }
  257. func summaryTestContainerInfo(seed int, podName string, podNamespace string, containerName string) v2.ContainerInfo {
  258. labels := map[string]string{}
  259. if podName != "" {
  260. labels = map[string]string{
  261. "io.kubernetes.pod.name": podName,
  262. "io.kubernetes.pod.uid": "UID" + podName,
  263. "io.kubernetes.pod.namespace": podNamespace,
  264. "io.kubernetes.container.name": containerName,
  265. }
  266. }
  267. // by default, kernel will set memory.limit_in_bytes to 1 << 63 if not bounded
  268. unlimitedMemory := uint64(1 << 63)
  269. spec := v2.ContainerSpec{
  270. CreationTime: testTime(creationTime, seed),
  271. HasCpu: true,
  272. HasMemory: true,
  273. HasNetwork: true,
  274. Labels: labels,
  275. Memory: v2.MemorySpec{
  276. Limit: unlimitedMemory,
  277. },
  278. CustomMetrics: generateCustomMetricSpec(),
  279. }
  280. stats := v2.ContainerStats{
  281. Timestamp: testTime(timestamp, seed),
  282. Cpu: &v1.CpuStats{},
  283. CpuInst: &v2.CpuInstStats{},
  284. Memory: &v1.MemoryStats{
  285. Usage: uint64(seed + offsetMemUsageBytes),
  286. WorkingSet: uint64(seed + offsetMemWorkingSetBytes),
  287. RSS: uint64(seed + offsetMemRSSBytes),
  288. ContainerData: v1.MemoryStatsMemoryData{
  289. Pgfault: uint64(seed + offsetMemPageFaults),
  290. Pgmajfault: uint64(seed + offsetMemMajorPageFaults),
  291. },
  292. },
  293. Network: &v2.NetworkStats{
  294. Interfaces: []v1.InterfaceStats{{
  295. Name: "eth0",
  296. RxBytes: uint64(seed + offsetNetRxBytes),
  297. RxErrors: uint64(seed + offsetNetRxErrors),
  298. TxBytes: uint64(seed + offsetNetTxBytes),
  299. TxErrors: uint64(seed + offsetNetTxErrors),
  300. }, {
  301. Name: "cbr0",
  302. RxBytes: 100,
  303. RxErrors: 100,
  304. TxBytes: 100,
  305. TxErrors: 100,
  306. }},
  307. },
  308. CustomMetrics: generateCustomMetrics(spec.CustomMetrics),
  309. }
  310. stats.Cpu.Usage.Total = uint64(seed + offsetCPUUsageCoreSeconds)
  311. stats.CpuInst.Usage.Total = uint64(seed + offsetCPUUsageCores)
  312. return v2.ContainerInfo{
  313. Spec: spec,
  314. Stats: []*v2.ContainerStats{&stats},
  315. }
  316. }
  317. func testTime(base time.Time, seed int) time.Time {
  318. return base.Add(time.Duration(seed) * time.Second)
  319. }
  320. func checkNetworkStats(t *testing.T, label string, seed int, stats *kubestats.NetworkStats) {
  321. assert.NotNil(t, stats)
  322. assert.EqualValues(t, testTime(timestamp, seed).Unix(), stats.Time.Time.Unix(), label+".Net.Time")
  323. assert.EqualValues(t, seed+offsetNetRxBytes, *stats.RxBytes, label+".Net.RxBytes")
  324. assert.EqualValues(t, seed+offsetNetRxErrors, *stats.RxErrors, label+".Net.RxErrors")
  325. assert.EqualValues(t, seed+offsetNetTxBytes, *stats.TxBytes, label+".Net.TxBytes")
  326. assert.EqualValues(t, seed+offsetNetTxErrors, *stats.TxErrors, label+".Net.TxErrors")
  327. }
  328. func checkCPUStats(t *testing.T, label string, seed int, stats *kubestats.CPUStats) {
  329. assert.EqualValues(t, testTime(timestamp, seed).Unix(), stats.Time.Time.Unix(), label+".CPU.Time")
  330. assert.EqualValues(t, seed+offsetCPUUsageCores, *stats.UsageNanoCores, label+".CPU.UsageCores")
  331. assert.EqualValues(t, seed+offsetCPUUsageCoreSeconds, *stats.UsageCoreNanoSeconds, label+".CPU.UsageCoreSeconds")
  332. }
  333. func checkMemoryStats(t *testing.T, label string, seed int, info v2.ContainerInfo, stats *kubestats.MemoryStats) {
  334. assert.EqualValues(t, testTime(timestamp, seed).Unix(), stats.Time.Time.Unix(), label+".Mem.Time")
  335. assert.EqualValues(t, seed+offsetMemUsageBytes, *stats.UsageBytes, label+".Mem.UsageBytes")
  336. assert.EqualValues(t, seed+offsetMemWorkingSetBytes, *stats.WorkingSetBytes, label+".Mem.WorkingSetBytes")
  337. assert.EqualValues(t, seed+offsetMemRSSBytes, *stats.RSSBytes, label+".Mem.RSSBytes")
  338. assert.EqualValues(t, seed+offsetMemPageFaults, *stats.PageFaults, label+".Mem.PageFaults")
  339. assert.EqualValues(t, seed+offsetMemMajorPageFaults, *stats.MajorPageFaults, label+".Mem.MajorPageFaults")
  340. if !info.Spec.HasMemory || isMemoryUnlimited(info.Spec.Memory.Limit) {
  341. assert.Nil(t, stats.AvailableBytes, label+".Mem.AvailableBytes")
  342. } else {
  343. expected := info.Spec.Memory.Limit - *stats.WorkingSetBytes
  344. assert.EqualValues(t, expected, *stats.AvailableBytes, label+".Mem.AvailableBytes")
  345. }
  346. }
  347. func checkFsStats(t *testing.T, capacity uint64, Available uint64, inodes uint64, inodesFree uint64, fs *kubestats.FsStats) {
  348. assert.EqualValues(t, capacity, *fs.CapacityBytes)
  349. assert.EqualValues(t, Available, *fs.AvailableBytes)
  350. assert.EqualValues(t, inodesFree, *fs.InodesFree)
  351. assert.EqualValues(t, inodes, *fs.Inodes)
  352. }
  353. func TestCustomMetrics(t *testing.T) {
  354. spec := []v1.MetricSpec{
  355. {
  356. Name: "qos",
  357. Type: v1.MetricGauge,
  358. Format: v1.IntType,
  359. Units: "per second",
  360. },
  361. {
  362. Name: "cpuLoad",
  363. Type: v1.MetricCumulative,
  364. Format: v1.FloatType,
  365. Units: "count",
  366. },
  367. }
  368. timestamp1 := time.Now()
  369. timestamp2 := time.Now().Add(time.Minute)
  370. metrics := map[string][]v1.MetricVal{
  371. "qos": {
  372. {
  373. Timestamp: timestamp1,
  374. IntValue: 10,
  375. },
  376. {
  377. Timestamp: timestamp2,
  378. IntValue: 100,
  379. },
  380. },
  381. "cpuLoad": {
  382. {
  383. Timestamp: timestamp1,
  384. FloatValue: 1.2,
  385. },
  386. {
  387. Timestamp: timestamp2,
  388. FloatValue: 2.1,
  389. },
  390. },
  391. }
  392. cInfo := v2.ContainerInfo{
  393. Spec: v2.ContainerSpec{
  394. CustomMetrics: spec,
  395. },
  396. Stats: []*v2.ContainerStats{
  397. {
  398. CustomMetrics: metrics,
  399. },
  400. },
  401. }
  402. sb := &summaryBuilder{}
  403. assert.Contains(t, sb.containerInfoV2ToUserDefinedMetrics(&cInfo),
  404. kubestats.UserDefinedMetric{
  405. UserDefinedMetricDescriptor: kubestats.UserDefinedMetricDescriptor{
  406. Name: "qos",
  407. Type: kubestats.MetricGauge,
  408. Units: "per second",
  409. },
  410. Time: unversioned.NewTime(timestamp2),
  411. Value: 100,
  412. },
  413. kubestats.UserDefinedMetric{
  414. UserDefinedMetricDescriptor: kubestats.UserDefinedMetricDescriptor{
  415. Name: "cpuLoad",
  416. Type: kubestats.MetricCumulative,
  417. Units: "count",
  418. },
  419. Time: unversioned.NewTime(timestamp2),
  420. Value: 2.1,
  421. })
  422. }