/* * Sun Public License * * The contents of this file are subject to the Sun Public License Version * 1.0 (the "License"). You may not use this file except in compliance with * the License. A copy of the License is available at http://www.sun.com/ * * The Original Code is the SLAMD Distributed Load Generation Engine. * The Initial Developer of the Original Code is Neil A. Wilson. * Portions created by Neil A. Wilson are Copyright (C) 2004-2010. * Some preexisting portions Copyright (C) 2002-2006 Sun Microsystems, Inc. * All Rights Reserved. * * Contributor(s): Neil A. Wilson */ package com.slamd.stat; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; import com.slamd.asn1.ASN1Writer; import com.slamd.client.ClientSideJob; /** * This class defines a thread that may be used to periodically save statistical * information on the client system while a job is in progress so that any * data it has collected so far will be preserved so that at least some results * may be provided. * * * @author Neil A. Wilson */ public class StatPersistenceThread extends Thread { /** * The length of time in milliseconds to sleep between polls to determine if * it is time to write the data out. */ public static final int POLL_INTERVAL = 5000; // The list of statistics being captured by this thread. private ArrayList<StatTracker> statList; // The job with which this persistence thread is currently associated. private ClientSideJob job; // The interval in seconds between saves of the job data. private int saveInterval; // The mutex used to provide threadsafe access to the stat list. private final Object statListMutex; // The unique ID assigned to the client with which this stat persistence // thread is associated. private String clientID; // The path to the directory into which the persistence data should be saved. private String saveDirectory; /** * Creates a new instance of this stat persistence thread with the provided * information. * * @param clientID The unique ID assigned to the client with which this * stat persistence thread is associated. It should * be unique among multiple clients on the same system. * @param saveDirectory The path to the directory into which the persistence * data should be saved. * @param saveInterval The interval in seconds between saves of job data. * * @throws IOException If the save directory does not exist and cannot be * created, or if it exists but is not a directory. */ public StatPersistenceThread(String clientID, String saveDirectory, int saveInterval) throws IOException { this.clientID = clientID; this.saveDirectory = saveDirectory; this.saveInterval = saveInterval; statList = new ArrayList<StatTracker>(); statListMutex = new Object(); job = null; setName("Stat Persistence Thread"); setDaemon(true); try { File saveDirFile = new File(saveDirectory); if (! saveDirFile.exists()) { if (! saveDirFile.mkdirs()) { throw new IOException("Save directory \"" + saveDirectory + "\" does not exist and could not be created."); } } else { if (! saveDirFile.isDirectory()) { throw new IOException("Save directory \"" + saveDirectory + "\" exists but is not a directory."); } } } catch (Exception e) { throw new IOException("Unable to determine whether save directory " + "exists: " + e); } } /** * Specifies the job with which this persistence thread should be used. * * @param job The job with which this persistence thread should be used. */ public void setJob(ClientSideJob job) { this.job = job; statList.clear(); } /** * Registers the provided stat tracker with the persistence thread. * * @param tracker The stat tracker to register. */ public void registerTracker(StatTracker tracker) { synchronized (statListMutex) { statList.add(tracker); } } /** * Indicates that the job has completed and that any remaining data should be * written to disk and the associated stat trackers discarded. * * @throws IOException If a problem occurs while writing the data to disk. */ public void jobDone() throws IOException { synchronized (statListMutex) { String jobID = job.getJobID(); job = null; if (statList.isEmpty()) { return; } StatTracker[] trackers = new StatTracker[statList.size()]; statList.toArray(trackers); statList.clear(); String fileName = saveDirectory + File.separator + clientID + "." + jobID; fileName = fileName.replace(':', '_'); File statFile = new File(fileName + ".temp"); OutputStream outputStream = new FileOutputStream(statFile, false); ASN1Writer writer = new ASN1Writer(outputStream); writer.writeElement(StatEncoder.trackersToSequence(trackers)); writer.close(); outputStream.close(); File origFile = new File(fileName); if (origFile.exists() && (! origFile.delete())) { job.logMessage("Stat Persistence Thread", "Unable to delete current persistent stat file " + fileName); } if (! statFile.renameTo(new File(fileName))) { throw new IOException("Unable to rename temporary file \"" + statFile.getAbsolutePath() + "\" to desired name"); } } } /** * Periodically writes any statistics collected for the active job to disk. */ public void run() { boolean jobActive = false; long nextCaptureTime = 0; // Create a loop that will wait for a job to become available, and then // once it does will periodically write statistical data for that job to // disk. while (true) { if (job == null) { // There is no job running right now, so we don't need to do anything. jobActive = false; } else { if (! jobActive) { // We didn't think that there was a job running, which means that we // need to reset the next capture time. jobActive = true; nextCaptureTime = System.currentTimeMillis() + (1000 * saveInterval); } else { // Check to see if it's time to save the data yet. if (System.currentTimeMillis() >= nextCaptureTime) { synchronized (statListMutex) { if (! statList.isEmpty()) { StatTracker[] trackers = new StatTracker[statList.size()]; statList.toArray(trackers); try { String fileName = saveDirectory + File.separator + clientID + "." + job.getJobID(); fileName = fileName.replace(':', '_'); File statFile = new File(fileName + ".temp"); OutputStream outputStream = new FileOutputStream(statFile, false); ASN1Writer writer = new ASN1Writer(outputStream); writer.writeElement(StatEncoder.trackersToSequence(trackers)); writer.close(); outputStream.close(); File origFile = new File(fileName); if (origFile.exists() && (! origFile.delete())) { job.logMessage("Stat Persistence Thread", "Unable to delete current persistent " + "stat file " + fileName); } if (! statFile.renameTo(new File(fileName))) { job.logMessage("Stat Persistence Thread", "Unable to rename temporary file \"" + statFile.getAbsolutePath() + "\" to desired name"); } } catch (Exception e) { job.logMessage("Stat Persistence Thread", "Unable to write statistical data to a " + "temporary file -- " + e); } } } nextCaptureTime = System.currentTimeMillis() + (1000 * saveInterval); } } } // At this point, we just a little bit and check again. try { Thread.sleep(POLL_INTERVAL); } catch (Exception e) {} } } }