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..fd92e4f4a
--- /dev/null
+++ b/trick_source/java/src/main/java/trick/jobperf/FrameRecord.java
@@ -0,0 +1,76 @@
+package trick.jobperf;
+import java.util.*;
+
+/**
+* Class CompareByDuration compares two JobExecutionEvent's by their duration.
+*/
+class CompareByDuration implements Comparator {
+ public int compare(JobExecutionEvent a, JobExecutionEvent b) {
+ Double dur_a = a.stop - a.start;
+ Double dur_b = b.stop - b.start;
+ if ( dur_a > dur_b) return -1;
+ if ( dur_a < dur_b) return 1;
+ return 0;
+ }
+}
+
+/**
+* Class CompareByDuration compares two JobExecutionEvent's by their start time.
+*/
+class CompareByStartTime implements Comparator {
+ public int compare(JobExecutionEvent a, JobExecutionEvent b) {
+ if ( a.start < b.start) return -1;
+ if ( a.start > a.start) return 1;
+ return 0;
+ }
+}
+
+/**
+* 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;
+ }
+
+ public void SortByJobEventDuration() {
+ Collections.sort( jobEvents, new CompareByDuration());
+ }
+
+ public void SortByStartTime() {
+ Collections.sort( jobEvents, new CompareByStartTime());
+ }
+
+ /**
+ * For each jobEvent in the frame, record the number of times
+ * its start time is contained within
+ * another jobs stop/stop range.
+ */
+ public void CalculateJobContainment() {
+ SortByJobEventDuration();
+ int N = jobEvents.size();
+ for (int i = 0 ; i < (N-1); i++) {
+ for (int j = i+1 ; j < N; j++) {
+ if ( jobEvents.get(i).contains( jobEvents.get(j) )) {
+ jobEvents.get(j).contained ++ ;
+ }
+ }
+ }
+ }
+}
diff --git a/trick_source/java/src/main/java/trick/jobperf/FrameViewCanvas.java b/trick_source/java/src/main/java/trick/jobperf/FrameViewCanvas.java
new file mode 100644
index 000000000..c8a7be9c1
--- /dev/null
+++ b/trick_source/java/src/main/java/trick/jobperf/FrameViewCanvas.java
@@ -0,0 +1,99 @@
+package trick.jobperf;
+
+import java.awt.*;
+import java.io.*;
+import java.util.*;
+import javax.swing.*;
+
+public class FrameViewCanvas extends JPanel {
+ private FrameRecord frame;
+ private TraceViewCanvas tvc;
+ private Font headingsFont;
+ private Font dataFont;
+
+ public FrameViewCanvas( TraceViewCanvas tvc, FrameRecord frame ) {
+ this.tvc = tvc;
+ this.frame = frame;
+ dataFont = new Font("Arial", Font.PLAIN, 18);
+ headingsFont = new Font("Arial", Font.BOLD, 18);
+
+ setPreferredSize(new Dimension(800, neededPanelHeight()));
+ }
+
+ 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);
+
+ // Panel Background Color Fill
+ g2d.setPaint(Color.WHITE);
+ g2d.fillRect(0, 0, getWidth(), getHeight());
+
+ // TITLE
+ g2d.setFont(headingsFont);
+ g2d.setPaint( Color.RED );
+ g2d.drawString("Frame Details", 100, 50);
+
+ // Column Headings
+ g2d.setFont(headingsFont);
+ g2d.setPaint( Color.BLUE );
+ g2d.drawString("Job-ID", 100, 80);
+ g2d.drawString("Job-Class", 180, 80);
+ g2d.drawString("Start-Time", 420, 80);
+ g2d.drawString("Stop-Time", 520, 80);
+ g2d.drawString("Duration", 620, 80);
+ g2d.drawString("Job-Name", 740, 80);
+
+ frame.SortByStartTime();
+
+ // For each job in the frame.
+ int jobY = 100;
+ for (JobExecutionEvent jobExec : frame.jobEvents) {
+ g2d.setPaint( tvc.idToColorMap.getColor( jobExec.id ) );
+ g2d.fillRect(50, jobY, 20, 20);
+ g2d.setPaint( Color.BLACK );
+ jobY += 20;
+ double duration = jobExec.stop - jobExec.start;
+
+ g2d.setFont(dataFont);
+ g2d.drawString(jobExec.id, 100, jobY);
+ g2d.drawString( String.format("%12.6f", jobExec.start), 420, jobY);
+ g2d.drawString( String.format("%12.6f", jobExec.stop), 520, jobY);
+ g2d.drawString( String.format("%12.6f", duration), 620, jobY);
+
+ JobSpecification jobSpec = tvc.jobSpecificationMap.getJobSpecification(jobExec.id);
+ if ( jobSpec == null) {
+ g2d.setPaint( Color.RED );
+ g2d.drawString("UNKNOWN", 180, jobY);
+ g2d.drawString("UNKNOWN", 740, jobY);
+ } else {
+ g2d.drawString(jobSpec.jobClass, 180, jobY);
+ g2d.drawString(jobSpec.name, 740, jobY);
+ }
+ }
+ frame.SortByJobEventDuration();
+ }
+
+ /**
+ * Calculate the height of the FrameViewCanvas (JPanel) needed to render the
+ * jobs in the frame.
+ */
+ private int neededPanelHeight() {
+ return 20 * frame.jobEvents.size() + 100;
+ }
+
+ /**
+ * This function paints the FrameViewCanvas (i.e, JPanel) when required.
+ */
+ @Override
+ public void paintComponent(Graphics g) {
+ super.paintComponent(g);
+ doDrawing(g);
+ }
+
+}
diff --git a/trick_source/java/src/main/java/trick/jobperf/FrameViewWindow.java b/trick_source/java/src/main/java/trick/jobperf/FrameViewWindow.java
new file mode 100644
index 000000000..2e0f8b971
--- /dev/null
+++ b/trick_source/java/src/main/java/trick/jobperf/FrameViewWindow.java
@@ -0,0 +1,31 @@
+package trick.jobperf;
+
+import java.awt.*;
+import java.io.*;
+import java.util.*;
+import javax.swing.*;
+
+public class FrameViewWindow extends JFrame {
+ public FrameViewWindow( TraceViewCanvas tvc, FrameRecord frame, int frameNumber ) {
+
+ FrameViewCanvas frameViewCanvas = new FrameViewCanvas(tvc, frame);
+
+ JScrollPane scrollPane = new JScrollPane( frameViewCanvas );
+ scrollPane.getVerticalScrollBar().setUnitIncrement( 20 );
+
+ JPanel scrollingFrameViewCanvas = new JPanel();
+ scrollingFrameViewCanvas.add(scrollPane);
+ scrollingFrameViewCanvas.setLayout(new BoxLayout(scrollingFrameViewCanvas, BoxLayout.X_AXIS));
+
+ setTitle("Frame " + frameNumber);
+ setPreferredSize(new Dimension(1200, 400));
+ add(scrollingFrameViewCanvas);
+ pack();
+ setVisible(true);
+ setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
+ setFocusable(true);
+ setVisible(true);
+
+ frameViewCanvas.repaint();
+ }
+}
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..0f7ad614d
--- /dev/null
+++ b/trick_source/java/src/main/java/trick/jobperf/JobExecutionEvent.java
@@ -0,0 +1,64 @@
+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.
+* indicates whether the job was run as
+* an "top-of-frame" job.
+*/
+class JobExecutionEvent {
+ public String id;
+ public boolean isTOF;
+ public boolean isEOF;
+ public double start;
+ public double stop;
+ public int contained;
+
+ /**
+ * @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 id, boolean isTOF, boolean isEOF, double start, double stop) {
+ this.id = id;
+ this.isTOF = isTOF;
+ this.isEOF = isEOF;
+ this.start = start;
+ this.stop = stop;
+ contained = 1;
+ }
+
+ /**
+ * Determine whether a job's start time is contained
+ * within another jobs stop/stop range.
+ */
+ public boolean contains( JobExecutionEvent other ) {
+ if ((other.start > this.start) &&
+ (other.start < this.stop)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * 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..c933cf09d
--- /dev/null
+++ b/trick_source/java/src/main/java/trick/jobperf/JobPerf.java
@@ -0,0 +1,202 @@
+package trick.jobperf;
+
+import java.awt.*;
+import java.io.*;
+import java.util.*;
+import javax.swing.*;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+/**
+* 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 timeLineFileName = "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 : {
+ timeLineFileName = args[ii];
+ } break;
+ } //switch
+ ++ii;
+ } // while
+
+ // All files shall be in the same directory as the timeline file.
+ String filesDir = Paths.get(timeLineFileName).toAbsolutePath().getParent().toString();
+ System.out.println( "\n\nFilesDir = " + filesDir + "\n\n");
+
+ // Generate the JobSpecificationMap from information extracted from the S_job_execution
+ // file, that should be in the same directory as the time-line file.
+ File s_job_execution_file = new File( filesDir + "/S_job_execution" );
+ JobSpecificationMap jobSpecificationMap = null;
+ try {
+ jobSpecificationMap = new JobSpecificationMap( s_job_execution_file );
+ } catch ( java.io.FileNotFoundException e ) {
+ System.out.println("File \"" + s_job_execution_file.toString() + "\" not found.\n");
+ System.exit(0);
+ } catch ( java.io.IOException e ) {
+ System.out.println("IO Exception while attempting to read " + s_job_execution_file.toString() + ".\n");
+ System.exit(0);
+ }
+
+ // Read Color Map
+ KeyedColorMap idToColorMap = null;
+ File colorMapFile = null;
+ try {
+ colorMapFile = new File(filesDir + "/IdToColors.txt");
+ idToColorMap = new KeyedColorMap( colorMapFile.toString());
+ if ( colorMapFile.exists()) {
+ idToColorMap.readFile();
+ }
+ } catch ( java.io.IOException e ) {
+ System.out.println("IO Exception while attempting to read " + colorMapFile.toString() + ".\n");
+ System.exit(0);
+ }
+
+ jobExecEvtList = getJobExecutionEventList(timeLineFileName, jobSpecificationMap);
+
+ if (printReport) {
+ jobStats = new JobStats(jobExecEvtList);
+ 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( jobSpecificationMap);
+ }
+ if (interactive) {
+ traceViewWindow = new TraceViewWindow(jobExecEvtList, idToColorMap, jobSpecificationMap);
+ }
+ }
+
+ /**
+ * 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,
+ JobSpecificationMap jobSpecificationMap ) {
+ String line;
+ String field[];
+
+ ArrayList jobExecEvtList = new ArrayList();
+ try {
+ BufferedReader in = new BufferedReader( new FileReader(fileName) );
+
+ // Strip the header line off the CSV file.
+ line = in.readLine();
+
+ // Iterate through and process each of the data lines.
+ while( (line = in.readLine()) !=null) {
+ boolean isTOF = false;
+ boolean isEOF = false;
+ field = line.split(",");
+
+ String id = field[0].trim();
+ JobSpecification jobSpec = jobSpecificationMap.getJobSpecification(id);
+ if (jobSpec != null) {
+ if (jobSpec.jobClass.equals("top_of_frame")) {
+ isTOF = true;
+ } else if (jobSpec.jobClass.equals("end_of_frame")) {
+ isEOF = true;
+ }
+ }
+ double start = Double.parseDouble( field[1]);
+ double stop = Double.parseDouble( field[2]);
+ 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/JobSpecification.java b/trick_source/java/src/main/java/trick/jobperf/JobSpecification.java
new file mode 100644
index 000000000..0b8e465f1
--- /dev/null
+++ b/trick_source/java/src/main/java/trick/jobperf/JobSpecification.java
@@ -0,0 +1,33 @@
+package trick.jobperf;
+
+import java.awt.*;
+import java.io.*;
+import java.util.*;
+
+
+/**
+* Class JobSpecification represents ...
+*/
+class JobSpecification {
+ public String name;
+ public String jobClass;
+ public int phase;
+
+ /**
+ * @param name identifies the relevant Trick job.
+ * @param jobClass the Trick job class.
+ * @param phase the Trick phase number of the Trick job.
+ */
+ public JobSpecification(String name, String jobClass, int phase) {
+ this.name = name;
+ this.jobClass = jobClass;
+ this.phase = phase;
+ }
+ /**
+ * Create a String representation of an object of this jobClass.
+ */
+ @Override
+ public String toString() {
+ return ( "JobSpecification: " + name + "," + jobClass + "," + phase );
+ }
+}
diff --git a/trick_source/java/src/main/java/trick/jobperf/JobSpecificationMap.java b/trick_source/java/src/main/java/trick/jobperf/JobSpecificationMap.java
new file mode 100644
index 000000000..af6996bd8
--- /dev/null
+++ b/trick_source/java/src/main/java/trick/jobperf/JobSpecificationMap.java
@@ -0,0 +1,59 @@
+package trick.jobperf;
+
+import java.awt.*;
+import java.io.*;
+import java.util.*;
+import java.nio.file.*;
+
+/**
+* Class JobSpecificationMap associates identifiers with unique RGB colors.
+*/
+public class JobSpecificationMap {
+ private Map jobSpecMap;
+
+ /**
+ * Constructor
+ */
+ public JobSpecificationMap( File file ) throws IOException, FileNotFoundException {
+ jobSpecMap = new HashMap();
+ System.out.println( "INSTANCIATING JobSpecificationMap("+ file.toString() +").");
+ BufferedReader in = new BufferedReader( new FileReader( file.toString()) );
+ String line;
+ String field[];
+
+ while( (line = in.readLine()) != null) {
+ if ( line.matches("\\s+1 [|].*$") ) {
+ field = line.split("[|]");
+ if (field.length == 9) {
+ String jobclass = field[2].trim();
+ int phase = Integer.parseInt( field[3].trim());
+ String id = String.format("%.2f", Double.parseDouble( field[7].trim()));
+ String name = field[8].trim();
+ jobSpecMap.put(id, new JobSpecification(name, jobclass, phase));
+ //System.out.println("JobSpec = " + id + "," + jobclass + "," + name);
+ }
+ }
+ }
+ in.close();
+ }
+
+ /**
+ * Add an identifier, and a JobSpecification to the JobSpecificationMap.
+ * @ param identifier Specifies the key.
+ */
+ public void addKey( String identifier, JobSpecification jobSpec) {
+ if (!jobSpecMap.containsKey(identifier)) {
+ jobSpecMap.put(identifier, jobSpec);
+ }
+ }
+
+ /**
+ * Given an identifier, return the corresponding JobSpecification.
+ * @param identifier the key.
+ * @return the JobSpecification associated with the key.
+ */
+ public JobSpecification getJobSpecification(String identifier) {
+ return jobSpecMap.get(identifier);
+ }
+
+} // class JobSpecificationMap
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..4df1e3521
--- /dev/null
+++ b/trick_source/java/src/main/java/trick/jobperf/JobStats.java
@@ -0,0 +1,198 @@
+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"; }
+ }
+ }
+
+ public SortCriterion currentSortCriterion = SortCriterion.MEAN;
+ public 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));
+ }
+ SortByMeanValue();
+ }
+
+ /**
+ * 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( JobSpecificationMap jobSpecificationMap ) {
+
+ 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 Name");
+ System.out.println("---------- -------------- -------------- -------------- -------------- ---------------------------");
+
+ for (StatisticsRecord jobStatisticsRecord : jobStatisticsList ) {
+
+ JobSpecification jobSpec = jobSpecificationMap.getJobSpecification( jobStatisticsRecord.id);
+ String jobName = null;
+ if (jobSpec != null) {
+ jobName = jobSpec.name;
+ } else {
+ jobName = "UNKNOWN";
+ }
+ System.out.println( String.format("%10s %14.6f %14.6f %14.6f %14.6f %s",
+ jobStatisticsRecord.id,
+ jobStatisticsRecord.meanValue,
+ jobStatisticsRecord.stddev,
+ jobStatisticsRecord.minValue,
+ jobStatisticsRecord.maxValue,
+ jobName
+ )
+ );
+ }
+ }
+}
diff --git a/trick_source/java/src/main/java/trick/jobperf/JobStatsViewCanvas.java b/trick_source/java/src/main/java/trick/jobperf/JobStatsViewCanvas.java
new file mode 100644
index 000000000..51cb675b1
--- /dev/null
+++ b/trick_source/java/src/main/java/trick/jobperf/JobStatsViewCanvas.java
@@ -0,0 +1,99 @@
+package trick.jobperf;
+
+import java.awt.*;
+import java.io.*;
+import java.util.*;
+import javax.swing.*;
+
+public class JobStatsViewCanvas extends JPanel {
+
+ private Font headingsFont;
+ private Font dataFont;
+ JobStats jobStats;
+ JobSpecificationMap jobSpecificationMap;
+
+ public JobStatsViewCanvas( JobStats jobStats,
+ JobSpecificationMap jobSpecificationMap ) {
+ this.jobStats = jobStats;
+ this.jobSpecificationMap = jobSpecificationMap;
+
+ dataFont = new Font("Arial", Font.PLAIN, 18);
+ headingsFont = new Font("Arial", Font.BOLD, 18);
+
+ setPreferredSize(new Dimension(800, neededPanelHeight()));
+ }
+
+ 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);
+
+ // Panel Background Color Fill
+ g2d.setPaint(Color.WHITE);
+ g2d.fillRect(0, 0, getWidth(), getHeight());
+
+ // Title
+ g2d.setFont(headingsFont);
+ g2d.setPaint( Color.RED );
+ g2d.drawString("Jobs Duration Statistics Sorted by " + jobStats.currentSortCriterion, 100, 50);
+
+ // Column Headings
+ g2d.setFont(headingsFont);
+
+ g2d.setPaint( Color.BLUE );
+ g2d.drawString("Job-ID", 100, 80);
+ g2d.drawString("Mean Dur", 200, 80);
+ g2d.drawString("Std Dev", 300, 80);
+ g2d.drawString("Min Dur", 400, 80);
+ g2d.drawString("Max Dur", 500, 80);
+ g2d.drawString("Job-Name", 600, 80);
+
+ // For each record
+ int jobY = 100;
+ for (StatisticsRecord jobStatisticsRecord : jobStats.jobStatisticsList ) {
+
+ g2d.setFont(dataFont);
+ g2d.setPaint( Color.BLACK );
+
+ g2d.drawString(jobStatisticsRecord.id, 100, jobY);
+ g2d.drawString( String.format("%14.6f", jobStatisticsRecord.meanValue), 180, jobY);
+ g2d.drawString( String.format("%14.6f", jobStatisticsRecord.stddev), 280, jobY);
+ g2d.drawString( String.format("%14.6f", jobStatisticsRecord.minValue), 380, jobY);
+ g2d.drawString( String.format("%14.6f", jobStatisticsRecord.maxValue), 480, jobY);
+
+ JobSpecification jobSpec = jobSpecificationMap.getJobSpecification( jobStatisticsRecord.id);
+ if (jobSpec != null) {
+ g2d.drawString(jobSpec.name, 600, jobY);
+ } else {
+ g2d.setPaint( Color.RED );
+ g2d.drawString("UNKNOWN", 600, jobY);
+ }
+
+ jobY += 20;
+ }
+ }
+
+ /**
+ * Calculate the height of the JobStatsCanvas (JPanel) needed to render the
+ * jobs in the frame.
+ */
+ private int neededPanelHeight() {
+ return 20 * jobStats.jobStatisticsList.size() + 100;
+ }
+
+ /**
+ * This function paints the JobStatsCanvas (i.e, JPanel) when required.
+ */
+ @Override
+ public void paintComponent(Graphics g) {
+ super.paintComponent(g);
+ doDrawing(g);
+ }
+
+}
diff --git a/trick_source/java/src/main/java/trick/jobperf/JobStatsViewWindow.java b/trick_source/java/src/main/java/trick/jobperf/JobStatsViewWindow.java
new file mode 100644
index 000000000..f8f9af15e
--- /dev/null
+++ b/trick_source/java/src/main/java/trick/jobperf/JobStatsViewWindow.java
@@ -0,0 +1,113 @@
+package trick.jobperf;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.io.*;
+import java.util.*;
+import javax.swing.*;
+
+class SortButtonsToolBar extends JToolBar implements ActionListener {
+ JobStatsViewCanvas statsViewCanvas;
+ private JButton sortByIDButton;
+ private JButton sortByMean;
+ private JButton sortByStdDev;
+ private JButton sortByMin;
+ private JButton sortByMax;
+
+ public SortButtonsToolBar( JobStatsViewCanvas statsViewCanvas ) {
+ this.statsViewCanvas = statsViewCanvas;
+
+ add( new JLabel("Sort by : "));
+ sortByIDButton = new JButton("ID");
+ sortByIDButton.addActionListener(this);
+ sortByIDButton.setActionCommand("sort-by-ID");
+ sortByIDButton.setToolTipText("Sort by Job ID");
+ add(sortByIDButton);
+
+ sortByMean = new JButton("Mean");
+ sortByMean.addActionListener(this);
+ sortByMean.setActionCommand("sort-by-mean");
+ sortByMean.setToolTipText("Sort by Mean Job Run Duration");
+ add(sortByMean);
+
+ sortByStdDev = new JButton("Std Dev");
+ sortByStdDev.addActionListener(this);
+ sortByStdDev.setActionCommand("sort-by-std-dev");
+ sortByStdDev.setToolTipText("Sort by Std Deviation of Job Run Duration");
+ add(sortByStdDev);
+
+ sortByMin = new JButton("Min");
+ sortByMin.addActionListener(this);
+ sortByMin.setActionCommand("sort-by-min");
+ sortByMin.setToolTipText("Sort by Minimum Job Run Duration");
+ add(sortByMin);
+
+ sortByMax = new JButton("Max");
+ sortByMax.addActionListener(this);
+ sortByMax.setActionCommand("sort-by-max");
+ sortByMax.setToolTipText("Sort by Maximum Job Run Duration");
+ add(sortByMax);
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ String s = e.getActionCommand();
+ switch (s) {
+ case "sort-by-ID":
+ statsViewCanvas.jobStats.SortByID();
+ statsViewCanvas.repaint();
+ break;
+ case "sort-by-mean":
+ statsViewCanvas.jobStats.SortByMeanValue();
+ statsViewCanvas.repaint();
+ break;
+ case "sort-by-std-dev":
+ statsViewCanvas.jobStats.SortByStdDev();
+ statsViewCanvas.repaint();
+ break;
+ case "sort-by-min":
+ statsViewCanvas.jobStats.SortByMinValue();
+ statsViewCanvas.repaint();
+ break;
+ case "sort-by-max":
+ statsViewCanvas.jobStats.SortByMaxValue();
+ statsViewCanvas.repaint();
+ break;
+ default:
+ System.out.println("Unknown Action Command:" + s);
+ break;
+ }
+ }
+}
+
+public class JobStatsViewWindow extends JFrame {
+
+ public JobStatsViewWindow( JobStats jobStats, JobSpecificationMap jobSpecificationMap ) {
+
+ JobStatsViewCanvas statsViewCanvas = new JobStatsViewCanvas( jobStats, jobSpecificationMap);
+
+ JScrollPane scrollPane = new JScrollPane( statsViewCanvas );
+ scrollPane.setPreferredSize(new Dimension(800, 400));
+ scrollPane.getVerticalScrollBar().setUnitIncrement( 20 );
+
+ SortButtonsToolBar toolbar = new SortButtonsToolBar( statsViewCanvas);
+
+ JPanel scrollingJobStatsCanvas = new JPanel();
+ scrollingJobStatsCanvas.setPreferredSize(new Dimension(800, 400));
+ scrollingJobStatsCanvas.add(toolbar);
+ scrollingJobStatsCanvas.add(scrollPane);
+
+ scrollingJobStatsCanvas.setLayout( new BoxLayout(scrollingJobStatsCanvas, BoxLayout.Y_AXIS));
+
+ setTitle("Job Statistics");
+ // setSize(800, 500);
+ setPreferredSize(new Dimension(1200, 500));
+ add(scrollingJobStatsCanvas);
+ pack();
+ setVisible(true);
+ setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
+ setFocusable(true);
+ setVisible(true);
+
+ statsViewCanvas.repaint();
+ }
+}
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..5e727453c
--- /dev/null
+++ b/trick_source/java/src/main/java/trick/jobperf/KeyedColorMap.java
@@ -0,0 +1,125 @@
+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;
+ String fileName;
+
+ /**
+ * Constructor
+ */
+ public KeyedColorMap(String fileName) {
+ this.fileName = fileName;
+ 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() 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() 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..a1e851b84
--- /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 id - the job identifier.
+ * @param mean - the mean value of job duration.
+ * @param stddev - 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 id, double mean, double stddev, double min, double max) {
+ this.id = id;
+ this.meanValue = mean;
+ this.stddev = stddev;
+ this.minValue = min;
+ this.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..1e5d72063
--- /dev/null
+++ b/trick_source/java/src/main/java/trick/jobperf/TraceViewCanvas.java
@@ -0,0 +1,446 @@
+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 = 12;
+ public static final int DEFAULT_TRACE_WIDTH = 18;
+ public static final int MAX_TRACE_WIDTH = 24;
+ public static final int LEFT_MARGIN = 100;
+ public static final int RIGHT_MARGIN = 100;
+ public static final int TOP_MARGIN = 50;
+ public static final int BOTTOM_MARGIN = 20;
+ public static final int DEFAULT_FRAMES_TO_RENDER = 100;
+
+ public KeyedColorMap idToColorMap;
+ public JobSpecificationMap jobSpecificationMap;
+ public JobStats jobStats;
+
+ private int traceWidth;
+ private double frameSize;
+ private double totalDuration;
+ private FrameRecord[] frameArray;
+ private int selectedFrameNumber;
+ private FrameRange frameRenderRange;
+ private BufferedImage image;
+ private TraceViewOutputToolBar outputToolBar;
+ private Cursor crossHairCursor;
+ private Cursor defaultCursor;
+ private Font frameFont12;
+ private Font frameFont18;
+
+ 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,
+ KeyedColorMap idToColorMap,
+ JobSpecificationMap jobSpecificationMap ) {
+
+ traceWidth = DEFAULT_TRACE_WIDTH;
+ frameSize = 1.0;
+ image = null;
+ selectedFrameNumber = 0;
+
+ this.outputToolBar = outputToolBar;
+ this.idToColorMap = idToColorMap;
+ this.jobSpecificationMap = jobSpecificationMap;
+
+ jobStats = new JobStats(jobExecEvtList);
+
+ crossHairCursor = new Cursor( Cursor.CROSSHAIR_CURSOR );
+ defaultCursor = new Cursor( Cursor.DEFAULT_CURSOR );
+
+ frameFont12 = new Font("Arial", Font.PLAIN, 12);
+ frameFont18 = new Font("Arial", Font.PLAIN, 18);
+
+ try {
+ boolean wasTOF = false;
+ boolean wasEOF = false;
+
+ List frameList = new ArrayList();
+ FrameRecord frameRecord = new FrameRecord();
+ for (JobExecutionEvent jobExec : jobExecEvtList ) {
+
+ if ((!wasTOF && jobExec.isTOF) || ( wasEOF && !jobExec.isEOF )) {
+
+ // Wrap up the previous frame record.
+ frameRecord.stop = jobExec.start;
+ frameRecord.CalculateJobContainment();
+ frameList.add(frameRecord);
+
+ // Start a new frame record.
+ frameRecord = new FrameRecord();
+ frameRecord.start = jobExec.start;
+ }
+ frameRecord.jobEvents.add(jobExec);
+
+ wasTOF = jobExec.isTOF;
+ wasEOF = jobExec.isEOF;
+
+ idToColorMap.addKey(jobExec.id);
+ }
+
+ frameArray = frameList.toArray( new FrameRecord[ frameList.size() ]);
+
+ // Determine 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 map to a file.
+ idToColorMap.writeFile();
+
+ // System.out.println("File loaded.\n");
+ } 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 int getLastRenderFrame() {
+ return frameRenderRange.last;
+ }
+
+ // public void moveRenderFrameRangeBy(int advance) {
+ //
+ // if ( ((frameRenderRange.first + advance) > 0) &&
+ // ((frameRenderRange.first + advance) < frameArray.length ))
+ //
+ //
+ //
+ // }
+ // }
+
+ 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 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();
+ }
+ }
+
+ /**
+ *
+ */
+ public void displaySelectedFrame() {
+ FrameViewWindow window = new FrameViewWindow( this, frameArray[selectedFrameNumber], selectedFrameNumber);
+ }
+
+ /**
+ *
+ */
+ public void displayJobStatsWindow() {
+ JobStatsViewWindow window = new JobStatsViewWindow( jobStats, jobSpecificationMap);
+ }
+
+ /**
+ * @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 );
+ outputToolBar.setJobID(id);
+
+ // Get and display the job name associated with the ID.
+ JobSpecification jobSpec = jobSpecificationMap.getJobSpecification(id);
+ if (jobSpec != null) {
+ outputToolBar.setJobName(jobSpec.name);
+ outputToolBar.setJobClass(jobSpec.jobClass);
+ }
+
+ // 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;
+ }
+
+ // Determine the subframe-time where we clicked from the x-coordinate
+ // of the click position.
+ double subFrameClickTime = 0.0;
+ if ( traceRectContains(x, y)) {
+ double pixelsPerSecond = (double)traceRectWidth() / frameSize;
+ subFrameClickTime = (x - LEFT_MARGIN) / pixelsPerSecond;
+ }
+
+ /**
+ * 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];
+ Double clickTime = frame.start + subFrameClickTime;
+ for (JobExecutionEvent jobExec : frame.jobEvents) {
+ if (id.equals( jobExec.id) &&
+ clickTime >= jobExec.start &&
+ clickTime <= jobExec.stop ) {
+ outputToolBar.setJobTimes(jobExec.start, jobExec.stop);
+ }
+ }
+ repaint();
+ } else {
+ outputToolBar.clearJobFields();
+ }
+ }
+
+ /**
+ * 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());
+
+ if (traceWidth >= DEFAULT_TRACE_WIDTH) {
+ g2d.setFont(frameFont18);
+ } else {
+ g2d.setFont(frameFont12);
+ }
+
+ FontMetrics fm = g2d.getFontMetrics();
+ int TX = 0;
+
+ String FN_text = "Frame #";
+ TX = (LEFT_MARGIN - fm.stringWidth(FN_text))/2;
+ g2d.drawString (FN_text, TX, 40);
+
+ g2d.drawString ("Top of Frame", LEFT_MARGIN, 40);
+
+ String EOF_text = "End of Frame";
+ TX = LEFT_MARGIN + traceRectWidth - fm.stringWidth(EOF_text);
+ g2d.drawString (EOF_text, TX, 40);
+
+ // 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];
+ int jobY = TOP_MARGIN + (n - frameRenderRange.first) * traceWidth;
+
+ // Draw frame number.
+ if (n == selectedFrameNumber) {
+ g2d.setPaint(Color.RED);
+ // g2d.drawString ( "\u25b6", 20, jobY + traceWidth - 2);
+ g2d.drawString ( "\u25c0", 80, jobY + traceWidth - 2);
+ // g2d.fillRect(LEFT_MARGIN-traceWidth, jobY, traceWidth, traceWidth);
+ } else {
+ g2d.setPaint(Color.BLACK);
+ }
+
+ g2d.drawString ( String.format("%d", n), 40, jobY + traceWidth - 2);
+
+ // Draw the frame
+ // NOTE that the jobEvents within the frame are expected to be sorted in duration-order,
+ // so that smaller sub-jobs are not obscurred.
+
+ for (JobExecutionEvent jobExec : frame.jobEvents) {
+ int jobStartX = (int)((jobExec.start - frame.start) * pixelsPerSecond) + LEFT_MARGIN;
+ int jobWidth = (int)((jobExec.stop - jobExec.start) * pixelsPerSecond);
+ g2d.setPaint( idToColorMap.getColor( jobExec.id ) );
+ int jobHeight = traceWidth - 2;
+ if (jobExec.contained > 1) {
+ jobHeight = traceWidth / jobExec.contained;
+ }
+
+ // int jobStartY = jobY + (traceWidth - 2) - jobHeight;
+ g2d.fillRect(jobStartX, jobY, jobWidth, jobHeight);
+
+ }
+ }
+ }
+
+ /**
+ * 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..6caf34709
--- /dev/null
+++ b/trick_source/java/src/main/java/trick/jobperf/TraceViewInputToolBar.java
@@ -0,0 +1,160 @@
+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 implements ActionListener {
+
+ private TraceViewCanvas traceView;
+ private JTextField frameSizeField;
+ private JButton frameDetailsButton;
+ private JButton advanceRangeButton;
+ private JButton retreatRangeButton;
+ private JTextField firstRenderFrameField;
+ private JTextField lastRenderFrameField;
+ /**
+ * Constructor
+ * @param tvc TraceViewCanvas to be controlled.
+ */
+ public TraceViewInputToolBar (TraceViewCanvas tvc) {
+ traceView = tvc;
+
+ add( new JLabel(" Frame Size (Avg): "));
+ frameSizeField = new JTextField(10);
+ 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();
+ }
+ }
+ });
+
+ frameDetailsButton = new JButton("Frame Details");
+ frameDetailsButton.addActionListener(this);
+ frameDetailsButton.setActionCommand("display-frame-details");
+ frameDetailsButton.setToolTipText("Display the job details of the selected frame.");
+ add(frameDetailsButton);
+
+ add( new JLabel( String.format(" Frames : [%d ... %d]", 0, traceView.getFrameTotal()-1 )));
+
+ add( new JLabel(" Selected Range: "));
+
+ firstRenderFrameField = new JTextField(10);
+ 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(10);
+ 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();
+ }
+ }
+ });
+
+ advanceRangeButton = new JButton("Advance");
+ advanceRangeButton.addActionListener(this);
+ advanceRangeButton.setActionCommand("advance-frame-range");
+ advanceRangeButton.setToolTipText("Advance the selected range of frames to be displayed.");
+ add(advanceRangeButton);
+
+ advanceRangeButton = new JButton("Retreat");
+ advanceRangeButton.addActionListener(this);
+ advanceRangeButton.setActionCommand("retreat-frame-range");
+ advanceRangeButton.setToolTipText("Retreat the selected range of frames to be displayed.");
+ add(advanceRangeButton);
+
+ add( new JLabel(" "));
+
+ // Add Trick LOGO here.
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ String s = e.getActionCommand();
+ switch (s) {
+ case "display-frame-details":
+ traceView.displaySelectedFrame();
+ break;
+ case "advance-frame-range":
+ // DO ACTION
+ break;
+ case "retreat-frame-range":
+ // DO ACTION
+ break;
+ default:
+ System.out.println("Unknown Action Command:" + s);
+ break;
+ }
+ }
+
+ 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..602c849e9
--- /dev/null
+++ b/trick_source/java/src/main/java/trick/jobperf/TraceViewMenuBar.java
@@ -0,0 +1,98 @@
+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 [viewMenu]
+* JMenu [traceSizeMenu]
+* JMenuItem [traceSizeIncrease], Action: Call traceView.incrementTraceWidth().
+* JMenuItem [traceSizeDecrease], 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");
+ KeyStroke ctrlQ = KeyStroke.getKeyStroke('Q', InputEvent.CTRL_MASK );
+ fileMenuExit.setAccelerator(ctrlQ);
+ fileMenuExit.addActionListener(this);
+ fileMenu.add(fileMenuExit);
+ add(fileMenu);
+
+ JMenu viewMenu = new JMenu("View");
+
+ JMenuItem traceSizeIncrease = new JMenuItem("Increase Trace Width");
+ traceSizeIncrease.setActionCommand("increase-trace_width");
+ KeyStroke ctrlPlus = KeyStroke.getKeyStroke('=', InputEvent.CTRL_MASK );
+ traceSizeIncrease.setAccelerator(ctrlPlus);
+ traceSizeIncrease.addActionListener(this);
+ viewMenu.add(traceSizeIncrease);
+
+ JMenuItem traceSizeDecrease = new JMenuItem("Decrease Trace Width");
+ traceSizeDecrease.setActionCommand("decrease-trace_width");
+ KeyStroke ctrlMinus = KeyStroke.getKeyStroke('-', InputEvent.CTRL_MASK);
+ traceSizeDecrease.setAccelerator(ctrlMinus);
+ traceSizeDecrease.addActionListener(this);
+ viewMenu.add(traceSizeDecrease);
+
+ viewMenu.addSeparator();
+
+ JMenuItem showFrame = new JMenuItem("Frame Details ...");
+ showFrame.setActionCommand("expand-selected-frame");
+ KeyStroke ctrlF = KeyStroke.getKeyStroke('F', InputEvent.CTRL_MASK);
+ showFrame.setAccelerator(ctrlF);
+ showFrame.addActionListener(this);
+ viewMenu.add(showFrame);
+
+ JMenuItem showStats = new JMenuItem("Job Statistics ...");
+ showStats.setActionCommand("show-job-stats");
+ KeyStroke ctrlV = KeyStroke.getKeyStroke('V', InputEvent.CTRL_MASK);
+ showStats.setAccelerator(ctrlV);
+ showStats.addActionListener(this);
+ viewMenu.add(showStats);
+
+ add(viewMenu);
+
+ }
+ @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 "expand-selected-frame":
+ traceView.displaySelectedFrame();
+ break;
+ case "show-job-stats":
+ traceView.jobStats.SortByID();
+ traceView.displayJobStatsWindow();
+ 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..5ea038ed5
--- /dev/null
+++ b/trick_source/java/src/main/java/trick/jobperf/TraceViewOutputToolBar.java
@@ -0,0 +1,104 @@
+package trick.jobperf;
+
+import java.awt.*;
+import javax.swing.*;
+
+/**
+* Class TraceViewOutputToolBar displays information output from a
+* TraceViewCanvas.
+*/
+class TraceViewOutputToolBar extends JToolBar {
+ private JTextField IDField;
+ private JTextField nameField;
+ private JTextField classField;
+ private JTextField startField;
+ private JTextField stopField;
+ // private JTextField frameNumberField;
+ private JTextField durationField;
+
+ /**
+ * Constructor
+ */
+ public TraceViewOutputToolBar () {
+
+ add( new JLabel(" Job ID: "));
+ IDField = new JTextField(5);
+ IDField.setEditable(false);
+ IDField.setText( "");
+ add(IDField);
+
+ add( new JLabel(" Name: "));
+ nameField = new JTextField(25);
+ nameField.setEditable(false);
+ nameField.setText( "");
+ add(nameField);
+
+ add( new JLabel(" Class: "));
+ classField = new JTextField(12);
+ classField.setEditable(false);
+ classField.setText( "");
+ add(classField);
+
+ add( new JLabel(" Start: "));
+ startField = new JTextField(6);
+ startField.setEditable(false);
+ startField.setText( "");
+ add(startField);
+
+ add( new JLabel(" Stop: "));
+ stopField = new JTextField(6);
+ stopField.setEditable(false);
+ stopField.setText( "");
+ add(stopField);
+
+ add( new JLabel(" Duration: "));
+ durationField = new JTextField(6);
+ durationField.setEditable(false);
+ durationField.setText( "");
+ add(durationField);
+ }
+
+ /**
+ * @param id job identifier to display.
+ */
+ public void setJobID(String id) {
+ IDField.setText( id );
+ }
+
+ /**
+ * @param id job identifier to display.
+ */
+ public void setJobName(String name) {
+ nameField.setText(name);
+ }
+
+ /**
+ * @param id job class to display.
+ */
+ public void setJobClass(String name) {
+ classField.setText(name);
+ }
+
+ /**
+ * @param time to be displayed in the job start field.
+ */
+ public void setJobTimes(Double start_time, Double stop_time) {
+ startField.setText( String.format("%8.4f", start_time) );
+ stopField.setText( String.format("%8.4f", stop_time) );
+ Double duration = stop_time - start_time;
+ durationField.setText( String.format("%8.4f", duration) );
+ }
+
+ /**
+ * Clear the startField and stopField.
+ */
+ public void clearJobFields() {
+ nameField.setText("");
+ classField.setText("");
+ startField.setText("");
+ stopField.setText("");
+ durationField.setText("");
+ IDField.setText("");
+ }
+
+}
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..c87f1b2d3
--- /dev/null
+++ b/trick_source/java/src/main/java/trick/jobperf/TraceViewWindow.java
@@ -0,0 +1,65 @@
+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,
+ KeyedColorMap idToColorMap,
+ JobSpecificationMap jobSpecificationMap ) {
+
+ TraceViewOutputToolBar outputToolBar = new TraceViewOutputToolBar();
+
+ TraceViewCanvas traceViewCanvas = new TraceViewCanvas( jobExecList, outputToolBar, idToColorMap, jobSpecificationMap);
+
+ 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..131fc9ba1 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,41 @@ 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,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;
+ fprintf(fp_log,"%5.2f, %f, %f\n", tl[ii].id, 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 +627,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