Просмотр исходного кода

Move network cost response parsing and helper methods to separate source file to add a bit more organization, add support to ComputeCostDataRange for network costs, and record up to date price metrics in prometheus

Matt Bolt 6 лет назад
Родитель
Сommit
3bc975d66b
5 измененных файлов с 367 добавлено и 306 удалено
  1. 40 295
      costmodel/costmodel.go
  2. 304 0
      costmodel/networkcosts.go
  3. 1 1
      go.mod
  4. 2 1
      go.sum
  5. 20 9
      main.go

+ 40 - 295
costmodel/costmodel.go

@@ -408,7 +408,7 @@ func (cm *CostModel) ComputeCostData(cli prometheusClient.Client, clientset kube
 		}
 	}
 
-	networkUsageMap, err := getNetworkUsageData(resultNetZoneRequests, resultNetRegionRequests, resultNetInternetRequests, false)
+	networkUsageMap, err := GetNetworkUsageData(resultNetZoneRequests, resultNetRegionRequests, resultNetInternetRequests, false)
 	if err != nil {
 		klog.V(1).Infof("Unable to get Network Cost Data: %s", err.Error())
 		return nil, err
@@ -516,7 +516,7 @@ func (cm *CostModel) ComputeCostData(cli prometheusClient.Client, clientset kube
 
 			var podNetCosts []*Vector
 			if usage, ok := networkUsageMap[ns+","+podName]; ok {
-				netCosts, err := getNetworkCost(usage, cloud)
+				netCosts, err := GetNetworkCost(usage, cloud)
 				if err != nil {
 					klog.V(3).Infof("Error pulling network costs: %s", err.Error())
 				} else {
@@ -1153,6 +1153,9 @@ func (cm *CostModel) ComputeCostDataRange(cli prometheusClient.Client, clientset
 	queryCPUUsage := fmt.Sprintf(queryCPUUsageStr, windowString, "")
 	queryGPURequests := fmt.Sprintf(queryGPURequestsStr, windowString, "", windowString, "")
 	queryPVRequests := fmt.Sprintf(queryPVRequestsStr)
+	queryNetZoneRequests := fmt.Sprintf(queryZoneNetworkUsage, windowString, "")
+	queryNetRegionRequests := fmt.Sprintf(queryRegionNetworkUsage, windowString, "")
+	queryNetInternetRequests := fmt.Sprintf(queryInternetNetworkUsage, windowString, "")
 	normalization := fmt.Sprintf(normalizationStr, windowString, "")
 
 	layout := "2006-01-02T15:04:05.000Z"
@@ -1182,7 +1185,7 @@ func (cm *CostModel) ComputeCostDataRange(cli prometheusClient.Client, clientset
 	}
 
 	var wg sync.WaitGroup
-	wg.Add(8)
+	wg.Add(11)
 
 	var promErr error
 	var resultRAMRequests interface{}
@@ -1215,6 +1218,21 @@ func (cm *CostModel) ComputeCostDataRange(cli prometheusClient.Client, clientset
 		resultPVRequests, promErr = QueryRange(cli, queryPVRequests, start, end, window)
 		defer wg.Done()
 	}()
+	var resultNetZoneRequests interface{}
+	go func() {
+		resultNetZoneRequests, promErr = QueryRange(cli, queryNetZoneRequests, start, end, window)
+		defer wg.Done()
+	}()
+	var resultNetRegionRequests interface{}
+	go func() {
+		resultNetRegionRequests, promErr = QueryRange(cli, queryNetRegionRequests, start, end, window)
+		defer wg.Done()
+	}()
+	var resultNetInternetRequests interface{}
+	go func() {
+		resultNetInternetRequests, promErr = QueryRange(cli, queryNetInternetRequests, start, end, window)
+		defer wg.Done()
+	}()
 	var normalizationResult interface{}
 	go func() {
 		normalizationResult, promErr = query(cli, normalization)
@@ -1277,6 +1295,12 @@ func (cm *CostModel) ComputeCostDataRange(cli prometheusClient.Client, clientset
 		}
 	}
 
+	networkUsageMap, err := GetNetworkUsageData(resultNetZoneRequests, resultNetRegionRequests, resultNetInternetRequests, true)
+	if err != nil {
+		klog.V(1).Infof("Unable to get Network Cost Data: %s", err.Error())
+		return nil, err
+	}
+
 	containerNameCost := make(map[string]*CostData)
 	containers := make(map[string]bool)
 
@@ -1365,6 +1389,16 @@ func (cm *CostModel) ComputeCostDataRange(cli prometheusClient.Client, clientset
 				}
 			}
 
+			var podNetCosts []*Vector
+			if usage, ok := networkUsageMap[ns+","+podName]; ok {
+				netCosts, err := GetNetworkCost(usage, cloud)
+				if err != nil {
+					klog.V(3).Infof("Error pulling network costs: %s", err.Error())
+				} else {
+					podNetCosts = netCosts
+				}
+			}
+
 			var podServices []string
 			if _, ok := podServicesMapping[ns]; ok {
 				if svcs, ok := podServicesMapping[ns][pod.GetObjectMeta().GetName()]; ok {
@@ -1419,8 +1453,10 @@ func (cm *CostModel) ComputeCostDataRange(cli prometheusClient.Client, clientset
 				}
 
 				var pvReq []*PersistentVolumeClaimData
+				var netReq []*Vector
 				if i == 0 { // avoid duplicating by just assigning all claims to the first container.
 					pvReq = podPVs
+					netReq = podNetCosts
 				}
 
 				costs := &CostData{
@@ -1441,6 +1477,7 @@ func (cm *CostModel) ComputeCostDataRange(cli prometheusClient.Client, clientset
 					GPUReq:          GPUReqV,
 					PVCData:         pvReq,
 					Labels:          podLabels,
+					NetworkData:     netReq,
 					NamespaceLabels: nsLabels,
 				}
 				costs.CPUAllocation = getContainerAllocation(costs.CPUReq, costs.CPUUsed)
@@ -1587,22 +1624,6 @@ type PersistentVolumeClaimData struct {
 	Values     []*Vector             `json:"values"`
 }
 
-// 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
-}
-
 func getCost(qr interface{}) (map[string][]*Vector, error) {
 	toReturn := make(map[string][]*Vector)
 	for _, val := range qr.(map[string]interface{})["data"].(map[string]interface{})["result"].([]interface{}) {
@@ -2109,282 +2130,6 @@ func GetContainerMetricVectors(qr interface{}, normalize bool, normalizationValu
 	return containerData, nil
 }
 
-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
-}
-
-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, _ := strconv.ParseFloat(strVal, 64)
-
-		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
-}
-
-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 wrapPrometheusError(qr interface{}) (string, error) {
 	e, ok := qr.(map[string]interface{})["error"]
 	if !ok {

+ 304 - 0
costmodel/networkcosts.go

@@ -0,0 +1,304 @@
+package costmodel
+
+import (
+	"math"
+	"fmt"
+	"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, _ := strconv.ParseFloat(strVal, 64)
+
+		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
+}

+ 1 - 1
go.mod

@@ -38,4 +38,4 @@ require (
 	k8s.io/apimachinery v0.0.0-20190913075812-e119e5e154b6
 	k8s.io/client-go v0.0.0-20190620085101-78d2af792bab
 	k8s.io/klog v0.4.0
-)
+)

+ 2 - 1
go.sum

@@ -4,6 +4,7 @@ cloud.google.com/go v0.34.0 h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg=
 cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 contrib.go.opencensus.io/exporter/ocagent v0.5.0 h1:TKXjQSRS0/cCDrP7KvkgU6SmILtF/yV2TOs/02K/WZQ=
 contrib.go.opencensus.io/exporter/ocagent v0.5.0/go.mod h1:ImxhfLRpxoYiSq891pBrLVhN+qmP8BTVvdH2YLs7Gl0=
+git.apache.org/thrift.git v0.12.0/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
 github.com/Azure/azure-sdk-for-go v24.1.0+incompatible h1:P7GocB7bhkyGbRL1tCy0m9FDqb1V/dqssch3jZieUHk=
 github.com/Azure/azure-sdk-for-go v24.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
 github.com/Azure/go-autorest v11.1.0+incompatible h1:9DfMsQdUMEtg1jKRTjtkNZsvOuZXJOMl4dN1kiQwAc8=
@@ -380,4 +381,4 @@ k8s.io/utils v0.0.0-20190308190857-21c4ce38f2a7 h1:8r+l4bNWjRlsFYlQJnKJ2p7s1YQPj
 k8s.io/utils v0.0.0-20190308190857-21c4ce38f2a7/go.mod h1:8k8uAuAQ0rXslZKaEWd0c3oVhZz7sSzSiPnVZayjIX0=
 sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
 sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
-sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
+sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=

+ 20 - 9
main.go

@@ -57,9 +57,9 @@ type Accesses struct {
 	CPUAllocationRecorder         *prometheus.GaugeVec
 	GPUAllocationRecorder         *prometheus.GaugeVec
 	ContainerUptimeRecorder       *prometheus.GaugeVec
-	NetworkZoneEgressRecorder     *prometheus.GaugeVec
-	NetworkRegionEgressRecorder   *prometheus.GaugeVec
-	NetworkInternetEgressRecorder *prometheus.GaugeVec
+	NetworkZoneEgressRecorder     prometheus.Gauge
+	NetworkRegionEgressRecorder   prometheus.Gauge
+	NetworkInternetEgressRecorder prometheus.Gauge
 	ServiceSelectorRecorder       *prometheus.GaugeVec
 	DeploymentSelectorRecorder    *prometheus.GaugeVec
 	Model                         *costModel.CostModel
@@ -395,6 +395,17 @@ func (a *Accesses) recordPrices() {
 			for _, pod := range podlist {
 				podStatus[pod.Name] = pod.Status.Phase
 			}
+
+			// Record network pricing at global scope
+			networkCosts, err := a.Cloud.NetworkPricing()
+			if err != nil {
+				klog.V(4).Infof("Failed to retrieve network costs: %s", err.Error())
+			} else {
+				a.NetworkZoneEgressRecorder.Set(networkCosts.ZoneNetworkEgressCost)
+				a.NetworkRegionEgressRecorder.Set(networkCosts.RegionNetworkEgressCost)
+				a.NetworkInternetEgressRecorder.Set(networkCosts.InternetNetworkEgressCost)
+			}
+
 			data, err := a.Model.ComputeCostData(a.PrometheusClient, a.KubeClientSet, a.Cloud, "2m", "", "")
 			if err != nil {
 				klog.V(1).Info("Error in price recording: " + err.Error())
@@ -625,18 +636,18 @@ func main() {
 		Help: "container_uptime_seconds Seconds a container has been running",
 	}, []string{"namespace", "pod", "container"})
 
-	NetworkZoneEgressRecorder := prometheus.NewGaugeVec(prometheus.GaugeOpts{
+	NetworkZoneEgressRecorder := prometheus.NewGauge(prometheus.GaugeOpts{
 		Name: "kubecost_network_zone_egress_cost",
 		Help: "kubecost_network_zone_egress_cost Total cost per GB egress across zones",
-	}, []string{"namespace", "pod"})
-	NetworkRegionEgressRecorder := prometheus.NewGaugeVec(prometheus.GaugeOpts{
+	})
+	NetworkRegionEgressRecorder := prometheus.NewGauge(prometheus.GaugeOpts{
 		Name: "kubecost_network_region_egress_cost",
 		Help: "kubecost_network_region_egress_cost Total cost per GB egress across regions",
-	}, []string{"namespace", "pod"})
-	NetworkInternetEgressRecorder := prometheus.NewGaugeVec(prometheus.GaugeOpts{
+	})
+	NetworkInternetEgressRecorder := prometheus.NewGauge(prometheus.GaugeOpts{
 		Name: "kubecost_network_internet_egress_cost",
 		Help: "kubecost_network_internet_egress_cost Total cost per GB of internet egress.",
-	}, []string{"namespace", "pod"})
+	})
 
 	prometheus.MustRegister(cpuGv)
 	prometheus.MustRegister(ramGv)