import { forEach, get, round } from "lodash"; import { costMetricToPropName } from "./cloudCost/tokens"; // rangeToCumulative takes an AllocationSetRange (type: array[AllocationSet]) // and accumulates the values into a single AllocationSet (type: object) export function rangeToCumulative(allocationSetRange, aggregateBy) { if (allocationSetRange.length === 0) { return null; } const result = {}; forEach(allocationSetRange, (allocSet) => { forEach(allocSet, (alloc) => { if (result[alloc.name] === undefined) { const hrs = get(alloc, "minutes", 0) / 60.0; result[alloc.name] = { name: alloc.name, [aggregateBy]: alloc.name, cpuCost: get(alloc, "cpuCost", 0), gpuCost: get(alloc, "gpuCost", 0), ramCost: get(alloc, "ramCost", 0), pvCost: get(alloc, "pvCost", 0), networkCost: get(alloc, "networkCost", 0), sharedCost: get(alloc, "sharedCost", 0), externalCost: get(alloc, "externalCost", 0), totalCost: get(alloc, "totalCost", 0), cpuUseCoreHrs: get(alloc, "cpuCoreUsageAverage", 0) * hrs, cpuReqCoreHrs: get(alloc, "cpuCoreRequestAverage", 0) * hrs, ramUseByteHrs: get(alloc, "ramByteUsageAverage", 0) * hrs, ramReqByteHrs: get(alloc, "ramByteRequestAverage", 0) * hrs, cpuEfficiency: get(alloc, "cpuEfficiency", 0), ramEfficiency: get(alloc, "ramEfficiency", 0), totalEfficiency: get(alloc, "totalEfficiency", 0), }; } else { const hrs = get(alloc, "minutes", 0) / 60.0; result[alloc.name].cpuCost += get(alloc, "cpuCost", 0); result[alloc.name].gpuCost += get(alloc, "gpuCost", 0); result[alloc.name].ramCost += get(alloc, "ramCost", 0); result[alloc.name].pvCost += get(alloc, "pvCost", 0); result[alloc.name].networkCost += get(alloc, "networkCost", 0); result[alloc.name].sharedCost += get(alloc, "sharedCost", 0); result[alloc.name].externalCost += get(alloc, "externalCost", 0); result[alloc.name].totalCost += get(alloc, "totalCost", 0); result[alloc.name].cpuUseCoreHrs += get(alloc, "cpuCoreUsageAverage", 0) * hrs; result[alloc.name].cpuReqCoreHrs += get(alloc, "cpuCoreRequestAverage", 0) * hrs; result[alloc.name].ramUseByteHrs += get(alloc, "ramByteUsageAverage", 0) * hrs; result[alloc.name].ramReqByteHrs += get(alloc, "ramByteRequestAverage", 0) * hrs; } }); }); // If the range is of length > 1 (i.e. it is not just a single set) then // compute efficiency for each result after accumulating. if (allocationSetRange.length > 1) { forEach(result, (alloc, name) => { // If we can't compute total efficiency, it defaults to 0.0 let totalEfficiency = 0.0; // CPU efficiency is defined as (usage/request). If request == 0.0 but // usage > 0, then efficiency gets set to 1.0. let cpuEfficiency = 0.0; if (alloc.cpuReqCoreHrs > 0) { cpuEfficiency = alloc.cpuUseCoreHrs / alloc.cpuReqCoreHrs; } else if (alloc.cpuUseCoreHrs > 0) { cpuEfficiency = 1.0; } // RAM efficiency is defined as (usage/request). If request == 0.0 but // usage > 0, then efficiency gets set to 1.0. let ramEfficiency = 0.0; if (alloc.ramReqByteHrs > 0) { ramEfficiency = alloc.ramUseByteHrs / alloc.ramReqByteHrs; } else if (alloc.ramUseByteHrs > 0) { ramEfficiency = 1.0; } // Compute efficiency as the cost-weighted average of CPU and RAM // efficiency if (alloc.cpuCost + alloc.ramCost > 0.0) { totalEfficiency = (alloc.cpuCost * cpuEfficiency + alloc.ramCost * ramEfficiency) / (alloc.cpuCost + alloc.ramCost); } result[name].cpuEfficiency = cpuEfficiency; result[name].ramEfficiency = ramEfficiency; result[name].totalEfficiency = totalEfficiency; }); } return result; } // cumulativeToTotals adds each entry in the given AllocationSet (type: object) // and returns a single Allocation (type: object) representing the totals export function cumulativeToTotals(allocationSet) { let totals = { name: "Totals", cpuCost: 0, gpuCost: 0, ramCost: 0, pvCost: 0, networkCost: 0, sharedCost: 0, externalCost: 0, totalCost: 0, cpuEfficiency: 0, ramEfficiency: 0, totalEfficiency: 0, }; // Use these for computing efficiency. As such, idle will not factor into // these numbers, including CPU and RAM cost. let cpuReqCoreHrs = 0; let cpuUseCoreHrs = 0; let ramReqByteHrs = 0; let ramUseByteHrs = 0; let cpuCost = 0; let ramCost = 0; forEach(allocationSet, (alloc, name) => { // Accumulate efficiency-related fields if (name !== "__idle__") { cpuReqCoreHrs += get(alloc, "cpuReqCoreHrs", 0.0); cpuUseCoreHrs += get(alloc, "cpuUseCoreHrs", 0.0); ramReqByteHrs += get(alloc, "ramReqByteHrs", 0.0); ramUseByteHrs += get(alloc, "ramUseByteHrs", 0.0); cpuCost += get(alloc, "cpuCost", 0.0); ramCost += get(alloc, "ramCost", 0.0); } // Sum cumulative fields totals.cpuCost += get(alloc, "cpuCost", 0); totals.gpuCost += get(alloc, "gpuCost", 0); totals.ramCost += get(alloc, "ramCost", 0); totals.pvCost += get(alloc, "pvCost", 0); totals.networkCost += get(alloc, "networkCost", 0); totals.sharedCost += get(alloc, "sharedCost", 0); totals.externalCost += get(alloc, "externalCost", 0); totals.totalCost += get(alloc, "totalCost", 0); }); // Compute efficiency if (cpuReqCoreHrs > 0) { totals.cpuEfficiency = cpuUseCoreHrs / cpuReqCoreHrs; } else if (cpuUseCoreHrs > 0) { totals.cpuEfficiency = 1.0; } if (ramReqByteHrs > 0) { totals.ramEfficiency = ramUseByteHrs / ramReqByteHrs; } else if (ramUseByteHrs > 0) { totals.ramEfficiency = 1.0; } if (cpuCost + ramCost > 0) { totals.totalEfficiency = (cpuCost * totals.cpuEfficiency + ramCost * totals.ramEfficiency) / (cpuCost + ramCost); } totals.cpuReqCoreHrs = cpuReqCoreHrs; totals.cpuUseCoreHrs = cpuUseCoreHrs; totals.ramReqByteHrs = ramReqByteHrs; totals.ramUseByteHrs = ramUseByteHrs; return totals; } export function toVerboseTimeRange(window) { const months = [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", ]; const start = new Date(); start.setUTCHours(0, 0, 0, 0); const end = new Date(); end.setUTCHours(0, 0, 0, 0); switch (window) { case "today": return `${start.getUTCDate()} ${ months[start.getUTCMonth()] } ${start.getUTCFullYear()}`; case "yesterday": start.setUTCDate(start.getUTCDate() - 1); return `${start.getUTCDate()} ${ months[start.getUTCMonth()] } ${start.getUTCFullYear()}`; case "week": start.setUTCDate(start.getUTCDate() - start.getUTCDay()); return `${start.getUTCDate()} ${ months[start.getUTCMonth()] } ${start.getUTCFullYear()} until now`; case "month": start.setUTCDate(1); return `${start.getUTCDate()} ${ months[start.getUTCMonth()] } ${start.getUTCFullYear()} until now`; case "lastweek": start.setUTCDate(start.getUTCDate() - (start.getUTCDay() + 7)); end.setUTCDate(end.getUTCDate() - (end.getUTCDay() + 1)); return `${start.getUTCDate()} ${ months[start.getUTCMonth()] } ${start.getUTCFullYear()} through ${end.getUTCDate()} ${ months[end.getUTCMonth()] } ${end.getUTCFullYear()}`; case "lastmonth": end.setUTCDate(1); end.setUTCDate(end.getUTCDate() - 1); start.setUTCDate(1); start.setUTCDate(start.getUTCDate() - 1); start.setUTCDate(1); return `${start.getUTCDate()} ${ months[start.getUTCMonth()] } ${start.getUTCFullYear()} through ${end.getUTCDate()} ${ months[end.getUTCMonth()] } ${end.getUTCFullYear()}`; case "6d": start.setUTCDate(start.getUTCDate() - 6); return `${start.getUTCDate()} ${ months[start.getUTCMonth()] } ${start.getUTCFullYear()} through now`; case "29d": start.setUTCDate(start.getUTCDate() - 29); return `${start.getUTCDate()} ${ months[start.getUTCMonth()] } ${start.getUTCFullYear()} through now`; case "59d": start.setUTCDate(start.getUTCDate() - 59); return `${start.getUTCDate()} ${ months[start.getUTCMonth()] } ${start.getUTCFullYear()} through now`; case "89d": start.setUTCDate(start.getUTCDate() - 89); return `${start.getUTCDate()} ${ months[start.getUTCMonth()] } ${start.getUTCFullYear()} through now`; } const splitDates = window.split(","); if (checkCustomWindow(window) && splitDates.length > 1) { let s = splitDates[0].split(/\D+/).slice(0, 3); let e = splitDates[1].split(/\D+/).slice(0, 3); if (s.length === 3 && e.length === 3) { start.setUTCFullYear(s[0], s[1] - 1, s[2]); end.setUTCFullYear(e[0], e[1] - 1, e[2]); if (start === end) { return `${start.getUTCDate()} ${ months[start.getUTCMonth()] } ${start.getUTCFullYear()}`; } else { return `${start.getUTCDate()} ${ months[start.getUTCMonth()] } ${start.getUTCFullYear()} through ${end.getUTCDate()} ${ months[end.getUTCMonth()] } ${end.getUTCFullYear()}`; } } } return null; } export function bytesToString(bytes) { const ei = Math.pow(1024, 6); if (bytes >= ei) { return `${round(bytes / ei, 1)} EiB`; } const pi = Math.pow(1024, 5); if (bytes >= pi) { return `${round(bytes / pi, 1)} PiB`; } const ti = Math.pow(1024, 4); if (bytes >= ti) { return `${round(bytes / ti, 1)} TiB`; } const gi = Math.pow(1024, 3); if (bytes >= gi) { return `${round(bytes / gi, 1)} GiB`; } const mi = Math.pow(1024, 2); if (bytes >= mi) { return `${round(bytes / mi, 1)} MiB`; } const ki = Math.pow(1024, 1); if (bytes >= ki) { return `${round(bytes / ki, 1)} KiB`; } return `${round(bytes, 1)} B`; } const currencyLocale = "en-US"; export function toCurrency(amount, currency, precision) { if (typeof amount !== "number") { console.warn( `Tried to convert "${amount}" to currency, but it is not a number` ); return ""; } if (currency === undefined || currency === "") { currency = "USD"; } const opts = { style: "currency", currency: currency, }; if (typeof precision === "number") { opts.minimumFractionDigits = precision; opts.maximumFractionDigits = precision; } return amount.toLocaleString(currencyLocale, opts); } export function checkCustomWindow(window) { // Example ISO interval string: 2020-12-02T00:00:00Z,2020-12-03T23:59:59Z 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/; return customDateRegex.test(window); } export function getCloudFilters(filters) { const filterNamesMap = { "invoice entity": "filterInvoiceEntityIDs", provider: "filterProviders", providerids: "filterProviderIDs", service: "filterServices", account: "filterAccountIDs", }; const params = new URLSearchParams(); const labelFilters = []; for (let filter of filters) { const mapped = filterNamesMap[filter.property.toLowerCase()]; if (mapped) { params.set(mapped, filter.value); } else if (filter.property === "Labels") { labelFilters.push(filter.value); } else if (filter.property.startsWith(":")) { labelFilters.push(`${filter.property.slice(6)}:${filter.value}`); } } if (labelFilters.length) { params.set("filterLabels", labelFilters.join(",")); } return `&${params.toString()}`; } export function formatSampleItemsForGraph({ data, costMetric }) { const costMetricPropName = costMetric ? costMetricToPropName[costMetric] : "amortizedNetCost"; const graphData = data.sets.map(({ cloudCosts, window: { end, start } }) => { return { end, items: Object.entries(cloudCosts).map(([name, item]) => ({ name, value: item.netCost.cost, })), start, }; }); const accumulator = {}; data.sets.forEach(({ cloudCosts, window }) => { Object.entries(cloudCosts).forEach(([name, cloudCostItem]) => { const { properties } = cloudCostItem; accumulator[name] ||= { cost: 0, start: "", end: "", providerID: "", labelName: "", kubernetesCost: 0, kubernetesPercent: 0, }; accumulator[name].cost += cloudCostItem[costMetricPropName].cost; accumulator[name].kubernetesCost += cloudCostItem[costMetricPropName].cost * cloudCostItem[costMetricPropName].kubernetesPercent; accumulator[name].start = window.start; accumulator[name].end = window.end; accumulator[name].providerID = properties.providerID; accumulator[name].labelName = properties.labels?.name; accumulator[name].kubernetesPercent = cloudCostItem[costMetricPropName].kubernetesPercent; }); }); const tableRows = Object.entries(accumulator).map( ([ name, { cost, start, end, providerID, kubernetesCost, kubernetesPercent, labelName, }, ]) => ({ cost, name, kubernetesCost, kubernetesPercent, start, end, providerID, labelName, }) ); const tableTotal = tableRows.reduce( (tr1, tr2) => ({ ...tr1, cost: tr1.cost + tr2.cost, kubernetesCost: tr1.kubernetesCost + tr2.kubernetesCost, }), { cost: 0, name: "", kubernetesCost: 0, kubernetesPercent: 0, end: "", start: "", labelName: "", providerID: "", } ); return { graphData, tableRows, tableTotal }; } export default { rangeToCumulative, cumulativeToTotals, toVerboseTimeRange, bytesToString, toCurrency, checkCustomWindow, };