org.openmainframe.ade.core.statistics.TimingStatistics.java Source code

Java tutorial

Introduction

Here is the source code for org.openmainframe.ade.core.statistics.TimingStatistics.java

Source

/*
     
Copyright IBM Corp. 2010, 2016
This file is part of Anomaly Detection Engine for Linux Logs (ADE).
    
ADE is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
    
ADE is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
    
You should have received a copy of the GNU General Public License
along with ADE.  If not, see <http://www.gnu.org/licenses/>.
     
*/
package org.openmainframe.ade.core.statistics;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.math3.util.Pair;
import org.openmainframe.ade.exceptions.AdeInternalException;

/** A class for obtaining profiling statistics.
 * Usage example:
 * 
 * TimingStatistics.start("myMeasureName");
 * .... some code ...
 * TimingStatistics.end("myMeasureName");
 * 
 * 
 * To obtain results call:
 *  TimingStatistics.printSummary(System.out);
 * Which will print for each measure name:
 *    number of start/end
 *    mean/min/max/std of elapsed time between each start and end pair 
 */
public class TimingStatistics {
    static String copyright() {
        return Copyright.IBM_COPYRIGHT;
    }

    /** Converts millisecond count to HH:MM:SS.SS where the seconds are reported with 0.01 precision */
    public static String timeToString(long period) {
        period /= 10;
        final long milsecs = period % 100;
        period /= 100;
        final long secs = period % 60;
        period /= 60;
        final long mins = period % 60;
        period /= 60;
        final long hours = period;
        return String.format("%02d:%02d:%02d.%02d", hours, mins, secs, milsecs);
    }

    private static class Measure {

        public String mName;
        public long mSum = 0;
        public long mMax = 0;
        public int mCount = 0;
        int mCurrentlyOpen = 0;
        boolean mUsedInParallel = false;

        public String toTabbedString() {
            String res = timeToString(mSum);
            if (mUsedInParallel) {
                res += "\t<Parallel>";
            } else {
                res += "\t" + timeToString(mCount > 0 ? mSum / mCount : 0);
            }
            res += "\t" + mCount + "\t" + timeToString(mMax);
            return res;
        }

        @Override
        public String toString() {
            String res = "Total ";
            if (mUsedInParallel) {
                res += " <Parallel>";
            } else {
                res += timeToString(mSum);
            }
            res += " mean " + timeToString(mCount > 0 ? mSum / mCount : 0) + " (x " + mCount + " times) max "
                    + timeToString(mMax);
            return res;
        }

        void open() {
            ++mCurrentlyOpen;
            if (mCurrentlyOpen > 1) {
                mUsedInParallel = true;
            }
        }

        void close(long elapsed) {
            --mCurrentlyOpen;
            if (mCurrentlyOpen < 0) {
                mCurrentlyOpen = 0;
            }
            mSum += elapsed;
            mCount++;

            if (elapsed > mMax) {
                mMax = elapsed;
            }
        }
    }

    private HashMap<String, Measure> mMeasures = new HashMap<String, Measure>();
    private Map<Pair<Long, String>, Long> mThreadMeasures = new HashMap<Pair<Long, String>, Long>();
    private List<Measure> mOrderedMeasures = new ArrayList<Measure>();
    private Set<String> mDuplicateStarts = new HashSet<String>();
    private Set<String> mEndsWithoutStarts = new HashSet<String>();

    private static TimingStatistics mInstance = new TimingStatistics();

    private static ThreadLocal<List<String>> mPrefixes = new ThreadLocal<List<String>>() {
        @Override
        protected List<String> initialValue() {
            return new ArrayList<String>();
        }
    };

    private static ThreadLocal<String> mFinalPrefix = new ThreadLocal<String>();

    private static Map<String, Measure> getMeasures() {
        return mInstance.mMeasures;
    }

    /** Get the statistics report as a String 
     * @throws AdeInternalException if UTF-8 encoding it not supported.*/
    public static String getSummary() throws AdeInternalException {
        return getSummary(null);
    }

    /** Get the statistics report for a specified measure as a String 
     * @throws AdeInternalException if UTF-8 encoding it not supported.*/
    public static String getSummary(String measure) throws AdeInternalException {
        final ByteArrayOutputStream bo = new ByteArrayOutputStream();
        PrintStream out;
        try {
            out = new PrintStream(bo, false, StandardCharsets.UTF_8.name());
        } catch (UnsupportedEncodingException e) {
            throw new AdeInternalException("Unsupported encoding encountered for the PrintStream.", e);
        }
        printSummary(out, measure);
        try {
            return bo.toString(StandardCharsets.UTF_8.name());
        } catch (UnsupportedEncodingException e) {
            throw new AdeInternalException("Unsupported encoding encountered for the ByteArrayOutputStream.", e);
        }
    }

    /** Print the statistics report to specified stream */
    public static void printSummary(PrintStream out) {
        printSummary(out, null);
        printErrorSummary(out);
    }

