package org.jactr.tools.tracer.sinks.trace.internal; /* * default logging */ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.util.Calendar; import java.util.Collection; import java.util.List; import java.util.zip.GZIPOutputStream; import javolution.util.FastList; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jactr.core.model.ModelTerminatedException; import org.jactr.tools.tracer.transformer.ITransformedEvent; public class TraceFileManager { /** * Logger definition */ static private final transient Log LOGGER = LogFactory .getLog(TraceFileManager.class); private File _outputDirectory; private GZIPOutputStream _gzipOutputStream; private ObjectOutputStream _currentObjectOutputStream; private List<ITransformedEvent> _pendingEvents; final private TraceIndex _traceIndex; private double _recordWindowSpan = 60; // 30sec private double _recordWindow[] = { Double.NaN, Double.MAX_VALUE }; private boolean _longRun = false; private File _currentFile; public TraceFileManager(File outputDirectory) { _pendingEvents = FastList.newInstance(); _traceIndex = new TraceIndex(outputDirectory); _outputDirectory = outputDirectory; _outputDirectory.mkdirs(); initialize(); } private void initialize() { try { _recordWindowSpan = Double.parseDouble(System .getProperty("jactr.sink.archive.windowSize")); } catch (Exception e) { _recordWindowSpan = 60; } try { _longRun = Boolean.parseBoolean(System .getProperty("jactr.sink.archive.longRun")); } catch (Exception e) { _longRun = false; } } synchronized public void open() { // noop _traceIndex.open(); } synchronized public void close() { _traceIndex.close(); if (_currentObjectOutputStream != null) { flush(); closeRecord(); } } synchronized public boolean record(ITransformedEvent event) { boolean createRecord = false; double eventTime = event.getSimulationTime(); if (eventTime >= _recordWindow[1]) try { if (LOGGER.isDebugEnabled()) LOGGER.debug(String.format("new time block @ %.2f", eventTime)); flushInternal(_pendingEvents, _currentObjectOutputStream); closeRecord(); } catch (Exception e) { LOGGER.error("Failed to flush", e); } finally { cleanup(); createRecord = true; } if (Double.isNaN(_recordWindow[0]) || createRecord) { // if (Double.isNaN(_recordWindow[0])) _recordWindow[0] = eventTime; // else // _recordWindow[0] = _recordWindow[1]; _recordWindow[1] = _recordWindow[0] + _recordWindowSpan; // if (LOGGER.isDebugEnabled()) // LOGGER.debug(String.format("Creating new timewindow [%.2f, %.2f]", // _recordWindow[0], _recordWindow[1])); newRecord(); } _pendingEvents.add(event); return true; } protected File createFile() { /* * for now, it's outputDirectory/hour/minute/00.sessionData */ Calendar birthday = Calendar.getInstance(); birthday.setTimeInMillis((long) _recordWindow[0] * 1000); int[] segments = null; if (_longRun) segments = new int[] { Calendar.YEAR, Calendar.DAY_OF_YEAR, Calendar.HOUR_OF_DAY, Calendar.MINUTE }; else segments = new int[] { Calendar.HOUR_OF_DAY, Calendar.MINUTE }; File path = _outputDirectory; for (int segment : segments) { path = new File(path, String.format("%1d", birthday.get(segment))); path.mkdirs(); } File fp = new File(path, String.format("%1d.sessionData", birthday.get(Calendar.SECOND))); if (fp.exists()) { String msg = String .format( "Tracefile %s already exists. This suggests you've got a long running model. Run with -Djactr.sink.archive.longRun=true", fp.getAbsolutePath()); if (LOGGER.isWarnEnabled()) LOGGER.warn(msg); throw new ModelTerminatedException(msg); } if (LOGGER.isDebugEnabled()) LOGGER.debug(String.format( "[%.2f, %.2f] Created new sessionData file %s", _recordWindow[0], _recordWindow[1], fp.getAbsolutePath())); _currentFile = fp; return fp; } private void newRecord() { /* * create the file. */ File indexFile = createFile(); try { _gzipOutputStream = new GZIPOutputStream(new FileOutputStream(indexFile), 4096); _currentObjectOutputStream = new ObjectOutputStream(_gzipOutputStream); _currentObjectOutputStream.writeDouble(_recordWindow[0]); _currentObjectOutputStream.writeDouble(_recordWindow[1]); /* * record the range for the index */ _traceIndex.update(indexFile, _recordWindow); } catch (Exception e) { LOGGER.error("Failed to create new record ", e); closeRecord(); } } private void closeRecord() { if (LOGGER.isDebugEnabled()) LOGGER.debug(String.format("Closing current session")); try { if (_currentObjectOutputStream != null) { _currentObjectOutputStream.flush(); _currentObjectOutputStream.close(); } if (_gzipOutputStream != null) { _gzipOutputStream.finish(); _gzipOutputStream.flush(); _gzipOutputStream.close(); } } catch (Exception e) { LOGGER.error("Failed to clean up", e); } finally { _currentObjectOutputStream = null; _gzipOutputStream = null; _recordWindow[0] = Double.NaN; _recordWindow[1] = Double.MAX_VALUE; } } /** * dump pending to file * * @return true if all is good */ synchronized public boolean flush() { try { _traceIndex.flush(); flushInternal(_pendingEvents, _currentObjectOutputStream); return true; } catch (IOException e) { LOGGER.error("failed to flush ", e); cleanup(); return false; } } private void flushInternal(Collection<ITransformedEvent> pendingEvents, ObjectOutputStream output) throws IOException { try { // if (LOGGER.isDebugEnabled()) // LOGGER // .debug(String.format("Flushing %d events", pendingEvents.size())); for (ITransformedEvent event : pendingEvents) { double eventTime = event.getSimulationTime(); if (eventTime < _recordWindow[0] || eventTime >= _recordWindow[1]) if (LOGGER.isWarnEnabled()) LOGGER.warn(String.format( "%s (@ %.2f) is outside of current record window (%.2f, %.2f)", event.getClass().getName(), eventTime, _recordWindow[0], _recordWindow[1])); output.writeObject(event); // if (event instanceof StringTableMessage && LOGGER.isDebugEnabled()) // LOGGER.debug(String.format("Wrote (%.2f) %s to [%.2f, %.2f] : %s", // event.getSimulationTime(), event, _recordWindow[0], // _recordWindow[1], _currentFile.getCanonicalPath())); } if (output != null) output.flush(); // _gzipOutputStream.finish(); } finally { pendingEvents.clear(); } } private void cleanup() { closeRecord(); } }