| // 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.Preconditions; |
| import com.google.common.collect.Iterators; |
| import com.google.devtools.build.lib.profiler.ProfilePhase; |
| import com.google.devtools.build.lib.profiler.ProfilerTask; |
| import com.google.devtools.build.lib.profiler.analysis.ProfileInfo; |
| import com.google.devtools.build.lib.profiler.analysis.ProfileInfo.AggregateAttr; |
| import com.google.devtools.build.lib.profiler.analysis.ProfileInfo.Task; |
| import java.util.EnumMap; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| /** |
| * 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 boolean wasExecuted; |
| |
| public PhaseStatistics(ProfilePhase phase) { |
| this.phase = phase; |
| this.taskDurations = new EnumMap<>(ProfilerTask.class); |
| this.taskCounts = new EnumMap<>(ProfilerTask.class); |
| } |
| |
| public PhaseStatistics(ProfilePhase phase, ProfileInfo info) { |
| this(phase); |
| addProfileInfo(info); |
| } |
| |
| /** Add statistics from {@link ProfileInfo} to the ones already accumulated for this phase. */ |
| public void addProfileInfo(ProfileInfo info) { |
| Task phaseTask = info.getPhaseTask(phase); |
| if (phaseTask != null) { |
| 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); |
| } |
| } |
| } |
| |
| /** 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); |
| } |
| |
| public ProfilePhase getProfilePhase() { |
| return phase; |
| } |
| |
| /** |
| * @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; |
| } |
| |
| /** @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; |
| } |
| |
| public long getPhaseDurationNanos() { |
| return phaseDurationNanos; |
| } |
| |
| /** @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(), |
| taskType -> getTotalDurationNanos(taskType) > 0 && wasExecuted(taskType)); |
| } |
| } |
| |