Moved dummyrunner under bin/.

This commit is contained in:
Griatch 2015-01-10 17:56:33 +01:00
parent f075d4aec1
commit 8e020bfb62
10 changed files with 415 additions and 377 deletions

View file

@ -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
View 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
View file

292
bin/testing/dummyrunner.py Normal file
View 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

View 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
View 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()

View 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)