/*- ******************************************************************************* * Copyright (c) 2011, 2016 Diamond Light Source Ltd. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Matthew Gerring - initial API and implementation and/or initial documentation *******************************************************************************/ package org.eclipse.dawnsci.nexus.test; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertThat; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; import org.eclipse.dawnsci.analysis.api.tree.DataNode; import org.eclipse.dawnsci.analysis.api.tree.TreeFile; import org.eclipse.dawnsci.hdf5.HDF5FileFactory; import org.eclipse.dawnsci.hdf5.nexus.NexusFileFactoryHDF5; import org.eclipse.dawnsci.nexus.NXdata; import org.eclipse.dawnsci.nexus.NXdetector; import org.eclipse.dawnsci.nexus.NXentry; import org.eclipse.dawnsci.nexus.NXinstrument; import org.eclipse.dawnsci.nexus.NXobject; import org.eclipse.dawnsci.nexus.NXpositioner; import org.eclipse.dawnsci.nexus.NXroot; import org.eclipse.dawnsci.nexus.NexusBaseClass; import org.eclipse.dawnsci.nexus.NexusException; import org.eclipse.dawnsci.nexus.NexusNodeFactory; import org.eclipse.dawnsci.nexus.ServiceHolder; import org.eclipse.dawnsci.nexus.TestUtils; import org.eclipse.dawnsci.nexus.builder.AbstractNexusObjectProvider; import org.eclipse.dawnsci.nexus.builder.NexusEntryBuilder; import org.eclipse.dawnsci.nexus.builder.NexusFileBuilder; import org.eclipse.dawnsci.nexus.builder.NexusScanFile; import org.eclipse.dawnsci.nexus.builder.data.NexusDataBuilder; import org.eclipse.dawnsci.nexus.builder.impl.DefaultNexusFileBuilder; import org.eclipse.dawnsci.nexus.test.util.NexusTestUtils; import org.eclipse.january.dataset.Dataset; import org.eclipse.january.dataset.DatasetFactory; import org.eclipse.january.dataset.IDataset; import org.eclipse.january.dataset.ILazyDataset; import org.eclipse.january.dataset.ILazyWriteableDataset; import org.eclipse.january.dataset.IntegerDataset; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; public class MultipleThreadNexusFileWriteTest { private static abstract class AbstractTestDevice<N extends NXobject> extends AbstractNexusObjectProvider<N> implements Callable<Boolean> { private int nextStepNumber = 0; private boolean initialized = false; private long stepTime; private int numSteps; private Exception exception = null; public AbstractTestDevice(String name, NexusBaseClass nexusBaseClass, String defaultDatasetName) { super(name, nexusBaseClass, defaultDatasetName); } public void initializeScan(final long stepTime, final int numSteps) { this.stepTime = stepTime; this.numSteps = numSteps; initialized = true; } protected void doStep() throws Exception { Thread.sleep(stepTime); writeNewData(nextStepNumber++); } protected abstract void writeNewData(int stepNumber) throws Exception; @Override public Boolean call() throws Exception { if (!initialized) { throw new IllegalStateException("device " + getName() + " not initialized"); } try { for (int i = 0; i < numSteps; i++) { doStep(); } return Boolean.TRUE; } catch (final Exception e) { System.err.println("Exception in device " + getName() + ": " + e.getMessage()); e.printStackTrace(); exception = e; return Boolean.FALSE; } } public Exception getException() { return exception; } } public static class TestDetector extends AbstractTestDevice<NXdetector> implements Callable<Boolean> { private final int numRows; private final int numColumns; public TestDetector(int rows, int columns) { super("detector", NexusBaseClass.NX_DETECTOR, NXdata.NX_DATA); this.numRows = rows; this.numColumns = columns; } @Override protected NXdetector createNexusObject() { final NXdetector detector = NexusNodeFactory.createNXdetector(); final ILazyWriteableDataset dataset = detector.initializeLazyDataset( NXdetector.NX_DATA, 3, Integer.class); dataset.setMaxShape(new int[] { ILazyWriteableDataset.UNLIMITED, numRows, numColumns }); return detector; } @Override protected void writeNewData(int stepNumber) throws Exception { final ILazyWriteableDataset dataset = getWriteableDataset(NXdetector.NX_DATA); final int[] startPos = new int[] { stepNumber, 0, 0 }; final int[] stopPos = new int[] { stepNumber + 1, numRows, numColumns }; final IDataset newData = createNewData(); dataset.setSlice(null, newData, startPos, stopPos, null); } private IDataset createNewData() { final IntegerDataset dataset = DatasetFactory.zeros(IntegerDataset.class, numColumns, numRows); final ThreadLocalRandom random = ThreadLocalRandom.current(); for (int rowNum = 0; rowNum < numRows; rowNum++) { for (int columnNum = 0; columnNum < numColumns; columnNum++) { dataset.setItem(random.nextInt(), rowNum, columnNum); } } return dataset; } } public static class TestPositioner extends AbstractTestDevice<NXpositioner> { private double[] testData; public TestPositioner(String name) { super(name, NexusBaseClass.NX_POSITIONER, NXpositioner.NX_VALUE); } @Override protected NXpositioner createNexusObject() { final NXpositioner positioner = NexusNodeFactory.createNXpositioner(); positioner.initializeLazyDataset(NXpositioner.NX_VALUE, 1, Double.class); return positioner; } @Override public void initializeScan(final long stepTime, final int numSteps) { final ThreadLocalRandom random = ThreadLocalRandom.current(); testData = new double[numSteps]; for (int i = 0; i < testData.length; i++) { testData[i] = random.nextDouble(0, Double.MAX_VALUE); } super.initializeScan(stepTime, numSteps); } @Override protected void writeNewData(int stepNumber) throws Exception { final ILazyWriteableDataset dataset = getWriteableDataset(NXpositioner.NX_VALUE); final int[] startPos = new int[] { stepNumber }; final int[] stopPos = new int[] { stepNumber + 1 }; final Dataset newData = DatasetFactory.createFromObject(testData[stepNumber]); dataset.setSlice(null, newData, startPos, stopPos, null); } } private static final int DETECTOR_ROWS = 1024; private static final int DETECTOR_COLUMNS = 1024; private static String testScratchDirectoryName; private TestDetector detector; private List<TestPositioner> positioners; @Before public void setUp() throws Exception { testScratchDirectoryName = TestUtils.generateDirectorynameFromClassname(getClass().getCanonicalName()); TestUtils.makeScratchDirectory(testScratchDirectoryName); ServiceHolder.setNexusFileFactory(new NexusFileFactoryHDF5()); } private List<TestPositioner> createPositioners(int numPositioners) { final List<TestPositioner> positioners = new ArrayList<>(numPositioners); for (int i = 0; i < numPositioners; i++) { positioners.add(new TestPositioner(String.format("pos%03d", i))); } return positioners; } String makeFileName(boolean async, final int numPositioners) { String s = async ? "Async" : "Sync"; return testScratchDirectoryName + "test" + numPositioners + s + "Positioners.nxs"; } private NexusScanFile createNexusFile(boolean async, final int numPositioners) throws NexusException { String fileName = makeFileName(async, numPositioners); NexusFileBuilder fileBuilder = new DefaultNexusFileBuilder(fileName); final NexusEntryBuilder entryBuilder = fileBuilder.newEntry(); entryBuilder.addDefaultGroups(); detector = new TestDetector(DETECTOR_ROWS, DETECTOR_COLUMNS); positioners = createPositioners(numPositioners); entryBuilder.add(detector); entryBuilder.addAll(positioners); final NexusDataBuilder dataBuilder = entryBuilder.createDefaultData(); dataBuilder.setPrimaryDevice(detector); for (TestPositioner positioner : positioners) { dataBuilder.addAxisDevice(positioner); } return fileBuilder.createFile(async); } private void initializeDevices(final long stepTime, final int numSteps) { detector.initializeScan(stepTime, numSteps); for (final TestPositioner positioner : positioners) { positioner.initializeScan(stepTime, numSteps); } } private void runThreads(int numPositioners, final int numSteps, long timeout) throws Exception { // run the devices final int numDevices = numPositioners + 1; final ExecutorService executors = Executors.newFixedThreadPool(numDevices); final List<AbstractTestDevice<?>> devices = new ArrayList<>(numDevices); devices.add(detector); devices.addAll(positioners); final List<Future<Boolean>> results = executors.invokeAll(devices, timeout, TimeUnit.MILLISECONDS); // check that all devices completed normally for (int i = 0; i < results.size(); i++) { final AbstractTestDevice<?> device = devices.get(i); final Future<Boolean> result = results.get(i); if (!result.isDone()) { // not actually possible as isDone true even if invokeAll times out throw new RuntimeException("Device " + device.getName() + " has not completed"); } if (Boolean.FALSE.equals(result.get(0, TimeUnit.MILLISECONDS))) { if (device.getException() != null) { throw new RuntimeException("Device " + device.getName() + " threw an exception.", device.getException()); } } } } private void checkFile(String filePath, int numPositioners, int numSteps) throws NexusException { final TreeFile file = NexusTestUtils.loadNexusFile(filePath, true); final NXroot root = (NXroot) file.getGroupNode(); final NXentry entry = root.getEntry(); final NXinstrument instrument = entry.getInstrument(); final Collection<NXpositioner> positioners = instrument.getAllPositioner().values(); assertThat(positioners.size(), is(equalTo(numPositioners))); int[] expectedShape = { numSteps }; for (final NXpositioner positioner : positioners) { final IDataset valueDataset = positioner.getValue(); assertThat(valueDataset, is(notNullValue())); assertArrayEquals(expectedShape, valueDataset.getShape()); } final NXdetector detector = instrument.getDetector(); assertThat(detector, is(notNullValue())); final DataNode detectorData = detector.getDataNode(NXdetector.NX_DATA); assertThat(detectorData, is(notNullValue())); final ILazyDataset detectorDataset = detectorData.getDataset(); assertThat(detectorDataset, is(notNullValue())); expectedShape = new int[] { numSteps, DETECTOR_ROWS, DETECTOR_COLUMNS }; assertArrayEquals(expectedShape, detectorDataset.getShape()); } public void doTestMultiplePositioners(boolean async, final int numPositioners, final int numSteps, final long stepTime) throws Exception { String filePath = makeFileName(async, numPositioners); NexusScanFile nexusFile = createNexusFile(async, numPositioners); initializeDevices(stepTime, numSteps); nexusFile.openToWrite(); // we need quite a long timeout so as not to fail on jenkins when there are other jobs running final long timeout = (numPositioners * 50) + (numSteps + 1) * stepTime * 2; System.err.println("Timeout = " + timeout); long startTime = System.currentTimeMillis(); runThreads(numPositioners, numSteps, timeout); nexusFile.close(); checkFile(filePath, numPositioners, numSteps); HDF5FileFactory.releaseFile(filePath, true); long elapsedTime = System.currentTimeMillis() - startTime; System.err.println("Took " + elapsedTime + "ms, timeout was " + timeout + "ms"); } public void doTestNPositionersSync(final int numPositioners) throws Exception { doTestMultiplePositioners(false, numPositioners, 100, 100); } public void doTestNPositionersAsync(final int numPositioners) throws Exception { doTestMultiplePositioners(true, numPositioners, 100, 100); } @Test public void test2PositionersSync() throws Exception { doTestNPositionersSync(2); } @Test public void test20PositionersSync() throws Exception { doTestNPositionersSync(20); } @Test public void test200PositionersSync() throws Exception { doTestNPositionersSync(200); } @Test public void test2PositionersAsync() throws Exception { doTestNPositionersAsync(2); } @Test public void test20PositionersAsync() throws Exception { doTestNPositionersAsync(20); } @Test public void test200PositionersAsync() throws Exception { doTestNPositionersAsync(200); } @Test @Ignore // this test times out most times public void test500PositionersAsync() throws Exception { doTestNPositionersAsync(500); } }