/* * Copyright 2015 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.revapi; import java.util.LinkedHashMap; import java.util.Map; import java.util.TreeMap; import java.util.stream.Collectors; /** * Quick and dirty execution time statistics collection class. * * @author Lukas Krejci * @since 0.4.1 */ public final class Stats { private static final Map<String, Collector> COLLECTORS = new TreeMap<>(); private Stats() { } public static Collector of(String stat) { if (!Revapi.TIMING_LOG.isDebugEnabled()) { return DummyCollector.INSTANCE; } Collector ret = COLLECTORS.get(stat); if (ret == null) { ret = new Collector(); COLLECTORS.put(stat, ret); } return ret; } public static String asString() { Map<String, Collector> map = COLLECTORS.entrySet().stream() .sorted((e1, e2) -> (int) (e2.getValue().totalTime - e1.getValue().totalTime)) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (m1, m2) -> m1, LinkedHashMap::new)); StringBuilder bld = new StringBuilder("Stats:"); if (map.isEmpty()) { return bld.append(" <none>").toString(); } for (Map.Entry<String, Collector> e : map.entrySet()) { bld.append('\n').append(e.getKey()).append(": ").append(e.getValue()); } return bld.toString(); } public static class Collector { volatile long occurrences; volatile long totalTime; volatile long currentStartTime; volatile long worstTime; volatile Object offender; private Collector() { } public long start() { return currentStartTime = System.currentTimeMillis(); } public void end(Object cause) { end(0, cause); } public void end(Object cause1, Object cause2) { end(0, new Tuple2(cause1, cause2)); } public void end(long additionalDuration, Object cause) { long duration = System.currentTimeMillis() - currentStartTime + additionalDuration; totalTime += duration; occurrences++; if (duration > worstTime) { worstTime = duration; offender = cause; } } public void end(long additionalDuration, Object cause1, Object cause2) { end(additionalDuration, new Tuple2(cause1, cause2)); } public long reset() { return System.currentTimeMillis() - currentStartTime; } @Override public String toString() { return "{occurrences = " + occurrences + ", total = " + totalTime + "ms, average = " + String.format("%.2fms", ((double) totalTime) / occurrences) + ", worstTime = " + worstTime + "ms caused by " + offender + "}"; } } private static final class DummyCollector extends Collector { static final DummyCollector INSTANCE = new DummyCollector(); @Override public void end(long additionalDuration, Object cause) { } @Override public void end(long additionalDuration, Object cause1, Object cause2) { } @Override public void end(Object cause) { } @Override public void end(Object cause1, Object cause2) { } @Override public long reset() { return 0; } @Override public long start() { return 0; } } private static final class Tuple2 { private final Object a; private final Object b; private Tuple2(Object a, Object b) { this.a = a; this.b = b; } @Override public String toString() { return "[" + a + ", " + b + "]"; } } }