/* * Copyright 2015 Terracotta, Inc., a Software AG company. * * 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 org.terracotta.offheapstore.paging; import org.terracotta.offheapstore.paging.UpfrontAllocatingPageSource; import org.terracotta.offheapstore.paging.PageSource; import static org.terracotta.offheapstore.util.MemoryUnit.KILOBYTES; import static org.terracotta.offheapstore.util.MemoryUnit.MEGABYTES; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Random; import org.jfree.chart.ChartFactory; import org.jfree.chart.ChartUtilities; import org.jfree.chart.JFreeChart; import org.jfree.chart.plot.PlotOrientation; import org.jfree.data.xy.AbstractXYDataset; import org.junit.Ignore; import org.junit.Test; import org.terracotta.offheapstore.OffHeapHashMap; import org.terracotta.offheapstore.WriteLockedOffHeapClockCache; import org.terracotta.offheapstore.buffersource.HeapBufferSource; import org.terracotta.offheapstore.storage.IntegerStorageEngine; import org.terracotta.offheapstore.storage.OffHeapBufferHalfStorageEngine; import org.terracotta.offheapstore.storage.SplitStorageEngine; import org.terracotta.offheapstore.storage.portability.ByteArrayPortability; import static org.hamcrest.collection.IsMapContaining.hasKey; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; public class TableStealingFromStorageIT { @Test public void testStealingFromOwnStorage() { PageSource source = new UpfrontAllocatingPageSource(new HeapBufferSource(), MEGABYTES.toBytes(1), MEGABYTES.toBytes(1)); OffHeapHashMap<Integer, byte[]> selfStealer = new WriteLockedOffHeapClockCache<Integer, byte[]>(source, true, new SplitStorageEngine<Integer, byte[]>(new IntegerStorageEngine(), new OffHeapBufferHalfStorageEngine<byte[]>(source, KILOBYTES.toBytes(16), ByteArrayPortability.INSTANCE, false, true))); long seed = System.nanoTime(); System.err.println("Random Seed = " + seed); Random rndm = new Random(seed); int terminalKey = 0; for (int key = 0; true; key++) { int size = selfStealer.size(); byte[] payload = new byte[rndm.nextInt(KILOBYTES.toBytes(1))]; Arrays.fill(payload, (byte) key); selfStealer.put(key, payload); if (size >= selfStealer.size()) { terminalKey = key; break; } } System.err.println("Terminal Key = " + terminalKey); for (int key = terminalKey + 1; key < 10 * terminalKey; key++) { byte[] payload = new byte[rndm.nextInt(KILOBYTES.toBytes(1))]; Arrays.fill(payload, (byte) key); selfStealer.put(key, payload); selfStealer.remove(rndm.nextInt(key)); } long measuredSize = 0; for (Entry<Integer, byte[]> e : selfStealer.entrySet()) { int key = e.getKey(); byte[] payload = e.getValue(); for (byte b : payload) { assertThat(b, is((byte) key)); } assertThat(selfStealer, hasKey(e.getKey())); measuredSize++; } assertThat(selfStealer.size(), is((int) measuredSize)); assertThat(selfStealer.getUsedSlotCount(), is(measuredSize)); } @Test @Ignore public void testSelfStealingIsStable() throws IOException { PageSource source = new UpfrontAllocatingPageSource(new HeapBufferSource(), MEGABYTES.toBytes(2), MEGABYTES.toBytes(1)); OffHeapHashMap<Integer, byte[]> selfStealer = new WriteLockedOffHeapClockCache<Integer, byte[]>(source, true, new SplitStorageEngine<Integer, byte[]>(new IntegerStorageEngine(), new OffHeapBufferHalfStorageEngine<byte[]>(source, KILOBYTES.toBytes(16), ByteArrayPortability.INSTANCE, false, true))); // failing seed value //long seed = 1302292028471110000L; long seed = System.nanoTime(); System.err.println("Random Seed = " + seed); Random rndm = new Random(seed); int payloadSize = rndm.nextInt(KILOBYTES.toBytes(1)); System.err.println("Payload Size = " + payloadSize); List<Long> sizes = new ArrayList<Long>(); List<Long> tableSizes = new ArrayList<Long>(); int terminalKey = 0; for (int key = 0; true; key++) { int size = selfStealer.size(); byte[] payload = new byte[payloadSize]; Arrays.fill(payload, (byte) key); selfStealer.put(key, payload); sizes.add(Long.valueOf(selfStealer.size())); tableSizes.add(selfStealer.getTableCapacity()); if (size >= selfStealer.size()) { terminalKey = key; selfStealer.remove(terminalKey); break; } } System.err.println("Terminal Key = " + terminalKey); int shrinkCount = 0; for (int key = terminalKey; key < 10 * terminalKey; key++) { byte[] payload = new byte[payloadSize]; Arrays.fill(payload, (byte) key); long preTableSize = selfStealer.getTableCapacity(); selfStealer.put(key, payload); if (rndm.nextBoolean()) { selfStealer.remove(rndm.nextInt(key)); } long postTableSize = selfStealer.getTableCapacity(); if (preTableSize > postTableSize) { shrinkCount++; } sizes.add(Long.valueOf(selfStealer.size())); tableSizes.add(postTableSize); } if (shrinkCount != 0) { Map<String, List<? extends Number>> data = new HashMap<String, List<? extends Number>>(); data.put("actual size", sizes); data.put("table size", tableSizes); JFreeChart chart = ChartFactory.createXYLineChart("Cache Size", "operations", "size", new LongListXYDataset(data), PlotOrientation.VERTICAL, true, false, false); File plotOutput = new File("target/TableStealingFromStorageTest.testSelfStealingIsStable.png"); ChartUtilities.saveChartAsPNG(plotOutput, chart, 640, 480); fail("Expected no shrink events after reaching equilibrium : saw " + shrinkCount + " [plot in " + plotOutput + "]"); } } static class LongListXYDataset extends AbstractXYDataset { private static final long serialVersionUID = 1119808858992366568L; private final List<String> keys; private final List<List<? extends Number>> data; public LongListXYDataset(Map<String, List<? extends Number>> series) { keys = new ArrayList<String>(series.size()); data = new ArrayList<List<? extends Number>>(series.size()); for (Entry<String, List<? extends Number>> e : series.entrySet()) { keys.add(e.getKey()); data.add(e.getValue()); } } @Override public int getItemCount(int seriesIndex) { return data.get(seriesIndex).size(); } @Override public Number getX(int seriesIndex, int dataIndex) { return Integer.valueOf(dataIndex); } @Override public Number getY(int seriesIndex, int dataIndex) { return data.get(seriesIndex).get(dataIndex); } @Override public int getSeriesCount() { return keys.size(); } @Override public Comparable<?> getSeriesKey(int seriesIndex) { return keys.get(seriesIndex); } } }