networkcosts.go 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. package costmodel
  2. import (
  3. "fmt"
  4. "math"
  5. "strconv"
  6. costAnalyzerCloud "github.com/kubecost/cost-model/cloud"
  7. "k8s.io/klog"
  8. )
  9. // NetworkUsageVNetworkUsageDataector contains the network usage values for egress network traffic
  10. type NetworkUsageData struct {
  11. ClusterID string
  12. PodName string
  13. Namespace string
  14. NetworkZoneEgress []*Vector
  15. NetworkRegionEgress []*Vector
  16. NetworkInternetEgress []*Vector
  17. }
  18. // NetworkUsageVector contains a network usage vector for egress network traffic
  19. type NetworkUsageVector struct {
  20. ClusterID string
  21. PodName string
  22. Namespace string
  23. Values []*Vector
  24. }
  25. // GetNetworkUsageData performs a join of the the results of zone, region, and internet usage queries to return a single
  26. // map containing network costs for each namespace+pod
  27. func GetNetworkUsageData(zr interface{}, rr interface{}, ir interface{}, defaultClusterID string, isRange bool) (map[string]*NetworkUsageData, error) {
  28. var vectorFn func(interface{}, string) (map[string]*NetworkUsageVector, error)
  29. if isRange {
  30. vectorFn = getNetworkUsageVectors
  31. } else {
  32. vectorFn = getNetworkUsageVector
  33. }
  34. zoneNetworkMap, err := vectorFn(zr, defaultClusterID)
  35. if err != nil {
  36. return nil, err
  37. }
  38. regionNetworkMap, err := vectorFn(rr, defaultClusterID)
  39. if err != nil {
  40. return nil, err
  41. }
  42. internetNetworkMap, err := vectorFn(ir, defaultClusterID)
  43. if err != nil {
  44. return nil, err
  45. }
  46. usageData := make(map[string]*NetworkUsageData)
  47. for k, v := range zoneNetworkMap {
  48. existing, ok := usageData[k]
  49. if !ok {
  50. usageData[k] = &NetworkUsageData{
  51. ClusterID: v.ClusterID,
  52. PodName: v.PodName,
  53. Namespace: v.Namespace,
  54. NetworkZoneEgress: v.Values,
  55. }
  56. continue
  57. }
  58. existing.NetworkZoneEgress = v.Values
  59. }
  60. for k, v := range regionNetworkMap {
  61. existing, ok := usageData[k]
  62. if !ok {
  63. usageData[k] = &NetworkUsageData{
  64. ClusterID: v.ClusterID,
  65. PodName: v.PodName,
  66. Namespace: v.Namespace,
  67. NetworkRegionEgress: v.Values,
  68. }
  69. continue
  70. }
  71. existing.NetworkRegionEgress = v.Values
  72. }
  73. for k, v := range internetNetworkMap {
  74. existing, ok := usageData[k]
  75. if !ok {
  76. usageData[k] = &NetworkUsageData{
  77. ClusterID: v.ClusterID,
  78. PodName: v.PodName,
  79. Namespace: v.Namespace,
  80. NetworkInternetEgress: v.Values,
  81. }
  82. continue
  83. }
  84. existing.NetworkInternetEgress = v.Values
  85. }
  86. return usageData, nil
  87. }
  88. // GetNetworkCost computes the actual cost for NetworkUsageData based on data provided by the Provider.
  89. func GetNetworkCost(usage *NetworkUsageData, cloud costAnalyzerCloud.Provider) ([]*Vector, error) {
  90. var results []*Vector
  91. pricing, err := cloud.NetworkPricing()
  92. if err != nil {
  93. return nil, err
  94. }
  95. zoneCost := pricing.ZoneNetworkEgressCost
  96. regionCost := pricing.RegionNetworkEgressCost
  97. internetCost := pricing.InternetNetworkEgressCost
  98. zlen := len(usage.NetworkZoneEgress)
  99. rlen := len(usage.NetworkRegionEgress)
  100. ilen := len(usage.NetworkInternetEgress)
  101. l := max(zlen, rlen, ilen)
  102. for i := 0; i < l; i++ {
  103. var cost float64 = 0
  104. var timestamp float64
  105. if i < zlen {
  106. cost += usage.NetworkZoneEgress[i].Value * zoneCost
  107. timestamp = usage.NetworkZoneEgress[i].Timestamp
  108. }
  109. if i < rlen {
  110. cost += usage.NetworkRegionEgress[i].Value * regionCost
  111. timestamp = usage.NetworkRegionEgress[i].Timestamp
  112. }
  113. if i < ilen {
  114. cost += usage.NetworkInternetEgress[i].Value * internetCost
  115. timestamp = usage.NetworkInternetEgress[i].Timestamp
  116. }
  117. results = append(results, &Vector{
  118. Value: cost,
  119. Timestamp: timestamp,
  120. })
  121. }
  122. return results, nil
  123. }
  124. func getNetworkUsageVector(qr interface{}, defaultClusterID string) (map[string]*NetworkUsageVector, error) {
  125. ncdmap := make(map[string]*NetworkUsageVector)
  126. data, ok := qr.(map[string]interface{})["data"]
  127. if !ok {
  128. e, err := wrapPrometheusError(qr)
  129. if err != nil {
  130. return nil, err
  131. }
  132. return nil, fmt.Errorf(e)
  133. }
  134. d, ok := data.(map[string]interface{})
  135. if !ok {
  136. return nil, fmt.Errorf("Data field improperly formatted in prometheus repsonse")
  137. }
  138. result, ok := d["result"]
  139. if !ok {
  140. return nil, fmt.Errorf("Result field not present in prometheus response")
  141. }
  142. results, ok := result.([]interface{})
  143. if !ok {
  144. return nil, fmt.Errorf("Result field improperly formatted in prometheus response")
  145. }
  146. for _, val := range results {
  147. metricInterface, ok := val.(map[string]interface{})["metric"]
  148. if !ok {
  149. return nil, fmt.Errorf("Metric field does not exist in data result vector")
  150. }
  151. metricMap, ok := metricInterface.(map[string]interface{})
  152. if !ok {
  153. return nil, fmt.Errorf("Metric field is improperly formatted")
  154. }
  155. podName, ok := metricMap["pod_name"]
  156. if !ok {
  157. return nil, fmt.Errorf("Pod Name does not exist in data result vector")
  158. }
  159. podNameStr, ok := podName.(string)
  160. if !ok {
  161. return nil, fmt.Errorf("Pod Name field improperly formatted")
  162. }
  163. namespace, ok := metricMap["namespace"]
  164. if !ok {
  165. return nil, fmt.Errorf("Namespace field does not exist in data result vector")
  166. }
  167. namespaceStr, ok := namespace.(string)
  168. if !ok {
  169. return nil, fmt.Errorf("Namespace field improperly formatted")
  170. }
  171. cid, ok := metricMap["cluster_id"]
  172. if !ok {
  173. klog.V(4).Info("Prometheus vector does not have cluster id")
  174. cid = defaultClusterID
  175. }
  176. clusterID, ok := cid.(string)
  177. if !ok {
  178. return nil, fmt.Errorf("Prometheus vector does not have string cluster_id")
  179. }
  180. dataPoint, ok := val.(map[string]interface{})["value"]
  181. if !ok {
  182. return nil, fmt.Errorf("Value field does not exist in data result vector")
  183. }
  184. value, ok := dataPoint.([]interface{})
  185. if !ok || len(value) != 2 {
  186. return nil, fmt.Errorf("Improperly formatted datapoint from Prometheus")
  187. }
  188. var vectors []*Vector
  189. strVal := value[1].(string)
  190. v, err := strconv.ParseFloat(strVal, 64)
  191. if err != nil {
  192. return nil, err
  193. }
  194. vectors = append(vectors, &Vector{
  195. Timestamp: value[0].(float64),
  196. Value: v,
  197. })
  198. key := namespaceStr + "," + podNameStr + "," + clusterID
  199. ncdmap[key] = &NetworkUsageVector{
  200. ClusterID: clusterID,
  201. Namespace: namespaceStr,
  202. PodName: podNameStr,
  203. Values: vectors,
  204. }
  205. }
  206. return ncdmap, nil
  207. }
  208. func getNetworkUsageVectors(qr interface{}, defaultClusterID string) (map[string]*NetworkUsageVector, error) {
  209. ncdmap := make(map[string]*NetworkUsageVector)
  210. data, ok := qr.(map[string]interface{})["data"]
  211. if !ok {
  212. e, err := wrapPrometheusError(qr)
  213. if err != nil {
  214. return nil, err
  215. }
  216. return nil, fmt.Errorf(e)
  217. }
  218. d, ok := data.(map[string]interface{})
  219. if !ok {
  220. return nil, fmt.Errorf("Data field improperly formatted in prometheus repsonse")
  221. }
  222. result, ok := d["result"]
  223. if !ok {
  224. return nil, fmt.Errorf("Result field not present in prometheus response")
  225. }
  226. results, ok := result.([]interface{})
  227. if !ok {
  228. return nil, fmt.Errorf("Result field improperly formatted in prometheus response")
  229. }
  230. for _, val := range results {
  231. metricInterface, ok := val.(map[string]interface{})["metric"]
  232. if !ok {
  233. return nil, fmt.Errorf("Metric field does not exist in data result vector")
  234. }
  235. metricMap, ok := metricInterface.(map[string]interface{})
  236. if !ok {
  237. return nil, fmt.Errorf("Metric field is improperly formatted")
  238. }
  239. podName, ok := metricMap["pod_name"]
  240. if !ok {
  241. return nil, fmt.Errorf("Pod Name does not exist in data result vector")
  242. }
  243. podNameStr, ok := podName.(string)
  244. if !ok {
  245. return nil, fmt.Errorf("Pod Name field improperly formatted")
  246. }
  247. namespace, ok := metricMap["namespace"]
  248. if !ok {
  249. return nil, fmt.Errorf("Namespace field does not exist in data result vector")
  250. }
  251. namespaceStr, ok := namespace.(string)
  252. if !ok {
  253. return nil, fmt.Errorf("Namespace field improperly formatted")
  254. }
  255. cid, ok := metricMap["cluster_id"]
  256. if !ok {
  257. klog.V(4).Info("Prometheus vector does not have cluster id")
  258. cid = defaultClusterID
  259. }
  260. clusterID, ok := cid.(string)
  261. if !ok {
  262. return nil, fmt.Errorf("Prometheus vector does not have string cluster_id")
  263. }
  264. values, ok := val.(map[string]interface{})["values"].([]interface{})
  265. if !ok {
  266. return nil, fmt.Errorf("Values field is improperly formatted")
  267. }
  268. var vectors []*Vector
  269. for _, value := range values {
  270. dataPoint, ok := value.([]interface{})
  271. if !ok || len(dataPoint) != 2 {
  272. return nil, fmt.Errorf("Improperly formatted datapoint from Prometheus")
  273. }
  274. strVal := dataPoint[1].(string)
  275. v, _ := strconv.ParseFloat(strVal, 64)
  276. vectors = append(vectors, &Vector{
  277. Timestamp: math.Round(dataPoint[0].(float64)/10) * 10,
  278. Value: v,
  279. })
  280. }
  281. key := namespaceStr + "," + podNameStr + "," + clusterID
  282. ncdmap[key] = &NetworkUsageVector{
  283. ClusterID: clusterID,
  284. Namespace: namespaceStr,
  285. PodName: podNameStr,
  286. Values: vectors,
  287. }
  288. }
  289. return ncdmap, nil
  290. }
  291. func max(x int, rest ...int) int {
  292. curr := x
  293. for _, v := range rest {
  294. if v > curr {
  295. curr = v
  296. }
  297. }
  298. return curr
  299. }