121 lines
3.2 KiB
JavaScript
121 lines
3.2 KiB
JavaScript
const CDP = require('chrome-remote-interface');
|
|
const { countBy, mean } = require('lodash');
|
|
const Tracelib = require('tracelib');
|
|
|
|
class CDPDataCollector {
|
|
tracingCategories;
|
|
state;
|
|
|
|
constructor(deps) {
|
|
this.state = this.getDefaultState();
|
|
this.tracingCategories = [
|
|
'disabled-by-default-v8.cpu_profile',
|
|
'disabled-by-default-v8.cpu_profiler',
|
|
'disabled-by-default-v8.cpu_profiler.hires',
|
|
'disabled-by-default-devtools.timeline.frame',
|
|
'disabled-by-default-devtools.timeline',
|
|
'disabled-by-default-devtools.timeline.inputs',
|
|
'disabled-by-default-devtools.timeline.stack',
|
|
'disabled-by-default-devtools.timeline.invalidationTracking',
|
|
'disabled-by-default-layout_shift.debug',
|
|
'disabled-by-default-cc.debug.scheduler.frames',
|
|
'disabled-by-default-blink.debug.display_lock',
|
|
];
|
|
}
|
|
|
|
getName = () => DataCollectorName.CDP;
|
|
|
|
resetState = async () => {
|
|
if (this.state.client) {
|
|
await this.state.client.close();
|
|
}
|
|
this.state = this.getDefaultState();
|
|
};
|
|
|
|
getDefaultState = () => ({
|
|
traceEvents: [],
|
|
});
|
|
|
|
// workaround for type declaration issues in cdp lib
|
|
asApis = (client) => client;
|
|
|
|
getClientApis = async () => this.asApis(await this.getClient());
|
|
|
|
getClient = async () => {
|
|
if (this.state.client) {
|
|
return this.state.client;
|
|
}
|
|
|
|
const client = await CDP({ port: this.deps.port });
|
|
|
|
const { Profiler, Page } = this.asApis(client);
|
|
await Promise.all([Page.enable(), Profiler.enable(), Profiler.setSamplingInterval({ interval: 100 })]);
|
|
|
|
this.state.client = client;
|
|
|
|
return client;
|
|
};
|
|
|
|
start = async ({ id }) => {
|
|
if (this.state.tracingPromise) {
|
|
throw new Error(`collection in progress - can't start another one! ${id}`);
|
|
}
|
|
|
|
const { Tracing, Profiler } = await this.getClientApis();
|
|
|
|
await Promise.all([
|
|
Tracing.start({
|
|
bufferUsageReportingInterval: 1000,
|
|
traceConfig: {
|
|
includedCategories: this.tracingCategories,
|
|
},
|
|
}),
|
|
Profiler.start(),
|
|
]);
|
|
|
|
Tracing.on('dataCollected', ({ value: events }) => {
|
|
this.state.traceEvents.push(...events);
|
|
});
|
|
|
|
let resolveFn;
|
|
this.state.tracingPromise = new Promise((resolve) => {
|
|
resolveFn = resolve;
|
|
});
|
|
Tracing.on('tracingComplete', ({ dataLossOccurred }) => {
|
|
const t = new Tracelib(this.state.traceEvents);
|
|
|
|
const eventCounts = countBy(this.state.traceEvents, (ev) => ev.name);
|
|
|
|
const fps = t.getFPS();
|
|
|
|
resolveFn({
|
|
eventCounts,
|
|
fps: mean(fps.values),
|
|
tracingDataLoss: dataLossOccurred ? 1 : 0,
|
|
warnings: t.getWarningCounts(),
|
|
});
|
|
});
|
|
};
|
|
|
|
stop = async (req) => {
|
|
if (!this.state.tracingPromise) {
|
|
throw new Error(`collection was never started - there is nothing to stop!`);
|
|
}
|
|
|
|
const { Tracing, Profiler } = await this.getClientApis();
|
|
|
|
// TODO: capture profiler data
|
|
const [, , traceData] = await Promise.all([Profiler.stop(), Tracing.end(), this.state.tracingPromise]);
|
|
|
|
await this.resetState();
|
|
|
|
return traceData;
|
|
};
|
|
|
|
close = async () => {
|
|
await this.resetState();
|
|
};
|
|
}
|
|
|
|
exports.CDPDataCollector = CDPDataCollector;
|