targetscraper_test.go 17 KB

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