package onlinefrontlines.profiler;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Arrays;
import java.util.Timer;
import java.lang.management.ManagementFactory;
import onlinefrontlines.utils.Tools;
/**
* The profiler class provides an interface to all profiling functionality
*
* @author jorrit
*
* Copyright (C) 2009-2013 Jorrit Rouwe
*
* This file is part of Online Frontlines.
*
* Online Frontlines 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.
*
* Online Frontlines 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 Online Frontlines. If not, see <http://www.gnu.org/licenses/>.
*/
public final class Profiler
{
/**
* Predefined categories
*/
public static final String CATEGORY_HTTP_REQUEST = "HTTP Request";
public static final String CATEGORY_SQL_QUERY = "SQL Query";
public static final String CATEGORY_SQL_STORED_PROCEDURE = "SQL Stored Procedure";
public static final String CATEGORY_GENERAL = "General";
/**
* Predefined series
*/
public static final String SERIES_USED_MEMORY = "Used Memory (Mb)";
public static final String SERIES_CPU_LOAD = "CPU Load";
/**
* Post fixes for time series
*/
private static final String RATE = " (calls / s)";
private static final String AVERAGE_TIME = " (average time in ms / call)";
private static final String MIN_TIME = " (min time in ms / call)";
private static final String MAX_TIME = " (max time in ms / call)";
/**
* Singleton instance
*/
private static Profiler instance = new Profiler();
/**
* Create timer to schedule updates
*/
private Timer timer;
/**
* All time accumulators
*/
private ConcurrentHashMap<String, ConcurrentHashMap<String, TimeAccumulatorWithChildren>> allTimeAccumulators = new ConcurrentHashMap<String, ConcurrentHashMap<String, TimeAccumulatorWithChildren>>();
/**
* All time series
*/
private ConcurrentHashMap<String, TimeSeries> allTimeSeries = new ConcurrentHashMap<String, TimeSeries>();
/**
* Singleton instance
*/
static public Profiler getInstance()
{
return instance;
}
/**
* Time series of memory usage
*/
public static class MemoryTimeSeries implements TimeSeriesCallback
{
public double getValue()
{
Runtime r = Runtime.getRuntime();
return 1e-6 * (r.totalMemory() - r.freeMemory());
}
}
/**
* Time series of CPU load
*/
public static class CPULoadTimeSeries implements TimeSeriesCallback
{
public double getValue()
{
try
{
return ManagementFactory.getOperatingSystemMXBean().getSystemLoadAverage();
}
catch (Exception e)
{
Tools.logException(e);
return -1;
}
}
}
/**
* Init profiler
*/
public void init()
{
// Create timer
assert(timer == null);
timer = new Timer(true);
// Register default categories
registerTimeAccumulatorCategory(CATEGORY_HTTP_REQUEST);
registerTimeAccumulatorCategory(CATEGORY_SQL_QUERY);
registerTimeAccumulatorCategory(CATEGORY_SQL_STORED_PROCEDURE);
registerTimeAccumulatorCategory(CATEGORY_GENERAL);
// Register default time series
registerTimeSeries(SERIES_USED_MEMORY, new MemoryTimeSeries());
registerTimeSeries(SERIES_CPU_LOAD, new CPULoadTimeSeries());
}
/**
* Exit profiler
*/
public void exit()
{
// Destroy timer
timer.cancel();
timer = null;
// Remove all data
allTimeAccumulators.clear();
allTimeSeries.clear();
}
/**
* Register new time accumulator category
*
* @param category
*/
public synchronized void registerTimeAccumulatorCategory(String category)
{
// Register category
allTimeAccumulators.put(category, new ConcurrentHashMap<String, TimeAccumulatorWithChildren>());
// Register corresponding time series
registerTimeSeries(TimeSeries.Type.RATE, category + RATE);
registerTimeSeries(TimeSeries.Type.AVERAGE, category + AVERAGE_TIME);
registerTimeSeries(TimeSeries.Type.MIN, category + MIN_TIME);
registerTimeSeries(TimeSeries.Type.MAX, category + MAX_TIME);
}
/**
* Register new time series
*
* @param name
*/
public synchronized void registerTimeSeries(TimeSeries.Type type, String name)
{
TimeSeries ts = new TimeSeries(type, null);
timer.scheduleAtFixedRate(ts, 0, TimeSeries.MS_PER_SAMPLE);
allTimeSeries.put(name, ts);
}
/**
* Register time series with callback to get value at fixed intervals
*
* @param name
* @param callback
*/
public synchronized void registerTimeSeries(String name, TimeSeriesCallback callback)
{
TimeSeries ts = new TimeSeries(TimeSeries.Type.VALUE, callback);
timer.scheduleAtFixedRate(ts, 0, TimeSeries.MS_PER_SAMPLE);
allTimeSeries.put(name, ts);
}
/**
* Get time accumulator
*
* @param category
* @param subCategory
* @return
*/
public TimeAccumulatorWithChildren getTimeAccumulator(String category, String subCategory)
{
// Find sub categories
ConcurrentHashMap<String, TimeAccumulatorWithChildren> sc = allTimeAccumulators.get(category);
if (sc == null)
return null;
// Find stat
TimeAccumulatorWithChildren a = sc.get(subCategory);
if (a == null)
{
a = new TimeAccumulatorWithChildren();
sc.put(subCategory, a);
}
return a;
}
/**
* Get time series
*
* @param name
* @return
*/
public TimeSeries getTimeSeries(String name)
{
return allTimeSeries.get(name);
}
/**
* Reset profiler
*/
public synchronized void reset()
{
// Reset stats
for (ConcurrentHashMap<String, TimeAccumulatorWithChildren> sc : allTimeAccumulators.values())
for (TimeAccumulatorWithChildren a : sc.values())
a.reset();
// Reset time series
for (TimeSeries s : allTimeSeries.values())
s.reset();
}
/**
* Accumulate time for time series
*
* @param category Category to accumulate for
* @param totalTime Time to accumulate in ms
*/
public void accumulateTime(String category, double totalTime)
{
accumulateTimeSeries(category + RATE, 1);
accumulateTimeSeries(category + AVERAGE_TIME, totalTime);
accumulateTimeSeries(category + MIN_TIME, totalTime);
accumulateTimeSeries(category + MAX_TIME, totalTime);
}
/**
* Accumulate value on time series
*
* @param name
* @param value
*/
public void accumulateTimeSeries(String name, double value)
{
TimeSeries s = getTimeSeries(name);
if (s != null)
s.accumulate(value);
}
/**
* Start sampling
*
* @param category
* @param subCategory
* @return
*/
public Sampler startSampler(String category, String subCategory)
{
Sampler sampler = new Sampler(this, category, subCategory);
sampler.start();
return sampler;
}
/**
* Get all categories
*/
public synchronized String[] getTimeAccumulatorCategories()
{
String[] c = allTimeAccumulators.keySet().toArray(new String[0]);
Arrays.sort(c);
return c;
}
/**
* Get all sub categories for a category
*/
public synchronized String[] getTimeAccumulatorSubCategories(String category)
{
ConcurrentHashMap<String, TimeAccumulatorWithChildren> sc = allTimeAccumulators.get(category);
if (sc == null)
return null;
return sc.keySet().toArray(new String[0]);
}
/**
* Get all time series names
*/
public synchronized String[] getTimeSeriesNames()
{
String[] n = allTimeSeries.keySet().toArray(new String[0]);
Arrays.sort(n);
return n;
}
}