| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307 |
- package costmodel
- import (
- "fmt"
- "math"
- "strconv"
- costAnalyzerCloud "github.com/kubecost/cost-model/cloud"
- )
- // NetworkUsageVNetworkUsageDataector contains the network usage values for egress network traffic
- type NetworkUsageData struct {
- PodName string
- Namespace string
- NetworkZoneEgress []*Vector
- NetworkRegionEgress []*Vector
- NetworkInternetEgress []*Vector
- }
- // NetworkUsageVector contains a network usage vector for egress network traffic
- type NetworkUsageVector struct {
- PodName string
- Namespace string
- Values []*Vector
- }
- // GetNetworkUsageData performs a join of the the results of zone, region, and internet usage queries to return a single
- // map containing network costs for each namespace+pod
- func GetNetworkUsageData(zr interface{}, rr interface{}, ir interface{}, isRange bool) (map[string]*NetworkUsageData, error) {
- var vectorFn func(interface{}) (map[string]*NetworkUsageVector, error)
- if isRange {
- vectorFn = getNetworkUsageVectors
- } else {
- vectorFn = getNetworkUsageVector
- }
- zoneNetworkMap, err := vectorFn(zr)
- if err != nil {
- return nil, err
- }
- regionNetworkMap, err := vectorFn(rr)
- if err != nil {
- return nil, err
- }
- internetNetworkMap, err := vectorFn(ir)
- if err != nil {
- return nil, err
- }
- usageData := make(map[string]*NetworkUsageData)
- for k, v := range zoneNetworkMap {
- existing, ok := usageData[k]
- if !ok {
- usageData[k] = &NetworkUsageData{
- PodName: v.PodName,
- Namespace: v.Namespace,
- NetworkZoneEgress: v.Values,
- }
- continue
- }
- existing.NetworkZoneEgress = v.Values
- }
- for k, v := range regionNetworkMap {
- existing, ok := usageData[k]
- if !ok {
- usageData[k] = &NetworkUsageData{
- PodName: v.PodName,
- Namespace: v.Namespace,
- NetworkRegionEgress: v.Values,
- }
- continue
- }
- existing.NetworkRegionEgress = v.Values
- }
- for k, v := range internetNetworkMap {
- existing, ok := usageData[k]
- if !ok {
- usageData[k] = &NetworkUsageData{
- PodName: v.PodName,
- Namespace: v.Namespace,
- NetworkInternetEgress: v.Values,
- }
- continue
- }
- existing.NetworkInternetEgress = v.Values
- }
- return usageData, nil
- }
- // GetNetworkCost computes the actual cost for NetworkUsageData based on data provided by the Provider.
- func GetNetworkCost(usage *NetworkUsageData, cloud costAnalyzerCloud.Provider) ([]*Vector, error) {
- var results []*Vector
- pricing, err := cloud.NetworkPricing()
- if err != nil {
- return nil, err
- }
- zoneCost := pricing.ZoneNetworkEgressCost
- regionCost := pricing.RegionNetworkEgressCost
- internetCost := pricing.InternetNetworkEgressCost
- zlen := len(usage.NetworkZoneEgress)
- rlen := len(usage.NetworkRegionEgress)
- ilen := len(usage.NetworkInternetEgress)
- l := max(zlen, rlen, ilen)
- for i := 0; i < l; i++ {
- var cost float64 = 0
- var timestamp float64
- if i < zlen {
- cost += usage.NetworkZoneEgress[i].Value * zoneCost
- timestamp = usage.NetworkZoneEgress[i].Timestamp
- }
- if i < rlen {
- cost += usage.NetworkRegionEgress[i].Value * regionCost
- timestamp = usage.NetworkRegionEgress[i].Timestamp
- }
- if i < ilen {
- cost += usage.NetworkInternetEgress[i].Value * internetCost
- timestamp = usage.NetworkInternetEgress[i].Timestamp
- }
- results = append(results, &Vector{
- Value: cost,
- Timestamp: timestamp,
- })
- }
- return results, nil
- }
- func getNetworkUsageVector(qr interface{}) (map[string]*NetworkUsageVector, error) {
- ncdmap := make(map[string]*NetworkUsageVector)
- data, ok := qr.(map[string]interface{})["data"]
- if !ok {
- e, err := wrapPrometheusError(qr)
- if err != nil {
- return nil, err
- }
- return nil, fmt.Errorf(e)
- }
- d, ok := data.(map[string]interface{})
- if !ok {
- return nil, fmt.Errorf("Data field improperly formatted in prometheus repsonse")
- }
- result, ok := d["result"]
- if !ok {
- return nil, fmt.Errorf("Result field not present in prometheus response")
- }
- results, ok := result.([]interface{})
- if !ok {
- return nil, fmt.Errorf("Result field improperly formatted in prometheus response")
- }
- for _, val := range results {
- metricInterface, ok := val.(map[string]interface{})["metric"]
- if !ok {
- return nil, fmt.Errorf("Metric field does not exist in data result vector")
- }
- metricMap, ok := metricInterface.(map[string]interface{})
- if !ok {
- return nil, fmt.Errorf("Metric field is improperly formatted")
- }
- podName, ok := metricMap["pod_name"]
- if !ok {
- return nil, fmt.Errorf("Pod Name does not exist in data result vector")
- }
- podNameStr, ok := podName.(string)
- if !ok {
- return nil, fmt.Errorf("Pod Name field improperly formatted")
- }
- namespace, ok := metricMap["namespace"]
- if !ok {
- return nil, fmt.Errorf("Namespace field does not exist in data result vector")
- }
- namespaceStr, ok := namespace.(string)
- if !ok {
- return nil, fmt.Errorf("Namespace field improperly formatted")
- }
- dataPoint, ok := val.(map[string]interface{})["value"]
- if !ok {
- return nil, fmt.Errorf("Value field does not exist in data result vector")
- }
- value, ok := dataPoint.([]interface{})
- if !ok || len(value) != 2 {
- return nil, fmt.Errorf("Improperly formatted datapoint from Prometheus")
- }
- var vectors []*Vector
- strVal := value[1].(string)
- v, err := strconv.ParseFloat(strVal, 64)
- if err != nil {
- return nil, err
- }
- vectors = append(vectors, &Vector{
- Timestamp: value[0].(float64),
- Value: v,
- })
- key := namespaceStr + "," + podNameStr
- ncdmap[key] = &NetworkUsageVector{
- Namespace: namespaceStr,
- PodName: podNameStr,
- Values: vectors,
- }
- }
- return ncdmap, nil
- }
- func getNetworkUsageVectors(qr interface{}) (map[string]*NetworkUsageVector, error) {
- ncdmap := make(map[string]*NetworkUsageVector)
- data, ok := qr.(map[string]interface{})["data"]
- if !ok {
- e, err := wrapPrometheusError(qr)
- if err != nil {
- return nil, err
- }
- return nil, fmt.Errorf(e)
- }
- d, ok := data.(map[string]interface{})
- if !ok {
- return nil, fmt.Errorf("Data field improperly formatted in prometheus repsonse")
- }
- result, ok := d["result"]
- if !ok {
- return nil, fmt.Errorf("Result field not present in prometheus response")
- }
- results, ok := result.([]interface{})
- if !ok {
- return nil, fmt.Errorf("Result field improperly formatted in prometheus response")
- }
- for _, val := range results {
- metricInterface, ok := val.(map[string]interface{})["metric"]
- if !ok {
- return nil, fmt.Errorf("Metric field does not exist in data result vector")
- }
- metricMap, ok := metricInterface.(map[string]interface{})
- if !ok {
- return nil, fmt.Errorf("Metric field is improperly formatted")
- }
- podName, ok := metricMap["pod_name"]
- if !ok {
- return nil, fmt.Errorf("Pod Name does not exist in data result vector")
- }
- podNameStr, ok := podName.(string)
- if !ok {
- return nil, fmt.Errorf("Pod Name field improperly formatted")
- }
- namespace, ok := metricMap["namespace"]
- if !ok {
- return nil, fmt.Errorf("Namespace field does not exist in data result vector")
- }
- namespaceStr, ok := namespace.(string)
- if !ok {
- return nil, fmt.Errorf("Namespace field improperly formatted")
- }
- values, ok := val.(map[string]interface{})["values"].([]interface{})
- if !ok {
- return nil, fmt.Errorf("Values field is improperly formatted")
- }
- var vectors []*Vector
- for _, value := range values {
- dataPoint, ok := value.([]interface{})
- if !ok || len(dataPoint) != 2 {
- return nil, fmt.Errorf("Improperly formatted datapoint from Prometheus")
- }
- strVal := dataPoint[1].(string)
- v, _ := strconv.ParseFloat(strVal, 64)
- vectors = append(vectors, &Vector{
- Timestamp: math.Round(dataPoint[0].(float64)/10) * 10,
- Value: v,
- })
- }
- key := namespaceStr + "," + podNameStr
- ncdmap[key] = &NetworkUsageVector{
- Namespace: namespaceStr,
- PodName: podNameStr,
- Values: vectors,
- }
- }
- return ncdmap, nil
- }
- func max(x int, rest ...int) int {
- curr := x
- for _, v := range rest {
- if v > curr {
- curr = v
- }
- }
- return curr
- }
|