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/common/disk.tsx
Views: 687
1
import { useEffect, useState } from "react";
2
import { Icon } from "@cocalc/frontend/components/icon";
3
import { A } from "@cocalc/frontend/components/A";
4
import { Alert, Button, InputNumber, Select, Space, Switch } from "antd";
5
import { SELECTOR_WIDTH } from "@cocalc/frontend/compute/google-cloud-config";
6
import { commas, currency, round2up } from "@cocalc/util/misc";
7
import {
8
markup,
9
hyperdiskCostParams,
10
DEFAULT_HYPERDISK_BALANCED_IOPS,
11
DEFAULT_HYPERDISK_BALANCED_THROUGHPUT,
12
} from "@cocalc/util/compute/cloud/google-cloud/compute-cost";
13
14
interface Props {
15
setConfig;
16
configuration;
17
disabled?: boolean;
18
priceData;
19
state;
20
// if noType is shown, do not render anything related to disk types
21
noType?: boolean;
22
minSizeGb: number;
23
maxSizeGb: number;
24
computeDiskCost;
25
extraHelp?;
26
beforeBody?;
27
rate?;
28
}
29
30
export default function Disk(props: Props) {
31
const {
32
setConfig,
33
configuration,
34
disabled,
35
priceData,
36
state = "deprovisioned",
37
noType,
38
minSizeGb,
39
maxSizeGb,
40
computeDiskCost,
41
extraHelp,
42
beforeBody,
43
rate,
44
} = props;
45
const [help, setHelp] = useState<boolean>(false);
46
const [newDiskSizeGb, setNewDiskSizeGb] = useState<number | null>(
47
configuration.diskSizeGb ?? minSizeGb,
48
);
49
const [newDiskType, setNewDiskType] = useState<string | null>(
50
configuration.diskType ?? "pd-standard",
51
);
52
useEffect(() => {
53
setNewDiskSizeGb(configuration.diskSizeGb ?? minSizeGb);
54
setNewDiskType(configuration.diskType ?? "pd-standard");
55
}, [configuration]);
56
57
useEffect(() => {
58
if (newDiskSizeGb == null) {
59
return;
60
}
61
const min = minSizeGb;
62
if (newDiskSizeGb < min) {
63
setNewDiskSizeGb(min);
64
}
65
}, [configuration.image]);
66
67
useEffect(() => {
68
const min = minSizeGb;
69
if ((newDiskSizeGb ?? 0) < min) {
70
setConfig({
71
diskSizeGb: min,
72
});
73
setNewDiskSizeGb(min);
74
}
75
}, [configuration.acceleratorType]);
76
77
const { requiredMachineTypes, supportedMachineTypes } = priceData?.extra?.[
78
"hyperdisk-balanced"
79
] ?? { requiredMachineTypes: [], supportedMachineTypes: [] };
80
const machineType = configuration.machineType?.split("-")[0];
81
82
return (
83
<div>
84
<div style={{ color: "#666", marginBottom: "5px" }}>
85
<Switch
86
size="small"
87
checkedChildren={"Help"}
88
unCheckedChildren={"Help"}
89
style={{ float: "right" }}
90
checked={help}
91
onChange={(val) => setHelp(val)}
92
/>
93
<b>
94
<Icon name="disk-drive" /> Persistent Disk Storage
95
</b>
96
</div>
97
{help && (
98
<Alert
99
showIcon
100
style={{ margin: "15px 0" }}
101
type="info"
102
message={"Persistent Disk Storage"}
103
description={
104
<div style={{ color: "#666", margin: "10px 0" }}>
105
<p>
106
You are charged for storage as long as the server is provisioned
107
(even if it is off).{" "}
108
<b>
109
If you run out of credit or hit your spending limit and don't
110
pay, then the disk will be automatically deleted.
111
</b>
112
</p>
113
<p>
114
While the server is running,{" "}
115
<i>
116
you can increase the disk size <b>without</b> restarting the
117
server
118
</i>
119
, and it will resize within a minute. You do not have to
120
manually do anything (e.g., reboot or use command line tools)
121
after increasing the disk size.
122
</p>
123
{extraHelp}
124
</div>
125
}
126
/>
127
)}{" "}
128
{beforeBody}
129
<p>
130
Configure the size of the persistent disk
131
{noType
132
? ""
133
: " and the type of storage, which determines how fast the disk is"}
134
.
135
</p>
136
<Space direction="vertical">
137
<InputNumber
138
style={{ width: SELECTOR_WIDTH }}
139
disabled={disabled}
140
min={
141
state == "deprovisioned"
142
? minSizeGb
143
: configuration.diskSizeGb ?? minSizeGb
144
}
145
max={maxSizeGb}
146
value={newDiskSizeGb}
147
addonAfter="GB"
148
onChange={(diskSizeGb) => {
149
setNewDiskSizeGb(diskSizeGb);
150
}}
151
onBlur={() => {
152
if (state == "deprovisioned") {
153
// only set on blur or every keystroke rerenders and cause loss of focus.
154
setConfig({
155
diskSizeGb: newDiskSizeGb ?? minSizeGb,
156
});
157
}
158
}}
159
/>
160
{state != "deprovisioned" &&
161
!disabled &&
162
newDiskSizeGb != null &&
163
(configuration.diskSizeGb ?? minSizeGb) != newDiskSizeGb && (
164
<Button
165
type="primary"
166
onClick={() => {
167
setConfig({
168
diskSizeGb: newDiskSizeGb,
169
});
170
}}
171
>
172
Enlarge by{" "}
173
{newDiskSizeGb - (configuration.diskSizeGb ?? minSizeGb)}{" "}
174
GB (additional cost{" "}
175
{rate ? <>&nbsp;at {rate}&nbsp;</> : undefined} &nbsp;is&nbsp;
176
{currency(
177
computeDiskCost({
178
configuration: {
179
...configuration,
180
diskSizeGb:
181
newDiskSizeGb - (configuration.diskSizeGb ?? minSizeGb),
182
},
183
priceData,
184
}) * 730,
185
)}
186
/month)
187
</Button>
188
)}
189
</Space>
190
<div style={{ color: "#666", margin: "10px 0" }}>
191
Set the size between{" "}
192
{state == "deprovisioned" ? (
193
<Button
194
size="small"
195
onClick={() => {
196
setConfig({
197
diskSizeGb: minSizeGb,
198
});
199
}}
200
>
201
{minSizeGb} GB
202
</Button>
203
) : (
204
<>{minSizeGb} GB</>
205
)}{" "}
206
and {commas(maxSizeGb)} GB.
207
{state != "deprovisioned" && (
208
<>
209
{" "}
210
<b>
211
You can increase the disk size at any time, even while the VM is
212
running.{" "}
213
</b>
214
You <b>cannot decrease the size</b>, without first deprovisioning
215
the server.
216
</>
217
)}
218
</div>
219
{!noType && (
220
<div>
221
<Space>
222
<Select
223
style={{ width: SELECTOR_WIDTH }}
224
disabled={
225
disabled || (state ?? "deprovisioned") != "deprovisioned"
226
}
227
value={newDiskType}
228
onChange={(diskType) => {
229
setNewDiskType(diskType);
230
setConfig({ diskType: diskType ?? "pd-standard" });
231
}}
232
options={[
233
{
234
disabled: requiredMachineTypes.includes(machineType),
235
value: "pd-balanced",
236
label: (
237
<div>
238
Balanced (SSD) disk{" "}
239
<div style={{ fontFamily: "monospace", float: "right" }}>
240
{currency(
241
markup({
242
cost:
243
priceData.disks["pd-balanced"]?.prices[
244
configuration.region
245
] * 730,
246
priceData,
247
}),
248
)}
249
/GB per month
250
</div>
251
</div>
252
),
253
},
254
{
255
disabled: requiredMachineTypes.includes(machineType),
256
value: "pd-ssd",
257
label: (
258
<div>
259
Performance (SSD) disk{" "}
260
<div style={{ fontFamily: "monospace", float: "right" }}>
261
{currency(
262
markup({
263
cost:
264
priceData.disks["pd-ssd"]?.prices[
265
configuration.region
266
] * 730,
267
priceData,
268
}),
269
)}
270
/GB per month
271
</div>
272
</div>
273
),
274
},
275
{
276
disabled: requiredMachineTypes.includes(machineType),
277
value: "pd-standard",
278
label: (
279
<div>
280
Standard (HDD) disk{" "}
281
<div style={{ fontFamily: "monospace", float: "right" }}>
282
{currency(
283
markup({
284
cost:
285
priceData.disks["pd-standard"]?.prices[
286
configuration.region
287
] * 730,
288
priceData,
289
}),
290
)}
291
/GB per month
292
</div>
293
</div>
294
),
295
},
296
{
297
disabled: !supportedMachineTypes.includes(machineType),
298
value: "hyperdisk-balanced",
299
label: (
300
<div>
301
Hyperdisk
302
<div style={{ fontFamily: "monospace", float: "right" }}>
303
<HyperdiskCost
304
region={configuration.region}
305
priceData={priceData}
306
/>
307
</div>
308
</div>
309
),
310
},
311
]}
312
></Select>
313
{configuration.diskType != "hyperdisk-balanced" && (
314
<div style={{ marginLeft: "15px" }}>
315
<b>Total Cost for {commas(configuration.diskSizeGb)} GB:</b>{" "}
316
{currency(
317
markup({
318
cost:
319
configuration.diskSizeGb *
320
priceData.disks[configuration.diskType]?.prices[
321
configuration.region
322
],
323
priceData,
324
}),
325
)}
326
/hour or{" "}
327
{currency(
328
markup({
329
cost:
330
configuration.diskSizeGb *
331
priceData.disks[configuration.diskType]?.prices[
332
configuration.region
333
] *
334
730,
335
priceData,
336
}),
337
)}
338
/month
339
</div>
340
)}
341
</Space>
342
343
{newDiskType == "pd-standard" && (
344
<div style={{ marginTop: "10px", color: "#666" }}>
345
<b>WARNING:</b> Small standard disks are slow. Expect an extra
346
10s-30s of startup time and slower application start. Balanced
347
disks are much faster.
348
</div>
349
)}
350
{newDiskType == "hyperdisk-balanced" && (
351
<HyperdiskInfo
352
diskSizeGb={configuration.diskSizeGb}
353
priceData={priceData}
354
region={configuration.region}
355
style={{ marginTop: "15px" }}
356
/>
357
)}
358
</div>
359
)}
360
</div>
361
);
362
}
363
364
function HyperdiskCost({ region, priceData }) {
365
const { capacity, iops, throughput } = hyperdiskCostParams({
366
region,
367
priceData,
368
});
369
const costProvisioned = markup({
370
cost:
371
iops * DEFAULT_HYPERDISK_BALANCED_IOPS +
372
throughput * DEFAULT_HYPERDISK_BALANCED_THROUGHPUT,
373
priceData,
374
});
375
return (
376
<div>
377
{currency(730 * costProvisioned)} +{" "}
378
{currency(
379
markup({
380
cost: capacity * 730,
381
priceData,
382
}),
383
)}
384
/GB per month
385
</div>
386
);
387
}
388
389
function HyperdiskInfo({ priceData, style, region, diskSizeGb }) {
390
const { capacity, iops, throughput } = hyperdiskCostParams({
391
region,
392
priceData,
393
});
394
const { requiredMachineTypes, supportedMachineTypes } =
395
priceData.extra["hyperdisk-balanced"];
396
const costProvisioned = markup({
397
cost:
398
iops * DEFAULT_HYPERDISK_BALANCED_IOPS +
399
throughput * DEFAULT_HYPERDISK_BALANCED_THROUGHPUT,
400
priceData,
401
});
402
const costCapacity = markup({ cost: capacity * diskSizeGb, priceData });
403
// NOTE: I did benchmarks and you get about 12MB/s even with 32 cpus.
404
// I think th bandwidth is capped by iops/256 = 3000/256 = 11.71875,
405
// even though the google docs say it's the min of that and some other
406
// huge number (i.e., the docs are backwards and wrong).
407
// https://cloud.google.com/compute/docs/disks/hyperdisks#hyperdisks
408
// "Min throughput The greater of IOPS divided by 256 or 140 MBps"
409
// But it should be "lesser" not "greater"!
410
return (
411
<Alert
412
style={style}
413
showIcon
414
type="info"
415
message={"Balanced Hyperdisks"}
416
description={
417
<>
418
Balanced hyperdisks provide {commas(DEFAULT_HYPERDISK_BALANCED_IOPS)}{" "}
419
<A href="https://cloud.google.com/compute/docs/disks/hyperdisks#hyperdisks">
420
IOPS
421
</A>{" "}
422
for any size disk. They can be used with machine types{" "}
423
{supportedMachineTypes.join(", ")} and are required for{" "}
424
{requiredMachineTypes.join(", ")}. The monthly cost is a fixed
425
provisioning cost, plus a cost per GB of data:
426
<div style={{ textAlign: "center", marginTop: "10px" }}>
427
{currency(costProvisioned * 730)}... &nbsp;&nbsp;+ &nbsp;&nbsp;
428
{diskSizeGb} GB ×{" "}
429
{currency(
430
markup({
431
cost: capacity * 730,
432
priceData,
433
}),
434
)}
435
.../GB per month&nbsp;&nbsp;~&nbsp;&nbsp;
436
{currency(round2up(730 * (costProvisioned + costCapacity)))} per
437
month
438
</div>
439
</>
440
}
441
/>
442
);
443
}
444
445