/*
* Copyright (C) 2012, 2016 higherfrequencytrading.com
* Copyright (C) 2016 Roman Leventov
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.openhft.chronicle.map;
import net.openhft.chronicle.core.values.LongValue;
import org.junit.Ignore;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.*;
import static java.lang.Math.log10;
import static java.lang.Math.round;
import static net.openhft.chronicle.values.Values.newNativeReference;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class EntryCountMapTest {
double score = 0;
int scoreCount = 0;
static final String TMP = System.getProperty("java.io.tmpdir");
static File getPersistenceFile() throws IOException {
File file = File.createTempFile("ecm-chm-test", ".deleteme");
file.deleteOnExit();
return file;
}
private static ChronicleMap<CharSequence, LongValue> getSharedMap(
long entries, int segments, int keySize) throws IOException {
ChronicleMapBuilder<CharSequence, LongValue> mapBuilder =
ChronicleMapBuilder.of(CharSequence.class, LongValue.class)
.allowSegmentTiering(false)
.entries(entries)
.actualSegments(segments)
.averageKeySize(keySize);
return mapBuilder.createPersistedTo(getPersistenceFile());
}
static final int ecmTests = Integer.getInteger("ecm.tests", 5);
@Ignore("HCOLL-279 fix net.openhft.chronicle.map.EntryCountMapTest#testVerySmall")
@Test
public void testVerySmall() throws IOException {
System.out.print("testVerySmall seeds");
for (int t = 0; t < ecmTests; t++) {
System.out.print(".");
int s = 1;
// regression test.
testEntriesMaxSize(s, 1, 1, t);
testEntriesMaxSize(s, 2, 2, t);
testEntriesMaxSize(s, 4, 4, t);
testEntriesMaxSize(s, 8, 8, t);
testEntriesMaxSize(s, 16, 16, t);
testEntriesMaxSize(s, 32, 32, t);
testEntriesMaxSize(s, 64, 64, t);
testEntriesMaxSize(s, 128, 128, t);
testEntriesMaxSize(s, 256, 256, t);
testEntriesMaxSize(s, 512, 512, t);
s = 2;
// regression test.
testEntriesMaxSize(s, 8, 16, t);
testEntriesMaxSize(s, 16, 38, t);
testEntriesMaxSize(s, 32, 64, t);
testEntriesMaxSize(s, 64, 95, t);
testEntriesMaxSize(s, 128, 168, t);
testEntriesMaxSize(s, 256, 322, t);
testEntriesMaxSize(s, 512, 640, t);
s = 4;
// regression test.
testEntriesMaxSize(s, 32, 72, t);
testEntriesMaxSize(s, 64, 120, t);
testEntriesMaxSize(s, 128, 200, t);
testEntriesMaxSize(s, 256, 380, t);
testEntriesMaxSize(s, 512, 650, t);
for (int s2 : new int[]{1, 2, 4}) {
testEntriesMaxSize(s2, 1000, 1300, t);
testEntriesMaxSize(s2, 2000, 2500, t);
testEntriesMaxSize(s2, 4000, 5000, t);
testEntriesMaxSize(s2, 5000, 6200, t);
testEntriesMaxSize(s2, 8000, 9900, t);
testEntriesMaxSize(s2, 12000, 15000, t);
testEntriesMaxSize(s2, 16000, 20000, t);
}
}
// hyperbolic average gives more weight to small numbers.
System.out.printf(" Score: %.2f%n", scoreCount / score);
}
@Test
public void testSmall() throws IOException, ExecutionException, InterruptedException {
System.out.print("testSmall seeds");
int procs = Runtime.getRuntime().availableProcessors();
ExecutorService es = Executors.newFixedThreadPool(procs);
for (int t = 0; t < ecmTests; t++) {
List<Future<?>> futures = new ArrayList<>();
{
int s = 8;
futures.add(testEntriesMaxSize(es, s, 250, 450, t));
futures.add(testEntriesMaxSize(es, s, 500, 840, t));
futures.add(testEntriesMaxSize(es, s, 1000, 1550, t));
futures.add(testEntriesMaxSize(es, s, 2000, 3200, t));
}
// regression test.
for (int s : new int[]{8, 16}) {
futures.add(testEntriesMaxSize(es, s, 3000, 5000, t));
futures.add(testEntriesMaxSize(es, s, 5000, 7500, t));
futures.add(testEntriesMaxSize(es, s, 10000, 15000, t));
}
for (int s : new int[]{32, 64, 128})
futures.add(testEntriesMaxSize(es, s, 250 * s, 250 * s * 5 / 3, t));
int s = 32;
for (int e : new int[]{16000, 25000, 50000})
futures.add(testEntriesMaxSize(es, s, e, e * 5 / 3, t));
s = 64;
for (int e : new int[]{25000, 50000, 100000})
futures.add(testEntriesMaxSize(es, s, e, e * 5 / 3, t));
s = 128;
for (int e : new int[]{50000, 100000, 200000})
futures.add(testEntriesMaxSize(es, s, e, e * 5 / 3, t));
for (Future<?> future : futures) {
System.out.print(".");
future.get();
}
}
es.shutdown();
// hyperbolic average gives more weight to small numbers.
System.out.printf(" Score: %.2f%n", scoreCount / score);
}
@Ignore("Long running, large tests test")
@Test
public void testMedium() throws IOException, ExecutionException, InterruptedException {
System.out.print("testMedium seeds");
int procs = Runtime.getRuntime().availableProcessors();
ExecutorService es = Executors.newFixedThreadPool(procs);
for (int t = 0; t < ecmTests; t++) {
// regression test.
List<Future<?>> futures = new ArrayList<>();
for (int s : new int[]{512, 256, 128, 64}) {
int s3 = s * s * 128;
futures.add(testEntriesMaxSize(es, s, s3 * 2, s3 * 24 / 10, t));
if (s < 512) {
futures.add(testEntriesMaxSize(es, s, s3 * 3 / 2, s3 * 9 / 5, t));
futures.add(testEntriesMaxSize(es, s, s3, s3 * 12 / 10, t));
}
}
for (Future<?> future : futures) {
System.out.print(".");
future.get();
}
}
es.shutdown();
// hyperbolic average gives more weight to small numbers.
System.out.printf("Score: %.2f%n", scoreCount / score);
}
private Future<Void> testEntriesMaxSize(ExecutorService es, final int segments,
final int minSize, final int maxSize, final int seed)
throws IOException {
assert minSize <= maxSize;
Random random = new Random(seed);
int counter = minSize + random.nextInt(9999 + maxSize);
final int stride = 1 + random.nextInt(100);
final int maxKeySize = "key:".length() +
(int) round(log10(moreThanMaxSize(maxSize) * stride + counter)) + 1;
return es.submit(new Callable<Void>() {
@Override
public Void call() {
File f = null;
try (final ChronicleMap<CharSequence, LongValue> map =
getSharedMap(minSize, segments, maxKeySize)) {
f = map.file();
testEntriesMaxSize0(segments, minSize, maxSize, seed, stride, map);
} finally {
if (f != null && f.exists())
f.delete();
return null;
}
}
});
}
void testEntriesMaxSize(int segments, int minSize, int maxSize, int seed) throws IOException {
assert minSize <= maxSize;
Random random = new Random(seed);
int counter = minSize + random.nextInt(9999 + maxSize);
int stride = 1 + random.nextInt(100);
int maxKeySize = "key:".length() +
(int) round(log10(moreThanMaxSize(maxSize) * stride + counter)) + 1;
File file;
try (ChronicleMap<CharSequence, LongValue> map = getSharedMap(minSize, segments, maxKeySize)) {
file = map.file();
testEntriesMaxSize0(segments, minSize, maxSize, counter, stride, map);
}
file.delete();
}
void testEntriesMaxSize0(int segments, int minSize, int maxSize, int counter, int stride,
ChronicleMap<CharSequence, LongValue> map) {
LongValue longValue = newNativeReference(LongValue.class);
try {
for (int j = 0; j < moreThanMaxSize(maxSize); j++) {
String key = "key:" + counter;
if (minSize > 10 && (counter & 15) == 7)
key += "-oversized-key";
counter += stride;
// give a biased hashcode distribution.
if ((Integer.bitCount(key.hashCode()) & 7) != 0) {
j--;
continue;
}
map.acquireUsing(key, longValue);
longValue.setValue(1);
}
dumpMapStats(segments, minSize, map);
fail("Expected the map to be full.");
} catch (IllegalStateException e) {
// calculate the hyperbolic average.
score += (double) minSize / map.size();
scoreCount++;
boolean condition = minSize <= map.size() && map.size() <= minSize * 2 + 8;
if (!condition) {
dumpMapStats(segments, minSize, map);
assertTrue("stride: " + stride + ", seg: " + segments + ", min: " + minSize +
", size: " + map.size(), condition);
} else if (map.size() > maxSize)
System.err.println(" warning, larger than expected, stride: " + stride +
", seg: " + segments + ", min: " + minSize +
", size: " + map.size());
}
}
private static int moreThanMaxSize(int maxSize) {
return maxSize * 14 / 10 + 300;
}
private void dumpMapStats(int segments, int minSize,
ChronicleMap<CharSequence, LongValue> map) {
long[] a = new long[map.segments()];
for (int i = 0; i < map.segments(); i++) {
try (MapSegmentContext<CharSequence, LongValue, ?> c = map.segmentContext(i)) {
a[i] = c.size();
}
}
System.out.println("segs: " + segments + " min: " + minSize + " was " + map.size() + " "
+ Arrays.toString(a)
+ " sum: " + sum(a));
}
private long sum(long[] longs) {
long sum = 0;
for (long i : longs) {
sum += i;
}
return sum;
}
}