001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  You may obtain a copy of the License at                                                              *
006// *                                                                                                                         *
007// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
008// *                                                                                                                         *
009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau.rest.stats;
014
015import static org.apache.juneau.internal.CollectionUtils.*;
016import java.util.*;
017import java.util.concurrent.atomic.*;
018
019import org.apache.juneau.cp.*;
020import org.apache.juneau.internal.*;
021import org.apache.juneau.marshaller.*;
022
023/**
024 * Represents an entry in {@link ThrownStore}.
025 *
026 * <h5 class='section'>See Also:</h5><ul>
027 *    <li class='link'><a class="doclink" href="../../../../../index.html#jrs.ExecutionStatistics">REST method execution statistics</a>
028 * </ul>
029 */
030public class ThrownStats implements Cloneable {
031
032   //-----------------------------------------------------------------------------------------------------------------
033   // Static
034   //-----------------------------------------------------------------------------------------------------------------
035
036   /**
037    * Static creator.
038    *
039    * @param beanStore The bean store to use for creating beans.
040    * @return A new builder for this object.
041    */
042   public static Builder create(BeanStore beanStore) {
043      return new Builder(beanStore);
044   }
045
046   //-----------------------------------------------------------------------------------------------------------------
047   // Builder
048   //-----------------------------------------------------------------------------------------------------------------
049
050   /**
051    * Builder class.
052    */
053   @FluentSetters
054   public static class Builder {
055
056      final BeanStore beanStore;
057      Throwable throwable;
058      long hash;
059      List<String> stackTrace;
060      ThrownStats causedBy;
061
062      BeanCreator<ThrownStats> creator;
063
064      /**
065       * Constructor.
066       *
067       * @param beanStore The bean store to use for creating beans.
068       */
069      protected Builder(BeanStore beanStore) {
070         this.beanStore = beanStore;
071         this.creator = beanStore.createBean(ThrownStats.class).builder(Builder.class, this);
072      }
073
074      /**
075       * Create a new {@link ThrownStats} using this builder.
076       *
077       * @return A new {@link ThrownStats}
078       */
079      public ThrownStats build() {
080         return creator.run();
081      }
082
083      /**
084       * Specifies a subclass of {@link ThrownStats} to create when the {@link #build()} method is called.
085       *
086       * @param value The new value for this setting.
087       * @return This object.
088       */
089      @FluentSetter
090      public Builder type(Class<? extends ThrownStats> value) {
091         creator.type(value == null ? ThrownStats.class : value);
092         return this;
093      }
094
095      /**
096       * Specifies the thrown exception.
097       *
098       * @param value The new value for this setting.
099       * @return This object.
100       */
101      @FluentSetter
102      public Builder throwable(Throwable value) {
103         this.throwable = value;
104         return this;
105      }
106
107      /**
108       * Specifies the calculated hash.
109       *
110       * @param value The new value for this setting.
111       * @return This object.
112       */
113      @FluentSetter
114      public Builder hash(long value) {
115         this.hash = value;
116         return this;
117      }
118
119      /**
120       * Specifies the normalized stacktrace.
121       *
122       * @param value The new value for this setting.
123       * @return This object.
124       */
125      @FluentSetter
126      public Builder stackTrace(List<String> value) {
127         this.stackTrace = value;
128         return this;
129      }
130
131      /**
132       * Specifies the caused-by exception.
133       *
134       * @param value The new value for this setting.
135       * @return This object.
136       */
137      @FluentSetter
138      public Builder causedBy(ThrownStats value) {
139         this.causedBy = value;
140         return this;
141      }
142
143      // <FluentSetters>
144
145      // </FluentSetters>
146   }
147
148   //-----------------------------------------------------------------------------------------------------------------
149   // Instance
150   //-----------------------------------------------------------------------------------------------------------------
151
152   private final long guid;
153   private final long hash;
154   private final Class<?> thrownClass;
155   private final String firstMessage;
156   private final List<String> stackTrace;
157   private final Optional<ThrownStats> causedBy;
158
159   private final AtomicInteger count;
160   private final AtomicLong firstOccurrence, lastOccurrence;
161
162   /**
163    * Constructor.
164    *
165    * @param builder The builder for this object.
166    */
167   protected ThrownStats(Builder builder) {
168      this.guid = new Random().nextLong();
169      this.thrownClass = builder.throwable.getClass();
170      this.firstMessage = builder.throwable.getMessage();
171      this.stackTrace = listBuilder(builder.stackTrace).copy().unmodifiable().build();
172      this.causedBy = optional(builder.causedBy);
173      this.hash = builder.hash;
174      this.count = new AtomicInteger(0);
175      long ct = System.currentTimeMillis();
176      this.firstOccurrence = new AtomicLong(ct);
177      this.lastOccurrence = new AtomicLong(ct);
178   }
179
180   /**
181    * Copy constructor.
182    */
183   private ThrownStats(ThrownStats x) {
184      this.guid = x.guid;
185      this.thrownClass = x.thrownClass;
186      this.firstMessage = x.firstMessage;
187      this.stackTrace = listBuilder(x.stackTrace).copy().unmodifiable().build();
188      this.causedBy = optional(x.causedBy.isPresent() ? x.causedBy.get().clone() : null);
189      this.hash = x.hash;
190      this.count = new AtomicInteger(x.count.get());
191      this.firstOccurrence = new AtomicLong(x.firstOccurrence.get());
192      this.lastOccurrence = new AtomicLong(x.lastOccurrence.get());
193   }
194
195   /**
196    * Returns a globally unique ID for this object.
197    *
198    * <p>
199    * A random long generated during the creation of this object.
200    * Allows this object to be differentiated from other similar objects in multi-node environments so that
201    * statistics can be reliably stored in a centralized location.
202    *
203    * @return The globally unique ID for this object.
204    */
205   public long getGuid() {
206      return guid;
207   }
208
209   /**
210    * Returns a hash of this exception that can typically be used to uniquely identify it.
211    *
212    * @return A hash of this exception.
213    */
214   public long getHash() {
215      return hash;
216   }
217
218   /**
219    * Returns the exception class.
220    *
221    * @return The exception class.
222    */
223   public Class<?> getThrownClass() {
224      return thrownClass;
225   }
226
227   /**
228    * Returns the number of times this exception occurred at a specific location in code.
229    *
230    * @return The number of times this exception occurred at a specific location in code.
231    */
232   public int getCount() {
233      return count.intValue();
234   }
235
236   /**
237    * Returns the UTC time of the first occurrence of this exception at a specific location in code.
238    *
239    * @return The UTC time of the first occurrence of this exception at a specific location in code.
240    */
241   public long getFirstOccurrence() {
242      return firstOccurrence.longValue();
243   }
244
245   /**
246    * Returns the UTC time of the last occurrence of this exception at a specific location in code.
247    *
248    * @return The UTC time of the last occurrence of this exception at a specific location in code.
249    */
250   public long getLastOccurrence() {
251      return lastOccurrence.longValue();
252   }
253
254   /**
255    * Returns the message of the first exception at a specific location in code.
256    *
257    * @return The message of the first exception at a specific location in code.
258    */
259   public String getFirstMessage() {
260      return firstMessage;
261   }
262
263   /**
264    * Returns the stack trace of the first exception at a specific location in code.
265    *
266    * @return The stack trace of the first exception at a specific location in code.
267    */
268   public List<String> getStackTrace() {
269      return stackTrace;
270   }
271
272   /**
273    * Returns the stats on the caused-by exception.
274    *
275    * @return The stats on the caused-by exception, never <jk>null</jk>.
276    */
277   public Optional<ThrownStats> getCausedBy() {
278      return causedBy;
279   }
280
281   /**
282    * Increments the occurrence count of this exception.
283    *
284    * @return This object.
285    */
286   public ThrownStats increment() {
287      count.incrementAndGet();
288      lastOccurrence.set(System.currentTimeMillis());
289      causedBy.ifPresent(ThrownStats::increment);
290      return this;
291   }
292
293   @Override /* Object */
294   public String toString() {
295      return Json5.of(this);
296   }
297
298   @Override /* Object */
299   public ThrownStats clone() {
300      return new ThrownStats(this);
301   }
302}