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.time.*; 023import java.util.*; 024import java.util.concurrent.atomic.*; 025 026import org.apache.juneau.commons.collections.*; 027import org.apache.juneau.commons.inject.*; 028 029/** 030 * Represents an entry in {@link ThrownStore}. 031 * 032 * <h5 class='section'>See Also:</h5><ul> 033 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/ExecutionStatistics">REST method execution statistics</a> 034 * </ul> 035 */ 036@SuppressWarnings({ 037 "java:S1192" // Duplicated literals (argument/property names) read more clearly inline than as constants 038}) 039public class ThrownStats { 040 041 /** 042 * Builder class. 043 */ 044 public static class Builder { 045 046 final BeanStore beanStore; 047 Throwable throwable; 048 long hash; 049 List<String> stackTrace; 050 ThrownStats causedBy; 051 Clock clock = Clock.systemUTC(); 052 053 private Class<? extends ThrownStats> implType; 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 * Create a new {@link ThrownStats} using this builder. 066 * 067 * @return A new {@link ThrownStats} 068 */ 069 public ThrownStats build() { 070 var t = or(implType, ThrownStats.class); 071 return BeanInstantiator.of(ThrownStats.class, beanStore) 072 .type(t) 073 .noBuilder() 074 .addBean(Builder.class, this) 075 .run(); 076 } 077 078 /** 079 * Specifies the caused-by exception. 080 * 081 * @param value The new value for this setting. 082 * @return This object. 083 */ 084 public Builder causedBy(ThrownStats value) { 085 causedBy = value; 086 return this; 087 } 088 089 /** 090 * Specifies the clock used to stamp the first and last occurrence times. 091 * 092 * <p> 093 * Defaults to {@link Clock#systemUTC()}. Tests can pass {@link Clock#fixed(Instant, ZoneId)} to make the 094 * reported occurrence times deterministic. 095 * 096 * @param value The new value for this setting. Must not be <jk>null</jk>. 097 * @return This object. 098 */ 099 public Builder clock(Clock value) { 100 clock = reqnn("value", value); 101 return this; 102 } 103 104 /** 105 * Specifies the calculated hash. 106 * 107 * @param value The new value for this setting. 108 * @return This object. 109 */ 110 public Builder hash(long value) { 111 hash = value; 112 return this; 113 } 114 115 /** 116 * Specifies the normalized stacktrace. 117 * 118 * @param value The new value for this setting. 119 * @return This object. 120 */ 121 public Builder stackTrace(List<String> value) { 122 stackTrace = value; 123 return this; 124 } 125 126 /** 127 * Specifies the thrown exception. 128 * 129 * @param value The new value for this setting. 130 * @return This object. 131 */ 132 public Builder throwable(Throwable value) { 133 throwable = value; 134 return this; 135 } 136 137 /** 138 * Specifies a subclass of {@link ThrownStats} to create when the {@link #build()} method is called. 139 * 140 * @param value The new value for this setting. Can be <jk>null</jk> (defaults to {@link ThrownStats}). 141 * @return This object. 142 */ 143 public Builder type(Class<? extends ThrownStats> value) { 144 implType = o(value).isPresent() ? value : ThrownStats.class; 145 return this; 146 } 147 } 148 149 /** 150 * Static creator. 151 * 152 * @param beanStore The bean store to use for creating beans. 153 * @return A new builder for this object. 154 */ 155 public static Builder create(BeanStore beanStore) { 156 return new Builder(beanStore); 157 } 158 159 private final long guid; 160 private final long hash; 161 private final Class<?> thrownClass; 162 private final String firstMessage; 163 private final List<String> stackTrace; 164 private final Optional<ThrownStats> causedBy; 165 166 private final Clock clock; 167 private final AtomicInteger count; 168 private final AtomicLong firstOccurrence; 169 private final AtomicLong lastOccurrence; 170 171 /** 172 * Copy constructor. 173 */ 174 private ThrownStats(ThrownStats x) { 175 this.guid = x.guid; 176 this.thrownClass = x.thrownClass; 177 this.firstMessage = x.firstMessage; 178 this.stackTrace = u(cp(x.stackTrace)); 179 this.causedBy = o(ThrownStats.copyOrNull(x.causedBy.orElse(null))); 180 this.hash = x.hash; 181 this.clock = x.clock; 182 this.count = new AtomicInteger(x.count.get()); 183 this.firstOccurrence = new AtomicLong(x.firstOccurrence.get()); 184 this.lastOccurrence = new AtomicLong(x.lastOccurrence.get()); 185 } 186 187 /** 188 * Null-safe copy: returns a deep copy of the specified value, or <jk>null</jk> if the value is <jk>null</jk>. 189 * 190 * @param value The value to copy. Can be <jk>null</jk>, in which case <jk>null</jk> is returned. 191 * @return A copy of the value, or <jk>null</jk> if the value was <jk>null</jk>. 192 */ 193 public static ThrownStats copyOrNull(ThrownStats value) { 194 return value == null ? null : new ThrownStats(value); 195 } 196 197 /** 198 * Constructor. 199 * 200 * @param builder The builder for this object. Must not be <jk>null</jk>. 201 */ 202 protected ThrownStats(Builder builder) { 203 this.guid = new Random().nextLong(); 204 this.thrownClass = builder.throwable.getClass(); 205 this.firstMessage = builder.throwable.getMessage(); 206 this.stackTrace = u(cp(builder.stackTrace)); 207 this.causedBy = o(builder.causedBy); 208 this.hash = builder.hash; 209 this.clock = builder.clock; 210 this.count = new AtomicInteger(0); 211 long ct = clock.millis(); 212 this.firstOccurrence = new AtomicLong(ct); 213 this.lastOccurrence = new AtomicLong(ct); 214 } 215 216 217 /** 218 * Returns the stats on the caused-by exception. 219 * 220 * @return The stats on the caused-by exception, never <jk>null</jk>. 221 */ 222 public Optional<ThrownStats> getCausedBy() { return causedBy; } 223 224 /** 225 * Returns the number of times this exception occurred at a specific location in code. 226 * 227 * @return The number of times this exception occurred at a specific location in code. 228 */ 229 public int getCount() { return count.intValue(); } 230 231 /** 232 * Returns the message of the first exception at a specific location in code. 233 * 234 * @return The message of the first exception at a specific location in code, or <jk>null</jk> if the exception had no message. 235 */ 236 public String getFirstMessage() { return firstMessage; } 237 238 /** 239 * Returns the UTC time of the first occurrence of this exception at a specific location in code. 240 * 241 * @return The UTC time of the first occurrence of this exception at a specific location in code. 242 */ 243 public long getFirstOccurrence() { return firstOccurrence.longValue(); } 244 245 /** 246 * Returns a globally unique ID for this object. 247 * 248 * <p> 249 * A random long generated during the creation of this object. 250 * Allows this object to be differentiated from other similar objects in multi-node environments so that 251 * statistics can be reliably stored in a centralized location. 252 * 253 * @return The globally unique ID for this object. 254 */ 255 public long getGuid() { return guid; } 256 257 /** 258 * Returns a hash of this exception that can typically be used to uniquely identify it. 259 * 260 * @return A hash of this exception. 261 */ 262 public long getHash() { return hash; } 263 264 /** 265 * Returns the UTC time of the last occurrence of this exception at a specific location in code. 266 * 267 * @return The UTC time of the last occurrence of this exception at a specific location in code. 268 */ 269 public long getLastOccurrence() { return lastOccurrence.longValue(); } 270 271 /** 272 * Returns the stack trace of the first exception at a specific location in code. 273 * 274 * @return The stack trace of the first exception at a specific location in code. 275 */ 276 public List<String> getStackTrace() { return stackTrace; } 277 278 /** 279 * Returns the exception class. 280 * 281 * @return The exception class. 282 */ 283 public Class<?> getThrownClass() { return thrownClass; } 284 285 /** 286 * Increments the occurrence count of this exception. 287 * 288 * @return This object. 289 */ 290 public ThrownStats increment() { 291 count.incrementAndGet(); 292 lastOccurrence.set(clock.millis()); 293 causedBy.ifPresent(ThrownStats::increment); 294 return this; 295 } 296 297 protected FluentMap<String,Object> properties() { 298 // @formatter:off 299 return filteredBeanPropertyMap() 300 .a("causedBy", causedBy.orElse(null)) 301 .a("count", getCount()) 302 .a("firstMessage", firstMessage) 303 .a("firstOccurrence", getFirstOccurrence()) 304 .a("guid", guid) 305 .a("hash", hash) 306 .a("lastOccurrence", getLastOccurrence()) 307 .a("stackTrace", stackTrace) 308 .a("thrownClass", thrownClass); 309 // @formatter:on 310 } 311 312 @Override /* Overridden from Object */ 313 public String toString() { 314 return r(properties()); 315 } 316}