intervals.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  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 representative 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, resolution time.Duration) 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. // Reset the start time by the offset as well. so that offset is not used in coefficient calculation!
  68. startTime := thisPVC.Start.Add(resolution)
  69. pvcWindow := kubecost.NewWindow(&startTime, &thisPVC.End)
  70. unmountedKey := getUnmountedPodKey(thisPVC.Cluster)
  71. var void struct{}
  72. activeKeys := map[podKey]struct{}{}
  73. currentTime := startTime
  74. // For each interval i.e. for any time a pod-PVC relation ends or starts...
  75. for _, point := range intervals {
  76. // If the current point happens at a later time than the previous point
  77. if !point.Time.Equal(currentTime) {
  78. for key := range activeKeys {
  79. pvcCostCoefficientMap[key] = append(
  80. pvcCostCoefficientMap[key],
  81. CoefficientComponent{
  82. Time: point.Time.Sub(currentTime).Minutes() / pvcWindow.Duration().Minutes(),
  83. Proportion: 1.0 / float64(len(activeKeys)),
  84. },
  85. )
  86. }
  87. // If there are no active keys attribute all cost to the unmounted pv
  88. if len(activeKeys) == 0 {
  89. pvcCostCoefficientMap[unmountedKey] = append(
  90. pvcCostCoefficientMap[unmountedKey],
  91. CoefficientComponent{
  92. Time: point.Time.Sub(currentTime).Minutes() / pvcWindow.Duration().Minutes(),
  93. Proportion: 1.0,
  94. },
  95. )
  96. }
  97. }
  98. // If the point was a start, increment and track
  99. if point.PointType == "start" {
  100. activeKeys[point.Key] = void
  101. }
  102. // If the point was an end, decrement and stop tracking
  103. if point.PointType == "end" {
  104. delete(activeKeys, point.Key)
  105. }
  106. currentTime = point.Time
  107. }
  108. // If all pod intervals end before the end of the PVC attribute the remaining cost to unmounted
  109. if currentTime.Before(thisPVC.End) {
  110. pvcCostCoefficientMap[unmountedKey] = append(
  111. pvcCostCoefficientMap[unmountedKey],
  112. CoefficientComponent{
  113. Time: thisPVC.End.Sub(currentTime).Minutes() / pvcWindow.Duration().Minutes(),
  114. Proportion: 1.0,
  115. },
  116. )
  117. }
  118. return pvcCostCoefficientMap
  119. }
  120. // getCoefficientFromComponents takes the components of a PVC-pod PV cost coefficient
  121. // determined by getPVCCostCoefficient and gets the resulting single
  122. // floating point coefficient.
  123. func getCoefficientFromComponents(coefficientComponents []CoefficientComponent) float64 {
  124. coefficient := 0.0
  125. for i := range coefficientComponents {
  126. proportion := coefficientComponents[i].Proportion
  127. time := coefficientComponents[i].Time
  128. coefficient += proportion * time
  129. }
  130. return coefficient
  131. }