Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

tracerbench

Package Overview
Dependencies
Maintainers
2
Versions
104
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

tracerbench - npm Package Compare versions

Comparing version 1.0.0-alpha.9 to 1.0.0-alpha.10

dist/trace/aggregator.d.ts

17

dist/index.d.ts

@@ -1,10 +0,11 @@

import * as Domains from "chrome-debugging-client/dist/protocol/tot";
import * as Trace from "./trace/index";
import * as Domains from 'chrome-debugging-client/dist/protocol/tot';
import * as Trace from './trace/index';
export { Domains, Trace };
export { IV8GCSample, IGCStat, IInitialRenderSamples, IIterationSample, IMarker, IPhaseSample, IRuntimeCallStat } from "./benchmarks/initial-render-metric";
export { InitialRenderBenchmark, IInitialRenderBenchmarkParams } from "./benchmarks/initial-render";
export { Benchmark, IBenchmarkMeta, IBenchmarkParams, IBenchmarkState, BrowserOptions } from "./benchmark";
export { Runner, IBenchmark } from "./runner";
export { ITab } from "./tab";
export * from "./util";
export { IV8GCSample, IGCStat, IInitialRenderSamples, IIterationSample, IMarker, IPhaseSample, IRuntimeCallStat } from './benchmarks/initial-render-metric';
export { InitialRenderBenchmark, IInitialRenderBenchmarkParams } from './benchmarks/initial-render';
export { Benchmark, IBenchmarkMeta, IBenchmarkParams, IBenchmarkState, BrowserOptions } from './benchmark';
export { Runner, IBenchmark } from './runner';
export { ITab } from './tab';
export { analyze, harTrace, loadTrace, liveTrace, networkConditions } from './trace';
export * from './util';
//# sourceMappingURL=index.d.ts.map

@@ -16,3 +16,9 @@ "use strict";

exports.Runner = runner_1.Runner;
var trace_1 = require("./trace");
exports.analyze = trace_1.analyze;
exports.harTrace = trace_1.harTrace;
exports.loadTrace = trace_1.loadTrace;
exports.liveTrace = trace_1.liveTrace;
exports.networkConditions = trace_1.networkConditions;
__export(require("./util"));
//# sourceMappingURL=index.js.map

@@ -1,2 +0,2 @@

