/**
* Copyright French Prime minister Office/SGMAP/DINSIC/Vitam Program (2015-2019)
*
* contact.vitam@culture.gouv.fr
*
* This software is a computer program whose purpose is to implement a digital archiving back-office system managing
* high volumetry securely and efficiently.
*
* This software is governed by the CeCILL 2.1 license under French law and abiding by the rules of distribution of free
* software. You can use, modify and/ or redistribute the software under the terms of the CeCILL 2.1 license as
* circulated by CEA, CNRS and INRIA at the following URL "http://www.cecill.info".
*
* As a counterpart to the access to the source code and rights to copy, modify and redistribute granted by the license,
* users are provided only with a limited warranty and the software's author, the holder of the economic rights, and the
* successive licensors have only limited liability.
*
* In this respect, the user's attention is drawn to the risks associated with loading, using, modifying and/or
* developing or reproducing the software by the user in light of its specific status of free software, that may mean
* that it is complicated to manipulate, and that also therefore means that it is reserved for developers and
* experienced professionals having in-depth computer knowledge. Users are therefore encouraged to load and test the
* software's suitability as regards their requirements in conditions enabling the security of their systems and/or data
* to be ensured and, more generally, to use and operate it in the same conditions as regards security.
*
* The fact that you are presently reading this means that you have had knowledge of the CeCILL 2.1 license and that you
* accept its terms.
*/
package fr.gouv.vitam.common.stream;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.TreeMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import fr.gouv.vitam.common.VitamConfiguration;
import fr.gouv.vitam.common.junit.FakeInputStream;
import fr.gouv.vitam.common.logging.VitamLogger;
import fr.gouv.vitam.common.logging.VitamLoggerFactory;
/**
* Tes for MultipleInputStreamHandler
*/
public class MultipleInputStreamHandlerTest {
private static final VitamLogger LOGGER = VitamLoggerFactory.getInstance(MultipleInputStreamHandlerTest.class);
private static final int INPUTSTREAM_SIZE = 65536 * 4;
private static final TreeMap<Long, String> TIMES = new TreeMap<>();
@BeforeClass
public static void setUpBeforeClass() throws Exception {
}
@AfterClass
public static void endingClass() {
final StringBuilder builder = new StringBuilder("Time Results:");
TIMES.forEach(new BiConsumer<Long, String>() {
@Override
public void accept(Long t, String u) {
builder.append("\n\t").append(u);
}
});
LOGGER.warn(builder.toString());
}
private void addTimer(long time, String result) {
while (TIMES.containsKey(time)) {
time++;
}
TIMES.put(time, time + " = " + result);
}
@Test
public void badInitialization() {
LOGGER.warn("start bad initialization Pool {}", MultipleInputStreamHandler.getPoolAvailability());
try (MultipleInputStreamHandler mish = new MultipleInputStreamHandler(null, 1)) {
fail("Should raized illegal argument");
} catch (final IllegalArgumentException e) {
// nothing
}
try (FakeInputStream fakeInputStream = new FakeInputStream(INPUTSTREAM_SIZE, true);
MultipleInputStreamHandler mish = new MultipleInputStreamHandler(fakeInputStream, 0)) {
fail("Should raized illegal argument");
} catch (final IllegalArgumentException e) {
// nothing
}
try (FakeInputStream fakeInputStream = new FakeInputStream(INPUTSTREAM_SIZE, true);
MultipleInputStreamHandler mish = new MultipleInputStreamHandler(fakeInputStream, 1)) {
mish.getInputStream(-1);
fail("Should raized illegal argument");
} catch (final IllegalArgumentException e) {
// nothing
}
try (FakeInputStream fakeInputStream = new FakeInputStream(INPUTSTREAM_SIZE, true);
MultipleInputStreamHandler mish = new MultipleInputStreamHandler(fakeInputStream, 1)) {
mish.getInputStream(1);
fail("Should raized illegal argument");
} catch (final IllegalArgumentException e) {
// ignore
}
LOGGER.warn("end bad initialization Pool {}", MultipleInputStreamHandler.getPoolAvailability());
}
@Ignore //@Test
public void testMultipleInputStreamHandlerSingle() {
final long start = System.nanoTime();
LOGGER.warn("start testMultipleInputStreamHandlerSingle Pool {}", MultipleInputStreamHandler.getPoolAvailability());
try (FakeInputStream fakeInputStream = new FakeInputStream(INPUTSTREAM_SIZE, true);
MultipleInputStreamHandler mish = new MultipleInputStreamHandler(fakeInputStream, 1)) {
assertNotNull(mish.toString());
final InputStream is = mish.getInputStream(0);
long total = 0;
assertNotNull(is.toString());
while (is.read() >= 0) {
total++;
}
assertEquals(INPUTSTREAM_SIZE, total);
} catch (final IOException e) {
fail("Should not raized an exception: " + e.getMessage());
}
final long stop = System.nanoTime();
LOGGER.debug("Read 1: \t{} ns", stop - start);
LOGGER.warn("end testMultipleInputStreamHandlerSingle Pool {}", MultipleInputStreamHandler.getPoolAvailability());
addTimer(stop - start, "SINGLE_BYTE :\t" + (stop - start));
}
private void testMultipleInputStreamHandlerBlock(int size) {
final long start = System.nanoTime();
try (FakeInputStream fakeInputStream = new FakeInputStream(INPUTSTREAM_SIZE, true);
MultipleInputStreamHandler mish = new MultipleInputStreamHandler(fakeInputStream, 1)) {
final InputStream is = mish.getInputStream(0);
int read;
long total = 0;
final byte[] buffer = new byte[size];
while ((read = is.read(buffer)) > 0) {
total += read;
}
assertEquals(INPUTSTREAM_SIZE, total);
} catch (final IOException e) {
fail("Should not raized an exception: " + e.getMessage());
}
final long stop = System.nanoTime();
LOGGER.debug("Read {}: \t{} ns", size, stop - start);
addTimer(stop - start, "SINGLE_BLOCK_" + size + " :\t" + (stop - start));
}
@Ignore //@Test
public void testMultipleInputStreamHandlerBlock() {
LOGGER.warn("start testMultipleInputStreamHandlerBlock Pool {}", MultipleInputStreamHandler.getPoolAvailability());
testMultipleInputStreamHandlerBlock(100);
testMultipleInputStreamHandlerBlock(512);
testMultipleInputStreamHandlerBlock(1024);
testMultipleInputStreamHandlerBlock(4000);
testMultipleInputStreamHandlerBlock(8192);
testMultipleInputStreamHandlerBlock(40000);
testMultipleInputStreamHandlerBlock(65536);
testMultipleInputStreamHandlerBlock(80000);
testMultipleInputStreamHandlerBlock(100000);
LOGGER.warn("stop testMultipleInputStreamHandlerBlock Pool {}", MultipleInputStreamHandler.getPoolAvailability());
}
@Ignore //@Test
public void testMultipleInputStreamHandlerMultipleShift8K() {
final long start = System.nanoTime();
final int size = 8192;
final int nb = 10;
LOGGER.warn("start testMultipleInputStreamHandlerMultipleShift8K Pool {}", MultipleInputStreamHandler.getPoolAvailability());
try (FakeInputStream fakeInputStream = new FakeInputStream(INPUTSTREAM_SIZE, true);
MultipleInputStreamHandler mish = new MultipleInputStreamHandler(fakeInputStream, nb)) {
final InputStream[] is = new InputStream[nb];
final long[] total = new long[nb];
for (int i = 0; i < nb; i++) {
is[i] = mish.getInputStream(i);
total[i] = 0;
}
int read;
final byte[] buffer = new byte[size];
int rank = 1;
while (total[0] < INPUTSTREAM_SIZE) {
rank = (rank + 1) % nb;
if ((read = is[rank].read(buffer)) > 0) {
total[rank] += read;
}
}
for (int i = 0; i < nb; i++) {
while ((read = is[i].read(buffer)) > 0) {
total[i] += read;
}
assertEquals("rank: " + i, INPUTSTREAM_SIZE, total[i]);
}
} catch (final IOException e) {
fail("Should not raized an exception: " + e.getMessage());
}
final long stop = System.nanoTime();
LOGGER.debug("Read {}: \t{} ns", size, stop - start);
LOGGER.warn("stop testMultipleInputStreamHandlerMultipleShift8K Pool {}", MultipleInputStreamHandler.getPoolAvailability());
addTimer(stop - start, "MULTIPLE_BLOCK_" + size + "_" + nb + " :\t" + (stop - start) + " \t" + (stop - start) / nb);
}
private static class ThreadReader implements Runnable {
private final InputStream is;
private final int size;
private final long[] total;
private final int rank;
private ThreadReader(int rank, long[] total, InputStream is, int size) {
this.rank = rank;
this.total = total;
this.is = is;
this.size = size;
}
@Override
public void run() {
int read;
total[rank] = 0;
final byte[] buffer = new byte[size];
try {
while ((read = is.read(buffer)) > 0) {
total[rank] += read;
}
} catch (final IOException e) {
LOGGER.error(e);
return;
}
}
}
private void testMultipleInputStreamHandlerMultipleMultiThread(int nb, int size, boolean block) {
final long start = System.nanoTime();
try (FakeInputStream fakeInputStream = new FakeInputStream(INPUTSTREAM_SIZE, block);
MultipleInputStreamHandler mish = new MultipleInputStreamHandler(fakeInputStream, nb)) {
InputStream is;
final long[] total = new long[nb];
final ExecutorService executor = Executors.newFixedThreadPool(nb);
for (int i = 0; i < nb; i++) {
is = mish.getInputStream(i);
final ThreadReader threadReader = new ThreadReader(i, total, is, size);
executor.execute(threadReader);
}
executor.shutdown();
while (!executor.awaitTermination(10000, TimeUnit.MILLISECONDS)) {
;
}
for (int i = 0; i < nb; i++) {
assertEquals(INPUTSTREAM_SIZE, total[i]);
}
} catch (final InterruptedException e) {
fail("Should not raized an exception: " + e.getMessage());
}
final long stop = System.nanoTime();
LOGGER.debug("Read {}: \t{} ns", size, stop - start);
addTimer((stop - start) / nb, "PARALLEL_VAR_SIZE_" + (block ? "BLOCK_" : "BYTE_") + size + "_" + nb + " :\t"
+ (stop - start) + " \t" + (stop - start) / nb);
}
@Ignore //@Test
public void testMultipleInputStreamHandlerMultipleMultiThread() {
LOGGER.warn("start testMultipleInputStreamHandlerMultipleMultiThread Pool {}", MultipleInputStreamHandler.getPoolAvailability());
testMultipleInputStreamHandlerMultipleMultiThread(1, 8192, true);
testMultipleInputStreamHandlerMultipleMultiThread(1, 8192, true);
testMultipleInputStreamHandlerMultipleMultiThread(10, 8192, true);
testMultipleInputStreamHandlerMultipleMultiThread(1, 65536, true);
testMultipleInputStreamHandlerMultipleMultiThread(10, 65536, true);
testMultipleInputStreamHandlerMultipleMultiThread(1, 8192, false);
testMultipleInputStreamHandlerMultipleMultiThread(10, 8192, false);
testMultipleInputStreamHandlerMultipleMultiThread(1, 65536, false);
testMultipleInputStreamHandlerMultipleMultiThread(10, 65536, false);
LOGGER.warn("stop testMultipleInputStreamHandlerMultipleMultiThread Pool {}", MultipleInputStreamHandler.getPoolAvailability());
}
@Ignore //@Test(timeout = 20000)
public void testMultipleInputStreamHandlerMultiRead() {
LOGGER.warn("start testMultipleInputStreamHandlerMultiRead Pool {}", MultipleInputStreamHandler.getPoolAvailability());
for (int i = 0; i < 2001; i++) {
if (i % 100 == 0) {
LOGGER.warn("Step {} Pool: {}", i, MultipleInputStreamHandler.getPoolAvailability());
}
testMultipleInputStreamHandlerMultipleMultiThread(1, 1024, true);
}
LOGGER.warn("stop testMultipleInputStreamHandlerMultiRead Pool {}", MultipleInputStreamHandler.getPoolAvailability());
}
@Ignore //@Test
public void testMultipleInputStreamHandlerMultipleMultiThreadWithVariableSizes() {
LOGGER.warn("start testMultipleInputStreamHandlerMultipleMultiThreadWithVariableSizes Pool {}", MultipleInputStreamHandler.getPoolAvailability());
for (int len = 100; len < 80000; len += 500) {
testMultipleInputStreamHandlerMultipleMultiThread(1, len, true);
testMultipleInputStreamHandlerMultipleMultiThread(10, len, true);
testMultipleInputStreamHandlerMultipleMultiThread(1, len, false);
testMultipleInputStreamHandlerMultipleMultiThread(10, len, false);
}
LOGGER.warn("stop testMultipleInputStreamHandlerMultipleMultiThreadWithVariableSizes Pool {}", MultipleInputStreamHandler.getPoolAvailability());
}
@Ignore //@Test
public void testClose() {
final int size = 8192;
final int nb = 10;
LOGGER.warn("start testClose Pool {}", MultipleInputStreamHandler.getPoolAvailability());
try (FakeInputStream fakeInputStream = new FakeInputStream(INPUTSTREAM_SIZE, true);
MultipleInputStreamHandler mish = new MultipleInputStreamHandler(fakeInputStream, nb)) {
final InputStream[] is = new InputStream[nb];
for (int i = 0; i < nb; i++) {
is[i] = mish.getInputStream(i);
}
LOGGER.warn("current testClose Pool {}", MultipleInputStreamHandler.getPoolAvailability());
final byte[] buffer = new byte[size];
int rank = 1;
for (int i = 0; i < 100; i++) {
rank = (rank + 1) % nb;
if (is[rank].read(buffer) < 0) {
break;
}
}
mish.close();
for (int i = 0; i < nb; i++) {
assertEquals("rank: " + i, -1, is[i].available());
}
} catch (final IOException e) {
fail("Should not raized an exception: " + e.getMessage());
}
LOGGER.warn("stop testClose Pool {}", MultipleInputStreamHandler.getPoolAvailability());
}
@Ignore //@Test
public void testMultipleClose() {
final int size = 8192;
final int nb = 10;
LOGGER.warn("start testMultipleClose Pool {}", MultipleInputStreamHandler.getPoolAvailability());
try (FakeInputStream fakeInputStream = new FakeInputStream(INPUTSTREAM_SIZE, true);
MultipleInputStreamHandler mish = new MultipleInputStreamHandler(fakeInputStream, nb)) {
final InputStream[] is = new InputStream[nb];
for (int i = 0; i < nb; i++) {
is[i] = mish.getInputStream(i);
}
final byte[] buffer = new byte[size];
int rank = 1;
for (int i = 0; i < 100; i++) {
rank = (rank + 1) % nb;
is[rank].read(buffer);
}
for (int i = 0; i < nb; i++) {
is[i].close();
assertEquals("rank: " + i, -1, is[i].available());
}
mish.close();
} catch (final IOException e) {
fail("Should not raized an exception: " + e.getMessage());
}
LOGGER.warn("stop testMultipleClose Pool {}", MultipleInputStreamHandler.getPoolAvailability());
}
@Ignore //@Test
public void testConcurrentMultipleIntputStreamHandler() {
int old = VitamConfiguration.DELAY_MULTIPLE_INPUTSTREAM;
VitamConfiguration.DELAY_MULTIPLE_INPUTSTREAM = 2000;
List<MultipleInputStreamHandler> list = new ArrayList<>(1000);
List<FakeInputStream> listStream = new ArrayList<>(1001);
LOGGER.warn("start allocate stream {}", MultipleInputStreamHandler.getPoolAvailability());
for (int i = 0; i < 1001; i++) {
listStream.add(new FakeInputStream(INPUTSTREAM_SIZE, true));
}
LOGGER.warn("start allocate MISH {}", MultipleInputStreamHandler.getPoolAvailability());
try {
for (int i = 0; i < 1000; i++) {
try {
MultipleInputStreamHandler mish = new MultipleInputStreamHandler(listStream.get(i), 1);
list.add(mish);
LOGGER.info(mish.toString());
} catch (IllegalArgumentException e) {
fail("Should not be interrupted");
}
}
// Try to allocate once more but not possible
LOGGER.warn("start allocate MISH 1 more than possible {}", MultipleInputStreamHandler.getPoolAvailability());
try {
list.add(new MultipleInputStreamHandler(listStream.get(1000), 1));
fail("Should be interrupted");
} catch (IllegalArgumentException e) {
// legal
}
LOGGER.warn("start free half of MISH and streams and reallocate streams {}",
MultipleInputStreamHandler.getPoolAvailability());
// Now free half of the list
for (int i = 999; i >= 500; i--) {
MultipleInputStreamHandler mish = list.remove(i);
mish.close();
list.add(mish);
LOGGER.info(mish.toString());
StreamUtils.closeSilently(listStream.remove(i));
listStream.add(new FakeInputStream(INPUTSTREAM_SIZE, true));
}
// Now reallocate 500
LOGGER.warn("start half of MISH {}", MultipleInputStreamHandler.getPoolAvailability());
for (int i = 500; i < 1000; i++) {
try {
MultipleInputStreamHandler mish = new MultipleInputStreamHandler(listStream.get(i), 1);
list.add(mish);
LOGGER.info(mish.toString());
} catch (IllegalArgumentException e) {
fail("Should not be interrupted");
}
}
LOGGER.warn("Closing all {}", MultipleInputStreamHandler.getPoolAvailability());
for (MultipleInputStreamHandler mish : list) {
mish.close();
LOGGER.info(mish.toString());
}
for (FakeInputStream fakeInputStream : listStream) {
StreamUtils.closeSilently(fakeInputStream);
}
LOGGER.warn("Restart from 0 with 1000 {}", MultipleInputStreamHandler.getPoolAvailability());
for (int i = 0; i < 1000; i++) {
listStream.add(new FakeInputStream(INPUTSTREAM_SIZE, true));
}
LOGGER.warn("Try reading {}", MultipleInputStreamHandler.getPoolAvailability());
for (int i = 0; i < 1000; i++) {
try {
MultipleInputStreamHandler mish = new MultipleInputStreamHandler(listStream.get(i), 1);
LOGGER.info(mish.toString());
InputStream stream = mish.getInputStream(0);
StreamUtils.closeSilently(stream);
mish.close();
LOGGER.info(mish.toString());
} catch (IllegalArgumentException e) {
fail("Should not be interrupted");
}
}
LOGGER.warn("End of test {}", MultipleInputStreamHandler.getPoolAvailability());
} finally {
for (FakeInputStream fakeInputStream : listStream) {
StreamUtils.closeSilently(fakeInputStream);
}
VitamConfiguration.DELAY_MULTIPLE_INPUTSTREAM = old;
}
}
}