targetscraper_test.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516
  1. package scrape
  2. import (
  3. "testing"
  4. "time"
  5. "github.com/opencost/opencost/modules/collector-source/pkg/metric"
  6. "github.com/opencost/opencost/modules/collector-source/pkg/scrape/target"
  7. )
  8. const networkScape = `
  9. # HELP kubecost_pod_network_egress_bytes kubecost_pod_network_egress_bytes_total egressed byte counts by pod.
  10. # TYPE kubecost_pod_network_egress_bytes counter
  11. kubecost_pod_network_egress_bytes_total{pod_name="pod1",namespace="namespace1",internet="false",same_region="true",same_zone="true",service="service1"} 3127969647
  12. kubecost_pod_network_egress_bytes_total{pod_name="pod2",namespace="namespace1",internet="true",same_region="false",same_zone="false",service=""} 335188219
  13. # HELP kubecost_pod_network_ingress_bytes kubecost_pod_network_ingress_bytes_total ingressed byte counts by pod.
  14. # TYPE kubecost_pod_network_ingress_bytes counter
  15. kubecost_pod_network_ingress_bytes_total{pod_name="pod1",namespace="namespace1",internet="true",same_region="false",same_zone="false",service="service1"} 17941460
  16. kubecost_pod_network_ingress_bytes_total{pod_name="pod2",namespace="namespace1",internet="false",same_region="true",same_zone="false",service=""} 13948766
  17. # HELP kubecost_network_costs_parsed_entries kubecost_network_costs_parsed_entries total parsed conntrack entries.
  18. # TYPE kubecost_network_costs_parsed_entries gauge
  19. # HELP kubecost_network_costs_parse_time kubecost_network_costs_parse_time total time in milliseconds it took to parse conntrack entries.
  20. # TYPE kubecost_network_costs_parse_time gauge
  21. # EOF
  22. `
  23. const opencostScrape = `
  24. # HELP kubecost_cluster_management_cost kubecost_cluster_management_cost Hourly cost paid as a cluster management fee.
  25. # TYPE kubecost_cluster_management_cost gauge
  26. kubecost_cluster_management_cost{provisioner_name="GKE"} 0.1
  27. # HELP kubecost_network_zone_egress_cost kubecost_network_zone_egress_cost Total cost per GB egress across zones
  28. # TYPE kubecost_network_zone_egress_cost gauge
  29. kubecost_network_zone_egress_cost 0.01
  30. # HELP kubecost_network_region_egress_cost kubecost_network_region_egress_cost Total cost per GB egress across regions
  31. # TYPE kubecost_network_region_egress_cost gauge
  32. kubecost_network_region_egress_cost 0.01
  33. # HELP kubecost_network_internet_egress_cost kubecost_network_internet_egress_cost Total cost per GB of internet egress.
  34. # TYPE kubecost_network_internet_egress_cost gauge
  35. kubecost_network_internet_egress_cost 0.12
  36. # HELP pv_hourly_cost pv_hourly_cost Cost per GB per hour on a persistent disk
  37. # TYPE pv_hourly_cost gauge
  38. pv_hourly_cost{persistentvolume="pvc-1",provider_id="pvc-1",volumename="pvc-1"} 5.479452054794521e-05
  39. pv_hourly_cost{persistentvolume="pvc-2",provider_id="pvc-2",volumename="pvc-2"} 5.479452054794521e-05
  40. # HELP kubecost_load_balancer_cost kubecost_load_balancer_cost Hourly cost of load balancer
  41. # TYPE kubecost_load_balancer_cost gauge
  42. kubecost_load_balancer_cost{ingress_ip="127.0.0.1",namespace="namespace1",service_name="service1"} 0.025
  43. # HELP container_cpu_allocation container_cpu_allocation Percent of a single CPU used in a minute
  44. # TYPE container_cpu_allocation gauge
  45. # HELP node_total_hourly_cost node_total_hourly_cost Total node cost per hour
  46. # TYPE node_total_hourly_cost gauge
  47. node_total_hourly_cost{arch="amd64",instance="node1",instance_type="e2-standard-2",node="node1",provider_id="node1",region="region1"} 0.06631302438846588
  48. node_total_hourly_cost{arch="amd64",instance="node2",instance_type="e2-standard-2",node="node2",provider_id="node2",region="region1"} 0.06631302438846588
  49. # HELP node_cpu_hourly_cost node_cpu_hourly_cost hourly cost for each cpu on this node
  50. # TYPE node_cpu_hourly_cost gauge
  51. node_cpu_hourly_cost{arch="amd64",instance="node1",instance_type="e2-standard-2",node="node1",provider_id="node1",region="region1"} 0.021811590000000002
  52. node_cpu_hourly_cost{arch="amd64",instance="node2",instance_type="e2-standard-2",node="node2",provider_id="node2",region="region1"} 0.021811590000000002
  53. # HELP node_ram_hourly_cost node_ram_hourly_cost hourly cost for each gb of ram on this node
  54. # TYPE node_ram_hourly_cost gauge
  55. node_ram_hourly_cost{arch="amd64",instance="node1",instance_type="e2-standard-2",node="node1",provider_id="node1",region="region1"} 0.00292353
  56. node_ram_hourly_cost{arch="amd64",instance="node2",instance_type="e2-standard-2",node="node2",provider_id="node2",region="region1"} 0.00292353
  57. # HELP node_gpu_hourly_cost node_gpu_hourly_cost hourly cost for each gpu on this node
  58. # TYPE node_gpu_hourly_cost gauge
  59. node_gpu_hourly_cost{arch="amd64",instance="node1",instance_type="e2-standard-2",node="node1",provider_id="node1",region="region1"} 0
  60. node_gpu_hourly_cost{arch="amd64",instance="node2",instance_type="e2-standard-2",node="node2",provider_id="node2",region="region1"} 0
  61. # HELP node_gpu_count node_gpu_count count of gpu on this node
  62. # TYPE node_gpu_count gauge
  63. node_gpu_count{arch="amd64",instance="node1",instance_type="e2-standard-2",node="node1",provider_id="node1",region="region1"} 0
  64. node_gpu_count{arch="amd64",instance="node2",instance_type="e2-standard-2",node="node2",provider_id="node2",region="region1"} 0
  65. # HELP kubecost_node_is_spot kubecost_node_is_spot Cloud provider info about node preemptibility
  66. # TYPE kubecost_node_is_spot gauge
  67. kubecost_node_is_spot{arch="amd64",instance="node1",instance_type="e2-standard-2",node="node1",provider_id="node1",region="region1"} 0
  68. kubecost_node_is_spot{arch="amd64",instance="node2",instance_type="e2-standard-2",node="node2",provider_id="node2",region="region1"} 0
  69. # HELP ignore_fake_metric fake metric that the scrapper should ignore
  70. # TYPE ignore_fake_metric gauge
  71. ignore_fake_metric{container="container1",instance="node1",namespace="namespace1",node="node1",pod="pod1"} 0.02
  72. # HELP container_cpu_allocation container_cpu_allocation Percent of a single CPU used in a minute
  73. # TYPE container_cpu_allocation gauge
  74. container_cpu_allocation{container="container1",instance="node1",namespace="namespace1",node="node1",pod="pod1"} 0.02
  75. container_cpu_allocation{container="container2",instance="node2",namespace="namespace1",node="node2",pod="pod2"} 0.01
  76. # HELP container_memory_allocation_bytes container_memory_allocation_bytes Bytes of RAM used
  77. # TYPE container_memory_allocation_bytes gauge
  78. container_memory_allocation_bytes{container="container1",instance="node1",namespace="namespace1",node="node1",pod="pod1"} 1.1528192e+07
  79. container_memory_allocation_bytes{container="container2",instance="node2",namespace="namespace1",node="node2",pod="pod2"} 1e+07
  80. # HELP container_gpu_allocation container_gpu_allocation GPU used
  81. # TYPE container_gpu_allocation gauge
  82. container_gpu_allocation{container="container1",instance="node1",namespace="namespace1",node="node1",pod="pod1"} 0
  83. container_gpu_allocation{container="container2",instance="node2",namespace="namespace1",node="node2",pod="pod2"} 0
  84. # HELP pod_pvc_allocation pod_pvc_allocation Bytes used by a PVC attached to a pod
  85. # TYPE pod_pvc_allocation gauge
  86. pod_pvc_allocation{namespace="namespace1",persistentvolume="pvc-1",persistentvolumeclaim="pvc1",pod="pod1"} 3.4359738368e+10
  87. pod_pvc_allocation{namespace="namespace1",persistentvolume="pvc-2",persistentvolumeclaim="pvc2",pod="pod2"} 3.4359738368e+10
  88. `
  89. const dcgmScrape = `
  90. # HELP DCGM_FI_PROF_GR_ENGINE_ACTIVE Ratio of time the graphics engine is active.
  91. # TYPE DCGM_FI_PROF_GR_ENGINE_ACTIVE gauge
  92. DCGM_FI_PROF_GR_ENGINE_ACTIVE{gpu="0",UUID="GPU-1",pci_bus_id="00000000:00:0A.0",device="nvidia0",modelName="Tesla T4",Hostname="localhost"} 0.999999
  93. # HELP DCGM_FI_DEV_DEC_UTIL Decoder utilization (in %).
  94. # TYPE DCGM_FI_DEV_DEC_UTIL gauge
  95. DCGM_FI_DEV_DEC_UTIL{gpu="0",UUID="GPU-1",pci_bus_id="00000000:00:0A.0",device="nvidia0",modelName="Tesla T4",Hostname="localhost"} 0
  96. `
  97. func TestTargetScraper_Scrape(t *testing.T) {
  98. start1, _ := time.Parse(time.RFC3339, Start1Str)
  99. tests := []struct {
  100. name string
  101. scrapperFactory func(metric.MetricUpdater) *TargetScraper
  102. expected []metric.UpdateArgs
  103. }{
  104. {
  105. name: "Network Scrape",
  106. scrapperFactory: func(updater metric.MetricUpdater) *TargetScraper {
  107. return newNetworkTargetScraper(
  108. target.NewDefaultTargetProvider(target.NewStringTarget(networkScape)),
  109. updater,
  110. )
  111. },
  112. expected: []metric.UpdateArgs{
  113. {
  114. MetricName: KubecostPodNetworkEgressBytesTotal,
  115. Labels: map[string]string{
  116. "pod_name": "pod1",
  117. "namespace": "namespace1",
  118. "internet": "false",
  119. "same_region": "true",
  120. "same_zone": "true",
  121. "service": "service1",
  122. },
  123. Value: 3127969647,
  124. Timestamp: start1,
  125. },
  126. {
  127. MetricName: KubecostPodNetworkEgressBytesTotal,
  128. Labels: map[string]string{
  129. "pod_name": "pod2",
  130. "namespace": "namespace1",
  131. "internet": "true",
  132. "same_region": "false",
  133. "same_zone": "false",
  134. "service": "",
  135. },
  136. Value: 335188219,
  137. Timestamp: start1,
  138. },
  139. {
  140. MetricName: KubecostPodNetworkIngressBytesTotal,
  141. Labels: map[string]string{
  142. "pod_name": "pod1",
  143. "namespace": "namespace1",
  144. "internet": "true",
  145. "same_region": "false",
  146. "same_zone": "false",
  147. "service": "service1",
  148. },
  149. Value: 17941460,
  150. Timestamp: start1,
  151. },
  152. {
  153. MetricName: KubecostPodNetworkIngressBytesTotal,
  154. Labels: map[string]string{
  155. "pod_name": "pod2",
  156. "namespace": "namespace1",
  157. "internet": "false",
  158. "same_region": "true",
  159. "same_zone": "false",
  160. "service": "",
  161. },
  162. Value: 13948766,
  163. Timestamp: start1,
  164. },
  165. },
  166. },
  167. {
  168. name: "Opencost Metric",
  169. scrapperFactory: func(updater metric.MetricUpdater) *TargetScraper {
  170. return newOpencostTargetScraper(target.NewDefaultTargetProvider(target.NewStringTarget(opencostScrape)),
  171. updater,
  172. )
  173. },
  174. expected: []metric.UpdateArgs{
  175. {
  176. MetricName: KubecostClusterManagementCost,
  177. Labels: map[string]string{
  178. "provisioner_name": "GKE",
  179. },
  180. Value: 0.1,
  181. },
  182. {
  183. MetricName: KubecostNetworkZoneEgressCost,
  184. Labels: map[string]string{},
  185. Value: 0.01,
  186. },
  187. {
  188. MetricName: KubecostNetworkRegionEgressCost,
  189. Labels: map[string]string{},
  190. Value: 0.01,
  191. },
  192. {
  193. MetricName: KubecostNetworkInternetEgressCost,
  194. Labels: map[string]string{},
  195. Value: 0.12,
  196. },
  197. {
  198. MetricName: PVHourlyCost,
  199. Labels: map[string]string{
  200. "persistentvolume": "pvc-1",
  201. "provider_id": "pvc-1",
  202. "volumename": "pvc-1",
  203. },
  204. Value: 5.479452054794521e-05,
  205. },
  206. {
  207. MetricName: PVHourlyCost,
  208. Labels: map[string]string{
  209. "persistentvolume": "pvc-2",
  210. "provider_id": "pvc-2",
  211. "volumename": "pvc-2",
  212. },
  213. Value: 5.479452054794521e-05,
  214. },
  215. {
  216. MetricName: KubecostLoadBalancerCost,
  217. Labels: map[string]string{
  218. "ingress_ip": "127.0.0.1",
  219. "namespace": "namespace1",
  220. "service_name": "service1",
  221. },
  222. Value: 0.025,
  223. },
  224. {
  225. MetricName: NodeTotalHourlyCost,
  226. Labels: map[string]string{
  227. "arch": "amd64",
  228. "instance": "node1",
  229. "instance_type": "e2-standard-2",
  230. "node": "node1",
  231. "provider_id": "node1",
  232. "region": "region1",
  233. },
  234. Value: 0.06631302438846588,
  235. },
  236. {
  237. MetricName: NodeTotalHourlyCost,
  238. Labels: map[string]string{
  239. "arch": "amd64",
  240. "instance": "node2",
  241. "instance_type": "e2-standard-2",
  242. "node": "node2",
  243. "provider_id": "node2",
  244. "region": "region1",
  245. },
  246. Value: 0.06631302438846588,
  247. },
  248. {
  249. MetricName: NodeCPUHourlyCost,
  250. Labels: map[string]string{
  251. "arch": "amd64",
  252. "instance": "node1",
  253. "instance_type": "e2-standard-2",
  254. "node": "node1",
  255. "provider_id": "node1",
  256. "region": "region1",
  257. },
  258. Value: 0.021811590000000002,
  259. },
  260. {
  261. MetricName: NodeCPUHourlyCost,
  262. Labels: map[string]string{
  263. "arch": "amd64",
  264. "instance": "node2",
  265. "instance_type": "e2-standard-2",
  266. "node": "node2",
  267. "provider_id": "node2",
  268. "region": "region1",
  269. },
  270. Value: 0.021811590000000002,
  271. },
  272. {
  273. MetricName: NodeRAMHourlyCost,
  274. Labels: map[string]string{
  275. "arch": "amd64",
  276. "instance": "node1",
  277. "instance_type": "e2-standard-2",
  278. "node": "node1",
  279. "provider_id": "node1",
  280. "region": "region1",
  281. },
  282. Value: 0.00292353,
  283. },
  284. {
  285. MetricName: NodeRAMHourlyCost,
  286. Labels: map[string]string{
  287. "arch": "amd64",
  288. "instance": "node2",
  289. "instance_type": "e2-standard-2",
  290. "node": "node2",
  291. "provider_id": "node2",
  292. "region": "region1",
  293. },
  294. Value: 0.00292353,
  295. },
  296. {
  297. MetricName: NodeGPUHourlyCost,
  298. Labels: map[string]string{
  299. "arch": "amd64",
  300. "instance": "node1",
  301. "instance_type": "e2-standard-2",
  302. "node": "node1",
  303. "provider_id": "node1",
  304. "region": "region1",
  305. },
  306. Value: 0,
  307. },
  308. {
  309. MetricName: NodeGPUHourlyCost,
  310. Labels: map[string]string{
  311. "arch": "amd64",
  312. "instance": "node2",
  313. "instance_type": "e2-standard-2",
  314. "node": "node2",
  315. "provider_id": "node2",
  316. "region": "region1",
  317. },
  318. Value: 0,
  319. },
  320. {
  321. MetricName: NodeGPUCount,
  322. Labels: map[string]string{
  323. "arch": "amd64",
  324. "instance": "node1",
  325. "instance_type": "e2-standard-2",
  326. "node": "node1",
  327. "provider_id": "node1",
  328. "region": "region1",
  329. },
  330. Value: 0,
  331. },
  332. {
  333. MetricName: NodeGPUCount,
  334. Labels: map[string]string{
  335. "arch": "amd64",
  336. "instance": "node2",
  337. "instance_type": "e2-standard-2",
  338. "node": "node2",
  339. "provider_id": "node2",
  340. "region": "region1",
  341. },
  342. Value: 0,
  343. },
  344. {
  345. MetricName: KubecostNodeIsSpot,
  346. Labels: map[string]string{
  347. "arch": "amd64",
  348. "instance": "node1",
  349. "instance_type": "e2-standard-2",
  350. "node": "node1",
  351. "provider_id": "node1",
  352. "region": "region1",
  353. },
  354. Value: 0,
  355. },
  356. {
  357. MetricName: KubecostNodeIsSpot,
  358. Labels: map[string]string{
  359. "arch": "amd64",
  360. "instance": "node2",
  361. "instance_type": "e2-standard-2",
  362. "node": "node2",
  363. "provider_id": "node2",
  364. "region": "region1",
  365. },
  366. Value: 0,
  367. },
  368. {
  369. MetricName: ContainerCPUAllocation,
  370. Labels: map[string]string{
  371. "container": "container1",
  372. "instance": "node1",
  373. "namespace": "namespace1",
  374. "node": "node1",
  375. "pod": "pod1",
  376. },
  377. Value: 0.02,
  378. },
  379. {
  380. MetricName: ContainerCPUAllocation,
  381. Labels: map[string]string{
  382. "container": "container2",
  383. "instance": "node2",
  384. "namespace": "namespace1",
  385. "node": "node2",
  386. "pod": "pod2",
  387. },
  388. Value: 0.01,
  389. },
  390. {
  391. MetricName: ContainerMemoryAllocationBytes,
  392. Labels: map[string]string{
  393. "container": "container1",
  394. "instance": "node1",
  395. "namespace": "namespace1",
  396. "node": "node1",
  397. "pod": "pod1",
  398. },
  399. Value: 1.1528192e+07,
  400. },
  401. {
  402. MetricName: ContainerMemoryAllocationBytes,
  403. Labels: map[string]string{
  404. "container": "container2",
  405. "instance": "node2",
  406. "namespace": "namespace1",
  407. "node": "node2",
  408. "pod": "pod2",
  409. },
  410. Value: 1e+07,
  411. },
  412. {
  413. MetricName: ContainerGPUAllocation,
  414. Labels: map[string]string{
  415. "container": "container1",
  416. "instance": "node1",
  417. "namespace": "namespace1",
  418. "node": "node1",
  419. "pod": "pod1",
  420. },
  421. Value: 0,
  422. },
  423. {
  424. MetricName: ContainerGPUAllocation,
  425. Labels: map[string]string{
  426. "container": "container2",
  427. "instance": "node2",
  428. "namespace": "namespace1",
  429. "node": "node2",
  430. "pod": "pod2",
  431. },
  432. Value: 0,
  433. },
  434. {
  435. MetricName: PodPVCAllocation,
  436. Labels: map[string]string{
  437. "namespace": "namespace1",
  438. "persistentvolume": "pvc-1",
  439. "persistentvolumeclaim": "pvc1",
  440. "pod": "pod1",
  441. },
  442. Value: 3.4359738368e+10,
  443. },
  444. {
  445. MetricName: PodPVCAllocation,
  446. Labels: map[string]string{
  447. "namespace": "namespace1",
  448. "persistentvolume": "pvc-2",
  449. "persistentvolumeclaim": "pvc2",
  450. "pod": "pod2",
  451. },
  452. Value: 3.4359738368e+10,
  453. },
  454. },
  455. },
  456. {
  457. name: "GPU Metric",
  458. scrapperFactory: func(updater metric.MetricUpdater) *TargetScraper {
  459. return newDCGMTargetScraper(target.NewDefaultTargetProvider(target.NewStringTarget(dcgmScrape)),
  460. updater,
  461. )
  462. },
  463. expected: []metric.UpdateArgs{
  464. {
  465. MetricName: DCGMFIPROFGRENGINEACTIVE,
  466. Labels: map[string]string{
  467. "gpu": "0",
  468. "UUID": "GPU-1",
  469. "pci_bus_id": "00000000:00:0A.0",
  470. "device": "nvidia0",
  471. "modelName": "Tesla T4",
  472. "Hostname": "localhost",
  473. },
  474. Value: 0.999999,
  475. },
  476. {
  477. MetricName: DCGMFIDEVDECUTIL,
  478. Labels: map[string]string{
  479. "gpu": "0",
  480. "UUID": "GPU-1",
  481. "pci_bus_id": "00000000:00:0A.0",
  482. "device": "nvidia0",
  483. "modelName": "Tesla T4",
  484. "Hostname": "localhost",
  485. },
  486. Value: 0,
  487. },
  488. },
  489. },
  490. }
  491. for _, tt := range tests {
  492. t.Run(tt.name, func(t *testing.T) {
  493. updateRecorder := metric.ArgRecordUpdater{}
  494. scrapper := tt.scrapperFactory(&updateRecorder)
  495. scrapper.Scrape()
  496. if len(updateRecorder.UpdateArgs) != len(tt.expected) {
  497. t.Errorf("Expected result length of %d, got %d", len(tt.expected), len(updateRecorder.UpdateArgs))
  498. }
  499. for i, expected := range tt.expected {
  500. updateArg := updateRecorder.UpdateArgs[i]
  501. err := expected.ValueEquals(updateArg)
  502. if err != nil {
  503. t.Errorf("Result did not match expected at index %d: %s", i, err.Error())
  504. }
  505. }
  506. })
  507. }
  508. }