UNPKG

flamegraph

Version:

Generates flamegraphs with Node.js or in the browser

163 lines (134 loc) 4.45 kB
'use strict'; var xtend = require('xtend') , format = require('util').format , colorMap = require('./color-map') function inspect(obj, depth) { console.error(require('util').inspect(obj, false, depth || 5, true)); } function oneDecimal(x) { return (Math.round(x * 10) / 10); } function htmlEscape(s) { return s .replace(/&/g, '&amp;') .replace(/</g, '&lt;') .replace(/>/g, '&gt;') } module.exports = /** * Extracts a context object from the parsed callgraph @see `stackparse.js`. * This context can then be used to generate the svg file via a template. * * @name contextify * @private * @function * @param {Object} parsed nodes * @param {Object} opts options that affect visual and how the nodes are filtered */ // Contextifier proto function contextify(parsed, opts) { var time = parsed.time , timeMax = opts.timemax , ypadTop = opts.fontsize * 4 // pad top, include title , ypadBottom = opts.fontsize * 2 + 10 // pad bottom, include labels , xpad = 10 // pad left and right , depthMax = 0 , frameHeight = opts.frameheight , paletteMap = {} if (timeMax < time && timeMax/time > 0.02) { console.error('Specified timemax %d is less than actual total %d, so it will be ignored', timeMax, time); timeMax = Infinity; } timeMax = Math.min(time, timeMax); var widthPerTime = (opts.imagewidth - 2 * xpad) / timeMax , minWidthTime = opts.minwidth / widthPerTime function markNarrowBlocks(nodes) { function mark(k) { var val = parsed.nodes[k]; if (typeof val.stime !== 'number') throw new Error('Missing start for ' + k); if ((val.etime - val.stime) < minWidthTime) { val.narrow = true; return; } val.narrow = false; depthMax = Math.max(val.depth, depthMax); } Object.keys(nodes).forEach(mark); } // NodeProcessor proto function processNode(node) { var func = node.func , depth = node.depth , etime = node.etime , stime = node.stime , factor = opts.factor , countName = opts.countname , isRoot = !func.length && depth === 0 ; if (isRoot) etime = timeMax; var samples = Math.round((etime - stime * factor) * 10) / 10 , samplesTxt = samples.toLocaleString() , pct , pctTxt , escapedFunc , name , sampleInfo if (isRoot) { name = 'all'; sampleInfo = format('(%s %s, 100%)', samplesTxt, countName); } else { pct = Math.round((100 * samples) / (timeMax * factor) * 10) / 10 pctTxt = pct.toLocaleString() escapedFunc = htmlEscape(func); name = escapedFunc; sampleInfo = format('(%s %s), %s%%)', samplesTxt, countName, pctTxt); } var x1 = oneDecimal(xpad + stime * widthPerTime) , x2 = oneDecimal(xpad + etime * widthPerTime) , y1 = oneDecimal(imageHeight - ypadBottom - (depth + 1) * frameHeight + 1) , y2 = oneDecimal(imageHeight - ypadBottom - depth * frameHeight) , chars = (x2 - x1) / (opts.fontsize * opts.fontwidth) , showText = false , text , text_x , text_y if (chars >= 3 ) { // enough room to display function name? showText = true; text = func.slice(0, chars); if (chars < func.length) text = text.slice(0, chars - 2) + '..'; text = htmlEscape(text); } return { name : name , search : name.toLowerCase() , samples : sampleInfo , rect_x : x1 , rect_y : y1 , rect_w : x2 - x1 , rect_h : y2 - y1 , rect_fill : colorMap(paletteMap, opts.colors, opts.hash, func) , text : text , text_x : x1 + (showText ? 3 : 0) , text_y : 3 + (y1 + y2) / 2 , narrow : node.narrow , func : htmlEscape(func) } } function processNodes(nodes) { var keys = Object.keys(nodes) , acc = new Array(keys.length); for (var i = 0; i < keys.length; i++) acc[i] = processNode(nodes[keys[i]]); return acc; } markNarrowBlocks(parsed.nodes); var imageHeight = (depthMax * frameHeight) + ypadTop + ypadBottom; var ctx = xtend(opts, { imageheight : imageHeight , xpad : xpad , titleX : opts.imagewidth / 2 , detailsY : imageHeight - (frameHeight / 2) }); ctx.nodes = processNodes(parsed.nodes); return ctx; }