package ca.uwaterloo.fydp.xcde;
import ca.uwaterloo.fydp.ossp.*;
import ca.uwaterloo.fydp.ossp.std.*;
import ca.uwaterloo.fydp.ossp.impl.OSSPImpl;
import java.util.*;
import java.net.InetAddress;
import java.net.UnknownHostException;
import ca.uwaterloo.fydp.ossp.impl.OSSPSimpleSynchronizer;
public class XCDEStressTest2 {
private static final int maxDepth = 10;
private static final int disconnectThreshold = 10;
private static class DirObj implements XCDEDirClientCallbacks {
public DirObj(List _cntnr, XCDEDirClient _cl) {
_cl.setCallbacks(this);
cntnr = _cntnr;
cntnr.add(_cl);
}
List cntnr; // the list the client
public void receiveState(XCDEDirClient _cl, List _ent) {
}
public void notifyOfDisconnect(XCDEDirClient _cl) {
// This routinely happens when deleted.
cntnr.remove(_cl);
}
public void notifyOfChange(XCDEDirClient _cl, OSSPStimulus stim) {
}
}
private static class RootDirObj extends DirObj implements XCDERootDirClientCallbacks {
public RootDirObj(List _cntnr, XCDEDirClient _cl) {
super(_cntnr, _cl);
}
public void receiveUsers(XCDEDirClient _cl, List _users) {
}
public void notifyOfDisconnect(XCDEDirClient _cl) {
System.err.println("The root was disconnected.");
System.exit(-1);
}
}
private static class DocObj implements XCDEDocClientCallbacks {
public DocObj(List _cntnr, XCDEDocClient _cl) {
_cl.setCallback(this);
cntnr = _cntnr;
cntnr.add(_cl);
}
List cntnr;
public void receiveState(XCDEDocClient _cl, XCDEDocument _doc) {
}
public void notifyOfDisconnect(XCDEDocClient _cl) {
cntnr.remove(_cl);
}
public void notifyOfChange(XCDEDocClient _cl, OSSPStimulus stim) {
}
}
private static java.util.Random rnd = new java.util.Random();
private static int randomInt(int max) {
return (int)((max + 1) * rnd.nextDouble());
}
private static OSSPStimulus randomEdit(XCDEDocument file) {
final double insertProb = 0.5;
double val = rnd.nextDouble();
int pos = randomInt(file.content.length());
int sel = randomInt(file.content.length() - pos);
if (val < insertProb) {
// Insert
return new XCDEDocChangeInsertion("anon", pos, randomString());
} else { // i.e. delete text
// Delete
return new XCDEDocChangeDeletion("anon", pos, sel);
}
}
private static String randomString() {
final int longestString = 32;
//
// Add 1 to the random number b/c element names of length 0 are
// illegal and inserts of length 0 are pointless.
//
int length = 1 + randomInt(longestString);
StringBuffer buf = new StringBuffer();
for (int i = 0; i < length; i++) {
// Choose a random ASCII letter or digit.
char c;
for (;;) {
c = (char)(byte)(rnd.nextDouble()*256);
if ((('a' <= c) && (c <= 'z')) || (('A' <= c) && (c <= 'Z')) || (('0' <= c) && (c <= '9'))) {
break;
}
}
buf.append(c);
}
return buf.toString();
}
private static String randomUsedUserName(XCDERootDirectory doc) {
return ((XCDERootDirectoryUserState)doc.users.get(randomInt(doc.users.size()-1))).userName;
}
private static String randomUnusedUserName(XCDERootDirectory doc) {
outer: for (;;) {
String s = randomString();
for (ListIterator i = doc.users.listIterator(); i.hasNext(); ) {
String name = ((XCDERootDirectoryUserState)i.next()).userName;
if (s.equals(name)) {
continue outer;
}
}
return s;
}
}
private static XCDEDocumentSelection randomSelection(XCDEDocument file) {
int pos = randomInt(file.content.length());
int sel = randomInt(file.content.length() - pos);
return new XCDEDocumentSelection(pos, sel);
}
private static XCDERootDirectoryUserState randomViewingUserState(String name, Vector path, OSSPDirectory dir) {
if (dir.elements.isEmpty()) {
// We'll abort doing a viewing user and instead do a non-viewing user.
return null;
}
OSSPDirectoryElement el = (OSSPDirectoryElement)dir.elements.get( randomInt(dir.elements.size()-1) );
path.add(el.name);
if (el.state instanceof OSSPDirectory) {
return randomViewingUserState(name, path, (OSSPDirectory)el.state);
} else {
// Found a file. Decide if we want to have a cursor in it or not.
final double selecting = 0.75;
double val = rnd.nextDouble();
if (val < selecting) {
return new XCDERootDirectoryUserState(name, (String[])path.toArray(new String[path.size()]), randomSelection((XCDEDocument)el.state), rnd.nextBoolean(), rnd.nextBoolean());
} else {
return new XCDERootDirectoryUserState(name, (String[])path.toArray(new String[path.size()]), null, rnd.nextBoolean(), rnd.nextBoolean());
}
}
}
private static XCDERootDirectoryUserState randomUserState(String name, XCDERootDirectory root) {
final double viewingProb = 0.5;
double val = rnd.nextDouble();
if (val < viewingProb) {
XCDERootDirectoryUserState tmp = randomViewingUserState(name, new Vector(), root);
if (tmp != null) {
return tmp;
}
// Else we ended up at an empty directory instead of a file. There might be files somewhere
// in the repository, but let's just not view anything.
}
return new XCDERootDirectoryUserState(name, null, null, rnd.nextBoolean(), rnd.nextBoolean());
}
/*
private static OSSPDirectoryElement randomElement(OSSPDirectory dir) {
return (OSSPDirectoryElement)dir.elements.get(randomInt(dir.elements.size()-1));
}
*/
private static String randomUsedElementName(OSSPDirectory dir) {
return ((OSSPDirectoryListingElement)dir.ls.get(randomInt(dir.elements.size()-1))).name;
}
private static OSSPDirectoryElement randomNewDirectoryElement(OSSPDirectory dir, int depth) {
final double createDirProb = 0.5;
String name = randomUnusedElementName(dir);
double val = rnd.nextDouble();
if (val < createDirProb && depth < maxDepth) {
// Create a dir.
return new OSSPDirectoryElement(name, randomDirectory(depth));
} else {
// Create a file.
return new OSSPDirectoryElement(name, randomDocument());
}
}
private static String randomUnusedElementName(OSSPDirectory dir) {
outer: for (;;) {
String s = randomString();
for (ListIterator i = dir.ls.listIterator(); i.hasNext(); ) {
String name = ((OSSPDirectoryListingElement)i.next()).name;
if (s.equals(name)) {
continue outer;
}
}
return s;
}
}
private static OSSPDirectory randomDirectory(int depth) {
final int maxInitialElements = 5;
int numElements = randomInt(maxInitialElements);
OSSPDirectory dir = new OSSPDirectory();
for (int i = 0; i < numElements; i++) {
OSSPDirectoryElement el = randomNewDirectoryElement(dir, depth+1);
dir.ls.add(new OSSPDirectoryListingElement(el.name, el.state.getClass().getName()));
dir.elements.add(el);
}
return dir;
}
private static XCDEDocument randomDocument() {
return new XCDEDocument(randomString());
}
/**
* Connects the specified number of clients to the specified server
* and has the clients make random concurrent changes
* to the server as fast as possible for the indicated amount of time.
* At the end, it has each client re-download the state and check it
* versus the local copy. The process is repeated the specified number
* of times. The results are printed to System.out.
*
* @param addr
* @param port
* @param numberOfClients
* @param timeInMillis
* @param repeats
*/
public static void stressTest(InetAddress addr, int port, final int numberOfClients, final long timeInMillis, int repeats, boolean noDelayMode) {
for (int trial = 0; trial < repeats; trial++) {
final OSSPSynchronizer sync = new OSSPSimpleSynchronizer();
// List of all objects opened by the clients.
for (int i = 0; i < numberOfClients; i++) {
final LinkedList objs = new LinkedList();
final XCDEDirClient root;
if (!noDelayMode) {
root = XCDE.connectToServer(addr, port, sync);
} else {
root = XCDE.connectToServerInNoDelayMode(addr, port, sync);
}
new RootDirObj(objs, root);
new Thread(new Runnable() {
public void run() {
try {
// Repeatedly lock the sync, randomly choose something from DirObjs, and
// randomly do something with it.
long end = System.currentTimeMillis() + timeInMillis;
while (System.currentTimeMillis() < end){
root.pace();
synchronized (sync) {
if (objs.size() == 0) {
System.exit(0);
}
Object o = objs.get(randomInt(objs.size()-1));
if (o instanceof XCDEDocClient) {
XCDEDocClient cl = (XCDEDocClient)o;
XCDEDocument doc = cl.getState();
boolean canEdit = cl.isOpen() && (doc != null);
// Our options are: open, edit, close, disconnect
final double editProb = 0.5, openProb = 0.75, closeProb = 0.90;
double val = rnd.nextDouble();
if (val < editProb && canEdit) {
cl.makeChange(randomEdit(doc));
} else if (val < openProb ) {
if (!cl.isOpen()) {
cl.open();
}
} else if (val < closeProb) {
// Disallow a close on an open thing that just doesn't yet have its state.
if (!(cl.isOpen() && doc == null)) {
cl.close();
}
} else {
// Allow a disconnect only if there is at least a certain
// number of connected objects.
if (objs.size() > disconnectThreshold) {
cl.disconnect();
objs.remove(cl);
}
}
} else {
XCDEDirClient cl = (XCDEDirClient)o;
List ls = cl.getState();
boolean canEdit = cl.isOpen() && (ls != null);
// Our options are: open, edit, close, disconnect, and connect to child
final double editProb = 0.4, childProb = 0.6, openProb = 0.8, closeProb = 0.90;
double val = rnd.nextDouble();
if (val < editProb && canEdit) {
OSSPDirectory dir = ((XCDEAbstractDirClient)cl)._getState();
// Our choices are: create a child, delete a child, add a user, change a user, and remove a user
// However, the last three only apply if this is a root dir.
if (cl instanceof XCDERootDirClient) {
XCDERootDirClient rcl = (XCDERootDirClient)cl;
List users = rcl.getUsers();
XCDERootDirectory root = (XCDERootDirectory)dir;
final double userProb = 0.25;
val = rnd.nextDouble();
if (val < userProb) {
final double addUserProb = 0.33, delUserProb = 0.66;
val = rnd.nextDouble();
if (val < addUserProb || users.isEmpty()) {
rcl.makeChange(new XCDERootDirectoryUserStimulusAdd(randomUserState(randomUnusedUserName(root), root)));
} else if (val < delUserProb) {
rcl.makeChange(new XCDERootDirectoryUserStimulusRemove(randomUsedUserName(root)));
} else {
final double sameName = 0.75;
val = rnd.nextDouble();
if (val < sameName) {
String name = randomUsedUserName(root);
rcl.makeChange(new XCDERootDirectoryUserStimulusChange(name, randomUserState(name, root)));
} else {
rcl.makeChange(new XCDERootDirectoryUserStimulusChange(randomUsedUserName(root), randomUserState(randomUnusedUserName(root), root)));
}
}
continue;
}
// Else continue on with a directory change.
}
// If we got here, we didn't do a user change. Do a directory change.
val = rnd.nextDouble();
final double createProb = 0.5;
if ((val < createProb) || ls.isEmpty()) {
cl.makeChange(new OSSPDirectoryStimulusInitiateCreate(randomNewDirectoryElement(dir, 0)));
} else {
// Delete an element.
cl.makeChange(new OSSPDirectoryStimulusDelete(randomUsedElementName(dir)));
}
} else if (val < childProb && canEdit) {
// Must randomly choose from the elements that are not already open.
// The list is indeed a LinkedList in reality so this code will work.
List ls2 = (List)((LinkedList)ls).clone();
outer: for (Iterator i = cl.getOpenChildren(); i.hasNext(); ) {
XCDEDirElement el = (XCDEDirElement)i.next();
// Found an open child. Remove it from the list of children to potentially open.
for (Iterator j = ls2.iterator(); j.hasNext(); ) {
OSSPDirectoryListingElement el2 = (OSSPDirectoryListingElement)j.next();
if (el.getDirElementName().equals(el2.name)) {
// Found it.
j.remove();
continue outer;
}
}
}
// ls2 is now a list of all the children we can open. If it's empty, we'll just skip
// this editing iteration.
if (!ls2.isEmpty()) {
OSSPDirectoryListingElement el = (OSSPDirectoryListingElement)ls2.get(randomInt(ls2.size()-1));
// Open this.
if (el.className.equals(OSSPDirectory.class.getName())) {
new DirObj(objs, cl.connectToDir(el.name));
} else {
new DocObj(objs, cl.connectToDoc(el.name));
}
}
} else if (val < openProb) {
// Disallow redundant opens.
if (!cl.isOpen()) {
cl.open();
}
} else if (val < closeProb) {
// Disallow a close on an open thing that just doesn't have its
// state yet.
if (!(cl.isOpen() && ls == null)) {
cl.close();
}
} else {
// Allow a disconnect only if there is at least a certain
// number of connected objects.
if (objs.size() > disconnectThreshold) {
cl.disconnect();
objs.remove(cl);
}
}
}
}
}
// Done. Must disconnect everything.
synchronized (sync) {
while (!objs.isEmpty()) {
Object ob = objs.removeFirst();
if (ob instanceof XCDEDirClient) {
((XCDEDirClient)ob).disconnect();
} else {
((XCDEDocClient)ob).disconnect();
}
}
}
} catch (Exception e) {
System.out.println("Exception during test thread. Error follows.");
e.printStackTrace();
System.exit(-1);
}
}
}).start();
}
}
}
/**
* args[0] = server machine name as string
* args[1] = server port number as string
* args[2] = number of clients to connect with
* args[3] = milliseconds to run each trial for
* args[4] = max. number of trials to do
*
* @param args
*/
public static void main(String[] args) throws UnknownHostException {
stressTest(InetAddress.getByName(args[0]), Integer.parseInt(args[1]),
Integer.parseInt(args[2]), Integer.parseInt(args[3]), Integer.parseInt(args[4]), Boolean.valueOf(args[5]).booleanValue());
}
}