targetscraper_test.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497
  1. package scrape
  2. import (
  3. "reflect"
  4. "testing"
  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. tests := []struct {
  99. name string
  100. scrapeText string
  101. targetScraperFactory func(provider target.TargetProvider) *TargetScraper
  102. expected []metric.Update
  103. }{
  104. {
  105. name: "Network Scrape",
  106. scrapeText: networkScape,
  107. targetScraperFactory: newNetworkTargetScraper,
  108. expected: []metric.Update{
  109. {
  110. Name: KubecostPodNetworkEgressBytesTotal,
  111. Labels: map[string]string{
  112. "pod_name": "pod1",
  113. "namespace": "namespace1",
  114. "internet": "false",
  115. "same_region": "true",
  116. "same_zone": "true",
  117. "service": "service1",
  118. },
  119. Value: 3127969647,
  120. },
  121. {
  122. Name: KubecostPodNetworkEgressBytesTotal,
  123. Labels: map[string]string{
  124. "pod_name": "pod2",
  125. "namespace": "namespace1",
  126. "internet": "true",
  127. "same_region": "false",
  128. "same_zone": "false",
  129. "service": "",
  130. },
  131. Value: 335188219,
  132. },
  133. {
  134. Name: KubecostPodNetworkIngressBytesTotal,
  135. Labels: map[string]string{
  136. "pod_name": "pod1",
  137. "namespace": "namespace1",
  138. "internet": "true",
  139. "same_region": "false",
  140. "same_zone": "false",
  141. "service": "service1",
  142. },
  143. Value: 17941460,
  144. },
  145. {
  146. Name: KubecostPodNetworkIngressBytesTotal,
  147. Labels: map[string]string{
  148. "pod_name": "pod2",
  149. "namespace": "namespace1",
  150. "internet": "false",
  151. "same_region": "true",
  152. "same_zone": "false",
  153. "service": "",
  154. },
  155. Value: 13948766,
  156. },
  157. },
  158. },
  159. {
  160. name: "Opencost Metric",
  161. scrapeText: opencostScrape,
  162. targetScraperFactory: newOpencostTargetScraper,
  163. expected: []metric.Update{
  164. {
  165. Name: KubecostClusterManagementCost,
  166. Labels: map[string]string{
  167. "provisioner_name": "GKE",
  168. },
  169. Value: 0.1,
  170. },
  171. {
  172. Name: KubecostNetworkZoneEgressCost,
  173. Value: 0.01,
  174. },
  175. {
  176. Name: KubecostNetworkRegionEgressCost,
  177. Value: 0.01,
  178. },
  179. {
  180. Name: KubecostNetworkInternetEgressCost,
  181. Value: 0.12,
  182. },
  183. {
  184. Name: PVHourlyCost,
  185. Labels: map[string]string{
  186. "persistentvolume": "pvc-1",
  187. "provider_id": "pvc-1",
  188. "volumename": "pvc-1",
  189. },
  190. Value: 5.479452054794521e-05,
  191. },
  192. {
  193. Name: PVHourlyCost,
  194. Labels: map[string]string{
  195. "persistentvolume": "pvc-2",
  196. "provider_id": "pvc-2",
  197. "volumename": "pvc-2",
  198. },
  199. Value: 5.479452054794521e-05,
  200. },
  201. {
  202. Name: KubecostLoadBalancerCost,
  203. Labels: map[string]string{
  204. "ingress_ip": "127.0.0.1",
  205. "namespace": "namespace1",
  206. "service_name": "service1",
  207. },
  208. Value: 0.025,
  209. },
  210. {
  211. Name: NodeTotalHourlyCost,
  212. Labels: map[string]string{
  213. "arch": "amd64",
  214. "instance": "node1",
  215. "instance_type": "e2-standard-2",
  216. "node": "node1",
  217. "provider_id": "node1",
  218. "region": "region1",
  219. },
  220. Value: 0.06631302438846588,
  221. },
  222. {
  223. Name: NodeTotalHourlyCost,
  224. Labels: map[string]string{
  225. "arch": "amd64",
  226. "instance": "node2",
  227. "instance_type": "e2-standard-2",
  228. "node": "node2",
  229. "provider_id": "node2",
  230. "region": "region1",
  231. },
  232. Value: 0.06631302438846588,
  233. },
  234. {
  235. Name: NodeCPUHourlyCost,
  236. Labels: map[string]string{
  237. "arch": "amd64",
  238. "instance": "node1",
  239. "instance_type": "e2-standard-2",
  240. "node": "node1",
  241. "provider_id": "node1",
  242. "region": "region1",
  243. },
  244. Value: 0.021811590000000002,
  245. },
  246. {
  247. Name: NodeCPUHourlyCost,
  248. Labels: map[string]string{
  249. "arch": "amd64",
  250. "instance": "node2",
  251. "instance_type": "e2-standard-2",
  252. "node": "node2",
  253. "provider_id": "node2",
  254. "region": "region1",
  255. },
  256. Value: 0.021811590000000002,
  257. },
  258. {
  259. Name: NodeRAMHourlyCost,
  260. Labels: map[string]string{
  261. "arch": "amd64",
  262. "instance": "node1",
  263. "instance_type": "e2-standard-2",
  264. "node": "node1",
  265. "provider_id": "node1",
  266. "region": "region1",
  267. },
  268. Value: 0.00292353,
  269. },
  270. {
  271. Name: NodeRAMHourlyCost,
  272. Labels: map[string]string{
  273. "arch": "amd64",
  274. "instance": "node2",
  275. "instance_type": "e2-standard-2",
  276. "node": "node2",
  277. "provider_id": "node2",
  278. "region": "region1",
  279. },
  280. Value: 0.00292353,
  281. },
  282. {
  283. Name: NodeGPUHourlyCost,
  284. Labels: map[string]string{
  285. "arch": "amd64",
  286. "instance": "node1",
  287. "instance_type": "e2-standard-2",
  288. "node": "node1",
  289. "provider_id": "node1",
  290. "region": "region1",
  291. },
  292. Value: 0,
  293. },
  294. {
  295. Name: NodeGPUHourlyCost,
  296. Labels: map[string]string{
  297. "arch": "amd64",
  298. "instance": "node2",
  299. "instance_type": "e2-standard-2",
  300. "node": "node2",
  301. "provider_id": "node2",
  302. "region": "region1",
  303. },
  304. Value: 0,
  305. },
  306. {
  307. Name: NodeGPUCount,
  308. Labels: map[string]string{
  309. "arch": "amd64",
  310. "instance": "node1",
  311. "instance_type": "e2-standard-2",
  312. "node": "node1",
  313. "provider_id": "node1",
  314. "region": "region1",
  315. },
  316. Value: 0,
  317. },
  318. {
  319. Name: NodeGPUCount,
  320. Labels: map[string]string{
  321. "arch": "amd64",
  322. "instance": "node2",
  323. "instance_type": "e2-standard-2",
  324. "node": "node2",
  325. "provider_id": "node2",
  326. "region": "region1",
  327. },
  328. Value: 0,
  329. },
  330. {
  331. Name: KubecostNodeIsSpot,
  332. Labels: map[string]string{
  333. "arch": "amd64",
  334. "instance": "node1",
  335. "instance_type": "e2-standard-2",
  336. "node": "node1",
  337. "provider_id": "node1",
  338. "region": "region1",
  339. },
  340. Value: 0,
  341. },
  342. {
  343. Name: KubecostNodeIsSpot,
  344. Labels: map[string]string{
  345. "arch": "amd64",
  346. "instance": "node2",
  347. "instance_type": "e2-standard-2",
  348. "node": "node2",
  349. "provider_id": "node2",
  350. "region": "region1",
  351. },
  352. Value: 0,
  353. },
  354. {
  355. Name: ContainerCPUAllocation,
  356. Labels: map[string]string{
  357. "container": "container1",
  358. "instance": "node1",
  359. "namespace": "namespace1",
  360. "node": "node1",
  361. "pod": "pod1",
  362. },
  363. Value: 0.02,
  364. },
  365. {
  366. Name: ContainerCPUAllocation,
  367. Labels: map[string]string{
  368. "container": "container2",
  369. "instance": "node2",
  370. "namespace": "namespace1",
  371. "node": "node2",
  372. "pod": "pod2",
  373. },
  374. Value: 0.01,
  375. },
  376. {
  377. Name: ContainerMemoryAllocationBytes,
  378. Labels: map[string]string{
  379. "container": "container1",
  380. "instance": "node1",
  381. "namespace": "namespace1",
  382. "node": "node1",
  383. "pod": "pod1",
  384. },
  385. Value: 1.1528192e+07,
  386. },
  387. {
  388. Name: ContainerMemoryAllocationBytes,
  389. Labels: map[string]string{
  390. "container": "container2",
  391. "instance": "node2",
  392. "namespace": "namespace1",
  393. "node": "node2",
  394. "pod": "pod2",
  395. },
  396. Value: 1e+07,
  397. },
  398. {
  399. Name: ContainerGPUAllocation,
  400. Labels: map[string]string{
  401. "container": "container1",
  402. "instance": "node1",
  403. "namespace": "namespace1",
  404. "node": "node1",
  405. "pod": "pod1",
  406. },
  407. Value: 0,
  408. },
  409. {
  410. Name: ContainerGPUAllocation,
  411. Labels: map[string]string{
  412. "container": "container2",
  413. "instance": "node2",
  414. "namespace": "namespace1",
  415. "node": "node2",
  416. "pod": "pod2",
  417. },
  418. Value: 0,
  419. },
  420. {
  421. Name: PodPVCAllocation,
  422. Labels: map[string]string{
  423. "namespace": "namespace1",
  424. "persistentvolume": "pvc-1",
  425. "persistentvolumeclaim": "pvc1",
  426. "pod": "pod1",
  427. },
  428. Value: 3.4359738368e+10,
  429. },
  430. {
  431. Name: PodPVCAllocation,
  432. Labels: map[string]string{
  433. "namespace": "namespace1",
  434. "persistentvolume": "pvc-2",
  435. "persistentvolumeclaim": "pvc2",
  436. "pod": "pod2",
  437. },
  438. Value: 3.4359738368e+10,
  439. },
  440. },
  441. },
  442. {
  443. name: "GPU Metric",
  444. scrapeText: dcgmScrape,
  445. targetScraperFactory: newDCGMTargetScraper,
  446. expected: []metric.Update{
  447. {
  448. Name: DCGMFIPROFGRENGINEACTIVE,
  449. Labels: map[string]string{
  450. "gpu": "0",
  451. "UUID": "GPU-1",
  452. "pci_bus_id": "00000000:00:0A.0",
  453. "device": "nvidia0",
  454. "modelName": "Tesla T4",
  455. "Hostname": "localhost",
  456. },
  457. Value: 0.999999,
  458. },
  459. {
  460. Name: DCGMFIDEVDECUTIL,
  461. Labels: map[string]string{
  462. "gpu": "0",
  463. "UUID": "GPU-1",
  464. "pci_bus_id": "00000000:00:0A.0",
  465. "device": "nvidia0",
  466. "modelName": "Tesla T4",
  467. "Hostname": "localhost",
  468. },
  469. Value: 0,
  470. },
  471. },
  472. },
  473. }
  474. for _, tt := range tests {
  475. t.Run(tt.name, func(t *testing.T) {
  476. scraper := tt.targetScraperFactory(target.NewDefaultTargetProvider(target.NewStringTarget(tt.scrapeText)))
  477. scrapeResults := scraper.Scrape()
  478. if len(scrapeResults) != len(tt.expected) {
  479. t.Errorf("Expected result length of %d, got %d", len(tt.expected), len(scrapeResults))
  480. }
  481. for i, expected := range tt.expected {
  482. got := scrapeResults[i]
  483. if !reflect.DeepEqual(expected, got) {
  484. t.Errorf("Result did not match expected at index %d: got %v, want %v", i, got, expected)
  485. }
  486. }
  487. })
  488. }
  489. }