mirror of
https://github.com/apache/impala.git
synced 2025-12-19 18:12:08 -05:00
For large JS scripts, code-analysis and linting rules were added to standardize changes for the webUI scripts in IMPALA-13473. This change corrects the scripts to align with these linting and code-analysis rules. Change-Id: I5c8851c3d2f4e95b7eebd61f379361818afca7ea Reviewed-on: http://gerrit.cloudera.org:8080/22950 Tested-by: Impala Public Jenkins <impala-public-jenkins@cloudera.com> Reviewed-by: Riza Suminto <riza.suminto@cloudera.com>
784 lines
30 KiB
JavaScript
784 lines
30 KiB
JavaScript
// Licensed to the Apache Software Foundation (ASF) under one
|
|
// or more contributor license agreements. See the NOTICE file
|
|
// distributed with this work for additional information
|
|
// regarding copyright ownership. The ASF licenses this file
|
|
// to you under the Apache License, Version 2.0 (the
|
|
// "License"); you may not use this file except in compliance
|
|
// with the License. You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing,
|
|
// software distributed under the License is distributed on an
|
|
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
// KIND, either express or implied. See the License for the
|
|
// specific language governing permissions and limitations
|
|
// under the License.
|
|
|
|
import {profile, set_maxts, maxts, decimals, set_decimals, diagram_width,
|
|
set_diagram_width, DIAGRAM_MIN_HEIGHT,
|
|
MARGIN_HEADER_FOOTER, BORDER_STROKE_WIDTH, MARGIN_CHART_END, clearDOMChildren,
|
|
resizeHorizontalAll} from "./global_members.js";
|
|
import {host_utilization_chart, getUtilizationWrapperHeight}
|
|
from "./host_utilization_diagram.js";
|
|
import {fragment_metrics_chart, getFragmentMetricsWrapperHeight,
|
|
updateFragmentMetricsChartOnClick} from "./fragment_metrics_diagram.js";
|
|
import {showTooltip, hideTooltip} from "./chart_commons.js";
|
|
import "./global_dom.js";
|
|
|
|
export let exportedForTest;
|
|
|
|
export let name_width;
|
|
export let page_additional_height;
|
|
|
|
const stroke_fill_colors = {black : "#000000", dark_grey : "#505050",
|
|
light_grey : "#F0F0F0", transperent : "rgba(0, 0, 0, 0)"};
|
|
const ROW_HEIGHT = 15;
|
|
const INTEGER_PART_ESTIMATE = 4;
|
|
const CHAR_WIDTH = 6;
|
|
const BUCKET_SIZE = 5;
|
|
|
|
let rownum;
|
|
let fragment_events_parse_successful = false;
|
|
let decimals_multiplier = Math.pow(10, 2);
|
|
|
|
// #phases_header
|
|
const phases = [
|
|
{color : "#C0C0FF", label : "Prepare"},
|
|
{color : "#E0E0E0", label : "Open"},
|
|
{color : "#FFFFC0", label : "Produce First Batch"},
|
|
{color : "#C0FFFF", label : "Send First Batch"},
|
|
{color : "#C0FFC0", label : "Process Remaining Batches"},
|
|
{color : "#FFC0C0", label : "Close"}
|
|
];
|
|
|
|
// #fragment_diagram
|
|
const fragment_colors = ["#A9A9A9", "#FF8C00", "#8A2BE2", "#A52A2A", "#00008B", "#006400",
|
|
"#228B22", "#4B0082", "#DAA520", "#008B8B", "#000000", "#DC143C"];
|
|
const fragment_diagram_title = getSvgTitle("");
|
|
|
|
let frag_name_width;
|
|
let chart_width;
|
|
let timestamp_gridline;
|
|
let fragments = [];
|
|
|
|
// #timeticks_footer
|
|
export let ntics = 10;
|
|
|
|
export function set_ntics(val) {
|
|
ntics = val;
|
|
}
|
|
|
|
function removeChildIfExists(parentElement, childElement) {
|
|
try {
|
|
parentElement.removeChild(childElement);
|
|
} catch(e) {
|
|
}
|
|
}
|
|
|
|
function rtd(num) { // round to decimals
|
|
return Math.round(num * decimals_multiplier) / decimals_multiplier;
|
|
}
|
|
|
|
// 'dasharray' and 'stroke_color' are optional parameters
|
|
function getSvgRect(fill_color, x, y, width, height, dasharray, stroke_color) {
|
|
const rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
|
|
rect.setAttribute("x", rtd(x));
|
|
rect.setAttribute("y", rtd(y));
|
|
rect.setAttribute("width", rtd(width));
|
|
rect.setAttribute("height", rtd(height));
|
|
rect.setAttribute("fill", fill_color);
|
|
rect.setAttribute("stroke-width", 0.5);
|
|
if (stroke_color) {
|
|
rect.setAttribute("stroke", stroke_color);
|
|
}
|
|
if (dasharray) {
|
|
rect.setAttribute("stroke-dasharray", dasharray);
|
|
}
|
|
return rect;
|
|
}
|
|
|
|
// 'max_width' is an optional parameters
|
|
function getSvgText(text, fill_color, x, y, height, container_center, max_width) {
|
|
const text_el = document.createElementNS("http://www.w3.org/2000/svg", "text");
|
|
text_el.appendChild(document.createTextNode(text));
|
|
text_el.setAttribute("x", x);
|
|
text_el.setAttribute("y", y);
|
|
text_el.style.fontSize = `${height / 1.5}px`;
|
|
if (container_center) {
|
|
text_el.setAttribute("dominant-baseline", "middle");
|
|
text_el.setAttribute("text-anchor", "middle");
|
|
}
|
|
text_el.setAttribute("fill", fill_color);
|
|
if (max_width) {
|
|
text_el.setAttribute("textLength", max_width);
|
|
text_el.setAttribute("lengthAdjust", "spacingAndGlyphs");
|
|
}
|
|
return text_el;
|
|
}
|
|
|
|
function getSvgLine(stroke_color, x1, y1, x2, y2, dash) {
|
|
const line = document.createElementNS("http://www.w3.org/2000/svg", "line");
|
|
line.setAttribute("x1", rtd(x1));
|
|
line.setAttribute("y1", rtd(y1));
|
|
line.setAttribute("x2", rtd(x2));
|
|
line.setAttribute("y2", rtd(y2));
|
|
line.setAttribute("stroke", stroke_color);
|
|
if (dash) {
|
|
line.setAttribute("stroke-dasharray", "2 2");
|
|
}
|
|
return line;
|
|
}
|
|
|
|
function getSvgTitle(text) {
|
|
const title = document.createElementNS("http://www.w3.org/2000/svg", "title");
|
|
title.textContent = text;
|
|
return title;
|
|
}
|
|
|
|
function getSvgGroup() {
|
|
const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
|
|
return group;
|
|
}
|
|
|
|
function markMinMaxAvg(event) {
|
|
if (event.no_bar !== undefined) return;
|
|
let min = event.ts_list[0];
|
|
let max = min;
|
|
let i = 1;
|
|
for (; i < event.ts_list.length; i++) {
|
|
if (min > event.ts_list[i]) min = event.ts_list[i];
|
|
if (max < event.ts_list[i]) max = event.ts_list[i];
|
|
}
|
|
const EVENT_SPAN_T = max - min;
|
|
event.parts = Array(BUCKET_SIZE).fill(null).map(
|
|
() => ({count : 0, min : Infinity, max : -Infinity, avg : 0}));
|
|
let k;
|
|
let ts;
|
|
let part;
|
|
for (i = 0; i < event.ts_list.length; i++) {
|
|
ts = event.ts_list[i];
|
|
k = (ts - min) * BUCKET_SIZE / EVENT_SPAN_T | 0;
|
|
if (k >= BUCKET_SIZE) k = BUCKET_SIZE - 1;
|
|
part = event.parts[k];
|
|
if (ts < part.min) part.min = ts;
|
|
if (ts > part.max) part.max = ts;
|
|
part.avg += ts;
|
|
part.count += 1;
|
|
}
|
|
for (k = 0; k < BUCKET_SIZE; k++) {
|
|
event.parts[k].avg /= event.parts[k].count;
|
|
}
|
|
}
|
|
|
|
function getPhaseDasharray(dx, dy, l_dx) {
|
|
return `0 ${rtd(l_dx)} ${rtd(dx - l_dx + dy)} ${rtd(dx)} ${rtd(dy)} 0`;
|
|
}
|
|
|
|
function attachBucketedPhaseTooltip(rect, part) {
|
|
rect.appendChild(getSvgTitle(`# of Inst. : ${part.count}\n`
|
|
+ `Min. : ${rtd(part.min / 1e9)}s\n`
|
|
+ `Max. : ${rtd(part.max / 1e9)}s\n`
|
|
+ `Avg. : ${rtd(part.avg / 1e9)}s`));
|
|
return rect;
|
|
}
|
|
|
|
function DrawBars(svg, rownum_l, row_height, events, xoffset, px_per_ns) {
|
|
// Structure of each 'events' element -
|
|
// {
|
|
// no_bar : < Boolean value is set, if event is not to be rendered >
|
|
// ts_list : < List of this plan node's event's timestamps from all instances >
|
|
// // A total of 5 buckets represented as 'parts'
|
|
// parts : [{ // Each object contains statistics of a single timestamps bucket
|
|
// count : < Number of timestamps in the current bucket >
|
|
// avg : < Average of timestamps in the current bucket >
|
|
// min : < Minimum timestamp in the current bucket >
|
|
// max : < Maximum timestamp in the current bucket >
|
|
// }, < ... 4 other objects of the same format > ]
|
|
// }
|
|
const BAR_HEIGHT = row_height - 2;
|
|
const LAST_E_INDEX = events.length - 1;
|
|
const LAST_TS_INDEX = events[LAST_E_INDEX].ts_list.length - 1;
|
|
const plan_node = getSvgGroup();
|
|
plan_node.classList.add("plan_node");
|
|
// coordinates start with (0,0) in the top-left corner
|
|
let y = rownum_l * row_height; // the y-position for the current phase rectangle
|
|
const cp_y = y; // copy of y, used to bring y-position back to the top(initial value)
|
|
let dx, dy; // dimensions of the current phase rectangle
|
|
let bucketed = false;
|
|
|
|
if (events[0].ts_list.length > BUCKET_SIZE) {
|
|
events.map(markMinMaxAvg);
|
|
bucketed = true;
|
|
} else {
|
|
events.map(ev => {
|
|
ev.ts_list.sort((a, b) => a - b);
|
|
});
|
|
}
|
|
|
|
let color_idx = LAST_E_INDEX;
|
|
for (let i = 0; i <= LAST_E_INDEX; ++i) {
|
|
if (events[i].no_bar) --color_idx;
|
|
}
|
|
|
|
let j;
|
|
let dasharray; // current stroke-dasharray
|
|
for (let i = LAST_E_INDEX; i >= 0; --i) {
|
|
y = cp_y; // reuse calculation, returing to the initial y-position
|
|
if (events[i].no_bar === undefined) {
|
|
let l_dx = 0; // previous phase rectangle's width (x-dimension)
|
|
if (bucketed) {
|
|
// Represent the aggregate distribution of instances for each phase
|
|
let part;
|
|
for (j = 0; j < events[i].parts.length; j++) {
|
|
part = events[i].parts[j]; // mantain a reference for reuse
|
|
if (part.count > 0) {
|
|
dy = part.count / events[i].ts_list.length * BAR_HEIGHT;
|
|
} else {
|
|
// skip to the next iteration, if the current bucket has no timestamps
|
|
continue;
|
|
}
|
|
dx = part.max * px_per_ns;
|
|
// 'stroke-dasharray' is a string of alternative lengths for
|
|
// dashes and no dashes. So, the length of dasharray of must be even.
|
|
// Width(x-dimension) of previous(upper) phase rectangle is stored to
|
|
// calculate boundaries such that only open boundaries are stroked with borders
|
|
// using the 'getPhaseDasharray' function
|
|
dasharray = getPhaseDasharray(dx, dy, l_dx);
|
|
plan_node.appendChild(attachBucketedPhaseTooltip(getSvgRect(
|
|
phases[color_idx].color, xoffset, y, dx, dy, dasharray,
|
|
stroke_fill_colors.black), part));
|
|
y += dy;
|
|
l_dx = dx;
|
|
}
|
|
} else {
|
|
// Represent phases of each instance seperately, according to the timestamps
|
|
dy = BAR_HEIGHT / events[i].ts_list.length;
|
|
for (j = 0; j < events[i].ts_list.length; j++) {
|
|
dx = events[i].ts_list[j] * px_per_ns;
|
|
// Calculations for the 'stroke-dasharray' have been done same as above
|
|
dasharray = getPhaseDasharray(dx, dy, l_dx);
|
|
plan_node.appendChild(getSvgRect(phases[color_idx].color, xoffset, y, dx, dy,
|
|
dasharray, stroke_fill_colors.black));
|
|
y += dy;
|
|
l_dx = dx;
|
|
}
|
|
}
|
|
--color_idx;
|
|
}
|
|
}
|
|
|
|
y = rownum_l * row_height;
|
|
if (events[LAST_E_INDEX].no_bar === undefined) {
|
|
// Plan node's top and bottom outlines
|
|
let top_edge_ts, bottom_edge_ts;
|
|
if (bucketed) {
|
|
top_edge_ts = events[LAST_E_INDEX].parts[0].max;
|
|
bottom_edge_ts = events[LAST_E_INDEX].parts[BUCKET_SIZE - 1].max;
|
|
} else {
|
|
top_edge_ts = events[LAST_E_INDEX].ts_list[0];
|
|
bottom_edge_ts = events[LAST_E_INDEX].ts_list[LAST_TS_INDEX];
|
|
}
|
|
plan_node.appendChild(getSvgLine(stroke_fill_colors.black, xoffset, y,
|
|
xoffset + top_edge_ts * px_per_ns, y, false));
|
|
plan_node.appendChild(getSvgLine(stroke_fill_colors.black, xoffset, y + BAR_HEIGHT,
|
|
xoffset + bottom_edge_ts * px_per_ns, y + BAR_HEIGHT,
|
|
false));
|
|
}
|
|
|
|
svg.appendChild(plan_node);
|
|
}
|
|
|
|
async function renderPhases() {
|
|
clearDOMChildren(phases_header);
|
|
let color_idx = 0;
|
|
const PHASE_WIDTH = Math.ceil(chart_width / phases.length);
|
|
phases.forEach(p => {
|
|
let x = name_width + Math.ceil(chart_width * color_idx / phases.length);
|
|
x = Math.min(x, diagram_width - PHASE_WIDTH);
|
|
|
|
phases_header.appendChild(getSvgRect(stroke_fill_colors.black, x, 0, PHASE_WIDTH,
|
|
ROW_HEIGHT));
|
|
phases_header.appendChild(getSvgRect(phases[color_idx++].color, x + 1, 1,
|
|
PHASE_WIDTH - 2, ROW_HEIGHT - 2));
|
|
phases_header.appendChild(getSvgText(p.label, stroke_fill_colors.black,
|
|
x + PHASE_WIDTH / 2, (ROW_HEIGHT + 4) / 2 , ROW_HEIGHT, true,
|
|
Math.min(p.label.length * CHAR_WIDTH, PHASE_WIDTH / 1.5)));
|
|
});
|
|
}
|
|
|
|
async function renderFragmentDiagram() {
|
|
clearDOMChildren(fragment_diagram);
|
|
const PX_PER_NS = chart_width / maxts;
|
|
let rownum_l = 0;
|
|
let pending_children = 0;
|
|
let pending_senders = 0;
|
|
let text_y = ROW_HEIGHT - 4;
|
|
fragment_diagram.appendChild(fragment_diagram_title);
|
|
fragments.forEach(function printFragment(fragment) {
|
|
if (!fragment.printed) {
|
|
fragment.printed = true;
|
|
|
|
const pending_fragments = [];
|
|
const fevents = fragment.events;
|
|
|
|
const frag_name = fragment.name.replace("Coordinator ", "").replace("Fragment ",
|
|
"");
|
|
|
|
fragment_diagram.appendChild(getSvgText(frag_name, fragment.color, 1, text_y,
|
|
ROW_HEIGHT, false));
|
|
|
|
// Fragment/sender timing row
|
|
const fragment_svg_group = getSvgGroup();
|
|
DrawBars(fragment_svg_group, rownum_l, ROW_HEIGHT, fevents, name_width, PX_PER_NS);
|
|
|
|
for (let i = 0; i < fragment.nodes.length; ++i) {
|
|
const node = fragment.nodes[i];
|
|
|
|
if (node.events !== undefined) {
|
|
// Plan node timing row
|
|
DrawBars(fragment_svg_group, rownum_l, ROW_HEIGHT, node.events, name_width,
|
|
PX_PER_NS);
|
|
|
|
if (node.type === "HASH_JOIN_NODE") {
|
|
fragment_diagram.appendChild(getSvgText("X", stroke_fill_colors.black,
|
|
name_width + Math.min.apply(null, node.events[2].ts_list) * PX_PER_NS,
|
|
text_y, ROW_HEIGHT, false));
|
|
fragment_diagram.appendChild(getSvgText("O", stroke_fill_colors.black,
|
|
name_width + Math.min.apply(null, node.events[2].ts_list) * PX_PER_NS,
|
|
text_y, ROW_HEIGHT, false));
|
|
}
|
|
}
|
|
|
|
if (node.is_receiver) {
|
|
pending_senders++;
|
|
} else if (node.is_sender) {
|
|
pending_senders--;
|
|
}
|
|
if (!node.is_sender) {
|
|
pending_children--;
|
|
}
|
|
|
|
const LABEL_X = frag_name_width + CHAR_WIDTH * pending_children;
|
|
const LABEL_WIDTH = Math.min(CHAR_WIDTH * node.name.length,
|
|
name_width - LABEL_X - 2);
|
|
fragment_diagram.appendChild(getSvgText(node.name, fragment.color,
|
|
LABEL_X, text_y, ROW_HEIGHT, false));
|
|
|
|
if (node.parent_node !== undefined) {
|
|
const Y = ROW_HEIGHT * node.parent_node.rendering.rownum;
|
|
if (node.is_sender) {
|
|
const X = name_width + Math.min.apply(null, fevents[3].ts_list) * PX_PER_NS;
|
|
// Dotted horizontal connector to received rows
|
|
fragment_diagram.appendChild(getSvgLine(fragment.color, name_width,
|
|
Y + ROW_HEIGHT / 2 - 1, X, Y + ROW_HEIGHT / 2 - 1, true));
|
|
|
|
// Dotted rectangle for received rows
|
|
const X2 = name_width + Math.max.apply(null, fevents[4].ts_list) * PX_PER_NS;
|
|
fragment_diagram.appendChild(getSvgRect(stroke_fill_colors.transperent,
|
|
X, Y + 4, X2 - X, ROW_HEIGHT - 10, "2 2", fragment.color));
|
|
}
|
|
|
|
if (node.is_sender && node.parent_node.rendering.rownum !== rownum_l - 1) {
|
|
// DAG edge on right side to distant sender
|
|
const X = name_width - pending_senders * CHAR_WIDTH - CHAR_WIDTH / 2;
|
|
fragment_diagram.appendChild(getSvgLine(fragment.color,
|
|
node.parent_node.rendering.label_end,
|
|
Y + ROW_HEIGHT / 2, X , Y + ROW_HEIGHT / 2, false));
|
|
fragment_diagram.appendChild(getSvgLine(fragment.color, X,
|
|
Y + ROW_HEIGHT / 2, X, text_y - ROW_HEIGHT / 2 + 3, false));
|
|
fragment_diagram.appendChild(getSvgLine(fragment.color, X,
|
|
text_y - ROW_HEIGHT / 2 + 3, LABEL_X + LABEL_WIDTH,
|
|
text_y - ROW_HEIGHT / 2 + 3, false));
|
|
|
|
} else {
|
|
// DAG edge from parent to immediate child
|
|
const X = frag_name_width + (pending_children + 1) * CHAR_WIDTH -
|
|
CHAR_WIDTH / 2;
|
|
fragment_diagram.appendChild(getSvgLine(fragment.color, X, Y +
|
|
ROW_HEIGHT - 3, X, text_y - ROW_HEIGHT + 6, false));
|
|
}
|
|
}
|
|
node.rendering = {rownum : rownum_l, label_end : LABEL_X + LABEL_WIDTH};
|
|
if (node.num_children) {
|
|
// Scan (leaf) node
|
|
pending_children += node.num_children - node.is_receiver;
|
|
}
|
|
text_y += ROW_HEIGHT;
|
|
rownum_l++;
|
|
|
|
if (node.is_receiver) {
|
|
if (plan_order.checked) {
|
|
printFragment(fragments[node.sender_frag_index]);
|
|
} else {
|
|
pending_fragments.push(fragments[node.sender_frag_index]);
|
|
}
|
|
}
|
|
}
|
|
fragment_svg_group.id = fragment.name;
|
|
fragment_svg_group.addEventListener("click", updateFragmentMetricsChartOnClick);
|
|
fragment_diagram.appendChild(fragment_svg_group);
|
|
// Visit sender fragments in reverse order to avoid dag edges crossing
|
|
pending_fragments.reverse().forEach(printFragment);
|
|
}
|
|
});
|
|
fragments.forEach(fragment => {
|
|
fragment.printed = false;
|
|
});
|
|
}
|
|
|
|
async function renderTimeticks() {
|
|
clearDOMChildren(timeticks_footer);
|
|
const SEC_PER_TIC = maxts / ntics / 1e9;
|
|
const PX_PER_TIC = chart_width / ntics;
|
|
let x = name_width;
|
|
const y = 0;
|
|
const text_y = ROW_HEIGHT - 4;
|
|
let timetick_label;
|
|
for (let i = 1; i <= ntics; ++i) {
|
|
timeticks_footer.appendChild(getSvgRect(stroke_fill_colors.black, x, y, PX_PER_TIC,
|
|
ROW_HEIGHT));
|
|
timeticks_footer.appendChild(getSvgRect(stroke_fill_colors.light_grey, x + 1,
|
|
y + 1, PX_PER_TIC - 2, ROW_HEIGHT - 2));
|
|
timetick_label = (i * SEC_PER_TIC).toFixed(decimals);
|
|
timeticks_footer.appendChild(getSvgText(timetick_label, stroke_fill_colors.black,
|
|
x + PX_PER_TIC - timetick_label.length * CHAR_WIDTH + 2, text_y, ROW_HEIGHT,
|
|
false));
|
|
x += PX_PER_TIC;
|
|
}
|
|
}
|
|
|
|
export function collectFragmentEventsFromProfile() {
|
|
rownum = 0;
|
|
let max_namelen = 0;
|
|
fragments = [];
|
|
const all_nodes = [];
|
|
const receiver_nodes = [];
|
|
let color_idx = 0;
|
|
try {
|
|
// First pass : compute sizes
|
|
const execution_profile = profile.child_profiles[2];
|
|
const EXECUTION_PROFILE_NAME = execution_profile.profile_name.split(" ").slice(0,2)
|
|
.join(" ");
|
|
console.assert(EXECUTION_PROFILE_NAME === "Execution Profile");
|
|
execution_profile.child_profiles.forEach(fp => {
|
|
|
|
if (fp.child_profiles !== undefined &&
|
|
fp.child_profiles[0].event_sequences !== undefined) {
|
|
let cp = fp.child_profiles[0];
|
|
const fevents = fp.child_profiles[0].event_sequences[0].events;
|
|
|
|
// Build list of timestamps that spans instances for each event
|
|
for (let en = 0; en < fevents.length; ++en) {
|
|
if (fevents[en].label.includes("AsyncCodegen")) {
|
|
fevents[en].no_bar = true;
|
|
continue;
|
|
}
|
|
fevents[en].ts_list = [ fevents[en].timestamp ];
|
|
}
|
|
for (let instance = 1; instance < fp.child_profiles.length; ++instance) {
|
|
if (fp.child_profiles[instance].event_sequences !== undefined) {
|
|
if (fp.child_profiles[instance].event_sequences[0].events.length ===
|
|
fevents.length) {
|
|
for (let en = 0; en < fevents.length; ++en) {
|
|
if (fevents[en].no_bar) continue;
|
|
fevents[en].ts_list.push(
|
|
fp.child_profiles[instance].event_sequences[0].events[en].timestamp);
|
|
}
|
|
} else {
|
|
let en = 0, i = 0;
|
|
while(i < fevents.length && en < fp.child_profiles[instance]
|
|
.event_sequences[0].events.length) {
|
|
if (fevents[i].no_bar) {
|
|
if (fevents[i].label === fp.child_profiles[instance]
|
|
.event_sequences[0].events[en].label) { en++; }
|
|
} else if (fp.child_profiles[instance].event_sequences[0]
|
|
.events[en].label === fevents[i].label) {
|
|
fevents[i].ts_list.push(fp.child_profiles[instance].event_sequences[0]
|
|
.events[en].timestamp);
|
|
++en;
|
|
}
|
|
++i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const fragment = {
|
|
name : fp.profile_name,
|
|
events : fevents,
|
|
nodes : [ ],
|
|
color : fragment_colors[color_idx]
|
|
};
|
|
// Pick a new color for each plan fragment
|
|
color_idx = (color_idx + 1) % fragment_colors.length;
|
|
set_maxts(Math.max(maxts, fevents[fevents.length - 1].timestamp));
|
|
max_namelen = Math.max(max_namelen, fp.profile_name.length);
|
|
const node_path = [];
|
|
const node_stack = [];
|
|
let node_name;
|
|
cp.child_profiles.forEach(function get_plan_nodes(pp, index) {
|
|
if (pp.node_metadata === undefined) return;
|
|
node_path.push(index);
|
|
// pp.profile_name : "AGGREGATION_NODE (id=52)"
|
|
const name_flds = pp.profile_name.split(/[()]/);
|
|
// name_flds : ["AGGREGATION_NODE ", "id=52", ""]
|
|
const node_type = name_flds[0].trim();
|
|
// node_type : "AGGREGATION_NODE"
|
|
const NODE_ID = name_flds.length > 1 ?
|
|
parseInt(name_flds[1].split(/[=]/)[1]) : 0;
|
|
// node_id : 52
|
|
node_name = pp.profile_name.replace("_NODE", "").replace("_", " ")
|
|
.replace("KrpcDataStreamSender", "SENDER")
|
|
.replace("Hash Join Builder", "JOIN BUILD")
|
|
.replace("join node_", "");
|
|
if (node_type.indexOf("SCAN_NODE") >= 0) {
|
|
const table_name = pp.info_strings.find(({key}) => key === "Table Name")
|
|
.value.split(/[.]/);
|
|
node_name = node_name.replace("SCAN",
|
|
`SCAN [${table_name[table_name.length - 1]}]`);
|
|
}
|
|
|
|
const IS_RECEIVER = node_type === "EXCHANGE_NODE" ||
|
|
node_type === "HASH_JOIN_NODE" && pp.num_children < 3;
|
|
|
|
const IS_SENDER = node_type === "Hash Join Builder" ||
|
|
node_type === "KrpcDataStreamSender";
|
|
let parent_node;
|
|
if (node_type === "PLAN_ROOT_SINK") {}
|
|
else if (pp.node_metadata.data_sink_id !== undefined) {
|
|
parent_node = receiver_nodes[NODE_ID]; // Exchange sender dst
|
|
} else if (pp.node_metadata.join_build_id !== undefined) {
|
|
parent_node = receiver_nodes[NODE_ID]; // Join sender dst
|
|
} else if (node_stack.length > 0) {
|
|
parent_node = node_stack[node_stack.length - 1];
|
|
} else if (all_nodes.length) {
|
|
parent_node = all_nodes[all_nodes.length - 1];
|
|
}
|
|
|
|
max_namelen = Math.max(max_namelen, node_name.length + node_stack.length + 1);
|
|
|
|
let node_events;
|
|
if (pp.event_sequences !== undefined) {
|
|
node_events = pp.event_sequences[0].events;
|
|
|
|
// Start the instance event list for each event with timestamps
|
|
// from this instance
|
|
for (let en = 0; en < node_events.length; ++en) {
|
|
node_events[en].ts_list = [ node_events[en].timestamp ];
|
|
if ((node_type === "HASH_JOIN_NODE" ||
|
|
node_type === "NESTED_LOOP_JOIN_NODE") && (en === 1 || en === 2)) {
|
|
node_events[en].no_bar = true;
|
|
}
|
|
}
|
|
}
|
|
let node = {
|
|
name : node_name,
|
|
type : node_type,
|
|
node_id : NODE_ID,
|
|
num_children : 0,
|
|
child_index : 0,
|
|
metadata : pp.node_metadata,
|
|
parent_node : parent_node,
|
|
events : node_events,
|
|
path : node_path.slice(0),
|
|
is_receiver : IS_RECEIVER,
|
|
is_sender : IS_SENDER
|
|
};
|
|
|
|
if (IS_SENDER) {
|
|
node.parent_node.sender_frag_index = fragments.length;
|
|
}
|
|
|
|
if (IS_RECEIVER) {
|
|
receiver_nodes[NODE_ID] = node;
|
|
}
|
|
|
|
if (parent_node !== undefined) {
|
|
node.child_index = parent_node.num_children++;
|
|
}
|
|
|
|
all_nodes.push(node);
|
|
|
|
fragment.nodes.push(node);
|
|
|
|
if (pp.child_profiles !== undefined) {
|
|
node_stack.push(node);
|
|
pp.child_profiles.forEach(get_plan_nodes);
|
|
node = node_stack.pop();
|
|
}
|
|
rownum++;
|
|
node_path.pop();
|
|
});
|
|
|
|
// For each node, retrieve the instance timestamps for the remaining instances
|
|
for (let ni = 0; ni < fragment.nodes.length; ++ni) {
|
|
const node = fragment.nodes[ni];
|
|
for (let cpn = 1; cpn < fp.child_profiles.length; ++cpn) {
|
|
cp = fp.child_profiles[cpn];
|
|
|
|
// Use the saved node path to traverse to the same position in this instance
|
|
for (let pi = 0; pi < node.path.length; ++pi) {
|
|
cp = cp.child_profiles[node.path[pi]];
|
|
}
|
|
console.assert(cp.node_metadata.data_sink_id === undefined ||
|
|
cp.profile_name.indexOf(`(dst_id=${node.node_id})`));
|
|
console.assert(cp.node_metadata.plan_node_id === undefined ||
|
|
cp.node_metadata.plan_node_id === node.node_id);
|
|
// Add instance events to this node
|
|
if (cp.event_sequences !== undefined) {
|
|
if (node.events.length === cp.event_sequences[0].events.length) {
|
|
for (let en = 0; en < node.events.length; ++en) {
|
|
node.events[en].ts_list.push(
|
|
cp.event_sequences[0].events[en].timestamp);
|
|
}
|
|
} else {
|
|
let i = 0, en = 0;
|
|
while(i < node.events.length && en < cp.event_sequences[0]
|
|
.events.length) {
|
|
if (node.events[i].label === cp.event_sequences[0].events[en].label) {
|
|
node.events[i]
|
|
.ts_list.push(cp.event_sequences[0].events[en].timestamp);
|
|
++en; ++i;
|
|
} else {
|
|
++i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
fragments.push(fragment);
|
|
}
|
|
});
|
|
frag_name_width = (Math.max(2, (fragments.length - 1).toString().length) + 3)
|
|
* CHAR_WIDTH;
|
|
name_width = max_namelen * CHAR_WIDTH + (frag_name_width + 2);
|
|
fragment_events_parse_successful = true;
|
|
} catch(e) {
|
|
fragment_events_parse_successful = false;
|
|
console.log(e);
|
|
}
|
|
}
|
|
|
|
export function setTimingDiagramDimensions(ignored_arg) {
|
|
// phashes header and timeticks footer with margin and border by considering the offset
|
|
page_additional_height = timing_diagram.offsetTop + (ROW_HEIGHT + MARGIN_HEADER_FOOTER
|
|
+ BORDER_STROKE_WIDTH * 4) * 2;
|
|
const FRAGMENT_DIAGRAM_INITIAL_HEIGHT = window.innerHeight - page_additional_height;
|
|
const REMAINING_HEIGHT = FRAGMENT_DIAGRAM_INITIAL_HEIGHT - getUtilizationWrapperHeight()
|
|
- getFragmentMetricsWrapperHeight();
|
|
const DISPLAY_HEIGHT = Math.max(DIAGRAM_MIN_HEIGHT, Math.min(REMAINING_HEIGHT,
|
|
rownum * ROW_HEIGHT));
|
|
chart_width = diagram_width - name_width - MARGIN_CHART_END - BORDER_STROKE_WIDTH;
|
|
|
|
phases_header.style.height = `${ROW_HEIGHT}px`;
|
|
fragment_diagram.parentElement.style.height = `${DISPLAY_HEIGHT}px`;
|
|
fragment_diagram.style.height = `${Math.max(DIAGRAM_MIN_HEIGHT,
|
|
rownum * ROW_HEIGHT)}px`;
|
|
timeticks_footer.style.height = `${ROW_HEIGHT}px`;
|
|
|
|
fragment_diagram.parentElement.style.width = `${diagram_width}px`;
|
|
phases_header.parentElement.style.width = `${diagram_width}px`;
|
|
timeticks_footer.parentElement.style.width = `${diagram_width}px`;
|
|
timing_diagram.style.width = `${diagram_width}px`;
|
|
|
|
fragment_diagram.style.width = `${diagram_width}px`;
|
|
phases_header.style.width = `${diagram_width}px`;
|
|
timeticks_footer.style.width = `${diagram_width}px`;
|
|
}
|
|
|
|
export function renderTimingDiagram() {
|
|
if (fragment_events_parse_successful) {
|
|
setTimingDiagramDimensions();
|
|
renderPhases();
|
|
renderFragmentDiagram();
|
|
renderTimeticks();
|
|
}
|
|
}
|
|
|
|
fragment_diagram.addEventListener("mouseout", e => {
|
|
hideTooltip(host_utilization_chart);
|
|
hideTooltip(fragment_metrics_chart);
|
|
removeChildIfExists(fragment_diagram, timestamp_gridline);
|
|
});
|
|
|
|
fragment_diagram.addEventListener("mousemove", e => {
|
|
if (e.clientX + scrollable_screen.scrollLeft >= name_width && e.clientX +
|
|
scrollable_screen.scrollLeft <= name_width + chart_width) {
|
|
removeChildIfExists(fragment_diagram, timestamp_gridline);
|
|
timestamp_gridline = getSvgLine(stroke_fill_colors.black, e.clientX +
|
|
scrollable_screen.scrollLeft, 0, e.clientX + scrollable_screen.scrollLeft,
|
|
parseInt(fragment_diagram.style.height), false);
|
|
fragment_diagram.appendChild(timestamp_gridline);
|
|
const GRIDLINE_TIME = maxts * (e.clientX + scrollable_screen.scrollLeft
|
|
- name_width) / chart_width / 1e9;
|
|
showTooltip(host_utilization_chart, GRIDLINE_TIME);
|
|
showTooltip(fragment_metrics_chart, GRIDLINE_TIME);
|
|
fragment_diagram_title.textContent = GRIDLINE_TIME.toFixed(decimals) + " s";
|
|
} else {
|
|
try {
|
|
host_utilization_chart.tooltip.hide();
|
|
} catch (except) {}
|
|
removeChildIfExists(fragment_diagram, timestamp_gridline);
|
|
fragment_diagram_title.textContent = "";
|
|
}
|
|
});
|
|
|
|
fragment_diagram.addEventListener("wheel", e => {
|
|
if (e.shiftKey) {
|
|
const WINDOW_DIAGRAM_WIDTH = window.innerWidth - BORDER_STROKE_WIDTH;
|
|
if (e.wheelDelta <= 0 && diagram_width <= WINDOW_DIAGRAM_WIDTH) return;
|
|
const NEXT_DIAGRAM_WIDTH = diagram_width + Math.round(e.wheelDelta);
|
|
hor_zoomout.disabled = false;
|
|
if (NEXT_DIAGRAM_WIDTH <= WINDOW_DIAGRAM_WIDTH) {
|
|
NEXT_DIAGRAM_WIDTH = WINDOW_DIAGRAM_WIDTH;
|
|
hor_zoomout.disabled = true;
|
|
}
|
|
const NEXT_CHART_WIDTH = NEXT_DIAGRAM_WIDTH - name_width - MARGIN_CHART_END
|
|
- BORDER_STROKE_WIDTH;
|
|
const NEXT_NTICS = (NEXT_DIAGRAM_WIDTH - diagram_width) * 10 / window.innerWidth;
|
|
NEXT_NTICS = ntics + Math.round(NEXT_NTICS);
|
|
if (NEXT_NTICS < 10) NEXT_NTICS = 10;
|
|
const RENDERING_CONSTRAINT = CHAR_WIDTH * (decimals + INTEGER_PART_ESTIMATE) >=
|
|
NEXT_CHART_WIDTH / NEXT_NTICS;
|
|
if (RENDERING_CONSTRAINT) return;
|
|
ntics = NEXT_NTICS;
|
|
set_diagram_width(NEXT_DIAGRAM_WIDTH);
|
|
resizeHorizontalAll();
|
|
}
|
|
});
|
|
|
|
timeticks_footer.addEventListener("wheel", e => {
|
|
if (e.shiftKey) {
|
|
if (e.altKey) {
|
|
if (e.wheelDelta <= 0) {
|
|
if (decimals <= 1) return;
|
|
set_decimals(decimals - 1);
|
|
decimals_multiplier /= 10;
|
|
} else {
|
|
const RENDERING_CONSTRAINT = CHAR_WIDTH * (decimals + 1) + INTEGER_PART_ESTIMATE
|
|
>= chart_width / ntics;
|
|
if (RENDERING_CONSTRAINT) return;
|
|
set_decimals(decimals + 1);
|
|
decimals_multiplier *= 10;
|
|
}
|
|
} else {
|
|
if (e.wheelDelta <= 0 && ntics <= 10) return;
|
|
const NEXT_NTICS = ntics + Math.round(e.wheelDelta / 200);
|
|
if (NEXT_NTICS <= 10) return;
|
|
const RENDERING_CONSTRAINT = CHAR_WIDTH * (decimals + INTEGER_PART_ESTIMATE)
|
|
>= chart_width / NEXT_NTICS;
|
|
if (RENDERING_CONSTRAINT) return;
|
|
ntics = NEXT_NTICS;
|
|
}
|
|
renderTimeticks();
|
|
}
|
|
});
|
|
|
|
plan_order.addEventListener("click", renderFragmentDiagram);
|
|
|
|
if (typeof process !== "undefined" && process.env.NODE_ENV === "test") {
|
|
exportedForTest = {getSvgRect, getSvgLine, getSvgText, getSvgTitle, getSvgGroup};
|
|
}
|