intervals.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  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, podInterval := range windows {
  53. start := NewIntervalPoint(*podInterval.Start(), "start", podKey)
  54. end := NewIntervalPoint(*podInterval.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, pvcIntervalMap map[podKey]kubecost.Window) map[podKey][]CoefficientComponent {
  64. pvcCostCoefficientMap := make(map[podKey][]CoefficientComponent)
  65. // pvcCostCoefficientMap is mutated in this function. The format is
  66. // such that the individual coefficient components are preserved for
  67. // testing purposes.
  68. activeKeys := map[podKey]struct{}{
  69. intervals[0].Key: struct{}{},
  70. }
  71. // For each interval i.e. for any time a pod-PVC relation ends or starts...
  72. for i := 1; i < len(intervals); i++ {
  73. // intervals will always have at least two IntervalPoints (one start/end)
  74. point := intervals[i]
  75. prevPoint := intervals[i-1]
  76. // If the current point happens at a later time than the previous point
  77. if !point.Time.Equal(prevPoint.Time) {
  78. for key := range activeKeys {
  79. if pvcIntervalMap[key].Duration().Minutes() != 0 {
  80. pvcCostCoefficientMap[key] = append(
  81. pvcCostCoefficientMap[key],
  82. CoefficientComponent{
  83. Time: point.Time.Sub(prevPoint.Time).Minutes() / pvcIntervalMap[key].Duration().Minutes(),
  84. Proportion: 1.0 / float64(len(activeKeys)),
  85. },
  86. )
  87. }
  88. }
  89. }
  90. // If the point was a start, increment and track
  91. if point.PointType == "start" {
  92. activeKeys[point.Key] = struct{}{}
  93. }
  94. // If the point was an end, decrement and stop tracking
  95. if point.PointType == "end" {
  96. delete(activeKeys, point.Key)
  97. }
  98. }
  99. return pvcCostCoefficientMap
  100. }
  101. // getCoefficientFromComponents takes the components of a PVC-pod PV cost coefficient
  102. // determined by getPVCCostCoefficient and gets the resulting single
  103. // floating point coefficient.
  104. func getCoefficientFromComponents(coefficientComponents []CoefficientComponent) float64 {
  105. coefficient := 0.0
  106. for i := range coefficientComponents {
  107. proportion := coefficientComponents[i].Proportion
  108. time := coefficientComponents[i].Time
  109. coefficient += proportion * time
  110. }
  111. return coefficient
  112. }