Skip to content

Commit 683ebd3

Browse files
cap10morgandawsontoth
authored andcommitted
chore: Move units code to lib & add tests
1 parent e11324a commit 683ebd3

File tree

4 files changed

+160
-52
lines changed

4 files changed

+160
-52
lines changed

src/features/instance/operations/queries/getAnalytics.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import type { InstanceClientIdConfig, InstanceTypeConfig } from '@/config/instanceClientConfig.ts';
22
import { queryOptions } from '@tanstack/react-query';
3+
import type { Units } from '@/lib/units';
34

45
export type MetricDataKey = string | ((metric: Metric) => number);
5-
export type Units = 'bytes' | 'secs' | 'reads' | 'writes' | 'messages';
6+
export type MetricUnits = Units | 'reads' | 'writes' | 'messages';
67
export interface MetricConfig {
78
id: string;
89
name: string;
910
label?: string;
1011
dataKey: MetricDataKey;
11-
units: Units;
12+
units: MetricUnits;
1213
path?: string;
1314
}
1415

src/features/instance/status/components/monitoring/MetricVisualization.tsx

Lines changed: 5 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { useQuery } from '@tanstack/react-query';
2-
import { getAnalyticsQueryOptions, type MetricConfig, type Metric, type MetricDataKey, type Units } from '@/features/instance/operations/queries/getAnalytics.ts';
2+
import { getAnalyticsQueryOptions, type MetricConfig, type Metric, type MetricDataKey, type MetricUnits } from '@/features/instance/operations/queries/getAnalytics.ts';
33
import type { InstanceClientIdConfig, InstanceTypeConfig } from '@/config/instanceClientConfig.ts';
44
import { useMemo, useState } from 'react';
55
import { Legend, Line, LineChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts';
6+
import { scaleValueToUnits, determineUnits } from '@/lib/units';
67
import { harperPalette } from '@/lib/colorPalette.ts';
78

89
type MetricValue = string | number | boolean;
@@ -17,53 +18,7 @@ interface MetricVisualizationParams {
1718
instanceParams: InstanceClientIdConfig & InstanceTypeConfig;
1819
}
1920

20-
const byteUnits = ['B', 'KiB', 'MiB', 'GiB', 'TiB'];
21-
const timeUnits = ['secs', 'mins', 'hrs'];
22-
23-
function convertToUnits(value: number, baseUnits: Units, units: string) {
24-
let availableUnits;
25-
let conversionBase;
26-
switch (baseUnits) {
27-
case 'bytes': {
28-
availableUnits = byteUnits;
29-
conversionBase = 1024;
30-
break;
31-
}
32-
case 'secs': {
33-
availableUnits = timeUnits;
34-
conversionBase = 60;
35-
break;
36-
}
37-
default:
38-
return value;
39-
}
40-
const converstionExponent = availableUnits.indexOf(units);
41-
const conversionFactor = conversionBase ** converstionExponent;
42-
const converted = (value / conversionFactor).toFixed(2);
43-
return Number(converted);
44-
}
45-
46-
function determineUnits(baseUnits: Units, value: number) {
47-
let availableUnits;
48-
switch (baseUnits) {
49-
case 'bytes':
50-
availableUnits = byteUnits;
51-
break;
52-
case 'secs':
53-
availableUnits = timeUnits;
54-
break;
55-
default:
56-
return baseUnits;
57-
}
58-
let i = 0;
59-
while (value > 1024 && i < availableUnits.length - 1) {
60-
value /= 1024;
61-
i++;
62-
}
63-
return availableUnits[i];
64-
}
65-
66-
function resolveMetricDataKey(metric: Metric, dataKey: MetricDataKey, baseUnits: Units, conversionUnits?: string) {
21+
function resolveMetricDataKey(metric: Metric, dataKey: MetricDataKey, baseUnits: MetricUnits, conversionUnits?: string) {
6722
let baseValue;
6823
if (typeof dataKey === 'string') {
6924
baseValue = metric[dataKey] as number ?? 0;
@@ -72,7 +27,7 @@ function resolveMetricDataKey(metric: Metric, dataKey: MetricDataKey, baseUnits:
7227
}
7328

7429
if (conversionUnits) {
75-
return convertToUnits(baseValue, baseUnits, conversionUnits);
30+
return scaleValueToUnits(baseValue, baseUnits, conversionUnits);
7631
}
7732

7833
return baseValue;
@@ -107,7 +62,7 @@ export function MetricVisualization({ metricConfig, startTime, endTime, instance
10762

10863
for (const metric of metrics) {
10964
const coalescedTime = Math.floor(metric.id / metric.period) * metric.period;
110-
const resolvedMetric = resolveMetricDataKey(metric, dataKey, units, conversionUnits);
65+
const resolvedMetric = resolveMetricDataKey(metric, dataKey, units, conversionUnits).toFixed(2);
11166

11267
if (coalescedMetrics[coalescedTime]) {
11368
coalescedMetrics[coalescedTime][metric.node] = resolvedMetric;

src/lib/units.test.ts

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { describe, expect, it } from 'vitest';
2+
import { determineUnits, scaleValueToUnits } from '@/lib/units.ts';
3+
4+
describe('determineUnits', () => {
5+
it('should return unsupported base units as-is', () => {
6+
expect(determineUnits('foos', 0)).toBe('foos');
7+
});
8+
9+
it("should return 'B' for bytes < 1,024", () => {
10+
expect(determineUnits('bytes', 0)).toBe('B');
11+
expect(determineUnits('bytes', 100)).toBe('B');
12+
expect(determineUnits('bytes', 1023)).toBe('B');
13+
});
14+
15+
it("should return 'KiB' for bytes >= 1,024 && < 1,048,576", () => {
16+
expect(determineUnits('bytes', 1024)).toBe('KiB');
17+
expect(determineUnits('bytes', 2048)).toBe('KiB');
18+
expect(determineUnits('bytes', 1048575)).toBe('KiB');
19+
});
20+
21+
it("should return 'MiB' for bytes >= 1,048,576 && < 1,073,741,824", () => {
22+
expect(determineUnits('bytes', 1048576)).toBe('MiB');
23+
expect(determineUnits('bytes', 10000000)).toBe('MiB');
24+
expect(determineUnits('bytes', 1073741823)).toBe('MiB');
25+
});
26+
27+
it("should return 'secs' for seconds < 60", () => {
28+
expect(determineUnits('secs', 0)).toBe('secs');
29+
expect(determineUnits('secs', 20)).toBe('secs');
30+
expect(determineUnits('secs', 59)).toBe('secs');
31+
});
32+
33+
it("should return 'mins' for seconds >= 60 && < 3,600", () => {
34+
expect(determineUnits('secs', 60)).toBe('mins');
35+
expect(determineUnits('secs', 2345)).toBe('mins');
36+
expect(determineUnits('secs', 3599)).toBe('mins');
37+
});
38+
39+
it("should return 'hrs' for seconds >= 3,600", () => {
40+
expect(determineUnits('secs', 3600)).toBe('hrs');
41+
expect(determineUnits('secs', 345679)).toBe('hrs');
42+
});
43+
});
44+
45+
describe('scaleValueToUnits', () => {
46+
it('should leave values with unsupported units as-is', () => {
47+
expect(scaleValueToUnits(12345, 'bytes', 'bazs')).toBe(12345);
48+
expect(scaleValueToUnits(6789, 'secs', 'quxes')).toBe(6789);
49+
});
50+
51+
it('should leave bytes scaled to bytes as-is', () => {
52+
expect(scaleValueToUnits(0, 'bytes', 'B')).toBe(0);
53+
expect(scaleValueToUnits(100, 'bytes', 'B')).toBe(100);
54+
expect(scaleValueToUnits(1024, 'bytes', 'B')).toBe(1024);
55+
expect(scaleValueToUnits(1000000, 'bytes', 'B')).toBe(1000000);
56+
});
57+
58+
it('should scale bytes to KiB', () => {
59+
expect(scaleValueToUnits(0, 'bytes', 'KiB')).toBe(0);
60+
expect(scaleValueToUnits(1023, 'bytes', 'KiB')).toBeCloseTo(1);
61+
expect(scaleValueToUnits(1024, 'bytes', 'KiB')).toBe(1);
62+
expect(scaleValueToUnits(1024 * 2, 'bytes', 'KiB')).toBe(2);
63+
expect(scaleValueToUnits(1024 * 10, 'bytes', 'KiB')).toBe(10);
64+
});
65+
66+
it('should scale bytes to MiB', () => {
67+
expect(scaleValueToUnits(0, 'bytes', 'MiB')).toBe(0);
68+
expect(scaleValueToUnits(1024, 'bytes', 'MiB')).toBeCloseTo(0.001);
69+
expect(scaleValueToUnits(1024 * 1024, 'bytes', 'MiB')).toBe(1);
70+
expect(scaleValueToUnits(1024 * 1024 * 2, 'bytes', 'MiB')).toBe(2);
71+
expect(scaleValueToUnits(1024 * 1024 * 10, 'bytes', 'MiB')).toBe(10);
72+
});
73+
74+
it('should leave secs scaled to secs as-is', () => {
75+
expect(scaleValueToUnits(0, 'secs', 'secs')).toBe(0);
76+
expect(scaleValueToUnits(100, 'secs', 'secs')).toBe(100);
77+
expect(scaleValueToUnits(1024, 'secs', 'secs')).toBe(1024);
78+
expect(scaleValueToUnits(1000000, 'secs', 'secs')).toBe(1000000);
79+
});
80+
81+
it('should scale secs to mins', () => {
82+
expect(scaleValueToUnits(0, 'secs', 'mins')).toBe(0);
83+
expect(scaleValueToUnits(59, 'secs', 'mins')).toBeLessThan(1);
84+
expect(scaleValueToUnits(60, 'secs', 'mins')).toBe(1);
85+
expect(scaleValueToUnits(600, 'secs', 'mins')).toBe(10);
86+
expect(scaleValueToUnits(3600, 'secs', 'mins')).toBe(60);
87+
});
88+
89+
it('should scale secs to hrs', () => {
90+
expect(scaleValueToUnits(0, 'secs', 'hrs')).toBe(0);
91+
expect(scaleValueToUnits(60 * 59, 'secs', 'hrs')).toBeLessThan(1);
92+
expect(scaleValueToUnits(60 * 60, 'secs', 'hrs')).toBe(1);
93+
expect(scaleValueToUnits(60 * 600, 'secs', 'hrs')).toBe(10);
94+
expect(scaleValueToUnits(60 * 3600, 'secs', 'hrs')).toBe(60);
95+
});
96+
});

src/lib/units.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
export type Units = 'bytes' | 'secs';
2+
3+
const dataUnits = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
4+
const timeUnits = ['secs', 'mins', 'hrs'];
5+
const unitsMultipliers = {
6+
bytes: 1024,
7+
secs: 60,
8+
};
9+
10+
export function scaleValueToUnits(value: number, baseUnits: string, units: string) {
11+
let availableUnits;
12+
let scaleBase;
13+
switch (baseUnits) {
14+
case 'bytes': {
15+
availableUnits = dataUnits;
16+
scaleBase = unitsMultipliers.bytes;
17+
break;
18+
}
19+
case 'secs': {
20+
availableUnits = timeUnits;
21+
scaleBase = unitsMultipliers.secs;
22+
break;
23+
}
24+
default:
25+
return value;
26+
}
27+
const scaleExponent = availableUnits.indexOf(units);
28+
if (scaleExponent === -1) {
29+
return value;
30+
}
31+
const scaleFactor = scaleBase ** scaleExponent;
32+
return value / scaleFactor;
33+
}
34+
35+
export function determineUnits(baseUnits: string, value: number) {
36+
let availableUnits;
37+
let unitsMultiplier;
38+
switch (baseUnits) {
39+
case 'bytes':
40+
availableUnits = dataUnits;
41+
unitsMultiplier = unitsMultipliers.bytes;
42+
break;
43+
case 'secs':
44+
availableUnits = timeUnits;
45+
unitsMultiplier = unitsMultipliers.secs;
46+
break;
47+
default:
48+
return baseUnits;
49+
}
50+
let i = 0;
51+
while (value >= unitsMultiplier && i < availableUnits.length - 1) {
52+
value /= unitsMultiplier;
53+
i++;
54+
}
55+
return availableUnits[i];
56+
}

0 commit comments

Comments
 (0)