diff --git a/app/src/components/callflowSingle.js b/app/src/components/callflowSingle.js index 398efaf7..a2c080de 100644 --- a/app/src/components/callflowSingle.js +++ b/app/src/components/callflowSingle.js @@ -436,7 +436,6 @@ export default { // Create a map for each dataset mapping the respective mean times. let map = {}; for (let module_name of module_list) { - console.log(module_name, this.$store.modules[this.selectedTargetDataset][module_name]) map[module_name] = this.$store.modules[this.selectedTargetDataset][module_name][this.$store.selectedMetric]["mean_time"]; } diff --git a/app/src/components/singleHistogram/singleHistogram.js b/app/src/components/singleHistogram/singleHistogram.js index 76bd4ec6..e7c8080b 100644 --- a/app/src/components/singleHistogram/singleHistogram.js +++ b/app/src/components/singleHistogram/singleHistogram.js @@ -12,6 +12,7 @@ import tpl from "../../html/histogram.html"; import ToolTip from "./tooltip"; import * as utils from "../utils"; import EventHandler from "../EventHandler"; +import { brush } from "d3"; export default { template: tpl, @@ -100,7 +101,6 @@ export default { this.binContainsProcID = temp[3]; this.logScaleBool = false; - this.$refs.ToolTip.init(this.svgID); this.xScale = d3.scaleBand() .domain(this.xVals) @@ -128,7 +128,8 @@ export default { d3.selectAll(".binRank").remove(); d3.selectAll(".lineRank").remove(); d3.selectAll(".tick").remove(); - // this.$refs.ToolTip.clear(); + d3.selectAll(".brush").remove(); + this.$refs.ToolTip.clear(); }, visualize(callsite) { @@ -139,6 +140,7 @@ export default { this.yAxis(); this.rankLineScale(); this.brushes(); + this.$refs.ToolTip.init(this.svgID); }, array_unique(arr) { @@ -235,6 +237,10 @@ export default { }; }, + sanitizeGroupProc(string) { + return string.replace("[", "").replace("]", ""); + }, + bars() { let self = this; this.svg.selectAll(".single-histogram-bar") @@ -252,6 +258,17 @@ export default { "stroke-width": "0.2px", "stroke": "#202020", }) + .style("z-index", 1) + .on("click", function(d, i) { + d3.select(this) + .attr("fill", self.$store.runtimeColor.highlight); + d3.selectAll(`.lineRank_${i}`) + .style("fill", "orange") + .style("fill-opacity", 1); + let groupProcStr = self.groupProcess(self.binContainsProcID[i]).string; + groupProcStr = this.sanitizeGroupProc(groupProcStr); + self.$refs.ToolTip.render(groupProcStr, d); + }) .on("mouseover", function (d, i) { d3.select(this) .attr("fill", self.$store.runtimeColor.highlight); @@ -259,6 +276,7 @@ export default { .style("fill", "orange") .style("fill-opacity", 1); let groupProcStr = self.groupProcess(self.binContainsProcID[i]).string; + groupProcStr = this.sanitizeGroupProc(groupProcStr); self.$refs.ToolTip.render(groupProcStr, d); }) .on("mouseout", function (d, i) { @@ -327,7 +345,8 @@ export default { const yAxis = d3.axisLeft(this.yScale) .ticks(10) .tickFormat((d, i) => { - return d; + if (d % 1 == 0) + return d; }); this.svg.append("text") @@ -367,8 +386,8 @@ export default { rankLineScale() { let rankCount = this.numOfRanks; - const ranklinescale = d3.scaleLinear() - .domain([0, rankCount - 1]) + this.ranklinescale = d3.scaleLinear() + .domain([0, rankCount]) .range([this.paddingFactor * this.padding.left, this.xAxisHeight]); this.freq.forEach((freqVal, idx) => { @@ -388,44 +407,31 @@ export default { let cumulativeBinSpace = 0; groupArray.forEach((group) => { - let line; + let start = 0, end = 0; if (group.length == 1) { - var start = group[0]; - var end = start + 1; - var topX1 = cumulativeBinSpace + binLocation; - var topX2 = cumulativeBinSpace + binLocation + (1) * widthPerRank; - - var botX3 = ranklinescale(start); - var botX4 = ranklinescale(start); - - var topY = this.boxHeight - this.histogramOffset; - var botY = this.boxHeight; - cumulativeBinSpace += (1) * widthPerRank; - - line = "M" + topX1 + " " + topY + - "L " + topX2 + " " + topY + - "L " + botX4 + " " + botY + - "L " + botX3 + " " + botY; - } else { - let start = group[0]; - let end = group[1]; - - let topX1 = cumulativeBinSpace + binLocation; - let topX2 = cumulativeBinSpace + (end - start + 1) * widthPerRank + binLocation; + start = group[0]; + end = start + 1; + } + else { + start = group[0]; + end = group[1] + 1; + } + + let topX1 = cumulativeBinSpace + binLocation + widthPerRank; + let topX2 = cumulativeBinSpace + (end - start + 1) * widthPerRank + binLocation; - let botX3 = ranklinescale(start); - let botX4 = ranklinescale(end); + let botX3 = this.ranklinescale(start); + let botX4 = this.ranklinescale(end); - let topY = this.boxHeight - this.histogramOffset; - let botY = this.boxHeight; + let topY = this.boxHeight - this.histogramOffset; + let botY = this.boxHeight; - cumulativeBinSpace += (end - start + 1) * widthPerRank; + cumulativeBinSpace += (end - start + 1) * widthPerRank; - line = "M" + topX1 + " " + topY + + const line = "M" + topX1 + " " + topY + "L " + topX2 + " " + topY + "L " + botX4 + " " + botY + "L " + botX3 + " " + botY; - } rankLinesG.append("path") .attr("d", line) @@ -439,7 +445,7 @@ export default { } }); - const rankLineAxis = d3.axisBottom(ranklinescale) + const rankLineAxis = d3.axisBottom(this.ranklinescale) .ticks(10) .tickFormat((d, i) => { if (d % 1 == 0) @@ -477,8 +483,8 @@ export default { this.brush = d3.brushX() .extent([ - [this.paddingFactor * this.padding.left, this.histogramHeight], - [this.paddingFactor * this.padding.left + this.xAxisHeight - (this.paddingFactor) * this.padding.left, this.histogramHeight + this.rankScaleHeight] + [this.paddingFactor * this.padding.left, this.yAxisHeight], + [this.paddingFactor * this.padding.left + this.xAxisHeight - (this.paddingFactor) * this.padding.left, this.yAxisHeight + this.rankScaleHeight] ]) .on("brush", this.brushing) .on("end", this.brushend); @@ -500,24 +506,27 @@ export default { brushing() { const brushScale = d3.scaleLinear() - .domain(this.xScale.domain()) + .domain([this.xScale.domain()[0], this.xScale.domain()[this.xScale.domain().length -1 ]]) .range(this.xScale.range()); let brushStart = d3.event.selection.map(brushScale.invert)[0]; let brushEnd = d3.event.selection.map(brushScale.invert)[1]; let brushPoints = this.xScale.domain().length; - this.localBrushStart = Math.floor(brushStart * brushPoints); - this.localBrushEnd = Math.ceil(brushEnd * brushPoints); + let brushMin = this.xScale.domain()[0]; + let brushMax = this.xScale.domain()[this.xScale.domain().length - 1]; + + this.localBrushStart = Math.floor((brushStart - brushMin)/(brushMax - brushMin) * brushPoints); + this.localBrushEnd = Math.ceil((brushEnd - brushMin)/(brushMax - brushMin) * brushPoints); // highlight rank lines that is brush - this.histogramSVG.selectAll(".binRank").attr("opacity", 0); + this.svg.selectAll(".binRank").attr("opacity", 0.5); for (let i = this.localBrushStart; i < this.localBrushEnd; i++) { - this.histogramSVG.selectAll(`.bin_${i}`).attr("opacity", 1); + this.svg.selectAll(`.bin_${i}`).attr("opacity", 1); } if (this.localBrushStart == this.localBrushEnd) { - this.histogramSVG.selectAll(".binRank").attr("opacity", 1); + this.svg.selectAll(".binRank").attr("opacity", 1); } }, @@ -525,6 +534,7 @@ export default { let self = this; const processIDList = []; for (let i = this.localBrushStart; i < this.localBrushEnd; i++) { + // console.log(self.binContainsProcID) if (self.binContainsProcID[i] != null) { const curList = self.binContainsProcID[i]; curList.forEach((processID) => { @@ -532,10 +542,11 @@ export default { }); } } - self.$socket.emit("split-rank", { + self.$socket.emit("split-mpi-rank", { "dataset": self.$store.selectedDataset, - "ids": processIDList + "ranks": processIDList }); + }, } }; \ No newline at end of file diff --git a/app/src/components/singleHistogram/tooltip.js b/app/src/components/singleHistogram/tooltip.js index 72676483..7376bd76 100644 --- a/app/src/components/singleHistogram/tooltip.js +++ b/app/src/components/singleHistogram/tooltip.js @@ -6,6 +6,7 @@ */ import * as d3 from "d3"; +import * as utils from "../utils"; export default { template: "", @@ -48,12 +49,29 @@ export default { render(data, node) { this.clear(); this.width = data.length * this.fontSize + 10 * this.fontSize; - var svgScale = d3.scaleLinear().domain([2, 11]).range([50, 150]); - console.log(d3.select("#" + this.parentID)); + const svgScale = d3.scaleLinear().domain([2, 11]).range([this.containerWidth, this.containerHeight]); this.mousePos = d3.mouse(d3.select("#" + this.parentID).node()); this.mousePosX = this.mousePos[0]; this.mousePosY = this.mousePos[1]; this.toolTipG.attr("height", svgScale(10) + "px"); + + this.node = node; + this.data = data; + this.addText("Processes (MPI ranks):" + this.data); + }, + + optimizeTextHeight(text) { + const measure = utils.measure(text); + const rows = measure.width/this.containerWidth; + + return { + "width": this.containerWidth, + "height": rows * measure.height + }; + }, + + addText(text) { + const measure = this.optimizeTextHeight(text); this.toolTipRect = this.toolTipG .append("rect") .attrs({ @@ -62,9 +80,8 @@ export default { "stroke": "black", "rx": "10px", "fill-opacity": 1, - "z-index": 100, - "width": this.containerWidth, - "height": this.containerHeight, + "width": measure.width + 20, + "height": measure.height, }) .attrs({ "x": () => { @@ -77,19 +94,9 @@ export default { "y": () => { return (this.mousePosY) + "px"; } - }); - this.node = node; - this.data = data; - this.processes(); - }, - - trunc(str, n) { - str = str.replace(//g, "proc "); - return (str.length > n) ? str.substr(0, n - 1) + "..." : str; - }, - + }) + .style("z-index", 2); - addText(text) { this.textCount += 1; this.toolTipText = this.toolTipG .append("text") @@ -108,13 +115,13 @@ export default { return (this.mousePosY) + 2 * this.offset + "px"; } }) - .text(text); + .style("z-index", 2) + .text(text) + .call(utils.textWrap, measure.width); }, processes() { let self = this; - this.addText("Processes (MPI ranks): " + this.data); - }, clear() { diff --git a/app/src/components/supergraph/encodings/tooltip.js b/app/src/components/supergraph/encodings/tooltip.js index 24e3014c..b09981d4 100644 --- a/app/src/components/supergraph/encodings/tooltip.js +++ b/app/src/components/supergraph/encodings/tooltip.js @@ -14,63 +14,92 @@ export default { components: {}, data: () => ({ - id: "", + id: "supernode-tooltip", textCount: 0, textxOffset: 20, textyOffset: 20, textPadding: 15, height: 200, - margin: 35 + margin: 35, + mousePosX: 0, + mousePosY: 0, + prevMousePosX: undefined, + prevMousePosY: undefined }), - sockets: { - tooltip(data) { - this.render(data); - }, - }, - methods: { init(id) { this.id = id; - this.toolTipDiv = d3.select("#" + this.id); - this.toolTipG = this.toolTipDiv.append("g"); - this.callgraphOverviewWidth = this.$store.viewWidth; - this.halfWidth = this.callgraphOverviewWidth / 2; + const toolTipDiv = d3.select("#" + this.id); + this.toolTipG = toolTipDiv.append("g"); }, + /** + * Set the position for the tooltip SVG. (x-positioning) + */ positionX() { let ret = 0; - if (this.mousePosX >= this.halfWidth) { - ret = this.mousePosX - this.halfWidth + this.textxOffset; - } - else { - ret = this.halfWidth - this.mousePosX + this.textxOffset; + if (this.mousePosX >= this.$store.viewWidth / 2) { + ret = this.mousePosX + this.textxOffset; } + ret = this.mousePosX - this.textxOffset; return ret; }, - positionY(node) { - if (this.mousePosY < node.y) { - return node.y - this.mousePosY + node.height / 2; + /** + * Set the position for the tooltip SVG. (y-positioning) + */ + positionY() { + let ret = 0; + if (this.mousePosY >= this.$store.viewHeight / 2) { + ret = this.mousePosY + this.textyOffset; } - return this.mousePosY - node.y + node.height / 2; + ret = this.mousePosY - this.textyOffset; + return ret; }, + /** + * The below function decides if we have to render the tooltip or not. + * + * @param {Graph} graph + * @param {*} node + */ visualize(graph, node) { - this.clear(); - this.xOffset = this.positionX(); - this.yOffset = this.positionY(node); - this.nodeHeight = node.height; - var svgScale = d3.scaleLinear().domain([2, 11]).range([50, 150]); + // Set current mouse position. this.mousePos = d3.mouse(d3.select("#" + this.id).node()); this.mousePosX = this.mousePos[0]; this.mousePosY = this.mousePos[1]; + + // Draw the tooltip again only if the distance is more than the width of the supernode. + // console.log(this.prevMousePosY, this.prevMousePosX, utils.distanceBtwnPoints(this.mousePosX, this.mousePosY, this.prevMousePosX, this.prevMousePosY)) + // if (this.prevMousePosX && this.prevMousePosY && utils.distanceBtwnPoints(this.mousePosX, this.mousePosY, this.prevMousePosX, this.prevMousePosY) > 0 ) { + this.clear(); + this.render(graph, node); + // } + + // Store the previous mouse positions to calculate the distance. + this.prevMousePosX = this.mousePosX; + this.prevMousePosY = this.mousePosY; + }, + + /** + * + * @param {*} graph + * @param {*} node + */ + render(graph, node) { + this.xOffset = this.positionX() + 40; + this.yOffset = this.positionY() + 40; + this.nodeHeight = node.height; + + const svgScale = d3.scaleLinear().domain([2, 11]).range([50, 150]); + this.toolTipG.attr("height", svgScale(10) + "px"); this.toolTipRect = this.toolTipG .append("rect") .attrs({ - "class": "toolTipContent", - "fill": "#e0e0e0", + "class": "tooltip-container", + "fill": "#fff", "stroke": "black", "rx": "10px", "fill-opacity": 1, @@ -81,22 +110,24 @@ export default { "x": this.xOffset, "y": this.yOffset }); - this.graph = graph; - this.node = node; - this.times(); - this.paths(); + this.runtimeInformation(node); + this.pathInformation(node); }, + /** + * Add a single line of text. + * + * @param {*} text + */ addText(text) { - let self = this; this.textCount += 1; this.toolTipText = this.toolTipG .append("text") .style("font-family", "sans-serif") .style("font-size", "") .attrs({ - "class": "toolTipContent", + "class": "tooltip-content", "x": () => { return this.xOffset + this.margin; }, @@ -107,31 +138,44 @@ export default { .text(text); }, - times() { - this.addText("Name: " + this.trunc(this.node.id, 40)); - // this.addText('Inclusive Time: ' + (this.node['time (inc)'] * 0.000001).toFixed(3) + "s - " + Math.floor(((this.node['time (inc)'] / this.$store.maxIncTime['ensemble']) * 100).toFixed(3)) + "%") - // this.addText('Exclusive Time: ' + (this.node['time'] * 0.000001).toFixed(3) + "s - " + Math.floor(((this.node['time'] / this.$store.maxExcTime['ensemble']) * 100).toFixed(3)) + "%") - // this.addText('Inclusive Time: ' + utils.formatRuntimeWithUnits(this.node.actual_time['Inclusive'])) - // this.addText('Exclusive Time: ' + utils.formatRuntimeWithUnits(this.node.actual_time['Exclusive'])) - this.addText("Inclusive Time: " + utils.formatRuntimeWithUnits(this.node["time (inc)"])); - this.addText("Exclusive Time: " + utils.formatRuntimeWithUnits(this.node["time"])); - // this.addText('Node value: ' + utils.formatRuntimeWithUnits(this.node.value)) - // this.addText('Node height: ' + this.node.height) - + /** + * + * @param {*} node + */ + runtimeInformation(node) { + this.addText("Name: " + utils.truncNames(node.id, 40)); + this.addText("Inclusive Time: " + utils.formatRuntimeWithUnits(node["time (inc)"])); + this.addText("Exclusive Time: " + utils.formatRuntimeWithUnits(node["time"])); }, - trunc(str, n) { - str = str.replace(//g, "proc "); - return (str.length > n) ? str.substr(0, n - 1) + "..." : str; - }, + /** + * + * @param {*} node + */ + pathInformation(node) { + let module_data = {}; + if (this.$store.selectedMode == "Single") { + module_data = this.$store.modules[this.$store.selectedTargetDataset]; + } + else if (this.$store.selectedMode == "Ensemble") { + module_data = this.$store.modules["ensemble"]; + } + + let callsite_data = {}; + if (this.$store.selectedMode == "Single") { + callsite_data = this.$store.callsites[this.$store.selectedTargetDataset]; + } + else if (this.$store.selectedMode == "Ensemble") { + callsite_data = this.$store.callsites["ensemble"]; + } - paths() { - let entry_functions = this.$store.modules["ensemble"][this.node.id]["callers"]; + // TODO : Improve the logic here to not process the string input multiple times. + let entry_functions = node[this.$store.selectedTargetDataset]["entry_function"].split(",").map(String); let entry_function_runtimes = {}; for (let i = 0; i < entry_functions.length; i += 1) { - let callsite = entry_functions[i].replace("'", "").replace("'", "").replace("[", "").replace("]", ""); - entry_function_runtimes[callsite] = this.$store.callsites["ensemble"][callsite][this.$store.selectedMetric]["mean_time"]; + let callsite = entry_functions[i].replace("'", "").replace("'", "").replace("[", "").replace("]", "").replace(" ", ""); + entry_function_runtimes[callsite] = callsite_data[callsite][this.$store.selectedMetric]["mean_time"]; } // Create items array @@ -148,14 +192,17 @@ export default { this.addText(""); this.addText("Entry call sites: "); + this.entryFunctionInformation(node, entry_function_data); + }, - // TODO: Bug here + entryFunctionInformation(node, entry_function_data) { + // Needs clean up for sure. for (var tIndex = 0; tIndex < Math.min(3, entry_function_data.length); tIndex++) { this.textCount += 1; - let fromColor = this.$store.color.getColorByValue(entry_function_data[tIndex][1]); - let toColor = this.$store.color.getColor(this.node); - let fromFunc = entry_function_data[tIndex][0]; - let toFunc = this.node.id; + let toColor = this.$store.runtimeColor.getColorByValue(entry_function_data[tIndex][1]); + let fromColor = this.$store.runtimeColor.getColorByValue(node); + let toFunc = entry_function_data[tIndex][0]; + let fromFunc = node.id; let xOffset = this.xOffset + this.margin; let yOffset = this.yOffset + this.textyOffset + this.textPadding * this.textCount; @@ -166,7 +213,7 @@ export default { "height": this.rectWidth, "x": xOffset + "px", "y": yOffset - 10 + "px", - "class": "toolTipContent" + "class": "tooltip-content", }) .style("fill", fromColor); @@ -175,16 +222,16 @@ export default { .attrs({ "x": xOffset + 15 + "px", "y": yOffset + "px", - "class": "toolTipContent", + "class": "tooltip-content", }) - .text(this.trunc(fromFunc, 10)); + .text(utils.truncNames(fromFunc, 10)); this.toolTipG .append("text") .attrs({ "x": xOffset + 120 + "px", "y": yOffset + "px", - "class": "toolTipContent", + "class": "tooltip-content", }) .text("->"); @@ -195,7 +242,7 @@ export default { "height": this.rectWidth, "x": xOffset + 140 + "px", "y": yOffset - 10 + "px", - "class": "toolTipContent", + "class": "tooltip-content", }) .style("fill", toColor); this.toolTipG @@ -203,20 +250,23 @@ export default { .attrs({ "x": xOffset + 155 + "px", "y": yOffset + "px", - "class": "toolTipContent", + "class": "tooltip-content", }) - .text(this.trunc(toFunc, 10)); + .text(utils.truncNames(toFunc, 10)); } let left_callsites = entry_function_data.length - 3; this.addText("and " + left_callsites + " call sites more."); - }, + + /** + * Clear the content in the tooltip. + */ clear() { - this.textCount = 0; - d3.selectAll(".toolTipContent").remove(); + this.textCount = 0; + d3.selectAll(".tooltip-container").remove(); + d3.selectAll(".tooltip-content").remove(); }, - } }; \ No newline at end of file diff --git a/app/src/components/supergraph/nodes.js b/app/src/components/supergraph/nodes.js index f6a97b27..de57387f 100644 --- a/app/src/components/supergraph/nodes.js +++ b/app/src/components/supergraph/nodes.js @@ -136,7 +136,7 @@ export default { } this.$refs.Guides.init(this.graph.nodes); } - // this.$refs.ToolTip.init(this.$parent.id) + this.$refs.ToolTip.init(this.$parent.id); }, preVis() { @@ -268,14 +268,14 @@ export default { }, mouseover(node) { - // this.$refs.ToolTip.visualize(self.graph, node) + this.$refs.ToolTip.visualize(self.graph, node); if (this.$store.selectedMode == "Ensemble" && this.$store.comparisonMode == false) { this.$refs.Guides.visualize(node, "temporary"); } }, mouseout(node) { - // this.$refs.ToolTip.clear() + this.$refs.ToolTip.clear(); if (this.$store.selectedMode == "Ensemble" && this.$store.comparisonMode == false) { this.$refs.Guides.clear(node, "temporary"); if (this.permanentGuides == false) { diff --git a/app/src/components/supergraph/supergraph.js b/app/src/components/supergraph/supergraph.js index 8b6b1821..7ae10c71 100644 --- a/app/src/components/supergraph/supergraph.js +++ b/app/src/components/supergraph/supergraph.js @@ -82,38 +82,19 @@ export default { sockets: { ensemble_supergraph(data) { data = JSON.parse(data); - console.debug("Data: ", data); - let nodes = []; - for (let i = 0; i < data.nodes.length; i += 1) { - console.debug("Node name: ", data.nodes[i].id); - console.debug("Time (inc): ", data.nodes[i]["time (inc)"]); - console.debug("Time: ", data.nodes[i]["time"]); - } - - for (let i = 0; i < data.links.length; i += 1) { - console.debug("Source: ", data.links[i].source); - console.debug("Target: ", data.links[i].target); - console.debug("Weight: ", data.links[i].weight); - } + this.debugData(data); this.render(data); }, single_supergraph(data) { data = JSON.parse(data); - console.debug("Data :", data); - let nodes = []; - for (let i = 0; i < data.nodes.length; i += 1) { - console.debug("Node name: ", data.nodes[i].id); - console.debug("Time (inc): ", data.nodes[i]["time (inc)"]); - console.debug("Time: ", data.nodes[i]["time"]); - } - - for (let i = 0; i < data.links.length; i += 1) { - console.debug("Source: ", data.links[i].source); - console.debug("Target: ", data.links[i].target); - console.debug("Weight: ", data.links[i].weight); - } + this.debugData(data); this.render(data); + }, + + split_mpi_distribution(data) { + data = JSON.parse(data); + console.debug("Data: ", data); } }, @@ -154,6 +135,21 @@ export default { this.sankeySVG.call(zoom); }, + debugData(data) { + console.debug("Data :", data); + for (let i = 0; i < data.nodes.length; i += 1) { + console.debug("Node name: ", data.nodes[i].id); + console.debug("Time (inc): ", data.nodes[i]["time (inc)"]); + console.debug("Time: ", data.nodes[i]["time"]); + } + + for (let i = 0; i < data.links.length; i += 1) { + console.debug("Source: ", data.links[i].source); + console.debug("Target: ", data.links[i].target); + console.debug("Weight: ", data.links[i].weight); + } + }, + clear() { this.$refs.EnsembleNodes.clear(); this.$refs.EnsembleEdges.clear(); @@ -264,7 +260,7 @@ export default { // Add intermediate nodes. postProcess(nodes, edges) { - console.log("===================Adding intermediate nodes=================="); + console.debug("===================Adding intermediate nodes=================="); const temp_nodes = nodes.slice(); const temp_edges = edges.slice(); diff --git a/app/src/components/utils.js b/app/src/components/utils.js index 63ad739a..0490937b 100644 --- a/app/src/components/utils.js +++ b/app/src/components/utils.js @@ -135,9 +135,80 @@ export function getGradients(store, node) { return gradients; } +/** + * Remove duplicates from an array. + * @param {*} arr + */ export function removeDuplicates(arr) { var seen = {}; return arr.filter(function (item) { return seen.hasOwnProperty(item) ? false : (seen[item] = true); }); +} + +// create a dummy element, apply the appropriate classes, +// and then measure the element +export function measure(text) { + if (!text || text.length === 0) return { height: 0, width: 0 }; + + const container = d3.select("body").append("svg").attr("class", "dummy"); + container.append("text").attrs({ x: -1000, y: -1000 }).text(text); + + const bbox = container.node().getBBox(); + container.remove(); + + return { height: bbox.height, width: bbox.width }; +} + +/** + * + * @param {*} text + * @param {*} width + */ +export function textWrap(text, width) { + text.each(function () { + var text = d3.select(this), + words = text.text().split(/\s+/).reverse(), + word, + line = [], + lineNumber = 0, + lineHeight = 1.1, // ems + x = text.attr("x"), + y = text.attr("y"), + dy = 0, + tspan = text.text(null).append("tspan").attr("dy", dy + "em"); + + while ((word = words.pop())) { + line.push(word); + tspan.text(line.join(" ")); + if (tspan.node().getComputedTextLength() > width) { + line.pop(); + tspan.text(line.join(" ")); + line = [word]; + tspan = text.append("tspan").attr("x", x).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word); + } + } + }); +} + +/** + * Calculate the distance between two given points. + * @param {Number} x1 1st coordinate (x) + * @param {Number} y1 1st coordinate (y) + * @param {Number} x2 2nd coordinate (x) + * @param {Number} y2 2nd coordinate (y) + */ +export function distanceBtwnPoints(x1, y1, x2, y2) { + const a = x1 - x2; + const b = y1 - y2; + return Math.abs(Math.sqrt(a * a + b * b)); +} + +/** + * Split string to lists by , (paranthesis proof) + * @param {*} string + */ +export function stringToList(string) { + const re = /(:\s|,\s)/; // regular expression with capturing parentheses + return string.split(re); } \ No newline at end of file diff --git a/callflow/callflow.py b/callflow/callflow.py index 431863be..9f533192 100644 --- a/callflow/callflow.py +++ b/callflow/callflow.py @@ -268,6 +268,7 @@ def request_single(self, operation): "supergraph", "miniHistogram", "function", + "split_mpi_distribution", ] assert "name" in operation assert operation["name"] in _OPERATIONS @@ -304,6 +305,9 @@ def request_single(self, operation): ) return functionlist.result + elif operation_name == "split_mpi_distribution": + pass + # flake8: noqa: C901 def request_ensemble(self, operation): """ diff --git a/callflow/layout/sankey.py b/callflow/layout/sankey.py index ca0ed703..ed1efa7e 100644 --- a/callflow/layout/sankey.py +++ b/callflow/layout/sankey.py @@ -23,7 +23,16 @@ class SankeyLayout: Sankey layout """ - _COLUMNS = ["actual_time", "time (inc)", "module", "name", "time", "type", "module"] + _COLUMNS = [ + "actual_time", + "time (inc)", + "module", + "name", + "time", + "type", + "module", + "entry_function", + ] def __init__( self, @@ -385,7 +394,7 @@ def _dataset_map(df, nxg, columns=[], tag=""): if node_dict["type"] == "component-node": module = node_name.split("=")[0] callsite = node_name.split("=")[1] - actual_time = SankeyLayout.callsite_time( + agg_time = SankeyLayout.callsite_time( group_df=target_module_group_df, module=module, callsite=callsite, @@ -396,7 +405,7 @@ def _dataset_map(df, nxg, columns=[], tag=""): elif node_dict["type"] == "super-node": module = node_name callsite = target_module_callsite_map[module].tolist() - actual_time = SankeyLayout.module_time( + agg_time = SankeyLayout.module_time( group_df=target_module_name_group_df, module_callsite_map=target_module_callsite_map, module=module, @@ -419,7 +428,7 @@ def _dataset_map(df, nxg, columns=[], tag=""): ret[node_name][column] = module elif column == "actual_time": - ret[node_name][column] = actual_time + ret[node_name][column] = agg_time elif column == "name": ret[node_name][column] = callsite @@ -427,8 +436,27 @@ def _dataset_map(df, nxg, columns=[], tag=""): elif column == "type": ret[node_name][column] = node_dict["type"] + elif column == "entry_function": + print( + SankeyLayout.get_entry_functions( + target_module_group_df, node_name + ) + ) + ret[node_name][column] = SankeyLayout.get_entry_functions( + target_module_group_df, node_name + ) + return ret + @staticmethod + def get_entry_functions(df, module): + """ + Get the entry function of a module from the dataframe. + """ + module_df = df.get_group(module) + entry_func_df = module_df.loc[module_df["entry_function"]] + return entry_func_df["callees"].unique().tolist()[0] + # -------------------------------------------------------------------------- @staticmethod def edge_type(nxg): diff --git a/callflow/operations/argparser.py b/callflow/operations/argparser.py index 90227654..6d753494 100644 --- a/callflow/operations/argparser.py +++ b/callflow/operations/argparser.py @@ -180,16 +180,17 @@ def _read_config(args: argparse.Namespace): LOGGER.debug("Scheme: default") if "runs" not in json and "profile_format" not in json: - raise Exception("Either 'runs' or 'profile_format' key must be provided in the config file.") + raise Exception( + "Either 'runs' or 'profile_format' key must be provided in the config file." + ) elif "runs" in json and "profile_format" not in json: scheme["properties"] = _SCHEME_PROFILE_FORMAT_MAPPER["default"]( json["runs"] ) elif "runs" not in json and "profile_format" in json: - scheme["properties"] = _SCHEME_PROFILE_FORMAT_MAPPER[json["profile_format"]]( - json["runs"] - ) - + scheme["properties"] = _SCHEME_PROFILE_FORMAT_MAPPER[ + json["profile_format"] + ](json["runs"]) if "module_map" in json["scheme"]: scheme["module_callsite_map"] = json["scheme"]["module_map"] @@ -217,7 +218,7 @@ def _scheme_dataset_map_default(run_props: dict): name = data["name"] scheme["runs"].append(name) scheme["paths"][name] = data["path"] - + # Assert if the profile_format is provided. if "profile_format" not in data: raise Exception(f"Profile format not specified for the dataset: {name}") diff --git a/docs/conf.py b/docs/conf.py index f0ac147f..7ad0cfd4 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -78,9 +78,10 @@ class CallFlowStyle(DefaultStyle): pygments_style = "callflow" -# Set the master_doc to avoid the issue (https://github.com/readthedocs/readthedocs.org/issues/2569) +# Sets the master_doc variable to avoid the issue (https://github.com/readthedocs/readthedocs.org/issues/2569) master_doc = "index" + # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for diff --git a/server/main.py b/server/main.py index eae90d3e..0e57425e 100644 --- a/server/main.py +++ b/server/main.py @@ -362,6 +362,21 @@ def compare(data): ) emit("compare", result, json=True) + @sockets.on("split_mpi_distribution", namespace="/") + def split_mpi_rank(data): + """ + Split a single run based on MPI distribution + """ + LOGGER.debug("[Socket request] compare_supergraph {data}") + result = self.callflow.request_single( + { + "name": "split_mpi_distribution", + "dataset": data["dataset"], + "ranks": data["ranks"], + } + ) + emit("split_mpi_distribution", result, json=True) + if __name__ == "__main__": # if verbose, level = 1