@ledgerhq/live-common
Version:
Common ground for the Ledger Live apps
171 lines (153 loc) • 4.79 kB
text/typescript
import { PerformanceObserver, PerformanceObserverCallback } from "node:perf_hooks";
import { AuditResult, NetworkAuditResult } from "./types";
export class SlowFrameDetector {
_count = 0;
_duration = 0;
_threshold: number;
_interval: NodeJS.Timeout | undefined;
constructor(threshold = 200) {
this._threshold = threshold;
}
start(): void {
if (this._interval) {
throw new Error("already started");
}
let lastFrame = Date.now();
this._interval = setInterval(() => {
const now = Date.now();
const diff = now - lastFrame;
if (diff > this._threshold) {
this._count++;
this._duration += diff;
}
lastFrame = now;
}, 10);
}
stop(): void {
if (!this._interval) {
throw new Error("not started");
}
clearInterval(this._interval);
this._interval = undefined;
}
result(): { count: number; duration: number } {
return {
count: this._count,
duration: this._duration,
};
}
}
// all the logic to measure and report back
export class Audit {
_jsBootTime: number;
_startTime: [number, number];
_startUsage: NodeJS.CpuUsage;
_startMemory: NodeJS.MemoryUsage;
_slowFrameDetector: SlowFrameDetector;
_networkAudit: NetworkAudit;
constructor() {
const jsBootTime = Date.now() - parseInt(process.env.START_TIME || "0", 10);
this._jsBootTime = jsBootTime;
this._startTime = process.hrtime();
this._startUsage = process.cpuUsage();
this._startMemory = process.memoryUsage();
this._slowFrameDetector = new SlowFrameDetector();
this._slowFrameDetector.start();
this._networkAudit = new NetworkAudit();
this._networkAudit.start();
}
_totalTime: number | undefined;
_cpuUserTime: number | undefined;
_cpuSystemTime: number | undefined;
_endMemory: NodeJS.MemoryUsage | undefined;
end(): void {
const endTime = process.hrtime(this._startTime);
const endUsage = process.cpuUsage(this._startUsage);
const endMemory = process.memoryUsage();
this._totalTime = (endTime[0] * 1e9 + endTime[1]) / 1e6; // ms
this._cpuUserTime = endUsage.user / 1e3; // ms
this._cpuSystemTime = endUsage.system / 1e3; // ms
this._endMemory = endMemory;
this._slowFrameDetector.stop();
this._networkAudit.stop();
}
_accountsJSONSize: number | undefined;
setAccountsJSONSize(size: number): void {
this._accountsJSONSize = size;
}
_preloadJSONSize: number | undefined;
setPreloadJSONSize(size: number): void {
this._preloadJSONSize = size;
}
result(): AuditResult {
if (!this._totalTime) {
throw new Error("audit not ended");
}
return {
jsBootTime: this._jsBootTime,
cpuUserTime: this._cpuUserTime!,
cpuSystemTime: this._cpuSystemTime!,
totalTime: this._totalTime,
memoryEnd: this._endMemory!,
memoryStart: this._startMemory,
accountsJSONSize: this._accountsJSONSize,
preloadJSONSize: this._preloadJSONSize,
network: this._networkAudit.result(),
slowFrames: this._slowFrameDetector.result(),
};
}
}
export class NetworkAudit {
_obs: PerformanceObserver | undefined;
_totalTime = 0;
_totalCount = 0;
_totalResponseSize = 0;
_totalDuplicateRequests = 0;
_urlsSeen = new Set<string>();
start(): void {
this._obs = new PerformanceObserver(this.onPerformanceEntry);
this._obs.observe({ type: "http" });
}
stop(): void {
if (this._obs) {
this._obs.disconnect();
this._obs = undefined;
}
}
onPerformanceEntry: PerformanceObserverCallback = (items, _observer) => {
const entries = items.getEntries();
for (const entry of entries) {
if (entry.entryType === "http") {
this._totalCount = (this._totalCount || 0) + 1;
if (entry.duration) {
this._totalTime = (this._totalTime || 0) + entry.duration;
}
const req = (entry.detail as any)?.req;
const res = (entry.detail as any)?.res;
if (res && req) {
const { url } = req;
if (this._urlsSeen.has(url)) {
this._totalDuplicateRequests = (this._totalDuplicateRequests || 0) + 1;
} else {
this._urlsSeen.add(url);
}
const { headers } = res;
if (headers) {
const contentLength = headers["content-length"];
if (contentLength) {
this._totalResponseSize = (this._totalResponseSize || 0) + parseInt(contentLength);
}
}
}
}
}
};
result(): NetworkAuditResult {
return {
totalTime: this._totalTime,
totalCount: this._totalCount,
totalResponseSize: this._totalResponseSize,
totalDuplicateRequests: this._totalDuplicateRequests,
};
}
}