/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.activemq.artemis.cli.commands.util; import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; import java.text.DecimalFormat; import java.util.concurrent.TimeUnit; import org.apache.activemq.artemis.core.io.IOCallback; import org.apache.activemq.artemis.core.io.IOCriticalErrorListener; import org.apache.activemq.artemis.core.io.SequentialFile; import org.apache.activemq.artemis.core.io.SequentialFileFactory; import org.apache.activemq.artemis.core.io.aio.AIOSequentialFileFactory; import org.apache.activemq.artemis.core.io.mapped.MappedSequentialFileFactory; import org.apache.activemq.artemis.core.io.nio.NIOSequentialFileFactory; import org.apache.activemq.artemis.core.server.ActiveMQMessageBundle; import org.apache.activemq.artemis.core.server.JournalType; import org.apache.activemq.artemis.jlibaio.LibaioContext; import org.apache.activemq.artemis.utils.ReusableLatch; /** * It will perform a simple test to evaluate how many syncs a disk can make per second * * * */ public class SyncCalculation { /** * It will perform a write test of blockSize * bocks, sinc on each write, for N tries. * It will return the lowest spent time from the tries. */ public static long syncTest(File datafolder, int blockSize, int blocks, int tries, boolean verbose, boolean fsync, JournalType journalType) throws Exception { SequentialFileFactory factory = newFactory(datafolder, fsync, journalType, blockSize * blocks); if (verbose) { System.out.println("Using " + factory.getClass().getName() + " to calculate sync times"); } SequentialFile file = factory.createSequentialFile("test.tmp"); try { file.delete(); file.open(); file.fill(blockSize * blocks); file.close(); long[] result = new long[tries]; byte[] block = new byte[blockSize]; for (int i = 0; i < block.length; i++) { block[i] = (byte) 't'; } ByteBuffer bufferBlock = factory.newBuffer(blockSize); bufferBlock.put(block); bufferBlock.position(0); final ReusableLatch latch = new ReusableLatch(0); IOCallback callback = new IOCallback() { @Override public void done() { latch.countDown(); } @Override public void onError(int errorCode, String errorMessage) { } }; DecimalFormat dcformat = new DecimalFormat("###.##"); for (int ntry = 0; ntry < tries; ntry++) { if (verbose) { System.out.println("**************************************************"); System.out.println(ntry + " of " + tries + " calculation"); } file.open(); file.position(0); long start = System.currentTimeMillis(); for (int i = 0; i < blocks; i++) { bufferBlock.position(0); latch.countUp(); file.writeDirect(bufferBlock, true, callback); if (!latch.await(5, TimeUnit.SECONDS)) { throw new IOException("Callback wasn't called"); } } long end = System.currentTimeMillis(); result[ntry] = (end - start); if (verbose) { double writesPerMillisecond = (double) blocks / (double) result[ntry]; System.out.println("Time = " + result[ntry] + " milliseconds"); System.out.println("Writes / millisecond = " + dcformat.format(writesPerMillisecond)); System.out.println("bufferTimeout = " + toNanos(result[ntry], blocks, verbose)); System.out.println("**************************************************"); } file.close(); } factory.releaseDirectBuffer(bufferBlock); long totalTime = Long.MAX_VALUE; for (int i = 0; i < tries; i++) { if (result[i] < totalTime) { totalTime = result[i]; } } return totalTime; } finally { try { file.close(); } catch (Exception e) { } try { file.delete(); } catch (Exception e) { } try { factory.stop(); } catch (Exception e) { } } } public static long toNanos(long time, long blocks, boolean verbose) { double blocksPerMillisecond = (double) blocks / (double) (time); if (verbose) { System.out.println("Blocks per millisecond::" + blocksPerMillisecond); } long nanoSeconds = TimeUnit.NANOSECONDS.convert(1, TimeUnit.MILLISECONDS); long timeWait = (long) (nanoSeconds / blocksPerMillisecond); if (verbose) { System.out.println("your system could make a sync every " + timeWait + " nanoseconds, and this will be your timeout"); } return timeWait; } private static SequentialFileFactory newFactory(File datafolder, boolean datasync, JournalType journalType, int fileSize) { SequentialFileFactory factory; if (journalType == JournalType.ASYNCIO && !LibaioContext.isLoaded()) { journalType = JournalType.NIO; } switch (journalType) { case NIO: factory = new NIOSequentialFileFactory(datafolder, 1).setDatasync(datasync); ((NIOSequentialFileFactory) factory).disableBufferReuse(); factory.start(); return factory; case ASYNCIO: factory = new AIOSequentialFileFactory(datafolder, 1).setDatasync(datasync); factory.start(); ((AIOSequentialFileFactory) factory).disableBufferReuse(); return factory; case MAPPED: factory = new MappedSequentialFileFactory(datafolder, new IOCriticalErrorListener() { @Override public void onIOException(Throwable code, String message, SequentialFile file) { } }, true).chunkBytes(fileSize).overlapBytes(0).setDatasync(datasync); factory.start(); return factory; default: throw ActiveMQMessageBundle.BUNDLE.invalidJournalType2(journalType); } } }