1 /*
2  * Copyright (C) 2015 The Dagger Authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package dagger.producers.monitoring;
18 
19 import com.google.common.collect.ImmutableList;
20 import com.google.common.collect.Iterables;
21 import dagger.internal.Beta;
22 import java.util.Collection;
23 import java.util.logging.Level;
24 import java.util.logging.Logger;
25 
26 /**
27  * Utility methods relating to timing.
28  *
29  * @since 2.1
30  */
31 // TODO(beder): Reduce the visibility of this class to package-private.
32 @Beta
33 @SuppressWarnings("GoodTime") // Should be using java.time.Instant/Duration as opposed to nanos
34 public final class TimingRecorders {
35   private static final Logger logger = Logger.getLogger(TimingRecorders.class.getName());
36 
37   /**
38    * Returns a timing recorder factory that delegates to the given factories, and ensures that any
39    * method called on this object, even transitively, does not throw a {@link RuntimeException} or
40    * return null.
41    *
42    * <p>If the delegate recorders throw an {@link Error}, then that will escape this recorder
43    * implementation. Errors are treated as unrecoverable conditions, and may cause the entire
44    * component's execution to fail.
45    */
46   public static ProductionComponentTimingRecorder.Factory
delegatingProductionComponentTimingRecorderFactory( Collection<ProductionComponentTimingRecorder.Factory> factories)47       delegatingProductionComponentTimingRecorderFactory(
48           Collection<ProductionComponentTimingRecorder.Factory> factories) {
49     switch (factories.size()) {
50       case 0:
51         return noOpProductionComponentTimingRecorderFactory();
52       case 1:
53         return new NonThrowingProductionComponentTimingRecorder.Factory(
54             Iterables.getOnlyElement(factories));
55       default:
56         return new DelegatingProductionComponentTimingRecorder.Factory(factories);
57     }
58   }
59 
60   /**
61    * A component recorder that delegates to a single recorder, and catches and logs all exceptions
62    * that the delegate throws.
63    */
64   private static final class NonThrowingProductionComponentTimingRecorder
65       implements ProductionComponentTimingRecorder {
66     private final ProductionComponentTimingRecorder delegate;
67 
NonThrowingProductionComponentTimingRecorder(ProductionComponentTimingRecorder delegate)68     NonThrowingProductionComponentTimingRecorder(ProductionComponentTimingRecorder delegate) {
69       this.delegate = delegate;
70     }
71 
72     @Override
producerTimingRecorderFor(ProducerToken token)73     public ProducerTimingRecorder producerTimingRecorderFor(ProducerToken token) {
74       try {
75         ProducerTimingRecorder recorder = delegate.producerTimingRecorderFor(token);
76         return recorder == null
77             ? ProducerTimingRecorder.noOp()
78             : new NonThrowingProducerTimingRecorder(recorder);
79       } catch (RuntimeException e) {
80         logProducerTimingRecorderForException(e, delegate, token);
81         return ProducerTimingRecorder.noOp();
82       }
83     }
84 
85     static final class Factory implements ProductionComponentTimingRecorder.Factory {
86       private final ProductionComponentTimingRecorder.Factory delegate;
87 
Factory(ProductionComponentTimingRecorder.Factory delegate)88       Factory(ProductionComponentTimingRecorder.Factory delegate) {
89         this.delegate = delegate;
90       }
91 
92       @Override
create(Object component)93       public ProductionComponentTimingRecorder create(Object component) {
94         try {
95           ProductionComponentTimingRecorder recorder = delegate.create(component);
96           return recorder == null
97               ? noOpProductionComponentTimingRecorder()
98               : new NonThrowingProductionComponentTimingRecorder(recorder);
99         } catch (RuntimeException e) {
100           logCreateException(e, delegate, component);
101           return noOpProductionComponentTimingRecorder();
102         }
103       }
104     }
105   }
106 
107   /**
108    * A producer recorder that delegates to a single recorder, and catches and logs all exceptions
109    * that the delegate throws.
110    */
111   private static final class NonThrowingProducerTimingRecorder extends ProducerTimingRecorder {
112     private final ProducerTimingRecorder delegate;
113 
NonThrowingProducerTimingRecorder(ProducerTimingRecorder delegate)114     NonThrowingProducerTimingRecorder(ProducerTimingRecorder delegate) {
115       this.delegate = delegate;
116     }
117 
118     @Override
recordMethod(long startedNanos, long durationNanos)119     public void recordMethod(long startedNanos, long durationNanos) {
120       try {
121         delegate.recordMethod(startedNanos, durationNanos);
122       } catch (RuntimeException e) {
123         logProducerTimingRecorderMethodException(e, delegate, "recordMethod");
124       }
125     }
126 
127     @Override
recordSuccess(long latencyNanos)128     public void recordSuccess(long latencyNanos) {
129       try {
130         delegate.recordSuccess(latencyNanos);
131       } catch (RuntimeException e) {
132         logProducerTimingRecorderMethodException(e, delegate, "recordSuccess");
133       }
134     }
135 
136     @Override
recordFailure(Throwable exception, long latencyNanos)137     public void recordFailure(Throwable exception, long latencyNanos) {
138       try {
139         delegate.recordFailure(exception, latencyNanos);
140       } catch (RuntimeException e) {
141         logProducerTimingRecorderMethodException(e, delegate, "recordFailure");
142       }
143     }
144 
145     @Override
recordSkip(Throwable exception)146     public void recordSkip(Throwable exception) {
147       try {
148         delegate.recordSkip(exception);
149       } catch (RuntimeException e) {
150         logProducerTimingRecorderMethodException(e, delegate, "recordSkip");
151       }
152     }
153   }
154 
155   /**
156    * A component recorder that delegates to several recorders, and catches and logs all exceptions
157    * that the delegates throw.
158    */
159   private static final class DelegatingProductionComponentTimingRecorder
160       implements ProductionComponentTimingRecorder {
161     private final ImmutableList<ProductionComponentTimingRecorder> delegates;
162 
DelegatingProductionComponentTimingRecorder( ImmutableList<ProductionComponentTimingRecorder> delegates)163     DelegatingProductionComponentTimingRecorder(
164         ImmutableList<ProductionComponentTimingRecorder> delegates) {
165       this.delegates = delegates;
166     }
167 
168     @Override
producerTimingRecorderFor(ProducerToken token)169     public ProducerTimingRecorder producerTimingRecorderFor(ProducerToken token) {
170       ImmutableList.Builder<ProducerTimingRecorder> recordersBuilder = ImmutableList.builder();
171       for (ProductionComponentTimingRecorder delegate : delegates) {
172         try {
173           ProducerTimingRecorder recorder = delegate.producerTimingRecorderFor(token);
174           if (recorder != null) {
175             recordersBuilder.add(recorder);
176           }
177         } catch (RuntimeException e) {
178           logProducerTimingRecorderForException(e, delegate, token);
179         }
180       }
181       ImmutableList<ProducerTimingRecorder> recorders = recordersBuilder.build();
182       switch (recorders.size()) {
183         case 0:
184           return ProducerTimingRecorder.noOp();
185         case 1:
186           return new NonThrowingProducerTimingRecorder(Iterables.getOnlyElement(recorders));
187         default:
188           return new DelegatingProducerTimingRecorder(recorders);
189       }
190     }
191 
192     static final class Factory implements ProductionComponentTimingRecorder.Factory {
193       private final ImmutableList<? extends ProductionComponentTimingRecorder.Factory> delegates;
194 
Factory(Iterable<? extends ProductionComponentTimingRecorder.Factory> delegates)195       Factory(Iterable<? extends ProductionComponentTimingRecorder.Factory> delegates) {
196         this.delegates = ImmutableList.copyOf(delegates);
197       }
198 
199       @Override
create(Object component)200       public ProductionComponentTimingRecorder create(Object component) {
201         ImmutableList.Builder<ProductionComponentTimingRecorder> recordersBuilder =
202             ImmutableList.builder();
203         for (ProductionComponentTimingRecorder.Factory delegate : delegates) {
204           try {
205             ProductionComponentTimingRecorder recorder = delegate.create(component);
206             if (recorder != null) {
207               recordersBuilder.add(recorder);
208             }
209           } catch (RuntimeException e) {
210             logCreateException(e, delegate, component);
211           }
212         }
213         ImmutableList<ProductionComponentTimingRecorder> recorders = recordersBuilder.build();
214         switch (recorders.size()) {
215           case 0:
216             return noOpProductionComponentTimingRecorder();
217           case 1:
218             return new NonThrowingProductionComponentTimingRecorder(
219                 Iterables.getOnlyElement(recorders));
220           default:
221             return new DelegatingProductionComponentTimingRecorder(recorders);
222         }
223       }
224     }
225   }
226 
227   /**
228    * A producer recorder that delegates to several recorders, and catches and logs all exceptions
229    * that the delegates throw.
230    */
231   private static final class DelegatingProducerTimingRecorder extends ProducerTimingRecorder {
232     private final ImmutableList<ProducerTimingRecorder> delegates;
233 
DelegatingProducerTimingRecorder(ImmutableList<ProducerTimingRecorder> delegates)234     DelegatingProducerTimingRecorder(ImmutableList<ProducerTimingRecorder> delegates) {
235       this.delegates = delegates;
236     }
237 
238     @Override
recordMethod(long startedNanos, long durationNanos)239     public void recordMethod(long startedNanos, long durationNanos) {
240       for (ProducerTimingRecorder delegate : delegates) {
241         try {
242           delegate.recordMethod(startedNanos, durationNanos);
243         } catch (RuntimeException e) {
244           logProducerTimingRecorderMethodException(e, delegate, "recordMethod");
245         }
246       }
247     }
248 
249     @Override
recordSuccess(long latencyNanos)250     public void recordSuccess(long latencyNanos) {
251       for (ProducerTimingRecorder delegate : delegates) {
252         try {
253           delegate.recordSuccess(latencyNanos);
254         } catch (RuntimeException e) {
255           logProducerTimingRecorderMethodException(e, delegate, "recordSuccess");
256         }
257       }
258     }
259 
260     @Override
recordFailure(Throwable exception, long latencyNanos)261     public void recordFailure(Throwable exception, long latencyNanos) {
262       for (ProducerTimingRecorder delegate : delegates) {
263         try {
264           delegate.recordFailure(exception, latencyNanos);
265         } catch (RuntimeException e) {
266           logProducerTimingRecorderMethodException(e, delegate, "recordFailure");
267         }
268       }
269     }
270 
271     @Override
recordSkip(Throwable exception)272     public void recordSkip(Throwable exception) {
273       for (ProducerTimingRecorder delegate : delegates) {
274         try {
275           delegate.recordSkip(exception);
276         } catch (RuntimeException e) {
277           logProducerTimingRecorderMethodException(e, delegate, "recordSkip");
278         }
279       }
280     }
281   }
282 
283   /** Returns a recorder factory that returns no-op component recorders. */
284   public static ProductionComponentTimingRecorder.Factory
noOpProductionComponentTimingRecorderFactory()285       noOpProductionComponentTimingRecorderFactory() {
286     return NO_OP_PRODUCTION_COMPONENT_TIMING_RECORDER_FACTORY;
287   }
288 
289   /** Returns a component recorder that returns no-op producer recorders. */
noOpProductionComponentTimingRecorder()290   public static ProductionComponentTimingRecorder noOpProductionComponentTimingRecorder() {
291     return NO_OP_PRODUCTION_COMPONENT_TIMING_RECORDER;
292   }
293 
294   private static final ProductionComponentTimingRecorder.Factory
295       NO_OP_PRODUCTION_COMPONENT_TIMING_RECORDER_FACTORY =
296           new ProductionComponentTimingRecorder.Factory() {
297             @Override
298             public ProductionComponentTimingRecorder create(Object component) {
299               return noOpProductionComponentTimingRecorder();
300             }
301           };
302 
303   private static final ProductionComponentTimingRecorder
304       NO_OP_PRODUCTION_COMPONENT_TIMING_RECORDER =
305           new ProductionComponentTimingRecorder() {
306             @Override
307             public ProducerTimingRecorder producerTimingRecorderFor(ProducerToken token) {
308               return ProducerTimingRecorder.noOp();
309             }
310           };
311 
logCreateException( RuntimeException e, ProductionComponentTimingRecorder.Factory factory, Object component)312   private static void logCreateException(
313       RuntimeException e, ProductionComponentTimingRecorder.Factory factory, Object component) {
314     logger.log(
315         Level.SEVERE,
316         "RuntimeException while calling ProductionComponentTimingRecorder.Factory.create on"
317             + " factory "
318             + factory
319             + " with component "
320             + component,
321         e);
322   }
323 
logProducerTimingRecorderForException( RuntimeException e, ProductionComponentTimingRecorder recorder, ProducerToken token)324   private static void logProducerTimingRecorderForException(
325       RuntimeException e, ProductionComponentTimingRecorder recorder, ProducerToken token) {
326     logger.log(
327         Level.SEVERE,
328         "RuntimeException while calling ProductionComponentTimingRecorder.producerTimingRecorderFor"
329             + "on recorder "
330             + recorder
331             + " with token "
332             + token,
333         e);
334   }
335 
logProducerTimingRecorderMethodException( RuntimeException e, ProducerTimingRecorder recorder, String method)336   private static void logProducerTimingRecorderMethodException(
337       RuntimeException e, ProducerTimingRecorder recorder, String method) {
338     logger.log(
339         Level.SEVERE,
340         "RuntimeException while calling ProducerTimingRecorder."
341             + method
342             + " on recorder "
343             + recorder,
344         e);
345   }
346 
TimingRecorders()347   private TimingRecorders() {}
348 }
349