AjayTripathy 6 лет назад
Родитель
Сommit
c20c56e5c3
11 измененных файлов с 595 добавлено и 45 удалено
  1. 3 0
      cloud/aws.json
  2. 26 0
      cloud/awsprovider.go
  3. 3 0
      cloud/azure.json
  4. 26 0
      cloud/azureprovider.go
  5. 4 1
      cloud/default.json
  6. 3 0
      cloud/gcp.json
  7. 26 0
      cloud/gcpprovider.go
  8. 82 41
      cloud/provider.go
  9. 81 3
      costmodel/costmodel.go
  10. 307 0
      costmodel/networkcosts.go
  11. 34 0
      main.go

+ 3 - 0
cloud/aws.json

@@ -7,6 +7,9 @@
     "GPU": "0.95",
     "spotRAM": "0.000892",
     "storage": "0.00005479452",
+    "zoneNetworkEgress": "0.01",
+    "regionNetworkEgress": "0.01",
+    "internetNetworkEgress": "0.15",
     "spotLabel": "kops.k8s.io/instancegroup",
     "spotLabelValue": "spotinstance-nodes",
     "awsServiceKeyName": "AKIAXW6UVLRRY5RQGGUX",

+ 26 - 0
cloud/awsprovider.go

@@ -670,6 +670,32 @@ func (aws *AWS) DownloadPricingData() error {
 	return nil
 }
 
+// Stubbed NetworkPricing for AWS. Pull directly from aws.json for now
+func (c *AWS) NetworkPricing() (*Network, error) {
+	cpricing, err := GetDefaultPricingData("aws.json")
+	if err != nil {
+		return nil, err
+	}
+	znec, err := strconv.ParseFloat(cpricing.ZoneNetworkEgress, 64)
+	if err != nil {
+		return nil, err
+	}
+	rnec, err := strconv.ParseFloat(cpricing.RegionNetworkEgress, 64)
+	if err != nil {
+		return nil, err
+	}
+	inec, err := strconv.ParseFloat(cpricing.InternetNetworkEgress, 64)
+	if err != nil {
+		return nil, err
+	}
+
+	return &Network{
+		ZoneNetworkEgressCost:     znec,
+		RegionNetworkEgressCost:   rnec,
+		InternetNetworkEgressCost: inec,
+	}, nil
+}
+
 // AllNodePricing returns all the billing data fetched.
 func (aws *AWS) AllNodePricing() (interface{}, error) {
 	aws.DownloadPricingDataLock.RLock()

+ 3 - 0
cloud/azure.json

@@ -6,6 +6,9 @@
     "RAM": "0.001917", 
     "spotRAM": "0.000382",
     "storage": "0.00005479452" ,
+    "zoneNetworkEgress": "0.01",
+    "regionNetworkEgress": "0.01",
+    "internetNetworkEgress": "0.12",
     "azureSubscriptionID": "",
     "azureClientID": "" ,
     "azureClientSecret": "" ,

+ 26 - 0
cloud/azureprovider.go

@@ -429,6 +429,32 @@ func (az *Azure) NodePricing(key Key) (*Node, error) {
 	}, nil
 }
 
+// Stubbed NetworkPricing for Azure. Pull directly from azure.json for now
+func (c *Azure) NetworkPricing() (*Network, error) {
+	cpricing, err := GetDefaultPricingData("azure.json")
+	if err != nil {
+		return nil, err
+	}
+	znec, err := strconv.ParseFloat(cpricing.ZoneNetworkEgress, 64)
+	if err != nil {
+		return nil, err
+	}
+	rnec, err := strconv.ParseFloat(cpricing.RegionNetworkEgress, 64)
+	if err != nil {
+		return nil, err
+	}
+	inec, err := strconv.ParseFloat(cpricing.InternetNetworkEgress, 64)
+	if err != nil {
+		return nil, err
+	}
+
+	return &Network{
+		ZoneNetworkEgressCost:     znec,
+		RegionNetworkEgressCost:   rnec,
+		InternetNetworkEgressCost: inec,
+	}, nil
+}
+
 type azurePvKey struct {
 	Labels                 map[string]string
 	StorageClass           string

+ 4 - 1
cloud/default.json

@@ -6,5 +6,8 @@
     "RAM": "0.004237",
     "spotRAM": "0.000892",
     "GPU": "0.95",
-    "storage": "0.00005479452"
+    "storage": "0.00005479452",
+    "zoneNetworkEgress": "0.01",
+    "regionNetworkEgress": "0.01",
+    "internetNetworkEgress": "0.12"
 }

