Created
July 19, 2019 14:40
-
-
Save filipesilva/9ff23fb136af2a192f42d3a2498dc6ef to your computer and use it in GitHub Desktop.
Angular CLI profiling script
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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