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.CollectionUtils.*; 020import static org.apache.juneau.commons.utils.Shorts.*; 021 022import java.lang.reflect.*; 023import java.util.*; 024import java.util.concurrent.atomic.*; 025 026import org.apache.juneau.commons.collections.*; 027import org.apache.juneau.commons.inject.*; 028 029/** 030 * Method execution statistics. 031 * 032 * Keeps track of number of starts/finishes on tasks and keeps an average run time. 033 * 034 * <h5 class='section'>See Also:</h5><ul> 035 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/ExecutionStatistics">REST method execution statistics</a> 036 * </ul> 037 */ 038@SuppressWarnings({ 039 "java:S1192" // Duplicated literals (argument/property names) read more clearly inline than as constants 040}) 041public class MethodExecStats { 042 043 /** 044 * Builder class. 045 */ 046 public static class Builder { 047 048 final BeanStore beanStore; 049 Method method; 050 ThrownStore thrownStore; 051 052 private MethodExecStats impl; 053 private Class<? extends MethodExecStats> type; 054 055 /** 056 * Constructor. 057 * 058 * @param beanStore The bean store to use for creating beans. 059 */ 060 protected Builder(BeanStore beanStore) { 061 this.beanStore = beanStore; 062 } 063 064 /** 065 * Returns the bean store. 066 * 067 * @return The bean store. 068 */ 069 public BeanStore beanStore() { 070 return beanStore; 071 } 072 073 /** 074 * Overrides the bean returned by the {@link #build()} method with a pre-instantiated instance. 075 * 076 * @param value The setting value. Can be <jk>null</jk> (the {@link #build()} method instantiates the {@link #type(Class) type} instead). 077 * @return This object. 078 */ 079 public Builder impl(Object value) { 080 impl = (MethodExecStats) value; 081 return this; 082 } 083 084 /** 085 * Specifies the Java method. 086 * 087 * @param value The new value for this setting. 088 * @return This object. 089 */ 090 public Builder method(Method value) { 091 method = value; 092 return this; 093 } 094 095 /** 096 * Specifies the thrown store for tracking exceptions. 097 * 098 * @param value The new value for this setting. 099 * @return This object. 100 */ 101 public Builder thrownStore(ThrownStore value) { 102 thrownStore = value; 103 return this; 104 } 105 106 /** 107 * Overrides the bean type produced by the {@link #build()} method. 108 * 109 * @param value The setting value. Can be <jk>null</jk> (defaults to {@link MethodExecStats}). 110 * @return This object. 111 */ 112 @SuppressWarnings({ 113 "unchecked" // Type erasure on reflective/generic cast; element type is verified at call site 114 }) 115 public Builder type(Class<?> value) { 116 type = (Class<? extends MethodExecStats>) value; 117 return this; 118 } 119 120 /** 121 * Creates the bean. 122 * 123 * @return A new bean. 124 */ 125 public MethodExecStats build() { 126 if (nn(impl)) 127 return impl; 128 if (type == null || type == MethodExecStats.class) 129 return new MethodExecStats(this); 130 return BeanInstantiator.of(MethodExecStats.class, beanStore) 131 .type(type) 132 .noBuilder() 133 .addBean(Builder.class, this) 134 .run(); 135 } 136 } 137 138 /** 139 * Static creator. 140 * 141 * @param beanStore The bean store to use for creating beans. 142 * @return A new builder for this object. 143 */ 144 public static Builder create(BeanStore beanStore) { 145 return new Builder(beanStore); 146 } 147 148 private final long guid; 149 private final Method method; 150 private final ThrownStore thrownStore; 151 152 private final AtomicInteger maxTime = new AtomicInteger(); 153 private final AtomicInteger minTime = new AtomicInteger(-1); 154 155 private final AtomicInteger starts = new AtomicInteger(); 156 private final AtomicInteger finishes = new AtomicInteger(); 157 private final AtomicInteger errors = new AtomicInteger(); 158 159 private final AtomicLong totalTime = new AtomicLong(); 160 161 /** 162 * Constructor. 163 * 164 * @param builder The builder for this object. Must not be <jk>null</jk>. 165 */ 166 protected MethodExecStats(Builder builder) { 167 this.guid = new Random().nextLong(); 168 this.method = builder.method; 169 this.thrownStore = nn(builder.thrownStore) ? builder.thrownStore : new ThrownStore(); 170 } 171 172 /** 173 * Call when an error occurs. 174 * 175 * @param e The exception thrown. Must not be <jk>null</jk> (a <jk>null</jk> argument throws {@link NullPointerException} via {@link ThrownStore#add(Throwable)}). 176 * @return This object. 177 */ 178 public MethodExecStats error(Throwable e) { 179 errors.incrementAndGet(); 180 thrownStore.add(e); 181 return this; 182 } 183 184 /** 185 * Call when task is finished. 186 * 187 * @param nanoTime The execution time of the task in nanoseconds. 188 * @return This object. 189 */ 190 public MethodExecStats finished(long nanoTime) { 191 finishes.incrementAndGet(); 192 int milliTime = (int)(nanoTime / 1_000_000); 193 totalTime.addAndGet(nanoTime); 194 int currentMin = minTime.get(); 195 if (currentMin == -1) { 196 minTime.compareAndSet(-1, milliTime); 197 } else { 198 minTime.updateAndGet(x -> Math.min(x, milliTime)); 199 } 200 maxTime.updateAndGet(x -> Math.max(x, milliTime)); 201 return this; 202 } 203 204 /** 205 * Returns the average execution time. 206 * 207 * @return The average execution time in milliseconds. 208 */ 209 public int getAvgTime() { 210 int runs = finishes.get(); 211 return runs == 0 ? 0 : (int)(getTotalTime() / runs); 212 } 213 214 /** 215 * Returns the number of times the {@link #error(Throwable)} method was called. 216 * 217 * @return The number of times the {@link #error(Throwable)} method was called. 218 */ 219 public int getErrors() { return errors.get(); } 220 221 /** 222 * Returns a globally unique ID for this object. 223 * 224 * <p> 225 * A random long generated during the creation of this object. 226 * Allows this object to be differentiated from other similar objects in multi-node environments so that 227 * statistics can be reliably stored in a centralized location. 228 * 229 * @return The globally unique ID for this object. 230 */ 231 public long getGuid() { return guid; } 232 233 /** 234 * Returns the max execution time. 235 * 236 * @return The max execution time in milliseconds. 237 */ 238 public int getMaxTime() { return maxTime.get(); } 239 240 /** 241 * Returns the method name of these stats. 242 * 243 * @return The method name of these stats. 244 */ 245 public Method getMethod() { return method; } 246 247 /** 248 * Returns the minimum execution time. 249 * 250 * @return The minimum execution time in milliseconds. 251 */ 252 public int getMinTime() { 253 int value = minTime.get(); 254 return value == -1 ? 0 : value; 255 } 256 257 /** 258 * Returns the number currently running method invocations. 259 * 260 * @return The number of currently running method invocations. 261 */ 262 public int getRunning() { return starts.get() - finishes.get(); } 263 264 /** 265 * Returns the number of times the {@link #started()} method was called. 266 * 267 * @return The number of times the {@link #started()} method was called. 268 */ 269 public int getRuns() { return starts.get(); } 270 271 /** 272 * Returns information on all stack traces of all exceptions encountered. 273 * 274 * @return Information on all stack traces of all exceptions encountered. 275 */ 276 public ThrownStore getThrownStore() { return thrownStore; } 277 278 /** 279 * Returns the total execution time. 280 * 281 * @return The total execution time in milliseconds. 282 */ 283 public long getTotalTime() { return totalTime.get() / 1_000_000; } 284 285 /** 286 * Call when task is started. 287 * 288 * @return This object. 289 */ 290 public MethodExecStats started() { 291 starts.incrementAndGet(); 292 return this; 293 } 294 295 protected FluentMap<String,Object> properties() { 296 // @formatter:off 297 return filteredBeanPropertyMap() 298 .a("avgTime", getAvgTime()) 299 .a("errors", getErrors()) 300 .a("guid", guid) 301 .a("maxTime", getMaxTime()) 302 .a("method", method) 303 .a("minTime", getMinTime()) 304 .a("running", getRunning()) 305 .a("runs", getRuns()) 306 .a("thrownStore", thrownStore) 307 .a("totalTime", getTotalTime()); 308 // @formatter:on 309 } 310 311 @Override /* Overridden from Object */ 312 public String toString() { 313 return r(properties()); 314 } 315}