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 org.apache.juneau.internal.CollectionUtils.*;
020
021import java.util.*;
022import java.util.concurrent.atomic.*;
023
024import org.apache.juneau.common.utils.*;
025import org.apache.juneau.cp.*;
026import org.apache.juneau.internal.*;
027import org.apache.juneau.marshaller.*;
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 */
036public class ThrownStats implements Cloneable {
037
038   //-----------------------------------------------------------------------------------------------------------------
039   // Static
040   //-----------------------------------------------------------------------------------------------------------------
041
042   /**
043    * Static creator.
044    *
045    * @param beanStore The bean store to use for creating beans.
046    * @return A new builder for this object.
047    */
048   public static Builder create(BeanStore beanStore) {
049      return new Builder(beanStore);
050   }
051
052   //-----------------------------------------------------------------------------------------------------------------
053   // Builder
054   //-----------------------------------------------------------------------------------------------------------------
055
056   /**
057    * Builder class.
058    */
059   public static class Builder {
060
061      final BeanStore beanStore;
062      Throwable throwable;
063      long hash;
064      List<String> stackTrace;
065      ThrownStats causedBy;
066
067      BeanCreator<ThrownStats> creator;
068
069      /**
070       * Constructor.
071       *
072       * @param beanStore The bean store to use for creating beans.
073       */
074      protected Builder(BeanStore beanStore) {
075         this.beanStore = beanStore;
076         this.creator = beanStore.createBean(ThrownStats.class).builder(Builder.class, this);
077      }
078
079      /**
080       * Create a new {@link ThrownStats} using this builder.
081       *
082       * @return A new {@link ThrownStats}
083       */
084      public ThrownStats build() {
085         return creator.run();
086      }
087
088      /**
089       * Specifies a subclass of {@link ThrownStats} to create when the {@link #build()} method is called.
090       *
091       * @param value The new value for this setting.
092       * @return This object.
093       */
094      public Builder type(Class<? extends ThrownStats> value) {
095         creator.type(value == null ? ThrownStats.class : value);
096         return this;
097      }
098
099      /**
100       * Specifies the thrown exception.
101       *
102       * @param value The new value for this setting.
103       * @return This object.
104       */
105      public Builder throwable(Throwable value) {
106         this.throwable = value;
107         return this;
108      }
109
110      /**
111       * Specifies the calculated hash.
112       *
113       * @param value The new value for this setting.
114       * @return This object.
115       */
116      public Builder hash(long value) {
117         this.hash = value;
118         return this;
119      }
120
121      /**
122       * Specifies the normalized stacktrace.
123       *
124       * @param value The new value for this setting.
125       * @return This object.
126       */
127      public Builder stackTrace(List<String> value) {
128         this.stackTrace = value;
129         return this;
130      }
131
132      /**
133       * Specifies the caused-by exception.
134       *
135       * @param value The new value for this setting.
136       * @return This object.
137       */
138      public Builder causedBy(ThrownStats value) {
139         this.causedBy = value;
140         return this;
141      }
142   }
143
144   //-----------------------------------------------------------------------------------------------------------------
145   // Instance
146   //-----------------------------------------------------------------------------------------------------------------
147
148   private final long guid;
149   private final long hash;
150   private final Class<?> thrownClass;
151   private final String firstMessage;
152   private final List<String> stackTrace;
153   private final Optional<ThrownStats> causedBy;
154
155   private final AtomicInteger count;
156   private final AtomicLong firstOccurrence, lastOccurrence;
157
158   /**
159    * Constructor.
160    *
161    * @param builder The builder for this object.
162    */
163   protected ThrownStats(Builder builder) {
164      this.guid = new Random().nextLong();
165      this.thrownClass = builder.throwable.getClass();
166      this.firstMessage = builder.throwable.getMessage();
167      this.stackTrace = listBuilder(builder.stackTrace).copy().unmodifiable().build();
168      this.causedBy = Utils.opt(builder.causedBy);
169      this.hash = builder.hash;
170      this.count = new AtomicInteger(0);
171      long ct = System.currentTimeMillis();
172      this.firstOccurrence = new AtomicLong(ct);
173      this.lastOccurrence = new AtomicLong(ct);
174   }
175
176   /**
177    * Copy constructor.
178    */
179   private ThrownStats(ThrownStats x) {
180      this.guid = x.guid;
181      this.thrownClass = x.thrownClass;
182      this.firstMessage = x.firstMessage;
183      this.stackTrace = listBuilder(x.stackTrace).copy().unmodifiable().build();
184      this.causedBy = Utils.opt(x.causedBy.isPresent() ? x.causedBy.get().clone() : null);
185      this.hash = x.hash;
186      this.count = new AtomicInteger(x.count.get());
187      this.firstOccurrence = new AtomicLong(x.firstOccurrence.get());
188      this.lastOccurrence = new AtomicLong(x.lastOccurrence.get());
189   }
190
191   /**
192    * Returns a globally unique ID for this object.
193    *
194    * <p>
195    * A random long generated during the creation of this object.
196    * Allows this object to be differentiated from other similar objects in multi-node environments so that
197    * statistics can be reliably stored in a centralized location.
198    *
199    * @return The globally unique ID for this object.
200    */
201   public long getGuid() {
202      return guid;
203   }
204
205   /**
206    * Returns a hash of this exception that can typically be used to uniquely identify it.
207    *
208    * @return A hash of this exception.
209    */
210   public long getHash() {
211      return hash;
212   }
213
214   /**
215    * Returns the exception class.
216    *
217    * @return The exception class.
218    */
219   public Class<?> getThrownClass() {
220      return thrownClass;
221   }
222
223   /**
224    * Returns the number of times this exception occurred at a specific location in code.
225    *
226    * @return The number of times this exception occurred at a specific location in code.
227    */
228   public int getCount() {
229      return count.intValue();
230   }
231
232   /**
233    * Returns the UTC time of the first occurrence of this exception at a specific location in code.
234    *
235    * @return The UTC time of the first occurrence of this exception at a specific location in code.
236    */
237   public long getFirstOccurrence() {
238      return firstOccurrence.longValue();
239   }
240
241   /**
242    * Returns the UTC time of the last occurrence of this exception at a specific location in code.
243    *
244    * @return The UTC time of the last occurrence of this exception at a specific location in code.
245    */
246   public long getLastOccurrence() {
247      return lastOccurrence.longValue();
248   }
249
250   /**
251    * Returns the message of the first exception at a specific location in code.
252    *
253    * @return The message of the first exception at a specific location in code.
254    */
255   public String getFirstMessage() {
256      return firstMessage;
257   }
258
259   /**
260    * Returns the stack trace of the first exception at a specific location in code.
261    *
262    * @return The stack trace of the first exception at a specific location in code.
263    */
264   public List<String> getStackTrace() {
265      return stackTrace;
266   }
267
268   /**
269    * Returns the stats on the caused-by exception.
270    *
271    * @return The stats on the caused-by exception, never <jk>null</jk>.
272    */
273   public Optional<ThrownStats> getCausedBy() {
274      return causedBy;
275   }
276
277   /**
278    * Increments the occurrence count of this exception.
279    *
280    * @return This object.
281    */
282   public ThrownStats increment() {
283      count.incrementAndGet();
284      lastOccurrence.set(System.currentTimeMillis());
285      causedBy.ifPresent(ThrownStats::increment);
286      return this;
287   }
288
289   @Override /* Object */
290   public String toString() {
291      return Json5.of(this);
292   }
293
294   @Override /* Object */
295   public ThrownStats clone() {
296      return new ThrownStats(this);
297   }
298}