// 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; import com.google.devtools.build.lib.util.BlazeClock; import com.google.devtools.build.lib.util.Clock; import com.google.devtools.build.lib.util.Preconditions; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Logger; import javax.annotation.Nullable; /** * A convenient way to actively get access to timing information (e.g. for logging and/or * profiling purposes) with minimal boilerplate. The lack of boilerplate comes at a performance * cost; do not use {@link AutoProfiler} on performance critical code. * * <p>The intended usage is: * * <pre> * {@code * try (AutoProfiler p = AutoProfiler.logged("<description of your code>")) { * // Your code here. * } * } * </pre> * * <p>but if the try-with-resources pattern is too cumbersome, you can also do * * <pre> * {@code * AutoProfiler p = AutoProfiler.logged("<description of your code>"); * // Your code here. * long elapsedTimeNanos = p.completeAndGetElapsedTimeNanos(); * } * </pre> * * <p>An {@link AutoProfiler} can also automatically talk to the active {@link Profiler} instance: * * <pre> * {@code * try (AutoProfiler p = AutoProfiler.profiled("<description of your code>")) { * // Your code here. * } * } * </pre> */ public class AutoProfiler implements AutoCloseable { private static final AtomicReference<Clock> CLOCK_REF = new AtomicReference<>(null); private static final AtomicReference<LoggingElapsedTimeReceiverFactory> LOGGING_ELAPSED_TIME_RECEIVER_FACTORY_REF = new AtomicReference<>( LoggingElapsedTimeReceiver.FACTORY); private final ElapsedTimeReceiver elapsedTimeReceiver; private final long startTimeNanos; private final AtomicBoolean closed = new AtomicBoolean(false); private AutoProfiler(ElapsedTimeReceiver elapsedTimeReceiver, long startTimeNanos) { this.elapsedTimeReceiver = elapsedTimeReceiver; this.startTimeNanos = startTimeNanos; } /** A opaque receiver of elapsed time information. */ public interface ElapsedTimeReceiver { /** * Receives the elapsed time of the lifetime of an {@link AutoProfiler} instance. * * <p>Note that System#nanoTime isn't guaranteed to be non-decreasing, so implementations should * check for non-positive {@code elapsedTimeNanos} if they care about this sort of thing. */ void accept(long elapsedTimeNanos); } /** * Sets the clock to use. May be called before trying to call any of the other static methods; * otherwise {@link BlazeClock#instance} will be used. * * <p>Calling this more than once (e.g. after calling any of the other static methods) results in * unspecified behavior. */ public static void setClock(Clock clock) { CLOCK_REF.set(clock); } private static long nanoTime() { Clock clock = CLOCK_REF.get(); return clock != null ? clock.nanoTime() : BlazeClock.instance().nanoTime(); } /** * Factory to use for creating {@link ElapsedTimeReceiver} instances for servicing * {@link #logged} et al. */ public interface LoggingElapsedTimeReceiverFactory { ElapsedTimeReceiver create( String taskDescription, Logger logger, long minTimeForLogging, TimeUnit timeUnit); } /** * Inject a custom {@link LoggingElapsedTimeReceiverFactory} for servicing {@link #logged} et al. * * <p>Use this if you want custom logging behavior. */ public static void setLoggingElapsedTimeReceiverFactory( LoggingElapsedTimeReceiverFactory factory) { LOGGING_ELAPSED_TIME_RECEIVER_FACTORY_REF.set(factory); } /** * Returns an {@link AutoProfiler} that, when closed, logs the elapsed time in milliseconds to * the given {@link Logger}. * * <p>The returned {@link AutoProfiler} is thread-safe. */ public static AutoProfiler logged(String taskDescription, Logger logger) { return logged(taskDescription, logger, TimeUnit.MILLISECONDS); } /** * Returns an {@link AutoProfiler} that, when closed, logs the elapsed time in the given * {@link TimeUnit} to the given {@link Logger}. * * <p>The returned {@link AutoProfiler} is thread-safe. */ public static AutoProfiler logged(String taskDescription, Logger logger, TimeUnit timeUnit) { return create( LOGGING_ELAPSED_TIME_RECEIVER_FACTORY_REF .get() .create(taskDescription, logger, 0L, timeUnit)); } /** * Returns an {@link AutoProfiler} that, when closed, logs the elapsed time in milliseconds to the * given {@link Logger} if it is greater than {@code minTimeForLoggingInMilliseconds}. * * <p>The returned {@link AutoProfiler} is thread-safe. */ public static AutoProfiler logged( String taskDescription, Logger logger, long minTimeForLoggingInMilliseconds) { return create( LOGGING_ELAPSED_TIME_RECEIVER_FACTORY_REF .get() .create( taskDescription, logger, minTimeForLoggingInMilliseconds, TimeUnit.MILLISECONDS)); } /** * Returns an {@link AutoProfiler} that, when closed, records the elapsed time using * {@link Profiler}. * * <p>The returned {@link AutoProfiler} is thread-safe. */ public static AutoProfiler profiled(Object object, ProfilerTask profilerTaskType) { return create(new ProfilingElapsedTimeReceiver(object, profilerTaskType)); } /** * Returns an {@link AutoProfiler} that, when closed, records the elapsed time using * {@link Profiler} and also logs it (in milliseconds) to the given {@link Logger}. * * <p>The returned {@link AutoProfiler} is thread-safe. */ public static AutoProfiler profiledAndLogged(String taskDescription, ProfilerTask profilerTaskType, Logger logger) { ElapsedTimeReceiver profilingReceiver = new ProfilingElapsedTimeReceiver(taskDescription, profilerTaskType); ElapsedTimeReceiver loggingReceiver = LOGGING_ELAPSED_TIME_RECEIVER_FACTORY_REF .get() .create(taskDescription, logger, 0L, TimeUnit.MILLISECONDS); return create(new SequencedElapsedTimeReceiver(profilingReceiver, loggingReceiver)); } /** * Returns an {@link AutoProfiler} that doesn't do anything when closed and so is only useful for * {@link #completeAndGetElapsedTimeNanos()}. */ public static AutoProfiler timed() { return create(NULL_RECEIVER); } /** * Returns an {@link AutoProfiler} that, when closed, invokes the given * {@link ElapsedTimeReceiver}. * * <p>The returned {@link AutoProfiler} is as thread-safe as the given * {@link ElapsedTimeReceiver} is. */ public static AutoProfiler create(ElapsedTimeReceiver elapsedTimeReceiver) { return new AutoProfiler(elapsedTimeReceiver, nanoTime()); } /** * Manually completes the profiling (useful to trigger the underlying action on completion). * * <p>At most one of {@link #complete}, {@link #completeAndGetElapsedTimeNanos} and {@link #close} * may be called. */ public void complete() { close(); } /** * Manually completes the profiling and returns the elapsed time in nanoseconds. * * <p>At most one of {@link #complete}, {@link #completeAndGetElapsedTimeNanos} and {@link #close} * may be called. */ public long completeAndGetElapsedTimeNanos() { long elapsedTimeNanos = nanoTime() - startTimeNanos; Preconditions.checkState(closed.compareAndSet(false, true)); elapsedTimeReceiver.accept(elapsedTimeNanos); return elapsedTimeNanos; } /** * Automatically completes the profiling. * * <p>At most one of {@link #complete}, {@link #completeAndGetElapsedTimeNanos} and {@link #close} * may be called. */ @Override public void close() { long elapsedTimeNanos = nanoTime() - startTimeNanos; Preconditions.checkState(closed.compareAndSet(false, true)); elapsedTimeReceiver.accept(elapsedTimeNanos); } private static final ElapsedTimeReceiver NULL_RECEIVER = new ElapsedTimeReceiver() { @Override public void accept(long elapsedTimeNanos) { } }; private static class SequencedElapsedTimeReceiver implements ElapsedTimeReceiver { private final ElapsedTimeReceiver firstReceiver; private final ElapsedTimeReceiver secondReceiver; private SequencedElapsedTimeReceiver(ElapsedTimeReceiver firstReceiver, ElapsedTimeReceiver secondReceiver) { this.firstReceiver = firstReceiver; this.secondReceiver = secondReceiver; } @Override public void accept(long elapsedTimeNanos) { firstReceiver.accept(elapsedTimeNanos); secondReceiver.accept(elapsedTimeNanos); } } /** {@link ElapsedTimeReceiver} that will not log a message if the time elapsed is too small. */ public abstract static class FilteringElapsedTimeReceiver implements ElapsedTimeReceiver { private final String taskDescription; private final TimeUnit timeUnit; private final long minNanosForLogging; protected FilteringElapsedTimeReceiver( String taskDescription, long minTimeForLogging, TimeUnit timeUnit) { this.taskDescription = taskDescription; this.timeUnit = timeUnit; this.minNanosForLogging = timeUnit.toNanos(minTimeForLogging); } protected abstract void log(String logMessage); /** * Returns a message about the amount of time spent doing a task if {@code elapsedTimeNanos} is * at least {@link #minNanosForLogging} and null otherwise. */ @Nullable private String loggingMessage(long elapsedTimeNanos) { if (elapsedTimeNanos >= minNanosForLogging) { return String.format( "Spent %d %s doing %s", timeUnit.convert(elapsedTimeNanos, TimeUnit.NANOSECONDS), timeUnit.toString().toLowerCase(), taskDescription); } else { return null; } } @Override public final void accept(long elapsedTimeNanos) { String logMessage = loggingMessage(elapsedTimeNanos); if (logMessage != null) { log(logMessage); } } } /** * {@link ElapsedTimeReceiver} that logs a message in {@link #accept} if the elapsed time is at * least a given threshold. */ public static class LoggingElapsedTimeReceiver extends FilteringElapsedTimeReceiver { private static final LoggingElapsedTimeReceiverFactory FACTORY = new LoggingElapsedTimeReceiverFactory() { @Override public ElapsedTimeReceiver create( String taskDescription, Logger logger, long minTimeForLogging, TimeUnit timeUnit) { return new LoggingElapsedTimeReceiver( taskDescription, logger, minTimeForLogging, timeUnit); } }; protected final Logger logger; protected LoggingElapsedTimeReceiver( String taskDescription, Logger logger, long minTimeForLogging, TimeUnit timeUnit) { super(taskDescription, minTimeForLogging, timeUnit); this.logger = logger; } @Override protected void log(String logMessage) { logger.info(logMessage); } } private static class ProfilingElapsedTimeReceiver implements ElapsedTimeReceiver { private final long startTimeNanos; private final Object object; private final ProfilerTask profilerTaskType; private ProfilingElapsedTimeReceiver(Object object, ProfilerTask profilerTaskType) { this.startTimeNanos = Profiler.nanoTimeMaybe(); this.object = object; this.profilerTaskType = profilerTaskType; } @Override public void accept(long elapsedTimeNanos) { if (elapsedTimeNanos > 0) { Profiler.instance().logSimpleTaskDuration(startTimeNanos, elapsedTimeNanos, profilerTaskType, object); } } } }