util.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. import { forEach, get, round } from 'lodash'
  2. // rangeToCumulative takes an AllocationSetRange (type: array[AllocationSet])
  3. // and accumulates the values into a single AllocationSet (type: object)
  4. export function rangeToCumulative(allocationSetRange, aggregateBy) {
  5. if (allocationSetRange.length === 0) {
  6. return null
  7. }
  8. const result = {}
  9. forEach(allocationSetRange, (allocSet) => {
  10. forEach(allocSet, (alloc) => {
  11. if (result[alloc.name] === undefined) {
  12. const hrs = get(alloc, 'minutes', 0) / 60.0
  13. result[alloc.name] = {
  14. name: alloc.name,
  15. [aggregateBy]: alloc.name,
  16. cpuCost: get(alloc, 'cpuCost', 0),
  17. gpuCost: get(alloc, 'gpuCost', 0),
  18. ramCost: get(alloc, 'ramCost', 0),
  19. pvCost: get(alloc, 'pvCost', 0),
  20. networkCost: get(alloc, 'networkCost', 0),
  21. sharedCost: get(alloc, 'sharedCost', 0),
  22. externalCost: get(alloc, 'externalCost', 0),
  23. totalCost: get(alloc, 'totalCost', 0),
  24. cpuUseCoreHrs: get(alloc, 'cpuCoreUsageAverage', 0) * hrs,
  25. cpuReqCoreHrs: get(alloc, 'cpuCoreRequestAverage', 0) * hrs,
  26. ramUseByteHrs: get(alloc, 'ramByteUsageAverage', 0) * hrs,
  27. ramReqByteHrs: get(alloc, 'ramByteRequestAverage', 0) * hrs,
  28. cpuEfficiency: get(alloc, 'cpuEfficiency', 0),
  29. ramEfficiency: get(alloc, 'ramEfficiency', 0),
  30. totalEfficiency: get(alloc, 'totalEfficiency', 0),
  31. }
  32. } else {
  33. const hrs = get(alloc, 'minutes', 0) / 60.0
  34. result[alloc.name].cpuCost += get(alloc, 'cpuCost', 0)
  35. result[alloc.name].gpuCost += get(alloc, 'gpuCost', 0)
  36. result[alloc.name].ramCost += get(alloc, 'ramCost', 0)
  37. result[alloc.name].pvCost += get(alloc, 'pvCost', 0)
  38. result[alloc.name].networkCost += get(alloc, 'networkCost', 0)
  39. result[alloc.name].sharedCost += get(alloc, 'sharedCost', 0)
  40. result[alloc.name].externalCost += get(alloc, 'externalCost', 0)
  41. result[alloc.name].totalCost += get(alloc, 'totalCost', 0)
  42. result[alloc.name].cpuUseCoreHrs += get(alloc, 'cpuCoreUsageAverage', 0) * hrs
  43. result[alloc.name].cpuReqCoreHrs += get(alloc, 'cpuCoreRequestAverage', 0) * hrs
  44. result[alloc.name].ramUseByteHrs += get(alloc, 'ramByteUsageAverage', 0) * hrs
  45. result[alloc.name].ramReqByteHrs += get(alloc, 'ramByteRequestAverage', 0) * hrs
  46. }
  47. })
  48. })
  49. // If the range is of length > 1 (i.e. it is not just a single set) then
  50. // compute efficiency for each result after accumulating.
  51. if (allocationSetRange.length > 1) {
  52. forEach(result, (alloc, name) => {
  53. // If we can't compute total efficiency, it defaults to 0.0
  54. let totalEfficiency = 0.0
  55. // CPU efficiency is defined as (usage/request). If request == 0.0 but
  56. // usage > 0, then efficiency gets set to 1.0.
  57. let cpuEfficiency = 0.0
  58. if (alloc.cpuReqCoreHrs > 0) {
  59. cpuEfficiency = alloc.cpuUseCoreHrs / alloc.cpuReqCoreHrs
  60. } else if (alloc.cpuUseCoreHrs > 0) {
  61. cpuEfficiency = 1.0
  62. }
  63. // RAM efficiency is defined as (usage/request). If request == 0.0 but
  64. // usage > 0, then efficiency gets set to 1.0.
  65. let ramEfficiency = 0.0
  66. if (alloc.ramReqByteHrs > 0) {
  67. ramEfficiency = alloc.ramUseByteHrs / alloc.ramReqByteHrs
  68. } else if (alloc.ramUseByteHrs > 0) {
  69. ramEfficiency = 1.0
  70. }
  71. // Compute efficiency as the cost-weighted average of CPU and RAM
  72. // efficiency
  73. if ((alloc.cpuCost + alloc.ramCost) > 0.0) {
  74. totalEfficiency = ((alloc.cpuCost*cpuEfficiency)+(alloc.ramCost*ramEfficiency)) / (alloc.cpuCost + alloc.ramCost)
  75. }
  76. result[name].cpuEfficiency = cpuEfficiency
  77. result[name].ramEfficiency = ramEfficiency
  78. result[name].totalEfficiency = totalEfficiency
  79. })
  80. }
  81. return result
  82. }
  83. // cumulativeToTotals adds each entry in the given AllocationSet (type: object)
  84. // and returns a single Allocation (type: object) representing the totals
  85. export function cumulativeToTotals(allocationSet) {
  86. let totals = {
  87. name: 'Totals',
  88. cpuCost: 0,
  89. gpuCost: 0,
  90. ramCost: 0,
  91. pvCost: 0,
  92. networkCost: 0,
  93. sharedCost: 0,
  94. externalCost: 0,
  95. totalCost: 0,
  96. cpuEfficiency: 0,
  97. ramEfficiency: 0,
  98. totalEfficiency: 0,
  99. }
  100. // Use these for computing efficiency. As such, idle will not factor into
  101. // these numbers, including CPU and RAM cost.
  102. let cpuReqCoreHrs = 0
  103. let cpuUseCoreHrs = 0
  104. let ramReqByteHrs = 0
  105. let ramUseByteHrs = 0
  106. let cpuCost = 0
  107. let ramCost = 0
  108. forEach(allocationSet, (alloc, name) => {
  109. // Accumulate efficiency-related fields
  110. if (name !== "__idle__") {
  111. cpuReqCoreHrs += get(alloc, 'cpuReqCoreHrs', 0.0)
  112. cpuUseCoreHrs += get(alloc, 'cpuUseCoreHrs', 0.0)
  113. ramReqByteHrs += get(alloc, 'ramReqByteHrs', 0.0)
  114. ramUseByteHrs += get(alloc, 'ramUseByteHrs', 0.0)
  115. cpuCost += get(alloc, 'cpuCost', 0.0)
  116. ramCost += get(alloc, 'ramCost', 0.0)
  117. }
  118. // Sum cumulative fields
  119. totals.cpuCost += get(alloc, 'cpuCost', 0)
  120. totals.gpuCost += get(alloc, 'gpuCost', 0)
  121. totals.ramCost += get(alloc, 'ramCost', 0)
  122. totals.pvCost += get(alloc, 'pvCost', 0)
  123. totals.networkCost += get(alloc, 'networkCost', 0)
  124. totals.sharedCost += get(alloc, 'sharedCost', 0)
  125. totals.externalCost += get(alloc, 'externalCost', 0)
  126. totals.totalCost += get(alloc, 'totalCost', 0)
  127. })
  128. // Compute efficiency
  129. if (cpuReqCoreHrs > 0) {
  130. totals.cpuEfficiency = cpuUseCoreHrs / cpuReqCoreHrs
  131. } else if (cpuUseCoreHrs > 0) {
  132. totals.cpuEfficiency = 1.0
  133. }
  134. if (ramReqByteHrs > 0) {
  135. totals.ramEfficiency = ramUseByteHrs / ramReqByteHrs
  136. } else if (ramUseByteHrs > 0) {
  137. totals.ramEfficiency = 1.0
  138. }
  139. if ((cpuCost + ramCost) > 0) {
  140. totals.totalEfficiency = ((cpuCost*totals.cpuEfficiency) + (ramCost*totals.ramEfficiency)) / (cpuCost + ramCost)
  141. }
  142. totals.cpuReqCoreHrs = cpuReqCoreHrs
  143. totals.cpuUseCoreHrs = cpuUseCoreHrs
  144. totals.ramReqByteHrs = ramReqByteHrs
  145. totals.ramUseByteHrs = ramUseByteHrs
  146. return totals
  147. }
  148. export function toVerboseTimeRange(window) {
  149. const months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
  150. const start = new Date()
  151. start.setUTCHours(0, 0, 0, 0)
  152. const end = new Date()
  153. end.setUTCHours(0, 0, 0, 0)
  154. switch (window) {
  155. case 'today':
  156. return `${start.getUTCDate()} ${months[start.getUTCMonth()]} ${start.getUTCFullYear()}`
  157. case 'yesterday':
  158. start.setUTCDate(start.getUTCDate()-1)
  159. return `${start.getUTCDate()} ${months[start.getUTCMonth()]} ${start.getUTCFullYear()}`
  160. case 'week':
  161. start.setUTCDate(start.getUTCDate()-start.getUTCDay())
  162. return `${start.getUTCDate()} ${months[start.getUTCMonth()]} ${start.getUTCFullYear()} until now`
  163. case 'month':
  164. start.setUTCDate(1)
  165. return `${start.getUTCDate()} ${months[start.getUTCMonth()]} ${start.getUTCFullYear()} until now`
  166. case 'lastweek':
  167. start.setUTCDate(start.getUTCDate()-(start.getUTCDay()+7))
  168. end.setUTCDate(end.getUTCDate()-(end.getUTCDay()+1))
  169. return `${start.getUTCDate()} ${months[start.getUTCMonth()]} ${start.getUTCFullYear()} through ${end.getUTCDate()} ${months[end.getUTCMonth()]} ${end.getUTCFullYear()}`
  170. case 'lastmonth':
  171. end.setUTCDate(1)
  172. end.setUTCDate(end.getUTCDate()-1)
  173. start.setUTCDate(1)
  174. start.setUTCDate(start.getUTCDate()-1)
  175. start.setUTCDate(1)
  176. return `${start.getUTCDate()} ${months[start.getUTCMonth()]} ${start.getUTCFullYear()} through ${end.getUTCDate()} ${months[end.getUTCMonth()]} ${end.getUTCFullYear()}`
  177. case '6d':
  178. start.setUTCDate(start.getUTCDate()-6)
  179. return `${start.getUTCDate()} ${months[start.getUTCMonth()]} ${start.getUTCFullYear()} through now`
  180. case '29d':
  181. start.setUTCDate(start.getUTCDate()-29)
  182. return `${start.getUTCDate()} ${months[start.getUTCMonth()]} ${start.getUTCFullYear()} through now`
  183. case '59d':
  184. start.setUTCDate(start.getUTCDate()-59)
  185. return `${start.getUTCDate()} ${months[start.getUTCMonth()]} ${start.getUTCFullYear()} through now`
  186. case '89d':
  187. start.setUTCDate(start.getUTCDate()-89)
  188. return `${start.getUTCDate()} ${months[start.getUTCMonth()]} ${start.getUTCFullYear()} through now`
  189. }
  190. const splitDates = window.split(",")
  191. if (checkCustomWindow(window) && splitDates.length > 1) {
  192. let s = splitDates[0].split(/\D+/).slice(0, 3)
  193. let e = splitDates[1].split(/\D+/).slice(0, 3)
  194. if (s.length === 3 && e.length === 3) {
  195. start.setUTCFullYear(s[0], s[1]-1, s[2])
  196. end.setUTCFullYear(e[0], e[1]-1, e[2])
  197. if (start === end) {
  198. return `${start.getUTCDate()} ${months[start.getUTCMonth()]} ${start.getUTCFullYear()}`
  199. } else {
  200. return `${start.getUTCDate()} ${months[start.getUTCMonth()]} ${start.getUTCFullYear()} through ${end.getUTCDate()} ${months[end.getUTCMonth()]} ${end.getUTCFullYear()}`
  201. }
  202. }
  203. }
  204. return null
  205. }
  206. export function bytesToString(bytes) {
  207. const ei = Math.pow(1024, 6)
  208. if (bytes >= ei) {
  209. return `${round(bytes/ei, 1)} EiB`
  210. }
  211. const pi = Math.pow(1024, 5)
  212. if (bytes >= pi) {
  213. return `${round(bytes/pi, 1)} PiB`
  214. }
  215. const ti = Math.pow(1024, 4)
  216. if (bytes >= ti) {
  217. return `${round(bytes/ti, 1)} TiB`
  218. }
  219. const gi = Math.pow(1024, 3)
  220. if (bytes >= gi) {
  221. return `${round(bytes/gi, 1)} GiB`
  222. }
  223. const mi = Math.pow(1024, 2)
  224. if (bytes >= mi) {
  225. return `${round(bytes/mi, 1)} MiB`
  226. }
  227. const ki = Math.pow(1024, 1)
  228. if (bytes >= ki) {
  229. return `${round(bytes/ki, 1)} KiB`
  230. }
  231. return `${round(bytes, 1)} B`
  232. }
  233. const currencyLocale = "en-US"
  234. export function toCurrency(amount, currency, precision) {
  235. if (typeof amount !== "number") {
  236. console.warn(`Tried to convert "${amount}" to currency, but it is not a number`)
  237. return ""
  238. }
  239. if (currency === undefined || currency === "") {
  240. currency = "USD"
  241. }
  242. const opts = {
  243. style: "currency",
  244. currency: currency,
  245. }
  246. if (typeof precision === "number") {
  247. opts.minimumFractionDigits = precision
  248. opts.maximumFractionDigits = precision
  249. }
  250. return amount.toLocaleString(currencyLocale, opts);
  251. }
  252. export function checkCustomWindow(window) {
  253. // Example ISO interval string: 2020-12-02T00:00:00Z,2020-12-03T23:59:59Z
  254. const customDateRegex = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z,\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z/
  255. return customDateRegex.test(window)
  256. }
  257. export default {
  258. rangeToCumulative,
  259. cumulativeToTotals,
  260. toVerboseTimeRange,
  261. bytesToString,
  262. toCurrency,
  263. checkCustomWindow,
  264. }