-
Notifications
You must be signed in to change notification settings - Fork 15
[feat] Network Costs Test #19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Manas23601
wants to merge
12
commits into
opencost:main
Choose a base branch
from
Manas23601:networkcost
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 8 commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
8644477
[feat] Network Costs
Manas23601 1a8cd56
remove unclosed tilde
Manas23601 bdcb084
run test in integration pipeline using bats
Manas23601 974807b
[Error] Fix Name of Test in bats
Manas23601 b233444
[feat] Network Zone Costs
Manas23601 39c1dc9
Edge case where all costs are below 1 cent
Manas23601 08b4800
Changed Variable name to a one that makes sense
Manas23601 290c12e
Two Similar Tests for Zone and Region Network Costs
Manas23601 6ff0fe0
Merge branch 'main' into networkcost
Manas23601 8e28d44
Merge branch 'main' into networkcost
Manas23601 3e905b1
merge main branch
Manas23601 86ec5e2
Merge branch 'networkcost' of github.com:Manas23601/opencost-integrat…
Manas23601 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| package main | ||
| package prometheus | ||
|
|
||
| import ( | ||
| "fmt" | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,195 @@ | ||
| package prometheus | ||
|
|
||
| // Description - Compares Network Costs from Prometheus and Allocation | ||
|
|
||
| import ( | ||
| // "fmt" | ||
| "github.com/opencost/opencost-integration-tests/pkg/api" | ||
| "github.com/opencost/opencost-integration-tests/pkg/prometheus" | ||
| "github.com/opencost/opencost-integration-tests/pkg/utils" | ||
| "slices" | ||
| "testing" | ||
| "time" | ||
| ) | ||
|
|
||
| const tolerance = 0.05 | ||
|
|
||
| func TestNetworkCosts(t *testing.T) { | ||
| apiObj := api.NewAPI() | ||
|
|
||
| testCases := []struct { | ||
| name string | ||
| window string | ||
| aggregate string | ||
| accumulate string | ||
| }{ | ||
| { | ||
| name: "Yesterday", | ||
| window: "24h", | ||
| aggregate: "namespace", | ||
| accumulate: "false", | ||
| }, | ||
| } | ||
|
|
||
| t.Logf("testCases: %v", testCases) | ||
|
|
||
| for _, tc := range testCases { | ||
| t.Run(tc.name, func(t *testing.T) { | ||
|
|
||
| // Any data that is in a "raw allocation only" is not valid in any | ||
| // sort of cumulative Allocation (like one that is added). | ||
|
|
||
| type NetworkCostsAggregate struct { | ||
| PromNetworkTransferBytes float64 | ||
| PromNetworkReceiveBytes float64 | ||
| Pods []string | ||
| AllocNetworkTransferBytes float64 | ||
| AllocNetworkReceiveBytes float64 | ||
| } | ||
| networkCostsNamespaceMap := make(map[string]*NetworkCostsAggregate) | ||
|
|
||
| queryEnd := time.Now().UTC().Truncate(time.Hour).Add(time.Hour) | ||
| endTime := queryEnd.Unix() | ||
| // Collect Namespace results from Prometheus | ||
| client := prometheus.NewClient() | ||
|
|
||
| //////////////////////////////////////////////////////////////////////////// | ||
| // Network Receive Bytes | ||
|
|
||
| // sum(increase(container_network_receive_bytes_total{pod!=""}[24h:5m])) by (pod, namespace) | ||
| //////////////////////////////////////////////////////////////////////////// | ||
|
|
||
| promNetworkReceiveInput := prometheus.PrometheusInput{ | ||
| Metric: "container_network_receive_bytes_total", | ||
| } | ||
| promNetworkReceiveInput.IgnoreFilters = map[string][]string{ | ||
| "pod": {""}, | ||
| } | ||
| promNetworkReceiveInput.Function = []string{"increase", "sum"} | ||
| promNetworkReceiveInput.QueryWindow = tc.window | ||
| promNetworkReceiveInput.QueryResolution = "5m" | ||
| promNetworkReceiveInput.AggregateBy = []string{"pod", "namespace"} | ||
| promNetworkReceiveInput.Time = &endTime | ||
|
|
||
| promNetworkReceiveResponse, err := client.RunPromQLQuery(promNetworkReceiveInput) | ||
| if err != nil { | ||
| t.Fatalf("Error while calling Prometheus API %v", err) | ||
| } | ||
|
|
||
| //////////////////////////////////////////////////////////////////////////// | ||
| // Network Transfer Bytes | ||
|
|
||
| // sum(increase(container_network_transmit_bytes_total{pod!="", %s}[%s:%dm])) by (pod_name, pod, namespace, %s) | ||
| //////////////////////////////////////////////////////////////////////////// | ||
|
|
||
| promNetworkTransferInput := prometheus.PrometheusInput{ | ||
| Metric: "container_network_transmit_bytes_total", | ||
| } | ||
| promNetworkTransferInput.IgnoreFilters = map[string][]string{ | ||
| "pod": {""}, | ||
| } | ||
| promNetworkTransferInput.Function = []string{"increase", "sum"} | ||
| promNetworkTransferInput.QueryWindow = tc.window | ||
| promNetworkTransferInput.QueryResolution = "5m" | ||
| promNetworkTransferInput.AggregateBy = []string{"pod", "namespace"} | ||
| promNetworkTransferInput.Time = &endTime | ||
|
|
||
| promNetworkTransferResponse, err := client.RunPromQLQuery(promNetworkTransferInput) | ||
| if err != nil { | ||
| t.Fatalf("Error while calling Prometheus API %v", err) | ||
| } | ||
|
|
||
| // Network Receive Bytes | ||
| for _, promNetworkReceiveResponse := range promNetworkReceiveResponse.Data.Result { | ||
| namespace := promNetworkReceiveResponse.Metric.Namespace | ||
| pod := promNetworkReceiveResponse.Metric.Pod | ||
| networkReceiveBytesPod := promNetworkReceiveResponse.Value.Value | ||
| networkCostsNamespace, ok := networkCostsNamespaceMap[namespace] | ||
| if !ok { | ||
| networkCostsNamespaceMap[namespace] = &NetworkCostsAggregate{ | ||
| PromNetworkReceiveBytes: networkReceiveBytesPod, | ||
| PromNetworkTransferBytes: 0.0, | ||
| AllocNetworkReceiveBytes: 0.0, | ||
| AllocNetworkTransferBytes: 0.0, | ||
| Pods: []string{pod}, | ||
| } | ||
| continue | ||
| } | ||
|
|
||
| networkCostsNamespace.Pods = append(networkCostsNamespace.Pods, pod) | ||
| networkCostsNamespace.PromNetworkReceiveBytes += networkReceiveBytesPod | ||
| } | ||
|
|
||
| // Network Transfer Bytes | ||
| for _, promNetworkTransferResponse := range promNetworkTransferResponse.Data.Result { | ||
| namespace := promNetworkTransferResponse.Metric.Namespace | ||
| pod := promNetworkTransferResponse.Metric.Pod | ||
| networkTransferBytesPod := promNetworkTransferResponse.Value.Value | ||
| networkCostsNamespace, ok := networkCostsNamespaceMap[namespace] | ||
| if !ok { | ||
| networkCostsNamespaceMap[namespace] = &NetworkCostsAggregate{ | ||
| PromNetworkReceiveBytes: networkTransferBytesPod, | ||
| PromNetworkTransferBytes: networkTransferBytesPod, | ||
| AllocNetworkReceiveBytes: 0.0, | ||
| AllocNetworkTransferBytes: 0.0, | ||
| Pods: []string{pod}, | ||
| } | ||
| continue | ||
| } | ||
| if !slices.Contains(networkCostsNamespace.Pods, pod) { | ||
| networkCostsNamespace.Pods = append(networkCostsNamespace.Pods, pod) | ||
| } | ||
| networkCostsNamespace.PromNetworkTransferBytes += networkTransferBytesPod | ||
| } | ||
|
|
||
|
|
||
| ///////////////////////////////////////////// | ||
| // API Client | ||
| ///////////////////////////////////////////// | ||
|
|
||
| // Why doesn't allocation work on Namespace aggregate? | ||
| apiResponse, err := apiObj.GetAllocation(api.AllocationRequest{ | ||
| Window: tc.window, | ||
| Aggregate: tc.aggregate, | ||
| Accumulate: tc.accumulate, | ||
| }) | ||
|
|
||
| if err != nil { | ||
| t.Fatalf("Error while calling Allocation API %v", err) | ||
| } | ||
| if apiResponse.Code != 200 { | ||
| t.Errorf("API returned non-200 code") | ||
| } | ||
|
|
||
| for namespace, allocationResponseItem := range apiResponse.Data[0] { | ||
| networkCostsNamespace, ok := networkCostsNamespaceMap[namespace] | ||
| if !ok { | ||
| networkCostsNamespaceMap[namespace] = &NetworkCostsAggregate{ | ||
| PromNetworkReceiveBytes: 0.0, | ||
| PromNetworkTransferBytes: 0.0, | ||
| AllocNetworkReceiveBytes: allocationResponseItem.NetworkReceiveBytes, | ||
| AllocNetworkTransferBytes: allocationResponseItem.NetworkTransferBytes, | ||
| } | ||
| continue | ||
| } | ||
| networkCostsNamespace.AllocNetworkReceiveBytes = allocationResponseItem.NetworkReceiveBytes | ||
| networkCostsNamespace.AllocNetworkTransferBytes = allocationResponseItem.NetworkTransferBytes | ||
| } | ||
|
|
||
| for namespace, networkCostValues := range networkCostsNamespaceMap { | ||
| t.Logf("Namespace %s", namespace) | ||
| withinRange, diff_percent := utils.AreWithinPercentage(networkCostValues.AllocNetworkTransferBytes, networkCostValues.PromNetworkTransferBytes, tolerance) | ||
| if !withinRange { | ||
| t.Errorf(" - NetworkTransferBytes[Fail]: DifferencePercent: %0.2f, Prometheus: %0.2f, /allocation: %0.2f", diff_percent, networkCostValues.PromNetworkTransferBytes, networkCostValues.AllocNetworkTransferBytes) | ||
| } else { | ||
| t.Logf(" - NetworkTransferBytes[Pass]: ~ %0.2f", networkCostValues.PromNetworkTransferBytes) | ||
| } | ||
| if !withinRange { | ||
| t.Errorf(" - NetworkReceiveBytes[Fail]: DifferencePercent: %0.2f, Prometheus: %0.2f, /allocation: %0.2f", diff_percent, networkCostValues.PromNetworkReceiveBytes, networkCostValues.AllocNetworkReceiveBytes) | ||
| } else { | ||
| t.Logf(" - NetworkReceiveBytes[Pass]: ~ %0.2f", networkCostValues.PromNetworkReceiveBytes) | ||
| } | ||
| } | ||
| }) | ||
| } | ||
| } |
180 changes: 180 additions & 0 deletions
180
test/integration/prometheus/network_internet_costs_test.go
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,180 @@ | ||
| package prometheus | ||
|
|
||
| // Description - Compares Network Internet Costs from Prometheus and Allocation | ||
|
|
||
| import ( | ||
| // "fmt" | ||
| "github.com/opencost/opencost-integration-tests/pkg/api" | ||
| "github.com/opencost/opencost-integration-tests/pkg/prometheus" | ||
| "github.com/opencost/opencost-integration-tests/pkg/utils" | ||
| "testing" | ||
| "time" | ||
| ) | ||
|
|
||
| const tolerance = 0.05 | ||
|
|
||
| func TestNetworkInternetCosts(t *testing.T) { | ||
| apiObj := api.NewAPI() | ||
|
|
||
| testCases := []struct { | ||
| name string | ||
| window string | ||
| aggregate string | ||
| accumulate string | ||
| }{ | ||
| { | ||
| name: "Yesterday", | ||
| window: "24h", | ||
| aggregate: "pod", | ||
| accumulate: "false", | ||
| }, | ||
| } | ||
|
|
||
| t.Logf("testCases: %v", testCases) | ||
|
|
||
| for _, tc := range testCases { | ||
| t.Run(tc.name, func(t *testing.T) { | ||
|
|
||
| // Any data that is in a "raw allocation only" is not valid in any | ||
| // sort of cumulative Allocation (like one that is added). | ||
|
|
||
| type NetworkCostsAggregate struct { | ||
| PromNetworkInternetGiB float64 | ||
| AllocNetworkInternetGiB float64 | ||
| } | ||
|
|
||
| networkCostsPodMap := make(map[string]*NetworkCostsAggregate) | ||
|
|
||
| queryEnd := time.Now().UTC().Truncate(time.Hour).Add(time.Hour) | ||
| endTime := queryEnd.Unix() | ||
| // Collect Namespace results from Prometheus | ||
| client := prometheus.NewClient() | ||
|
|
||
| //////////////////////////////////////////////////////////////////////////// | ||
| // Network Internet GiB | ||
|
|
||
| // sum(increase(kubecost_pod_network_egress_bytes_total{internet="true"}[24h:5m])) by (pod_name, namespace) / 1024 / 1024 / 1024 | ||
| // Apply Division by 1024^3 when you are looping over the response | ||
| //////////////////////////////////////////////////////////////////////////// | ||
|
|
||
| promNetworkInternetInput := prometheus.PrometheusInput{ | ||
| Metric: "kubecost_pod_network_egress_bytes_total", | ||
| } | ||
| promNetworkInternetInput.Filters = map[string]string{ | ||
| "internet": "true", | ||
| } | ||
| promNetworkInternetInput.Function = []string{"increase", "sum"} | ||
| promNetworkInternetInput.QueryWindow = tc.window | ||
| promNetworkInternetInput.QueryResolution = "5m" | ||
| promNetworkInternetInput.AggregateBy = []string{"pod_name", "namespace"} | ||
| promNetworkInternetInput.Time = &endTime | ||
|
|
||
| promNetworkInternetResponse, err := client.RunPromQLQuery(promNetworkInternetInput) | ||
| if err != nil { | ||
| t.Fatalf("Error while calling Prometheus API %v", err) | ||
| } | ||
|
|
||
| //////////////////////////////////////////////////////////////////////////// | ||
| // Network Internet price per GiB | ||
|
|
||
| // avg(avg_over_time(kubecost_network_internet_egress_cost{%s}[%s])) by (%s) | ||
| //////////////////////////////////////////////////////////////////////////// | ||
|
|
||
| promNetworkInternetCostInput := prometheus.PrometheusInput{ | ||
| Metric: "kubecost_network_internet_egress_cost", | ||
| } | ||
| promNetworkInternetCostInput.Function = []string{"avg_over_time", "avg"} | ||
| promNetworkInternetCostInput.QueryWindow = tc.window | ||
| promNetworkInternetCostInput.Time = &endTime | ||
|
|
||
| promNetworkInternetCostResponse, err := client.RunPromQLQuery(promNetworkInternetCostInput) | ||
| if err != nil { | ||
| t.Fatalf("Error while calling Prometheus API %v", err) | ||
| } | ||
|
|
||
| // -------------------------------- | ||
| // Network Internet Cost for all Pods | ||
| // -------------------------------- | ||
|
|
||
| networkInternetCost := promNetworkInternetCostResponse.Data.Result[0].Value.Value | ||
|
|
||
| // -------------------------------- | ||
| // Assign Network Costs to Pods and Cumulate based on Namespace | ||
| // -------------------------------- | ||
|
|
||
| // Form a key based on namespace and pod name | ||
|
|
||
| for _, promNetworkInternetItem := range promNetworkInternetResponse.Data.Result { | ||
| // namespace := promNetworkInternetItem.Metric.Namespace | ||
| pod := promNetworkInternetItem.Metric.PodName | ||
| gib := promNetworkInternetItem.Value.Value | ||
|
|
||
|
|
||
| networkCostsPodMap[pod] = &NetworkCostsAggregate{ | ||
| PromNetworkInternetGiB: (gib / 1024 / 1024 / 1024) * networkInternetCost, | ||
| AllocNetworkInternetGiB: 0.0, | ||
| } | ||
|
|
||
| // networkCostsNamespace, ok := networkCostsPodMap[namespace] | ||
| // if !ok { | ||
| // networkCostsPodMap[pod] = &NetworkCostsAggregate{ | ||
| // PromNetworkInternetGiB: (gib / 1024 / 1024 / 1024) * networkInternetCost, | ||
| // AllocNetworkInternetGiB: 0.0, | ||
| // } | ||
| // continue | ||
| // } | ||
| } | ||
|
|
||
|
|
||
| ///////////////////////////////////////////// | ||
| // API Client | ||
| ///////////////////////////////////////////// | ||
|
|
||
| // Why doesn't allocation work on Namespace aggregate? | ||
| apiResponse, err := apiObj.GetAllocation(api.AllocationRequest{ | ||
| Window: tc.window, | ||
| Aggregate: tc.aggregate, | ||
| Accumulate: tc.accumulate, | ||
| }) | ||
|
|
||
| if err != nil { | ||
| t.Fatalf("Error while calling Allocation API %v", err) | ||
| } | ||
| if apiResponse.Code != 200 { | ||
| t.Errorf("API returned non-200 code") | ||
| } | ||
|
|
||
| for pod, allocationResponseItem := range apiResponse.Data[0] { | ||
| networkCostsPod, ok := networkCostsPodMap[pod] | ||
| if !ok { | ||
| networkCostsPodMap[pod] = &NetworkCostsAggregate{ | ||
| PromNetworkInternetGiB: 0.0, | ||
| AllocNetworkInternetGiB: allocationResponseItem.NetworkInternetCost, | ||
| } | ||
| continue | ||
| } | ||
| networkCostsPod.AllocNetworkInternetGiB = allocationResponseItem.NetworkInternetCost | ||
| } | ||
|
|
||
| validCostsSeen := false | ||
| negligilbleCost := 0.01 // 1 Cent of a Dollar | ||
| for pod, networkCostValues := range networkCostsPodMap { | ||
| if networkCostValues.AllocNetworkInternetGiB < negligilbleCost { | ||
| continue | ||
| } else { | ||
| validCostsSeen = true | ||
| } | ||
| t.Logf("Pod %s", pod) | ||
| withinRange, diff_percent := utils.AreWithinPercentage(networkCostValues.AllocNetworkInternetGiB, networkCostValues.PromNetworkInternetGiB, tolerance) | ||
| if !withinRange { | ||
| t.Errorf(" - NetworkInternetCost[Fail]: DifferencePercent: %0.2f, Prometheus: %0.9f, /allocation: %0.9f", diff_percent, networkCostValues.PromNetworkInternetGiB, networkCostValues.AllocNetworkInternetGiB) | ||
| } else { | ||
| t.Logf(" - NetworkInternetCost[Pass]: ~ %0.5f", networkCostValues.PromNetworkInternetGiB) | ||
| } | ||
| } | ||
| if !validCostsSeen { | ||
| t.Errorf("NetWork Internet Costs for all Pods are below 1 cent and hence cannot be considered as costs from resource usage and validated.") | ||
| } | ||
| }) | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is this commented?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wanted the option to drill down to a pod level if needed. do you think we need to go down further down?