diff --git a/core/trino-web-ui/pom.xml b/core/trino-web-ui/pom.xml index df1e75ccc365..f46ed335964b 100644 --- a/core/trino-web-ui/pom.xml +++ b/core/trino-web-ui/pom.xml @@ -99,13 +99,13 @@ - flow check + check npm verify - run flow + run check src/main/resources/webapp/src diff --git a/core/trino-web-ui/src/main/resources/webapp/src/.npmrc b/core/trino-web-ui/src/main/resources/webapp/src/.npmrc new file mode 100644 index 000000000000..38f11c645a00 --- /dev/null +++ b/core/trino-web-ui/src/main/resources/webapp/src/.npmrc @@ -0,0 +1 @@ +registry=https://registry.npmjs.org diff --git a/core/trino-web-ui/src/main/resources/webapp/src/.prettierrc.json b/core/trino-web-ui/src/main/resources/webapp/src/.prettierrc.json new file mode 100644 index 000000000000..f0db82f11154 --- /dev/null +++ b/core/trino-web-ui/src/main/resources/webapp/src/.prettierrc.json @@ -0,0 +1,7 @@ +{ + "trailingComma": "es5", + "tabWidth": 4, + "semi": false, + "singleQuote": true, + "printWidth": 120 +} diff --git a/core/trino-web-ui/src/main/resources/webapp/src/components/ClusterHUD.jsx b/core/trino-web-ui/src/main/resources/webapp/src/components/ClusterHUD.jsx index d051ee5da074..b02d0c4e4631 100644 --- a/core/trino-web-ui/src/main/resources/webapp/src/components/ClusterHUD.jsx +++ b/core/trino-web-ui/src/main/resources/webapp/src/components/ClusterHUD.jsx @@ -12,9 +12,15 @@ * limitations under the License. */ -import React from "react"; +import React from 'react' -import {addExponentiallyWeightedToHistory, addToHistory, formatCount, formatDataSizeBytes, precisionRound} from "../utils"; +import { + addExponentiallyWeightedToHistory, + addToHistory, + formatCount, + formatDataSizeBytes, + precisionRound, +} from '../utils' const SPARKLINE_PROPERTIES = { width: '100%', @@ -24,11 +30,11 @@ const SPARKLINE_PROPERTIES = { spotColor: '#1EDCFF', tooltipClassname: 'sparkline-tooltip', disableHiddenCheck: true, -}; +} export class ClusterHUD extends React.Component { constructor(props) { - super(props); + super(props) this.state = { runningQueries: [], queuedQueries: [], @@ -48,250 +54,373 @@ export class ClusterHUD extends React.Component { lastCpuTime: null, initialized: false, - }; + } - this.refreshLoop = this.refreshLoop.bind(this); + this.refreshLoop = this.refreshLoop.bind(this) } resetTimer() { - clearTimeout(this.timeoutId); + clearTimeout(this.timeoutId) // stop refreshing when query finishes or fails if (this.state.query === null || !this.state.ended) { - this.timeoutId = setTimeout(this.refreshLoop, 1000); + this.timeoutId = setTimeout(this.refreshLoop, 1000) } } refreshLoop() { - clearTimeout(this.timeoutId); // to stop multiple series of refreshLoop from going on simultaneously - $.get('/ui/api/stats', function (clusterState) { + clearTimeout(this.timeoutId) // to stop multiple series of refreshLoop from going on simultaneously + $.get( + '/ui/api/stats', + function (clusterState) { + let newRowInputRate = [] + let newByteInputRate = [] + let newPerWorkerCpuTimeRate = [] + if (this.state.lastRefresh !== null) { + const rowsInputSinceRefresh = clusterState.totalInputRows - this.state.lastInputRows + const bytesInputSinceRefresh = clusterState.totalInputBytes - this.state.lastInputBytes + const cpuTimeSinceRefresh = clusterState.totalCpuTimeSecs - this.state.lastCpuTime + const secsSinceRefresh = (Date.now() - this.state.lastRefresh) / 1000.0 - let newRowInputRate = []; - let newByteInputRate = []; - let newPerWorkerCpuTimeRate = []; - if (this.state.lastRefresh !== null) { - const rowsInputSinceRefresh = clusterState.totalInputRows - this.state.lastInputRows; - const bytesInputSinceRefresh = clusterState.totalInputBytes - this.state.lastInputBytes; - const cpuTimeSinceRefresh = clusterState.totalCpuTimeSecs - this.state.lastCpuTime; - const secsSinceRefresh = (Date.now() - this.state.lastRefresh) / 1000.0; + newRowInputRate = addExponentiallyWeightedToHistory( + rowsInputSinceRefresh / secsSinceRefresh, + this.state.rowInputRate + ) + newByteInputRate = addExponentiallyWeightedToHistory( + bytesInputSinceRefresh / secsSinceRefresh, + this.state.byteInputRate + ) + newPerWorkerCpuTimeRate = addExponentiallyWeightedToHistory( + cpuTimeSinceRefresh / clusterState.activeWorkers / secsSinceRefresh, + this.state.perWorkerCpuTimeRate + ) + } - newRowInputRate = addExponentiallyWeightedToHistory(rowsInputSinceRefresh / secsSinceRefresh, this.state.rowInputRate); - newByteInputRate = addExponentiallyWeightedToHistory(bytesInputSinceRefresh / secsSinceRefresh, this.state.byteInputRate); - newPerWorkerCpuTimeRate = addExponentiallyWeightedToHistory((cpuTimeSinceRefresh / clusterState.activeWorkers) / secsSinceRefresh, this.state.perWorkerCpuTimeRate); - } - - this.setState({ - // instantaneous stats - runningQueries: addToHistory(clusterState.runningQueries, this.state.runningQueries), - queuedQueries: addToHistory(clusterState.queuedQueries, this.state.queuedQueries), - blockedQueries: addToHistory(clusterState.blockedQueries, this.state.blockedQueries), - activeWorkers: addToHistory(clusterState.activeWorkers, this.state.activeWorkers), + this.setState({ + // instantaneous stats + runningQueries: addToHistory(clusterState.runningQueries, this.state.runningQueries), + queuedQueries: addToHistory(clusterState.queuedQueries, this.state.queuedQueries), + blockedQueries: addToHistory(clusterState.blockedQueries, this.state.blockedQueries), + activeWorkers: addToHistory(clusterState.activeWorkers, this.state.activeWorkers), - // moving averages - runningDrivers: addExponentiallyWeightedToHistory(clusterState.runningDrivers, this.state.runningDrivers), - reservedMemory: addExponentiallyWeightedToHistory(clusterState.reservedMemory, this.state.reservedMemory), + // moving averages + runningDrivers: addExponentiallyWeightedToHistory( + clusterState.runningDrivers, + this.state.runningDrivers + ), + reservedMemory: addExponentiallyWeightedToHistory( + clusterState.reservedMemory, + this.state.reservedMemory + ), - // moving averages for diffs - rowInputRate: newRowInputRate, - byteInputRate: newByteInputRate, - perWorkerCpuTimeRate: newPerWorkerCpuTimeRate, + // moving averages for diffs + rowInputRate: newRowInputRate, + byteInputRate: newByteInputRate, + perWorkerCpuTimeRate: newPerWorkerCpuTimeRate, - lastInputRows: clusterState.totalInputRows, - lastInputBytes: clusterState.totalInputBytes, - lastCpuTime: clusterState.totalCpuTimeSecs, + lastInputRows: clusterState.totalInputRows, + lastInputBytes: clusterState.totalInputBytes, + lastCpuTime: clusterState.totalCpuTimeSecs, - initialized: true, + initialized: true, - lastRefresh: Date.now() - }); - this.resetTimer(); - }.bind(this)) - .fail(function () { - this.resetTimer(); - }.bind(this)); + lastRefresh: Date.now(), + }) + this.resetTimer() + }.bind(this) + ).fail( + function () { + this.resetTimer() + }.bind(this) + ) } componentDidMount() { - this.refreshLoop(); + this.refreshLoop() } componentDidUpdate() { // prevent multiple calls to componentDidUpdate (resulting from calls to setState or otherwise) within the refresh interval from re-rendering sparklines/charts - if (this.state.lastRender === null || (Date.now() - this.state.lastRender) >= 1000) { - const renderTimestamp = Date.now(); - $('#running-queries-sparkline').sparkline(this.state.runningQueries, $.extend({}, SPARKLINE_PROPERTIES, {chartRangeMin: 0})); - $('#blocked-queries-sparkline').sparkline(this.state.blockedQueries, $.extend({}, SPARKLINE_PROPERTIES, {chartRangeMin: 0})); - $('#queued-queries-sparkline').sparkline(this.state.queuedQueries, $.extend({}, SPARKLINE_PROPERTIES, {chartRangeMin: 0})); + if (this.state.lastRender === null || Date.now() - this.state.lastRender >= 1000) { + const renderTimestamp = Date.now() + $('#running-queries-sparkline').sparkline( + this.state.runningQueries, + $.extend({}, SPARKLINE_PROPERTIES, { chartRangeMin: 0 }) + ) + $('#blocked-queries-sparkline').sparkline( + this.state.blockedQueries, + $.extend({}, SPARKLINE_PROPERTIES, { chartRangeMin: 0 }) + ) + $('#queued-queries-sparkline').sparkline( + this.state.queuedQueries, + $.extend({}, SPARKLINE_PROPERTIES, { chartRangeMin: 0 }) + ) - $('#active-workers-sparkline').sparkline(this.state.activeWorkers, $.extend({}, SPARKLINE_PROPERTIES, {chartRangeMin: 0})); - $('#running-drivers-sparkline').sparkline(this.state.runningDrivers, $.extend({}, SPARKLINE_PROPERTIES, {numberFormatter: precisionRound})); - $('#reserved-memory-sparkline').sparkline(this.state.reservedMemory, $.extend({}, SPARKLINE_PROPERTIES, {numberFormatter: formatDataSizeBytes})); + $('#active-workers-sparkline').sparkline( + this.state.activeWorkers, + $.extend({}, SPARKLINE_PROPERTIES, { chartRangeMin: 0 }) + ) + $('#running-drivers-sparkline').sparkline( + this.state.runningDrivers, + $.extend({}, SPARKLINE_PROPERTIES, { + numberFormatter: precisionRound, + }) + ) + $('#reserved-memory-sparkline').sparkline( + this.state.reservedMemory, + $.extend({}, SPARKLINE_PROPERTIES, { + numberFormatter: formatDataSizeBytes, + }) + ) - $('#row-input-rate-sparkline').sparkline(this.state.rowInputRate, $.extend({}, SPARKLINE_PROPERTIES, {numberFormatter: formatCount})); - $('#byte-input-rate-sparkline').sparkline(this.state.byteInputRate, $.extend({}, SPARKLINE_PROPERTIES, {numberFormatter: formatDataSizeBytes})); - $('#cpu-time-rate-sparkline').sparkline(this.state.perWorkerCpuTimeRate, $.extend({}, SPARKLINE_PROPERTIES, {numberFormatter: precisionRound})); + $('#row-input-rate-sparkline').sparkline( + this.state.rowInputRate, + $.extend({}, SPARKLINE_PROPERTIES, { + numberFormatter: formatCount, + }) + ) + $('#byte-input-rate-sparkline').sparkline( + this.state.byteInputRate, + $.extend({}, SPARKLINE_PROPERTIES, { + numberFormatter: formatDataSizeBytes, + }) + ) + $('#cpu-time-rate-sparkline').sparkline( + this.state.perWorkerCpuTimeRate, + $.extend({}, SPARKLINE_PROPERTIES, { + numberFormatter: precisionRound, + }) + ) this.setState({ - lastRender: renderTimestamp - }); + lastRender: renderTimestamp, + }) } - $('[data-toggle="tooltip"]').tooltip(); + $('[data-toggle="tooltip"]').tooltip() } render() { - return (
-
-
-
-
- - Running queries - -
-
-
-
- - Active workers - + return ( +
+
+
+
+
+ + Running queries + +
-
-
-
- - Rows/sec - +
+
+ + Active workers + +
-
-
-
-
-
- - {this.state.runningQueries[this.state.runningQueries.length - 1]} - -
Loading ...
+
+
+ + Rows/sec + +
-
-
- + -
-
- - {formatCount(this.state.rowInputRate[this.state.rowInputRate.length - 1])} - -
Loading ...
+ -
-
-
-
-
- - Queued queries - +
+
+ + {formatCount(this.state.rowInputRate[this.state.rowInputRate.length - 1])} + + +
Loading ...
+
+
-
-
- - Runnable drivers - +
+
+
+ + Queued queries + +
-
-
-
- - Bytes/sec - +
+
+ + Runnable drivers + +
-
-
-
-
-
- - {this.state.queuedQueries[this.state.queuedQueries.length - 1]} - -
Loading ...
+
+
+ + Bytes/sec + +
-
-
- - {formatCount(this.state.runningDrivers[this.state.runningDrivers.length - 1])} - -
Loading ...
+
+
+
+ + {this.state.queuedQueries[this.state.queuedQueries.length - 1]} + + +
Loading ...
+
+
-
-
-
- - {formatDataSizeBytes(this.state.byteInputRate[this.state.byteInputRate.length - 1])} - -
Loading ...
+
+
+ + {formatCount(this.state.runningDrivers[this.state.runningDrivers.length - 1])} + + +
Loading ...
+
+
-
-
-
-
-
- - Blocked Queries - +
+
+ + {formatDataSizeBytes(this.state.byteInputRate[this.state.byteInputRate.length - 1])} + + +
Loading ...
+
+
-
-
- - Reserved Memory (B) - +
+
+
+ + Blocked Queries + +
-
-
-
- - Worker Parallelism - +
+
+ + Reserved Memory (B) + +
-
-
-
-
-
- - {this.state.blockedQueries[this.state.blockedQueries.length - 1]} - -
Loading ...
+
+
+ + Worker Parallelism + +
-
-
- - {formatDataSizeBytes(this.state.reservedMemory[this.state.reservedMemory.length - 1])} - -
Loading ...
+
+
+
+ + {this.state.blockedQueries[this.state.blockedQueries.length - 1]} + + +
Loading ...
+
+
-
-
-
- - {formatCount(this.state.perWorkerCpuTimeRate[this.state.perWorkerCpuTimeRate.length - 1])} - -
Loading ...
+
+
+ + {formatDataSizeBytes( + this.state.reservedMemory[this.state.reservedMemory.length - 1] + )} + + +
Loading ...
+
+
+
+
+
+ + {formatCount( + this.state.perWorkerCpuTimeRate[this.state.perWorkerCpuTimeRate.length - 1] + )} + + +
Loading ...
+
+
-
); + ) } } - diff --git a/core/trino-web-ui/src/main/resources/webapp/src/components/LivePlan.jsx b/core/trino-web-ui/src/main/resources/webapp/src/components/LivePlan.jsx index 48aff6fe3740..2614e79f68dc 100644 --- a/core/trino-web-ui/src/main/resources/webapp/src/components/LivePlan.jsx +++ b/core/trino-web-ui/src/main/resources/webapp/src/components/LivePlan.jsx @@ -13,13 +13,20 @@ */ //@flow -import React from "react"; -import ReactDOMServer from "react-dom/server"; -import * as dagreD3 from "dagre-d3"; -import * as d3 from "d3"; - -import {formatRows, getStageStateColor, initializeGraph, initializeSvg, parseAndFormatDataSize, truncateString} from "../utils"; -import {QueryHeader} from "./QueryHeader"; +import React from 'react' +import ReactDOMServer from 'react-dom/server' +import * as dagreD3 from 'dagre-d3' +import * as d3 from 'd3' + +import { + formatRows, + getStageStateColor, + initializeGraph, + initializeSvg, + parseAndFormatDataSize, + truncateString, +} from '../utils' +import { QueryHeader } from './QueryHeader' type StageStatisticsProps = { stage: any, @@ -37,18 +44,18 @@ type StageNodeInfo = { class StageStatistics extends React.Component { static getStages(queryInfo: any): Map { - const stages: Map = new Map(); - StageStatistics.flattenStage(queryInfo.outputStage, stages); - return stages; + const stages: Map = new Map() + StageStatistics.flattenStage(queryInfo.outputStage, stages) + return stages } - static flattenStage(stageInfo : any, result : any) { + static flattenStage(stageInfo: any, result: any) { stageInfo.subStages.forEach(function (stage) { - StageStatistics.flattenStage(stage, result); - }); + StageStatistics.flattenStage(stage, result) + }) - const nodes = new Map(); - StageStatistics.flattenNode(result, stageInfo.plan.root, JSON.parse(stageInfo.plan.jsonRepresentation), nodes); + const nodes = new Map() + StageStatistics.flattenNode(result, stageInfo.plan.root, JSON.parse(stageInfo.plan.jsonRepresentation), nodes) result.set(stageInfo.plan.id, { stageId: stageInfo.stageId, @@ -57,47 +64,52 @@ class StageStatistics extends React.Component) { + static flattenNode(stages: any, rootNodeInfo: any, node: any, result: Map) { result.set(node.id, { id: node.id, name: node['name'], descriptor: node['descriptor'], details: node['details'], - sources: node.children.map(node => node.id), - }); + sources: node.children.map((node) => node.id), + }) node.children.forEach(function (child) { - StageStatistics.flattenNode(stages, rootNodeInfo, child, result); - }); + StageStatistics.flattenNode(stages, rootNodeInfo, child, result) + }) } - render() : any { - const stage = this.props.stage; - const stats = this.props.stage.stageStats; + render(): any { + const stage = this.props.stage + const stats = this.props.stage.stageStats return (

Stage {stage.id}

