Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/src/packages/util/compute/cloud/google-cloud/storage-costs.ts
Views: 687
/*1Estimating Google Cloud storage costs.2*/34import type { CloudFilesystemMetric } from "@cocalc/util/db-schema/cloud-filesystems";5import type { GoogleCloudData } from "@cocalc/util/compute/cloud/google-cloud/compute-cost";6import { GOOGLE_REGION_PREFIX_TO_LOCATION } from "@cocalc/util/db-schema/cloud-filesystems";7import { capitalize } from "@cocalc/util/misc";8import { commas, human_readable_size } from "@cocalc/util/misc";910const MS_IN_MONTH = 730 * 60 * 60 * 1000;11const GB = 1024 * 1024 * 1024;1213interface Options {14metrics: CloudFilesystemMetric[];15priceData: GoogleCloudData;16}1718export function estimateCost_bytes_used({ metrics, priceData }: Options): {19cost: number[];20rate_per_GB_per_month: number;21} {22let cost: number[] = [0];23if (metrics.length == 0) {24return { cost, rate_per_GB_per_month: 0 };25}26const { bucket_location, bucket_storage_class } = metrics[0];27const rate_per_GB_per_month = getAtRestPrice({28priceData,29bucket_storage_class,30bucket_location,31});32const rate_per_byte_per_ms = rate_per_GB_per_month / MS_IN_MONTH / GB;33let s = 0;34for (let i = 1; i < metrics.length; i++) {35const avg_bytes_used =36(metrics[i].bytes_used + metrics[i - 1].bytes_used) / 2;37const ms = metrics[i].timestamp - metrics[i - 1].timestamp;38s += avg_bytes_used * rate_per_byte_per_ms * ms;39cost.push(s);40}41return { cost, rate_per_GB_per_month };42}4344// return at rest price per GB per month45function getAtRestPrice({ priceData, bucket_storage_class, bucket_location }) {46const cls = bucket_storage_class.includes("autoclass")47? "Standard"48: capitalize(bucket_storage_class);49const { atRest } = priceData.storage;50if (bucket_location.includes("-")) {51return atRest.regions[bucket_location][cls];52} else {53return atRest.multiRegions[bucket_location][cls];54}55}5657function sortByServer(metrics: CloudFilesystemMetric[]) {58const byServer: { [id: number]: CloudFilesystemMetric[] } = {};59for (const metric of metrics) {60const id = metric.compute_server_id;61if (byServer[id] == null) {62byServer[id] = [metric];63} else {64byServer[id].push(metric);65}66}67return byServer;68}6970export function estimateCost({71field,72metrics,73priceData,74}: {75metrics: CloudFilesystemMetric[];76priceData: GoogleCloudData;77field: string;78}): {79cost_min: number;80cost_max: number;81total: number;82desc: string;83} {84if (field == "bytes_put") {85return estimateCost_bytes_put({ metrics, priceData });86} else if (field == "bytes_get") {87return estimateCost_bytes_get({ metrics, priceData });88} else if (field == "objects_put") {89return estimateCost_objects_put({ metrics, priceData });90} else if (field == "objects_get") {91return estimateCost_objects_get({ metrics, priceData });92}93return { cost_min: 0, cost_max: 0, total: 0, desc: "" };94}9596function estimateCost_bytes_put({ metrics, priceData }: Options): {97cost_min: number;98cost_max: number;99total: number;100desc: string;101} {102const x = estimateCost_field({103metrics,104priceData,105field: "bytes_put",106getPrice: (opts) => {107const { min, max } = getUploadPrice(opts);108return { min: min / GB, max: max / GB };109},110});111const desc = `to upload ${human_readable_size(x.total)} data`;112return { ...x, desc };113}114115function estimateCost_bytes_get({ metrics, priceData }: Options): {116cost_min: number;117cost_max: number;118total: number;119desc: string;120} {121const x = estimateCost_field({122metrics,123priceData,124field: "bytes_get",125getPrice: (opts) => {126const { min, max } = getDownloadPrice(opts);127return { min: min / GB, max: max / GB };128},129});130const desc = `to download ${human_readable_size(x.total)} data`;131return { ...x, desc };132}133134function estimateCost_objects_put({ metrics, priceData }: Options): {135cost_min: number;136cost_max: number;137total: number;138desc: string;139} {140const x = estimateCost_field({141metrics,142priceData,143field: "objects_put",144getPrice: (opts) => {145const cost = getClassA1000Price(opts);146return { min: cost / 1000, max: cost / 1000 };147},148});149const desc = `to upload ${commas(x.total)} objects (class A operations)`;150return { ...x, desc };151}152153function estimateCost_objects_get({ metrics, priceData }: Options): {154cost_min: number;155cost_max: number;156total: number;157desc: string;158} {159const x = estimateCost_field({160metrics,161priceData,162field: "objects_get",163getPrice: (opts) => {164const cost = getClassB1000Price(opts);165return { min: cost / 1000, max: cost / 1000 };166},167});168const desc = `to download ${commas(x.total)} objects (class B operations)`;169return { ...x, desc };170}171172function estimateCost_field({ metrics, priceData, field, getPrice }): {173cost_min: number;174cost_max: number;175total: number;176} {177if (metrics.length == 0) {178return { cost_min: 0, cost_max: 0, total: 0 };179}180// divide up by compute server id181const byServer = sortByServer(metrics);182// compute the data183let cost_min = 0,184cost_max = 0,185total = 0;186for (const id in byServer) {187const metrics = byServer[id];188const { bucket_location, compute_server_location, bucket_storage_class } =189metrics[0];190const { min, max } = getPrice({191priceData,192bucket_location,193bucket_storage_class,194compute_server_location,195});196let value = 0;197let process_uptime = 0;198for (let i = 1; i < metrics.length; i++) {199if (metrics[i].process_uptime > process_uptime) {200value += (metrics[i][field] ?? 0) - (metrics[i - 1][field] ?? 0);201}202process_uptime = metrics[i].process_uptime;203}204total += value;205cost_min += value * min;206cost_max += value * max;207}208return { cost_min, cost_max, total };209}210211// price per GB to upload data212function getUploadPrice({213priceData,214bucket_location,215compute_server_location,216}): { min: number; max: number } {217if (compute_server_location == "world") {218return { min: 0, max: 0 };219}220if (compute_server_location == "unknown" || !compute_server_location) {221return { min: 0, max: 0 };222}223if (bucket_location == compute_server_location) {224return { min: 0, max: 0 };225}226const bucketLoc =227GOOGLE_REGION_PREFIX_TO_LOCATION[bucket_location.split("-")[0]];228const computeLoc =229GOOGLE_REGION_PREFIX_TO_LOCATION[compute_server_location.split("-")[0]];230const s =231priceData.storage.dataTransferInsideGoogleCloud[computeLoc][bucketLoc];232return { min: s, max: s };233}234235function getDownloadPrice({236priceData,237bucket_location,238compute_server_location,239}): { min: number; max: number } {240if (compute_server_location == "world") {241return { min: 0.12, max: 0.12 };242}243if (compute_server_location == "unknown" || !compute_server_location) {244// not in google cloud -- it's 0.12 or more in some edge cases.245return { min: 0.12, max: 0.23 };246}247if (bucket_location == compute_server_location) {248return { min: 0, max: 0 };249}250const bucketLoc =251GOOGLE_REGION_PREFIX_TO_LOCATION[bucket_location.split("-")[0]];252const computeLoc =253GOOGLE_REGION_PREFIX_TO_LOCATION[compute_server_location.split("-")[0]];254const s =255priceData.storage.dataTransferInsideGoogleCloud[computeLoc][bucketLoc];256return { min: s, max: s };257}258259function getClassA1000Price({260priceData,261bucket_location,262bucket_storage_class,263}): number {264let cls;265if (bucket_storage_class.includes("auto")) {266cls = "standard";267} else {268cls = bucket_storage_class;269}270if (bucket_location.includes("-")) {271// single region272return priceData.storage.singleRegionOperations[cls].classA1000;273} else {274return priceData.storage.multiRegionOperations[cls].classA1000;275}276}277278function getClassB1000Price({279priceData,280bucket_location,281bucket_storage_class,282}): number {283let cls;284if (bucket_storage_class.includes("auto")) {285cls = "standard";286} else {287cls = bucket_storage_class;288}289if (bucket_location.includes("-")) {290// single region291return priceData.storage.singleRegionOperations[cls].classB1000;292} else {293return priceData.storage.multiRegionOperations[cls].classB1000;294}295}296297298