UNPKG

knip

Version:

Find and fix unused dependencies, exports and files in your TypeScript and JavaScript projects

147 lines (146 loc) 5.66 kB
import os from 'node:os'; import { PerformanceObserver, performance } from 'node:perf_hooks'; import { memoryUsage } from 'node:process'; import parsedArgValues from './cli-arguments.js'; import { getStats } from './math.js'; import { Table } from './table.js'; const { performance: enableTimerify = false, 'performance-fn': timerifyOnlyFnName, memory: enableMemoryUsage = false, 'memory-realtime': memoryRealtime = false, } = parsedArgValues; const isTimerifyFunctions = enableTimerify || !!timerifyOnlyFnName; const isMemoryUsageEnabled = enableMemoryUsage || memoryRealtime; export const timerify = (fn, name = fn.name) => { if (!isTimerifyFunctions) return fn; if (timerifyOnlyFnName && name !== timerifyOnlyFnName) return fn; return performance.timerify(Object.defineProperty(fn, 'name', { get: () => name })); }; const getMemInfo = () => Object.assign({ freemem: os.freemem() }, memoryUsage()); const twoFixed = (value) => (typeof value === 'number' ? value.toFixed(2) : value); const inMB = (bytes) => bytes / 1024 / 1024; const keys = ['heapUsed', 'heapTotal', 'freemem']; const logHead = () => console.log(keys.map(key => key.padStart(10)).join(' ')); const log = (memInfo) => console.log(keys.map(key => twoFixed(inMB(memInfo[key])).padStart(10)).join(' ')); class Performance { isEnabled; isTimerifyFunctions; isMemoryUsageEnabled; startTime = 0; endTime = 0; perfEntries = []; memEntries = []; perfId; memId; fnObserver; memObserver; memoryUsageStart; freeMemoryStart; constructor({ isTimerifyFunctions = false, isMemoryUsageEnabled = false }) { this.isEnabled = isTimerifyFunctions || isMemoryUsageEnabled; this.isTimerifyFunctions = isTimerifyFunctions; this.isMemoryUsageEnabled = isMemoryUsageEnabled; this.startTime = performance.now(); const instanceId = Math.floor(performance.now() * 100); this.perfId = `perf-${instanceId}`; this.memId = `mem-${instanceId}`; if (isTimerifyFunctions) { this.fnObserver = new PerformanceObserver(items => { for (const entry of items.getEntries()) { this.perfEntries.push(entry); } }); this.fnObserver.observe({ type: 'function' }); } if (isMemoryUsageEnabled) { this.memObserver = new PerformanceObserver(items => { for (const entry of items.getEntries()) { this.memEntries.push(entry); } }); this.memObserver.observe({ type: 'mark' }); if (memoryRealtime) logHead(); this.addMemoryMark(0); } } setMark(name) { const id = `${this.perfId}:${name}`; performance.mark(`${id}:start`); } clearMark(name) { const id = `${this.perfId}:${name}`; performance.mark(`${id}:end`); performance.measure(id, `${id}:start`, `${id}:end`); performance.clearMarks(`${id}:start`); performance.clearMarks(`${id}:end`); } async flush() { this.setMark('_flush'); await new Promise(resolve => setTimeout(resolve, 1)); this.clearMark('_flush'); } getPerfEntriesByName() { return this.perfEntries.reduce((entries, entry) => { const name = entry.name.replace(`${this.perfId}:`, ''); entries[name] = entries[name] ?? []; entries[name].push(entry.duration); return entries; }, {}); } getTimerifiedFunctionsTable() { const entriesByName = this.getPerfEntriesByName(); const table = new Table({ header: true }); for (const [name, values] of Object.entries(entriesByName)) { const stats = getStats(values); table.row(); table.cell('Name', name); table.cell('size', values.length); table.cell('min', stats.min, twoFixed); table.cell('max', stats.max, twoFixed); table.cell('median', stats.median, twoFixed); table.cell('sum', stats.sum, twoFixed); } table.sort('sum|desc'); return table.toString(); } addMemoryMark(index) { if (!this.isMemoryUsageEnabled) return; const id = `${this.memId}:${index}`; const detail = getMemInfo(); performance.mark(id, { detail }); if (memoryRealtime && detail) log(detail); } getMemoryUsageTable() { const table = new Table({ header: true }); for (const entry of this.memEntries) { if (!entry.detail) continue; table.row(); table.cell('heapUsed', inMB(entry.detail.heapUsed), twoFixed); table.cell('heapTotal', inMB(entry.detail.heapTotal), twoFixed); table.cell('freemem', inMB(entry.detail.freemem), twoFixed); } return table.toString(); } getCurrentDurationInMs(startTime) { return performance.now() - (startTime ?? this.startTime); } getMemHeapUsage() { return (memoryUsage().heapUsed ?? 0) - (this.memoryUsageStart?.heapUsed ?? 0); } getCurrentMemUsageInMb() { return twoFixed(inMB(this.getMemHeapUsage())); } async finalize() { if (!this.isEnabled) return; await this.flush(); } reset() { this.perfEntries = []; this.fnObserver?.disconnect(); this.memObserver?.disconnect(); } } export const perfObserver = new Performance({ isTimerifyFunctions, isMemoryUsageEnabled });