diff --git a/presto-benchmark-runner/src/test/java/com/facebook/presto/benchmark/prestoaction/TestPrestoExceptionClassifier.java b/presto-benchmark-runner/src/test/java/com/facebook/presto/benchmark/prestoaction/TestPrestoExceptionClassifier.java index ec61672293665..4003982cb0283 100644 --- a/presto-benchmark-runner/src/test/java/com/facebook/presto/benchmark/prestoaction/TestPrestoExceptionClassifier.java +++ b/presto-benchmark-runner/src/test/java/com/facebook/presto/benchmark/prestoaction/TestPrestoExceptionClassifier.java @@ -42,7 +42,7 @@ public class TestPrestoExceptionClassifier { - private static final QueryStats QUERY_STATS = new QueryStats("id", "", false, false, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, Optional.empty()); + private static final QueryStats QUERY_STATS = new QueryStats("id", "", false, false, false, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, Optional.empty()); private final SqlExceptionClassifier classifier = new PrestoExceptionClassifier(ImmutableSet.of()); diff --git a/presto-client/src/main/java/com/facebook/presto/client/StatementStats.java b/presto-client/src/main/java/com/facebook/presto/client/StatementStats.java index 5890fe47424ea..ffed1ff5de1cf 100644 --- a/presto-client/src/main/java/com/facebook/presto/client/StatementStats.java +++ b/presto-client/src/main/java/com/facebook/presto/client/StatementStats.java @@ -29,6 +29,7 @@ public class StatementStats { private final String state; + private final boolean waitingForPrerequisites; private final boolean queued; private final boolean scheduled; private final int nodes; @@ -38,6 +39,7 @@ public class StatementStats private final int completedSplits; private final long cpuTimeMillis; private final long wallTimeMillis; + private final long waitingForPrerequisitesTimeMillis; private final long queuedTimeMillis; private final long elapsedTimeMillis; private final long processedRows; @@ -51,6 +53,7 @@ public class StatementStats @JsonCreator public StatementStats( @JsonProperty("state") String state, + @JsonProperty("waitingForPrerequisites") boolean waitingForPrerequisites, @JsonProperty("queued") boolean queued, @JsonProperty("scheduled") boolean scheduled, @JsonProperty("nodes") int nodes, @@ -60,6 +63,7 @@ public StatementStats( @JsonProperty("completedSplits") int completedSplits, @JsonProperty("cpuTimeMillis") long cpuTimeMillis, @JsonProperty("wallTimeMillis") long wallTimeMillis, + @JsonProperty("waitingForPrerequisitesTimeMillis") long waitingForPrerequisitesTimeMillis, @JsonProperty("queuedTimeMillis") long queuedTimeMillis, @JsonProperty("elapsedTimeMillis") long elapsedTimeMillis, @JsonProperty("processedRows") long processedRows, @@ -71,6 +75,7 @@ public StatementStats( @JsonProperty("rootStage") StageStats rootStage) { this.state = requireNonNull(state, "state is null"); + this.waitingForPrerequisites = waitingForPrerequisites; this.queued = queued; this.scheduled = scheduled; this.nodes = nodes; @@ -80,6 +85,7 @@ public StatementStats( this.completedSplits = completedSplits; this.cpuTimeMillis = cpuTimeMillis; this.wallTimeMillis = wallTimeMillis; + this.waitingForPrerequisitesTimeMillis = waitingForPrerequisitesTimeMillis; this.queuedTimeMillis = queuedTimeMillis; this.elapsedTimeMillis = elapsedTimeMillis; this.processedRows = processedRows; @@ -97,6 +103,12 @@ public String getState() return state; } + @JsonProperty + public boolean isWaitingForPrerequisites() + { + return waitingForPrerequisites; + } + @JsonProperty public boolean isQueued() { @@ -151,6 +163,12 @@ public long getWallTimeMillis() return wallTimeMillis; } + @JsonProperty + public long getWaitingForPrerequisitesTimeMillis() + { + return waitingForPrerequisitesTimeMillis; + } + @JsonProperty public long getQueuedTimeMillis() { @@ -220,6 +238,7 @@ public String toString() { return toStringHelper(this) .add("state", state) + .add("waitingForPrerequisites", waitingForPrerequisites) .add("queued", queued) .add("scheduled", scheduled) .add("nodes", nodes) @@ -229,6 +248,7 @@ public String toString() .add("completedSplits", completedSplits) .add("cpuTimeMillis", cpuTimeMillis) .add("wallTimeMillis", wallTimeMillis) + .add("waitingForPrerequisitesTimeMillis", waitingForPrerequisitesTimeMillis) .add("queuedTimeMillis", queuedTimeMillis) .add("elapsedTimeMillis", elapsedTimeMillis) .add("processedRows", processedRows) @@ -249,6 +269,7 @@ public static Builder builder() public static class Builder { private String state; + private boolean waitingForPrerequisites; private boolean queued; private boolean scheduled; private int nodes; @@ -258,6 +279,7 @@ public static class Builder private int completedSplits; private long cpuTimeMillis; private long wallTimeMillis; + private long waitingForPrerequisitesTimeMillis; private long queuedTimeMillis; private long elapsedTimeMillis; private long processedRows; @@ -282,6 +304,12 @@ public Builder setNodes(int nodes) return this; } + public Builder setWaitingForPrerequisites(boolean waitingForPrerequisites) + { + this.waitingForPrerequisites = waitingForPrerequisites; + return this; + } + public Builder setQueued(boolean queued) { this.queued = queued; @@ -330,6 +358,12 @@ public Builder setWallTimeMillis(long wallTimeMillis) return this; } + public Builder setWaitingForPrerequisitesTimeMillis(long waitingForPrerequisitesTimeMillis) + { + this.waitingForPrerequisitesTimeMillis = waitingForPrerequisitesTimeMillis; + return this; + } + public Builder setQueuedTimeMillis(long queuedTimeMillis) { this.queuedTimeMillis = queuedTimeMillis; @@ -388,6 +422,7 @@ public StatementStats build() { return new StatementStats( state, + waitingForPrerequisites, queued, scheduled, nodes, @@ -397,6 +432,7 @@ public StatementStats build() completedSplits, cpuTimeMillis, wallTimeMillis, + waitingForPrerequisitesTimeMillis, queuedTimeMillis, elapsedTimeMillis, processedRows, diff --git a/presto-jdbc/src/main/java/com/facebook/presto/jdbc/QueryStats.java b/presto-jdbc/src/main/java/com/facebook/presto/jdbc/QueryStats.java index 245b0bd5ea3a4..bbc578c185750 100644 --- a/presto-jdbc/src/main/java/com/facebook/presto/jdbc/QueryStats.java +++ b/presto-jdbc/src/main/java/com/facebook/presto/jdbc/QueryStats.java @@ -25,6 +25,7 @@ public final class QueryStats { private final String queryId; private final String state; + private final boolean waitingForPrerequisites; private final boolean queued; private final boolean scheduled; private final int nodes; @@ -34,6 +35,7 @@ public final class QueryStats private final int completedSplits; private final long cpuTimeMillis; private final long wallTimeMillis; + private final long waitingForPrerequisitesTimeMillis; private final long queuedTimeMillis; private final long elapsedTimeMillis; private final long processedRows; @@ -46,6 +48,7 @@ public final class QueryStats public QueryStats( String queryId, String state, + boolean waitingForPrerequisites, boolean queued, boolean scheduled, int nodes, @@ -55,6 +58,7 @@ public QueryStats( int completedSplits, long cpuTimeMillis, long wallTimeMillis, + long waitingForPrerequisitesTimeMillis, long queuedTimeMillis, long elapsedTimeMillis, long processedRows, @@ -66,6 +70,7 @@ public QueryStats( { this.queryId = requireNonNull(queryId, "queryId is null"); this.state = requireNonNull(state, "state is null"); + this.waitingForPrerequisites = waitingForPrerequisites; this.queued = queued; this.scheduled = scheduled; this.nodes = nodes; @@ -75,6 +80,7 @@ public QueryStats( this.completedSplits = completedSplits; this.cpuTimeMillis = cpuTimeMillis; this.wallTimeMillis = wallTimeMillis; + this.waitingForPrerequisitesTimeMillis = waitingForPrerequisitesTimeMillis; this.queuedTimeMillis = queuedTimeMillis; this.elapsedTimeMillis = elapsedTimeMillis; this.processedRows = processedRows; @@ -90,6 +96,7 @@ static QueryStats create(String queryId, StatementStats stats) return new QueryStats( queryId, stats.getState(), + stats.isWaitingForPrerequisites(), stats.isQueued(), stats.isScheduled(), stats.getNodes(), @@ -99,6 +106,7 @@ static QueryStats create(String queryId, StatementStats stats) stats.getCompletedSplits(), stats.getCpuTimeMillis(), stats.getWallTimeMillis(), + stats.getWaitingForPrerequisitesTimeMillis(), stats.getQueuedTimeMillis(), stats.getElapsedTimeMillis(), stats.getProcessedRows(), @@ -119,6 +127,11 @@ public String getState() return state; } + public boolean isWaitingForPrerequisites() + { + return waitingForPrerequisites; + } + public boolean isQueued() { return queued; @@ -164,6 +177,11 @@ public long getWallTimeMillis() return wallTimeMillis; } + public long getWaitingForPrerequisitesTimeMillis() + { + return waitingForPrerequisitesTimeMillis; + } + public long getQueuedTimeMillis() { return queuedTimeMillis; diff --git a/presto-jdbc/src/test/java/com/facebook/presto/jdbc/TestProgressMonitor.java b/presto-jdbc/src/test/java/com/facebook/presto/jdbc/TestProgressMonitor.java index 962fc00d94e04..9de6e61cf781d 100644 --- a/presto-jdbc/src/test/java/com/facebook/presto/jdbc/TestProgressMonitor.java +++ b/presto-jdbc/src/test/java/com/facebook/presto/jdbc/TestProgressMonitor.java @@ -70,10 +70,11 @@ private List createResults() { List columns = ImmutableList.of(new Column("_col0", BigintType.BIGINT)); return ImmutableList.builder() - .add(newQueryResults(null, 1, null, null, "QUEUED")) - .add(newQueryResults(1, 2, columns, null, "RUNNING")) + .add(newQueryResults(null, 1, null, null, "WAITING_FOR_PREREQUISITES")) + .add(newQueryResults(null, 2, null, null, "QUEUED")) .add(newQueryResults(1, 3, columns, null, "RUNNING")) - .add(newQueryResults(0, 4, columns, ImmutableList.of(ImmutableList.of(253161)), "RUNNING")) + .add(newQueryResults(1, 4, columns, null, "RUNNING")) + .add(newQueryResults(0, 5, columns, ImmutableList.of(ImmutableList.of(253161)), "RUNNING")) .add(newQueryResults(null, null, columns, null, "FINISHED")) .build(); } @@ -89,7 +90,7 @@ private String newQueryResults(Integer partialCancelId, Integer nextUriId, List< nextUriId == null ? null : server.url(format("/v1/statement/%s/%s", queryId, nextUriId)).uri(), responseColumns, data, - new StatementStats(state, state.equals("QUEUED"), true, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, null), + new StatementStats(state, state.equals("WAITING_FOR_PREREQUISITES"), state.equals("QUEUED"), true, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, null), null, ImmutableList.of(), null, @@ -128,7 +129,7 @@ public void test() List queryStatsList = progressMonitor.finish(); assertGreaterThanOrEqual(queryStatsList.size(), 5); // duplicate stats is possible - assertEquals(queryStatsList.get(0).getState(), "QUEUED"); + assertEquals(queryStatsList.get(0).getState(), "WAITING_FOR_PREREQUISITES"); assertEquals(queryStatsList.get(queryStatsList.size() - 1).getState(), "FINISHED"); } } diff --git a/presto-main/src/main/java/com/facebook/presto/dispatcher/DefaultQueryPrerequisites.java b/presto-main/src/main/java/com/facebook/presto/dispatcher/DefaultQueryPrerequisites.java new file mode 100644 index 0000000000000..129902509ff44 --- /dev/null +++ b/presto-main/src/main/java/com/facebook/presto/dispatcher/DefaultQueryPrerequisites.java @@ -0,0 +1,34 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.facebook.presto.dispatcher; + +import com.facebook.presto.spi.QueryId; +import com.facebook.presto.spi.prerequisites.QueryPrerequisites; +import com.facebook.presto.spi.prerequisites.QueryPrerequisitesContext; + +import java.util.concurrent.CompletableFuture; + +import static java.util.concurrent.CompletableFuture.completedFuture; + +public class DefaultQueryPrerequisites + implements QueryPrerequisites +{ + private static final CompletableFuture COMPLETED_FUTURE = completedFuture(null); + + @Override + public CompletableFuture waitForPrerequisites(QueryId queryId, QueryPrerequisitesContext context) + { + return COMPLETED_FUTURE; + } +} diff --git a/presto-main/src/main/java/com/facebook/presto/dispatcher/DispatchInfo.java b/presto-main/src/main/java/com/facebook/presto/dispatcher/DispatchInfo.java index 4700765798ad8..2b08af85aa5d9 100644 --- a/presto-main/src/main/java/com/facebook/presto/dispatcher/DispatchInfo.java +++ b/presto-main/src/main/java/com/facebook/presto/dispatcher/DispatchInfo.java @@ -25,30 +25,45 @@ public class DispatchInfo private final Optional coordinatorLocation; private final Optional failureInfo; private final Duration elapsedTime; - private final Duration queuedTime; + private final Duration waitingForPrerequisitesTime; + private final Optional queuedTime; - public static DispatchInfo queued(Duration elapsedTime, Duration queuedTime) + public static DispatchInfo waitingForPrerequisites(Duration elapsedTime, Duration waitingForPrerequisitesTime) { - return new DispatchInfo(Optional.empty(), Optional.empty(), elapsedTime, queuedTime); + return new DispatchInfo(Optional.empty(), Optional.empty(), elapsedTime, waitingForPrerequisitesTime, Optional.empty()); } - public static DispatchInfo dispatched(CoordinatorLocation coordinatorLocation, Duration elapsedTime, Duration queuedTime) + public static DispatchInfo queued(Duration elapsedTime, Duration waitingForPrerequisitesTime, Duration queuedTime) + { + requireNonNull(queuedTime, "queuedTime is null"); + return new DispatchInfo(Optional.empty(), Optional.empty(), elapsedTime, waitingForPrerequisitesTime, Optional.of(queuedTime)); + } + + public static DispatchInfo dispatched(CoordinatorLocation coordinatorLocation, Duration elapsedTime, Duration waitingForPrerequisitesTime, Duration queuedTime) { requireNonNull(coordinatorLocation, "coordinatorLocation is null"); - return new DispatchInfo(Optional.of(coordinatorLocation), Optional.empty(), elapsedTime, queuedTime); + requireNonNull(queuedTime, "queuedTime is null"); + return new DispatchInfo(Optional.of(coordinatorLocation), Optional.empty(), elapsedTime, waitingForPrerequisitesTime, Optional.of(queuedTime)); } - public static DispatchInfo failed(ExecutionFailureInfo failureInfo, Duration elapsedTime, Duration queuedTime) + public static DispatchInfo failed(ExecutionFailureInfo failureInfo, Duration elapsedTime, Duration waitingForPrerequisitesTime, Duration queuedTime) { requireNonNull(failureInfo, "coordinatorLocation is null"); - return new DispatchInfo(Optional.empty(), Optional.of(failureInfo), elapsedTime, queuedTime); + requireNonNull(queuedTime, "queuedTime is null"); + return new DispatchInfo(Optional.empty(), Optional.of(failureInfo), elapsedTime, waitingForPrerequisitesTime, Optional.of(queuedTime)); } - private DispatchInfo(Optional coordinatorLocation, Optional failureInfo, Duration elapsedTime, Duration queuedTime) + private DispatchInfo( + Optional coordinatorLocation, + Optional failureInfo, + Duration elapsedTime, + Duration waitingForPrerequisitesTime, + Optional queuedTime) { this.coordinatorLocation = requireNonNull(coordinatorLocation, "coordinatorLocation is null"); this.failureInfo = requireNonNull(failureInfo, "failureInfo is null"); this.elapsedTime = requireNonNull(elapsedTime, "elapsedTime is null"); + this.waitingForPrerequisitesTime = requireNonNull(waitingForPrerequisitesTime, "waitingForPrerequisitesTime is null"); this.queuedTime = requireNonNull(queuedTime, "queuedTime is null"); } @@ -67,7 +82,12 @@ public Duration getElapsedTime() return elapsedTime; } - public Duration getQueuedTime() + public Duration getWaitingForPrerequisitesTime() + { + return waitingForPrerequisitesTime; + } + + public Optional getQueuedTime() { return queuedTime; } diff --git a/presto-main/src/main/java/com/facebook/presto/dispatcher/DispatchManager.java b/presto-main/src/main/java/com/facebook/presto/dispatcher/DispatchManager.java index 7dca7de417d61..f7d27ffe57d13 100644 --- a/presto-main/src/main/java/com/facebook/presto/dispatcher/DispatchManager.java +++ b/presto-main/src/main/java/com/facebook/presto/dispatcher/DispatchManager.java @@ -213,13 +213,14 @@ private void createQueryInternal(QueryId queryId, String slug, int retryCoun retryCount, selectionContext.getResourceGroupId(), queryType, - warningCollector); + warningCollector, + (dq) -> resourceGroupManager.submit(preparedQuery.getStatement(), dq, selectionContext, queryExecutor)); boolean queryAdded = queryCreated(dispatchQuery); if (queryAdded && !dispatchQuery.isDone()) { try { clusterStatusSender.registerQuery(dispatchQuery); - resourceGroupManager.submit(preparedQuery.getStatement(), dispatchQuery, selectionContext, queryExecutor); + dispatchQuery.startWaitingForPrerequisites(); } catch (Throwable e) { // dispatch query has already been registered, so just fail it directly diff --git a/presto-main/src/main/java/com/facebook/presto/dispatcher/DispatchQueryFactory.java b/presto-main/src/main/java/com/facebook/presto/dispatcher/DispatchQueryFactory.java index ea4fbcd911ab2..d2a420b57091d 100644 --- a/presto-main/src/main/java/com/facebook/presto/dispatcher/DispatchQueryFactory.java +++ b/presto-main/src/main/java/com/facebook/presto/dispatcher/DispatchQueryFactory.java @@ -20,6 +20,7 @@ import com.facebook.presto.spi.resourceGroups.ResourceGroupId; import java.util.Optional; +import java.util.function.Consumer; public interface DispatchQueryFactory { @@ -31,5 +32,6 @@ DispatchQuery createDispatchQuery( int retryCount, ResourceGroupId resourceGroup, Optional queryType, - WarningCollector warningCollector); + WarningCollector warningCollector, + Consumer queryQueuer); } diff --git a/presto-main/src/main/java/com/facebook/presto/dispatcher/FailedDispatchQuery.java b/presto-main/src/main/java/com/facebook/presto/dispatcher/FailedDispatchQuery.java index cf91b33a28183..25ad331ac0da9 100644 --- a/presto-main/src/main/java/com/facebook/presto/dispatcher/FailedDispatchQuery.java +++ b/presto-main/src/main/java/com/facebook/presto/dispatcher/FailedDispatchQuery.java @@ -67,6 +67,7 @@ public FailedDispatchQuery( this.dispatchInfo = DispatchInfo.failed( failure, basicQueryInfo.getQueryStats().getElapsedTime(), + basicQueryInfo.getQueryStats().getWaitingForPrerequisitesTime(), basicQueryInfo.getQueryStats().getQueuedTime()); } @@ -100,6 +101,9 @@ public void addStateChangeListener(StateChangeListener stateChangeLi executor.execute(() -> stateChangeListener.stateChanged(FAILED)); } + @Override + public void startWaitingForPrerequisites() {} + @Override public void startWaitingForResources() {} diff --git a/presto-main/src/main/java/com/facebook/presto/dispatcher/LocalDispatchQuery.java b/presto-main/src/main/java/com/facebook/presto/dispatcher/LocalDispatchQuery.java index eadfe04a112d5..b6a1d13cd02df 100644 --- a/presto-main/src/main/java/com/facebook/presto/dispatcher/LocalDispatchQuery.java +++ b/presto-main/src/main/java/com/facebook/presto/dispatcher/LocalDispatchQuery.java @@ -26,6 +26,8 @@ import com.facebook.presto.spi.ErrorCode; import com.facebook.presto.spi.PrestoException; import com.facebook.presto.spi.QueryId; +import com.facebook.presto.spi.prerequisites.QueryPrerequisites; +import com.facebook.presto.spi.prerequisites.QueryPrerequisitesContext; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import io.airlift.units.DataSize; @@ -33,6 +35,7 @@ import org.joda.time.DateTime; import java.util.Optional; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import java.util.function.Consumer; @@ -40,6 +43,7 @@ import static com.facebook.airlift.concurrent.MoreFutures.addSuccessCallback; import static com.facebook.airlift.concurrent.MoreFutures.tryGetFutureValue; import static com.facebook.presto.execution.QueryState.FAILED; +import static com.facebook.presto.execution.QueryState.QUEUED; import static com.facebook.presto.spi.StandardErrorCode.GENERIC_INTERNAL_ERROR; import static com.facebook.presto.spi.StandardErrorCode.USER_CANCELED; import static com.facebook.presto.util.Failures.toFailure; @@ -60,27 +64,34 @@ public class LocalDispatchQuery private final Executor queryExecutor; + private final Consumer queryQueuer; private final Consumer querySubmitter; private final SettableFuture submitted = SettableFuture.create(); private final boolean retry; + private final QueryPrerequisites queryPrerequisites; + public LocalDispatchQuery( QueryStateMachine stateMachine, QueryMonitor queryMonitor, ListenableFuture queryExecutionFuture, ClusterSizeMonitor clusterSizeMonitor, Executor queryExecutor, + Consumer queryQueuer, Consumer querySubmitter, - boolean retry) + boolean retry, + QueryPrerequisites queryPrerequisites) { this.stateMachine = requireNonNull(stateMachine, "stateMachine is null"); this.queryMonitor = requireNonNull(queryMonitor, "queryMonitor is null"); this.queryExecutionFuture = requireNonNull(queryExecutionFuture, "queryExecutionFuture is null"); this.clusterSizeMonitor = requireNonNull(clusterSizeMonitor, "clusterSizeMonitor is null"); this.queryExecutor = requireNonNull(queryExecutor, "queryExecutor is null"); + this.queryQueuer = requireNonNull(queryQueuer, "queryQueuer is null"); this.querySubmitter = requireNonNull(querySubmitter, "querySubmitter is null"); this.retry = retry; + this.queryPrerequisites = requireNonNull(queryPrerequisites, "queryPrerequisites is null"); addExceptionCallback(queryExecutionFuture, throwable -> { if (stateMachine.transitionToFailed(throwable)) { @@ -94,6 +105,62 @@ public LocalDispatchQuery( }); } + @Override + public void startWaitingForPrerequisites() + { + // It's possible that queryExecution fails before we start for prerequisites, in that case, don't even + // start waiting for prerequisites + if (isDone()) { + return; + } + + try { + Session session = stateMachine.getSession(); + CompletableFuture prerequisitesFuture = queryPrerequisites.waitForPrerequisites( + stateMachine.getQueryId(), + new QueryPrerequisitesContext( + session.getCatalog(), + session.getSchema(), + stateMachine.getBasicQueryInfo(Optional.empty()).getQuery(), + session.getSystemProperties(), + session.getConnectorProperties())); + + addStateChangeListener(state -> { + if (state.isDone()) { + queryPrerequisites.queryFinished(stateMachine.getQueryId()); + if (!prerequisitesFuture.isDone()) { + prerequisitesFuture.cancel(true); + } + } + }); + + prerequisitesFuture.whenCompleteAsync((result, throwable) -> { + if (throwable != null) { + fail(throwable); + return; + } + + queueQuery(); + }, queryExecutor); + } + catch (Throwable t) { + fail(t); + throw t; + } + } + + private void queueQuery() + { + if (stateMachine.transitionToQueued()) { + try { + queryQueuer.accept(this); + } + catch (Throwable t) { + fail(t); + } + } + } + @Override public void startWaitingForResources() { @@ -158,12 +225,15 @@ public DispatchInfo getDispatchInfo() if (queryInfo.getState() == FAILED) { ExecutionFailureInfo failureInfo = stateMachine.getFailureInfo() .orElseGet(() -> toFailure(new PrestoException(GENERIC_INTERNAL_ERROR, "Query failed for an unknown reason"))); - return DispatchInfo.failed(failureInfo, queryInfo.getQueryStats().getElapsedTime(), queryInfo.getQueryStats().getQueuedTime()); + return DispatchInfo.failed(failureInfo, queryInfo.getQueryStats().getElapsedTime(), queryInfo.getQueryStats().getWaitingForPrerequisitesTime(), queryInfo.getQueryStats().getQueuedTime()); } if (dispatched) { - return DispatchInfo.dispatched(new LocalCoordinatorLocation(), queryInfo.getQueryStats().getElapsedTime(), queryInfo.getQueryStats().getQueuedTime()); + return DispatchInfo.dispatched(new LocalCoordinatorLocation(), queryInfo.getQueryStats().getElapsedTime(), queryInfo.getQueryStats().getWaitingForPrerequisitesTime(), queryInfo.getQueryStats().getQueuedTime()); + } + if (queryInfo.getState() == QUEUED) { + return DispatchInfo.queued(queryInfo.getQueryStats().getElapsedTime(), queryInfo.getQueryStats().getWaitingForPrerequisitesTime(), queryInfo.getQueryStats().getQueuedTime()); } - return DispatchInfo.queued(queryInfo.getQueryStats().getElapsedTime(), queryInfo.getQueryStats().getQueuedTime()); + return DispatchInfo.waitingForPrerequisites(queryInfo.getQueryStats().getElapsedTime(), queryInfo.getQueryStats().getWaitingForPrerequisitesTime()); } @Override diff --git a/presto-main/src/main/java/com/facebook/presto/dispatcher/LocalDispatchQueryFactory.java b/presto-main/src/main/java/com/facebook/presto/dispatcher/LocalDispatchQueryFactory.java index c72172a4c9fe3..5d6ab3c36fe93 100644 --- a/presto-main/src/main/java/com/facebook/presto/dispatcher/LocalDispatchQueryFactory.java +++ b/presto-main/src/main/java/com/facebook/presto/dispatcher/LocalDispatchQueryFactory.java @@ -37,6 +37,7 @@ import java.util.Map; import java.util.Optional; +import java.util.function.Consumer; import static com.facebook.presto.spi.StandardErrorCode.NOT_SUPPORTED; import static com.facebook.presto.util.StatementUtils.isTransactionControlStatement; @@ -57,6 +58,8 @@ public class LocalDispatchQueryFactory private final Map, QueryExecutionFactory> executionFactories; private final ListeningExecutorService executor; + private final QueryPrerequisitesManager queryPrerequisitesManager; + @Inject public LocalDispatchQueryFactory( QueryManager queryManager, @@ -67,7 +70,8 @@ public LocalDispatchQueryFactory( LocationFactory locationFactory, Map, QueryExecutionFactory> executionFactories, ClusterSizeMonitor clusterSizeMonitor, - DispatchExecutor dispatchExecutor) + DispatchExecutor dispatchExecutor, + QueryPrerequisitesManager queryPrerequisitesManager) { this.queryManager = requireNonNull(queryManager, "queryManager is null"); this.transactionManager = requireNonNull(transactionManager, "transactionManager is null"); @@ -80,6 +84,7 @@ public LocalDispatchQueryFactory( this.clusterSizeMonitor = requireNonNull(clusterSizeMonitor, "clusterSizeMonitor is null"); this.executor = requireNonNull(dispatchExecutor, "executorService is null").getExecutor(); + this.queryPrerequisitesManager = requireNonNull(queryPrerequisitesManager, "queryPrerequisitesManager is null"); } @Override @@ -91,7 +96,8 @@ public DispatchQuery createDispatchQuery( int retryCount, ResourceGroupId resourceGroup, Optional queryType, - WarningCollector warningCollector) + WarningCollector warningCollector, + Consumer queryQueuer) { QueryStateMachine stateMachine = QueryStateMachine.begin( query, @@ -123,7 +129,9 @@ public DispatchQuery createDispatchQuery( queryExecutionFuture, clusterSizeMonitor, executor, + queryQueuer, queryManager::createQuery, - retryCount > 0); + retryCount > 0, + queryPrerequisitesManager); } } diff --git a/presto-main/src/main/java/com/facebook/presto/dispatcher/QueryPrerequisitesManager.java b/presto-main/src/main/java/com/facebook/presto/dispatcher/QueryPrerequisitesManager.java new file mode 100644 index 0000000000000..b399005318464 --- /dev/null +++ b/presto-main/src/main/java/com/facebook/presto/dispatcher/QueryPrerequisitesManager.java @@ -0,0 +1,91 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.facebook.presto.dispatcher; + +import com.facebook.airlift.log.Logger; +import com.facebook.presto.spi.QueryId; +import com.facebook.presto.spi.prerequisites.QueryPrerequisites; +import com.facebook.presto.spi.prerequisites.QueryPrerequisitesContext; +import com.facebook.presto.spi.prerequisites.QueryPrerequisitesFactory; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicReference; + +import static com.facebook.presto.util.PropertiesUtil.loadProperties; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.base.Strings.isNullOrEmpty; +import static java.lang.String.format; +import static java.util.Objects.requireNonNull; + +public class QueryPrerequisitesManager + implements QueryPrerequisites +{ + private static final Logger log = Logger.get(QueryPrerequisitesManager.class); + + private static final File QUERY_PREREQUISITES_CONFIG = new File("etc/query-prerequisites.properties"); + private static final String QUERY_PREREQUISITES_PROPERTY_NAME = "query-prerequisites.factory"; + private final Map queryPrerequisitesFactories = new ConcurrentHashMap<>(); + private final QueryPrerequisites defaultQueryPrerequisites = new DefaultQueryPrerequisites(); + private final AtomicReference queryPrerequisites = new AtomicReference<>(defaultQueryPrerequisites); + + public void addQueryPrerequisitesFactory(QueryPrerequisitesFactory queryPrerequisitesFactory) + { + requireNonNull(queryPrerequisitesFactory, "queryPrerequisitesFactory is null"); + + if (queryPrerequisitesFactories.putIfAbsent(queryPrerequisitesFactory.getName(), queryPrerequisitesFactory) != null) { + throw new IllegalArgumentException(format("Query Prerequisites '%s' is already registered", queryPrerequisitesFactory.getName())); + } + } + + public void loadQueryPrerequisites() + throws Exception + { + if (QUERY_PREREQUISITES_CONFIG.exists()) { + Map properties = new HashMap<>(loadProperties(QUERY_PREREQUISITES_CONFIG)); + + String factoryName = properties.remove(QUERY_PREREQUISITES_PROPERTY_NAME); + checkArgument(!isNullOrEmpty(factoryName), + "Query Prerequisites configuration %s does not contain %s", QUERY_PREREQUISITES_CONFIG.getAbsoluteFile(), QUERY_PREREQUISITES_PROPERTY_NAME); + + log.info("-- Loading query prerequisites factory --"); + + QueryPrerequisitesFactory queryPrerequisitesFactory = queryPrerequisitesFactories.get(factoryName); + checkState(queryPrerequisitesFactory != null, "Query prerequisites factory %s is not registered", factoryName); + + QueryPrerequisites queryPrerequisites = queryPrerequisitesFactory.create(properties); + checkState(this.queryPrerequisites.compareAndSet(defaultQueryPrerequisites, queryPrerequisites), "Query prerequisites has already been set"); + + log.info("-- Loaded query prerequisites %s --", factoryName); + } + } + + @Override + public CompletableFuture waitForPrerequisites(QueryId queryId, QueryPrerequisitesContext context) + { + checkState(queryPrerequisites.get() != null, "Query prerequisites not initiated"); + return queryPrerequisites.get().waitForPrerequisites(queryId, context); + } + + @Override + public void queryFinished(QueryId queryId) + { + checkState(queryPrerequisites.get() != null, "Query prerequisites not initiated"); + queryPrerequisites.get().queryFinished(queryId); + } +} diff --git a/presto-main/src/main/java/com/facebook/presto/dispatcher/QueryPrerequisitesManagerModule.java b/presto-main/src/main/java/com/facebook/presto/dispatcher/QueryPrerequisitesManagerModule.java new file mode 100644 index 0000000000000..8c515cb1edf07 --- /dev/null +++ b/presto-main/src/main/java/com/facebook/presto/dispatcher/QueryPrerequisitesManagerModule.java @@ -0,0 +1,28 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.facebook.presto.dispatcher; + +import com.google.inject.Binder; +import com.google.inject.Module; +import com.google.inject.Scopes; + +public class QueryPrerequisitesManagerModule + implements Module +{ + @Override + public void configure(Binder binder) + { + binder.bind(QueryPrerequisitesManager.class).in(Scopes.SINGLETON); + } +} diff --git a/presto-main/src/main/java/com/facebook/presto/event/QueryMonitor.java b/presto-main/src/main/java/com/facebook/presto/event/QueryMonitor.java index 6cf7cc6b6d5ff..59caaafb66540 100644 --- a/presto-main/src/main/java/com/facebook/presto/event/QueryMonitor.java +++ b/presto-main/src/main/java/com/facebook/presto/event/QueryMonitor.java @@ -154,6 +154,7 @@ public void queryImmediateFailureEvent(BasicQueryInfo queryInfo, ExecutionFailur ofMillis(0), ofMillis(0), ofMillis(queryInfo.getQueryStats().getQueuedTime().toMillis()), + ofMillis(queryInfo.getQueryStats().getWaitingForPrerequisitesTime().toMillis()), Optional.empty(), 0, 0, @@ -284,6 +285,7 @@ private QueryStatistics createQueryStatistics(QueryInfo queryInfo) ofMillis(queryStats.getRetriedCpuTime().toMillis()), ofMillis(queryStats.getTotalScheduledTime().toMillis()), ofMillis(queryStats.getQueuedTime().toMillis()), + ofMillis(queryStats.getWaitingForPrerequisitesTime().toMillis()), Optional.of(ofMillis(queryStats.getAnalysisTime().toMillis())), queryStats.getPeakRunningTasks(), queryStats.getPeakUserMemoryReservation().toBytes(), diff --git a/presto-main/src/main/java/com/facebook/presto/execution/ManagedQueryExecution.java b/presto-main/src/main/java/com/facebook/presto/execution/ManagedQueryExecution.java index d9d63b2920e61..d9c5ca959aacc 100644 --- a/presto-main/src/main/java/com/facebook/presto/execution/ManagedQueryExecution.java +++ b/presto-main/src/main/java/com/facebook/presto/execution/ManagedQueryExecution.java @@ -24,6 +24,8 @@ public interface ManagedQueryExecution { + void startWaitingForPrerequisites(); + void startWaitingForResources(); void fail(Throwable cause); diff --git a/presto-main/src/main/java/com/facebook/presto/execution/QueryState.java b/presto-main/src/main/java/com/facebook/presto/execution/QueryState.java index 8d0040da32776..08ee3b915b776 100644 --- a/presto-main/src/main/java/com/facebook/presto/execution/QueryState.java +++ b/presto-main/src/main/java/com/facebook/presto/execution/QueryState.java @@ -25,41 +25,45 @@ public enum QueryState { /** - * Query has been accepted and is awaiting execution. + * Query has been accepted and is waiting for its prerequisites to be complete. */ - QUEUED(false, 1), + WAITING_FOR_PREREQUISITES(false, 1), + /** + * Query is awaiting execution. + */ + QUEUED(false, 2), /** * Query is waiting for the required resources (beta). */ - WAITING_FOR_RESOURCES(false, 2), + WAITING_FOR_RESOURCES(false, 3), /** * Query is being dispatched to a coordinator. */ - DISPATCHING(false, 3), + DISPATCHING(false, 4), /** * Query is being planned. */ - PLANNING(false, 4), + PLANNING(false, 5), /** * Query execution is being started. */ - STARTING(false, 5), + STARTING(false, 6), /** * Query has at least one running task. */ - RUNNING(false, 6), + RUNNING(false, 7), /** * Query is finishing (e.g. commit for autocommit queries) */ - FINISHING(false, 7), + FINISHING(false, 8), /** * Query has finished executing and all output has been consumed. */ - FINISHED(true, 8), + FINISHED(true, 9), /** * Query execution failed. */ - FAILED(true, 9); + FAILED(true, 10); public static final Set TERMINAL_QUERY_STATES = Stream.of(QueryState.values()).filter(QueryState::isDone).collect(toImmutableSet()); diff --git a/presto-main/src/main/java/com/facebook/presto/execution/QueryStateMachine.java b/presto-main/src/main/java/com/facebook/presto/execution/QueryStateMachine.java index af24c04fe3cac..0675f42b47dfb 100644 --- a/presto-main/src/main/java/com/facebook/presto/execution/QueryStateMachine.java +++ b/presto-main/src/main/java/com/facebook/presto/execution/QueryStateMachine.java @@ -74,6 +74,7 @@ import static com.facebook.presto.execution.QueryState.RUNNING; import static com.facebook.presto.execution.QueryState.STARTING; import static com.facebook.presto.execution.QueryState.TERMINAL_QUERY_STATES; +import static com.facebook.presto.execution.QueryState.WAITING_FOR_PREREQUISITES; import static com.facebook.presto.execution.QueryState.WAITING_FOR_RESOURCES; import static com.facebook.presto.execution.StageInfo.getAllStages; import static com.facebook.presto.memory.LocalMemoryManager.GENERAL_POOL; @@ -173,7 +174,7 @@ private QueryStateMachine( this.queryStateTimer = new QueryStateTimer(ticker); this.metadata = requireNonNull(metadata, "metadata is null"); - this.queryState = new StateMachine<>("query " + query, executor, QUEUED, TERMINAL_QUERY_STATES); + this.queryState = new StateMachine<>("query " + query, executor, WAITING_FOR_PREREQUISITES, TERMINAL_QUERY_STATES); this.finalQueryInfo = new StateMachine<>("finalQueryInfo-" + queryId, executor, Optional.empty()); this.outputManager = new QueryOutputManager(executor); this.warningCollector = requireNonNull(warningCollector, "warningCollector is null"); @@ -343,6 +344,7 @@ public BasicQueryInfo getBasicQueryInfo(Optional rootS BasicQueryStats queryStats = new BasicQueryStats( queryStateTimer.getCreateTime(), getEndTime().orElse(null), + queryStateTimer.getWaitingForPrerequisitesTime(), queryStateTimer.getQueuedTime(), queryStateTimer.getElapsedTime(), queryStateTimer.getExecutionTime(), @@ -633,6 +635,12 @@ public boolean isDone() return queryState.get().isDone(); } + public boolean transitionToQueued() + { + queryStateTimer.beginQueued(); + return queryState.setIf(QUEUED, currentState -> currentState.ordinal() < QUEUED.ordinal()); + } + public boolean transitionToWaitingForResources() { queryStateTimer.beginWaitingForResources(); @@ -943,6 +951,7 @@ private static QueryStats pruneQueryStats(QueryStats queryStats) queryStats.getLastHeartbeat(), queryStats.getEndTime(), queryStats.getElapsedTime(), + queryStats.getWaitingForPrerequisitesTime(), queryStats.getQueuedTime(), queryStats.getResourceWaitingTime(), queryStats.getDispatchingTime(), diff --git a/presto-main/src/main/java/com/facebook/presto/execution/QueryStateTimer.java b/presto-main/src/main/java/com/facebook/presto/execution/QueryStateTimer.java index 3ef00a04aea63..5b16138484f98 100644 --- a/presto-main/src/main/java/com/facebook/presto/execution/QueryStateTimer.java +++ b/presto-main/src/main/java/com/facebook/presto/execution/QueryStateTimer.java @@ -33,12 +33,14 @@ public class QueryStateTimer private final DateTime createTime = DateTime.now(); private final long createNanos; + private final AtomicReference beginQueuedNanos = new AtomicReference<>(); private final AtomicReference beginResourceWaitingNanos = new AtomicReference<>(); private final AtomicReference beginDispatchingNanos = new AtomicReference<>(); private final AtomicReference beginPlanningNanos = new AtomicReference<>(); private final AtomicReference beginFinishingNanos = new AtomicReference<>(); private final AtomicReference endNanos = new AtomicReference<>(); + private final AtomicReference waitingForPrerequisitesTime = new AtomicReference<>(); private final AtomicReference queuedTime = new AtomicReference<>(); private final AtomicReference resourceWaitingTime = new AtomicReference<>(); private final AtomicReference dispatchingTime = new AtomicReference<>(); @@ -62,6 +64,17 @@ public QueryStateTimer(Ticker ticker) // State transitions // + public void beginQueued() + { + beginQueued(tickerNanos()); + } + + private void beginQueued(long now) + { + waitingForPrerequisitesTime.compareAndSet(null, nanosSince(createNanos, now)); + beginQueuedNanos.compareAndSet(null, now); + } + public void beginWaitingForResources() { beginWaitingForResources(tickerNanos()); @@ -69,7 +82,8 @@ public void beginWaitingForResources() private void beginWaitingForResources(long now) { - queuedTime.compareAndSet(null, nanosSince(createNanos, now)); + beginQueued(now); + queuedTime.compareAndSet(null, nanosSince(beginQueuedNanos, now)); beginResourceWaitingNanos.compareAndSet(null, now); } @@ -183,17 +197,22 @@ public Duration getElapsedTime() return nanosSince(createNanos, tickerNanos()); } - public Duration getQueuedTime() + public Duration getWaitingForPrerequisitesTime() { - Duration queuedTime = this.queuedTime.get(); - if (queuedTime != null) { - return queuedTime; + Duration waitingForPrerequisitesTime = this.waitingForPrerequisitesTime.get(); + if (waitingForPrerequisitesTime != null) { + return waitingForPrerequisitesTime; } - // if queue time is not set, the query is still queued + // if prerequisite wait time is not set, the query is still waiting for prerequisites to finish return getElapsedTime(); } + public Duration getQueuedTime() + { + return getDuration(queuedTime, beginQueuedNanos); + } + public Duration getResourceWaitingTime() { return getDuration(resourceWaitingTime, beginResourceWaitingNanos); diff --git a/presto-main/src/main/java/com/facebook/presto/execution/QueryStats.java b/presto-main/src/main/java/com/facebook/presto/execution/QueryStats.java index e2774c74c0ec6..846e6a5196f39 100644 --- a/presto-main/src/main/java/com/facebook/presto/execution/QueryStats.java +++ b/presto-main/src/main/java/com/facebook/presto/execution/QueryStats.java @@ -52,6 +52,7 @@ public class QueryStats private final DateTime endTime; private final Duration elapsedTime; + private final Duration waitingForPrerequisitesTime; private final Duration queuedTime; private final Duration resourceWaitingTime; private final Duration dispatchingTime; @@ -117,6 +118,7 @@ public QueryStats( @JsonProperty("endTime") DateTime endTime, @JsonProperty("elapsedTime") Duration elapsedTime, + @JsonProperty("waitingForPrerequisitesTime") Duration waitingForPrerequisitesTime, @JsonProperty("queuedTime") Duration queuedTime, @JsonProperty("resourceWaitingTime") Duration resourceWaitingTime, @JsonProperty("dispatchingTime") Duration dispatchingTime, @@ -180,6 +182,7 @@ public QueryStats( this.endTime = endTime; this.elapsedTime = requireNonNull(elapsedTime, "elapsedTime is null"); + this.waitingForPrerequisitesTime = requireNonNull(waitingForPrerequisitesTime, "waitingForPrerequisitesTime is null"); this.queuedTime = requireNonNull(queuedTime, "queuedTime is null"); this.resourceWaitingTime = requireNonNull(resourceWaitingTime, "resourceWaitingTime is null"); this.dispatchingTime = requireNonNull(dispatchingTime, "dispatchingTime is null"); @@ -373,6 +376,7 @@ public static QueryStats create( queryStateTimer.getEndTime().orElse(null), queryStateTimer.getElapsedTime(), + queryStateTimer.getWaitingForPrerequisitesTime(), queryStateTimer.getQueuedTime(), queryStateTimer.getResourceWaitingTime(), queryStateTimer.getDispatchingTime(), @@ -466,6 +470,7 @@ public static QueryStats immediateFailureQueryStats() new Duration(0, MILLISECONDS), new Duration(0, MILLISECONDS), new Duration(0, MILLISECONDS), + new Duration(0, MILLISECONDS), 0, 0, 0, @@ -536,6 +541,12 @@ public Duration getElapsedTime() return elapsedTime; } + @JsonProperty + public Duration getWaitingForPrerequisitesTime() + { + return waitingForPrerequisitesTime; + } + @JsonProperty public Duration getResourceWaitingTime() { diff --git a/presto-main/src/main/java/com/facebook/presto/server/BasicQueryStats.java b/presto-main/src/main/java/com/facebook/presto/server/BasicQueryStats.java index b723b7f91f6f0..7880fefda523f 100644 --- a/presto-main/src/main/java/com/facebook/presto/server/BasicQueryStats.java +++ b/presto-main/src/main/java/com/facebook/presto/server/BasicQueryStats.java @@ -46,6 +46,7 @@ public class BasicQueryStats private final DateTime createTime; private final DateTime endTime; + private final Duration waitingForPrerequisitesTime; private final Duration queuedTime; private final Duration elapsedTime; private final Duration executionTime; @@ -82,6 +83,7 @@ public class BasicQueryStats public BasicQueryStats( @JsonProperty("createTime") DateTime createTime, @JsonProperty("endTime") DateTime endTime, + @JsonProperty("waitingForPrerequisitesTime") Duration waitingForPrerequisitesTime, @JsonProperty("queuedTime") Duration queuedTime, @JsonProperty("elapsedTime") Duration elapsedTime, @JsonProperty("executionTime") Duration executionTime, @@ -109,6 +111,7 @@ public BasicQueryStats( this.createTime = createTime; this.endTime = endTime; + this.waitingForPrerequisitesTime = requireNonNull(waitingForPrerequisitesTime, "waitingForPrerequisitesTimex is null"); this.queuedTime = requireNonNull(queuedTime, "queuedTime is null"); this.elapsedTime = requireNonNull(elapsedTime, "elapsedTime is null"); this.executionTime = requireNonNull(executionTime, "executionTime is null"); @@ -149,6 +152,7 @@ public BasicQueryStats(QueryStats queryStats) { this(queryStats.getCreateTime(), queryStats.getEndTime(), + queryStats.getWaitingForPrerequisitesTime(), queryStats.getQueuedTime(), queryStats.getElapsedTime(), queryStats.getExecutionTime(), @@ -183,6 +187,7 @@ public static BasicQueryStats immediateFailureQueryStats() new Duration(0, MILLISECONDS), new Duration(0, MILLISECONDS), new Duration(0, MILLISECONDS), + new Duration(0, MILLISECONDS), 0, 0, 0, @@ -378,4 +383,11 @@ public OptionalDouble getProgressPercentage() { return progressPercentage; } + + @ThriftField(26) + @JsonProperty + public Duration getWaitingForPrerequisitesTime() + { + return waitingForPrerequisitesTime; + } } diff --git a/presto-main/src/main/java/com/facebook/presto/server/PluginManager.java b/presto-main/src/main/java/com/facebook/presto/server/PluginManager.java index 3bcf00d19159b..65693c59ae8f6 100644 --- a/presto-main/src/main/java/com/facebook/presto/server/PluginManager.java +++ b/presto-main/src/main/java/com/facebook/presto/server/PluginManager.java @@ -20,6 +20,7 @@ import com.facebook.presto.common.type.ParametricType; import com.facebook.presto.common.type.Type; import com.facebook.presto.connector.ConnectorManager; +import com.facebook.presto.dispatcher.QueryPrerequisitesManager; import com.facebook.presto.eventlistener.EventListenerManager; import com.facebook.presto.execution.resourceGroups.ResourceGroupManager; import com.facebook.presto.metadata.Metadata; @@ -30,6 +31,7 @@ import com.facebook.presto.spi.connector.ConnectorFactory; import com.facebook.presto.spi.eventlistener.EventListenerFactory; import com.facebook.presto.spi.function.FunctionNamespaceManagerFactory; +import com.facebook.presto.spi.prerequisites.QueryPrerequisitesFactory; import com.facebook.presto.spi.resourceGroups.ResourceGroupConfigurationManagerFactory; import com.facebook.presto.spi.security.PasswordAuthenticatorFactory; import com.facebook.presto.spi.security.SystemAccessControlFactory; @@ -99,6 +101,7 @@ public class PluginManager private final BlockEncodingManager blockEncodingManager; private final TempStorageManager tempStorageManager; private final SessionPropertyDefaults sessionPropertyDefaults; + private final QueryPrerequisitesManager queryPrerequisitesManager; private final ArtifactResolver resolver; private final File installedPluginsDir; private final List plugins; @@ -118,6 +121,7 @@ public PluginManager( EventListenerManager eventListenerManager, BlockEncodingManager blockEncodingManager, TempStorageManager tempStorageManager, + QueryPrerequisitesManager queryPrerequisitesManager, SessionPropertyDefaults sessionPropertyDefaults) { requireNonNull(nodeInfo, "nodeInfo is null"); @@ -140,6 +144,7 @@ public PluginManager( this.eventListenerManager = requireNonNull(eventListenerManager, "eventListenerManager is null"); this.blockEncodingManager = requireNonNull(blockEncodingManager, "blockEncodingManager is null"); this.tempStorageManager = requireNonNull(tempStorageManager, "tempStorageManager is null"); + this.queryPrerequisitesManager = requireNonNull(queryPrerequisitesManager, "queryPrerequisitesManager is null"); this.sessionPropertyDefaults = requireNonNull(sessionPropertyDefaults, "sessionPropertyDefaults is null"); this.disabledConnectors = requireNonNull(config.getDisabledConnectors(), "disabledConnectors is null"); } @@ -257,6 +262,11 @@ public void installPlugin(Plugin plugin) log.info("Registering temp storage %s", tempStorageFactory.getName()); tempStorageManager.addTempStorageFactory(tempStorageFactory); } + + for (QueryPrerequisitesFactory queryPrerequisitesFactory : plugin.getQueryPrerequisitesFactories()) { + log.info("Registering query prerequisite factory %s", queryPrerequisitesFactory.getName()); + queryPrerequisitesManager.addQueryPrerequisitesFactory(queryPrerequisitesFactory); + } } private URLClassLoader buildClassLoader(String plugin) diff --git a/presto-main/src/main/java/com/facebook/presto/server/PrestoServer.java b/presto-main/src/main/java/com/facebook/presto/server/PrestoServer.java index 53bffda137b2e..d3a09089d55e9 100644 --- a/presto-main/src/main/java/com/facebook/presto/server/PrestoServer.java +++ b/presto-main/src/main/java/com/facebook/presto/server/PrestoServer.java @@ -31,6 +31,8 @@ import com.facebook.airlift.tracetoken.TraceTokenModule; import com.facebook.drift.server.DriftServer; import com.facebook.drift.transport.netty.server.DriftNettyServerTransport; +import com.facebook.presto.dispatcher.QueryPrerequisitesManager; +import com.facebook.presto.dispatcher.QueryPrerequisitesManagerModule; import com.facebook.presto.eventlistener.EventListenerManager; import com.facebook.presto.eventlistener.EventListenerModule; import com.facebook.presto.execution.resourceGroups.ResourceGroupManager; @@ -124,7 +126,8 @@ public void run() new ServerMainModule(sqlParserOptions), new GracefulShutdownModule(), new WarningCollectorModule(), - new TempStorageModule()); + new TempStorageModule(), + new QueryPrerequisitesManagerModule()); modules.addAll(getAdditionalModules()); @@ -156,6 +159,7 @@ public void run() injector.getInstance(PasswordAuthenticatorManager.class).loadPasswordAuthenticator(); injector.getInstance(EventListenerManager.class).loadConfiguredEventListener(); injector.getInstance(TempStorageManager.class).loadTempStorages(); + injector.getInstance(QueryPrerequisitesManager.class).loadQueryPrerequisites(); injector.getInstance(Announcer.class).start(); diff --git a/presto-main/src/main/java/com/facebook/presto/server/protocol/Query.java b/presto-main/src/main/java/com/facebook/presto/server/protocol/Query.java index bb992179d1075..ee91977d30b84 100644 --- a/presto-main/src/main/java/com/facebook/presto/server/protocol/Query.java +++ b/presto-main/src/main/java/com/facebook/presto/server/protocol/Query.java @@ -77,7 +77,7 @@ import static com.facebook.presto.SystemSessionProperties.isExchangeChecksumEnabled; import static com.facebook.presto.SystemSessionProperties.isExchangeCompressionEnabled; import static com.facebook.presto.execution.QueryState.FAILED; -import static com.facebook.presto.execution.QueryState.QUEUED; +import static com.facebook.presto.execution.QueryState.WAITING_FOR_PREREQUISITES; import static com.facebook.presto.server.protocol.QueryResourceUtil.toStatementStats; import static com.facebook.presto.spi.StandardErrorCode.GENERIC_INTERNAL_ERROR; import static com.facebook.presto.util.Failures.toFailure; @@ -403,8 +403,8 @@ private synchronized QueryResults getNextResultWithRetry(long token, UriInfo uri queryResults.getColumns(), null, StatementStats.builder() - .setState(QUEUED.toString()) - .setQueued(true) + .setState(WAITING_FOR_PREREQUISITES.toString()) + .setWaitingForPrerequisites(true) .build(), null, ImmutableList.of(), diff --git a/presto-main/src/main/java/com/facebook/presto/server/protocol/QueryResourceUtil.java b/presto-main/src/main/java/com/facebook/presto/server/protocol/QueryResourceUtil.java index 0f9ef6454ccb8..9840dd157172a 100644 --- a/presto-main/src/main/java/com/facebook/presto/server/protocol/QueryResourceUtil.java +++ b/presto-main/src/main/java/com/facebook/presto/server/protocol/QueryResourceUtil.java @@ -127,6 +127,7 @@ public static StatementStats toStatementStats(QueryInfo queryInfo) return StatementStats.builder() .setState(queryInfo.getState().toString()) + .setWaitingForPrerequisites(queryInfo.getState() == QueryState.WAITING_FOR_PREREQUISITES) .setQueued(queryInfo.getState() == QueryState.QUEUED) .setScheduled(queryInfo.isScheduled()) .setNodes(globalUniqueNodes(outputStage).size()) @@ -136,6 +137,7 @@ public static StatementStats toStatementStats(QueryInfo queryInfo) .setCompletedSplits(queryStats.getCompletedDrivers()) .setCpuTimeMillis(queryStats.getTotalCpuTime().toMillis()) .setWallTimeMillis(queryStats.getTotalScheduledTime().toMillis()) + .setWaitingForPrerequisitesTimeMillis(queryStats.getWaitingForPrerequisitesTime().toMillis()) .setQueuedTimeMillis(queryStats.getQueuedTime().toMillis()) .setElapsedTimeMillis(queryStats.getElapsedTime().toMillis()) .setProcessedRows(queryStats.getRawInputPositions()) diff --git a/presto-main/src/main/java/com/facebook/presto/server/protocol/QueuedStatementResource.java b/presto-main/src/main/java/com/facebook/presto/server/protocol/QueuedStatementResource.java index 31cfe0a8363df..5118977462dfe 100644 --- a/presto-main/src/main/java/com/facebook/presto/server/protocol/QueuedStatementResource.java +++ b/presto-main/src/main/java/com/facebook/presto/server/protocol/QueuedStatementResource.java @@ -71,6 +71,7 @@ import static com.facebook.airlift.http.server.AsyncResponseHandler.bindAsyncResponse; import static com.facebook.presto.execution.QueryState.FAILED; import static com.facebook.presto.execution.QueryState.QUEUED; +import static com.facebook.presto.execution.QueryState.WAITING_FOR_PREREQUISITES; import static com.facebook.presto.server.security.RoleType.USER; import static com.facebook.presto.spi.StandardErrorCode.GENERIC_INTERNAL_ERROR; import static com.facebook.presto.spi.StandardErrorCode.RETRY_QUERY_NOT_FOUND; @@ -311,9 +312,10 @@ private static QueryResults createQueryResults( UriInfo uriInfo, String xForwardedProto, Duration elapsedTime, - Duration queuedTime) + Optional queuedTime, + Duration waitingForPrerequisitesTime) { - QueryState state = queryError.map(error -> FAILED).orElse(QUEUED); + QueryState state = queryError.map(error -> FAILED).orElse(queuedTime.isPresent() ? QUEUED : WAITING_FOR_PREREQUISITES); return new QueryResults( queryId.toString(), getQueryHtmlUri(queryId, uriInfo, xForwardedProto), @@ -323,9 +325,10 @@ private static QueryResults createQueryResults( null, StatementStats.builder() .setState(state.toString()) - .setQueued(state == QUEUED) + .setWaitingForPrerequisites(state == WAITING_FOR_PREREQUISITES) .setElapsedTimeMillis(elapsedTime.toMillis()) - .setQueuedTimeMillis(queuedTime.toMillis()) + .setQueuedTimeMillis(queuedTime.orElse(NO_DURATION).toMillis()) + .setWaitingForPrerequisitesTimeMillis(waitingForPrerequisitesTime.toMillis()) .build(), queryError.orElse(null), ImmutableList.of(), @@ -433,7 +436,7 @@ public synchronized QueryResults getInitialQueryResults(UriInfo uriInfo, String 1, uriInfo, xForwardedProto, - DispatchInfo.queued(NO_DURATION, NO_DURATION)); + DispatchInfo.waitingForPrerequisites(NO_DURATION, NO_DURATION)); } public ListenableFuture toResponse(long token, UriInfo uriInfo, String xForwardedProto, Duration maxWait, boolean compressionEnabled) @@ -453,7 +456,7 @@ public ListenableFuture toResponse(long token, UriInfo uriInfo, String token + 1, uriInfo, xForwardedProto, - DispatchInfo.queued(NO_DURATION, NO_DURATION)); + DispatchInfo.waitingForPrerequisites(NO_DURATION, NO_DURATION)); return immediateFuture(withCompressionConfiguration(Response.ok(queryResults), compressionEnabled).build()); } } @@ -504,7 +507,8 @@ private QueryResults createQueryResults(long token, UriInfo uriInfo, String xFor uriInfo, xForwardedProto, dispatchInfo.getElapsedTime(), - dispatchInfo.getQueuedTime()); + dispatchInfo.getQueuedTime(), + dispatchInfo.getWaitingForPrerequisitesTime()); } private URI getNextUri(long token, UriInfo uriInfo, String xForwardedProto, DispatchInfo dispatchInfo) diff --git a/presto-main/src/main/java/com/facebook/presto/server/testing/TestingPrestoServer.java b/presto-main/src/main/java/com/facebook/presto/server/testing/TestingPrestoServer.java index 33434c8edd129..1abfa5ce3960a 100644 --- a/presto-main/src/main/java/com/facebook/presto/server/testing/TestingPrestoServer.java +++ b/presto-main/src/main/java/com/facebook/presto/server/testing/TestingPrestoServer.java @@ -35,6 +35,7 @@ import com.facebook.presto.connector.ConnectorManager; import com.facebook.presto.cost.StatsCalculator; import com.facebook.presto.dispatcher.DispatchManager; +import com.facebook.presto.dispatcher.QueryPrerequisitesManagerModule; import com.facebook.presto.eventlistener.EventListenerManager; import com.facebook.presto.execution.QueryInfo; import com.facebook.presto.execution.QueryManager; @@ -270,6 +271,7 @@ public TestingPrestoServer( .add(new ServerSecurityModule()) .add(new ServerMainModule(parserOptions)) .add(new TestingWarningCollectorModule()) + .add(new QueryPrerequisitesManagerModule()) .add(binder -> { binder.bind(TestingAccessControlManager.class).in(Scopes.SINGLETON); binder.bind(TestingEventListenerManager.class).in(Scopes.SINGLETON); diff --git a/presto-main/src/main/java/com/facebook/presto/testing/LocalQueryRunner.java b/presto-main/src/main/java/com/facebook/presto/testing/LocalQueryRunner.java index e9921a403fa18..4780d3ee1dbea 100644 --- a/presto-main/src/main/java/com/facebook/presto/testing/LocalQueryRunner.java +++ b/presto-main/src/main/java/com/facebook/presto/testing/LocalQueryRunner.java @@ -41,6 +41,7 @@ import com.facebook.presto.cost.StatsCalculator; import com.facebook.presto.cost.StatsNormalizer; import com.facebook.presto.cost.TaskCountEstimator; +import com.facebook.presto.dispatcher.QueryPrerequisitesManager; import com.facebook.presto.eventlistener.EventListenerManager; import com.facebook.presto.execution.AlterFunctionTask; import com.facebook.presto.execution.CommitTask; @@ -430,6 +431,7 @@ private LocalQueryRunner(Session defaultSession, FeaturesConfig featuresConfig, new EventListenerManager(), blockEncodingManager, new TestingTempStorageManager(), + new QueryPrerequisitesManager(), new SessionPropertyDefaults(nodeInfo)); connectorManager.addConnectorFactory(globalSystemConnectorFactory); diff --git a/presto-main/src/main/resources/webapp/dist/query.js b/presto-main/src/main/resources/webapp/dist/query.js index e81a2b61c1814..2f709e070a576 100644 --- a/presto-main/src/main/resources/webapp/dist/query.js +++ b/presto-main/src/main/resources/webapp/dist/query.js @@ -106,7 +106,7 @@ eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n}); /***/ (function(module, exports, __webpack_require__) { "use strict"; -eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.QueryDetail = undefined;\n\nvar _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();\n\nvar _react = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n\nvar _react2 = _interopRequireDefault(_react);\n\nvar _reactable = __webpack_require__(/*! reactable */ \"./node_modules/reactable/lib/reactable.js\");\n\nvar _reactable2 = _interopRequireDefault(_reactable);\n\nvar _utils = __webpack_require__(/*! ../utils */ \"./utils.js\");\n\nvar _QueryHeader = __webpack_require__(/*! ./QueryHeader */ \"./components/QueryHeader.jsx\");\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return call && (typeof call === \"object\" || typeof call === \"function\") ? call : self; }\n\nfunction _inherits(subClass, superClass) { if (typeof superClass !== \"function\" && superClass !== null) { throw new TypeError(\"Super expression must either be null or a function, not \" + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /*\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nvar Table = _reactable2.default.Table,\n Thead = _reactable2.default.Thead,\n Th = _reactable2.default.Th,\n Tr = _reactable2.default.Tr,\n Td = _reactable2.default.Td;\n\nvar TaskList = function (_React$Component) {\n _inherits(TaskList, _React$Component);\n\n function TaskList() {\n _classCallCheck(this, TaskList);\n\n return _possibleConstructorReturn(this, (TaskList.__proto__ || Object.getPrototypeOf(TaskList)).apply(this, arguments));\n }\n\n _createClass(TaskList, [{\n key: \"render\",\n value: function render() {\n var tasks = this.props.tasks;\n\n if (tasks === undefined || tasks.length === 0) {\n return _react2.default.createElement(\n \"div\",\n { className: \"row error-message\" },\n _react2.default.createElement(\n \"div\",\n { className: \"col-xs-12\" },\n _react2.default.createElement(\n \"h4\",\n null,\n \"No threads in the selected group\"\n )\n )\n );\n }\n\n var showPortNumbers = TaskList.showPortNumbers(tasks);\n\n var renderedTasks = tasks.map(function (task) {\n var elapsedTime = (0, _utils.parseDuration)(task.stats.elapsedTimeInNanos + \"ns\");\n if (elapsedTime === 0) {\n elapsedTime = Date.now() - Date.parse(task.stats.createTime);\n }\n\n return _react2.default.createElement(\n Tr,\n { key: task.taskId },\n _react2.default.createElement(\n Td,\n { column: \"id\", value: task.taskId },\n _react2.default.createElement(\n \"a\",\n { href: \"/v1/taskInfo/\" + task.taskId + \"?pretty\" },\n (0, _utils.getTaskIdSuffix)(task.taskId)\n )\n ),\n _react2.default.createElement(\n Td,\n { column: \"host\", value: (0, _utils.getHostname)(task.taskStatus.self) },\n _react2.default.createElement(\n \"a\",\n { href: \"worker.html?\" + task.nodeId, className: \"font-light\", target: \"_blank\" },\n showPortNumbers ? (0, _utils.getHostAndPort)(task.taskStatus.self) : (0, _utils.getHostname)(task.taskStatus.self)\n )\n ),\n _react2.default.createElement(\n Td,\n { column: \"state\", value: TaskList.formatState(task.taskStatus.state, task.stats.fullyBlocked) },\n TaskList.formatState(task.taskStatus.state, task.stats.fullyBlocked)\n ),\n _react2.default.createElement(\n Td,\n { column: \"rows\", value: task.stats.rawInputPositions },\n (0, _utils.formatCount)(task.stats.rawInputPositions)\n ),\n _react2.default.createElement(\n Td,\n { column: \"rowsSec\", value: (0, _utils.computeRate)(task.stats.rawInputPositions, elapsedTime) },\n (0, _utils.formatCount)((0, _utils.computeRate)(task.stats.rawInputPositions, elapsedTime))\n ),\n _react2.default.createElement(\n Td,\n { column: \"bytes\", value: task.stats.rawInputDataSizeInBytes },\n (0, _utils.formatDataSizeBytes)(task.stats.rawInputDataSizeInBytes)\n ),\n _react2.default.createElement(\n Td,\n { column: \"bytesSec\", value: (0, _utils.computeRate)(task.stats.rawInputDataSizeInBytes, elapsedTime) },\n (0, _utils.formatDataSizeBytes)((0, _utils.computeRate)(task.stats.rawInputDataSizeInBytes, elapsedTime))\n ),\n _react2.default.createElement(\n Td,\n { column: \"splitsPending\", value: task.stats.queuedDrivers },\n task.stats.queuedDrivers\n ),\n _react2.default.createElement(\n Td,\n { column: \"splitsRunning\", value: task.stats.runningDrivers },\n task.stats.runningDrivers\n ),\n _react2.default.createElement(\n Td,\n { column: \"splitsBlocked\", value: task.stats.blockedDrivers },\n task.stats.blockedDrivers\n ),\n _react2.default.createElement(\n Td,\n { column: \"splitsDone\", value: task.stats.completedDrivers },\n task.stats.completedDrivers\n ),\n _react2.default.createElement(\n Td,\n { column: \"elapsedTime\", value: (0, _utils.parseDuration)(task.stats.elapsedTimeInNanos + \"ns\") },\n (0, _utils.formatDuration)((0, _utils.parseDuration)(task.stats.elapsedTimeInNanos + \"ns\"))\n ),\n _react2.default.createElement(\n Td,\n { column: \"cpuTime\", value: (0, _utils.parseDuration)(task.stats.totalCpuTimeInNanos + \"ns\") },\n (0, _utils.formatDuration)((0, _utils.parseDuration)(task.stats.totalCpuTimeInNanos + \"ns\"))\n ),\n _react2.default.createElement(\n Td,\n { column: \"bufferedBytes\", value: task.outputBuffers.totalBufferedBytes },\n (0, _utils.formatDataSizeBytes)(task.outputBuffers.totalBufferedBytes)\n )\n );\n });\n\n return _react2.default.createElement(\n Table,\n { id: \"tasks\", className: \"table table-striped sortable\", sortable: [{\n column: 'id',\n sortFunction: TaskList.compareTaskId\n }, 'host', 'state', 'splitsPending', 'splitsRunning', 'splitsBlocked', 'splitsDone', 'rows', 'rowsSec', 'bytes', 'bytesSec', 'elapsedTime', 'cpuTime', 'bufferedBytes'],\n defaultSort: { column: 'id', direction: 'asc' } },\n _react2.default.createElement(\n Thead,\n null,\n _react2.default.createElement(\n Th,\n { column: \"id\" },\n \"ID\"\n ),\n _react2.default.createElement(\n Th,\n { column: \"host\" },\n \"Host\"\n ),\n _react2.default.createElement(\n Th,\n { column: \"state\" },\n \"State\"\n ),\n _react2.default.createElement(\n Th,\n { column: \"splitsPending\" },\n _react2.default.createElement(\"span\", { className: \"glyphicon glyphicon-pause\", style: _utils.GLYPHICON_HIGHLIGHT,\n \"data-toggle\": \"tooltip\", \"data-placement\": \"top\",\n title: \"Pending splits\" })\n ),\n _react2.default.createElement(\n Th,\n { column: \"splitsRunning\" },\n _react2.default.createElement(\"span\", { className: \"glyphicon glyphicon-play\", style: _utils.GLYPHICON_HIGHLIGHT,\n \"data-toggle\": \"tooltip\", \"data-placement\": \"top\",\n title: \"Running splits\" })\n ),\n _react2.default.createElement(\n Th,\n { column: \"splitsBlocked\" },\n _react2.default.createElement(\"span\", { className: \"glyphicon glyphicon-bookmark\",\n style: _utils.GLYPHICON_HIGHLIGHT, \"data-toggle\": \"tooltip\",\n \"data-placement\": \"top\",\n title: \"Blocked splits\" })\n ),\n _react2.default.createElement(\n Th,\n { column: \"splitsDone\" },\n _react2.default.createElement(\"span\", { className: \"glyphicon glyphicon-ok\", style: _utils.GLYPHICON_HIGHLIGHT,\n \"data-toggle\": \"tooltip\", \"data-placement\": \"top\",\n title: \"Completed splits\" })\n ),\n _react2.default.createElement(\n Th,\n { column: \"rows\" },\n \"Rows\"\n ),\n _react2.default.createElement(\n Th,\n { column: \"rowsSec\" },\n \"Rows/s\"\n ),\n _react2.default.createElement(\n Th,\n { column: \"bytes\" },\n \"Bytes\"\n ),\n _react2.default.createElement(\n Th,\n { column: \"bytesSec\" },\n \"Bytes/s\"\n ),\n _react2.default.createElement(\n Th,\n { column: \"elapsedTime\" },\n \"Elapsed\"\n ),\n _react2.default.createElement(\n Th,\n { column: \"cpuTime\" },\n \"CPU Time\"\n ),\n _react2.default.createElement(\n Th,\n { column: \"bufferedBytes\" },\n \"Buffered\"\n )\n ),\n renderedTasks\n );\n }\n }], [{\n key: \"removeQueryId\",\n value: function removeQueryId(id) {\n var pos = id.indexOf('.');\n if (pos !== -1) {\n return id.substring(pos + 1);\n }\n return id;\n }\n }, {\n key: \"compareTaskId\",\n value: function compareTaskId(taskA, taskB) {\n var taskIdArrA = TaskList.removeQueryId(taskA).split(\".\");\n var taskIdArrB = TaskList.removeQueryId(taskB).split(\".\");\n\n if (taskIdArrA.length > taskIdArrB.length) {\n return 1;\n }\n for (var i = 0; i < taskIdArrA.length; i++) {\n var anum = Number.parseInt(taskIdArrA[i]);\n var bnum = Number.parseInt(taskIdArrB[i]);\n if (anum !== bnum) {\n return anum > bnum ? 1 : -1;\n }\n }\n\n return 0;\n }\n }, {\n key: \"showPortNumbers\",\n value: function showPortNumbers(tasks) {\n // check if any host has multiple port numbers\n var hostToPortNumber = {};\n for (var i = 0; i < tasks.length; i++) {\n var taskUri = tasks[i].taskStatus.self;\n var hostname = (0, _utils.getHostname)(taskUri);\n var port = (0, _utils.getPort)(taskUri);\n if (hostname in hostToPortNumber && hostToPortNumber[hostname] !== port) {\n return true;\n }\n hostToPortNumber[hostname] = port;\n }\n\n return false;\n }\n }, {\n key: \"formatState\",\n value: function formatState(state, fullyBlocked) {\n if (fullyBlocked && state === \"RUNNING\") {\n return \"BLOCKED\";\n } else {\n return state;\n }\n }\n }]);\n\n return TaskList;\n}(_react2.default.Component);\n\nvar BAR_CHART_WIDTH = 800;\n\nvar BAR_CHART_PROPERTIES = {\n type: 'bar',\n barSpacing: '0',\n height: '80px',\n barColor: '#747F96',\n zeroColor: '#8997B3',\n chartRangeMin: 0,\n tooltipClassname: 'sparkline-tooltip',\n tooltipFormat: 'Task {{offset:offset}} - {{value}}',\n disableHiddenCheck: true\n};\n\nvar HISTOGRAM_WIDTH = 175;\n\nvar HISTOGRAM_PROPERTIES = {\n type: 'bar',\n barSpacing: '0',\n height: '80px',\n barColor: '#747F96',\n zeroColor: '#747F96',\n zeroAxis: true,\n chartRangeMin: 0,\n tooltipClassname: 'sparkline-tooltip',\n tooltipFormat: '{{offset:offset}} -- {{value}} tasks',\n disableHiddenCheck: true\n};\n\nvar StageSummary = function (_React$Component2) {\n _inherits(StageSummary, _React$Component2);\n\n function StageSummary(props) {\n _classCallCheck(this, StageSummary);\n\n var _this2 = _possibleConstructorReturn(this, (StageSummary.__proto__ || Object.getPrototypeOf(StageSummary)).call(this, props));\n\n _this2.state = {\n expanded: false,\n lastRender: null,\n taskFilter: TASK_FILTER.ALL\n };\n return _this2;\n }\n\n _createClass(StageSummary, [{\n key: \"getExpandedIcon\",\n value: function getExpandedIcon() {\n return this.state.expanded ? \"glyphicon-chevron-up\" : \"glyphicon-chevron-down\";\n }\n }, {\n key: \"getExpandedStyle\",\n value: function getExpandedStyle() {\n return this.state.expanded ? {} : { display: \"none\" };\n }\n }, {\n key: \"toggleExpanded\",\n value: function toggleExpanded() {\n this.setState({\n expanded: !this.state.expanded\n });\n }\n }, {\n key: \"componentDidUpdate\",\n value: function componentDidUpdate() {\n var stage = this.props.stage;\n var numTasks = stage.latestAttemptExecutionInfo.tasks.length;\n\n // sort the x-axis\n stage.latestAttemptExecutionInfo.tasks.sort(function (taskA, taskB) {\n return (0, _utils.getTaskNumber)(taskA.taskId) - (0, _utils.getTaskNumber)(taskB.taskId);\n });\n\n var scheduledTimes = stage.latestAttemptExecutionInfo.tasks.map(function (task) {\n return (0, _utils.parseDuration)(task.stats.totalScheduledTimeInNanos + \"ns\");\n });\n var cpuTimes = stage.latestAttemptExecutionInfo.tasks.map(function (task) {\n return (0, _utils.parseDuration)(task.stats.totalCpuTimeInNanos + \"ns\");\n });\n\n // prevent multiple calls to componentDidUpdate (resulting from calls to setState or otherwise) within the refresh interval from re-rendering sparklines/charts\n if (this.state.lastRender === null || Date.now() - this.state.lastRender >= 1000) {\n var renderTimestamp = Date.now();\n var stageId = (0, _utils.getStageNumber)(stage.stageId);\n\n StageSummary.renderHistogram('#scheduled-time-histogram-' + stageId, scheduledTimes, _utils.formatDuration);\n StageSummary.renderHistogram('#cpu-time-histogram-' + stageId, cpuTimes, _utils.formatDuration);\n\n if (this.state.expanded) {\n // this needs to be a string otherwise it will also be passed to numberFormatter\n var tooltipValueLookups = { 'offset': {} };\n for (var i = 0; i < numTasks; i++) {\n tooltipValueLookups['offset'][i] = (0, _utils.getStageNumber)(stage.stageId) + \".\" + i;\n }\n\n var stageBarChartProperties = $.extend({}, BAR_CHART_PROPERTIES, { barWidth: BAR_CHART_WIDTH / numTasks, tooltipValueLookups: tooltipValueLookups });\n\n $('#scheduled-time-bar-chart-' + stageId).sparkline(scheduledTimes, $.extend({}, stageBarChartProperties, { numberFormatter: _utils.formatDuration }));\n $('#cpu-time-bar-chart-' + stageId).sparkline(cpuTimes, $.extend({}, stageBarChartProperties, { numberFormatter: _utils.formatDuration }));\n }\n\n this.setState({\n lastRender: renderTimestamp\n });\n }\n }\n }, {\n key: \"renderStageExecutionAttemptsTasks\",\n value: function renderStageExecutionAttemptsTasks(attempts) {\n var _this3 = this;\n\n return attempts.map(function (attempt) {\n return _this3.renderTaskList(attempt.tasks);\n });\n }\n }, {\n key: \"renderTaskList\",\n value: function renderTaskList(tasks) {\n var _this4 = this;\n\n tasks = this.state.expanded ? tasks : [];\n tasks = tasks.filter(function (task) {\n return _this4.state.taskFilter.predicate(task.taskStatus.state);\n }, this);\n return _react2.default.createElement(\n \"tr\",\n { style: this.getExpandedStyle() },\n _react2.default.createElement(\n \"td\",\n { colSpan: \"6\" },\n _react2.default.createElement(TaskList, { tasks: tasks })\n )\n );\n }\n }, {\n key: \"renderTaskFilterListItem\",\n value: function renderTaskFilterListItem(taskFilter) {\n return _react2.default.createElement(\n \"li\",\n null,\n _react2.default.createElement(\n \"a\",\n { href: \"#\", className: this.state.taskFilter === taskFilter ? \"selected\" : \"\",\n onClick: this.handleTaskFilterClick.bind(this, taskFilter) },\n taskFilter.text\n )\n );\n }\n }, {\n key: \"handleTaskFilterClick\",\n value: function handleTaskFilterClick(filter, event) {\n this.setState({\n taskFilter: filter\n });\n event.preventDefault();\n }\n }, {\n key: \"renderTaskFilter\",\n value: function renderTaskFilter() {\n return _react2.default.createElement(\n \"div\",\n { className: \"row\" },\n _react2.default.createElement(\n \"div\",\n { className: \"col-xs-6\" },\n _react2.default.createElement(\n \"h3\",\n null,\n \"Tasks\"\n )\n ),\n _react2.default.createElement(\n \"div\",\n { className: \"col-xs-6\" },\n _react2.default.createElement(\n \"table\",\n { className: \"header-inline-links\" },\n _react2.default.createElement(\n \"tbody\",\n null,\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n null,\n _react2.default.createElement(\n \"div\",\n { className: \"input-group-btn text-right\" },\n _react2.default.createElement(\n \"button\",\n { type: \"button\", className: \"btn btn-default dropdown-toggle pull-right text-right\",\n \"data-toggle\": \"dropdown\", \"aria-haspopup\": \"true\",\n \"aria-expanded\": \"false\" },\n \"Show: \",\n this.state.taskFilter.text,\n \" \",\n _react2.default.createElement(\"span\", { className: \"caret\" })\n ),\n _react2.default.createElement(\n \"ul\",\n { className: \"dropdown-menu\" },\n this.renderTaskFilterListItem(TASK_FILTER.ALL),\n this.renderTaskFilterListItem(TASK_FILTER.PLANNED),\n this.renderTaskFilterListItem(TASK_FILTER.RUNNING),\n this.renderTaskFilterListItem(TASK_FILTER.FINISHED),\n this.renderTaskFilterListItem(TASK_FILTER.FAILED)\n )\n )\n )\n )\n )\n )\n )\n );\n }\n }, {\n key: \"render\",\n value: function render() {\n var stage = this.props.stage;\n if (stage === undefined || !stage.hasOwnProperty('plan')) {\n return _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n null,\n \"Information about this stage is unavailable.\"\n )\n );\n }\n\n var totalBufferedBytes = stage.latestAttemptExecutionInfo.tasks.map(function (task) {\n return task.outputBuffers.totalBufferedBytes;\n }).reduce(function (a, b) {\n return a + b;\n }, 0);\n\n var stageId = (0, _utils.getStageNumber)(stage.stageId);\n\n return _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"stage-id\" },\n _react2.default.createElement(\n \"div\",\n { className: \"stage-state-color\", style: { borderLeftColor: (0, _utils.getStageStateColor)(stage) } },\n stageId\n )\n ),\n _react2.default.createElement(\n \"td\",\n null,\n _react2.default.createElement(\n \"table\",\n { className: \"table single-stage-table\" },\n _react2.default.createElement(\n \"tbody\",\n null,\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n null,\n _react2.default.createElement(\n \"table\",\n { className: \"stage-table stage-table-time\" },\n _react2.default.createElement(\n \"thead\",\n null,\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"th\",\n { className: \"stage-table-stat-title stage-table-stat-header\" },\n \"Time\"\n ),\n _react2.default.createElement(\"th\", null)\n )\n ),\n _react2.default.createElement(\n \"tbody\",\n null,\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"stage-table-stat-title\" },\n \"Scheduled\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"stage-table-stat-text\" },\n stage.latestAttemptExecutionInfo.stats.totalScheduledTime\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"stage-table-stat-title\" },\n \"Blocked\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"stage-table-stat-text\" },\n stage.latestAttemptExecutionInfo.stats.totalBlockedTime\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"stage-table-stat-title\" },\n \"CPU\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"stage-table-stat-text\" },\n stage.latestAttemptExecutionInfo.stats.totalCpuTime\n )\n )\n )\n )\n ),\n _react2.default.createElement(\n \"td\",\n null,\n _react2.default.createElement(\n \"table\",\n { className: \"stage-table stage-table-memory\" },\n _react2.default.createElement(\n \"thead\",\n null,\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"th\",\n { className: \"stage-table-stat-title stage-table-stat-header\" },\n \"Memory\"\n ),\n _react2.default.createElement(\"th\", null)\n )\n ),\n _react2.default.createElement(\n \"tbody\",\n null,\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"stage-table-stat-title\" },\n \"Cumulative\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"stage-table-stat-text\" },\n (0, _utils.formatDataSizeBytes)(stage.latestAttemptExecutionInfo.stats.cumulativeUserMemory / 1000)\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"stage-table-stat-title\" },\n \"Current\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"stage-table-stat-text\" },\n stage.latestAttemptExecutionInfo.stats.userMemoryReservation\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"stage-table-stat-title\" },\n \"Buffers\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"stage-table-stat-text\" },\n (0, _utils.formatDataSize)(totalBufferedBytes)\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"stage-table-stat-title\" },\n \"Peak\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"stage-table-stat-text\" },\n stage.latestAttemptExecutionInfo.stats.peakUserMemoryReservation\n )\n )\n )\n )\n ),\n _react2.default.createElement(\n \"td\",\n null,\n _react2.default.createElement(\n \"table\",\n { className: \"stage-table stage-table-tasks\" },\n _react2.default.createElement(\n \"thead\",\n null,\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"th\",\n { className: \"stage-table-stat-title stage-table-stat-header\" },\n \"Tasks\"\n ),\n _react2.default.createElement(\"th\", null)\n )\n ),\n _react2.default.createElement(\n \"tbody\",\n null,\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"stage-table-stat-title\" },\n \"Pending\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"stage-table-stat-text\" },\n stage.latestAttemptExecutionInfo.tasks.filter(function (task) {\n return task.taskStatus.state === \"PLANNED\";\n }).length\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"stage-table-stat-title\" },\n \"Running\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"stage-table-stat-text\" },\n stage.latestAttemptExecutionInfo.tasks.filter(function (task) {\n return task.taskStatus.state === \"RUNNING\";\n }).length\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"stage-table-stat-title\" },\n \"Blocked\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"stage-table-stat-text\" },\n stage.latestAttemptExecutionInfo.tasks.filter(function (task) {\n return task.stats.fullyBlocked;\n }).length\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"stage-table-stat-title\" },\n \"Total\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"stage-table-stat-text\" },\n stage.latestAttemptExecutionInfo.tasks.length\n )\n )\n )\n )\n ),\n _react2.default.createElement(\n \"td\",\n null,\n _react2.default.createElement(\n \"table\",\n { className: \"stage-table histogram-table\" },\n _react2.default.createElement(\n \"thead\",\n null,\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"th\",\n { className: \"stage-table-stat-title stage-table-chart-header\" },\n \"Scheduled Time Skew\"\n )\n )\n ),\n _react2.default.createElement(\n \"tbody\",\n null,\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"histogram-container\" },\n _react2.default.createElement(\n \"span\",\n { className: \"histogram\", id: \"scheduled-time-histogram-\" + stageId },\n _react2.default.createElement(\"div\", { className: \"loader\" })\n )\n )\n )\n )\n )\n ),\n _react2.default.createElement(\n \"td\",\n null,\n _react2.default.createElement(\n \"table\",\n { className: \"stage-table histogram-table\" },\n _react2.default.createElement(\n \"thead\",\n null,\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"th\",\n { className: \"stage-table-stat-title stage-table-chart-header\" },\n \"CPU Time Skew\"\n )\n )\n ),\n _react2.default.createElement(\n \"tbody\",\n null,\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"histogram-container\" },\n _react2.default.createElement(\n \"span\",\n { className: \"histogram\", id: \"cpu-time-histogram-\" + stageId },\n _react2.default.createElement(\"div\", { className: \"loader\" })\n )\n )\n )\n )\n )\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"expand-charts-container\" },\n _react2.default.createElement(\n \"a\",\n { onClick: this.toggleExpanded.bind(this), className: \"expand-charts-button\" },\n _react2.default.createElement(\"span\", { className: \"glyphicon \" + this.getExpandedIcon(), style: _utils.GLYPHICON_HIGHLIGHT, \"data-toggle\": \"tooltip\", \"data-placement\": \"top\", title: \"More\" })\n )\n )\n ),\n _react2.default.createElement(\n \"tr\",\n { style: this.getExpandedStyle() },\n _react2.default.createElement(\n \"td\",\n { colSpan: \"6\" },\n _react2.default.createElement(\n \"table\",\n { className: \"expanded-chart\" },\n _react2.default.createElement(\n \"tbody\",\n null,\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"stage-table-stat-title expanded-chart-title\" },\n \"Task Scheduled Time\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"bar-chart-container\" },\n _react2.default.createElement(\n \"span\",\n { className: \"bar-chart\", id: \"scheduled-time-bar-chart-\" + stageId },\n _react2.default.createElement(\"div\", { className: \"loader\" })\n )\n )\n )\n )\n )\n )\n ),\n _react2.default.createElement(\n \"tr\",\n { style: this.getExpandedStyle() },\n _react2.default.createElement(\n \"td\",\n { colSpan: \"6\" },\n _react2.default.createElement(\n \"table\",\n { className: \"expanded-chart\" },\n _react2.default.createElement(\n \"tbody\",\n null,\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"stage-table-stat-title expanded-chart-title\" },\n \"Task CPU Time\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"bar-chart-container\" },\n _react2.default.createElement(\n \"span\",\n { className: \"bar-chart\", id: \"cpu-time-bar-chart-\" + stageId },\n _react2.default.createElement(\"div\", { className: \"loader\" })\n )\n )\n )\n )\n )\n )\n ),\n _react2.default.createElement(\n \"tr\",\n { style: this.getExpandedStyle() },\n _react2.default.createElement(\n \"td\",\n { colSpan: \"6\" },\n this.renderTaskFilter()\n )\n ),\n this.renderStageExecutionAttemptsTasks([stage.latestAttemptExecutionInfo]),\n this.renderStageExecutionAttemptsTasks(stage.previousAttemptsExecutionInfos)\n )\n )\n )\n );\n }\n }], [{\n key: \"renderHistogram\",\n value: function renderHistogram(histogramId, inputData, numberFormatter) {\n var numBuckets = Math.min(HISTOGRAM_WIDTH, Math.sqrt(inputData.length));\n var dataMin = Math.min.apply(null, inputData);\n var dataMax = Math.max.apply(null, inputData);\n var bucketSize = (dataMax - dataMin) / numBuckets;\n\n var histogramData = [];\n if (bucketSize === 0) {\n histogramData = [inputData.length];\n } else {\n for (var i = 0; i < numBuckets + 1; i++) {\n histogramData.push(0);\n }\n\n for (var _i in inputData) {\n var dataPoint = inputData[_i];\n var bucket = Math.floor((dataPoint - dataMin) / bucketSize);\n histogramData[bucket] = histogramData[bucket] + 1;\n }\n }\n\n var tooltipValueLookups = { 'offset': {} };\n for (var _i2 = 0; _i2 < histogramData.length; _i2++) {\n tooltipValueLookups['offset'][_i2] = numberFormatter(dataMin + _i2 * bucketSize) + \"-\" + numberFormatter(dataMin + (_i2 + 1) * bucketSize);\n }\n\n var stageHistogramProperties = $.extend({}, HISTOGRAM_PROPERTIES, { barWidth: HISTOGRAM_WIDTH / histogramData.length, tooltipValueLookups: tooltipValueLookups });\n $(histogramId).sparkline(histogramData, stageHistogramProperties);\n }\n }]);\n\n return StageSummary;\n}(_react2.default.Component);\n\nvar StageList = function (_React$Component3) {\n _inherits(StageList, _React$Component3);\n\n function StageList() {\n _classCallCheck(this, StageList);\n\n return _possibleConstructorReturn(this, (StageList.__proto__ || Object.getPrototypeOf(StageList)).apply(this, arguments));\n }\n\n _createClass(StageList, [{\n key: \"getStages\",\n value: function getStages(stage) {\n if (stage === undefined || !stage.hasOwnProperty('subStages')) {\n return [];\n }\n\n return [].concat.apply(stage, stage.subStages.map(this.getStages, this));\n }\n }, {\n key: \"render\",\n value: function render() {\n var stages = this.getStages(this.props.outputStage);\n\n if (stages === undefined || stages.length === 0) {\n return _react2.default.createElement(\n \"div\",\n { className: \"row\" },\n _react2.default.createElement(\n \"div\",\n { className: \"col-xs-12\" },\n \"No stage information available.\"\n )\n );\n }\n\n var renderedStages = stages.map(function (stage) {\n return _react2.default.createElement(StageSummary, { key: stage.stageId, stage: stage });\n });\n\n return _react2.default.createElement(\n \"div\",\n { className: \"row\" },\n _react2.default.createElement(\n \"div\",\n { className: \"col-xs-12\" },\n _react2.default.createElement(\n \"table\",\n { className: \"table\", id: \"stage-list\" },\n _react2.default.createElement(\n \"tbody\",\n null,\n renderedStages\n )\n )\n )\n );\n }\n }]);\n\n return StageList;\n}(_react2.default.Component);\n\nvar SMALL_SPARKLINE_PROPERTIES = {\n width: '100%',\n height: '57px',\n fillColor: '#3F4552',\n lineColor: '#747F96',\n spotColor: '#1EDCFF',\n tooltipClassname: 'sparkline-tooltip',\n disableHiddenCheck: true\n};\n\nvar TASK_FILTER = {\n ALL: {\n text: \"All\",\n predicate: function predicate() {\n return true;\n }\n },\n PLANNED: {\n text: \"Planned\",\n predicate: function predicate(state) {\n return state === 'PLANNED';\n }\n },\n RUNNING: {\n text: \"Running\",\n predicate: function predicate(state) {\n return state === 'RUNNING';\n }\n },\n FINISHED: {\n text: \"Finished\",\n predicate: function predicate(state) {\n return state === 'FINISHED';\n }\n },\n FAILED: {\n text: \"Aborted/Canceled/Failed\",\n predicate: function predicate(state) {\n return state === 'FAILED' || state === 'ABORTED' || state === 'CANCELED';\n }\n }\n};\n\nvar QueryDetail = exports.QueryDetail = function (_React$Component4) {\n _inherits(QueryDetail, _React$Component4);\n\n function QueryDetail(props) {\n _classCallCheck(this, QueryDetail);\n\n var _this6 = _possibleConstructorReturn(this, (QueryDetail.__proto__ || Object.getPrototypeOf(QueryDetail)).call(this, props));\n\n _this6.state = {\n query: null,\n lastSnapshotStages: null,\n\n lastScheduledTime: 0,\n lastCpuTime: 0,\n lastRowInput: 0,\n lastByteInput: 0,\n\n scheduledTimeRate: [],\n cpuTimeRate: [],\n rowInputRate: [],\n byteInputRate: [],\n\n reservedMemory: [],\n\n initialized: false,\n ended: false,\n\n lastRefresh: null,\n lastRender: null,\n\n stageRefresh: true\n };\n\n _this6.refreshLoop = _this6.refreshLoop.bind(_this6);\n return _this6;\n }\n\n _createClass(QueryDetail, [{\n key: \"resetTimer\",\n value: function resetTimer() {\n clearTimeout(this.timeoutId);\n // stop refreshing when query finishes or fails\n if (this.state.query === null || !this.state.ended) {\n // task.info-update-interval is set to 3 seconds by default\n this.timeoutId = setTimeout(this.refreshLoop, 3000);\n }\n }\n }, {\n key: \"refreshLoop\",\n value: function refreshLoop() {\n var _this7 = this;\n\n clearTimeout(this.timeoutId); // to stop multiple series of refreshLoop from going on simultaneously\n var queryId = (0, _utils.getFirstParameter)(window.location.search);\n $.get('/v1/query/' + queryId, function (query) {\n var lastSnapshotStages = this.state.lastSnapshotStage;\n if (this.state.stageRefresh) {\n lastSnapshotStages = query.outputStage;\n }\n\n var lastRefresh = this.state.lastRefresh;\n var lastScheduledTime = this.state.lastScheduledTime;\n var lastCpuTime = this.state.lastCpuTime;\n var lastRowInput = this.state.lastRowInput;\n var lastByteInput = this.state.lastByteInput;\n var alreadyEnded = this.state.ended;\n var nowMillis = Date.now();\n\n this.setState({\n query: query,\n lastSnapshotStage: lastSnapshotStages,\n\n lastScheduledTime: (0, _utils.parseDuration)(query.queryStats.totalScheduledTime),\n lastCpuTime: (0, _utils.parseDuration)(query.queryStats.totalCpuTime),\n lastRowInput: query.queryStats.processedInputPositions,\n lastByteInput: (0, _utils.parseDataSize)(query.queryStats.processedInputDataSize),\n\n initialized: true,\n ended: query.finalQueryInfo,\n\n lastRefresh: nowMillis\n });\n\n // i.e. don't show sparklines if we've already decided not to update or if we don't have one previous measurement\n if (alreadyEnded || lastRefresh === null && query.state === \"RUNNING\") {\n this.resetTimer();\n return;\n }\n\n if (lastRefresh === null) {\n lastRefresh = nowMillis - (0, _utils.parseDuration)(query.queryStats.elapsedTime);\n }\n\n var elapsedSecsSinceLastRefresh = (nowMillis - lastRefresh) / 1000.0;\n if (elapsedSecsSinceLastRefresh >= 0) {\n var currentScheduledTimeRate = ((0, _utils.parseDuration)(query.queryStats.totalScheduledTime) - lastScheduledTime) / (elapsedSecsSinceLastRefresh * 1000);\n var currentCpuTimeRate = ((0, _utils.parseDuration)(query.queryStats.totalCpuTime) - lastCpuTime) / (elapsedSecsSinceLastRefresh * 1000);\n var currentRowInputRate = (query.queryStats.processedInputPositions - lastRowInput) / elapsedSecsSinceLastRefresh;\n var currentByteInputRate = ((0, _utils.parseDataSize)(query.queryStats.processedInputDataSize) - lastByteInput) / elapsedSecsSinceLastRefresh;\n this.setState({\n scheduledTimeRate: (0, _utils.addToHistory)(currentScheduledTimeRate, this.state.scheduledTimeRate),\n cpuTimeRate: (0, _utils.addToHistory)(currentCpuTimeRate, this.state.cpuTimeRate),\n rowInputRate: (0, _utils.addToHistory)(currentRowInputRate, this.state.rowInputRate),\n byteInputRate: (0, _utils.addToHistory)(currentByteInputRate, this.state.byteInputRate),\n reservedMemory: (0, _utils.addToHistory)((0, _utils.parseDataSize)(query.queryStats.userMemoryReservation), this.state.reservedMemory)\n });\n }\n this.resetTimer();\n }.bind(this)).error(function () {\n _this7.setState({\n initialized: true\n });\n _this7.resetTimer();\n });\n }\n }, {\n key: \"handleStageRefreshClick\",\n value: function handleStageRefreshClick() {\n if (this.state.stageRefresh) {\n this.setState({\n stageRefresh: false,\n lastSnapshotStages: this.state.query.outputStage\n });\n } else {\n this.setState({\n stageRefresh: true\n });\n }\n }\n }, {\n key: \"renderStageRefreshButton\",\n value: function renderStageRefreshButton() {\n if (this.state.stageRefresh) {\n return _react2.default.createElement(\n \"button\",\n { className: \"btn btn-info live-button\", onClick: this.handleStageRefreshClick.bind(this) },\n \"Auto-Refresh: On\"\n );\n } else {\n return _react2.default.createElement(\n \"button\",\n { className: \"btn btn-info live-button\", onClick: this.handleStageRefreshClick.bind(this) },\n \"Auto-Refresh: Off\"\n );\n }\n }\n }, {\n key: \"componentDidMount\",\n value: function componentDidMount() {\n this.refreshLoop();\n }\n }, {\n key: \"componentDidUpdate\",\n value: function componentDidUpdate() {\n // prevent multiple calls to componentDidUpdate (resulting from calls to setState or otherwise) within the refresh interval from re-rendering sparklines/charts\n if (this.state.lastRender === null || Date.now() - this.state.lastRender >= 1000) {\n var renderTimestamp = Date.now();\n $('#scheduled-time-rate-sparkline').sparkline(this.state.scheduledTimeRate, $.extend({}, SMALL_SPARKLINE_PROPERTIES, {\n chartRangeMin: 0,\n numberFormatter: _utils.precisionRound\n }));\n $('#cpu-time-rate-sparkline').sparkline(this.state.cpuTimeRate, $.extend({}, SMALL_SPARKLINE_PROPERTIES, { chartRangeMin: 0, numberFormatter: _utils.precisionRound }));\n $('#row-input-rate-sparkline').sparkline(this.state.rowInputRate, $.extend({}, SMALL_SPARKLINE_PROPERTIES, { numberFormatter: _utils.formatCount }));\n $('#byte-input-rate-sparkline').sparkline(this.state.byteInputRate, $.extend({}, SMALL_SPARKLINE_PROPERTIES, { numberFormatter: _utils.formatDataSize }));\n $('#reserved-memory-sparkline').sparkline(this.state.reservedMemory, $.extend({}, SMALL_SPARKLINE_PROPERTIES, { numberFormatter: _utils.formatDataSize }));\n\n if (this.state.lastRender === null) {\n $('#query').each(function (i, block) {\n hljs.highlightBlock(block);\n });\n }\n\n this.setState({\n lastRender: renderTimestamp\n });\n }\n\n $('[data-toggle=\"tooltip\"]').tooltip();\n new Clipboard('.copy-button');\n }\n }, {\n key: \"renderStages\",\n value: function renderStages() {\n if (this.state.lastSnapshotStage === null) {\n return;\n }\n\n return _react2.default.createElement(\n \"div\",\n null,\n _react2.default.createElement(\n \"div\",\n { className: \"row\" },\n _react2.default.createElement(\n \"div\",\n { className: \"col-xs-9\" },\n _react2.default.createElement(\n \"h3\",\n null,\n \"Stages\"\n )\n ),\n _react2.default.createElement(\n \"div\",\n { className: \"col-xs-3\" },\n _react2.default.createElement(\n \"table\",\n { className: \"header-inline-links\" },\n _react2.default.createElement(\n \"tbody\",\n null,\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n null,\n this.renderStageRefreshButton()\n )\n )\n )\n )\n )\n ),\n _react2.default.createElement(\n \"div\",\n { className: \"row\" },\n _react2.default.createElement(\n \"div\",\n { className: \"col-xs-12\" },\n _react2.default.createElement(StageList, { key: this.state.query.queryId, outputStage: this.state.lastSnapshotStage })\n )\n )\n );\n }\n }, {\n key: \"renderSessionProperties\",\n value: function renderSessionProperties() {\n var query = this.state.query;\n\n var properties = [];\n for (var property in query.session.systemProperties) {\n if (query.session.systemProperties.hasOwnProperty(property)) {\n properties.push(_react2.default.createElement(\n \"span\",\n null,\n \"- \",\n property + \"=\" + query.session.systemProperties[property],\n \" \",\n _react2.default.createElement(\"br\", null)\n ));\n }\n }\n\n for (var catalog in query.session.catalogProperties) {\n if (query.session.catalogProperties.hasOwnProperty(catalog)) {\n for (var _property in query.session.catalogProperties[catalog]) {\n if (query.session.catalogProperties[catalog].hasOwnProperty(_property)) {\n properties.push(_react2.default.createElement(\n \"span\",\n null,\n \"- \",\n catalog + \".\" + _property + \"=\" + query.session.catalogProperties[catalog][_property],\n \" \",\n _react2.default.createElement(\"br\", null)\n ));\n }\n }\n }\n }\n\n return properties;\n }\n }, {\n key: \"renderResourceEstimates\",\n value: function renderResourceEstimates() {\n var query = this.state.query;\n var estimates = query.session.resourceEstimates;\n var renderedEstimates = [];\n\n for (var resource in estimates) {\n if (estimates.hasOwnProperty(resource)) {\n var upperChars = resource.match(/([A-Z])/g) || [];\n var snakeCased = resource;\n for (var i = 0, n = upperChars.length; i < n; i++) {\n snakeCased = snakeCased.replace(new RegExp(upperChars[i]), '_' + upperChars[i].toLowerCase());\n }\n\n renderedEstimates.push(_react2.default.createElement(\n \"span\",\n null,\n \"- \",\n snakeCased + \"=\" + query.session.resourceEstimates[resource],\n \" \",\n _react2.default.createElement(\"br\", null)\n ));\n }\n }\n\n return renderedEstimates;\n }\n }, {\n key: \"renderWarningInfo\",\n value: function renderWarningInfo() {\n var query = this.state.query;\n if (query.warnings.length > 0) {\n return _react2.default.createElement(\n \"div\",\n { className: \"row\" },\n _react2.default.createElement(\n \"div\",\n { className: \"col-xs-12\" },\n _react2.default.createElement(\n \"h3\",\n null,\n \"Warnings\"\n ),\n _react2.default.createElement(\"hr\", { className: \"h3-hr\" }),\n _react2.default.createElement(\n \"table\",\n { className: \"table\", id: \"warnings-table\" },\n query.warnings.map(function (warning) {\n return _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n null,\n warning.warningCode.name\n ),\n _react2.default.createElement(\n \"td\",\n null,\n warning.message\n )\n );\n })\n )\n )\n );\n } else {\n return null;\n }\n }\n }, {\n key: \"renderFailureInfo\",\n value: function renderFailureInfo() {\n var query = this.state.query;\n if (query.failureInfo) {\n return _react2.default.createElement(\n \"div\",\n { className: \"row\" },\n _react2.default.createElement(\n \"div\",\n { className: \"col-xs-12\" },\n _react2.default.createElement(\n \"h3\",\n null,\n \"Error Information\"\n ),\n _react2.default.createElement(\"hr\", { className: \"h3-hr\" }),\n _react2.default.createElement(\n \"table\",\n { className: \"table\" },\n _react2.default.createElement(\n \"tbody\",\n null,\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Error Type\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n query.errorType\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Error Code\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n query.errorCode.name + \" (\" + this.state.query.errorCode.code + \")\"\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Stack Trace\",\n _react2.default.createElement(\n \"a\",\n { className: \"btn copy-button\", \"data-clipboard-target\": \"#stack-trace\", \"data-toggle\": \"tooltip\", \"data-placement\": \"right\", title: \"Copy to clipboard\" },\n _react2.default.createElement(\"span\", { className: \"glyphicon glyphicon-copy\", \"aria-hidden\": \"true\", alt: \"Copy to clipboard\" })\n )\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n _react2.default.createElement(\n \"pre\",\n { id: \"stack-trace\" },\n QueryDetail.formatStackTrace(query.failureInfo)\n )\n )\n )\n )\n )\n )\n );\n } else {\n return \"\";\n }\n }\n }, {\n key: \"render\",\n value: function render() {\n var query = this.state.query;\n\n if (query === null || this.state.initialized === false) {\n var label = _react2.default.createElement(\n \"div\",\n { className: \"loader\" },\n \"Loading...\"\n );\n if (this.state.initialized) {\n label = \"Query not found\";\n }\n return _react2.default.createElement(\n \"div\",\n { className: \"row error-message\" },\n _react2.default.createElement(\n \"div\",\n { className: \"col-xs-12\" },\n _react2.default.createElement(\n \"h4\",\n null,\n label\n )\n )\n );\n }\n\n return _react2.default.createElement(\n \"div\",\n null,\n _react2.default.createElement(_QueryHeader.QueryHeader, { query: query }),\n _react2.default.createElement(\n \"div\",\n { className: \"row\" },\n _react2.default.createElement(\n \"div\",\n { className: \"col-xs-6\" },\n _react2.default.createElement(\n \"h3\",\n null,\n \"Session\"\n ),\n _react2.default.createElement(\"hr\", { className: \"h3-hr\" }),\n _react2.default.createElement(\n \"table\",\n { className: \"table\" },\n _react2.default.createElement(\n \"tbody\",\n null,\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"User\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text wrap-text\" },\n _react2.default.createElement(\n \"span\",\n { id: \"query-user\" },\n query.session.user\n ),\n \"\\xA0\\xA0\",\n _react2.default.createElement(\n \"a\",\n { href: \"#\", className: \"copy-button\", \"data-clipboard-target\": \"#query-user\", \"data-toggle\": \"tooltip\", \"data-placement\": \"right\", title: \"Copy to clipboard\" },\n _react2.default.createElement(\"span\", { className: \"glyphicon glyphicon-copy\", \"aria-hidden\": \"true\", alt: \"Copy to clipboard\" })\n )\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Principal\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text wrap-text\" },\n query.session.principal\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Source\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text wrap-text\" },\n query.session.source\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Catalog\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n query.session.catalog\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Schema\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n query.session.schema\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Client Address\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n query.session.remoteUserAddress\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Client Tags\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n query.session.clientTags.join(\", \")\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Session Properties\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text wrap-text\" },\n this.renderSessionProperties()\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Resource Estimates\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text wrap-text\" },\n this.renderResourceEstimates()\n )\n )\n )\n )\n ),\n _react2.default.createElement(\n \"div\",\n { className: \"col-xs-6\" },\n _react2.default.createElement(\n \"h3\",\n null,\n \"Execution\"\n ),\n _react2.default.createElement(\"hr\", { className: \"h3-hr\" }),\n _react2.default.createElement(\n \"table\",\n { className: \"table\" },\n _react2.default.createElement(\n \"tbody\",\n null,\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Resource Group\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text wrap-text\" },\n query.resourceGroupId ? query.resourceGroupId.join(\".\") : \"n/a\"\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Submission Time\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n (0, _utils.formatShortDateTime)(new Date(query.queryStats.createTime))\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Completion Time\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n query.queryStats.endTime ? (0, _utils.formatShortDateTime)(new Date(query.queryStats.endTime)) : \"\"\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Elapsed Time\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n query.queryStats.elapsedTime\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Queued Time\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n query.queryStats.queuedTime\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Planning Time\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n query.queryStats.totalPlanningTime\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Execution Time\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n query.queryStats.executionTime\n )\n )\n )\n )\n )\n ),\n _react2.default.createElement(\n \"div\",\n { className: \"row\" },\n _react2.default.createElement(\n \"div\",\n { className: \"col-xs-12\" },\n _react2.default.createElement(\n \"div\",\n { className: \"row\" },\n _react2.default.createElement(\n \"div\",\n { className: \"col-xs-6\" },\n _react2.default.createElement(\n \"h3\",\n null,\n \"Resource Utilization Summary\"\n ),\n _react2.default.createElement(\"hr\", { className: \"h3-hr\" }),\n _react2.default.createElement(\n \"table\",\n { className: \"table\" },\n _react2.default.createElement(\n \"tbody\",\n null,\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"CPU Time\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n query.queryStats.totalCpuTime\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Scheduled Time\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n query.queryStats.totalScheduledTime\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Blocked Time\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n query.queryStats.totalBlockedTime\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Input Rows\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n (0, _utils.formatCount)(query.queryStats.processedInputPositions)\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Input Data\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n query.queryStats.processedInputDataSize\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Raw Input Rows\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n (0, _utils.formatCount)(query.queryStats.rawInputPositions)\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Raw Input Data\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n query.queryStats.rawInputDataSize\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Peak User Memory\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n query.queryStats.peakUserMemoryReservation\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Peak Total Memory\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n query.queryStats.peakTotalMemoryReservation\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Memory Pool\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n query.memoryPool\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Cumulative User Memory\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n (0, _utils.formatDataSizeBytes)(query.queryStats.cumulativeUserMemory / 1000.0) + \" seconds\"\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Output Rows\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n (0, _utils.formatCount)(query.queryStats.outputPositions)\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Output Data\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n query.queryStats.outputDataSize\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Written Output Rows\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n (0, _utils.formatCount)(query.queryStats.writtenOutputPositions)\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Written Output Logical Data Size\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n query.queryStats.writtenOutputLogicalDataSize\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Written Output Physical Data Size\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n query.queryStats.writtenOutputPhysicalDataSize\n )\n ),\n (0, _utils.parseDataSize)(query.queryStats.spilledDataSize) > 0 && _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Spilled Data\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n query.queryStats.spilledDataSize\n )\n )\n )\n )\n ),\n _react2.default.createElement(\n \"div\",\n { className: \"col-xs-6\" },\n _react2.default.createElement(\n \"h3\",\n null,\n \"Timeline\"\n ),\n _react2.default.createElement(\"hr\", { className: \"h3-hr\" }),\n _react2.default.createElement(\n \"table\",\n { className: \"table\" },\n _react2.default.createElement(\n \"tbody\",\n null,\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Parallelism\"\n ),\n _react2.default.createElement(\n \"td\",\n { rowSpan: \"2\" },\n _react2.default.createElement(\n \"div\",\n { className: \"query-stats-sparkline-container\" },\n _react2.default.createElement(\n \"span\",\n { className: \"sparkline\", id: \"cpu-time-rate-sparkline\" },\n _react2.default.createElement(\n \"div\",\n { className: \"loader\" },\n \"Loading ...\"\n )\n )\n )\n )\n ),\n _react2.default.createElement(\n \"tr\",\n { className: \"tr-noborder\" },\n _react2.default.createElement(\n \"td\",\n { className: \"info-sparkline-text\" },\n (0, _utils.formatCount)(this.state.cpuTimeRate[this.state.cpuTimeRate.length - 1])\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Scheduled Time/s\"\n ),\n _react2.default.createElement(\n \"td\",\n { rowSpan: \"2\" },\n _react2.default.createElement(\n \"div\",\n { className: \"query-stats-sparkline-container\" },\n _react2.default.createElement(\n \"span\",\n { className: \"sparkline\", id: \"scheduled-time-rate-sparkline\" },\n _react2.default.createElement(\n \"div\",\n { className: \"loader\" },\n \"Loading ...\"\n )\n )\n )\n )\n ),\n _react2.default.createElement(\n \"tr\",\n { className: \"tr-noborder\" },\n _react2.default.createElement(\n \"td\",\n { className: \"info-sparkline-text\" },\n (0, _utils.formatCount)(this.state.scheduledTimeRate[this.state.scheduledTimeRate.length - 1])\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Input Rows/s\"\n ),\n _react2.default.createElement(\n \"td\",\n { rowSpan: \"2\" },\n _react2.default.createElement(\n \"div\",\n { className: \"query-stats-sparkline-container\" },\n _react2.default.createElement(\n \"span\",\n { className: \"sparkline\", id: \"row-input-rate-sparkline\" },\n _react2.default.createElement(\n \"div\",\n { className: \"loader\" },\n \"Loading ...\"\n )\n )\n )\n )\n ),\n _react2.default.createElement(\n \"tr\",\n { className: \"tr-noborder\" },\n _react2.default.createElement(\n \"td\",\n { className: \"info-sparkline-text\" },\n (0, _utils.formatCount)(this.state.rowInputRate[this.state.rowInputRate.length - 1])\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Input Bytes/s\"\n ),\n _react2.default.createElement(\n \"td\",\n { rowSpan: \"2\" },\n _react2.default.createElement(\n \"div\",\n { className: \"query-stats-sparkline-container\" },\n _react2.default.createElement(\n \"span\",\n { className: \"sparkline\", id: \"byte-input-rate-sparkline\" },\n _react2.default.createElement(\n \"div\",\n { className: \"loader\" },\n \"Loading ...\"\n )\n )\n )\n )\n ),\n _react2.default.createElement(\n \"tr\",\n { className: \"tr-noborder\" },\n _react2.default.createElement(\n \"td\",\n { className: \"info-sparkline-text\" },\n (0, _utils.formatDataSize)(this.state.byteInputRate[this.state.byteInputRate.length - 1])\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Memory Utilization\"\n ),\n _react2.default.createElement(\n \"td\",\n { rowSpan: \"2\" },\n _react2.default.createElement(\n \"div\",\n { className: \"query-stats-sparkline-container\" },\n _react2.default.createElement(\n \"span\",\n { className: \"sparkline\", id: \"reserved-memory-sparkline\" },\n _react2.default.createElement(\n \"div\",\n { className: \"loader\" },\n \"Loading ...\"\n )\n )\n )\n )\n ),\n _react2.default.createElement(\n \"tr\",\n { className: \"tr-noborder\" },\n _react2.default.createElement(\n \"td\",\n { className: \"info-sparkline-text\" },\n (0, _utils.formatDataSize)(this.state.reservedMemory[this.state.reservedMemory.length - 1])\n )\n )\n )\n )\n )\n )\n )\n ),\n this.renderWarningInfo(),\n this.renderFailureInfo(),\n _react2.default.createElement(\n \"div\",\n { className: \"row\" },\n _react2.default.createElement(\n \"div\",\n { className: \"col-xs-12\" },\n _react2.default.createElement(\n \"h3\",\n null,\n \"Query\",\n _react2.default.createElement(\n \"a\",\n { className: \"btn copy-button\", \"data-clipboard-target\": \"#query-text\", \"data-toggle\": \"tooltip\", \"data-placement\": \"right\", title: \"Copy to clipboard\" },\n _react2.default.createElement(\"span\", { className: \"glyphicon glyphicon-copy\", \"aria-hidden\": \"true\", alt: \"Copy to clipboard\" })\n )\n ),\n _react2.default.createElement(\n \"pre\",\n { id: \"query\" },\n _react2.default.createElement(\n \"code\",\n { className: \"lang-sql\", id: \"query-text\" },\n query.query\n )\n )\n )\n ),\n this.renderStages()\n );\n }\n }], [{\n key: \"formatStackTrace\",\n value: function formatStackTrace(info) {\n return QueryDetail.formatStackTraceHelper(info, [], \"\", \"\");\n }\n }, {\n key: \"formatStackTraceHelper\",\n value: function formatStackTraceHelper(info, parentStack, prefix, linePrefix) {\n var s = linePrefix + prefix + QueryDetail.failureInfoToString(info) + \"\\n\";\n\n if (info.stack) {\n var sharedStackFrames = 0;\n if (parentStack !== null) {\n sharedStackFrames = QueryDetail.countSharedStackFrames(info.stack, parentStack);\n }\n\n for (var i = 0; i < info.stack.length - sharedStackFrames; i++) {\n s += linePrefix + \"\\tat \" + info.stack[i] + \"\\n\";\n }\n if (sharedStackFrames !== 0) {\n s += linePrefix + \"\\t... \" + sharedStackFrames + \" more\" + \"\\n\";\n }\n }\n\n if (info.suppressed) {\n for (var _i3 = 0; _i3 < info.suppressed.length; _i3++) {\n s += QueryDetail.formatStackTraceHelper(info.suppressed[_i3], info.stack, \"Suppressed: \", linePrefix + \"\\t\");\n }\n }\n\n if (info.cause) {\n s += QueryDetail.formatStackTraceHelper(info.cause, info.stack, \"Caused by: \", linePrefix);\n }\n\n return s;\n }\n }, {\n key: \"countSharedStackFrames\",\n value: function countSharedStackFrames(stack, parentStack) {\n var n = 0;\n var minStackLength = Math.min(stack.length, parentStack.length);\n while (n < minStackLength && stack[stack.length - 1 - n] === parentStack[parentStack.length - 1 - n]) {\n n++;\n }\n return n;\n }\n }, {\n key: \"failureInfoToString\",\n value: function failureInfoToString(t) {\n return t.message !== null ? t.type + \": \" + t.message : t.type;\n }\n }]);\n\n return QueryDetail;\n}(_react2.default.Component);\n\n//# sourceURL=webpack:///./components/QueryDetail.jsx?"); +eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.QueryDetail = undefined;\n\nvar _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();\n\nvar _react = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n\nvar _react2 = _interopRequireDefault(_react);\n\nvar _reactable = __webpack_require__(/*! reactable */ \"./node_modules/reactable/lib/reactable.js\");\n\nvar _reactable2 = _interopRequireDefault(_reactable);\n\nvar _utils = __webpack_require__(/*! ../utils */ \"./utils.js\");\n\nvar _QueryHeader = __webpack_require__(/*! ./QueryHeader */ \"./components/QueryHeader.jsx\");\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return call && (typeof call === \"object\" || typeof call === \"function\") ? call : self; }\n\nfunction _inherits(subClass, superClass) { if (typeof superClass !== \"function\" && superClass !== null) { throw new TypeError(\"Super expression must either be null or a function, not \" + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /*\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nvar Table = _reactable2.default.Table,\n Thead = _reactable2.default.Thead,\n Th = _reactable2.default.Th,\n Tr = _reactable2.default.Tr,\n Td = _reactable2.default.Td;\n\nvar TaskList = function (_React$Component) {\n _inherits(TaskList, _React$Component);\n\n function TaskList() {\n _classCallCheck(this, TaskList);\n\n return _possibleConstructorReturn(this, (TaskList.__proto__ || Object.getPrototypeOf(TaskList)).apply(this, arguments));\n }\n\n _createClass(TaskList, [{\n key: \"render\",\n value: function render() {\n var tasks = this.props.tasks;\n\n if (tasks === undefined || tasks.length === 0) {\n return _react2.default.createElement(\n \"div\",\n { className: \"row error-message\" },\n _react2.default.createElement(\n \"div\",\n { className: \"col-xs-12\" },\n _react2.default.createElement(\n \"h4\",\n null,\n \"No threads in the selected group\"\n )\n )\n );\n }\n\n var showPortNumbers = TaskList.showPortNumbers(tasks);\n\n var renderedTasks = tasks.map(function (task) {\n var elapsedTime = (0, _utils.parseDuration)(task.stats.elapsedTimeInNanos + \"ns\");\n if (elapsedTime === 0) {\n elapsedTime = Date.now() - Date.parse(task.stats.createTime);\n }\n\n return _react2.default.createElement(\n Tr,\n { key: task.taskId },\n _react2.default.createElement(\n Td,\n { column: \"id\", value: task.taskId },\n _react2.default.createElement(\n \"a\",\n { href: \"/v1/taskInfo/\" + task.taskId + \"?pretty\" },\n (0, _utils.getTaskIdSuffix)(task.taskId)\n )\n ),\n _react2.default.createElement(\n Td,\n { column: \"host\", value: (0, _utils.getHostname)(task.taskStatus.self) },\n _react2.default.createElement(\n \"a\",\n { href: \"worker.html?\" + task.nodeId, className: \"font-light\", target: \"_blank\" },\n showPortNumbers ? (0, _utils.getHostAndPort)(task.taskStatus.self) : (0, _utils.getHostname)(task.taskStatus.self)\n )\n ),\n _react2.default.createElement(\n Td,\n { column: \"state\", value: TaskList.formatState(task.taskStatus.state, task.stats.fullyBlocked) },\n TaskList.formatState(task.taskStatus.state, task.stats.fullyBlocked)\n ),\n _react2.default.createElement(\n Td,\n { column: \"rows\", value: task.stats.rawInputPositions },\n (0, _utils.formatCount)(task.stats.rawInputPositions)\n ),\n _react2.default.createElement(\n Td,\n { column: \"rowsSec\", value: (0, _utils.computeRate)(task.stats.rawInputPositions, elapsedTime) },\n (0, _utils.formatCount)((0, _utils.computeRate)(task.stats.rawInputPositions, elapsedTime))\n ),\n _react2.default.createElement(\n Td,\n { column: \"bytes\", value: task.stats.rawInputDataSizeInBytes },\n (0, _utils.formatDataSizeBytes)(task.stats.rawInputDataSizeInBytes)\n ),\n _react2.default.createElement(\n Td,\n { column: \"bytesSec\", value: (0, _utils.computeRate)(task.stats.rawInputDataSizeInBytes, elapsedTime) },\n (0, _utils.formatDataSizeBytes)((0, _utils.computeRate)(task.stats.rawInputDataSizeInBytes, elapsedTime))\n ),\n _react2.default.createElement(\n Td,\n { column: \"splitsPending\", value: task.stats.queuedDrivers },\n task.stats.queuedDrivers\n ),\n _react2.default.createElement(\n Td,\n { column: \"splitsRunning\", value: task.stats.runningDrivers },\n task.stats.runningDrivers\n ),\n _react2.default.createElement(\n Td,\n { column: \"splitsBlocked\", value: task.stats.blockedDrivers },\n task.stats.blockedDrivers\n ),\n _react2.default.createElement(\n Td,\n { column: \"splitsDone\", value: task.stats.completedDrivers },\n task.stats.completedDrivers\n ),\n _react2.default.createElement(\n Td,\n { column: \"elapsedTime\", value: (0, _utils.parseDuration)(task.stats.elapsedTimeInNanos + \"ns\") },\n (0, _utils.formatDuration)((0, _utils.parseDuration)(task.stats.elapsedTimeInNanos + \"ns\"))\n ),\n _react2.default.createElement(\n Td,\n { column: \"cpuTime\", value: (0, _utils.parseDuration)(task.stats.totalCpuTimeInNanos + \"ns\") },\n (0, _utils.formatDuration)((0, _utils.parseDuration)(task.stats.totalCpuTimeInNanos + \"ns\"))\n ),\n _react2.default.createElement(\n Td,\n { column: \"bufferedBytes\", value: task.outputBuffers.totalBufferedBytes },\n (0, _utils.formatDataSizeBytes)(task.outputBuffers.totalBufferedBytes)\n )\n );\n });\n\n return _react2.default.createElement(\n Table,\n { id: \"tasks\", className: \"table table-striped sortable\", sortable: [{\n column: 'id',\n sortFunction: TaskList.compareTaskId\n }, 'host', 'state', 'splitsPending', 'splitsRunning', 'splitsBlocked', 'splitsDone', 'rows', 'rowsSec', 'bytes', 'bytesSec', 'elapsedTime', 'cpuTime', 'bufferedBytes'],\n defaultSort: { column: 'id', direction: 'asc' } },\n _react2.default.createElement(\n Thead,\n null,\n _react2.default.createElement(\n Th,\n { column: \"id\" },\n \"ID\"\n ),\n _react2.default.createElement(\n Th,\n { column: \"host\" },\n \"Host\"\n ),\n _react2.default.createElement(\n Th,\n { column: \"state\" },\n \"State\"\n ),\n _react2.default.createElement(\n Th,\n { column: \"splitsPending\" },\n _react2.default.createElement(\"span\", { className: \"glyphicon glyphicon-pause\", style: _utils.GLYPHICON_HIGHLIGHT,\n \"data-toggle\": \"tooltip\", \"data-placement\": \"top\",\n title: \"Pending splits\" })\n ),\n _react2.default.createElement(\n Th,\n { column: \"splitsRunning\" },\n _react2.default.createElement(\"span\", { className: \"glyphicon glyphicon-play\", style: _utils.GLYPHICON_HIGHLIGHT,\n \"data-toggle\": \"tooltip\", \"data-placement\": \"top\",\n title: \"Running splits\" })\n ),\n _react2.default.createElement(\n Th,\n { column: \"splitsBlocked\" },\n _react2.default.createElement(\"span\", { className: \"glyphicon glyphicon-bookmark\",\n style: _utils.GLYPHICON_HIGHLIGHT, \"data-toggle\": \"tooltip\",\n \"data-placement\": \"top\",\n title: \"Blocked splits\" })\n ),\n _react2.default.createElement(\n Th,\n { column: \"splitsDone\" },\n _react2.default.createElement(\"span\", { className: \"glyphicon glyphicon-ok\", style: _utils.GLYPHICON_HIGHLIGHT,\n \"data-toggle\": \"tooltip\", \"data-placement\": \"top\",\n title: \"Completed splits\" })\n ),\n _react2.default.createElement(\n Th,\n { column: \"rows\" },\n \"Rows\"\n ),\n _react2.default.createElement(\n Th,\n { column: \"rowsSec\" },\n \"Rows/s\"\n ),\n _react2.default.createElement(\n Th,\n { column: \"bytes\" },\n \"Bytes\"\n ),\n _react2.default.createElement(\n Th,\n { column: \"bytesSec\" },\n \"Bytes/s\"\n ),\n _react2.default.createElement(\n Th,\n { column: \"elapsedTime\" },\n \"Elapsed\"\n ),\n _react2.default.createElement(\n Th,\n { column: \"cpuTime\" },\n \"CPU Time\"\n ),\n _react2.default.createElement(\n Th,\n { column: \"bufferedBytes\" },\n \"Buffered\"\n )\n ),\n renderedTasks\n );\n }\n }], [{\n key: \"removeQueryId\",\n value: function removeQueryId(id) {\n var pos = id.indexOf('.');\n if (pos !== -1) {\n return id.substring(pos + 1);\n }\n return id;\n }\n }, {\n key: \"compareTaskId\",\n value: function compareTaskId(taskA, taskB) {\n var taskIdArrA = TaskList.removeQueryId(taskA).split(\".\");\n var taskIdArrB = TaskList.removeQueryId(taskB).split(\".\");\n\n if (taskIdArrA.length > taskIdArrB.length) {\n return 1;\n }\n for (var i = 0; i < taskIdArrA.length; i++) {\n var anum = Number.parseInt(taskIdArrA[i]);\n var bnum = Number.parseInt(taskIdArrB[i]);\n if (anum !== bnum) {\n return anum > bnum ? 1 : -1;\n }\n }\n\n return 0;\n }\n }, {\n key: \"showPortNumbers\",\n value: function showPortNumbers(tasks) {\n // check if any host has multiple port numbers\n var hostToPortNumber = {};\n for (var i = 0; i < tasks.length; i++) {\n var taskUri = tasks[i].taskStatus.self;\n var hostname = (0, _utils.getHostname)(taskUri);\n var port = (0, _utils.getPort)(taskUri);\n if (hostname in hostToPortNumber && hostToPortNumber[hostname] !== port) {\n return true;\n }\n hostToPortNumber[hostname] = port;\n }\n\n return false;\n }\n }, {\n key: \"formatState\",\n value: function formatState(state, fullyBlocked) {\n if (fullyBlocked && state === \"RUNNING\") {\n return \"BLOCKED\";\n } else {\n return state;\n }\n }\n }]);\n\n return TaskList;\n}(_react2.default.Component);\n\nvar BAR_CHART_WIDTH = 800;\n\nvar BAR_CHART_PROPERTIES = {\n type: 'bar',\n barSpacing: '0',\n height: '80px',\n barColor: '#747F96',\n zeroColor: '#8997B3',\n chartRangeMin: 0,\n tooltipClassname: 'sparkline-tooltip',\n tooltipFormat: 'Task {{offset:offset}} - {{value}}',\n disableHiddenCheck: true\n};\n\nvar HISTOGRAM_WIDTH = 175;\n\nvar HISTOGRAM_PROPERTIES = {\n type: 'bar',\n barSpacing: '0',\n height: '80px',\n barColor: '#747F96',\n zeroColor: '#747F96',\n zeroAxis: true,\n chartRangeMin: 0,\n tooltipClassname: 'sparkline-tooltip',\n tooltipFormat: '{{offset:offset}} -- {{value}} tasks',\n disableHiddenCheck: true\n};\n\nvar StageSummary = function (_React$Component2) {\n _inherits(StageSummary, _React$Component2);\n\n function StageSummary(props) {\n _classCallCheck(this, StageSummary);\n\n var _this2 = _possibleConstructorReturn(this, (StageSummary.__proto__ || Object.getPrototypeOf(StageSummary)).call(this, props));\n\n _this2.state = {\n expanded: false,\n lastRender: null,\n taskFilter: TASK_FILTER.ALL\n };\n return _this2;\n }\n\n _createClass(StageSummary, [{\n key: \"getExpandedIcon\",\n value: function getExpandedIcon() {\n return this.state.expanded ? \"glyphicon-chevron-up\" : \"glyphicon-chevron-down\";\n }\n }, {\n key: \"getExpandedStyle\",\n value: function getExpandedStyle() {\n return this.state.expanded ? {} : { display: \"none\" };\n }\n }, {\n key: \"toggleExpanded\",\n value: function toggleExpanded() {\n this.setState({\n expanded: !this.state.expanded\n });\n }\n }, {\n key: \"componentDidUpdate\",\n value: function componentDidUpdate() {\n var stage = this.props.stage;\n var numTasks = stage.latestAttemptExecutionInfo.tasks.length;\n\n // sort the x-axis\n stage.latestAttemptExecutionInfo.tasks.sort(function (taskA, taskB) {\n return (0, _utils.getTaskNumber)(taskA.taskId) - (0, _utils.getTaskNumber)(taskB.taskId);\n });\n\n var scheduledTimes = stage.latestAttemptExecutionInfo.tasks.map(function (task) {\n return (0, _utils.parseDuration)(task.stats.totalScheduledTimeInNanos + \"ns\");\n });\n var cpuTimes = stage.latestAttemptExecutionInfo.tasks.map(function (task) {\n return (0, _utils.parseDuration)(task.stats.totalCpuTimeInNanos + \"ns\");\n });\n\n // prevent multiple calls to componentDidUpdate (resulting from calls to setState or otherwise) within the refresh interval from re-rendering sparklines/charts\n if (this.state.lastRender === null || Date.now() - this.state.lastRender >= 1000) {\n var renderTimestamp = Date.now();\n var stageId = (0, _utils.getStageNumber)(stage.stageId);\n\n StageSummary.renderHistogram('#scheduled-time-histogram-' + stageId, scheduledTimes, _utils.formatDuration);\n StageSummary.renderHistogram('#cpu-time-histogram-' + stageId, cpuTimes, _utils.formatDuration);\n\n if (this.state.expanded) {\n // this needs to be a string otherwise it will also be passed to numberFormatter\n var tooltipValueLookups = { 'offset': {} };\n for (var i = 0; i < numTasks; i++) {\n tooltipValueLookups['offset'][i] = (0, _utils.getStageNumber)(stage.stageId) + \".\" + i;\n }\n\n var stageBarChartProperties = $.extend({}, BAR_CHART_PROPERTIES, { barWidth: BAR_CHART_WIDTH / numTasks, tooltipValueLookups: tooltipValueLookups });\n\n $('#scheduled-time-bar-chart-' + stageId).sparkline(scheduledTimes, $.extend({}, stageBarChartProperties, { numberFormatter: _utils.formatDuration }));\n $('#cpu-time-bar-chart-' + stageId).sparkline(cpuTimes, $.extend({}, stageBarChartProperties, { numberFormatter: _utils.formatDuration }));\n }\n\n this.setState({\n lastRender: renderTimestamp\n });\n }\n }\n }, {\n key: \"renderStageExecutionAttemptsTasks\",\n value: function renderStageExecutionAttemptsTasks(attempts) {\n var _this3 = this;\n\n return attempts.map(function (attempt) {\n return _this3.renderTaskList(attempt.tasks);\n });\n }\n }, {\n key: \"renderTaskList\",\n value: function renderTaskList(tasks) {\n var _this4 = this;\n\n tasks = this.state.expanded ? tasks : [];\n tasks = tasks.filter(function (task) {\n return _this4.state.taskFilter.predicate(task.taskStatus.state);\n }, this);\n return _react2.default.createElement(\n \"tr\",\n { style: this.getExpandedStyle() },\n _react2.default.createElement(\n \"td\",\n { colSpan: \"6\" },\n _react2.default.createElement(TaskList, { tasks: tasks })\n )\n );\n }\n }, {\n key: \"renderTaskFilterListItem\",\n value: function renderTaskFilterListItem(taskFilter) {\n return _react2.default.createElement(\n \"li\",\n null,\n _react2.default.createElement(\n \"a\",\n { href: \"#\", className: this.state.taskFilter === taskFilter ? \"selected\" : \"\",\n onClick: this.handleTaskFilterClick.bind(this, taskFilter) },\n taskFilter.text\n )\n );\n }\n }, {\n key: \"handleTaskFilterClick\",\n value: function handleTaskFilterClick(filter, event) {\n this.setState({\n taskFilter: filter\n });\n event.preventDefault();\n }\n }, {\n key: \"renderTaskFilter\",\n value: function renderTaskFilter() {\n return _react2.default.createElement(\n \"div\",\n { className: \"row\" },\n _react2.default.createElement(\n \"div\",\n { className: \"col-xs-6\" },\n _react2.default.createElement(\n \"h3\",\n null,\n \"Tasks\"\n )\n ),\n _react2.default.createElement(\n \"div\",\n { className: \"col-xs-6\" },\n _react2.default.createElement(\n \"table\",\n { className: \"header-inline-links\" },\n _react2.default.createElement(\n \"tbody\",\n null,\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n null,\n _react2.default.createElement(\n \"div\",\n { className: \"input-group-btn text-right\" },\n _react2.default.createElement(\n \"button\",\n { type: \"button\", className: \"btn btn-default dropdown-toggle pull-right text-right\",\n \"data-toggle\": \"dropdown\", \"aria-haspopup\": \"true\",\n \"aria-expanded\": \"false\" },\n \"Show: \",\n this.state.taskFilter.text,\n \" \",\n _react2.default.createElement(\"span\", { className: \"caret\" })\n ),\n _react2.default.createElement(\n \"ul\",\n { className: \"dropdown-menu\" },\n this.renderTaskFilterListItem(TASK_FILTER.ALL),\n this.renderTaskFilterListItem(TASK_FILTER.PLANNED),\n this.renderTaskFilterListItem(TASK_FILTER.RUNNING),\n this.renderTaskFilterListItem(TASK_FILTER.FINISHED),\n this.renderTaskFilterListItem(TASK_FILTER.FAILED)\n )\n )\n )\n )\n )\n )\n )\n );\n }\n }, {\n key: \"render\",\n value: function render() {\n var stage = this.props.stage;\n if (stage === undefined || !stage.hasOwnProperty('plan')) {\n return _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n null,\n \"Information about this stage is unavailable.\"\n )\n );\n }\n\n var totalBufferedBytes = stage.latestAttemptExecutionInfo.tasks.map(function (task) {\n return task.outputBuffers.totalBufferedBytes;\n }).reduce(function (a, b) {\n return a + b;\n }, 0);\n\n var stageId = (0, _utils.getStageNumber)(stage.stageId);\n\n return _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"stage-id\" },\n _react2.default.createElement(\n \"div\",\n { className: \"stage-state-color\", style: { borderLeftColor: (0, _utils.getStageStateColor)(stage) } },\n stageId\n )\n ),\n _react2.default.createElement(\n \"td\",\n null,\n _react2.default.createElement(\n \"table\",\n { className: \"table single-stage-table\" },\n _react2.default.createElement(\n \"tbody\",\n null,\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n null,\n _react2.default.createElement(\n \"table\",\n { className: \"stage-table stage-table-time\" },\n _react2.default.createElement(\n \"thead\",\n null,\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"th\",\n { className: \"stage-table-stat-title stage-table-stat-header\" },\n \"Time\"\n ),\n _react2.default.createElement(\"th\", null)\n )\n ),\n _react2.default.createElement(\n \"tbody\",\n null,\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"stage-table-stat-title\" },\n \"Scheduled\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"stage-table-stat-text\" },\n stage.latestAttemptExecutionInfo.stats.totalScheduledTime\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"stage-table-stat-title\" },\n \"Blocked\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"stage-table-stat-text\" },\n stage.latestAttemptExecutionInfo.stats.totalBlockedTime\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"stage-table-stat-title\" },\n \"CPU\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"stage-table-stat-text\" },\n stage.latestAttemptExecutionInfo.stats.totalCpuTime\n )\n )\n )\n )\n ),\n _react2.default.createElement(\n \"td\",\n null,\n _react2.default.createElement(\n \"table\",\n { className: \"stage-table stage-table-memory\" },\n _react2.default.createElement(\n \"thead\",\n null,\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"th\",\n { className: \"stage-table-stat-title stage-table-stat-header\" },\n \"Memory\"\n ),\n _react2.default.createElement(\"th\", null)\n )\n ),\n _react2.default.createElement(\n \"tbody\",\n null,\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"stage-table-stat-title\" },\n \"Cumulative\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"stage-table-stat-text\" },\n (0, _utils.formatDataSizeBytes)(stage.latestAttemptExecutionInfo.stats.cumulativeUserMemory / 1000)\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"stage-table-stat-title\" },\n \"Current\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"stage-table-stat-text\" },\n stage.latestAttemptExecutionInfo.stats.userMemoryReservation\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"stage-table-stat-title\" },\n \"Buffers\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"stage-table-stat-text\" },\n (0, _utils.formatDataSize)(totalBufferedBytes)\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"stage-table-stat-title\" },\n \"Peak\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"stage-table-stat-text\" },\n stage.latestAttemptExecutionInfo.stats.peakUserMemoryReservation\n )\n )\n )\n )\n ),\n _react2.default.createElement(\n \"td\",\n null,\n _react2.default.createElement(\n \"table\",\n { className: \"stage-table stage-table-tasks\" },\n _react2.default.createElement(\n \"thead\",\n null,\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"th\",\n { className: \"stage-table-stat-title stage-table-stat-header\" },\n \"Tasks\"\n ),\n _react2.default.createElement(\"th\", null)\n )\n ),\n _react2.default.createElement(\n \"tbody\",\n null,\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"stage-table-stat-title\" },\n \"Pending\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"stage-table-stat-text\" },\n stage.latestAttemptExecutionInfo.tasks.filter(function (task) {\n return task.taskStatus.state === \"PLANNED\";\n }).length\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"stage-table-stat-title\" },\n \"Running\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"stage-table-stat-text\" },\n stage.latestAttemptExecutionInfo.tasks.filter(function (task) {\n return task.taskStatus.state === \"RUNNING\";\n }).length\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"stage-table-stat-title\" },\n \"Blocked\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"stage-table-stat-text\" },\n stage.latestAttemptExecutionInfo.tasks.filter(function (task) {\n return task.stats.fullyBlocked;\n }).length\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"stage-table-stat-title\" },\n \"Total\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"stage-table-stat-text\" },\n stage.latestAttemptExecutionInfo.tasks.length\n )\n )\n )\n )\n ),\n _react2.default.createElement(\n \"td\",\n null,\n _react2.default.createElement(\n \"table\",\n { className: \"stage-table histogram-table\" },\n _react2.default.createElement(\n \"thead\",\n null,\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"th\",\n { className: \"stage-table-stat-title stage-table-chart-header\" },\n \"Scheduled Time Skew\"\n )\n )\n ),\n _react2.default.createElement(\n \"tbody\",\n null,\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"histogram-container\" },\n _react2.default.createElement(\n \"span\",\n { className: \"histogram\", id: \"scheduled-time-histogram-\" + stageId },\n _react2.default.createElement(\"div\", { className: \"loader\" })\n )\n )\n )\n )\n )\n ),\n _react2.default.createElement(\n \"td\",\n null,\n _react2.default.createElement(\n \"table\",\n { className: \"stage-table histogram-table\" },\n _react2.default.createElement(\n \"thead\",\n null,\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"th\",\n { className: \"stage-table-stat-title stage-table-chart-header\" },\n \"CPU Time Skew\"\n )\n )\n ),\n _react2.default.createElement(\n \"tbody\",\n null,\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"histogram-container\" },\n _react2.default.createElement(\n \"span\",\n { className: \"histogram\", id: \"cpu-time-histogram-\" + stageId },\n _react2.default.createElement(\"div\", { className: \"loader\" })\n )\n )\n )\n )\n )\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"expand-charts-container\" },\n _react2.default.createElement(\n \"a\",\n { onClick: this.toggleExpanded.bind(this), className: \"expand-charts-button\" },\n _react2.default.createElement(\"span\", { className: \"glyphicon \" + this.getExpandedIcon(), style: _utils.GLYPHICON_HIGHLIGHT, \"data-toggle\": \"tooltip\", \"data-placement\": \"top\", title: \"More\" })\n )\n )\n ),\n _react2.default.createElement(\n \"tr\",\n { style: this.getExpandedStyle() },\n _react2.default.createElement(\n \"td\",\n { colSpan: \"6\" },\n _react2.default.createElement(\n \"table\",\n { className: \"expanded-chart\" },\n _react2.default.createElement(\n \"tbody\",\n null,\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"stage-table-stat-title expanded-chart-title\" },\n \"Task Scheduled Time\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"bar-chart-container\" },\n _react2.default.createElement(\n \"span\",\n { className: \"bar-chart\", id: \"scheduled-time-bar-chart-\" + stageId },\n _react2.default.createElement(\"div\", { className: \"loader\" })\n )\n )\n )\n )\n )\n )\n ),\n _react2.default.createElement(\n \"tr\",\n { style: this.getExpandedStyle() },\n _react2.default.createElement(\n \"td\",\n { colSpan: \"6\" },\n _react2.default.createElement(\n \"table\",\n { className: \"expanded-chart\" },\n _react2.default.createElement(\n \"tbody\",\n null,\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"stage-table-stat-title expanded-chart-title\" },\n \"Task CPU Time\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"bar-chart-container\" },\n _react2.default.createElement(\n \"span\",\n { className: \"bar-chart\", id: \"cpu-time-bar-chart-\" + stageId },\n _react2.default.createElement(\"div\", { className: \"loader\" })\n )\n )\n )\n )\n )\n )\n ),\n _react2.default.createElement(\n \"tr\",\n { style: this.getExpandedStyle() },\n _react2.default.createElement(\n \"td\",\n { colSpan: \"6\" },\n this.renderTaskFilter()\n )\n ),\n this.renderStageExecutionAttemptsTasks([stage.latestAttemptExecutionInfo]),\n this.renderStageExecutionAttemptsTasks(stage.previousAttemptsExecutionInfos)\n )\n )\n )\n );\n }\n }], [{\n key: \"renderHistogram\",\n value: function renderHistogram(histogramId, inputData, numberFormatter) {\n var numBuckets = Math.min(HISTOGRAM_WIDTH, Math.sqrt(inputData.length));\n var dataMin = Math.min.apply(null, inputData);\n var dataMax = Math.max.apply(null, inputData);\n var bucketSize = (dataMax - dataMin) / numBuckets;\n\n var histogramData = [];\n if (bucketSize === 0) {\n histogramData = [inputData.length];\n } else {\n for (var i = 0; i < numBuckets + 1; i++) {\n histogramData.push(0);\n }\n\n for (var _i in inputData) {\n var dataPoint = inputData[_i];\n var bucket = Math.floor((dataPoint - dataMin) / bucketSize);\n histogramData[bucket] = histogramData[bucket] + 1;\n }\n }\n\n var tooltipValueLookups = { 'offset': {} };\n for (var _i2 = 0; _i2 < histogramData.length; _i2++) {\n tooltipValueLookups['offset'][_i2] = numberFormatter(dataMin + _i2 * bucketSize) + \"-\" + numberFormatter(dataMin + (_i2 + 1) * bucketSize);\n }\n\n var stageHistogramProperties = $.extend({}, HISTOGRAM_PROPERTIES, { barWidth: HISTOGRAM_WIDTH / histogramData.length, tooltipValueLookups: tooltipValueLookups });\n $(histogramId).sparkline(histogramData, stageHistogramProperties);\n }\n }]);\n\n return StageSummary;\n}(_react2.default.Component);\n\nvar StageList = function (_React$Component3) {\n _inherits(StageList, _React$Component3);\n\n function StageList() {\n _classCallCheck(this, StageList);\n\n return _possibleConstructorReturn(this, (StageList.__proto__ || Object.getPrototypeOf(StageList)).apply(this, arguments));\n }\n\n _createClass(StageList, [{\n key: \"getStages\",\n value: function getStages(stage) {\n if (stage === undefined || !stage.hasOwnProperty('subStages')) {\n return [];\n }\n\n return [].concat.apply(stage, stage.subStages.map(this.getStages, this));\n }\n }, {\n key: \"render\",\n value: function render() {\n var stages = this.getStages(this.props.outputStage);\n\n if (stages === undefined || stages.length === 0) {\n return _react2.default.createElement(\n \"div\",\n { className: \"row\" },\n _react2.default.createElement(\n \"div\",\n { className: \"col-xs-12\" },\n \"No stage information available.\"\n )\n );\n }\n\n var renderedStages = stages.map(function (stage) {\n return _react2.default.createElement(StageSummary, { key: stage.stageId, stage: stage });\n });\n\n return _react2.default.createElement(\n \"div\",\n { className: \"row\" },\n _react2.default.createElement(\n \"div\",\n { className: \"col-xs-12\" },\n _react2.default.createElement(\n \"table\",\n { className: \"table\", id: \"stage-list\" },\n _react2.default.createElement(\n \"tbody\",\n null,\n renderedStages\n )\n )\n )\n );\n }\n }]);\n\n return StageList;\n}(_react2.default.Component);\n\nvar SMALL_SPARKLINE_PROPERTIES = {\n width: '100%',\n height: '57px',\n fillColor: '#3F4552',\n lineColor: '#747F96',\n spotColor: '#1EDCFF',\n tooltipClassname: 'sparkline-tooltip',\n disableHiddenCheck: true\n};\n\nvar TASK_FILTER = {\n ALL: {\n text: \"All\",\n predicate: function predicate() {\n return true;\n }\n },\n PLANNED: {\n text: \"Planned\",\n predicate: function predicate(state) {\n return state === 'PLANNED';\n }\n },\n RUNNING: {\n text: \"Running\",\n predicate: function predicate(state) {\n return state === 'RUNNING';\n }\n },\n FINISHED: {\n text: \"Finished\",\n predicate: function predicate(state) {\n return state === 'FINISHED';\n }\n },\n FAILED: {\n text: \"Aborted/Canceled/Failed\",\n predicate: function predicate(state) {\n return state === 'FAILED' || state === 'ABORTED' || state === 'CANCELED';\n }\n }\n};\n\nvar QueryDetail = exports.QueryDetail = function (_React$Component4) {\n _inherits(QueryDetail, _React$Component4);\n\n function QueryDetail(props) {\n _classCallCheck(this, QueryDetail);\n\n var _this6 = _possibleConstructorReturn(this, (QueryDetail.__proto__ || Object.getPrototypeOf(QueryDetail)).call(this, props));\n\n _this6.state = {\n query: null,\n lastSnapshotStages: null,\n\n lastScheduledTime: 0,\n lastCpuTime: 0,\n lastRowInput: 0,\n lastByteInput: 0,\n\n scheduledTimeRate: [],\n cpuTimeRate: [],\n rowInputRate: [],\n byteInputRate: [],\n\n reservedMemory: [],\n\n initialized: false,\n ended: false,\n\n lastRefresh: null,\n lastRender: null,\n\n stageRefresh: true\n };\n\n _this6.refreshLoop = _this6.refreshLoop.bind(_this6);\n return _this6;\n }\n\n _createClass(QueryDetail, [{\n key: \"resetTimer\",\n value: function resetTimer() {\n clearTimeout(this.timeoutId);\n // stop refreshing when query finishes or fails\n if (this.state.query === null || !this.state.ended) {\n // task.info-update-interval is set to 3 seconds by default\n this.timeoutId = setTimeout(this.refreshLoop, 3000);\n }\n }\n }, {\n key: \"refreshLoop\",\n value: function refreshLoop() {\n var _this7 = this;\n\n clearTimeout(this.timeoutId); // to stop multiple series of refreshLoop from going on simultaneously\n var queryId = (0, _utils.getFirstParameter)(window.location.search);\n $.get('/v1/query/' + queryId, function (query) {\n var lastSnapshotStages = this.state.lastSnapshotStage;\n if (this.state.stageRefresh) {\n lastSnapshotStages = query.outputStage;\n }\n\n var lastRefresh = this.state.lastRefresh;\n var lastScheduledTime = this.state.lastScheduledTime;\n var lastCpuTime = this.state.lastCpuTime;\n var lastRowInput = this.state.lastRowInput;\n var lastByteInput = this.state.lastByteInput;\n var alreadyEnded = this.state.ended;\n var nowMillis = Date.now();\n\n this.setState({\n query: query,\n lastSnapshotStage: lastSnapshotStages,\n\n lastScheduledTime: (0, _utils.parseDuration)(query.queryStats.totalScheduledTime),\n lastCpuTime: (0, _utils.parseDuration)(query.queryStats.totalCpuTime),\n lastRowInput: query.queryStats.processedInputPositions,\n lastByteInput: (0, _utils.parseDataSize)(query.queryStats.processedInputDataSize),\n\n initialized: true,\n ended: query.finalQueryInfo,\n\n lastRefresh: nowMillis\n });\n\n // i.e. don't show sparklines if we've already decided not to update or if we don't have one previous measurement\n if (alreadyEnded || lastRefresh === null && query.state === \"RUNNING\") {\n this.resetTimer();\n return;\n }\n\n if (lastRefresh === null) {\n lastRefresh = nowMillis - (0, _utils.parseDuration)(query.queryStats.elapsedTime);\n }\n\n var elapsedSecsSinceLastRefresh = (nowMillis - lastRefresh) / 1000.0;\n if (elapsedSecsSinceLastRefresh >= 0) {\n var currentScheduledTimeRate = ((0, _utils.parseDuration)(query.queryStats.totalScheduledTime) - lastScheduledTime) / (elapsedSecsSinceLastRefresh * 1000);\n var currentCpuTimeRate = ((0, _utils.parseDuration)(query.queryStats.totalCpuTime) - lastCpuTime) / (elapsedSecsSinceLastRefresh * 1000);\n var currentRowInputRate = (query.queryStats.processedInputPositions - lastRowInput) / elapsedSecsSinceLastRefresh;\n var currentByteInputRate = ((0, _utils.parseDataSize)(query.queryStats.processedInputDataSize) - lastByteInput) / elapsedSecsSinceLastRefresh;\n this.setState({\n scheduledTimeRate: (0, _utils.addToHistory)(currentScheduledTimeRate, this.state.scheduledTimeRate),\n cpuTimeRate: (0, _utils.addToHistory)(currentCpuTimeRate, this.state.cpuTimeRate),\n rowInputRate: (0, _utils.addToHistory)(currentRowInputRate, this.state.rowInputRate),\n byteInputRate: (0, _utils.addToHistory)(currentByteInputRate, this.state.byteInputRate),\n reservedMemory: (0, _utils.addToHistory)((0, _utils.parseDataSize)(query.queryStats.userMemoryReservation), this.state.reservedMemory)\n });\n }\n this.resetTimer();\n }.bind(this)).error(function () {\n _this7.setState({\n initialized: true\n });\n _this7.resetTimer();\n });\n }\n }, {\n key: \"handleStageRefreshClick\",\n value: function handleStageRefreshClick() {\n if (this.state.stageRefresh) {\n this.setState({\n stageRefresh: false,\n lastSnapshotStages: this.state.query.outputStage\n });\n } else {\n this.setState({\n stageRefresh: true\n });\n }\n }\n }, {\n key: \"renderStageRefreshButton\",\n value: function renderStageRefreshButton() {\n if (this.state.stageRefresh) {\n return _react2.default.createElement(\n \"button\",\n { className: \"btn btn-info live-button\", onClick: this.handleStageRefreshClick.bind(this) },\n \"Auto-Refresh: On\"\n );\n } else {\n return _react2.default.createElement(\n \"button\",\n { className: \"btn btn-info live-button\", onClick: this.handleStageRefreshClick.bind(this) },\n \"Auto-Refresh: Off\"\n );\n }\n }\n }, {\n key: \"componentDidMount\",\n value: function componentDidMount() {\n this.refreshLoop();\n }\n }, {\n key: \"componentDidUpdate\",\n value: function componentDidUpdate() {\n // prevent multiple calls to componentDidUpdate (resulting from calls to setState or otherwise) within the refresh interval from re-rendering sparklines/charts\n if (this.state.lastRender === null || Date.now() - this.state.lastRender >= 1000) {\n var renderTimestamp = Date.now();\n $('#scheduled-time-rate-sparkline').sparkline(this.state.scheduledTimeRate, $.extend({}, SMALL_SPARKLINE_PROPERTIES, {\n chartRangeMin: 0,\n numberFormatter: _utils.precisionRound\n }));\n $('#cpu-time-rate-sparkline').sparkline(this.state.cpuTimeRate, $.extend({}, SMALL_SPARKLINE_PROPERTIES, { chartRangeMin: 0, numberFormatter: _utils.precisionRound }));\n $('#row-input-rate-sparkline').sparkline(this.state.rowInputRate, $.extend({}, SMALL_SPARKLINE_PROPERTIES, { numberFormatter: _utils.formatCount }));\n $('#byte-input-rate-sparkline').sparkline(this.state.byteInputRate, $.extend({}, SMALL_SPARKLINE_PROPERTIES, { numberFormatter: _utils.formatDataSize }));\n $('#reserved-memory-sparkline').sparkline(this.state.reservedMemory, $.extend({}, SMALL_SPARKLINE_PROPERTIES, { numberFormatter: _utils.formatDataSize }));\n\n if (this.state.lastRender === null) {\n $('#query').each(function (i, block) {\n hljs.highlightBlock(block);\n });\n }\n\n this.setState({\n lastRender: renderTimestamp\n });\n }\n\n $('[data-toggle=\"tooltip\"]').tooltip();\n new Clipboard('.copy-button');\n }\n }, {\n key: \"renderStages\",\n value: function renderStages() {\n if (this.state.lastSnapshotStage === null) {\n return;\n }\n\n return _react2.default.createElement(\n \"div\",\n null,\n _react2.default.createElement(\n \"div\",\n { className: \"row\" },\n _react2.default.createElement(\n \"div\",\n { className: \"col-xs-9\" },\n _react2.default.createElement(\n \"h3\",\n null,\n \"Stages\"\n )\n ),\n _react2.default.createElement(\n \"div\",\n { className: \"col-xs-3\" },\n _react2.default.createElement(\n \"table\",\n { className: \"header-inline-links\" },\n _react2.default.createElement(\n \"tbody\",\n null,\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n null,\n this.renderStageRefreshButton()\n )\n )\n )\n )\n )\n ),\n _react2.default.createElement(\n \"div\",\n { className: \"row\" },\n _react2.default.createElement(\n \"div\",\n { className: \"col-xs-12\" },\n _react2.default.createElement(StageList, { key: this.state.query.queryId, outputStage: this.state.lastSnapshotStage })\n )\n )\n );\n }\n }, {\n key: \"renderSessionProperties\",\n value: function renderSessionProperties() {\n var query = this.state.query;\n\n var properties = [];\n for (var property in query.session.systemProperties) {\n if (query.session.systemProperties.hasOwnProperty(property)) {\n properties.push(_react2.default.createElement(\n \"span\",\n null,\n \"- \",\n property + \"=\" + query.session.systemProperties[property],\n \" \",\n _react2.default.createElement(\"br\", null)\n ));\n }\n }\n\n for (var catalog in query.session.catalogProperties) {\n if (query.session.catalogProperties.hasOwnProperty(catalog)) {\n for (var _property in query.session.catalogProperties[catalog]) {\n if (query.session.catalogProperties[catalog].hasOwnProperty(_property)) {\n properties.push(_react2.default.createElement(\n \"span\",\n null,\n \"- \",\n catalog + \".\" + _property + \"=\" + query.session.catalogProperties[catalog][_property],\n \" \",\n _react2.default.createElement(\"br\", null)\n ));\n }\n }\n }\n }\n\n return properties;\n }\n }, {\n key: \"renderResourceEstimates\",\n value: function renderResourceEstimates() {\n var query = this.state.query;\n var estimates = query.session.resourceEstimates;\n var renderedEstimates = [];\n\n for (var resource in estimates) {\n if (estimates.hasOwnProperty(resource)) {\n var upperChars = resource.match(/([A-Z])/g) || [];\n var snakeCased = resource;\n for (var i = 0, n = upperChars.length; i < n; i++) {\n snakeCased = snakeCased.replace(new RegExp(upperChars[i]), '_' + upperChars[i].toLowerCase());\n }\n\n renderedEstimates.push(_react2.default.createElement(\n \"span\",\n null,\n \"- \",\n snakeCased + \"=\" + query.session.resourceEstimates[resource],\n \" \",\n _react2.default.createElement(\"br\", null)\n ));\n }\n }\n\n return renderedEstimates;\n }\n }, {\n key: \"renderWarningInfo\",\n value: function renderWarningInfo() {\n var query = this.state.query;\n if (query.warnings.length > 0) {\n return _react2.default.createElement(\n \"div\",\n { className: \"row\" },\n _react2.default.createElement(\n \"div\",\n { className: \"col-xs-12\" },\n _react2.default.createElement(\n \"h3\",\n null,\n \"Warnings\"\n ),\n _react2.default.createElement(\"hr\", { className: \"h3-hr\" }),\n _react2.default.createElement(\n \"table\",\n { className: \"table\", id: \"warnings-table\" },\n query.warnings.map(function (warning) {\n return _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n null,\n warning.warningCode.name\n ),\n _react2.default.createElement(\n \"td\",\n null,\n warning.message\n )\n );\n })\n )\n )\n );\n } else {\n return null;\n }\n }\n }, {\n key: \"renderFailureInfo\",\n value: function renderFailureInfo() {\n var query = this.state.query;\n if (query.failureInfo) {\n return _react2.default.createElement(\n \"div\",\n { className: \"row\" },\n _react2.default.createElement(\n \"div\",\n { className: \"col-xs-12\" },\n _react2.default.createElement(\n \"h3\",\n null,\n \"Error Information\"\n ),\n _react2.default.createElement(\"hr\", { className: \"h3-hr\" }),\n _react2.default.createElement(\n \"table\",\n { className: \"table\" },\n _react2.default.createElement(\n \"tbody\",\n null,\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Error Type\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n query.errorType\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Error Code\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n query.errorCode.name + \" (\" + this.state.query.errorCode.code + \")\"\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Stack Trace\",\n _react2.default.createElement(\n \"a\",\n { className: \"btn copy-button\", \"data-clipboard-target\": \"#stack-trace\", \"data-toggle\": \"tooltip\", \"data-placement\": \"right\", title: \"Copy to clipboard\" },\n _react2.default.createElement(\"span\", { className: \"glyphicon glyphicon-copy\", \"aria-hidden\": \"true\", alt: \"Copy to clipboard\" })\n )\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n _react2.default.createElement(\n \"pre\",\n { id: \"stack-trace\" },\n QueryDetail.formatStackTrace(query.failureInfo)\n )\n )\n )\n )\n )\n )\n );\n } else {\n return \"\";\n }\n }\n }, {\n key: \"render\",\n value: function render() {\n var query = this.state.query;\n\n if (query === null || this.state.initialized === false) {\n var label = _react2.default.createElement(\n \"div\",\n { className: \"loader\" },\n \"Loading...\"\n );\n if (this.state.initialized) {\n label = \"Query not found\";\n }\n return _react2.default.createElement(\n \"div\",\n { className: \"row error-message\" },\n _react2.default.createElement(\n \"div\",\n { className: \"col-xs-12\" },\n _react2.default.createElement(\n \"h4\",\n null,\n label\n )\n )\n );\n }\n\n return _react2.default.createElement(\n \"div\",\n null,\n _react2.default.createElement(_QueryHeader.QueryHeader, { query: query }),\n _react2.default.createElement(\n \"div\",\n { className: \"row\" },\n _react2.default.createElement(\n \"div\",\n { className: \"col-xs-6\" },\n _react2.default.createElement(\n \"h3\",\n null,\n \"Session\"\n ),\n _react2.default.createElement(\"hr\", { className: \"h3-hr\" }),\n _react2.default.createElement(\n \"table\",\n { className: \"table\" },\n _react2.default.createElement(\n \"tbody\",\n null,\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"User\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text wrap-text\" },\n _react2.default.createElement(\n \"span\",\n { id: \"query-user\" },\n query.session.user\n ),\n \"\\xA0\\xA0\",\n _react2.default.createElement(\n \"a\",\n { href: \"#\", className: \"copy-button\", \"data-clipboard-target\": \"#query-user\", \"data-toggle\": \"tooltip\", \"data-placement\": \"right\", title: \"Copy to clipboard\" },\n _react2.default.createElement(\"span\", { className: \"glyphicon glyphicon-copy\", \"aria-hidden\": \"true\", alt: \"Copy to clipboard\" })\n )\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Principal\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text wrap-text\" },\n query.session.principal\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Source\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text wrap-text\" },\n query.session.source\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Catalog\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n query.session.catalog\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Schema\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n query.session.schema\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Client Address\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n query.session.remoteUserAddress\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Client Tags\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n query.session.clientTags.join(\", \")\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Session Properties\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text wrap-text\" },\n this.renderSessionProperties()\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Resource Estimates\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text wrap-text\" },\n this.renderResourceEstimates()\n )\n )\n )\n )\n ),\n _react2.default.createElement(\n \"div\",\n { className: \"col-xs-6\" },\n _react2.default.createElement(\n \"h3\",\n null,\n \"Execution\"\n ),\n _react2.default.createElement(\"hr\", { className: \"h3-hr\" }),\n _react2.default.createElement(\n \"table\",\n { className: \"table\" },\n _react2.default.createElement(\n \"tbody\",\n null,\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Resource Group\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text wrap-text\" },\n query.resourceGroupId ? query.resourceGroupId.join(\".\") : \"n/a\"\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Submission Time\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n (0, _utils.formatShortDateTime)(new Date(query.queryStats.createTime))\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Completion Time\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n query.queryStats.endTime ? (0, _utils.formatShortDateTime)(new Date(query.queryStats.endTime)) : \"\"\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Elapsed Time\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n query.queryStats.elapsedTime\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Prerequisites Wait Time\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n query.queryStats.waitingForPrerequisitesTime\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Queued Time\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n query.queryStats.queuedTime\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Planning Time\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n query.queryStats.totalPlanningTime\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Execution Time\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n query.queryStats.executionTime\n )\n )\n )\n )\n )\n ),\n _react2.default.createElement(\n \"div\",\n { className: \"row\" },\n _react2.default.createElement(\n \"div\",\n { className: \"col-xs-12\" },\n _react2.default.createElement(\n \"div\",\n { className: \"row\" },\n _react2.default.createElement(\n \"div\",\n { className: \"col-xs-6\" },\n _react2.default.createElement(\n \"h3\",\n null,\n \"Resource Utilization Summary\"\n ),\n _react2.default.createElement(\"hr\", { className: \"h3-hr\" }),\n _react2.default.createElement(\n \"table\",\n { className: \"table\" },\n _react2.default.createElement(\n \"tbody\",\n null,\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"CPU Time\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n query.queryStats.totalCpuTime\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Scheduled Time\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n query.queryStats.totalScheduledTime\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Blocked Time\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n query.queryStats.totalBlockedTime\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Input Rows\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n (0, _utils.formatCount)(query.queryStats.processedInputPositions)\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Input Data\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n query.queryStats.processedInputDataSize\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Raw Input Rows\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n (0, _utils.formatCount)(query.queryStats.rawInputPositions)\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Raw Input Data\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n query.queryStats.rawInputDataSize\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Peak User Memory\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n query.queryStats.peakUserMemoryReservation\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Peak Total Memory\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n query.queryStats.peakTotalMemoryReservation\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Memory Pool\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n query.memoryPool\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Cumulative User Memory\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n (0, _utils.formatDataSizeBytes)(query.queryStats.cumulativeUserMemory / 1000.0) + \" seconds\"\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Output Rows\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n (0, _utils.formatCount)(query.queryStats.outputPositions)\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Output Data\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n query.queryStats.outputDataSize\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Written Output Rows\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n (0, _utils.formatCount)(query.queryStats.writtenOutputPositions)\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Written Output Logical Data Size\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n query.queryStats.writtenOutputLogicalDataSize\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Written Output Physical Data Size\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n query.queryStats.writtenOutputPhysicalDataSize\n )\n ),\n (0, _utils.parseDataSize)(query.queryStats.spilledDataSize) > 0 && _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Spilled Data\"\n ),\n _react2.default.createElement(\n \"td\",\n { className: \"info-text\" },\n query.queryStats.spilledDataSize\n )\n )\n )\n )\n ),\n _react2.default.createElement(\n \"div\",\n { className: \"col-xs-6\" },\n _react2.default.createElement(\n \"h3\",\n null,\n \"Timeline\"\n ),\n _react2.default.createElement(\"hr\", { className: \"h3-hr\" }),\n _react2.default.createElement(\n \"table\",\n { className: \"table\" },\n _react2.default.createElement(\n \"tbody\",\n null,\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Parallelism\"\n ),\n _react2.default.createElement(\n \"td\",\n { rowSpan: \"2\" },\n _react2.default.createElement(\n \"div\",\n { className: \"query-stats-sparkline-container\" },\n _react2.default.createElement(\n \"span\",\n { className: \"sparkline\", id: \"cpu-time-rate-sparkline\" },\n _react2.default.createElement(\n \"div\",\n { className: \"loader\" },\n \"Loading ...\"\n )\n )\n )\n )\n ),\n _react2.default.createElement(\n \"tr\",\n { className: \"tr-noborder\" },\n _react2.default.createElement(\n \"td\",\n { className: \"info-sparkline-text\" },\n (0, _utils.formatCount)(this.state.cpuTimeRate[this.state.cpuTimeRate.length - 1])\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Scheduled Time/s\"\n ),\n _react2.default.createElement(\n \"td\",\n { rowSpan: \"2\" },\n _react2.default.createElement(\n \"div\",\n { className: \"query-stats-sparkline-container\" },\n _react2.default.createElement(\n \"span\",\n { className: \"sparkline\", id: \"scheduled-time-rate-sparkline\" },\n _react2.default.createElement(\n \"div\",\n { className: \"loader\" },\n \"Loading ...\"\n )\n )\n )\n )\n ),\n _react2.default.createElement(\n \"tr\",\n { className: \"tr-noborder\" },\n _react2.default.createElement(\n \"td\",\n { className: \"info-sparkline-text\" },\n (0, _utils.formatCount)(this.state.scheduledTimeRate[this.state.scheduledTimeRate.length - 1])\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Input Rows/s\"\n ),\n _react2.default.createElement(\n \"td\",\n { rowSpan: \"2\" },\n _react2.default.createElement(\n \"div\",\n { className: \"query-stats-sparkline-container\" },\n _react2.default.createElement(\n \"span\",\n { className: \"sparkline\", id: \"row-input-rate-sparkline\" },\n _react2.default.createElement(\n \"div\",\n { className: \"loader\" },\n \"Loading ...\"\n )\n )\n )\n )\n ),\n _react2.default.createElement(\n \"tr\",\n { className: \"tr-noborder\" },\n _react2.default.createElement(\n \"td\",\n { className: \"info-sparkline-text\" },\n (0, _utils.formatCount)(this.state.rowInputRate[this.state.rowInputRate.length - 1])\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Input Bytes/s\"\n ),\n _react2.default.createElement(\n \"td\",\n { rowSpan: \"2\" },\n _react2.default.createElement(\n \"div\",\n { className: \"query-stats-sparkline-container\" },\n _react2.default.createElement(\n \"span\",\n { className: \"sparkline\", id: \"byte-input-rate-sparkline\" },\n _react2.default.createElement(\n \"div\",\n { className: \"loader\" },\n \"Loading ...\"\n )\n )\n )\n )\n ),\n _react2.default.createElement(\n \"tr\",\n { className: \"tr-noborder\" },\n _react2.default.createElement(\n \"td\",\n { className: \"info-sparkline-text\" },\n (0, _utils.formatDataSize)(this.state.byteInputRate[this.state.byteInputRate.length - 1])\n )\n ),\n _react2.default.createElement(\n \"tr\",\n null,\n _react2.default.createElement(\n \"td\",\n { className: \"info-title\" },\n \"Memory Utilization\"\n ),\n _react2.default.createElement(\n \"td\",\n { rowSpan: \"2\" },\n _react2.default.createElement(\n \"div\",\n { className: \"query-stats-sparkline-container\" },\n _react2.default.createElement(\n \"span\",\n { className: \"sparkline\", id: \"reserved-memory-sparkline\" },\n _react2.default.createElement(\n \"div\",\n { className: \"loader\" },\n \"Loading ...\"\n )\n )\n )\n )\n ),\n _react2.default.createElement(\n \"tr\",\n { className: \"tr-noborder\" },\n _react2.default.createElement(\n \"td\",\n { className: \"info-sparkline-text\" },\n (0, _utils.formatDataSize)(this.state.reservedMemory[this.state.reservedMemory.length - 1])\n )\n )\n )\n )\n )\n )\n )\n ),\n this.renderWarningInfo(),\n this.renderFailureInfo(),\n _react2.default.createElement(\n \"div\",\n { className: \"row\" },\n _react2.default.createElement(\n \"div\",\n { className: \"col-xs-12\" },\n _react2.default.createElement(\n \"h3\",\n null,\n \"Query\",\n _react2.default.createElement(\n \"a\",\n { className: \"btn copy-button\", \"data-clipboard-target\": \"#query-text\", \"data-toggle\": \"tooltip\", \"data-placement\": \"right\", title: \"Copy to clipboard\" },\n _react2.default.createElement(\"span\", { className: \"glyphicon glyphicon-copy\", \"aria-hidden\": \"true\", alt: \"Copy to clipboard\" })\n )\n ),\n _react2.default.createElement(\n \"pre\",\n { id: \"query\" },\n _react2.default.createElement(\n \"code\",\n { className: \"lang-sql\", id: \"query-text\" },\n query.query\n )\n )\n )\n ),\n this.renderStages()\n );\n }\n }], [{\n key: \"formatStackTrace\",\n value: function formatStackTrace(info) {\n return QueryDetail.formatStackTraceHelper(info, [], \"\", \"\");\n }\n }, {\n key: \"formatStackTraceHelper\",\n value: function formatStackTraceHelper(info, parentStack, prefix, linePrefix) {\n var s = linePrefix + prefix + QueryDetail.failureInfoToString(info) + \"\\n\";\n\n if (info.stack) {\n var sharedStackFrames = 0;\n if (parentStack !== null) {\n sharedStackFrames = QueryDetail.countSharedStackFrames(info.stack, parentStack);\n }\n\n for (var i = 0; i < info.stack.length - sharedStackFrames; i++) {\n s += linePrefix + \"\\tat \" + info.stack[i] + \"\\n\";\n }\n if (sharedStackFrames !== 0) {\n s += linePrefix + \"\\t... \" + sharedStackFrames + \" more\" + \"\\n\";\n }\n }\n\n if (info.suppressed) {\n for (var _i3 = 0; _i3 < info.suppressed.length; _i3++) {\n s += QueryDetail.formatStackTraceHelper(info.suppressed[_i3], info.stack, \"Suppressed: \", linePrefix + \"\\t\");\n }\n }\n\n if (info.cause) {\n s += QueryDetail.formatStackTraceHelper(info.cause, info.stack, \"Caused by: \", linePrefix);\n }\n\n return s;\n }\n }, {\n key: \"countSharedStackFrames\",\n value: function countSharedStackFrames(stack, parentStack) {\n var n = 0;\n var minStackLength = Math.min(stack.length, parentStack.length);\n while (n < minStackLength && stack[stack.length - 1 - n] === parentStack[parentStack.length - 1 - n]) {\n n++;\n }\n return n;\n }\n }, {\n key: \"failureInfoToString\",\n value: function failureInfoToString(t) {\n return t.message !== null ? t.type + \": \" + t.message : t.type;\n }\n }]);\n\n return QueryDetail;\n}(_react2.default.Component);\n\n//# sourceURL=webpack:///./components/QueryDetail.jsx?"); /***/ }), diff --git a/presto-main/src/main/resources/webapp/src/components/QueryDetail.jsx b/presto-main/src/main/resources/webapp/src/components/QueryDetail.jsx index 7d628d0d1bc8d..e8b8d71826ba2 100644 --- a/presto-main/src/main/resources/webapp/src/components/QueryDetail.jsx +++ b/presto-main/src/main/resources/webapp/src/components/QueryDetail.jsx @@ -1228,6 +1228,14 @@ export class QueryDetail extends React.Component { {query.queryStats.elapsedTime} + + + Prerequisites Wait Time + + + {query.queryStats.waitingForPrerequisitesTime} + + Queued Time diff --git a/presto-main/src/test/java/com/facebook/presto/dispatcher/TestLocalDispatchQuery.java b/presto-main/src/test/java/com/facebook/presto/dispatcher/TestLocalDispatchQuery.java index 346f53acee6d2..1292304fb8156 100644 --- a/presto-main/src/test/java/com/facebook/presto/dispatcher/TestLocalDispatchQuery.java +++ b/presto-main/src/test/java/com/facebook/presto/dispatcher/TestLocalDispatchQuery.java @@ -20,6 +20,7 @@ import com.facebook.presto.eventlistener.EventListenerManager; import com.facebook.presto.execution.ClusterSizeMonitor; import com.facebook.presto.execution.ExecutionFailureInfo; +import com.facebook.presto.execution.QueryExecution; import com.facebook.presto.execution.QueryStateMachine; import com.facebook.presto.execution.StageInfo; import com.facebook.presto.execution.resourceGroups.QueryQueueFullException; @@ -27,20 +28,27 @@ import com.facebook.presto.metadata.MetadataManager; import com.facebook.presto.metadata.SessionPropertyManager; import com.facebook.presto.operator.OperatorInfo; +import com.facebook.presto.spi.PrestoException; +import com.facebook.presto.spi.QueryId; import com.facebook.presto.spi.eventlistener.EventListener; import com.facebook.presto.spi.eventlistener.EventListenerFactory; import com.facebook.presto.spi.eventlistener.QueryCompletedEvent; import com.facebook.presto.spi.eventlistener.QueryCreatedEvent; import com.facebook.presto.spi.eventlistener.SplitCompletedEvent; +import com.facebook.presto.spi.prerequisites.QueryPrerequisites; +import com.facebook.presto.spi.prerequisites.QueryPrerequisitesContext; import com.facebook.presto.spi.resourceGroups.ResourceGroupId; import com.facebook.presto.spi.security.AccessDeniedException; import com.facebook.presto.transaction.TransactionManager; import com.google.common.collect.ImmutableMap; +import com.google.common.util.concurrent.SettableFuture; import io.airlift.units.Duration; import org.testng.annotations.Test; import java.util.Map; import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import static com.facebook.airlift.json.JsonCodec.jsonCodec; @@ -48,7 +56,10 @@ import static com.facebook.presto.execution.QueryState.DISPATCHING; import static com.facebook.presto.execution.QueryState.FAILED; import static com.facebook.presto.execution.QueryState.QUEUED; +import static com.facebook.presto.execution.QueryState.WAITING_FOR_PREREQUISITES; import static com.facebook.presto.execution.TaskTestUtils.createQueryStateMachine; +import static com.facebook.presto.spi.StandardErrorCode.ABANDONED_QUERY; +import static com.facebook.presto.spi.StandardErrorCode.ABANDONED_TASK; import static com.facebook.presto.spi.StandardErrorCode.GENERIC_INSUFFICIENT_RESOURCES; import static com.facebook.presto.spi.StandardErrorCode.GENERIC_INTERNAL_ERROR; import static com.facebook.presto.spi.StandardErrorCode.PERMISSION_DENIED; @@ -71,6 +82,7 @@ public class TestLocalDispatchQuery { + private static final QueryPrerequisites QUERY_PREREQUISITES = new DefaultQueryPrerequisites(); private final MetadataManager metadata = MetadataManager.createTestMetadataManager(); @Test @@ -84,8 +96,10 @@ public void testSimpleExecutionCreationFailure() immediateFailedFuture(new IllegalStateException("abc")), createClusterSizeMonitor(0), directExecutor(), + dispatchQuery -> {}, execution -> {}, - false); + false, + QUERY_PREREQUISITES); assertEquals(query.getBasicQueryInfo().getState(), FAILED); assertEquals(query.getBasicQueryInfo().getErrorCode(), GENERIC_INTERNAL_ERROR.toErrorCode()); @@ -98,21 +112,203 @@ public void testSimpleExecutionCreationFailure() public void testQueryQueuedExceptionBeforeDispatch() { QueryStateMachine stateMachine = createStateMachine(); - stateMachine.transitionToFailed(new QueryQueueFullException(new ResourceGroupId("global"))); CountingEventListener eventListener = new CountingEventListener(); + SettableFuture queryExecutionFuture = SettableFuture.create(); + LocalDispatchQuery query = new LocalDispatchQuery( stateMachine, createQueryMonitor(eventListener), - immediateFailedFuture(new IllegalStateException("abc")), + queryExecutionFuture, createClusterSizeMonitor(0), directExecutor(), + dispatchQuery -> { + throw new QueryQueueFullException(new ResourceGroupId("global")); + }, execution -> {}, - false); + false, + QUERY_PREREQUISITES); + + query.startWaitingForPrerequisites(); + queryExecutionFuture.setException(new IllegalStateException("abc")); assertEquals(query.getBasicQueryInfo().getState(), FAILED); assertEquals(query.getBasicQueryInfo().getErrorCode(), QUERY_QUEUE_FULL.toErrorCode()); + assertTrue(eventListener.getQueryCompletedEvent().isPresent()); + assertTrue(eventListener.getQueryCompletedEvent().get().getFailureInfo().isPresent()); + assertEquals(eventListener.getQueryCompletedEvent().get().getFailureInfo().get().getErrorCode(), QUERY_QUEUE_FULL.toErrorCode()); + } + + @Test + public void testErrorInPrerequisitesFuture() + { + QueryStateMachine stateMachine = createStateMachine(); + CountingEventListener eventListener = new CountingEventListener(); + + LocalDispatchQuery query = new LocalDispatchQuery( + stateMachine, + createQueryMonitor(eventListener), + immediateFuture(null), + createClusterSizeMonitor(0), + directExecutor(), + dispatchQuery -> {}, + execution -> { + throw new AccessDeniedException("sdf"); + }, + false, + (queryId, context) -> { + CompletableFuture future = new CompletableFuture<>(); + future.completeExceptionally(new PrestoException(ABANDONED_TASK, "something went wrong")); + return future; + }); + + assertEquals(query.getBasicQueryInfo().getState(), WAITING_FOR_PREREQUISITES); assertFalse(eventListener.getQueryCompletedEvent().isPresent()); + + query.startWaitingForPrerequisites(); + assertEquals(query.getBasicQueryInfo().getState(), FAILED); + assertEquals(query.getBasicQueryInfo().getErrorCode(), ABANDONED_TASK.toErrorCode()); + assertTrue(eventListener.getQueryCompletedEvent().isPresent()); + assertTrue(eventListener.getQueryCompletedEvent().get().getFailureInfo().isPresent()); + assertEquals(eventListener.getQueryCompletedEvent().get().getFailureInfo().get().getErrorCode(), ABANDONED_TASK.toErrorCode()); + } + + @Test + public void testErrorInPrerequisitesSubmission() + { + QueryStateMachine stateMachine = createStateMachine(); + CountingEventListener eventListener = new CountingEventListener(); + + LocalDispatchQuery query = new LocalDispatchQuery( + stateMachine, + createQueryMonitor(eventListener), + immediateFuture(null), + createClusterSizeMonitor(0), + directExecutor(), + dispatchQuery -> {}, + execution -> { + throw new AccessDeniedException("sdf"); + }, + false, + (queryId, context) -> { + throw new PrestoException(ABANDONED_QUERY, "something went wrong"); + }); + + assertEquals(query.getBasicQueryInfo().getState(), WAITING_FOR_PREREQUISITES); + assertFalse(eventListener.getQueryCompletedEvent().isPresent()); + + try { + query.startWaitingForPrerequisites(); + fail("Exception should be thrown"); + } + catch (Throwable t) { + assertEquals(query.getBasicQueryInfo().getState(), FAILED); + assertEquals(query.getBasicQueryInfo().getErrorCode(), ABANDONED_QUERY.toErrorCode()); + assertTrue(eventListener.getQueryCompletedEvent().isPresent()); + assertTrue(eventListener.getQueryCompletedEvent().get().getFailureInfo().isPresent()); + assertEquals(eventListener.getQueryCompletedEvent().get().getFailureInfo().get().getErrorCode(), ABANDONED_QUERY.toErrorCode()); + } + } + + @Test + public void testPrerequisitesQueryFinishedCalled() + { + QueryStateMachine stateMachine = createStateMachine(); + CountingEventListener eventListener = new CountingEventListener(); + CompletableFuture prequisitesFuture = new CompletableFuture<>(); + AtomicBoolean queryFinishedCalled = new AtomicBoolean(); + + LocalDispatchQuery query = new LocalDispatchQuery( + stateMachine, + createQueryMonitor(eventListener), + immediateFuture(null), + createClusterSizeMonitor(0), + directExecutor(), + dispatchQuery -> {}, + execution -> {}, + false, + new QueryPrerequisites() { + @Override + public CompletableFuture waitForPrerequisites(QueryId queryId, QueryPrerequisitesContext context) + { + return prequisitesFuture; + } + + @Override + public void queryFinished(QueryId queryId) + { + queryFinishedCalled.set(true); + } + }); + + assertEquals(query.getBasicQueryInfo().getState(), WAITING_FOR_PREREQUISITES); + assertFalse(eventListener.getQueryCompletedEvent().isPresent()); + + query.startWaitingForPrerequisites(); + prequisitesFuture.complete(null); + query.fail(new PrestoException(ABANDONED_QUERY, "foo")); + + assertTrue(queryFinishedCalled.get()); + } + + @Test + public void testPrerequisiteFutureCancellationWhenQueryCancelled() + { + QueryStateMachine stateMachine = createStateMachine(); + CountingEventListener eventListener = new CountingEventListener(); + CompletableFuture prequisitesFuture = new CompletableFuture<>(); + + LocalDispatchQuery query = new LocalDispatchQuery( + stateMachine, + createQueryMonitor(eventListener), + immediateFuture(null), + createClusterSizeMonitor(0), + directExecutor(), + dispatchQuery -> {}, + execution -> {}, + false, + (queryId, context) -> prequisitesFuture); + + assertEquals(query.getBasicQueryInfo().getState(), WAITING_FOR_PREREQUISITES); + assertFalse(eventListener.getQueryCompletedEvent().isPresent()); + + query.startWaitingForPrerequisites(); + query.fail(new PrestoException(ABANDONED_QUERY, "foo")); + + assertTrue(prequisitesFuture.isCancelled()); + } + + @Test + public void testQueryQueueSubmission() + { + QueryStateMachine stateMachine = createStateMachine(); + CountingEventListener eventListener = new CountingEventListener(); + AtomicBoolean queryQueuerCalled = new AtomicBoolean(); + CompletableFuture prerequisitesFuture = new CompletableFuture<>(); + + LocalDispatchQuery query = new LocalDispatchQuery( + stateMachine, + createQueryMonitor(eventListener), + immediateFuture(null), + createClusterSizeMonitor(0), + directExecutor(), + dispatchQuery -> { + queryQueuerCalled.compareAndSet(false, true); + }, + execution -> {}, + false, + (queryId, context) -> prerequisitesFuture); + + assertEquals(stateMachine.getBasicQueryInfo(Optional.empty()).getState(), WAITING_FOR_PREREQUISITES); + query.startWaitingForPrerequisites(); + + assertEquals(stateMachine.getBasicQueryInfo(Optional.empty()).getState(), WAITING_FOR_PREREQUISITES); + assertFalse(queryQueuerCalled.get()); + + prerequisitesFuture.complete(null); + + assertEquals(stateMachine.getBasicQueryInfo(Optional.empty()).getState(), QUEUED); + assertTrue(queryQueuerCalled.get()); } @Test @@ -127,12 +323,14 @@ public void testErrorInQuerySubmitter() immediateFuture(null), createClusterSizeMonitor(0), directExecutor(), + dispatchQuery -> {}, execution -> { throw new AccessDeniedException("sdf"); }, - false); + false, + QUERY_PREREQUISITES); - assertEquals(query.getBasicQueryInfo().getState(), QUEUED); + assertEquals(query.getBasicQueryInfo().getState(), WAITING_FOR_PREREQUISITES); assertFalse(eventListener.getQueryCompletedEvent().isPresent()); query.startWaitingForResources(); @@ -157,10 +355,12 @@ public void testTimeOutWaitingForClusterResources() immediateFuture(null), createClusterSizeMonitor(1), directExecutor(), + dispatchQuery -> {}, execution -> {}, - false); + false, + QUERY_PREREQUISITES); - assertEquals(query.getBasicQueryInfo().getState(), QUEUED); + assertEquals(query.getBasicQueryInfo().getState(), WAITING_FOR_PREREQUISITES); assertFalse(eventListener.getQueryCompletedEvent().isPresent()); query.startWaitingForResources(); @@ -186,10 +386,12 @@ public void testQueryCancellation() immediateFuture(null), createClusterSizeMonitor(0), directExecutor(), + dispatchQuery -> {}, execution -> {}, - false); + false, + QUERY_PREREQUISITES); - assertEquals(query.getBasicQueryInfo().getState(), QUEUED); + assertEquals(query.getBasicQueryInfo().getState(), WAITING_FOR_PREREQUISITES); assertFalse(eventListener.getQueryCompletedEvent().isPresent()); query.cancel(); @@ -213,10 +415,12 @@ public void testQueryDispatched() immediateFuture(null), createClusterSizeMonitor(0), directExecutor(), + dispatchQuery -> {}, execution -> {}, - false); + false, + QUERY_PREREQUISITES); - assertEquals(query.getBasicQueryInfo().getState(), QUEUED); + assertEquals(query.getBasicQueryInfo().getState(), WAITING_FOR_PREREQUISITES); assertFalse(eventListener.getQueryCompletedEvent().isPresent()); query.startWaitingForResources(); diff --git a/presto-main/src/test/java/com/facebook/presto/execution/MockManagedQueryExecution.java b/presto-main/src/test/java/com/facebook/presto/execution/MockManagedQueryExecution.java index e9e7be867cce3..834265406aecd 100644 --- a/presto-main/src/test/java/com/facebook/presto/execution/MockManagedQueryExecution.java +++ b/presto-main/src/test/java/com/facebook/presto/execution/MockManagedQueryExecution.java @@ -37,6 +37,7 @@ import static com.facebook.presto.execution.QueryState.FINISHED; import static com.facebook.presto.execution.QueryState.QUEUED; import static com.facebook.presto.execution.QueryState.RUNNING; +import static com.facebook.presto.execution.QueryState.WAITING_FOR_PREREQUISITES; import static com.facebook.presto.testing.TestingSession.testSessionBuilder; import static io.airlift.units.DataSize.Unit.BYTE; import static io.airlift.units.DataSize.succinctBytes; @@ -50,7 +51,7 @@ public class MockManagedQueryExecution private final DataSize memoryUsage; private final Duration cpuUsage; private final Session session; - private QueryState state = QUEUED; + private QueryState state = WAITING_FOR_PREREQUISITES; private Throwable failureCause; public MockManagedQueryExecution(long memoryUsage) @@ -116,6 +117,7 @@ public BasicQueryInfo getBasicQueryInfo() new BasicQueryStats( new DateTime(1), new DateTime(2), + new Duration(2, NANOSECONDS), new Duration(3, NANOSECONDS), new Duration(4, NANOSECONDS), new Duration(5, NANOSECONDS), @@ -167,6 +169,13 @@ public QueryState getState() return state; } + @Override + public void startWaitingForPrerequisites() + { + state = QUEUED; + fireStateChange(); + } + @Override public void startWaitingForResources() { diff --git a/presto-main/src/test/java/com/facebook/presto/execution/TestQueryStateMachine.java b/presto-main/src/test/java/com/facebook/presto/execution/TestQueryStateMachine.java index 8c787596f0fc3..a3556b70d288c 100644 --- a/presto-main/src/test/java/com/facebook/presto/execution/TestQueryStateMachine.java +++ b/presto-main/src/test/java/com/facebook/presto/execution/TestQueryStateMachine.java @@ -61,6 +61,7 @@ import static com.facebook.presto.execution.QueryState.QUEUED; import static com.facebook.presto.execution.QueryState.RUNNING; import static com.facebook.presto.execution.QueryState.STARTING; +import static com.facebook.presto.execution.QueryState.WAITING_FOR_PREREQUISITES; import static com.facebook.presto.execution.QueryState.WAITING_FOR_RESOURCES; import static com.facebook.presto.spi.StandardErrorCode.GENERIC_INTERNAL_ERROR; import static com.facebook.presto.spi.StandardErrorCode.USER_CANCELED; @@ -106,6 +107,9 @@ public void tearDown() public void testBasicStateChanges() { QueryStateMachine stateMachine = createQueryStateMachine(); + assertState(stateMachine, WAITING_FOR_PREREQUISITES); + + assertTrue(stateMachine.transitionToQueued()); assertState(stateMachine, QUEUED); assertTrue(stateMachine.transitionToDispatching()); @@ -129,6 +133,9 @@ public void testBasicStateChanges() public void testStateChangesWithResourceWaiting() { QueryStateMachine stateMachine = createQueryStateMachine(); + assertState(stateMachine, WAITING_FOR_PREREQUISITES); + + assertTrue(stateMachine.transitionToQueued()); assertState(stateMachine, QUEUED); assertTrue(stateMachine.transitionToWaitingForResources()); @@ -152,25 +159,45 @@ public void testStateChangesWithResourceWaiting() } @Test - public void testQueued() + public void testWaitingForPrerequisites() { // all time before the first state transition is accounted to queueing - assertAllTimeSpentInQueueing(QUEUED, queryStateMachine -> {}); - assertAllTimeSpentInQueueing(WAITING_FOR_RESOURCES, QueryStateMachine::transitionToWaitingForResources); - assertAllTimeSpentInQueueing(DISPATCHING, QueryStateMachine::transitionToDispatching); - assertAllTimeSpentInQueueing(PLANNING, QueryStateMachine::transitionToPlanning); - assertAllTimeSpentInQueueing(STARTING, QueryStateMachine::transitionToStarting); - assertAllTimeSpentInQueueing(RUNNING, QueryStateMachine::transitionToRunning); - - assertAllTimeSpentInQueueing(FINISHED, stateMachine -> { + assertAllTimeSpentInWaitingForPrerequisites(WAITING_FOR_PREREQUISITES, queryStateMachine -> {}); + assertAllTimeSpentInWaitingForPrerequisites(QUEUED, QueryStateMachine::transitionToQueued); + assertAllTimeSpentInWaitingForPrerequisites(WAITING_FOR_RESOURCES, QueryStateMachine::transitionToWaitingForResources); + assertAllTimeSpentInWaitingForPrerequisites(DISPATCHING, QueryStateMachine::transitionToDispatching); + assertAllTimeSpentInWaitingForPrerequisites(PLANNING, QueryStateMachine::transitionToPlanning); + assertAllTimeSpentInWaitingForPrerequisites(STARTING, QueryStateMachine::transitionToStarting); + assertAllTimeSpentInWaitingForPrerequisites(RUNNING, QueryStateMachine::transitionToRunning); + + assertAllTimeSpentInWaitingForPrerequisites(FINISHED, stateMachine -> { stateMachine.transitionToFinishing(); tryGetFutureValue(stateMachine.getStateChange(FINISHING), 2, SECONDS); }); - assertAllTimeSpentInQueueing(FAILED, stateMachine -> stateMachine.transitionToFailed(FAILED_CAUSE)); + assertAllTimeSpentInWaitingForPrerequisites(FAILED, stateMachine -> stateMachine.transitionToFailed(FAILED_CAUSE)); + } + + @Test + public void testQueued() + { + QueryStateMachine stateMachine = createQueryStateMachine(); + assertTrue(stateMachine.transitionToQueued()); + assertState(stateMachine, QUEUED); + + assertTrue(stateMachine.transitionToWaitingForResources()); + assertState(stateMachine, WAITING_FOR_RESOURCES); + + // Ensure that we can directly move to failed from queued + stateMachine = createQueryStateMachine(); + assertTrue(stateMachine.transitionToQueued()); + assertState(stateMachine, QUEUED); + + assertTrue(stateMachine.transitionToFailed(FAILED_CAUSE)); + assertState(stateMachine, FAILED, FAILED_CAUSE); } - private void assertAllTimeSpentInQueueing(QueryState expectedState, Consumer stateTransition) + private void assertAllTimeSpentInWaitingForPrerequisites(QueryState expectedState, Consumer stateTransition) { TestingTicker ticker = new TestingTicker(); QueryStateMachine stateMachine = createQueryStateMachineWithTicker(ticker); @@ -180,7 +207,8 @@ private void assertAllTimeSpentInQueueing(QueryState expectedState, Consumer>() {}).to(new TypeLiteral>() {}); binder.bind(LegacyResourceGroupConfigurationManager.class).in(Scopes.SINGLETON); binder.bind(ClusterMemoryPoolManager.class).toInstance(((poolId, listener) -> {})); + binder.bind(QueryPrerequisitesManager.class).in(Scopes.SINGLETON); // TODO: Decouple and remove: required by SessionPropertyDefaults, PluginManager, InternalResourceGroupManager, ConnectorManager configBinder(binder).bindConfig(NodeConfig.class); diff --git a/presto-spark-base/src/main/java/com/facebook/presto/spark/PrestoSparkQueryExecutionFactory.java b/presto-spark-base/src/main/java/com/facebook/presto/spark/PrestoSparkQueryExecutionFactory.java index 606812dbfd9f6..b2c4a0aad53d8 100644 --- a/presto-spark-base/src/main/java/com/facebook/presto/spark/PrestoSparkQueryExecutionFactory.java +++ b/presto-spark-base/src/main/java/com/facebook/presto/spark/PrestoSparkQueryExecutionFactory.java @@ -739,6 +739,7 @@ private static PrestoSparkQueryStatusInfo createPrestoSparkQueryInfo( // nullify stage stats to keep the object slim stats = new StatementStats( stats.getState(), + stats.isWaitingForPrerequisites(), stats.isQueued(), stats.isScheduled(), stats.getNodes(), @@ -748,6 +749,7 @@ private static PrestoSparkQueryStatusInfo createPrestoSparkQueryInfo( stats.getCompletedSplits(), stats.getCpuTimeMillis(), stats.getWallTimeMillis(), + stats.getWaitingForPrerequisitesTimeMillis(), stats.getQueuedTimeMillis(), stats.getElapsedTimeMillis(), stats.getProcessedRows(), diff --git a/presto-spi/src/main/java/com/facebook/presto/spi/Plugin.java b/presto-spi/src/main/java/com/facebook/presto/spi/Plugin.java index 570b1b9f50ceb..208b5dee4f378 100644 --- a/presto-spi/src/main/java/com/facebook/presto/spi/Plugin.java +++ b/presto-spi/src/main/java/com/facebook/presto/spi/Plugin.java @@ -19,6 +19,7 @@ import com.facebook.presto.spi.connector.ConnectorFactory; import com.facebook.presto.spi.eventlistener.EventListenerFactory; import com.facebook.presto.spi.function.FunctionNamespaceManagerFactory; +import com.facebook.presto.spi.prerequisites.QueryPrerequisitesFactory; import com.facebook.presto.spi.resourceGroups.ResourceGroupConfigurationManagerFactory; import com.facebook.presto.spi.security.PasswordAuthenticatorFactory; import com.facebook.presto.spi.security.SystemAccessControlFactory; @@ -91,4 +92,9 @@ default Iterable getTempStorageFactories() { return emptyList(); } + + default Iterable getQueryPrerequisitesFactories() + { + return emptyList(); + } } diff --git a/presto-spi/src/main/java/com/facebook/presto/spi/eventlistener/QueryStatistics.java b/presto-spi/src/main/java/com/facebook/presto/spi/eventlistener/QueryStatistics.java index 228fe93916052..491993bd622e2 100644 --- a/presto-spi/src/main/java/com/facebook/presto/spi/eventlistener/QueryStatistics.java +++ b/presto-spi/src/main/java/com/facebook/presto/spi/eventlistener/QueryStatistics.java @@ -24,6 +24,7 @@ public class QueryStatistics private final Duration retriedCpuTime; private final Duration wallTime; private final Duration queuedTime; + private final Duration waitingForPrerequisitesTime; private final Optional analysisTime; private final int peakRunningTasks; @@ -52,6 +53,7 @@ public QueryStatistics( Duration retriedCpuTime, Duration wallTime, Duration queuedTime, + Duration waitingForPrerequisitesTime, Optional analysisTime, int peakRunningTasks, long peakUserMemoryBytes, @@ -75,6 +77,7 @@ public QueryStatistics( this.retriedCpuTime = requireNonNull(retriedCpuTime, "retriedCpuTime is null"); this.wallTime = requireNonNull(wallTime, "wallTime is null"); this.queuedTime = requireNonNull(queuedTime, "queuedTime is null"); + this.waitingForPrerequisitesTime = requireNonNull(waitingForPrerequisitesTime, "waitingForPrerequisitesTime is null"); this.analysisTime = requireNonNull(analysisTime, "analysisTime is null"); this.peakRunningTasks = peakRunningTasks; this.peakUserMemoryBytes = peakUserMemoryBytes; @@ -115,6 +118,11 @@ public Duration getQueuedTime() return queuedTime; } + public Duration getWaitingForPrerequisitesTime() + { + return waitingForPrerequisitesTime; + } + public Optional getAnalysisTime() { return analysisTime; diff --git a/presto-spi/src/main/java/com/facebook/presto/spi/prerequisites/QueryPrerequisites.java b/presto-spi/src/main/java/com/facebook/presto/spi/prerequisites/QueryPrerequisites.java new file mode 100644 index 0000000000000..2cd35ffded248 --- /dev/null +++ b/presto-spi/src/main/java/com/facebook/presto/spi/prerequisites/QueryPrerequisites.java @@ -0,0 +1,40 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.facebook.presto.spi.prerequisites; + +import com.facebook.presto.spi.QueryId; + +import java.util.concurrent.CompletableFuture; + +/** + * An interface to plugin custom business logic that will be executed before the query is queued. + */ +public interface QueryPrerequisites +{ + /** + * Given the query context, implementations can perform actions or ensure that all conditions + * are ready for the query to execute. The returned CompletableFuture will indicate + * when the query is ready to be queued for execution. If the returned future finishes successfully, + * it will trigger the query to be queued and its failure will fail the query. + */ + CompletableFuture waitForPrerequisites(QueryId queryId, QueryPrerequisitesContext context); + + /** + * Optional method for the implementations to implement if they want to be informed about the finishing + * of queries (either successfully or unsuccessfully). + */ + default void queryFinished(QueryId queryId) + { + } +} diff --git a/presto-spi/src/main/java/com/facebook/presto/spi/prerequisites/QueryPrerequisitesContext.java b/presto-spi/src/main/java/com/facebook/presto/spi/prerequisites/QueryPrerequisitesContext.java new file mode 100644 index 0000000000000..2d60fb74ad439 --- /dev/null +++ b/presto-spi/src/main/java/com/facebook/presto/spi/prerequisites/QueryPrerequisitesContext.java @@ -0,0 +1,69 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.facebook.presto.spi.prerequisites; + +import com.facebook.presto.spi.ConnectorId; + +import java.util.Map; +import java.util.Optional; + +import static java.util.Objects.requireNonNull; + +public class QueryPrerequisitesContext +{ + private final Optional catalog; + private final Optional schema; + private final String query; + private final Map systemProperties; + private final Map> connectorProperties; + + public QueryPrerequisitesContext( + Optional catalog, + Optional schema, + String query, + Map systemProperties, + Map> connectorProperties) + { + this.catalog = requireNonNull(catalog, "catalog is null"); + this.schema = requireNonNull(schema, "schema is null"); + this.query = requireNonNull(query, "query is null"); + this.systemProperties = requireNonNull(systemProperties, "systemProperties is null"); + this.connectorProperties = requireNonNull(connectorProperties, "connectorProperties is null"); + } + + public Optional getCatalog() + { + return catalog; + } + + public Optional getSchema() + { + return schema; + } + + public String getQuery() + { + return query; + } + + public Map getSystemProperties() + { + return systemProperties; + } + + public Map> getConnectorProperties() + { + return connectorProperties; + } +} diff --git a/presto-spi/src/main/java/com/facebook/presto/spi/prerequisites/QueryPrerequisitesFactory.java b/presto-spi/src/main/java/com/facebook/presto/spi/prerequisites/QueryPrerequisitesFactory.java new file mode 100644 index 0000000000000..83c55edc5ee5d --- /dev/null +++ b/presto-spi/src/main/java/com/facebook/presto/spi/prerequisites/QueryPrerequisitesFactory.java @@ -0,0 +1,23 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.facebook.presto.spi.prerequisites; + +import java.util.Map; + +public interface QueryPrerequisitesFactory +{ + String getName(); + + QueryPrerequisites create(Map properties); +} diff --git a/presto-tests/src/test/java/com/facebook/presto/memory/TestClusterMemoryLeakDetector.java b/presto-tests/src/test/java/com/facebook/presto/memory/TestClusterMemoryLeakDetector.java index 023e9db11f3af..36f589adb05d1 100644 --- a/presto-tests/src/test/java/com/facebook/presto/memory/TestClusterMemoryLeakDetector.java +++ b/presto-tests/src/test/java/com/facebook/presto/memory/TestClusterMemoryLeakDetector.java @@ -80,6 +80,7 @@ private static BasicQueryInfo createQueryInfo(String queryId, QueryState state) new BasicQueryStats( DateTime.parse("1991-09-06T05:00-05:30"), DateTime.parse("1991-09-06T05:01-05:30"), + Duration.valueOf("4m"), Duration.valueOf("8m"), Duration.valueOf("7m"), Duration.valueOf("34m"), diff --git a/presto-verifier/src/test/java/com/facebook/presto/verifier/framework/TestLimitQueryDeterminismAnalyzer.java b/presto-verifier/src/test/java/com/facebook/presto/verifier/framework/TestLimitQueryDeterminismAnalyzer.java index b294c30619644..ac7a4128fecee 100644 --- a/presto-verifier/src/test/java/com/facebook/presto/verifier/framework/TestLimitQueryDeterminismAnalyzer.java +++ b/presto-verifier/src/test/java/com/facebook/presto/verifier/framework/TestLimitQueryDeterminismAnalyzer.java @@ -80,7 +80,7 @@ public Statement getLastStatement() private static final long ROW_COUNT_WITH_LIMIT = 1000; private static final QueryActionStats QUERY_STATS = new QueryActionStats( - Optional.of(new QueryStats("id", "", false, false, 1, 2, 3, 4, 5, 0, 7, 8, 9, 10, 11, 0, 0, 0, Optional.empty())), + Optional.of(new QueryStats("id", "", false, false, false, 1, 2, 3, 4, 5, 0, 7, 8, 9, 10, 11, 12, 0, 0, 0, Optional.empty())), Optional.empty()); private static final ParsingOptions PARSING_OPTIONS = ParsingOptions.builder().setDecimalLiteralTreatment(AS_DOUBLE).build(); private static final SqlParser sqlParser = new SqlParser(new SqlParserOptions().allowIdentifierSymbol(COLON, AT_SIGN)); diff --git a/presto-verifier/src/test/java/com/facebook/presto/verifier/prestoaction/TestPrestoExceptionClassifier.java b/presto-verifier/src/test/java/com/facebook/presto/verifier/prestoaction/TestPrestoExceptionClassifier.java index a340204ff64d6..20cac55be95dc 100644 --- a/presto-verifier/src/test/java/com/facebook/presto/verifier/prestoaction/TestPrestoExceptionClassifier.java +++ b/presto-verifier/src/test/java/com/facebook/presto/verifier/prestoaction/TestPrestoExceptionClassifier.java @@ -58,7 +58,7 @@ public class TestPrestoExceptionClassifier { private static final QueryStage QUERY_STAGE = CONTROL_MAIN; private static final QueryActionStats QUERY_ACTION_STATS = new QueryActionStats( - Optional.of(new QueryStats("id", "", false, false, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, Optional.empty())), + Optional.of(new QueryStats("id", "", false, false, false, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, Optional.empty())), Optional.empty()); private final SqlExceptionClassifier classifier = PrestoExceptionClassifier.defaultBuilder().build(); diff --git a/presto-verifier/src/test/java/com/facebook/presto/verifier/resolver/AbstractTestPrestoQueryFailureResolver.java b/presto-verifier/src/test/java/com/facebook/presto/verifier/resolver/AbstractTestPrestoQueryFailureResolver.java index 5955e6089c02e..8093e89368e29 100644 --- a/presto-verifier/src/test/java/com/facebook/presto/verifier/resolver/AbstractTestPrestoQueryFailureResolver.java +++ b/presto-verifier/src/test/java/com/facebook/presto/verifier/resolver/AbstractTestPrestoQueryFailureResolver.java @@ -105,7 +105,7 @@ protected FailureResolver getFailureResolver() protected static QueryStats createQueryStats(long cpuTimeMillis, long peakTotalMemoryBytes) { - return new QueryStats("id", "", false, false, 1, 2, 3, 4, 5, cpuTimeMillis, 7, 8, 9, 10, 11, 12, peakTotalMemoryBytes, 13, Optional.empty()); + return new QueryStats("id", "", false, false, false, 1, 2, 3, 4, 5, cpuTimeMillis, 7, 8, 9, 10, 11, 12, 13, peakTotalMemoryBytes, 13, Optional.empty()); } protected static QueryActionStats createQueryActionStats(long cpuTimeMillis, long peakTotalMemoryBytes)