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}