/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.test.unit;
import java.io.ByteArrayInputStream;
import java.io.StringReader;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.Random;
import org.h2.engine.Constants;
import org.h2.store.DataHandler;
import org.h2.store.FileStore;
import org.h2.store.LobStorage;
import org.h2.test.TestBase;
import org.h2.test.utils.MemoryFootprint;
import org.h2.tools.SimpleResultSet;
import org.h2.util.SmallLRUCache;
import org.h2.util.TempFileDeleter;
import org.h2.util.Utils;
import org.h2.value.Value;
import org.h2.value.ValueArray;
import org.h2.value.ValueBoolean;
import org.h2.value.ValueByte;
import org.h2.value.ValueBytes;
import org.h2.value.ValueDate;
import org.h2.value.ValueDecimal;
import org.h2.value.ValueDouble;
import org.h2.value.ValueFloat;
import org.h2.value.ValueInt;
import org.h2.value.ValueJavaObject;
import org.h2.value.ValueLong;
import org.h2.value.ValueNull;
import org.h2.value.ValueResultSet;
import org.h2.value.ValueShort;
import org.h2.value.ValueString;
import org.h2.value.ValueStringFixed;
import org.h2.value.ValueStringIgnoreCase;
import org.h2.value.ValueTime;
import org.h2.value.ValueTimestamp;
import org.h2.value.ValueUuid;
/**
* Tests the memory consumption of values. Values can estimate how much memory
* they occupy, and this tests if this estimation is correct.
*/
public class TestValueMemory extends TestBase implements DataHandler {
private Random random = new Random(1);
private SmallLRUCache<String, String[]> lobFileListCache = SmallLRUCache.newInstance(128);
private LobStorage lobStorage;
/**
* Run just this test.
*
* @param a ignored
*/
public static void main(String... a) throws Exception {
// run using -javaagent:ext/h2-1.2.139.jar
TestBase test = TestBase.createCaller().init();
test.config.traceTest = true;
test.test();
}
public void test() throws SQLException {
testCompare();
for (int i = 0; i < Value.TYPE_COUNT; i++) {
Value v = create(i);
String s = "type: " + v.getType() +
" calculated: " + v.getMemory() +
" real: " + MemoryFootprint.getObjectSize(v) + " " +
v.getClass().getName() + ": " + v.toString();
trace(s);
}
for (int i = 0; i < Value.TYPE_COUNT; i++) {
assertEquals(i, create(i).getType());
testType(i);
}
}
private void testCompare() {
ValueDecimal a = ValueDecimal.get(new BigDecimal("0.0"));
ValueDecimal b = ValueDecimal.get(new BigDecimal("-0.00"));
assertTrue(a.hashCode() != b.hashCode());
assertFalse(a.equals(b));
}
private void testType(int type) throws SQLException {
System.gc();
System.gc();
long first = Utils.getMemoryUsed();
ArrayList<Value> list = new ArrayList<Value>();
long memory = 0;
while (memory < 1000000) {
Value v = create(type);
memory += v.getMemory() + Constants.MEMORY_POINTER;
list.add(v);
}
Object[] array = list.toArray();
IdentityHashMap<Object, Object> map = new IdentityHashMap<Object, Object>();
for (Object a : array) {
map.put(a, a);
}
int size = map.size();
map.clear();
map = null;
list = null;
System.gc();
System.gc();
long used = Utils.getMemoryUsed() - first;
memory /= 1024;
if (config.traceTest || used > memory * 3) {
String msg = "Type: " + type + " Used memory: " + used +
" calculated: " + memory + " length: " + array.length + " size: " + size;
if (config.traceTest) {
trace(msg);
}
if (used > memory * 3) {
fail(msg);
}
}
}
private Value create(int type) throws SQLException {
switch (type) {
case Value.NULL:
return ValueNull.INSTANCE;
case Value.BOOLEAN:
return ValueBoolean.get(false);
case Value.BYTE:
return ValueByte.get((byte) random.nextInt());
case Value.SHORT:
return ValueShort.get((short) random.nextInt());
case Value.INT:
return ValueInt.get(random.nextInt());
case Value.LONG:
return ValueLong.get(random.nextLong());
case Value.DECIMAL:
return ValueDecimal.get(new BigDecimal(random.nextInt()));
// + "12123344563456345634565234523451312312"
case Value.DOUBLE:
return ValueDouble.get(random.nextDouble());
case Value.FLOAT:
return ValueFloat.get(random.nextFloat());
case Value.TIME:
return ValueTime.get(new java.sql.Time(random.nextLong()));
case Value.DATE:
return ValueDate.get(new java.sql.Date(random.nextLong()));
case Value.TIMESTAMP:
return ValueTimestamp.get(new java.sql.Timestamp(random.nextLong()));
case Value.BYTES:
return ValueBytes.get(randomBytes(random.nextInt(1000)));
case Value.STRING:
return ValueString.get(randomString(random.nextInt(100)));
case Value.STRING_IGNORECASE:
return ValueStringIgnoreCase.get(randomString(random.nextInt(100)));
case Value.BLOB: {
int len = (int) Math.abs(random.nextGaussian() * 10);
byte[] data = randomBytes(len);
return getLobStorage().createBlob(new ByteArrayInputStream(data), len);
}
case Value.CLOB: {
int len = (int) Math.abs(random.nextGaussian() * 10);
String s = randomString(len);
return getLobStorage().createClob(new StringReader(s), len);
}
case Value.ARRAY: {
int len = random.nextInt(20);
Value[] list = new Value[len];
for (int i = 0; i < list.length; i++) {
list[i] = create(Value.STRING);
}
return ValueArray.get(list);
}
case Value.RESULT_SET:
return ValueResultSet.get(new SimpleResultSet());
case Value.JAVA_OBJECT:
return ValueJavaObject.getNoCopy(randomBytes(random.nextInt(100)));
case Value.UUID:
return ValueUuid.get(random.nextLong(), random.nextLong());
case Value.STRING_FIXED:
return ValueStringFixed.get(randomString(random.nextInt(100)));
default:
throw new AssertionError("type=" + type);
}
}
private byte[] randomBytes(int len) {
byte[] data = new byte[len];
if (random.nextBoolean()) {
// don't initialize always (compression)
random.nextBytes(data);
}
return data;
}
private String randomString(int len) {
char[] chars = new char[len];
if (random.nextBoolean()) {
// don't initialize always (compression)
for (int i = 0; i < chars.length; i++) {
chars[i] = (char) (random.nextGaussian() * 100);
}
}
return new String(chars);
}
public void checkPowerOff() {
// nothing to do
}
public void checkWritingAllowed() {
// nothing to do
}
public void freeUpDiskSpace() {
// nothing to do
}
public String getDatabasePath() {
return getBaseDir() + "/valueMemory";
}
public String getLobCompressionAlgorithm(int type) {
return "LZF";
}
public Object getLobSyncObject() {
return this;
}
public int getMaxLengthInplaceLob() {
return 100;
}
public FileStore openFile(String name, String mode, boolean mustExist) {
return FileStore.open(this, name, mode);
}
public SmallLRUCache<String, String[]> getLobFileListCache() {
return lobFileListCache;
}
public TempFileDeleter getTempFileDeleter() {
return TempFileDeleter.getInstance();
}
public LobStorage getLobStorage() {
if (lobStorage == null) {
lobStorage = new LobStorage(this);
}
return lobStorage;
}
public Connection getLobConnection() {
return null;
}
}