001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  You may obtain a copy of the License at                                                              *
006// *                                                                                                                         *
007// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
008// *                                                                                                                         *
009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau.rest.stats;
014
015import static java.util.stream.Collectors.*;
016import java.lang.reflect.*;
017import java.util.*;
018import java.util.concurrent.*;
019import org.apache.juneau.*;
020import org.apache.juneau.cp.*;
021import org.apache.juneau.internal.*;
022
023/**
024 * Method execution statistics database.
025 *
026 * <p>
027 * Used for tracking basic call statistics on Java methods.
028 *
029 * <h5 class='section'>See Also:</h5><ul>
030 *    <li class='link'><a class="doclink" href="../../../../../index.html#jrs.ExecutionStatistics">REST method execution statistics</a>
031 * </ul>
032 */
033public class MethodExecStore {
034
035   //-----------------------------------------------------------------------------------------------------------------
036   // Static
037   //-----------------------------------------------------------------------------------------------------------------
038
039   /**
040    * Static creator.
041    *
042    * @param beanStore The bean store to use for creating beans.
043    * @return A new builder for this object.
044    */
045   public static Builder create(BeanStore beanStore) {
046      return new Builder(beanStore);
047   }
048
049   /**
050    * Static creator.
051    *
052    * @return A new builder for this object.
053    */
054   public static Builder create() {
055      return new Builder(BeanStore.INSTANCE);
056   }
057
058   //-----------------------------------------------------------------------------------------------------------------
059   // Builder
060   //-----------------------------------------------------------------------------------------------------------------
061
062   /**
063    * Builder class.
064    */
065   @FluentSetters
066   public static class Builder extends BeanBuilder<MethodExecStore> {
067
068      ThrownStore thrownStore;
069      Class<? extends MethodExecStats> statsImplClass;
070
071      /**
072       * Constructor.
073       *
074       * @param beanStore The bean store to use for creating beans.
075       */
076      protected Builder(BeanStore beanStore) {
077         super(MethodExecStore.class, beanStore);
078      }
079
080      @Override /* BeanBuilder */
081      protected MethodExecStore buildDefault() {
082         return new MethodExecStore(this);
083      }
084
085      //-------------------------------------------------------------------------------------------------------------
086      // Properties
087      //-------------------------------------------------------------------------------------------------------------
088
089      /**
090       * Specifies a subclass of {@link MethodExecStats} to use for individual method statistics.
091       *
092       * @param value The new value for this setting.
093       * @return  This object.
094       */
095      public Builder statsImplClass(Class<? extends MethodExecStats> value) {
096         statsImplClass = value;
097         return this;
098      }
099
100      /**
101       * Specifies the store to use for gathering statistics on thrown exceptions.
102       *
103       * <p>
104       * Can be used to capture thrown exception stats across multiple {@link MethodExecStore} objects.
105       *
106       * <p>
107       * If not specified, one will be created by default for the {@link MethodExecStore} object.
108       *
109       * @param value The store to use for gathering statistics on thrown exceptions.
110       * @return This object.
111       */
112      public Builder thrownStore(ThrownStore value) {
113         thrownStore = value;
114         return this;
115      }
116
117      /**
118       * Same as {@link #thrownStore(ThrownStore)} but only sets the new value if the current value is <jk>null</jk>.
119       *
120       * @param value The new value for this setting.
121       * @return This object.
122       */
123      public Builder thrownStoreOnce(ThrownStore value) {
124         if (thrownStore == null)
125            thrownStore = value;
126         return this;
127      }
128
129      // <FluentSetters>
130
131      @Override /* GENERATED - org.apache.juneau.BeanBuilder */
132      public Builder impl(Object value) {
133         super.impl(value);
134         return this;
135      }
136
137      @Override /* GENERATED - org.apache.juneau.BeanBuilder */
138      public Builder type(Class<?> value) {
139         super.type(value);
140         return this;
141      }
142
143      // </FluentSetters>
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}