dashboard/shell/utils/units.js

185 lines
4.3 KiB
JavaScript

export const UNITS = ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'];
export const FRACTIONAL = ['', 'm', 'u', 'n', 'p', 'f', 'a', 'z', 'y']; // milli micro nano pico femto
export function formatSi(inValue, {
increment = 1000,
addSuffix = true,
addSuffixSpace = true,
suffix = '',
firstSuffix = null,
startingExponent = 0,
minExponent = 0,
maxExponent = 99,
maxPrecision = 2,
canRoundToZero = true,
} = {}) {
let val = inValue;
let exp = startingExponent;
const divide = maxExponent >= 0;
// TODO More to think about re: min > max
if (divide) {
while ( ( val >= increment && exp + 1 < UNITS.length && exp < maxExponent ) || exp < minExponent ) {
val = val / increment;
exp++;
}
} else {
while ( ( val < increment && exp + 1 < FRACTIONAL.length && exp < (maxExponent * -1) ) || exp < (minExponent * -1) ) {
val = val * increment;
exp++;
}
}
let out = '';
if ( val < 10 && maxPrecision >= 1 ) {
out = `${ Math.round(val * (10 ** maxPrecision) ) / (10 ** maxPrecision) }`;
} else {
out = `${ Math.round(val) }`;
}
if (out === '0' && !canRoundToZero && inValue !== 0) {
const exponent = exponentNeeded(inValue, increment);
return formatSi(inValue, {
increment,
addSuffix,
suffix,
firstSuffix,
startingExponent,
minExponent: exponent,
maxExponent: exponent,
maxPrecision,
canRoundToZero: true,
});
}
if ( addSuffix ) {
if (addSuffixSpace) {
out += ` `;
}
if ( exp === 0 && firstSuffix !== null) {
out += `${ firstSuffix }`;
} else {
out += `${ divide ? UNITS[exp] : FRACTIONAL[exp] }${ suffix }` || '';
}
}
return out;
}
export function exponentNeeded(val, increment = 1000) {
let exp = 0;
while ( val >= increment ) {
val = val / increment;
exp++;
}
return exp;
}
export function parseSi(inValue, opt) {
opt = opt || {};
let increment = opt.increment;
const allowFractional = opt.allowFractional !== false;
if ( !inValue || typeof inValue !== 'string' || !inValue.length ) {
return NaN;
}
inValue = inValue.replace(/,/g, '');
// eslint-disable-next-line prefer-const
let [, valStr, unit, incStr] = inValue.match(/^([0-9.-]+)\s*([^0-9.-]?)([^0-9.-]?)/);
const val = parseFloat(valStr);
if ( !unit ) {
return val;
}
// micro "mu" symbol -> u
if ( unit.charCodeAt(0) === 181 ) {
unit = 'u';
}
const divide = FRACTIONAL.includes(unit);
const multiply = UNITS.includes(unit.toUpperCase());
if ( !increment ) {
// Automatically handle 1 KB = 1000B, 1 KiB = 1024B if no increment set
if ( (multiply || divide) && incStr === 'i' ) {
increment = 1024;
} else {
increment = 1000;
}
}
if ( divide && allowFractional ) {
const exp = FRACTIONAL.indexOf(unit);
return val / (increment ** exp);
}
if ( multiply ) {
const exp = UNITS.indexOf(unit.toUpperCase());
return val * (increment ** exp);
}
// Unrecognized unit character
return val;
}
export const MEMORY_PARSE_RULES = {
memory: {
format: {
addSuffix: true,
firstSuffix: 'B',
increment: 1024,
maxExponent: 99,
maxPrecision: 2,
minExponent: 0,
startingExponent: 0,
suffix: 'iB',
}
}
};
export function createMemoryFormat(n) {
const exponent = exponentNeeded(n, MEMORY_PARSE_RULES.memory.format.increment);
return {
...MEMORY_PARSE_RULES.memory.format,
maxExponent: exponent,
minExponent: exponent,
};
}
function createMemoryUnits(n) {
const exponent = exponentNeeded(n, MEMORY_PARSE_RULES.memory.format.increment);
return `${ UNITS[exponent] }${ MEMORY_PARSE_RULES.memory.format.suffix }`;
}
export function createMemoryValues(total, useful) {
const parsedTotal = parseSi((total || '0').toString());
const parsedUseful = parseSi((useful || '0').toString());
const format = createMemoryFormat(parsedTotal);
const formattedTotal = formatSi(parsedTotal, format);
const formattedUseful = formatSi(parsedUseful, format);
return {
total: Number.parseFloat(formattedTotal),
useful: Number.parseFloat(formattedUseful),
units: createMemoryUnits(parsedTotal)
};
}
export default {
exponentNeeded,
formatSi,
parseSi,
};