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.mstat;
014
015import java.util.*;
016import java.util.concurrent.*;
017import java.util.stream.*;
018
019/**
020 * An in-memory cache of stack traces.
021 *
022 * <p>
023 * Used for preventing duplication of stack traces in log files and replacing them with small hashes.
024 */
025public class ExceptionStore {
026
027   private final ConcurrentHashMap<Integer,ExceptionStats> sdb = new ConcurrentHashMap<>();
028   private final ConcurrentHashMap<Integer,ExceptionInfo> idb = new ConcurrentHashMap<>();
029   private final ExceptionHasher hasher;
030   private final long cacheTimeout;
031
032   /**
033    * Constructor.
034    */
035   public ExceptionStore() {
036      this(-1, null);
037   }
038
039
040
041   /**
042    * Constructor.
043    *
044    * @param cacheTimeout
045    *    The amount of time in milliseconds to cache stack trace info in this database before discarding.
046    *    <br>If <c>-1</c>, never discard.
047    * @param stopClass
048    *    When this class is encountered in a stack trace, stop calculating the hash.
049    *    <br>Can be <jk>null</jk>.
050    */
051   public ExceptionStore(long cacheTimeout, Class<?> stopClass) {
052      this.hasher = new ExceptionHasher(stopClass);
053      this.cacheTimeout = cacheTimeout;
054   }
055
056   /**
057    * Adds the specified throwable to this database.
058    *
059    * @param e The exception to add.
060    * @return This object (for method chaining).
061    */
062   public ExceptionStore add(Throwable e) {
063      find(e).increment();
064      return this;
065   }
066
067   /**
068    * Retrieves the stack trace information for the specified exception.
069    *
070    * @param e The exception.
071    * @return A clone of the stack trace info, never <jk>null</jk>.
072    */
073   public ExceptionStats getStackTraceInfo(Throwable e) {
074      return find(e).clone();
075   }
076
077   /**
078    * Clears out the stack trace cache.
079    */
080   public void reset() {
081      sdb.clear();
082   }
083
084   /**
085    * Returns the list of all stack traces in this database.
086    *
087    * @return The list of all stack traces in this database, cloned and sorted by count descending.
088    */
089   public List<ExceptionStats> getClonedStats() {
090      return sdb.values().stream().map(x -> x.clone()).sorted().collect(Collectors.toList());
091   }
092
093   private ExceptionStats find(Throwable e) {
094
095      if (e == null)
096         return null;
097
098      int hash = hasher.hash(e);
099
100      ExceptionStats stc = sdb.get(hash);
101      if (stc != null && ! stc.isExpired())
102         return stc;
103
104      stc = ExceptionStats
105         .create()
106         .hash(Integer.toHexString(hash))
107         .timeout(cacheTimeout == -1 ? Long.MAX_VALUE : System.currentTimeMillis() + cacheTimeout)
108         .message(e.getMessage())
109         .exceptionClass(e.getClass().getName())
110         .stackTrace(hasher.getStackTrace(e))
111         .causedBy(e.getCause() == null ? null : findInfo(e.getCause()));
112
113      sdb.put(hash, stc);
114
115      return sdb.get(hash);
116   }
117
118   private ExceptionInfo findInfo(Throwable e) {
119
120      int hash = hasher.hash(e);
121
122      ExceptionInfo ei = sdb.get(hash);
123      if (ei == null) {
124         ei = ExceptionInfo
125            .create()
126            .hash(Integer.toHexString(hash))
127            .message(e.getMessage())
128            .exceptionClass(e.getClass().getName())
129            .stackTrace(hasher.getStackTrace(e))
130            .causedBy(e.getCause() == null ? null : findInfo(e.getCause()));
131         idb.put(hash, ei);
132      }
133
134      return ei;
135   }
136}