package net.kennux.cubicworld.profiler;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map.Entry;
import net.kennux.cubicworld.util.ConsoleHelper;
import com.badlogic.gdx.Gdx;
/**
* <pre>
* Profiler used to profile different code sections.
* Reset this after every frame, so this profiler collects data every frame.
*
* This class is fully threadsafe.
* </pre>
*
* @author KennuX
*
*/
public class Profiler
{
public enum FileFormat
{
BINARY, PLAINTEXT
}
/**
* When this is set to true the profiler will write to the file opened with it.
* Set this to false in a production environment to not waste performance.
*/
private static final boolean writeToFile = false;
/**
* The current running profilings.
* Key is the name of the profiling, value is the time millis when the
* profiling was started.
*/
private HashMap<String, Long> currentProfilings;
/**
* Contains the additional infos passed in in startProfiling().
* Value may be null!
*/
private HashMap<String, String> additionalInfos;
/**
* Contains the finished profilings's runtime's in nanoseconds.
*/
private HashMap<String, Long> finishedProfilings;
private Object lockObject = new Object();
/**
* The output stream to the file opened in openProfilingFile().
*/
private DataOutputStream profilingFileOutput;
private FileFormat fileFormat;
/**
* Initially contains -1.
* Will get resetted to -1 in reset().
*
* After the first profiling was started this gets set to the
* System.nanoTime().
*/
private long frameStarted = -1;
public Profiler()
{
if (this.currentProfilings == null)
this.currentProfilings = new HashMap<String, Long>();
if (this.finishedProfilings == null)
this.finishedProfilings = new HashMap<String, Long>();
if (this.additionalInfos == null)
this.additionalInfos = new HashMap<String, String>();
}
/**
* Returns the profiling result for the given name.
* Call this before reset()!
*
* Will return null if there is no profiling result.
*
* @param name
* @return
*/
public float getProfilerResult(String name)
{
synchronized (this.lockObject)
{
if (finishedProfilings.containsKey(name))
{
return finishedProfilings.get(name).floatValue();
}
return 0;
}
}
/**
* Returns all profiler results currently stored in this profiler.
*
* @return
*/
public ProfilerResult[] getResults()
{
ProfilerResult[] results = new ProfilerResult[this.finishedProfilings.size()];
int i = 0;
// Pack entries into an array of profiler results.
synchronized (this.lockObject)
{
for (Entry<String, Long> entry : finishedProfilings.entrySet())
{
results[i] = new ProfilerResult(entry.getKey(), entry.getValue(), additionalInfos.get(entry.getKey()));
i++;
}
}
return results;
}
/**
* <pre>
* Will create a file at the given path.
* If the file already exists it will get DELETED and recreated.
* If a file got opened and reset() gets executed, the current results will get written to the file as one frame.
* </pre>
*
* @throws IOException
*/
public void openProfilingFile(String path, FileFormat format) throws IOException
{
synchronized (this.lockObject)
{
File profilingFile = new File(path);
if (profilingFile.exists())
{
profilingFile.delete();
}
if (profilingFile.createNewFile())
{
profilingFileOutput = new DataOutputStream(new FileOutputStream(profilingFile));
}
fileFormat = format;
}
}
/**
* <pre>
* Call this after your frame got rendered / tick was processed.
* Will remove all current profilings and re-initialize the profile.
* Also clears the profiler result set.
*
* This function will write to the profiling file if it was set used
* openProfilingFile().
* </pre>
*/
@SuppressWarnings("unused")
// Because of the writeToFile flag.
public void reset()
{
synchronized (this.lockObject)
{
// Write to file?
if (writeToFile && profilingFileOutput != null)
{
// Write Frame Header
long frameId = -1;
if (Gdx.graphics != null)
frameId = Gdx.graphics.getFrameId();
long timeStamp = System.currentTimeMillis();
int entries = finishedProfilings.size();
try
{
switch (fileFormat)
{
case BINARY:
// Write in binary format for further usage in the
// viewer
profilingFileOutput.writeLong(frameId);
profilingFileOutput.writeLong(timeStamp);
profilingFileOutput.writeInt(entries);
// Write data
for (Entry<String, Long> entry : finishedProfilings.entrySet())
{
profilingFileOutput.write(entry.getKey().getBytes());
profilingFileOutput.writeLong(entry.getValue().longValue());
}
break;
case PLAINTEXT:
// Write in simple plaintext format
profilingFileOutput.write("<====================================================================================>\r\n\r\n\r\n".getBytes());
profilingFileOutput.write(("Profile Frame: " + frameId + "\r\n").getBytes());
profilingFileOutput.write(("Profiling time: " + new SimpleDateFormat("HH:mm:ss").format(Calendar.getInstance().getTime()) + "\r\n").getBytes());
profilingFileOutput.write(("Profiling entries: " + entries + "\r\n").getBytes());
profilingFileOutput.write("\r\n---------------------------------------\r\n".getBytes());
// Write down entries
for (Entry<String, Long> entry : finishedProfilings.entrySet())
{
profilingFileOutput.write(("Entry: " + entry.getKey() + "\r\n").getBytes());
profilingFileOutput.write(("Nanoseconds: " + entry.getValue().longValue() + "\r\n").getBytes());
profilingFileOutput.write(("Milliseconds: " + (entry.getValue().longValue() / 1000000.0f) + "\r\n").getBytes());
profilingFileOutput.write(("Additional info: " + additionalInfos.get(entry.getKey()) + "\r\n").getBytes());
profilingFileOutput.write("\r\n---------------------------------------\r\n".getBytes());
}
profilingFileOutput.write("<====================================================================================>\r\n\r\n\r\n".getBytes());
break;
}
// Flush
profilingFileOutput.flush();
}
catch (IOException e)
{
// Something bad happend
ConsoleHelper.writeLog("error", "Profiling save failed!", "Profiler");
ConsoleHelper.logError(e);
profilingFileOutput = null;
}
}
// The actual reset
frameStarted = -1;
currentProfilings.clear();
additionalInfos.clear();
finishedProfilings.clear();
}
}
/**
* Starts a profiling for the given name.
*
* @param name
* @param additionalInfo
* Additional info, like parameters. Can be null.
*/
public void startProfiling(String name, String additionalInfo)
{
synchronized (this.lockObject)
{
// First this frame?
if (frameStarted == -1)
{
frameStarted = System.nanoTime();
}
currentProfilings.put(name, System.nanoTime());
additionalInfos.put(name, additionalInfo);
}
}
/**
* <pre>
* Stops the profiling for the given profiling name.
* Will add the result of the profiling to the profiler result set.
*
* If no profiling with the given name is running atm nothing will happen in
* here.
* </pre>
*
* @param name
*/
public void stopProfiling(String name)
{
synchronized (this.lockObject)
{
if (currentProfilings.containsKey(name))
{
// Get profilier start time.
long timeDifference = System.nanoTime() - currentProfilings.get(name);
finishedProfilings.put(name, timeDifference);
currentProfilings.remove(name);
}
}
}
}