blob: d6366f92ba093c5b6d63e2a360d492958cfc7d04 [file] [log] [blame]
/*
* 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.lucene.util;
import java.io.IOException;
import org.apache.lucene.store.DataInput;
import org.apache.lucene.store.IndexOutput;
/**
* Intentionally slow IndexOutput for testing.
*/
public class ThrottledIndexOutput extends IndexOutput {
public static final int DEFAULT_MIN_WRITTEN_BYTES = 1024;
private final int bytesPerSecond;
private IndexOutput delegate;
private long flushDelayMillis;
private long closeDelayMillis;
private long seekDelayMillis;
private long pendingBytes;
private long minBytesWritten;
private long timeElapsed;
private final byte[] bytes = new byte[1];
public ThrottledIndexOutput newFromDelegate(IndexOutput output) {
return new ThrottledIndexOutput(bytesPerSecond, flushDelayMillis,
closeDelayMillis, seekDelayMillis, minBytesWritten, output);
}
public ThrottledIndexOutput(int bytesPerSecond, long delayInMillis,
IndexOutput delegate) {
this(bytesPerSecond, delayInMillis, delayInMillis, delayInMillis,
DEFAULT_MIN_WRITTEN_BYTES, delegate);
}
public ThrottledIndexOutput(int bytesPerSecond, long delays,
int minBytesWritten, IndexOutput delegate) {
this(bytesPerSecond, delays, delays, delays, minBytesWritten, delegate);
}
public static final int mBitsToBytes(int mbits) {
return mbits * 125000000;
}
public ThrottledIndexOutput(int bytesPerSecond, long flushDelayMillis,
long closeDelayMillis, long seekDelayMillis, long minBytesWritten,
IndexOutput delegate) {
super("ThrottledIndexOutput(" + delegate + ")", delegate == null ? "n/a" : delegate.getName());
assert bytesPerSecond > 0;
this.delegate = delegate;
this.bytesPerSecond = bytesPerSecond;
this.flushDelayMillis = flushDelayMillis;
this.closeDelayMillis = closeDelayMillis;
this.seekDelayMillis = seekDelayMillis;
this.minBytesWritten = minBytesWritten;
}
@Override
public void close() throws IOException {
try {
sleep(closeDelayMillis + getDelay(true));
} finally {
delegate.close();
}
}
@Override
public long getFilePointer() {
return delegate.getFilePointer();
}
@Override
public void writeByte(byte b) throws IOException {
bytes[0] = b;
writeBytes(bytes, 0, 1);
}
@Override
public void writeBytes(byte[] b, int offset, int length) throws IOException {
final long before = System.nanoTime();
// TODO: sometimes, write only half the bytes, then
// sleep, then 2nd half, then sleep, so we sometimes
// interrupt having only written not all bytes
delegate.writeBytes(b, offset, length);
timeElapsed += System.nanoTime() - before;
pendingBytes += length;
sleep(getDelay(false));
}
protected long getDelay(boolean closing) {
if (pendingBytes > 0 && (closing || pendingBytes > minBytesWritten)) {
long actualBps = (timeElapsed / pendingBytes) * 1000000000l; // nano to sec
if (actualBps > bytesPerSecond) {
long expected = (pendingBytes * 1000l / bytesPerSecond) ;
final long delay = expected - (timeElapsed / 1000000l) ;
pendingBytes = 0;
timeElapsed = 0;
return delay;
}
}
return 0;
}
private static final void sleep(long ms) {
if (ms <= 0) {
return;
}
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
throw new ThreadInterruptedException(e);
}
}
@Override
public void copyBytes(DataInput input, long numBytes) throws IOException {
delegate.copyBytes(input, numBytes);
}
@Override
public long getChecksum() throws IOException {
return delegate.getChecksum();
}
}