* -------------------------------------------------------------------------
* This file is part of the MindStudio project.
* Copyright (c) 2025 Huawei Technologies Co.,Ltd.
*
* MindStudio is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
* -------------------------------------------------------------------------
*/
import { clamp } from 'lodash';
import type { TimeStamp } from '../entity/common';
import { adaptTime, getLastValue, isLowerUnit } from './adaptTimeForLength';
import type { GetLength, GetPadder, Time, TimeOptions } from './adaptTimeForLength';
export type Readable = (inputs: number[]) => { value: number[]; unit: string };
const MEMUNIT = [
'KB',
'MB',
'GB',
'TB',
'PB',
'EB',
'ZB',
'YB',
'BB',
];
const KILO = 1024;
* Output readable memory data, e.g. 204800KB => 200MB
* @param inputKB numbers in KB
* @returns numbers that readable
*/
export const getReadableMem: Readable = (inputKB) => {
if (inputKB.length === 0) {
return {
value: [0],
unit: MEMUNIT[0],
};
}
let data = Math.max(...inputKB);
let unitIndex = 0;
const zoom = (): void => {
if (data > KILO && unitIndex < (MEMUNIT.length - 1)) {
data /= KILO;
unitIndex += 1;
zoom();
}
};
zoom();
return {
value: inputKB.map(n => n / Math.pow(KILO, unitIndex)),
unit: MEMUNIT[unitIndex],
};
};
export function formatMemoryData(data: number): string {
const item = getReadableMem([data]);
return `${item.value[0].toFixed(2)} ${item.unit}`;
}
export const getTextualDuration = (endTimeAll: number | undefined): string => {
if (endTimeAll === undefined) {
return '00:00:00.000';
}
const time = endTimeAll;
const hour = Math.floor(time / 3600000);
const minute = Math.floor(time % 3600000 / 60000);
const second = Math.floor(time % 60000 / 1000);
const millisecond = time % 1000;
let mesc = '';
if (millisecond < 10) {
mesc = `00${millisecond}`;
} else if (millisecond < 100) {
mesc = `0${millisecond}`;
} else {
mesc = `${millisecond}`;
}
return `${hour < 10 ? (`0${hour}`) : hour}:${minute < 10 ? (`0${minute}`) : minute}:${second < 10 ? (`0${second}`) : second}.${mesc}`;
};
* Converts a fixed-point number to percent string.
*
* @param numPercent the number to be displayed as a percent string
* @param digits the number of digits to appear after the decimal point, should be between 0 to 20 inclusive
* @returns a percent string representation of the given number
*/
export const toPercent = (numPercent: number, digits?: number): string => {
return `${(numPercent * 100).toFixed(digits)}%`;
};
const DEFAULT_DURATION_OPTIONS: TimeOptions = {
precision: 'ns',
maxChars: Number.MAX_SAFE_INTEGER,
};
export const getDuration = (time: TimeStamp, options: TimeOptions = DEFAULT_DURATION_OPTIONS): string => {
const getLength: GetLength = (timeItem) => {
const timeLength = timeItem?.length ?? 0;
const timeUnitLength = timeItem?.unit.length ?? 0;
return timeLength + timeUnitLength + 1;
};
const getPadder: GetPadder = (splitTime, emptyPadder, originPadStartIdx, timesIdx, originLength) => {
let padStartIdx = originPadStartIdx;
let length = originLength;
const handleEmptyPadder = (index: number): void => {
if (emptyPadder[index] !== undefined) {
splitTime.unshift(emptyPadder[index]);
}
};
if (options?.segments !== undefined) {
if (padStartIdx !== 0) {
splitTime.unshift(...emptyPadder.slice(-padStartIdx).map(item => item));
}
} else {
if (padStartIdx === Infinity) {
return [splitTime, timesIdx];
}
do {
handleEmptyPadder(padStartIdx);
(length as number) += getLength(emptyPadder[padStartIdx]);
padStartIdx--;
} while ((length as number) < (options.maxChars as number) - 2 && padStartIdx >= 0);
}
return [splitTime, timesIdx];
};
const { splitTime: duration, tail } = adaptTime(time, { getLength, getPadder, ...options });
const removeInvalidNum = (times: Time[]): Time[] => {
for (let i = 0; i < times.length; i++) {
if (times[i].value !== 0 || i === times.length - 1) {
return times.slice(i);
}
}
return times;
};
const validTime = removeInvalidNum(duration);
return validTime.map((item, index) => `${getLastValue(index === validTime.length - 1, item.value, tail)}${item.unit}`).join(' ');
};
const DEFAULT_TIMESTAMP_OPTIONS: TimeOptions = {
precision: 'ms',
segments: 6,
};
const getLength = (time?: Time): number => {
let padder = 2;
if (time?.unit === 'ms') {
padder = 3;
}
const length = time?.length ?? 0;
const padderLength = time?.value === 0 ? padder : 0;
return length + Math.max(0, padderLength - length);
};
export const getTimestamp = (originTime: TimeStamp, options: TimeOptions = DEFAULT_TIMESTAMP_OPTIONS): string => {
let time = originTime;
const sign = time < 0 ? '-' : '';
time = Math.abs(time);
const mode = options?.maxChars === undefined
? { segments: 6, ...options }
: options;
const getPadder: GetPadder = (splitTime, originEmptyPadder, originPadStartIdx, originTimesIdx, originLength) => {
let emptyPadder = originEmptyPadder;
let padStartIdx = originPadStartIdx;
let timesIdx = originTimesIdx;
let length = originLength;
let resTime: Time[] = [];
const handleEmptyPadder = (index: number): void => {
if (emptyPadder[index] !== undefined) {
splitTime.unshift(emptyPadder[index]);
}
};
if (padStartIdx === Infinity || (mode.maxChars as number) === Infinity) {
return [resTime, timesIdx];
}
if (mode?.segments !== undefined) {
emptyPadder = emptyPadder.reverse();
for (let i = 0; i <= padStartIdx || isLowerUnit(splitTime[0]) || splitTime[0]?.unit === 's'; i++) {
handleEmptyPadder(i);
}
resTime = splitTime.slice(0, mode.segments);
timesIdx = clamp(splitTime.length - resTime.length, 0, splitTime.length) - timesIdx;
} else {
do {
handleEmptyPadder(padStartIdx);
(length as number) += getLength(emptyPadder[padStartIdx]);
padStartIdx--;
} while ((length as number) < (mode.maxChars as number) - 2 && padStartIdx >= 0);
resTime = splitTime;
}
return [resTime, timesIdx];
};
const { splitTime: timestamp } = adaptTime(time, { getLength, getPadder, ...mode });
let separator: ':' | '.' = ':';
let maxLength = 2;
return sign + timestamp.reduce((acc, item, index) => {
if (isLowerUnit(item)) {
maxLength = 3;
separator = '.';
}
const isInvalidHour = item.unit === 'hour' && item.value === 0;
const splitter = index === 0 || isInvalidHour ? '' : separator;
const res = isInvalidHour ? '' : `${acc}${splitter}${item.value.toString().padStart(maxLength, '0')}`;
return res;
}, '');
};
* toLocalTimeString
* @param timestamp timestamp
*/
export function toLocalTimeString(timestamp: number): string {
const date = new Date(timestamp);
return `${date.toLocaleDateString('zh', { hour12: false }).replaceAll('/', '-')
} ${date.toLocaleTimeString('zh', { hour12: false }).replaceAll('/', '-')}`;
}
* Converts a number to thousand separated form. eg: 12345678 => 12,345,678
*/
export function thousandSeparated(n: number): string {
return Math.floor(n).toLocaleString('en-US');
}
* Transform camel-case field to frontend readable string
* @param camelCaseStr eg: sampleString
* @returns eg: Sample String
*/
export function transformCamelCaseToReadable(camelCaseStr: string): string {
if (camelCaseStr.length < 2 || !/^[a-zA-Z]+$/.test(camelCaseStr)) {
throw new Error('Invalid camel case string for func transformCamelCaseToReadable()! Input string must be pure letters whose length is over 2.');
}
const initialUpperStr = camelCaseStr[0].toUpperCase() + camelCaseStr.substring(1);
const pattern = /[A-Z][a-z]+/g;
const readableStr = initialUpperStr.match(pattern);
return readableStr?.join(' ') as string;
}
export const nsToMs = (numPercent: number, digits?: number): string => {
return `${(numPercent / 1e6).toFixed(digits)}`;
};
export function formatDigit(input: number): string {
if (input >= 10) {
return input.toFixed(2);
} else {
return input.toPrecision(4);
}
}
export function formatTimestamp(timestamp: number, format: string = 'YYYY-MM-DD HH:mm:ss'): string {
const date = new Date(timestamp);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
const milliseconds = String(date.getMilliseconds()).padStart(3, '0');
return format
.replace('YYYY', year.toString())
.replace('MM', month)
.replace('DD', day)
.replace('HH', hours)
.replace('mm', minutes)
.replace('ss', seconds)
.replace('SSS', milliseconds);
}