treewide: adapt for monorepo layout; initial TUI work

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: Id40b5f5ccb55a8a1ea2793192a38f0256a6a6964
This commit is contained in:
raf 2026-02-07 12:43:59 +03:00
commit 33ec901788
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF
35 changed files with 1699 additions and 413 deletions

View file

@ -0,0 +1,2 @@
export * from './types';
export * from './parser';

105
packages/core/src/parser.ts Normal file
View file

@ -0,0 +1,105 @@
import { StatsData } from './types';
const num = (val: unknown): number => (typeof val === 'number' ? val : 0);
export function parseStats(json: Record<string, unknown>): StatsData {
const timeObj = json.time as Record<string, unknown> | undefined;
const envsObj = json.envs as Record<string, unknown> | undefined;
const listObj = json.list as Record<string, unknown> | undefined;
const valuesObj = json.values as Record<string, unknown> | undefined;
const symbolsObj = json.symbols as Record<string, unknown> | undefined;
const setsObj = json.sets as Record<string, unknown> | undefined;
const sizesObj = json.sizes as Record<string, unknown> | undefined;
const stats: StatsData = {
cpuTime: num(json.cpuTime),
time: {
cpu: num(timeObj?.cpu) || num(json.cpuTime),
gc: num(timeObj?.gc),
gcNonIncremental: num(timeObj?.gcNonIncremental),
gcFraction: num(timeObj?.gcFraction),
gcNonIncrementalFraction: num(timeObj?.gcNonIncrementalFraction),
},
envs: {
number: num(envsObj?.number),
elements: num(envsObj?.elements),
bytes: num(envsObj?.bytes),
},
list: {
elements: num(listObj?.elements),
bytes: num(listObj?.bytes),
concats: num(listObj?.concats),
},
values: { number: num(valuesObj?.number), bytes: num(valuesObj?.bytes) },
symbols: { number: num(symbolsObj?.number), bytes: num(symbolsObj?.bytes) },
sets: {
number: num(setsObj?.number),
elements: num(setsObj?.elements),
bytes: num(setsObj?.bytes),
},
sizes: {
Env: num(sizesObj?.Env),
Value: num(sizesObj?.Value),
Bindings: num(sizesObj?.Bindings),
Attr: num(sizesObj?.Attr),
},
nrExprs: num(json.nrExprs),
nrThunks: num(json.nrThunks),
nrAvoided: num(json.nrAvoided),
nrLookups: num(json.nrLookups),
nrOpUpdates: num(json.nrOpUpdates),
nrOpUpdateValuesCopied: num(json.nrOpUpdateValuesCopied),
nrPrimOpCalls: num(json.nrPrimOpCalls),
nrFunctionCalls: num(json.nrFunctionCalls),
};
if (json.gc && typeof json.gc === 'object') {
const gc = json.gc as Record<string, unknown>;
stats.gc = {
heapSize: num(gc.heapSize),
totalBytes: num(gc.totalBytes),
cycles: num(gc.cycles),
};
}
if (json.primops && typeof json.primops === 'object')
stats.primops = json.primops as Record<string, number>;
if (json.functions && Array.isArray(json.functions))
stats.functions = json.functions as StatsData['functions'];
if (json.attributes && Array.isArray(json.attributes))
stats.attributes = json.attributes as StatsData['attributes'];
const storeFields = [
'narInfoRead',
'narInfoReadAverted',
'narInfoMissing',
'narInfoWrite',
'narRead',
'narReadBytes',
'narReadCompressedBytes',
'narWrite',
'narWriteAverted',
'narWriteBytes',
'narWriteCompressedBytes',
] as const;
for (const field of storeFields) {
if (typeof json[field] === 'number')
(stats as unknown as Record<string, number>)[field] = json[field] as number;
}
return stats;
}
export function calculateChange(
current: number,
previous: number,
): { value: number; percent: number; isReduction: boolean } {
if (previous === 0) {
const percent = current === 0 ? 0 : 100;
return { value: current, percent, isReduction: false };
}
const value = current - previous;
const percent = (value / previous) * 100;
return { value, percent, isReduction: value < 0 };
}

View file

@ -0,0 +1,85 @@
export interface StatsData {
cpuTime: number;
time: {
cpu: number;
gc?: number;
gcNonIncremental?: number;
gcFraction?: number;
gcNonIncrementalFraction?: number;
};
envs: {
number: number;
elements: number;
bytes: number;
};
list: {
elements: number;
bytes: number;
concats: number;
};
values: {
number: number;
bytes: number;
};
symbols: {
number: number;
bytes: number;
};
sets: {
number: number;
elements: number;
bytes: number;
};
sizes: {
Env: number;
Value: number;
Bindings: number;
Attr: number;
};
nrExprs: number;
nrThunks: number;
nrAvoided: number;
nrLookups: number;
nrOpUpdates: number;
nrOpUpdateValuesCopied: number;
nrPrimOpCalls: number;
nrFunctionCalls: number;
gc?: {
heapSize: number;
totalBytes: number;
cycles: number;
};
primops?: Record<string, number>;
functions?: Array<{
name: string | null;
file: string;
line: number;
column: number;
count: number;
}>;
attributes?: Array<{
file: string;
line: number;
column: number;
count: number;
}>;
narInfoRead?: number;
narInfoReadAverted?: number;
narInfoMissing?: number;
narInfoWrite?: number;
narRead?: number;
narReadBytes?: number;
narReadCompressedBytes?: number;
narWrite?: number;
narWriteAverted?: number;
narWriteBytes?: number;
narWriteCompressedBytes?: number;
}
export interface ComparisonEntry {
id: number;
name: string;
data: StatsData;
raw: Record<string, unknown>;
timestamp: Date;
}