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