Skip to content

Instantly share code, notes, and snippets.

@filipesilva
Created July 19, 2019 14:40
Show Gist options
  • Save filipesilva/9ff23fb136af2a192f42d3a2498dc6ef to your computer and use it in GitHub Desktop.
Save filipesilva/9ff23fb136af2a192f42d3a2498dc6ef to your computer and use it in GitHub Desktop.
Angular CLI profiling script
const fs = require("fs");
const path = require("path");
// Usage instructions:
// - open node_modules/@angular/cli/lib/init.js
// - replace the code below
// ```
// cli({
// cliArgs: process.argv.slice(2),
// inputStream: standardInput,
// outputStream: process.stdout,
// })
// ```
// with
// ```
// require('../../../../profile')(() => cli({
// cliArgs: process.argv.slice(2),
// inputStream: standardInput,
// outputStream: process.stdout,
// }))
// - flip the profilling flags on top of the profileWrapFn function to enable profiling those things.
// - run `npx ng <command>`, or `yarn ng <command>`, or a package.jsonscripts that calls `ng`, to
// use the profiling code.
// - open these profiles in Chrome via chrome://inspect/#devices, then click
// `Open dedicated DevTools for Node`, then load the profiles in the profiler tab (for .cpuprofile)
// or memory tab (for .heapsnapshot or .heapprofile).
const profileWrapFn = async fn => {
// Profiling flags.
const CPU_PROFILE = true;
const HEAP_SNAPSHOT = false;
const HEAP_PROFILE = false;
const REPORT_MEMORY = false;
const sampleInterval = 30 * 1000;
let before = async () => { };
let after = async () => { };
// Call fn after interval ms, wait for it to finish, then repeat.
// Returns a callback that calls fn one last time and stops repeating.
const rollingTimeout = (fn, interval) => {
let lastTimeout;
const chainedTimeout = () =>
lastTimeout = setTimeout(async () => {
// debugger;
await fn();
chainedTimeout();
}, interval);
chainedTimeout();
const stopCb = async () => {
clearTimeout(lastTimeout);
// debugger;
await fn();
};
return stopCb;
};
if (REPORT_MEMORY) {
// Report how much memory is used inside V8 every reportInterval.
const reportInterval = 1 * 1000;
const reportMemoryUsage = () => {
const used = process.memoryUsage().heapUsed / 1024 / 1024;
console.log(`\nAngular CLI is using approximately ${used} MB of memory.`);
}
setInterval(reportMemoryUsage, reportInterval);
}
// Check if we need to profile this run.
if (CPU_PROFILE || HEAP_SNAPSHOT || HEAP_PROFILE) {
// No colons ISO format, suitable for filenames.
const basicISONow = () => new Date().toISOString().replace(/[-.:]/g, '');
const inspector = require('inspector');
const promisify = require('util').promisify;
// https://chromedevtools.github.io/devtools-protocol/v8/HeapProfiler
const session = new inspector.Session();
// Take a single heap snapshot, and wait for it to be written to disk.
// These snapshots can be quite big and also take some time to write, so they shouldn't be
// written too frequently.
const writeHeapSnapshot = async () => {
const profilePath = path.resolve(process.cwd(), `NG_PROFILE_${basicISONow()}.heapsnapshot`);
console.error('\n@angular/cli: saving heap snapshot to', profilePath);
const fd = fs.openSync(profilePath, 'w');
session.on('HeapProfiler.addHeapSnapshotChunk', (m) => {
try {
fs.writeSync(fd, m.params.chunk);
} catch (e) {
// TODO: figure out why I sometimes get the errors below
// (node:1260) [EPERM] Error: EPERM: operation not permitted, write
}
});
await session.postPromise('HeapProfiler.takeHeapSnapshot');
fs.closeSync(fd);
console.error('\n@angular/cli: saved heap snapshot to', profilePath);
}
// Take a single heap snapshot, and wait for it to be written to disk.
const writeHeapProfile = async () => {
const { profile } = await session.postPromise('HeapProfiler.getSamplingProfile');
const profilePath = path.resolve(process.cwd(), `NG_PROFILE_${basicISONow()}.heapprofile`);
fs.writeFileSync(profilePath, JSON.stringify(profile));
console.error('\n@angular/cli: saved heap profile to', profilePath);
}
// Write a CPU profile, and wait for it to be written to disk.
const writeCPUProfile = async () => {
const { profile } = await session.postPromise('Profiler.stop');
const profilePath = path.resolve(process.cwd(), `NG_PROFILE_${basicISONow()}.cpuprofile`);
fs.writeFileSync(profilePath, JSON.stringify(profile));
console.error('\n@angular/cli: saved CPU profile to', profilePath);
}
// Adding a method because of https://github.com/nodejs/node/issues/13338#issuecomment-307165905.
session.postPromise = promisify(session.post);
session.connect();
if (CPU_PROFILE) {
console.error('\n@angular/cli: taking CPU profile');
// Start the CPU profiler.
before = async () => {
await session.postPromise('Profiler.enable');
await session.postPromise('Profiler.start');
};
// Save the CPU profile after execution has finished.
after = writeCPUProfile;
} else if (HEAP_SNAPSHOT) {
console.error('\n@angular/cli: taking heap snapshots');
// Create a heap snapshot every sampleInterval seconds.
let stopCb;
before = async () => stopCb = rollingTimeout(writeHeapSnapshot, sampleInterval);
after = async () => stopCb();
} else if (HEAP_PROFILE) {
console.error('\n@angular/cli: taking heap profiles');
// Start the heap profiler. Create a heap profile every sampleInterval seconds.
let stopCb;
before = async () => {
await session.postPromise('HeapProfiler.enable');
await session.postPromise('HeapProfiler.startSampling');
stopCb = rollingTimeout(writeHeapProfile, sampleInterval);
};
after = async () => stopCb();
}
}
await before();
const fnResult = await fn();
await after();
return fnResult;
}
module.exports = profileWrapFn;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment