Bureaucracy smoke test and resulting bug fixes

* Added RegistryTester smoke test which randomly creates and destroys agents 
  in batches
* Fixed bug where correct agent id was never making it to client
* Implemented Bureau.toString (+refactored summarize method)
* Fixed nasty bug due to my assumption that SafeSubscriber =~ Subscriber, they 
  are in fact completely different


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@5032 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Jamie Doornbos
2008-04-30 17:55:40 +00:00
parent 490b5b9764
commit e74035a05c
5 changed files with 313 additions and 49 deletions
@@ -49,8 +49,12 @@ public abstract class BureauDirector extends BasicDirector
}
};
_subscriber = new SafeSubscriber<AgentObject>(agentId, delegator);
_subscriber.subscribe(_ctx.getDObjectManager());
Log.info("Subscribing to object " + agentId);
SafeSubscriber<AgentObject> subscriber =
new SafeSubscriber<AgentObject>(agentId, delegator);
_subscribers.put(agentId, subscriber);
subscriber.subscribe(_ctx.getDObjectManager());
}
/**
@@ -62,6 +66,7 @@ public abstract class BureauDirector extends BasicDirector
agent = _agents.remove(agentId);
if (agent == null) {
Log.warning("Lost an agent, id " + agentId);
}
else {
try {
@@ -71,7 +76,13 @@ public abstract class BureauDirector extends BasicDirector
Log.warning("Stopping an agent caused an exception");
Log.logStackTrace(t);
}
_subscriber.unsubscribe(_ctx.getDObjectManager());
SafeSubscriber<AgentObject> subscriber = _subscribers.remove(agentId);
if (subscriber == null) {
Log.warning("Lost a subscriber for agent " + agent);
}
else {
subscriber.unsubscribe(_ctx.getDObjectManager());
}
_bureauService.agentDestroyed(_ctx.getClient(), agentId);
}
}
@@ -82,6 +93,9 @@ public abstract class BureauDirector extends BasicDirector
protected synchronized void objectAvailable (AgentObject agentObject)
{
int oid = agentObject.getOid();
Log.info("Object " + oid + " now available");
Agent agent;
try {
agent = createAgent(agentObject);
@@ -151,5 +165,6 @@ public abstract class BureauDirector extends BasicDirector
protected BureauContext _ctx;
protected BureauService _bureauService;
protected IntMap<Agent> _agents = IntMaps.newHashIntMap();
protected SafeSubscriber<AgentObject> _subscriber;
protected IntMap<SafeSubscriber<AgentObject>> _subscribers =
IntMaps.newHashIntMap();
}
@@ -123,12 +123,12 @@ public class BureauRegistry
Bureau bureau = _bureaus.get(agent.bureauId);
if (bureau != null && bureau.ready()) {
_omgr.registerObject(agent);
Log.info("Bureau ready, sending createAgent " +
StringUtil.toString(agent));
BureauSender.createAgent(bureau.clientObj, agent.getOid());
// !TODO: is this the right place to register the object?
_omgr.registerObject(agent);
bureau.agentStates.put(agent, Bureau.STARTED);
bureau.summarize();
@@ -193,7 +193,7 @@ public class BureauRegistry
return;
}
Log.warning("Destroying agent " + StringUtil.toString(agent));
Log.info("Destroying agent " + StringUtil.toString(agent));
// transition the agent to a new state and perform the effect of the transition
switch (found.state) {
@@ -480,35 +480,48 @@ public class BureauRegistry
// The states of the various agents allocated to this bureau
Map<AgentObject, Integer> agentStates = Maps.newHashMap();
public String toString ()
{
StringBuilder builder = new StringBuilder();
builder.append("[Bureau id=").append(bureauId).append(", client=");
if (clientObj == null) {
builder.append("null");
}
else {
builder.append(clientObj.getOid());
}
builder.append(", process=").append(process);
builder.append(", totalAgents=").append(agentStates.size());
agentSummary(builder.append(", ")).append("]");
return builder.toString();
}
boolean ready ()
{
return clientObj != null;
}
int countInState (int state)
StringBuilder agentSummary (StringBuilder str)
{
Integer state1 = state;
if (!agentStates.containsValue(state1)) {
return 0;
int counts[] = {0, 0, 0, 0, 0};
for (Map.Entry<AgentObject, Integer> me : agentStates.entrySet()) {
counts[me.getValue()]++;
}
int count = 0;
for (Map.Entry<AgentObject, Integer> me : agentStates.entrySet()) {
if (me.getValue() == state1) {
++count;
}
}
return count;
str.append(counts[PENDING]).append(" pending, ").
append(counts[STARTED]).append(" started, ").
append(counts[RUNNING]).append(" running, ").
append(counts[DESTROYED]).append(" destroyed, ").
append(counts[STILL_BORN]).append(" still born");
return str;
}
void summarize ()
{
Log.info("Bureau " + bureauId + " [" +
countInState(PENDING) + " pending, " +
countInState(STARTED) + " started, " +
countInState(RUNNING) + " running, " +
countInState(DESTROYED) + " destroyed, " +
countInState(STILL_BORN) + " still born]");
StringBuilder str = new StringBuilder();
str.append("Bureau ").append(bureauId).append(" [");
agentSummary(str).append("]");
Log.info(str.toString());
}
}
+15
View File
@@ -187,6 +187,21 @@
</java>
</target>
<target name="bureau-testregistry" depends="compile"
description="Run the bureau test server and tests the registry.">
<java fork="true" classname="com.threerings.bureau.server.RegistryTester">
<classpath refid="classpath"/>
<sysproperty key="maxAgents" value="500"/>
<sysproperty key="numBureaus" value="5"/>
<sysproperty key="maxOps" value="5"/>
<sysproperty key="createChance" value="70"/>
<sysproperty key="minDelay" value="500"/>
<sysproperty key="maxDelay" value="1500"/>
<sysproperty key="seed" value="0"/>
</java>
</target>
<!-- NOTE: this target is launched by the bureau-runserver target, modelling the way bureaus -->
<!-- work. As such it is not currently useful on its own -->
<target name="bureau-runclient" depends="compile"
@@ -0,0 +1,231 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.bureau.server;
import com.samskivert.util.OneLineLogFormatter;
import com.google.common.collect.Lists;
import java.util.Collections;
import com.threerings.bureau.data.AgentObject;
import java.util.List;
import java.util.Random;
/**
* Uses a TestServer to pound on the BureauRegistry. Sends random sequences of
* startAgent and destroyAgent. Most aspects of the randomness are configurable using system
* properties.
* TODO: add more types of testing
* TODO: allow a quitAfter configuration parameter to shut down automatically
*/
public class RegistryTester
{
/**
* Convenience function for getting an integral property. Also logs the value.
*/
public static int intProp (String name, int defaultVal)
{
String val = System.getProperty(name);
if (val == null) {
TestServer.log.info("Property " + name + " is " + defaultVal);
return defaultVal;
}
int ival = Integer.parseInt(val);
TestServer.log.info("Property " + name + " is " + ival);
return ival;
}
/**
* Creates a new server and runs the registry tester on it.
*/
public static void main (String[] args)
{
// make log pretty
OneLineLogFormatter.configureDefaultHandler();
TestServer server = new TestServer();
RegistryTester tester = new RegistryTester(server);
try {
server.init();
tester.start();
server.run();
} catch (Exception e) {
TestServer.log.warning("Unable to initialize server.");
TestServer.logStackTrace(e);
}
}
/**
* Creates a new registry tester.
*/
public RegistryTester (TestServer server)
{
_server = server;
_maxAgents = intProp("maxAgents", 500);
_numBureaus = intProp("numBureaus", 5);
_maxOps = intProp("maxOps", 5);
_createChance = intProp("createChance", 70);
_minDelay = intProp("minDelay", 500);
_maxDelay = intProp("maxDelay", 2000);
// stop the tests when the server shuts down
// TODO: this is not called on Ctrl-C, need a way to shut down gracefully
_server.registerShutdowner(new TestServer.Shutdowner() {
public void shutdown () {
TestServer.log.info("Shutting down tests");
_stop = true;
}
});
}
/**
* Starts the test thread.
*/
public void start ()
{
Thread thread = new Thread("Registry test thread") {
public void run () {
TestServer.log.info(getName() + " started");
runTestThread();
TestServer.log.info(getName() + " stopped");
}
};
thread.start();
}
/**
* Stops the test thread.
*/
public void stop ()
{
_stop = true;
}
/**
* The main loop for the tests.
*/
protected void runTestThread ()
{
// set up the rngs (2 are needed since one is called from the dobj thread)
long seed = Long.parseLong(System.getProperty("seed", "0"));
if (seed == 0) {
seed = System.currentTimeMillis();
}
_rng1 = new Random(seed);
_rng2 = new Random(seed);
TestServer.log.info("Running tests, seed is " + seed);
// runnable that generates N requests to create or destroy agents
Runnable createOrDestroyAgents = new Runnable() {
public void run () {
int ops = 1 + _rng1.nextInt(_maxOps);
TestServer.log.info("Starting " + ops + " agent requests");
for (int i = 0; i < ops; ++i) {
randomlyCreateOrDestroyAgent();
}
TestServer.log.info("Finished " + ops + " agent requests");
}
};
// the main test loop
while (!_stop) {
try {
// sleep for a bit
int sleep = _maxDelay - _minDelay + 1;
sleep = _minDelay + _rng2.nextInt(sleep);
Thread.sleep(sleep);
}
catch (InterruptedException ie) {
break;
}
// create or destroy some agents
_server.omgr.postRunnable(createOrDestroyAgents);
}
// clean up
_server.omgr.postRunnable(new Runnable() {
public void run () {
for (AgentObject obj : _agents) {
_server.breg.destroyAgent(obj);
}
}
});
}
/**
* Does what the name says using the configuration values.
*/
protected void randomlyCreateOrDestroyAgent ()
{
int size = _agents.size();
if (size >= _maxAgents ||
(size != 0 && _rng1.nextInt(100) >= _createChance)) {
AgentObject toRemove = _agents.remove(_rng1.nextInt(size));
TestServer.log.info("Removing agent " + toRemove.getOid());
_server.breg.destroyAgent(toRemove);
}
else {
AgentObject added = create(_rng1.nextInt(_numBureaus) + 1);
_agents.add(added);
TestServer.log.info("Added agent " + added.getOid());
}
}
/**
* Create and return a new agent for the given bureau.
*/
protected AgentObject create (int bureau)
{
AgentObject obj = new AgentObject();
obj.bureauType = "test";
obj.bureauId = "test-" + bureau;
_server.breg.startAgent(obj);
return obj;
}
protected TestServer _server;
protected boolean _stop;
protected Random _rng1;
protected Random _rng2;
protected List<AgentObject> _agents = Lists.newArrayList();
// maximum number of agents to keep around
protected int _maxAgents;
// number of bureaus to select between
protected int _numBureaus;
// number of operations to perform in one dobj queue task
protected int _maxOps;
// chance of creating a new agent if limit has not been reached
protected int _createChance;
// minimum delay between batches of requests
protected int _minDelay;
// maximum delay between batches of requests
protected int _maxDelay;
}
@@ -21,24 +21,35 @@
package com.threerings.bureau.server;
import com.threerings.bureau.Log;
import com.threerings.bureau.data.AgentObject;
import com.threerings.presents.server.PresentsServer;
import com.samskivert.util.OneLineLogFormatter;
import com.samskivert.util.StringUtil;
import com.google.common.collect.Lists;
import java.io.File;
import java.util.List;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Extends a presents server to include a bureau registry.
*/
public class TestServer extends PresentsServer
{
/** We dispatch our log messages through this logger. */
public static Logger log = Logger.getLogger(TestServer.class.getName());
/**
* The bureau registry for the server. Will be null until <code>init</code> is called.
*/
public static BureauRegistry breg;
public static void logStackTrace (Throwable t)
{
log.log(Level.WARNING, t.getMessage(), t);
}
/**
* Creates a new server and runs it.
*/
@@ -50,21 +61,11 @@ public class TestServer extends PresentsServer
final TestServer server = new TestServer();
try {
server.init();
// request a new agent
// TODO: more tests here - create a thread that posts different kinds of requests
// and somehow monitors results from client
server.omgr.postRunnable(new Runnable() {
public void run () {
server.createTestAgent();
}
});
server.run();
} catch (Exception e) {
Log.warning("Unable to initialize server.");
Log.logStackTrace(e);
log.warning("Unable to initialize server.");
logStackTrace(e);
}
}
@@ -93,15 +94,4 @@ public class TestServer extends PresentsServer
}
});
}
/**
* Requests that the bureau client create an agent.
*/
protected void createTestAgent ()
{
AgentObject obj = new AgentObject();
obj.bureauType = "test";
obj.bureauId = "test";
breg.startAgent(obj);
}
}