import { ITraceEvent } from "./trace_event";
import { ITraceEvent } from './trace_event';
export default class Bounds {

@@ -3,0 +3,0 @@ min: number;

@@ -1,6 +0,11 @@

export { default as Bounds } from "./bounds";
export { default as Process } from "./process";
export { default as Trace } from "./trace";
export { default as Thread } from "./thread";
export * from "./trace_event";
export { default as Bounds } from './bounds';
export { default as Process } from './process';
export { default as Trace } from './trace';
export { default as Thread } from './thread';
export { analyze } from './analyze';
export { harTrace } from './archive_trace';
export { loadTrace } from './load_trace';
export { liveTrace } from './live_trace';
export { networkConditions } from './conditions';
export * from './trace_event';
//# sourceMappingURL=index.d.ts.map

@@ -14,3 +14,13 @@ "use strict";

exports.Thread = thread_1.default;
var analyze_1 = require("./analyze");
exports.analyze = analyze_1.analyze;
var archive_trace_1 = require("./archive_trace");
exports.harTrace = archive_trace_1.harTrace;
var load_trace_1 = require("./load_trace");
exports.loadTrace = load_trace_1.loadTrace;
var live_trace_1 = require("./live_trace");
exports.liveTrace = live_trace_1.liveTrace;
var conditions_1 = require("./conditions");
exports.networkConditions = conditions_1.networkConditions;
__export(require("./trace_event"));
//# sourceMappingURL=index.js.map

@@ -1,4 +0,4 @@

import Bounds from "./bounds";
import Thread from "./thread";
import { ITraceEvent } from "./trace_event";
import Bounds from './bounds';
import Thread from './thread';
import { ITraceEvent } from './trace_event';
export default class Process {

@@ -5,0 +5,0 @@ id: number;

@@ -1,3 +0,3 @@

import Bounds from "./bounds";
import { ITraceEvent } from "./trace_event";
import Bounds from './bounds';
import { ITraceEvent } from './trace_event';
export default class Thread {

@@ -4,0 +4,0 @@ bounds: Bounds;

@@ -57,6 +57,28 @@ export declare const TRACE_EVENT_PHASE_BEGIN = "B";

export declare const TRACE_EVENT_SCOPE_NAME_THREAD: TRACE_EVENT_SCOPE_NAME_THREAD;
export declare type TRACE_EVENT_SCOPE_NAME_GLOBAL = "g";
export declare type TRACE_EVENT_SCOPE_NAME_PROCESS = "p";
export declare type TRACE_EVENT_SCOPE_NAME_THREAD = "t";
export declare type TRACE_EVENT_SCOPE_NAME_GLOBAL = 'g';
export declare type TRACE_EVENT_SCOPE_NAME_PROCESS = 'p';
export declare type TRACE_EVENT_SCOPE_NAME_THREAD = 't';
export declare type TRACE_EVENT_SCOPE = TRACE_EVENT_SCOPE_NAME_GLOBAL | TRACE_EVENT_SCOPE_NAME_PROCESS | TRACE_EVENT_SCOPE_NAME_THREAD;
export declare const enum TRACE_EVENT_NAME {
TRACING_STARTED_IN_PAGE = "TracingStartedInPage",
PROFILE = "Profile",
PROFILE_CHUNK = "ProfileChunk",
CPU_PROFILE = "CpuProfile",
V8_EXECUTE = "V8.Execute"
}
export declare const enum PROCESS_NAME {
BROWSER = "Browser",
RENDERER = "Renderer",
GPU = "GPU Process"
}
export declare const enum TRACE_METADATA_NAME {
PROCESS_NAME = "process_name",
PROCESS_LABELS = "process_labels",
PROCESS_SORT_INDEX = "process_sort_index",
PROCESS_UPTIME_SECONDS = "process_uptime_seconds",
THREAD_NAME = "thread_name",
THREAD_SORT_INDEX = "thread_sort_index",
NUM_CPUS = "num_cpus",
TRACE_BUFFER_OVERFLOWED = "trace_buffer_overflowed"
}
/** Serialized TraceEvent */

@@ -109,3 +131,3 @@ export interface ITraceEvent {

[key: string]: any;
} | "__stripped__";
} | '__stripped__';
/**

@@ -168,2 +190,110 @@ * Optional flag indicating whether the tts is meaningful for related async events.

}
export interface ICpuProfile {
nodes: ICpuProfileNode[];
/** startTime in microseconds of CPU profile */
startTime: number;
/** node id of node that was sampled */
samples: number[];
/** delta from when the profile started to when the sample was taken in microseconds */
timeDeltas: number[];
}
export interface ICpuProfileNode {
id: number;
callFrame: ICallFrame;
children?: number[];
positionTicks?: {
line: number;
ticks: number;
};
}
export interface IProfileEvent extends ITraceEvent {
ph: TRACE_EVENT_PHASE_SAMPLE;
name: TRACE_EVENT_NAME.PROFILE;
args: {
data: {
startTime: number;
};
};
id: string;
}
export interface IProfileChunkEvent extends ITraceEvent {
ph: TRACE_EVENT_PHASE_SAMPLE;
name: TRACE_EVENT_NAME.PROFILE_CHUNK;
args: {
data: IProfileChunk;
};
id: string;
}
export interface IProfileChunk {
cpuProfile: {
nodes: IProfileNode[];
samples: number[];
};
timeDeltas: number[];
}
export interface IProfileNode {
id: number;
parent?: number;
callFrame: ICallFrame;
}
export interface ICallFrame {
functionName: string;
scriptId: string | number;
url: string;
lineNumber: number;
columnNumber: number;
}
export interface ICpuProfileEvent extends ITraceEvent {
ph: TRACE_EVENT_PHASE_INSTANT;
name: TRACE_EVENT_NAME.CPU_PROFILE;
args: {
data: {
cpuProfile: ICpuProfile;
};
};
}
export interface ICpuProfile {
nodes: ICpuProfileNode[];
/**
* startTime in microseconds of CPU profile
*/
startTime: number;
endTime: number;
/**
* id of root node
*/
samples: number[];
/**
* offset from startTime if first or previous time
*/
timeDeltas: number[];
duration: number;
}
export declare const enum FUNCTION_NAME {
ROOT = "(root)",
PROGRAM = "(program)",
IDLE = "(idle)",
GC = "(garbage collector)"
}
export interface ICpuProfileNode {
id: number;
callFrame: ICallFrame;
children?: number[];
positionTicks?: {
line: number;
ticks: number;
};
sampleCount: number;
min: number;
max: number;
total: number;
self: number;
}
export interface ISample {
delta: number;
timestamp: number;
prev: ISample | null;
next: ISample | null;
node: ICpuProfileNode;
}
//# sourceMappingURL=trace_event.d.ts.map
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TRACE_EVENT_PHASE_BEGIN = "B";
exports.TRACE_EVENT_PHASE_END = "E";
exports.TRACE_EVENT_PHASE_COMPLETE = "X";
exports.TRACE_EVENT_PHASE_INSTANT = "I";
exports.TRACE_EVENT_PHASE_ASYNC_BEGIN = "S";
exports.TRACE_EVENT_PHASE_ASYNC_STEP_INTO = "T";
exports.TRACE_EVENT_PHASE_ASYNC_STEP_PAST = "p";
exports.TRACE_EVENT_PHASE_ASYNC_END = "F";
exports.TRACE_EVENT_PHASE_NESTABLE_ASYNC_BEGIN = "b";
exports.TRACE_EVENT_PHASE_NESTABLE_ASYNC_END = "e";
exports.TRACE_EVENT_PHASE_NESTABLE_ASYNC_INSTANT = "n";
exports.TRACE_EVENT_PHASE_FLOW_BEGIN = "s";
exports.TRACE_EVENT_PHASE_FLOW_STEP = "t";
exports.TRACE_EVENT_PHASE_FLOW_END = "f";
exports.TRACE_EVENT_PHASE_METADATA = "M";
exports.TRACE_EVENT_PHASE_COUNTER = "C";
exports.TRACE_EVENT_PHASE_SAMPLE = "P";
exports.TRACE_EVENT_PHASE_CREATE_OBJECT = "N";
exports.TRACE_EVENT_PHASE_SNAPSHOT_OBJECT = "O";
exports.TRACE_EVENT_PHASE_DELETE_OBJECT = "D";
exports.TRACE_EVENT_PHASE_MEMORY_DUMP = "v";
exports.TRACE_EVENT_PHASE_MARK = "R";
exports.TRACE_EVENT_PHASE_CLOCK_SYNC = "c";
exports.TRACE_EVENT_PHASE_ENTER_CONTEXT = "(";
exports.TRACE_EVENT_PHASE_LEAVE_CONTEXT = ")";
exports.TRACE_EVENT_PHASE_LINK_IDS = "=";
exports.TRACE_EVENT_SCOPE_NAME_GLOBAL = "g";
exports.TRACE_EVENT_SCOPE_NAME_PROCESS = "p";
exports.TRACE_EVENT_SCOPE_NAME_THREAD = "t";
exports.TRACE_EVENT_PHASE_BEGIN = 'B';
exports.TRACE_EVENT_PHASE_END = 'E';
exports.TRACE_EVENT_PHASE_COMPLETE = 'X';
exports.TRACE_EVENT_PHASE_INSTANT = 'I';
exports.TRACE_EVENT_PHASE_ASYNC_BEGIN = 'S';
exports.TRACE_EVENT_PHASE_ASYNC_STEP_INTO = 'T';
exports.TRACE_EVENT_PHASE_ASYNC_STEP_PAST = 'p';
exports.TRACE_EVENT_PHASE_ASYNC_END = 'F';
exports.TRACE_EVENT_PHASE_NESTABLE_ASYNC_BEGIN = 'b';
exports.TRACE_EVENT_PHASE_NESTABLE_ASYNC_END = 'e';
exports.TRACE_EVENT_PHASE_NESTABLE_ASYNC_INSTANT = 'n';
exports.TRACE_EVENT_PHASE_FLOW_BEGIN = 's';
exports.TRACE_EVENT_PHASE_FLOW_STEP = 't';
exports.TRACE_EVENT_PHASE_FLOW_END = 'f';
exports.TRACE_EVENT_PHASE_METADATA = 'M';
exports.TRACE_EVENT_PHASE_COUNTER = 'C';
exports.TRACE_EVENT_PHASE_SAMPLE = 'P';
exports.TRACE_EVENT_PHASE_CREATE_OBJECT = 'N';
exports.TRACE_EVENT_PHASE_SNAPSHOT_OBJECT = 'O';
exports.TRACE_EVENT_PHASE_DELETE_OBJECT = 'D';
exports.TRACE_EVENT_PHASE_MEMORY_DUMP = 'v';
exports.TRACE_EVENT_PHASE_MARK = 'R';
exports.TRACE_EVENT_PHASE_CLOCK_SYNC = 'c';
exports.TRACE_EVENT_PHASE_ENTER_CONTEXT = '(';
exports.TRACE_EVENT_PHASE_LEAVE_CONTEXT = ')';
exports.TRACE_EVENT_PHASE_LINK_IDS = '=';
exports.TRACE_EVENT_SCOPE_NAME_GLOBAL = 'g';
exports.TRACE_EVENT_SCOPE_NAME_PROCESS = 'p';
exports.TRACE_EVENT_SCOPE_NAME_THREAD = 't';
//# sourceMappingURL=trace_event.js.map

@@ -1,5 +0,6 @@

import Bounds from "./bounds";
import Process from "./process";
import Thread from "./thread";
import { ITraceEvent } from "./trace_event";
import Bounds from './bounds';
import Process from './process';
import Thread from './thread';
import CpuProfile from './cpu-profile';
import { ITraceEvent } from './trace_event';
export default class Trace {

@@ -16,5 +17,10 @@ processes: Process[];

private stack;
private lastTracingStartedInPageEvent?;
private _cpuProfile?;
private profileMap;
cpuProfile(min: number, max: number): CpuProfile;
process(pid: number): Process;
thread(pid: number, tid: number): Thread;
addEvents(events: ITraceEvent[]): void;
cpuProfileBuildModel(event: any): void;
buildModel(): void;

@@ -21,0 +27,0 @@ getParent(event: ITraceEvent): void;

"use strict";
/* tslint:disable:variable-name */
/* tslint:disable:no-console */
/* tslint:disable:no-bitwise */
Object.defineProperty(exports, "__esModule", { value: true });

@@ -6,2 +9,4 @@ const array_binsearch_1 = require("array-binsearch");

const process_1 = require("./process");
const cpu_profile_1 = require("./cpu-profile");
const render_events_1 = require("./render-events");
const trace_event_1 = require("./trace_event");

@@ -19,3 +24,12 @@ const trace_event_comparator_1 = require("./trace_event_comparator");

this.stack = [];
this.profileMap = new Map();
}
cpuProfile(min, max) {
const { _cpuProfile } = this;
if (_cpuProfile === undefined) {
console.trace('public cpuProfile');
throw new Error('trace is missing CpuProfile');
}
return new cpu_profile_1.default(_cpuProfile, this.events, min, max);
}
process(pid) {

@@ -37,7 +51,83 @@ let process = this.findProcess(pid);

}
cpuProfileBuildModel(event) {
if (event.ph === trace_event_1.TRACE_EVENT_PHASE_INSTANT &&
event.cat === 'disabled-by-default-devtools.timeline') {
if (event.name === 'CpuProfile') {
this._cpuProfile = event.args.data.cpuProfile;
}
else if (event.name === 'TracingStartedInPage') {
this.lastTracingStartedInPageEvent = event;
}
}
else if (event.ph === trace_event_1.TRACE_EVENT_PHASE_SAMPLE) {
if (event.name === 'Profile') {
const profile = event;
this.profileMap.set(profile.id, {
pid: profile.pid,
tid: profile.tid,
cpuProfile: {
startTime: profile.args.data.startTime,
endTime: 0,
duration: 0,
nodes: [],
samples: [],
timeDeltas: []
}
});
}
else if (event.name === 'ProfileChunk') {
const profileChunk = event;
const profileEntry = this.profileMap.get(profileChunk.id);
if (profileChunk.args.data.cpuProfile.nodes) {
profileChunk.args.data.cpuProfile.nodes.forEach((node) => {
profileEntry.cpuProfile.nodes.push(Object.assign(node, {
sampleCount: 0,
min: 0,
max: 0,
total: 0,
self: 0
}));
});
}
profileEntry.cpuProfile.samples.push(...profileChunk.args.data.cpuProfile.samples);
profileEntry.cpuProfile.timeDeltas.push(...profileChunk.args.data.timeDeltas);
}
}
// determine main process
if (this.lastTracingStartedInPageEvent) {
// if this was recorded with the Performance tab, this should be the main process
this.mainProcess = this.process(this.lastTracingStartedInPageEvent.pid);
}
else {
// fallback to Renderer process with most events
this.mainProcess = this.processes
.filter(p => p.name === "Renderer" /* RENDERER */)
.reduce((a, b) => (b.events.length > a.events.length ? b : a));
}
for (const profileEntry of this.profileMap.values()) {
if (profileEntry.pid === this.mainProcess.id &&
profileEntry.tid === this.mainProcess.mainThread.id) {
this._cpuProfile = profileEntry.cpuProfile;
const { nodes } = profileEntry.cpuProfile;
const nodeMap = new Map();
nodes.forEach(node => nodeMap.set(node.id, node));
nodes.forEach(node => {
if (node.parent !== undefined) {
const parent = nodeMap.get(node.parent);
if (parent.children) {
parent.children.push(node.id);
}
else {
parent.children = [node.id];
}
}
});
break;
}
}
}
buildModel() {
const { events } = this;
if (this.stack.length > 0) {
/* tslint:disable:no-console */
console.error("trace has incomplete B phase events");
console.error('trace has incomplete B phase events');
this.stack.length = 0;

@@ -49,2 +139,3 @@ }

process.addEvent(event);
this.cpuProfileBuildModel(event);
}

@@ -76,3 +167,3 @@ }

