/* * Copyright 2017-present Facebook, Inc. * * 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.buck.rules; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.timing.ClockDuration; import com.google.common.annotations.VisibleForTesting; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.ThreadSafe; /** * Tracks the total duration of work spent on each build rule. * * <p>Computation associated with build rules are broken into several phases. Those phases are * invoked from various places in {@link CachingBuildEngine} at various times and on various * threads. In order to track the total duration of all of those phases combined we need some * central store, i.e. this class. */ public class BuildRuleDurationTracker { private final ConcurrentMap<BuildTarget, DurationHolder> durations = new ConcurrentHashMap<>(); /** This method should only be used for testing. */ @VisibleForTesting public void setDuration(BuildRule rule, ClockDuration duration) { durations.put(rule.getBuildTarget(), new DurationHolder(duration)); } public ClockDuration doBeginning(BuildRule rule, long wallMillisTime, long nanoTime) { return durations .computeIfAbsent(rule.getBuildTarget(), (key) -> new DurationHolder()) .doBeginning(wallMillisTime, nanoTime); } public ClockDuration doEnding( BuildRule rule, long wallMillisTime, long nanoTime, long threadUserNanoDuration) { return durations .computeIfAbsent(rule.getBuildTarget(), (key) -> new DurationHolder()) .doEnding(wallMillisTime, nanoTime, threadUserNanoDuration); } @ThreadSafe private static class DurationHolder { // intervals can be nested so we need to keep the nesting count @GuardedBy("this") private int inProgressCount = 0; // start time of the current in-progress interval @GuardedBy("this") private long wallMillisStarted = 0; @GuardedBy("this") private long nanoStarted = 0; // accumulated duration till the current in-progress interval @GuardedBy("this") private long wallMillisDuration; @GuardedBy("this") private long nanoDuration; @GuardedBy("this") private long threadUserNanoDuration; public DurationHolder() { this(ClockDuration.ZERO); } public DurationHolder(ClockDuration initialDuration) { wallMillisDuration = initialDuration.getWallMillisDuration(); nanoDuration = initialDuration.getNanoDuration(); threadUserNanoDuration = initialDuration.getThreadUserNanoDuration(); } public synchronized ClockDuration getDurationAt(long wallMillisTime, long nanoTime) { return new ClockDuration( wallMillisDuration + wallMillisTime - wallMillisStarted, nanoDuration + nanoTime - nanoStarted, threadUserNanoDuration); } public synchronized ClockDuration doBeginning(long wallMillisTime, long nanoTime) { if (inProgressCount++ == 0) { wallMillisStarted = wallMillisTime; nanoStarted = nanoTime; } return getDurationAt(wallMillisTime, nanoTime); } public synchronized ClockDuration doEnding( long wallMillisTime, long nanoTime, long threadUserNanoDuration) { this.threadUserNanoDuration += threadUserNanoDuration; ClockDuration duration = getDurationAt(wallMillisTime, nanoTime); if (--inProgressCount == 0) { wallMillisDuration += wallMillisTime - wallMillisStarted; nanoDuration += nanoTime - nanoStarted; } return duration; } } }