package org.epics.archiverappliance.retrieval.mimeresponses; import java.io.IOException; import java.io.OutputStream; import java.nio.channels.Channels; import java.nio.channels.WritableByteChannel; import java.sql.Timestamp; import java.util.HashMap; import java.util.LinkedList; import org.apache.log4j.Logger; import org.epics.archiverappliance.Event; import org.epics.archiverappliance.EventStream; import org.epics.archiverappliance.EventStreamDesc; import org.epics.archiverappliance.common.TimeUtils; import org.epics.archiverappliance.config.ArchDBRTypes; import org.epics.archiverappliance.data.SampleValue; import org.epics.archiverappliance.engine.membuf.ArrayListEventStream; import org.epics.archiverappliance.retrieval.RemotableEventStreamDesc; import com.jmatio.io.MatFileWriter; import com.jmatio.types.MLArray; import com.jmatio.types.MLChar; import com.jmatio.types.MLDouble; import com.jmatio.types.MLStructure; import com.jmatio.types.MLUInt64; import com.jmatio.types.MLUInt8; /** * Generate a ".mat" matlab file * The response contains two objects, a header and a data object. * The header object is indexed by the string <code>header</code> and is a MLStructure with fields for pvname, start and end times etc. * The data object is indexed by the string <code>data</code> and is a MLStructure with these fields. * <ol> * <li><code>epochSeconds</code> - contains Java epoch seconds as a 1x1 uint64 array. The times are in UTC; so any conversion to local time needs to happen at the client.</li> * <li><code>values</code> - contains the values for the samples. All scalars come as a 1x1 double array. Waveforms come as a 1x<i>elementcount</i> double array where <i>elementcount</i> is the EPICS element count of the waveform.</li> * <li><code>nanos</code> - contains the nano second value of the EPICS record processing timestamp as a 1x1 uint64 array. Some installations embed the beam code/pulse id into this field; however, this is done at the IOC side and as far as this code are concerned, this is the nanoseconds.</li> * <li><code>isDST</code> - contains booleans that indicate if the time indicated by <code>epochSeconds</code> was in daylight savings time in the timezone of the server. This is an attempt to get around the deficiencies in Matlab w.r.t timezones.</li> * </ol> * * If needed, we can add separate fields for status and severity. * * Owing to the column-major nature, this response consumes a lot of memory. * If we are serving large datasets using Matlab responses, we should increase the heap size for the retrieval war to large values. * * @author mshankar * */ public class MatlabResponse implements MimeResponse { private static Logger logger = Logger.getLogger(MatlabResponse.class.getName()); private MLStructure headerStruct = new MLStructure("header", new int[] {1, 1}); private MLStructure dataStruct = new MLStructure("data", new int[] {1, 1}); private WritableByteChannel channel = null; private ArrayListEventStream dest = null; private boolean typesSet = false; private ArchDBRTypes dbrType = ArchDBRTypes.DBR_SCALAR_DOUBLE; private int elementCount = 1; @Override public void consumeEvent(Event e) throws Exception { dest.add(e.makeClone()); if(!typesSet) { typesSet = true; dbrType = e.getDBRType(); elementCount = e.getSampleValue().getElementCount(); } } @Override public void setOutputStream(OutputStream os) { channel = Channels.newChannel(os); } @Override public void processingPV(String pv, Timestamp start, Timestamp end, EventStreamDesc streamDesc) { headerStruct.setField("source", new MLChar("source", "Archiver appliance")); headerStruct.setField("pvName", new MLChar("pvName", pv)); headerStruct.setField("from", new MLChar("from", TimeUtils.convertToISO8601String(start))); headerStruct.setField("to", new MLChar("to", TimeUtils.convertToISO8601String(end))); dest = new ArrayListEventStream(0, new RemotableEventStreamDesc(streamDesc.getArchDBRType(), pv, TimeUtils.computeYearForEpochSeconds(TimeUtils.convertToEpochSeconds(start)))); } @Override public void swicthingToStream(EventStream strm) { // Not much to do here for now. } @Override public void close() { try { dataStruct.setField("epochSeconds", new GenTimeArray().generateColumn(dest)); dataStruct.setField("values", new GenValueArray().generateColumn(dest)); dataStruct.setField("nanos", new GenNanosArray().generateColumn(dest)); dataStruct.setField("isDST", new GenisDSTArray().generateColumn(dest)); LinkedList<MLArray> dataList = new LinkedList<MLArray>(); dataList.add(headerStruct); dataList.add(dataStruct); new MatFileWriter(channel, dataList); } catch (IOException ex) { if(ex != null && ex.toString() != null && ex.toString().contains("ClientAbortException")) { // We check for ClientAbortException etc this way to avoid including tomcat jars in the build path. // This is thrown if the client closes abruptly - that is, if Matlab crashes. logger.debug("Exception generating matlab file", ex); } else { logger.error("Exception generating matlab file", ex); } } finally { try { channel.close(); channel = null; } catch (Exception ex) { } } } private interface GenMLArray { public MLArray generateColumn(ArrayListEventStream dest); } private class GenTimeArray implements GenMLArray { public MLArray generateColumn(ArrayListEventStream dest) { MLUInt64 ret = new MLUInt64("epochSeconds", new int[] {dest.size(), 1} ); int i = 0; for(Event e : dest) { ret.set(e.getEpochSeconds(), i++); } return ret; } } private class GenisDSTArray implements GenMLArray { public MLArray generateColumn(ArrayListEventStream dest) { MLUInt8 ret = new MLUInt8("isDST", new int[] {dest.size(), 1} ); int i = 0; for(Event e : dest) { ret.set(TimeUtils.isDST(e.getEventTimeStamp()) ? (byte) 1 : (byte) 0, i++); } return ret; } } private class GenValueArray implements GenMLArray { public MLArray generateColumn(ArrayListEventStream dest) { if(dbrType.isWaveForm()) { MLDouble ret = new MLDouble("values", new int[] {dest.size(), elementCount} ); int i = 0; for(Event e : dest) { SampleValue sampleValue = e.getSampleValue(); for(int col = 0; col < sampleValue.getElementCount(); col++) { ret.set(sampleValue.getValue(col).doubleValue(), i, col); } i++; } return ret; } else { MLDouble ret = new MLDouble("values", new int[] {dest.size(), 1} ); int i = 0; for(Event e : dest) { ret.set(e.getSampleValue().getValue().doubleValue(), i++); } return ret; } } } private class GenNanosArray implements GenMLArray { public MLArray generateColumn(ArrayListEventStream dest) { MLUInt64 ret = new MLUInt64("nanos", new int[] {dest.size(), 1} ); int i = 0; for(Event e : dest) { ret.set((long) e.getEventTimeStamp().getNanos(), i++); } return ret; } } @Override public HashMap<String, String> getExtraHeaders() { return null; } }