Files
2016-04-27 01:07:35 -04:00

664 lines
22 KiB
Java

//******************************************************************************
//
// File: pj2.java
// Package: ---
// Unit: Class pj2
//
// This Java source file is copyright (C) 2015 by Alan Kaminsky. All rights
// reserved. For further information, contact the author, Alan Kaminsky, at
// ark@cs.rit.edu.
//
// This Java source file is part of the Parallel Java 2 Library ("PJ2"). PJ2 is
// free software; you can redistribute it and/or modify it under the terms of
// the GNU General Public License as published by the Free Software Foundation;
// either version 3 of the License, or (at your option) any later version.
//
// PJ2 is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
// A PARTICULAR PURPOSE. See the GNU General Public License for more details.
//
// A copy of the GNU General Public License is provided in the file gpl.txt. You
// may also obtain a copy of the GNU General Public License on the World Wide
// Web at http://www.gnu.org/licenses/gpl.html.
//
//******************************************************************************
import edu.rit.pj2.Debug;
import edu.rit.pj2.JarClassLoader;
import edu.rit.pj2.Job;
import edu.rit.pj2.Rule;
import edu.rit.pj2.Schedule;
import edu.rit.pj2.Task;
import edu.rit.pj2.TaskSpec;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.util.EnumSet;
/**
* Class pj2 is the PJ2 job and task launcher main program.
* <P>
* Usage: <TT>java pj2 [threads=<I>threads</I>] [schedule=<I>schedule</I>]
* [chunk=<I>chunk</I>] [nodeName=<I>nodeName</I>] [cores=<I>cores</I>]
* [gpus=<I>gpus</I>] [workers=<I>workers</I>]
* [masterSchedule=<I>masterSchedule</I>] [masterChunk=<I>masterChunk</I>]
* [tracker=<I>host</I>[:<I>port</I>]] [listen=<I>host</I>[:<I>port</I>]]
* [debug=<I>message</I>[,<I>message</I>...]]
* [jvmFlags=<I>flag</I>[,<I>flag</I>]] [jar=<I>file</I>]
* [timelimit=<I>timelimit</I>] <I>jobOrTaskClass</I> [<I>arg</I> ...]</TT>
* <P>
* PJ2 is designed for writing two kinds of parallel programs: tightly coupled
* single-node and loosely coupled multi-node.
* <P>
* <B>Tightly coupled single-node parallel programs.</B>
* A tightly coupled single-node parallel program is intended to run on one
* multicore node. The program consists of multiple threads that run in parallel
* on separate cores. The threads communicate and coordinate by reading and
* writing variables in shared memory.
* <P>
* This kind of PJ2 program is expressed as a subclass of class {@linkplain
* edu.rit.pj2.Task Task}. The program is executed by running the <TT>pj2</TT>
* program as follows:
* <P>
* <TT>$ java pj2 MainTask <I>arg</I> ...</TT>
* <P>
* The above command runs class MainTask, a subclass of class {@linkplain
* edu.rit.pj2.Task Task}. Replace <TT>MainTask</TT> with the fully-qualified
* class name of the desired task subclass. The <TT>pj2</TT> program creates an
* instance of the task subclass, then calls that task's {@link
* edu.rit.pj2.Task#main(String[]) main()} method, passing in an array of the
* command line argument strings (zero or more). The {@link
* edu.rit.pj2.Task#main(String[]) main()} method carries out the computation.
* When the {@link edu.rit.pj2.Task#main(String[]) main()} method returns, the
* <TT>pj2</TT> program terminates.
* <P>
* The computation can contain sequential statements executed by a single
* thread, interspersed with parallel statements executed by multiple threads.
* Running on a multicore computer, the Java Virtual Machine (JVM) schedules
* each thread on a separate core, thus executing the program in parallel.
* <P>
* <B>Loosely coupled multi-node parallel programs.</B>
* A loosely coupled multi-node parallel program is intended to run on multiple
* nodes. The nodes may be located in a cluster parallel computer, in a grid, or
* in a cloud. The program consists of multiple {@linkplain edu.rit.pj2.Task
* Task}s that run in parallel on separate nodes. The tasks coordinate and
* communicate with each other by reading and writing {@linkplain
* edu.rit.pj2.Tuple Tuple}s in <I>tuple space.</I> The tasks are intended to be
* <I>coarse-grained,</I> meaning each task runs for minutes or hours, not
* milliseconds. The tasks are intended to communicate <I>small to medium
* amounts of data</I> via tuple space, not enormous quantities of data.
* <P>
* This kind of PJ2 program is expressed as a subclass of class {@linkplain
* edu.rit.pj2.Job Job}. The program is executed by running the <TT>pj2</TT>
* program as follows:
* <P>
* <TT>$ java pj2 jar=<I>file</I> MainJob <I>arg</I> ...</TT>
* <P>
* The above command runs class MainJob, a subclass of class {@linkplain
* edu.rit.pj2.Job Job}. Replace <TT>MainJob</TT> with the fully-qualified class
* name of the desired job subclass. The <TT>pj2</TT> program creates an
* instance of the job subclass, then calls that job's {@link
* edu.rit.pj2.Job#main(String[]) main()} method, passing in an array of the
* command line argument strings (zero or more). The {@link
* edu.rit.pj2.Job#main(String[]) main()} method defines {@linkplain
* edu.rit.pj2.Rule Rule}s that specify which {@linkplain edu.rit.pj2.Task
* Task}s are to be executed as part of the job. The <TT>pj2</TT> program
* creates and executes those tasks, which carry out the job's computation. The
* tasks may execute in the <TT>pj2</TT> program's process or on computational
* nodes that are part of a cluster, grid, or cloud. When all the tasks have
* finished executing, the <TT>pj2</TT> program terminates.
* <P>
* Running on a cluster, grid, or cloud, the program executes each task on a
* separate node, thus executing the program in parallel. Each task receives
* input tuples that are taken out of tuple space, performs computations, and
* generates output tuples that are written into tuple space. Each task executes
* independently of other tasks. All inter-task coordination and communication
* goes through tuple space.
* <P>
* <B>Properties.</B>
* Certain properties of the job or task can be specified on the <TT>pj2</TT>
* command line by including the appropriate option before the job or task class
* name. These override the default property values. The properties are:
* <UL>
* <P><LI>
* <TT>threads</TT> specifies the number of threads that will execute a parallel
* for loop. Specify <TT>threads=all</TT> to indicate that a parallel for loop
* will be executed by as many threads as there are cores on the machine on
* which the task is running. The default is <TT>threads=all</TT>. See the
* {@link edu.rit.pj2.Job#threads(int) Job.threads()} or {@link
* edu.rit.pj2.Task#threads(int) Task.threads()} method for further information.
* <P><LI>
* <TT>schedule</TT> specifies how the iterations of a parallel for loop are
* partitioned among the threads executing the parallel for loop. The default is
* a fixed schedule. See the {@link
* edu.rit.pj2.Job#schedule(edu.rit.pj2.Schedule) Job.schedule()} or {@link
* edu.rit.pj2.Task#schedule(edu.rit.pj2.Schedule) Task.schedule()} method for
* further information.
* <P><LI>
* <TT>chunk</TT> specifies how the iterations of a parallel for loop are
* partitioned among the threads executing the parallel for loop. The
* interpretation of the default <TT>chunk</TT> property depends on the
* <TT>schedule</TT> property. See the {@link edu.rit.pj2.Job#chunk(int)
* Job.chunk()} or {@link edu.rit.pj2.Task#chunk(int) Task.chunk()} method for
* further information.
* <P><LI>
* <TT>nodeName</TT> specifies the name of the node on which a task must run.
* Specify <TT>nodeName=any</TT> to indicate that a task can run on any node.
* The default is <TT>nodeName=any</TT>. See the {@link
* edu.rit.pj2.Job#nodeName(String) Job.nodeName()} or {@link
* edu.rit.pj2.Task#nodeName(String) Task.nodeName()} method for further
* information.
* <P><LI>
* <TT>cores</TT> specifies the number of CPU cores a task requires. Specify
* <TT>cores=all</TT> to indicate that a task requires all the CPU cores on the
* node. The default is <TT>cores=all</TT>. See the {@link
* edu.rit.pj2.Job#cores(int) Job.cores()} or {@link edu.rit.pj2.Task#cores(int)
* Task.cores()} method for further information.
* <P><LI>
* <TT>gpus</TT> specifies the number of GPU accelerators a task requires. The
* default is none (0). See the {@link edu.rit.pj2.Job#gpus(int) Job.gpus()} or
* {@link edu.rit.pj2.Task#gpus(int) Task.gpus()} method for further
* information.
* <P><LI>
* <TT>workers</TT> specifies the number of worker tasks that will execute a
* master-worker cluster parallel for loop. The default is one worker task. See
* the {@link edu.rit.pj2.Job#workers(int) Job.workers()} method for further
* information.
* <P><LI>
* <TT>masterSchedule</TT> specifies how the iterations of a master-worker
* cluster parallel for loop are partitioned among the worker tasks executing
* the parallel for loop. The default is a fixed schedule. See the {@link
* edu.rit.pj2.Job#masterSchedule(edu.rit.pj2.Schedule) Job.masterSchedule()}
* method for further information.
* <P><LI>
* <TT>masterChunk</TT> specifies how the iterations of a master-worker cluster
* parallel for loop are partitioned among the worker tasks executing the
* parallel for loop. The interpretation of the default <TT>masterChunk</TT>
* property depends on the <TT>masterSchedule</TT> property. See the {@link
* edu.rit.pj2.Job#masterChunk(int) Job.masterChunk()} method for further
* information.
* <P><LI>
* <TT>tracker</TT> specifies the host and port at which the Tracker is
* listening for connections from jobs. If <TT>tracker=none</TT> is specified,
* the job will not connect to the Tracker. See the {@link
* edu.rit.pj2.Job#trackerHost(String) Job.trackerHost()} and {@link
* edu.rit.pj2.Job#trackerPort(int) Job.trackerPort()} methods for further
* information.
* <P><LI>
* <TT>listen</TT> specifies the host and port at which the job itself will
* listen for connections from the nodes that will run the job's tasks. See the
* {@link edu.rit.pj2.Job#listenHost(String) Job.listenHost()} and {@link
* edu.rit.pj2.Job#listenPort(int) Job.listenPort()} methods for further
* information.
* <P><LI>
* <TT>debug</TT> tells a job to print a certain debugging message or messages.
* Multiple messages may be specified, separated by commas (no spaces). Specify
* <TT>debug=all</TT> to turn on all debugging messages. Specify
* <TT>debug=none</TT> to turn off all debugging messages. See the {@link
* edu.rit.pj2.Job#debug(edu.rit.pj2.Debug[]) Job.debug()} method for further
* information.
* <P><LI>
* <TT>jvmFlags</TT> specifies one or more flags that are included on the
* command lines of the JVMs that run each of the tasks in the job. Multiple
* flags may be specified, separated by commas (no spaces). See the {@link
* edu.rit.pj2.Job#jvmFlags(String[]) Job.jvmFlags()} method for further
* information.
* <P><LI>
* <TT>jar</TT> specifies the Java archive (JAR) file that contains the class
* files needed by a job and its tasks. For further information, see class
* {@linkplain edu.rit.pj2.Job Job}.
* <P><LI>
* <TT>timelimit</TT> specifies a time limit in seconds for the job. The default
* is no time limit. See the {@link edu.rit.pj2.Job#timeLimit(int)
* Job.timeLimit()} method for further information.
* </UL>
* <P>
* <B>Abnormal termination.</B>
* If code in a job or task throws an exception that propagates out of the
* <TT>main()</TT> method, the <TT>pj2</TT> program prints an exception stack
* trace and terminates.
* <P>
* Code in a job or task must <I>not</I> call the <TT>System.exit()</TT> method.
* If a job or task needs to terminate itself, the job or task should throw an
* exception that propagates out of every method without being caught, such as a
* {@linkplain edu.rit.pj2.TerminateException TerminateException} or any other
* runtime exception. The exception's detail message can explain why the job or
* task terminated. This exception propagates out of the <TT>main()</TT> method
* and causes the <TT>pj2</TT> program to terminate.
*
* @see edu.rit.pj2
* @see edu.rit.pj2.Job
* @see edu.rit.pj2.Task
*
* @author Alan Kaminsky
* @version 01-Oct-2015
*/
public class pj2
{
// Prevent construction.
private pj2()
{
}
// Command line arguments.
private static Integer threads = null;
private static Schedule schedule = null;
private static Integer chunk = null;
private static String nodeName = null;
private static Integer cores = null;
private static Integer gpus = null;
private static Integer workers = null;
private static Schedule masterSchedule = null;
private static Integer masterChunk = null;
private static String tracker = null;
private static String listen = null;
private static EnumSet<Debug> debug = null;
private static String[] jvmFlags = null;
private static String jar = null;
private static Integer timeLimit = null;
private static String jobOrTaskClass = null;
private static Class<?> jtclass = null;
private static String[] taskArgs = null;
// Exported operations.
/**
* PJ2 launcher main program.
*
* @param args Property settings (optional), job or task class name, job's
* or task's command line arguments (zero or more).
*/
public static void main
(String[] args)
{
try
{
// Parse command line arguments.
int argi = 0;
while (argi < args.length && jobOrTaskClass == null)
{
if (args[argi].startsWith ("threads="))
{
String t = args[argi].substring (8);
if (t.equals ("all"))
threads = new Integer (Job.THREADS_EQUALS_CORES);
else
{
try
{
threads = new Integer (t);
if (threads < 1)
usageIllegal (args[argi]);
}
catch (NumberFormatException exc)
{
usageIllegal (args[argi]);
}
}
}
else if (args[argi].startsWith ("schedule="))
{
try
{
schedule = Schedule.valueOf (args[argi].substring (9));
}
catch (IllegalArgumentException exc)
{
usageIllegal (args[argi]);
}
}
else if (args[argi].startsWith ("chunk="))
{
try
{
chunk = new Integer (args[argi].substring (6));
if (chunk < 1)
usageIllegal (args[argi]);
}
catch (NumberFormatException exc)
{
usageIllegal (args[argi]);
}
}
else if (args[argi].startsWith ("nodeName="))
{
nodeName = args[argi].substring (9);
if (nodeName.equals ("any"))
nodeName = Job.ANY_NODE_NAME;
}
else if (args[argi].startsWith ("cores="))
{
String c = args[argi].substring (6);
if (c.equals ("all"))
cores = new Integer (Job.ALL_CORES);
else
{
try
{
cores = new Integer (c);
if (cores < 1)
usageIllegal (args[argi]);
}
catch (NumberFormatException exc)
{
usageIllegal (args[argi]);
}
}
}
else if (args[argi].startsWith ("gpus="))
{
String g = args[argi].substring (5);
try
{
gpus = new Integer (g);
if (gpus < 0)
usageIllegal (args[argi]);
}
catch (NumberFormatException exc)
{
usageIllegal (args[argi]);
}
}
else if (args[argi].startsWith ("workers="))
{
try
{
workers = new Integer (args[argi].substring (8));
if (workers < 1)
usageIllegal (args[argi]);
}
catch (NumberFormatException exc)
{
usageIllegal (args[argi]);
}
}
else if (args[argi].startsWith ("masterSchedule="))
{
try
{
masterSchedule =
Schedule.valueOf (args[argi].substring (15));
}
catch (IllegalArgumentException exc)
{
usageIllegal (args[argi]);
}
}
else if (args[argi].startsWith ("masterChunk="))
{
try
{
masterChunk = new Integer (args[argi].substring (12));
if (masterChunk < 1)
usageIllegal (args[argi]);
}
catch (NumberFormatException exc)
{
usageIllegal (args[argi]);
}
}
else if (args[argi].startsWith ("tracker="))
{
tracker = args[argi].substring (8);
}
else if (args[argi].startsWith ("listen="))
{
listen = args[argi].substring (7);
}
else if (args[argi].startsWith ("debug="))
{
String d = args[argi].substring (6);
if (d.equals ("all"))
debug = EnumSet.allOf (Debug.class);
else if (d.equals ("none"))
debug = EnumSet.noneOf (Debug.class);
else
{
try
{
debug = EnumSet.noneOf (Debug.class);
for (String s : d.split (","))
debug.add (Debug.valueOf (s));
}
catch (IllegalArgumentException exc)
{
usageIllegal (args[argi]);
}
}
}
else if (args[argi].startsWith ("jvmFlags="))
{
jvmFlags = args[argi] .substring (9) .split (",");
}
else if (args[argi].startsWith ("jar="))
{
jar = args[argi].substring (4);
}
else if (args[argi].startsWith ("timelimit="))
{
try
{
timeLimit = new Integer (args[argi].substring (10));
if (timeLimit < 1)
usageIllegal (args[argi]);
}
catch (NumberFormatException exc)
{
usageIllegal (args[argi]);
}
}
else
{
jobOrTaskClass = args[argi];
}
++ argi;
}
int numTaskArgs = args.length - argi;
taskArgs = new String [numTaskArgs];
System.arraycopy (args, argi, taskArgs, 0, numTaskArgs);
// Install JAR class loader if specified.
if (jar != null)
{
BufferedInputStream in = new BufferedInputStream
(new FileInputStream (jar));
ByteArrayOutputStream out = new ByteArrayOutputStream();
int b;
while ((b = in.read()) != -1)
out.write (b);
in.close();
Thread.currentThread().setContextClassLoader
(new JarClassLoader
(Thread.currentThread().getContextClassLoader(),
out.toByteArray()));
}
// Get job or task class.
if (jobOrTaskClass == null)
usage ("Missing jobOrTaskClass");
try
{
jtclass = Class.forName (jobOrTaskClass, true,
Thread.currentThread().getContextClassLoader());
}
catch (ClassNotFoundException exc)
{
usage (String.format
("Job or task class %s not found", jobOrTaskClass));
}
// Job to be executed.
Job job = null;
// Execute a parallel program consisting of a task.
if (Task.class.isAssignableFrom (jtclass))
{
// Create instance of task class. This is just to make sure the
// task class can be instantiated.
Task task = null;
try
{
task = (Task) jtclass.newInstance();
}
catch (IllegalAccessException exc)
{
usage (String.format
("Task class %s or its nullary constructor cannot be accessed",
jobOrTaskClass));
}
catch (InstantiationException exc)
{
usage (String.format
("Task class %s cannot be instantiated",
jobOrTaskClass));
}
task = null;
// Set up a single-task Job.
job = new Job()
{
public void main (String[] args)
{
rule() .task ((Class<Task>) jtclass) .args (taskArgs);
}
};
job.debug (EnumSet.noneOf (Debug.class));
}
// Execute a parallel program consisting of a job.
else if (Job.class.isAssignableFrom (jtclass))
{
// Create instance of job class.
try
{
job = (Job) jtclass.newInstance();
}
catch (IllegalAccessException exc)
{
usage (String.format
("Job class %s or its nullary constructor cannot be accessed",
jobOrTaskClass));
}
catch (InstantiationException exc)
{
usage (String.format
("Job class %s cannot be instantiated",
jobOrTaskClass));
}
}
// Class is not a task class or a job class.
else
usage (String.format
("Class %s is not a Job or a Task", jobOrTaskClass));
// Override job's property settings if any.
if (threads != null)
job.threads (threads);
if (schedule != null)
job.schedule (schedule);
if (chunk != null)
job.chunk (chunk);
if (nodeName != null)
job.nodeName (nodeName);
if (cores != null)
job.cores (cores);
if (gpus != null)
job.gpus (gpus);
if (workers != null)
job.workers (workers);
if (masterSchedule != null)
job.masterSchedule (masterSchedule);
if (masterChunk != null)
job.masterChunk (masterChunk);
if (tracker != null)
try
{
int i = tracker.indexOf (':');
if (i == -1)
job.trackerHost (tracker);
else
{
job.trackerHost (tracker.substring (0, i));
job.trackerPort (Integer.parseInt
(tracker.substring (i + 1)));
}
}
catch (Exception exc)
{
usage (String.format ("tracker=%s illegal", tracker));
}
if (listen != null)
try
{
int i = listen.indexOf (':');
if (i == -1)
job.listenHost (listen);
else
{
job.listenHost (listen.substring (0, i));
job.listenPort (Integer.parseInt
(listen.substring (i + 1)));
}
}
catch (Exception exc)
{
usage (String.format ("listen=%s illegal", listen));
}
if (debug != null)
job.debug (debug);
if (jvmFlags != null)
job.jvmFlags (jvmFlags);
if (jar != null)
job.jar (new File (jar));
if (timeLimit != null)
job.timeLimit (timeLimit);
// Execute job.
job.main (taskArgs);
job.execute();
// Exit program.
System.exit (0);
}
// On any exception, print stack trace and terminate.
catch (Throwable exc)
{
exc.printStackTrace (System.err);
System.exit (1);
}
}
// Hidden operations.
/**
* Print an illegal argument usage message and exit.
*
* @param arg Command line argument.
*/
private static void usageIllegal
(String arg)
{
usage (arg + " illegal");
}
/**
* Print a usage message and exit.
*
* @param msg Error message.
*/
private static void usage
(String msg)
{
System.err.printf ("pj2: %s%n", msg);
System.err.println ("Usage: java pj2 [threads=<threads>] [schedule=<schedule>] [chunk=<chunk>] [nodeName=<nodeName>] [cores=<cores>] [gpus=<gpus>] [workers=<workers>] [masterSchedule=<masterSchedule>] [masterChunk=<masterChunk>] [tracker=<host>[:<port>]] [listen=<host>[:<port>]] [debug=<message>[,<message>...]] [jar=<file>] [timelimit=<timelimit>] <jobOrTaskClass> [<arg> ...]");
System.exit (1);
}
}