package org.epics.archiverappliance.engine.test; import static org.junit.Assert.assertTrue; import java.io.IOException; import java.util.HashMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import org.apache.log4j.Logger; import org.epics.archiverappliance.Event; import org.epics.archiverappliance.common.BasicContext; import org.epics.archiverappliance.common.TimeUtils; import org.epics.archiverappliance.config.ArchDBRTypes; import org.epics.archiverappliance.engine.membuf.ArrayListEventStream; import org.epics.archiverappliance.engine.model.SampleBuffer; import org.epics.archiverappliance.engine.pv.PVMetrics; import org.epics.archiverappliance.retrieval.channelarchiver.HashMapEvent; import org.junit.After; import org.junit.Before; import org.junit.Test; /** * A small test to test the double buffering algorithm we use. * We reuse the SampleBuffer; but simulate the writer using the same approach that the WriterRunnable uses. * @author mshankar * */ public class DoubleBufferTest { private static Logger logger = Logger.getLogger(DoubleBufferTest.class.getName()); private int eventsAdded = 0; private int eventsStored = 0; @Before public void setUp() throws Exception { } @After public void tearDown() throws Exception { } enum StoredState { ALL_STORED, SOME_LOSS, MAYBE_SOME_LOSS}; @Test public void testDoubleBuffering() throws Exception { testSampleBufferDoubleBufferingForBufferSize(7, StoredState.SOME_LOSS); testSampleBufferDoubleBufferingForBufferSize(8, StoredState.SOME_LOSS); testSampleBufferDoubleBufferingForBufferSize(9, StoredState.SOME_LOSS); testSampleBufferDoubleBufferingForBufferSize(10, StoredState.MAYBE_SOME_LOSS); testSampleBufferDoubleBufferingForBufferSize(11, StoredState.ALL_STORED); testSampleBufferDoubleBufferingForBufferSize(12, StoredState.ALL_STORED); testSampleBufferDoubleBufferingForBufferSize(13, StoredState.ALL_STORED); testSampleBufferDoubleBufferingForBufferSize(14, StoredState.ALL_STORED); testSampleBufferDoubleBufferingForBufferSize(15, StoredState.ALL_STORED); testSampleBufferDoubleBufferingForBufferSize(16, StoredState.ALL_STORED); testSampleBufferDoubleBufferingForBufferSize(17, StoredState.ALL_STORED); } private void testSampleBufferDoubleBufferingForBufferSize(int bufferSize, StoredState allStored) throws Exception { logger.info("Testing for buffer size " + bufferSize); this.eventsAdded = 0; this.eventsStored = 0; SampleBuffer buffer = new SampleBuffer("TestSampleBuffer", bufferSize, ArchDBRTypes.DBR_SCALAR_DOUBLE, new PVMetrics("TestSampleBuffer", null, -1, ArchDBRTypes.DBR_SCALAR_DOUBLE)); ScheduledExecutorService dataGen = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() { @Override public Thread newThread(Runnable r) { Thread t = new Thread(r, "Data generator"); return t; } }); dataGen.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { HashMap<String, Object> eventAttrs = new HashMap<String, Object>(); eventAttrs.put(HashMapEvent.SECS_FIELD_NAME, Long.toString(TimeUtils.getCurrentEpochSeconds())); eventAttrs.put(HashMapEvent.NANO_FIELD_NAME, "0"); eventAttrs.put(HashMapEvent.VALUE_FIELD_NAME, TimeUtils.getCurrentEpochSeconds()); buffer.add(new HashMapEvent(ArchDBRTypes.DBR_SCALAR_DOUBLE, eventAttrs)); eventsAdded++; } catch(Exception ex) { ex.printStackTrace(); } } }, 0, 1, TimeUnit.MILLISECONDS); ScheduledExecutorService writer = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() { @Override public Thread newThread(Runnable r) { Thread t = new Thread(r, "Writer"); return t; } }); writer.scheduleAtFixedRate(new Runnable() { @Override public void run() { buffer.resetSamples(); ArrayListEventStream previousSamples = buffer.getPreviousSamples(); try (BasicContext basicContext = new BasicContext()) { if (previousSamples.size() > 0) { for(@SuppressWarnings("unused") Event event : previousSamples) { eventsStored++; } } } catch (IOException e) { logger.error("Exception saving data in test", e); } } }, 10, 10, TimeUnit.MILLISECONDS); Thread.currentThread().sleep(1*1000); dataGen.shutdown(); Thread.currentThread().sleep(1*1000); writer.shutdown(); Thread.currentThread().sleep(1*1000); assertTrue("No data generated for event count " + eventsAdded + " and stored events " + eventsStored, eventsAdded> 0 && eventsStored > 0); if(allStored == StoredState.ALL_STORED) { assertTrue("Generated event count " + eventsAdded + " and stored events " + eventsStored + " are not the same for buffer size " + bufferSize, eventsAdded == eventsStored); } else if(allStored == StoredState.SOME_LOSS) { assertTrue("Generated event count " + eventsAdded + " and stored events " + eventsStored + " must have some loss for buffer size " + bufferSize, eventsAdded > eventsStored); } } }