util.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  1. import { forEach, get, round } from "lodash";
  2. import { costMetricToPropName } from "./cloudCost/tokens";
  3. // rangeToCumulative takes an AllocationSetRange (type: array[AllocationSet])
  4. // and accumulates the values into a single AllocationSet (type: object)
  5. export function rangeToCumulative(allocationSetRange, aggregateBy) {
  6. if (allocationSetRange.length === 0) {
  7. return null;
  8. }
  9. const result = {};
  10. forEach(allocationSetRange, (allocSet) => {
  11. forEach(allocSet, (alloc) => {
  12. if (result[alloc.name] === undefined) {
  13. const hrs = get(alloc, "minutes", 0) / 60.0;
  14. result[alloc.name] = {
  15. name: alloc.name,
  16. [aggregateBy]: alloc.name,
  17. cpuCost: get(alloc, "cpuCost", 0),
  18. gpuCost: get(alloc, "gpuCost", 0),
  19. ramCost: get(alloc, "ramCost", 0),
  20. pvCost: get(alloc, "pvCost", 0),
  21. networkCost: get(alloc, "networkCost", 0),
  22. sharedCost: get(alloc, "sharedCost", 0),
  23. externalCost: get(alloc, "externalCost", 0),
  24. totalCost: get(alloc, "totalCost", 0),
  25. cpuUseCoreHrs: get(alloc, "cpuCoreUsageAverage", 0) * hrs,
  26. cpuReqCoreHrs: get(alloc, "cpuCoreRequestAverage", 0) * hrs,
  27. ramUseByteHrs: get(alloc, "ramByteUsageAverage", 0) * hrs,
  28. ramReqByteHrs: get(alloc, "ramByteRequestAverage", 0) * hrs,
  29. cpuEfficiency: get(alloc, "cpuEfficiency", 0),
  30. ramEfficiency: get(alloc, "ramEfficiency", 0),
  31. totalEfficiency: get(alloc, "totalEfficiency", 0),
  32. };
  33. } else {
  34. const hrs = get(alloc, "minutes", 0) / 60.0;
  35. result[alloc.name].cpuCost += get(alloc, "cpuCost", 0);
  36. result[alloc.name].gpuCost += get(alloc, "gpuCost", 0);
  37. result[alloc.name].ramCost += get(alloc, "ramCost", 0);
  38. result[alloc.name].pvCost += get(alloc, "pvCost", 0);
  39. result[alloc.name].networkCost += get(alloc, "networkCost", 0);
  40. result[alloc.name].sharedCost += get(alloc, "sharedCost", 0);
  41. result[alloc.name].externalCost += get(alloc, "externalCost", 0);
  42. result[alloc.name].totalCost += get(alloc, "totalCost", 0);
  43. result[alloc.name].cpuUseCoreHrs +=
  44. get(alloc, "cpuCoreUsageAverage", 0) * hrs;
  45. result[alloc.name].cpuReqCoreHrs +=
  46. get(alloc, "cpuCoreRequestAverage", 0) * hrs;
  47. result[alloc.name].ramUseByteHrs +=
  48. get(alloc, "ramByteUsageAverage", 0) * hrs;
  49. result[alloc.name].ramReqByteHrs +=
  50. get(alloc, "ramByteRequestAverage", 0) * hrs;
  51. }
  52. });
  53. });
  54. // If the range is of length > 1 (i.e. it is not just a single set) then
  55. // compute efficiency for each result after accumulating.
  56. if (allocationSetRange.length > 1) {
  57. forEach(result, (alloc, name) => {
  58. // If we can't compute total efficiency, it defaults to 0.0
  59. let totalEfficiency = 0.0;
  60. // CPU efficiency is defined as (usage/request). If request == 0.0 but
  61. // usage > 0, then efficiency gets set to 1.0.
  62. let cpuEfficiency = 0.0;
  63. if (alloc.cpuReqCoreHrs > 0) {
  64. cpuEfficiency = alloc.cpuUseCoreHrs / alloc.cpuReqCoreHrs;
  65. } else if (alloc.cpuUseCoreHrs > 0) {
  66. cpuEfficiency = 1.0;
  67. }
  68. // RAM efficiency is defined as (usage/request). If request == 0.0 but
  69. // usage > 0, then efficiency gets set to 1.0.
  70. let ramEfficiency = 0.0;
  71. if (alloc.ramReqByteHrs > 0) {
  72. ramEfficiency = alloc.ramUseByteHrs / alloc.ramReqByteHrs;
  73. } else if (alloc.ramUseByteHrs > 0) {
  74. ramEfficiency = 1.0;
  75. }
  76. // Compute efficiency as the cost-weighted average of CPU and RAM
  77. // efficiency
  78. if (alloc.cpuCost + alloc.ramCost > 0.0) {
  79. totalEfficiency =
  80. (alloc.cpuCost * cpuEfficiency + alloc.ramCost * ramEfficiency) /
  81. (alloc.cpuCost + alloc.ramCost);
  82. }
  83. result[name].cpuEfficiency = cpuEfficiency;
  84. result[name].ramEfficiency = ramEfficiency;
  85. result[name].totalEfficiency = totalEfficiency;
  86. });
  87. }
  88. return result;
  89. }
  90. // cumulativeToTotals adds each entry in the given AllocationSet (type: object)
  91. // and returns a single Allocation (type: object) representing the totals
  92. export function cumulativeToTotals(allocationSet) {
  93. let totals = {
  94. name: "Totals",
  95. cpuCost: 0,
  96. gpuCost: 0,
  97. ramCost: 0,
  98. pvCost: 0,
  99. networkCost: 0,
  100. sharedCost: 0,
  101. externalCost: 0,
  102. totalCost: 0,
  103. cpuEfficiency: 0,
  104. ramEfficiency: 0,
  105. totalEfficiency: 0,
  106. };
  107. // Use these for computing efficiency. As such, idle will not factor into
  108. // these numbers, including CPU and RAM cost.
  109. let cpuReqCoreHrs = 0;
  110. let cpuUseCoreHrs = 0;
  111. let ramReqByteHrs = 0;
  112. let ramUseByteHrs = 0;
  113. let cpuCost = 0;
  114. let ramCost = 0;
  115. forEach(allocationSet, (alloc, name) => {
  116. // Accumulate efficiency-related fields
  117. if (name !== "__idle__") {
  118. cpuReqCoreHrs += get(alloc, "cpuReqCoreHrs", 0.0);
  119. cpuUseCoreHrs += get(alloc, "cpuUseCoreHrs", 0.0);
  120. ramReqByteHrs += get(alloc, "ramReqByteHrs", 0.0);
  121. ramUseByteHrs += get(alloc, "ramUseByteHrs", 0.0);
  122. cpuCost += get(alloc, "cpuCost", 0.0);
  123. ramCost += get(alloc, "ramCost", 0.0);
  124. }
  125. // Sum cumulative fields
  126. totals.cpuCost += get(alloc, "cpuCost", 0);
  127. totals.gpuCost += get(alloc, "gpuCost", 0);
  128. totals.ramCost += get(alloc, "ramCost", 0);
  129. totals.pvCost += get(alloc, "pvCost", 0);
  130. totals.networkCost += get(alloc, "networkCost", 0);
  131. totals.sharedCost += get(alloc, "sharedCost", 0);
  132. totals.externalCost += get(alloc, "externalCost", 0);
  133. totals.totalCost += get(alloc, "totalCost", 0);
  134. });
  135. // Compute efficiency
  136. if (cpuReqCoreHrs > 0) {
  137. totals.cpuEfficiency = cpuUseCoreHrs / cpuReqCoreHrs;
  138. } else if (cpuUseCoreHrs > 0) {
  139. totals.cpuEfficiency = 1.0;
  140. }
  141. if (ramReqByteHrs > 0) {
  142. totals.ramEfficiency = ramUseByteHrs / ramReqByteHrs;
  143. } else if (ramUseByteHrs > 0) {
  144. totals.ramEfficiency = 1.0;
  145. }
  146. if (cpuCost + ramCost > 0) {
  147. totals.totalEfficiency =
  148. (cpuCost * totals.cpuEfficiency + ramCost * totals.ramEfficiency) /
  149. (cpuCost + ramCost);
  150. }
  151. totals.cpuReqCoreHrs = cpuReqCoreHrs;
  152. totals.cpuUseCoreHrs = cpuUseCoreHrs;
  153. totals.ramReqByteHrs = ramReqByteHrs;
  154. totals.ramUseByteHrs = ramUseByteHrs;
  155. return totals;
  156. }
  157. export function toVerboseTimeRange(window) {
  158. const months = [
  159. "January",
  160. "February",
  161. "March",
  162. "April",
  163. "May",
  164. "June",
  165. "July",
  166. "August",
  167. "September",
  168. "October",
  169. "November",
  170. "December",
  171. ];
  172. const start = new Date();
  173. start.setUTCHours(0, 0, 0, 0);
  174. const end = new Date();
  175. end.setUTCHours(0, 0, 0, 0);
  176. switch (window) {
  177. case "today":
  178. return `${start.getUTCDate()} ${
  179. months[start.getUTCMonth()]
  180. } ${start.getUTCFullYear()}`;
  181. case "yesterday":
  182. start.setUTCDate(start.getUTCDate() - 1);
  183. return `${start.getUTCDate()} ${
  184. months[start.getUTCMonth()]
  185. } ${start.getUTCFullYear()}`;
  186. case "week":
  187. start.setUTCDate(start.getUTCDate() - start.getUTCDay());
  188. return `${start.getUTCDate()} ${
  189. months[start.getUTCMonth()]
  190. } ${start.getUTCFullYear()} until now`;
  191. case "month":
  192. start.setUTCDate(1);
  193. return `${start.getUTCDate()} ${
  194. months[start.getUTCMonth()]
  195. } ${start.getUTCFullYear()} until now`;
  196. case "lastweek":
  197. start.setUTCDate(start.getUTCDate() - (start.getUTCDay() + 7));
  198. end.setUTCDate(end.getUTCDate() - (end.getUTCDay() + 1));
  199. return `${start.getUTCDate()} ${
  200. months[start.getUTCMonth()]
  201. } ${start.getUTCFullYear()} through ${end.getUTCDate()} ${
  202. months[end.getUTCMonth()]
  203. } ${end.getUTCFullYear()}`;
  204. case "lastmonth":
  205. end.setUTCDate(1);
  206. end.setUTCDate(end.getUTCDate() - 1);
  207. start.setUTCDate(1);
  208. start.setUTCDate(start.getUTCDate() - 1);
  209. start.setUTCDate(1);
  210. return `${start.getUTCDate()} ${
  211. months[start.getUTCMonth()]
  212. } ${start.getUTCFullYear()} through ${end.getUTCDate()} ${
  213. months[end.getUTCMonth()]
  214. } ${end.getUTCFullYear()}`;
  215. case "6d":
  216. start.setUTCDate(start.getUTCDate() - 6);
  217. return `${start.getUTCDate()} ${
  218. months[start.getUTCMonth()]
  219. } ${start.getUTCFullYear()} through now`;
  220. case "29d":
  221. start.setUTCDate(start.getUTCDate() - 29);
  222. return `${start.getUTCDate()} ${
  223. months[start.getUTCMonth()]
  224. } ${start.getUTCFullYear()} through now`;
  225. case "59d":
  226. start.setUTCDate(start.getUTCDate() - 59);
  227. return `${start.getUTCDate()} ${
  228. months[start.getUTCMonth()]
  229. } ${start.getUTCFullYear()} through now`;
  230. case "89d":
  231. start.setUTCDate(start.getUTCDate() - 89);
  232. return `${start.getUTCDate()} ${
  233. months[start.getUTCMonth()]
  234. } ${start.getUTCFullYear()} through now`;
  235. }
  236. const splitDates = window.split(",");
  237. if (checkCustomWindow(window) && splitDates.length > 1) {
  238. let s = splitDates[0].split(/\D+/).slice(0, 3);
  239. let e = splitDates[1].split(/\D+/).slice(0, 3);
  240. if (s.length === 3 && e.length === 3) {
  241. start.setUTCFullYear(s[0], s[1] - 1, s[2]);
  242. end.setUTCFullYear(e[0], e[1] - 1, e[2]);
  243. if (start === end) {
  244. return `${start.getUTCDate()} ${
  245. months[start.getUTCMonth()]
  246. } ${start.getUTCFullYear()}`;
  247. } else {
  248. return `${start.getUTCDate()} ${
  249. months[start.getUTCMonth()]
  250. } ${start.getUTCFullYear()} through ${end.getUTCDate()} ${
  251. months[end.getUTCMonth()]
  252. } ${end.getUTCFullYear()}`;
  253. }
  254. }
  255. }
  256. return null;
  257. }
  258. export function bytesToString(bytes) {
  259. const ei = Math.pow(1024, 6);
  260. if (bytes >= ei) {
  261. return `${round(bytes / ei, 1)} EiB`;
  262. }
  263. const pi = Math.pow(1024, 5);
  264. if (bytes >= pi) {
  265. return `${round(bytes / pi, 1)} PiB`;
  266. }
  267. const ti = Math.pow(1024, 4);
  268. if (bytes >= ti) {
  269. return `${round(bytes / ti, 1)} TiB`;
  270. }
  271. const gi = Math.pow(1024, 3);
  272. if (bytes >= gi) {
  273. return `${round(bytes / gi, 1)} GiB`;
  274. }
  275. const mi = Math.pow(1024, 2);
  276. if (bytes >= mi) {
  277. return `${round(bytes / mi, 1)} MiB`;
  278. }
  279. const ki = Math.pow(1024, 1);
  280. if (bytes >= ki) {
  281. return `${round(bytes / ki, 1)} KiB`;
  282. }
  283. return `${round(bytes, 1)} B`;
  284. }
  285. const currencyLocale = "en-US";
  286. export function toCurrency(amount, currency, precision) {
  287. if (typeof amount !== "number") {
  288. console.warn(
  289. `Tried to convert "${amount}" to currency, but it is not a number`
  290. );
  291. return "";
  292. }
  293. if (currency === undefined || currency === "") {
  294. currency = "USD";
  295. }
  296. const opts = {
  297. style: "currency",
  298. currency: currency,
  299. };
  300. if (typeof precision === "number") {
  301. opts.minimumFractionDigits = precision;
  302. opts.maximumFractionDigits = precision;
  303. }
  304. return amount.toLocaleString(currencyLocale, opts);
  305. }
  306. export function checkCustomWindow(window) {
  307. // Example ISO interval string: 2020-12-02T00:00:00Z,2020-12-03T23:59:59Z
  308. const customDateRegex =
  309. /\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/;
  310. return customDateRegex.test(window);
  311. }
  312. export function getCloudFilters(filters) {
  313. const filterNamesMap = {
  314. "invoice entity": "filterInvoiceEntityIDs",
  315. provider: "filterProviders",
  316. providerids: "filterProviderIDs",
  317. service: "filterServices",
  318. account: "filterAccountIDs",
  319. };
  320. const params = new URLSearchParams();
  321. const labelFilters = [];
  322. for (let filter of filters) {
  323. const mapped = filterNamesMap[filter.property.toLowerCase()];
  324. if (mapped) {
  325. params.set(mapped, filter.value);
  326. } else if (filter.property === "Labels") {
  327. labelFilters.push(filter.value);
  328. } else if (filter.property.startsWith(":")) {
  329. labelFilters.push(`${filter.property.slice(6)}:${filter.value}`);
  330. }
  331. }
  332. if (labelFilters.length) {
  333. params.set("filterLabels", labelFilters.join(","));
  334. }
  335. return `&${params.toString()}`;
  336. }
  337. export function formatSampleItemsForGraph({ data, costMetric }) {
  338. const costMetricPropName = costMetric
  339. ? costMetricToPropName[costMetric]
  340. : "amortizedNetCost";
  341. const graphData = data.sets.map(({ cloudCosts, window: { end, start } }) => {
  342. return {
  343. end,
  344. items: Object.entries(cloudCosts).map(([name, item]) => ({
  345. name,
  346. value: item.netCost.cost,
  347. })),
  348. start,
  349. };
  350. });
  351. const accumulator = {};
  352. data.sets.forEach(({ cloudCosts, window }) => {
  353. Object.entries(cloudCosts).forEach(([name, cloudCostItem]) => {
  354. const { properties } = cloudCostItem;
  355. accumulator[name] ||= {
  356. cost: 0,
  357. start: "",
  358. end: "",
  359. providerID: "",
  360. labelName: "",
  361. kubernetesCost: 0,
  362. kubernetesPercent: 0,
  363. };
  364. accumulator[name].cost += cloudCostItem[costMetricPropName].cost;
  365. accumulator[name].kubernetesCost +=
  366. cloudCostItem[costMetricPropName].cost *
  367. cloudCostItem[costMetricPropName].kubernetesPercent;
  368. accumulator[name].start = window.start;
  369. accumulator[name].end = window.end;
  370. accumulator[name].providerID = properties.providerID;
  371. accumulator[name].labelName = properties.labels?.name;
  372. accumulator[name].kubernetesPercent =
  373. cloudCostItem[costMetricPropName].kubernetesPercent;
  374. });
  375. });
  376. const tableRows = Object.entries(accumulator).map(
  377. ([
  378. name,
  379. {
  380. cost,
  381. start,
  382. end,
  383. providerID,
  384. kubernetesCost,
  385. kubernetesPercent,
  386. labelName,
  387. },
  388. ]) => ({
  389. cost,
  390. name,
  391. kubernetesCost,
  392. kubernetesPercent,
  393. start,
  394. end,
  395. providerID,
  396. labelName,
  397. })
  398. );
  399. const tableTotal = tableRows.reduce(
  400. (tr1, tr2) => ({
  401. ...tr1,
  402. cost: tr1.cost + tr2.cost,
  403. kubernetesCost: tr1.kubernetesCost + tr2.kubernetesCost,
  404. }),
  405. {
  406. cost: 0,
  407. name: "",
  408. kubernetesCost: 0,
  409. kubernetesPercent: 0,
  410. end: "",
  411. start: "",
  412. labelName: "",
  413. providerID: "",
  414. }
  415. );
  416. return { graphData, tableRows, tableTotal };
  417. }
  418. export default {
  419. rangeToCumulative,
  420. cumulativeToTotals,
  421. toVerboseTimeRange,
  422. bytesToString,
  423. toCurrency,
  424. checkCustomWindow,
  425. };