/* * 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.operator; import com.facebook.presto.Session; import com.facebook.presto.execution.TaskId; import com.facebook.presto.sql.planner.plan.PlanNodeId; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.util.concurrent.ListenableFuture; import io.airlift.stats.CounterStat; import io.airlift.units.DataSize; import io.airlift.units.Duration; import org.joda.time.DateTime; import java.lang.management.ManagementFactory; import java.lang.management.ThreadMXBean; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.Iterables.getFirst; import static com.google.common.collect.Iterables.getLast; import static com.google.common.collect.Iterables.transform; import static io.airlift.units.DataSize.Unit.BYTE; import static io.airlift.units.DataSize.succinctBytes; import static java.util.Objects.requireNonNull; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.NANOSECONDS; /** * Only calling getDriverStats is ThreadSafe */ public class DriverContext { private static final ThreadMXBean THREAD_MX_BEAN = ManagementFactory.getThreadMXBean(); private final PipelineContext pipelineContext; private final Executor executor; private final AtomicBoolean finished = new AtomicBoolean(); private final DateTime createdTime = DateTime.now(); private final long createNanos = System.nanoTime(); private final AtomicLong startNanos = new AtomicLong(); private final AtomicLong endNanos = new AtomicLong(); private final AtomicLong intervalWallStart = new AtomicLong(); private final AtomicLong intervalCpuStart = new AtomicLong(); private final AtomicLong intervalUserStart = new AtomicLong(); private final AtomicLong processCalls = new AtomicLong(); private final AtomicLong processWallNanos = new AtomicLong(); private final AtomicLong processCpuNanos = new AtomicLong(); private final AtomicLong processUserNanos = new AtomicLong(); private final AtomicReference<BlockedMonitor> blockedMonitor = new AtomicReference<>(); private final AtomicLong blockedWallNanos = new AtomicLong(); private final AtomicReference<DateTime> executionStartTime = new AtomicReference<>(); private final AtomicReference<DateTime> executionEndTime = new AtomicReference<>(); private final AtomicLong memoryReservation = new AtomicLong(); private final AtomicLong peakMemoryReservation = new AtomicLong(); private final AtomicLong systemMemoryReservation = new AtomicLong(); private final List<OperatorContext> operatorContexts = new CopyOnWriteArrayList<>(); private final boolean partitioned; public DriverContext(PipelineContext pipelineContext, Executor executor, boolean partitioned) { this.pipelineContext = requireNonNull(pipelineContext, "pipelineContext is null"); this.executor = requireNonNull(executor, "executor is null"); this.partitioned = partitioned; } public TaskId getTaskId() { return pipelineContext.getTaskId(); } public OperatorContext addOperatorContext(int operatorId, PlanNodeId planNodeId, String operatorType) { checkArgument(operatorId >= 0, "operatorId is negative"); for (OperatorContext operatorContext : operatorContexts) { checkArgument(operatorId != operatorContext.getOperatorId(), "A context already exists for operatorId %s", operatorId); } OperatorContext operatorContext = new OperatorContext(operatorId, planNodeId, operatorType, this, executor); operatorContexts.add(operatorContext); return operatorContext; } public List<OperatorContext> getOperatorContexts() { return ImmutableList.copyOf(operatorContexts); } public PipelineContext getPipelineContext() { return pipelineContext; } public Session getSession() { return pipelineContext.getSession(); } public void startProcessTimer() { if (startNanos.compareAndSet(0, System.nanoTime())) { pipelineContext.start(); executionStartTime.set(DateTime.now()); } intervalWallStart.set(System.nanoTime()); intervalCpuStart.set(currentThreadCpuTime()); intervalUserStart.set(currentThreadUserTime()); } public void recordProcessed() { processCalls.incrementAndGet(); processWallNanos.getAndAdd(nanosBetween(intervalWallStart.get(), System.nanoTime())); processCpuNanos.getAndAdd(nanosBetween(intervalCpuStart.get(), currentThreadCpuTime())); processUserNanos.getAndAdd(nanosBetween(intervalUserStart.get(), currentThreadUserTime())); } public void recordBlocked(ListenableFuture<?> blocked) { requireNonNull(blocked, "blocked is null"); BlockedMonitor monitor = new BlockedMonitor(); BlockedMonitor oldMonitor = blockedMonitor.getAndSet(monitor); if (oldMonitor != null) { oldMonitor.run(); } blocked.addListener(monitor, executor); } public void finished() { if (!finished.compareAndSet(false, true)) { // already finished return; } executionEndTime.set(DateTime.now()); endNanos.set(System.nanoTime()); pipelineContext.driverFinished(this); } public void failed(Throwable cause) { pipelineContext.failed(cause); finished.set(true); } public boolean isDone() { return finished.get() || pipelineContext.isDone(); } public void transferMemoryToTaskContext(long bytes) { pipelineContext.transferMemoryToTaskContext(bytes); checkArgument(memoryReservation.addAndGet(-bytes) >= 0, "Tried to transfer more memory than is reserved"); } public ListenableFuture<?> reserveMemory(long bytes) { ListenableFuture<?> future = pipelineContext.reserveMemory(bytes); long newMemoryReservation = memoryReservation.addAndGet(bytes); peakMemoryReservation.accumulateAndGet(newMemoryReservation, Math::max); return future; } public ListenableFuture<?> reserveSystemMemory(long bytes) { checkArgument(bytes >= 0, "bytes is negative"); ListenableFuture<?> future = pipelineContext.reserveSystemMemory(bytes); systemMemoryReservation.getAndAdd(bytes); return future; } public ListenableFuture<?> reserveSpill(long bytes) { return pipelineContext.reserveSpill(bytes); } public boolean tryReserveMemory(long bytes) { if (pipelineContext.tryReserveMemory(bytes)) { long newMemoryReservation = memoryReservation.addAndGet(bytes); peakMemoryReservation.accumulateAndGet(newMemoryReservation, Math::max); return true; } return false; } public void freeMemory(long bytes) { if (bytes == 0) { return; } checkArgument(bytes > 0, "bytes is negative"); checkArgument(bytes <= memoryReservation.get(), "tried to free more memory than is reserved"); pipelineContext.freeMemory(bytes); memoryReservation.getAndAdd(-bytes); } public void freeSystemMemory(long bytes) { if (bytes == 0) { return; } checkArgument(bytes > 0, "bytes is negative"); checkArgument(bytes <= systemMemoryReservation.get(), "tried to free more memory than is reserved"); pipelineContext.freeSystemMemory(bytes); systemMemoryReservation.getAndAdd(-bytes); } public void freeSpill(long bytes) { if (bytes == 0) { return; } checkArgument(bytes > 0, "bytes is negative"); pipelineContext.freeSpill(bytes); } public long getSystemMemoryUsage() { return systemMemoryReservation.get(); } public long getMemoryUsage() { return memoryReservation.get(); } public void moreMemoryAvailable() { operatorContexts.forEach(OperatorContext::moreMemoryAvailable); } public boolean isVerboseStats() { return pipelineContext.isVerboseStats(); } public boolean isCpuTimerEnabled() { return pipelineContext.isCpuTimerEnabled(); } public CounterStat getInputDataSize() { OperatorContext inputOperator = getFirst(operatorContexts, null); if (inputOperator != null) { return inputOperator.getInputDataSize(); } else { return new CounterStat(); } } public CounterStat getInputPositions() { OperatorContext inputOperator = getFirst(operatorContexts, null); if (inputOperator != null) { return inputOperator.getInputPositions(); } else { return new CounterStat(); } } public CounterStat getOutputDataSize() { OperatorContext inputOperator = getLast(operatorContexts, null); if (inputOperator != null) { return inputOperator.getOutputDataSize(); } else { return new CounterStat(); } } public CounterStat getOutputPositions() { OperatorContext inputOperator = getLast(operatorContexts, null); if (inputOperator != null) { return inputOperator.getOutputPositions(); } else { return new CounterStat(); } } public DriverStats getDriverStats() { long totalScheduledTime = processWallNanos.get(); long totalCpuTime = processCpuNanos.get(); long totalUserTime = processUserNanos.get(); long totalBlockedTime = blockedWallNanos.get(); BlockedMonitor blockedMonitor = this.blockedMonitor.get(); if (blockedMonitor != null) { totalBlockedTime += blockedMonitor.getBlockedTime(); } List<OperatorStats> operators = ImmutableList.copyOf(transform(operatorContexts, OperatorContext::getOperatorStats)); OperatorStats inputOperator = getFirst(operators, null); DataSize rawInputDataSize; long rawInputPositions; Duration rawInputReadTime; DataSize processedInputDataSize; long processedInputPositions; DataSize outputDataSize; long outputPositions; if (inputOperator != null) { rawInputDataSize = inputOperator.getInputDataSize(); rawInputPositions = inputOperator.getInputPositions(); rawInputReadTime = inputOperator.getAddInputWall(); processedInputDataSize = inputOperator.getOutputDataSize(); processedInputPositions = inputOperator.getOutputPositions(); OperatorStats outputOperator = requireNonNull(getLast(operators, null)); outputDataSize = outputOperator.getOutputDataSize(); outputPositions = outputOperator.getOutputPositions(); } else { rawInputDataSize = new DataSize(0, BYTE); rawInputPositions = 0; rawInputReadTime = new Duration(0, MILLISECONDS); processedInputDataSize = new DataSize(0, BYTE); processedInputPositions = 0; outputDataSize = new DataSize(0, BYTE); outputPositions = 0; } long startNanos = this.startNanos.get(); if (startNanos < createNanos) { startNanos = System.nanoTime(); } Duration queuedTime = new Duration(startNanos - createNanos, NANOSECONDS); long endNanos = this.endNanos.get(); Duration elapsedTime; if (endNanos >= startNanos) { elapsedTime = new Duration(endNanos - createNanos, NANOSECONDS); } else { elapsedTime = new Duration(0, NANOSECONDS); } ImmutableSet.Builder<BlockedReason> builder = ImmutableSet.builder(); for (OperatorStats operator : operators) { if (operator.getBlockedReason().isPresent()) { builder.add(operator.getBlockedReason().get()); } } return new DriverStats( createdTime, executionStartTime.get(), executionEndTime.get(), queuedTime.convertToMostSuccinctTimeUnit(), elapsedTime.convertToMostSuccinctTimeUnit(), succinctBytes(memoryReservation.get()), succinctBytes(peakMemoryReservation.get()), succinctBytes(systemMemoryReservation.get()), new Duration(totalScheduledTime, NANOSECONDS).convertToMostSuccinctTimeUnit(), new Duration(totalCpuTime, NANOSECONDS).convertToMostSuccinctTimeUnit(), new Duration(totalUserTime, NANOSECONDS).convertToMostSuccinctTimeUnit(), new Duration(totalBlockedTime, NANOSECONDS).convertToMostSuccinctTimeUnit(), blockedMonitor != null, builder.build(), rawInputDataSize.convertToMostSuccinctDataSize(), rawInputPositions, rawInputReadTime, processedInputDataSize.convertToMostSuccinctDataSize(), processedInputPositions, outputDataSize.convertToMostSuccinctDataSize(), outputPositions, ImmutableList.copyOf(transform(operatorContexts, OperatorContext::getOperatorStats))); } public boolean isPartitioned() { return partitioned; } private long currentThreadUserTime() { if (!isCpuTimerEnabled()) { return 0; } return THREAD_MX_BEAN.getCurrentThreadUserTime(); } private long currentThreadCpuTime() { if (!isCpuTimerEnabled()) { return 0; } return THREAD_MX_BEAN.getCurrentThreadCpuTime(); } private static long nanosBetween(long start, long end) { return Math.abs(end - start); } // hack for index joins @Deprecated public Executor getExecutor() { return executor; } private class BlockedMonitor implements Runnable { private final long start = System.nanoTime(); private boolean finished; @Override public void run() { synchronized (this) { if (finished) { return; } finished = true; blockedMonitor.compareAndSet(this, null); blockedWallNanos.getAndAdd(getBlockedTime()); } } public long getBlockedTime() { return nanosBetween(start, System.nanoTime()); } } }