// Copyright 2015 The Bazel Authors. All rights reserved. // // 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.google.devtools.build.lib.profiler.statistics; import com.google.common.base.Predicate; import com.google.common.collect.Iterators; import com.google.devtools.build.lib.profiler.ProfileInfo; import com.google.devtools.build.lib.profiler.ProfileInfo.AggregateAttr; import com.google.devtools.build.lib.profiler.ProfileInfo.Task; import com.google.devtools.build.lib.profiler.ProfilePhase; import com.google.devtools.build.lib.profiler.ProfilerTask; import com.google.devtools.build.lib.util.Preconditions; import java.util.EnumMap; import java.util.Iterator; import java.util.List; import javax.annotation.Nullable; /** * Extracts and keeps statistics for one {@link ProfilePhase} for formatting to various outputs. */ public final class PhaseStatistics implements Iterable<ProfilerTask> { private final ProfilePhase phase; private long phaseDurationNanos; private long totalDurationNanos; private final EnumMap<ProfilerTask, Long> taskDurations; private final EnumMap<ProfilerTask, Long> taskCounts; private final PhaseVfsStatistics vfsStatistics; private boolean wasExecuted; public PhaseStatistics(ProfilePhase phase, boolean generateVfsStatistics) { this.phase = phase; this.taskDurations = new EnumMap<>(ProfilerTask.class); this.taskCounts = new EnumMap<>(ProfilerTask.class); if (generateVfsStatistics) { vfsStatistics = new PhaseVfsStatistics(phase); } else { vfsStatistics = null; } } public PhaseStatistics(ProfilePhase phase, ProfileInfo info, String workSpaceName, boolean vfs) { this(phase, vfs); addProfileInfo(workSpaceName, info); } /** * Add statistics from {@link ProfileInfo} to the ones already accumulated for this phase. */ public void addProfileInfo(String workSpaceName, ProfileInfo info) { Task phaseTask = info.getPhaseTask(phase); if (phaseTask != null) { if (vfsStatistics != null) { vfsStatistics.addProfileInfo(workSpaceName, info); } wasExecuted = true; long infoPhaseDuration = info.getPhaseDuration(phaseTask); phaseDurationNanos += infoPhaseDuration; List<Task> taskList = info.getTasksForPhase(phaseTask); long duration = infoPhaseDuration; for (Task task : taskList) { // Tasks on the phaseTask thread already accounted for in the phaseDuration. if (task.threadId != phaseTask.threadId) { duration += task.durationNanos; } } totalDurationNanos += duration; for (ProfilerTask type : ProfilerTask.values()) { AggregateAttr attr = info.getStatsForType(type, taskList); long totalTime = Math.max(0, attr.totalTime); long count = Math.max(0, attr.count); add(taskCounts, type, count); add(taskDurations, type, totalTime); } } } /** * Add statistics accumulated in another PhaseStatistics object to this one. */ public void add(PhaseStatistics other) { Preconditions.checkArgument( phase == other.phase, "Should not combine statistics from different phases"); if (other.wasExecuted) { if (vfsStatistics != null && other.vfsStatistics != null) { vfsStatistics.add(other.vfsStatistics); } wasExecuted = true; phaseDurationNanos += other.phaseDurationNanos; totalDurationNanos += other.totalDurationNanos; for (ProfilerTask type : other) { long otherCount = other.getCount(type); long otherDuration = other.getTotalDurationNanos(type); add(taskCounts, type, otherCount); add(taskDurations, type, otherDuration); } } } public ProfilePhase getProfilePhase() { return phase; } @Nullable public PhaseVfsStatistics getVfsStatistics() { return vfsStatistics; } /** * @return true if no {@link ProfilerTask}s have been executed in this phase, false otherwise */ public boolean isEmpty() { return taskCounts.isEmpty(); } /** * @return true if the phase was not executed at all, false otherwise */ public boolean wasExecuted() { return wasExecuted; } public long getPhaseDurationNanos() { return phaseDurationNanos; } /** * @return true if a task of the given {@link ProfilerTask} type was executed in this phase */ public boolean wasExecuted(ProfilerTask taskType) { Long count = taskCounts.get(taskType); return count != null && count != 0; } /** * @return the sum of all task durations of the given type */ public long getTotalDurationNanos(ProfilerTask taskType) { Long duration = taskDurations.get(taskType); if (duration == null) { return 0; } return duration; } /** * @return the average duration of all {@link ProfilerTask} */ public double getMeanDuration(ProfilerTask taskType) { if (wasExecuted(taskType)) { double duration = taskDurations.get(taskType); long count = taskCounts.get(taskType); return duration / count; } return 0; } /** * @return the duration of all {@link ProfilerTask} executed in the phase relative to the total * phase duration */ public double getTotalRelativeDuration(ProfilerTask taskType) { Long duration = taskDurations.get(taskType); if (duration == null || duration == 0) { return 0; } // sanity check for broken profile files Preconditions.checkState( totalDurationNanos != 0, "Profiler tasks of type %s have non-zero duration %s in phase %s but the phase itself has" + " zero duration. Most likely the profile file is broken.", taskType, duration, phase); return (double) duration / totalDurationNanos; } /** * @return how many tasks of the given type were executed in this phase */ public long getCount(ProfilerTask taskType) { Long count = taskCounts.get(taskType); if (count == null) { return 0; } return count; } /** * Iterator over all {@link ProfilerTask}s that were executed at least once and have a total * duration greater than 0. */ @Override public Iterator<ProfilerTask> iterator() { return Iterators.filter( taskCounts.keySet().iterator(), new Predicate<ProfilerTask>() { @Override public boolean apply(ProfilerTask taskType) { return getTotalDurationNanos(taskType) > 0 && wasExecuted(taskType); } }); } /** * Helper method to sum up long values within an {@link EnumMap}. */ private static <T extends Enum<T>> void add(EnumMap<T, Long> map, T key, long value) { long previous; if (map.containsKey(key)) { previous = map.get(key); } else { previous = 0; } map.put(key, previous + value); } }