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/frontend/compute/cloud-filesystem/metrics.tsx
Views: 687
import { useEffect, useMemo, useRef, useState } from "react";1import { getMetrics } from "./api";2import type { CloudFilesystemMetric } from "@cocalc/util/db-schema/cloud-filesystems";3import Plot from "@cocalc/frontend/components/plotly";4import { field_cmp } from "@cocalc/util/misc";5import { Button, Spin, Tooltip } from "antd";6import useCounter from "@cocalc/frontend/app-framework/counter-hook";7import { Icon } from "@cocalc/frontend/components/icon";8import ShowError from "@cocalc/frontend/components/error";9import {10estimateCost_bytes_used,11estimateCost,12} from "@cocalc/util/compute/cloud/google-cloud/storage-costs";13import { useGoogleCloudPriceData } from "@cocalc/frontend/compute/api";14import { currency } from "@cocalc/util/misc";15import { markup } from "@cocalc/util/compute/cloud/google-cloud/compute-cost";1617const GiB = 1024 * 1024 * 1024;18const DIGITS = 4;1920export default function Metrics({ id }) {21const [metrics, setMetrics] = useState<CloudFilesystemMetric[] | null>(null);22const { val: counter, inc: refresh } = useCounter();23const [refreshing, setRefreshing] = useState<boolean>(false);24const [priceData, priceDataError] = useGoogleCloudPriceData();25const [error, setError] = useState<string>("");26const costsRef = useRef<any>({});2728useEffect(() => {29(async () => {30let metrics;31try {32setRefreshing(true);33metrics = await getMetrics({ id });34} catch (err) {35setError(`${err}`);36} finally {37setRefreshing(false);38}39setMetrics(metrics.sort(field_cmp("timestamp")));40})();41}, [counter]);4243if (metrics == null || priceData == null) {44return (45<div style={{ margin: "10px", textAlign: "center" }}>46Loading Metrics... <Spin />47</div>48);49}5051return (52<>53<div>54<Button style={{ float: "right" }} onClick={refresh}>55<Icon name="refresh" />56Refresh{" "}57{refreshing ? <Spin style={{ marginLeft: "15px" }} /> : undefined}58</Button>59</div>60<ShowError error={error} setError={setError} />61<ShowError error={priceDataError} />62<ShowTotal costsRef={costsRef} priceData={priceData} counter={counter} />63<PlotDiskUsage64metrics={metrics}65priceData={priceData}66costsRef={costsRef}67/>68<PlotMetric69metrics={metrics}70title="Data Uploaded (Network Transfer)"71label="Data (GB)"72field={"bytes_put"}73scale={1 / GiB}74priceData={priceData}75costsRef={costsRef}76/>77<PlotMetric78metrics={metrics}79title="Objects Uploaded (Class A Operations)"80label="Objects"81field={"objects_put"}82priceData={priceData}83costsRef={costsRef}84/>85<PlotMetric86metrics={metrics}87title="Data Downloaded (Network Transfer)"88label="Data (GB)"89field={"bytes_get"}90scale={1 / GiB}91priceData={priceData}92costsRef={costsRef}93/>94<PlotMetric95metrics={metrics}96title="Objects Downloaded (Class B Operations)"97label="Objects"98field={"objects_get"}99priceData={priceData}100costsRef={costsRef}101/>102{/*<PlotMetric103metrics={metrics}104title="Deleted Objects (Class C Operations)"105label="Deletes"106field={"objects_delete"}107priceData={priceData}108costsRef={costsRef}109/>*/}110Storage and network usage are calculated in binary gigabytes (GB), also111known as gibibytes (GiB), where GiB is 1024<sup>3</sup>=2<sup>30</sup>{" "}112bytes.113</>114);115}116117function ShowTotal({ costsRef, priceData, counter }) {118const { inc } = useCounter();119useEffect(() => {120// i'm hungry and need to be done with this for now.121// todo: just compute all the costs first and pass them down122// instead of using a ref.123setTimeout(inc, 1);124setTimeout(inc, 10);125setTimeout(inc, 500);126}, [counter]);127128let cost = 0;129for (const i in costsRef.current) {130cost += costsRef.current[i];131}132return (133<div>134<h3>135Estimated total cost during this period:{" "}136<Money cost={cost} priceData={priceData} />137</h3>138This is the sum of at rest storage, data transfer, and object creation and139deletion operations. It is only an estimate. See the plots and breakdown140below.141</div>142);143}144145function PlotDiskUsage({ metrics, priceData, costsRef }) {146const data = useMemo(() => {147if (metrics == null) {148return [];149}150return [151{152x: metrics.map(({ timestamp }) => new Date(timestamp)),153y: metrics.map(({ bytes_used }) => bytes_used / GiB),154type: "scatter",155},156];157}, [metrics]);158const { cost: v, rate_per_GB_per_month } = estimateCost_bytes_used({159metrics,160priceData,161});162const cost = v.length > 0 ? v[v.length - 1] : 0;163costsRef.current.bytes_used = cost;164165return (166<>167<Plot168data={data}169layout={{170title: "Total Disk Space Used",171xaxis: {172title: "Time",173},174yaxis: {175title: "Disk Used (GB)",176},177}}178/>179Estimated total at rest data storage cost during this period:{" "}180<strong>181<Money cost={cost} priceData={priceData} />182</strong>183. This uses a rate of{" "}184<Money cost={rate_per_GB_per_month} priceData={priceData} /> / GB per185month. Your actual cost will depend on the exact data stored, compression,186and other parameters.187</>188);189}190191function PlotMetric({192metrics,193priceData,194title,195label,196field,197scale = 1,198style,199costsRef,200}: {201metrics: CloudFilesystemMetric[];202priceData;203title: string;204label: string;205field: string;206scale?: number;207style?;208costsRef;209}) {210scale = scale ?? 1;211const cost = useMemo(() => {212if (priceData == null || metrics == null) {213return null;214}215const c = estimateCost({ field, priceData, metrics });216if (c != null) {217costsRef.current[field] = c.cost_min;218}219return c;220}, [priceData, metrics]);221const data = useMemo(() => {222if (metrics == null) {223return [];224}225let total = 0;226let state: {227[id: number]: { value: number; process_uptime: number };228} = {};229230const x: Date[] = [];231const y: number[] = [];232for (const {233compute_server_id,234timestamp,235// @ts-ignore236[field]: value = 0,237process_uptime,238} of metrics) {239if (240state[compute_server_id] != null &&241state[compute_server_id].process_uptime < process_uptime242) {243total += value - state[compute_server_id].value;244}245state[compute_server_id] = {246value,247process_uptime,248};249x.push(new Date(timestamp));250y.push(total * scale);251}252253return [{ x, y, type: "scatter" }];254}, [metrics]);255256return (257<div style={style}>258<Plot259data={data}260layout={{261title,262xaxis: {263title: "Time",264},265yaxis: {266title: label,267},268}}269/>270<div>271<ShowCostEstimate cost={cost} priceData={priceData} />272</div>273</div>274);275}276277function ShowCostEstimate({ cost, priceData }) {278if (cost == null) return null;279const { cost_min: min, cost_max: max, desc } = cost;280if (min == max) {281return (282<div>283Total cost during this period {desc}:{" "}284<b>285<Money cost={max} priceData={priceData} />286</b>287</div>288);289}290return (291<div>292Total cost during this period {desc}:{" "}293<b>294between <Money cost={min} priceData={priceData} /> and{" "}295<Money cost={max} priceData={priceData} />296</b>297. This is a range because there is an active onprem server that might be298in Australia or China (if not, the cost is the lower value).299</div>300);301}302303function Money({ cost, priceData }) {304if (priceData == null) {305return <>-</>;306}307return (308<Tooltip title={currency(markup({ cost, priceData }), DIGITS)}>309{currency(markup({ cost, priceData }), 2)}310</Tooltip>311);312}313314315