intervals.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. package costmodel
  2. import (
  3. "sort"
  4. "time"
  5. "github.com/kubecost/cost-model/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. // CoefficientComponent is a representitive struct holding two fields which describe an interval
  16. // as part of a single number cost coefficient calculation:
  17. // 1. Proportion: The division of cost based on how many pods were running between those points
  18. // 2. Time: The ratio of the time between those points to the total time that pod was running
  19. type CoefficientComponent struct {
  20. Proportion float64
  21. Time float64
  22. }
  23. // NewIntervalPoint creates and returns a new IntervalPoint instance with given parameters.
  24. func NewIntervalPoint(time time.Time, pointType string, key podKey) IntervalPoint {
  25. return IntervalPoint{
  26. Time: time,
  27. PointType: pointType,
  28. Key: key,
  29. }
  30. }
  31. // getIntervalPointFromWindows takes a map of podKeys to windows
  32. // and returns a sorted list of IntervalPoints representing the
  33. // starts and ends of all those windows.
  34. func getIntervalPointsFromWindows(windows map[podKey]kubecost.Window) []IntervalPoint {
  35. var intervals []IntervalPoint
  36. for podKey, podInterval := range windows {
  37. start := NewIntervalPoint(*podInterval.Start(), "start", podKey)
  38. end := NewIntervalPoint(*podInterval.End(), "end", podKey)
  39. intervals = append(intervals, []IntervalPoint{start, end}...)
  40. }
  41. sortIntervalPoints(intervals)
  42. return intervals
  43. }
  44. // sortIntervalPoints sorts a list of IntervalPoints from earliest
  45. // to latest IntervalPoint.Time. In the case that two IntervalPoints
  46. // have the same time, the point with Type "start" is treated as coming
  47. // before the "end".
  48. func sortIntervalPoints(intervals []IntervalPoint) {
  49. sort.Slice(intervals, func(i, j int) bool {
  50. if intervals[i].Time.Equal(intervals[j].Time) {
  51. return intervals[i].PointType == "start" && intervals[j].PointType == "end"
  52. }
  53. return intervals[i].Time.Before(intervals[j].Time)
  54. })
  55. }
  56. // getPVCCostCoefficients gets a coefficient which represents the scale
  57. // factor that each PVC in a pvcIntervalMap and corresponding slice of
  58. // IntervalPoints intervals uses to calculate a cost for that PVC's PV.
  59. func getPVCCostCoefficients(intervals []IntervalPoint, pvcIntervalMap map[podKey]kubecost.Window) map[podKey][]CoefficientComponent {
  60. pvcCostCoefficientMap := make(map[podKey][]CoefficientComponent)
  61. // pvcCostCoefficientMap is mutated in this function. The format is
  62. // such that the individual coefficient components are preserved for
  63. // testing purposes.
  64. activePods := 1.0
  65. activeKeys := map[podKey]struct{}{
  66. intervals[0].Key: struct{}{},
  67. }
  68. // For each interval i.e. for any time a pod-PVC relation ends or starts...
  69. for i := 1; i < len(intervals); i++ {
  70. // intervals will always have at least two IntervalPoints (one start/end)
  71. point := intervals[i]
  72. prevPoint := intervals[i-1]
  73. // If the current point happens at a later time than the previous point
  74. if !point.Time.Equal(prevPoint.Time) {
  75. for key := range activeKeys {
  76. pvcCostCoefficientMap[key] = append(
  77. pvcCostCoefficientMap[key],
  78. CoefficientComponent{
  79. Time: point.Time.Sub(prevPoint.Time).Minutes() / pvcIntervalMap[key].Duration().Minutes(),
  80. Proportion: 1.0 / activePods,
  81. },
  82. )
  83. }
  84. }
  85. // If the point was a start, increment and track
  86. if point.PointType == "start" {
  87. activePods += 1
  88. activeKeys[point.Key] = struct{}{}
  89. }
  90. // If the point was an end, decrement and stop tracking
  91. if point.PointType == "end" {
  92. activePods -= 1
  93. delete(activeKeys, point.Key)
  94. }
  95. }
  96. return pvcCostCoefficientMap
  97. }
  98. // getCoefficient takes the components of a PVC-pod PV cost coefficient
  99. // determined by getPVCCostCoefficient and gets the resulting single
  100. // floating point coefficient.
  101. func getCoefficient(coefficientComponents []CoefficientComponent) float64 {
  102. coefficient := 0.0
  103. for i := range coefficientComponents {
  104. proportion := coefficientComponents[i].Proportion
  105. time := coefficientComponents[i].Time
  106. coefficient += proportion * time
  107. }
  108. return coefficient
  109. }