CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
sagemathinc

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

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