2
0

intervals.go 5.2 KB

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