001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.rest.server.stats;
018
019import static org.apache.juneau.commons.utils.CollectionUtils.*;
020import static org.apache.juneau.commons.utils.Shorts.*;
021
022import java.lang.reflect.*;
023import java.util.*;
024import java.util.concurrent.atomic.*;
025
026import org.apache.juneau.commons.collections.*;
027import org.apache.juneau.commons.inject.*;
028
029/**
030 * Method execution statistics.
031 *
032 * Keeps track of number of starts/finishes on tasks and keeps an average run time.
033 *
034 * <h5 class='section'>See Also:</h5><ul>
035 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/ExecutionStatistics">REST method execution statistics</a>
036 * </ul>
037 */
038@SuppressWarnings({
039   "java:S1192" // Duplicated literals (argument/property names) read more clearly inline than as constants
040})
041public class MethodExecStats {
042
043   /**
044    * Builder class.
045    */
046   public static class Builder {
047
048      final BeanStore beanStore;
049      Method method;
050      ThrownStore thrownStore;
051
052      private MethodExecStats impl;
053      private Class<? extends MethodExecStats> type;
054
055      /**
056       * Constructor.
057       *
058       * @param beanStore The bean store to use for creating beans.
059       */
060      protected Builder(BeanStore beanStore) {
061         this.beanStore = beanStore;
062      }
063
064      /**
065       * Returns the bean store.
066       *
067       * @return The bean store.
068       */
069      public BeanStore beanStore() {
070         return beanStore;
071      }
072
073      /**
074       * Overrides the bean returned by the {@link #build()} method with a pre-instantiated instance.
075       *
076       * @param value The setting value. Can be <jk>null</jk> (the {@link #build()} method instantiates the {@link #type(Class) type} instead).
077       * @return This object.
078       */
079      public Builder impl(Object value) {
080         impl = (MethodExecStats) value;
081         return this;
082      }
083
084      /**
085       * Specifies the Java method.
086       *
087       * @param value The new value for this setting.
088       * @return This object.
089       */
090      public Builder method(Method value) {
091         method = value;
092         return this;
093      }
094
095      /**
096       * Specifies the thrown store for tracking exceptions.
097       *
098       * @param value The new value for this setting.
099       * @return This object.
100       */
101      public Builder thrownStore(ThrownStore value) {
102         thrownStore = value;
103         return this;
104      }
105
106      /**
107       * Overrides the bean type produced by the {@link #build()} method.
108       *
109       * @param value The setting value. Can be <jk>null</jk> (defaults to {@link MethodExecStats}).
110       * @return This object.
111       */
112      @SuppressWarnings({
113         "unchecked" // Type erasure on reflective/generic cast; element type is verified at call site
114      })
115      public Builder type(Class<?> value) {
116         type = (Class<? extends MethodExecStats>) value;
117         return this;
118      }
119
120      /**
121       * Creates the bean.
122       *
123       * @return A new bean.
124       */
125      public MethodExecStats build() {
126         if (nn(impl))
127            return impl;
128         if (type == null || type == MethodExecStats.class)
129            return new MethodExecStats(this);
130         return BeanInstantiator.of(MethodExecStats.class, beanStore)
131            .type(type)
132            .noBuilder()
133            .addBean(Builder.class, this)
134            .run();
135      }
136   }
137
138   /**
139    * Static creator.
140    *
141    * @param beanStore The bean store to use for creating beans.
142    * @return A new builder for this object.
143    */
144   public static Builder create(BeanStore beanStore) {
145      return new Builder(beanStore);
146   }
147
148   private final long guid;
149   private final Method method;
150   private final ThrownStore thrownStore;
151
152   private final AtomicInteger maxTime = new AtomicInteger();
153   private final AtomicInteger minTime = new AtomicInteger(-1);
154
155   private final AtomicInteger starts = new AtomicInteger();
156   private final AtomicInteger finishes = new AtomicInteger();
157   private final AtomicInteger errors = new AtomicInteger();
158
159   private final AtomicLong totalTime = new AtomicLong();
160
161   /**
162    * Constructor.
163    *
164    * @param builder The builder for this object. Must not be <jk>null</jk>.
165    */
166   protected MethodExecStats(Builder builder) {
167      this.guid = new Random().nextLong();
168      this.method = builder.method;
169      this.thrownStore = nn(builder.thrownStore) ? builder.thrownStore : new ThrownStore();
170   }
171
172   /**
173    * Call when an error occurs.
174    *
175    * @param e The exception thrown.  Must not be <jk>null</jk> (a <jk>null</jk> argument throws {@link NullPointerException} via {@link ThrownStore#add(Throwable)}).
176    * @return This object.
177    */
178   public MethodExecStats error(Throwable e) {
179      errors.incrementAndGet();
180      thrownStore.add(e);
181      return this;
182   }
183
184   /**
185    * Call when task is finished.
186    *
187    * @param nanoTime The execution time of the task in nanoseconds.
188    * @return This object.
189    */
190   public MethodExecStats finished(long nanoTime) {
191      finishes.incrementAndGet();
192      int milliTime = (int)(nanoTime / 1_000_000);
193      totalTime.addAndGet(nanoTime);
194      int currentMin = minTime.get();
195      if (currentMin == -1) {
196         minTime.compareAndSet(-1, milliTime);
197      } else {
198         minTime.updateAndGet(x -> Math.min(x, milliTime));
199      }
200      maxTime.updateAndGet(x -> Math.max(x, milliTime));
201      return this;
202   }
203
204   /**
205    * Returns the average execution time.
206    *
207    * @return The average execution time in milliseconds.
208    */
209   public int getAvgTime() {
210      int runs = finishes.get();
211      return runs == 0 ? 0 : (int)(getTotalTime() / runs);
212   }
213
214   /**
215    * Returns the number of times the {@link #error(Throwable)} method was called.
216    *
217    * @return The number of times the {@link #error(Throwable)} method was called.
218    */
219   public int getErrors() { return errors.get(); }
220
221   /**
222    * Returns a globally unique ID for this object.
223    *
224    * <p>
225    * A random long generated during the creation of this object.
226    * Allows this object to be differentiated from other similar objects in multi-node environments so that
227    * statistics can be reliably stored in a centralized location.
228    *
229    * @return The globally unique ID for this object.
230    */
231   public long getGuid() { return guid; }
232
233   /**
234    * Returns the max execution time.
235    *
236    * @return The max execution time in milliseconds.
237    */
238   public int getMaxTime() { return maxTime.get(); }
239
240   /**
241    * Returns the method name of these stats.
242    *
243    * @return The method name of these stats.
244    */
245   public Method getMethod() { return method; }
246
247   /**
248    * Returns the minimum execution time.
249    *
250    * @return The minimum execution time in milliseconds.
251    */
252   public int getMinTime() { 
253      int value = minTime.get();
254      return value == -1 ? 0 : value;
255   }
256
257   /**
258    * Returns the number currently running method invocations.
259    *
260    * @return The number of currently running method invocations.
261    */
262   public int getRunning() { return starts.get() - finishes.get(); }
263
264   /**
265    * Returns the number of times the {@link #started()} method was called.
266    *
267    * @return The number of times the {@link #started()} method was called.
268    */
269   public int getRuns() { return starts.get(); }
270
271   /**
272    * Returns information on all stack traces of all exceptions encountered.
273    *
274    * @return Information on all stack traces of all exceptions encountered.
275    */
276   public ThrownStore getThrownStore() { return thrownStore; }
277
278   /**
279    * Returns the total execution time.
280    *
281    * @return The total execution time in milliseconds.
282    */
283   public long getTotalTime() { return totalTime.get() / 1_000_000; }
284
285   /**
286    * Call when task is started.
287    *
288    * @return This object.
289    */
290   public MethodExecStats started() {
291      starts.incrementAndGet();
292      return this;
293   }
294
295   protected FluentMap<String,Object> properties() {
296      // @formatter:off
297      return filteredBeanPropertyMap()
298         .a("avgTime", getAvgTime())
299         .a("errors", getErrors())
300         .a("guid", guid)
301         .a("maxTime", getMaxTime())
302         .a("method", method)
303         .a("minTime", getMinTime())
304         .a("running", getRunning())
305         .a("runs", getRuns())
306         .a("thrownStore", thrownStore)
307         .a("totalTime", getTotalTime());
308      // @formatter:on
309   }
310
311   @Override /* Overridden from Object */
312   public String toString() {
313      return r(properties());
314   }
315}