allocation.go 76 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345
  1. package kubecost
  2. import (
  3. "bytes"
  4. "fmt"
  5. "sort"
  6. "strings"
  7. "sync"
  8. "time"
  9. "github.com/kubecost/cost-model/pkg/log"
  10. "github.com/kubecost/cost-model/pkg/util"
  11. "github.com/kubecost/cost-model/pkg/util/json"
  12. )
  13. // TODO Clean-up use of IsEmpty; nil checks should be separated for safety.
  14. // TODO Consider making Allocation an interface, which is fulfilled by structs
  15. // like KubernetesAllocation, IdleAllocation, and ExternalAllocation.
  16. // ExternalSuffix indicates an external allocation
  17. const ExternalSuffix = "__external__"
  18. // IdleSuffix indicates an idle allocation property
  19. const IdleSuffix = "__idle__"
  20. // SharedSuffix indicates an shared allocation property
  21. const SharedSuffix = "__shared__"
  22. // UnallocatedSuffix indicates an unallocated allocation property
  23. const UnallocatedSuffix = "__unallocated__"
  24. // UnmountedSuffix indicated allocation to an unmounted PV
  25. const UnmountedSuffix = "__unmounted__"
  26. // ShareWeighted indicates that a shared resource should be shared as a
  27. // proportion of the cost of the remaining allocations.
  28. const ShareWeighted = "__weighted__"
  29. // ShareEven indicates that a shared resource should be shared evenly across
  30. // all remaining allocations.
  31. const ShareEven = "__even__"
  32. // ShareNone indicates that a shareable resource should not be shared
  33. const ShareNone = "__none__"
  34. // Allocation is a unit of resource allocation and cost for a given window
  35. // of time and for a given kubernetes construct with its associated set of
  36. // properties.
  37. // TODO:CLEANUP consider dropping name in favor of just Allocation and an
  38. // Assets-style key() function for AllocationSet.
  39. type Allocation struct {
  40. Name string `json:"name"`
  41. Properties *AllocationProperties `json:"properties,omitempty"`
  42. Window Window `json:"window"`
  43. Start time.Time `json:"start"`
  44. End time.Time `json:"end"`
  45. CPUCoreHours float64 `json:"cpuCoreHours"`
  46. CPUCoreRequestAverage float64 `json:"cpuCoreRequestAverage"`
  47. CPUCoreUsageAverage float64 `json:"cpuCoreUsageAverage"`
  48. CPUCost float64 `json:"cpuCost"`
  49. CPUCostAdjustment float64 `json:"cpuCostAdjustment"`
  50. GPUHours float64 `json:"gpuHours"`
  51. GPUCost float64 `json:"gpuCost"`
  52. GPUCostAdjustment float64 `json:"gpuCostAdjustment"`
  53. NetworkTransferBytes float64 `json:"networkTransferBytes"`
  54. NetworkReceiveBytes float64 `json:"networkReceiveBytes"`
  55. NetworkCost float64 `json:"networkCost"`
  56. NetworkCostAdjustment float64 `json:"networkCostAdjustment"`
  57. LoadBalancerCost float64 `json:"loadBalancerCost"`
  58. LoadBalancerCostAdjustment float64 `json:"loadBalancerCostAdjustment"`
  59. PVs PVAllocations `json:"-"`
  60. PVCostAdjustment float64 `json:"pvCostAdjustment"`
  61. RAMByteHours float64 `json:"ramByteHours"`
  62. RAMBytesRequestAverage float64 `json:"ramByteRequestAverage"`
  63. RAMBytesUsageAverage float64 `json:"ramByteUsageAverage"`
  64. RAMCost float64 `json:"ramCost"`
  65. RAMCostAdjustment float64 `json:"ramCostAdjustment"`
  66. SharedCost float64 `json:"sharedCost"`
  67. ExternalCost float64 `json:"externalCost"`
  68. // RawAllocationOnly is a pointer so if it is not present it will be
  69. // marshalled as null rather than as an object with Go default values.
  70. RawAllocationOnly *RawAllocationOnlyData `json:"rawAllocationOnly"`
  71. }
  72. // RawAllocationOnlyData is information that only belong in "raw" Allocations,
  73. // those which have not undergone aggregation, accumulation, or any other form
  74. // of combination to produce a new Allocation from other Allocations.
  75. //
  76. // Max usage data belongs here because computing the overall maximum from two
  77. // or more Allocations is a non-trivial operation that cannot be defined without
  78. // maintaining a large amount of state. Consider the following example:
  79. // _______________________________________________
  80. //
  81. // A1 Using 3 CPU ---- ----- ------
  82. // A2 Using 2 CPU ---- ----- ----
  83. // A3 Using 1 CPU --- --
  84. // _______________________________________________
  85. // Time ---->
  86. //
  87. // The logical maximum CPU usage is 5, but this cannot be calculated iteratively,
  88. // which is how we calculate aggregations and accumulations of Allocations currently.
  89. // This becomes a problem I could call "maximum sum of overlapping intervals" and is
  90. // essentially a variant of an interval scheduling algorithm.
  91. //
  92. // If we had types to differentiate between regular Allocations and AggregatedAllocations
  93. // then this type would be unnecessary and its fields would go into the regular Allocation
  94. // and not in the AggregatedAllocation.
  95. type RawAllocationOnlyData struct {
  96. CPUCoreUsageMax float64 `json:"cpuCoreUsageMax"`
  97. RAMBytesUsageMax float64 `json:"ramByteUsageMax"`
  98. }
  99. // PVAllocations is a map of Disk Asset Identifiers to the
  100. // usage of them by an Allocation as recorded in a PVAllocation
  101. type PVAllocations map[PVKey]*PVAllocation
  102. // Clone creates a deep copy of a PVAllocations
  103. func (pv *PVAllocations) Clone() PVAllocations {
  104. if pv == nil || *pv == nil {
  105. return nil
  106. }
  107. apv := *pv
  108. clonePV := PVAllocations{}
  109. for k, v := range apv {
  110. clonePV[k] = &PVAllocation{
  111. ByteHours: v.ByteHours,
  112. Cost: v.Cost,
  113. }
  114. }
  115. return clonePV
  116. }
  117. // Add adds contents of that to the calling PVAllocations
  118. func (pv *PVAllocations) Add(that PVAllocations) PVAllocations {
  119. apv := pv.Clone()
  120. if that != nil {
  121. if apv == nil {
  122. apv = PVAllocations{}
  123. }
  124. for pvKey, thatPVAlloc := range that {
  125. apvAlloc, ok := apv[pvKey]
  126. if !ok {
  127. apvAlloc = &PVAllocation{}
  128. }
  129. apvAlloc.Cost += thatPVAlloc.Cost
  130. apvAlloc.ByteHours += thatPVAlloc.ByteHours
  131. apv[pvKey] = apvAlloc
  132. }
  133. }
  134. return apv
  135. }
  136. // PVKey for identifying Disk type assets
  137. type PVKey struct {
  138. Cluster string `json:"cluster"`
  139. Name string `json:"name"`
  140. }
  141. // PVAllocation contains the byte hour usage
  142. // and cost of an Allocation for a single PV
  143. type PVAllocation struct {
  144. ByteHours float64 `json:"byteHours"`
  145. Cost float64 `json:"cost"`
  146. }
  147. // AllocationMatchFunc is a function that can be used to match Allocations by
  148. // returning true for any given Allocation if a condition is met.
  149. type AllocationMatchFunc func(*Allocation) bool
  150. // Add returns the result of summing the two given Allocations, which sums the
  151. // summary fields (e.g. costs, resources) and recomputes efficiency. Neither of
  152. // the two original Allocations are mutated in the process.
  153. func (a *Allocation) Add(that *Allocation) (*Allocation, error) {
  154. if a == nil {
  155. return that.Clone(), nil
  156. }
  157. if that == nil {
  158. return a.Clone(), nil
  159. }
  160. // Note: no need to clone "that", as add only mutates the receiver
  161. agg := a.Clone()
  162. agg.add(that)
  163. return agg, nil
  164. }
  165. // Clone returns a deep copy of the given Allocation
  166. func (a *Allocation) Clone() *Allocation {
  167. if a == nil {
  168. return nil
  169. }
  170. return &Allocation{
  171. Name: a.Name,
  172. Properties: a.Properties.Clone(),
  173. Window: a.Window.Clone(),
  174. Start: a.Start,
  175. End: a.End,
  176. CPUCoreHours: a.CPUCoreHours,
  177. CPUCoreRequestAverage: a.CPUCoreRequestAverage,
  178. CPUCoreUsageAverage: a.CPUCoreUsageAverage,
  179. CPUCost: a.CPUCost,
  180. CPUCostAdjustment: a.CPUCostAdjustment,
  181. GPUHours: a.GPUHours,
  182. GPUCost: a.GPUCost,
  183. GPUCostAdjustment: a.GPUCostAdjustment,
  184. NetworkTransferBytes: a.NetworkTransferBytes,
  185. NetworkReceiveBytes: a.NetworkReceiveBytes,
  186. NetworkCost: a.NetworkCost,
  187. NetworkCostAdjustment: a.NetworkCostAdjustment,
  188. LoadBalancerCost: a.LoadBalancerCost,
  189. LoadBalancerCostAdjustment: a.LoadBalancerCostAdjustment,
  190. PVs: a.PVs.Clone(),
  191. PVCostAdjustment: a.PVCostAdjustment,
  192. RAMByteHours: a.RAMByteHours,
  193. RAMBytesRequestAverage: a.RAMBytesRequestAverage,
  194. RAMBytesUsageAverage: a.RAMBytesUsageAverage,
  195. RAMCost: a.RAMCost,
  196. RAMCostAdjustment: a.RAMCostAdjustment,
  197. SharedCost: a.SharedCost,
  198. ExternalCost: a.ExternalCost,
  199. RawAllocationOnly: a.RawAllocationOnly.Clone(),
  200. }
  201. }
  202. // Clone returns a deep copy of the given RawAllocationOnlyData
  203. func (r *RawAllocationOnlyData) Clone() *RawAllocationOnlyData {
  204. if r == nil {
  205. return nil
  206. }
  207. return &RawAllocationOnlyData{
  208. CPUCoreUsageMax: r.CPUCoreUsageMax,
  209. RAMBytesUsageMax: r.RAMBytesUsageMax,
  210. }
  211. }
  212. // Equal returns true if the values held in the given Allocation precisely
  213. // match those of the receiving Allocation. nil does not match nil. Floating
  214. // point values need to match according to util.IsApproximately, which accounts
  215. // for small, reasonable floating point error margins.
  216. func (a *Allocation) Equal(that *Allocation) bool {
  217. if a == nil || that == nil {
  218. return false
  219. }
  220. if a.Name != that.Name {
  221. return false
  222. }
  223. if !a.Properties.Equal(that.Properties) {
  224. return false
  225. }
  226. if !a.Window.Equal(that.Window) {
  227. return false
  228. }
  229. if !a.Start.Equal(that.Start) {
  230. return false
  231. }
  232. if !a.End.Equal(that.End) {
  233. return false
  234. }
  235. if !util.IsApproximately(a.CPUCoreHours, that.CPUCoreHours) {
  236. return false
  237. }
  238. if !util.IsApproximately(a.CPUCost, that.CPUCost) {
  239. return false
  240. }
  241. if !util.IsApproximately(a.CPUCostAdjustment, that.CPUCostAdjustment) {
  242. return false
  243. }
  244. if !util.IsApproximately(a.GPUHours, that.GPUHours) {
  245. return false
  246. }
  247. if !util.IsApproximately(a.GPUCost, that.GPUCost) {
  248. return false
  249. }
  250. if !util.IsApproximately(a.GPUCostAdjustment, that.GPUCostAdjustment) {
  251. return false
  252. }
  253. if !util.IsApproximately(a.NetworkTransferBytes, that.NetworkTransferBytes) {
  254. return false
  255. }
  256. if !util.IsApproximately(a.NetworkReceiveBytes, that.NetworkReceiveBytes) {
  257. return false
  258. }
  259. if !util.IsApproximately(a.NetworkCost, that.NetworkCost) {
  260. return false
  261. }
  262. if !util.IsApproximately(a.NetworkCostAdjustment, that.NetworkCostAdjustment) {
  263. return false
  264. }
  265. if !util.IsApproximately(a.LoadBalancerCost, that.LoadBalancerCost) {
  266. return false
  267. }
  268. if !util.IsApproximately(a.LoadBalancerCostAdjustment, that.LoadBalancerCostAdjustment) {
  269. return false
  270. }
  271. if !util.IsApproximately(a.PVCostAdjustment, that.PVCostAdjustment) {
  272. return false
  273. }
  274. if !util.IsApproximately(a.RAMByteHours, that.RAMByteHours) {
  275. return false
  276. }
  277. if !util.IsApproximately(a.RAMCost, that.RAMCost) {
  278. return false
  279. }
  280. if !util.IsApproximately(a.RAMCostAdjustment, that.RAMCostAdjustment) {
  281. return false
  282. }
  283. if !util.IsApproximately(a.SharedCost, that.SharedCost) {
  284. return false
  285. }
  286. if !util.IsApproximately(a.ExternalCost, that.ExternalCost) {
  287. return false
  288. }
  289. if a.RawAllocationOnly == nil && that.RawAllocationOnly != nil {
  290. return false
  291. }
  292. if a.RawAllocationOnly != nil && that.RawAllocationOnly == nil {
  293. return false
  294. }
  295. if a.RawAllocationOnly != nil && that.RawAllocationOnly != nil {
  296. if !util.IsApproximately(a.RawAllocationOnly.CPUCoreUsageMax, that.RawAllocationOnly.CPUCoreUsageMax) {
  297. return false
  298. }
  299. if !util.IsApproximately(a.RawAllocationOnly.RAMBytesUsageMax, that.RawAllocationOnly.RAMBytesUsageMax) {
  300. return false
  301. }
  302. }
  303. aPVs := a.PVs
  304. thatPVs := that.PVs
  305. if len(aPVs) == len(thatPVs) {
  306. for k, pv := range aPVs {
  307. tv, ok := thatPVs[k]
  308. if !ok || *tv != *pv {
  309. return false
  310. }
  311. }
  312. } else {
  313. return false
  314. }
  315. return true
  316. }
  317. // TotalCost is the total cost of the Allocation including adjustments
  318. func (a *Allocation) TotalCost() float64 {
  319. return a.CPUTotalCost() + a.GPUTotalCost() + a.RAMTotalCost() + a.PVTotalCost() + a.NetworkTotalCost() + a.LBTotalCost() + a.SharedTotalCost() + a.ExternalCost
  320. }
  321. // CPUTotalCost calculates total CPU cost of Allocation including adjustment
  322. func (a *Allocation) CPUTotalCost() float64 {
  323. return a.CPUCost + a.CPUCostAdjustment
  324. }
  325. // GPUTotalCost calculates total GPU cost of Allocation including adjustment
  326. func (a *Allocation) GPUTotalCost() float64 {
  327. return a.GPUCost + a.GPUCostAdjustment
  328. }
  329. // RAMTotalCost calculates total RAM cost of Allocation including adjustment
  330. func (a *Allocation) RAMTotalCost() float64 {
  331. return a.RAMCost + a.RAMCostAdjustment
  332. }
  333. // PVTotalCost calculates total PV cost of Allocation including adjustment
  334. func (a *Allocation) PVTotalCost() float64 {
  335. return a.PVCost() + a.PVCostAdjustment
  336. }
  337. // NetworkTotalCost calculates total Network cost of Allocation including adjustment
  338. func (a *Allocation) NetworkTotalCost() float64 {
  339. return a.NetworkCost + a.NetworkCostAdjustment
  340. }
  341. // LBTotalCost calculates total LB cost of Allocation including adjustment
  342. func (a *Allocation) LBTotalCost() float64 {
  343. return a.LoadBalancerCost + a.LoadBalancerCostAdjustment
  344. }
  345. // SharedTotalCost calculates total shared cost of Allocation including adjustment
  346. func (a *Allocation) SharedTotalCost() float64 {
  347. return a.SharedCost
  348. }
  349. // PVCost calculate cumulative cost of all PVs that Allocation is attached to
  350. func (a *Allocation) PVCost() float64 {
  351. cost := 0.0
  352. for _, pv := range a.PVs {
  353. cost += pv.Cost
  354. }
  355. return cost
  356. }
  357. // PVByteHours calculate cumulative ByteHours of all PVs that Allocation is attached to
  358. func (a *Allocation) PVByteHours() float64 {
  359. byteHours := 0.0
  360. for _, pv := range a.PVs {
  361. byteHours += pv.ByteHours
  362. }
  363. return byteHours
  364. }
  365. // CPUEfficiency is the ratio of usage to request. If there is no request and
  366. // no usage or cost, then efficiency is zero. If there is no request, but there
  367. // is usage or cost, then efficiency is 100%.
  368. func (a *Allocation) CPUEfficiency() float64 {
  369. if a.CPUCoreRequestAverage > 0 {
  370. return a.CPUCoreUsageAverage / a.CPUCoreRequestAverage
  371. }
  372. if a.CPUCoreUsageAverage == 0.0 || a.CPUCost == 0.0 {
  373. return 0.0
  374. }
  375. return 1.0
  376. }
  377. // RAMEfficiency is the ratio of usage to request. If there is no request and
  378. // no usage or cost, then efficiency is zero. If there is no request, but there
  379. // is usage or cost, then efficiency is 100%.
  380. func (a *Allocation) RAMEfficiency() float64 {
  381. if a.RAMBytesRequestAverage > 0 {
  382. return a.RAMBytesUsageAverage / a.RAMBytesRequestAverage
  383. }
  384. if a.RAMBytesUsageAverage == 0.0 || a.RAMCost == 0.0 {
  385. return 0.0
  386. }
  387. return 1.0
  388. }
  389. // TotalEfficiency is the cost-weighted average of CPU and RAM efficiency. If
  390. // there is no cost at all, then efficiency is zero.
  391. func (a *Allocation) TotalEfficiency() float64 {
  392. if a.RAMTotalCost()+a.CPUTotalCost() > 0 {
  393. ramCostEff := a.RAMEfficiency() * a.RAMTotalCost()
  394. cpuCostEff := a.CPUEfficiency() * a.CPUTotalCost()
  395. return (ramCostEff + cpuCostEff) / (a.CPUTotalCost() + a.RAMTotalCost())
  396. }
  397. return 0.0
  398. }
  399. // CPUCores converts the Allocation's CPUCoreHours into average CPUCores
  400. func (a *Allocation) CPUCores() float64 {
  401. if a.Minutes() <= 0.0 {
  402. return 0.0
  403. }
  404. return a.CPUCoreHours / (a.Minutes() / 60.0)
  405. }
  406. // RAMBytes converts the Allocation's RAMByteHours into average RAMBytes
  407. func (a *Allocation) RAMBytes() float64 {
  408. if a.Minutes() <= 0.0 {
  409. return 0.0
  410. }
  411. return a.RAMByteHours / (a.Minutes() / 60.0)
  412. }
  413. // GPUs converts the Allocation's GPUHours into average GPUs
  414. func (a *Allocation) GPUs() float64 {
  415. if a.Minutes() <= 0.0 {
  416. return 0.0
  417. }
  418. return a.GPUHours / (a.Minutes() / 60.0)
  419. }
  420. // PVBytes converts the Allocation's PVByteHours into average PVBytes
  421. func (a *Allocation) PVBytes() float64 {
  422. if a.Minutes() <= 0.0 {
  423. return 0.0
  424. }
  425. return a.PVByteHours() / (a.Minutes() / 60.0)
  426. }
  427. // ResetAdjustments sets all cost adjustment fields to zero
  428. func (a *Allocation) ResetAdjustments() {
  429. a.CPUCostAdjustment = 0.0
  430. a.GPUCostAdjustment = 0.0
  431. a.RAMCostAdjustment = 0.0
  432. a.PVCostAdjustment = 0.0
  433. a.NetworkCostAdjustment = 0.0
  434. a.LoadBalancerCostAdjustment = 0.0
  435. }
  436. // MarshalJSON implements json.Marshaler interface
  437. func (a *Allocation) MarshalJSON() ([]byte, error) {
  438. buffer := bytes.NewBufferString("{")
  439. jsonEncodeString(buffer, "name", a.Name, ",")
  440. jsonEncode(buffer, "properties", a.Properties, ",")
  441. jsonEncode(buffer, "window", a.Window, ",")
  442. jsonEncodeString(buffer, "start", a.Start.Format(time.RFC3339), ",")
  443. jsonEncodeString(buffer, "end", a.End.Format(time.RFC3339), ",")
  444. jsonEncodeFloat64(buffer, "minutes", a.Minutes(), ",")
  445. jsonEncodeFloat64(buffer, "cpuCores", a.CPUCores(), ",")
  446. jsonEncodeFloat64(buffer, "cpuCoreRequestAverage", a.CPUCoreRequestAverage, ",")
  447. jsonEncodeFloat64(buffer, "cpuCoreUsageAverage", a.CPUCoreUsageAverage, ",")
  448. jsonEncodeFloat64(buffer, "cpuCoreHours", a.CPUCoreHours, ",")
  449. jsonEncodeFloat64(buffer, "cpuCost", a.CPUCost, ",")
  450. jsonEncodeFloat64(buffer, "cpuCostAdjustment", a.CPUCostAdjustment, ",")
  451. jsonEncodeFloat64(buffer, "cpuEfficiency", a.CPUEfficiency(), ",")
  452. jsonEncodeFloat64(buffer, "gpuCount", a.GPUs(), ",")
  453. jsonEncodeFloat64(buffer, "gpuHours", a.GPUHours, ",")
  454. jsonEncodeFloat64(buffer, "gpuCost", a.GPUCost, ",")
  455. jsonEncodeFloat64(buffer, "gpuCostAdjustment", a.GPUCostAdjustment, ",")
  456. jsonEncodeFloat64(buffer, "networkTransferBytes", a.NetworkTransferBytes, ",")
  457. jsonEncodeFloat64(buffer, "networkReceiveBytes", a.NetworkReceiveBytes, ",")
  458. jsonEncodeFloat64(buffer, "networkCost", a.NetworkCost, ",")
  459. jsonEncodeFloat64(buffer, "networkCostAdjustment", a.NetworkCostAdjustment, ",")
  460. jsonEncodeFloat64(buffer, "loadBalancerCost", a.LoadBalancerCost, ",")
  461. jsonEncodeFloat64(buffer, "loadBalancerCostAdjustment", a.LoadBalancerCostAdjustment, ",")
  462. jsonEncodeFloat64(buffer, "pvBytes", a.PVBytes(), ",")
  463. jsonEncodeFloat64(buffer, "pvByteHours", a.PVByteHours(), ",")
  464. jsonEncodeFloat64(buffer, "pvCost", a.PVCost(), ",")
  465. jsonEncode(buffer, "pvs", a.PVs, ",") // Todo Sean: this does not work properly
  466. jsonEncodeFloat64(buffer, "pvCostAdjustment", a.PVCostAdjustment, ",")
  467. jsonEncodeFloat64(buffer, "ramBytes", a.RAMBytes(), ",")
  468. jsonEncodeFloat64(buffer, "ramByteRequestAverage", a.RAMBytesRequestAverage, ",")
  469. jsonEncodeFloat64(buffer, "ramByteUsageAverage", a.RAMBytesUsageAverage, ",")
  470. jsonEncodeFloat64(buffer, "ramByteHours", a.RAMByteHours, ",")
  471. jsonEncodeFloat64(buffer, "ramCost", a.RAMCost, ",")
  472. jsonEncodeFloat64(buffer, "ramCostAdjustment", a.RAMCostAdjustment, ",")
  473. jsonEncodeFloat64(buffer, "ramEfficiency", a.RAMEfficiency(), ",")
  474. jsonEncodeFloat64(buffer, "sharedCost", a.SharedCost, ",")
  475. jsonEncodeFloat64(buffer, "externalCost", a.ExternalCost, ",")
  476. jsonEncodeFloat64(buffer, "totalCost", a.TotalCost(), ",")
  477. jsonEncodeFloat64(buffer, "totalEfficiency", a.TotalEfficiency(), ",")
  478. jsonEncode(buffer, "rawAllocationOnly", a.RawAllocationOnly, "")
  479. buffer.WriteString("}")
  480. return buffer.Bytes(), nil
  481. }
  482. // Resolution returns the duration of time covered by the Allocation
  483. func (a *Allocation) Resolution() time.Duration {
  484. return a.End.Sub(a.Start)
  485. }
  486. // IsAggregated is true if the given Allocation has been aggregated, which we
  487. // define by a lack of AllocationProperties.
  488. func (a *Allocation) IsAggregated() bool {
  489. return a == nil || a.Properties == nil
  490. }
  491. // IsExternal is true if the given Allocation represents external costs.
  492. func (a *Allocation) IsExternal() bool {
  493. return strings.Contains(a.Name, ExternalSuffix)
  494. }
  495. // IsIdle is true if the given Allocation represents idle costs.
  496. func (a *Allocation) IsIdle() bool {
  497. return strings.Contains(a.Name, IdleSuffix)
  498. }
  499. // IsUnallocated is true if the given Allocation represents unallocated costs.
  500. func (a *Allocation) IsUnallocated() bool {
  501. return strings.Contains(a.Name, UnallocatedSuffix)
  502. }
  503. // Minutes returns the number of minutes the Allocation represents, as defined
  504. // by the difference between the end and start times.
  505. func (a *Allocation) Minutes() float64 {
  506. return a.End.Sub(a.Start).Minutes()
  507. }
  508. // Share adds the TotalCost of the given Allocation to the SharedCost of the
  509. // receiving Allocation. No Start, End, Window, or AllocationProperties are considered.
  510. // Neither Allocation is mutated; a new Allocation is always returned.
  511. func (a *Allocation) Share(that *Allocation) (*Allocation, error) {
  512. if that == nil {
  513. return a.Clone(), nil
  514. }
  515. if a == nil {
  516. return nil, fmt.Errorf("cannot share with nil Allocation")
  517. }
  518. agg := a.Clone()
  519. agg.SharedCost += that.TotalCost()
  520. return agg, nil
  521. }
  522. // String represents the given Allocation as a string
  523. func (a *Allocation) String() string {
  524. return fmt.Sprintf("%s%s=%.2f", a.Name, NewWindow(&a.Start, &a.End), a.TotalCost())
  525. }
  526. func (a *Allocation) add(that *Allocation) {
  527. if a == nil {
  528. log.Warningf("Allocation.AggregateBy: trying to add a nil receiver")
  529. return
  530. }
  531. // Preserve string properties that are matching between the two allocations
  532. a.Properties = a.Properties.Intersection(that.Properties)
  533. // Expand the window to encompass both Allocations
  534. a.Window = a.Window.Expand(that.Window)
  535. // Sum non-cumulative fields by turning them into cumulative, adding them,
  536. // and then converting them back into averages after minutes have been
  537. // combined (just below).
  538. cpuReqCoreMins := a.CPUCoreRequestAverage * a.Minutes()
  539. cpuReqCoreMins += that.CPUCoreRequestAverage * that.Minutes()
  540. cpuUseCoreMins := a.CPUCoreUsageAverage * a.Minutes()
  541. cpuUseCoreMins += that.CPUCoreUsageAverage * that.Minutes()
  542. ramReqByteMins := a.RAMBytesRequestAverage * a.Minutes()
  543. ramReqByteMins += that.RAMBytesRequestAverage * that.Minutes()
  544. ramUseByteMins := a.RAMBytesUsageAverage * a.Minutes()
  545. ramUseByteMins += that.RAMBytesUsageAverage * that.Minutes()
  546. // Expand Start and End to be the "max" of among the given Allocations
  547. if that.Start.Before(a.Start) {
  548. a.Start = that.Start
  549. }
  550. if that.End.After(a.End) {
  551. a.End = that.End
  552. }
  553. // Convert cumulative request and usage back into rates
  554. // TODO:TEST write a unit test that fails if this is done incorrectly
  555. if a.Minutes() > 0 {
  556. a.CPUCoreRequestAverage = cpuReqCoreMins / a.Minutes()
  557. a.CPUCoreUsageAverage = cpuUseCoreMins / a.Minutes()
  558. a.RAMBytesRequestAverage = ramReqByteMins / a.Minutes()
  559. a.RAMBytesUsageAverage = ramUseByteMins / a.Minutes()
  560. } else {
  561. a.CPUCoreRequestAverage = 0.0
  562. a.CPUCoreUsageAverage = 0.0
  563. a.RAMBytesRequestAverage = 0.0
  564. a.RAMBytesUsageAverage = 0.0
  565. }
  566. // Sum all cumulative resource fields
  567. a.CPUCoreHours += that.CPUCoreHours
  568. a.GPUHours += that.GPUHours
  569. a.RAMByteHours += that.RAMByteHours
  570. a.NetworkTransferBytes += that.NetworkTransferBytes
  571. a.NetworkReceiveBytes += that.NetworkReceiveBytes
  572. // Sum all cumulative cost fields
  573. a.CPUCost += that.CPUCost
  574. a.GPUCost += that.GPUCost
  575. a.RAMCost += that.RAMCost
  576. a.NetworkCost += that.NetworkCost
  577. a.LoadBalancerCost += that.LoadBalancerCost
  578. a.SharedCost += that.SharedCost
  579. a.ExternalCost += that.ExternalCost
  580. // Sum PVAllocations
  581. a.PVs = a.PVs.Add(that.PVs)
  582. // Sum all cumulative adjustment fields
  583. a.CPUCostAdjustment += that.CPUCostAdjustment
  584. a.RAMCostAdjustment += that.RAMCostAdjustment
  585. a.GPUCostAdjustment += that.GPUCostAdjustment
  586. a.PVCostAdjustment += that.PVCostAdjustment
  587. a.NetworkCostAdjustment += that.NetworkCostAdjustment
  588. a.LoadBalancerCostAdjustment += that.LoadBalancerCostAdjustment
  589. // Any data that is in a "raw allocation only" is not valid in any
  590. // sort of cumulative Allocation (like one that is added).
  591. a.RawAllocationOnly = nil
  592. }
  593. // AllocationSet stores a set of Allocations, each with a unique name, that share
  594. // a window. An AllocationSet is mutable, so treat it like a threadsafe map.
  595. type AllocationSet struct {
  596. sync.RWMutex
  597. allocations map[string]*Allocation
  598. externalKeys map[string]bool
  599. idleKeys map[string]bool
  600. FromSource string // stores the name of the source used to compute the data
  601. Window Window
  602. Warnings []string
  603. Errors []string
  604. }
  605. // NewAllocationSet instantiates a new AllocationSet and, optionally, inserts
  606. // the given list of Allocations
  607. func NewAllocationSet(start, end time.Time, allocs ...*Allocation) *AllocationSet {
  608. as := &AllocationSet{
  609. allocations: map[string]*Allocation{},
  610. externalKeys: map[string]bool{},
  611. idleKeys: map[string]bool{},
  612. Window: NewWindow(&start, &end),
  613. }
  614. for _, a := range allocs {
  615. as.Insert(a)
  616. }
  617. return as
  618. }
  619. // AllocationAggregationOptions provide advanced functionality to AggregateBy, including
  620. // filtering results and sharing allocations. FilterFuncs are a list of match
  621. // functions such that, if any function fails, the allocation is ignored.
  622. // ShareFuncs are a list of match functions such that, if any function
  623. // succeeds, the allocation is marked as a shared resource. ShareIdle is a
  624. // simple flag for sharing idle resources.
  625. type AllocationAggregationOptions struct {
  626. FilterFuncs []AllocationMatchFunc
  627. SplitIdle bool
  628. IdleByNode bool
  629. MergeUnallocated bool
  630. ShareFuncs []AllocationMatchFunc
  631. ShareIdle string
  632. ShareSplit string
  633. SharedHourlyCosts map[string]float64
  634. }
  635. // AggregateBy aggregates the Allocations in the given AllocationSet by the given
  636. // AllocationProperty. This will only be legal if the AllocationSet is divisible by the
  637. // given AllocationProperty; e.g. Containers can be divided by Namespace, but not vice-a-versa.
  638. func (as *AllocationSet) AggregateBy(aggregateBy []string, options *AllocationAggregationOptions) error {
  639. // The order of operations for aggregating allocations is as follows:
  640. // 1. Partition external, idle, and shared allocations into separate sets.
  641. // Also, create the aggSet into which the results will be aggregated.
  642. // 2. Compute sharing coefficients for idle and shared resources
  643. // a) if idle allocation is to be shared, compute idle coefficients
  644. // b) if idle allocation is NOT shared, but filters are present, compute
  645. // idle filtration coefficients for the purpose of only returning the
  646. // portion of idle allocation that would have been shared with the
  647. // unfiltered results. (See unit tests 5.a,b,c)
  648. // c) generate shared allocation for then given shared overhead, which
  649. // must happen after (2a) and (2b)
  650. // d) if there are shared resources, compute share coefficients
  651. // 3. Drop any allocation that fails any of the filters
  652. // 4. Distribute idle allocations according to the idle coefficients
  653. // 5. Generate aggregation key and insert allocation into the output set
  654. // 6. If idle is shared and resources are shared, some idle might be shared
  655. // with a shared resource. Distribute that to the shared resources
  656. // prior to sharing them with the aggregated results.
  657. // 7. Apply idle filtration coefficients from step (2b)
  658. // 8. Distribute shared allocations according to the share coefficients.
  659. // 9. If there are external allocations that can be aggregated into
  660. // the output (i.e. they can be used to generate a valid key for
  661. // the given properties) then aggregate; otherwise... ignore them?
  662. // 10. If the merge idle option is enabled, merge any remaining idle
  663. // allocations into a single idle allocation
  664. if options == nil {
  665. options = &AllocationAggregationOptions{}
  666. }
  667. if as.IsEmpty() {
  668. return nil
  669. }
  670. // aggSet will collect the aggregated allocations
  671. aggSet := &AllocationSet{
  672. Window: as.Window.Clone(),
  673. }
  674. // externalSet will collect external allocations
  675. externalSet := &AllocationSet{
  676. Window: as.Window.Clone(),
  677. }
  678. // idleSet will be shared among aggSet after initial aggregation
  679. // is complete
  680. idleSet := &AllocationSet{
  681. Window: as.Window.Clone(),
  682. }
  683. // shareSet will be shared among aggSet after initial aggregation
  684. // is complete
  685. shareSet := &AllocationSet{
  686. Window: as.Window.Clone(),
  687. }
  688. as.Lock()
  689. defer as.Unlock()
  690. // (1) Loop and find all of the external, idle, and shared allocations. Add
  691. // them to their respective sets, removing them from the set of allocations
  692. // to aggregate.
  693. for _, alloc := range as.allocations {
  694. // External allocations get aggregated post-hoc (see step 6) and do
  695. // not necessarily contain complete sets of properties, so they are
  696. // moved to a separate AllocationSet.
  697. if alloc.IsExternal() {
  698. delete(as.externalKeys, alloc.Name)
  699. delete(as.allocations, alloc.Name)
  700. externalSet.Insert(alloc)
  701. continue
  702. }
  703. // Idle allocations should be separated into idleSet if they are to be
  704. // shared later on. If they are not to be shared, then add them to the
  705. // aggSet like any other allocation.
  706. if alloc.IsIdle() {
  707. delete(as.idleKeys, alloc.Name)
  708. delete(as.allocations, alloc.Name)
  709. if options.ShareIdle == ShareEven || options.ShareIdle == ShareWeighted {
  710. idleSet.Insert(alloc)
  711. } else {
  712. aggSet.Insert(alloc)
  713. }
  714. continue
  715. }
  716. // Shared allocations must be identified and separated prior to
  717. // aggregation and filtering. That is, if any of the ShareFuncs return
  718. // true for the allocation, then move it to shareSet.
  719. for _, sf := range options.ShareFuncs {
  720. if sf(alloc) {
  721. delete(as.idleKeys, alloc.Name)
  722. delete(as.allocations, alloc.Name)
  723. shareSet.Insert(alloc)
  724. break
  725. }
  726. }
  727. }
  728. // It's possible that no more un-shared, non-idle, non-external allocations
  729. // remain at this point. This always results in an emptySet, so return early.
  730. if len(as.allocations) == 0 {
  731. emptySet := &AllocationSet{
  732. Window: as.Window.Clone(),
  733. }
  734. as.allocations = emptySet.allocations
  735. return nil
  736. }
  737. // (2) In order to correctly share idle and shared costs, we first compute
  738. // sharing coefficients, which represent the proportion of each cost to
  739. // share with each allocation. Idle allocations are shared per-cluster or per-node,
  740. // per-allocation, and per-resource, while shared resources are shared per-
  741. // allocation only.
  742. //
  743. // For an idleCoefficient example, the entries:
  744. // [cluster1][cluster1/namespace1/pod1/container1][cpu] = 0.166667
  745. // [cluster1][cluster1/namespace1/pod1/container1][gpu] = 0.166667
  746. // [cluster1][cluster1/namespace1/pod1/container1][ram] = 0.687500
  747. // mean that the allocation "cluster1/namespace1/pod1/container1" will
  748. // receive 16.67% of cluster1's idle CPU and GPU costs and 68.75% of its
  749. // RAM costs.
  750. //
  751. // For a shareCoefficient example, the entries:
  752. // [namespace2] = 0.666667
  753. // [__filtered__] = 0.333333
  754. // mean that the post-aggregation allocation "namespace2" will receive
  755. // 66.67% of the shared resource costs, while the remaining 33.33% will
  756. // be filtered out, as they were shared with allocations that did not pass
  757. // one of the given filters.
  758. //
  759. // In order to maintain stable results when multiple operations are being
  760. // carried out (e.g. sharing idle, sharing resources, and filtering) these
  761. // coefficients are computed for the full set of allocations prior to
  762. // adding shared overhead and prior to applying filters.
  763. var err error
  764. // (2a) If there are idle costs to be shared, compute the coefficients for
  765. // sharing them among the non-idle, non-aggregated allocations (including
  766. // the shared allocations).
  767. var idleCoefficients map[string]map[string]map[string]float64
  768. if idleSet.Length() > 0 && options.ShareIdle != ShareNone {
  769. idleCoefficients, err = computeIdleCoeffs(options, as, shareSet)
  770. if err != nil {
  771. log.Warningf("AllocationSet.AggregateBy: compute idle coeff: %s", err)
  772. return fmt.Errorf("error computing idle coefficients: %s", err)
  773. }
  774. }
  775. // (2b) If idle costs are not to be shared, but there are filters, then we
  776. // need to track the amount of each idle allocation to "filter" in order to
  777. // maintain parity with the results when idle is shared. That is, we want
  778. // to return only the idle costs that would have been shared with the given
  779. // results, even if the filter had not been applied.
  780. //
  781. // For example, consider these results from aggregating by namespace with
  782. // two clusters:
  783. //
  784. // namespace1: 25.00
  785. // namespace2: 30.00
  786. // namespace3: 15.00
  787. // idle: 30.00
  788. //
  789. // When we then filter by cluster==cluster1, namespaces 2 and 3 are
  790. // reduced by the amount that existed on cluster2. Then, idle must also be
  791. // reduced by the relevant amount:
  792. //
  793. // namespace1: 25.00
  794. // namespace2: 15.00
  795. // idle: 20.00
  796. //
  797. // Note that this can happen for any field, not just cluster, so we again
  798. // need to track this on a per-cluster or per-node, per-allocation, per-resource basis.
  799. var idleFiltrationCoefficients map[string]map[string]map[string]float64
  800. if len(options.FilterFuncs) > 0 && options.ShareIdle == ShareNone {
  801. idleFiltrationCoefficients, err = computeIdleCoeffs(options, as, shareSet)
  802. if err != nil {
  803. return fmt.Errorf("error computing idle filtration coefficients: %s", err)
  804. }
  805. }
  806. // (2c) Convert SharedHourlyCosts to Allocations in the shareSet. This must
  807. // come after idle coefficients are computes so that allocations generated
  808. // by shared overhead do not skew the idle coefficient computation.
  809. for name, cost := range options.SharedHourlyCosts {
  810. if cost > 0.0 {
  811. hours := as.Resolution().Hours()
  812. // If set ends in the future, adjust hours accordingly
  813. diff := time.Now().Sub(as.End())
  814. if diff < 0.0 {
  815. hours += diff.Hours()
  816. }
  817. totalSharedCost := cost * hours
  818. shareSet.Insert(&Allocation{
  819. Name: fmt.Sprintf("%s/%s", name, SharedSuffix),
  820. Start: as.Start(),
  821. End: as.End(),
  822. SharedCost: totalSharedCost,
  823. Properties: &AllocationProperties{Cluster: SharedSuffix}, // The allocation needs to belong to a cluster,but it really doesn't matter which one, so just make it clear.
  824. })
  825. }
  826. }
  827. // (2d) Compute share coefficients for shared resources. These are computed
  828. // after idle coefficients, and are computed for the aggregated allocations
  829. // of the main allocation set. See above for details and an example.
  830. var shareCoefficients map[string]float64
  831. if shareSet.Length() > 0 {
  832. shareCoefficients, err = computeShareCoeffs(aggregateBy, options, as)
  833. if err != nil {
  834. return fmt.Errorf("error computing share coefficients: %s", err)
  835. }
  836. }
  837. // (3-5) Filter, distribute idle cost, and aggregate (in that order)
  838. for _, alloc := range as.allocations {
  839. idleId, err := alloc.getIdleId(options)
  840. if err != nil {
  841. log.DedupedWarningf(3, "AllocationSet.AggregateBy: missing idleId for allocation: %s", alloc.Name)
  842. }
  843. skip := false
  844. // (3) If any of the filter funcs fail, immediately skip the allocation.
  845. for _, ff := range options.FilterFuncs {
  846. if !ff(alloc) {
  847. skip = true
  848. break
  849. }
  850. }
  851. if skip {
  852. // If we are tracking idle filtration coefficients, delete the
  853. // entry corresponding to the filtered allocation. (Deleting the
  854. // entry will result in that proportional amount being removed
  855. // from the idle allocation at the end of the process.)
  856. if idleFiltrationCoefficients != nil {
  857. if ifcc, ok := idleFiltrationCoefficients[idleId]; ok {
  858. delete(ifcc, alloc.Name)
  859. }
  860. }
  861. continue
  862. }
  863. // (4) Distribute idle allocations according to the idle coefficients
  864. // NOTE: if idle allocation is off (i.e. ShareIdle == ShareNone) then
  865. // all idle allocations will be in the aggSet at this point, so idleSet
  866. // will be empty and we won't enter this block.
  867. if idleSet.Length() > 0 {
  868. // Distribute idle allocations by coefficient per-idleId, per-allocation
  869. for _, idleAlloc := range idleSet.allocations {
  870. // Only share idle if the idleId matches; i.e. the allocation
  871. // is from the same idleId as the idle costs
  872. iaidleId, err := idleAlloc.getIdleId(options)
  873. if err != nil {
  874. log.Errorf("AllocationSet.AggregateBy: Idle allocation is missing idleId %s", idleAlloc.Name)
  875. return err
  876. }
  877. if iaidleId != idleId {
  878. continue
  879. }
  880. // Make sure idle coefficients exist
  881. if _, ok := idleCoefficients[idleId]; !ok {
  882. log.Warningf("AllocationSet.AggregateBy: error getting idle coefficient: no idleId '%s' for '%s'", idleId, alloc.Name)
  883. continue
  884. }
  885. if _, ok := idleCoefficients[idleId][alloc.Name]; !ok {
  886. log.Warningf("AllocationSet.AggregateBy: error getting idle coefficient for '%s'", alloc.Name)
  887. continue
  888. }
  889. alloc.CPUCoreHours += idleAlloc.CPUCoreHours * idleCoefficients[idleId][alloc.Name]["cpu"]
  890. alloc.GPUHours += idleAlloc.GPUHours * idleCoefficients[idleId][alloc.Name]["gpu"]
  891. alloc.RAMByteHours += idleAlloc.RAMByteHours * idleCoefficients[idleId][alloc.Name]["ram"]
  892. idleCPUCost := idleAlloc.CPUCost * idleCoefficients[idleId][alloc.Name]["cpu"]
  893. idleGPUCost := idleAlloc.GPUCost * idleCoefficients[idleId][alloc.Name]["gpu"]
  894. idleRAMCost := idleAlloc.RAMCost * idleCoefficients[idleId][alloc.Name]["ram"]
  895. alloc.CPUCost += idleCPUCost
  896. alloc.GPUCost += idleGPUCost
  897. alloc.RAMCost += idleRAMCost
  898. }
  899. }
  900. // (5) generate key to use for aggregation-by-key and allocation name
  901. key := alloc.generateKey(aggregateBy)
  902. alloc.Name = key
  903. if options.MergeUnallocated && alloc.IsUnallocated() {
  904. alloc.Name = UnallocatedSuffix
  905. }
  906. // Inserting the allocation with the generated key for a name will
  907. // perform the actual basic aggregation step.
  908. aggSet.Insert(alloc)
  909. }
  910. // (6) If idle is shared and resources are shared, it's possible that some
  911. // amount of idle cost will be shared with a shared resource. Distribute
  912. // that idle allocation, if it exists, to the respective shared allocations
  913. // before sharing with the aggregated allocations.
  914. if idleSet.Length() > 0 && shareSet.Length() > 0 {
  915. for _, alloc := range shareSet.allocations {
  916. idleId, err := alloc.getIdleId(options)
  917. if err != nil {
  918. log.DedupedWarningf(3, "AllocationSet.AggregateBy: missing idleId for allocation: %s", alloc.Name)
  919. }
  920. // Distribute idle allocations by coefficient per-idleId, per-allocation
  921. for _, idleAlloc := range idleSet.allocations {
  922. // Only share idle if the idleId matches; i.e. the allocation
  923. // is from the same idleId as the idle costs
  924. iaidleId, _ := idleAlloc.getIdleId(options)
  925. if iaidleId != idleId {
  926. continue
  927. }
  928. // Make sure idle coefficients exist
  929. if _, ok := idleCoefficients[idleId]; !ok {
  930. log.Warningf("AllocationSet.AggregateBy: error getting idle coefficient: no idleId '%s' for '%s'", idleId, alloc.Name)
  931. continue
  932. }
  933. if _, ok := idleCoefficients[idleId][alloc.Name]; !ok {
  934. log.Warningf("AllocationSet.AggregateBy: error getting idle coefficient for '%s'", alloc.Name)
  935. continue
  936. }
  937. alloc.CPUCoreHours += idleAlloc.CPUCoreHours * idleCoefficients[idleId][alloc.Name]["cpu"]
  938. alloc.GPUHours += idleAlloc.GPUHours * idleCoefficients[idleId][alloc.Name]["gpu"]
  939. alloc.RAMByteHours += idleAlloc.RAMByteHours * idleCoefficients[idleId][alloc.Name]["ram"]
  940. idleCPUCost := idleAlloc.CPUCost * idleCoefficients[idleId][alloc.Name]["cpu"]
  941. idleGPUCost := idleAlloc.GPUCost * idleCoefficients[idleId][alloc.Name]["gpu"]
  942. idleRAMCost := idleAlloc.RAMCost * idleCoefficients[idleId][alloc.Name]["ram"]
  943. alloc.CPUCost += idleCPUCost
  944. alloc.GPUCost += idleGPUCost
  945. alloc.RAMCost += idleRAMCost
  946. }
  947. }
  948. }
  949. // groupingIdleFiltrationCoeffs is used to track per-resource idle
  950. // coefficients on a cluster-by-cluster or node-by-node basis depending
  951. // on the IdleByNode option. It is, essentailly, an aggregation of
  952. // idleFiltrationCoefficients after they have been
  953. // filtered above (in step 3)
  954. var groupingIdleFiltrationCoeffs map[string]map[string]float64
  955. if idleFiltrationCoefficients != nil {
  956. groupingIdleFiltrationCoeffs = map[string]map[string]float64{}
  957. for idleId, m := range idleFiltrationCoefficients {
  958. if _, ok := groupingIdleFiltrationCoeffs[idleId]; !ok {
  959. groupingIdleFiltrationCoeffs[idleId] = map[string]float64{
  960. "cpu": 0.0,
  961. "gpu": 0.0,
  962. "ram": 0.0,
  963. }
  964. }
  965. for _, n := range m {
  966. for resource, val := range n {
  967. groupingIdleFiltrationCoeffs[idleId][resource] += val
  968. }
  969. }
  970. }
  971. }
  972. // (7) If we have both un-shared idle allocations and idle filtration
  973. // coefficients then apply those. See step (2b) for an example.
  974. if len(aggSet.idleKeys) > 0 && groupingIdleFiltrationCoeffs != nil {
  975. for idleKey := range aggSet.idleKeys {
  976. idleAlloc := aggSet.Get(idleKey)
  977. iaidleId, err := idleAlloc.getIdleId(options)
  978. if err != nil {
  979. log.Errorf("AllocationSet.AggregateBy: Idle allocation is missing idleId %s", idleAlloc.Name)
  980. return err
  981. }
  982. if resourceCoeffs, ok := groupingIdleFiltrationCoeffs[iaidleId]; ok {
  983. idleAlloc.CPUCost *= resourceCoeffs["cpu"]
  984. idleAlloc.CPUCoreHours *= resourceCoeffs["cpu"]
  985. idleAlloc.RAMCost *= resourceCoeffs["ram"]
  986. idleAlloc.RAMByteHours *= resourceCoeffs["ram"]
  987. }
  988. }
  989. }
  990. // (8) Distribute shared allocations according to the share coefficients.
  991. if shareSet.Length() > 0 {
  992. for _, alloc := range aggSet.allocations {
  993. for _, sharedAlloc := range shareSet.allocations {
  994. if _, ok := shareCoefficients[alloc.Name]; !ok {
  995. log.Warningf("AllocationSet.AggregateBy: error getting share coefficienct for '%s'", alloc.Name)
  996. continue
  997. }
  998. alloc.SharedCost += sharedAlloc.TotalCost() * shareCoefficients[alloc.Name]
  999. }
  1000. }
  1001. }
  1002. // (9) Aggregate external allocations into aggregated allocations. This may
  1003. // not be possible for every external allocation, but attempt to find an
  1004. // exact key match, given each external allocation's proerties, and
  1005. // aggregate if an exact match is found.
  1006. for _, alloc := range externalSet.allocations {
  1007. skip := false
  1008. for _, ff := range options.FilterFuncs {
  1009. if !ff(alloc) {
  1010. skip = true
  1011. break
  1012. }
  1013. }
  1014. if !skip {
  1015. key := alloc.generateKey(aggregateBy)
  1016. alloc.Name = key
  1017. aggSet.Insert(alloc)
  1018. }
  1019. }
  1020. // (10) Combine all idle allocations into a single "__idle__" allocation
  1021. if !options.SplitIdle {
  1022. for _, idleAlloc := range aggSet.IdleAllocations() {
  1023. aggSet.Delete(idleAlloc.Name)
  1024. idleAlloc.Name = IdleSuffix
  1025. aggSet.Insert(idleAlloc)
  1026. }
  1027. }
  1028. as.allocations = aggSet.allocations
  1029. return nil
  1030. }
  1031. func computeShareCoeffs(aggregateBy []string, options *AllocationAggregationOptions, as *AllocationSet) (map[string]float64, error) {
  1032. // Compute coeffs by totalling per-allocation, then dividing by the total.
  1033. coeffs := map[string]float64{}
  1034. // Compute totals for all allocations
  1035. total := 0.0
  1036. // ShareEven counts each aggregation with even weight, whereas ShareWeighted
  1037. // counts each aggregation proportionally to its respective costs
  1038. shareType := options.ShareSplit
  1039. // Record allocation values first, then normalize by totals to get percentages
  1040. for _, alloc := range as.allocations {
  1041. if alloc.IsIdle() {
  1042. // Skip idle allocations in coefficient calculation
  1043. continue
  1044. }
  1045. // Determine the post-aggregation key under which the allocation will
  1046. // be shared.
  1047. name := alloc.generateKey(aggregateBy)
  1048. // If the current allocation will be filtered out in step 3, contribute
  1049. // its share of the shared coefficient to a "__filtered__" bin, which
  1050. // will ultimately be dropped. This step ensures that the shared cost
  1051. // of a non-filtered allocation will be conserved even when the filter
  1052. // is removed. (Otherwise, all the shared cost will get redistributed
  1053. // over the unfiltered results, inflating their shared costs.)
  1054. filtered := false
  1055. for _, ff := range options.FilterFuncs {
  1056. if !ff(alloc) {
  1057. filtered = true
  1058. break
  1059. }
  1060. }
  1061. if filtered {
  1062. name = "__filtered__"
  1063. }
  1064. if shareType == ShareEven {
  1065. // Even distribution is not additive - set to 1.0 for everything
  1066. coeffs[name] = 1.0
  1067. // Total for even distribution is always the number of coefficients
  1068. total = float64(len(coeffs))
  1069. } else {
  1070. // Both are additive for weighted distribution, where each
  1071. // cumulative coefficient will be divided by the total.
  1072. coeffs[name] += alloc.TotalCost()
  1073. total += alloc.TotalCost()
  1074. }
  1075. }
  1076. // Normalize coefficients by totals
  1077. for a := range coeffs {
  1078. if coeffs[a] > 0 && total > 0 {
  1079. coeffs[a] /= total
  1080. } else {
  1081. log.Warningf("ETL: invalid values for shared coefficients: %d, %d", coeffs[a], total)
  1082. coeffs[a] = 0.0
  1083. }
  1084. }
  1085. return coeffs, nil
  1086. }
  1087. func computeIdleCoeffs(options *AllocationAggregationOptions, as *AllocationSet, shareSet *AllocationSet) (map[string]map[string]map[string]float64, error) {
  1088. types := []string{"cpu", "gpu", "ram"}
  1089. // Compute idle coefficients, then save them in AllocationAggregationOptions
  1090. coeffs := map[string]map[string]map[string]float64{}
  1091. // Compute totals per resource for CPU, GPU, RAM, and PV
  1092. totals := map[string]map[string]float64{}
  1093. // ShareEven counts each allocation with even weight, whereas ShareWeighted
  1094. // counts each allocation proportionally to its respective costs
  1095. shareType := options.ShareIdle
  1096. // Record allocation values first, then normalize by totals to get percentages
  1097. for _, alloc := range as.allocations {
  1098. if alloc.IsIdle() {
  1099. // Skip idle allocations in coefficient calculation
  1100. continue
  1101. }
  1102. idleId, err := alloc.getIdleId(options)
  1103. if err != nil {
  1104. log.DedupedWarningf(3, "Missing Idle Key for %s", alloc.Name)
  1105. }
  1106. // get the name key for the allocation
  1107. name := alloc.Name
  1108. // Create key based tables if they don't exist
  1109. if _, ok := coeffs[idleId]; !ok {
  1110. coeffs[idleId] = map[string]map[string]float64{}
  1111. }
  1112. if _, ok := totals[idleId]; !ok {
  1113. totals[idleId] = map[string]float64{}
  1114. }
  1115. if _, ok := coeffs[idleId][name]; !ok {
  1116. coeffs[idleId][name] = map[string]float64{}
  1117. }
  1118. if shareType == ShareEven {
  1119. for _, r := range types {
  1120. // Not additive - hard set to 1.0
  1121. coeffs[idleId][name][r] = 1.0
  1122. // totals are additive
  1123. totals[idleId][r] += 1.0
  1124. }
  1125. } else {
  1126. coeffs[idleId][name]["cpu"] += alloc.CPUTotalCost()
  1127. coeffs[idleId][name]["gpu"] += alloc.GPUTotalCost()
  1128. coeffs[idleId][name]["ram"] += alloc.RAMTotalCost()
  1129. totals[idleId]["cpu"] += alloc.CPUTotalCost()
  1130. totals[idleId]["gpu"] += alloc.GPUTotalCost()
  1131. totals[idleId]["ram"] += alloc.RAMTotalCost()
  1132. }
  1133. }
  1134. // Do the same for shared allocations
  1135. for _, alloc := range shareSet.allocations {
  1136. if alloc.IsIdle() {
  1137. // Skip idle allocations in coefficient calculation
  1138. continue
  1139. }
  1140. // idleId will be providerId or cluster
  1141. idleId, err := alloc.getIdleId(options)
  1142. if err != nil {
  1143. log.DedupedWarningf(3, "Missing Idle Key in share set for %s", alloc.Name)
  1144. }
  1145. // get the name key for the allocation
  1146. name := alloc.Name
  1147. // Create idleId based tables if they don't exist
  1148. if _, ok := coeffs[idleId]; !ok {
  1149. coeffs[idleId] = map[string]map[string]float64{}
  1150. }
  1151. if _, ok := totals[idleId]; !ok {
  1152. totals[idleId] = map[string]float64{}
  1153. }
  1154. if _, ok := coeffs[idleId][name]; !ok {
  1155. coeffs[idleId][name] = map[string]float64{}
  1156. }
  1157. if shareType == ShareEven {
  1158. for _, r := range types {
  1159. // Not additive - hard set to 1.0
  1160. coeffs[idleId][name][r] = 1.0
  1161. // totals are additive
  1162. totals[idleId][r] += 1.0
  1163. }
  1164. } else {
  1165. coeffs[idleId][name]["cpu"] += alloc.CPUTotalCost()
  1166. coeffs[idleId][name]["gpu"] += alloc.GPUTotalCost()
  1167. coeffs[idleId][name]["ram"] += alloc.RAMTotalCost()
  1168. totals[idleId]["cpu"] += alloc.CPUTotalCost()
  1169. totals[idleId]["gpu"] += alloc.GPUTotalCost()
  1170. totals[idleId]["ram"] += alloc.RAMTotalCost()
  1171. }
  1172. }
  1173. // Normalize coefficients by totals
  1174. for c := range coeffs {
  1175. for a := range coeffs[c] {
  1176. for _, r := range types {
  1177. if coeffs[c][a][r] > 0 && totals[c][r] > 0 {
  1178. coeffs[c][a][r] /= totals[c][r]
  1179. }
  1180. }
  1181. }
  1182. }
  1183. return coeffs, nil
  1184. }
  1185. // getIdleId returns the providerId or cluster of an Allocation depending on the IdleByNode
  1186. // option in the AllocationAggregationOptions and an error if the respective field is missing
  1187. func (a *Allocation) getIdleId(options *AllocationAggregationOptions) (string, error) {
  1188. var idleId string
  1189. if options.IdleByNode {
  1190. // Key allocations to ProviderId to match against node
  1191. idleId = a.Properties.ProviderID
  1192. if idleId == "" {
  1193. return idleId, fmt.Errorf("ProviderId is not set")
  1194. }
  1195. } else {
  1196. // key the allocations by cluster id
  1197. idleId = a.Properties.Cluster
  1198. if idleId == "" {
  1199. return idleId, fmt.Errorf("ClusterProp is not set")
  1200. }
  1201. }
  1202. return idleId, nil
  1203. }
  1204. func (a *Allocation) generateKey(aggregateBy []string) string {
  1205. if a == nil {
  1206. return ""
  1207. }
  1208. // Names will ultimately be joined into a single name, which uniquely
  1209. // identifies allocations.
  1210. names := []string{}
  1211. for _, agg := range aggregateBy {
  1212. switch true {
  1213. case agg == AllocationClusterProp:
  1214. names = append(names, a.Properties.Cluster)
  1215. case agg == AllocationNodeProp:
  1216. names = append(names, a.Properties.Node)
  1217. case agg == AllocationNamespaceProp:
  1218. names = append(names, a.Properties.Namespace)
  1219. case agg == AllocationControllerKindProp:
  1220. controllerKind := a.Properties.ControllerKind
  1221. if controllerKind == "" {
  1222. // Indicate that allocation has no controller
  1223. controllerKind = UnallocatedSuffix
  1224. }
  1225. names = append(names, controllerKind)
  1226. case agg == AllocationDaemonSetProp || agg == AllocationStatefulSetProp || agg == AllocationDeploymentProp || agg == AllocationJobProp:
  1227. controller := a.Properties.Controller
  1228. if agg != a.Properties.ControllerKind || controller == "" {
  1229. // The allocation does not have the specified controller kind
  1230. controller = UnallocatedSuffix
  1231. }
  1232. names = append(names, controller)
  1233. case agg == AllocationControllerProp:
  1234. controller := a.Properties.Controller
  1235. if controller == "" {
  1236. // Indicate that allocation has no controller
  1237. controller = UnallocatedSuffix
  1238. } else if a.Properties.ControllerKind != "" {
  1239. controller = fmt.Sprintf("%s:%s", a.Properties.ControllerKind, controller)
  1240. }
  1241. names = append(names, controller)
  1242. case agg == AllocationPodProp:
  1243. names = append(names, a.Properties.Pod)
  1244. case agg == AllocationContainerProp:
  1245. names = append(names, a.Properties.Container)
  1246. case agg == AllocationServiceProp:
  1247. services := a.Properties.Services
  1248. if services == nil || len(services) == 0 {
  1249. // Indicate that allocation has no services
  1250. names = append(names, UnallocatedSuffix)
  1251. } else {
  1252. // This just uses the first service
  1253. for _, service := range services {
  1254. names = append(names, service)
  1255. break
  1256. }
  1257. }
  1258. case strings.HasPrefix(agg, "label:"):
  1259. labels := a.Properties.Labels
  1260. if labels == nil {
  1261. // Indicate that allocation has no labels
  1262. names = append(names, UnallocatedSuffix)
  1263. } else {
  1264. labelNames := []string{}
  1265. aggLabels := strings.Split(strings.TrimPrefix(agg, "label:"), ";")
  1266. for _, labelName := range aggLabels {
  1267. if val, ok := labels[labelName]; ok {
  1268. labelNames = append(labelNames, fmt.Sprintf("%s=%s", labelName, val))
  1269. } else if indexOf(UnallocatedSuffix, labelNames) == -1 { // if UnallocatedSuffix not already in names
  1270. labelNames = append(labelNames, UnallocatedSuffix)
  1271. }
  1272. }
  1273. // resolve arbitrary ordering. e.g., app=app0/env=env0 is the same agg as env=env0/app=app0
  1274. if len(labelNames) > 1 {
  1275. sort.Strings(labelNames)
  1276. }
  1277. unallocatedSuffixIndex := indexOf(UnallocatedSuffix, labelNames)
  1278. // suffix should be at index 0 if it exists b/c of underscores
  1279. if unallocatedSuffixIndex != -1 {
  1280. labelNames = append(labelNames[:unallocatedSuffixIndex], labelNames[unallocatedSuffixIndex+1:]...)
  1281. labelNames = append(labelNames, UnallocatedSuffix) // append to end
  1282. }
  1283. names = append(names, labelNames...)
  1284. }
  1285. case strings.HasPrefix(agg, "annotation:"):
  1286. annotations := a.Properties.Annotations
  1287. if annotations == nil {
  1288. // Indicate that allocation has no annotations
  1289. names = append(names, UnallocatedSuffix)
  1290. } else {
  1291. annotationNames := []string{}
  1292. aggAnnotations := strings.Split(strings.TrimPrefix(agg, "annotation:"), ";")
  1293. for _, annotationName := range aggAnnotations {
  1294. if val, ok := annotations[annotationName]; ok {
  1295. annotationNames = append(annotationNames, fmt.Sprintf("%s=%s", annotationName, val))
  1296. } else if indexOf(UnallocatedSuffix, annotationNames) == -1 { // if UnallocatedSuffix not already in names
  1297. annotationNames = append(annotationNames, UnallocatedSuffix)
  1298. }
  1299. }
  1300. // resolve arbitrary ordering. e.g., app=app0/env=env0 is the same agg as env=env0/app=app0
  1301. if len(annotationNames) > 1 {
  1302. sort.Strings(annotationNames)
  1303. }
  1304. unallocatedSuffixIndex := indexOf(UnallocatedSuffix, annotationNames)
  1305. // suffix should be at index 0 if it exists b/c of underscores
  1306. if unallocatedSuffixIndex != -1 {
  1307. annotationNames = append(annotationNames[:unallocatedSuffixIndex], annotationNames[unallocatedSuffixIndex+1:]...)
  1308. annotationNames = append(annotationNames, UnallocatedSuffix) // append to end
  1309. }
  1310. names = append(names, annotationNames...)
  1311. }
  1312. }
  1313. }
  1314. return strings.Join(names, "/")
  1315. }
  1316. // TODO:CLEANUP get rid of this
  1317. // Helper function to check for slice membership. Not sure if repeated elsewhere in our codebase.
  1318. func indexOf(v string, arr []string) int {
  1319. for i, s := range arr {
  1320. // This is caseless equivalence
  1321. if strings.EqualFold(v, s) {
  1322. return i
  1323. }
  1324. }
  1325. return -1
  1326. }
  1327. // Clone returns a new AllocationSet with a deep copy of the given
  1328. // AllocationSet's allocations.
  1329. func (as *AllocationSet) Clone() *AllocationSet {
  1330. if as == nil {
  1331. return nil
  1332. }
  1333. as.RLock()
  1334. defer as.RUnlock()
  1335. allocs := map[string]*Allocation{}
  1336. for k, v := range as.allocations {
  1337. allocs[k] = v.Clone()
  1338. }
  1339. externalKeys := map[string]bool{}
  1340. for k, v := range as.externalKeys {
  1341. externalKeys[k] = v
  1342. }
  1343. idleKeys := map[string]bool{}
  1344. for k, v := range as.idleKeys {
  1345. idleKeys[k] = v
  1346. }
  1347. return &AllocationSet{
  1348. allocations: allocs,
  1349. externalKeys: externalKeys,
  1350. idleKeys: idleKeys,
  1351. Window: as.Window.Clone(),
  1352. }
  1353. }
  1354. // ComputeIdleAllocations computes the idle allocations for the AllocationSet,
  1355. // given a set of Assets. Ideally, assetSet should contain only Nodes, but if
  1356. // it contains other Assets, they will be ignored; only CPU, GPU and RAM are
  1357. // considered for idle allocation. If the Nodes have adjustments, then apply
  1358. // the adjustments proportionally to each of the resources so that total
  1359. // allocation with idle reflects the adjusted node costs. One idle allocation
  1360. // per-cluster will be computed and returned, keyed by cluster_id.
  1361. func (as *AllocationSet) ComputeIdleAllocations(assetSet *AssetSet) (map[string]*Allocation, error) {
  1362. if as == nil {
  1363. return nil, fmt.Errorf("cannot compute idle allocation for nil AllocationSet")
  1364. }
  1365. if assetSet == nil {
  1366. return nil, fmt.Errorf("cannot compute idle allocation with nil AssetSet")
  1367. }
  1368. if !as.Window.Equal(assetSet.Window) {
  1369. return nil, fmt.Errorf("cannot compute idle allocation for sets with mismatched windows: %s != %s", as.Window, assetSet.Window)
  1370. }
  1371. window := as.Window
  1372. // Build a map of cumulative cluster asset costs, per resource; i.e.
  1373. // cluster-to-{cpu|gpu|ram}-to-cost.
  1374. assetClusterResourceCosts := map[string]map[string]float64{}
  1375. assetSet.Each(func(key string, a Asset) {
  1376. if node, ok := a.(*Node); ok {
  1377. if _, ok := assetClusterResourceCosts[node.Properties().Cluster]; !ok {
  1378. assetClusterResourceCosts[node.Properties().Cluster] = map[string]float64{}
  1379. }
  1380. // adjustmentRate is used to scale resource costs proportionally
  1381. // by the adjustment. This is necessary because we only get one
  1382. // adjustment per Node, not one per-resource-per-Node.
  1383. //
  1384. // e.g. total cost = $90, adjustment = -$10 => 0.9
  1385. // e.g. total cost = $150, adjustment = -$300 => 0.3333
  1386. // e.g. total cost = $150, adjustment = $50 => 1.5
  1387. adjustmentRate := 1.0
  1388. if node.TotalCost()-node.Adjustment() == 0 {
  1389. // If (totalCost - adjustment) is 0.0 then adjustment cancels
  1390. // the entire node cost and we should make everything 0
  1391. // without dividing by 0.
  1392. adjustmentRate = 0.0
  1393. log.DedupedWarningf(5, "Compute Idle Allocations: Node Cost Adjusted to $0.00 for %s", node.properties.Name)
  1394. } else if node.Adjustment() != 0.0 {
  1395. // adjustmentRate is the ratio of cost-with-adjustment (i.e. TotalCost)
  1396. // to cost-without-adjustment (i.e. TotalCost - Adjustment).
  1397. adjustmentRate = node.TotalCost() / (node.TotalCost() - node.Adjustment())
  1398. }
  1399. cpuCost := node.CPUCost * (1.0 - node.Discount) * adjustmentRate
  1400. gpuCost := node.GPUCost * (1.0 - node.Discount) * adjustmentRate
  1401. ramCost := node.RAMCost * (1.0 - node.Discount) * adjustmentRate
  1402. assetClusterResourceCosts[node.Properties().Cluster]["cpu"] += cpuCost
  1403. assetClusterResourceCosts[node.Properties().Cluster]["gpu"] += gpuCost
  1404. assetClusterResourceCosts[node.Properties().Cluster]["ram"] += ramCost
  1405. }
  1406. })
  1407. // Determine start, end on a per-cluster basis
  1408. clusterStarts := map[string]time.Time{}
  1409. clusterEnds := map[string]time.Time{}
  1410. // Subtract allocated costs from asset costs, leaving only the remaining
  1411. // idle costs.
  1412. as.Each(func(name string, a *Allocation) {
  1413. cluster := a.Properties.Cluster
  1414. if cluster == "" {
  1415. // Failed to find allocation's cluster
  1416. return
  1417. }
  1418. if _, ok := assetClusterResourceCosts[cluster]; !ok {
  1419. // Failed to find assets for allocation's cluster
  1420. return
  1421. }
  1422. // Set cluster (start, end) if they are either not currently set,
  1423. // or if the detected (start, end) of the current allocation falls
  1424. // before or after, respectively, the current values.
  1425. if s, ok := clusterStarts[cluster]; !ok || a.Start.Before(s) {
  1426. clusterStarts[cluster] = a.Start
  1427. }
  1428. if e, ok := clusterEnds[cluster]; !ok || a.End.After(e) {
  1429. clusterEnds[cluster] = a.End
  1430. }
  1431. assetClusterResourceCosts[cluster]["cpu"] -= a.CPUTotalCost()
  1432. assetClusterResourceCosts[cluster]["gpu"] -= a.GPUTotalCost()
  1433. assetClusterResourceCosts[cluster]["ram"] -= a.RAMTotalCost()
  1434. })
  1435. // Turn remaining un-allocated asset costs into idle allocations
  1436. idleAllocs := map[string]*Allocation{}
  1437. for cluster, resources := range assetClusterResourceCosts {
  1438. // Default start and end to the (start, end) of the given window, but
  1439. // use the actual, detected (start, end) pair if they are available.
  1440. start := *window.Start()
  1441. if s, ok := clusterStarts[cluster]; ok && window.Contains(s) {
  1442. start = s
  1443. }
  1444. end := *window.End()
  1445. if e, ok := clusterEnds[cluster]; ok && window.Contains(e) {
  1446. end = e
  1447. }
  1448. idleAlloc := &Allocation{
  1449. Name: fmt.Sprintf("%s/%s", cluster, IdleSuffix),
  1450. Window: window.Clone(),
  1451. Properties: &AllocationProperties{Cluster: cluster},
  1452. Start: start,
  1453. End: end,
  1454. CPUCost: resources["cpu"],
  1455. GPUCost: resources["gpu"],
  1456. RAMCost: resources["ram"],
  1457. }
  1458. // Do not continue if multiple idle allocations are computed for a
  1459. // single cluster.
  1460. if _, ok := idleAllocs[cluster]; ok {
  1461. return nil, fmt.Errorf("duplicate idle allocations for cluster %s", cluster)
  1462. }
  1463. idleAllocs[cluster] = idleAlloc
  1464. }
  1465. return idleAllocs, nil
  1466. }
  1467. // ComputeIdleAllocationsByNode computes the idle allocations for the AllocationSet,
  1468. // given a set of Assets. Ideally, assetSet should contain only Nodes, but if
  1469. // it contains other Assets, they will be ignored; only CPU, GPU and RAM are
  1470. // considered for idle allocation. If the Nodes have adjustments, then apply
  1471. // the adjustments proportionally to each of the resources so that total
  1472. // allocation with idle reflects the adjusted node costs. One idle allocation
  1473. // per-node will be computed and returned, keyed by cluster_id.
  1474. func (as *AllocationSet) ComputeIdleAllocationsByNode(assetSet *AssetSet) (map[string]*Allocation, error) {
  1475. if as == nil {
  1476. return nil, fmt.Errorf("cannot compute idle allocation for nil AllocationSet")
  1477. }
  1478. if assetSet == nil {
  1479. return nil, fmt.Errorf("cannot compute idle allocation with nil AssetSet")
  1480. }
  1481. if !as.Window.Equal(assetSet.Window) {
  1482. return nil, fmt.Errorf("cannot compute idle allocation for sets with mismatched windows: %s != %s", as.Window, assetSet.Window)
  1483. }
  1484. window := as.Window
  1485. // Build a map of cumulative cluster asset costs, per resource; i.e.
  1486. // cluster-to-{cpu|gpu|ram}-to-cost.
  1487. assetNodeResourceCosts := map[string]map[string]float64{}
  1488. nodesByProviderId := map[string]*Node{}
  1489. assetSet.Each(func(key string, a Asset) {
  1490. if node, ok := a.(*Node); ok {
  1491. if _, ok := assetNodeResourceCosts[node.Properties().ProviderID]; ok || node.Properties().ProviderID == "" {
  1492. log.DedupedWarningf(5, "Compute Idle Allocations By Node: Node missing providerId: %s", node.properties.Name)
  1493. return
  1494. }
  1495. nodesByProviderId[node.Properties().ProviderID] = node
  1496. assetNodeResourceCosts[node.Properties().ProviderID] = map[string]float64{}
  1497. // adjustmentRate is used to scale resource costs proportionally
  1498. // by the adjustment. This is necessary because we only get one
  1499. // adjustment per Node, not one per-resource-per-Node.
  1500. //
  1501. // e.g. total cost = $90, adjustment = -$10 => 0.9
  1502. // e.g. total cost = $150, adjustment = -$300 => 0.3333
  1503. // e.g. total cost = $150, adjustment = $50 => 1.5
  1504. adjustmentRate := 1.0
  1505. if node.TotalCost()-node.Adjustment() == 0 {
  1506. // If (totalCost - adjustment) is 0.0 then adjustment cancels
  1507. // the entire node cost and we should make everything 0
  1508. // without dividing by 0.
  1509. adjustmentRate = 0.0
  1510. log.DedupedWarningf(5, "Compute Idle Allocations: Node Cost Adjusted to $0.00 for %s", node.properties.Name)
  1511. } else if node.Adjustment() != 0.0 {
  1512. // adjustmentRate is the ratio of cost-with-adjustment (i.e. TotalCost)
  1513. // to cost-without-adjustment (i.e. TotalCost - Adjustment).
  1514. adjustmentRate = node.TotalCost() / (node.TotalCost() - node.Adjustment())
  1515. }
  1516. cpuCost := node.CPUCost * (1.0 - node.Discount) * adjustmentRate
  1517. gpuCost := node.GPUCost * (1.0 - node.Discount) * adjustmentRate
  1518. ramCost := node.RAMCost * (1.0 - node.Discount) * adjustmentRate
  1519. assetNodeResourceCosts[node.Properties().ProviderID]["cpu"] += cpuCost
  1520. assetNodeResourceCosts[node.Properties().ProviderID]["gpu"] += gpuCost
  1521. assetNodeResourceCosts[node.Properties().ProviderID]["ram"] += ramCost
  1522. }
  1523. })
  1524. // Determine start, end on a per-cluster basis
  1525. nodeStarts := map[string]time.Time{}
  1526. nodeEnds := map[string]time.Time{}
  1527. // Subtract allocated costs from asset costs, leaving only the remaining
  1528. // idle costs.
  1529. as.Each(func(name string, a *Allocation) {
  1530. providerId := a.Properties.ProviderID
  1531. if providerId == "" {
  1532. // Failed to find allocation's node
  1533. log.DedupedWarningf(5, "Compute Idle Allocations By Node: Allocation missing providerId: %s", a.Name)
  1534. return
  1535. }
  1536. if _, ok := assetNodeResourceCosts[providerId]; !ok {
  1537. // Failed to find assets for allocation's node
  1538. return
  1539. }
  1540. // Set cluster (start, end) if they are either not currently set,
  1541. // or if the detected (start, end) of the current allocation falls
  1542. // before or after, respectively, the current values.
  1543. if s, ok := nodeStarts[providerId]; !ok || a.Start.Before(s) {
  1544. nodeStarts[providerId] = a.Start
  1545. }
  1546. if e, ok := nodeEnds[providerId]; !ok || a.End.After(e) {
  1547. nodeEnds[providerId] = a.End
  1548. }
  1549. assetNodeResourceCosts[providerId]["cpu"] -= a.CPUTotalCost()
  1550. assetNodeResourceCosts[providerId]["gpu"] -= a.GPUTotalCost()
  1551. assetNodeResourceCosts[providerId]["ram"] -= a.RAMTotalCost()
  1552. })
  1553. // Turn remaining un-allocated asset costs into idle allocations
  1554. idleAllocs := map[string]*Allocation{}
  1555. for providerId, resources := range assetNodeResourceCosts {
  1556. // Default start and end to the (start, end) of the given window, but
  1557. // use the actual, detected (start, end) pair if they are available.
  1558. start := *window.Start()
  1559. if s, ok := nodeStarts[providerId]; ok && window.Contains(s) {
  1560. start = s
  1561. }
  1562. end := *window.End()
  1563. if e, ok := nodeEnds[providerId]; ok && window.Contains(e) {
  1564. end = e
  1565. }
  1566. node := nodesByProviderId[providerId]
  1567. idleAlloc := &Allocation{
  1568. Name: fmt.Sprintf("%s/%s", node.properties.Name, IdleSuffix),
  1569. Window: window.Clone(),
  1570. Properties: &AllocationProperties{
  1571. Cluster: node.properties.Cluster,
  1572. Node: node.properties.Name,
  1573. ProviderID: providerId,
  1574. },
  1575. Start: start,
  1576. End: end,
  1577. CPUCost: resources["cpu"],
  1578. GPUCost: resources["gpu"],
  1579. RAMCost: resources["ram"],
  1580. }
  1581. // Do not continue if multiple idle allocations are computed for a
  1582. // single node.
  1583. if _, ok := idleAllocs[providerId]; ok {
  1584. return nil, fmt.Errorf("duplicate idle allocations for node Provider ID: %s", providerId)
  1585. }
  1586. idleAllocs[providerId] = idleAlloc
  1587. }
  1588. return idleAllocs, nil
  1589. }
  1590. // Delete removes the allocation with the given name from the set
  1591. func (as *AllocationSet) Delete(name string) {
  1592. if as == nil {
  1593. return
  1594. }
  1595. as.Lock()
  1596. defer as.Unlock()
  1597. delete(as.externalKeys, name)
  1598. delete(as.idleKeys, name)
  1599. delete(as.allocations, name)
  1600. }
  1601. // Each invokes the given function for each Allocation in the set
  1602. func (as *AllocationSet) Each(f func(string, *Allocation)) {
  1603. if as == nil {
  1604. return
  1605. }
  1606. for k, a := range as.allocations {
  1607. f(k, a)
  1608. }
  1609. }
  1610. // End returns the End time of the AllocationSet window
  1611. func (as *AllocationSet) End() time.Time {
  1612. if as == nil {
  1613. log.Warningf("AllocationSet: calling End on nil AllocationSet")
  1614. return time.Unix(0, 0)
  1615. }
  1616. if as.Window.End() == nil {
  1617. log.Warningf("AllocationSet: AllocationSet with illegal window: End is nil; len(as.allocations)=%d", len(as.allocations))
  1618. return time.Unix(0, 0)
  1619. }
  1620. return *as.Window.End()
  1621. }
  1622. // Get returns the Allocation at the given key in the AllocationSet
  1623. func (as *AllocationSet) Get(key string) *Allocation {
  1624. as.RLock()
  1625. defer as.RUnlock()
  1626. if alloc, ok := as.allocations[key]; ok {
  1627. return alloc
  1628. }
  1629. return nil
  1630. }
  1631. // ExternalAllocations returns a map of the external allocations in the set.
  1632. // Returns clones of the actual Allocations, so mutability is not a problem.
  1633. func (as *AllocationSet) ExternalAllocations() map[string]*Allocation {
  1634. externals := map[string]*Allocation{}
  1635. if as.IsEmpty() {
  1636. return externals
  1637. }
  1638. as.RLock()
  1639. defer as.RUnlock()
  1640. for key := range as.externalKeys {
  1641. if alloc, ok := as.allocations[key]; ok {
  1642. externals[key] = alloc.Clone()
  1643. }
  1644. }
  1645. return externals
  1646. }
  1647. // ExternalCost returns the total aggregated external costs of the set
  1648. func (as *AllocationSet) ExternalCost() float64 {
  1649. if as.IsEmpty() {
  1650. return 0.0
  1651. }
  1652. as.RLock()
  1653. defer as.RUnlock()
  1654. externalCost := 0.0
  1655. for _, alloc := range as.allocations {
  1656. externalCost += alloc.ExternalCost
  1657. }
  1658. return externalCost
  1659. }
  1660. // IdleAllocations returns a map of the idle allocations in the AllocationSet.
  1661. // Returns clones of the actual Allocations, so mutability is not a problem.
  1662. func (as *AllocationSet) IdleAllocations() map[string]*Allocation {
  1663. idles := map[string]*Allocation{}
  1664. if as.IsEmpty() {
  1665. return idles
  1666. }
  1667. as.RLock()
  1668. defer as.RUnlock()
  1669. for key := range as.idleKeys {
  1670. if alloc, ok := as.allocations[key]; ok {
  1671. idles[key] = alloc.Clone()
  1672. }
  1673. }
  1674. return idles
  1675. }
  1676. // Insert aggregates the current entry in the AllocationSet by the given Allocation,
  1677. // but only if the Allocation is valid, i.e. matches the AllocationSet's window. If
  1678. // there is no existing entry, one is created. Nil error response indicates success.
  1679. func (as *AllocationSet) Insert(that *Allocation) error {
  1680. return as.insert(that)
  1681. }
  1682. func (as *AllocationSet) insert(that *Allocation) error {
  1683. if as == nil {
  1684. return fmt.Errorf("cannot insert into nil AllocationSet")
  1685. }
  1686. as.Lock()
  1687. defer as.Unlock()
  1688. if as.allocations == nil {
  1689. as.allocations = map[string]*Allocation{}
  1690. }
  1691. if as.externalKeys == nil {
  1692. as.externalKeys = map[string]bool{}
  1693. }
  1694. if as.idleKeys == nil {
  1695. as.idleKeys = map[string]bool{}
  1696. }
  1697. // Add the given Allocation to the existing entry, if there is one;
  1698. // otherwise just set directly into allocations
  1699. if _, ok := as.allocations[that.Name]; !ok {
  1700. as.allocations[that.Name] = that
  1701. } else {
  1702. as.allocations[that.Name].add(that)
  1703. }
  1704. // If the given Allocation is an external one, record that
  1705. if that.IsExternal() {
  1706. as.externalKeys[that.Name] = true
  1707. }
  1708. // If the given Allocation is an idle one, record that
  1709. if that.IsIdle() {
  1710. as.idleKeys[that.Name] = true
  1711. }
  1712. return nil
  1713. }
  1714. // IsEmpty returns true if the AllocationSet is nil, or if it contains
  1715. // zero allocations.
  1716. func (as *AllocationSet) IsEmpty() bool {
  1717. if as == nil || len(as.allocations) == 0 {
  1718. return true
  1719. }
  1720. as.RLock()
  1721. defer as.RUnlock()
  1722. return as.allocations == nil || len(as.allocations) == 0
  1723. }
  1724. // Length returns the number of Allocations in the set
  1725. func (as *AllocationSet) Length() int {
  1726. if as == nil {
  1727. return 0
  1728. }
  1729. as.RLock()
  1730. defer as.RUnlock()
  1731. return len(as.allocations)
  1732. }
  1733. // Map clones and returns a map of the AllocationSet's Allocations
  1734. func (as *AllocationSet) Map() map[string]*Allocation {
  1735. if as.IsEmpty() {
  1736. return map[string]*Allocation{}
  1737. }
  1738. return as.Clone().allocations
  1739. }
  1740. // MarshalJSON JSON-encodes the AllocationSet
  1741. func (as *AllocationSet) MarshalJSON() ([]byte, error) {
  1742. as.RLock()
  1743. defer as.RUnlock()
  1744. return json.Marshal(as.allocations)
  1745. }
  1746. // Resolution returns the AllocationSet's window duration
  1747. func (as *AllocationSet) Resolution() time.Duration {
  1748. return as.Window.Duration()
  1749. }
  1750. // Set uses the given Allocation to overwrite the existing entry in the
  1751. // AllocationSet under the Allocation's name.
  1752. func (as *AllocationSet) Set(alloc *Allocation) error {
  1753. if as.IsEmpty() {
  1754. as.Lock()
  1755. as.allocations = map[string]*Allocation{}
  1756. as.externalKeys = map[string]bool{}
  1757. as.idleKeys = map[string]bool{}
  1758. as.Unlock()
  1759. }
  1760. as.Lock()
  1761. defer as.Unlock()
  1762. as.allocations[alloc.Name] = alloc
  1763. // If the given Allocation is an external one, record that
  1764. if alloc.IsExternal() {
  1765. as.externalKeys[alloc.Name] = true
  1766. }
  1767. // If the given Allocation is an idle one, record that
  1768. if alloc.IsIdle() {
  1769. as.idleKeys[alloc.Name] = true
  1770. }
  1771. return nil
  1772. }
  1773. // Start returns the Start time of the AllocationSet window
  1774. func (as *AllocationSet) Start() time.Time {
  1775. if as == nil {
  1776. log.Warningf("AllocationSet: calling Start on nil AllocationSet")
  1777. return time.Unix(0, 0)
  1778. }
  1779. if as.Window.Start() == nil {
  1780. log.Warningf("AllocationSet: AllocationSet with illegal window: Start is nil; len(as.allocations)=%d", len(as.allocations))
  1781. return time.Unix(0, 0)
  1782. }
  1783. return *as.Window.Start()
  1784. }
  1785. // String represents the given Allocation as a string
  1786. func (as *AllocationSet) String() string {
  1787. if as == nil {
  1788. return "<nil>"
  1789. }
  1790. return fmt.Sprintf("AllocationSet{length: %d; window: %s; totalCost: %.2f}",
  1791. as.Length(), as.Window, as.TotalCost())
  1792. }
  1793. // TotalCost returns the sum of all TotalCosts of the allocations contained
  1794. func (as *AllocationSet) TotalCost() float64 {
  1795. if as.IsEmpty() {
  1796. return 0.0
  1797. }
  1798. as.RLock()
  1799. defer as.RUnlock()
  1800. tc := 0.0
  1801. for _, a := range as.allocations {
  1802. tc += a.TotalCost()
  1803. }
  1804. return tc
  1805. }
  1806. // UTCOffset returns the AllocationSet's configured UTCOffset.
  1807. func (as *AllocationSet) UTCOffset() time.Duration {
  1808. _, zone := as.Start().Zone()
  1809. return time.Duration(zone) * time.Second
  1810. }
  1811. func (as *AllocationSet) accumulate(that *AllocationSet) (*AllocationSet, error) {
  1812. if as.IsEmpty() {
  1813. return that.Clone(), nil
  1814. }
  1815. if that.IsEmpty() {
  1816. return as.Clone(), nil
  1817. }
  1818. // Set start, end to min(start), max(end)
  1819. start := as.Start()
  1820. end := as.End()
  1821. if that.Start().Before(start) {
  1822. start = that.Start()
  1823. }
  1824. if that.End().After(end) {
  1825. end = that.End()
  1826. }
  1827. acc := NewAllocationSet(start, end)
  1828. as.RLock()
  1829. defer as.RUnlock()
  1830. that.RLock()
  1831. defer that.RUnlock()
  1832. for _, alloc := range as.allocations {
  1833. err := acc.insert(alloc)
  1834. if err != nil {
  1835. return nil, err
  1836. }
  1837. }
  1838. for _, alloc := range that.allocations {
  1839. err := acc.insert(alloc)
  1840. if err != nil {
  1841. return nil, err
  1842. }
  1843. }
  1844. return acc, nil
  1845. }
  1846. // AllocationSetRange is a thread-safe slice of AllocationSets. It is meant to
  1847. // be used such that the AllocationSets held are consecutive and coherent with
  1848. // respect to using the same aggregation properties, UTC offset, and
  1849. // resolution. However these rules are not necessarily enforced, so use wisely.
  1850. type AllocationSetRange struct {
  1851. sync.RWMutex
  1852. allocations []*AllocationSet
  1853. FromStore string // stores the name of the store used to retrieve the data
  1854. }
  1855. // NewAllocationSetRange instantiates a new range composed of the given
  1856. // AllocationSets in the order provided.
  1857. func NewAllocationSetRange(allocs ...*AllocationSet) *AllocationSetRange {
  1858. return &AllocationSetRange{
  1859. allocations: allocs,
  1860. }
  1861. }
  1862. // Accumulate sums each AllocationSet in the given range, returning a single cumulative
  1863. // AllocationSet for the entire range.
  1864. func (asr *AllocationSetRange) Accumulate() (*AllocationSet, error) {
  1865. var allocSet *AllocationSet
  1866. var err error
  1867. asr.RLock()
  1868. defer asr.RUnlock()
  1869. for _, as := range asr.allocations {
  1870. allocSet, err = allocSet.accumulate(as)
  1871. if err != nil {
  1872. return nil, err
  1873. }
  1874. }
  1875. return allocSet, nil
  1876. }
  1877. // TODO accumulate into lower-resolution chunks of the given resolution
  1878. // func (asr *AllocationSetRange) AccumulateBy(resolution time.Duration) *AllocationSetRange
  1879. // AggregateBy aggregates each AllocationSet in the range by the given
  1880. // properties and options.
  1881. func (asr *AllocationSetRange) AggregateBy(aggregateBy []string, options *AllocationAggregationOptions) error {
  1882. aggRange := &AllocationSetRange{allocations: []*AllocationSet{}}
  1883. asr.Lock()
  1884. defer asr.Unlock()
  1885. for _, as := range asr.allocations {
  1886. err := as.AggregateBy(aggregateBy, options)
  1887. if err != nil {
  1888. return err
  1889. }
  1890. aggRange.allocations = append(aggRange.allocations, as)
  1891. }
  1892. asr.allocations = aggRange.allocations
  1893. return nil
  1894. }
  1895. // Append appends the given AllocationSet to the end of the range. It does not
  1896. // validate whether or not that violates window continuity.
  1897. func (asr *AllocationSetRange) Append(that *AllocationSet) {
  1898. asr.Lock()
  1899. defer asr.Unlock()
  1900. asr.allocations = append(asr.allocations, that)
  1901. }
  1902. // Each invokes the given function for each AllocationSet in the range
  1903. func (asr *AllocationSetRange) Each(f func(int, *AllocationSet)) {
  1904. if asr == nil {
  1905. return
  1906. }
  1907. for i, as := range asr.allocations {
  1908. f(i, as)
  1909. }
  1910. }
  1911. // Get retrieves the AllocationSet at the given index of the range.
  1912. func (asr *AllocationSetRange) Get(i int) (*AllocationSet, error) {
  1913. if i < 0 || i >= len(asr.allocations) {
  1914. return nil, fmt.Errorf("AllocationSetRange: index out of range: %d", i)
  1915. }
  1916. asr.RLock()
  1917. defer asr.RUnlock()
  1918. return asr.allocations[i], nil
  1919. }
  1920. // InsertRange merges the given AllocationSetRange into the receiving one by
  1921. // lining up sets with matching windows, then inserting each allocation from
  1922. // the given ASR into the respective set in the receiving ASR. If the given
  1923. // ASR contains an AllocationSet from a window that does not exist in the
  1924. // receiving ASR, then an error is returned. However, the given ASR does not
  1925. // need to cover the full range of the receiver.
  1926. func (asr *AllocationSetRange) InsertRange(that *AllocationSetRange) error {
  1927. if asr == nil {
  1928. return fmt.Errorf("cannot insert range into nil AllocationSetRange")
  1929. }
  1930. // keys maps window to index in asr
  1931. keys := map[string]int{}
  1932. asr.Each(func(i int, as *AllocationSet) {
  1933. if as == nil {
  1934. return
  1935. }
  1936. keys[as.Window.String()] = i
  1937. })
  1938. // Nothing to merge, so simply return
  1939. if len(keys) == 0 {
  1940. return nil
  1941. }
  1942. var err error
  1943. that.Each(func(j int, thatAS *AllocationSet) {
  1944. if thatAS == nil || err != nil {
  1945. return
  1946. }
  1947. // Find matching AllocationSet in asr
  1948. i, ok := keys[thatAS.Window.String()]
  1949. if !ok {
  1950. err = fmt.Errorf("cannot merge AllocationSet into window that does not exist: %s", thatAS.Window.String())
  1951. return
  1952. }
  1953. as, err := asr.Get(i)
  1954. if err != nil {
  1955. err = fmt.Errorf("AllocationSetRange index does not exist: %d", i)
  1956. return
  1957. }
  1958. // Insert each Allocation from the given set
  1959. thatAS.Each(func(k string, alloc *Allocation) {
  1960. err = as.Insert(alloc)
  1961. if err != nil {
  1962. err = fmt.Errorf("error inserting allocation: %s", err)
  1963. return
  1964. }
  1965. })
  1966. })
  1967. // err might be nil
  1968. return err
  1969. }
  1970. // Length returns the length of the range, which is zero if nil
  1971. func (asr *AllocationSetRange) Length() int {
  1972. if asr == nil || asr.allocations == nil {
  1973. return 0
  1974. }
  1975. asr.RLock()
  1976. defer asr.RUnlock()
  1977. return len(asr.allocations)
  1978. }
  1979. // MarshalJSON JSON-encodes the range
  1980. func (asr *AllocationSetRange) MarshalJSON() ([]byte, error) {
  1981. asr.RLock()
  1982. asr.RUnlock()
  1983. return json.Marshal(asr.allocations)
  1984. }
  1985. // Slice copies the underlying slice of AllocationSets, maintaining order,
  1986. // and returns the copied slice.
  1987. func (asr *AllocationSetRange) Slice() []*AllocationSet {
  1988. if asr == nil || asr.allocations == nil {
  1989. return nil
  1990. }
  1991. asr.RLock()
  1992. defer asr.RUnlock()
  1993. copy := []*AllocationSet{}
  1994. for _, as := range asr.allocations {
  1995. copy = append(copy, as.Clone())
  1996. }
  1997. return copy
  1998. }
  1999. // String represents the given AllocationSetRange as a string
  2000. func (asr *AllocationSetRange) String() string {
  2001. if asr == nil {
  2002. return "<nil>"
  2003. }
  2004. return fmt.Sprintf("AllocationSetRange{length: %d}", asr.Length())
  2005. }
  2006. // UTCOffset returns the detected UTCOffset of the AllocationSets within the
  2007. // range. Defaults to 0 if the range is nil or empty. Does not warn if there
  2008. // are sets with conflicting UTCOffsets (just returns the first).
  2009. func (asr *AllocationSetRange) UTCOffset() time.Duration {
  2010. if asr.Length() == 0 {
  2011. return 0
  2012. }
  2013. as, err := asr.Get(0)
  2014. if err != nil {
  2015. return 0
  2016. }
  2017. return as.UTCOffset()
  2018. }
  2019. // Window returns the full window that the AllocationSetRange spans, from the
  2020. // start of the first AllocationSet to the end of the last one.
  2021. func (asr *AllocationSetRange) Window() Window {
  2022. if asr == nil || asr.Length() == 0 {
  2023. return NewWindow(nil, nil)
  2024. }
  2025. start := asr.allocations[0].Start()
  2026. end := asr.allocations[asr.Length()-1].End()
  2027. return NewWindow(&start, &end)
  2028. }