/* * Title: CloudSim Toolkit * Description: CloudSim (Cloud Simulation) Toolkit for Modeling and Simulation of Clouds * Licence: GPL - http://www.gnu.org/copyleft/gpl.html * * Copyright (c) 2009-2012, The University of Melbourne, Australia */ package org.cloudbus.cloudsim.util; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Enumeration; import java.util.zip.GZIPInputStream; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import org.cloudbus.cloudsim.Cloudlet; import org.cloudbus.cloudsim.UtilizationModel; import org.cloudbus.cloudsim.UtilizationModelFull; /** * This class is responsible for reading resource traces from a file and creating a list of jobs * ({@link Cloudlet Cloudlets}). * <p/> * <b>NOTE:</b> * <ul> * <li>This class can only take <tt>one</tt> trace file of the following format: <i>ASCII text, zip, * gz.</i> * <li>If you need to load multiple trace files, then you need to create multiple instances of this * class <tt>each with a unique entity name</tt>. * <li>If size of the trace file is huge or contains lots of traces, please increase the JVM heap * size accordingly by using <tt>java -Xmx</tt> option when running the simulation. * <li>The default job file size for sending to and receiving from a resource is * {@link gridsim.net.Link#DEFAULT_MTU}. However, you can specify the file size by using * {@link #setCloudletFileSize(int)}. * <li>A job run time is only for 1 PE <tt>not</tt> the total number of allocated PEs. Therefore, a * Cloudlet length is also calculated for 1 PE.<br> * For example, job #1 in the trace has a run time of 100 seconds for 2 processors. This means each * processor runs job #1 for 100 seconds, if the processors have the same specification. * </ul> * * @todo The last item in the list above is not true. The cloudlet length is not * divided by the number of PEs. If there is more than 1 PE, all PEs run the same * number of MI as specified in the {@link Cloudlet#cloudletLength} attribute. * See {@link Cloudlet#setNumberOfPes(int)} method documentation. * * <p/> * By default, this class follows the standard workload format as specified in * <a href="http://www.cs.huji.ac.il/labs/parallel/workload/"> * http://www.cs.huji.ac.il/labs/parallel/workload/</a> <br/> * However, you can use other format by calling the methods below before running the simulation: * <ul> * <li> {@link #setComment(String)} * <li> {@link #setField(int, int, int, int, int)} * </ul> * * @author Anthony Sulistio * @author Marcos Dias de Assuncao * @since 5.0 * * @see Workload */ public class WorkloadFileReader implements WorkloadModel { /** * Trace file name. */ private final File file; /** * The Cloudlet's PE rating (in MIPS), considering that all PEs of a Cloudlet * have the same rate. */ private final int rating; /** * List of Cloudlets created from the trace {@link #file}. */ private ArrayList<Cloudlet> jobs = null; /* Index of fields from the Standard Workload Format. */ /** * Field index of job number. */ private int JOB_NUM = 1 - 1; /** * Field index of submit time of a job. */ private int SUBMIT_TIME = 2 - 1; /** * Field index of running time of a job. */ private final int RUN_TIME = 4 - 1; /** * Field index of number of processors needed for a job. */ private final int NUM_PROC = 5 - 1; /** * Field index of required number of processors. */ private int REQ_NUM_PROC = 8 - 1; /** * Field index of required running time. */ private int REQ_RUN_TIME = 9 - 1; /** * Field index of user who submitted the job. */ private final int USER_ID = 12 - 1; /** * Field index of group of the user who submitted the job. */ private final int GROUP_ID = 13 - 1; /** * Max number of fields in the trace file. */ private int MAX_FIELD = 18; /** * A string that denotes the start of a comment. */ private String COMMENT = ";"; /** * If the field index of the job number ({@link #JOB_NUM}) is equals * to this constant, it means the number of the job doesn't have to be * gotten from the trace file, but has to be generated by this workload generator * class. */ private static final int IRRELEVANT = -1; /** * A temp array storing all the fields read from a line of the trace file. */ private String[] fieldArray = null; /** * Create a new WorkloadFileReader object. * * @param fileName the workload trace filename in one of the following formats: * <i>ASCII text, zip, gz.</i> * @param rating the cloudlet's PE rating (in MIPS), considering that all PEs * of a cloudlet have the same rate * @throws FileNotFoundException * @throws IllegalArgumentException This happens for the following conditions: * <ul> * <li>the workload trace file name is null or empty * <li>the resource PE rating <= 0 * </ul> * @pre fileName != null * @pre rating > 0 * @post $none */ public WorkloadFileReader(final String fileName, final int rating) throws FileNotFoundException { if (fileName == null || fileName.length() == 0) { throw new IllegalArgumentException("Invalid trace file name."); } else if (rating <= 0) { throw new IllegalArgumentException("Resource PE rating must be > 0."); } file = new File(fileName); if (!file.exists()) { throw new FileNotFoundException("Workload trace " + fileName + " does not exist"); } this.rating = rating; } /** * Reads job information from a trace file and generates the respective cloudlets. * * @return the list of cloudlets read from the file; <code>null</code> in case of failure. * @see #file */ @Override public ArrayList<Cloudlet> generateWorkload() { if (jobs == null) { jobs = new ArrayList<Cloudlet>(); // create a temp array fieldArray = new String[MAX_FIELD]; try { /*@todo It would be implemented using specific classes to avoid using ifs. If a new format is included, the code has to be changed to include another if*/ if (file.getName().endsWith(".gz")) { readGZIPFile(file); } else if (file.getName().endsWith(".zip")) { readZipFile(file); } else { readFile(file); } } catch (final FileNotFoundException e) { } catch (final IOException e) { } } return jobs; } /** * Sets the string that identifies the start of a comment line. * * @param cmt a character that denotes the start of a comment, e.g. ";" or "#" * @return <code>true</code> if it is successful, <code>false</code> otherwise * @pre comment != null * @post $none */ public boolean setComment(final String cmt) { boolean success = false; if (cmt != null && cmt.length() > 0) { COMMENT = cmt; success = true; } return success; } /** * Tells this class what to look in the trace file. This method should be called before the * start of the simulation. * <p/> * By default, this class follows the standard workload format as specified in <a * href="http://www.cs.huji.ac.il/labs/parallel/workload/"> * http://www.cs.huji.ac.il/labs/parallel/workload/</a> <br> * However, you can use other format by calling this method. * <p/> * The parameters must be a positive integer number starting from 1. A special case is where * <tt>jobNum == {@link #IRRELEVANT}</tt>, meaning the job or cloudlet ID will be generate * by the Workload class, instead of reading from the trace file. * * @param maxField max. number of field/column in one row * @param jobNum field/column number for locating the job ID * @param submitTime field/column number for locating the job submit time * @param runTime field/column number for locating the job run time * @param numProc field/column number for locating the number of PEs required to run a job * @return <code>true</code> if successful, <code>false</code> otherwise * @throws IllegalArgumentException if any of the arguments are not within the acceptable ranges * @pre maxField > 0 * @pre submitTime > 0 * @pre runTime > 0 * @pre numProc > 0 * @post $none */ public boolean setField( final int maxField, final int jobNum, final int submitTime, final int runTime, final int numProc) { // need to subtract by 1 since array starts at 0. if (jobNum > 0) { JOB_NUM = jobNum - 1; } else if (jobNum == 0) { throw new IllegalArgumentException("Invalid job number field."); } else { JOB_NUM = -1; } // get the max. number of field if (maxField > 0) { MAX_FIELD = maxField; } else { throw new IllegalArgumentException("Invalid max. number of field."); } // get the submit time field if (submitTime > 0) { SUBMIT_TIME = submitTime - 1; } else { throw new IllegalArgumentException("Invalid submit time field."); } // get the run time field if (runTime > 0) { REQ_RUN_TIME = runTime - 1; } else { throw new IllegalArgumentException("Invalid run time field."); } // get the number of processors field if (numProc > 0) { REQ_NUM_PROC = numProc - 1; } else { throw new IllegalArgumentException("Invalid number of processors field."); } return true; } // ------------------- PRIVATE METHODS ------------------- /** * Creates a Cloudlet with the given information and adds to the list of {@link #jobs}. * * @param id a Cloudlet ID * @param submitTime Cloudlet's submit time * @param runTime The number of seconds the Cloudlet has to run. Considering that * and the {@link #rating}, the {@link Cloudlet#cloudletLength} is computed. * @param numProc number of Cloudlet's PEs * @param reqRunTime user estimated run time * (@todo the parameter is not being used and it is not clear what it is) * @param userID user id * @param groupID user's group id * @pre id >= 0 * @pre submitTime >= 0 * @pre runTime >= 0 * @pre numProc > 0 * @post $none * @see #rating */ private void createJob( final int id, final long submitTime, final int runTime, final int numProc, final int reqRunTime, final int userID, final int groupID) { // create the cloudlet final int len = runTime * rating; UtilizationModel utilizationModel = new UtilizationModelFull(); final Cloudlet wgl = new Cloudlet( id, len, numProc, 0, 0, utilizationModel, utilizationModel, utilizationModel); jobs.add(wgl); } /** * Extracts relevant information from a given array of fields, * representing a line from the trace file, and create a cloudlet * using this information. * * @param array the array of fields generated from a line of the trace file. * @param line the line number * @pre array != null * @pre line > 0 * @todo The name of the method doesn't describe what it in fact does. */ private void extractField(final String[] array, final int line) { try { Integer obj = null; // get the job number int id = 0; if (JOB_NUM == IRRELEVANT) { id = jobs.size() + 1; } else { obj = new Integer(array[JOB_NUM].trim()); id = obj.intValue(); } // get the submit time final Long l = new Long(array[SUBMIT_TIME].trim()); final long submitTime = l.intValue(); // get the user estimated run time obj = new Integer(array[REQ_RUN_TIME].trim()); final int reqRunTime = obj.intValue(); // if the required run time field is ignored, then use // the actual run time obj = new Integer(array[RUN_TIME].trim()); int runTime = obj.intValue(); final int userID = new Integer(array[USER_ID].trim()).intValue(); final int groupID = new Integer(array[GROUP_ID].trim()).intValue(); // according to the SWF manual, runtime of 0 is possible due // to rounding down. E.g. runtime is 0.4 seconds -> runtime = 0 if (runTime <= 0) { runTime = 1; // change to 1 second } // get the number of allocated processors obj = new Integer(array[REQ_NUM_PROC].trim()); int numProc = obj.intValue(); // if the required num of allocated processors field is ignored // or zero, then use the actual field if (numProc == IRRELEVANT || numProc == 0) { obj = new Integer(array[NUM_PROC].trim()); numProc = obj.intValue(); } // finally, check if the num of PEs required is valid or not if (numProc <= 0) { numProc = 1; } createJob(id, submitTime, runTime, numProc, reqRunTime, userID, groupID); } catch (final Exception e) { } } /** * Breaks a line from the trace file into many fields into the * {@link #fieldArray}. * * @param line a line from the trace file * @param lineNum the line number * @pre line != null * @pre lineNum > 0 * @post $none */ private void parseValue(final String line, final int lineNum) { // skip a comment line if (line.startsWith(COMMENT)) { return; } final String[] sp = line.split("\\s+"); // split the fields based on a // space int len = 0; // length of a string int index = 0; // the index of an array // check for each field in the array for (final String elem : sp) { len = elem.length(); // get the length of a string // if it is empty then ignore if (len == 0) { continue; } fieldArray[index] = elem; index++; } if (index == MAX_FIELD) { extractField(fieldArray, lineNum); } } /** * Reads traces from a text file, one line at a time. * * @param fl a file name * @return <code>true</code> if successful, <code>false</code> otherwise. * @throws IOException if the there was any error reading the file * @throws FileNotFoundException if the file was not found */ private boolean readFile(final File fl) throws IOException, FileNotFoundException { boolean success = false; BufferedReader reader = null; try { reader = new BufferedReader(new InputStreamReader(new FileInputStream(fl))); // read one line at the time int line = 1; String readLine = null; while (reader.ready() && (readLine = reader.readLine()) != null) { parseValue(readLine, line); line++; } reader.close(); success = true; } finally { if (reader != null) { reader.close(); } } return success; } /** * Reads traces from a gzip file, one line at a time. * * @param fl a gzip file name * @return <code>true</code> if successful; <code>false</code> otherwise. * @throws IOException if the there was any error reading the file * @throws FileNotFoundException if the file was not found */ private boolean readGZIPFile(final File fl) throws IOException, FileNotFoundException { boolean success = false; BufferedReader reader = null; try { reader = new BufferedReader(new InputStreamReader(new GZIPInputStream(new FileInputStream(fl)))); // read one line at the time int line = 1; String readLine = null; while (reader.ready() && (readLine = reader.readLine()) != null) { parseValue(readLine, line); line++; } reader.close(); success = true; } finally { if (reader != null) { reader.close(); } } return success; } /** * Reads traces from a Zip file, one line at a time. * * @param fl a zip file name * @return <code>true</code> if reading a file is successful; <code>false</code> otherwise. * @throws IOException if the there was any error reading the file */ private boolean readZipFile(final File fl) throws IOException { boolean success = false; ZipFile zipFile = null; try { BufferedReader reader = null; // ZipFile offers an Enumeration of all the files in the file zipFile = new ZipFile(fl); final Enumeration<? extends ZipEntry> e = zipFile.entries(); while (e.hasMoreElements()) { success = false; // reset the value again final ZipEntry zipEntry = e.nextElement(); reader = new BufferedReader(new InputStreamReader(zipFile.getInputStream(zipEntry))); // read one line at the time int line = 1; String readLine = null; while (reader.ready() && (readLine = reader.readLine()) != null) { parseValue(readLine, line); line++; } reader.close(); success = true; } } finally { if (zipFile != null) { zipFile.close(); } } return success; } }