+ 3 - 0
cloud/gcp.json

@@ -7,5 +7,8 @@
     "spotRAM": "0.000892",
     "projectID": "guestbook-227502",
     "storage": "0.00005479452",
+    "zoneNetworkEgress": "0.01",
+    "regionNetworkEgress": "0.01",
+    "internetNetworkEgress": "0.12",
     "billingDataDataset": "billing_data.gcp_billing_export_v1_01AC9F_74CF1D_5565A2"
 }

+ 26 - 0
cloud/gcpprovider.go

@@ -724,6 +724,32 @@ func (gcp *GCP) PVPricing(pvk PVKey) (*PV, error) {
 	return pricing.PV, nil
 }
 
+// Stubbed NetworkPricing for GCP. Pull directly from gcp.json for now
+func (c *GCP) NetworkPricing() (*Network, error) {
+	cpricing, err := GetDefaultPricingData("gcp.json")
+	if err != nil {
+		return nil, err
+	}
+	znec, err := strconv.ParseFloat(cpricing.ZoneNetworkEgress, 64)
+	if err != nil {
+		return nil, err
+	}
+	rnec, err := strconv.ParseFloat(cpricing.RegionNetworkEgress, 64)
+	if err != nil {
+		return nil, err
+	}
+	inec, err := strconv.ParseFloat(cpricing.InternetNetworkEgress, 64)
+	if err != nil {
+		return nil, err
+	}
+
+	return &Network{
+		ZoneNetworkEgressCost:     znec,
+		RegionNetworkEgressCost:   rnec,
+		InternetNetworkEgressCost: inec,
+	}, nil
+}
+
 type pvKey struct {
 	Labels                 map[string]string
 	StorageClass           string

+ 82 - 41
cloud/provider.go

@@ -10,6 +10,7 @@ import (
 	"net/url"
 	"os"
 	"reflect"
+	"strconv"
 	"strings"
 	"sync"
 
@@ -56,6 +57,14 @@ type Node struct {
 	GPUCost          string `json:"gpuCost"`
 }
 
+// Network is the interface by which the provider and cost model communicate network egress prices.
+// The provider will best-effort try to fill out this struct.
+type Network struct {
+	ZoneNetworkEgressCost     float64
+	RegionNetworkEgressCost   float64
+	InternetNetworkEgressCost float64
+}
+
 // PV is the interface by which the provider and cost model communicate PV prices.
 // The provider will best-effort try to fill out this struct.
 type PV struct {
@@ -95,6 +104,7 @@ type Provider interface {
 	GetDisks() ([]byte, error)
 	NodePricing(Key) (*Node, error)
 	PVPricing(PVKey) (*PV, error)
+	NetworkPricing() (*Network, error)
 	AllNodePricing() (interface{}, error)
 	DownloadPricingData() error
 	GetKey(map[string]string) Key
@@ -132,15 +142,18 @@ func GetDefaultPricingData(fname string) (*CustomPricing, error) {
 		return customPricing, nil
 	} else if os.IsNotExist(err) {
 		c := &CustomPricing{
-			Provider:            fname,
-			Description:         "Default prices based on GCP us-central1",
-			CPU:                 "0.031611",
-			SpotCPU:             "0.006655",
-			RAM:                 "0.004237",
-			SpotRAM:             "0.000892",
-			GPU:                 "0.95",
-			Storage:             "0.00005479452",
-			CustomPricesEnabled: "false",
+			Provider:              fname,
+			Description:           "Default prices based on GCP us-central1",
+			CPU:                   "0.031611",
+			SpotCPU:               "0.006655",
+			RAM:                   "0.004237",
+			SpotRAM:               "0.000892",
+			GPU:                   "0.95",
+			Storage:               "0.00005479452",
+			ZoneNetworkEgress:     "0.01",
+			RegionNetworkEgress:   "0.01",
+			InternetNetworkEgress: "0.12",
+			CustomPricesEnabled:   "false",
 		}
 		cj, err := json.Marshal(c)
 		if err != nil {
@@ -160,38 +173,41 @@ func GetDefaultPricingData(fname string) (*CustomPricing, error) {
 const KeyUpdateType = "athenainfo"
 
 type CustomPricing struct {
-	Provider            string `json:"provider"`
-	Description         string `json:"description"`
-	CPU                 string `json:"CPU"`
-	SpotCPU             string `json:"spotCPU"`
-	RAM                 string `json:"RAM"`
-	SpotRAM             string `json:"spotRAM"`
-	GPU                 string `json:"GPU"`
-	SpotGPU             string `json:"spotGPU"`
-	Storage             string `json:"storage"`
-	SpotLabel           string `json:"spotLabel,omitempty"`
-	SpotLabelValue      string `json:"spotLabelValue,omitempty"`
-	GpuLabel            string `json:"gpuLabel,omitempty"`
-	GpuLabelValue       string `json:"gpuLabelValue,omitempty"`
-	ServiceKeyName      string `json:"awsServiceKeyName,omitempty"`
-	ServiceKeySecret    string `json:"awsServiceKeySecret,omitempty"`
-	SpotDataRegion      string `json:"awsSpotDataRegion,omitempty"`
-	SpotDataBucket      string `json:"awsSpotDataBucket,omitempty"`
-	SpotDataPrefix      string `json:"awsSpotDataPrefix,omitempty"`
-	ProjectID           string `json:"projectID,omitempty"`
-	AthenaBucketName    string `json:"athenaBucketName"`
-	AthenaRegion        string `json:"athenaRegion"`
-	AthenaDatabase      string `json:"athenaDatabase"`
-	AthenaTable         string `json:"athenaTable"`
-	BillingDataDataset  string `json:"billingDataDataset,omitempty"`
-	CustomPricesEnabled string `json:"customPricesEnabled"`
-	AzureSubscriptionID string `json:"azureSubscriptionID"`
-	AzureClientID       string `json:"azureClientID"`
-	AzureClientSecret   string `json:"azureClientSecret"`
-	AzureTenantID       string `json:"azureTenantID"`
-	CurrencyCode        string `json:"currencyCode"`
-	Discount            string `json:"discount"`
-	ClusterName         string `json:"clusterName"`
+	Provider              string `json:"provider"`
+	Description           string `json:"description"`
+	CPU                   string `json:"CPU"`
+	SpotCPU               string `json:"spotCPU"`
+	RAM                   string `json:"RAM"`
+	SpotRAM               string `json:"spotRAM"`
+	GPU                   string `json:"GPU"`
+	SpotGPU               string `json:"spotGPU"`
+	Storage               string `json:"storage"`
+	ZoneNetworkEgress     string `json:"zoneNetworkEgress"`
+	RegionNetworkEgress   string `json:"regionNetworkEgress"`
+	InternetNetworkEgress string `json:"internetNetworkEgress"`
+	SpotLabel             string `json:"spotLabel,omitempty"`
+	SpotLabelValue        string `json:"spotLabelValue,omitempty"`
+	GpuLabel              string `json:"gpuLabel,omitempty"`
+	GpuLabelValue         string `json:"gpuLabelValue,omitempty"`
+	ServiceKeyName        string `json:"awsServiceKeyName,omitempty"`
+	ServiceKeySecret      string `json:"awsServiceKeySecret,omitempty"`
+	SpotDataRegion        string `json:"awsSpotDataRegion,omitempty"`
+	SpotDataBucket        string `json:"awsSpotDataBucket,omitempty"`
+	SpotDataPrefix        string `json:"awsSpotDataPrefix,omitempty"`
+	ProjectID             string `json:"projectID,omitempty"`
+	AthenaBucketName      string `json:"athenaBucketName"`
+	AthenaRegion          string `json:"athenaRegion"`
+	AthenaDatabase        string `json:"athenaDatabase"`
+	AthenaTable           string `json:"athenaTable"`
+	BillingDataDataset    string `json:"billingDataDataset,omitempty"`
+	CustomPricesEnabled   string `json:"customPricesEnabled"`
+	AzureSubscriptionID   string `json:"azureSubscriptionID"`
+	AzureClientID         string `json:"azureClientID"`
+	AzureClientSecret     string `json:"azureClientSecret"`
+	AzureTenantID         string `json:"azureTenantID"`
+	CurrencyCode          string `json:"currencyCode"`
+	Discount              string `json:"discount"`
+	ClusterName           string `json:"clusterName"`
 }
 
 func SetCustomPricingField(obj *CustomPricing, name string, value string) error {
@@ -419,6 +435,31 @@ func (c *CustomProvider) PVPricing(pvk PVKey) (*PV, error) {
 	}, nil
 }
 
+func (c *CustomProvider) NetworkPricing() (*Network, error) {
+	cpricing, err := GetDefaultPricingData("default")
+	if err != nil {
+		return nil, err
+	}
+	znec, err := strconv.ParseFloat(cpricing.ZoneNetworkEgress, 64)
+	if err != nil {
+		return nil, err
+	}
+	rnec, err := strconv.ParseFloat(cpricing.RegionNetworkEgress, 64)
+	if err != nil {
+		return nil, err
+	}
+	inec, err := strconv.ParseFloat(cpricing.InternetNetworkEgress, 64)
+	if err != nil {
+		return nil, err
+	}
+
+	return &Network{
+		ZoneNetworkEgressCost:     znec,
+		RegionNetworkEgressCost:   rnec,
+		InternetNetworkEgressCost: inec,
+	}, nil
+}
+
 func (*CustomProvider) GetPVKey(pv *v1.PersistentVolume, parameters map[string]string) PVKey {
 	return &awsPVKey{
 		Labels:           pv.Labels,

+ 81 - 3
costmodel/costmodel.go

@@ -78,6 +78,7 @@ type CostData struct {
 	CPUAllocation   []*Vector                    `json:"cpuallocated,omitempty"`
 	GPUReq          []*Vector                    `json:"gpureq,omitempty"`
 	PVCData         []*PersistentVolumeClaimData `json:"pvcData,omitempty"`
+	NetworkData     []*Vector                    `json:"network,omitempty"`
 	Labels          map[string]string            `json:"labels,omitempty"`
 	NamespaceLabels map[string]string            `json:"namespaceLabels,omitempty"`
 	ClusterID       string                       `json:"clusterId"`
@@ -140,7 +141,10 @@ const (
 						* 
 						on (persistentvolumeclaim, namespace) group_right(storageclass, volumename) 
 				sum(kube_persistentvolumeclaim_resource_requests_storage_bytes) by (persistentvolumeclaim, namespace)`
-	normalizationStr = `max(count_over_time(kube_pod_container_resource_requests_memory_bytes{}[%s] %s))`
+	queryZoneNetworkUsage     = `sum(increase(kubecost_pod_network_egress_bytes_total{internet="false", sameZone="false", sameRegion="true"}[%s] %s)) by (namespace,pod_name) / 1024 / 1024 / 1024`
+	queryRegionNetworkUsage   = `sum(increase(kubecost_pod_network_egress_bytes_total{internet="false", sameZone="false", sameRegion="false"}[%s] %s)) by (namespace,pod_name) / 1024 / 1024 / 1024`
+	queryInternetNetworkUsage = `sum(increase(kubecost_pod_network_egress_bytes_total{internet="true"}[%s] %s)) by (namespace,pod_name) / 1024 / 1024 / 1024`
+	normalizationStr          = `max(count_over_time(kube_pod_container_resource_requests_memory_bytes{}[%s] %s))`
 )
 
 type PrometheusMetadata struct {
@@ -256,12 +260,15 @@ func (cm *CostModel) ComputeCostData(cli prometheusClient.Client, clientset kube
 	queryCPUUsage := fmt.Sprintf(queryCPUUsageStr, window, offset)
 	queryGPURequests := fmt.Sprintf(queryGPURequestsStr, window, offset, window, offset)
 	queryPVRequests := fmt.Sprintf(queryPVRequestsStr)
+	queryNetZoneRequests := fmt.Sprintf(queryZoneNetworkUsage, window, "")
+	queryNetRegionRequests := fmt.Sprintf(queryRegionNetworkUsage, window, "")
+	queryNetInternetRequests := fmt.Sprintf(queryInternetNetworkUsage, window, "")
 	normalization := fmt.Sprintf(normalizationStr, window, offset)
 
 	clustID := os.Getenv(CLUSTER_ID)
 
 	var wg sync.WaitGroup
-	wg.Add(8)
+	wg.Add(11)
 
 	var promErr error
 	var resultRAMRequests interface{}
@@ -294,6 +301,21 @@ func (cm *CostModel) ComputeCostData(cli prometheusClient.Client, clientset kube
 		resultPVRequests, promErr = Query(cli, queryPVRequests)
 		defer wg.Done()
 	}()
+	var resultNetZoneRequests interface{}
+	go func() {
+		resultNetZoneRequests, promErr = Query(cli, queryNetZoneRequests)
+		defer wg.Done()
+	}()
+	var resultNetRegionRequests interface{}
+	go func() {
+		resultNetRegionRequests, promErr = Query(cli, queryNetRegionRequests)
+		defer wg.Done()
+	}()
+	var resultNetInternetRequests interface{}
+	go func() {
+		resultNetInternetRequests, promErr = Query(cli, queryNetInternetRequests)
+		defer wg.Done()
+	}()
 	var normalizationResult interface{}
 	go func() {
 		normalizationResult, promErr = Query(cli, normalization)
@@ -355,6 +377,12 @@ func (cm *CostModel) ComputeCostData(cli prometheusClient.Client, clientset kube
 		}
 	}
 
+	networkUsageMap, err := GetNetworkUsageData(resultNetZoneRequests, resultNetRegionRequests, resultNetInternetRequests, false)
+	if err != nil {
+		klog.V(1).Infof("Unable to get Network Cost Data: %s", err.Error())
+		networkUsageMap = make(map[string]*NetworkUsageData)
+	}
+
 	containerNameCost := make(map[string]*CostData)
 	containers := make(map[string]bool)
 
@@ -455,6 +483,16 @@ func (cm *CostModel) ComputeCostData(cli prometheusClient.Client, clientset kube
 				}
 			}
 
+			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 {
@@ -497,8 +535,10 @@ func (cm *CostModel) ComputeCostData(cli prometheusClient.Client, clientset kube
 				}
 
 				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{
@@ -518,6 +558,7 @@ func (cm *CostModel) ComputeCostData(cli prometheusClient.Client, clientset kube
 					CPUUsed:         CPUUsedV,
 					GPUReq:          GPUReqV,
 					PVCData:         pvReq,
+					NetworkData:     netReq,
 					Labels:          podLabels,
 					NamespaceLabels: nsLabels,
 					ClusterID:       clustID,
@@ -1069,6 +1110,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"
@@ -1099,7 +1143,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{}
@@ -1132,6 +1176,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)
@@ -1194,6 +1253,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())
+		networkUsageMap = make(map[string]*NetworkUsageData)
+	}
+
 	containerNameCost := make(map[string]*CostData)
 	containers := make(map[string]bool)
 
@@ -1282,6 +1347,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 {
@@ -1336,8 +1411,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{
@@ -1358,6 +1435,7 @@ func (cm *CostModel) ComputeCostDataRange(cli prometheusClient.Client, clientset
 					GPUReq:          GPUReqV,
 					PVCData:         pvReq,
 					Labels:          podLabels,
+					NetworkData:     netReq,
 					NamespaceLabels: nsLabels,
 					ClusterID:       clustID,
 				}

+ 307 - 0
costmodel/networkcosts.go

@@ -0,0 +1,307 @@
+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
+}

+ 34 - 0
main.go

@@ -55,6 +55,9 @@ type Accesses struct {
 	GPUAllocationRecorder         *prometheus.GaugeVec
 	PVAllocationRecorder          *prometheus.GaugeVec
 	ContainerUptimeRecorder       *prometheus.GaugeVec
+	NetworkZoneEgressRecorder     prometheus.Gauge
+	NetworkRegionEgressRecorder   prometheus.Gauge
+	NetworkInternetEgressRecorder prometheus.Gauge
 	ServiceSelectorRecorder       *prometheus.GaugeVec
 	DeploymentSelectorRecorder    *prometheus.GaugeVec
 	Model                         *costModel.CostModel
@@ -484,6 +487,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())
@@ -727,6 +741,19 @@ func main() {
 		Help: "container_uptime_seconds Seconds a container has been running",
 	}, []string{"namespace", "pod", "container"})
 
+	NetworkZoneEgressRecorder := prometheus.NewGauge(prometheus.GaugeOpts{
+		Name: "kubecost_network_zone_egress_cost",
+		Help: "kubecost_network_zone_egress_cost Total cost per GB egress across zones",
+	})
+	NetworkRegionEgressRecorder := prometheus.NewGauge(prometheus.GaugeOpts{
+		Name: "kubecost_network_region_egress_cost",
+		Help: "kubecost_network_region_egress_cost Total cost per GB egress across regions",
+	})
+	NetworkInternetEgressRecorder := prometheus.NewGauge(prometheus.GaugeOpts{
+		Name: "kubecost_network_internet_egress_cost",
+		Help: "kubecost_network_internet_egress_cost Total cost per GB of internet egress.",
+	})
+
 	prometheus.MustRegister(cpuGv)
 	prometheus.MustRegister(ramGv)
 	prometheus.MustRegister(gpuGv)
@@ -735,7 +762,11 @@ func main() {
 	prometheus.MustRegister(RAMAllocation)
 	prometheus.MustRegister(CPUAllocation)
 	prometheus.MustRegister(ContainerUptimeRecorder)
+<<<<<<< HEAD
 	prometheus.MustRegister(PVAllocation)
+=======
+	prometheus.MustRegister(NetworkZoneEgressRecorder, NetworkRegionEgressRecorder, NetworkInternetEgressRecorder)
+>>>>>>> 82ec229e122566565cb40a9930c7500ba5b59b1c
 	prometheus.MustRegister(costModel.ServiceCollector{
 		KubeClientSet: kubeClientset,
 	})
@@ -756,6 +787,9 @@ func main() {
 		GPUAllocationRecorder:         GPUAllocation,
 		PVAllocationRecorder:          PVAllocation,
 		ContainerUptimeRecorder:       ContainerUptimeRecorder,
+		NetworkZoneEgressRecorder:     NetworkZoneEgressRecorder,
+		NetworkRegionEgressRecorder:   NetworkRegionEgressRecorder,
+		NetworkInternetEgressRecorder: NetworkInternetEgressRecorder,
 		PersistentVolumePriceRecorder: pvGv,
 		Model:                         costModel.NewCostModel(kubeClientset),
 	}