targetscraper_test.go 18 KB

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