UNPKG

hardhat

Version:

Hardhat is an extensible developer tool that helps smart contract developers increase productivity by reliably bringing together the tools they want.

255 lines (226 loc) 7.99 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.saveFlamegraph = exports.createFlamegraphHtmlFile = exports.profileToFlamegraph = void 0; const fs_1 = __importDefault(require("fs")); const errors_1 = require("./errors"); const task_profiling_1 = require("./task-profiling"); function profileToFlamegraph(profile) { (0, errors_1.assertHardhatInvariant)(profile.end !== undefined, `Formatting invalid task profile for ${profile.name}. No end was recorded.`); return { name: profile.name, // We assume this is a safe int, which is ok unless a task runs for months value: Number(profile.end - profile.start), children: profile.children.map((c) => profileToFlamegraph(c)), parallel: profile.parallel === true, }; } exports.profileToFlamegraph = profileToFlamegraph; /** * Merges compatible children of toFold and its children. * * Compatible in a traditional Flamegraph means having the same name. We * modified that notion and also require their `parallel` flag to have the same * value. This means that parallel and non-parallel calls to the same function * are shown with two Flamegraph "blocks". * * The parallel block shows the max running time, instead of the sum of them. **/ function foldFramegraph(toFold) { if (toFold.children.length === 0) { return { name: toFold.name, value: toFold.value, children: [], parallel: toFold.parallel, }; } const children = toFold.children.map((c) => foldFramegraph(c)); children.sort((a, b) => a.parallel === b.parallel ? 0 : b.parallel ? -1 : 1); children.sort((a, b) => a.name.localeCompare(b.name)); const foldedChildren = [children[0]]; const mergedChildren = new Set(); for (let i = 1; i < children.length; i++) { const latest = foldedChildren[foldedChildren.length - 1]; if (children[i].name === latest.name && children[i].parallel === latest.parallel) { if (latest.parallel) { latest.value = Math.max(latest.value, children[i].value); } else { latest.value += children[i].value; } latest.children.push(...children[i].children); mergedChildren.add(foldedChildren.length - 1); } else { foldedChildren.push(children[i]); } } for (const i of mergedChildren.values()) { foldedChildren[i] = foldFramegraph(foldedChildren[i]); } return { name: toFold.name, value: toFold.value, children: foldedChildren, parallel: toFold.parallel, }; } function createFlamegraphHtmlFile(flamegraph) { const content = getFlamegraphFileContent(foldFramegraph(flamegraph)); const path = "flamegraph.html"; fs_1.default.writeFileSync(path, content, { encoding: "utf8" }); return path; } exports.createFlamegraphHtmlFile = createFlamegraphHtmlFile; function getFlamegraphFileContent(flamegraph) { const data = JSON.stringify(foldFramegraph(flamegraph), undefined, 2); return `<!-- Based on d3-flamegraph's example. See its license in: https://github.com/spiermar/d3-flame-graph/blob/master/LICENSE --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"> <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/gh/spiermar/d3-flame-graph@2.0.3/dist/d3-flamegraph.css"> <style> /* Space out content a bit */ body { padding-top: 20px; padding-bottom: 20px; } /* Custom page header */ .header { padding-bottom: 20px; padding-right: 15px; padding-left: 15px; border-bottom: 1px solid #e5e5e5; } /* Make the masthead heading the same height as the navigation */ .header h3 { margin-top: 0; margin-bottom: 0; line-height: 40px; } /* Customize container */ .container { max-width: 990px; } </style> <title>Hardhat task flamegraph</title> </head> <body> <div class="container"> <div class="header clearfix"> <nav> <div class="pull-right"> <form class="form-inline" id="form"> <a class="btn" href="javascript: resetZoom();">Reset zoom</a> <a class="btn" href="javascript: clear();">Clear</a> <div class="form-group"> <input type="text" class="form-control" id="term"> </div> <a class="btn btn-primary" href="javascript: search();">Search</a> </form> </div> </nav> <h3 class="text-muted">Hardhat task flamegraph</h3> </div> <div id="chart"> </div> <hr> <div id="details"> </div> </div> <!-- D3.js --> <script src="https://d3js.org/d3.v4.min.js" charset="utf-8"></script> <!-- d3-tip --> <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/d3-tip/0.9.1/d3-tip.min.js"></script> <!-- d3-flamegraph --> <script type="text/javascript" src="https://cdn.jsdelivr.net/gh/spiermar/d3-flame-graph@2.0.3/dist/d3-flamegraph.min.js"></script> <script type="text/javascript"> const flameGraph = d3.flamegraph() .width(960) .cellHeight(18) .transitionDuration(750) .minFrameSize(5) .transitionEase(d3.easeCubic) .sort(true) .title("") .onClick(onClick) .differential(false) .selfValue(false); function label(d) { if (d.data.parallel) { return "(multiple parallel runs) task: " + d.data.name + ", max time: " + readableTime(d.data.value); } return "task: " + d.data.name + ", time: " + readableTime(d.data.value); } function readableTime(t) { const NANOSECONDS_TO_MILLISECONDS = 1_000_000; const NANOSECONDS_TO_SECONDS = 1_000_000_000; if (t < NANOSECONDS_TO_MILLISECONDS) { return t + "ns"; } if (t < NANOSECONDS_TO_SECONDS) { return (t / NANOSECONDS_TO_MILLISECONDS).toFixed(4) + "ms"; } return (t / NANOSECONDS_TO_SECONDS).toFixed(4) + "s"; } const tip = d3.tip() .direction("s") .offset([8, 0]) .attr('class', 'd3-flame-graph-tip') .html(label); flameGraph.tooltip(tip); const details = document.getElementById("details"); flameGraph.setDetailsElement(details); flameGraph.label(label); flameGraph.setColorMapper(function(d, originalColor) { if (d.highlight) { return '#E600E6'; } if (d.data.parallel) { return '#1478eb' } return "#EB5414" }); d3.select("#chart") .datum(${data}) .call(flameGraph); document.getElementById("form").addEventListener("submit", function(event){ event.preventDefault(); search(); }); function search() { const term = document.getElementById("term").value; flameGraph.search(term); } function clear() { document.getElementById('term').value = ''; flameGraph.clear(); } function resetZoom() { flameGraph.resetZoom(); } function onClick(d) { console.info("Clicked on " + d.data.name); } </script> </body> </html> `; } /** * Converts the TaskProfile into a flamegraph, saves it, and returns its path. */ function saveFlamegraph(profile) { (0, task_profiling_1.flagParallelChildren)(profile); const flamegraph = profileToFlamegraph(profile); return createFlamegraphHtmlFile(flamegraph); } exports.saveFlamegraph = saveFlamegraph; //# sourceMappingURL=flamegraph.js.map