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}