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