diff --git a/bin/trick-jperf b/bin/trick-jperf new file mode 100755 index 000000000..7fb8d3468 --- /dev/null +++ b/bin/trick-jperf @@ -0,0 +1,8 @@ +#!/usr/bin/perl + +use FindBin qw($RealBin); +use lib ("$RealBin/../libexec/trick/pm", "$RealBin/../lib/trick/pm") ; +use launch_java ; + +launch_java("JPERF", "JPerf") ; + diff --git a/include/trick/FrameLog.hh b/include/trick/FrameLog.hh index edff7600b..35f519543 100644 --- a/include/trick/FrameLog.hh +++ b/include/trick/FrameLog.hh @@ -20,6 +20,8 @@ namespace Trick { /** Data to save for each timeline sample.\n */ struct timeline_t { bool trick_job; + bool isEndOfFrame; + bool isTopOfFrame; double id; long long start; long long stop; diff --git a/include/trick/JobData.hh b/include/trick/JobData.hh index 8ad72103e..3c5fde622 100644 --- a/include/trick/JobData.hh +++ b/include/trick/JobData.hh @@ -47,6 +47,12 @@ namespace Trick { /** Indicates if a scheduler is handling this job */ bool handled; /**< trick_units(--) */ + /** Indicates whether this is an "top_of_frame" job. */ + bool isTopOfFrame; /**< trick_units(--) */ + + /** Indicates whether this is an "end_of_frame" job. */ + bool isEndOfFrame; /**< trick_units(--) */ + /** The cycle time */ double cycle; /**< trick_units(s) */ diff --git a/trick_source/java/pom.xml b/trick_source/java/pom.xml index ee69706ca..c9cecb6a7 100644 --- a/trick_source/java/pom.xml +++ b/trick_source/java/pom.xml @@ -282,6 +282,22 @@ MM + + + jobperf + package + + shade + + + + + trick.jobperf.JobPerf + + + JPerf + + diff --git a/trick_source/java/src/main/java/trick/jobperf/FrameRecord.java b/trick_source/java/src/main/java/trick/jobperf/FrameRecord.java new file mode 100644 index 000000000..2f4ce8f7e --- /dev/null +++ b/trick_source/java/src/main/java/trick/jobperf/FrameRecord.java @@ -0,0 +1,27 @@ +package trick.jobperf; +import java.util.*; +/** +* Class FrameRecord represents the set of jobs that have been executed during a +* frame. +*/ +public class FrameRecord { + public ArrayList jobEvents; + public double start; + public double stop; + /** + * Constructor + */ + public FrameRecord() { + start = 0.0; + stop = 0.0; + jobEvents = new ArrayList() + + ; + } + /** + * @return the stop time minus the start time. + */ + public double getDuration() { + return stop - start; + } +} diff --git a/trick_source/java/src/main/java/trick/jobperf/InvalidFrameBoundsExpection.java b/trick_source/java/src/main/java/trick/jobperf/InvalidFrameBoundsExpection.java new file mode 100644 index 000000000..faded8ec7 --- /dev/null +++ b/trick_source/java/src/main/java/trick/jobperf/InvalidFrameBoundsExpection.java @@ -0,0 +1,12 @@ +package trick.jobperf; + +/** +* Class InvalidFrameBoundsExpection is an exception indicating +* that the user has specified an illegal range for the frames +* to be rendered. +*/ +class InvalidFrameBoundsExpection extends Exception { + public InvalidFrameBoundsExpection(String message) { + super(message); + } +} diff --git a/trick_source/java/src/main/java/trick/jobperf/JobExecutionEvent.java b/trick_source/java/src/main/java/trick/jobperf/JobExecutionEvent.java new file mode 100644 index 000000000..180a26772 --- /dev/null +++ b/trick_source/java/src/main/java/trick/jobperf/JobExecutionEvent.java @@ -0,0 +1,50 @@ +package trick.jobperf; + +import java.awt.*; +import java.awt.event.*; +import java.awt.image.BufferedImage; +import java.io.*; +import java.lang.Math; +import java.util.*; +import java.util.List; +import javax.swing.*; +import javax.swing.event.*; +import java.net.URL; + + +/** +* Class JobExecutionEvent represents one execution/run of a Trick job. +* identifies the job. and specify the +* clock times at which the job started and finished. +* and indicate whether the job was run as +* an "end-of-frame", or a "top-of-frame" job. +*/ +class JobExecutionEvent { + public String id; + public boolean isEOF; + public boolean isTOF; + public double start; + public double stop; + + /** + * @param identifier identifies the relavant Trick job. + * @param isTopOfFrame true if the job is a "top-of-frame" job, otherwise false. + * @param isEndOfFrame true if the job is a "end-of-frame" job, otherwise false. + * @param start_time the start time (seconds) of the identified job. + * @param stop_time the stop time (seconds) of the identified job. + */ + public JobExecutionEvent(String identifier, boolean isTopOfFrame, boolean isEndOfFrame, double start_time, double stop_time) { + id = identifier; + isEOF = isEndOfFrame; + isTOF = isTopOfFrame; + start = start_time; + stop = stop_time; + } + /** + * Create a String representation of an object of this class. + */ + @Override + public String toString() { + return ( "JobExecutionEvent: " + id + "," + start + "," + stop ); + } +} diff --git a/trick_source/java/src/main/java/trick/jobperf/JobPerf.java b/trick_source/java/src/main/java/trick/jobperf/JobPerf.java new file mode 100644 index 000000000..205de5d1c --- /dev/null +++ b/trick_source/java/src/main/java/trick/jobperf/JobPerf.java @@ -0,0 +1,164 @@ +package trick.jobperf; + +import java.awt.*; +import java.io.*; +import java.util.*; +import javax.swing.*; + +/** + * Capabilites That Need To Be Added + * - a way to filter the data to be within a user specified sub time period + * within the data set. + */ + +/** +* Class JobPerf is an application that renders time-line data from a Trick based + simulation. It also generates run-time statistics reports for the simulation + jobs. It can be run with or without a GUI. +*/ +public class JobPerf { + ArrayList jobExecEvtList; + JobStats jobStats; + + /** + * Constructor + * @param args the command line arguments. + */ + public JobPerf( String[] args ) { + TraceViewWindow traceViewWindow; + boolean interactive = true; + boolean printReport = false; + JobStats.SortCriterion sortOrder = JobStats.SortCriterion.MEAN; + String fileName = "in.csv"; + + int ii = 0; + while (ii < args.length) { + switch (args[ii]) { + case "-h" : + case "--help" : { + printHelpText(); + System.exit(0); + } break; + case "-x" : + case "--nogui" : { + interactive = false; + } break; + case "-p" : + case "--report" : { + printReport = true; + } break; + case "-s0" : + case "--sort=id" : { + sortOrder = JobStats.SortCriterion.ID; + } break; + case "-s1" : + case "--sort=mean" : { + sortOrder = JobStats.SortCriterion.MEAN; + } break; + case "-s2" : + case "--sort=stddev" : { + sortOrder = JobStats.SortCriterion.STDDEV; + } break; + case "-s3" : + case "--sort=max" : { + sortOrder = JobStats.SortCriterion.MAX; + } break; + case "-s4" : + case "--sort=min" : { + sortOrder = JobStats.SortCriterion.MIN; + } break; + default : { + fileName = args[ii]; + } break; + } //switch + ++ii; + } // while + + jobExecEvtList = getJobExecutionEventList(fileName); + jobStats = new JobStats(jobExecEvtList); + if (printReport) { + if (sortOrder == JobStats.SortCriterion.ID ) jobStats.SortByID(); + if (sortOrder == JobStats.SortCriterion.MEAN ) jobStats.SortByMeanValue(); + if (sortOrder == JobStats.SortCriterion.STDDEV ) jobStats.SortByStdDev(); + if (sortOrder == JobStats.SortCriterion.MAX ) jobStats.SortByMaxValue(); + if (sortOrder == JobStats.SortCriterion.MIN ) jobStats.SortByMinValue(); + jobStats.write(); + } + if (interactive) { + traceViewWindow = new TraceViewWindow(jobExecEvtList); + } + } + + /** + * Print the usage instructions to the terminal. + */ + private static void printHelpText() { + System.out.println( + "----------------------------------------------------------------------\n" + + "usage: trick-jperf [options] \n\n" + + "options: \n" + + "-h, --help\n" + + " Print this help text and exit.\n" + + "-x, --nogui\n" + + " Don't run as a GUI application. Command line only.\n" + + "-p, --report\n" + + " Write sorted job statics report to the terminal.\n" + + "-s0, --sort=id\n" + + " Sort job statistics by identifier.\n" + + "-s1, --sort=mean [default]\n" + + " Sort job statistics by mean duration.\n" + + "-s2, --sort=stddev\n" + + " Sort job statistics by standard deviation of duration.\n" + + "-s3, --sort=min\n" + + " Sort job statistics by minimum duration.\n" + + "-s4, --sort=max\n" + + " Sort job statistics by maximum duration.\n" + + "----------------------------------------------------------------------\n" + ); + } + + /** + * Read the timeline file, resulting in a ArrayList. + */ + private ArrayList getJobExecutionEventList( String fileName ) { + String line; + String field[]; + + ArrayList jobExecEvtList = new ArrayList(); + try { + BufferedReader in = new BufferedReader( new FileReader(fileName) ); + + // Strip the header off the CSV file. + line = in.readLine(); + while( (line = in.readLine()) !=null) { + field = line.split(","); + String id = field[0]; + boolean isTOF = false; + if (Integer.parseInt(field[1]) == 1) isTOF = true; + boolean isEOF = false; + if (Integer.parseInt(field[2]) == 1) isEOF = true; + double start = Double.parseDouble( field[3]); + double stop = Double.parseDouble( field[4]); + + if (start < stop) { + JobExecutionEvent evt = new JobExecutionEvent(id, isTOF, isEOF, start, stop); + jobExecEvtList.add( evt); + } + } + } catch ( java.io.FileNotFoundException e ) { + System.out.println("File \"" + fileName + "\" not found.\n"); + System.exit(0); + } catch ( java.io.IOException e ) { + System.out.println("IO Exception.\n"); + System.exit(0); + } + return jobExecEvtList; + } + + /** + * Entry point for the Java application. + */ + public static void main(String[] args) { + JobPerf jobPerf = new JobPerf( args ); + } +} diff --git a/trick_source/java/src/main/java/trick/jobperf/JobStats.java b/trick_source/java/src/main/java/trick/jobperf/JobStats.java new file mode 100644 index 000000000..fd709c406 --- /dev/null +++ b/trick_source/java/src/main/java/trick/jobperf/JobStats.java @@ -0,0 +1,184 @@ +package trick.jobperf; + +import java.io.*; +import java.util.*; + +/** +* Class CompareByID compares two StatisticsRecord's by id. +*/ +class CompareByID implements Comparator { + public int compare(StatisticsRecord a, StatisticsRecord b) { + return a.id.compareTo(b.id); + } +} +/** +* Class CompareByMeanValue compares two StatisticsRecord's by meanValue. +*/ +class CompareByMeanValue implements Comparator { + public int compare(StatisticsRecord a, StatisticsRecord b) { + if ( a.meanValue < b.meanValue) return -1; + if ( a.meanValue > b.meanValue) return 1; + return 0; + } +} +/** +* Class CompareByStdDev compares two StatisticsRecord's by stddev. +*/ +class CompareByStdDev implements Comparator { + public int compare(StatisticsRecord a, StatisticsRecord b) { + if ( a.stddev < b.stddev) return -1; + if ( a.stddev > b.stddev) return 1; + return 0; + } +} +/** +* Class CompareByMaxDuration compares two StatisticsRecord's by maxValue. +*/ +class CompareByMaxDuration implements Comparator { + public int compare(StatisticsRecord a, StatisticsRecord b) { + if ( a.maxValue < b.maxValue) return -1; + if ( a.maxValue > b.maxValue) return 1; + return 0; + } +} +/** +* Class CompareByMinDuration compares two StatisticsRecord's by minValue. +*/ +class CompareByMinDuration implements Comparator { + public int compare(StatisticsRecord a, StatisticsRecord b) { + if ( a.minValue < b.minValue) return -1; + if ( a.minValue > b.minValue) return 1; + return 0; + } +} + +/** +* Class JobStats represents the statistics, i.e., mean, std deviation, max value, +* and min value of the run-duration of each of the Trick jobs in jobExecList. The +* statistic records can be sorted by any of the statistics, and by the job id, +* prior to being written as a report. +*/ +public class JobStats { + + /** + * Enum SortCriterion enumerates the valid ways that JobStats records can be + * sorted. + */ + enum SortCriterion { + ID { + @Override + public String toString() { return "Identifier"; } + }, + MEAN { + @Override + public String toString() { return "Mean Value"; } + }, + STDDEV { + @Override + public String toString() { return "Standard Deviation"; } + }, + MAX { + @Override + public String toString() { return "Maximum Value"; } + }, + MIN { + @Override + public String toString() { return "Minimum Value"; } + } + } + + SortCriterion currentSortCriterion = SortCriterion.MEAN; + ArrayList jobStatisticsList; + + /** + * Constructor + * @param jobExecList - the timeline data. + */ + public JobStats( ArrayList jobExecList ) { + + Map runRegistryMap + = new HashMap(); + + for (JobExecutionEvent jobExec : jobExecList ) { + RunRegistry runRegistry = runRegistryMap.get(jobExec.id); + if (runRegistry != null) { + runRegistry.addTimeSpan(jobExec.start, jobExec.stop); + } else { + runRegistry = new RunRegistry(); + runRegistry.addTimeSpan(jobExec.start, jobExec.stop); + runRegistryMap.put(jobExec.id, runRegistry); + } + } + + jobStatisticsList = new ArrayList(); + + for (Map.Entry entry : runRegistryMap.entrySet()) { + String id = entry.getKey(); + RunRegistry runRegistry = entry.getValue(); + double mean = runRegistry.getMeanDuration(); + double stddev = runRegistry.getStdDev(); + double min = runRegistry.getMinDuration(); + double max = runRegistry.getMaxDuration(); + + jobStatisticsList.add( new StatisticsRecord(id, mean, stddev, min, max)); + } + } + + /** + * Sort by mean duration in descending order. + */ + public void SortByID() { + Collections.sort( jobStatisticsList, new CompareByID()); + currentSortCriterion = SortCriterion.ID; + } + + /** + * Sort by mean duration in descending order. + */ + public void SortByMeanValue() { + Collections.sort( jobStatisticsList, Collections.reverseOrder( new CompareByMeanValue())); + currentSortCriterion = SortCriterion.MEAN; + } + + /** + * Sort by standard deviation of duration in descending order. + */ + public void SortByStdDev() { + Collections.sort( jobStatisticsList, Collections.reverseOrder( new CompareByStdDev())); + currentSortCriterion = SortCriterion.STDDEV; + } + + /** + * Sort by maximum duration in descending order. + */ + public void SortByMaxValue() { + Collections.sort( jobStatisticsList, Collections.reverseOrder( new CompareByMaxDuration())); + currentSortCriterion = SortCriterion.MAX; + } + + /** + * Sort by minimum duration in descending order. + */ + public void SortByMinValue() { + Collections.sort( jobStatisticsList, Collections.reverseOrder( new CompareByMinDuration())); + currentSortCriterion = SortCriterion.MIN; + } + + /** + Write a text report to System.out. + */ + public void write() { + System.out.println(" [Job Duration Statistics Sorted by " + currentSortCriterion +"]"); + System.out.println("----------------------------------------------------------------------"); + System.out.println(" Job Id Mean Duration Std Dev Min Duration Max Duration"); + System.out.println("---------- -------------- -------------- -------------- --------------"); + for (StatisticsRecord jobStatisticsRecord : jobStatisticsList ) { + System.out.println( String.format("%10s %14.6f %14.6f %14.6f %14.6f", + jobStatisticsRecord.id, + jobStatisticsRecord.meanValue, + jobStatisticsRecord.stddev, + jobStatisticsRecord.minValue, + jobStatisticsRecord.maxValue)); + } + } +} diff --git a/trick_source/java/src/main/java/trick/jobperf/KeyedColorMap.java b/trick_source/java/src/main/java/trick/jobperf/KeyedColorMap.java new file mode 100644 index 000000000..7c91e0079 --- /dev/null +++ b/trick_source/java/src/main/java/trick/jobperf/KeyedColorMap.java @@ -0,0 +1,123 @@ +package trick.jobperf; + +import java.awt.*; +import java.io.*; +import java.util.*; + +/** +* Class KeyedColorMap associates identifiers with unique RGB colors. +*/ +public class KeyedColorMap { + private Map colorMap; + int minLuminance; + + /** + * Constructor + */ + public KeyedColorMap() { + colorMap = new HashMap(); + minLuminance = 30; + } + + /** + * Generate a random color, that's not too dark. + * @ return the generated color. + */ + private Color generateColor () { + Random rand = new Random(); + boolean found = false; + int R = 0; + int G = 0; + int B = 0; + + while (!found) { + R = rand.nextInt(256); + G = rand.nextInt(256); + B = rand.nextInt(256); + found = true; + // Reference: https://www.w3.org/TR/AERT/#color-contrast + double luminance = (0.299*R + 0.587*G + 0.114*B); + if (luminance < minLuminance ) found = false; + } + return new Color( R,G,B); + } + + /** + * Add an identifier, and a generated Color to the KeyedColorMap. + * The Color will be generated randomly. + * @ param identifier Specifies the key for which a color will be generated. + */ + public void addKey( String identifier ) { + if (!colorMap.containsKey(identifier)) { + colorMap.put(identifier, generateColor()); + } + } + + /** + * Given an identifier, return its color. + * @param identifier the key. + * @return the Color associated with the key. + */ + public Color getColor(String identifier) { + return colorMap.get(identifier); + } + + /** + * Given a color, return the associated key, otherwise return null. + * @param searchColor the Color to search for. + * @return the identifier associated with the searchColor. + */ + public String getKeyOfColor(Color searchColor) { + for (Map.Entry entry : colorMap.entrySet()) { + String id = entry.getKey(); + Color color = entry.getValue(); + if (color.getRGB() == searchColor.getRGB()) { + return id; + } + } + return null; + } + + /** + * Write the identifier, color key/value pairs of the KeyedColorMap to a file. + * @param fileName + */ + public void writeFile(String fileName) throws IOException { + BufferedWriter out = new BufferedWriter( new FileWriter(fileName) ); + for (Map.Entry entry : colorMap.entrySet()) { + String id = entry.getKey(); + Color color = entry.getValue(); + String line = String.format(id + "," + color.getRed() + + "," + color.getGreen() + + "," + color.getBlue() + "\n"); + out.write(line, 0, line.length()); + } + out.flush(); + out.close(); + } // method writeFile + + /** + * Read identifier, color key-value pairs into the KeyedColorMap from a file. + * @param fileName + */ + public void readFile(String fileName) throws IOException { + try { + BufferedReader in = new BufferedReader( new FileReader(fileName) ); + String line; + String field[]; + + while( (line = in.readLine()) !=null) { + field = line.split(","); + String id = field[0]; + int R = Integer.parseInt( field[1]); + int G = Integer.parseInt( field[2]); + int B = Integer.parseInt( field[3]); + colorMap.put(id, new Color(R,G,B)); + } + in.close(); + } catch ( java.io.FileNotFoundException e ) { + System.out.println("File \"" + fileName + "\" not found.\n"); + } + } // method readFile + +} // class KeyedColorMap diff --git a/trick_source/java/src/main/java/trick/jobperf/RunRegistry.java b/trick_source/java/src/main/java/trick/jobperf/RunRegistry.java new file mode 100644 index 000000000..94d3407b7 --- /dev/null +++ b/trick_source/java/src/main/java/trick/jobperf/RunRegistry.java @@ -0,0 +1,73 @@ +package trick.jobperf; + +import java.lang.Math; +import java.util.*; + +/** +* Class RunRegistry represents a list of timeSpan's on which we can calculate +* the average (mean), standard deviation, minimum, and maximum of the timeSpans +* in the list. +*/ +public class RunRegistry { + ArrayList timeSpanList; + /* + * Constructor + */ + public RunRegistry() { + timeSpanList = new ArrayList(); + } + void addTimeSpan(double start, double stop) { + TimeSpan timeSpan = new TimeSpan(start, stop); + timeSpanList.add(timeSpan); + } + void addTimeSpan(TimeSpan timeSpan) { + timeSpanList.add(timeSpan); + } + double getMeanDuration() { + double mean = 0.0; + int N = timeSpanList.size(); + if (N > 0) { + double sum = 0.0; + for (TimeSpan timeSpan : timeSpanList ) { + sum += timeSpan.getDuration(); + } + mean = sum / N; + } + return mean; + } + double getStdDev() { + double stddev = 0.0; + int N = timeSpanList.size(); + if (N > 0) { + double sum = 0.0; + double mean = getMeanDuration(); + for (TimeSpan timeSpan : timeSpanList ) { + double duration = timeSpan.getDuration(); + double difference = duration - mean; + sum += difference * difference; + } + stddev = Math.sqrt( sum / N ); + } + return stddev; + } + double getMaxDuration() { + double maxDuration = Double.MIN_VALUE; + for (TimeSpan timeSpan : timeSpanList ) { + double duration = timeSpan.getDuration(); + if (duration > maxDuration) { + maxDuration = duration; + } + } + return maxDuration; + } + double getMinDuration() { + double minDuration = Double.MAX_VALUE; + for (TimeSpan timeSpan : timeSpanList ) { + double duration = timeSpan.getDuration(); + if (duration < minDuration) { + minDuration = duration; + } + } + return minDuration; + } +} diff --git a/trick_source/java/src/main/java/trick/jobperf/StatisticsRecord.java b/trick_source/java/src/main/java/trick/jobperf/StatisticsRecord.java new file mode 100644 index 000000000..53159543e --- /dev/null +++ b/trick_source/java/src/main/java/trick/jobperf/StatisticsRecord.java @@ -0,0 +1,28 @@ +package trick.jobperf; + +/** +* Class StatisticsRecord represents the statistics, i.e., mean, std deviation, +* max value, and min value of the run-duration of an identified Trick job. +*/ +public class StatisticsRecord { + public String id; + public double meanValue; + public double stddev; + public double maxValue; + public double minValue; + /** + * Constructor + * @param s - the job identifier. + * @param mean - the mean value of job duration. + * @param sd - the standard deviation of job duration. + * @param min - the minimum value of job duration. + * @param max - the maximum value of job duration. + */ + public StatisticsRecord( String s, double mean, double sd, double min, double max) { + id = s; + meanValue = mean; + stddev = sd; + minValue = min; + maxValue = max; + } +} diff --git a/trick_source/java/src/main/java/trick/jobperf/TimeSpan.java b/trick_source/java/src/main/java/trick/jobperf/TimeSpan.java new file mode 100644 index 000000000..f441fd4c6 --- /dev/null +++ b/trick_source/java/src/main/java/trick/jobperf/TimeSpan.java @@ -0,0 +1,24 @@ +package trick.jobperf; + +/** +* Class TimeSpan represents a span of time. +*/ +public class TimeSpan { + public double start; + public double stop; + /** + * Constructor + * @param begin the start time. + * @param end the end time. + */ + public TimeSpan( double begin, double end) { + start = begin; + stop = end; + } + /** + * @return the stop time minus the start time. + */ + public double getDuration() { + return stop - start; + } +} diff --git a/trick_source/java/src/main/java/trick/jobperf/TraceViewCanvas.java b/trick_source/java/src/main/java/trick/jobperf/TraceViewCanvas.java new file mode 100644 index 000000000..485b0f25d --- /dev/null +++ b/trick_source/java/src/main/java/trick/jobperf/TraceViewCanvas.java @@ -0,0 +1,366 @@ +package trick.jobperf; + +import java.awt.*; +import java.awt.event.*; +import java.awt.image.BufferedImage; +import java.io.*; +import java.util.*; +import java.util.List; +import javax.swing.*; +import javax.swing.event.*; + +/** +* Class TraceViewCanvas renders the simulation timeline data stored in +* an ArrayList of JobExecutionEvent's [jobExecEvtList]. Information regarding +* mouse clicks are sent to the TraceViewOutputToolBar [outputToolBar.] +* @author John M. Penn +*/ +public class TraceViewCanvas extends JPanel { + + public static final int MIN_TRACE_WIDTH = 4; + public static final int DEFAULT_TRACE_WIDTH = 15; + public static final int MAX_TRACE_WIDTH = 30; + public static final int LEFT_MARGIN = 100; + public static final int RIGHT_MARGIN = 100; + public static final int TOP_MARGIN = 20; + public static final int BOTTOM_MARGIN = 20; + public static final int DEFAULT_FRAMES_TO_RENDER = 100; + + private int traceWidth; + private double frameSize; + private double totalDuration; + private FrameRecord[] frameArray; + private int selectedFrameNumber; + private FrameRange frameRenderRange; + private KeyedColorMap idToColorMap; + private BufferedImage image; + private TraceViewOutputToolBar sToolBar; + private Cursor crossHairCursor; + private Cursor defaultCursor; + + public class FrameRange { + public int first; + public int last; + FrameRange (int first, int last) { + this.first = first; + this.last = last; + } + public boolean contains(int n) { + return ((first <= n) && (n <= last)); + } + public int size() { + return last - first + 1; + } + } + + /** + * Constructor + * @param jobExecEvtList the job time line data. + * @param outputToolBar the toolbar to which data is to be sent for display. + */ + public TraceViewCanvas( ArrayList jobExecEvtList, + TraceViewOutputToolBar outputToolBar ) { + + traceWidth = DEFAULT_TRACE_WIDTH; + frameSize = 1.0; + image = null; + selectedFrameNumber = 0; + sToolBar = outputToolBar; + crossHairCursor = new Cursor( Cursor.CROSSHAIR_CURSOR ); + defaultCursor = new Cursor( Cursor.DEFAULT_CURSOR ); + + try { + idToColorMap = new KeyedColorMap(); + File colorfile = new File("IdToColors.txt"); + if (colorfile.exists()) { + idToColorMap.readFile("IdToColors.txt"); + } + boolean wasTOF = false; + + List frameList = new ArrayList(); + FrameRecord frameRecord = new FrameRecord(); + for (JobExecutionEvent jobExec : jobExecEvtList ) { + + if (!wasTOF && jobExec.isTOF) { + // Wrap up the previous frame record. + frameRecord.stop = jobExec.start; + frameList.add(frameRecord); + + // Start a new frame record. + frameRecord = new FrameRecord(); + frameRecord.start = jobExec.start; + } + frameRecord.jobEvents.add(jobExec); + + wasTOF = jobExec.isTOF; + idToColorMap.addKey(jobExec.id); + } + frameArray = frameList.toArray( new FrameRecord[ frameList.size() ]); + + // Estimate the total duration and the average frame size. Notice + // that we skip the first frame. + totalDuration = 0.0; + for (int n=1; n < frameArray.length; n++) { + totalDuration += frameArray[n].getDuration(); + } + frameSize = totalDuration/(frameArray.length-1); + + // Set the range of frames to be rendered. + int last_frame_to_render = frameArray.length-1; + if ( frameArray.length > DEFAULT_FRAMES_TO_RENDER) { + last_frame_to_render = DEFAULT_FRAMES_TO_RENDER-1; + } + frameRenderRange = new FrameRange(0, last_frame_to_render); + + // Write the color file. + idToColorMap.writeFile("IdToColors.txt"); + + System.out.println("File loaded.\n"); + } catch ( java.io.FileNotFoundException e ) { + System.out.println("File not found.\n"); + System.exit(0); + } catch ( java.io.IOException e ) { + System.out.println("IO Exception.\n"); + System.exit(0); + } + + setPreferredSize(new Dimension(500, neededPanelHeight())); + + ViewListener viewListener = new ViewListener(); + addMouseListener(viewListener); + addMouseMotionListener(viewListener); + } + + public int getFrameTotal() { + return frameArray.length; + } + + public int getFirstRenderFrame() { + return frameRenderRange.first; + } + + public void setFirstRenderFrame(int first) throws InvalidFrameBoundsExpection { + if ((first >= 0) && (first <= frameRenderRange.last)) { + frameRenderRange = new FrameRange(first, frameRenderRange.last); + setPreferredSize(new Dimension(500, neededPanelHeight())); + repaint(); + } else { + throw new InvalidFrameBoundsExpection(""); + } + } + + public int getLastRenderFrame() { + return frameRenderRange.last; + } + + public void setLastRenderFrame(int last) throws InvalidFrameBoundsExpection { + if ((last >= frameRenderRange.first) && (last < frameArray.length)) { + frameRenderRange = new FrameRange(frameRenderRange.first, last); + // Re-render this TraceViewCanvas. + setPreferredSize(new Dimension(500, neededPanelHeight())); + repaint(); + } else { + throw new InvalidFrameBoundsExpection(""); + } + } + + /** + * @return the current working frame size (seconds) used for rendering. + * Initially this is estimated from the timeline data, but it can be set to + * the actual realtime frame size of the user's sim. + */ + public double getFrameSize() { + return frameSize; + } + /** + * Set the frame size (seconds) to be used for rendering the timeline data. + * @param duration the frame size. + */ + public void setFrameSize(double time) { + frameSize = time; + repaint(); + } + + /** + * Increment the width to be used to render the job traces if the current + * trace width is less than MAX_TRACE_WIDTH. + */ + public void incrementTraceWidth() { + if (traceWidth < MAX_TRACE_WIDTH) { + traceWidth ++; + repaint(); + } + } + + /** + * Decrement the width to be used to render the job traces if the current + * trace width is greater than MIN_TRACE_WIDTH. + */ + public void decrementTraceWidth() { + if (traceWidth > MIN_TRACE_WIDTH) { + traceWidth --; + repaint(); + } + } + + /** + * @return true if the trace rectangle contains the point , otherwise + * false. + */ + private boolean traceRectContains(int x, int y) { + int traceRectXMax = getWidth() - RIGHT_MARGIN; + if ( x < (LEFT_MARGIN)) return false; + if ( x > (traceRectXMax)) return false; + if (( y < TOP_MARGIN) || (y > (TOP_MARGIN + traceRectHeight()))) return false; + return true; + } + + /** + * Class ViewListener monitors mouse activity within the TraceViewCanvas. + */ + private class ViewListener extends MouseInputAdapter { + + @Override + public void mouseReleased(MouseEvent e) { + int x = e.getX(); + int y = e.getY(); + Color color = new Color ( image.getRGB(x,y) ); + + // Get and display the ID of the job associated with the color. + String id = idToColorMap.getKeyOfColor( color ); + sToolBar.setJobID(id); + + // Determine the frame number that we clicked on from the y- + // coordinate of the click position. + + if ( y > TOP_MARGIN) { + selectedFrameNumber = (y - TOP_MARGIN) / traceWidth + frameRenderRange.first; + sToolBar.setFrameNumber(selectedFrameNumber); + } + + // Determine the subframe-time where we clicked from the x-coordinate + // of the click position. + if ( traceRectContains(x, y)) { + double pixelsPerSecond = (double)traceRectWidth() / frameSize; + double subFrameTime = (x - LEFT_MARGIN) / pixelsPerSecond; + sToolBar.setSubFrameTime(subFrameTime); + } + + /** + * If we clicked on a job trace (above), show the start and stop + * times of the job, otherwise clear the start and stop fields. + */ + if (id != null) { + FrameRecord frame = frameArray[selectedFrameNumber]; + for (JobExecutionEvent jobExec : frame.jobEvents) { + if (id.equals( jobExec.id)) { + sToolBar.setJobStartTime(jobExec.start); + sToolBar.setJobStopTime(jobExec.stop); + } + } + repaint(); + } else { + sToolBar.clearJobStartStopTime(); + } + } + + /** + * Set the cursor to a crossHairCursor if it's over the frame traces, + * otherwise, set it to the defaultCursor. + */ + @Override + public void mouseMoved(MouseEvent e) { + int x = e.getX(); + int y = e.getY(); + if ( traceRectContains(x, y)) { + setCursor(crossHairCursor); + } else { + setCursor(defaultCursor); + } + } + } + + /** + * @return the height of the trace rectangle. + */ + private int traceRectHeight() { + return traceWidth * frameRenderRange.size(); + } + + /** + * @return the width of the trace rectangle. + */ + private int traceRectWidth() { + return ( getWidth() - LEFT_MARGIN - RIGHT_MARGIN); + } + + /** + * Calculate the height of the TraceViewCanvas (JPanel) needed to render the + * selected range of frames. + */ + private int neededPanelHeight() { + return traceWidth * frameRenderRange.size() + TOP_MARGIN + BOTTOM_MARGIN; + } + + /** + * Render the job execution traces in the jobExecEvtList. + */ + private void doDrawing(Graphics g) { + Graphics2D g2d = (Graphics2D) g; + + RenderingHints rh = new RenderingHints( + RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + + rh.put(RenderingHints.KEY_RENDERING, + RenderingHints.VALUE_RENDER_QUALITY); + + int traceRectHeight = traceRectHeight(); + int traceRectWidth = traceRectWidth(); + double pixelsPerSecond = (double)traceRectWidth / frameSize; + + // Panel Background Color Fill + g2d.setPaint(Color.WHITE); + g2d.fillRect(0, 0, getWidth(), getHeight()); + + // Frame Trace Rectangle Fill + g2d.setPaint(Color.BLACK); + g2d.fillRect(LEFT_MARGIN, TOP_MARGIN, traceRectWidth, traceRectHeight()); + + // Draw each frame in the selected range of frames to be rendered. + for (int n = frameRenderRange.first; + n <= frameRenderRange.last; + n++) { + + FrameRecord frame = frameArray[n]; + + // Draw the frame + for (JobExecutionEvent jobExec : frame.jobEvents) { + int jobY = TOP_MARGIN + (n - frameRenderRange.first) * traceWidth; + int jobStartX = LEFT_MARGIN + (int)((jobExec.start - frame.start) * pixelsPerSecond); + int jobWidth = (int)( (jobExec.stop - jobExec.start) * pixelsPerSecond); + + g2d.setPaint(Color.BLACK); + if (n == selectedFrameNumber) { + g2d.setPaint(Color.GREEN); + } + g2d.drawString ( String.format("%d", n), 50, jobY + traceWidth/2); + g2d.setPaint( idToColorMap.getColor( jobExec.id ) ); + g2d.fillRect(jobStartX, jobY, jobWidth, traceWidth-2); + } + } + } + + /** + * This function paints the TraceViewCanvas (i.e, JPanel) when required. + */ + @Override + public void paintComponent(Graphics g) { + super.paintComponent(g); + image = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB); + Graphics2D g2 = image.createGraphics(); + doDrawing(g2); + g.drawImage(image, 0, 0, this); + g2.dispose(); + } +} diff --git a/trick_source/java/src/main/java/trick/jobperf/TraceViewInputToolBar.java b/trick_source/java/src/main/java/trick/jobperf/TraceViewInputToolBar.java new file mode 100644 index 000000000..c441b1f74 --- /dev/null +++ b/trick_source/java/src/main/java/trick/jobperf/TraceViewInputToolBar.java @@ -0,0 +1,118 @@ +package trick.jobperf; + +import java.awt.*; +import java.awt.event.*; +import java.io.*; +import javax.swing.*; +import java.net.URL; + +/** +* Class TraceViewInputToolBar initially displays an estimate of the frame size +* for the JobPerf input timeline data. A user may also enter the intended frame +* size into the JTextField, and pressing the "Set" button, which calls +* traceView.setFrameSize( newFrameSize ); +* +* Class TraceViewInputToolBar aggregates the following GUI components: +* TraceViewInputToolBar (isa JToolBar) +* JLabel () +* JTextField [frameSizeField] +* JLabel +* JLabel +* JTextField [firstRenderFrameField] +* JLabel +* JTextField [lastRenderFrameField] +*/ +public class TraceViewInputToolBar extends JToolBar { + + private TraceViewCanvas traceView; + private JTextField frameSizeField; + private JTextField firstRenderFrameField; + private JTextField lastRenderFrameField; + /** + * Constructor + * @param tvc TraceViewCanvas to be controlled. + */ + public TraceViewInputToolBar (TraceViewCanvas tvc) { + traceView = tvc; + + add( new JLabel(" Frame Size: ")); + frameSizeField = new JTextField(15); + frameSizeField.setText( String.format("%8.4f", traceView.getFrameSize()) ); + add(frameSizeField); + frameSizeField.addKeyListener( new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_ENTER) { + setFrameSize(); + } + } + }); + + add( new JLabel( String.format(" Total Frame Range: %d ... %d", 0, traceView.getFrameTotal()-1 ))); + + add( new JLabel(" Selected Frame Range: ")); + + firstRenderFrameField = new JTextField(15); + firstRenderFrameField.setText( String.format("%d", traceView.getFirstRenderFrame()) ); + add(firstRenderFrameField); + firstRenderFrameField.addKeyListener( new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_ENTER) { + setFirstRenderFrame(); + } + } + }); + + add( new JLabel("...")); + lastRenderFrameField = new JTextField(15); + lastRenderFrameField.setText( String.format("%d", traceView.getLastRenderFrame()) ); + add(lastRenderFrameField); + lastRenderFrameField.addKeyListener( new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_ENTER) { + setLastRenderFrame(); + } + } + }); + + // Add Trick LOGO here. + } + + private void setFirstRenderFrame() { + int newStartFrame = 0; + try { + newStartFrame = Integer.parseInt( firstRenderFrameField.getText() ); + traceView.setFirstRenderFrame( newStartFrame ); + } catch ( NumberFormatException e) { + firstRenderFrameField.setText( String.format("%d", traceView.getFirstRenderFrame())); + } catch ( InvalidFrameBoundsExpection e) { + firstRenderFrameField.setText( String.format("%d", traceView.getFirstRenderFrame())); + } + } + + private void setLastRenderFrame() { + int newFinalFrame = 0; + try { + newFinalFrame = Integer.parseInt( lastRenderFrameField.getText() ); + traceView.setLastRenderFrame( newFinalFrame ); + } catch ( NumberFormatException e) { + lastRenderFrameField.setText( String.format("%d", traceView.getLastRenderFrame())); + } catch (InvalidFrameBoundsExpection e) { + lastRenderFrameField.setText( String.format("%d", traceView.getLastRenderFrame())); + } + } + + private void setFrameSize() { + double newFrameSize = 0.0; + try { + newFrameSize = Double.parseDouble( frameSizeField.getText() ); + } catch ( NumberFormatException e) { + frameSizeField.setText( String.format("%8.4f", traceView.getFrameSize()) ); + } + if ( newFrameSize > 0.0) { + traceView.setFrameSize( newFrameSize ); + } + } +} diff --git a/trick_source/java/src/main/java/trick/jobperf/TraceViewMenuBar.java b/trick_source/java/src/main/java/trick/jobperf/TraceViewMenuBar.java new file mode 100644 index 000000000..cae9d40be --- /dev/null +++ b/trick_source/java/src/main/java/trick/jobperf/TraceViewMenuBar.java @@ -0,0 +1,72 @@ +package trick.jobperf; + +import java.awt.*; +import java.awt.event.*; +import java.io.*; +import javax.swing.*; + +/** +* Class TraceViewMenuBar represents the menu bar of the JobPerf application. +* It aggregates the following GUI components: +* JMenuBar [this] +* JMenu [fileMenu] +* JMenuItem [fileMenuExit], Action: Call System.exit(0); +* JMenu [optionsMenu] +* JMenu [traceSizeMenu] +* JMenuItem [traceSizeMenuIncrease], Action: Call traceView.incrementTraceWidth(). +* JMenuItem [traceSizeMenuDecrease], Action: Call traceView.decrementTraceWidth() +*/ +public class TraceViewMenuBar extends JMenuBar implements ActionListener { + + private TraceViewCanvas traceView; + + /** + * Constructor + * @param tvc the TraceViewCanvas to be controlled by this menu bar. + */ + public TraceViewMenuBar(TraceViewCanvas tvc) { + traceView = tvc; + + JMenu fileMenu = new JMenu("File"); + JMenuItem fileMenuExit = new JMenuItem("Exit"); + fileMenuExit.setActionCommand("exit"); + fileMenuExit.addActionListener(this); + fileMenu.add(fileMenuExit); + add(fileMenu); + + JMenu optionsMenu = new JMenu("Options"); + JMenu traceSizeMenu = new JMenu("TraceSize"); + JMenuItem traceSizeMenuIncrease = new JMenuItem("Increase Trace Width"); + traceSizeMenuIncrease.setActionCommand("increase-trace_width"); + KeyStroke ctrlPlus = KeyStroke.getKeyStroke('=', InputEvent.CTRL_MASK ); + traceSizeMenuIncrease.setAccelerator(ctrlPlus); + traceSizeMenuIncrease.addActionListener(this); + traceSizeMenu.add(traceSizeMenuIncrease); + JMenuItem traceSizeMenuDecrease = new JMenuItem("Decrease Trace Width"); + traceSizeMenuDecrease.setActionCommand("decrease-trace_width"); + KeyStroke ctrlMinus = KeyStroke.getKeyStroke('-', InputEvent.CTRL_MASK); + traceSizeMenuDecrease.setAccelerator(ctrlMinus); + traceSizeMenuDecrease.addActionListener(this); + traceSizeMenu.add(traceSizeMenuDecrease); + optionsMenu.add(traceSizeMenu); + add(optionsMenu); + + } + @Override + public void actionPerformed(ActionEvent e) { + String s = e.getActionCommand(); + switch (s) { + case "increase-trace_width": + traceView.incrementTraceWidth(); + break; + case "decrease-trace_width": + traceView.decrementTraceWidth(); + break; + case "exit": + System.exit(0); + default: + System.out.println("Unknown Action Command:" + s); + break; + } + } +} diff --git a/trick_source/java/src/main/java/trick/jobperf/TraceViewOutputToolBar.java b/trick_source/java/src/main/java/trick/jobperf/TraceViewOutputToolBar.java new file mode 100644 index 000000000..d0ea6a380 --- /dev/null +++ b/trick_source/java/src/main/java/trick/jobperf/TraceViewOutputToolBar.java @@ -0,0 +1,90 @@ +package trick.jobperf; + +import javax.swing.*; + +/** +* Class TraceViewOutputToolBar displays information output from a +* TraceViewCanvas. Specifically, this displays the Job ID, frame number, and +* subFrame Time associated with a mouse click position on the TraceViewCanvas. +*/ +class TraceViewOutputToolBar extends JToolBar { + private JTextField IDField; + private JTextField startField; + private JTextField stopField; + private JTextField frameNumberField; + private JTextField subFrameTimeField; + + /** + * Constructor + */ + public TraceViewOutputToolBar () { + + add( new JLabel(" Job ID: ")); + IDField = new JTextField(15); + IDField.setEditable(false); + IDField.setText( ""); + add(IDField); + + add( new JLabel(" Job Start: ")); + startField = new JTextField(15); + startField.setEditable(false); + startField.setText( ""); + add(startField); + + add( new JLabel(" Job Stop: ")); + stopField = new JTextField(15); + stopField.setEditable(false); + stopField.setText( ""); + add(stopField); + + add( new JLabel(" Frame Number: ")); + frameNumberField = new JTextField(15); + frameNumberField.setEditable(false); + frameNumberField.setText( "0"); + add(frameNumberField); + + add( new JLabel(" Subframe Time: ")); + subFrameTimeField = new JTextField(15); + subFrameTimeField.setEditable(false); + subFrameTimeField.setText( "0.00"); + add(subFrameTimeField); + } + /** + * @param id job identifier to display. + */ + public void setJobID(String id) { + IDField.setText( id ); + } + /** + * @param time to be displayed in the job start field. + */ + public void setJobStartTime(Double time) { + startField.setText( String.format("%8.4f", time) ); + } + /** + * @param time to be displayed in the job stop field. + */ + public void setJobStopTime(Double time) { + stopField.setText( String.format("%8.4f", time) ); + } + /** + * Clear the startField and stopField. + */ + public void clearJobStartStopTime() { + startField.setText(""); + stopField.setText(""); + IDField.setText(""); + } + /** + * @param fn frame number to display. + */ + public void setFrameNumber(int fn) { + frameNumberField.setText( String.format("%d", fn)); + } + /** + * @param time subframe time to display. + */ + public void setSubFrameTime(double time) { + subFrameTimeField.setText( String.format("%8.4f", time)); + } +} diff --git a/trick_source/java/src/main/java/trick/jobperf/TraceViewWindow.java b/trick_source/java/src/main/java/trick/jobperf/TraceViewWindow.java new file mode 100644 index 000000000..b9ba346ee --- /dev/null +++ b/trick_source/java/src/main/java/trick/jobperf/TraceViewWindow.java @@ -0,0 +1,61 @@ +package trick.jobperf; + +import java.awt.*; +import java.util.*; +import javax.swing.*; + +/** +* Class TraceViewWindow represents the main window of the JobPerf application. +* It aggregates the following GUI components: +* +* - TraceViewMenuBar [menuBar] +* - TraceViewInputToolBar [toolbar] +* - JPanel [mainPanel] +* - JPanel [tracePanel] +* - JScrollPane [scrollPane] +* - TraceViewCanvas [traceViewCanvas] +* - TraceViewOutputToolBar [outputToolBar] +*/ +public class TraceViewWindow extends JFrame { + + /** + * Constructor + * @param jobExecList an ArrayList of JobExecutionEvent, i.e., the job timeline data. + */ + public TraceViewWindow( ArrayList jobExecList ) { + TraceViewOutputToolBar outputToolBar = new TraceViewOutputToolBar(); + TraceViewCanvas traceViewCanvas = new TraceViewCanvas( jobExecList, outputToolBar); + + TraceViewMenuBar menuBar = new TraceViewMenuBar( traceViewCanvas); + setJMenuBar(menuBar); + + TraceViewInputToolBar nToolBar = new TraceViewInputToolBar( traceViewCanvas ); + add(nToolBar, BorderLayout.NORTH); + + JScrollPane scrollPane = new JScrollPane( traceViewCanvas ); + scrollPane.setPreferredSize(new Dimension(800, 400)); + scrollPane.getVerticalScrollBar().setUnitIncrement( 20 ); + + JPanel tracePanel = new JPanel(); + tracePanel.setPreferredSize(new Dimension(800, 400)); + tracePanel.add(scrollPane); + tracePanel.setLayout(new BoxLayout(tracePanel, BoxLayout.X_AXIS)); + + JPanel mainPanel = new JPanel(); + mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS)); + mainPanel.add(tracePanel); + + add(outputToolBar, BorderLayout.SOUTH); + + setTitle("JobPerf"); + setSize(800, 500); + add(mainPanel); + pack(); + setVisible(true); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setFocusable(true); + setVisible(true); + + traceViewCanvas.repaint(); + } +} diff --git a/trick_source/sim_services/FrameLog/FrameLog.cpp b/trick_source/sim_services/FrameLog/FrameLog.cpp index 2eb4516e3..f69807293 100644 --- a/trick_source/sim_services/FrameLog/FrameLog.cpp +++ b/trick_source/sim_services/FrameLog/FrameLog.cpp @@ -384,6 +384,7 @@ int Trick::FrameLog::frame_clock_stop(Trick::JobData * curr_job) { mode = Run; } } + /** @li Save all cyclic job start & stop times for this frame into timeline structure. */ if ((mode==Run) || (mode==Step)) { // cyclic job if (tl_count[thread] < tl_max_samples) { @@ -391,6 +392,8 @@ int Trick::FrameLog::frame_clock_stop(Trick::JobData * curr_job) { timeline[thread][tl_count[thread]].start = target_job->rt_start_time; timeline[thread][tl_count[thread]].stop = target_job->rt_stop_time; timeline[thread][tl_count[thread]].trick_job = target_job->tags.count("TRK"); + timeline[thread][tl_count[thread]].isEndOfFrame = target_job->isEndOfFrame; + timeline[thread][tl_count[thread]].isTopOfFrame = target_job->isTopOfFrame; tl_count[thread]++; } /** @li Save all non-cyclic job start & stop times for this frame into timeline_other structure. */ @@ -582,8 +585,44 @@ int Trick::FrameLog::shutdown() { return(0) ; } + +// ================================================================ +// NEW Time-line for Jperf +// ================================================================ + for (int thread_num = 0; thread_num < num_threads; thread_num ++) { + + if (thread_num == 0) { + snprintf(log_buff, sizeof(log_buff), "%s/log_newtimeline.csv", command_line_args_get_output_dir()); + } else { + snprintf(log_buff, sizeof(log_buff), "%s/log_newtimelineC%d.csv", command_line_args_get_output_dir(), thread_num); + } + + FILE *fp_log; + if ((fp_log = fopen(log_buff, "w")) == NULL) { + message_publish(MSG_ERROR, "Could not open log_timeline.csv file for Job Timeline Logging\n") ; + exit(0); + } + + fprintf(fp_log,"jobID,isTopOfFrame,isEndOfFrame,startTime,stopTime\n"); + + time_scale = 1.0 / exec_get_time_tic_value(); + tl = timeline[thread_num]; + for ( ii = 0 ; ii < tl_count[thread_num] ; ii++ ) { + start = tl[ii].start * time_scale; + stop = tl[ii].stop * time_scale; + int isTrickJob = (tl[ii].trick_job) ? 1 : 0; + int isEndOfFrame = (tl[ii].isEndOfFrame) ? 1 : 0; + int isTopOfFrame = (tl[ii].isTopOfFrame) ? 1 : 0; + fprintf(fp_log,"%f,%d,%d,%f,%f\n", tl[ii].id, isTopOfFrame, isEndOfFrame, start, stop); + } + fflush(fp_log); + fclose(fp_log); + } + /** @li Manually create the log_timeline and log_timeline_init files from saved timeline data. */ if (fp_time_main == NULL) { + + snprintf(log_buff, sizeof(log_buff), "%s/log_timeline.csv", command_line_args_get_output_dir()); if ((fp_time_main = fopen(log_buff, "w")) == NULL) { message_publish(MSG_ERROR, "Could not open log_timeline.csv file for Job Timeline Logging\n") ; @@ -591,11 +630,14 @@ int Trick::FrameLog::shutdown() { } fprintf(fp_time_main, "trick_frame_log.frame_log.job_time {s},"); fprintf(fp_time_main, "trick_frame_log.frame_log.job_trick_id {--},frame_log.frame_log.job_user_id {--}"); + for (jj=1; jj