UNPKG

3rd-party-cpu-abuser

Version:

From a Chrome Dev Tools recorded timeline saved as JSON, this will help you identify quickly wich domain name eats most of the CPU

150 lines (121 loc) 4.5 kB
const fs = require('fs') const path = require('path') const Table = require('cli-table') // full external API exports.toTableConsole = options => toTableConsole(main(options)) exports.data = main // actual code // supported options : // file: (String) Path to a file // minTime: (Number) under this limit, the domain is ignored. Set to 0 to have them all function main(options) { try { fs.accessSync(options.file, fs.constants.R_OK) } catch (e) { throw 'Please provide a .json file to analyze' } // in ms : under this limit, we ignore the domain if('minTime' in options && !Number.isNaN(Number(options.minTime))) var ignoreTime = Number(options.minTime) else var ignoreTime = 150 const groupBy = options.groupBy || 'Subdomain' const startMark = options.startMark || null const endMark = options.endMark || null // GET DATA // uses https://github.com/paulirish/devtools-timeline-model const DevtoolsTimelineModel = require('devtools-timeline-model') const model = new DevtoolsTimelineModel(fs.readFileSync(options.file, 'utf8')) // Allow processing to be restricted by start/endMarks e.g. firstPaint let startTimeStamp = 0 let endTimeStamp = Infinity if(startMark !== null || endMark !== null) { const events = rootFrameEvents(model) // Start timestamp if (startMark !== null) { let startMarks = events.filter(entry => entry.name === startMark) if (startMarks.length === 0) { throw 'No entry for ' + startMark } if (startMarks.length > 1) { console.warn('Found more than one entry for ' + startMark + ', using first') } startTimeStamp = startMarks[0].startTime; } // End timestamp if (endMark !== null) { let endMarks = events.filter(entry => entry.name === endMark) if (endMarks.length === 0) { throw 'No entry for ' + endMark } if (endMarks.length > 1) { console.warn('Found more than one entry for ' + endMark + ', using first') } endTimeStamp = endMarks[0].startTime; } } // get all CPU time, already grouped by option (default FQDN) const topCosts = [...model.bottomUpGroupBy(groupBy, startTimeStamp, endTimeStamp).children.values()] const completeTime = topCosts.reduce((total, node) => total + node.totalTime, 0) // @TODO : test forced relayouts // https://github.com/paulirish/automated-chrome-profiling/blob/master/test-for-layout-thrashing.js /* console.log(model.frameModel()) const forcedReflowEvents = model.frameModel().frames() .filter( e => e.name == 'UpdateLayoutTree' || e.name == 'Layout') .filter( e => e.args && e.args.beginData && e.args.beginData.stackTrace && e.args.beginData.stackTrace.length) console.log('forcedReflowEvents', forcedReflowEvents) */ // ignore if < 150 ms, sort by time const filteredTopCosts = topCosts // rename the unassigned .map((node) => { node.id = node.id || '(no-domain)' return node }) // ignore small contributions .filter((node) => node.totalTime > ignoreTime) // .sort((b, a) => a.time - b.time) const data = { // First, some stats totalCPUTime: completeTime, totalDomains: topCosts.length, totalOffendersDomains: filteredTopCosts.length, // then the filtered data cpuTimePerDomain:{} } // associate a domain to a time filteredTopCosts.forEach((node) => data.cpuTimePerDomain[node.id] = node.totalTime ) return data } function toTableConsole(data) { const tableSummary = new Table() let output = '' tableSummary.push( { 'Total CPU busy time (ms)': data.totalCPUTime.toFixed(2) }, { 'Total number of domains': data.totalDomains }, { 'Number of big offenders': data.totalOffendersDomains } ) output += tableSummary.toString() output += '\n' const tableData = new Table({ head: ['CPU Time (ms)', 'domain name'] }) for(domainName in data.cpuTimePerDomain) { tableData.push([data.cpuTimePerDomain[domainName].toFixed(2), domainName]) } output += tableData.toString() return output } function rootFrameEvents(model) { const rootFrames = model.sandbox._timelineModel.rootFrames() if(rootFrames.length === 0) { throw 'No root frame in trace' } if(rootFrames.length > 1) { throw 'More than one root frame in trace' } const rootFrameId = rootFrames[0].frameId; return model.sandbox._timelineModel._mainThreadEvents.filter(entry => entry.args.frame === rootFrameId) }