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.stats;
018
019import static java.util.stream.Collectors.*;
020
021import java.lang.reflect.*;
022import java.util.*;
023import java.util.concurrent.*;
024
025import org.apache.juneau.*;
026import org.apache.juneau.cp.*;
027import org.apache.juneau.internal.*;
028
029/**
030 * Method execution statistics database.
031 *
032 * <p>
033 * Used for tracking basic call statistics on Java methods.
034 *
035 * <h5 class='section'>See Also:</h5><ul>
036 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/ExecutionStatistics">REST method execution statistics</a>
037 * </ul>
038 */
039public class MethodExecStore {
040
041   //-----------------------------------------------------------------------------------------------------------------
042   // Static
043   //-----------------------------------------------------------------------------------------------------------------
044
045   /**
046    * Static creator.
047    *
048    * @param beanStore The bean store to use for creating beans.
049    * @return A new builder for this object.
050    */
051   public static Builder create(BeanStore beanStore) {
052      return new Builder(beanStore);
053   }
054
055   /**
056    * Static creator.
057    *
058    * @return A new builder for this object.
059    */
060   public static Builder create() {
061      return new Builder(BeanStore.INSTANCE);
062   }
063
064   //-----------------------------------------------------------------------------------------------------------------
065   // Builder
066   //-----------------------------------------------------------------------------------------------------------------
067
068   /**
069    * Builder class.
070    */
071   public static class Builder extends BeanBuilder<MethodExecStore> {
072
073      ThrownStore thrownStore;
074      Class<? extends MethodExecStats> statsImplClass;
075
076      /**
077       * Constructor.
078       *
079       * @param beanStore The bean store to use for creating beans.
080       */
081      protected Builder(BeanStore beanStore) {
082         super(MethodExecStore.class, beanStore);
083      }
084
085      @Override /* BeanBuilder */
086      protected MethodExecStore buildDefault() {
087         return new MethodExecStore(this);
088      }
089
090      //-------------------------------------------------------------------------------------------------------------
091      // Properties
092      //-------------------------------------------------------------------------------------------------------------
093
094      /**
095       * Specifies a subclass of {@link MethodExecStats} to use for individual method statistics.
096       *
097       * @param value The new value for this setting.
098       * @return  This object.
099       */
100      public Builder statsImplClass(Class<? extends MethodExecStats> value) {
101         statsImplClass = value;
102         return this;
103      }
104
105      /**
106       * Specifies the store to use for gathering statistics on thrown exceptions.
107       *
108       * <p>
109       * Can be used to capture thrown exception stats across multiple {@link MethodExecStore} objects.
110       *
111       * <p>
112       * If not specified, one will be created by default for the {@link MethodExecStore} object.
113       *
114       * @param value The store to use for gathering statistics on thrown exceptions.
115       * @return This object.
116       */
117      public Builder thrownStore(ThrownStore value) {
118         thrownStore = value;
119         return this;
120      }
121
122      /**
123       * Same as {@link #thrownStore(ThrownStore)} but only sets the new value if the current value is <jk>null</jk>.
124       *
125       * @param value The new value for this setting.
126       * @return This object.
127       */
128      public Builder thrownStoreOnce(ThrownStore value) {
129         if (thrownStore == null)
130            thrownStore = value;
131         return this;
132      }
133      @Override /* Overridden from BeanBuilder */
134      public Builder impl(Object value) {
135         super.impl(value);
136         return this;
137      }
138
139      @Override /* Overridden from BeanBuilder */
140      public Builder type(Class<?> value) {
141         super.type(value);
142         return this;
143      }
144   }
145
146   //-----------------------------------------------------------------------------------------------------------------
147   // Instance
148   //-----------------------------------------------------------------------------------------------------------------
149
150   private final ThrownStore thrownStore;
151   private final BeanStore beanStore;
152   private final Class<? extends MethodExecStats> statsImplClass;
153   private final ConcurrentHashMap<Method,MethodExecStats> db = new ConcurrentHashMap<>();
154
155   /**
156    * Constructor.
157    *
158    * @param builder The store to use for storing thrown exception statistics.
159    */
160   protected MethodExecStore(Builder builder) {
161      this.beanStore = builder.beanStore();
162      this.thrownStore = builder.thrownStore != null ? builder.thrownStore : beanStore.getBean(ThrownStore.class).orElseGet(ThrownStore::new);
163      this.statsImplClass = builder.statsImplClass;
164   }
165
166   /**
167    * Returns the statistics for the specified method.
168    *
169    * <p>
170    * Creates a new stats object if one has not already been created.
171    *
172    * @param m The method to return the statistics for.
173    * @return The statistics for the specified method.  Never <jk>null</jk>.
174    */
175   public MethodExecStats getStats(Method m) {
176      MethodExecStats stats = db.get(m);
177      if (stats == null) {
178         stats = MethodExecStats
179            .create(beanStore)
180            .type(statsImplClass)
181            .method(m)
182            .thrownStore(ThrownStore.create(beanStore).parent(thrownStore).build())
183            .build();
184         db.putIfAbsent(m, stats);
185         stats = db.get(m);
186      }
187      return stats;
188   }
189
190   /**
191    * Returns all the statistics in this store.
192    *
193    * @return All the statistics in this store.
194    */
195   public Collection<MethodExecStats> getStats() {
196      return db.values();
197   }
198
199   /**
200    * Returns timing information on all method executions on this class.
201    *
202    * @return A list of timing statistics ordered by average execution time descending.
203    */
204   public List<MethodExecStats> getStatsByTotalTime() {
205      return getStats().stream().sorted(Comparator.comparingLong(MethodExecStats::getTotalTime).reversed()).collect(toList());
206   }
207
208   /**
209    * Returns the timing information returned by {@link #getStatsByTotalTime()} in a readable format.
210    *
211    * @return A report of all method execution times ordered by .
212    */
213   public String getReport() {
214      StringBuilder sb = new StringBuilder()
215         .append(" Method                         Runs      Running   Errors   Avg          Total     \n")
216         .append("------------------------------ --------- --------- -------- ------------ -----------\n");
217      getStatsByTotalTime()
218         .stream()
219         .sorted(Comparator.comparingDouble(MethodExecStats::getTotalTime).reversed())
220         .forEach(x -> sb.append(String.format("%30s %9d %9d %9d %10dms %10dms\n", x.getMethod(), x.getRuns(), x.getRunning(), x.getErrors(), x.getAvgTime(), x.getTotalTime())));
221      return sb.toString();
222
223   }
224
225   /**
226    * Returns the thrown exception store being used by this store.
227    *
228    * @return The thrown exception store being used by this store.
229    */
230   public ThrownStore getThrownStore() {
231      return thrownStore;
232   }
233}