/* * 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.sql.planner.planPrinter; import com.facebook.presto.sql.planner.plan.PlanNodeId; import io.airlift.units.DataSize; import io.airlift.units.Duration; import java.util.Map; import java.util.Set; import static com.facebook.presto.util.MoreMaps.mergeMaps; import static com.google.common.base.Preconditions.checkArgument; import static io.airlift.units.DataSize.succinctBytes; import static java.lang.Double.max; import static java.lang.Math.sqrt; import static java.util.Objects.requireNonNull; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.stream.Collectors.toMap; class PlanNodeStats { private final PlanNodeId planNodeId; private final Duration planNodeWallTime; private final long planNodeInputPositions; private final DataSize planNodeInputDataSize; private final long planNodeOutputPositions; private final DataSize planNodeOutputDataSize; private final Map<String, OperatorInputStats> operatorInputStats; private final Map<String, OperatorHashCollisionsStats> operatorHashCollisionsStats; PlanNodeStats( PlanNodeId planNodeId, Duration planNodeWallTime, long planNodeInputPositions, DataSize planNodeInputDataSize, long planNodeOutputPositions, DataSize planNodeOutputDataSize, Map<String, OperatorInputStats> operatorInputStats, Map<String, OperatorHashCollisionsStats> operatorHashCollisionsStats) { this.planNodeId = requireNonNull(planNodeId, "planNodeId is null"); this.planNodeWallTime = requireNonNull(planNodeWallTime, "planNodeWallTime is null"); this.planNodeInputPositions = planNodeInputPositions; this.planNodeInputDataSize = planNodeInputDataSize; this.planNodeOutputPositions = planNodeOutputPositions; this.planNodeOutputDataSize = planNodeOutputDataSize; this.operatorInputStats = requireNonNull(operatorInputStats, "operatorInputStats is null"); this.operatorHashCollisionsStats = requireNonNull(operatorHashCollisionsStats, "operatorHashCollisionsStats is null"); } private static double computedStdDev(double sumSquared, double sum, long n) { double average = sum / n; double variance = (sumSquared - 2 * sum * average + average * average * n) / n; // variance might be negative because of numeric inaccuracy, therefore we need to use max return sqrt(max(variance, 0d)); } public PlanNodeId getPlanNodeId() { return planNodeId; } public Duration getPlanNodeWallTime() { return planNodeWallTime; } public Set<String> getOperatorTypes() { return operatorInputStats.keySet(); } public long getPlanNodeInputPositions() { return planNodeInputPositions; } public DataSize getPlanNodeInputDataSize() { return planNodeInputDataSize; } public long getPlanNodeOutputPositions() { return planNodeOutputPositions; } public DataSize getPlanNodeOutputDataSize() { return planNodeOutputDataSize; } public Map<String, Double> getOperatorInputPositionsAverages() { return operatorInputStats.entrySet().stream() .collect(toMap( Map.Entry::getKey, entry -> (double) entry.getValue().getInputPositions() / operatorInputStats.get(entry.getKey()).getTotalDrivers())); } public Map<String, Double> getOperatorInputPositionsStdDevs() { return operatorInputStats.entrySet().stream() .collect(toMap( Map.Entry::getKey, entry -> computedStdDev( entry.getValue().getSumSquaredInputPositions(), entry.getValue().getInputPositions(), entry.getValue().getTotalDrivers()))); } public Map<String, Double> getOperatorHashCollisionsAverages() { return operatorHashCollisionsStats.entrySet().stream() .collect(toMap( Map.Entry::getKey, entry -> entry.getValue().getWeightedHashCollisions() / operatorInputStats.get(entry.getKey()).getInputPositions())); } public Map<String, Double> getOperatorHashCollisionsStdDevs() { return operatorHashCollisionsStats.entrySet().stream() .collect(toMap( Map.Entry::getKey, entry -> computedWeightedStdDev( entry.getValue().getWeightedSumSquaredHashCollisions(), entry.getValue().getWeightedHashCollisions(), operatorInputStats.get(entry.getKey()).getInputPositions()))); } private static double computedWeightedStdDev(double sumSquared, double sum, double totalWeight) { double average = sum / totalWeight; double variance = (sumSquared - 2 * sum * average) / totalWeight + average * average; // variance might be negative because of numeric inaccuracy, therefore we need to use max return sqrt(max(variance, 0d)); } public Map<String, Double> getOperatorExpectedCollisionsAverages() { return operatorHashCollisionsStats.entrySet().stream() .collect(toMap( Map.Entry::getKey, entry -> entry.getValue().getWeightedExpectedHashCollisions() / operatorInputStats.get(entry.getKey()).getInputPositions())); } public static PlanNodeStats merge(PlanNodeStats left, PlanNodeStats right) { checkArgument(left.getPlanNodeId().equals(right.getPlanNodeId()), "planNodeIds do not match. %s != %s", left.getPlanNodeId(), right.getPlanNodeId()); long planNodeInputPositions = left.planNodeInputPositions + right.planNodeInputPositions; DataSize planNodeInputDataSize = succinctBytes(left.planNodeInputDataSize.toBytes() + right.planNodeInputDataSize.toBytes()); long planNodeOutputPositions = left.planNodeOutputPositions + right.planNodeOutputPositions; DataSize planNodeOutputDataSize = succinctBytes(left.planNodeOutputDataSize.toBytes() + right.planNodeOutputDataSize.toBytes()); Map<String, OperatorInputStats> operatorInputStats = mergeMaps(left.operatorInputStats, right.operatorInputStats, OperatorInputStats::merge); Map<String, OperatorHashCollisionsStats> operatorHashCollisionsStats = mergeMaps(left.operatorHashCollisionsStats, right.operatorHashCollisionsStats, OperatorHashCollisionsStats::merge); return new PlanNodeStats( left.getPlanNodeId(), new Duration(left.getPlanNodeWallTime().toMillis() + right.getPlanNodeWallTime().toMillis(), MILLISECONDS), planNodeInputPositions, planNodeInputDataSize, planNodeOutputPositions, planNodeOutputDataSize, operatorInputStats, operatorHashCollisionsStats); } }