/** * Copyright 2012 Akiban Technologies, Inc. * * Licensed 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 com.persistit; import static com.persistit.util.Util.NS_PER_S; import java.io.File; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import com.persistit.util.ArgParser; /** * Benchmark for primitive I/O simulating HARD (durable) commit. This code is * intended to explore two elements: * * (1) Pre-extending the journal file (so that FileChannel.force(false) usually * does not need to write any metadata. * * (2) Performing I/O in fixed-length blocks so that to write a some bytes the * file system does not first need to read existing data from disk. * * Parameters * * align - smallest unit of I/O (default = 1) datapath - directory in which fake * journal file will be written (default = /tmp/persistit_test_data) buffersize * - emulated journal buffer size (default = 64M) * * * @author peter * */ public class JournalManagerBench { private final byte[] NULLS = new byte[65536]; private final String[] ARG_TEMPLATE = new String[] { "duration|int:10:10:86400|Duration of test in seconds", "policy|String:HARD|Commit policy: SOFT, HARD or GROUP", "datapath|String:/tmp/persistit_test_data|Datapath property", "buffersize|int:64:1:1024|Emulated journal buffer size in MBytes", "extension|int:0:0:1024|MBytes by which to extend file when full", "prealloc|int:0:0:1024|Preallocated file size in MBytes", "align|int:1:1:65536|Blocking factor for I/O size", "recsize|int:123:64:65536|Emulated transaction record size" }; final ByteBuffer buffer; final ArgParser ap; private File file; private FileChannel fc; private long writeAddress = 0; private long currentAddress = 0; long count = 0; long minTime = Long.MAX_VALUE; long maxTime = Long.MIN_VALUE; final byte[] bytes = new byte[65536]; public static void main(final String[] args) throws Exception { final JournalManagerBench jmb = new JournalManagerBench(args); jmb.runTest(); } private JournalManagerBench(final String[] args) throws Exception { ap = new ArgParser("JournalManagerBench", args, ARG_TEMPLATE).strict(); buffer = ByteBuffer.allocate(ap.getIntValue("buffersize") * 1024 * 1024); } @SuppressWarnings("resource") private void runTest() throws Exception { file = new File(ap.getStringValue("datapath"), "JManBench_TestFile"); fc = new RandomAccessFile(file, "rw").getChannel(); preallocateFile(ap.getIntValue("prealloc") * 1024 * 1024); for (int i = 0; i < bytes.length; i++) { bytes[i] = (byte) ('-'); } final int align = ap.getIntValue("align"); final long extension = ap.getIntValue("extension") * 1024 * 1024; final long start = System.nanoTime(); final long expires = start + ap.getIntValue("duration") * NS_PER_S; long now = System.nanoTime(); while (now < expires) { doOneCycle(now - start, align, extension, 100); final long then = System.nanoTime(); count++; minTime = Math.min(minTime, then - now); maxTime = Math.max(maxTime, then - now); now = then; } final long elapsed = now - start; System.out.printf("%,d commits took %,dms at a rate of %,d/second minimum time=%,dns maximumTime=%,dns\n", count, elapsed, (count * NS_PER_S) / elapsed, minTime, maxTime); } private void preallocateFile(final long size) throws Exception { if (size > 0 && fc.size() > size) { System.out.printf("Truncating file %s from %,d to %,d\n", file, fc.size(), size); fc.truncate(size); } else if (fc.size() < size) { System.out.printf("Preallocating file %s to size %,d ", file, size); while (true) { long remaining = size - fc.size(); if (remaining <= 0) { break; } if (remaining > buffer.capacity()) { remaining = buffer.capacity(); final long unaligned = fc.size() % 16384; if (unaligned > 0) { remaining = remaining - (16384 - unaligned); } } buffer.position(0).limit((int) remaining); fc.write(buffer, fc.size()); System.out.print("."); } fc.force(true); System.out.println("done"); } } private void doOneCycle(final long time, final int align, final long extension, final int size) throws Exception { // Make a fake transaction record final String header = String.format("\nsize=%06d count=%06d time=%012d\n", size, count, time); final byte[] b = header.getBytes(); System.arraycopy(b, 0, bytes, 0, b.length); // Add the record, possibly offset to maintaining alignment final int toRewrite = (int) (currentAddress - writeAddress); buffer.position(toRewrite); buffer.put(bytes, 0, size); boolean extended = false; int position = buffer.position(); // If extension is needed, add those bytes long currentSize; if (extension > 0 && writeAddress + buffer.position() > (currentSize = fc.size())) { long newSize = currentSize + extension; if (newSize - writeAddress > buffer.capacity()) { newSize = writeAddress + buffer.capacity(); assert newSize > currentSize; } int add = (int) (newSize - writeAddress - buffer.position()); while (add > 0) { buffer.put(NULLS, 0, Math.min(NULLS.length, add)); add -= NULLS.length; } extended = true; } // Write and force the buffer buffer.flip(); fc.write(buffer, writeAddress); fc.force(extended); // Align the bytes to the beginning of the buffer as needed currentAddress = writeAddress + position; buffer.limit(position); position = (position / align) * align; buffer.position(position); buffer.compact(); writeAddress += position; } }