blob: 5fbe3e349ad231455944e954dc861ddad6ee6ec3 [file] [log] [blame]
/*
* Copyright (C) 2015 The Dagger Authors.
*
* 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 dagger.producers.monitoring;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import dagger.internal.Beta;
import java.util.Collection;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Utility methods relating to timing.
*
* @since 2.1
*/
// TODO(beder): Reduce the visibility of this class to package-private.
@Beta
public final class TimingRecorders {
private static final Logger logger = Logger.getLogger(TimingRecorders.class.getName());
/**
* Returns a timing recorder factory that delegates to the given factories, and ensures that any
* method called on this object, even transitively, does not throw a {@link RuntimeException} or
* return null.
*
* <p>If the delegate recorders throw an {@link Error}, then that will escape this recorder
* implementation. Errors are treated as unrecoverable conditions, and may cause the entire
* component's execution to fail.
*/
public static ProductionComponentTimingRecorder.Factory
delegatingProductionComponentTimingRecorderFactory(
Collection<ProductionComponentTimingRecorder.Factory> factories) {
switch (factories.size()) {
case 0:
return noOpProductionComponentTimingRecorderFactory();
case 1:
return new NonThrowingProductionComponentTimingRecorder.Factory(
Iterables.getOnlyElement(factories));
default:
return new DelegatingProductionComponentTimingRecorder.Factory(factories);
}
}
/**
* A component recorder that delegates to a single recorder, and catches and logs all exceptions
* that the delegate throws.
*/
private static final class NonThrowingProductionComponentTimingRecorder
implements ProductionComponentTimingRecorder {
private final ProductionComponentTimingRecorder delegate;
NonThrowingProductionComponentTimingRecorder(ProductionComponentTimingRecorder delegate) {
this.delegate = delegate;
}
@Override
public ProducerTimingRecorder producerTimingRecorderFor(ProducerToken token) {
try {
ProducerTimingRecorder recorder = delegate.producerTimingRecorderFor(token);
return recorder == null
? ProducerTimingRecorder.noOp()
: new NonThrowingProducerTimingRecorder(recorder);
} catch (RuntimeException e) {
logProducerTimingRecorderForException(e, delegate, token);
return ProducerTimingRecorder.noOp();
}
}
static final class Factory implements ProductionComponentTimingRecorder.Factory {
private final ProductionComponentTimingRecorder.Factory delegate;
Factory(ProductionComponentTimingRecorder.Factory delegate) {
this.delegate = delegate;
}
@Override
public ProductionComponentTimingRecorder create(Object component) {
try {
ProductionComponentTimingRecorder recorder = delegate.create(component);
return recorder == null
? noOpProductionComponentTimingRecorder()
: new NonThrowingProductionComponentTimingRecorder(recorder);
} catch (RuntimeException e) {
logCreateException(e, delegate, component);
return noOpProductionComponentTimingRecorder();
}
}
}
}
/**
* A producer recorder that delegates to a single recorder, and catches and logs all exceptions
* that the delegate throws.
*/
private static final class NonThrowingProducerTimingRecorder extends ProducerTimingRecorder {
private final ProducerTimingRecorder delegate;
NonThrowingProducerTimingRecorder(ProducerTimingRecorder delegate) {
this.delegate = delegate;
}
@Override
public void recordMethod(long startedNanos, long durationNanos) {
try {
delegate.recordMethod(startedNanos, durationNanos);
} catch (RuntimeException e) {
logProducerTimingRecorderMethodException(e, delegate, "recordMethod");
}
}
@Override
public void recordSuccess(long latencyNanos) {
try {
delegate.recordSuccess(latencyNanos);
} catch (RuntimeException e) {
logProducerTimingRecorderMethodException(e, delegate, "recordSuccess");
}
}
@Override
public void recordFailure(Throwable exception, long latencyNanos) {
try {
delegate.recordFailure(exception, latencyNanos);
} catch (RuntimeException e) {
logProducerTimingRecorderMethodException(e, delegate, "recordFailure");
}
}
@Override
public void recordSkip(Throwable exception) {
try {
delegate.recordSkip(exception);
} catch (RuntimeException e) {
logProducerTimingRecorderMethodException(e, delegate, "recordSkip");
}
}
}
/**
* A component recorder that delegates to several recorders, and catches and logs all exceptions
* that the delegates throw.
*/
private static final class DelegatingProductionComponentTimingRecorder
implements ProductionComponentTimingRecorder {
private final ImmutableList<ProductionComponentTimingRecorder> delegates;
DelegatingProductionComponentTimingRecorder(
ImmutableList<ProductionComponentTimingRecorder> delegates) {
this.delegates = delegates;
}
@Override
public ProducerTimingRecorder producerTimingRecorderFor(ProducerToken token) {
ImmutableList.Builder<ProducerTimingRecorder> recordersBuilder = ImmutableList.builder();
for (ProductionComponentTimingRecorder delegate : delegates) {
try {
ProducerTimingRecorder recorder = delegate.producerTimingRecorderFor(token);
if (recorder != null) {
recordersBuilder.add(recorder);
}
} catch (RuntimeException e) {
logProducerTimingRecorderForException(e, delegate, token);
}
}
ImmutableList<ProducerTimingRecorder> recorders = recordersBuilder.build();
switch (recorders.size()) {
case 0:
return ProducerTimingRecorder.noOp();
case 1:
return new NonThrowingProducerTimingRecorder(Iterables.getOnlyElement(recorders));
default:
return new DelegatingProducerTimingRecorder(recorders);
}
}
static final class Factory implements ProductionComponentTimingRecorder.Factory {
private final ImmutableList<? extends ProductionComponentTimingRecorder.Factory> delegates;
Factory(Iterable<? extends ProductionComponentTimingRecorder.Factory> delegates) {
this.delegates = ImmutableList.copyOf(delegates);
}
@Override
public ProductionComponentTimingRecorder create(Object component) {
ImmutableList.Builder<ProductionComponentTimingRecorder> recordersBuilder =
ImmutableList.builder();
for (ProductionComponentTimingRecorder.Factory delegate : delegates) {
try {
ProductionComponentTimingRecorder recorder = delegate.create(component);
if (recorder != null) {
recordersBuilder.add(recorder);
}
} catch (RuntimeException e) {
logCreateException(e, delegate, component);
}
}
ImmutableList<ProductionComponentTimingRecorder> recorders = recordersBuilder.build();
switch (recorders.size()) {
case 0:
return noOpProductionComponentTimingRecorder();
case 1:
return new NonThrowingProductionComponentTimingRecorder(
Iterables.getOnlyElement(recorders));
default:
return new DelegatingProductionComponentTimingRecorder(recorders);
}
}
}
}
/**
* A producer recorder that delegates to several recorders, and catches and logs all exceptions
* that the delegates throw.
*/
private static final class DelegatingProducerTimingRecorder extends ProducerTimingRecorder {
private final ImmutableList<ProducerTimingRecorder> delegates;
DelegatingProducerTimingRecorder(ImmutableList<ProducerTimingRecorder> delegates) {
this.delegates = delegates;
}
@Override
public void recordMethod(long startedNanos, long durationNanos) {
for (ProducerTimingRecorder delegate : delegates) {
try {
delegate.recordMethod(startedNanos, durationNanos);
} catch (RuntimeException e) {
logProducerTimingRecorderMethodException(e, delegate, "recordMethod");
}
}
}
@Override
public void recordSuccess(long latencyNanos) {
for (ProducerTimingRecorder delegate : delegates) {
try {
delegate.recordSuccess(latencyNanos);
} catch (RuntimeException e) {
logProducerTimingRecorderMethodException(e, delegate, "recordSuccess");
}
}
}
@Override
public void recordFailure(Throwable exception, long latencyNanos) {
for (ProducerTimingRecorder delegate : delegates) {
try {
delegate.recordFailure(exception, latencyNanos);
} catch (RuntimeException e) {
logProducerTimingRecorderMethodException(e, delegate, "recordFailure");
}
}
}
@Override
public void recordSkip(Throwable exception) {
for (ProducerTimingRecorder delegate : delegates) {
try {
delegate.recordSkip(exception);
} catch (RuntimeException e) {
logProducerTimingRecorderMethodException(e, delegate, "recordSkip");
}
}
}
}
/** Returns a recorder factory that returns no-op component recorders. */
public static ProductionComponentTimingRecorder.Factory
noOpProductionComponentTimingRecorderFactory() {
return NO_OP_PRODUCTION_COMPONENT_TIMING_RECORDER_FACTORY;
}
/** Returns a component recorder that returns no-op producer recorders. */
public static ProductionComponentTimingRecorder noOpProductionComponentTimingRecorder() {
return NO_OP_PRODUCTION_COMPONENT_TIMING_RECORDER;
}
private static final ProductionComponentTimingRecorder.Factory
NO_OP_PRODUCTION_COMPONENT_TIMING_RECORDER_FACTORY =
new ProductionComponentTimingRecorder.Factory() {
@Override
public ProductionComponentTimingRecorder create(Object component) {
return noOpProductionComponentTimingRecorder();
}
};
private static final ProductionComponentTimingRecorder
NO_OP_PRODUCTION_COMPONENT_TIMING_RECORDER =
new ProductionComponentTimingRecorder() {
@Override
public ProducerTimingRecorder producerTimingRecorderFor(ProducerToken token) {
return ProducerTimingRecorder.noOp();
}
};
private static void logCreateException(
RuntimeException e, ProductionComponentTimingRecorder.Factory factory, Object component) {
logger.log(
Level.SEVERE,
"RuntimeException while calling ProductionComponentTimingRecorder.Factory.create on"
+ " factory "
+ factory
+ " with component "
+ component,
e);
}
private static void logProducerTimingRecorderForException(
RuntimeException e, ProductionComponentTimingRecorder recorder, ProducerToken token) {
logger.log(
Level.SEVERE,
"RuntimeException while calling ProductionComponentTimingRecorder.producerTimingRecorderFor"
+ "on recorder "
+ recorder
+ " with token "
+ token,
e);
}
private static void logProducerTimingRecorderMethodException(
RuntimeException e, ProducerTimingRecorder recorder, String method) {
logger.log(
Level.SEVERE,
"RuntimeException while calling ProducerTimingRecorder."
+ method
+ " on recorder "
+ recorder,
e);
}
private TimingRecorders() {}
}