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}