19675: Add CWL size formatter and display MiB max ram/disk request alongside
authorStephen Smith <stephen@curii.com>
Tue, 19 Dec 2023 15:49:05 +0000 (10:49 -0500)
committerStephen Smith <stephen@curii.com>
Tue, 19 Dec 2023 15:49:05 +0000 (10:49 -0500)
human readable units

Arvados-DCO-1.1-Signed-off-by: Stephen Smith <stephen@curii.com>

services/workbench2/src/common/formatters.test.ts
services/workbench2/src/common/formatters.ts
services/workbench2/src/views/instance-types-panel/instance-types-panel.tsx

index 7f9ffa0c35618007f32cf3b2de0b9623a22d00fd..cde1a4f9d253f987c3c83962c83cdb197bf5dc9b 100644 (file)
@@ -2,7 +2,57 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { formatUploadSpeed, formatCost } from "./formatters";
+import { formatFileSize, formatUploadSpeed, formatCost, formatCWLResourceSize } from "./formatters";
+
+describe('formatFileSize', () => {
+    it('should pick the largest unit', () => {
+        const base = 1024;
+        const testCases = [
+            {input: 0, output: '0 B'},
+            {input: 1, output: '1 B'},
+            {input: 1023, output: '1023 B'},
+            {input: base, output: '1.0 KiB'},
+            {input: 1.1 * base, output: '1.1 KiB'},
+            {input: 1.5 * base, output: '1.5 KiB'},
+            {input: base ** 2, output: '1.0 MiB'},
+            {input: 1.5 * (base ** 2), output: '1.5 MiB'},
+            {input: base ** 3, output: '1.0 GiB'},
+            {input: base ** 4, output: '1.0 TiB'},
+        ];
+
+        for (const { input, output } of testCases) {
+            expect(formatFileSize(input)).toBe(output);
+        }
+    });
+
+    it('should handle accidental empty string or undefined input', () => {
+        expect(formatFileSize('')).toBe('-');
+        expect(formatFileSize(undefined)).toBe('-');
+    });
+
+    it('should handle accidental non-empty string input', () => {
+        expect(formatFileSize('foo')).toBe('0 B');
+    });
+});
+
+describe('formatCWLResourceSize', () => {
+    it('should format bytes as MiB', () => {
+        const base = 1024 ** 2;
+
+        const testCases = [
+            {input: 0, output: '0 MiB'},
+            {input: 1, output: '0 MiB'},
+            {input: base - 1, output: '1 MiB'},
+            {input: 2 * base, output: '2 MiB'},
+            {input: 1024 * base, output: '1024 MiB'},
+            {input: 10000 * base, output: '10000 MiB'},
+        ];
+
+        for (const { input, output } of testCases) {
+            expect(formatCWLResourceSize(input)).toBe(output);
+        }
+    });
+});
 
 describe('formatUploadSpeed', () => {
     it('should show speed less than 1MB/s', () => {
index 3366af0d561e233d644dbe51fdb1ef88a530e4a2..e44a21e026c7c6fda90490a42e020f5bdb53f25b 100644 (file)
@@ -41,6 +41,10 @@ export const formatFileSize = (size?: number | string) => {
     return '0 B';
 };
 
+export const formatCWLResourceSize = (size: number) => {
+    return `${(size / CWL_SIZE.base).toFixed(0)} ${CWL_SIZE.unit}`;
+};
+
 export const formatTime = (time: number, seconds?: boolean) => {
     const minutes = Math.floor((time / (1000 * 60)) % 60).toFixed(0);
     const hours = Math.floor(time / (1000 * 60 * 60)).toFixed(0);
@@ -78,15 +82,15 @@ export function formatUploadSpeed(
 
 const FILE_SIZES = [
     {
-        base: 1099511627776,
+        base: 1024 ** 4,
         unit: 'TiB',
     },
     {
-        base: 1073741824,
+        base: 1024 ** 3,
         unit: 'GiB',
     },
     {
-        base: 1048576,
+        base: 1024 ** 2,
         unit: 'MiB',
     },
     {
@@ -99,6 +103,11 @@ const FILE_SIZES = [
     },
 ];
 
+const CWL_SIZE = {
+    base: 1024 ** 2,
+    unit: 'MiB',
+};
+
 export const formatPropertyValue = (
     pv: PropertyValue,
     vocabulary?: Vocabulary
index a18f5d2368417877458514dfbd921591a228d9fb..006a119797cbaa47218a7822244aca8093ba3423 100644 (file)
@@ -10,7 +10,7 @@ import { RootState } from 'store/store';
 import { connect } from 'react-redux';
 import { ClusterConfigJSON } from 'common/config';
 import { NotFoundView } from 'views/not-found-panel/not-found-panel';
-import { formatCost, formatFileSize } from 'common/formatters';
+import { formatCWLResourceSize, formatCost, formatFileSize } from 'common/formatters';
 
 type CssRules = 'root' | 'instanceType';
 
@@ -43,6 +43,8 @@ export const InstanceTypesPanel = withStyles(styles)(connect(mapStateToProps)(
                     {Object.keys(instances).length > 0 ?
                         Object.keys(instances).map((instanceKey) => {
                             const instanceType = instances[instanceKey];
+                            const diskRequest = instanceType.IncludedScratch;
+                            const ramRequest = instanceType.RAM - config.Containers.ReserveExtraRAM;
 
                             return <Grid data-cy={instanceKey} className={classes.instanceType} item sm={6} xs={12} key={instanceKey}>
                                 <Card>
@@ -63,10 +65,10 @@ export const InstanceTypesPanel = withStyles(styles)(connect(mapStateToProps)(
                                             Preemptible: {instanceType.Preemptible.toString()}
                                         </Typography>
                                         <Typography>
-                                            Max disk request: {formatFileSize(instanceType.IncludedScratch)}
+                                            Max disk request: {formatCWLResourceSize(diskRequest)} ({formatFileSize(diskRequest)})
                                         </Typography>
                                         <Typography>
-                                            Max ram request: {formatFileSize(instanceType.RAM - config.Containers.ReserveExtraRAM)}
+                                            Max RAM request: {formatCWLResourceSize(ramRequest)} ({formatFileSize(ramRequest)})
                                         </Typography>
                                         {instanceType.CUDA && instanceType.CUDA.DeviceCount > 0 ?
                                             <>