intervals.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. package costmodel
  2. import (
  3. "sort"
  4. "time"
  5. "github.com/opencost/opencost/pkg/kubecost"
  6. )
  7. // IntervalPoint describes a start or end of a window of time
  8. // Currently, this used in PVC-pod relations to detect/calculate
  9. // coefficients for PV cost when a PVC is shared between pods.
  10. type IntervalPoint struct {
  11. Time time.Time
  12. PointType string
  13. Key podKey
  14. }
  15. // IntervalPoints describes a slice of IntervalPoint structs
  16. type IntervalPoints []IntervalPoint
  17. // Requisite functions for implementing sort.Sort for
  18. // IntervalPointList
  19. func (ips IntervalPoints) Len() int {
  20. return len(ips)
  21. }
  22. func (ips IntervalPoints) Less(i, j int) bool {
  23. if ips[i].Time.Equal(ips[j].Time) {
  24. return ips[i].PointType == "start" && ips[j].PointType == "end"
  25. }
  26. return ips[i].Time.Before(ips[j].Time)
  27. }
  28. func (ips IntervalPoints) Swap(i, j int) {
  29. ips[i], ips[j] = ips[j], ips[i]
  30. }
  31. // NewIntervalPoint creates and returns a new IntervalPoint instance with given parameters.
  32. func NewIntervalPoint(time time.Time, pointType string, key podKey) IntervalPoint {
  33. return IntervalPoint{
  34. Time: time,
  35. PointType: pointType,
  36. Key: key,
  37. }
  38. }
  39. // CoefficientComponent is a representitive struct holding two fields which describe an interval
  40. // as part of a single number cost coefficient calculation:
  41. // 1. Proportion: The division of cost based on how many pods were running between those points
  42. // 2. Time: The ratio of the time between those points to the total time that pod was running
  43. type CoefficientComponent struct {
  44. Proportion float64
  45. Time float64
  46. }
  47. // getIntervalPointFromWindows takes a map of podKeys to windows
  48. // and returns a sorted list of IntervalPoints representing the
  49. // starts and ends of all those windows.
  50. func getIntervalPointsFromWindows(windows map[podKey]kubecost.Window) IntervalPoints {
  51. var intervals IntervalPoints
  52. for podKey, podWindow := range windows {
  53. start := NewIntervalPoint(*podWindow.Start(), "start", podKey)
  54. end := NewIntervalPoint(*podWindow.End(), "end", podKey)
  55. intervals = append(intervals, []IntervalPoint{start, end}...)
  56. }
  57. sort.Sort(intervals)
  58. return intervals
  59. }
  60. // getPVCCostCoefficients gets a coefficient which represents the scale
  61. // factor that each PVC in a pvcIntervalMap and corresponding slice of
  62. // IntervalPoints intervals uses to calculate a cost for that PVC's PV.
  63. func getPVCCostCoefficients(intervals IntervalPoints, thisPVC *pvc) map[podKey][]CoefficientComponent {
  64. // pvcCostCoefficientMap has a format such that the individual coefficient
  65. // components are preserved for testing purposes.
  66. pvcCostCoefficientMap := make(map[podKey][]CoefficientComponent)
  67. pvcWindow := kubecost.NewWindow(&thisPVC.Start, &thisPVC.End)
  68. unmountedKey := getUnmountedPodKey(thisPVC.Cluster)
  69. var void struct{}
  70. activeKeys := map[podKey]struct{}{}
  71. currentTime := thisPVC.Start
  72. // For each interval i.e. for any time a pod-PVC relation ends or starts...
  73. for _, point := range intervals {
  74. // If the current point happens at a later time than the previous point
  75. if !point.Time.Equal(currentTime) {
  76. for key := range activeKeys {
  77. pvcCostCoefficientMap[key] = append(
  78. pvcCostCoefficientMap[key],
  79. CoefficientComponent{
  80. Time: point.Time.Sub(currentTime).Minutes() / pvcWindow.Duration().Minutes(),
  81. Proportion: 1.0 / float64(len(activeKeys)),
  82. },
  83. )
  84. }
  85. // If there are no active keys attribute all cost to the unmounted pv
  86. if len(activeKeys) == 0 {
  87. pvcCostCoefficientMap[unmountedKey] = append(
  88. pvcCostCoefficientMap[unmountedKey],
  89. CoefficientComponent{
  90. Time: point.Time.Sub(currentTime).Minutes() / pvcWindow.Duration().Minutes(),
  91. Proportion: 1.0,
  92. },
  93. )
  94. }
  95. }
  96. // If the point was a start, increment and track
  97. if point.PointType == "start" {
  98. activeKeys[point.Key] = void
  99. }
  100. // If the point was an end, decrement and stop tracking
  101. if point.PointType == "end" {
  102. delete(activeKeys, point.Key)
  103. }
  104. currentTime = point.Time
  105. }
  106. // If all pod intervals end before the end of the PVC attribute the remaining cost to unmounted
  107. if currentTime.Before(thisPVC.End) {
  108. pvcCostCoefficientMap[unmountedKey] = append(
  109. pvcCostCoefficientMap[unmountedKey],
  110. CoefficientComponent{
  111. Time: thisPVC.End.Sub(currentTime).Minutes() / pvcWindow.Duration().Minutes(),
  112. Proportion: 1.0,
  113. },
  114. )
  115. }
  116. return pvcCostCoefficientMap
  117. }
  118. // getCoefficientFromComponents takes the components of a PVC-pod PV cost coefficient
  119. // determined by getPVCCostCoefficient and gets the resulting single
  120. // floating point coefficient.
  121. func getCoefficientFromComponents(coefficientComponents []CoefficientComponent) float64 {
  122. coefficient := 0.0
  123. for i := range coefficientComponents {
  124. proportion := coefficientComponents[i].Proportion
  125. time := coefficientComponents[i].Time
  126. coefficient += proportion * time
  127. }
  128. return coefficient
  129. }