mirror of
https://github.com/berkeleydb/je.git
synced 2024-11-15 01:46:24 +00:00
790 lines
31 KiB
Java
790 lines
31 KiB
Java
|
/*-
|
|||
|
* Copyright (C) 2002, 2017, Oracle and/or its affiliates. All rights reserved.
|
|||
|
*
|
|||
|
* This file was distributed by Oracle as part of a version of Oracle Berkeley
|
|||
|
* DB Java Edition made available at:
|
|||
|
*
|
|||
|
* http://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index.html
|
|||
|
*
|
|||
|
* Please see the LICENSE file included in the top-level directory of the
|
|||
|
* appropriate version of Oracle Berkeley DB Java Edition for a copy of the
|
|||
|
* license and additional information.
|
|||
|
*/
|
|||
|
|
|||
|
import java.io.File;
|
|||
|
import java.util.Random;
|
|||
|
|
|||
|
import com.sleepycat.je.Database;
|
|||
|
import com.sleepycat.je.DatabaseConfig;
|
|||
|
import com.sleepycat.je.DatabaseEntry;
|
|||
|
import com.sleepycat.je.DatabaseException;
|
|||
|
import com.sleepycat.je.Environment;
|
|||
|
import com.sleepycat.je.EnvironmentConfig;
|
|||
|
import com.sleepycat.je.EnvironmentStats;
|
|||
|
import com.sleepycat.je.LockMode;
|
|||
|
import com.sleepycat.je.OperationStatus;
|
|||
|
import com.sleepycat.je.StatsConfig;
|
|||
|
import com.sleepycat.je.utilint.JVMSystemUtils;
|
|||
|
|
|||
|
/**
|
|||
|
* Applications with a large number of databases, randomly open and close
|
|||
|
* databases at any time when needed. The mapping tree nodes (roots) in closed
|
|||
|
* databases won't be evicted from cache immediately. As the applications run
|
|||
|
* over time, this could cause a lot of waste in cache or even bad performance
|
|||
|
* and OutOfMemoryError if cache overflows.
|
|||
|
*
|
|||
|
* We want to simulate such a scenario to test the efficiency of eviction of
|
|||
|
* closed databases for SR 13415, to make sure that the eviction would not
|
|||
|
* cause corruption or concurrency bugs:
|
|||
|
* + Ensure that concurrency bugs don't occur when multiple threads are trying
|
|||
|
* to close, evict and open a single database.
|
|||
|
* + Another potential problem is that the database doesn't open correctly
|
|||
|
* after being closed and evicted;
|
|||
|
* + Cache budgeting is not done correctly during eviction or re-loading of
|
|||
|
* the database after eviction.
|
|||
|
*
|
|||
|
*/
|
|||
|
public class ClosedDbEviction {
|
|||
|
private static int nDataAccessDbs = 1;
|
|||
|
private static int nRegularDbs = 100000;
|
|||
|
private static int nDbRecords = 100;
|
|||
|
private static int nInitThreads = 8;
|
|||
|
private static int nContentionThreads = 4;
|
|||
|
private static int nDbsPerSet = 5;
|
|||
|
private static int nKeepOpenedDbs = 100;
|
|||
|
private static int subDir = 3;
|
|||
|
private static boolean offHeap = false;
|
|||
|
private static int nOps[] = new int[nContentionThreads];
|
|||
|
private static long nTxnPerRecovery = 1000000l;
|
|||
|
private static long nTotalTxns = 100000000l;
|
|||
|
private static boolean verbose = false;
|
|||
|
private static boolean init = false;
|
|||
|
private static boolean contention = false;
|
|||
|
private static boolean evict = false;
|
|||
|
private static boolean recovery = false;
|
|||
|
private static boolean runDataAccessThread = true;
|
|||
|
private static String homeDir = "./tmp";
|
|||
|
private static Environment env = null;
|
|||
|
private static Database dataAccessDb = null;
|
|||
|
private static Database metadataDb = null;
|
|||
|
private static Database[] openDbList = new Database[nKeepOpenedDbs];
|
|||
|
private static Random random = new Random();
|
|||
|
private static Runtime rt = Runtime.getRuntime();
|
|||
|
|
|||
|
public static void main(String[] args) {
|
|||
|
try {
|
|||
|
ClosedDbEviction eviction = new ClosedDbEviction();
|
|||
|
eviction.start(args);
|
|||
|
} catch (Throwable e) {
|
|||
|
e.printStackTrace();
|
|||
|
System.exit(1);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/* Output command-line input arguments to log. */
|
|||
|
private void printArgs(String[] args) {
|
|||
|
System.out.print("\nCommand line arguments:");
|
|||
|
for (String arg : args) {
|
|||
|
System.out.print(' ');
|
|||
|
System.out.print(arg);
|
|||
|
}
|
|||
|
System.out.println();
|
|||
|
}
|
|||
|
|
|||
|
void start(String[] args) {
|
|||
|
try {
|
|||
|
if (args.length == 0) {
|
|||
|
throw new IllegalArgumentException();
|
|||
|
}
|
|||
|
|
|||
|
/* Parse command-line input arguments. */
|
|||
|
for (int i = 0; i < args.length; i++) {
|
|||
|
String arg = args[i];
|
|||
|
String arg2 = (i < args.length - 1) ? args[i + 1] : null;
|
|||
|
if (arg.equals("-v")) {
|
|||
|
verbose = true;
|
|||
|
} else if (arg.equals("-h")) {
|
|||
|
if (arg2 == null) {
|
|||
|
throw new IllegalArgumentException(arg);
|
|||
|
}
|
|||
|
homeDir = args[++i];
|
|||
|
} else if (arg.equals("-init")) {
|
|||
|
if (arg2 == null) {
|
|||
|
throw new IllegalArgumentException(arg);
|
|||
|
}
|
|||
|
try {
|
|||
|
nRegularDbs = Integer.parseInt(args[++i]);
|
|||
|
} catch (NumberFormatException e) {
|
|||
|
throw new IllegalArgumentException(arg2);
|
|||
|
}
|
|||
|
init = true;
|
|||
|
} else if (arg.equals("-contention")) {
|
|||
|
if (arg2 == null) {
|
|||
|
throw new IllegalArgumentException(arg);
|
|||
|
}
|
|||
|
try {
|
|||
|
nTotalTxns = Long.parseLong(args[++i]);
|
|||
|
} catch (NumberFormatException e) {
|
|||
|
throw new IllegalArgumentException(arg2);
|
|||
|
}
|
|||
|
contention = true;
|
|||
|
} else if (arg.equals("-evict")) {
|
|||
|
if (arg2 == null) {
|
|||
|
throw new IllegalArgumentException(arg);
|
|||
|
}
|
|||
|
try {
|
|||
|
nTotalTxns = Long.parseLong(args[++i]);
|
|||
|
} catch (NumberFormatException e) {
|
|||
|
throw new IllegalArgumentException(arg2);
|
|||
|
}
|
|||
|
evict = true;
|
|||
|
} else if (arg.equals("-recovery")) {
|
|||
|
if (arg2 == null) {
|
|||
|
throw new IllegalArgumentException(arg);
|
|||
|
}
|
|||
|
try {
|
|||
|
nTxnPerRecovery = Long.parseLong(args[++i]);
|
|||
|
} catch (NumberFormatException e) {
|
|||
|
throw new IllegalArgumentException(arg2);
|
|||
|
}
|
|||
|
recovery = true;
|
|||
|
} else if (arg.equals("-subDir")) {
|
|||
|
if (arg2 == null) {
|
|||
|
throw new IllegalArgumentException(arg);
|
|||
|
}
|
|||
|
try {
|
|||
|
subDir = Integer.parseInt(args[++i]);
|
|||
|
} catch (NumberFormatException e) {
|
|||
|
throw new IllegalArgumentException(arg2);
|
|||
|
}
|
|||
|
} else if (arg.equals("-offheap")) {
|
|||
|
if (arg2 == null) {
|
|||
|
throw new IllegalArgumentException(arg);
|
|||
|
}
|
|||
|
offHeap = Boolean.parseBoolean(args[++i]);
|
|||
|
} else {
|
|||
|
throw new IllegalArgumentException(arg);
|
|||
|
}
|
|||
|
}
|
|||
|
/* Correctness self-check: nTotalTxns >= nTxnPerRecovery. */
|
|||
|
if (nTotalTxns < nTxnPerRecovery) {
|
|||
|
System.err.println
|
|||
|
("ERROR: <nTotalTxns> argument should be larger than " +
|
|||
|
nTxnPerRecovery + "!");
|
|||
|
System.exit(1);
|
|||
|
}
|
|||
|
printArgs(args);
|
|||
|
} catch (IllegalArgumentException e) {
|
|||
|
System.out.println
|
|||
|
("Usage: ClosedDbEviction [-v] -h <envHome> -init <nDbs>\n" +
|
|||
|
"Usage: ClosedDbEviction [-v] -h <envHome> " +
|
|||
|
"[-contention <nTotalTxns> | -evict <nTotalTxns>] " +
|
|||
|
"[-recovery <nTxnsPerRecovery>]");
|
|||
|
e.printStackTrace();
|
|||
|
System.exit(1);
|
|||
|
}
|
|||
|
|
|||
|
try {
|
|||
|
if (init) {
|
|||
|
doInit();
|
|||
|
} else if (contention) {
|
|||
|
doContention();
|
|||
|
} else if (evict) {
|
|||
|
doEvict();
|
|||
|
} else {
|
|||
|
System.err.println("No such argument.");
|
|||
|
System.exit(1);
|
|||
|
}
|
|||
|
} catch (Throwable e) {
|
|||
|
e.printStackTrace();
|
|||
|
System.exit(1);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Initialize nRegularDBs, one dataAccessDb and one metadataDB.
|
|||
|
*/
|
|||
|
private void doInit() {
|
|||
|
|
|||
|
class InitThread extends Thread {
|
|||
|
public int id;
|
|||
|
private Environment env = null;
|
|||
|
private Database db = null;
|
|||
|
|
|||
|
/**
|
|||
|
* Constructor used for initializing databases.
|
|||
|
*/
|
|||
|
InitThread(int id, Environment env) {
|
|||
|
this.id = id;
|
|||
|
this.env = env;
|
|||
|
}
|
|||
|
|
|||
|
public void run() {
|
|||
|
try {
|
|||
|
DatabaseConfig dbConfig = new DatabaseConfig();
|
|||
|
dbConfig.setAllowCreate(true);
|
|||
|
DatabaseEntry key = new DatabaseEntry();
|
|||
|
DatabaseEntry data = new DatabaseEntry();
|
|||
|
for (int i = 0;
|
|||
|
i <= ((nRegularDbs + nDataAccessDbs) / nInitThreads);
|
|||
|
i++) {
|
|||
|
|
|||
|
int dbId = id + (i * nInitThreads);
|
|||
|
int totalRecords = nDbRecords;
|
|||
|
boolean isDataAccessDb = false;
|
|||
|
String dbName = "db" + dbId;
|
|||
|
dbConfig.setDeferredWrite(dbId <= (nRegularDbs / 10));
|
|||
|
if (dbId >= nRegularDbs) {
|
|||
|
if (dbId < (nRegularDbs + nDataAccessDbs)) {
|
|||
|
isDataAccessDb = true;
|
|||
|
dbName = "dataAccessDb";
|
|||
|
totalRecords = 10 * nDbRecords;
|
|||
|
} else {
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
/* Open the database. */
|
|||
|
db = env.openDatabase(null, dbName, dbConfig);
|
|||
|
/* Insert totalRecords into database. */
|
|||
|
for (int j = 0; j < totalRecords; j++) {
|
|||
|
key.setData(Integer.toString(j).getBytes("UTF-8"));
|
|||
|
makeData(data, j, isDataAccessDb);
|
|||
|
OperationStatus status = db.put(null, key, data);
|
|||
|
if (status != OperationStatus.SUCCESS) {
|
|||
|
System.err.println
|
|||
|
("ERROR: failed to insert the #" + j +
|
|||
|
" key/data pair into " +
|
|||
|
db.getDatabaseName());
|
|||
|
System.exit(1);
|
|||
|
}
|
|||
|
}
|
|||
|
db.close();
|
|||
|
}
|
|||
|
} catch (Throwable e) {
|
|||
|
e.printStackTrace();
|
|||
|
System.exit(1);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Generate the data. nDataAccessDbs should have a bigger size of
|
|||
|
* data entry; regularDbs only make data entry equal to
|
|||
|
* (index + "th-dataEntry").
|
|||
|
*/
|
|||
|
private void makeData(DatabaseEntry data,
|
|||
|
int index,
|
|||
|
boolean isDataAccessDb) throws Exception {
|
|||
|
|
|||
|
assert (data != null) : "makeData: Null data pointer";
|
|||
|
|
|||
|
if (isDataAccessDb) {
|
|||
|
byte[] bytes = new byte[1024];
|
|||
|
for (int i = 0; i < bytes.length; i++) {
|
|||
|
bytes[i] = (byte) i;
|
|||
|
}
|
|||
|
data.setData(bytes);
|
|||
|
} else {
|
|||
|
data.setData((Integer.toString(index) + "th-dataEntry").
|
|||
|
getBytes("UTF-8"));
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Initialize "nRegularDbs" regular Dbs, one dataAccessDb and one
|
|||
|
* metaDataDb according to these rules:
|
|||
|
* - The "nRegularDBs" databases, with the dbIds range from
|
|||
|
* 0 to (nRegularDBs - 1). Each of them would have "nDbRecords".
|
|||
|
* - 10% of all "nRegularDBs" are deferredWrite databases.
|
|||
|
* - 90% of all "nRegularDBs" are regular databases.
|
|||
|
* - The dataAccessDb has "10 * nDbRecords" key/data pairs.
|
|||
|
* - The metaDataDb is to save "nRegularDbs" info for contention test.
|
|||
|
*/
|
|||
|
try {
|
|||
|
openEnv(128 * 1024 * 1024);
|
|||
|
saveMetadata();
|
|||
|
InitThread[] threads = new InitThread[nInitThreads];
|
|||
|
long startTime = System.currentTimeMillis();
|
|||
|
for (int i = 0; i < threads.length; i++) {
|
|||
|
InitThread t = new InitThread(i, env);
|
|||
|
t.start();
|
|||
|
threads[i] = t;
|
|||
|
}
|
|||
|
for (int i = 0; i < threads.length; i++) {
|
|||
|
threads[i].join();
|
|||
|
}
|
|||
|
long endTime = System.currentTimeMillis();
|
|||
|
if (verbose) {
|
|||
|
float elapsedSeconds = (endTime - startTime) / 1000f;
|
|||
|
float throughput = (nRegularDbs * nDbRecords) / elapsedSeconds;
|
|||
|
System.out.println
|
|||
|
("\nInitialization Statistics Report" +
|
|||
|
"\n Run starts at: " + (new java.util.Date(startTime)) +
|
|||
|
", finishes at: " + (new java.util.Date(endTime)) +
|
|||
|
"\n Initialized " + nRegularDbs + " databases, " +
|
|||
|
"each contains " + nDbRecords + " records." +
|
|||
|
"\n Elapsed seconds: " + elapsedSeconds +
|
|||
|
", throughput: " + throughput + " ops/sec.");
|
|||
|
}
|
|||
|
closeEnv();
|
|||
|
} catch (DatabaseException de) {
|
|||
|
de.printStackTrace();
|
|||
|
System.exit(1);
|
|||
|
} catch (Throwable e) {
|
|||
|
e.printStackTrace();
|
|||
|
System.exit(1);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Simulate some contentions to make sure that the eviction would not
|
|||
|
* cause corruption or concurrency bugs.
|
|||
|
*/
|
|||
|
private void doContention() {
|
|||
|
|
|||
|
class ContentionThread extends Thread {
|
|||
|
public int id;
|
|||
|
private float dataCheckPossibility = .01f;
|
|||
|
private long txns;
|
|||
|
private boolean done = false;
|
|||
|
private Database currentDb = null;
|
|||
|
private Database lastOpenedDb = null;
|
|||
|
|
|||
|
/**
|
|||
|
* Constructor used for initializing databases.
|
|||
|
*/
|
|||
|
ContentionThread(int id, long txns) {
|
|||
|
this.id = id;
|
|||
|
this.txns = txns;
|
|||
|
}
|
|||
|
|
|||
|
public void run() {
|
|||
|
try {
|
|||
|
|
|||
|
/* Start dataAccessThread here. */
|
|||
|
startDataAccessor();
|
|||
|
|
|||
|
/*
|
|||
|
* All contention threads try to open "nDbsPerSet" DBs
|
|||
|
* from the same set concurrently.
|
|||
|
*/
|
|||
|
while (!done) {
|
|||
|
int dbId = random.nextInt(nDbsPerSet);
|
|||
|
currentDb = env.openDatabase(null, "db" + dbId, null);
|
|||
|
if (lastOpenedDb != null) {
|
|||
|
lastOpenedDb.close();
|
|||
|
}
|
|||
|
lastOpenedDb = currentDb;
|
|||
|
if (random.nextFloat() <= dataCheckPossibility) {
|
|||
|
verifyData();
|
|||
|
}
|
|||
|
nOps[id]++;
|
|||
|
if (nOps[id] > txns) {
|
|||
|
if (lastOpenedDb != null) {
|
|||
|
lastOpenedDb.close();
|
|||
|
}
|
|||
|
done = true;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/* Stop dataAccessThread here. */
|
|||
|
stopDataAccessor();
|
|||
|
} catch (Throwable e) {
|
|||
|
e.printStackTrace();
|
|||
|
System.exit(1);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private void startDataAccessor() {
|
|||
|
runDataAccessThread = true;
|
|||
|
}
|
|||
|
|
|||
|
private void stopDataAccessor() {
|
|||
|
runDataAccessThread = false;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Do the corruption check: just check that the data
|
|||
|
* that is present looks correct.
|
|||
|
*/
|
|||
|
private void verifyData() throws Exception {
|
|||
|
long dbCount = currentDb.count();
|
|||
|
if (dbCount != nDbRecords) {
|
|||
|
System.err.println
|
|||
|
("WARNING: total records in " +
|
|||
|
currentDb.getDatabaseName() + ": " + dbCount +
|
|||
|
" doesn't meet the expected value: " + nDbRecords);
|
|||
|
System.exit(1);
|
|||
|
} else {
|
|||
|
DatabaseEntry key = new DatabaseEntry();
|
|||
|
DatabaseEntry data = new DatabaseEntry();
|
|||
|
for (int i = 0; i < nDbRecords; i++) {
|
|||
|
key.setData(Integer.toString(i).getBytes("UTF-8"));
|
|||
|
OperationStatus status =
|
|||
|
currentDb.get(null, key, data, LockMode.DEFAULT);
|
|||
|
if (status != OperationStatus.SUCCESS) {
|
|||
|
System.err.println
|
|||
|
("ERROR: failed to retrieve the #" +
|
|||
|
i + " key/data pair from " +
|
|||
|
currentDb.getDatabaseName());
|
|||
|
System.exit(1);
|
|||
|
} else if (!(new String(data.getData(), "UTF-8")).
|
|||
|
equals((Integer.toString(i) +
|
|||
|
"th-dataEntry"))) {
|
|||
|
System.err.println
|
|||
|
("ERROR: current key/data pair: " + i +
|
|||
|
"/" + (new String(data.getData(), "UTF-8")) +
|
|||
|
" doesn't match the expected: " +
|
|||
|
i + "/" + i +"th-dataEntry in " +
|
|||
|
currentDb.getDatabaseName());
|
|||
|
System.exit(1);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
class DataAccessThread extends Thread {
|
|||
|
|
|||
|
public void run() {
|
|||
|
try {
|
|||
|
while (runDataAccessThread) {
|
|||
|
/* Access records to fill up cache. */
|
|||
|
DatabaseEntry key = new DatabaseEntry();
|
|||
|
key.setData(Integer.
|
|||
|
toString(random.nextInt(10 * nDbRecords)).
|
|||
|
getBytes("UTF-8"));
|
|||
|
DatabaseEntry data = new DatabaseEntry();
|
|||
|
OperationStatus status =
|
|||
|
dataAccessDb.get(null, key, data,
|
|||
|
LockMode.DEFAULT);
|
|||
|
if (status != OperationStatus.SUCCESS) {
|
|||
|
System.err.println
|
|||
|
("ERROR: failed to retrieve the #" +
|
|||
|
new String(key.getData(), "UTF-8") +
|
|||
|
" key/data pair from dataAccessDb.");
|
|||
|
System.exit(1);
|
|||
|
}
|
|||
|
}
|
|||
|
} catch (Throwable e) {
|
|||
|
e.printStackTrace();
|
|||
|
System.exit(1);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Simulate some contentions according to following rules:
|
|||
|
* - Several threads try to open/close a set of databases repeatedly.
|
|||
|
* - The other thread will continually access records from dataAccessDb
|
|||
|
* to fill up cache.
|
|||
|
*/
|
|||
|
try {
|
|||
|
long startTime = System.currentTimeMillis();
|
|||
|
long txns = nTotalTxns;
|
|||
|
if (recovery) {
|
|||
|
txns = nTxnPerRecovery;
|
|||
|
}
|
|||
|
for (int loop = 0; loop < nTotalTxns / txns; loop++) {
|
|||
|
/* Clear nOps[] before each run starts. */
|
|||
|
for (int i = 0; i < nContentionThreads; i++) {
|
|||
|
nOps[i] = 0;
|
|||
|
}
|
|||
|
openEnv(1024 * 1024);
|
|||
|
readMetadata();
|
|||
|
DataAccessThread dat = new DataAccessThread();
|
|||
|
ContentionThread[] threads =
|
|||
|
new ContentionThread[nContentionThreads];
|
|||
|
for (int i = 0; i < threads.length; i++) {
|
|||
|
ContentionThread t =
|
|||
|
new ContentionThread(i, txns);
|
|||
|
t.start();
|
|||
|
threads[i] = t;
|
|||
|
}
|
|||
|
dat.start();
|
|||
|
for (int i = 0; i < threads.length; i++) {
|
|||
|
threads[i].join();
|
|||
|
}
|
|||
|
dat.join();
|
|||
|
if (!checkStats(txns)) {
|
|||
|
System.err.println
|
|||
|
("doContention: stats check failed.");
|
|||
|
System.exit(1);
|
|||
|
}
|
|||
|
closeEnv();
|
|||
|
}
|
|||
|
long endTime = System.currentTimeMillis();
|
|||
|
float elapsedSecs = (endTime - startTime) / 1000f;
|
|||
|
float throughput = nTotalTxns / elapsedSecs;
|
|||
|
if (verbose) {
|
|||
|
System.out.println
|
|||
|
("\nContention Test Statistics Report" +
|
|||
|
"\n Starts at: " + (new java.util.Date(startTime)) +
|
|||
|
", Finishes at: " + (new java.util.Date(endTime)) +
|
|||
|
"\n Total operations: " + nTotalTxns +
|
|||
|
", Elapsed seconds: " + elapsedSecs +
|
|||
|
", Throughput: " + throughput + " ops/sec.");
|
|||
|
}
|
|||
|
} catch (DatabaseException de) {
|
|||
|
de.printStackTrace();
|
|||
|
System.exit(1);
|
|||
|
} catch (Throwable e) {
|
|||
|
e.printStackTrace();
|
|||
|
System.exit(1);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private void doEvict() {
|
|||
|
final int offset = random.nextInt(nRegularDbs - nKeepOpenedDbs);
|
|||
|
|
|||
|
class EvictThread extends Thread {
|
|||
|
public int id;
|
|||
|
private float dataAccessPossibility = .01f;
|
|||
|
private long txns = 0;
|
|||
|
private Database currentDb = null;
|
|||
|
private Database lastOpenedDb = null;
|
|||
|
|
|||
|
/**
|
|||
|
* Constructor.
|
|||
|
*/
|
|||
|
public EvictThread(int id, long txns) {
|
|||
|
this.id = id;
|
|||
|
this.txns = txns;
|
|||
|
}
|
|||
|
|
|||
|
public void run() {
|
|||
|
try {
|
|||
|
int dbId;
|
|||
|
boolean done = false;
|
|||
|
while (!done) {
|
|||
|
dbId = random.nextInt(nRegularDbs);
|
|||
|
if ((0 <= (dbId - offset)) &&
|
|||
|
((dbId - offset) < nKeepOpenedDbs)) {
|
|||
|
|
|||
|
/*
|
|||
|
* Randomly select nKeepOpenedDbs databases opened
|
|||
|
* in a time. The dbId ranges from <offset> to
|
|||
|
* <offset + nKeepOpenedDbs - 1>.
|
|||
|
*/
|
|||
|
if (openDbList[dbId - offset] == null) {
|
|||
|
openDbList[dbId - offset] =
|
|||
|
env.openDatabase(null, "db" + dbId, null);
|
|||
|
}
|
|||
|
} else {
|
|||
|
/* Each thread select randomly from all DBs. */
|
|||
|
currentDb =
|
|||
|
env.openDatabase(null, "db" + dbId, null);
|
|||
|
if (random.nextFloat() < dataAccessPossibility) {
|
|||
|
DatabaseEntry key = new DatabaseEntry();
|
|||
|
DatabaseEntry data = new DatabaseEntry();
|
|||
|
key.setData(Integer.toString
|
|||
|
(random.nextInt(nDbRecords)).
|
|||
|
getBytes("UTF-8"));
|
|||
|
currentDb.get(null, key, data,
|
|||
|
LockMode.DEFAULT);
|
|||
|
}
|
|||
|
if (lastOpenedDb != null) {
|
|||
|
lastOpenedDb.close();
|
|||
|
}
|
|||
|
lastOpenedDb = currentDb;
|
|||
|
}
|
|||
|
nOps[id]++;
|
|||
|
if (nOps[id] > txns) {
|
|||
|
if (lastOpenedDb != null) {
|
|||
|
lastOpenedDb.close();
|
|||
|
}
|
|||
|
/* Close nKeepOpenedDbs before exit. */
|
|||
|
for (int i = 0; i < nKeepOpenedDbs; i++) {
|
|||
|
currentDb = openDbList[i];
|
|||
|
if (currentDb != null) {
|
|||
|
currentDb.close();
|
|||
|
openDbList[i] = null;
|
|||
|
}
|
|||
|
}
|
|||
|
done = true;
|
|||
|
}
|
|||
|
}
|
|||
|
} catch (Throwable e) {
|
|||
|
e.printStackTrace();
|
|||
|
System.exit(1);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Simulate some contentions according to following rules:
|
|||
|
* - Several threads try to open/close a set of databases repeatedly.
|
|||
|
* - The other thread will continually access records from dataAccessDb
|
|||
|
* to fill up cache.
|
|||
|
*/
|
|||
|
try {
|
|||
|
long startTime = System.currentTimeMillis();
|
|||
|
long txns = nTotalTxns;
|
|||
|
if (recovery) {
|
|||
|
txns = nTxnPerRecovery;
|
|||
|
}
|
|||
|
for (int loop = 0; loop < nTotalTxns / txns; loop++) {
|
|||
|
/* Clear nOps[] before each run starts. */
|
|||
|
for (int i = 0; i < nContentionThreads; i++) {
|
|||
|
nOps[i] = 0;
|
|||
|
}
|
|||
|
/* When using Zing JDK, the cache size should be increased. */
|
|||
|
if (JVMSystemUtils.ZING_JVM) {
|
|||
|
openEnv(8 * 1024 * 1024);
|
|||
|
} else {
|
|||
|
openEnv(512 * 1024);
|
|||
|
}
|
|||
|
readMetadata();
|
|||
|
EvictThread[] threads = new EvictThread[nContentionThreads];
|
|||
|
for (int i = 0; i < threads.length; i++) {
|
|||
|
EvictThread t = new EvictThread(i, txns);
|
|||
|
t.start();
|
|||
|
threads[i] = t;
|
|||
|
}
|
|||
|
for (int i = 0; i < threads.length; i++) {
|
|||
|
threads[i].join();
|
|||
|
}
|
|||
|
if (!checkStats(txns)) {
|
|||
|
System.err.println("doEvict: stats check failed.");
|
|||
|
System.exit(1);
|
|||
|
}
|
|||
|
closeEnv();
|
|||
|
}
|
|||
|
long endTime = System.currentTimeMillis();
|
|||
|
if (verbose) {
|
|||
|
float elapsedSeconds = (endTime - startTime) / 1000f;
|
|||
|
float throughput = nTotalTxns / elapsedSeconds;
|
|||
|
System.out.println
|
|||
|
("\nEviction Test Statistics Report" +
|
|||
|
"\n Run starts at: " + (new java.util.Date(startTime)) +
|
|||
|
", finishes at: " + (new java.util.Date(endTime)) +
|
|||
|
"\n Total operations: " + nTotalTxns +
|
|||
|
", Elapsed seconds: " + elapsedSeconds +
|
|||
|
", Throughput: " + throughput + " ops/sec.");
|
|||
|
}
|
|||
|
} catch (DatabaseException de) {
|
|||
|
de.printStackTrace();
|
|||
|
System.exit(1);
|
|||
|
} catch (Throwable e) {
|
|||
|
e.printStackTrace();
|
|||
|
System.exit(1);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Open an Environment.
|
|||
|
*/
|
|||
|
private void openEnv(long cacheSize) throws DatabaseException {
|
|||
|
|
|||
|
EnvironmentConfig envConfig = new EnvironmentConfig();
|
|||
|
envConfig.setAllowCreate(true);
|
|||
|
envConfig.setCacheSize(cacheSize);
|
|||
|
if (offHeap) {
|
|||
|
/* Do not reduce main cache size, test will run too slowly. */
|
|||
|
envConfig.setOffHeapCacheSize(cacheSize);
|
|||
|
}
|
|||
|
if (subDir > 0) {
|
|||
|
envConfig.setConfigParam
|
|||
|
(EnvironmentConfig.LOG_N_DATA_DIRECTORIES, subDir + "");
|
|||
|
Utils.createSubDirs(new File(homeDir), subDir, true);
|
|||
|
}
|
|||
|
env = new Environment(new File(homeDir), envConfig);
|
|||
|
if (contention) {
|
|||
|
dataAccessDb = env.openDatabase(null, "dataAccessDb", null);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Check to see if stats looks correct.
|
|||
|
*/
|
|||
|
private boolean checkStats(long txns) throws DatabaseException {
|
|||
|
|
|||
|
/* Get EnvironmentStats numbers. */
|
|||
|
StatsConfig statsConfig = new StatsConfig();
|
|||
|
statsConfig.setFast(true);
|
|||
|
statsConfig.setClear(true);
|
|||
|
EnvironmentStats stats = env.getStats(statsConfig);
|
|||
|
long evictedINs = stats.getNNodesExplicitlyEvicted();
|
|||
|
long evictedRoots = stats.getNRootNodesEvicted();
|
|||
|
long dataBytes = stats.getDataBytes();
|
|||
|
/* Check the eviction of INs and ROOTs actually happens. */
|
|||
|
boolean nodesCheck = (evictedINs > 0);
|
|||
|
boolean rootsCheck = (evictedRoots > 0);
|
|||
|
if (verbose) {
|
|||
|
System.out.printf
|
|||
|
("\n\tEviction Statistics(calc txns: %d)%n" +
|
|||
|
" Data Pass/Fail%n" +
|
|||
|
" ---------- ---------%n" +
|
|||
|
"EvictedINs: %10d %9S%n" +
|
|||
|
"EvictedRoots:%10d %9S%n" +
|
|||
|
"DataBytes: %10d%n" +
|
|||
|
"jvm.maxMem: %10d%n" +
|
|||
|
"jvm.freeMem: %10d%n" +
|
|||
|
"jvm.totlMem: %10d%n",
|
|||
|
txns, evictedINs, (nodesCheck ? "PASS" : "FAIL"),
|
|||
|
evictedRoots, (rootsCheck ? "PASS" : "FAIL"),
|
|||
|
dataBytes, rt.maxMemory(), rt.freeMemory(), rt.totalMemory());
|
|||
|
System.out.println
|
|||
|
("The test criteria: EvictedINs > 0, EvictedRoots > 0.");
|
|||
|
}
|
|||
|
|
|||
|
return nodesCheck && rootsCheck;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Close the Databases and Environment.
|
|||
|
*/
|
|||
|
private void closeEnv() throws DatabaseException {
|
|||
|
|
|||
|
if (dataAccessDb != null) {
|
|||
|
dataAccessDb.close();
|
|||
|
}
|
|||
|
if (env != null) {
|
|||
|
env.close();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Store meta-data information into metadataDb.
|
|||
|
*/
|
|||
|
private void saveMetadata() throws Exception {
|
|||
|
|
|||
|
/* Store meta-data information into one additional database. */
|
|||
|
DatabaseConfig dbConfig = new DatabaseConfig();
|
|||
|
dbConfig.setAllowCreate(true);
|
|||
|
metadataDb = env.openDatabase(null, "metadataDb", dbConfig);
|
|||
|
OperationStatus status =
|
|||
|
metadataDb.put(null,
|
|||
|
new DatabaseEntry("nRegularDbs".getBytes("UTF-8")),
|
|||
|
new DatabaseEntry(Integer.
|
|||
|
toString(nRegularDbs).
|
|||
|
getBytes("UTF-8")));
|
|||
|
if (status != OperationStatus.SUCCESS) {
|
|||
|
System.err.println
|
|||
|
("Not able to save info into the metadata database.");
|
|||
|
System.exit(1);
|
|||
|
}
|
|||
|
metadataDb.close();
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Retrieve meta-data information from metadataDb.
|
|||
|
*/
|
|||
|
private void readMetadata() throws Exception {
|
|||
|
|
|||
|
/* Retrieve meta-data information from metadataDB. */
|
|||
|
metadataDb = env.openDatabase(null, "metadataDb", null);
|
|||
|
DatabaseEntry key = new DatabaseEntry("nRegularDbs".getBytes("UTF-8"));
|
|||
|
DatabaseEntry data = new DatabaseEntry();
|
|||
|
OperationStatus status =
|
|||
|
metadataDb.get(null, key, data, LockMode.DEFAULT);
|
|||
|
if (status != OperationStatus.SUCCESS) {
|
|||
|
System.err.println
|
|||
|
("Couldn't retrieve info from the metadata database.");
|
|||
|
System.exit(1);
|
|||
|
}
|
|||
|
nRegularDbs = Integer.parseInt(new String (data.getData(), "UTF-8"));
|
|||
|
metadataDb.close();
|
|||
|
}
|
|||
|
}
|