addEvent(event) {
if (event.ph === trace_event_1.TRACE_EVENT_PHASE_END) {
if (event.ph === trace_event_1.TRACE_EVENT_PHASE_END || render_events_1.isRenderEnd(event)) {
this.endEvent(event);

@@ -84,3 +175,2 @@ return;

if (index < 0) {
/* tslint:disable:no-bitwise */
index = ~index;

@@ -97,3 +187,3 @@ }

}
if (event.ph === trace_event_1.TRACE_EVENT_PHASE_BEGIN) {
if (event.ph === trace_event_1.TRACE_EVENT_PHASE_BEGIN || render_events_1.isRenderStart(event)) {
this.stack.push(event);

@@ -123,10 +213,10 @@ }

}
throw new Error("could not find matching B phase for E phase event");
throw new Error('could not find matching B phase for E phase event');
}
completeEvent(begin, end) {
let args = "__stripped__";
if (typeof begin.args === "object" && begin.args !== null) {
let args = '__stripped__';
if (typeof begin.args === 'object' && begin.args !== null) {
args = Object.assign({}, begin.args);
}
if (typeof end.args === "object" && end.args !== null) {
if (typeof end.args === 'object' && end.args !== null) {
args = Object.assign(args === undefined ? {} : args, end.args);

@@ -148,54 +238,54 @@ }

const index = array_binsearch_1.default(events, begin, trace_event_comparator_1.default);
events[index] = complete;
events.splice(index, 0, complete);
}
addMetadata(event) {
const { pid, tid } = event;
if (event.args === "__stripped__") {
if (event.args === '__stripped__') {
return;
}
switch (event.name) {
case "num_cpus":
case 'num_cpus':
this.numberOfProcessors = event.args.number;
break;
case "process_name":
case 'process_name':
const processName = event.args.name;
const process = this.process(pid);
process.name = processName;
if (processName === "GPU Process") {
if (processName === 'GPU Process') {
this.gpuProcess = process;
}
else if (processName === "Browser") {
else if (processName === 'Browser') {
this.browserProcess = process;
}
else if (processName === "Renderer") {
else if (processName === 'Renderer') {
this.rendererProcesses.push(process);
}
break;
case "process_labels":
case 'process_labels':
this.process(pid).labels = event.args.labels;
break;
case "process_sort_index":
case 'process_sort_index':
this.process(pid).sortIndex = event.args.sort_index;
break;
case "trace_buffer_overflowed":
case 'trace_buffer_overflowed':
this.process(pid).traceBufferOverflowedAt = event.args.overflowed_at_ts;
break;
case "thread_name":
case 'thread_name':
const threadName = event.args.name;
const thread = this.thread(pid, tid);
thread.name = threadName;
if (threadName === "CrRendererMain") {
if (threadName === 'CrRendererMain') {
this.process(pid).mainThread = thread;
}
else if (threadName === "ScriptStreamerThread") {
else if (threadName === 'ScriptStreamerThread') {
this.process(pid).scriptStreamerThread = thread;
}
break;
case "thread_sort_index":
case 'thread_sort_index':
this.thread(pid, tid).sortIndex = event.args.sort_index;
break;
case "IsTimeTicksHighResolution":
case 'IsTimeTicksHighResolution':
this.process(pid).isTimeTicksHighResolution = event.args.value;
break;
case "TraceConfig":
case 'TraceConfig':
this.process(pid).traceConfig = event.args.value;

@@ -202,0 +292,0 @@ break;

{
"name": "tracerbench",
"version": "1.0.0-alpha.9",
"version": "1.0.0-alpha.10",
"description": "Benchmark runner for trace metrics",

@@ -33,6 +33,8 @@ "keywords": [

"lint": "node scripts/lint",
"prepare": "tsc -b",
"test": "yarn lint && node dist/test/build.js && node dist/test/test.js",
"lint:opt": "tslint -p test -t stylish",
"prepare": "yarn build",
"test": "yarn lint && yarn test:build && yarn test:only && yarn test:pp",
"test:build": "node dist/test/build.js",
"test:only": "node dist/test/test.js",
"test:pp": "nyc --extension .ts mocha --forbid-only \"test/pp/*.test.ts\"",
"watch": "tsc -b -w",

@@ -42,17 +44,29 @@ "report": "Rscript bin/report.R test/results/results.json"

"dependencies": {
"@types/d3": "^5.7.0",
"@types/node": "*",
"array-binsearch": "^1.0.1",
"chrome-debugging-client": "^0.6.8"
"chalk": "^2.4.2",
"chrome-debugging-client": "^0.6.8",
"d3": "^5.7.0",
"silent-error": "^1.1.1",
"tslib": "^1.9.3"
},
"devDependencies": {
"@types/chai": "^4.1.7",
"@types/glob": "^7.1.1",
"@types/mocha": "^5.2.6",
"@types/tar": "^4.0.0",
"chai": "^4.2.0",
"finalhandler": "^1.1.1",
"jquery": "^3.3.1",
"mkdirp": "^0.5.1",
"mocha": "^6.0.2",
"nyc": "^13.1.0",
"tar": "^4.4.8",
"tslint": "^5.12.1",
"typescript": "^3.2.4"
"ts-node": "^8.0.3",
"tslint": "^5.14.0",
"typescript": "^3.3.4000"
},
"engine": "node >= 8",
"gitHead": "6fe758f18fa80b9a1617b10771adca8c061ef381"
"gitHead": "2c9278a98b205aa1305dacd8870055d2c87202a1"
}

@@ -1,59 +0,105 @@

# TracerBench
## TracerBench: Automated Chrome Tracing For Benchmarking
[![Build Status](https://travis-ci.org/TracerBench/tracerbench.svg?branch=master)](https://travis-ci.org/TracerBench/tracerbench)
TracerBench is a benchmarking tool for benchmarking web applications by automating chrome traces then extracting a metric from them, while controlling that each sample is independent.
TracerBench: a web application benchmarking tool providing clear, usable insights for performance regressions with continuous integration hooks. This includes Response, Animation, Idle, and Load analysis, among others by automating chrome traces, controlling that each sample is independent and extracting metrics from them. TracerBench is focused on getting a low variance for a metric across many samples versus getting a hard to replicate performance report. At its core, TracerBench is framework agnostic, though it will have tight Ember.js integrations out-of-the-box.
Motivation, one trace varies to much to detect regressions in small changes to an app unless the effect size is very large. Additionally, most statistical tests I know of assume sample independence which given caching like Chrome's v8 caching is quite difficult to meet.
# Motivation
It is similar to [Telemetry](https://github.com/catapult-project/catapult/blob/master/telemetry/docs/run_benchmarks_locally.md) which is used to benchmark chromium.
There’s currently a gap in performance analysis tooling for web applications, especially for Ember Applications. Developers today struggle to quickly find and analyze performance regressions which would empower them to make quick, iterative changes within their local development environment. Regressions need to be automatically uncovered, propagated and reported with both regression size and actionable data that explains the problem to maximize value and usefulness for the developer.
It is similar to [Lighthouse](https://github.com/GoogleChrome/lighthouse) which also automates tracing then extracting metrics. TracerBench is focused on getting a low variance for a metric across many samples versus getting a hard to replicate performance report. Lighthouse enables many disabled-by-default tracing categories and tracerbench can be run without any disabled-by-default and minimal impact on the application.
A single trace varies far to much to detect regressions in small changes to an app unless the effect size is very large. Additionally, most statistical tests assume sample independence which given caching like Chrome's v8 caching is quite difficult to meet.
## Basic Usage
TracerBench has been greatly inspired by the Chromium benchmark tool [Telemetry](https://github.com/catapult-project/catapult/blob/master/telemetry/docs/run_benchmarks_locally.md).
The most basic benchmark is the `InitialRenderBenchmark`.
When comparing TracerBench to [Lighthouse](https://github.com/GoogleChrome/lighthouse). The primary difference is TracerBench is focused on getting a low variance for a metric across many samples versus getting a hard to replicate performance report. Lighthouse enables many "disabled-by-default" tracing categories while TracerBench can be instrumented without any "disabled-by-default" and with minimal impact on your application; as such TracerBench instrumentation can be "check-in" and left in your application without worry of negative performance impacts.
```js
import { InitialRenderBenchmark, Runner } from "tracerbench";
# CLI
let control = new InitialRenderBenchmark({
name: "control",
url: "http://localhost:8001/",
endMarker: "renderEnd",
browser: {
type: "canary"
}
});
The recommended way of consuming TracerBench is via the (TracerBench-CLI)[https://github.com/TracerBench/tracerbench/tree/master/packages/cli]
let experiment = new InitialRenderBenchmark({
name: "experiment",
url: "http://localhost:8002/",
endMarker: "renderEnd",
browser: {
type: "canary"
}
});
# Ambient Noise
let runner = new Runner([control, experiment]);
runner
.run(50)
.then(result => {
console.log(result);
})
.catch(err => {
console.error(err);
process.exit(1);
});
When running a TracerBench recording command its exceptionally important to reduce ambient noise that could negatively impact the reliability of the test results. TL;DR don't just jump into leveraging TracerBench without first performing below.
### Mitigating Ambient Noise
As a general rule of thumb to "zero-out" your environment its recommended you close/exit:
- all running applications other than those strictly needed to run a test, (osx menu bar & dock)
- software updates, file syncing, browser-tabs, (osx spotlight)
- when manually running tests and _not_ using the default headless chrome. be sure to exit all browser extensions
### Testing Ambient Noise
Assuming the pre-req mitigations above are complete, to test the ambient noise of your environment you can run and measure a few A/A tests. For example the control against the control. The results of which should all be near identical with no significant result.
# CLI Workflow
Assuming the TracerBench-CLI is globally [installed](https://github.com/TracerBench/tracerbench/blob/master/packages/cli/README.md#usage) and you are leveraging the optional config file [tb-config](https://github.com/TracerBench/tracerbench/blob/master/packages/cli/README.md#optional-config).
### Basic Example of benchmarking for timings
1. Start by having TracerBench record a HAR:
```s
$ tracerbench create-archive --url http://localhost:8000
...
✔ DevTools listening on ws://<address>
✔ { timestamp: 241968.79908 }
✔ HAR successfully generated from http://localhost:8000 and available here: ./trace.har
✔ Cookies successfully generated and available here: ./cookies.json
```
In the app you must place a marker to let TracerBench know that you are done rendering to DOM, it searches forward from this to find the next paint event. This is done by using a `performance.mark` function call.
2. Now have TracerBench record a Trace of that HAR:
```s
$ tracerbench trace --url http://localhost:8000 --har ./trace.har
...
✔ DevTools listening on ws://<address>
...
✔ starting trace
✔ navigating to http://localhost:8000
...
✔ stopping trace
✔ Trace JSON file successfully generated and available here: ./trace.json
```
3. Now that you have "trace.har" and "trace.json" files you can benchmark for the list of timings:
```s
$ tracerbench timeline:show --urlOrFrame http://localhost:8000
...
✔ Timings: 0.00 ms navigationStart
✔ Timings: 0.29 ms fetchStart
✔ Timings: 14.75 ms responseEnd
✔ Timings: 16.56 ms unloadEventStart
✔ Timings: 16.58 ms unloadEventEnd
✔ Timings: 16.91 ms domLoading
✔ Timings: 17.24 ms CommitLoad 14D19FA88C4BD379EA6C8D2BBEA9F939 http://localhost:8000/
✔ Timings: 362.58 ms domInteractive
✔ Timings: 362.64 ms domContentLoadedEventStart
✔ Timings: 363.22 ms domContentLoadedEventEnd
✔ Timings: 400.03 ms domComplete
✔ Timings: 400.06 ms loadEventStart
✔ Timings: 400.08 ms loadEventEnd
✔ Timings: 410.85 ms firstLayout
```
# Manual Workflow
### Instrument your web application
In your app you must place a marker to let TracerBench know that you are done rendering to the DOM, it searches forward from this to find the next paint event. This is done by using a `performance.mark` function call.
```js
function endTrace() {
// just before paint
requestAnimationFrame(function () {
requestAnimationFrame(() => {
// after paint
requestAnimationFrame(function () {
document.location.href = "about:blank";
requestAnimationFrame(() => {
document.location.href = 'about:blank';
});

@@ -64,3 +110,3 @@ });

renderMyApp();
performance.mark("renderEnd");
performance.mark('renderEnd');
endTrace();

@@ -70,1 +116,100 @@ ```

In the example above we would mark right after we render the app and then call an `endTrace` function that ensures that we schedule after paint that transitions to a blank page. Internally tracerbench will see this as the cue to start a new sample.
### Init Benchmark & Runner
The most common and recommended consumption of TracerBench is via the [TracerBench-CLI](https://github.com/TracerBench/tracerbench/tree/master/packages/cli). Optionally, TracerBench does however expose an API directly. The most basic consumption of this is via the `InitialRenderBenchmark` and `Runner` with the option to leverage[HAR-Remix](https://github.com/TracerBench/har-remix) to serve recorded HARs.
```js
import * as fs from 'fs-extra';
import { InitialRenderBenchmark, Runner } from 'tracerbench';
// the number of samples TracerBench will run. Higher sample count = more accurate. However the duration of the test will increase. The recommendation is somewhere between 30-60 samples.
const samplesCount = 40;
// the markers leveraged tuning your web application. additionally this assumes you have tuned your web application with the following marker "renderEnd" (see "Instrument your web application" above)
const markers = [{ start: 'renderEnd', label: 'renderEnd' }];
// the interface for optional chrome browser options is robust. typings available here: https://github.com/TracerBench/chrome-debugging-client/blob/ce0cdf3341fbbff2164a1d46bac16885d39deb15/lib/types.ts#L114-L128
const browser = {
type: 'canary',
additionalArguments: [
'--headless',
'--disable-gpu',
'--hide-scrollbars',
'--mute-audio',
'--v8-cache-options=none',
'--disable-cache',
'--disable-v8-idle-tasks',
'--crash-dumps-dir=./tmp'
]
};
// name, url, markers and browser are all required options
const control = new InitialRenderBenchmark({
// some name for your control app
name: 'control',
// serve your control tuned application locally or
// via HAR Remix
url: 'http://localhost:8001/',
markers,
browser,
// location to save only the control trace to
saveTraces: () => `./control-trace.json`
});
const experiment = new InitialRenderBenchmark({
name: 'experiment',
url: 'http://localhost:8002/',
markers,
browser,
// location to save only the experiment trace to
saveTraces: () => `./experiment-trace.json`
});
// the runner uses the config of each benchmark to test against
// the output of which
const runner = new Runner([control, experiment]);
runner
.run(samplesCount)
.then(results => {
console.log(results);
// optionally output the results using fs to a path of your choice
// now its time for some statistical analysis (see "Statistical Analysis")
fs.writeFileSync(`./trace-results.json`, JSON.stringify(results, null, 2));
})
.catch(err => {
console.error(err);
process.exit(1);
});
```
# Trace-Results
The typings for "trace-results.json" is as follows:
- [samples: IITerationSample](https://github.com/TracerBench/tracerbench/blob/0508e9867b8bb8624739e16f0e812211a8346cc1/packages/tracerbench/src/benchmarks/initial-render-metric.ts#L73-L106)
- [phases: IPhaseSample](https://github.com/TracerBench/tracerbench/blob/0508e9867b8bb8624739e16f0e812211a8346cc1/packages/tracerbench/src/benchmarks/initial-render-metric.ts#L126-L141)
- [gc: IV8GCSample](https://github.com/TracerBench/tracerbench/blob/0508e9867b8bb8624739e16f0e812211a8346cc1/packages/tracerbench/src/benchmarks/initial-render-metric.ts#L39-L46)
- [blinkGC: IBlinkGCSample](https://github.com/TracerBench/tracerbench/blob/0508e9867b8bb8624739e16f0e812211a8346cc1/packages/tracerbench/src/benchmarks/initial-render-metric.ts#L48-L51)
- [runtimeCallStats?: IRuntimeCallStats](https://github.com/TracerBench/tracerbench/blob/0508e9867b8bb8624739e16f0e812211a8346cc1/packages/tracerbench/src/benchmarks/initial-render-metric.ts#L53-L71)
```ts
[{
"meta": {
"browserVersion": string,
"cpus": string[]
},
"samples": IITerationSample[{
"duration": number,
"js": number,
"phases": IPhaseSample[],
"gc": IV8GCSample[],
"blinkGC": IBlinkGCSample[],
"runtimeCallStats": IRuntimeCallStats
}],
"set": string
}]
```
# Statistical Analysis
Assuming you have the output results ("trace-results.json") from your TracerBench run, its time to perform statistical analysis on the [Trace-Results](#Trace-Results) JSON file.
TracerBench does not currently expose an API to manually handle stat-analysis, however an industry standard is leveraging [SciPy](http://www.numpy.org/).

@@ -1,3 +0,3 @@

import * as Domains from "chrome-debugging-client/dist/protocol/tot";
import * as Trace from "./trace/index";
import * as Domains from 'chrome-debugging-client/dist/protocol/tot';
import * as Trace from './trace/index';
export { Domains, Trace };

@@ -12,7 +12,7 @@ export {

IRuntimeCallStat
} from "./benchmarks/initial-render-metric";
} from './benchmarks/initial-render-metric';
export {
InitialRenderBenchmark,
IInitialRenderBenchmarkParams
} from "./benchmarks/initial-render";
} from './benchmarks/initial-render';
export {

@@ -24,5 +24,12 @@ Benchmark,

BrowserOptions
} from "./benchmark";
export { Runner, IBenchmark } from "./runner";
export { ITab } from "./tab";
export * from "./util";
} from './benchmark';
export { Runner, IBenchmark } from './runner';
export { ITab } from './tab';
export {
analyze,
harTrace,
loadTrace,
liveTrace,
networkConditions
} from './trace';
export * from './util';

@@ -5,3 +5,3 @@ import {

TRACE_EVENT_PHASE_METADATA
} from "./trace_event";
} from './trace_event';

@@ -8,0 +8,0 @@ export default class Bounds {

@@ -1,5 +0,10 @@

export { default as Bounds } from "./bounds";
export { default as Process } from "./process";
export { default as Trace } from "./trace";
export { default as Thread } from "./thread";
export * from "./trace_event";
export { default as Bounds } from './bounds';
export { default as Process } from './process';
export { default as Trace } from './trace';
export { default as Thread } from './thread';
export { analyze } from './analyze';
export { harTrace } from './archive_trace';
export { loadTrace } from './load_trace';
export { liveTrace } from './live_trace';
export { networkConditions } from './conditions';
export * from './trace_event';

@@ -1,4 +0,4 @@

import Bounds from "./bounds";
import Thread from "./thread";
import { ITraceEvent } from "./trace_event";
import Bounds from './bounds';
import Thread from './thread';
import { ITraceEvent } from './trace_event';

@@ -5,0 +5,0 @@ export default class Process {

@@ -1,3 +0,3 @@

import Bounds from "./bounds";
import { ITraceEvent } from "./trace_event";
import Bounds from './bounds';
import { ITraceEvent } from './trace_event';

@@ -4,0 +4,0 @@ export default class Thread {

@@ -1,27 +0,27 @@

export const TRACE_EVENT_PHASE_BEGIN = "B";
export const TRACE_EVENT_PHASE_END = "E";
export const TRACE_EVENT_PHASE_COMPLETE = "X";
export const TRACE_EVENT_PHASE_INSTANT = "I";
export const TRACE_EVENT_PHASE_ASYNC_BEGIN = "S";
export const TRACE_EVENT_PHASE_ASYNC_STEP_INTO = "T";
export const TRACE_EVENT_PHASE_ASYNC_STEP_PAST = "p";
export const TRACE_EVENT_PHASE_ASYNC_END = "F";
export const TRACE_EVENT_PHASE_NESTABLE_ASYNC_BEGIN = "b";
export const TRACE_EVENT_PHASE_NESTABLE_ASYNC_END = "e";
export const TRACE_EVENT_PHASE_NESTABLE_ASYNC_INSTANT = "n";
export const TRACE_EVENT_PHASE_FLOW_BEGIN = "s";
export const TRACE_EVENT_PHASE_FLOW_STEP = "t";
export const TRACE_EVENT_PHASE_FLOW_END = "f";
export const TRACE_EVENT_PHASE_METADATA = "M";
export const TRACE_EVENT_PHASE_COUNTER = "C";
export const TRACE_EVENT_PHASE_SAMPLE = "P";
export const TRACE_EVENT_PHASE_CREATE_OBJECT = "N";
export const TRACE_EVENT_PHASE_SNAPSHOT_OBJECT = "O";
export const TRACE_EVENT_PHASE_DELETE_OBJECT = "D";
export const TRACE_EVENT_PHASE_MEMORY_DUMP = "v";
export const TRACE_EVENT_PHASE_MARK = "R";
export const TRACE_EVENT_PHASE_CLOCK_SYNC = "c";
export const TRACE_EVENT_PHASE_ENTER_CONTEXT = "(";
export const TRACE_EVENT_PHASE_LEAVE_CONTEXT = ")";
export const TRACE_EVENT_PHASE_LINK_IDS = "=";
export const TRACE_EVENT_PHASE_BEGIN = 'B';
export const TRACE_EVENT_PHASE_END = 'E';
export const TRACE_EVENT_PHASE_COMPLETE = 'X';
export const TRACE_EVENT_PHASE_INSTANT = 'I';
export const TRACE_EVENT_PHASE_ASYNC_BEGIN = 'S';
export const TRACE_EVENT_PHASE_ASYNC_STEP_INTO = 'T';
export const TRACE_EVENT_PHASE_ASYNC_STEP_PAST = 'p';
export const TRACE_EVENT_PHASE_ASYNC_END = 'F';
export const TRACE_EVENT_PHASE_NESTABLE_ASYNC_BEGIN = 'b';
export const TRACE_EVENT_PHASE_NESTABLE_ASYNC_END = 'e';
export const TRACE_EVENT_PHASE_NESTABLE_ASYNC_INSTANT = 'n';
export const TRACE_EVENT_PHASE_FLOW_BEGIN = 's';
export const TRACE_EVENT_PHASE_FLOW_STEP = 't';
export const TRACE_EVENT_PHASE_FLOW_END = 'f';
export const TRACE_EVENT_PHASE_METADATA = 'M';
export const TRACE_EVENT_PHASE_COUNTER = 'C';
export const TRACE_EVENT_PHASE_SAMPLE = 'P';
export const TRACE_EVENT_PHASE_CREATE_OBJECT = 'N';
export const TRACE_EVENT_PHASE_SNAPSHOT_OBJECT = 'O';
export const TRACE_EVENT_PHASE_DELETE_OBJECT = 'D';
export const TRACE_EVENT_PHASE_MEMORY_DUMP = 'v';
export const TRACE_EVENT_PHASE_MARK = 'R';
export const TRACE_EVENT_PHASE_CLOCK_SYNC = 'c';
export const TRACE_EVENT_PHASE_ENTER_CONTEXT = '(';
export const TRACE_EVENT_PHASE_LEAVE_CONTEXT = ')';
export const TRACE_EVENT_PHASE_LINK_IDS = '=';

@@ -83,9 +83,9 @@ export type TRACE_EVENT_PHASE_BEGIN = typeof TRACE_EVENT_PHASE_BEGIN;

export const TRACE_EVENT_SCOPE_NAME_GLOBAL: TRACE_EVENT_SCOPE_NAME_GLOBAL = "g";
export const TRACE_EVENT_SCOPE_NAME_GLOBAL: TRACE_EVENT_SCOPE_NAME_GLOBAL = 'g';
export const TRACE_EVENT_SCOPE_NAME_PROCESS: TRACE_EVENT_SCOPE_NAME_PROCESS =
"p";
export const TRACE_EVENT_SCOPE_NAME_THREAD: TRACE_EVENT_SCOPE_NAME_THREAD = "t";
export type TRACE_EVENT_SCOPE_NAME_GLOBAL = "g";
export type TRACE_EVENT_SCOPE_NAME_PROCESS = "p";
export type TRACE_EVENT_SCOPE_NAME_THREAD = "t";
'p';
export const TRACE_EVENT_SCOPE_NAME_THREAD: TRACE_EVENT_SCOPE_NAME_THREAD = 't';
export type TRACE_EVENT_SCOPE_NAME_GLOBAL = 'g';
export type TRACE_EVENT_SCOPE_NAME_PROCESS = 'p';
export type TRACE_EVENT_SCOPE_NAME_THREAD = 't';
export type TRACE_EVENT_SCOPE =

@@ -96,2 +96,27 @@ | TRACE_EVENT_SCOPE_NAME_GLOBAL

export const enum TRACE_EVENT_NAME {
TRACING_STARTED_IN_PAGE = 'TracingStartedInPage',
PROFILE = 'Profile',
PROFILE_CHUNK = 'ProfileChunk',
CPU_PROFILE = 'CpuProfile',
V8_EXECUTE = 'V8.Execute'
}
export const enum PROCESS_NAME {
BROWSER = 'Browser',
RENDERER = 'Renderer',
GPU = 'GPU Process'
}
export const enum TRACE_METADATA_NAME {
PROCESS_NAME = 'process_name',
PROCESS_LABELS = 'process_labels',
PROCESS_SORT_INDEX = 'process_sort_index',
PROCESS_UPTIME_SECONDS = 'process_uptime_seconds',
THREAD_NAME = 'thread_name',
THREAD_SORT_INDEX = 'thread_sort_index',
NUM_CPUS = 'num_cpus',
TRACE_BUFFER_OVERFLOWED = 'trace_buffer_overflowed'
}
/*

@@ -157,3 +182,3 @@ StringAppendF(out, "{\"pid\":%i,\"tid\":%i,\"ts\":%" PRId64

*/
args: { [key: string]: any } | "__stripped__";
args: { [key: string]: any } | '__stripped__';

@@ -227,1 +252,131 @@ /**

}
export interface ICpuProfile {
nodes: ICpuProfileNode[];
/** startTime in microseconds of CPU profile */
startTime: number;
/** node id of node that was sampled */
samples: number[];
/** delta from when the profile started to when the sample was taken in microseconds */
timeDeltas: number[];
}
export interface ICpuProfileNode {
id: number;
callFrame: ICallFrame;
children?: number[];
positionTicks?: {
line: number;
ticks: number;
};
}
export interface IProfileEvent extends ITraceEvent {
ph: TRACE_EVENT_PHASE_SAMPLE;
name: TRACE_EVENT_NAME.PROFILE;
args: {
data: {
startTime: number;
};
};
id: string;
}
export interface IProfileChunkEvent extends ITraceEvent {
ph: TRACE_EVENT_PHASE_SAMPLE;
name: TRACE_EVENT_NAME.PROFILE_CHUNK;
args: {
data: IProfileChunk;
};
id: string;
}
export interface IProfileChunk {
cpuProfile: {
nodes: IProfileNode[];
samples: number[];
};
timeDeltas: number[];
}
export interface IProfileNode {
id: number;
parent?: number;
callFrame: ICallFrame;
}
export interface ICallFrame {
functionName: string;
scriptId: string | number;
url: string;
lineNumber: number;
columnNumber: number;
}
export interface ICpuProfileEvent extends ITraceEvent {
ph: TRACE_EVENT_PHASE_INSTANT;
name: TRACE_EVENT_NAME.CPU_PROFILE;
args: {
data: {
cpuProfile: ICpuProfile;
};
};
}
export interface ICpuProfile {
nodes: ICpuProfileNode[];
/**
* startTime in microseconds of CPU profile
*/
startTime: number;
endTime: number;
/**
* id of root node
*/
samples: number[];
/**
* offset from startTime if first or previous time
*/
timeDeltas: number[];
duration: number;
}
export const enum FUNCTION_NAME {
ROOT = '(root)',
PROGRAM = '(program)',
IDLE = '(idle)',
GC = '(garbage collector)'
}
export interface ICpuProfileNode {
id: number;
callFrame: ICallFrame;
children?: number[];
positionTicks?: {
line: number;
ticks: number;
};
sampleCount: number;
min: number;
max: number;
total: number;
self: number;
}
export interface ISample {
delta: number;
timestamp: number;
prev: ISample | null;
next: ISample | null;
node: ICpuProfileNode;
}

@@ -1,7 +0,21 @@

import binsearch from "array-binsearch";
import Bounds from "./bounds";
import Process from "./process";
import Thread from "./thread";
/* tslint:disable:variable-name */
/* tslint:disable:no-console */
/* tslint:disable:no-bitwise */
import binsearch from 'array-binsearch';
import Bounds from './bounds';
import Process from './process';
import Thread from './thread';
import CpuProfile from './cpu-profile';
import { isRenderEnd, isRenderStart } from './render-events';
import {
ICpuProfile,
ICpuProfileEvent,
ICpuProfileNode,
IProfileChunkEvent,
IProfileEvent,
IProfileNode,
PROCESS_NAME,
ITraceEvent,

@@ -11,7 +25,13 @@ TRACE_EVENT_PHASE_BEGIN,

TRACE_EVENT_PHASE_END,
TRACE_EVENT_PHASE_METADATA
} from "./trace_event";
TRACE_EVENT_PHASE_METADATA,
TRACE_EVENT_PHASE_SAMPLE,
TRACE_EVENT_PHASE_INSTANT
} from './trace_event';
import traceEventComparator from "./trace_event_comparator";
import traceEventComparator from './trace_event_comparator';
interface IPartialCpuProfile extends ICpuProfile {
nodes: Array<ICpuProfileNode & IProfileNode>;
}
export default class Trace {

@@ -30,2 +50,22 @@ public processes: Process[] = [];

private lastTracingStartedInPageEvent?: ITraceEvent;
private _cpuProfile?: ICpuProfile;
private profileMap = new Map<
string,
{
pid: number;
tid: number;
cpuProfile: IPartialCpuProfile;
}
>();
public cpuProfile(min: number, max: number): CpuProfile {
const { _cpuProfile } = this;
if (_cpuProfile === undefined) {
console.trace('public cpuProfile');
throw new Error('trace is missing CpuProfile');
}
return new CpuProfile(_cpuProfile, this.events, min, max);
}
public process(pid: number): Process {

@@ -50,7 +90,91 @@ let process = this.findProcess(pid);

public cpuProfileBuildModel(event: any) {
if (
event.ph === TRACE_EVENT_PHASE_INSTANT &&
event.cat === 'disabled-by-default-devtools.timeline'
) {
if (event.name === 'CpuProfile') {
this._cpuProfile = (event as ICpuProfileEvent).args.data.cpuProfile;
} else if (event.name === 'TracingStartedInPage') {
this.lastTracingStartedInPageEvent = event;
}
} else if (event.ph === TRACE_EVENT_PHASE_SAMPLE) {
if (event.name === 'Profile') {
const profile = event as IProfileEvent;
this.profileMap.set(profile.id, {
pid: profile.pid,
tid: profile.tid,
cpuProfile: {
startTime: profile.args.data.startTime,
endTime: 0,
duration: 0,
nodes: [] as Array<ICpuProfileNode & IProfileNode>,
samples: [] as number[],
timeDeltas: [] as number[]
}
});
} else if (event.name === 'ProfileChunk') {
const profileChunk = event as IProfileChunkEvent;
const profileEntry = this.profileMap.get(profileChunk.id)!;
if (profileChunk.args.data.cpuProfile.nodes) {
profileChunk.args.data.cpuProfile.nodes.forEach((node: any) => {
profileEntry.cpuProfile.nodes.push(
Object.assign(node, {
sampleCount: 0,
min: 0,
max: 0,
total: 0,
self: 0
})
);
});
}
profileEntry.cpuProfile.samples.push(
...profileChunk.args.data.cpuProfile.samples
);
profileEntry.cpuProfile.timeDeltas.push(
...profileChunk.args.data.timeDeltas
);
}
}
// determine main process
if (this.lastTracingStartedInPageEvent) {
// if this was recorded with the Performance tab, this should be the main process
this.mainProcess = this.process(this.lastTracingStartedInPageEvent.pid);
} else {
// fallback to Renderer process with most events
this.mainProcess = this.processes
.filter(p => p.name === PROCESS_NAME.RENDERER)
.reduce((a, b) => (b.events.length > a.events.length ? b : a));
}
for (const profileEntry of this.profileMap.values()) {
if (
profileEntry.pid === this.mainProcess.id &&
profileEntry.tid === this.mainProcess.mainThread!.id
) {
this._cpuProfile = profileEntry.cpuProfile;
const { nodes } = profileEntry.cpuProfile;
const nodeMap = new Map<number, typeof nodes[0]>();
nodes.forEach(node => nodeMap.set(node.id, node));
nodes.forEach(node => {
if (node.parent !== undefined) {
const parent = nodeMap.get(node.parent)!;
if (parent.children) {
parent.children.push(node.id);
} else {
parent.children = [node.id];
}
}
});
break;
}
}
}
public buildModel() {
const { events } = this;
if (this.stack.length > 0) {
/* tslint:disable:no-console */
console.error("trace has incomplete B phase events");
console.error('trace has incomplete B phase events');
this.stack.length = 0;

@@ -62,2 +186,3 @@ }

process.addEvent(event);
this.cpuProfileBuildModel(event);
}

@@ -91,3 +216,3 @@ }

private addEvent(event: ITraceEvent) {
if (event.ph === TRACE_EVENT_PHASE_END) {
if (event.ph === TRACE_EVENT_PHASE_END || isRenderEnd(event)) {
this.endEvent(event);

@@ -99,3 +224,2 @@ return;

if (index < 0) {
/* tslint:disable:no-bitwise */
index = ~index;

@@ -111,3 +235,3 @@ } else {

}
if (event.ph === TRACE_EVENT_PHASE_BEGIN) {
if (event.ph === TRACE_EVENT_PHASE_BEGIN || isRenderStart(event)) {
this.stack.push(event);

@@ -141,11 +265,11 @@ }

}
throw new Error("could not find matching B phase for E phase event");
throw new Error('could not find matching B phase for E phase event');
}
private completeEvent(begin: ITraceEvent, end: ITraceEvent) {
let args: { [key: string]: any } | "__stripped__" = "__stripped__";
if (typeof begin.args === "object" && begin.args !== null) {
let args: { [key: string]: any } | '__stripped__' = '__stripped__';
if (typeof begin.args === 'object' && begin.args !== null) {
args = Object.assign({}, begin.args);
}
if (typeof end.args === "object" && end.args !== null) {
if (typeof end.args === 'object' && end.args !== null) {
args = Object.assign(args === undefined ? {} : args, end.args);

@@ -168,3 +292,3 @@ }

const index = binsearch(events, begin, traceEventComparator);
events[index] = complete;
events.splice(index, 0, complete);
}

@@ -174,47 +298,47 @@

const { pid, tid } = event;
if (event.args === "__stripped__") {
if (event.args === '__stripped__') {
return;
}
switch (event.name) {
case "num_cpus":
case 'num_cpus':
this.numberOfProcessors = event.args.number;
break;
case "process_name":
case 'process_name':
const processName: string = event.args.name;
const process = this.process(pid);
process.name = processName;
if (processName === "GPU Process") {
if (processName === 'GPU Process') {
this.gpuProcess = process;
} else if (processName === "Browser") {
} else if (processName === 'Browser') {
this.browserProcess = process;
} else if (processName === "Renderer") {
} else if (processName === 'Renderer') {
this.rendererProcesses.push(process);
}
break;
case "process_labels":
case 'process_labels':
this.process(pid).labels = event.args.labels;
break;
case "process_sort_index":
case 'process_sort_index':
this.process(pid).sortIndex = event.args.sort_index;
break;
case "trace_buffer_overflowed":
case 'trace_buffer_overflowed':
this.process(pid).traceBufferOverflowedAt = event.args.overflowed_at_ts;
break;
case "thread_name":
case 'thread_name':
const threadName: string = event.args.name;
const thread = this.thread(pid, tid);
thread.name = threadName;
if (threadName === "CrRendererMain") {
if (threadName === 'CrRendererMain') {
this.process(pid).mainThread = thread;
} else if (threadName === "ScriptStreamerThread") {
} else if (threadName === 'ScriptStreamerThread') {
this.process(pid).scriptStreamerThread = thread;
}
break;
case "thread_sort_index":
case 'thread_sort_index':
this.thread(pid, tid).sortIndex = event.args.sort_index;
break;
case "IsTimeTicksHighResolution":
case 'IsTimeTicksHighResolution':
this.process(pid).isTimeTicksHighResolution = event.args.value;
break;
case "TraceConfig":
case 'TraceConfig':
this.process(pid).traceConfig = event.args.value;

@@ -221,0 +345,0 @@ break;

@@ -6,2 +6,2 @@ {

}
}
}

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc