summaryallocation.go 57 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871
  1. package opencost
  2. import (
  3. "errors"
  4. "fmt"
  5. "strings"
  6. "sync"
  7. "time"
  8. "github.com/opencost/opencost/core/pkg/filter/ast"
  9. "github.com/opencost/opencost/core/pkg/filter/matcher"
  10. "github.com/opencost/opencost/core/pkg/log"
  11. "github.com/opencost/opencost/core/pkg/util/timeutil"
  12. )
  13. // SummaryAllocation summarizes an Allocation, keeping only fields necessary
  14. // for providing a high-level view of identifying the Allocation over a period
  15. // of time (Start, End) over which it ran, and inspecting the associated per-
  16. // resource costs (subtotaled with adjustments), total cost, and efficiency.
  17. //
  18. // SummaryAllocation does not have a concept of Window (i.e. the time period
  19. // within which it is defined, as opposed to the Start and End times). That
  20. // context must be provided by a SummaryAllocationSet.
  21. type SummaryAllocation struct {
  22. Name string `json:"name"`
  23. Properties *AllocationProperties `json:"-"`
  24. Start time.Time `json:"start"`
  25. End time.Time `json:"end"`
  26. CPUCoreRequestAverage float64 `json:"cpuCoreRequestAverage"`
  27. CPUCoreUsageAverage float64 `json:"cpuCoreUsageAverage"`
  28. CPUCost float64 `json:"cpuCost"`
  29. CPUCostIdle float64 `json:"cpuCostIdle"`
  30. GPURequestAverage *float64 `json:"gpuRequestAverage"`
  31. GPUUsageAverage *float64 `json:"gpuUsageAverage"`
  32. GPUCost float64 `json:"gpuCost"`
  33. GPUCostIdle float64 `json:"gpuCostIdle"`
  34. NetworkCost float64 `json:"networkCost"`
  35. LoadBalancerCost float64 `json:"loadBalancerCost"`
  36. PVCost float64 `json:"pvCost"`
  37. RAMBytesRequestAverage float64 `json:"ramByteRequestAverage"`
  38. RAMBytesUsageAverage float64 `json:"ramByteUsageAverage"`
  39. RAMCost float64 `json:"ramCost"`
  40. RAMCostIdle float64 `json:"ramCostIdle"`
  41. SharedCost float64 `json:"sharedCost"`
  42. ExternalCost float64 `json:"externalCost"`
  43. Share bool `json:"-"`
  44. UnmountedPVCost float64 `json:"-"`
  45. Efficiency float64 `json:"efficiency"`
  46. }
  47. // NewSummaryAllocation converts an Allocation to a SummaryAllocation by
  48. // dropping unnecessary fields and consolidating others (e.g. adjustments).
  49. // Reconciliation happens here because that process is synonymous with the
  50. // consolidation of adjustment fields.
  51. func NewSummaryAllocation(alloc *Allocation, reconcile, reconcileNetwork bool) *SummaryAllocation {
  52. if alloc == nil {
  53. return nil
  54. }
  55. var gpuRequestAvg, gpuUsageAvg *float64
  56. if alloc.GPUAllocation != nil {
  57. gpuRequestAvg = alloc.GPUAllocation.GPURequestAverage
  58. gpuUsageAvg = alloc.GPUAllocation.GPUUsageAverage
  59. }
  60. sa := &SummaryAllocation{
  61. Name: alloc.Name,
  62. Properties: alloc.Properties,
  63. Start: alloc.Start,
  64. End: alloc.End,
  65. CPUCoreRequestAverage: alloc.CPUCoreRequestAverage,
  66. CPUCoreUsageAverage: alloc.CPUCoreUsageAverage,
  67. CPUCost: alloc.CPUCost + alloc.CPUCostAdjustment,
  68. CPUCostIdle: alloc.CPUCostIdle,
  69. GPURequestAverage: gpuRequestAvg,
  70. GPUUsageAverage: gpuUsageAvg,
  71. GPUCost: alloc.GPUCost + alloc.GPUCostAdjustment,
  72. GPUCostIdle: alloc.GPUCostIdle,
  73. NetworkCost: alloc.NetworkCost + alloc.NetworkCostAdjustment,
  74. LoadBalancerCost: alloc.LoadBalancerCost + alloc.LoadBalancerCostAdjustment,
  75. PVCost: alloc.PVCost() + alloc.PVCostAdjustment,
  76. RAMBytesRequestAverage: alloc.RAMBytesRequestAverage,
  77. RAMBytesUsageAverage: alloc.RAMBytesUsageAverage,
  78. RAMCost: alloc.RAMCost + alloc.RAMCostAdjustment,
  79. RAMCostIdle: alloc.RAMCostIdle,
  80. SharedCost: alloc.SharedCost,
  81. ExternalCost: alloc.ExternalCost,
  82. UnmountedPVCost: alloc.UnmountedPVCost,
  83. }
  84. // Revert adjustments if reconciliation is off. If only network
  85. // reconciliation is off, only revert network adjustment.
  86. if !reconcile {
  87. sa.CPUCost -= alloc.CPUCostAdjustment
  88. sa.GPUCost -= alloc.GPUCostAdjustment
  89. sa.NetworkCost -= alloc.NetworkCostAdjustment
  90. sa.LoadBalancerCost -= alloc.LoadBalancerCostAdjustment
  91. sa.PVCost -= alloc.PVCostAdjustment
  92. sa.RAMCost -= alloc.RAMCostAdjustment
  93. } else if !reconcileNetwork {
  94. sa.NetworkCost -= alloc.NetworkCostAdjustment
  95. }
  96. // If the allocation is unmounted, set UnmountedPVCost to the full PVCost.
  97. if sa.IsUnmounted() {
  98. sa.UnmountedPVCost = sa.PVCost
  99. }
  100. sa.Efficiency = sa.TotalEfficiency()
  101. return sa
  102. }
  103. // Add sums two SummaryAllocations, adding the given SummaryAllocation to the
  104. // receiving one, thus mutating the receiver. For performance reasons, it
  105. // simply drops Properties, so a SummaryAllocation can only be Added once.
  106. func (sa *SummaryAllocation) Add(that *SummaryAllocation) error {
  107. if sa == nil || that == nil {
  108. return errors.New("cannot Add a nil SummaryAllocation")
  109. }
  110. // Once Added, a SummaryAllocation has no Properties. This saves us from
  111. // having to compute the intersection of two sets of Properties, which is
  112. // expensive.
  113. sa.Properties = nil
  114. // Sum non-cumulative fields by turning them into cumulative, adding them,
  115. // and then converting them back into averages after minutes have been
  116. // combined (just below).
  117. cpuReqCoreMins := sa.CPUCoreRequestAverage * sa.Minutes()
  118. cpuReqCoreMins += that.CPUCoreRequestAverage * that.Minutes()
  119. cpuUseCoreMins := sa.CPUCoreUsageAverage * sa.Minutes()
  120. cpuUseCoreMins += that.CPUCoreUsageAverage * that.Minutes()
  121. ramReqByteMins := sa.RAMBytesRequestAverage * sa.Minutes()
  122. ramReqByteMins += that.RAMBytesRequestAverage * that.Minutes()
  123. ramUseByteMins := sa.RAMBytesUsageAverage * sa.Minutes()
  124. ramUseByteMins += that.RAMBytesUsageAverage * that.Minutes()
  125. var gpuReqMins *float64 = nil
  126. if sa.GPURequestAverage != nil {
  127. result := *sa.GPURequestAverage * sa.Minutes()
  128. gpuReqMins = &result
  129. }
  130. if sa.GPURequestAverage != nil && that.GPURequestAverage != nil {
  131. if gpuReqMins == nil {
  132. result := *that.GPURequestAverage * that.Minutes()
  133. gpuReqMins = &result
  134. } else {
  135. result := *gpuReqMins + *that.GPURequestAverage*that.Minutes()
  136. gpuReqMins = &result
  137. }
  138. }
  139. var gpuUseMins *float64 = nil
  140. if sa.GPUUsageAverage != nil {
  141. result := *sa.GPUUsageAverage * sa.Minutes()
  142. gpuUseMins = &result
  143. }
  144. if that.GPUUsageAverage != nil {
  145. if gpuUseMins == nil {
  146. result := *that.GPUUsageAverage * that.Minutes()
  147. gpuUseMins = &result
  148. } else {
  149. result := *gpuUseMins + *that.GPUUsageAverage*that.Minutes()
  150. gpuUseMins = &result
  151. }
  152. }
  153. // Expand Start and End to be the "max" of among the given Allocations
  154. if that.Start.Before(sa.Start) {
  155. sa.Start = that.Start
  156. }
  157. if that.End.After(sa.End) {
  158. sa.End = that.End
  159. }
  160. // Convert cumulative request and usage back into rates
  161. if sa.Minutes() > 0 {
  162. sa.CPUCoreRequestAverage = cpuReqCoreMins / sa.Minutes()
  163. sa.CPUCoreUsageAverage = cpuUseCoreMins / sa.Minutes()
  164. sa.RAMBytesRequestAverage = ramReqByteMins / sa.Minutes()
  165. sa.RAMBytesUsageAverage = ramUseByteMins / sa.Minutes()
  166. var gpuReqAvgVal, gpuUsageAvgVal *float64
  167. if gpuReqMins != nil {
  168. result := *gpuReqMins / sa.Minutes()
  169. gpuReqAvgVal = &result
  170. }
  171. if gpuUseMins != nil {
  172. result := *gpuUseMins / sa.Minutes()
  173. gpuUsageAvgVal = &result
  174. }
  175. sa.GPURequestAverage = gpuReqAvgVal
  176. sa.GPUUsageAverage = gpuUsageAvgVal
  177. } else {
  178. sa.CPUCoreRequestAverage = 0.0
  179. sa.CPUCoreUsageAverage = 0.0
  180. sa.RAMBytesRequestAverage = 0.0
  181. sa.RAMBytesUsageAverage = 0.0
  182. sa.GPURequestAverage = nil
  183. sa.GPUUsageAverage = nil
  184. }
  185. // Sum all cumulative cost fields
  186. sa.CPUCost += that.CPUCost
  187. sa.ExternalCost += that.ExternalCost
  188. sa.GPUCost += that.GPUCost
  189. sa.LoadBalancerCost += that.LoadBalancerCost
  190. sa.NetworkCost += that.NetworkCost
  191. sa.PVCost += that.PVCost
  192. sa.RAMCost += that.RAMCost
  193. sa.SharedCost += that.SharedCost
  194. sa.Efficiency = sa.TotalEfficiency()
  195. return nil
  196. }
  197. // Clone copies the SummaryAllocation and returns the copy
  198. func (sa *SummaryAllocation) Clone() *SummaryAllocation {
  199. return &SummaryAllocation{
  200. Name: sa.Name,
  201. Properties: sa.Properties.Clone(),
  202. Start: sa.Start,
  203. End: sa.End,
  204. CPUCoreRequestAverage: sa.CPUCoreRequestAverage,
  205. CPUCoreUsageAverage: sa.CPUCoreUsageAverage,
  206. CPUCost: sa.CPUCost,
  207. GPURequestAverage: sa.GPURequestAverage,
  208. GPUUsageAverage: sa.GPUUsageAverage,
  209. GPUCost: sa.GPUCost,
  210. NetworkCost: sa.NetworkCost,
  211. LoadBalancerCost: sa.LoadBalancerCost,
  212. PVCost: sa.PVCost,
  213. RAMBytesRequestAverage: sa.RAMBytesRequestAverage,
  214. RAMBytesUsageAverage: sa.RAMBytesUsageAverage,
  215. RAMCost: sa.RAMCost,
  216. SharedCost: sa.SharedCost,
  217. ExternalCost: sa.ExternalCost,
  218. Efficiency: sa.Efficiency,
  219. }
  220. }
  221. // CPUEfficiency is the ratio of usage to request. If there is no request and
  222. // no usage or cost, then efficiency is zero. If there is no request, but there
  223. // is usage or cost, then efficiency is 100%.
  224. func (sa *SummaryAllocation) CPUEfficiency() float64 {
  225. if sa == nil || sa.IsIdle() {
  226. return 0.0
  227. }
  228. if sa.CPUCoreRequestAverage > 0 {
  229. return sa.CPUCoreUsageAverage / sa.CPUCoreRequestAverage
  230. }
  231. if sa.CPUCoreUsageAverage == 0.0 || sa.CPUCost == 0.0 {
  232. return 0.0
  233. }
  234. return 1.0
  235. }
  236. func (sa *SummaryAllocation) Equal(that *SummaryAllocation) bool {
  237. if sa == nil || that == nil {
  238. return false
  239. }
  240. if sa.Name != that.Name {
  241. return false
  242. }
  243. if sa.Start != that.Start {
  244. return false
  245. }
  246. if sa.End != that.End {
  247. return false
  248. }
  249. if sa.CPUCoreRequestAverage != that.CPUCoreRequestAverage {
  250. return false
  251. }
  252. if sa.CPUCoreUsageAverage != that.CPUCoreUsageAverage {
  253. return false
  254. }
  255. if sa.CPUCost != that.CPUCost {
  256. return false
  257. }
  258. if sa.GPURequestAverage != that.GPURequestAverage {
  259. return false
  260. }
  261. if sa.GPUUsageAverage != that.GPUUsageAverage {
  262. return false
  263. }
  264. if sa.GPUCost != that.GPUCost {
  265. return false
  266. }
  267. if sa.NetworkCost != that.NetworkCost {
  268. return false
  269. }
  270. if sa.LoadBalancerCost != that.LoadBalancerCost {
  271. return false
  272. }
  273. if sa.PVCost != that.PVCost {
  274. return false
  275. }
  276. if sa.RAMBytesRequestAverage != that.RAMBytesRequestAverage {
  277. return false
  278. }
  279. if sa.RAMBytesUsageAverage != that.RAMBytesUsageAverage {
  280. return false
  281. }
  282. if sa.RAMCost != that.RAMCost {
  283. return false
  284. }
  285. if sa.SharedCost != that.SharedCost {
  286. return false
  287. }
  288. if sa.ExternalCost != that.ExternalCost {
  289. return false
  290. }
  291. return true
  292. }
  293. func (sa *SummaryAllocation) generateKey(aggregateBy []string, labelConfig *LabelConfig) string {
  294. if sa == nil {
  295. return ""
  296. }
  297. return sa.Properties.GenerateKey(aggregateBy, labelConfig)
  298. }
  299. // IsExternal is true if the given SummaryAllocation represents external costs.
  300. func (sa *SummaryAllocation) IsExternal() bool {
  301. if sa == nil {
  302. return false
  303. }
  304. return strings.Contains(sa.Name, ExternalSuffix)
  305. }
  306. // IsIdle is true if the given SummaryAllocation represents idle costs.
  307. func (sa *SummaryAllocation) IsIdle() bool {
  308. if sa == nil {
  309. return false
  310. }
  311. return strings.Contains(sa.Name, IdleSuffix)
  312. }
  313. // IsUnallocated is true if the given SummaryAllocation represents unallocated
  314. // costs.
  315. func (sa *SummaryAllocation) IsUnallocated() bool {
  316. if sa == nil {
  317. return false
  318. }
  319. return strings.Contains(sa.Name, UnallocatedSuffix)
  320. }
  321. // IsUnmounted is true if the given SummaryAllocation represents unmounted
  322. // volume costs.
  323. func (sa *SummaryAllocation) IsUnmounted() bool {
  324. if sa == nil {
  325. return false
  326. }
  327. return strings.Contains(sa.Name, UnmountedSuffix)
  328. }
  329. // Minutes returns the number of minutes the SummaryAllocation represents, as
  330. // defined by the difference between the end and start times.
  331. func (sa *SummaryAllocation) Minutes() float64 {
  332. if sa == nil {
  333. return 0.0
  334. }
  335. return sa.End.Sub(sa.Start).Minutes()
  336. }
  337. // RAMEfficiency is the ratio of usage to request. If there is no request and
  338. // no usage or cost, then efficiency is zero. If there is no request, but there
  339. // is usage or cost, then efficiency is 100%.
  340. func (sa *SummaryAllocation) RAMEfficiency() float64 {
  341. if sa == nil || sa.IsIdle() {
  342. return 0.0
  343. }
  344. if sa.RAMBytesRequestAverage > 0 {
  345. return sa.RAMBytesUsageAverage / sa.RAMBytesRequestAverage
  346. }
  347. if sa.RAMBytesUsageAverage == 0.0 || sa.RAMCost == 0.0 {
  348. return 0.0
  349. }
  350. return 1.0
  351. }
  352. // TotalCost is the total cost of the SummaryAllocation
  353. func (sa *SummaryAllocation) TotalCost() float64 {
  354. if sa == nil {
  355. return 0.0
  356. }
  357. return sa.CPUCost + sa.GPUCost + sa.RAMCost + sa.PVCost + sa.NetworkCost + sa.LoadBalancerCost + sa.SharedCost + sa.ExternalCost
  358. }
  359. // TotalEfficiency is the cost-weighted average of CPU and RAM efficiency. If
  360. // there is no cost at all, then efficiency is zero.
  361. func (sa *SummaryAllocation) TotalEfficiency() float64 {
  362. if sa == nil || sa.IsIdle() {
  363. return 0.0
  364. }
  365. if sa.RAMCost+sa.CPUCost > 0 {
  366. ramCostEff := sa.RAMEfficiency() * sa.RAMCost
  367. cpuCostEff := sa.CPUEfficiency() * sa.CPUCost
  368. return (ramCostEff + cpuCostEff) / (sa.CPUCost + sa.RAMCost)
  369. }
  370. return 0.0
  371. }
  372. // SummaryAllocationSet stores a set of SummaryAllocations, each with a unique
  373. // name, that share a window. An AllocationSet is mutable, so treat it like a
  374. // threadsafe map.
  375. type SummaryAllocationSet struct {
  376. sync.RWMutex
  377. externalKeys map[string]bool
  378. idleKeys map[string]bool
  379. SummaryAllocations map[string]*SummaryAllocation `json:"allocations"`
  380. Window Window `json:"window"`
  381. }
  382. // NewSummaryAllocationSet converts an AllocationSet to a SummaryAllocationSet.
  383. // Filter functions, keep functions, and reconciliation parameters are
  384. // required for unfortunate reasons to do with performance and legacy order-of-
  385. // operations details, as well as the fact that reconciliation has been
  386. // pushed down to the conversion step between Allocation and SummaryAllocation.
  387. //
  388. // This filter is an AllocationMatcher, not an AST, because at this point we
  389. // already have the data and want to make sure that the filter has already
  390. // gone through a compile step to deal with things like aliases.
  391. func NewSummaryAllocationSet(as *AllocationSet, filter, keep AllocationMatcher, reconcile, reconcileNetwork bool) *SummaryAllocationSet {
  392. if as == nil {
  393. return nil
  394. }
  395. // If we can know the exact size of the map, use it. If filters or sharing
  396. // functions are present, we can't know the size, so we make a default map.
  397. var sasMap map[string]*SummaryAllocation
  398. if filter == nil {
  399. // No filters, so make the map of summary allocations exactly the size
  400. // of the origin allocation set.
  401. sasMap = make(map[string]*SummaryAllocation, len(as.Allocations))
  402. } else {
  403. // There are filters, so start with a standard map
  404. sasMap = make(map[string]*SummaryAllocation)
  405. }
  406. sas := &SummaryAllocationSet{
  407. SummaryAllocations: sasMap,
  408. Window: as.Window.Clone(),
  409. }
  410. for _, alloc := range as.Allocations {
  411. // First, detect if the allocation should be kept. If so, mark it as
  412. // such, insert it, and continue.
  413. if keep != nil && keep.Matches(alloc) {
  414. sa := NewSummaryAllocation(alloc, reconcile, reconcileNetwork)
  415. sa.Share = true
  416. sas.Insert(sa)
  417. continue
  418. }
  419. // If the allocation does not pass any of the given filter functions,
  420. // do not insert it into the set.
  421. if filter != nil && !filter.Matches(alloc) {
  422. continue
  423. }
  424. err := sas.Insert(NewSummaryAllocation(alloc, reconcile, reconcileNetwork))
  425. if err != nil {
  426. log.Errorf("SummaryAllocation: error inserting summary of %s", alloc.Name)
  427. }
  428. }
  429. for key := range as.ExternalKeys {
  430. sas.externalKeys[key] = true
  431. }
  432. for key := range as.IdleKeys {
  433. sas.idleKeys[key] = true
  434. }
  435. return sas
  436. }
  437. // Clone creates a deep copy of the SummaryAllocationSet
  438. func (sas *SummaryAllocationSet) Clone() *SummaryAllocationSet {
  439. sas.RLock()
  440. defer sas.RUnlock()
  441. externalKeys := make(map[string]bool, len(sas.externalKeys))
  442. for k, v := range sas.externalKeys {
  443. externalKeys[k] = v
  444. }
  445. idleKeys := make(map[string]bool, len(sas.idleKeys))
  446. for k, v := range sas.idleKeys {
  447. idleKeys[k] = v
  448. }
  449. summaryAllocations := make(map[string]*SummaryAllocation, len(sas.SummaryAllocations))
  450. for k, v := range sas.SummaryAllocations {
  451. summaryAllocations[k] = v.Clone()
  452. }
  453. return &SummaryAllocationSet{
  454. externalKeys: externalKeys,
  455. idleKeys: idleKeys,
  456. SummaryAllocations: summaryAllocations,
  457. Window: sas.Window.Clone(),
  458. }
  459. }
  460. // Add sums two SummaryAllocationSets, which Adds all SummaryAllocations in the
  461. // given SummaryAllocationSet to their counterparts in the receiving set. Add
  462. // also expands the Window to include both constituent Windows, in the case
  463. // that Add is being used from accumulating (as opposed to aggregating). For
  464. // performance reasons, the function may return either a new set, or an
  465. // unmodified original, so it should not be assumed that the original sets are
  466. // safeuly usable after calling Add.
  467. func (sas *SummaryAllocationSet) Add(that *SummaryAllocationSet) (*SummaryAllocationSet, error) {
  468. if sas == nil || len(sas.SummaryAllocations) == 0 {
  469. return that, nil
  470. }
  471. if that == nil || len(that.SummaryAllocations) == 0 {
  472. return sas, nil
  473. }
  474. if sas.Window.IsOpen() {
  475. return nil, errors.New("cannot add a SummaryAllocationSet with an open window")
  476. }
  477. // Set start, end to min(start), max(end)
  478. start := *sas.Window.Start()
  479. end := *sas.Window.End()
  480. if that.Window.Start().Before(start) {
  481. start = *that.Window.Start()
  482. }
  483. if that.Window.End().After(end) {
  484. end = *that.Window.End()
  485. }
  486. acc := &SummaryAllocationSet{
  487. SummaryAllocations: make(map[string]*SummaryAllocation, len(sas.SummaryAllocations)),
  488. Window: NewClosedWindow(start, end),
  489. }
  490. sas.RLock()
  491. defer sas.RUnlock()
  492. that.RLock()
  493. defer that.RUnlock()
  494. for _, alloc := range sas.SummaryAllocations {
  495. err := acc.Insert(alloc)
  496. if err != nil {
  497. return nil, err
  498. }
  499. }
  500. for _, alloc := range that.SummaryAllocations {
  501. err := acc.Insert(alloc)
  502. if err != nil {
  503. return nil, err
  504. }
  505. }
  506. return acc, nil
  507. }
  508. func (sas *SummaryAllocationSet) GetUnmountedPVCost() float64 {
  509. upvc := 0.0
  510. for _, sa := range sas.SummaryAllocations {
  511. upvc += sa.UnmountedPVCost
  512. }
  513. return upvc
  514. }
  515. // AggregateBy aggregates the Allocations in the given AllocationSet by the given
  516. // AllocationProperty. This will only be legal if the AllocationSet is divisible by the
  517. // given AllocationProperty; e.g. Containers can be divided by Namespace, but not vice-a-versa.
  518. func (sas *SummaryAllocationSet) AggregateBy(aggregateBy []string, options *AllocationAggregationOptions) error {
  519. if sas == nil || len(sas.SummaryAllocations) == 0 {
  520. return nil
  521. }
  522. if sas.Window.IsOpen() {
  523. return errors.New("cannot aggregate a SummaryAllocationSet with an open window")
  524. }
  525. if options == nil {
  526. options = &AllocationAggregationOptions{}
  527. }
  528. if options.LabelConfig == nil {
  529. options.LabelConfig = NewLabelConfig()
  530. }
  531. var filter AllocationMatcher
  532. if options.Filter == nil {
  533. filter = &matcher.AllPass[*Allocation]{}
  534. } else {
  535. compiler := NewAllocationMatchCompiler(options.LabelConfig)
  536. var err error
  537. filter, err = compiler.Compile(options.Filter)
  538. if err != nil {
  539. return fmt.Errorf("compiling filter '%s': %w", ast.ToPreOrderShortString(options.Filter), err)
  540. }
  541. }
  542. if filter == nil {
  543. return fmt.Errorf("unexpected nil filter")
  544. }
  545. // Check if we have any work to do; if not, then early return. If
  546. // aggregateBy is nil, we don't aggregate anything. On the other hand,
  547. // an empty slice implies that we should aggregate everything. (See
  548. // generateKey for why that makes sense.)
  549. shouldAggregate := aggregateBy != nil
  550. shouldKeep := len(options.SharedHourlyCosts) > 0 || options.Share != nil
  551. if !shouldAggregate && !shouldKeep {
  552. return nil
  553. }
  554. // The order of operations for aggregating a SummaryAllotionSet is as
  555. // follows:
  556. //
  557. // 1. Partition external, idle, and shared allocations into separate sets.
  558. // Also, create the resultSet into which the results will be aggregated.
  559. //
  560. // 2. Record resource totals for shared costs and unmounted volumes so
  561. // that we can account for them in computing idle coefficients.
  562. //
  563. // 3. Retrieve pre-computed allocation resource totals, which will be used
  564. // to compute idle sharing coefficients.
  565. //
  566. // 4. Convert shared hourly cost into a cumulative allocation to share,
  567. // and insert it into the share set.
  568. //
  569. // 5. Compute sharing coefficients per-aggregation, if sharing resources.
  570. //
  571. // 6. Distribute idle allocations according to the idle coefficients.
  572. //
  573. // 7. Record allocation resource totals (after filtration) if filters have
  574. // been applied. (Used for filtering proportional amount of idle.)
  575. //
  576. // 8. Generate aggregation key and insert allocation into the output set
  577. //
  578. // 9. If idle is shared and resources are shared, it's probable that some
  579. // amount of idle cost will be shared with a shared resource.
  580. // Distribute that idle cost, if it exists, among the respective shared
  581. // allocations before sharing them with the aggregated allocations.
  582. //
  583. // 10. Apply idle filtration, which "filters" the idle cost, or scales it
  584. // by the proportion of allocation resources remaining after filters
  585. // have been applied.
  586. //
  587. // 11. Distribute shared resources according to sharing coefficients.
  588. //
  589. // 12. Insert external allocations into the result set.
  590. //
  591. // 13. Insert any undistributed idle, in the case that idle
  592. // coefficients end up being zero and some idle is not shared.
  593. //
  594. // 14. Combine all idle allocations into a single idle allocation, unless
  595. // the option to keep idle split by cluster or node is enabled.
  596. // 1. Partition external, idle, and shared allocations into separate sets.
  597. // Also, create the resultSet into which the results will be aggregated.
  598. // resultSet will collect the aggregated allocations
  599. resultSet := &SummaryAllocationSet{
  600. Window: sas.Window.Clone(),
  601. }
  602. // externalSet will collect external allocations
  603. externalSet := &SummaryAllocationSet{
  604. Window: sas.Window.Clone(),
  605. }
  606. // idleSet will be shared among resultSet after initial aggregation
  607. // is complete
  608. idleSet := &SummaryAllocationSet{
  609. Window: sas.Window.Clone(),
  610. }
  611. // shareSet will be shared among resultSet after initial aggregation
  612. // is complete
  613. shareSet := &SummaryAllocationSet{
  614. Window: sas.Window.Clone(),
  615. }
  616. sas.Lock()
  617. defer sas.Unlock()
  618. // 2. Record resource totals for shared costs, aggregating by cluster or by
  619. // node (depending on if idle is partitioned by cluster or node) so that we
  620. // can account for them in computing idle coefficients. Do the same for
  621. // unmounted volume costs, which only require a total cost.
  622. sharedResourceTotals := map[string]*AllocationTotals{}
  623. totalUnmountedCost := 0.0
  624. // 1 & 2. Identify set membership and aggregate aforementioned totals.
  625. for _, sa := range sas.SummaryAllocations {
  626. if sa.Share {
  627. var key string
  628. if options.IdleByNode {
  629. key = fmt.Sprintf("%s/%s", sa.Properties.Cluster, sa.Properties.Node)
  630. } else {
  631. key = sa.Properties.Cluster
  632. }
  633. if _, ok := sharedResourceTotals[key]; !ok {
  634. sharedResourceTotals[key] = &AllocationTotals{}
  635. }
  636. sharedResourceTotals[key].CPUCost += sa.CPUCost
  637. sharedResourceTotals[key].GPUCost += sa.GPUCost
  638. sharedResourceTotals[key].LoadBalancerCost += sa.LoadBalancerCost
  639. sharedResourceTotals[key].NetworkCost += sa.NetworkCost
  640. sharedResourceTotals[key].PersistentVolumeCost += sa.PVCost
  641. sharedResourceTotals[key].RAMCost += sa.RAMCost
  642. sharedResourceTotals[key].UnmountedPVCost += sa.UnmountedPVCost
  643. shareSet.Insert(sa)
  644. delete(sas.SummaryAllocations, sa.Name)
  645. continue
  646. }
  647. // External allocations get aggregated post-hoc (see step 6) and do
  648. // not necessarily contain complete sets of properties, so they are
  649. // moved to a separate AllocationSet.
  650. if sa.IsExternal() {
  651. delete(sas.externalKeys, sa.Name)
  652. delete(sas.SummaryAllocations, sa.Name)
  653. externalSet.Insert(sa)
  654. continue
  655. }
  656. // Idle allocations should be separated into idleSet if they are to be
  657. // shared later on. If they are not to be shared, then add them to the
  658. // resultSet like any other allocation.
  659. if sa.IsIdle() {
  660. delete(sas.idleKeys, sa.Name)
  661. delete(sas.SummaryAllocations, sa.Name)
  662. if options.ShareIdle == ShareEven || options.ShareIdle == ShareWeighted {
  663. idleSet.Insert(sa)
  664. } else {
  665. resultSet.Insert(sa)
  666. }
  667. continue
  668. }
  669. // Track total unmounted cost because it must be taken out of total
  670. // allocated costs for sharing coefficients.
  671. if sa.IsUnmounted() {
  672. totalUnmountedCost += sa.TotalCost()
  673. }
  674. }
  675. // It's possible that no more un-shared, non-idle, non-external allocations
  676. // remain at this point. This always results in an emptySet, so return early.
  677. if len(sas.SummaryAllocations) == 0 {
  678. sas.SummaryAllocations = map[string]*SummaryAllocation{}
  679. return nil
  680. }
  681. // 3. Retrieve pre-computed allocation resource totals, which will be used
  682. // to compute idle coefficients, based on the ratio of an allocation's per-
  683. // resource cost to the per-resource totals of that allocation's cluster or
  684. // node. Whether to perform this operation based on cluster or node is an
  685. // option. (See IdleByNode documentation; defaults to idle-by-cluster.)
  686. var allocTotals map[string]*AllocationTotals
  687. var ok bool
  688. if options.AllocationTotalsStore != nil {
  689. if options.IdleByNode {
  690. allocTotals, ok = options.AllocationTotalsStore.GetAllocationTotalsByNode(*sas.Window.Start(), *sas.Window.End())
  691. if !ok {
  692. return fmt.Errorf("nil allocation resource totals by node for %s", sas.Window)
  693. }
  694. } else {
  695. allocTotals, ok = options.AllocationTotalsStore.GetAllocationTotalsByCluster(*sas.Window.Start(), *sas.Window.End())
  696. if !ok {
  697. return fmt.Errorf("nil allocation resource totals by cluster for %s", sas.Window)
  698. }
  699. }
  700. }
  701. // If reconciliation has been fully or partially disabled, clear the
  702. // relevant adjustments from the alloc totals
  703. if allocTotals != nil && (!options.Reconcile || !options.ReconcileNetwork) {
  704. if !options.Reconcile {
  705. for _, tot := range allocTotals {
  706. tot.ClearAdjustments()
  707. }
  708. } else if !options.ReconcileNetwork {
  709. for _, tot := range allocTotals {
  710. tot.NetworkCostAdjustment = 0.0
  711. }
  712. }
  713. }
  714. // If filters have been applied, then we need to record allocation resource
  715. // totals after filtration (i.e. the allocations that are present) so that
  716. // we can identify the proportion of idle cost to keep. That is, we should
  717. // only return the idle cost that would be shared with the remaining
  718. // allocations, even if we're keeping idle separate. The totals should be
  719. // recorded by idle-key (cluster or node, depending on the IdleByNode
  720. // option). Instantiating this map is a signal to record the totals.
  721. var allocTotalsAfterFilters map[string]*AllocationTotals
  722. if len(resultSet.idleKeys) > 0 && options.Filter != nil {
  723. allocTotalsAfterFilters = make(map[string]*AllocationTotals, len(resultSet.idleKeys))
  724. }
  725. // If we're recording allocTotalsAfterFilters and there are shared costs,
  726. // then record those resource totals here so that idle for those shared
  727. // resources gets included.
  728. if allocTotalsAfterFilters != nil {
  729. for key, rt := range sharedResourceTotals {
  730. if _, ok := allocTotalsAfterFilters[key]; !ok {
  731. allocTotalsAfterFilters[key] = &AllocationTotals{}
  732. }
  733. // Record only those fields required for computing idle
  734. allocTotalsAfterFilters[key].CPUCost += rt.CPUCost
  735. allocTotalsAfterFilters[key].GPUCost += rt.GPUCost
  736. allocTotalsAfterFilters[key].RAMCost += rt.RAMCost
  737. }
  738. }
  739. // 4. Convert shared hourly cost into a cumulative allocation to share,
  740. // and insert it into the share set.
  741. for name, cost := range options.SharedHourlyCosts {
  742. if cost > 0.0 {
  743. hours := sas.Window.Hours()
  744. // If set ends in the future, adjust hours accordingly
  745. diff := time.Since(*sas.Window.End())
  746. if diff < 0.0 {
  747. hours += diff.Hours()
  748. }
  749. totalSharedCost := cost * hours
  750. shareSet.Insert(&SummaryAllocation{
  751. Name: fmt.Sprintf("%s/%s", name, SharedSuffix),
  752. Properties: &AllocationProperties{},
  753. Start: *sas.Window.Start(),
  754. End: *sas.Window.End(),
  755. SharedCost: totalSharedCost,
  756. })
  757. }
  758. }
  759. // Sharing coefficients are recorded by post-aggregation-key (e.g. if
  760. // aggregating by namespace, then the key will be the namespace) and only
  761. // need to be recorded if there are shared resources. Instantiating this
  762. // map is the signal to record sharing coefficients.
  763. var sharingCoeffs map[string]float64
  764. if len(shareSet.SummaryAllocations) > 0 {
  765. sharingCoeffs = map[string]float64{}
  766. }
  767. // Loop over all remaining SummaryAllocations (after filters, sharing, &c.)
  768. // doing the following, in this order:
  769. // 5. Compute sharing coefficients, if there are shared resources
  770. // 6. Distribute idle cost, if sharing idle
  771. // 7. Record allocTotalsAfterFiltration, if filters have been applied
  772. // 8. Aggregate by key
  773. for _, sa := range sas.SummaryAllocations {
  774. // Generate key to use for aggregation-by-key and allocation name
  775. key := sa.generateKey(aggregateBy, options.LabelConfig)
  776. // 5. Incrementally add to sharing coefficients before adding idle
  777. // cost, which would skew the coefficients. These coefficients will be
  778. // later divided by a total, turning them into a coefficient between
  779. // 0.0 and 1.0.
  780. // NOTE: SummaryAllocation does not support ShareEven, so only record
  781. // by cost for cost-weighted distribution.
  782. // if sharingCoeffs != nil {
  783. if sharingCoeffs != nil && !sa.IsUnmounted() {
  784. sharingCoeffs[key] += sa.TotalCost() - sa.SharedCost - sa.UnmountedPVCost
  785. }
  786. // 6. Distribute idle allocations according to the idle coefficients.
  787. // NOTE: if idle allocation is off (i.e. options.ShareIdle: ShareNone)
  788. // then all idle allocations will be in the resultSet at this point, so
  789. // idleSet will be empty and we won't enter this block.
  790. if len(idleSet.SummaryAllocations) > 0 {
  791. for _, idle := range idleSet.SummaryAllocations {
  792. // Idle key is either cluster or node, as determined by the
  793. // IdleByNode option.
  794. var key string
  795. // Only share idle allocation with current allocation (sa) if
  796. // the relevant properties match (i.e. cluster and/or node)
  797. if idle.Properties.Cluster != sa.Properties.Cluster {
  798. continue
  799. }
  800. key = idle.Properties.Cluster
  801. if options.IdleByNode {
  802. if idle.Properties.Node != sa.Properties.Node {
  803. continue
  804. }
  805. key = fmt.Sprintf("%s/%s", idle.Properties.Cluster, idle.Properties.Node)
  806. }
  807. cpuCoeff, gpuCoeff, ramCoeff := ComputeIdleCoefficients(options.ShareIdle, key, sa.CPUCost, sa.GPUCost, sa.RAMCost, allocTotals)
  808. sa.CPUCost += idle.CPUCost * cpuCoeff
  809. sa.GPUCost += idle.GPUCost * gpuCoeff
  810. sa.RAMCost += idle.RAMCost * ramCoeff
  811. }
  812. }
  813. // The key becomes the allocation's name, which is used as the key by
  814. // which the allocation is inserted into the set.
  815. sa.Name = key
  816. // If merging unallocated allocations, rename all unallocated
  817. // allocations as simply __unallocated__
  818. if options.MergeUnallocated && sa.IsUnallocated() {
  819. sa.Name = UnallocatedSuffix
  820. }
  821. // 7. Record filtered resource totals for idle allocation filtration,
  822. // only if necessary.
  823. if allocTotalsAfterFilters != nil {
  824. key := sa.Properties.Cluster
  825. if options.IdleByNode {
  826. key = fmt.Sprintf("%s/%s", sa.Properties.Cluster, sa.Properties.Node)
  827. }
  828. if _, ok := allocTotalsAfterFilters[key]; !ok {
  829. allocTotalsAfterFilters[key] = &AllocationTotals{}
  830. }
  831. allocTotalsAfterFilters[key].CPUCost += sa.CPUCost
  832. allocTotalsAfterFilters[key].GPUCost += sa.GPUCost
  833. allocTotalsAfterFilters[key].RAMCost += sa.RAMCost
  834. }
  835. // 8. Inserting the allocation with the generated key for a name
  836. // performs the actual aggregation step.
  837. resultSet.Insert(sa)
  838. }
  839. // 9. If idle is shared and resources are shared, it's probable that some
  840. // amount of idle cost will be shared with a shared resource. Distribute
  841. // that idle cost, if it exists, among the respective shared allocations
  842. // before sharing them with the aggregated allocations.
  843. if len(idleSet.SummaryAllocations) > 0 && len(shareSet.SummaryAllocations) > 0 {
  844. for _, sa := range shareSet.SummaryAllocations {
  845. for _, idle := range idleSet.SummaryAllocations {
  846. var key string
  847. // Only share idle allocation with current allocation (sa) if
  848. // the relevant property matches (i.e. Cluster or Node,
  849. // depending on which idle sharing option is selected)
  850. if options.IdleByNode {
  851. if idle.Properties.Cluster != sa.Properties.Cluster || idle.Properties.Node != sa.Properties.Node {
  852. continue
  853. }
  854. key = fmt.Sprintf("%s/%s", idle.Properties.Cluster, idle.Properties.Node)
  855. } else {
  856. if idle.Properties.Cluster != sa.Properties.Cluster {
  857. continue
  858. }
  859. key = idle.Properties.Cluster
  860. }
  861. cpuCoeff, gpuCoeff, ramCoeff := ComputeIdleCoefficients(options.ShareIdle, key, sa.CPUCost, sa.GPUCost, sa.RAMCost, allocTotals)
  862. sa.CPUCost += idle.CPUCost * cpuCoeff
  863. sa.GPUCost += idle.GPUCost * gpuCoeff
  864. sa.RAMCost += idle.RAMCost * ramCoeff
  865. }
  866. }
  867. }
  868. // 10. Apply idle filtration, which "filters" the idle cost, i.e. scales
  869. // idle allocation costs per-resource by the proportion of allocation
  870. // resources remaining after filtering. In effect, this returns only the
  871. // idle costs that would have been shared with the remaining allocations,
  872. // even if idle is kept separated.
  873. if allocTotalsAfterFilters != nil {
  874. for idleKey := range resultSet.idleKeys {
  875. ia := resultSet.SummaryAllocations[idleKey]
  876. var key string
  877. if options.IdleByNode {
  878. key = fmt.Sprintf("%s/%s", ia.Properties.Cluster, ia.Properties.Node)
  879. } else {
  880. key = ia.Properties.Cluster
  881. }
  882. // Percentage of idle that should remain after filters are applied,
  883. // which equals the proportion of filtered-to-actual cost.
  884. cpuFilterCoeff := 0.0
  885. if allocTotals[key].TotalCPUCost() > 0.0 {
  886. filteredAlloc, ok := allocTotalsAfterFilters[key]
  887. if ok {
  888. cpuFilterCoeff = filteredAlloc.TotalCPUCost() / allocTotals[key].TotalCPUCost()
  889. } else {
  890. cpuFilterCoeff = 0.0
  891. }
  892. }
  893. gpuFilterCoeff := 0.0
  894. if allocTotals[key].TotalGPUCost() > 0.0 {
  895. filteredAlloc, ok := allocTotalsAfterFilters[key]
  896. if ok {
  897. gpuFilterCoeff = filteredAlloc.TotalGPUCost() / allocTotals[key].TotalGPUCost()
  898. } else {
  899. gpuFilterCoeff = 0.0
  900. }
  901. }
  902. ramFilterCoeff := 0.0
  903. if allocTotals[key].TotalRAMCost() > 0.0 {
  904. filteredAlloc, ok := allocTotalsAfterFilters[key]
  905. if ok {
  906. ramFilterCoeff = filteredAlloc.TotalRAMCost() / allocTotals[key].TotalRAMCost()
  907. } else {
  908. ramFilterCoeff = 0.0
  909. }
  910. }
  911. ia.CPUCost *= cpuFilterCoeff
  912. ia.GPUCost *= gpuFilterCoeff
  913. ia.RAMCost *= ramFilterCoeff
  914. }
  915. }
  916. // 11. Distribute shared resources according to sharing coefficients.
  917. // NOTE: ShareEven is not supported
  918. if len(shareSet.SummaryAllocations) > 0 {
  919. shareCoeffSum := 0.0
  920. sharingCoeffDenominator := 0.0
  921. for _, rt := range allocTotals {
  922. // Here, the allocation totals
  923. sharingCoeffDenominator += rt.TotalCost() // does NOT include unmounted PVs at all
  924. }
  925. // Do not include the shared costs, themselves, when determining
  926. // sharing coefficients.
  927. for _, rt := range sharedResourceTotals {
  928. // Due to the fact that sharingCoeffDenominator already has no
  929. // unmounted PV costs, we need to be careful not to additionally
  930. // subtract the unmounted PV cost when we remove shared costs
  931. // from the denominator.
  932. sharingCoeffDenominator -= (rt.TotalCost() - rt.UnmountedPVCost)
  933. }
  934. if sharingCoeffDenominator <= 0.0 {
  935. log.Warnf("SummaryAllocation: sharing coefficient denominator is %f", sharingCoeffDenominator)
  936. } else {
  937. // Compute sharing coeffs by dividing the thus-far accumulated
  938. // numerators by the now-finalized denominator.
  939. for key := range sharingCoeffs {
  940. // Do not share the value with unmounted suffix since it's not included in the computation.
  941. if key == UnmountedSuffix {
  942. continue
  943. }
  944. if sharingCoeffs[key] > 0.0 {
  945. sharingCoeffs[key] /= sharingCoeffDenominator
  946. shareCoeffSum += sharingCoeffs[key]
  947. } else {
  948. log.Warnf("SummaryAllocation: detected illegal sharing coefficient for %s: %v (setting to zero)", key, sharingCoeffs[key])
  949. sharingCoeffs[key] = 0.0
  950. }
  951. }
  952. for key, sa := range resultSet.SummaryAllocations {
  953. // Idle and unmounted allocations, by definition, do not
  954. // receive shared cost
  955. if sa.IsIdle() || sa.IsUnmounted() {
  956. continue
  957. }
  958. sharingCoeff := sharingCoeffs[key]
  959. // Distribute each shared cost with the current allocation on the
  960. // basis of the proportion of the allocation's cost (ShareWeighted)
  961. // or count (ShareEven) to the total aggregated cost or count. This
  962. // condition should hold in spite of filters because the sharing
  963. // coefficient denominator is held constant by pre-computed
  964. // resource totals and the post-aggregation total cost of the
  965. // remaining allocations will, by definition, not be affected.
  966. for _, shared := range shareSet.SummaryAllocations {
  967. sa.SharedCost += shared.TotalCost() * sharingCoeff
  968. }
  969. }
  970. }
  971. }
  972. // 12. Insert external allocations into the result set.
  973. for _, sa := range externalSet.SummaryAllocations {
  974. // Make an allocation with the same properties and test that
  975. // against the FilterFunc to see if the external allocation should
  976. // be filtered or not.
  977. // TODO:CLEANUP do something about external cost, this stinks
  978. ea := &Allocation{Properties: sa.Properties}
  979. if filter.Matches(ea) {
  980. key := sa.generateKey(aggregateBy, options.LabelConfig)
  981. sa.Name = key
  982. resultSet.Insert(sa)
  983. }
  984. }
  985. // 13. Distribute remaining, undistributed idle. Undistributed idle is any
  986. // per-resource idle cost for which there can be no idle coefficient
  987. // computed because there is zero usage across all allocations.
  988. for _, isa := range idleSet.SummaryAllocations {
  989. // Make an allocation with the same properties and test that
  990. // against the FilterFunc to see if the external allocation should
  991. // be filtered or not.
  992. // TODO:CLEANUP do something about external cost, this stinks
  993. ia := &Allocation{Properties: isa.Properties}
  994. // if the idle does not apply to the non-filtered values, skip it
  995. if !filter.Matches(ia) {
  996. continue
  997. }
  998. key := isa.Properties.Cluster
  999. if options.IdleByNode {
  1000. key = fmt.Sprintf("%s/%s", isa.Properties.Cluster, isa.Properties.Node)
  1001. }
  1002. rt, ok := allocTotals[key]
  1003. if !ok {
  1004. log.Warnf("SummaryAllocation: AggregateBy: cannot handle undistributed idle for '%s'", key)
  1005. continue
  1006. }
  1007. hasUndistributableCost := false
  1008. if isa.CPUCost > 0.0 && rt.CPUCost == 0.0 {
  1009. // There is idle CPU cost, but no allocated CPU cost, so that cost
  1010. // is undistributable and must be inserted.
  1011. hasUndistributableCost = true
  1012. } else {
  1013. // Cost was entirely distributed, so zero it out
  1014. isa.CPUCost = 0.0
  1015. }
  1016. if isa.GPUCost > 0.0 && rt.GPUCost == 0.0 {
  1017. // There is idle GPU cost, but no allocated GPU cost, so that cost
  1018. // is undistributable and must be inserted.
  1019. hasUndistributableCost = true
  1020. } else {
  1021. // Cost was entirely distributed, so zero it out
  1022. isa.GPUCost = 0.0
  1023. }
  1024. if isa.RAMCost > 0.0 && rt.RAMCost == 0.0 {
  1025. // There is idle CPU cost, but no allocated CPU cost, so that cost
  1026. // is undistributable and must be inserted.
  1027. hasUndistributableCost = true
  1028. } else {
  1029. // Cost was entirely distributed, so zero it out
  1030. isa.RAMCost = 0.0
  1031. }
  1032. if hasUndistributableCost {
  1033. isa.Name = fmt.Sprintf("%s/%s", key, IdleSuffix)
  1034. resultSet.Insert(isa)
  1035. }
  1036. }
  1037. // 14. Combine all idle allocations into a single idle allocation, unless
  1038. // the option to keep idle split by cluster or node is enabled.
  1039. if !options.SplitIdle {
  1040. for _, ia := range resultSet.idleAllocations() {
  1041. resultSet.Delete(ia.Name)
  1042. ia.Name = IdleSuffix
  1043. resultSet.Insert(ia)
  1044. }
  1045. }
  1046. // Replace the existing set's data with the new, aggregated summary data
  1047. sas.SummaryAllocations = resultSet.SummaryAllocations
  1048. return nil
  1049. }
  1050. // Delete removes the allocation with the given name from the set
  1051. func (sas *SummaryAllocationSet) Delete(name string) {
  1052. if sas == nil {
  1053. return
  1054. }
  1055. sas.Lock()
  1056. defer sas.Unlock()
  1057. delete(sas.externalKeys, name)
  1058. delete(sas.idleKeys, name)
  1059. delete(sas.SummaryAllocations, name)
  1060. }
  1061. // Each invokes the given function for each SummaryAllocation in the set
  1062. func (sas *SummaryAllocationSet) Each(f func(string, *SummaryAllocation)) {
  1063. if sas == nil {
  1064. return
  1065. }
  1066. for k, a := range sas.SummaryAllocations {
  1067. f(k, a)
  1068. }
  1069. }
  1070. func (sas *SummaryAllocationSet) Equal(that *SummaryAllocationSet) bool {
  1071. if sas == nil || that == nil {
  1072. return false
  1073. }
  1074. sas.RLock()
  1075. defer sas.RUnlock()
  1076. if !sas.Window.Equal(that.Window) {
  1077. return false
  1078. }
  1079. if len(sas.SummaryAllocations) != len(that.SummaryAllocations) {
  1080. return false
  1081. }
  1082. for name, sa := range sas.SummaryAllocations {
  1083. thatSA, ok := that.SummaryAllocations[name]
  1084. if !ok {
  1085. return false
  1086. }
  1087. if !sa.Equal(thatSA) {
  1088. return false
  1089. }
  1090. }
  1091. return true
  1092. }
  1093. // IdleAllocations returns a map of the idle allocations in the AllocationSet.
  1094. func (sas *SummaryAllocationSet) idleAllocations() map[string]*SummaryAllocation {
  1095. idles := map[string]*SummaryAllocation{}
  1096. if sas == nil || len(sas.SummaryAllocations) == 0 {
  1097. return idles
  1098. }
  1099. sas.RLock()
  1100. defer sas.RUnlock()
  1101. for key := range sas.idleKeys {
  1102. if sa, ok := sas.SummaryAllocations[key]; ok {
  1103. idles[key] = sa
  1104. }
  1105. }
  1106. return idles
  1107. }
  1108. // Insert aggregates the current entry in the SummaryAllocationSet by the given Allocation,
  1109. // but only if the Allocation is valid, i.e. matches the SummaryAllocationSet's window. If
  1110. // there is no existing entry, one is created. Nil error response indicates success.
  1111. func (sas *SummaryAllocationSet) Insert(sa *SummaryAllocation) error {
  1112. if sas == nil {
  1113. return fmt.Errorf("cannot insert into nil SummaryAllocationSet")
  1114. }
  1115. if sa == nil {
  1116. return fmt.Errorf("cannot insert a nil SummaryAllocation")
  1117. }
  1118. sas.Lock()
  1119. defer sas.Unlock()
  1120. if sas.SummaryAllocations == nil {
  1121. sas.SummaryAllocations = map[string]*SummaryAllocation{}
  1122. }
  1123. if sas.externalKeys == nil {
  1124. sas.externalKeys = map[string]bool{}
  1125. }
  1126. if sas.idleKeys == nil {
  1127. sas.idleKeys = map[string]bool{}
  1128. }
  1129. // Add the given Allocation to the existing entry, if there is one;
  1130. // otherwise just set directly into allocations
  1131. if _, ok := sas.SummaryAllocations[sa.Name]; ok {
  1132. err := sas.SummaryAllocations[sa.Name].Add(sa)
  1133. if err != nil {
  1134. return fmt.Errorf("SummaryAllocationSet.Insert: error trying to Add: %s", err)
  1135. }
  1136. } else {
  1137. sas.SummaryAllocations[sa.Name] = sa
  1138. }
  1139. // If the given Allocation is an external one, record that
  1140. if sa.IsExternal() {
  1141. sas.externalKeys[sa.Name] = true
  1142. }
  1143. // If the given Allocation is an idle one, record that
  1144. if sa.IsIdle() {
  1145. sas.idleKeys[sa.Name] = true
  1146. }
  1147. return nil
  1148. }
  1149. func (sas *SummaryAllocationSet) TotalCost() float64 {
  1150. if sas == nil {
  1151. return 0.0
  1152. }
  1153. sas.RLock()
  1154. defer sas.RUnlock()
  1155. tc := 0.0
  1156. for _, sa := range sas.SummaryAllocations {
  1157. tc += sa.TotalCost()
  1158. }
  1159. return tc
  1160. }
  1161. // RAMEfficiency func to calculate average RAM efficiency over SummaryAllocationSet
  1162. func (sas *SummaryAllocationSet) RAMEfficiency() float64 {
  1163. if sas == nil {
  1164. return 0.0
  1165. }
  1166. sas.RLock()
  1167. defer sas.RUnlock()
  1168. totalRAMBytesMinutesUsage := 0.0
  1169. totalRAMBytesMinutesRequest := 0.0
  1170. totalRAMCost := 0.0
  1171. for _, sa := range sas.SummaryAllocations {
  1172. if sa.IsIdle() {
  1173. continue
  1174. }
  1175. totalRAMBytesMinutesUsage += sa.RAMBytesUsageAverage * sa.Minutes()
  1176. totalRAMBytesMinutesRequest += sa.RAMBytesRequestAverage * sa.Minutes()
  1177. totalRAMCost += sa.RAMCost
  1178. }
  1179. if totalRAMBytesMinutesRequest > 0 {
  1180. return totalRAMBytesMinutesUsage / totalRAMBytesMinutesRequest
  1181. }
  1182. if totalRAMBytesMinutesUsage == 0.0 || totalRAMCost == 0.0 {
  1183. return 0.0
  1184. }
  1185. return 1.0
  1186. }
  1187. // CPUEfficiency func to calculate average CPU efficiency over SummaryAllocationSet
  1188. func (sas *SummaryAllocationSet) CPUEfficiency() float64 {
  1189. if sas == nil {
  1190. return 0.0
  1191. }
  1192. sas.RLock()
  1193. defer sas.RUnlock()
  1194. totalCPUCoreMinutesUsage := 0.0
  1195. totalCPUCoreMinutesRequest := 0.0
  1196. totalCPUCost := 0.0
  1197. for _, sa := range sas.SummaryAllocations {
  1198. if sa.IsIdle() {
  1199. continue
  1200. }
  1201. totalCPUCoreMinutesUsage += sa.CPUCoreUsageAverage * sa.Minutes()
  1202. totalCPUCoreMinutesRequest += sa.CPUCoreRequestAverage * sa.Minutes()
  1203. totalCPUCost += sa.CPUCost
  1204. }
  1205. if totalCPUCoreMinutesRequest > 0 {
  1206. return totalCPUCoreMinutesUsage / totalCPUCoreMinutesRequest
  1207. }
  1208. if totalCPUCoreMinutesUsage == 0.0 || totalCPUCost == 0.0 {
  1209. return 0.0
  1210. }
  1211. return 1.0
  1212. }
  1213. // TotalEfficiency func to calculate average Total efficiency over SummaryAllocationSet
  1214. func (sas *SummaryAllocationSet) TotalEfficiency() float64 {
  1215. if sas == nil {
  1216. return 0.0
  1217. }
  1218. sas.RLock()
  1219. defer sas.RUnlock()
  1220. totalRAMCost := 0.0
  1221. totalCPUCost := 0.0
  1222. for _, sa := range sas.SummaryAllocations {
  1223. if sa.IsIdle() {
  1224. continue
  1225. }
  1226. totalRAMCost += sa.RAMCost
  1227. totalCPUCost += sa.CPUCost
  1228. }
  1229. if totalRAMCost+totalCPUCost > 0 {
  1230. return (totalRAMCost*sas.RAMEfficiency() + totalCPUCost*sas.CPUEfficiency()) / (totalRAMCost + totalCPUCost)
  1231. }
  1232. return 0.0
  1233. }
  1234. // SummaryAllocationSetRange is a thread-safe slice of SummaryAllocationSets.
  1235. type SummaryAllocationSetRange struct {
  1236. sync.RWMutex
  1237. Step time.Duration `json:"step"`
  1238. SummaryAllocationSets []*SummaryAllocationSet `json:"sets"`
  1239. Window Window `json:"window"`
  1240. Message string `json:"-"`
  1241. }
  1242. // NewSummaryAllocationSetRange instantiates a new range composed of the given
  1243. // SummaryAllocationSets in the order provided. The expectations about the
  1244. // SummaryAllocationSets are as follows:
  1245. // - window durations are all equal
  1246. // - sets are consecutive (i.e. chronologically sorted)
  1247. // - there are no gaps between sets
  1248. // - sets do not have overlapping windows
  1249. func NewSummaryAllocationSetRange(sass ...*SummaryAllocationSet) *SummaryAllocationSetRange {
  1250. var step time.Duration
  1251. window := NewWindow(nil, nil)
  1252. for _, sas := range sass {
  1253. if window.Start() == nil || (sas.Window.Start() != nil && sas.Window.Start().Before(*window.Start())) {
  1254. window.start = sas.Window.Start()
  1255. }
  1256. if window.End() == nil || (sas.Window.End() != nil && sas.Window.End().After(*window.End())) {
  1257. window.end = sas.Window.End()
  1258. }
  1259. if step == 0 {
  1260. step = sas.Window.Duration()
  1261. } else if step != sas.Window.Duration() {
  1262. log.Warnf("instantiating range with step %s using set of step %s is illegal", step, sas.Window.Duration())
  1263. }
  1264. }
  1265. return &SummaryAllocationSetRange{
  1266. Step: step,
  1267. SummaryAllocationSets: sass,
  1268. Window: window,
  1269. }
  1270. }
  1271. // Accumulate sums each AllocationSet in the given range, returning a single cumulative
  1272. // AllocationSet for the entire range.
  1273. func (sasr *SummaryAllocationSetRange) accumulate() (*SummaryAllocationSet, error) {
  1274. var result *SummaryAllocationSet
  1275. var err error
  1276. sasr.RLock()
  1277. defer sasr.RUnlock()
  1278. for _, sas := range sasr.SummaryAllocationSets {
  1279. result, err = result.Add(sas)
  1280. if err != nil {
  1281. return nil, err
  1282. }
  1283. }
  1284. return result, nil
  1285. }
  1286. // newAccumulation clones the first available SummaryAllocationSet to use as the data structure to
  1287. // accumulate the remaining data. This leaves the original SummaryAllocationSetRange intact.
  1288. func (sasr *SummaryAllocationSetRange) newAccumulation() (*SummaryAllocationSet, error) {
  1289. var result *SummaryAllocationSet
  1290. var err error
  1291. sasr.RLock()
  1292. defer sasr.RUnlock()
  1293. for _, sas := range sasr.SummaryAllocationSets {
  1294. // we want to clone the first summary allocation set, then just Add the others
  1295. // to the clone. We will eventually use the clone to create the set range.
  1296. if result == nil {
  1297. result = sas.Clone()
  1298. continue
  1299. }
  1300. // Copy if sas is non-nil
  1301. var sasCopy *SummaryAllocationSet = nil
  1302. if sas != nil {
  1303. sasCopy = sas.Clone()
  1304. }
  1305. // nil is ok to pass into Add
  1306. result, err = result.Add(sasCopy)
  1307. if err != nil {
  1308. return nil, err
  1309. }
  1310. }
  1311. return result, nil
  1312. }
  1313. // AggregateBy aggregates each AllocationSet in the range by the given
  1314. // properties and options.
  1315. func (sasr *SummaryAllocationSetRange) AggregateBy(aggregateBy []string, options *AllocationAggregationOptions) error {
  1316. sasr.Lock()
  1317. defer sasr.Unlock()
  1318. for _, sas := range sasr.SummaryAllocationSets {
  1319. err := sas.AggregateBy(aggregateBy, options)
  1320. if err != nil {
  1321. // Wipe out data so that corrupt data cannot be mistakenly used
  1322. sasr.SummaryAllocationSets = []*SummaryAllocationSet{}
  1323. return err
  1324. }
  1325. }
  1326. return nil
  1327. }
  1328. // Append appends the given AllocationSet to the end of the range. It does not
  1329. // validate whether or not that violates window continuity.
  1330. func (sasr *SummaryAllocationSetRange) Append(sas *SummaryAllocationSet) {
  1331. sasr.Lock()
  1332. defer sasr.Unlock()
  1333. // Append to list of sets
  1334. sasr.SummaryAllocationSets = append(sasr.SummaryAllocationSets, sas)
  1335. // Set step, if not set
  1336. if sasr.Step == 0 {
  1337. sasr.Step = sas.Window.Duration()
  1338. }
  1339. // Adjust window
  1340. if sasr.Window.Start() == nil || (sas.Window.Start() != nil && sas.Window.Start().Before(*sasr.Window.Start())) {
  1341. sasr.Window.start = sas.Window.Start()
  1342. }
  1343. if sasr.Window.End() == nil || (sas.Window.End() != nil && sas.Window.End().After(*sasr.Window.End())) {
  1344. sasr.Window.end = sas.Window.End()
  1345. }
  1346. }
  1347. // Each invokes the given function for each AllocationSet in the range
  1348. func (sasr *SummaryAllocationSetRange) Each(f func(int, *SummaryAllocationSet)) {
  1349. if sasr == nil {
  1350. return
  1351. }
  1352. for i, as := range sasr.SummaryAllocationSets {
  1353. f(i, as)
  1354. }
  1355. }
  1356. // InsertExternalAllocations takes all allocations in the given
  1357. // AllocationSetRange (they should all be considered "external") and inserts
  1358. // them into the receiving SummaryAllocationSetRange.
  1359. // TODO:CLEANUP replace this with a better idea (or get rid of external
  1360. // allocations, as such, altogether)
  1361. func (sasr *SummaryAllocationSetRange) InsertExternalAllocations(that *AllocationSetRange) error {
  1362. if sasr == nil {
  1363. return fmt.Errorf("cannot insert range into nil AllocationSetRange")
  1364. }
  1365. // keys maps window to index in range
  1366. keys := map[string]int{}
  1367. for i, as := range sasr.SummaryAllocationSets {
  1368. if as == nil {
  1369. continue
  1370. }
  1371. keys[as.Window.String()] = i
  1372. }
  1373. // Nothing to merge, so simply return
  1374. if len(keys) == 0 {
  1375. return nil
  1376. }
  1377. var err error
  1378. for _, thatAS := range that.Allocations {
  1379. if thatAS == nil || err != nil {
  1380. continue
  1381. }
  1382. // Find matching AllocationSet in asr
  1383. i, ok := keys[thatAS.Window.String()]
  1384. if !ok {
  1385. err = fmt.Errorf("cannot merge AllocationSet into window that does not exist: %s", thatAS.Window.String())
  1386. continue
  1387. }
  1388. sas := sasr.SummaryAllocationSets[i]
  1389. // Insert each Allocation from the given set
  1390. for _, alloc := range thatAS.Allocations {
  1391. externalSA := NewSummaryAllocation(alloc, true, true)
  1392. // This error will be returned below
  1393. // TODO:CLEANUP should Each have early-error-return functionality?
  1394. err = sas.Insert(externalSA)
  1395. }
  1396. }
  1397. // err might be nil
  1398. return err
  1399. }
  1400. func (sasr *SummaryAllocationSetRange) TotalCost() float64 {
  1401. if sasr == nil {
  1402. return 0.0
  1403. }
  1404. sasr.RLock()
  1405. defer sasr.RUnlock()
  1406. tc := 0.0
  1407. for _, sas := range sasr.SummaryAllocationSets {
  1408. tc += sas.TotalCost()
  1409. }
  1410. return tc
  1411. }
  1412. // TODO remove after testing
  1413. func (sasr *SummaryAllocationSetRange) Print(verbose bool) {
  1414. fmt.Printf("%s (dur=%s, len=%d, cost=%.5f)\n", sasr.Window, sasr.Window.Duration(), len(sasr.SummaryAllocationSets), sasr.TotalCost())
  1415. for _, sas := range sasr.SummaryAllocationSets {
  1416. fmt.Printf(" > %s (dur=%s, len=%d, cost=%.5f) \n", sas.Window, sas.Window.Duration(), len(sas.SummaryAllocations), sas.TotalCost())
  1417. for key, sa := range sas.SummaryAllocations {
  1418. if verbose {
  1419. fmt.Printf(" {\"%s\", cpu: %.5f, gpu: %.5f, lb: %.5f, net: %.5f, pv: %.5f, ram: %.5f, shared: %.5f, external: %.5f}\n",
  1420. key, sa.CPUCost, sa.GPUCost, sa.LoadBalancerCost, sa.NetworkCost, sa.PVCost, sa.RAMCost, sa.SharedCost, sa.ExternalCost)
  1421. } else {
  1422. fmt.Printf(" - \"%s\": %.5f\n", key, sa.TotalCost())
  1423. }
  1424. }
  1425. }
  1426. }
  1427. func (sasr *SummaryAllocationSetRange) Accumulate(accumulateBy AccumulateOption) (*SummaryAllocationSetRange, error) {
  1428. switch accumulateBy {
  1429. case AccumulateOptionNone:
  1430. return sasr.accumulateByNone()
  1431. case AccumulateOptionAll:
  1432. return sasr.accumulateByAll()
  1433. case AccumulateOptionHour:
  1434. return sasr.accumulateByHour()
  1435. case AccumulateOptionDay:
  1436. return sasr.accumulateByDay()
  1437. case AccumulateOptionWeek:
  1438. return sasr.accumulateByWeek()
  1439. case AccumulateOptionMonth:
  1440. return sasr.accumulateByMonth()
  1441. default:
  1442. // this should never happen
  1443. return nil, fmt.Errorf("unexpected error, invalid accumulateByType: %s", accumulateBy)
  1444. }
  1445. }
  1446. func (sasr *SummaryAllocationSetRange) accumulateByNone() (*SummaryAllocationSetRange, error) {
  1447. return sasr.clone(), nil
  1448. }
  1449. func (sasr *SummaryAllocationSetRange) accumulateByAll() (*SummaryAllocationSetRange, error) {
  1450. var err error
  1451. var result *SummaryAllocationSet
  1452. result, err = sasr.newAccumulation()
  1453. if err != nil {
  1454. return nil, fmt.Errorf("error running accumulate: %s", err)
  1455. }
  1456. accumulated := NewSummaryAllocationSetRange(result)
  1457. return accumulated, nil
  1458. }
  1459. func (sasr *SummaryAllocationSetRange) accumulateByHour() (*SummaryAllocationSetRange, error) {
  1460. // ensure that the summary allocation sets have a 1-hour window, if a set exists
  1461. if len(sasr.SummaryAllocationSets) > 0 && sasr.SummaryAllocationSets[0].Window.Duration() != time.Hour {
  1462. return nil, fmt.Errorf("window duration must equal 1 hour; got:%s", sasr.SummaryAllocationSets[0].Window.Duration())
  1463. }
  1464. result := sasr.clone()
  1465. return result, nil
  1466. }
  1467. func (sasr *SummaryAllocationSetRange) accumulateByDay() (*SummaryAllocationSetRange, error) {
  1468. // if the summary allocation set window is 1-day, just return the existing summary allocation set range
  1469. if len(sasr.SummaryAllocationSets) > 0 && sasr.SummaryAllocationSets[0].Window.Duration() == time.Hour*24 {
  1470. return sasr, nil
  1471. }
  1472. var toAccumulate *SummaryAllocationSetRange
  1473. result := NewSummaryAllocationSetRange()
  1474. for i, as := range sasr.SummaryAllocationSets {
  1475. if as.Window.Duration() != time.Hour {
  1476. return nil, fmt.Errorf("window duration must equal 1 hour; got:%s", as.Window.Duration())
  1477. }
  1478. hour := as.Window.Start().Hour()
  1479. if toAccumulate == nil {
  1480. toAccumulate = NewSummaryAllocationSetRange()
  1481. as = as.Clone()
  1482. }
  1483. toAccumulate.Append(as)
  1484. sas, err := toAccumulate.accumulate()
  1485. if err != nil {
  1486. return nil, fmt.Errorf("error accumulating result: %s", err)
  1487. }
  1488. toAccumulate = NewSummaryAllocationSetRange(sas)
  1489. if hour == 23 || i == len(sasr.SummaryAllocationSets)-1 {
  1490. if length := len(toAccumulate.SummaryAllocationSets); length != 1 {
  1491. return nil, fmt.Errorf("failed accumulation, detected %d sets instead of 1", length)
  1492. }
  1493. result.Append(toAccumulate.SummaryAllocationSets[0])
  1494. toAccumulate = nil
  1495. }
  1496. }
  1497. return result, nil
  1498. }
  1499. func (sasr *SummaryAllocationSetRange) accumulateByMonth() (*SummaryAllocationSetRange, error) {
  1500. var toAccumulate *SummaryAllocationSetRange
  1501. result := NewSummaryAllocationSetRange()
  1502. for i, as := range sasr.SummaryAllocationSets {
  1503. if as.Window.Duration() != time.Hour*24 {
  1504. return nil, fmt.Errorf("window duration must equal 24 hours; got:%s", as.Window.Duration())
  1505. }
  1506. _, month, _ := as.Window.Start().Date()
  1507. _, nextDayMonth, _ := as.Window.Start().Add(time.Hour * 24).Date()
  1508. if toAccumulate == nil {
  1509. toAccumulate = NewSummaryAllocationSetRange()
  1510. as = as.Clone()
  1511. }
  1512. toAccumulate.Append(as)
  1513. sas, err := toAccumulate.accumulate()
  1514. if err != nil {
  1515. return nil, fmt.Errorf("error building monthly accumulation: %s", err)
  1516. }
  1517. toAccumulate = NewSummaryAllocationSetRange(sas)
  1518. // either the month has ended, or there are no more summary allocation sets
  1519. if month != nextDayMonth || i == len(sasr.SummaryAllocationSets)-1 {
  1520. if length := len(toAccumulate.SummaryAllocationSets); length != 1 {
  1521. return nil, fmt.Errorf("failed accumulation, detected %d sets instead of 1", length)
  1522. }
  1523. result.Append(toAccumulate.SummaryAllocationSets[0])
  1524. toAccumulate = nil
  1525. }
  1526. }
  1527. return result, nil
  1528. }
  1529. func (sasr *SummaryAllocationSetRange) accumulateByWeek() (*SummaryAllocationSetRange, error) {
  1530. if len(sasr.SummaryAllocationSets) > 0 && sasr.SummaryAllocationSets[0].Window.Duration() == timeutil.Week {
  1531. return sasr, nil
  1532. }
  1533. var toAccumulate *SummaryAllocationSetRange
  1534. result := NewSummaryAllocationSetRange()
  1535. for i, as := range sasr.SummaryAllocationSets {
  1536. if as.Window.Duration() != time.Hour*24 {
  1537. return nil, fmt.Errorf("window duration must equal 24 hours; got:%s", as.Window.Duration())
  1538. }
  1539. dayOfWeek := as.Window.Start().Weekday()
  1540. if toAccumulate == nil {
  1541. toAccumulate = NewSummaryAllocationSetRange()
  1542. as = as.Clone()
  1543. }
  1544. toAccumulate.Append(as)
  1545. sas, err := toAccumulate.accumulate()
  1546. if err != nil {
  1547. return nil, fmt.Errorf("error accumulating result: %s", err)
  1548. }
  1549. toAccumulate = NewSummaryAllocationSetRange(sas)
  1550. // current assumption is the week always ends on Saturday, or when there are no more summary allocation sets
  1551. if dayOfWeek == time.Saturday || i == len(sasr.SummaryAllocationSets)-1 {
  1552. if length := len(toAccumulate.SummaryAllocationSets); length != 1 {
  1553. return nil, fmt.Errorf("failed accumulation, detected %d sets instead of 1", length)
  1554. }
  1555. result.Append(toAccumulate.SummaryAllocationSets[0])
  1556. toAccumulate = nil
  1557. }
  1558. }
  1559. return result, nil
  1560. }
  1561. func (sasr *SummaryAllocationSetRange) Clone() *SummaryAllocationSetRange {
  1562. return sasr.clone()
  1563. }
  1564. // clone returns a new SummaryAllocationSetRange cloned from the existing SASR
  1565. func (sasr *SummaryAllocationSetRange) clone() *SummaryAllocationSetRange {
  1566. sasrSource := NewSummaryAllocationSetRange()
  1567. sasrSource.Window = sasr.Window.Clone()
  1568. sasrSource.Step = sasr.Step
  1569. sasrSource.Message = sasr.Message
  1570. for _, sas := range sasr.SummaryAllocationSets {
  1571. var sasClone *SummaryAllocationSet = nil
  1572. if sas != nil {
  1573. sasClone = sas.Clone()
  1574. }
  1575. sasrSource.Append(sasClone)
  1576. }
  1577. return sasrSource
  1578. }