    /** Print the statistics report to specified stream in UTF-8 format.
     * @throws FileNotFoundException 
     * @throws AdeInternalException if UTF-8 encoding is not supported. */
    public static void printToFile(File outFile) throws FileNotFoundException, AdeInternalException {
        PrintStream out;
        try {
            out = new PrintStream(outFile, StandardCharsets.UTF_8.name());
        } catch (UnsupportedEncodingException e) {
            throw new AdeInternalException("Unsupported encoding encountered.", e);
        }
        printTabbedSummary(out, null);
        printErrorSummary(out);
        out.close();
    }

    private static void printErrorSummary(PrintStream out) {
        if (!mInstance.mDuplicateStarts.isEmpty()) {
            out.println("ERROR: start eithout end on " + mInstance.mDuplicateStarts);
        }
        if (!mInstance.mDuplicateStarts.isEmpty()) {
            out.println("ERROR: start eithout end on " + mInstance.mDuplicateStarts);
        }
    }

    /** Print the statistics report to specified stream for a specified measure */
    static public void printTabbedSummary(PrintStream out, String measure) {
        if (measure == null) {
            out.println("Profiling statistics: (hh:mm:ss)");
            out.println("Name\tTotal\tMean\tCount\tMax");
        }

        for (Measure m : getSortedMeasures()) {
            if (measure == null || m.mName.equals(measure)) {
                out.printf(m.mName + "\t" + m.toTabbedString());
            }
            out.println();
        }
    }

    /** Print the statistics report to specified stream for a specified measure */
    static public void printSummary(PrintStream out, String measure) {
        if (measure == null) {
            out.println("Profiling statistics: (hh:mm:ss)");
        }
        for (Measure m : getSortedMeasures()) {
            if (measure == null || m.mName.equals(measure)) {
                out.printf("  %-25s: %s", m.mName, getMeasures().get(m.mName).toString());
                out.println();
            }
        }
    }

    private static List<Measure> getSortedMeasures() {
        final List<Measure> sortedMeasures = new ArrayList<Measure>(mInstance.mOrderedMeasures);
        final Comparator<Measure> comparator = new Comparator<Measure>() {
            public int compare(Measure m1, Measure m2) {
                return Long_compare(m1.mSum, m2.mSum);
            }

            private int Long_compare(long l1, long l2) {
                return Long.valueOf(l1).compareTo(Long.valueOf(l2));
            }
        };

        Collections.sort(sortedMeasures, comparator);
        return sortedMeasures;
    }

    /** Start measuring the specified measure name */
    synchronized static public void start(String name) {
        if (mFinalPrefix.get() != null) {
            name = mFinalPrefix.get() + name;
        }
        final Pair<Long, String> key = new Pair<Long, String>(Thread.currentThread().getId(), name);
        final Long old = mInstance.mThreadMeasures.put(key, System.currentTimeMillis());
        if (old != null) {
            mInstance.mDuplicateStarts.add(name);
        }
        getOrAddMeasure(name).open();
    }

    static private Measure getOrAddMeasure(String name) {
        Measure measure = getMeasures().get(name);
        if (measure == null) {
            measure = new Measure();
            measure.mName = name;
            getMeasures().put(name, measure);
            mInstance.mOrderedMeasures.add(measure);
        }
        return measure;
    }

    /** Stop measuring the specified measure name and update statistics */
    synchronized static public void end(String name) {
        if (mFinalPrefix.get() != null) {
            name = mFinalPrefix.get() + name;
        }
        final Pair<Long, String> key = new Pair<Long, String>(Thread.currentThread().getId(), name);
        final Long lastTime = mInstance.mThreadMeasures.remove(key);
        if (lastTime == null) {
            mInstance.mEndsWithoutStarts.add(name);
            return;
        }
        final Measure measure = getMeasures().get(name);
        // this isn't supposed to happen at this point
        if (measure == null) {
            return;
        }
        measure.close(System.currentTimeMillis() - lastTime);
    }

    /** Clear all internal data collected */
    public static void clear() {
        getMeasures().clear();
        mPrefixes.get().clear();
        mInstance.mOrderedMeasures.clear();
    }

    synchronized static public void pushPrefix(String prefix) {
        mPrefixes.get().add(prefix);
        recalcFinalPrefix();
        start("all");
    }

    synchronized static public void popPrefix() {
        end("all");
        if (!mPrefixes.get().isEmpty()) {
            mPrefixes.get().remove(mPrefixes.get().size() - 1);
        }
        recalcFinalPrefix();
    }

    private static void recalcFinalPrefix() {

        final StringBuilder sb = new StringBuilder();
        final List<String> prefs = mPrefixes.get();
        for (String p : prefs) {
            sb.append(p);
            sb.append("-");
        }
        mFinalPrefix.set(sb.toString());
    }
}