From 91b515fb77998126f3af42db80ad2c0dccb93e55 Mon Sep 17 00:00:00 2001 From: Wekoslav Stefanovski Date: Sun, 8 Dec 2024 15:21:15 +0100 Subject: [PATCH] Added performancer --- 2024/ts/utils/performancer.ts | 139 ++++++++++++++++++++++++++++++---- 1 file changed, 123 insertions(+), 16 deletions(-) diff --git a/2024/ts/utils/performancer.ts b/2024/ts/utils/performancer.ts index 9743255..e662165 100644 --- a/2024/ts/utils/performancer.ts +++ b/2024/ts/utils/performancer.ts @@ -5,13 +5,24 @@ export type PerformancerInterval = { } export type PerformancerData = { - intervals: PerformancerInterval[]; name: string; get totalDuration(): number; get count(): number; } -const createPerformancerItem = (name: string): PerformancerData => { +type SimplePerformancerData = PerformancerData & { + set count(value: number); + set totalDuration(value: number); + lastStart: number; +} + +export type FullPerformancerData = PerformancerData & { + intervals: PerformancerInterval[]; +} + +type PerformanceWrapper = (fn: (...args: Args) => Return, name?: string) => (...args: Args) => Return; + +const createFullPerformancerItem = (name: string): FullPerformancerData => { const intervals: PerformancerInterval[] = []; return { intervals, @@ -25,25 +36,85 @@ const createPerformancerItem = (name: string): PerformancerData => { }; }; +const createSimplePerformancerItem = (name: string): SimplePerformancerData => { + let count = 0; + let totalDuration = 0; + return { + name, + get totalDuration() { + return totalDuration; + }, + set totalDuration(value: number) { + totalDuration = value; + }, + get count() { + return count; + }, + set count(value: number) { + count = value; + }, + lastStart: 0 + }; +} + +export type PerformancerOptions = { + readonly trackDetails?: boolean; +} + +const PerformancerOptions = { + default: { + trackDetails: false + } +}; + export type Performancer = { - readonly start: (name: string) => void; + readonly monitor: PerformanceWrapper; + readonly begin: (name: string, options?: PerformancerOptions) => void; readonly end: (name: string) => void; readonly get: (name: string) => PerformancerData | undefined; readonly printAll: (printDetails?: boolean) => void; readonly print: (name: string, printDetails?: boolean) => void; } +const isSimpleItem = (item: PerformancerData): item is SimplePerformancerData => { + return (item as SimplePerformancerData).lastStart !== undefined; +} + +const isFullItem = (item: PerformancerData): item is FullPerformancerData => { + return (item as FullPerformancerData).intervals !== undefined; +} + const toThreeDecimals = (value: number) => Math.round(value * 1000) / 1000; const createPerformancer = (): Performancer => { + const monitor = (fn: (...args: Args) => Return, name: string = fn.name) => { + return (...args: Args) => { + begin(name); + const result = fn(...args); + end(name); + return result; + }; + } + const items: Map = new Map(); - const start = (name: string) => { - if (!items.has(name)) { - items.set(name, createPerformancerItem(name)); + const begin = (name: string, options: PerformancerOptions = PerformancerOptions.default) => { + if (options.trackDetails) { + if (!items.has(name)) { + items.set(name, createFullPerformancerItem(name)); + } + const start = performance.now(); + const performancerItem = items.get(name) as FullPerformancerData; + performancerItem.intervals.push({ start, end: 0, duration: 0 }); + } else { + if (!items.has(name)) { + items.set(name, createSimplePerformancerItem(name)); + } + const start = performance.now(); + const performancerItem = items.get(name) as SimplePerformancerData; + performancerItem.lastStart = start; + performancerItem.count += 1; } - const start = performance.now(); - items.get(name)!.intervals.push({ start, end: 0, duration: 0 }); }; const end = (name: string) => { @@ -52,13 +123,19 @@ const createPerformancer = (): Performancer => { if (!item) { throw new Error("No item found"); } - const interval = item.intervals.pop(); - if (!interval) { - throw new Error("No interval found"); + if (isSimpleItem(item)) { + item.totalDuration += end - item.lastStart; + } else if (isFullItem(item)) { + const interval = item.intervals.pop(); + if (!interval) { + throw new Error("No interval found"); + } + const duration = toThreeDecimals(end - interval.start); + const finishedInterval = { ...interval, end, duration }; + item.intervals.push(finishedInterval); + } else { + throw new Error("Invalid item type"); } - const duration = toThreeDecimals(end - interval.start); - const finishedInterval = { ...interval, end, duration }; - item.intervals.push(finishedInterval); }; const get = (name: string) => items.get(name); @@ -77,7 +154,7 @@ const createPerformancer = (): Performancer => { return; } console.log(`Performance for ${name}:`); - if (printDetails) { + if (printDetails && isFullItem(item)) { for (const interval of item.intervals) { console.log(` - ${interval.duration}ms`); } @@ -89,7 +166,8 @@ const createPerformancer = (): Performancer => { } return { - start, + monitor, + begin, end, get, printAll, @@ -99,3 +177,32 @@ const createPerformancer = (): Performancer => { export const Performancer = createPerformancer(); + + +export const monitored = (fn: (...args: Args) => Return, name: string = fn.name) => { + + const performanceData = { + name, + count: 0, + totalDuration: 0, + print: () => { + console.log(`Performance for ${name}:`); + console.log(`${performanceData.count} total calls`); + console.log(`Total duration: ${performanceData.totalDuration.toFixed(3)}ms`); + console.log(`Average duration: ${(performanceData.totalDuration / performanceData.count).toFixed(3)}ms`); + } + } + + const result = (...args: Args) => { + const start = performance.now(); + const result = fn(...args); + const end = performance.now(); + performanceData.count += 1; + performanceData.totalDuration += end - start; + return result; + }; + + result.performance = performanceData; + + return result; +}