{stage.state} -
- CPU: {stats.totalCpuTime}
- Buffered: {parseAndFormatDataSize(stats.bufferedDataSize)}
- {stats.fullyBlocked ? -
Blocked: {stats.totalBlockedTime}
: +
+ CPU: {stats.totalCpuTime} +
+ Buffered: {parseAndFormatDataSize(stats.bufferedDataSize)} +
+ {stats.fullyBlocked ? ( +
Blocked: {stats.totalBlockedTime}
+ ) : (
Blocked: {stats.totalBlockedTime}
- } + )} Memory: {parseAndFormatDataSize(stats.userMemoryReservation)} -
- Splits: {"Q:" + stats.queuedDrivers + ", R:" + stats.runningDrivers + ", F:" + stats.completedDrivers} -
- Input: {parseAndFormatDataSize(stats.rawInputDataSize) + " / " + formatRows(stats.rawInputPositions)} +
+ Splits:{' '} + {'Q:' + stats.queuedDrivers + ', R:' + stats.runningDrivers + ', F:' + stats.completedDrivers} +
+ Input:{' '} + {parseAndFormatDataSize(stats.rawInputDataSize) + ' / ' + formatRows(stats.rawInputPositions)}
- ); + ) } } @@ -112,31 +124,35 @@ type PlanNodeState = {} class PlanNode extends React.Component { constructor(props: PlanNodeProps) { - super(props); + super(props) } - render() : any { + render(): any { // get join distribution type by matching details to a regular expression - var distribution = ""; - var matchArray = this.props.details.join("\n").match(/Distribution:\s+(\w+)/); + var distribution = '' + var matchArray = this.props.details.join('\n').match(/Distribution:\s+(\w+)/) if (matchArray !== null) { - distribution = " (" + matchArray[1] + ")"; + distribution = ' (' + matchArray[1] + ')' } var descriptor = Object.entries(this.props.descriptor) - .map(([key, value]) => key + " = " + String(value)) - .join(", "); - descriptor = "(" + descriptor + ")"; + .map(([key, value]) => key + ' = ' + String(value)) + .join(', ') + descriptor = '(' + descriptor + ')' return ( -
" + this.props.name + "" + descriptor}> +
' + this.props.name + '' + descriptor} + > {this.props.name + distribution} -
- {truncateString(descriptor, 35)} -
+
{truncateString(descriptor, 35)}
- ); + ) } } @@ -157,10 +173,10 @@ type LivePlanState = { } export class LivePlan extends React.Component { - timeoutId: TimeoutID; + timeoutId: TimeoutID constructor(props: LivePlanProps) { - super(props); + super(props) this.state = { initialized: false, ended: false, @@ -170,166 +186,204 @@ export class LivePlan extends React.Component { graph: initializeGraph(), svg: null, render: new dagreD3.render(), - }; + } } resetTimer() { - clearTimeout(this.timeoutId); + clearTimeout(this.timeoutId) // stop refreshing when query finishes or fails if (this.state.query === null || !this.state.ended) { - this.timeoutId = setTimeout(this.refreshLoop.bind(this), 1000); + this.timeoutId = setTimeout(this.refreshLoop.bind(this), 1000) } } refreshLoop = () => { - clearTimeout(this.timeoutId); // to stop multiple series of refreshLoop from going on simultaneously + clearTimeout(this.timeoutId) // to stop multiple series of refreshLoop from going on simultaneously fetch('/ui/api/query/' + this.props.queryId) - .then(response => response.json()) - .then(query => { + .then((response) => response.json()) + .then((query) => { this.setState({ query: query, initialized: true, ended: query.finalQueryInfo, - }); - this.resetTimer(); + }) + this.resetTimer() }) .catch(() => { this.setState({ initialized: true, - }); - this.resetTimer(); - }); + }) + this.resetTimer() + }) } static handleStageClick(stageCssId: string) { - window.open("stage.html?" + stageCssId, '_blank'); + window.open('stage.html?' + stageCssId, '_blank') } componentDidMount() { - this.refreshLoop.bind(this)(); - new window.ClipboardJS('.copy-button'); + this.refreshLoop.bind(this)() + new window.ClipboardJS('.copy-button') } updateD3Stage(stage: StageNodeInfo, graph: any, allStages: Map) { - const clusterId = stage.stageId; - const stageRootNodeId = "stage-" + stage.id + "-root"; - const color = getStageStateColor(stage); + const clusterId = stage.stageId + const stageRootNodeId = 'stage-' + stage.id + '-root' + const color = getStageStateColor(stage) - graph.setNode(clusterId, {style: 'fill: ' + color, labelStyle: 'fill: #fff'}); + graph.setNode(clusterId, { + style: 'fill: ' + color, + labelStyle: 'fill: #fff', + }) // this is a non-standard use of ReactDOMServer, but it's the cleanest way to unify DagreD3 with React - const html = ReactDOMServer.renderToString(); - - graph.setNode(stageRootNodeId, {class: "stage-stats", label: html, labelType: "html"}); - graph.setParent(stageRootNodeId, clusterId); - graph.setEdge("node-" + stage.root, stageRootNodeId, {style: "visibility: hidden"}); - - stage.nodes.forEach(node => { - const nodeId = "node-" + node.id; - const nodeHtml = ReactDOMServer.renderToString(); - - graph.setNode(nodeId, {label: nodeHtml, style: 'fill: #fff', labelType: "html"}); - graph.setParent(nodeId, clusterId); + const html = ReactDOMServer.renderToString() + + graph.setNode(stageRootNodeId, { + class: 'stage-stats', + label: html, + labelType: 'html', + }) + graph.setParent(stageRootNodeId, clusterId) + graph.setEdge('node-' + stage.root, stageRootNodeId, { + style: 'visibility: hidden', + }) + + stage.nodes.forEach((node) => { + const nodeId = 'node-' + node.id + const nodeHtml = ReactDOMServer.renderToString() + + graph.setNode(nodeId, { + label: nodeHtml, + style: 'fill: #fff', + labelType: 'html', + }) + graph.setParent(nodeId, clusterId) - node.sources.forEach(source => { - graph.setEdge("node-" + source, nodeId, {class: "plan-edge", arrowheadClass: "plan-arrowhead"}); - }); + node.sources.forEach((source) => { + graph.setEdge('node-' + source, nodeId, { + class: 'plan-edge', + arrowheadClass: 'plan-arrowhead', + }) + }) - var sourceFragmentIds = node.descriptor['sourceFragmentIds']; + var sourceFragmentIds = node.descriptor['sourceFragmentIds'] if (sourceFragmentIds) { - var remoteSources = sourceFragmentIds.replace('[', '').replace(']', '').split(', '); + var remoteSources = sourceFragmentIds.replace('[', '').replace(']', '').split(', ') if (remoteSources.length > 0) { - graph.setNode(nodeId, {label: '', shape: "circle"}); + graph.setNode(nodeId, { label: '', shape: 'circle' }) - remoteSources.forEach(sourceId => { - const source = allStages.get(sourceId); + remoteSources.forEach((sourceId) => { + const source = allStages.get(sourceId) if (source) { - const sourceStats = source.stageStats; - graph.setEdge("stage-" + sourceId + "-root", nodeId, { - class: "plan-edge", - style: "stroke-width: 4px", - arrowheadClass: "plan-arrowhead", - label: parseAndFormatDataSize(sourceStats.outputDataSize) + " / " + formatRows(sourceStats.outputPositions), - labelStyle: "color: #fff; font-weight: bold; font-size: 24px;", - labelType: "html", - }); + const sourceStats = source.stageStats + graph.setEdge('stage-' + sourceId + '-root', nodeId, { + class: 'plan-edge', + style: 'stroke-width: 4px', + arrowheadClass: 'plan-arrowhead', + label: + parseAndFormatDataSize(sourceStats.outputDataSize) + + ' / ' + + formatRows(sourceStats.outputPositions), + labelStyle: 'color: #fff; font-weight: bold; font-size: 24px;', + labelType: 'html', + }) } - }); + }) } } - }); + }) } updateD3Graph() { if (!this.state.svg) { this.setState({ - svg: initializeSvg("#plan-canvas"), - }); - return; + svg: initializeSvg('#plan-canvas'), + }) + return } if (!this.state.query) { - return; + return } - const graph = this.state.graph; - const stages = StageStatistics.getStages(this.state.query); - stages.forEach(stage => { - this.updateD3Stage(stage, graph, stages); - }); - - const inner = d3.select("#plan-canvas g"); - this.state.render(inner, graph); - - const svg = this.state.svg; - svg.selectAll("g.cluster").on("click", LivePlan.handleStageClick); - - const width = parseInt(window.getComputedStyle(document.getElementById("live-plan"), null).getPropertyValue("width").replace(/px/, "")) - 50; - const height = parseInt(window.getComputedStyle(document.getElementById("live-plan"), null).getPropertyValue("height").replace(/px/, "")) - 50; - - const graphHeight = graph.graph().height + 100; - const graphWidth = graph.graph().width + 100; + const graph = this.state.graph + const stages = StageStatistics.getStages(this.state.query) + stages.forEach((stage) => { + this.updateD3Stage(stage, graph, stages) + }) + + const inner = d3.select('#plan-canvas g') + this.state.render(inner, graph) + + const svg = this.state.svg + svg.selectAll('g.cluster').on('click', LivePlan.handleStageClick) + + const width = + parseInt( + window + .getComputedStyle(document.getElementById('live-plan'), null) + .getPropertyValue('width') + .replace(/px/, '') + ) - 50 + const height = + parseInt( + window + .getComputedStyle(document.getElementById('live-plan'), null) + .getPropertyValue('height') + .replace(/px/, '') + ) - 50 + + const graphHeight = graph.graph().height + 100 + const graphWidth = graph.graph().width + 100 if (this.state.ended) { // Zoom doesn't deal well with DOM changes - const initialScale = Math.min(width / graphWidth, height / graphHeight); - const zoom = d3.zoom().scaleExtent([initialScale, 1]).on("zoom", function () { - inner.attr("transform", d3.event.transform); - }); - - svg.call(zoom); - svg.call(zoom.transform, d3.zoomIdentity.translate((width - graph.graph().width * initialScale) / 2, 20).scale(initialScale)); - svg.attr('height', height); - svg.attr('width', width); - } - else { - svg.attr('height', graphHeight); - svg.attr('width', graphWidth); + const initialScale = Math.min(width / graphWidth, height / graphHeight) + const zoom = d3 + .zoom() + .scaleExtent([initialScale, 1]) + .on('zoom', function () { + inner.attr('transform', d3.event.transform) + }) + + svg.call(zoom) + svg.call( + zoom.transform, + d3.zoomIdentity.translate((width - graph.graph().width * initialScale) / 2, 20).scale(initialScale) + ) + svg.attr('height', height) + svg.attr('width', width) + } else { + svg.attr('height', graphHeight) + svg.attr('width', graphWidth) } } componentDidUpdate() { - this.updateD3Graph(); + this.updateD3Graph() //$FlowFixMe $('[data-toggle="tooltip"]').tooltip() } - render() : any { - const query = this.state.query; + render(): any { + const query = this.state.query if (query === null || this.state.initialized === false) { - let label = (
Loading...
); + let label =
Loading...
if (this.state.initialized) { - label =
Query not found
; + label =
Query not found
} return (
-

{label}

+
+

{label}

+
- ); + ) } - let loadingMessage = null; + let loadingMessage = null if (query && !query.outputStage) { loadingMessage = (
@@ -342,7 +396,7 @@ export class LivePlan extends React.Component { } // TODO: Refactor components to move refreshLoop to parent rather than using this property - const queryHeader = this.props.isEmbedded ? null : ; + const queryHeader = this.props.isEmbedded ? null : return (
{queryHeader} @@ -351,13 +405,14 @@ export class LivePlan extends React.Component { {loadingMessage}
- {this.state.ended ? "Scroll to zoom." : "Zoom disabled while query is running." } Click stage to view additional statistics + {this.state.ended ? 'Scroll to zoom.' : 'Zoom disabled while query is running.'} Click + stage to view additional statistics
- +
- ); + ) } } diff --git a/core/trino-web-ui/src/main/resources/webapp/src/components/PageTitle.jsx b/core/trino-web-ui/src/main/resources/webapp/src/components/PageTitle.jsx index f5051d33e6dc..6a48c881c954 100644 --- a/core/trino-web-ui/src/main/resources/webapp/src/components/PageTitle.jsx +++ b/core/trino-web-ui/src/main/resources/webapp/src/components/PageTitle.jsx @@ -12,10 +12,10 @@ * limitations under the License. */ //@flow -import React from "react"; +import React from 'react' type Props = { - title: string + title: string, } type State = { @@ -28,10 +28,10 @@ type State = { } export class PageTitle extends React.Component { - timeoutId: TimeoutID; + timeoutId: TimeoutID constructor(props: Props) { - super(props); + super(props) this.state = { noConnection: false, lightShown: false, @@ -39,70 +39,69 @@ export class PageTitle extends React.Component { lastSuccess: Date.now(), modalShown: false, errorText: null, - }; + } } refreshLoop = () => { - clearTimeout(this.timeoutId); - fetch("/ui/api/cluster") - .then(response => { + clearTimeout(this.timeoutId) + fetch('/ui/api/cluster') + .then((response) => { if (response.status === 401) { - location.reload(); + location.reload() } - return response.json(); + return response.json() }) - .then(info => { + .then((info) => { this.setState({ info: info, noConnection: false, lastSuccess: Date.now(), modalShown: false, - }); + }) //$FlowFixMe$ Bootstrap 3 plugin - $('#no-connection-modal').modal('hide'); - this.resetTimer(); + $('#no-connection-modal').modal('hide') + this.resetTimer() }) - .catch(fail => { + .catch((fail) => { this.setState({ noConnection: true, lightShown: !this.state.lightShown, - errorText: fail - }); - this.resetTimer(); + errorText: fail, + }) + this.resetTimer() - if (!this.state.modalShown && (fail || (Date.now() - this.state.lastSuccess) > 30 * 1000)) { + if (!this.state.modalShown && (fail || Date.now() - this.state.lastSuccess > 30 * 1000)) { //$FlowFixMe$ Bootstrap 3 plugin - $('#no-connection-modal').modal(); - this.setState({modalShown: true}); + $('#no-connection-modal').modal() + this.setState({ modalShown: true }) } - }); + }) } resetTimer() { - clearTimeout(this.timeoutId); - this.timeoutId = setTimeout(this.refreshLoop.bind(this), 1000); + clearTimeout(this.timeoutId) + this.timeoutId = setTimeout(this.refreshLoop.bind(this), 1000) } componentDidMount() { - this.refreshLoop.bind(this)(); + this.refreshLoop.bind(this)() } - renderStatusLight() : any { + renderStatusLight(): any { if (this.state.noConnection) { if (this.state.lightShown) { - return ; - } - else { - return + return + } else { + return } } - return ; + return } - render() : any { - const info = this.state.info; + render(): any { + const info = this.state.info if (!info) { - return null; + return null } return ( @@ -112,14 +111,16 @@ export class PageTitle extends React.Component {
- - - - + + + +
- - - {this.props.title} -
+ + + + + {this.props.title} +
@@ -127,30 +128,41 @@ export class PageTitle extends React.Component {
  • - Version
    - {info.nodeVersion.version} + Version +
    + + {info.nodeVersion.version} +
  • - Environment
    - {info.environment} + Environment +
    + + {info.environment} +
  • - Uptime
    + Uptime +
    - {this.renderStatusLight()} - + {this.renderStatusLight()} +
      - {info.uptime} + + {info.uptime} +
  • - Log Out + + Log Out +
  • @@ -165,13 +177,13 @@ export class PageTitle extends React.Component {

    Unable to connect to server

    -

    {this.state.errorText ? "Error: " + this.state.errorText : null}

    +

    {this.state.errorText ? 'Error: ' + this.state.errorText : null}

- ); + ) } } diff --git a/core/trino-web-ui/src/main/resources/webapp/src/components/QueryDetail.jsx b/core/trino-web-ui/src/main/resources/webapp/src/components/QueryDetail.jsx index 4e9b7435f358..231e7d71220c 100644 --- a/core/trino-web-ui/src/main/resources/webapp/src/components/QueryDetail.jsx +++ b/core/trino-web-ui/src/main/resources/webapp/src/components/QueryDetail.jsx @@ -12,8 +12,8 @@ * limitations under the License. */ -import React from "react"; -import Reactable from "reactable"; +import React from 'react' +import Reactable from 'reactable' import { addToHistory, @@ -35,96 +35,106 @@ import { parseAndFormatDataSize, parseDataSize, parseDuration, - precisionRound -} from "../utils"; -import {QueryHeader} from "./QueryHeader"; + precisionRound, +} from '../utils' +import { QueryHeader } from './QueryHeader' const Table = Reactable.Table, Thead = Reactable.Thead, Th = Reactable.Th, Tr = Reactable.Tr, - Td = Reactable.Td; + Td = Reactable.Td class TaskList extends React.Component { static removeQueryId(id) { - const pos = id.indexOf('.'); + const pos = id.indexOf('.') if (pos !== -1) { - return id.substring(pos + 1); + return id.substring(pos + 1) } - return id; + return id } static compareTaskId(taskA, taskB) { - const taskIdArrA = TaskList.removeQueryId(taskA).split("."); - const taskIdArrB = TaskList.removeQueryId(taskB).split("."); + const taskIdArrA = TaskList.removeQueryId(taskA).split('.') + const taskIdArrB = TaskList.removeQueryId(taskB).split('.') if (taskIdArrA.length > taskIdArrB.length) { - return 1; + return 1 } for (let i = 0; i < taskIdArrA.length; i++) { - const anum = Number.parseInt(taskIdArrA[i]); - const bnum = Number.parseInt(taskIdArrB[i]); + const anum = Number.parseInt(taskIdArrA[i]) + const bnum = Number.parseInt(taskIdArrB[i]) if (anum !== bnum) { - return anum > bnum ? 1 : -1; + return anum > bnum ? 1 : -1 } } - return 0; + return 0 } static showPortNumbers(tasks) { // check if any host has multiple port numbers - const hostToPortNumber = {}; + const hostToPortNumber = {} for (let i = 0; i < tasks.length; i++) { - const taskUri = tasks[i].taskStatus.self; - const hostname = getHostname(taskUri); - const port = getPort(taskUri); - if ((hostname in hostToPortNumber) && (hostToPortNumber[hostname] !== port)) { - return true; + const taskUri = tasks[i].taskStatus.self + const hostname = getHostname(taskUri) + const port = getPort(taskUri) + if (hostname in hostToPortNumber && hostToPortNumber[hostname] !== port) { + return true } - hostToPortNumber[hostname] = port; + hostToPortNumber[hostname] = port } - return false; + return false } static formatState(state, fullyBlocked) { - if (fullyBlocked && state === "RUNNING") { - return "BLOCKED"; - } - else { - return state; + if (fullyBlocked && state === 'RUNNING') { + return 'BLOCKED' + } else { + return state } } render() { - const tasks = this.props.tasks; - const taskRetriesEnabled = this.props.taskRetriesEnabled; + const tasks = this.props.tasks + const taskRetriesEnabled = this.props.taskRetriesEnabled if (tasks === undefined || tasks.length === 0) { return (
-

No threads in the selected group

-
); +
+

No threads in the selected group

+
+
+ ) } - const showPortNumbers = TaskList.showPortNumbers(tasks); + const showPortNumbers = TaskList.showPortNumbers(tasks) - const renderedTasks = tasks.map(task => { - let elapsedTime = parseDuration(task.stats.elapsedTime); + const renderedTasks = tasks.map((task) => { + let elapsedTime = parseDuration(task.stats.elapsedTime) if (elapsedTime === 0) { - elapsedTime = Date.now() - Date.parse(task.stats.createTime); + elapsedTime = Date.now() - Date.parse(task.stats.createTime) } return ( - + {getTaskIdSuffix(task.taskStatus.taskId)} - + {showPortNumbers ? getHostAndPort(task.taskStatus.self) : getHostname(task.taskStatus.self)} @@ -170,21 +180,23 @@ class TaskList extends React.Component { {parseAndFormatDataSize(task.stats.peakUserMemoryReservation)} - {taskRetriesEnabled && - - {parseAndFormatDataSize(task.estimatedMemory)} - - } + {taskRetriesEnabled && ( + + {parseAndFormatDataSize(task.estimatedMemory)} + + )} - ); - }); + ) + }) return ( - + defaultSort={{ column: 'id', direction: 'asc' }} + > - - - - - - - - - - - - - - - - {taskRetriesEnabled && - - } + + + + + + + + + + + + + + + + {taskRetriesEnabled && } {renderedTasks}
IDHostStateRowsRows/sBytesBytes/sElapsedCPU TimeMemPeak MemEst MemIDHostState + + + + + + + + RowsRows/sBytesBytes/sElapsedCPU TimeMemPeak MemEst Mem
- ); + ) } } -const BAR_CHART_WIDTH = 800; +const BAR_CHART_WIDTH = 800 const BAR_CHART_PROPERTIES = { type: 'bar', @@ -246,9 +285,9 @@ const BAR_CHART_PROPERTIES = { tooltipClassname: 'sparkline-tooltip', tooltipFormat: 'Task {{offset:offset}} - {{value}}', disableHiddenCheck: true, -}; +} -const HISTOGRAM_WIDTH = 175; +const HISTOGRAM_WIDTH = 175 const HISTOGRAM_PROPERTIES = { type: 'bar', @@ -261,24 +300,24 @@ const HISTOGRAM_PROPERTIES = { tooltipClassname: 'sparkline-tooltip', tooltipFormat: '{{offset:offset}} -- {{value}} tasks', disableHiddenCheck: true, -}; +} class StageSummary extends React.Component { constructor(props) { - super(props); + super(props) this.state = { expanded: false, lastRender: null, - taskFilter: TASK_FILTER.ALL - }; + taskFilter: TASK_FILTER.ALL, + } } getExpandedIcon() { - return this.state.expanded ? "glyphicon-chevron-up" : "glyphicon-chevron-down"; + return this.state.expanded ? 'glyphicon-chevron-up' : 'glyphicon-chevron-down' } getExpandedStyle() { - return this.state.expanded ? {} : {display: "none"}; + return this.state.expanded ? {} : { display: 'none' } } toggleExpanded() { @@ -288,400 +327,434 @@ class StageSummary extends React.Component { } static renderHistogram(histogramId, inputData, numberFormatter) { - const numBuckets = Math.min(HISTOGRAM_WIDTH, Math.sqrt(inputData.length)); - const dataMin = Math.min.apply(null, inputData); - const dataMax = Math.max.apply(null, inputData); - const bucketSize = (dataMax - dataMin) / numBuckets; + const numBuckets = Math.min(HISTOGRAM_WIDTH, Math.sqrt(inputData.length)) + const dataMin = Math.min.apply(null, inputData) + const dataMax = Math.max.apply(null, inputData) + const bucketSize = (dataMax - dataMin) / numBuckets - let histogramData = []; + let histogramData = [] if (bucketSize === 0) { - histogramData = [inputData.length]; - } - else { + histogramData = [inputData.length] + } else { for (let i = 0; i < numBuckets + 1; i++) { - histogramData.push(0); + histogramData.push(0) } for (let i in inputData) { - const dataPoint = inputData[i]; - const bucket = Math.floor((dataPoint - dataMin) / bucketSize); - histogramData[bucket] = histogramData[bucket] + 1; + const dataPoint = inputData[i] + const bucket = Math.floor((dataPoint - dataMin) / bucketSize) + histogramData[bucket] = histogramData[bucket] + 1 } } - const tooltipValueLookups = {'offset': {}}; + const tooltipValueLookups = { offset: {} } for (let i = 0; i < histogramData.length; i++) { - tooltipValueLookups['offset'][i] = numberFormatter(dataMin + (i * bucketSize)) + "-" + numberFormatter(dataMin + ((i + 1) * bucketSize)); + tooltipValueLookups['offset'][i] = + numberFormatter(dataMin + i * bucketSize) + '-' + numberFormatter(dataMin + (i + 1) * bucketSize) } - const stageHistogramProperties = $.extend({}, HISTOGRAM_PROPERTIES, {barWidth: (HISTOGRAM_WIDTH / histogramData.length), tooltipValueLookups: tooltipValueLookups}); - $(histogramId).sparkline(histogramData, stageHistogramProperties); + const stageHistogramProperties = $.extend({}, HISTOGRAM_PROPERTIES, { + barWidth: HISTOGRAM_WIDTH / histogramData.length, + tooltipValueLookups: tooltipValueLookups, + }) + $(histogramId).sparkline(histogramData, stageHistogramProperties) } componentDidUpdate() { - const stage = this.props.stage; - const numTasks = stage.tasks.length; + const stage = this.props.stage + const numTasks = stage.tasks.length // sort the x-axis - stage.tasks.sort((taskA, taskB) => getTaskNumber(taskA.taskStatus.taskId) - getTaskNumber(taskB.taskStatus.taskId)); + stage.tasks.sort( + (taskA, taskB) => getTaskNumber(taskA.taskStatus.taskId) - getTaskNumber(taskB.taskStatus.taskId) + ) - const scheduledTimes = stage.tasks.map(task => parseDuration(task.stats.totalScheduledTime)); - const cpuTimes = stage.tasks.map(task => parseDuration(task.stats.totalCpuTime)); + const scheduledTimes = stage.tasks.map((task) => parseDuration(task.stats.totalScheduledTime)) + const cpuTimes = stage.tasks.map((task) => parseDuration(task.stats.totalCpuTime)) // prevent multiple calls to componentDidUpdate (resulting from calls to setState or otherwise) within the refresh interval from re-rendering sparklines/charts - if (this.state.lastRender === null || (Date.now() - this.state.lastRender) >= 1000) { - const renderTimestamp = Date.now(); - const stageId = getStageNumber(stage.stageId); + if (this.state.lastRender === null || Date.now() - this.state.lastRender >= 1000) { + const renderTimestamp = Date.now() + const stageId = getStageNumber(stage.stageId) - StageSummary.renderHistogram('#scheduled-time-histogram-' + stageId, scheduledTimes, formatDuration); - StageSummary.renderHistogram('#cpu-time-histogram-' + stageId, cpuTimes, formatDuration); + StageSummary.renderHistogram('#scheduled-time-histogram-' + stageId, scheduledTimes, formatDuration) + StageSummary.renderHistogram('#cpu-time-histogram-' + stageId, cpuTimes, formatDuration) if (this.state.expanded) { // this needs to be a string otherwise it will also be passed to numberFormatter - const tooltipValueLookups = {'offset': {}}; + const tooltipValueLookups = { offset: {} } for (let i = 0; i < numTasks; i++) { - tooltipValueLookups['offset'][i] = getStageNumber(stage.stageId) + "." + i; + tooltipValueLookups['offset'][i] = getStageNumber(stage.stageId) + '.' + i } - const stageBarChartProperties = $.extend({}, BAR_CHART_PROPERTIES, {barWidth: BAR_CHART_WIDTH / numTasks, tooltipValueLookups: tooltipValueLookups}); + const stageBarChartProperties = $.extend({}, BAR_CHART_PROPERTIES, { + barWidth: BAR_CHART_WIDTH / numTasks, + tooltipValueLookups: tooltipValueLookups, + }) - $('#scheduled-time-bar-chart-' + stageId).sparkline(scheduledTimes, $.extend({}, stageBarChartProperties, {numberFormatter: formatDuration})); - $('#cpu-time-bar-chart-' + stageId).sparkline(cpuTimes, $.extend({}, stageBarChartProperties, {numberFormatter: formatDuration})); + $('#scheduled-time-bar-chart-' + stageId).sparkline( + scheduledTimes, + $.extend({}, stageBarChartProperties, { + numberFormatter: formatDuration, + }) + ) + $('#cpu-time-bar-chart-' + stageId).sparkline( + cpuTimes, + $.extend({}, stageBarChartProperties, { + numberFormatter: formatDuration, + }) + ) } this.setState({ - lastRender: renderTimestamp - }); + lastRender: renderTimestamp, + }) } } renderTaskList(taskRetriesEnabled) { - let tasks = this.state.expanded ? this.props.stage.tasks : []; - tasks = tasks.filter(task => this.state.taskFilter(task.taskStatus.state), this); - return (); + let tasks = this.state.expanded ? this.props.stage.tasks : [] + tasks = tasks.filter((task) => this.state.taskFilter(task.taskStatus.state), this) + return } handleTaskFilterClick(filter, event) { this.setState({ - taskFilter: filter - }); - event.preventDefault(); + taskFilter: filter, + }) + event.preventDefault() } renderTaskFilterListItem(taskFilter, taskFilterText) { return ( -
  • {taskFilterText}
  • - ); +
  • + + {taskFilterText} + +
  • + ) } renderTaskFilter() { - return (
    -
    -

    Tasks

    -
    -
    - - - - - - -
    -
    - -
      - {this.renderTaskFilterListItem(TASK_FILTER.ALL, "All")} - {this.renderTaskFilterListItem(TASK_FILTER.PLANNED, "Planned")} - {this.renderTaskFilterListItem(TASK_FILTER.RUNNING, "Running")} - {this.renderTaskFilterListItem(TASK_FILTER.FINISHED, "Finished")} - {this.renderTaskFilterListItem(TASK_FILTER.FAILED, "Aborted/Canceled/Failed")} -
    -
    -
    -
    -
    - ); + return ( +
    +
    +

    Tasks

    +
    +
    + + + + + + +
    +
    + +
      + {this.renderTaskFilterListItem(TASK_FILTER.ALL, 'All')} + {this.renderTaskFilterListItem(TASK_FILTER.PLANNED, 'Planned')} + {this.renderTaskFilterListItem(TASK_FILTER.RUNNING, 'Running')} + {this.renderTaskFilterListItem(TASK_FILTER.FINISHED, 'Finished')} + {this.renderTaskFilterListItem( + TASK_FILTER.FAILED, + 'Aborted/Canceled/Failed' + )} +
    +
    +
    +
    +
    + ) } render() { - const stage = this.props.stage; - const taskRetriesEnabled = this.props.taskRetriesEnabled; + const stage = this.props.stage + const taskRetriesEnabled = this.props.taskRetriesEnabled if (stage === undefined || !stage.hasOwnProperty('plan')) { return ( Information about this stage is unavailable. - ); + + ) } const totalBufferedBytes = stage.tasks - .map(task => task.outputBuffers.totalBufferedBytes) - .reduce((a, b) => a + b, 0); + .map((task) => task.outputBuffers.totalBufferedBytes) + .reduce((a, b) => a + b, 0) - const stageId = getStageNumber(stage.stageId); + const stageId = getStageNumber(stage.stageId) return ( -
    {stageId}
    +
    + {stageId} +
    - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + +
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - Time - -
    - Scheduled - - {stage.stageStats.totalScheduledTime} -
    - Blocked - - {stage.stageStats.totalBlockedTime} -
    - CPU - - {stage.stageStats.totalCpuTime} -
    - Failed - - {stage.stageStats.failedScheduledTime} -
    - CPU Failed - - {stage.stageStats.failedCpuTime} -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - Memory - -
    - Cumulative - - {formatDataSizeBytes(stage.stageStats.cumulativeUserMemory / 1000)} -
    - Current - - {parseAndFormatDataSize(stage.stageStats.userMemoryReservation)} -
    - Buffers - - {formatDataSize(totalBufferedBytes)} -
    - Peak - - {parseAndFormatDataSize(stage.stageStats.peakUserMemoryReservation)} -
    - Failed - - {formatDataSizeBytes(stage.stageStats.failedCumulativeUserMemory / 1000)} -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - Tasks - -
    - Pending - - {stage.tasks.filter(task => task.taskStatus.state === "PLANNED").length} -
    - Running - - {stage.tasks.filter(task => task.taskStatus.state === "RUNNING").length} -
    - Blocked - - {stage.tasks.filter(task => task.stats.fullyBlocked).length} -
    - Failed - - {stage.tasks.filter(task => task.taskStatus.state === "FAILED").length} -
    - Total - - {stage.tasks.length} -
    -
    - - - - - - - - - - - -
    - Scheduled Time Skew -
    -
    -
    -
    - - - - - - - - - - - -
    - CPU Time Skew -
    -
    -
    -
    - - - -
    - - - - - - - -
    - Task Scheduled Time - -
    -
    -
    - - - - - - - -
    - Task CPU Time - -
    -
    -
    - {this.renderTaskFilter()} -
    - {this.renderTaskList(taskRetriesEnabled)} -
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Time +
    Scheduled + {stage.stageStats.totalScheduledTime} +
    Blocked + {stage.stageStats.totalBlockedTime} +
    CPU + {stage.stageStats.totalCpuTime} +
    Failed + {stage.stageStats.failedScheduledTime} +
    CPU Failed + {stage.stageStats.failedCpuTime} +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + Memory + +
    Cumulative + {formatDataSizeBytes(stage.stageStats.cumulativeUserMemory / 1000)} +
    Current + {parseAndFormatDataSize(stage.stageStats.userMemoryReservation)} +
    Buffers + {formatDataSize(totalBufferedBytes)} +
    Peak + {parseAndFormatDataSize(stage.stageStats.peakUserMemoryReservation)} +
    Failed + {formatDataSizeBytes( + stage.stageStats.failedCumulativeUserMemory / 1000 + )} +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + Tasks + +
    Pending + { + stage.tasks.filter( + (task) => task.taskStatus.state === 'PLANNED' + ).length + } +
    Running + { + stage.tasks.filter( + (task) => task.taskStatus.state === 'RUNNING' + ).length + } +
    Blocked + {stage.tasks.filter((task) => task.stats.fullyBlocked).length} +
    Failed + { + stage.tasks.filter((task) => task.taskStatus.state === 'FAILED') + .length + } +
    Total{stage.tasks.length}
    +
    + + + + + + + + + + + +
    + Scheduled Time Skew +
    + +
    + +
    +
    + + + + + + + + + + + +
    + CPU Time Skew +
    + +
    + +
    +
    + + + +
    + + + + + + + +
    + Task Scheduled Time + + +
    + +
    +
    + + + + + + + +
    + Task CPU Time + + +
    + +
    +
    {this.renderTaskFilter()}
    {this.renderTaskList(taskRetriesEnabled)}
    - ); + + ) } } @@ -691,36 +764,34 @@ class StageList extends React.Component { return [] } - return [].concat.apply(stage, stage.subStages.map(this.getStages, this)); + return [].concat.apply(stage, stage.subStages.map(this.getStages, this)) } render() { - const stages = this.getStages(this.props.outputStage); - const taskRetriesEnabled = this.props.taskRetriesEnabled; + const stages = this.getStages(this.props.outputStage) + const taskRetriesEnabled = this.props.taskRetriesEnabled if (stages === undefined || stages.length === 0) { return (
    -
    - No stage information available. -
    +
    No stage information available.
    - ); + ) } - const renderedStages = stages.map(stage => ); + const renderedStages = stages.map((stage) => ( + + )) return (
    - - {renderedStages} - + {renderedStages}
    - ); + ) } } @@ -732,20 +803,29 @@ const SMALL_SPARKLINE_PROPERTIES = { spotColor: '#1EDCFF', tooltipClassname: 'sparkline-tooltip', disableHiddenCheck: true, -}; +} const TASK_FILTER = { - ALL: function () { return true }, - PLANNED: function (state) { return state === 'PLANNED' }, - RUNNING: function (state) { return state === 'RUNNING' }, - FINISHED: function (state) { return state === 'FINISHED' }, - FAILED: function (state) { return state === 'FAILED' || state === 'ABORTED' || state === 'CANCELED' }, -}; + ALL: function () { + return true + }, + PLANNED: function (state) { + return state === 'PLANNED' + }, + RUNNING: function (state) { + return state === 'RUNNING' + }, + FINISHED: function (state) { + return state === 'FINISHED' + }, + FAILED: function (state) { + return state === 'FAILED' || state === 'ABORTED' || state === 'CANCELED' + }, +} export class QueryDetail extends React.Component { - constructor(props) { - super(props); + super(props) this.state = { query: null, lastSnapshotStages: null, @@ -777,144 +857,166 @@ export class QueryDetail extends React.Component { taskRefresh: true, taskFilter: TASK_FILTER.ALL, - }; + } - this.refreshLoop = this.refreshLoop.bind(this); + this.refreshLoop = this.refreshLoop.bind(this) } static formatStackTrace(info) { - return QueryDetail.formatStackTraceHelper(info, [], "", ""); + return QueryDetail.formatStackTraceHelper(info, [], '', '') } static formatStackTraceHelper(info, parentStack, prefix, linePrefix) { - let s = linePrefix + prefix + QueryDetail.failureInfoToString(info) + "\n"; + let s = linePrefix + prefix + QueryDetail.failureInfoToString(info) + '\n' if (info.stack) { - let sharedStackFrames = 0; + let sharedStackFrames = 0 if (parentStack !== null) { - sharedStackFrames = QueryDetail.countSharedStackFrames(info.stack, parentStack); + sharedStackFrames = QueryDetail.countSharedStackFrames(info.stack, parentStack) } for (let i = 0; i < info.stack.length - sharedStackFrames; i++) { - s += linePrefix + "\tat " + info.stack[i] + "\n"; + s += linePrefix + '\tat ' + info.stack[i] + '\n' } if (sharedStackFrames !== 0) { - s += linePrefix + "\t... " + sharedStackFrames + " more" + "\n"; + s += linePrefix + '\t... ' + sharedStackFrames + ' more' + '\n' } } if (info.suppressed) { for (let i = 0; i < info.suppressed.length; i++) { - s += QueryDetail.formatStackTraceHelper(info.suppressed[i], info.stack, "Suppressed: ", linePrefix + "\t"); + s += QueryDetail.formatStackTraceHelper( + info.suppressed[i], + info.stack, + 'Suppressed: ', + linePrefix + '\t' + ) } } if (info.cause) { - s += QueryDetail.formatStackTraceHelper(info.cause, info.stack, "Caused by: ", linePrefix); + s += QueryDetail.formatStackTraceHelper(info.cause, info.stack, 'Caused by: ', linePrefix) } - return s; + return s } static countSharedStackFrames(stack, parentStack) { - let n = 0; - const minStackLength = Math.min(stack.length, parentStack.length); + let n = 0 + const minStackLength = Math.min(stack.length, parentStack.length) while (n < minStackLength && stack[stack.length - 1 - n] === parentStack[parentStack.length - 1 - n]) { - n++; + n++ } - return n; + return n } static failureInfoToString(t) { - return (t.message !== null) ? (t.type + ": " + t.message) : t.type; + return t.message !== null ? t.type + ': ' + t.message : t.type } resetTimer() { - clearTimeout(this.timeoutId); + clearTimeout(this.timeoutId) // stop refreshing when query finishes or fails if (this.state.query === null || !this.state.queryEnded) { // task.info-update-interval is set to 3 seconds by default - this.timeoutId = setTimeout(this.refreshLoop, 3000); + this.timeoutId = setTimeout(this.refreshLoop, 3000) } } refreshLoop() { - clearTimeout(this.timeoutId); // to stop multiple series of refreshLoop from going on simultaneously - const queryId = getFirstParameter(window.location.search); - $.get('/ui/api/query/' + queryId, function (query) { - let lastSnapshotStages = this.state.lastSnapshotStage; - if (this.state.stageRefresh) { - lastSnapshotStages = query.outputStage; - } - let lastSnapshotTasks = this.state.lastSnapshotTasks; - if (this.state.taskRefresh) { - lastSnapshotTasks = query.outputStage; - } - - let lastRefresh = this.state.lastRefresh; - const lastScheduledTime = this.state.lastScheduledTime; - const lastCpuTime = this.state.lastCpuTime; - const lastRowInput = this.state.lastRowInput; - const lastByteInput = this.state.lastByteInput; - const lastPhysicalInput = this.state.lastPhysicalInput; - const lastPhysicalTime = this.state.lastPhysicalTime; - const alreadyEnded = this.state.queryEnded; - const nowMillis = Date.now(); + clearTimeout(this.timeoutId) // to stop multiple series of refreshLoop from going on simultaneously + const queryId = getFirstParameter(window.location.search) + $.get( + '/ui/api/query/' + queryId, + function (query) { + let lastSnapshotStages = this.state.lastSnapshotStage + if (this.state.stageRefresh) { + lastSnapshotStages = query.outputStage + } + let lastSnapshotTasks = this.state.lastSnapshotTasks + if (this.state.taskRefresh) { + lastSnapshotTasks = query.outputStage + } - this.setState({ - query: query, - lastSnapshotStage: lastSnapshotStages, - lastSnapshotTasks: lastSnapshotTasks, + let lastRefresh = this.state.lastRefresh + const lastScheduledTime = this.state.lastScheduledTime + const lastCpuTime = this.state.lastCpuTime + const lastRowInput = this.state.lastRowInput + const lastByteInput = this.state.lastByteInput + const lastPhysicalInput = this.state.lastPhysicalInput + const lastPhysicalTime = this.state.lastPhysicalTime + const alreadyEnded = this.state.queryEnded + const nowMillis = Date.now() - lastPhysicalTime: parseDuration(query.queryStats.physicalInputReadTime), - lastScheduledTime: parseDuration(query.queryStats.totalScheduledTime), - lastCpuTime: parseDuration(query.queryStats.totalCpuTime), - lastRowInput: query.queryStats.processedInputPositions, - lastByteInput: parseDataSize(query.queryStats.processedInputDataSize), - lastPhysicalInput: parseDataSize(query.queryStats.physicalInputDataSize), + this.setState({ + query: query, + lastSnapshotStage: lastSnapshotStages, + lastSnapshotTasks: lastSnapshotTasks, - initialized: true, - queryEnded: !!query.finalQueryInfo, + lastPhysicalTime: parseDuration(query.queryStats.physicalInputReadTime), + lastScheduledTime: parseDuration(query.queryStats.totalScheduledTime), + lastCpuTime: parseDuration(query.queryStats.totalCpuTime), + lastRowInput: query.queryStats.processedInputPositions, + lastByteInput: parseDataSize(query.queryStats.processedInputDataSize), + lastPhysicalInput: parseDataSize(query.queryStats.physicalInputDataSize), - lastRefresh: nowMillis, - }); + initialized: true, + queryEnded: !!query.finalQueryInfo, - // i.e. don't show sparklines if we've already decided not to update or if we don't have one previous measurement - if (alreadyEnded || (lastRefresh === null && query.state === "RUNNING")) { - this.resetTimer(); - return; - } + lastRefresh: nowMillis, + }) - if (lastRefresh === null) { - lastRefresh = nowMillis - parseDuration(query.queryStats.elapsedTime); - } + // i.e. don't show sparklines if we've already decided not to update or if we don't have one previous measurement + if (alreadyEnded || (lastRefresh === null && query.state === 'RUNNING')) { + this.resetTimer() + return + } - const elapsedSecsSinceLastRefresh = (nowMillis - lastRefresh) / 1000.0; - if (elapsedSecsSinceLastRefresh >= 0) { - const currentScheduledTimeRate = (parseDuration(query.queryStats.totalScheduledTime) - lastScheduledTime) / (elapsedSecsSinceLastRefresh * 1000); - const currentCpuTimeRate = (parseDuration(query.queryStats.totalCpuTime) - lastCpuTime) / (elapsedSecsSinceLastRefresh * 1000); - const currentPhysicalReadTime = (parseDuration(query.queryStats.physicalInputReadTime) - lastPhysicalTime) / 1000; - const currentRowInputRate = (query.queryStats.processedInputPositions - lastRowInput) / elapsedSecsSinceLastRefresh; - const currentByteInputRate = (parseDataSize(query.queryStats.processedInputDataSize) - lastByteInput) / elapsedSecsSinceLastRefresh; - const currentPhysicalInputRate = currentPhysicalReadTime > 0 ? (parseDataSize(query.queryStats.physicalInputDataSize) - lastPhysicalInput) / currentPhysicalReadTime : 0; + if (lastRefresh === null) { + lastRefresh = nowMillis - parseDuration(query.queryStats.elapsedTime) + } - this.setState({ - scheduledTimeRate: addToHistory(currentScheduledTimeRate, this.state.scheduledTimeRate), - cpuTimeRate: addToHistory(currentCpuTimeRate, this.state.cpuTimeRate), - rowInputRate: addToHistory(currentRowInputRate, this.state.rowInputRate), - byteInputRate: addToHistory(currentByteInputRate, this.state.byteInputRate), - reservedMemory: addToHistory(parseDataSize(query.queryStats.totalMemoryReservation), this.state.reservedMemory), - physicalInputRate: addToHistory(currentPhysicalInputRate, this.state.physicalInputRate), - }); - } - this.resetTimer(); - }.bind(this)) - .fail(() => { - this.setState({ - initialized: true, - }); - this.resetTimer(); - }); + const elapsedSecsSinceLastRefresh = (nowMillis - lastRefresh) / 1000.0 + if (elapsedSecsSinceLastRefresh >= 0) { + const currentScheduledTimeRate = + (parseDuration(query.queryStats.totalScheduledTime) - lastScheduledTime) / + (elapsedSecsSinceLastRefresh * 1000) + const currentCpuTimeRate = + (parseDuration(query.queryStats.totalCpuTime) - lastCpuTime) / + (elapsedSecsSinceLastRefresh * 1000) + const currentPhysicalReadTime = + (parseDuration(query.queryStats.physicalInputReadTime) - lastPhysicalTime) / 1000 + const currentRowInputRate = + (query.queryStats.processedInputPositions - lastRowInput) / elapsedSecsSinceLastRefresh + const currentByteInputRate = + (parseDataSize(query.queryStats.processedInputDataSize) - lastByteInput) / + elapsedSecsSinceLastRefresh + const currentPhysicalInputRate = + currentPhysicalReadTime > 0 + ? (parseDataSize(query.queryStats.physicalInputDataSize) - lastPhysicalInput) / + currentPhysicalReadTime + : 0 + + this.setState({ + scheduledTimeRate: addToHistory(currentScheduledTimeRate, this.state.scheduledTimeRate), + cpuTimeRate: addToHistory(currentCpuTimeRate, this.state.cpuTimeRate), + rowInputRate: addToHistory(currentRowInputRate, this.state.rowInputRate), + byteInputRate: addToHistory(currentByteInputRate, this.state.byteInputRate), + reservedMemory: addToHistory( + parseDataSize(query.queryStats.totalMemoryReservation), + this.state.reservedMemory + ), + physicalInputRate: addToHistory(currentPhysicalInputRate, this.state.physicalInputRate), + }) + } + this.resetTimer() + }.bind(this) + ).fail(() => { + this.setState({ + initialized: true, + }) + this.resetTimer() + }) } handleStageRefreshClick() { @@ -922,65 +1024,104 @@ export class QueryDetail extends React.Component { this.setState({ stageRefresh: false, lastSnapshotStages: this.state.query.outputStage, - }); - } - else { + }) + } else { this.setState({ stageRefresh: true, - }); + }) } } renderStageRefreshButton() { if (this.state.stageRefresh) { - return - } - else { - return + return ( + + ) + } else { + return ( + + ) } } componentDidMount() { - this.refreshLoop(); + this.refreshLoop() } componentDidUpdate() { // prevent multiple calls to componentDidUpdate (resulting from calls to setState or otherwise) within the refresh interval from re-rendering sparklines/charts - if (this.state.lastRender === null || (Date.now() - this.state.lastRender) >= 1000 || (this.state.ended && !this.state.renderingEnded)) { - const renderTimestamp = Date.now(); - $('#scheduled-time-rate-sparkline').sparkline(this.state.scheduledTimeRate, $.extend({}, SMALL_SPARKLINE_PROPERTIES, { - chartRangeMin: 0, - numberFormatter: precisionRound - })); - $('#cpu-time-rate-sparkline').sparkline(this.state.cpuTimeRate, $.extend({}, SMALL_SPARKLINE_PROPERTIES, {chartRangeMin: 0, numberFormatter: precisionRound})); - $('#row-input-rate-sparkline').sparkline(this.state.rowInputRate, $.extend({}, SMALL_SPARKLINE_PROPERTIES, {numberFormatter: formatCount})); - $('#byte-input-rate-sparkline').sparkline(this.state.byteInputRate, $.extend({}, SMALL_SPARKLINE_PROPERTIES, {numberFormatter: formatDataSize})); - $('#reserved-memory-sparkline').sparkline(this.state.reservedMemory, $.extend({}, SMALL_SPARKLINE_PROPERTIES, {numberFormatter: formatDataSize})); - $('#physical-input-rate-sparkline').sparkline(this.state.physicalInputRate, $.extend({}, SMALL_SPARKLINE_PROPERTIES, {numberFormatter: formatDataSize})); + if ( + this.state.lastRender === null || + Date.now() - this.state.lastRender >= 1000 || + (this.state.ended && !this.state.renderingEnded) + ) { + const renderTimestamp = Date.now() + $('#scheduled-time-rate-sparkline').sparkline( + this.state.scheduledTimeRate, + $.extend({}, SMALL_SPARKLINE_PROPERTIES, { + chartRangeMin: 0, + numberFormatter: precisionRound, + }) + ) + $('#cpu-time-rate-sparkline').sparkline( + this.state.cpuTimeRate, + $.extend({}, SMALL_SPARKLINE_PROPERTIES, { + chartRangeMin: 0, + numberFormatter: precisionRound, + }) + ) + $('#row-input-rate-sparkline').sparkline( + this.state.rowInputRate, + $.extend({}, SMALL_SPARKLINE_PROPERTIES, { + numberFormatter: formatCount, + }) + ) + $('#byte-input-rate-sparkline').sparkline( + this.state.byteInputRate, + $.extend({}, SMALL_SPARKLINE_PROPERTIES, { + numberFormatter: formatDataSize, + }) + ) + $('#reserved-memory-sparkline').sparkline( + this.state.reservedMemory, + $.extend({}, SMALL_SPARKLINE_PROPERTIES, { + numberFormatter: formatDataSize, + }) + ) + $('#physical-input-rate-sparkline').sparkline( + this.state.physicalInputRate, + $.extend({}, SMALL_SPARKLINE_PROPERTIES, { + numberFormatter: formatDataSize, + }) + ) if (this.state.lastRender === null) { $('#query').each((i, block) => { - hljs.highlightBlock(block); - }); + hljs.highlightBlock(block) + }) $('#prepared-query').each((i, block) => { - hljs.highlightBlock(block); - }); + hljs.highlightBlock(block) + }) } this.setState({ renderingEnded: this.state.ended, lastRender: renderTimestamp, - }); + }) } - $('[data-toggle="tooltip"]').tooltip(); - new window.ClipboardJS('.copy-button'); + $('[data-toggle="tooltip"]').tooltip() + new window.ClipboardJS('.copy-button') } renderStages(taskRetriesEnabled) { if (this.state.lastSnapshotStage === null) { - return; + return } return ( @@ -992,37 +1133,45 @@ export class QueryDetail extends React.Component {
    - - - + + +
    - {this.renderStageRefreshButton()} -
    {this.renderStageRefreshButton()}
    - +
    - ); + ) } renderPreparedQuery() { - const query = this.state.query; + const query = this.state.query if (!query.hasOwnProperty('preparedQuery') || query.preparedQuery === null) { - return; + return } return (

    Prepared Query - - + +

                         
    @@ -1030,18 +1179,20 @@ export class QueryDetail extends React.Component {
                         
                     
    - ); + ) } renderSessionProperties() { - const query = this.state.query; + const query = this.state.query - const properties = []; + const properties = [] for (let property in query.session.systemProperties) { if (query.session.systemProperties.hasOwnProperty(property)) { properties.push( - - {property + "=" + query.session.systemProperties[property]}
    - ); + + - {property + '=' + query.session.systemProperties[property]}
    +
    + ) } } @@ -1050,298 +1201,250 @@ export class QueryDetail extends React.Component { for (let property in query.session.catalogProperties[catalog]) { if (query.session.catalogProperties[catalog].hasOwnProperty(property)) { properties.push( - - {catalog + "." + property + "=" + query.session.catalogProperties[catalog][property]}
    - ); + + - {catalog + '.' + property + '=' + query.session.catalogProperties[catalog][property]}{' '} +
    +
    + ) } } } } - return properties; + return properties } renderResourceEstimates() { - const query = this.state.query; - const estimates = query.session.resourceEstimates; - const renderedEstimates = []; + const query = this.state.query + const estimates = query.session.resourceEstimates + const renderedEstimates = [] for (let resource in estimates) { if (estimates.hasOwnProperty(resource)) { - const upperChars = resource.match(/([A-Z])/g) || []; - let snakeCased = resource; + const upperChars = resource.match(/([A-Z])/g) || [] + let snakeCased = resource for (let i = 0, n = upperChars.length; i < n; i++) { - snakeCased = snakeCased.replace(new RegExp(upperChars[i]), '_' + upperChars[i].toLowerCase()); + snakeCased = snakeCased.replace(new RegExp(upperChars[i]), '_' + upperChars[i].toLowerCase()) } renderedEstimates.push( - - {snakeCased + "=" + query.session.resourceEstimates[resource]}
    + + - {snakeCased + '=' + query.session.resourceEstimates[resource]}
    +
    ) } } - return renderedEstimates; + return renderedEstimates } renderWarningInfo() { - const query = this.state.query; + const query = this.state.query if (query.warnings.length > 0) { return (

    Warnings

    -
    +
    - {query.warnings.map((warning) => + {query.warnings.map((warning) => ( - - + + - )} + ))}
    - {warning.warningCode.name} - - {warning.message} - {warning.warningCode.name}{warning.message}
    - ); - } - else { - return null; + ) + } else { + return null } } renderFailureInfo() { - const query = this.state.query; + const query = this.state.query if (query.failureInfo) { return (

    Error Information

    -
    +
    - - - - - - - - - - - - + + + + + + + + + + + +
    - Error Type - - {query.errorType} -
    - Error Code - - {query.errorCode.name + " (" + this.state.query.errorCode.code + ")"} -
    - Stack Trace - - - -
    -                                            {QueryDetail.formatStackTrace(query.failureInfo)}
    -                                        
    -
    Error Type{query.errorType}
    Error Code + {query.errorCode.name + ' (' + this.state.query.errorCode.code + ')'} +
    + Stack Trace + + + +
    {QueryDetail.formatStackTrace(query.failureInfo)}
    +
    - ); - } - else { - return ""; + ) + } else { + return '' } } render() { - const query = this.state.query; + const query = this.state.query if (query === null || this.state.initialized === false) { - let label = (
    Loading...
    ); + let label =
    Loading...
    if (this.state.initialized) { - label = "Query not found"; + label = 'Query not found' } return (
    -

    {label}

    +
    +

    {label}

    +
    - ); + ) } - const taskRetriesEnabled = query.retryPolicy == "TASK"; + const taskRetriesEnabled = query.retryPolicy == 'TASK' return (
    - +

    Session

    -
    +
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    - User - - {query.session.user} -    - - -
    - Principal - - {query.session.principal} -
    - Source - - {query.session.source} -
    - Catalog - - {query.session.catalog} -
    - Schema - - {query.session.schema} -
    - Time zone - - {query.session.timeZone} -
    - Client Address - - {query.session.remoteUserAddress} -
    - Client Tags - - {query.session.clientTags.join(", ")} -
    - Session Properties - - {this.renderSessionProperties()} -
    - Resource Estimates - - {this.renderResourceEstimates()} -
    User + {query.session.user} +    + + +
    Principal{query.session.principal}
    Source{query.session.source}
    Catalog{query.session.catalog}
    Schema{query.session.schema}
    Time zone{query.session.timeZone}
    Client Address{query.session.remoteUserAddress}
    Client Tags{query.session.clientTags.join(', ')}
    Session Properties{this.renderSessionProperties()}
    Resource Estimates{this.renderResourceEstimates()}

    Execution

    -
    +
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    - Resource Group - - {query.resourceGroupId ? query.resourceGroupId.join(".") : "n/a"} -
    - Submission Time - - {formatShortDateTime(new Date(query.queryStats.createTime))} -
    - Completion Time - - {query.queryStats.endTime ? formatShortDateTime(new Date(query.queryStats.endTime)) : ""} -
    - Elapsed Time - - {query.queryStats.elapsedTime} -
    - Queued Time - - {query.queryStats.queuedTime} -
    - Analysis Time - - {query.queryStats.analysisTime} -
    - Planning Time - - {query.queryStats.planningTime} -
    - Execution Time - - {query.queryStats.executionTime} -
    Resource Group + {query.resourceGroupId ? query.resourceGroupId.join('.') : 'n/a'} +
    Submission Time + {formatShortDateTime(new Date(query.queryStats.createTime))} +
    Completion Time + {query.queryStats.endTime + ? formatShortDateTime(new Date(query.queryStats.endTime)) + : ''} +
    Elapsed Time{query.queryStats.elapsedTime}
    Queued Time{query.queryStats.queuedTime}
    Analysis Time{query.queryStats.analysisTime}
    Planning Time{query.queryStats.planningTime}
    Execution Time{query.queryStats.executionTime}
    @@ -1351,336 +1454,309 @@ export class QueryDetail extends React.Component {

    Resource Utilization Summary

    -
    +
    - - - - {taskRetriesEnabled && - - } - - - - - - - - - {taskRetriesEnabled && - - } - - - - - {taskRetriesEnabled && - - } - - - - - {taskRetriesEnabled && - - } - - - - - {taskRetriesEnabled && - - } - - - - - {taskRetriesEnabled && - - } - - - - - {taskRetriesEnabled && - - } - - - - - {taskRetriesEnabled && - - } - - - - - {taskRetriesEnabled && - - } - - - - - - {parseDataSize(query.queryStats.peakRevocableMemoryReservation) > 0 && - - - - - } - - - - - - - - {taskRetriesEnabled && - - } - - - - - {taskRetriesEnabled && - - } - - - - - {taskRetriesEnabled && - - } - - - - - - - - - - - - - {taskRetriesEnabled && - - } - - {parseDataSize(query.queryStats.spilledDataSize) > 0 && - - - - - } + + + + {taskRetriesEnabled && ( + + )} + + + + + + + + + {taskRetriesEnabled && ( + + )} + + + + + {taskRetriesEnabled && ( + + )} + + + + + {taskRetriesEnabled && ( + + )} + + + + + {taskRetriesEnabled && ( + + )} + + + + + {taskRetriesEnabled && ( + + )} + + + + + {taskRetriesEnabled && ( + + )} + + + + + {taskRetriesEnabled && ( + + )} + + + + + {taskRetriesEnabled && ( + + )} + + + + + + {parseDataSize(query.queryStats.peakRevocableMemoryReservation) > 0 && ( + + + + + )} + + + + + + + + {taskRetriesEnabled && ( + + )} + + + + + {taskRetriesEnabled && ( + + )} + + + + + {taskRetriesEnabled && ( + + )} + + + + + + + + + + + + + {taskRetriesEnabled && ( + + )} + + {parseDataSize(query.queryStats.spilledDataSize) > 0 && ( + + + + + )}
    - CPU Time - - {query.queryStats.totalCpuTime} - - {query.queryStats.failedCpuTime} -
    - Planning CPU Time - - {query.queryStats.planningCpuTime} -
    - Scheduled Time - - {query.queryStats.totalScheduledTime} - - {query.queryStats.failedScheduledTime} -
    - Input Rows - - {formatCount(query.queryStats.processedInputPositions)} - - {query.queryStats.failedProcessedInputPositions} -
    - Input Data - - {parseAndFormatDataSize(query.queryStats.processedInputDataSize)} - - {query.queryStats.failedProcessedInputDataSize} -
    - Physical Input Rows - - {formatCount(query.queryStats.physicalInputPositions)} - - {formatCount(query.queryStats.failedPhysicalInputPositions)} -
    - Physical Input Data - - {parseAndFormatDataSize(query.queryStats.physicalInputDataSize)} - - {parseAndFormatDataSize(query.queryStats.failedPhysicalInputDataSize)} -
    - Physical Input Read Time - - {query.queryStats.physicalInputReadTime} - - {query.queryStats.failedPhysicalInputReadTime} -
    - Internal Network Rows - - {formatCount(query.queryStats.internalNetworkInputPositions)} - - {formatCount(query.queryStats.failedInternalNetworkInputPositions)} -
    - Internal Network Data - - {parseAndFormatDataSize(query.queryStats.internalNetworkInputDataSize)} - - {parseAndFormatDataSize(query.queryStats.failedInternalNetworkInputDataSize)} -
    - Peak User Memory - - {parseAndFormatDataSize(query.queryStats.peakUserMemoryReservation)} -
    - Peak Revocable Memory - - {parseAndFormatDataSize(query.queryStats.peakRevocableMemoryReservation)} -
    - Peak Total Memory - - {parseAndFormatDataSize(query.queryStats.peakTotalMemoryReservation)} -
    - Cumulative User Memory - - {formatDataSize(query.queryStats.cumulativeUserMemory / 1000.0) + "*seconds"} - - {formatDataSize(query.queryStats.failedCumulativeUserMemory / 1000.0) + "*seconds"} -
    - Output Rows - - {formatCount(query.queryStats.outputPositions)} - - {formatCount(query.queryStats.failedOutputPositions)} -
    - Output Data - - {parseAndFormatDataSize(query.queryStats.outputDataSize)} - - {parseAndFormatDataSize(query.queryStats.failedOutputDataSize)} -
    - Written Rows - - {formatCount(query.queryStats.writtenPositions)} -
    - Logical Written Data - - {parseAndFormatDataSize(query.queryStats.logicalWrittenDataSize)} -
    - Physical Written Data - - {parseAndFormatDataSize(query.queryStats.physicalWrittenDataSize)} - - {parseAndFormatDataSize(query.queryStats.failedPhysicalWrittenDataSize)} -
    - Spilled Data - - {parseAndFormatDataSize(query.queryStats.spilledDataSize)} -
    CPU Time{query.queryStats.totalCpuTime}{query.queryStats.failedCpuTime}
    Planning CPU Time{query.queryStats.planningCpuTime}
    Scheduled Time{query.queryStats.totalScheduledTime}{query.queryStats.failedScheduledTime}
    Input Rows + {formatCount(query.queryStats.processedInputPositions)} + + {query.queryStats.failedProcessedInputPositions} +
    Input Data + {parseAndFormatDataSize(query.queryStats.processedInputDataSize)} + + {query.queryStats.failedProcessedInputDataSize} +
    Physical Input Rows + {formatCount(query.queryStats.physicalInputPositions)} + + {formatCount(query.queryStats.failedPhysicalInputPositions)} +
    Physical Input Data + {parseAndFormatDataSize(query.queryStats.physicalInputDataSize)} + + {parseAndFormatDataSize( + query.queryStats.failedPhysicalInputDataSize + )} +
    Physical Input Read Time{query.queryStats.physicalInputReadTime} + {query.queryStats.failedPhysicalInputReadTime} +
    Internal Network Rows + {formatCount(query.queryStats.internalNetworkInputPositions)} + + {formatCount(query.queryStats.failedInternalNetworkInputPositions)} +
    Internal Network Data + {parseAndFormatDataSize(query.queryStats.internalNetworkInputDataSize)} + + {parseAndFormatDataSize( + query.queryStats.failedInternalNetworkInputDataSize + )} +
    Peak User Memory + {parseAndFormatDataSize(query.queryStats.peakUserMemoryReservation)} +
    Peak Revocable Memory + {parseAndFormatDataSize( + query.queryStats.peakRevocableMemoryReservation + )} +
    Peak Total Memory + {parseAndFormatDataSize(query.queryStats.peakTotalMemoryReservation)} +
    Cumulative User Memory + {formatDataSize(query.queryStats.cumulativeUserMemory / 1000.0) + + '*seconds'} + + {formatDataSize( + query.queryStats.failedCumulativeUserMemory / 1000.0 + ) + '*seconds'} +
    Output Rows + {formatCount(query.queryStats.outputPositions)} + + {formatCount(query.queryStats.failedOutputPositions)} +
    Output Data + {parseAndFormatDataSize(query.queryStats.outputDataSize)} + + {parseAndFormatDataSize(query.queryStats.failedOutputDataSize)} +
    Written Rows + {formatCount(query.queryStats.writtenPositions)} +
    Logical Written Data + {parseAndFormatDataSize(query.queryStats.logicalWrittenDataSize)} +
    Physical Written Data + {parseAndFormatDataSize(query.queryStats.physicalWrittenDataSize)} + + {parseAndFormatDataSize( + query.queryStats.failedPhysicalWrittenDataSize + )} +
    Spilled Data + {parseAndFormatDataSize(query.queryStats.spilledDataSize)} +

    Timeline

    -
    +
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    - Parallelism - -
    -
    Loading ...
    -
    -
    - {formatCount(this.state.cpuTimeRate[this.state.cpuTimeRate.length - 1])} -
    - Scheduled Time/s - -
    -
    Loading ...
    -
    -
    - {formatCount(this.state.scheduledTimeRate[this.state.scheduledTimeRate.length - 1])} -
    - Input Rows/s - -
    -
    Loading ...
    -
    -
    - {formatCount(this.state.rowInputRate[this.state.rowInputRate.length - 1])} -
    - Input Bytes/s - -
    -
    Loading ...
    -
    -
    - {formatDataSize(this.state.byteInputRate[this.state.byteInputRate.length - 1])} -
    - Physical Input Bytes/s - -
    -
    Loading ...
    -
    -
    - {formatDataSize(this.state.physicalInputRate[this.state.physicalInputRate.length - 1])} -
    - Memory Utilization - -
    -
    Loading ...
    -
    -
    - {formatDataSize(this.state.reservedMemory[this.state.reservedMemory.length - 1])} -
    Parallelism +
    + +
    Loading ...
    +
    +
    +
    + {formatCount(this.state.cpuTimeRate[this.state.cpuTimeRate.length - 1])} +
    Scheduled Time/s +
    + +
    Loading ...
    +
    +
    +
    + {formatCount( + this.state.scheduledTimeRate[ + this.state.scheduledTimeRate.length - 1 + ] + )} +
    Input Rows/s +
    + +
    Loading ...
    +
    +
    +
    + {formatCount( + this.state.rowInputRate[this.state.rowInputRate.length - 1] + )} +
    Input Bytes/s +
    + +
    Loading ...
    +
    +
    +
    + {formatDataSize( + this.state.byteInputRate[this.state.byteInputRate.length - 1] + )} +
    Physical Input Bytes/s +
    + +
    Loading ...
    +
    +
    +
    + {formatDataSize( + this.state.physicalInputRate[ + this.state.physicalInputRate.length - 1 + ] + )} +
    Memory Utilization +
    + +
    Loading ...
    +
    +
    +
    + {formatDataSize( + this.state.reservedMemory[this.state.reservedMemory.length - 1] + )} +
    @@ -1693,8 +1769,14 @@ export class QueryDetail extends React.Component {

    Query - - +

    @@ -1707,6 +1789,6 @@ export class QueryDetail extends React.Component {
                     
    {this.renderStages(taskRetriesEnabled)}
    - ); + ) } } diff --git a/core/trino-web-ui/src/main/resources/webapp/src/components/QueryHeader.jsx b/core/trino-web-ui/src/main/resources/webapp/src/components/QueryHeader.jsx index 5b8fff88e41e..feae0ea5f90d 100644 --- a/core/trino-web-ui/src/main/resources/webapp/src/components/QueryHeader.jsx +++ b/core/trino-web-ui/src/main/resources/webapp/src/components/QueryHeader.jsx @@ -12,111 +12,162 @@ * limitations under the License. */ -import React from "react"; +import React from 'react' -import {getProgressBarPercentage, getProgressBarTitle, getQueryStateColor, isQueryEnded} from "../utils"; +import { getProgressBarPercentage, getProgressBarTitle, getQueryStateColor, isQueryEnded } from '../utils' export class QueryHeader extends React.Component { constructor(props) { - super(props); + super(props) } renderProgressBar() { - const query = this.props.query; - const progressBarStyle = {width: getProgressBarPercentage(query) + "%", backgroundColor: getQueryStateColor(query)}; + const query = this.props.query + const progressBarStyle = { + width: getProgressBarPercentage(query) + '%', + backgroundColor: getQueryStateColor(query), + } if (isQueryEnded(query)) { return (
    -
    +
    {getProgressBarTitle(query, false)}
    - ); + ) } return ( - - + - - - + + + +
    -
    -
    - {getProgressBarTitle(query, false)} +
    +
    +
    + {getProgressBarTitle(query, false)} +
    - -
    - $.ajax({url: '/ui/api/query/' + query.queryId + '/preempted', type: 'PUT', data: "Preempted via web UI"})} className="btn btn-warning" - target="_blank"> - Preempt - - - $.ajax({url: '/ui/api/query/' + query.queryId + '/killed', type: 'PUT', data: "Killed via web UI"})} className="btn btn-warning" - target="_blank"> - Kill - -
    + + $.ajax({ + url: '/ui/api/query/' + query.queryId + '/preempted', + type: 'PUT', + data: 'Preempted via web UI', + }) + } + className="btn btn-warning" + target="_blank" + > + Preempt + + + + $.ajax({ + url: '/ui/api/query/' + query.queryId + '/killed', + type: 'PUT', + data: 'Killed via web UI', + }) + } + className="btn btn-warning" + target="_blank" + > + Kill + +
    - ); + ) } renderTab(path, name) { - const queryId = this.props.query.queryId; + const queryId = this.props.query.queryId if (window.location.pathname.includes(path)) { - return {name}; + return ( + + {name} + + ) } - return {name}; + return ( + + {name} + + ) } render() { - const query = this.props.query; + const query = this.props.query return (

    {query.queryId} - - +

    - - - + + +
    - {this.renderTab("query.html", "Overview")} -   - {this.renderTab("plan.html", "Live Plan")} -   - {this.renderTab("stage.html", "Stage Performance")} -   - {this.renderTab("timeline.html", "Splits")} -   - JSON -   - {this.renderTab("references.html", "References")} -
    + {this.renderTab('query.html', 'Overview')} +   + {this.renderTab('plan.html', 'Live Plan')} +   + {this.renderTab('stage.html', 'Stage Performance')} +   + {this.renderTab('timeline.html', 'Splits')} +   + + JSON + +   + {this.renderTab('references.html', 'References')} +
    -
    +
    -
    - {this.renderProgressBar()} -
    +
    {this.renderProgressBar()}
    - ); + ) } } diff --git a/core/trino-web-ui/src/main/resources/webapp/src/components/QueryList.jsx b/core/trino-web-ui/src/main/resources/webapp/src/components/QueryList.jsx index d72f12c51e51..8b42ba4e99d6 100644 --- a/core/trino-web-ui/src/main/resources/webapp/src/components/QueryList.jsx +++ b/core/trino-web-ui/src/main/resources/webapp/src/components/QueryList.jsx @@ -12,7 +12,7 @@ * limitations under the License. */ -import React from "react"; +import React from 'react' import { formatDataSizeBytes, @@ -26,113 +26,146 @@ import { parseAndFormatDataSize, parseDataSize, parseDuration, - truncateString -} from "../utils"; + truncateString, +} from '../utils' export class QueryListItem extends React.Component { static stripQueryTextWhitespace(queryText) { - const maxLines = 6; - const lines = queryText.split("\n"); - let minLeadingWhitespace = -1; + const maxLines = 6 + const lines = queryText.split('\n') + let minLeadingWhitespace = -1 for (let i = 0; i < lines.length; i++) { if (minLeadingWhitespace === 0) { - break; + break } if (lines[i].trim().length === 0) { - continue; + continue } - const leadingWhitespace = lines[i].search(/\S/); + const leadingWhitespace = lines[i].search(/\S/) - if (leadingWhitespace > -1 && ((leadingWhitespace < minLeadingWhitespace) || minLeadingWhitespace === -1)) { - minLeadingWhitespace = leadingWhitespace; + if (leadingWhitespace > -1 && (leadingWhitespace < minLeadingWhitespace || minLeadingWhitespace === -1)) { + minLeadingWhitespace = leadingWhitespace } } - let formattedQueryText = ""; + let formattedQueryText = '' for (let i = 0; i < lines.length; i++) { - const trimmedLine = lines[i].substring(minLeadingWhitespace).replace(/\s+$/g, ''); + const trimmedLine = lines[i].substring(minLeadingWhitespace).replace(/\s+$/g, '') if (trimmedLine.length > 0) { - formattedQueryText += trimmedLine; - if (i < (maxLines - 1)) { - formattedQueryText += "\n"; - } - else { - formattedQueryText += "\n..."; - break; + formattedQueryText += trimmedLine + if (i < maxLines - 1) { + formattedQueryText += '\n' + } else { + formattedQueryText += '\n...' + break } } } - return formattedQueryText; + return formattedQueryText } render() { - const query = this.props.query; - const progressBarStyle = {width: getProgressBarPercentage(query) + "%", backgroundColor: getQueryStateColor(query)}; + const query = this.props.query + const progressBarStyle = { + width: getProgressBarPercentage(query) + '%', + backgroundColor: getQueryStateColor(query), + } const splitDetails = (
    -    + +    {query.queryStats.completedDrivers} -    - {(query.state === "FINISHED" || query.state === "FAILED") ? 0 : query.queryStats.runningDrivers} + +    + {query.state === 'FINISHED' || query.state === 'FAILED' ? 0 : query.queryStats.runningDrivers} -    - {(query.state === "FINISHED" || query.state === "FAILED") ? 0 : query.queryStats.queuedDrivers} - - {query.retryPolicy === "TASK" && - -    - {query.queryStats.failedTasks} + +    + {query.state === 'FINISHED' || query.state === 'FAILED' ? 0 : query.queryStats.queuedDrivers} + + {query.retryPolicy === 'TASK' && ( + + +    + {query.queryStats.failedTasks} - } -
    ); + )} +
    + ) const timingDetails = (
    - -    + + +    {query.queryStats.executionTime} -    + +    {query.queryStats.elapsedTime} - -    + + +    {query.queryStats.totalCpuTime} -
    ); +
    + ) const memoryDetails = (
    - -    + + +    {parseAndFormatDataSize(query.queryStats.totalMemoryReservation)} -    + +    {parseAndFormatDataSize(query.queryStats.peakTotalMemoryReservation)} -    + +    {formatDataSizeBytes(query.queryStats.cumulativeUserMemory / 1000.0)} -
    ); +
    + ) - let user = ({query.sessionUser}); + let user = {query.sessionUser} if (query.sessionPrincipal) { user = ( - {query.sessionUser} - ); + + {query.sessionUser} + + + ) } return ( @@ -140,17 +173,31 @@ export class QueryListItem extends React.Component {
    -
    - {query.queryId} + -
    +
    {formatShortTime(new Date(Date.parse(query.queryStats.createTime)))}
    -    + +    {truncateString(user, 35)}
    @@ -158,7 +205,8 @@ export class QueryListItem extends React.Component {
    -    + +    {truncateString(query.sessionSource, 35)}
    @@ -166,27 +214,33 @@ export class QueryListItem extends React.Component {
    -    - {truncateString(query.resourceGroupId ? query.resourceGroupId.join(".") : "n/a", 35)} + +    + + {truncateString( + query.resourceGroupId ? query.resourceGroupId.join('.') : 'n/a', + 35 + )} +
    -
    - {splitDetails} -
    -
    - {timingDetails} -
    -
    - {memoryDetails} -
    +
    {splitDetails}
    +
    {timingDetails}
    +
    {memoryDetails}
    -
    +
    {getProgressBarTitle(query, true)}
    @@ -194,63 +248,91 @@ export class QueryListItem extends React.Component {
    -
    {QueryListItem.stripQueryTextWhitespace(query.queryTextPreview)}
    +
    +                                    
    +                                        {QueryListItem.stripQueryTextWhitespace(query.queryTextPreview)}
    +                                    
    +                                
    - ); + ) } } class DisplayedQueriesList extends React.Component { render() { - const queryNodes = this.props.queries.map(function (query) { - return ( - - ); - }.bind(this)); - return ( -
    - {queryNodes} -
    - ); + const queryNodes = this.props.queries.map( + function (query) { + return + }.bind(this) + ) + return
    {queryNodes}
    } } const FILTER_TYPE = { RUNNING: function (query) { - return !(query.state === "QUEUED" || query.state === "FINISHED" || query.state === "FAILED"); + return !(query.state === 'QUEUED' || query.state === 'FINISHED' || query.state === 'FAILED') + }, + QUEUED: function (query) { + return query.state === 'QUEUED' + }, + FINISHED: function (query) { + return query.state === 'FINISHED' }, - QUEUED: function (query) { return query.state === "QUEUED"}, - FINISHED: function (query) { return query.state === "FINISHED"}, -}; +} const SORT_TYPE = { - CREATED: function (query) {return Date.parse(query.queryStats.createTime)}, - ELAPSED: function (query) {return parseDuration(query.queryStats.elapsedTime)}, - EXECUTION: function (query) {return parseDuration(query.queryStats.executionTime)}, - CPU: function (query) {return parseDuration(query.queryStats.totalCpuTime)}, - CUMULATIVE_MEMORY: function (query) {return query.queryStats.cumulativeUserMemory}, - CURRENT_MEMORY: function (query) {return parseDataSize(query.queryStats.userMemoryReservation)}, -}; + CREATED: function (query) { + return Date.parse(query.queryStats.createTime) + }, + ELAPSED: function (query) { + return parseDuration(query.queryStats.elapsedTime) + }, + EXECUTION: function (query) { + return parseDuration(query.queryStats.executionTime) + }, + CPU: function (query) { + return parseDuration(query.queryStats.totalCpuTime) + }, + CUMULATIVE_MEMORY: function (query) { + return query.queryStats.cumulativeUserMemory + }, + CURRENT_MEMORY: function (query) { + return parseDataSize(query.queryStats.userMemoryReservation) + }, +} const ERROR_TYPE = { - USER_ERROR: function (query) {return query.state === "FAILED" && query.errorType === "USER_ERROR"}, - INTERNAL_ERROR: function (query) {return query.state === "FAILED" && query.errorType === "INTERNAL_ERROR"}, - INSUFFICIENT_RESOURCES: function (query) {return query.state === "FAILED" && query.errorType === "INSUFFICIENT_RESOURCES"}, - EXTERNAL: function (query) {return query.state === "FAILED" && query.errorType === "EXTERNAL"}, -}; + USER_ERROR: function (query) { + return query.state === 'FAILED' && query.errorType === 'USER_ERROR' + }, + INTERNAL_ERROR: function (query) { + return query.state === 'FAILED' && query.errorType === 'INTERNAL_ERROR' + }, + INSUFFICIENT_RESOURCES: function (query) { + return query.state === 'FAILED' && query.errorType === 'INSUFFICIENT_RESOURCES' + }, + EXTERNAL: function (query) { + return query.state === 'FAILED' && query.errorType === 'EXTERNAL' + }, +} const SORT_ORDER = { - ASCENDING: function (value) {return value}, - DESCENDING: function (value) {return -value} -}; + ASCENDING: function (value) { + return value + }, + DESCENDING: function (value) { + return -value + }, +} export class QueryList extends React.Component { constructor(props) { - super(props); + super(props) this.state = { allQueries: [], displayedQueries: [], @@ -263,25 +345,25 @@ export class QueryList extends React.Component { maxQueries: 100, lastRefresh: Date.now(), lastReorder: Date.now(), - initialized: false - }; + initialized: false, + } - this.refreshLoop = this.refreshLoop.bind(this); - this.handleSearchStringChange = this.handleSearchStringChange.bind(this); - this.executeSearch = this.executeSearch.bind(this); - this.handleSortClick = this.handleSortClick.bind(this); + this.refreshLoop = this.refreshLoop.bind(this) + this.handleSearchStringChange = this.handleSearchStringChange.bind(this) + this.executeSearch = this.executeSearch.bind(this) + this.handleSortClick = this.handleSortClick.bind(this) } sortQueries(queries, sortType, sortOrder) { queries.sort(function (queryA, queryB) { - return sortOrder(sortType(queryA) - sortType(queryB)); - }, this); + return sortOrder(sortType(queryA) - sortType(queryB)) + }, this) } sortAndLimitQueries(queries, sortType, sortOrder, maxQueries) { - this.sortQueries(queries, sortType, sortOrder); + this.sortQueries(queries, sortType, sortOrder) if (queries.length > maxQueries) { - queries.splice(maxQueries, (queries.length - maxQueries)); + queries.splice(maxQueries, queries.length - maxQueries) } } @@ -289,296 +371,379 @@ export class QueryList extends React.Component { const stateFilteredQueries = queries.filter(function (query) { for (let i = 0; i < stateFilters.length; i++) { if (stateFilters[i](query)) { - return true; + return true } } for (let i = 0; i < errorTypeFilters.length; i++) { if (errorTypeFilters[i](query)) { - return true; + return true } } - return false; - }); + return false + }) if (searchString === '') { - return stateFilteredQueries; - } - else { + return stateFilteredQueries + } else { return stateFilteredQueries.filter(function (query) { - const term = searchString.toLowerCase(); - if (query.queryId.toLowerCase().indexOf(term) !== -1 || + const term = searchString.toLowerCase() + if ( + query.queryId.toLowerCase().indexOf(term) !== -1 || getHumanReadableState(query).toLowerCase().indexOf(term) !== -1 || - query.queryTextPreview.toLowerCase().indexOf(term) !== -1) { - return true; + query.queryTextPreview.toLowerCase().indexOf(term) !== -1 + ) { + return true } if (query.sessionUser && query.sessionUser.toLowerCase().indexOf(term) !== -1) { - return true; + return true } if (query.sessionSource && query.sessionSource.toLowerCase().indexOf(term) !== -1) { - return true; + return true } - if (query.resourceGroupId && query.resourceGroupId.join(".").toLowerCase().indexOf(term) !== -1) { - return true; + if (query.resourceGroupId && query.resourceGroupId.join('.').toLowerCase().indexOf(term) !== -1) { + return true } - if (query.errorCode && query.errorCode.name && query.errorCode.name.toLowerCase().indexOf(term) !== -1) { - return true; + if ( + query.errorCode && + query.errorCode.name && + query.errorCode.name.toLowerCase().indexOf(term) !== -1 + ) { + return true } - - }, this); + }, this) } } resetTimer() { - clearTimeout(this.timeoutId); + clearTimeout(this.timeoutId) // stop refreshing when query finishes or fails if (this.state.query === null || !this.state.ended) { - this.timeoutId = setTimeout(this.refreshLoop, 1000); + this.timeoutId = setTimeout(this.refreshLoop, 1000) } } refreshLoop() { - clearTimeout(this.timeoutId); // to stop multiple series of refreshLoop from going on simultaneously - clearTimeout(this.searchTimeoutId); - - $.get('/ui/api/query', function (queryList) { - const queryMap = queryList.reduce(function (map, query) { - map[query.queryId] = query; - return map; - }, {}); - - let updatedQueries = []; - this.state.displayedQueries.forEach(function (oldQuery) { - if (oldQuery.queryId in queryMap) { - updatedQueries.push(queryMap[oldQuery.queryId]); - queryMap[oldQuery.queryId] = false; + clearTimeout(this.timeoutId) // to stop multiple series of refreshLoop from going on simultaneously + clearTimeout(this.searchTimeoutId) + + $.get( + '/ui/api/query', + function (queryList) { + const queryMap = queryList.reduce(function (map, query) { + map[query.queryId] = query + return map + }, {}) + + let updatedQueries = [] + this.state.displayedQueries.forEach(function (oldQuery) { + if (oldQuery.queryId in queryMap) { + updatedQueries.push(queryMap[oldQuery.queryId]) + queryMap[oldQuery.queryId] = false + } + }) + + let newQueries = [] + for (const queryId in queryMap) { + if (queryMap[queryId]) { + newQueries.push(queryMap[queryId]) + } } - }); - - let newQueries = []; - for (const queryId in queryMap) { - if (queryMap[queryId]) { - newQueries.push(queryMap[queryId]); + newQueries = this.filterQueries( + newQueries, + this.state.stateFilters, + this.state.errorTypeFilters, + this.state.searchString + ) + + const lastRefresh = Date.now() + let lastReorder = this.state.lastReorder + + if (this.state.reorderInterval !== 0 && lastRefresh - lastReorder >= this.state.reorderInterval) { + updatedQueries = this.filterQueries( + updatedQueries, + this.state.stateFilters, + this.state.errorTypeFilters, + this.state.searchString + ) + updatedQueries = updatedQueries.concat(newQueries) + this.sortQueries(updatedQueries, this.state.currentSortType, this.state.currentSortOrder) + lastReorder = Date.now() + } else { + this.sortQueries(newQueries, this.state.currentSortType, this.state.currentSortOrder) + updatedQueries = updatedQueries.concat(newQueries) } - } - newQueries = this.filterQueries(newQueries, this.state.stateFilters, this.state.errorTypeFilters, this.state.searchString); - - const lastRefresh = Date.now(); - let lastReorder = this.state.lastReorder; - - if (this.state.reorderInterval !== 0 && ((lastRefresh - lastReorder) >= this.state.reorderInterval)) { - updatedQueries = this.filterQueries(updatedQueries, this.state.stateFilters, this.state.errorTypeFilters, this.state.searchString); - updatedQueries = updatedQueries.concat(newQueries); - this.sortQueries(updatedQueries, this.state.currentSortType, this.state.currentSortOrder); - lastReorder = Date.now(); - } - else { - this.sortQueries(newQueries, this.state.currentSortType, this.state.currentSortOrder); - updatedQueries = updatedQueries.concat(newQueries); - } - if (this.state.maxQueries !== 0 && (updatedQueries.length > this.state.maxQueries)) { - updatedQueries.splice(this.state.maxQueries, (updatedQueries.length - this.state.maxQueries)); - } + if (this.state.maxQueries !== 0 && updatedQueries.length > this.state.maxQueries) { + updatedQueries.splice(this.state.maxQueries, updatedQueries.length - this.state.maxQueries) + } - this.setState({ - allQueries: queryList, - displayedQueries: updatedQueries, - lastRefresh: lastRefresh, - lastReorder: lastReorder, - initialized: true - }); - this.resetTimer(); - }.bind(this)) - .fail(function () { this.setState({ + allQueries: queryList, + displayedQueries: updatedQueries, + lastRefresh: lastRefresh, + lastReorder: lastReorder, initialized: true, - }); - this.resetTimer(); - }.bind(this)); + }) + this.resetTimer() + }.bind(this) + ).fail( + function () { + this.setState({ + initialized: true, + }) + this.resetTimer() + }.bind(this) + ) } componentDidMount() { - this.refreshLoop(); + this.refreshLoop() } handleSearchStringChange(event) { - const newSearchString = event.target.value; - clearTimeout(this.searchTimeoutId); + const newSearchString = event.target.value + clearTimeout(this.searchTimeoutId) this.setState({ - searchString: newSearchString - }); + searchString: newSearchString, + }) - this.searchTimeoutId = setTimeout(this.executeSearch, 200); + this.searchTimeoutId = setTimeout(this.executeSearch, 200) } executeSearch() { - clearTimeout(this.searchTimeoutId); - - const newDisplayedQueries = this.filterQueries(this.state.allQueries, this.state.stateFilters, this.state.errorTypeFilters, this.state.searchString); - this.sortAndLimitQueries(newDisplayedQueries, this.state.currentSortType, this.state.currentSortOrder, this.state.maxQueries); + clearTimeout(this.searchTimeoutId) + + const newDisplayedQueries = this.filterQueries( + this.state.allQueries, + this.state.stateFilters, + this.state.errorTypeFilters, + this.state.searchString + ) + this.sortAndLimitQueries( + newDisplayedQueries, + this.state.currentSortType, + this.state.currentSortOrder, + this.state.maxQueries + ) this.setState({ - displayedQueries: newDisplayedQueries - }); + displayedQueries: newDisplayedQueries, + }) } renderMaxQueriesListItem(maxQueries, maxQueriesText) { return ( -
  • {maxQueriesText} +
  • + + {maxQueriesText} +
  • - ); + ) } handleMaxQueriesClick(newMaxQueries) { - const filteredQueries = this.filterQueries(this.state.allQueries, this.state.stateFilters, this.state.errorTypeFilters, this.state.searchString); - this.sortAndLimitQueries(filteredQueries, this.state.currentSortType, this.state.currentSortOrder, newMaxQueries); + const filteredQueries = this.filterQueries( + this.state.allQueries, + this.state.stateFilters, + this.state.errorTypeFilters, + this.state.searchString + ) + this.sortAndLimitQueries( + filteredQueries, + this.state.currentSortType, + this.state.currentSortOrder, + newMaxQueries + ) this.setState({ maxQueries: newMaxQueries, - displayedQueries: filteredQueries - }); + displayedQueries: filteredQueries, + }) } renderReorderListItem(interval, intervalText) { return ( -
  • {intervalText}
  • - ); +
  • + + {intervalText} + +
  • + ) } handleReorderClick(interval) { if (this.state.reorderInterval !== interval) { this.setState({ reorderInterval: interval, - }); + }) } } renderSortListItem(sortType, sortText) { if (this.state.currentSortType === sortType) { - const directionArrow = this.state.currentSortOrder === SORT_ORDER.ASCENDING ? : - ; + const directionArrow = + this.state.currentSortOrder === SORT_ORDER.ASCENDING ? ( + + ) : ( + + ) return (
  • {sortText} {directionArrow} -
  • ); - } - else { + + ) + } else { return (
  • {sortText} -
  • ); + + ) } } handleSortClick(sortType) { - const newSortType = sortType; - let newSortOrder = SORT_ORDER.DESCENDING; + const newSortType = sortType + let newSortOrder = SORT_ORDER.DESCENDING if (this.state.currentSortType === sortType && this.state.currentSortOrder === SORT_ORDER.DESCENDING) { - newSortOrder = SORT_ORDER.ASCENDING; + newSortOrder = SORT_ORDER.ASCENDING } - const newDisplayedQueries = this.filterQueries(this.state.allQueries, this.state.stateFilters, this.state.errorTypeFilters, this.state.searchString); - this.sortAndLimitQueries(newDisplayedQueries, newSortType, newSortOrder, this.state.maxQueries); + const newDisplayedQueries = this.filterQueries( + this.state.allQueries, + this.state.stateFilters, + this.state.errorTypeFilters, + this.state.searchString + ) + this.sortAndLimitQueries(newDisplayedQueries, newSortType, newSortOrder, this.state.maxQueries) this.setState({ displayedQueries: newDisplayedQueries, currentSortType: newSortType, - currentSortOrder: newSortOrder - }); + currentSortOrder: newSortOrder, + }) } renderFilterButton(filterType, filterText) { - let checkmarkStyle = {color: '#57aac7'}; - let classNames = "btn btn-sm btn-info style-check"; + let checkmarkStyle = { color: '#57aac7' } + let classNames = 'btn btn-sm btn-info style-check' if (this.state.stateFilters.indexOf(filterType) > -1) { - classNames += " active"; - checkmarkStyle = {color: '#ffffff'}; + classNames += ' active' + checkmarkStyle = { color: '#ffffff' } } return ( - ); + ) } handleStateFilterClick(filter) { - const newFilters = this.state.stateFilters.slice(); + const newFilters = this.state.stateFilters.slice() if (this.state.stateFilters.indexOf(filter) > -1) { - newFilters.splice(newFilters.indexOf(filter), 1); - } - else { - newFilters.push(filter); + newFilters.splice(newFilters.indexOf(filter), 1) + } else { + newFilters.push(filter) } - const filteredQueries = this.filterQueries(this.state.allQueries, newFilters, this.state.errorTypeFilters, this.state.searchString); - this.sortAndLimitQueries(filteredQueries, this.state.currentSortType, this.state.currentSortOrder, this.state.maxQueries); + const filteredQueries = this.filterQueries( + this.state.allQueries, + newFilters, + this.state.errorTypeFilters, + this.state.searchString + ) + this.sortAndLimitQueries( + filteredQueries, + this.state.currentSortType, + this.state.currentSortOrder, + this.state.maxQueries + ) this.setState({ stateFilters: newFilters, - displayedQueries: filteredQueries - }); + displayedQueries: filteredQueries, + }) } renderErrorTypeListItem(errorType, errorTypeText) { - let checkmarkStyle = {color: '#ffffff'}; + let checkmarkStyle = { color: '#ffffff' } if (this.state.errorTypeFilters.indexOf(errorType) > -1) { - checkmarkStyle = GLYPHICON_HIGHLIGHT; + checkmarkStyle = GLYPHICON_HIGHLIGHT } return (
  • - +  {errorTypeText} -
  • ); + + ) } handleErrorTypeFilterClick(errorType) { - const newFilters = this.state.errorTypeFilters.slice(); + const newFilters = this.state.errorTypeFilters.slice() if (this.state.errorTypeFilters.indexOf(errorType) > -1) { - newFilters.splice(newFilters.indexOf(errorType), 1); - } - else { - newFilters.push(errorType); + newFilters.splice(newFilters.indexOf(errorType), 1) + } else { + newFilters.push(errorType) } - const filteredQueries = this.filterQueries(this.state.allQueries, this.state.stateFilters, newFilters, this.state.searchString); - this.sortAndLimitQueries(filteredQueries, this.state.currentSortType, this.state.currentSortOrder, this.state.maxQueries); + const filteredQueries = this.filterQueries( + this.state.allQueries, + this.state.stateFilters, + newFilters, + this.state.searchString + ) + this.sortAndLimitQueries( + filteredQueries, + this.state.currentSortType, + this.state.currentSortOrder, + this.state.maxQueries + ) this.setState({ errorTypeFilters: newFilters, - displayedQueries: filteredQueries - }); + displayedQueries: filteredQueries, + }) } render() { - let queryList = ; + let queryList = if (this.state.displayedQueries === null || this.state.displayedQueries.length === 0) { - let label = (
    Loading...
    ); + let label =
    Loading...
    if (this.state.initialized) { if (this.state.allQueries === null || this.state.allQueries.length === 0) { - label = "No queries"; - } - else { - label = "No queries matched filters"; + label = 'No queries' + } else { + label = 'No queries matched filters' } } queryList = (
    -

    {label}

    +
    +

    {label}

    +
    - ); + ) } return ( @@ -586,62 +751,92 @@ export class QueryList extends React.Component {
    - + State:
    - {this.renderFilterButton(FILTER_TYPE.RUNNING, "Running")} - {this.renderFilterButton(FILTER_TYPE.QUEUED, "Queued")} - {this.renderFilterButton(FILTER_TYPE.FINISHED, "Finished")} -
      - {this.renderErrorTypeListItem(ERROR_TYPE.INTERNAL_ERROR, "Internal Error")} - {this.renderErrorTypeListItem(ERROR_TYPE.EXTERNAL, "External Error")} - {this.renderErrorTypeListItem(ERROR_TYPE.INSUFFICIENT_RESOURCES, "Resources Error")} - {this.renderErrorTypeListItem(ERROR_TYPE.USER_ERROR, "User Error")} + {this.renderErrorTypeListItem(ERROR_TYPE.INTERNAL_ERROR, 'Internal Error')} + {this.renderErrorTypeListItem(ERROR_TYPE.EXTERNAL, 'External Error')} + {this.renderErrorTypeListItem(ERROR_TYPE.INSUFFICIENT_RESOURCES, 'Resources Error')} + {this.renderErrorTypeListItem(ERROR_TYPE.USER_ERROR, 'User Error')}
     
    -
      - {this.renderSortListItem(SORT_TYPE.CREATED, "Creation Time")} - {this.renderSortListItem(SORT_TYPE.ELAPSED, "Elapsed Time")} - {this.renderSortListItem(SORT_TYPE.CPU, "CPU Time")} - {this.renderSortListItem(SORT_TYPE.EXECUTION, "Execution Time")} - {this.renderSortListItem(SORT_TYPE.CURRENT_MEMORY, "Current Memory")} - {this.renderSortListItem(SORT_TYPE.CUMULATIVE_MEMORY, "Cumulative User Memory")} + {this.renderSortListItem(SORT_TYPE.CREATED, 'Creation Time')} + {this.renderSortListItem(SORT_TYPE.ELAPSED, 'Elapsed Time')} + {this.renderSortListItem(SORT_TYPE.CPU, 'CPU Time')} + {this.renderSortListItem(SORT_TYPE.EXECUTION, 'Execution Time')} + {this.renderSortListItem(SORT_TYPE.CURRENT_MEMORY, 'Current Memory')} + {this.renderSortListItem(SORT_TYPE.CUMULATIVE_MEMORY, 'Cumulative User Memory')}
     
    -
      - {this.renderReorderListItem(1000, "1s")} - {this.renderReorderListItem(5000, "5s")} - {this.renderReorderListItem(10000, "10s")} - {this.renderReorderListItem(30000, "30s")} -
    • - {this.renderReorderListItem(0, "Off")} + {this.renderReorderListItem(1000, '1s')} + {this.renderReorderListItem(5000, '5s')} + {this.renderReorderListItem(10000, '10s')} + {this.renderReorderListItem(30000, '30s')} +
    • + {this.renderReorderListItem(0, 'Off')}
     
    -
      - {this.renderMaxQueriesListItem(20, "20 queries")} - {this.renderMaxQueriesListItem(50, "50 queries")} - {this.renderMaxQueriesListItem(100, "100 queries")} -
    • - {this.renderMaxQueriesListItem(0, "All queries")} + {this.renderMaxQueriesListItem(20, '20 queries')} + {this.renderMaxQueriesListItem(50, '50 queries')} + {this.renderMaxQueriesListItem(100, '100 queries')} +
    • + {this.renderMaxQueriesListItem(0, 'All queries')}
    @@ -649,7 +844,6 @@ export class QueryList extends React.Component {
    {queryList}
    - ); + ) } } - diff --git a/core/trino-web-ui/src/main/resources/webapp/src/components/ReferenceDetail.jsx b/core/trino-web-ui/src/main/resources/webapp/src/components/ReferenceDetail.jsx index 34cae94f755d..ac680dd081f4 100644 --- a/core/trino-web-ui/src/main/resources/webapp/src/components/ReferenceDetail.jsx +++ b/core/trino-web-ui/src/main/resources/webapp/src/components/ReferenceDetail.jsx @@ -12,7 +12,7 @@ * limitations under the License. */ -import React from "react"; +import React from 'react' import { formatCount, @@ -26,158 +26,158 @@ import { isQueryEnded, parseAndFormatDataSize, parseDataSize, - parseDuration -} from "../utils"; -import {QueryHeader} from "./QueryHeader"; + parseDuration, +} from '../utils' +import { QueryHeader } from './QueryHeader' export class ReferenceDetail extends React.Component { constructor(props) { - super(props); + super(props) this.state = { initialized: false, ended: false, query: null, - }; + } - this.refreshLoop = this.refreshLoop.bind(this); + this.refreshLoop = this.refreshLoop.bind(this) } resetTimer() { - clearTimeout(this.timeoutId); + clearTimeout(this.timeoutId) // stop refreshing when query finishes or fails if (this.state.query === null || !this.state.ended) { - this.timeoutId = setTimeout(this.refreshLoop, 1000); + this.timeoutId = setTimeout(this.refreshLoop, 1000) } } refreshLoop() { - clearTimeout(this.timeoutId); // to stop multiple series of refreshLoop from going on simultaneously - const queryString = getFirstParameter(window.location.search).split('.'); - const queryId = queryString[0]; + clearTimeout(this.timeoutId) // to stop multiple series of refreshLoop from going on simultaneously + const queryString = getFirstParameter(window.location.search).split('.') + const queryId = queryString[0] - $.get('/ui/api/query/' + queryId + "?pruned=true", query => { + $.get('/ui/api/query/' + queryId + '?pruned=true', (query) => { this.setState({ initialized: true, ended: query.finalQueryInfo, query: query, - }); - this.resetTimer(); + }) + this.resetTimer() }).fail(() => { this.setState({ initialized: true, - }); - this.resetTimer(); - }); + }) + this.resetTimer() + }) } componentDidMount() { - this.refreshLoop(); + this.refreshLoop() } renderReferencedTables(tables) { - if (!tables || tables.length === 0) { - return ( -
    -

    Referenced Tables

    -
    - - - No referenced tables. - - -
    - ); - } + if (!tables || tables.length === 0) { return (

    Referenced Tables

    -
    - - - {tables.map(table => ( +
    + + + + + ) + } + return ( +
    +

    Referenced Tables

    +
    +
    + No referenced tables. +
    + + {tables.map((table) => ( ))} - -
    {`${table.catalog}.${table.schema}.${table.table} (Authorization: ${table.authorization}, Directly Referenced: ${table.directlyReferenced})`}
    -
    - ); - } + + +
    + ) + } - renderRoutines(routines) { - if (!routines || routines.length === 0) { - return ( -
    -

    Routines

    -
    - - - No referenced routines. - - -
    - ); - } + renderRoutines(routines) { + if (!routines || routines.length === 0) { return (

    Routines

    -
    - - - {routines.map(routine => ( +
    + + + + + ) + } + return ( +
    +

    Routines

    +
    +
    + No referenced routines. +
    + + {routines.map((routine) => ( ))} - -
    {`${routine.routine} (Authorization: ${routine.authorization})`}
    -
    - ); - } + + +
    + ) + } - render() { - const { query, jsonData, initialized } = this.state; - if (!query) { - let label = initialized ? "Query not found" :
    Loading...
    ; - return ( -
    -

    {label}

    + render() { + const { query, jsonData, initialized } = this.state + if (!query) { + let label = initialized ? 'Query not found' :
    Loading...
    + return ( +
    +
    +

    {label}

    - ); - } +
    + ) + } - const referencedTables = query.referencedTables || []; - const routines = query.routines || []; + const referencedTables = query.referencedTables || [] + const routines = query.routines || [] - let referencesData = this.state.ended ? ( + let referencesData = this.state.ended ? ( +
    + {this.renderReferencedTables(referencedTables)} + {this.renderRoutines(routines)} +
    + ) : ( +
    - {this.renderReferencedTables(referencedTables)} - {this.renderRoutines(routines)} +

    References will appear automatically when query completes.

    +
    Loading...
    - ) : ( -
    -
    -

    References will appear automatically when query completes.

    -
    Loading...
    -
    -
    - ); +
    + ) - return ( -
    - -
    -
    -
    - {referencesData} -
    -
    + return ( +
    + +
    +
    +
    {referencesData}
    - ); - } +
    + ) + } } diff --git a/core/trino-web-ui/src/main/resources/webapp/src/components/StageDetail.jsx b/core/trino-web-ui/src/main/resources/webapp/src/components/StageDetail.jsx index c4fc9c2f055b..67d29b2296a5 100644 --- a/core/trino-web-ui/src/main/resources/webapp/src/components/StageDetail.jsx +++ b/core/trino-web-ui/src/main/resources/webapp/src/components/StageDetail.jsx @@ -12,11 +12,11 @@ * limitations under the License. */ -import React from "react"; -import ReactDOM from "react-dom"; -import ReactDOMServer from "react-dom/server"; -import * as dagreD3 from "dagre-d3"; -import * as d3 from "d3"; +import React from 'react' +import ReactDOM from 'react-dom' +import ReactDOMServer from 'react-dom/server' +import * as dagreD3 from 'dagre-d3' +import * as d3 from 'd3' import { formatCount, @@ -30,92 +30,82 @@ import { isQueryEnded, parseAndFormatDataSize, parseDataSize, - parseDuration -} from "../utils"; -import {QueryHeader} from "./QueryHeader"; + parseDuration, +} from '../utils' +import { QueryHeader } from './QueryHeader' function getTotalWallTime(operator) { - return parseDuration(operator.addInputWall) + parseDuration(operator.getOutputWall) + parseDuration(operator.finishWall) + parseDuration(operator.blockedWall) + return ( + parseDuration(operator.addInputWall) + + parseDuration(operator.getOutputWall) + + parseDuration(operator.finishWall) + + parseDuration(operator.blockedWall) + ) } function getTotalCpuTime(operator) { - return parseDuration(operator.addInputCpu) + parseDuration(operator.getOutputCpu) + parseDuration(operator.finishCpu) + return ( + parseDuration(operator.addInputCpu) + parseDuration(operator.getOutputCpu) + parseDuration(operator.finishCpu) + ) } class OperatorSummary extends React.Component { render() { - const operator = this.props.operator; + const operator = this.props.operator - const totalWallTime = getTotalWallTime(operator); - const totalCpuTime = getTotalCpuTime(operator); + const totalWallTime = getTotalWallTime(operator) + const totalCpuTime = getTotalCpuTime(operator) - const rowInputRate = totalWallTime === 0 ? 0 : (1.0 * operator.inputPositions) / (totalWallTime / 1000.0); - const byteInputRate = totalWallTime === 0 ? 0 : (1.0 * parseDataSize(operator.inputDataSize)) / (totalWallTime / 1000.0); + const rowInputRate = totalWallTime === 0 ? 0 : (1.0 * operator.inputPositions) / (totalWallTime / 1000.0) + const byteInputRate = + totalWallTime === 0 ? 0 : (1.0 * parseDataSize(operator.inputDataSize)) / (totalWallTime / 1000.0) return (
    -
    - {operator.operatorType} -
    -
    - {formatCount(rowInputRate) + " rows/s (" + formatDataSize(byteInputRate) + "/s)"} -
    +
    {operator.operatorType}
    +
    {formatCount(rowInputRate) + ' rows/s (' + formatDataSize(byteInputRate) + '/s)'}
    - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + +
    - Output - - {formatCount(operator.outputPositions) + " rows (" + parseAndFormatDataSize(operator.outputDataSize) + ")"} -
    - Drivers - - {operator.totalDrivers} -
    - CPU Time - - {formatDuration(totalCpuTime)} -
    - Wall Time - - {formatDuration(totalWallTime)} -
    - Blocked - - {formatDuration(parseDuration(operator.blockedWall))} -
    - Input - - {formatCount(operator.inputPositions) + " rows (" + parseAndFormatDataSize(operator.inputDataSize) + ")"} -
    Output + {formatCount(operator.outputPositions) + + ' rows (' + + parseAndFormatDataSize(operator.outputDataSize) + + ')'} +
    Drivers{operator.totalDrivers}
    CPU Time{formatDuration(totalCpuTime)}
    Wall Time{formatDuration(totalWallTime)}
    Blocked{formatDuration(parseDuration(operator.blockedWall))}
    Input + {formatCount(operator.inputPositions) + + ' rows (' + + parseAndFormatDataSize(operator.inputDataSize) + + ')'} +
    - ); + ) } } @@ -128,130 +118,140 @@ const BAR_CHART_PROPERTIES = { tooltipClassname: 'sparkline-tooltip', tooltipFormat: 'Task {{offset:offset}} - {{value}}', disableHiddenCheck: true, -}; +} class OperatorStatistic extends React.Component { componentDidMount() { - const operators = this.props.operators; - const statistic = operators.map(this.props.supplier); - const numTasks = operators.length; + const operators = this.props.operators + const statistic = operators.map(this.props.supplier) + const numTasks = operators.length - const tooltipValueLookups = {'offset': {}}; + const tooltipValueLookups = { offset: {} } for (let i = 0; i < numTasks; i++) { - tooltipValueLookups['offset'][i] = "" + i; + tooltipValueLookups['offset'][i] = '' + i } - const stageBarChartProperties = $.extend({}, BAR_CHART_PROPERTIES, {barWidth: 800 / numTasks, tooltipValueLookups: tooltipValueLookups}); - $('#' + this.props.id).sparkline(statistic, $.extend({}, stageBarChartProperties, {numberFormatter: this.props.renderer})); + const stageBarChartProperties = $.extend({}, BAR_CHART_PROPERTIES, { + barWidth: 800 / numTasks, + tooltipValueLookups: tooltipValueLookups, + }) + $('#' + this.props.id).sparkline( + statistic, + $.extend({}, stageBarChartProperties, { + numberFormatter: this.props.renderer, + }) + ) } render() { return (
    -
    - {this.props.name} -
    +
    {this.props.name}
    - +
    - ); + ) } } class OperatorDetail extends React.Component { constructor(props) { - super(props); + super(props) this.state = { - selectedStatistics: this.getInitialStatistics() - }; + selectedStatistics: this.getInitialStatistics(), + } } getInitialStatistics() { return [ { - name: "Total CPU Time", - id: "totalCpuTime", + name: 'Total CPU Time', + id: 'totalCpuTime', supplier: getTotalCpuTime, - renderer: formatDuration + renderer: formatDuration, }, { - name: "Total Wall Time", - id: "totalWallTime", + name: 'Total Wall Time', + id: 'totalWallTime', supplier: getTotalWallTime, - renderer: formatDuration + renderer: formatDuration, }, { - name: "Input Rows", - id: "inputPositions", - supplier: operator => operator.inputPositions, - renderer: formatCount + name: 'Input Rows', + id: 'inputPositions', + supplier: (operator) => operator.inputPositions, + renderer: formatCount, }, { - name: "Input Data Size", - id: "inputDataSize", - supplier: operator => parseDataSize(operator.inputDataSize), - renderer: formatDataSize + name: 'Input Data Size', + id: 'inputDataSize', + supplier: (operator) => parseDataSize(operator.inputDataSize), + renderer: formatDataSize, }, { - name: "Output Rows", - id: "outputPositions", - supplier: operator => operator.outputPositions, - renderer: formatCount + name: 'Output Rows', + id: 'outputPositions', + supplier: (operator) => operator.outputPositions, + renderer: formatCount, }, { - name: "Output Data Size", - id: "outputDataSize", - supplier: operator => parseDataSize(operator.outputDataSize), - renderer: formatDataSize + name: 'Output Data Size', + id: 'outputDataSize', + supplier: (operator) => parseDataSize(operator.outputDataSize), + renderer: formatDataSize, }, - ]; + ] } getOperatorTasks() { // sort the x-axis const tasks = this.props.tasks.sort(function (taskA, taskB) { - return getTaskNumber(taskA.taskStatus.taskId) - getTaskNumber(taskB.taskStatus.taskId); - }); + return getTaskNumber(taskA.taskStatus.taskId) - getTaskNumber(taskB.taskStatus.taskId) + }) - const operatorSummary = this.props.operator; + const operatorSummary = this.props.operator - const operatorTasks = []; - tasks.forEach(task => { - task.stats.pipelines.forEach(pipeline => { + const operatorTasks = [] + tasks.forEach((task) => { + task.stats.pipelines.forEach((pipeline) => { if (pipeline.pipelineId === operatorSummary.pipelineId) { - pipeline.operatorSummaries.forEach(operator => { + pipeline.operatorSummaries.forEach((operator) => { if (operatorSummary.operatorId === operator.operatorId) { - operatorTasks.push(operator); + operatorTasks.push(operator) } - }); + }) } - }); - }); + }) + }) - return operatorTasks; + return operatorTasks } render() { - const operator = this.props.operator; - const operatorTasks = this.getOperatorTasks(); - const totalWallTime = getTotalWallTime(operator); - const totalCpuTime = getTotalCpuTime(operator); + const operator = this.props.operator + const operatorTasks = this.getOperatorTasks() + const totalWallTime = getTotalWallTime(operator) + const totalCpuTime = getTotalCpuTime(operator) - const rowInputRate = totalWallTime === 0 ? 0 : (1.0 * operator.inputPositions) / totalWallTime; - const byteInputRate = totalWallTime === 0 ? 0 : (1.0 * parseDataSize(operator.inputDataSize)) / (totalWallTime / 1000.0); + const rowInputRate = totalWallTime === 0 ? 0 : (1.0 * operator.inputPositions) / totalWallTime + const byteInputRate = + totalWallTime === 0 ? 0 : (1.0 * parseDataSize(operator.inputDataSize)) / (totalWallTime / 1000.0) - const rowOutputRate = totalWallTime === 0 ? 0 : (1.0 * operator.outputPositions) / totalWallTime; - const byteOutputRate = totalWallTime === 0 ? 0 : (1.0 * parseDataSize(operator.outputDataSize)) / (totalWallTime / 1000.0); + const rowOutputRate = totalWallTime === 0 ? 0 : (1.0 * operator.outputPositions) / totalWallTime + const byteOutputRate = + totalWallTime === 0 ? 0 : (1.0 * parseDataSize(operator.outputDataSize)) / (totalWallTime / 1000.0) return (
    - +

    Pipeline {operator.pipelineId} -
    +
    {operator.operatorType}

    @@ -259,102 +259,82 @@ class OperatorDetail extends React.Component {
    - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + +
    - Input - - {formatCount(operator.inputPositions) + " rows (" + parseAndFormatDataSize(operator.inputDataSize) + ")"} -
    - Input Rate - - {formatCount(rowInputRate) + " rows/s (" + formatDataSize(byteInputRate) + "/s)"} -
    - Output - - {formatCount(operator.outputPositions) + " rows (" + parseAndFormatDataSize(operator.outputDataSize) + ")"} -
    - Output Rate - - {formatCount(rowOutputRate) + " rows/s (" + formatDataSize(byteOutputRate) + "/s)"} -
    Input + {formatCount(operator.inputPositions) + + ' rows (' + + parseAndFormatDataSize(operator.inputDataSize) + + ')'} +
    Input Rate + {formatCount(rowInputRate) + + ' rows/s (' + + formatDataSize(byteInputRate) + + '/s)'} +
    Output + {formatCount(operator.outputPositions) + + ' rows (' + + parseAndFormatDataSize(operator.outputDataSize) + + ')'} +
    Output Rate + {formatCount(rowOutputRate) + + ' rows/s (' + + formatDataSize(byteOutputRate) + + '/s)'} +
    - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + +
    - CPU Time - - {formatDuration(totalCpuTime)} -
    - Wall Time - - {formatDuration(totalWallTime)} -
    - Blocked - - {formatDuration(parseDuration(operator.blockedWall))} -
    - Drivers - - {operator.totalDrivers} -
    - Tasks - - {operatorTasks.length} -
    CPU Time{formatDuration(totalCpuTime)}
    Wall Time{formatDuration(totalWallTime)}
    Blocked{formatDuration(parseDuration(operator.blockedWall))}
    Drivers{operator.totalDrivers}
    Tasks{operatorTasks.length}
    - - Statistic - + Statistic
    - - Tasks - + Tasks
    - { - this.state.selectedStatistics.map(function (statistic) { + {this.state.selectedStatistics.map( + function (statistic) { return ( - ); - }.bind(this)) - } -

    -

    + operators={operatorTasks} + /> + ) + }.bind(this) + )} +

    +

    - ); + ) } } class StageOperatorGraph extends React.Component { componentDidMount() { - this.updateD3Graph(); + this.updateD3Graph() } componentDidUpdate() { - this.updateD3Graph(); + this.updateD3Graph() } handleOperatorClick(operatorCssId) { - $('#operator-detail-modal').modal(); + $('#operator-detail-modal').modal() - const pipelineId = parseInt(operatorCssId.split('-')[1]); - const operatorId = parseInt(operatorCssId.split('-')[2]); - const stage = this.props.stage; + const pipelineId = parseInt(operatorCssId.split('-')[1]) + const operatorId = parseInt(operatorCssId.split('-')[2]) + const stage = this.props.stage - let operatorStageSummary = null; - const operatorSummaries = stage.stageStats.operatorSummaries; + let operatorStageSummary = null + const operatorSummaries = stage.stageStats.operatorSummaries for (let i = 0; i < operatorSummaries.length; i++) { if (operatorSummaries[i].pipelineId === pipelineId && operatorSummaries[i].operatorId === operatorId) { - operatorStageSummary = operatorSummaries[i]; + operatorStageSummary = operatorSummaries[i] } } - ReactDOM.render(, - document.getElementById('operator-detail')); + ReactDOM.render( + , + document.getElementById('operator-detail') + ) } computeOperatorGraphs(planNode, operatorMap) { - const sources = getChildren(planNode); + const sources = getChildren(planNode) - const sourceResults = new Map(); - sources.forEach(source => { - const sourceResult = this.computeOperatorGraphs(source, operatorMap); + const sourceResults = new Map() + sources.forEach((source) => { + const sourceResult = this.computeOperatorGraphs(source, operatorMap) sourceResult.forEach((operator, pipelineId) => { if (sourceResults.has(pipelineId)) { - console.error("Multiple sources for ", planNode['@type'], " had the same pipeline ID"); - return sourceResults; + console.error('Multiple sources for ', planNode['@type'], ' had the same pipeline ID') + return sourceResults } - sourceResults.set(pipelineId, operator); - }); - }); + sourceResults.set(pipelineId, operator) + }) + }) - let nodeOperators = operatorMap.get(planNode.id); + let nodeOperators = operatorMap.get(planNode.id) if (!nodeOperators || nodeOperators.length === 0) { - return sourceResults; + return sourceResults } - const pipelineOperators = new Map(); - nodeOperators.forEach(operator => { + const pipelineOperators = new Map() + nodeOperators.forEach((operator) => { if (!pipelineOperators.has(operator.pipelineId)) { - pipelineOperators.set(operator.pipelineId, []); + pipelineOperators.set(operator.pipelineId, []) } - pipelineOperators.get(operator.pipelineId).push(operator); - }); + pipelineOperators.get(operator.pipelineId).push(operator) + }) - const result = new Map(); + const result = new Map() pipelineOperators.forEach((pipelineOperators, pipelineId) => { // sort deep-copied operators in this pipeline from source to sink - const linkedOperators = pipelineOperators.map(a => Object.assign({}, a)).sort((a, b) => a.operatorId - b.operatorId); - const sinkOperator = linkedOperators[linkedOperators.length - 1]; - const sourceOperator = linkedOperators[0]; + const linkedOperators = pipelineOperators + .map((a) => Object.assign({}, a)) + .sort((a, b) => a.operatorId - b.operatorId) + const sinkOperator = linkedOperators[linkedOperators.length - 1] + const sourceOperator = linkedOperators[0] if (sourceResults.has(pipelineId)) { - const pipelineChildResult = sourceResults.get(pipelineId); + const pipelineChildResult = sourceResults.get(pipelineId) if (pipelineChildResult) { - sourceOperator.child = pipelineChildResult; + sourceOperator.child = pipelineChildResult } } // chain operators at this level - let currentOperator = sourceOperator; - linkedOperators.slice(1).forEach(source => { - source.child = currentOperator; - currentOperator = source; - }); + let currentOperator = sourceOperator + linkedOperators.slice(1).forEach((source) => { + source.child = currentOperator + currentOperator = source + }) - result.set(pipelineId, sinkOperator); - }); + result.set(pipelineId, sinkOperator) + }) sourceResults.forEach((operator, pipelineId) => { if (!result.has(pipelineId)) { - result.set(pipelineId, operator); + result.set(pipelineId, operator) } - }); + }) - return result; + return result } computeOperatorMap() { - const operatorMap = new Map(); - this.props.stage.stageStats.operatorSummaries.forEach(operator => { + const operatorMap = new Map() + this.props.stage.stageStats.operatorSummaries.forEach((operator) => { if (!operatorMap.has(operator.planNodeId)) { operatorMap.set(operator.planNodeId, []) } - operatorMap.get(operator.planNodeId).push(operator); - }); + operatorMap.get(operator.planNodeId).push(operator) + }) - return operatorMap; + return operatorMap } computeD3StageOperatorGraph(graph, operator, sink, pipelineNode) { - const operatorNodeId = "operator-" + operator.pipelineId + "-" + operator.operatorId; + const operatorNodeId = 'operator-' + operator.pipelineId + '-' + operator.operatorId // this is a non-standard use of ReactDOMServer, but it's the cleanest way to unify DagreD3 with React - const html = ReactDOMServer.renderToString(); - graph.setNode(operatorNodeId, {class: "operator-stats", label: html, labelType: "html"}); - - if (operator.hasOwnProperty("child")) { - this.computeD3StageOperatorGraph(graph, operator.child, operatorNodeId, pipelineNode); + const html = ReactDOMServer.renderToString( + + ) + graph.setNode(operatorNodeId, { + class: 'operator-stats', + label: html, + labelType: 'html', + }) + + if (operator.hasOwnProperty('child')) { + this.computeD3StageOperatorGraph(graph, operator.child, operatorNodeId, pipelineNode) } if (sink !== null) { - graph.setEdge(operatorNodeId, sink, {class: "plan-edge", arrowheadClass: "plan-arrowhead"}); + graph.setEdge(operatorNodeId, sink, { + class: 'plan-edge', + arrowheadClass: 'plan-arrowhead', + }) } - graph.setParent(operatorNodeId, pipelineNode); + graph.setParent(operatorNodeId, pipelineNode) } updateD3Graph() { if (!this.props.stage) { - return; + return } - const stage = this.props.stage; - const operatorMap = this.computeOperatorMap(); - const operatorGraphs = this.computeOperatorGraphs(stage.plan.root, operatorMap); + const stage = this.props.stage + const operatorMap = this.computeOperatorMap() + const operatorGraphs = this.computeOperatorGraphs(stage.plan.root, operatorMap) - const graph = initializeGraph(); + const graph = initializeGraph() operatorGraphs.forEach((operator, pipelineId) => { - const pipelineNodeId = "pipeline-" + pipelineId; - graph.setNode(pipelineNodeId, {label: "Pipeline " + pipelineId + " ", clusterLabelPos: 'top', style: 'fill: #2b2b2b', labelStyle: 'fill: #fff'}); + const pipelineNodeId = 'pipeline-' + pipelineId + graph.setNode(pipelineNodeId, { + label: 'Pipeline ' + pipelineId + ' ', + clusterLabelPos: 'top', + style: 'fill: #2b2b2b', + labelStyle: 'fill: #fff', + }) this.computeD3StageOperatorGraph(graph, operator, null, pipelineNodeId) - }); + }) - $("#operator-canvas").html(""); + $('#operator-canvas').html('') if (operatorGraphs.size > 0) { - $(".graph-container").css("display", "block"); - const svg = initializeSvg("#operator-canvas"); - const render = new dagreD3.render(); - render(d3.select("#operator-canvas g"), graph); - - svg.selectAll("g.operator-stats").on("click", this.handleOperatorClick.bind(this)); - svg.attr("height", graph.graph().height); - svg.attr("width", graph.graph().width); - } - else { - $(".graph-container").css("display", "none"); + $('.graph-container').css('display', 'block') + const svg = initializeSvg('#operator-canvas') + const render = new dagreD3.render() + render(d3.select('#operator-canvas g'), graph) + + svg.selectAll('g.operator-stats').on('click', this.handleOperatorClick.bind(this)) + svg.attr('height', graph.graph().height) + svg.attr('width', graph.graph().width) + } else { + $('.graph-container').css('display', 'none') } } render() { - const stage = this.props.stage; + const stage = this.props.stage if (!stage.hasOwnProperty('plan')) { return (
    -

    Stage does not have a plan

    +
    +

    Stage does not have a plan

    +
    - ); + ) } - if (!stage.hasOwnProperty('stageStats') || !stage.stageStats.hasOwnProperty("operatorSummaries") || stage.stageStats.operatorSummaries.length === 0) { + if ( + !stage.hasOwnProperty('stageStats') || + !stage.stageStats.hasOwnProperty('operatorSummaries') || + stage.stageStats.operatorSummaries.length === 0 + ) { return (

    Operator data not available for {stage.stageId}

    - ); + ) } - return null; + return null } } export class StageDetail extends React.Component { constructor(props) { - super(props); + super(props) this.state = { initialized: false, ended: false, @@ -563,116 +567,122 @@ export class StageDetail extends React.Component { query: null, lastRefresh: null, - lastRender: null - }; + lastRender: null, + } - this.refreshLoop = this.refreshLoop.bind(this); + this.refreshLoop = this.refreshLoop.bind(this) } resetTimer() { - clearTimeout(this.timeoutId); + clearTimeout(this.timeoutId) // stop refreshing when query finishes or fails if (this.state.query === null || !this.state.ended) { - this.timeoutId = setTimeout(this.refreshLoop, 1000); + this.timeoutId = setTimeout(this.refreshLoop, 1000) } } refreshLoop() { - clearTimeout(this.timeoutId); // to stop multiple series of refreshLoop from going on simultaneously - const queryString = getFirstParameter(window.location.search).split('.'); - const queryId = queryString[0]; + clearTimeout(this.timeoutId) // to stop multiple series of refreshLoop from going on simultaneously + const queryString = getFirstParameter(window.location.search).split('.') + const queryId = queryString[0] - let selectedStageId = this.state.selectedStageId; + let selectedStageId = this.state.selectedStageId if (selectedStageId === null) { - selectedStageId = 0; + selectedStageId = 0 if (queryString.length > 1) { - selectedStageId = parseInt(queryString[1]); + selectedStageId = parseInt(queryString[1]) } } - $.get('/ui/api/query/' + queryId, query => { + $.get('/ui/api/query/' + queryId, (query) => { this.setState({ initialized: true, ended: query.finalQueryInfo, selectedStageId: selectedStageId, query: query, - }); - this.resetTimer(); + }) + this.resetTimer() }).fail(() => { this.setState({ initialized: true, - }); - this.resetTimer(); - }); + }) + this.resetTimer() + }) } componentDidMount() { - this.refreshLoop(); - new window.ClipboardJS('.copy-button'); + this.refreshLoop() + new window.ClipboardJS('.copy-button') } findStage(stageId, currentStage) { if (stageId === null) { - return null; + return null } if (currentStage.stageId === stageId) { - return currentStage; + return currentStage } for (let i = 0; i < currentStage.subStages.length; i++) { - const stage = this.findStage(stageId, currentStage.subStages[i]); + const stage = this.findStage(stageId, currentStage.subStages[i]) if (stage !== null) { - return stage; + return stage } } - return null; + return null } getAllStageIds(result, currentStage) { - result.push(currentStage.plan.id); - currentStage.subStages.forEach(stage => { - this.getAllStageIds(result, stage); - }); + result.push(currentStage.plan.id) + currentStage.subStages.forEach((stage) => { + this.getAllStageIds(result, stage) + }) } render() { if (!this.state.query) { - let label = (
    Loading...
    ); + let label =
    Loading...
    if (this.state.initialized) { - label = "Query not found"; + label = 'Query not found' } return (
    -

    {label}

    +
    +

    {label}

    +
    - ); + ) } if (!this.state.query.outputStage) { return (
    -

    Query does not have an output stage

    +
    +

    Query does not have an output stage

    +
    - ); + ) } - const query = this.state.query; - const allStages = []; - this.getAllStageIds(allStages, query.outputStage); + const query = this.state.query + const allStages = [] + this.getAllStageIds(allStages, query.outputStage) - const stage = this.findStage(query.queryId + "." + this.state.selectedStageId, query.outputStage); + const stage = this.findStage(query.queryId + '.' + this.state.selectedStageId, query.outputStage) if (stage === null) { return (
    -

    Stage not found

    +
    +

    Stage not found

    +
    - ); + ) } - let stageOperatorGraph = null; + let stageOperatorGraph = null if (!isQueryEnded(query)) { stageOperatorGraph = (
    @@ -682,41 +692,56 @@ export class StageDetail extends React.Component {
    ) - } - else { - stageOperatorGraph = ; + } else { + stageOperatorGraph = } return (
    - +

    Stage {stage.plan.id}

    -
    +
    -
    -
    +
    -
    - {stageOperatorGraph} -
    +
    {stageOperatorGraph}
    - ); + ) } } diff --git a/core/trino-web-ui/src/main/resources/webapp/src/components/WorkerList.jsx b/core/trino-web-ui/src/main/resources/webapp/src/components/WorkerList.jsx index a6c9a6a5e05d..516098102e79 100644 --- a/core/trino-web-ui/src/main/resources/webapp/src/components/WorkerList.jsx +++ b/core/trino-web-ui/src/main/resources/webapp/src/components/WorkerList.jsx @@ -12,7 +12,7 @@ * limitations under the License. */ -import React from "react"; +import React from 'react' const SMALL_SPARKLINE_PROPERTIES = { width: '100%', @@ -22,108 +22,117 @@ const SMALL_SPARKLINE_PROPERTIES = { spotColor: '#1EDCFF', tooltipClassname: 'sparkline-tooltip', disableHiddenCheck: true, -}; +} export class WorkerList extends React.Component { constructor(props) { - super(props); + super(props) this.state = { initialized: false, - workers: [] - }; - this.refreshLoop = this.refreshLoop.bind(this); + workers: [], + } + this.refreshLoop = this.refreshLoop.bind(this) } refreshLoop() { - clearTimeout(this.timeoutId); - $.get('/ui/api/worker', function (workers) { - if (workers != null) { - workers.sort(function (workerA, workerB) { - if (workerA.coordinator && !workerB.coordinator) { - return -1; - } - if (!workerA.coordinator && workerB.coordinator) { - return 1; - } - return workerA.nodeId.localeCompare(workerB.nodeId); - }); - } + clearTimeout(this.timeoutId) + $.get( + '/ui/api/worker', + function (workers) { + if (workers != null) { + workers.sort(function (workerA, workerB) { + if (workerA.coordinator && !workerB.coordinator) { + return -1 + } + if (!workerA.coordinator && workerB.coordinator) { + return 1 + } + return workerA.nodeId.localeCompare(workerB.nodeId) + }) + } + this.setState({ + initialized: true, + workers: workers, + }) + this.resetTimer() + }.bind(this) + ).fail(() => { this.setState({ initialized: true, - workers: workers }) - this.resetTimer(); - }.bind(this)) - .fail(() => { - this.setState({ - initialized: true, - }); - this.resetTimer(); - }); + this.resetTimer() + }) } resetTimer() { - clearTimeout(this.timeoutId); - this.timeoutId = setTimeout(this.refreshLoop.bind(this), 1000); + clearTimeout(this.timeoutId) + this.timeoutId = setTimeout(this.refreshLoop.bind(this), 1000) } componentDidMount() { - this.refreshLoop(); + this.refreshLoop() } render() { - const workers = this.state.workers; + const workers = this.state.workers if (workers === null) { if (this.state.initialized === false) { - return ( -
    Loading...
    - ); - } - else { + return
    Loading...
    + } else { return (
    -

    Worker list information could not be loaded

    +
    +

    Worker list information could not be loaded

    +
    - ); + ) } } let workerList = function () { - let trs = []; + let trs = [] workers.forEach((worker) => { trs.push( - {worker.nodeId} - {worker.nodeIp} + + + {worker.nodeId} + + + + + {worker.nodeIp} + + {worker.nodeVersion} {String(worker.coordinator)} {worker.state} - ); - }); - return trs; - }; + ) + }) + return trs + } return (

    Overview

    -
    +
    - - - - - - - - {workerList()} + + + + + + + + {workerList()}
    Node IDNode IPNode VersionCoordinatorState
    Node IDNode IPNode VersionCoordinatorState
    - ); + ) } } diff --git a/core/trino-web-ui/src/main/resources/webapp/src/components/WorkerStatus.jsx b/core/trino-web-ui/src/main/resources/webapp/src/components/WorkerStatus.jsx index 57b3995955ea..d3bfff51c630 100644 --- a/core/trino-web-ui/src/main/resources/webapp/src/components/WorkerStatus.jsx +++ b/core/trino-web-ui/src/main/resources/webapp/src/components/WorkerStatus.jsx @@ -12,9 +12,9 @@ * limitations under the License. */ -import React from "react"; +import React from 'react' -import {addToHistory, formatCount, formatDataSize, getFirstParameter, precisionRound} from "../utils"; +import { addToHistory, formatCount, formatDataSize, getFirstParameter, precisionRound } from '../utils' const SMALL_SPARKLINE_PROPERTIES = { width: '100%', @@ -24,11 +24,11 @@ const SMALL_SPARKLINE_PROPERTIES = { spotColor: '#1EDCFF', tooltipClassname: 'sparkline-tooltip', disableHiddenCheck: true, -}; +} export class WorkerStatus extends React.Component { constructor(props) { - super(props); + super(props) this.state = { serverInfo: null, initialized: false, @@ -38,69 +38,100 @@ export class WorkerStatus extends React.Component { systemCpuLoad: [], heapPercentUsed: [], nonHeapUsed: [], - }; + } - this.refreshLoop = this.refreshLoop.bind(this); + this.refreshLoop = this.refreshLoop.bind(this) } resetTimer() { - clearTimeout(this.timeoutId); + clearTimeout(this.timeoutId) // stop refreshing when query finishes or fails if (this.state.query === null || !this.state.ended) { - this.timeoutId = setTimeout(this.refreshLoop, 1000); + this.timeoutId = setTimeout(this.refreshLoop, 1000) } } refreshLoop() { - clearTimeout(this.timeoutId); // to stop multiple series of refreshLoop from going on simultaneously - const nodeId = getFirstParameter(window.location.search); - $.get('/ui/api/worker/' + nodeId + '/status', function (serverInfo) { - this.setState({ - serverInfo: serverInfo, - initialized: true, + clearTimeout(this.timeoutId) // to stop multiple series of refreshLoop from going on simultaneously + const nodeId = getFirstParameter(window.location.search) + $.get( + '/ui/api/worker/' + nodeId + '/status', + function (serverInfo) { + this.setState({ + serverInfo: serverInfo, + initialized: true, - processCpuLoad: addToHistory(serverInfo.processCpuLoad * 100.0, this.state.processCpuLoad), - systemCpuLoad: addToHistory(serverInfo.systemCpuLoad * 100.0, this.state.systemCpuLoad), - heapPercentUsed: addToHistory(serverInfo.heapUsed * 100.0 / serverInfo.heapAvailable, this.state.heapPercentUsed), - nonHeapUsed: addToHistory(serverInfo.nonHeapUsed, this.state.nonHeapUsed), - }); + processCpuLoad: addToHistory(serverInfo.processCpuLoad * 100.0, this.state.processCpuLoad), + systemCpuLoad: addToHistory(serverInfo.systemCpuLoad * 100.0, this.state.systemCpuLoad), + heapPercentUsed: addToHistory( + (serverInfo.heapUsed * 100.0) / serverInfo.heapAvailable, + this.state.heapPercentUsed + ), + nonHeapUsed: addToHistory(serverInfo.nonHeapUsed, this.state.nonHeapUsed), + }) - this.resetTimer(); - }.bind(this)) - .fail(function () { + this.resetTimer() + }.bind(this) + ).fail( + function () { this.setState({ initialized: true, - }); - this.resetTimer(); - }.bind(this)); + }) + this.resetTimer() + }.bind(this) + ) } componentDidMount() { - this.refreshLoop(); + this.refreshLoop() } componentDidUpdate() { - $('#process-cpu-load-sparkline').sparkline(this.state.processCpuLoad, $.extend({}, SMALL_SPARKLINE_PROPERTIES, {chartRangeMin: 0, numberFormatter: precisionRound})); - $('#system-cpu-load-sparkline').sparkline(this.state.systemCpuLoad, $.extend({}, SMALL_SPARKLINE_PROPERTIES, {chartRangeMin: 0, numberFormatter: precisionRound})); - $('#heap-percent-used-sparkline').sparkline(this.state.heapPercentUsed, $.extend({}, SMALL_SPARKLINE_PROPERTIES, {chartRangeMin: 0, numberFormatter: precisionRound})); - $('#nonheap-used-sparkline').sparkline(this.state.nonHeapUsed, $.extend({}, SMALL_SPARKLINE_PROPERTIES, {chartRangeMin: 0, numberFormatter: formatDataSize})); + $('#process-cpu-load-sparkline').sparkline( + this.state.processCpuLoad, + $.extend({}, SMALL_SPARKLINE_PROPERTIES, { + chartRangeMin: 0, + numberFormatter: precisionRound, + }) + ) + $('#system-cpu-load-sparkline').sparkline( + this.state.systemCpuLoad, + $.extend({}, SMALL_SPARKLINE_PROPERTIES, { + chartRangeMin: 0, + numberFormatter: precisionRound, + }) + ) + $('#heap-percent-used-sparkline').sparkline( + this.state.heapPercentUsed, + $.extend({}, SMALL_SPARKLINE_PROPERTIES, { + chartRangeMin: 0, + numberFormatter: precisionRound, + }) + ) + $('#nonheap-used-sparkline').sparkline( + this.state.nonHeapUsed, + $.extend({}, SMALL_SPARKLINE_PROPERTIES, { + chartRangeMin: 0, + numberFormatter: formatDataSize, + }) + ) - $('[data-toggle="tooltip"]').tooltip(); - new window.ClipboardJS('.copy-button'); + $('[data-toggle="tooltip"]').tooltip() + new window.ClipboardJS('.copy-button') } static renderPoolBar(name, pool) { if (!pool) { - return; + return } - const size = pool.maxBytes; - const reserved = pool.reservedBytes; - const revocable = pool.reservedRevocableBytes; + const size = pool.maxBytes + const reserved = pool.reservedBytes + const revocable = pool.reservedRevocableBytes - const percentageReservedNonRevocable = reserved === 0 ? 0 : Math.max(Math.round(reserved * 100.0 / size), 15); - const percentageRevocable = revocable === 0 ? 0 : Math.max(Math.round(revocable * 100.0 / size), 15); - const percentageFree = 100 - (percentageRevocable + percentageReservedNonRevocable); + const percentageReservedNonRevocable = reserved === 0 ? 0 : Math.max(Math.round((reserved * 100.0) / size), 15) + const percentageRevocable = revocable === 0 ? 0 : Math.max(Math.round((revocable * 100.0) / size), 15) + const percentageFree = 100 - (percentageRevocable + percentageReservedNonRevocable) return (
    @@ -110,8 +141,12 @@ export class WorkerStatus extends React.Component {

    {name} Pool

    -
    -
    +
    +
    {formatDataSize(size)} total
    @@ -119,17 +154,29 @@ export class WorkerStatus extends React.Component {
    -
    +
    -
    +
    {formatDataSize(reserved)}
    -
    +
    {formatDataSize(revocable)}
    -
    +
    {formatDataSize(size - reserved - revocable)}
    @@ -146,7 +193,7 @@ export class WorkerStatus extends React.Component {
    @@ -154,13 +201,21 @@ export class WorkerStatus extends React.Component {
    - {Math.round(reserved * 100.0 / total)}% + {Math.round((reserved * 100.0) / total)}%
    - - {formatDataSize(reserved)} + + {formatDataSize(reserved)}
    @@ -173,12 +228,12 @@ export class WorkerStatus extends React.Component { renderPoolQueries(pool) { if (!pool) { - return; + return } - const queries = {}; - const reservations = pool.queryMemoryReservations; - const revocableReservations = pool.queryMemoryRevocableReservations; + const queries = {} + const reservations = pool.queryMemoryReservations + const revocableReservations = pool.queryMemoryRevocableReservations for (let query in reservations) { queries[query] = [reservations[query], 0] @@ -187,35 +242,34 @@ export class WorkerStatus extends React.Component { for (let query in revocableReservations) { if (queries.hasOwnProperty(query)) { queries[query][1] = revocableReservations[query] - } - else { + } else { queries[query] = [0, revocableReservations[query]] } } - const size = pool.maxBytes; + const size = pool.maxBytes if (Object.keys(queries).length === 0) { return (
    - - - + + +
    - No queries using pool -
    No queries using pool
    - ); + ) } return (
    - {Object.keys(queries).map(key => WorkerStatus.renderPoolQuery(key, queries[key][0], queries[key][1], size))} + {Object.keys(queries).map((key) => + WorkerStatus.renderPoolQuery(key, queries[key][0], queries[key][1], size) + )}
    @@ -223,20 +277,19 @@ export class WorkerStatus extends React.Component { } render() { - const serverInfo = this.state.serverInfo; + const serverInfo = this.state.serverInfo if (serverInfo === null) { if (this.state.initialized === false) { - return ( -
    Loading...
    - ); - } - else { + return
    Loading...
    + } else { return (
    -

    Node information could not be loaded

    +
    +

    Node information could not be loaded

    +
    - ); + ) } } @@ -245,80 +298,95 @@ export class WorkerStatus extends React.Component {

    Overview

    -
    +
    - - - - - - - - - - - - + + + + + + + + + + + +
    - Node ID - - {serverInfo.nodeId} -    - - - -
    - Heap Memory - - {formatDataSize(serverInfo.heapAvailable)} -
    - Processors - - {serverInfo.processors} -
    Node ID + {serverInfo.nodeId} +    + + + +
    Heap Memory + + {formatDataSize(serverInfo.heapAvailable)} + +
    Processors + {serverInfo.processors} +
    - - - - - - - - - - - - + + + + + + + + + + + +
    - Uptime - - {serverInfo.uptime} -
    - External Address - - {serverInfo.externalAddress} -    - - - -
    - Internal Address - - {serverInfo.internalAddress} -    - - - -
    Uptime{serverInfo.uptime}
    External Address + {serverInfo.externalAddress} +    + + + +
    Internal Address + {serverInfo.internalAddress} +    + + + +
    @@ -326,78 +394,97 @@ export class WorkerStatus extends React.Component {

    Resource Utilization

    -
    +
    -
    - - - - - - - - - - - - - - + + + + + + + + + + + + + +
    - Process CPU Utilization - -
    -
    Loading ...
    -
    -
    - {formatCount(this.state.processCpuLoad[this.state.processCpuLoad.length - 1])}% -
    - System CPU Utilization - -
    -
    Loading ...
    -
    -
    - {formatCount(this.state.systemCpuLoad[this.state.systemCpuLoad.length - 1])}% -
    Process CPU Utilization +
    + +
    Loading ...
    +
    +
    +
    + {formatCount( + this.state.processCpuLoad[ + this.state.processCpuLoad.length - 1 + ] + )} + % +
    System CPU Utilization +
    + +
    Loading ...
    +
    +
    +
    + {formatCount( + this.state.systemCpuLoad[ + this.state.systemCpuLoad.length - 1 + ] + )} + % +
    - - - - - - - - - - - - - - + + + + + + + + + + + + + +
    - Heap Utilization - -
    -
    Loading ...
    -
    -
    - {formatCount(this.state.heapPercentUsed[this.state.heapPercentUsed.length - 1])}% -
    - Non-Heap Memory Used - -
    -
    Loading ...
    -
    -
    - {formatDataSize(this.state.nonHeapUsed[this.state.nonHeapUsed.length - 1])} -
    Heap Utilization +
    + +
    Loading ...
    +
    +
    +
    + {formatCount( + this.state.heapPercentUsed[ + this.state.heapPercentUsed.length - 1 + ] + )} + % +
    Non-Heap Memory Used +
    + +
    Loading ...
    +
    +
    +
    + {formatDataSize( + this.state.nonHeapUsed[this.state.nonHeapUsed.length - 1] + )} +
    @@ -409,16 +496,16 @@ export class WorkerStatus extends React.Component {

    Memory

    -
    +
    - {WorkerStatus.renderPoolBar("Memory Usage", serverInfo.memoryInfo.pool)} + {WorkerStatus.renderPoolBar('Memory Usage', serverInfo.memoryInfo.pool)} {this.renderPoolQueries(serverInfo.memoryInfo.pool)}
    - ); + ) } } diff --git a/core/trino-web-ui/src/main/resources/webapp/src/components/WorkerThreadList.jsx b/core/trino-web-ui/src/main/resources/webapp/src/components/WorkerThreadList.jsx index 235b918bdf88..01b86aed1ba7 100644 --- a/core/trino-web-ui/src/main/resources/webapp/src/components/WorkerThreadList.jsx +++ b/core/trino-web-ui/src/main/resources/webapp/src/components/WorkerThreadList.jsx @@ -12,21 +12,21 @@ * limitations under the License. */ -import React from "react"; +import React from 'react' -import {getFirstParameter} from "../utils"; +import { getFirstParameter } from '../utils' -const ALL_THREADS = "All Threads"; -const QUERY_THREADS = "Running Queries"; +const ALL_THREADS = 'All Threads' +const QUERY_THREADS = 'Running Queries' -const ALL_THREAD_STATE = "ALL"; -const THREAD_STATES = [ALL_THREAD_STATE, "RUNNABLE", "BLOCKED", "WAITING", "TIMED_WAITING", "NEW", "TERMINATED"]; -const QUERY_THREAD_REGEX = new RegExp(/([0-9])*_([0-9])*_([0-9])*_.*?\.([0-9])*\.([0-9])*-([0-9])*-([0-9])*/); -const THREAD_GROUP_REGEXP = new RegExp(/(.*?)-[0-9]+/); +const ALL_THREAD_STATE = 'ALL' +const THREAD_STATES = [ALL_THREAD_STATE, 'RUNNABLE', 'BLOCKED', 'WAITING', 'TIMED_WAITING', 'NEW', 'TERMINATED'] +const QUERY_THREAD_REGEX = new RegExp(/([0-9])*_([0-9])*_([0-9])*_.*?\.([0-9])*\.([0-9])*-([0-9])*-([0-9])*/) +const THREAD_GROUP_REGEXP = new RegExp(/(.*?)-[0-9]+/) export class WorkerThreadList extends React.Component { constructor(props) { - super(props); + super(props) this.state = { serverInfo: null, initialized: false, @@ -38,49 +38,53 @@ export class WorkerThreadList extends React.Component { selectedGroup: ALL_THREADS, selectedThreadState: ALL_THREAD_STATE, - }; + } } captureSnapshot() { - const nodeId = getFirstParameter(window.location.search); - $.get('/ui/api/worker/' + nodeId + '/thread', function (threads) { - this.setState({ - threads: WorkerThreadList.processThreads(threads), - snapshotTime: new Date(), - initialized: true, - }); - }.bind(this)) - .fail(function () { - this.setState({ - initialized: true, - }); - }.bind(this)); + const nodeId = getFirstParameter(window.location.search) + $.get( + '/ui/api/worker/' + nodeId + '/thread', + function (threads) { + this.setState({ + threads: WorkerThreadList.processThreads(threads), + snapshotTime: new Date(), + initialized: true, + }) + }.bind(this) + ).fail( + function () { + this.setState({ + initialized: true, + }) + }.bind(this) + ) } componentDidUpdate() { - new window.ClipboardJS('.copy-button'); + new window.ClipboardJS('.copy-button') } static processThreads(threads) { - const result = {}; + const result = {} - result[ALL_THREADS] = threads; + result[ALL_THREADS] = threads for (let i = 0; i < threads.length; i++) { - const thread = threads[i]; + const thread = threads[i] if (thread.name.match(QUERY_THREAD_REGEX)) { if (!result[QUERY_THREADS]) { - result[QUERY_THREADS] = []; + result[QUERY_THREADS] = [] } result[QUERY_THREADS].push(thread) } - const match = THREAD_GROUP_REGEXP.exec(thread.name); - const threadGroup = match ? match[1] : thread.name; + const match = THREAD_GROUP_REGEXP.exec(thread.name) + const threadGroup = match ? match[1] : thread.name if (!result[threadGroup]) { - result[threadGroup] = []; + result[threadGroup] = [] } - result[threadGroup].push(thread); + result[threadGroup].push(thread) } return result @@ -88,175 +92,217 @@ export class WorkerThreadList extends React.Component { handleGroupClick(selectedGroup, event) { this.setState({ - selectedGroup: selectedGroup - }); - event.preventDefault(); + selectedGroup: selectedGroup, + }) + event.preventDefault() } handleThreadStateClick(selectedThreadState, event) { this.setState({ - selectedThreadState: selectedThreadState - }); - event.preventDefault(); + selectedThreadState: selectedThreadState, + }) + event.preventDefault() } handleNewSnapshotClick(event) { this.setState({ - initialized: false - }); - this.captureSnapshot(); - event.preventDefault(); + initialized: false, + }) + this.captureSnapshot() + event.preventDefault() } filterThreads(group, state) { - return this.state.threads[group].filter(t => t.state === state || state === ALL_THREAD_STATE); + return this.state.threads[group].filter((t) => t.state === state || state === ALL_THREAD_STATE) } renderGroupListItem(group) { return (
  • - + {group} ({this.filterThreads(group, this.state.selectedThreadState).length})
  • - ); + ) } renderThreadStateListItem(threadState) { return (
  • - + {threadState} ({this.filterThreads(this.state.selectedGroup, threadState).length})
  • - ); + ) } renderStackLine(threadId) { return (stackLine, index) => { return (
    -   at {stackLine.className}.{stackLine.method} - ({stackLine.file}:{stackLine.line}) -
    ); - }; +   at {stackLine.className}.{stackLine.method}( + + {stackLine.file}:{stackLine.line} + + ) +
    + ) + } } renderThread(threadInfo) { return (
    - {threadInfo.name} {threadInfo.state} #{threadInfo.id} {threadInfo.lockOwnerId} - - + + {threadInfo.name} {threadInfo.state} #{threadInfo.id} {threadInfo.lockOwnerId} + + + -
    - +
    + {threadInfo.stackTrace.map(this.renderStackLine(threadInfo.id))} -
    -   -
    +
     
    - ); + ) } render() { - const threads = this.state.threads; + const threads = this.state.threads - let display = null; - let toolbar = null; + let display = null + let toolbar = null if (threads === null) { if (this.state.initialized === false) { display = (
    -
    +
    + +
    - ); - } - else { + ) + } else { display = (
    -

    Thread snapshot could not be loaded

    +
    +

    Thread snapshot could not be loaded

    +
    - ); + ) } - } - else { + } else { toolbar = (
    - - - - - + + - +       + + + +
    - Snapshot at {this.state.snapshotTime.toTimeString()} -    - - -    -    - -
    - -
      - {Object.keys(threads).map(group => this.renderGroupListItem(group))} -
    -
    -
    -
    -
    + Snapshot at {this.state.snapshotTime.toTimeString()} +    + + -
      - {THREAD_STATES.map(state => this.renderThreadStateListItem(state))} -
    - -
    +
    + +
      + {Object.keys(threads).map((group) => this.renderGroupListItem(group))} +
    +
    +
    +
    + +
      + {THREAD_STATES.map((state) => this.renderThreadStateListItem(state))} +
    +
    +
    - ); + ) - const filteredThreads = this.filterThreads(this.state.selectedGroup, this.state.selectedThreadState); - let displayedThreads; + const filteredThreads = this.filterThreads(this.state.selectedGroup, this.state.selectedThreadState) + let displayedThreads if (filteredThreads.length === 0 && this.state.selectedThreadState === ALL_THREAD_STATE) { displayedThreads = (
    -

    No threads in group '{this.state.selectedGroup}'

    -
    ); - } - else if (filteredThreads.length === 0 && this.state.selectedGroup === ALL_THREADS) { +
    +

    No threads in group '{this.state.selectedGroup}'

    +
    +
    + ) + } else if (filteredThreads.length === 0 && this.state.selectedGroup === ALL_THREADS) { displayedThreads = (
    -

    No threads with state {this.state.selectedThreadState}

    -
    ); - } - else if (filteredThreads.length === 0) { +
    +

    No threads with state {this.state.selectedThreadState}

    +
    +
    + ) + } else if (filteredThreads.length === 0) { displayedThreads = (
    -

    No threads in group '{this.state.selectedGroup}' with state {this.state.selectedThreadState}

    -
    ); - } - else { - displayedThreads = ( -
    -                        {filteredThreads.map(t => this.renderThread(t))}
    -                    
    ); +
    +

    + No threads in group '{this.state.selectedGroup}' with state{' '} + {this.state.selectedThreadState} +

    +
    +
    + ) + } else { + displayedThreads =
    {filteredThreads.map((t) => this.renderThread(t))}
    } - display = ( -
    - {displayedThreads} -
    - ); + display =
    {displayedThreads}
    } return ( @@ -265,8 +311,14 @@ export class WorkerThreadList extends React.Component {

    Thread Snapshot - - + +  

    @@ -275,11 +327,11 @@ export class WorkerThreadList extends React.Component {
    -
    +
    {display}
    - ); + ) } } diff --git a/core/trino-web-ui/src/main/resources/webapp/src/package-lock.json b/core/trino-web-ui/src/main/resources/webapp/src/package-lock.json index 11884116fbf4..3206bab8fdca 100644 --- a/core/trino-web-ui/src/main/resources/webapp/src/package-lock.json +++ b/core/trino-web-ui/src/main/resources/webapp/src/package-lock.json @@ -22,6 +22,7 @@ "@babel/preset-react": "^7.12.10", "babel-loader": "^9.1.3", "flow-bin": "^0.241.0", + "prettier": "3.3.3", "terser-webpack-plugin": "^5.3.10", "webpack": "^5.93.0", "webpack-cli": "^5.1.4" @@ -4108,6 +4109,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/prettier": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/prop-types": { "version": "15.6.2", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", diff --git a/core/trino-web-ui/src/main/resources/webapp/src/package.json b/core/trino-web-ui/src/main/resources/webapp/src/package.json index bd1e33efcff5..2a78ba44d795 100644 --- a/core/trino-web-ui/src/main/resources/webapp/src/package.json +++ b/core/trino-web-ui/src/main/resources/webapp/src/package.json @@ -4,15 +4,16 @@ "license": "Apache-2.0", "private": true, "devDependencies": { - "babel-loader": "^9.1.3", "@babel/core": "^7.12.0", - "@babel/preset-flow": "^7.24.7", "@babel/preset-env": "^7.24.8", + "@babel/preset-flow": "^7.24.7", "@babel/preset-react": "^7.12.10", + "babel-loader": "^9.1.3", "flow-bin": "^0.241.0", + "prettier": "3.3.3", + "terser-webpack-plugin": "^5.3.10", "webpack": "^5.93.0", - "webpack-cli": "^5.1.4", - "terser-webpack-plugin": "^5.3.10" + "webpack-cli": "^5.1.4" }, "dependencies": { "d3": "^5.7.0", @@ -31,6 +32,8 @@ "scripts": { "package": "npm install && webpack --config webpack.config.js", "watch": "npm install && webpack --config webpack.config.js --watch", - "flow": "flow" + "flow": "flow", + "check": "flow && prettier --check **/*.js **/*.jsx", + "lint": "prettier --check **/*.js **/*.jsx" } } diff --git a/core/trino-web-ui/src/main/resources/webapp/src/utils.js b/core/trino-web-ui/src/main/resources/webapp/src/utils.js index 5699a987135c..ee9a6df1e57b 100644 --- a/core/trino-web-ui/src/main/resources/webapp/src/utils.js +++ b/core/trino-web-ui/src/main/resources/webapp/src/utils.js @@ -13,16 +13,16 @@ */ //@flow -import * as dagreD3 from "dagre-d3"; -import * as d3 from "d3"; +import * as dagreD3 from 'dagre-d3' +import * as d3 from 'd3' // Query display // ============= -export const GLYPHICON_DEFAULT = {color: '#1edcff'}; -export const GLYPHICON_HIGHLIGHT = {color: '#999999'}; +export const GLYPHICON_DEFAULT = { color: '#1edcff' } +export const GLYPHICON_HIGHLIGHT = { color: '#999999' } -const EMDASH = "\u2014"; +const EMDASH = '\u2014' const STATE_COLOR_MAP = { QUEUED: '#1b8f72', @@ -34,196 +34,187 @@ const STATE_COLOR_MAP = { CANCELED: '#858959', INSUFFICIENT_RESOURCES: '#7f5b72', EXTERNAL_ERROR: '#ca7640', - UNKNOWN_ERROR: '#943524' -}; + UNKNOWN_ERROR: '#943524', +} -export function getQueryStateColor(query: any): string -{ +export function getQueryStateColor(query: any): string { switch (query.state) { - case "QUEUED": - return STATE_COLOR_MAP.QUEUED; - case "PLANNING": - return STATE_COLOR_MAP.PLANNING; - case "STARTING": - case "FINISHING": - case "RUNNING": + case 'QUEUED': + return STATE_COLOR_MAP.QUEUED + case 'PLANNING': + return STATE_COLOR_MAP.PLANNING + case 'STARTING': + case 'FINISHING': + case 'RUNNING': if (query.queryStats && query.queryStats.fullyBlocked) { - return STATE_COLOR_MAP.BLOCKED; + return STATE_COLOR_MAP.BLOCKED } - return STATE_COLOR_MAP.RUNNING; - case "FAILED": + return STATE_COLOR_MAP.RUNNING + case 'FAILED': switch (query.errorType) { - case "USER_ERROR": + case 'USER_ERROR': if (query.errorCode.name === 'USER_CANCELED') { - return STATE_COLOR_MAP.CANCELED; + return STATE_COLOR_MAP.CANCELED } - return STATE_COLOR_MAP.USER_ERROR; - case "EXTERNAL": - return STATE_COLOR_MAP.EXTERNAL_ERROR; - case "INSUFFICIENT_RESOURCES": - return STATE_COLOR_MAP.INSUFFICIENT_RESOURCES; + return STATE_COLOR_MAP.USER_ERROR + case 'EXTERNAL': + return STATE_COLOR_MAP.EXTERNAL_ERROR + case 'INSUFFICIENT_RESOURCES': + return STATE_COLOR_MAP.INSUFFICIENT_RESOURCES default: - return STATE_COLOR_MAP.UNKNOWN_ERROR; + return STATE_COLOR_MAP.UNKNOWN_ERROR } - case "FINISHED": - return STATE_COLOR_MAP.FINISHED; + case 'FINISHED': + return STATE_COLOR_MAP.FINISHED default: - return STATE_COLOR_MAP.QUEUED; + return STATE_COLOR_MAP.QUEUED } } -export function getStageStateColor(stage: any): string -{ +export function getStageStateColor(stage: any): string { switch (stage.state) { - case "PLANNED": - return STATE_COLOR_MAP.QUEUED; - case "SCHEDULING": - case "SCHEDULING_SPLITS": - case "SCHEDULED": - return STATE_COLOR_MAP.PLANNING; - case "RUNNING": + case 'PLANNED': + return STATE_COLOR_MAP.QUEUED + case 'SCHEDULING': + case 'SCHEDULING_SPLITS': + case 'SCHEDULED': + return STATE_COLOR_MAP.PLANNING + case 'RUNNING': if (stage.stageStats && stage.stageStats.fullyBlocked) { - return STATE_COLOR_MAP.BLOCKED; + return STATE_COLOR_MAP.BLOCKED } - return STATE_COLOR_MAP.RUNNING; - case "FINISHED": - return STATE_COLOR_MAP.FINISHED; - case "CANCELED": - case "ABORTED": - return STATE_COLOR_MAP.CANCELED; - case "FAILED": - return STATE_COLOR_MAP.UNKNOWN_ERROR; + return STATE_COLOR_MAP.RUNNING + case 'FINISHED': + return STATE_COLOR_MAP.FINISHED + case 'CANCELED': + case 'ABORTED': + return STATE_COLOR_MAP.CANCELED + case 'FAILED': + return STATE_COLOR_MAP.UNKNOWN_ERROR default: - return "#b5b5b5" + return '#b5b5b5' } } // This relies on the fact that BasicQueryInfo and QueryInfo have all the fields // necessary to compute this string, and that these fields are consistently named. -export function getHumanReadableState(query: any, forOverviewPage: boolean): string -{ - if (query.state === "RUNNING") { - let title = "RUNNING"; +export function getHumanReadableState(query: any, forOverviewPage: boolean): string { + if (query.state === 'RUNNING') { + let title = 'RUNNING' if (query.scheduled && query.queryStats.totalDrivers > 0 && query.queryStats.runningDrivers >= 0) { if (query.queryStats.fullyBlocked) { - title = "BLOCKED"; + title = 'BLOCKED' if (query.queryStats.blockedReasons && query.queryStats.blockedReasons.length > 0) { - title += " (" + query.queryStats.blockedReasons.join(", ") + ")"; + title += ' (' + query.queryStats.blockedReasons.join(', ') + ')' } } - if (query.memoryPool === "reserved") { - title += " (RESERVED)" + if (query.memoryPool === 'reserved') { + title += ' (RESERVED)' } - return title; + return title } } - if (query.state === "FAILED") { - let errorMsg = ""; + if (query.state === 'FAILED') { + let errorMsg = '' switch (query.errorType) { - case "USER_ERROR": - errorMsg = "USER ERROR"; - if (query.errorCode.name === "USER_CANCELED") { - errorMsg = "USER CANCELED"; + case 'USER_ERROR': + errorMsg = 'USER ERROR' + if (query.errorCode.name === 'USER_CANCELED') { + errorMsg = 'USER CANCELED' } - break; - case "INTERNAL_ERROR": - errorMsg = "INTERNAL ERROR"; - break; - case "INSUFFICIENT_RESOURCES": - errorMsg = "INSUFFICIENT RESOURCES"; - break; - case "EXTERNAL": - errorMsg = "EXTERNAL ERROR"; - break; + break + case 'INTERNAL_ERROR': + errorMsg = 'INTERNAL ERROR' + break + case 'INSUFFICIENT_RESOURCES': + errorMsg = 'INSUFFICIENT RESOURCES' + break + case 'EXTERNAL': + errorMsg = 'EXTERNAL ERROR' + break } if (forOverviewPage && query.errorCode && query.errorCode.name) { - errorMsg += " " + EMDASH + " " + query.errorCode.name; + errorMsg += ' ' + EMDASH + ' ' + query.errorCode.name } - return errorMsg; + return errorMsg } - return query.state; + return query.state } -export function getProgressBarPercentage(query: any): number -{ - const progress = query.queryStats.progressPercentage; +export function getProgressBarPercentage(query: any): number { + const progress = query.queryStats.progressPercentage // progress bars should appear 'full' when query progress is not meaningful - if (!progress || query.state !== "RUNNING") { - return 100; + if (!progress || query.state !== 'RUNNING') { + return 100 } - return Math.round(progress); + return Math.round(progress) } -export function getProgressBarTitle(query: any, forOverviewPage: boolean): string -{ - if (query.queryStats.progressPercentage && query.state === "RUNNING") { - return getHumanReadableState(query, forOverviewPage) + " (" + getProgressBarPercentage(query) + "%)" +export function getProgressBarTitle(query: any, forOverviewPage: boolean): string { + if (query.queryStats.progressPercentage && query.state === 'RUNNING') { + return getHumanReadableState(query, forOverviewPage) + ' (' + getProgressBarPercentage(query) + '%)' } return getHumanReadableState(query, forOverviewPage) } -export function isQueryEnded(query: any): boolean -{ - return ["FINISHED", "FAILED", "CANCELED"].indexOf(query.state) > -1; +export function isQueryEnded(query: any): boolean { + return ['FINISHED', 'FAILED', 'CANCELED'].indexOf(query.state) > -1 } // Sparkline-related functions // =========================== // display at most 5 minutes worth of data on the sparklines -const MAX_HISTORY = 60 * 5; +const MAX_HISTORY = 60 * 5 // alpha param of exponentially weighted moving average. picked arbitrarily - lower values means more smoothness -const MOVING_AVERAGE_ALPHA = 0.2; +const MOVING_AVERAGE_ALPHA = 0.2 -export function addToHistory (value: number, valuesArray: number[]): number[] { +export function addToHistory(value: number, valuesArray: number[]): number[] { if (valuesArray.length === 0) { - return valuesArray.concat([value]); + return valuesArray.concat([value]) } - return valuesArray.concat([value]).slice(Math.max(valuesArray.length - MAX_HISTORY, 0)); + return valuesArray.concat([value]).slice(Math.max(valuesArray.length - MAX_HISTORY, 0)) } -export function addExponentiallyWeightedToHistory (value: number, valuesArray: number[]): number[] { +export function addExponentiallyWeightedToHistory(value: number, valuesArray: number[]): number[] { if (valuesArray.length === 0) { - return valuesArray.concat([value]); + return valuesArray.concat([value]) } - let movingAverage = (value * MOVING_AVERAGE_ALPHA) + (valuesArray[valuesArray.length - 1] * (1 - MOVING_AVERAGE_ALPHA)); + let movingAverage = value * MOVING_AVERAGE_ALPHA + valuesArray[valuesArray.length - 1] * (1 - MOVING_AVERAGE_ALPHA) if (value < 1) { - movingAverage = 0; + movingAverage = 0 } - return valuesArray.concat([movingAverage]).slice(Math.max(valuesArray.length - MAX_HISTORY, 0)); + return valuesArray.concat([movingAverage]).slice(Math.max(valuesArray.length - MAX_HISTORY, 0)) } // DagreD3 Graph-related functions // =============================== -export function initializeGraph() : any -{ - return new dagreD3.graphlib.Graph({compound: true}) - .setGraph({rankdir: 'BT'}) - .setDefaultEdgeLabel(function () { return {}; }); +export function initializeGraph(): any { + return new dagreD3.graphlib.Graph({ compound: true }).setGraph({ rankdir: 'BT' }).setDefaultEdgeLabel(function () { + return {} + }) } -export function initializeSvg(selector: any) : any -{ - const svg = d3.select(selector); - svg.append("g"); +export function initializeSvg(selector: any): any { + const svg = d3.select(selector) + svg.append('g') - return svg; + return svg } -export function getChildren(nodeInfo: any) : any -{ +export function getChildren(nodeInfo: any): any { // TODO: Remove this function by migrating StageDetail to use node JSON representation switch (nodeInfo['@type']) { case 'aggregation': @@ -253,19 +244,19 @@ export function getChildren(nodeInfo: any) : any case 'topNRanking': case 'unnest': case 'window': - return [nodeInfo.source]; + return [nodeInfo.source] case 'join': - return [nodeInfo.left, nodeInfo.right]; + return [nodeInfo.left, nodeInfo.right] case 'semiJoin': - return [nodeInfo.source, nodeInfo.filteringSource]; + return [nodeInfo.source, nodeInfo.filteringSource] case 'spatialJoin': - return [nodeInfo.left, nodeInfo.right]; + return [nodeInfo.left, nodeInfo.right] case 'indexJoin': - return [nodeInfo.probeSource, nodeInfo.indexSource]; + return [nodeInfo.probeSource, nodeInfo.indexSource] case 'exchange': case 'intersect': case 'union': - return nodeInfo.sources; + return nodeInfo.sources case 'indexSource': case 'loadCachedData': case 'refreshMaterializedView': @@ -274,12 +265,12 @@ export function getChildren(nodeInfo: any) : any case 'tableScan': case 'tableUpdate': case 'values': - break; + break default: - console.log("NOTE: Unhandled PlanNode: " + nodeInfo['@type']); + console.log('NOTE: Unhandled PlanNode: ' + nodeInfo['@type']) } - return []; + return [] } // Utility functions @@ -287,10 +278,10 @@ export function getChildren(nodeInfo: any) : any export function truncateString(inputString: string, length: number): string { if (inputString && inputString.length > length) { - return inputString.substring(0, length) + "..."; + return inputString.substring(0, length) + '...' } - return inputString; + return inputString } export function getStageNumber(stageId: string): number { @@ -302,222 +293,230 @@ export function getTaskIdSuffix(taskId: string): string { } export function getTaskNumber(taskId: string): number { - return Number.parseInt(getTaskIdSuffix(getTaskIdSuffix(taskId))); + return Number.parseInt(getTaskIdSuffix(getTaskIdSuffix(taskId))) } export function getFirstParameter(searchString: string): string { - const searchText = searchString.substring(1); + const searchText = searchString.substring(1) if (searchText.indexOf('&') !== -1) { - return searchText.substring(0, searchText.indexOf('&')); + return searchText.substring(0, searchText.indexOf('&')) } - return searchText; + return searchText } export function getHostname(url: string): string { - let hostname = new URL(url).hostname; - if ((hostname.charAt(0) === '[') && (hostname.charAt(hostname.length - 1) === ']')) { - hostname = hostname.substr(1, hostname.length - 2); + let hostname = new URL(url).hostname + if (hostname.charAt(0) === '[' && hostname.charAt(hostname.length - 1) === ']') { + hostname = hostname.substr(1, hostname.length - 2) } - return hostname; + return hostname } export function getPort(url: string): string { - return new URL(url).port; + return new URL(url).port } export function getHostAndPort(urlStr: string): string { - const url = new URL(urlStr); - return url.hostname + ":" + url.port; + const url = new URL(urlStr) + return url.hostname + ':' + url.port } export function computeRate(count: number, ms: number): number { if (ms === 0) { - return 0; + return 0 } - return (count / ms) * 1000.0; + return (count / ms) * 1000.0 } export function precisionRound(n: number): string { - if (n === undefined){ - return "n/a"; + if (n === undefined) { + return 'n/a' } if (n < 10) { - return n.toFixed(2); + return n.toFixed(2) } if (n < 100) { - return n.toFixed(1); + return n.toFixed(1) } - return Math.round(n).toString(); + return Math.round(n).toString() } export function formatDuration(duration: number): string { - let unit = "ms"; + let unit = 'ms' if (duration > 1000) { - duration /= 1000; - unit = "s"; + duration /= 1000 + unit = 's' } - if (unit === "s" && duration > 60) { - duration /= 60; - unit = "m"; + if (unit === 's' && duration > 60) { + duration /= 60 + unit = 'm' } - if (unit === "m" && duration > 60) { - duration /= 60; - unit = "h"; + if (unit === 'm' && duration > 60) { + duration /= 60 + unit = 'h' } - if (unit === "h" && duration > 24) { - duration /= 24; - unit = "d"; + if (unit === 'h' && duration > 24) { + duration /= 24 + unit = 'd' } - if (unit === "d" && duration > 7) { - duration /= 7; - unit = "w"; + if (unit === 'd' && duration > 7) { + duration /= 7 + unit = 'w' } - return precisionRound(duration) + unit; + return precisionRound(duration) + unit } export function formatRows(count: number): string { if (count === 1) { - return "1 row"; + return '1 row' } - return formatCount(count) + " rows"; + return formatCount(count) + ' rows' } export function formatCount(count: number): string { - let unit = ""; + let unit = '' if (count > 1000) { - count /= 1000; - unit = "K"; + count /= 1000 + unit = 'K' } if (count > 1000) { - count /= 1000; - unit = "M"; + count /= 1000 + unit = 'M' } if (count > 1000) { - count /= 1000; - unit = "B"; + count /= 1000 + unit = 'B' } if (count > 1000) { - count /= 1000; - unit = "T"; + count /= 1000 + unit = 'T' } if (count > 1000) { - count /= 1000; - unit = "Q"; + count /= 1000 + unit = 'Q' } - return precisionRound(count) + unit; + return precisionRound(count) + unit } export function formatDataSizeBytes(size: number): string { - return formatDataSizeMinUnit(size, ""); + return formatDataSizeMinUnit(size, '') } export function formatDataSize(size: number): string { - return formatDataSizeMinUnit(size, "B"); + return formatDataSizeMinUnit(size, 'B') } function formatDataSizeMinUnit(size: number, minUnit: string): string { - let unit = minUnit; + let unit = minUnit if (size === 0) { - return "0" + unit; + return '0' + unit } if (size >= 1024) { - size /= 1024; - unit = "K" + minUnit; + size /= 1024 + unit = 'K' + minUnit } if (size >= 1024) { - size /= 1024; - unit = "M" + minUnit; + size /= 1024 + unit = 'M' + minUnit } if (size >= 1024) { - size /= 1024; - unit = "G" + minUnit; + size /= 1024 + unit = 'G' + minUnit } if (size >= 1024) { - size /= 1024; - unit = "T" + minUnit; + size /= 1024 + unit = 'T' + minUnit } if (size >= 1024) { - size /= 1024; - unit = "P" + minUnit; + size /= 1024 + unit = 'P' + minUnit } - return precisionRound(size) + unit; + return precisionRound(size) + unit } export function parseDataSize(value: string): ?number { - const DATA_SIZE_PATTERN = /^\s*(\d+(?:\.\d+)?)\s*([a-zA-Z]+)\s*$/; - const match = DATA_SIZE_PATTERN.exec(value); + const DATA_SIZE_PATTERN = /^\s*(\d+(?:\.\d+)?)\s*([a-zA-Z]+)\s*$/ + const match = DATA_SIZE_PATTERN.exec(value) if (match === null) { - return null; + return null } - const number = parseFloat(match[1]); + const number = parseFloat(match[1]) switch (match[2]) { - case "B": - return number; - case "kB": - return number * Math.pow(2, 10); - case "MB": - return number * Math.pow(2, 20); - case "GB": - return number * Math.pow(2, 30); - case "TB": - return number * Math.pow(2, 40); - case "PB": - return number * Math.pow(2, 50); + case 'B': + return number + case 'kB': + return number * Math.pow(2, 10) + case 'MB': + return number * Math.pow(2, 20) + case 'GB': + return number * Math.pow(2, 30) + case 'TB': + return number * Math.pow(2, 40) + case 'PB': + return number * Math.pow(2, 50) default: - return null; + return null } } export function parseAndFormatDataSize(value: string): string { - const parsed = parseDataSize(value); + const parsed = parseDataSize(value) if (parsed == null) { - return ""; + return '' } - return formatDataSize(parsed); + return formatDataSize(parsed) } export function parseDuration(value: string): ?number { - const DURATION_PATTERN = /^\s*(\d+(?:\.\d+)?)\s*([a-zA-Z]+)\s*$/; + const DURATION_PATTERN = /^\s*(\d+(?:\.\d+)?)\s*([a-zA-Z]+)\s*$/ - const match = DURATION_PATTERN.exec(value); + const match = DURATION_PATTERN.exec(value) if (match === null) { - return null; + return null } - const number = parseFloat(match[1]); + const number = parseFloat(match[1]) switch (match[2]) { - case "ns": - return number / 1000000.0; - case "us": - return number / 1000.0; - case "ms": - return number; - case "s": - return number * 1000; - case "m": - return number * 1000 * 60; - case "h": - return number * 1000 * 60 * 60; - case "d": - return number * 1000 * 60 * 60 * 24; + case 'ns': + return number / 1000000.0 + case 'us': + return number / 1000.0 + case 'ms': + return number + case 's': + return number * 1000 + case 'm': + return number * 1000 * 60 + case 'h': + return number * 1000 * 60 * 60 + case 'd': + return number * 1000 * 60 * 60 * 24 default: - return null; + return null } } export function formatShortTime(date: Date): string { - const hours = (date.getHours() % 12) || 12; - const minutes = (date.getMinutes() < 10 ? "0" : "") + date.getMinutes(); - return hours + ":" + minutes + (date.getHours() >= 12 ? "pm" : "am"); + const hours = date.getHours() % 12 || 12 + const minutes = (date.getMinutes() < 10 ? '0' : '') + date.getMinutes() + return hours + ':' + minutes + (date.getHours() >= 12 ? 'pm' : 'am') } export function formatShortDateTime(date: Date): string { - const year = date.getFullYear(); - const month = "" + (date.getMonth() + 1); - const dayOfMonth = "" + date.getDate(); - return year + "-" + (month[1] ? month : "0" + month[0]) + "-" + (dayOfMonth[1] ? dayOfMonth: "0" + dayOfMonth[0]) + " " + formatShortTime(date); + const year = date.getFullYear() + const month = '' + (date.getMonth() + 1) + const dayOfMonth = '' + date.getDate() + return ( + year + + '-' + + (month[1] ? month : '0' + month[0]) + + '-' + + (dayOfMonth[1] ? dayOfMonth : '0' + dayOfMonth[0]) + + ' ' + + formatShortTime(date) + ) } diff --git a/core/trino-web-ui/src/main/resources/webapp/src/webpack.config.js b/core/trino-web-ui/src/main/resources/webapp/src/webpack.config.js index 30d2b1d6752a..5d211df759fd 100644 --- a/core/trino-web-ui/src/main/resources/webapp/src/webpack.config.js +++ b/core/trino-web-ui/src/main/resources/webapp/src/webpack.config.js @@ -1,36 +1,36 @@ -const TerserPlugin = require("terser-webpack-plugin"); +const TerserPlugin = require('terser-webpack-plugin') module.exports = { cache: { type: 'filesystem', }, entry: { - 'index': __dirname +'/index.jsx', - 'query': __dirname +'/query.jsx', - 'plan': __dirname +'/plan.jsx', - 'embedded_plan': __dirname +'/embedded_plan.jsx', - 'references': __dirname +'/references.jsx', - 'stage': __dirname +'/stage.jsx', - 'worker': __dirname +'/worker.jsx', - 'workers': __dirname +'/workers.jsx', - 'timeline': __dirname +'/timeline.jsx', + index: __dirname + '/index.jsx', + query: __dirname + '/query.jsx', + plan: __dirname + '/plan.jsx', + embedded_plan: __dirname + '/embedded_plan.jsx', + references: __dirname + '/references.jsx', + stage: __dirname + '/stage.jsx', + worker: __dirname + '/worker.jsx', + workers: __dirname + '/workers.jsx', + timeline: __dirname + '/timeline.jsx', }, - mode: "production", + mode: 'production', module: { rules: [ { test: /\.(js|jsx)$/, exclude: /node_modules/, - use: ['babel-loader'] - } - ] + use: ['babel-loader'], + }, + ], }, resolve: { - extensions: ['*', '.js', '.jsx'] + extensions: ['*', '.js', '.jsx'], }, output: { path: __dirname + '/../dist', - filename: '[name].js' + filename: '[name].js', }, optimization: { minimize: true, @@ -45,5 +45,5 @@ module.exports = { extractComments: false, }), ], - } -}; + }, +} diff --git a/core/trino-web-ui/src/main/resources/webapp/src/yarn.lock b/core/trino-web-ui/src/main/resources/webapp/src/yarn.lock index 2d346eff8bb7..8f272a6bd19e 100644 --- a/core/trino-web-ui/src/main/resources/webapp/src/yarn.lock +++ b/core/trino-web-ui/src/main/resources/webapp/src/yarn.lock @@ -2375,6 +2375,11 @@ pkg-dir@^7.0.0: dependencies: find-up "^6.3.0" +prettier@3.3.3: + version "3.3.3" + resolved "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz" + integrity sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew== + prop-types@^15.6.2: version "15.6.2" resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz"