/*
* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code 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 General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.max.vm.log.test;
import java.util.*;
import java.util.concurrent.*;
import com.sun.max.annotate.*;
import com.sun.max.vm.*;
import com.sun.max.vm.log.*;
import com.sun.max.vm.log.VMLog.Flusher;
import com.sun.max.vm.log.VMLog.Record;
import com.sun.max.vm.log.hosted.*;
import com.sun.max.vm.log.nat.VMLogNative.NativeRecord;
import com.sun.max.vm.log.nat.thread.var.*;
import com.sun.max.vm.thread.*;
import com.sun.max.vm.ti.*;
/**
* A stress test for {@link VMLog}.
* Must be included in the boot image, which is controlled by the "max.vmlog.stresstest" property.
* Registers as a {@link VMTI} handler as a way of getting control.
*/
public class VMLogStressTest {
private static class XFlusherDebugLogger extends VMLogger {
XFlusherDebugLogger() {
super("XFlusherDebug", 1, "debug XFlusher");
}
}
private static final XFlusherDebugLogger xFlusherDebugLogger = new XFlusherDebugLogger();
private static class XFlusher extends Flusher {
@Override
public void flushRecord(VmThread vmThread, Record r, int uuid) {
NativeRecord nativeRecord = (NativeRecord) r;
xFlusherDebugLogger.log(0, nativeRecord.address);
logger.trace(r);
}
}
private static class XVMLog extends VMLogNativeThreadVariableUnbound {
private static final String X_RECORD_NAME = "X_RECORD";
private static final String X_BUFFER_NAME = "X_BUFFER";
private static final String X_BUFFER_OFFSETS_NAME = "X_BUFFER_OFFSETS";
private static final VmThreadLocal X_RECORD = new VmThreadLocal(X_RECORD_NAME, true, "VMLog Stress Test Record");
private static final VmThreadLocal X_BUFFER = new VmThreadLocal(X_BUFFER_NAME, false, "VMLog Stress Test buffer");
private static final VmThreadLocal X_BUFFER_OFFSETS = new VmThreadLocal(X_BUFFER_OFFSETS_NAME, false, "VMLog Stress Test buffer first/next offsets");
@Override
public void initialize(MaxineVM.Phase phase) {
super.initialize(phase);
if (MaxineVM.isHosted() && phase == MaxineVM.Phase.BOOTSTRAPPING) {
setNativeRecordThreadLocal(X_RECORD);
setBufferThreadLocals(X_BUFFER, X_BUFFER_OFFSETS);
}
}
int numLogEntries() {
return logEntries;
}
}
private static class Tester extends Thread {
private Random rand;
private LoggedData loggedData;
Tester(int i) {
rand = new Random(46713 + i);
setName("Tester-" + i);
}
@Override
public void run() {
loggedData = new LoggedData();
loggedDataMap.put(VmThread.fromJava(this).id(), loggedData);
long uuid = 0;
int myIterations = iterations;
try {
while (myIterations > 0) {
int argc = rand.nextInt(4);
long[] args = new long[argc + 1];
for (int i = 1; i <= args.length; i++) {
args[i - 1] = rand.nextLong();
}
int index = loggedData.save(new Data(uuid, args));
switch (argc) {
case 0:
logger.logFoo1(uuid, index, args[0]);
break;
case 1:
logger.logFoo2(uuid, index, args[0], args[1]);
break;
case 2:
logger.logFoo3(uuid, index, args[0], args[1], args[2]);
break;
case 3:
logger.logFoo4(uuid, index, args[0], args[1], args[2], args[3]);
}
uuid++;
myIterations--;
}
} catch (Throwable ex) {
Log.println(ex.getMessage());
MaxineVM.native_exit(1);
}
}
}
private static class Data {
final long uuid;
final long[] data;
Data(long uuid, long[] data) {
this.uuid = uuid;
this.data = data;
}
}
/**
* A per-thread store of logged records.
*
*/
private static class LoggedData {
/**
* index where next record will be stored (circular buffer).
*/
int index;
/**
* Circular buffer of logged records.
*/
Data[] dataStore = new Data[xvmLog.numLogEntries() * 2];
/**
* The last uuid checked.
*/
long lastUuid = -1;
/**
* Stores the data in the buffer and returns the index at which it was stored.
* @param data
* @return
*/
int save(Data data) {
int result = index;
dataStore[index++] = data;
if (index >= dataStore.length) {
index = 0;
}
return result;
}
/**
* Checks that the data at index {@code uuid} matches {@code args}.
* and that records are delivered in the correct order with no duplicates/omissions.
* @param uuid
* @param args
*/
void check(long uuid, long index, long[] args) {
asert(uuid == lastUuid + 1);
lastUuid = uuid;
Data storedData = dataStore[(int) index];
asert(storedData.uuid == uuid);
asert(storedData.data.length == args.length);
for (int i = 0; i < args.length; i++) {
asert(storedData.data[i] == args[i]);
}
}
@NEVER_INLINE
void asert(boolean value) {
if (!value) {
throw new RuntimeException("logged data mismatch");
}
}
}
private static int iterations = 1000000;
private static class VMTIHandler extends NullVMTIHandler {
@Override
public void vmInitialized() {
int numThreads = 1;
String extArg = System.getProperty("max.vmlog.stresstest");
if (extArg == null) {
return;
}
String[] args = extArg.split(",");
for (int i = 0; i < args.length; i++) {
String arg = args[i];
if (arg.startsWith("t=")) {
// TODO
} else if (arg.startsWith("c=")) {
iterations = getValue(arg);
}
}
logger.enable(true);
Thread[] threads = new Thread[numThreads];
for (int t = 0; t < numThreads; t++) {
threads[t] = new Tester(t);
threads[t].start();
}
}
private static int getValue(String arg) {
int ix = arg.indexOf('=');
return Integer.parseInt(arg.substring(ix + 1));
}
}
private static final XVMLogger logger = new XVMLogger();
private static final XVMLog xvmLog = new XVMLog();
static {
xvmLog.initialize(MaxineVM.Phase.BOOTSTRAPPING);
xvmLog.registerCustom(logger, new XFlusher());
VMTI.registerEventHandler(new VMTIHandler());
}
private static volatile boolean done;
private static Map<Integer, LoggedData> loggedDataMap = new ConcurrentHashMap<Integer, LoggedData>();
private static class XVMLogger extends XVMLoggerAuto {
XVMLogger() {
super("Stress Tester");
}
@NEVER_INLINE
private static LoggedData getLoggedData(int threadId) {
LoggedData result = loggedDataMap.get(threadId);
assert result != null;
return result;
}
@Override
@NEVER_INLINE
protected void traceFoo1(int threadId, long uuid, long index, long arg1) {
getLoggedData(threadId).check(uuid, index, new long[] {arg1});
}
@Override
@NEVER_INLINE
protected void traceFoo2(int threadId, long uuid, long index, long arg1, long arg2) {
getLoggedData(threadId).check(uuid, index, new long[] {arg1, arg2});
}
@Override
@NEVER_INLINE
protected void traceFoo3(int threadId, long uuid, long index, long arg1, long arg2, long arg3) {
getLoggedData(threadId).check(uuid, index, new long[] {arg1, arg2, arg3});
}
@Override
@NEVER_INLINE
protected void traceFoo4(int threadId, long uuid, long index, long arg1, long arg2, long arg3, long arg4) {
getLoggedData(threadId).check(uuid, index, new long[] {arg1, arg2, arg3, arg4});
}
}
@HOSTED_ONLY
@VMLoggerInterface(hidden = true, traceThread = true)
interface XVMLoggerInterface {
void foo1(long uuid, long index, long value1);
void foo2(long uuid, long index, long value1, long value2);
void foo3(long uuid, long index, long value1, long value2, long value3);
void foo4(long uuid, long index, long value1, long value2, long value3, long value4);
}
// START GENERATED CODE
private static abstract class XVMLoggerAuto extends com.sun.max.vm.log.VMLogger {
public enum Operation {
Foo1, Foo2, Foo3,
Foo4;
@SuppressWarnings("hiding")
public static final Operation[] VALUES = values();
}
private static final int[] REFMAPS = null;
protected XVMLoggerAuto(String name) {
super(name, Operation.VALUES.length, REFMAPS);
}
@Override
public String operationName(int opCode) {
return Operation.VALUES[opCode].name();
}
@INLINE
public final void logFoo1(long arg1, long arg2, long arg3) {
log(Operation.Foo1.ordinal(), longArg(arg1), longArg(arg2), longArg(arg3));
}
protected abstract void traceFoo1(int threadId, long arg1, long arg2, long arg3);
@INLINE
public final void logFoo2(long arg1, long arg2, long arg3, long arg4) {
log(Operation.Foo2.ordinal(), longArg(arg1), longArg(arg2), longArg(arg3), longArg(arg4));
}
protected abstract void traceFoo2(int threadId, long arg1, long arg2, long arg3, long arg4);
@INLINE
public final void logFoo3(long arg1, long arg2, long arg3, long arg4, long arg5) {
log(Operation.Foo3.ordinal(), longArg(arg1), longArg(arg2), longArg(arg3), longArg(arg4), longArg(arg5));
}
protected abstract void traceFoo3(int threadId, long arg1, long arg2, long arg3, long arg4, long arg5);
@INLINE
public final void logFoo4(long arg1, long arg2, long arg3, long arg4, long arg5,
long arg6) {
log(Operation.Foo4.ordinal(), longArg(arg1), longArg(arg2), longArg(arg3), longArg(arg4), longArg(arg5),
longArg(arg6));
}
protected abstract void traceFoo4(int threadId, long arg1, long arg2, long arg3, long arg4, long arg5,
long arg6);
@Override
protected void trace(Record r) {
int threadId = r.getThreadId();
switch (r.getOperation()) {
case 0: { //Foo1
traceFoo1(threadId, toLong(r, 1), toLong(r, 2), toLong(r, 3));
break;
}
case 1: { //Foo2
traceFoo2(threadId, toLong(r, 1), toLong(r, 2), toLong(r, 3), toLong(r, 4));
break;
}
case 2: { //Foo3
traceFoo3(threadId, toLong(r, 1), toLong(r, 2), toLong(r, 3), toLong(r, 4), toLong(r, 5));
break;
}
case 3: { //Foo4
traceFoo4(threadId, toLong(r, 1), toLong(r, 2), toLong(r, 3), toLong(r, 4), toLong(r, 5), toLong(r, 6));
break;
}
}
}
}
// END GENERATED CODE
}