Moved dummyrunner under bin/.
This commit is contained in:
parent
f075d4aec1
commit
8e020bfb62
10 changed files with 415 additions and 377 deletions
31
bin/evennia
31
bin/evennia
|
|
@ -15,7 +15,7 @@ import signal
|
|||
import shutil
|
||||
import importlib
|
||||
from argparse import ArgumentParser
|
||||
from subprocess import Popen, check_output
|
||||
from subprocess import Popen, check_output, call
|
||||
import django
|
||||
|
||||
# Signal processing
|
||||
|
|
@ -27,6 +27,8 @@ EVENNIA_BIN = os.path.join(EVENNIA_ROOT, "bin")
|
|||
EVENNIA_LIB = os.path.join(EVENNIA_ROOT, "evennia")
|
||||
EVENNIA_RUNNER = os.path.join(EVENNIA_BIN, "evennia_runner.py")
|
||||
EVENNIA_TEMPLATE = os.path.join(EVENNIA_ROOT, "game_template")
|
||||
EVENNIA_BINTESTING = os.path.join(EVENNIA_BIN, "testing")
|
||||
EVENNIA_DUMMYRUNNER = os.path.join(EVENNIA_BINTESTING, "dummyrunner.py")
|
||||
|
||||
TWISTED_BINARY = "twistd"
|
||||
|
||||
|
|
@ -675,6 +677,22 @@ def init_game_directory(path):
|
|||
|
||||
print INFO_WINDOWS_BATFILE.format(twistd_path=twistd_path)
|
||||
|
||||
def run_dummyrunner(number_of_dummies):
|
||||
"""
|
||||
Start an instance of the dummyrunner
|
||||
|
||||
The dummy players' behavior can be customized by adding a
|
||||
dummyrunner_settings.py config file in the game's conf directory.
|
||||
"""
|
||||
number_of_dummies = str(int(number_of_dummies)) if number_of_dummies else 1
|
||||
cmdstr = [sys.executable, EVENNIA_DUMMYRUNNER, "-N", number_of_dummies]
|
||||
config_file = os.path.join(SETTINGS_PATH, "dummyrunner_settings.py")
|
||||
if os.path.exists(config_file):
|
||||
cmdstr.extend(["--config", config_file])
|
||||
try:
|
||||
call(cmdstr, env=getenv())
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
def run_menu():
|
||||
"""
|
||||
|
|
@ -834,8 +852,10 @@ def main():
|
|||
help="Start given processes in interactive mode.")
|
||||
parser.add_argument('--init', action='store', dest="init", metavar="name",
|
||||
help="Creates a new game directory 'name' at the current location.")
|
||||
parser.add_argument('-p', '--prof', action='store_true', dest='profiler', default=False,
|
||||
parser.add_argument('--profile', action='store_true', dest='profiler', default=False,
|
||||
help="Start given server component under the Python profiler.")
|
||||
parser.add_argument('--dummyrunner', nargs=1, action='store', dest='dummyrunner', metavar="N",
|
||||
help="Tests a running server by connecting N dummy players to it.")
|
||||
parser.add_argument("mode", metavar="option", nargs='?', default="help",
|
||||
help="Operational mode or management option. Commonly start, stop, reload, migrate, or menu (default).")
|
||||
parser.add_argument("service", metavar="component", nargs='?', choices=["all", "server", "portal"], default="all",
|
||||
|
|
@ -858,7 +878,7 @@ def main():
|
|||
if args.show_version:
|
||||
print show_version_info(mode=="help")
|
||||
sys.exit()
|
||||
if mode == "help":
|
||||
if mode == "help" and not args.dummyrunner:
|
||||
print ABOUT_INFO
|
||||
sys.exit()
|
||||
|
||||
|
|
@ -866,7 +886,10 @@ def main():
|
|||
# and initializes django for the game directory
|
||||
init_game_directory(CURRENT_DIR)
|
||||
|
||||
if mode == 'menu':
|
||||
if args.dummyrunner:
|
||||
# launch the dummy runner
|
||||
run_dummyrunner(args.dummyrunner[0])
|
||||
elif mode == 'menu':
|
||||
# launch menu for operation
|
||||
run_menu()
|
||||
elif mode in ('start', 'reload', 'stop'):
|
||||
|
|
|
|||
6
bin/testing/README.txt
Normal file
6
bin/testing/README.txt
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
|
||||
Dummyrunner
|
||||
|
||||
This is a test system for stress-testing the server. It will launch numbers
|
||||
of "dummy players" to connect to the server and do various sequences of actions.
|
||||
See header of dummyrunner.py for usage.
|
||||
0
bin/testing/__init__.py
Normal file
0
bin/testing/__init__.py
Normal file
292
bin/testing/dummyrunner.py
Normal file
292
bin/testing/dummyrunner.py
Normal file
|
|
@ -0,0 +1,292 @@
|
|||
"""
|
||||
Dummy client runner
|
||||
|
||||
This module implements a stand-alone launcher for stress-testing
|
||||
an Evennia game. It will launch any number of fake clients. These
|
||||
clients will log into the server and start doing random operations.
|
||||
Customizing and weighing these operations differently depends on
|
||||
which type of game is tested. The module contains a testing module
|
||||
for plain Evennia.
|
||||
|
||||
Please note that you shouldn't run this on a production server!
|
||||
Launch the program without any arguments or options to see a
|
||||
full step-by-step setup help.
|
||||
|
||||
Basically (for testing default Evennia):
|
||||
|
||||
- Use an empty/testing database.
|
||||
- set PERMISSION_PLAYER_DEFAULT = "Builders"
|
||||
- start server, eventually with profiling active
|
||||
- launch this client runner
|
||||
|
||||
If you want to customize the runner's client actions
|
||||
(because you changed the cmdset or needs to better
|
||||
match your use cases or add more actions), you can
|
||||
change which actions by adding a path to
|
||||
|
||||
DUMMYRUNNER_ACTIONS_MODULE = <path.to.your.module>
|
||||
|
||||
in your settings. See utils.dummyrunner_actions.py
|
||||
for instructions on how to define this module.
|
||||
|
||||
"""
|
||||
|
||||
import time, random
|
||||
from argparse import ArgumentParser
|
||||
from twisted.conch import telnet
|
||||
from twisted.internet import reactor, protocol
|
||||
from twisted.internet.task import LoopingCall
|
||||
|
||||
from django.conf import settings
|
||||
from evennia.utils import mod_import
|
||||
|
||||
# Load the dummyrunner settings module
|
||||
|
||||
DUMMYRUNNER_SETTINGS = mod_import(settings.DUMMYRUNNER_SETTINGS_MODULE)
|
||||
DATESTRING = "%Y%m%d%H%M%S"
|
||||
|
||||
# Settings
|
||||
|
||||
# number of clients to launch if no input is given on command line
|
||||
NCLIENTS = 1
|
||||
# time between each 'tick', in seconds, if not set on command
|
||||
# line. All launched clients will be called upon to possibly do an
|
||||
# action with this frequency.
|
||||
TIMESTEP = DUMMYRUNNER_SETTINGS.TIMESTEP
|
||||
# chance of a client performing an action, per timestep. This helps to
|
||||
# spread out usage randomly, like it would be in reality.
|
||||
CHANCE_OF_ACTION = DUMMYRUNNER_SETTINGS.CHANCE_OF_ACTION
|
||||
# spread out the login action separately, having many players create accounts
|
||||
# and connect simultaneously is generally unlikely.
|
||||
CHANCE_OF_LOGIN = DUMMYRUNNER_SETTINGS.CHANCE_OF_LOGIN
|
||||
# Port to use, if not specified on command line
|
||||
TELNET_PORT = DUMMYRUNNER_SETTINGS.TELNET_PORT or settings.TELNET_PORTS[0]
|
||||
#
|
||||
NLOGGED_IN = 0
|
||||
|
||||
# Messages
|
||||
|
||||
INFO_STARTING = \
|
||||
"""
|
||||
Dummyrunner starting, {N} dummy player(s).
|
||||
|
||||
Use Ctrl-C to stop/disconnect clients.
|
||||
"""
|
||||
|
||||
ERROR_FEW_ACTIONS = \
|
||||
"""
|
||||
Dummyrunner error: The ACTIONS tuple is too short: it must contain at
|
||||
least login- and logout functions.
|
||||
"""
|
||||
|
||||
|
||||
HELPTEXT = """
|
||||
DO NOT RUN THIS ON A PRODUCTION SERVER! USE A CLEAN/TESTING DATABASE!
|
||||
|
||||
This stand-alone program launches dummy telnet clients against a
|
||||
running Evennia server. The idea is to mimic real players logging in
|
||||
and repeatedly doing resource-heavy commands so as to stress test the
|
||||
game. It uses the default command set to log in and issue commands, so
|
||||
if that was customized, some of the functionality will not be tested
|
||||
(it will not fail, the commands will just not be recognized). The
|
||||
running clients will create new objects and rooms all over the place
|
||||
as part of their running, so using a clean/testing database is
|
||||
strongly recommended.
|
||||
|
||||
Setup:
|
||||
1) setup a fresh/clean database (if using sqlite, just safe-copy
|
||||
away your real evennia.db3 file and create a new one with
|
||||
manage.py)
|
||||
2) in game/settings.py, add
|
||||
|
||||
PERMISSION_PLAYER_DEFAULT="Builders"
|
||||
|
||||
You can also customize the dummyrunner by modifying
|
||||
a setting file specified by DUMMYRUNNER_SETTINGS_MODULE
|
||||
|
||||
3) Start Evennia like normal, optionally with profiling (--profile)
|
||||
4) run this dummy runner via the evennia launcher:
|
||||
|
||||
evennia --dummyrunner <nr_of_clients>
|
||||
|
||||
5) Log on and determine if game remains responsive despite the
|
||||
heavier load. Note that if you do profiling, there is an
|
||||
additional overhead from the profiler too!
|
||||
6) If you use profiling, let the game run long enough to gather
|
||||
data, then stop the server, ideally from inside it with
|
||||
@shutdown. You can inspect the server.prof file from a python
|
||||
prompt (see Python's manual on cProfiler).
|
||||
|
||||
"""
|
||||
|
||||
#------------------------------------------------------------
|
||||
# Helper functions
|
||||
#------------------------------------------------------------
|
||||
|
||||
|
||||
ICOUNT = 0
|
||||
def idcounter():
|
||||
"makes unique ids"
|
||||
global ICOUNT
|
||||
ICOUNT += 1
|
||||
return str(ICOUNT)
|
||||
|
||||
|
||||
GCOUNT = 0
|
||||
def gidcounter():
|
||||
"makes globally unique ids"
|
||||
global GCOUNT
|
||||
GCOUNT += 1
|
||||
return "%s-%s" % (time.strftime(DATESTRING), ICOUNT)
|
||||
|
||||
|
||||
def makeiter(obj):
|
||||
"makes everything iterable"
|
||||
if not hasattr(obj, '__iter__'):
|
||||
return [obj]
|
||||
return obj
|
||||
|
||||
#------------------------------------------------------------
|
||||
# Client classes
|
||||
#------------------------------------------------------------
|
||||
|
||||
class DummyClient(telnet.StatefulTelnetProtocol):
|
||||
"""
|
||||
Handles connection to a running Evennia server,
|
||||
mimicking a real player by sending commands on
|
||||
a timer.
|
||||
"""
|
||||
|
||||
def connectionMade(self):
|
||||
|
||||
# public properties
|
||||
self.cid = idcounter()
|
||||
self.key = "Dummy-%s" % self.cid
|
||||
self.gid = "%s-%s" % (time.strftime(DATESTRING), self.cid)
|
||||
self.istep = 0
|
||||
self.loggedin = False
|
||||
self.exits = [] # exit names created
|
||||
self.objs = [] # obj names created
|
||||
|
||||
self._report = ""
|
||||
self._cmdlist = [] # already stepping in a cmd definition
|
||||
nactions = len(self.factory.actions) # this has already been normalized
|
||||
if nactions < 2:
|
||||
raise RuntimeError(ERROR_FEW_ACTIONS)
|
||||
self._login = self.factory.actions[0]
|
||||
self._logout = self.factory.actions[1]
|
||||
self._actions = self.factory.actions[2:]
|
||||
|
||||
reactor.addSystemEventTrigger('before', 'shutdown', self.logout)
|
||||
|
||||
# start client tick
|
||||
d = LoopingCall(self.step)
|
||||
# dissipate exact step by up to +/- 0.5 second
|
||||
timestep = TIMESTEP + (-0.5 + (random.random()*1.0))
|
||||
d.start(timestep, now=True).addErrback(self.error)
|
||||
|
||||
|
||||
def dataReceived(self, data):
|
||||
"Echo incoming data to stdout"
|
||||
pass
|
||||
|
||||
def connectionLost(self, reason):
|
||||
"loosing the connection"
|
||||
|
||||
def error(self, err):
|
||||
"error callback"
|
||||
print err
|
||||
|
||||
def counter(self):
|
||||
"produces a unique id, also between clients"
|
||||
return gidcounter()
|
||||
|
||||
def logout(self):
|
||||
"Causes the client to log out of the server. Triggered by ctrl-c signal."
|
||||
cmd = self._logout(self)
|
||||
print "client %s(%s) logout (%s actions)" % (self.key, self.cid, self.istep)
|
||||
self.sendLine(cmd)
|
||||
|
||||
def step(self):
|
||||
"""
|
||||
Perform a step. This is called repeatedly by the runner
|
||||
and causes the client to issue commands to the server.
|
||||
This holds all "intelligence" of the dummy client.
|
||||
"""
|
||||
global NLOGGED_IN
|
||||
|
||||
rand = random.random()
|
||||
|
||||
if not self._cmdlist:
|
||||
# no commands ready. Load some.
|
||||
|
||||
if not self.loggedin:
|
||||
if rand < CHANCE_OF_LOGIN:
|
||||
# get the login commands
|
||||
self._cmdlist = list(makeiter(self._login(self)))
|
||||
NLOGGED_IN += 1 # this is for book-keeping
|
||||
print "connecting client %s (%i/%i)..." % (self.key, NLOGGED_IN, NCLIENTS)
|
||||
self.loggedin = True
|
||||
else:
|
||||
# we always pick a cumulatively random function
|
||||
crand = random.random()
|
||||
cfunc = [func for cprob, func in self._actions if cprob >= crand][0]
|
||||
self._cmdlist = list(makeiter(cfunc(self)))
|
||||
|
||||
# at this point we always have a list of commands
|
||||
if rand < CHANCE_OF_ACTION:
|
||||
# send to the game
|
||||
self.sendLine(str(self._cmdlist.pop(0)))
|
||||
self.istep += 1
|
||||
|
||||
|
||||
class DummyFactory(protocol.ClientFactory):
|
||||
protocol = DummyClient
|
||||
def __init__(self, actions):
|
||||
"Setup the factory base (shared by all clients)"
|
||||
self.actions = actions
|
||||
|
||||
#------------------------------------------------------------
|
||||
# Access method:
|
||||
# Starts clients and connects them to a running server.
|
||||
#------------------------------------------------------------
|
||||
|
||||
def start_all_dummy_clients(nclients):
|
||||
|
||||
global NCLIENTS
|
||||
NCLIENTS = int(nclients)
|
||||
actions = DUMMYRUNNER_SETTINGS.ACTIONS
|
||||
|
||||
# make sure the probabilities add up to 1
|
||||
pratio = 1.0 / sum(tup[0] for tup in actions[2:])
|
||||
flogin, flogout, probs, cfuncs = actions[0], actions[1], [tup[0] * pratio for tup in actions[2:]], [tup[1] for tup in actions[2:]]
|
||||
# create cumulative probabilies for the random actions
|
||||
cprobs = [sum(v for i,v in enumerate(probs) if i<=k) for k in range(len(probs))]
|
||||
# rebuild a new, optimized action structure
|
||||
actions = (flogin, flogout, zip(cprobs, cfuncs))
|
||||
|
||||
# setting up all clients (they are automatically started)
|
||||
factory = DummyFactory(actions)
|
||||
for i in range(NCLIENTS):
|
||||
reactor.connectTCP("localhost", TELNET_PORT, factory)
|
||||
# start reactor
|
||||
reactor.run()
|
||||
|
||||
#------------------------------------------------------------
|
||||
# Command line interface
|
||||
#------------------------------------------------------------
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
# parsing command line with default vals
|
||||
parser = ArgumentParser(description=HELPTEXT)
|
||||
parser.add_argument("-N", nargs=1, default=1, dest="nclients",
|
||||
help="Number of clients to start")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
print INFO_STARTING.format(N=args.nclients[0])
|
||||
t0 = time.time()
|
||||
start_all_dummy_clients(nclients=args.nclients[0])
|
||||
ttot = time.time() - t0
|
||||
print "... dummy client runner stopped after %i seconds." % ttot
|
||||
264
bin/testing/dummyrunner_settings.py
Normal file
264
bin/testing/dummyrunner_settings.py
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
"""
|
||||
Settings and actions for the dummyrunner
|
||||
|
||||
This module defines dummyrunner settings and sets up
|
||||
the actions available to dummy players.
|
||||
|
||||
The settings are global variables:
|
||||
|
||||
TIMESTEP - time in seconds between each 'tick'
|
||||
CHANCE_OF_ACTION - chance 0-1 of action happening
|
||||
CHANCE_OF_LOGIN - chance 0-1 of login happening
|
||||
TELNET_PORT - port to use, defaults to settings.TELNET_PORT
|
||||
ACTIONS - see below
|
||||
|
||||
ACTIONS is a tuple
|
||||
|
||||
(login_func, logout_func, (0.3, func1), (0.1, func2) ... )
|
||||
|
||||
where the first entry is the function to call on first connect, with a
|
||||
chance of occurring given by CHANCE_OF_LOGIN. This function is usually
|
||||
responsible for logging in the player. The second entry is always
|
||||
called when the dummyrunner disconnects from the server and should
|
||||
thus issue a logout command. The other entries are tuples (chance,
|
||||
func). They are picked randomly, their commonality based on the
|
||||
cumulative chance given (the chance is normalized between all options
|
||||
so if will still work also if the given chances don't add up to 1).
|
||||
Since each function can return a list of game-command strings, each
|
||||
function may result in multiple operations.
|
||||
|
||||
An action-function is called with a "client" argument which is a
|
||||
reference to the dummy client currently performing the action. It
|
||||
returns a string or a list of command strings to execute. Use the
|
||||
client object for optionally saving data between actions.
|
||||
|
||||
The client object has the following relevant properties and methods:
|
||||
key - an optional client key. This is only used for dummyrunner output.
|
||||
Default is "Dummy-<cid>"
|
||||
cid - client id
|
||||
gid - globally unique id, hashed with time stamp
|
||||
istep - the current step
|
||||
exits - an empty list. Can be used to store exit names
|
||||
objs - an empty list. Can be used to store object names
|
||||
counter() - returns a unique increasing id, hashed with time stamp
|
||||
to make it unique also between dummyrunner instances.
|
||||
|
||||
The return should either be a single command string or a tuple of
|
||||
command strings. This list of commands will always be executed every
|
||||
TIMESTEP with a chance given by CHANCE_OF_ACTION by in the order given
|
||||
(no randomness) and allows for setting up a more complex chain of
|
||||
commands (such as creating an account and logging in).
|
||||
|
||||
"""
|
||||
# Dummy runner settings
|
||||
|
||||
# Time between each dummyrunner "tick", in seconds. Each dummy
|
||||
# will be called with this frequency.
|
||||
TIMESTEP = 2
|
||||
|
||||
# Chance of a dummy actually performing an action on a given tick.
|
||||
# This spreads out usage randomly, like it would be in reality.
|
||||
CHANCE_OF_ACTION = 0.05
|
||||
|
||||
# Chance of a currently unlogged-in dummy performing its login
|
||||
# action every tick. This emulates not all players logging in
|
||||
# at exactly the same time.
|
||||
CHANCE_OF_LOGIN = 0.33
|
||||
|
||||
# Which telnet port to connect to. If set to None, uses the first
|
||||
# default telnet port of the running server.
|
||||
TELNET_PORT = None
|
||||
|
||||
|
||||
# Setup actions tuple
|
||||
|
||||
# some convenient templates
|
||||
|
||||
DUMMY_NAME = "Dummy-%s"
|
||||
DUMMY_PWD = "password-%s"
|
||||
START_ROOM = "testing_room_start_%s"
|
||||
ROOM_TEMPLATE = "testing_room_%s"
|
||||
EXIT_TEMPLATE = "exit_%s"
|
||||
OBJ_TEMPLATE = "testing_obj_%s"
|
||||
TOBJ_TEMPLATE = "testing_button_%s"
|
||||
TOBJ_TYPECLASS = "contrib.tutorial_examples.red_button.RedButton"
|
||||
|
||||
|
||||
# action function definitions (pick and choose from
|
||||
# these to build a client "usage profile"
|
||||
|
||||
# login/logout
|
||||
|
||||
def c_login(client):
|
||||
"logins to the game"
|
||||
# we always use a new client name
|
||||
cname = DUMMY_NAME % client.gid
|
||||
cpwd = DUMMY_PWD % client.gid
|
||||
|
||||
# set up for digging a first room (to move to and keep the
|
||||
# login room clean)
|
||||
roomname = ROOM_TEMPLATE % client.counter()
|
||||
exitname1 = EXIT_TEMPLATE % client.counter()
|
||||
exitname2 = EXIT_TEMPLATE % client.counter()
|
||||
client.exits.extend([exitname1, exitname2])
|
||||
|
||||
cmds = ('create %s %s' % (cname, cpwd),
|
||||
'connect %s %s' % (cname, cpwd),
|
||||
'@dig %s' % START_ROOM % client.cid,
|
||||
'@teleport %s' % START_ROOM % client.cid,
|
||||
'@dig %s = %s, %s' % (roomname, exitname1, exitname2)
|
||||
)
|
||||
return cmds
|
||||
|
||||
def c_login_nodig(client):
|
||||
"logins, don't dig its own room"
|
||||
cname = DUMMY_NAME % client.gid
|
||||
cpwd = DUMMY_PWD % client.gid
|
||||
|
||||
cmds = ('create %s %s' % (cname, cpwd),
|
||||
'connect %s %s' % (cname, cpwd))
|
||||
return cmds
|
||||
|
||||
def c_logout(client):
|
||||
"logouts of the game"
|
||||
return "@quit"
|
||||
|
||||
# random commands
|
||||
|
||||
def c_looks(client):
|
||||
"looks at various objects"
|
||||
cmds = ["look %s" % obj for obj in client.objs]
|
||||
if not cmds:
|
||||
cmds = ["look %s" % exi for exi in client.exits]
|
||||
if not cmds:
|
||||
cmds = "look"
|
||||
return cmds
|
||||
|
||||
def c_examines(client):
|
||||
"examines various objects"
|
||||
cmds = ["examine %s" % obj for obj in client.objs]
|
||||
if not cmds:
|
||||
cmds = ["examine %s" % exi for exi in client.exits]
|
||||
if not cmds:
|
||||
cmds = "examine me"
|
||||
return cmds
|
||||
|
||||
def c_help(client):
|
||||
"reads help files"
|
||||
cmds = ('help',
|
||||
'help @teleport',
|
||||
'help look',
|
||||
'help @tunnel',
|
||||
'help @dig')
|
||||
return cmds
|
||||
|
||||
def c_digs(client):
|
||||
"digs a new room, storing exit names on client"
|
||||
roomname = ROOM_TEMPLATE % client.counter()
|
||||
exitname1 = EXIT_TEMPLATE % client.counter()
|
||||
exitname2 = EXIT_TEMPLATE % client.counter()
|
||||
client.exits.extend([exitname1, exitname2])
|
||||
return '@dig/tel %s = %s, %s' % (roomname, exitname1, exitname2)
|
||||
|
||||
def c_creates_obj(client):
|
||||
"creates normal objects, storing their name on client"
|
||||
objname = OBJ_TEMPLATE % client.counter()
|
||||
client.objs.append(objname)
|
||||
cmds = ('@create %s' % objname,
|
||||
'@desc %s = "this is a test object' % objname,
|
||||
'@set %s/testattr = this is a test attribute value.' % objname,
|
||||
'@set %s/testattr2 = this is a second test attribute.' % objname)
|
||||
return cmds
|
||||
|
||||
def c_creates_button(client):
|
||||
"creates example button, storing name on client"
|
||||
objname = TOBJ_TEMPLATE % client.counter()
|
||||
client.objs.append(objname)
|
||||
cmds = ('@create %s:%s' % (objname, TOBJ_TYPECLASS),
|
||||
'@desc %s = test red button!' % objname)
|
||||
return cmds
|
||||
|
||||
def c_socialize(client):
|
||||
"socializechats on channel"
|
||||
cmds = ('ooc Hello!',
|
||||
'ooc Testing ...',
|
||||
'ooc Testing ... times 2',
|
||||
'say Yo!',
|
||||
'emote stands looking around.')
|
||||
return cmds
|
||||
|
||||
def c_moves(client):
|
||||
"moves to a previously created room, using the stored exits"
|
||||
cmds = client.exits # try all exits - finally one will work
|
||||
return "look" if not cmds else cmds
|
||||
|
||||
def c_moves_n(client):
|
||||
"move through north exit if available"
|
||||
return "north"
|
||||
|
||||
def c_moves_s(client):
|
||||
"move through south exit if available"
|
||||
return "south"
|
||||
|
||||
# Action tuple (required)
|
||||
#
|
||||
# This is a tuple of client action functions. The first element is the
|
||||
# function the client should use to log into the game and move to
|
||||
# STARTROOM . The second element is the logout command, for cleanly
|
||||
# exiting the mud. The following elements are 2-tuples of (probability,
|
||||
# action_function). The probablities should normally sum up to 1,
|
||||
# otherwise the system will normalize them.
|
||||
#
|
||||
|
||||
## "normal builder" definitionj
|
||||
#ACTIONS = ( c_login,
|
||||
# c_logout,
|
||||
# (0.5, c_looks),
|
||||
# (0.08, c_examines),
|
||||
# (0.1, c_help),
|
||||
# (0.01, c_digs),
|
||||
# (0.01, c_creates_obj),
|
||||
# (0.3, c_moves))
|
||||
## "heavy" builder definition
|
||||
#ACTIONS = ( c_login,
|
||||
# c_logout,
|
||||
# (0.2, c_looks),
|
||||
# (0.1, c_examines),
|
||||
# (0.2, c_help),
|
||||
# (0.1, c_digs),
|
||||
# (0.1, c_creates_obj),
|
||||
# #(0.01, c_creates_button),
|
||||
# (0.2, c_moves))
|
||||
## "passive player" definition
|
||||
#ACTIONS = ( c_login,
|
||||
# c_logout,
|
||||
# (0.7, c_looks),
|
||||
# #(0.1, c_examines),
|
||||
# (0.3, c_help))
|
||||
# #(0.1, c_digs),
|
||||
# #(0.1, c_creates_obj),
|
||||
# #(0.1, c_creates_button),
|
||||
# #(0.4, c_moves))
|
||||
## "normal player" definition
|
||||
ACTIONS = ( c_login,
|
||||
c_logout,
|
||||
(0.01, c_digs),
|
||||
(0.39, c_looks),
|
||||
(0.2, c_help),
|
||||
(0.4, c_moves))
|
||||
#ACTIONS = (c_login_nodig,
|
||||
# c_logout,
|
||||
# (1.0, c_moves_n))
|
||||
## "socializing heavy builder" definition
|
||||
#ACTIONS = (c_login,
|
||||
# c_logout,
|
||||
# (0.1, c_socialize),
|
||||
# (0.1, c_looks),
|
||||
# (0.2, c_help),
|
||||
# (0.1, c_creates_obj),
|
||||
# (0.2, c_digs),
|
||||
# (0.3, c_moves))
|
||||
## "heavy digger memory tester" definition
|
||||
#ACTIONS = (c_login,
|
||||
# c_logout,
|
||||
# (1.0, c_digs))
|
||||
101
bin/testing/memplot.py
Normal file
101
bin/testing/memplot.py
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
"""
|
||||
Script that saves memory and idmapper data over time.
|
||||
|
||||
Data will be saved to game/logs/memoryusage.log. Note that
|
||||
the script will append to this file if it already exists.
|
||||
|
||||
Call this module directly to plot the log (requires matplotlib and numpy).
|
||||
"""
|
||||
import os, sys
|
||||
import time
|
||||
#TODO!
|
||||
#sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))))
|
||||
#os.environ['DJANGO_SETTINGS_MODULE'] = 'game.settings'
|
||||
import ev
|
||||
from evennia.utils.idmapper import base as _idmapper
|
||||
|
||||
LOGFILE = "logs/memoryusage.log"
|
||||
INTERVAL = 30 # log every 30 seconds
|
||||
|
||||
class Memplot(ev.Script):
|
||||
def at_script_creation(self):
|
||||
self.key = "memplot"
|
||||
self.desc = "Save server memory stats to file"
|
||||
self.start_delay = False
|
||||
self.persistent = True
|
||||
self.interval = INTERVAL
|
||||
self.db.starttime = time.time()
|
||||
|
||||
def at_repeat(self):
|
||||
|
||||
pid = os.getpid()
|
||||
rmem = float(os.popen('ps -p %d -o %s | tail -1' % (pid, "rss")).read()) / 1000.0 # resident memory
|
||||
vmem = float(os.popen('ps -p %d -o %s | tail -1' % (pid, "vsz")).read()) / 1000.0 # virtual memory
|
||||
total_num, cachedict = _idmapper.cache_size()
|
||||
t0 = (time.time() - self.db.starttime) / 60.0 # save in minutes
|
||||
|
||||
with open(LOGFILE, "a") as f:
|
||||
f.write("%s, %s, %s, %s\n" % (t0, rmem, vmem, int(total_num)))
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
# plot output from the file
|
||||
|
||||
from matplotlib import pyplot as pp
|
||||
import numpy
|
||||
|
||||
data = numpy.genfromtxt("../../../game/" + LOGFILE, delimiter=",")
|
||||
secs = data[:,0]
|
||||
rmem = data[:,1]
|
||||
vmem = data[:,2]
|
||||
nobj = data[:,3]
|
||||
|
||||
# calculate derivative of obj creation
|
||||
#oderiv = (0.5*(nobj[2:] - nobj[:-2]) / (secs[2:] - secs[:-2])).copy()
|
||||
#oderiv = (0.5*(rmem[2:] - rmem[:-2]) / (secs[2:] - secs[:-2])).copy()
|
||||
|
||||
fig = pp.figure()
|
||||
ax1 = fig.add_subplot(111)
|
||||
ax1.set_title("1000 bots (normal players with light building)")
|
||||
ax1.set_xlabel("Time (mins)")
|
||||
ax1.set_ylabel("Memory usage (MB)")
|
||||
ax1.plot(secs, rmem, "r", label="RMEM", lw=2)
|
||||
ax1.plot(secs, vmem, "b", label="VMEM", lw=2)
|
||||
ax1.legend(loc="upper left")
|
||||
|
||||
ax2 = ax1.twinx()
|
||||
ax2.plot(secs, nobj, "g--", label="objs in cache", lw=2)
|
||||
#ax2.plot(secs[:-2], oderiv/60.0, "g--", label="Objs/second", lw=2)
|
||||
#ax2.plot(secs[:-2], oderiv, "g--", label="Objs/second", lw=2)
|
||||
ax2.set_ylabel("Number of objects")
|
||||
ax2.legend(loc="lower right")
|
||||
ax2.annotate("First 500 bots\nconnecting", xy=(10, 4000))
|
||||
ax2.annotate("Next 500 bots\nconnecting", xy=(350,10000))
|
||||
#ax2.annotate("@reload", xy=(185,600))
|
||||
|
||||
# # plot mem vs cachesize
|
||||
# nobj, rmem, vmem = nobj[:262].copy(), rmem[:262].copy(), vmem[:262].copy()
|
||||
#
|
||||
# fig = pp.figure()
|
||||
# ax1 = fig.add_subplot(111)
|
||||
# ax1.set_title("Memory usage per cache size")
|
||||
# ax1.set_xlabel("Cache size (number of objects)")
|
||||
# ax1.set_ylabel("Memory usage (MB)")
|
||||
# ax1.plot(nobj, rmem, "r", label="RMEM", lw=2)
|
||||
# ax1.plot(nobj, vmem, "b", label="VMEM", lw=2)
|
||||
#
|
||||
|
||||
## # empirical estimate of memory usage: rmem = 35.0 + 0.0157 * Ncache
|
||||
## # Ncache = int((rmem - 35.0) / 0.0157) (rmem in MB)
|
||||
#
|
||||
# rderiv_aver = 0.0157
|
||||
# fig = pp.figure()
|
||||
# ax1 = fig.add_subplot(111)
|
||||
# ax1.set_title("Relation between memory and cache size")
|
||||
# ax1.set_xlabel("Memory usage (MB)")
|
||||
# ax1.set_ylabel("Idmapper Cache Size (number of objects)")
|
||||
# rmem = numpy.linspace(35, 2000, 2000)
|
||||
# nobjs = numpy.array([int((mem - 35.0) / 0.0157) for mem in rmem])
|
||||
# ax1.plot(rmem, nobjs, "r", lw=2)
|
||||
|
||||
pp.show()
|
||||
41
bin/testing/test_queries.py
Normal file
41
bin/testing/test_queries.py
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
"""
|
||||
This is a little routine for viewing the sql queries that are executed by a given
|
||||
query as well as count them for optimization testing.
|
||||
|
||||
"""
|
||||
import sys, os
|
||||
#sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))))
|
||||
#os.environ["DJANGO_SETTINGS_MODULE"] = "game.settings"
|
||||
from django.db import connection
|
||||
|
||||
|
||||
def count_queries(exec_string, setup_string):
|
||||
"""
|
||||
Display queries done by exec_string. Use setup_string
|
||||
to setup the environment to test.
|
||||
"""
|
||||
|
||||
exec setup_string
|
||||
|
||||
num_queries_old = len(connection.queries)
|
||||
exec exec_string
|
||||
nqueries = len(connection.queries) - num_queries_old
|
||||
|
||||
for query in connection.queries[-nqueries if nqueries else 1:]:
|
||||
print query["time"], query["sql"]
|
||||
print "Number of queries: %s" % nqueries
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
# setup tests here
|
||||
|
||||
setup_string = \
|
||||
"""
|
||||
from evennia.objects.models import ObjectDB
|
||||
g = ObjectDB.objects.get(db_key="Griatch")
|
||||
"""
|
||||
exec_string = \
|
||||
"""
|
||||
g.tags.all()
|
||||
"""
|
||||
count_queries(exec_string, setup_string)
|
||||
Loading…
Add table
Add a link
Reference in a new issue