/*
* Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Business Objects nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Executor.java
* Created: Jan 27, 2003 at 2:16:12 PM
* By: RCypher
*/
package org.openquark.cal.internal.machine.lecc;
import java.lang.reflect.InvocationTargetException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import org.openquark.cal.compiler.DataConstructor;
import org.openquark.cal.compiler.QualifiedName;
import org.openquark.cal.compiler.TypeExpr;
import org.openquark.cal.compiler.io.EntryPoint;
import org.openquark.cal.internal.machine.DynamicRuntimeEnvironment;
import org.openquark.cal.internal.machine.EntryPointImpl;
import org.openquark.cal.internal.runtime.lecc.LECCMachineConfiguration;
import org.openquark.cal.internal.runtime.lecc.RTApplication;
import org.openquark.cal.internal.runtime.lecc.RTData;
import org.openquark.cal.internal.runtime.lecc.RTExecutionContext;
import org.openquark.cal.internal.runtime.lecc.RTFullApp;
import org.openquark.cal.internal.runtime.lecc.RTOApp2;
import org.openquark.cal.internal.runtime.lecc.RTOApp3;
import org.openquark.cal.internal.runtime.lecc.RTPartialApp;
import org.openquark.cal.internal.runtime.lecc.RTValue;
import org.openquark.cal.machine.CALExecutor;
import org.openquark.cal.machine.MachineFunction;
import org.openquark.cal.machine.Program;
import org.openquark.cal.machine.StatsGenerator;
import org.openquark.cal.machine.StatsGenerator.StatsObject;
import org.openquark.cal.module.Cal.Core.CAL_Prelude;
import org.openquark.cal.runtime.CALExecutorException;
import org.openquark.cal.runtime.ExecutionContext;
import org.openquark.cal.runtime.ResourceAccess;
/**
* This is the Executor for the LECC 'machine'
* <p>
* Creation: Jan 27, 2003
* @author Raymond Cypher
*/
public class Executor implements CALExecutor {
/** This is the program object representing the universe of functions that the executor can run. */
private final Program program;
/** Provides access to the resources of the current environment (e.g. from the workspace, or from Eclipse). */
private final ResourceAccess resourceAccess;
/** A Set of registered statistics generators. */
private final Set<StatsGenerator> statsGenerators = new HashSet<StatsGenerator> ();
/** Preallocated exception for VM errors, since in this case, we may not be able to allocate a new object when the exception occurs. */
CALExecutorException.InternalException.VMException vmException = CALExecutorException.InternalException.VMException.makeInitial();
private final RTExecutionContext executionContext;
/**
* Constructor for Executor.
* @param program
* @param resourceAccess
* the ResourceAccess instance to provide access to the resources of the current
* environment (e.g. from the workspace, or from Eclipse).
* @param context
*/
public Executor(Program program, ResourceAccess resourceAccess, ExecutionContext context) {
super();
if (program == null) {
throw new NullPointerException ("lecc.Executor: program can't be null.");
}
if (resourceAccess == null) {
throw new NullPointerException ("lecc.Executor: resourceAccess can't be null.");
}
if (context == null) {
throw new NullPointerException ("lecc.Executor: context can't be null.");
}
this.program = program;
this.resourceAccess = resourceAccess;
this.executionContext = (RTExecutionContext)context;
}
/**
* Run the function specified by the EntryPoint using the given Java arguments.
* @param entryPoint
* @param arguments (null is accepted and used as the 0-length Object array).
* @return the result of the execution
* @throws CALExecutorException
*/
public Object exec(EntryPoint entryPoint, Object[] arguments) throws CALExecutorException {
// Since we are starting execution we want to reset to initial state.
reset();
if (entryPoint == null) {
throw new CALExecutorException.InternalException ("null EntryPoint.");
}
QualifiedName entryPointSCName = ((EntryPointImpl)entryPoint).getFunctionName();
CALExecutorException error = null;
Object result = null;
RTValue startPoint = null;
try {
instrument (new ExecTimeInfo(true,
executionContext.getNReductions(),
executionContext.getNMethodCalls(),
executionContext.getNDataTypeInstances()));
try {
LECCModule startModule =
(LECCModule) program.getModule(entryPointSCName.getModuleName());
MachineFunction mf = startModule.getFunction(entryPointSCName);
startPoint = makeStartPointInstance(mf);
} catch (Exception e) {
throw new CALExecutorException.InternalException ("Unable to create instance of " + entryPointSCName, e);
}
if (startPoint == null) {
throw new CALExecutorException.InternalException ("Unable to create start point instance for: " + entryPointSCName);
}
if (arguments != null) {
for (int i = 0; i < arguments.length; ++i) {
startPoint = startPoint.apply(new RTData.CAL_Opaque(arguments[i]));
}
}
startingInitialExecution();
result = startPoint.evaluate(executionContext).getOpaqueValue();
} catch (RTValue.InternalException e) {
error = new CALExecutorException.InternalException (e.getErrorInfo(), "Error in evaluation: ", e);
} catch (CALExecutorException e) {
error = e;
} catch (LinkageError e) {
// LinkageErrors can happen during classloading.
// For example, an internal error during bytecode generation.
error = new CALExecutorException.InternalException ("Fatal Executor error.", e);
} catch (Error e) {
// Assume that something really bad has happened: reset the state of the
// machine and force a finalization/gc.
program.resetCachedResults(executionContext);
reset ();
for (int i = 0; i < 10; ++i) {
//System.out.println ("trying to free memory " + i);
System.runFinalization();
System.gc();
}
vmException.initCause(e);
error = (vmException);
} catch (Exception e) {
error = new CALExecutorException.InternalException ("Fatal Executor error.", e);
}
startPoint = null;
instrument (new ExecTimeInfo (false,
executionContext.getNReductions(),
executionContext.getNMethodCalls(),
executionContext.getNDataTypeInstances()));
if (LECCMachineConfiguration.generateCallCounts()) {
instrument (new CallCountInfo (executionContext.getCallCounts(), "Call counts:"));
instrument (new CallCountInfo (executionContext.getDcConstructorCounts(), "DataConstructor instance counts:"));
instrument (new CallCountInfo (executionContext.getDcFunctionCounts(), "DataConstructor functional form counts:"));
}
finishedInitialExecution ();
if (error != null) {
throw error;
}
reset ();
// Clear the RuntimeEnvironment field of the execution context. This prevents
// an out-of date instance of Program from being held.
executionContext.clearRuntimeEnvironment();
return result;
}
/**
* Create an instance of the generated which is the starting
* point for evaluating the entry point supercombinator.
* This can involve resolving to an aliased function or
* literal value.
*
* @param entryPoint
* @return - the start point instance
* @throws InvocationTargetException
* @throws IllegalAccessException
* @throws ClassNotFoundException
* @throws NoSuchMethodException
* @throws NoSuchFieldException
*/
private RTValue makeStartPointInstance(MachineFunction entryPoint)
throws InvocationTargetException, IllegalAccessException,
ClassNotFoundException, NoSuchMethodException, NoSuchFieldException {
final QualifiedName idName = CAL_Prelude.Functions.id;
// An instance of Prelude.id
RTValue idFunction = null;
if (!entryPoint.getQualifiedName().equals(idName)) {
LECCModule startModule =
(LECCModule) program.getModule(idName.getModuleName());
MachineFunction mf = startModule.getFunction(idName);
idFunction = getInstanceOfGeneratedClass(mf);
}
RTValue startPoint = getInstanceOfGeneratedClass(entryPoint);
// At this point we want to apply the identity function (Prelude.id) to
// the entry point. It has been
// found that this extra level of indirection allows the Sun client JVM
// to more efficiently garbage-collect
// discarded graph nodes for some types of functions.
if (idFunction != null) {
//System.out.println("applying id");
startPoint = idFunction.apply(startPoint);
}
return startPoint;
}
/**
* Create an instance of the generated class corresponding to the
* entry point supercombinator.
* @param machineFunction - The MachineFunction representing the CAL function for
* which an instance of the generated class is desired.
* @return - the start point instance
* @throws InvocationTargetException
* @throws IllegalAccessException
* @throws ClassNotFoundException
* @throws NoSuchMethodException
* @throws NoSuchFieldException
*/
/*
* @implementation
* If you modify this method, you should also modify the related method
* StandaloneJarBuilder.getInstanceOfGeneratedClassJavaExpression
*/
private RTValue getInstanceOfGeneratedClass(MachineFunction machineFunction)
throws InvocationTargetException, IllegalAccessException,
ClassNotFoundException, NoSuchMethodException, NoSuchFieldException {
// System.out.println ("****************** start -
// makeStartPointInstance " + entryPointSCName + " => thread = " +
// Thread.currentThread().getName());
// long stime = System.currentTimeMillis();
RTValue startPoint = null;
if (machineFunction == null) {
throw new NullPointerException("MachineFunction argument cannot be null.");
}
// The specified target may be an alias of another function, or it may
// be defined as a literal value
// (either directly or from following an alias chain). In either case
// there is no source/class
// generated. So we need to either return the literal value or follow
// the alias to a
// function for which there is a generated source/class file.
if (machineFunction.getLiteralValue() != null) {
return new RTData.CAL_Opaque(machineFunction.getLiteralValue());
}
QualifiedName entryPointSCName = machineFunction.getQualifiedName();
if (machineFunction.getAliasOf() != null) {
entryPointSCName = machineFunction.getAliasOf();
}
LECCModule startModule = (LECCModule) program.getModule(
entryPointSCName.getModuleName());
machineFunction = startModule.getFunction(entryPointSCName);
CALClassLoader loader = ((LECCModule) program.getModule(
entryPointSCName.getModuleName())).getClassLoader();
if (loader == null) {
return null;
}
String className = "Unknown";
// Munge the qualified name into a class name.
// Get local name and capitalized local name
if (machineFunction.isDataConstructor()) {
// this is a DataConstructor
className = CALToJavaNames.createFullClassNameFromDC(
entryPointSCName, startModule);
DataConstructor dc = startModule.getModuleTypeInfo().getDataConstructor(
entryPointSCName.getUnqualifiedName());
if (LECCMachineConfiguration.TREAT_ENUMS_AS_INTS && TypeExpr.isEnumType(dc.getTypeConstructor())) {
// an enum data cons treated as an int - we should just return the boxed ordinal
return RTData.CAL_Int.make(dc.getOrdinal());
} else {
if (className.endsWith("$TagDC")) {
if (entryPointSCName.equals(CAL_Prelude.DataConstructors.True)) {
return RTData.CAL_Boolean.TRUE;
} else if (entryPointSCName
.equals(CAL_Prelude.DataConstructors.False)) {
return RTData.CAL_Boolean.FALSE;
} else {
className = className.substring(0, className.length() - 6);
startPoint = loader.getTagDCStartPointInstance(className,
dc.getOrdinal());
}
} else {
startPoint = loader.getStartPointInstance(className, machineFunction, executionContext);
}
}
} else {
className = CALToJavaNames
.createFullClassNameFromSC(entryPointSCName, startModule);
startPoint = loader.getStartPointInstance(className, machineFunction, executionContext);
}
// System.out.println ("class loading time: " +
// (System.currentTimeMillis() - stime) + " => thread = " +
// Thread.currentThread().getName());
// System.out.println ("****************** end -
// makeStartPointInstance"+ " => thread = " +
// Thread.currentThread().getName());
return startPoint;
}
/**
* Reset the Executor.
*/
void reset() {
vmException = CALExecutorException.InternalException.VMException.makeInitial();
executionContext.reset(new DynamicRuntimeEnvironment(program, resourceAccess));
}
/**
* Register a stats generator.
* @param gen
*/
public void addStatsGenerator (StatsGenerator gen) {
statsGenerators.add (gen);
}
/**
* Remove a stats generator.
* @param gen
*/
public void removeStatsGenerator (StatsGenerator gen) {
statsGenerators.remove (gen);
}
/**
* Ask the runtime to quit.
* Note that you can only ask the runtime to quit; you can't un-quit - we may want to have something like this later
* if we want to implement "restart."
* Creation date: (3/21/02 1:39:40 PM)
*/
public void requestQuit() {
executionContext.requestQuit();
}
/**
* {@inheritDoc}
*/
public void requestSuspend() {
executionContext.requestSuspend();
}
/**
* Set the regular expressions that the tracing code uses to filter the traces.
*
* @param patterns The list of regular expressions to use when filtering traces.
*/
public void setTraceFilters(List<Pattern> patterns) {
executionContext.setTraceFilters(patterns);
}
/**
* This is used for diagnostics and instrumentation.
* @param object Object
*/
void instrument (StatsGenerator.ProfileObj object) {
//System.out.println ("Instrument -> " + object.toString ());
for (final StatsGenerator gen : statsGenerators) {
gen.i_instrument(object);
}
}
/**
* A helper function for derived classes to override.
* Allows derived classes to do any special signaling/handling
* when the initial evaluation is starting.
*/
void startingInitialExecution () {
if (LECCMachineConfiguration.generateAppCounts()) {
RTFullApp.resetNInstances();
RTApplication.resetNInstances();
RTValue.resetNApplicationCalls();
RTOApp2.resetNInstances();
RTOApp3.resetNInstances();
RTPartialApp.resetNInstances();
}
}
/**
* A helper function for derived classes to override.
* Allows derived classes to do any special signaling/handling
* when the initial evaluation is complete.
*/
void finishedInitialExecution () {
if (LECCMachineConfiguration.generateAppCounts()) {
System.out.println (RTValue.getNApplicationCalls() + " calls to apply().");
System.out.println ("RTFullApp -> " + RTFullApp.getNInstances());
System.out.println ("RTApplication -> " + RTApplication.getNInstances());
System.out.println ("RTOApp2 -> " + RTOApp2.getNInstances());
System.out.println ("RTOApp3 -> " + RTOApp3.getNInstances());
System.out.println ("RTPartialApp -> " + RTPartialApp.getNInstances());
}
}
/** {@inheritDoc} */
public RTExecutionContext getContext () {
return executionContext;
}
/**
* Class used to tell the statistics generator about execution time info.
* @author rcypher
*/
static class ExecTimeInfo implements StatsGenerator.ProfileObj {
/** Does this object indicate the start or end of an event. */
private boolean start;
/** The number of reductions in this event. */
private long reductions;
/** The number of method calls in this event. */
private long methods;
/** The number of data instances in this event. */
private long dataInstances;
public ExecTimeInfo (boolean start, long reductions, long methods, long dataInstances) {
this.start = start;
this.reductions = reductions;
this.dataInstances = dataInstances;
this.methods = methods;
}
/**
* Update the given StatsObject with the information in this ProfileObject.
* @param stats
* @return - the updated StatsObject, null if no update occurs
*/
public StatsObject updateStats (StatsObject stats) {
if (stats == null) {
stats = new ExecTimeStats ();
}
// If the StatsObject is not compatible return null.
if (!(stats instanceof ExecTimeStats)) {
return null;
}
ExecTimeStats mStats = (ExecTimeStats)stats;
if (start) {
// If we are starting execution we update the state in the StatsObject
mStats.executionStart = System.currentTimeMillis();
mStats.execStarted = true;
} else {
// If we are ending execution we want to update the StatsObject
// being sure to handle a StatsObject that hasn't been started.
if (!mStats.execStarted) {
if (mStats.execTimes.size() == 0) {
return mStats;
}
mStats.execTimes.remove(mStats.execTimes.size() - 1);
}
mStats.execTimes.add (new Long (System.currentTimeMillis() - mStats.executionStart));
mStats.execStarted = false;
}
// Update the counts if necessary.
if (reductions != -1) {
mStats.reductionsExec = reductions;
}
if (methods != -1) {
mStats.methodsExec = methods;
}
if (dataInstances != -1) {
mStats.dataInstancesExec = dataInstances;
}
return stats;
}
/**
* Class used to track execution time statistics.
* Instances of this class will be held by the StatsGenerator
* @author rcypher
*/
protected static class ExecTimeStats extends StatsGenerator.PerformanceStatsObject {
long executionTime = -1;
/** Time in ms at which the execution started. */
long executionStart = 0;
// vectors to hold individual contributions
final List<Long> execTimes = new ArrayList<Long> ();
// Keep track of the state of execution.
boolean execStarted = false;
/** Count of reductions. */
long reductionsExec;
/** Count of method calls. */
long methodsExec;
/** Count of data instances. */
long dataInstancesExec;
/**
* Generate a short message describing the information in this StatsObject.
* @return - a string describing the information.
*/
public String generateShortMessage() {
DecimalFormat numFormat = new DecimalFormat("###,###.###");
StringBuilder sb = new StringBuilder();
if (LECCMachineConfiguration.generateStatistics()) {
sb.append (numFormat.format(reductionsExec) + " reductions in " + numFormat.format(getRunTime()) + "ms");
if (getRunTime() > 0) {
sb.append (", reductions/s = " + numFormat.format(((reductionsExec*1000)/getRunTime())));
}
} else {
sb.append ("Run Time: " + numFormat.format(getRunTime()) + " ms\n");
}
return sb.toString();
}
/**
* Generate a long message describing the information in this StatsObject.
* @return - a string describing the information.
*/
public String generateLongMessage() {
DecimalFormat numFormat = new DecimalFormat("###,###.###");
StringBuilder sb = new StringBuilder ();
sb.append ("Run Time: " + numFormat.format(getRunTime()) + " ms\n");
if (LECCMachineConfiguration.generateStatistics()) {
if (reductionsExec > 0 && getRunTime() > 0) {
sb.append (" Reductions: " + numFormat.format(reductionsExec) + " (" + numFormat.format(((reductionsExec*1000)/getRunTime())) + "/s)");
sb.append("\n");
}
if (methodsExec > 0 && getRunTime() > 0) {
sb.append (" Methods Calls: " + numFormat.format(methodsExec) + " (" + numFormat.format(((methodsExec*1000)/getRunTime())) + "/s)");
sb.append ("\n");
}
if (dataInstancesExec > 0 && getRunTime() > 0) {
sb.append (" Data Instances: " + numFormat.format(dataInstancesExec) + " (" + numFormat.format(((dataInstancesExec*1000)/getRunTime())) + "/s)");
sb.append ("\n");
}
}
return sb.toString();
}
/**
* Summarize a list of StatsObject of the same actual type as this.
* @param v - A List of StatsObject
* @return a summarized StatsObject
* {@inheritDoc}
*/
public StatsObject summarize (List<StatsObject> v) {
// Create a SummarizedExecTimes object
// containing the summarized info for the list
// of StatsObject
// If there are more than 3 runs we will discard the first run
final int nRuns = v.size();
final int nstart = (nRuns >= 3) ? 1 : 0;
final int nend = nRuns;
final int ncount = nend - nstart;
long allExecTimes[] = new long [nRuns];
{
int i = 0;
for (final StatsObject statsObject : v) {
ExecTimeStats stats = (ExecTimeStats) statsObject;
allExecTimes [i] = stats.getRunTime();
i++;
}
}
long firstExecTime = allExecTimes [0];
SummarizedExecTimes ptr = new SummarizedExecTimes ();
ptr.allExecTimes = new long[allExecTimes.length];
System.arraycopy(allExecTimes, 0, ptr.allExecTimes, 0, allExecTimes.length);
long avgExecTime = 0;
for (int i = nstart; i < nend; ++i) {
avgExecTime += allExecTimes [i];
}
avgExecTime /= (ncount);
// calculate standard deviation.
double stDev = 0.0;
long avg = 0;
for (int i = nstart; i < nend; ++i) {
avg += (allExecTimes [i]);
}
avg /= ncount;
long summ = 0;
for (int i = nstart; i < nend; ++i) {
long diff = allExecTimes [i] - avg;
summ += (diff * diff);
}
double stdDevPercent;
if (ncount > 1) {
stDev = (double)summ /(double)(ncount-1);
stDev = Math.sqrt(stDev);
stDev = ((int)(stDev * 100.0)) / 100.0;
stdDevPercent = (stDev / (avgExecTime)) * 100.0;
stdDevPercent = ((int)(stdDevPercent * 100.0)) / 100.0;
} else {
stDev = stdDevPercent = 0.0;
}
//calculate the standard error:
double standardError = stDev / Math.sqrt(ncount);
ptr.frRunTime = firstExecTime;
ptr.avgRunTime = avgExecTime;
ptr.stdDev = stDev;
ptr.stdDevPercent = stdDevPercent;
ptr.standardError = standardError;
ptr.methodsExec = methodsExec;
ptr.reductionsExec = reductionsExec;
ptr.dataInstancesExec = dataInstancesExec;
return ptr;
}
/**
* Return the execution time (i.e. time spent running before reaching initial WHNF).
* @return long
*/
public long getRunTime () {
if (executionTime == -1) {
executionTime = 0;
for (final Long l : execTimes) {
executionTime += l.longValue();
}
}
return executionTime;
}
@Override
public long getAverageRunTime() {
return getRunTime();
}
@Override
public long getMedianRunTime() {
return getRunTime();
}
@Override
public double getStdDeviation() {
return 0.0;
}
@Override
public int getNRuns() {
return 1;
}
@Override
public long[] getRunTimes() {
return new long[]{getRunTime()};
}
}
/**
* Class used to summarize execution time statistics for a series of runs.
* @author rcypher
*/
protected static class SummarizedExecTimes extends StatsGenerator.PerformanceStatsObject {
/** Run time for the first run. */
long frRunTime;
/** Average run time. */
long avgRunTime;
/** Standard deviation of the run times. */
double stdDev;
/** Standard deviation as a percent of the average run time. */
double stdDevPercent;
/** Standard error of the mean. */
double standardError;
/** Times for all runs. */
long allExecTimes[];
/** The number of reductions for a single run. */
long reductionsExec;
/** The number of method calls for a single run. */
long methodsExec;
/** The number of data constructor instances for a single run. */
long dataInstancesExec;
/**
* Generate a short message describing the information in this StatsObject.
* @return - a string describing the information.
*/
public String generateShortMessage() {
StringBuilder sb = new StringBuilder ();
DecimalFormat numFormat = new DecimalFormat("###,###.###");
if (LECCMachineConfiguration.generateStatistics()) {
sb.append ("Summary: time = " + numFormat.format(avgRunTime) + ", reductions/s = " + numFormat.format(((reductionsExec*1000)/avgRunTime)));
} else {
sb.append ("Average time = " + numFormat.format(avgRunTime) + "ms.");
}
return sb.toString();
}
/**
* Generate a long message describing the information in this StatsObject.
* @return - a string describing the information.
*/
public String generateLongMessage() {
StringBuilder sb = new StringBuilder ();
DecimalFormat numFormat = new DecimalFormat("###,###.###");
if (allExecTimes.length >= 3) {
sb.append("Individual runs: (first run discarded for statistics due to potential class loading and code generation)\n");
for (int i = 0; i < allExecTimes.length; ++i) {
sb.append ("run " + i + ":\t" + numFormat.format(allExecTimes[i]));
if (i == 0) {
sb.append(" (discarded)\n");
} else {
sb.append('\n');
}
}
} else {
sb.append("Individual runs: \n");
for (int i = 0; i < allExecTimes.length; ++i) {
sb.append ("run " + i + ":\t" + numFormat.format(allExecTimes[i]) + "\n");
}
}
sb.append("First run time = " + numFormat.format(frRunTime) + "ms\n");
sb.append("Average time = " + numFormat.format(avgRunTime) + "ms\n");
sb.append("Minimum time = " + numFormat.format(getMinTime()) + "ms\n");
sb.append("Standard deviation of runs = " + numFormat.format(stdDev) + "ms or " + numFormat.format(stdDevPercent) + "% of average\n");
sb.append("Standard error of mean = " + numFormat.format(standardError) + "ms or " + numFormat.format(standardError/avgRunTime*100) + "% of average\n");
if (LECCMachineConfiguration.generateStatistics()) {
if (reductionsExec > 0 && getRunTime() > 0) {
sb.append (" Reductions: " + numFormat.format(reductionsExec) + " (" + numFormat.format(((reductionsExec*1000)/getRunTime())) + "/s)");
sb.append("\n");
}
if (methodsExec > 0 && getRunTime() > 0) {
sb.append (" Methods Calls: " + numFormat.format(methodsExec) + " (" + numFormat.format(((methodsExec*1000)/getRunTime())) + "/s)");
sb.append ("\n");
}
if (dataInstancesExec > 0 && getRunTime() > 0) {
sb.append (" Data Instances: " + numFormat.format(dataInstancesExec) + " (" + numFormat.format(((dataInstancesExec*1000)/getRunTime())) + "/s)");
sb.append ("\n");
}
}
return sb.toString();
}
/**
* Return a summarization of the List of StatsObject.
* @param runs
* @return the summarized StatsObject
* {@inheritDoc}
*/
public StatsObject summarize (List<StatsObject> runs) {
// This is a summarized object so no further summarization can be done.
if (runs == null) {
return null;
}
return this;
}
/**
* @return the average run time, since this is a summarization of multiple runs
*/
public long getRunTime() {
return avgRunTime;
}
/**
* @return the average run time
*/
@Override
public long getAverageRunTime() {
return avgRunTime;
}
/**
* @return the median run time
*/
@Override
public long getMedianRunTime() {
if (allExecTimes.length == 1) {
return allExecTimes[0];
}
long times[] = new long[allExecTimes.length];
System.arraycopy(allExecTimes, 0, times, 0, times.length);
Arrays.sort(times);
if ((times.length % 2) == 0) {
return (times[times.length/2] + times[(times.length/2)-1]) / 2;
}
return times[(times.length/2)];
}
/**
* @return the standard deviation
*/
@Override
public double getStdDeviation() {
return stdDev;
}
/**
* @return the standard error of the mean
*/
public double getStandardError() {
return standardError;
}
/**
* @return the number of runs used in calculating the average and standard deviation.
*/
@Override
public int getNRuns() {
return allExecTimes.length >= 3 ? allExecTimes.length - 1 : allExecTimes.length;
}
/**
* Return the times for all runs. Note that there may be more than are indicated by
* the value returned by getNRuns(). This is because we discard the highest/lowest values,
* if there are more than three runs, when calculating average run time and standard
* deviation.
* @return the times for all runs.
*/
@Override
public long[] getRunTimes() {
long times[] = new long[allExecTimes.length];
System.arraycopy(allExecTimes, 0, times, 0, times.length);
return times;
}
}
}
/**
* A profile object that collects information about the frequency of
* invocation for different CAL entities. This object is used to communicate
* the information to the StatsGenerator
*
* @author rcypher
*/
static class CallCountInfo implements StatsGenerator.ProfileObj {
/** Map of QualifiedName -> CallCount. Associates a CallCount object with a named entity. */
private final Map<QualifiedName, CallCount> counts;
/** Describes the type of counts being recorded. E.g. supercombinator call counts,
* data constructor call counts, etc.
*/
private final String type;
CallCountInfo (Map<QualifiedName, Integer> counts, String type) {
final Map<QualifiedName, CallCount> convertedCounts = new HashMap<QualifiedName, CallCount>();
for (Map.Entry<QualifiedName, Integer> entry : counts.entrySet()) {
QualifiedName name = entry.getKey();
convertedCounts.put(name, new CallCount(name, entry.getValue()));
}
this.counts = convertedCounts;
this.type = type;
}
/**
* Update the supplied statistics object.
* If the StatsObject is of incorrect type return null.
* If the StatsObject is null create a new one.
* @param stats
* @return StatsObject
*/
public StatsObject updateStats (StatsObject stats) {
if (stats == null) {
stats = new CallCountStats (type);
}
if (!(stats instanceof CallCountStats)) {
return null;
}
CallCountStats mStats = (CallCountStats)stats;
if (!mStats.type.equals(type)) {
return null;
}
Map<QualifiedName, CallCount> allCounts = mStats.counts;
for (final Map.Entry<QualifiedName, CallCount> entry : allCounts.entrySet()) {
QualifiedName key = entry.getKey();
CallCount pc = counts.get(key);
CallCount tc = entry.getValue();
if (tc == null) {
tc = new CallCount(pc.getName(), 0);
allCounts.put(key, tc);
}
tc.incrementBy(pc.getCount());
}
return mStats;
}
/**
* A class used to accumulate call count information in a StatsGenerator.
* Instances of this class will be held by the StatsGenerator to accumulate
* information and ultimately to generate a message describing the accumulated info.
*
* @author rcypher
*/
protected static class CallCountStats implements StatsGenerator.StatsObject {
/** Associates a named entity to a CallCount instance. */
Map<QualifiedName, CallCount> counts = new HashMap<QualifiedName, CallCount>();
/** A description of the type of counts being recorded. e.g. supercombinator, dataconstructor, etc. */
String type;
public CallCountStats (String type) {
this.type = type;
}
/**
* Generate a short message describing these statistics.
* @return String
*/
public String generateShortMessage() {
// There is only one version of the description of these statistics so
// we simply pass on the call.
return generateLongMessage();
}
/**
* Generate a long message describing these statistics.
* @return String
*/
public String generateLongMessage() {
// Sort the counts by frequency.
List<CallCount> sortedCounts = new ArrayList<CallCount> ();
for(final Map.Entry<QualifiedName, CallCount> entry : counts.entrySet()) {
sortedCounts.add (entry.getValue());
}
Collections.sort (sortedCounts);
// Build up a message displaying the
// counts sorted by frequency and accumulate a
// total counts value to append at the end.
StringBuilder sb = new StringBuilder();
sb.append(type + "\n");
long totalCounts = 0;
for (final CallCount count : sortedCounts) {
sb.append (count.getCount() + "\t\t-> " + count.getName() + "\n");
totalCounts += count.getCount();
}
sb.append (" total counts = " + totalCounts + "\n\n");
if (totalCounts == 0) {
return "";
}
return sb.toString();
}
/**
* Summarize the list of stats objects.
* The objects in the list will be of the same type
* as the executing instance.
* This method is called when a set of StatsObjects has
* been accumulated across several runs.
* @param runs
* @return StatsObject.
* {@inheritDoc}
*/
public StatsObject summarize(List<StatsObject> runs) {
if (runs == null) {
return null;
}
return this;
}
}
}
/**
* A class for holding a count of the number of times a CAL
* function is invoked.
* Implements comparable by comparing the count.
* @author rcypher
*/
static final class CallCount implements Comparable<CallCount> {
/** Name of the CAL function. */
private final QualifiedName name;
/** Number of times the function has been invoked. */
private int count;
CallCount (QualifiedName name, int initialCount) {
this.name = name;
this.count = initialCount;
}
public void increment () {
count++;
}
public void incrementBy(int i) {
count += i;
}
public QualifiedName getName() {
return name;
}
public int getCount() {
return count;
}
/**
* Compare two CallCount object based on
* the count value.
* @param cc - the object to compare to.
* @return int
*/
public int compareTo (CallCount cc) {
int nameCompare = name.compareTo(cc.name);
if (nameCompare != 0) {
return nameCompare;
}
if (cc.count > count) {
return 1;
} else if (cc.count == count) {
return 0;
} else {
return -1;
}
}
@Override
public boolean equals (Object o) {
if (o == null || !(o instanceof CallCount)) {
return false;
}
CallCount other = (CallCount)o;
return count == other.count && name.equals(other.name);
}
@Override
public int hashCode () {
return name.hashCode() + count;
}
}
}