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.utils;
014
015import static org.apache.juneau.utils.StackTraceUtils.*;
016
017import java.util.*;
018import java.util.concurrent.*;
019import java.util.stream.*;
020
021/**
022 * An in-memory cache of stack traces.
023 *
024 * <p>
025 * Used for preventing duplication of stack traces in log files and replacing them with small hashes.
026 */
027public class StackTraceDatabase {
028
029   private final ConcurrentHashMap<Integer,StackTraceInfo> db = new ConcurrentHashMap<>();
030   private final String stopClass;
031   private final long cacheTimeout;
032
033   /**
034    * Constructor.
035    */
036   public StackTraceDatabase() {
037      this(-1, null);
038   }
039
040   /**
041    * Constructor.
042    *
043    * @param cacheTimeout
044    *    The amount of time in milliseconds to cache stack trace info in this database before discarding.
045    *    <br>If <c>-1</c>, never discard.
046    * @param stopClass
047    *    When this class is encountered in a stack trace, stop calculating the hash.
048    *    <br>Can be <jk>null</jk>.
049    */
050   public StackTraceDatabase(long cacheTimeout, Class<?> stopClass) {
051      this.stopClass = stopClass == null ? "" : stopClass.getName();
052      this.cacheTimeout = cacheTimeout;
053   }
054
055   /**
056    * Adds the specified throwable to this database.
057    *
058    * @param e The exception to add.
059    * @return This object (for method chaining).
060    */
061   public StackTraceDatabase add(Throwable e) {
062      find(e).increment();
063      return this;
064   }
065
066   /**
067    * Retrieves the stack trace information for the specified exception.
068    *
069    * @param e The exception.
070    * @return A clone of the stack trace info, never <jk>null</jk>.
071    */
072   public StackTraceInfo getStackTraceInfo(Throwable e) {
073      return find(e).clone();
074   }
075
076   /**
077    * Clears out the stack trace cache.
078    */
079   public void reset() {
080      db.clear();
081   }
082
083   /**
084    * Returns the list of all stack traces in this database.
085    *
086    * @return The list of all stack traces in this database, cloned and sorted by count descending.
087    */
088   public List<StackTraceInfo> getClonedStackTraceInfos() {
089      return db.values().stream().map(x -> x.clone()).sorted().collect(Collectors.toList());
090   }
091
092   private StackTraceInfo find(Throwable e) {
093      int hash = hash(e, stopClass);
094      StackTraceInfo stc = db.get(hash);
095      long time = System.currentTimeMillis();
096
097      if (stc != null && stc.timeout > time) {
098         return stc;
099      }
100
101      String n = e == null ? null : e.getClass().getSimpleName();
102      long t = cacheTimeout == -1 ? Long.MAX_VALUE : time + cacheTimeout;
103      stc = new StackTraceInfo(n, t, hash);
104
105      db.put(hash, stc);
106
107      return db.get(hash);
108   }
109}