Cleaned up dummyrunner and fixed a lot of issues

This commit is contained in:
Griatch 2021-06-02 00:24:21 +02:00
parent d7b66eecca
commit 677a34d06e
6 changed files with 408 additions and 142 deletions

View file

@ -37,7 +37,8 @@ DUMMYSESSION = namedtuple("DummySession", ["sessid"])(0)
# Portal-SessionHandler class
# -------------------------------------------------------------
DOS_PROTECTION_MSG = _("{servername} DoS protection is active. You are queued to connect in {num} seconds ...")
DOS_PROTECTION_MSG = _("{servername} DoS protection is active."
"You are queued to connect in {num} seconds ...")
class PortalSessionHandler(SessionHandler):

View file

@ -40,8 +40,16 @@ 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, time_format
import django
django.setup()
import evennia # noqa
evennia._init()
from django.conf import settings # noqa
from evennia.utils import mod_import, time_format # noqa
from evennia.commands.command import Command # noqa
from evennia.commands.cmdset import CmdSet # noqa
from evennia.utils.ansi import strip_ansi # noqa
# Load the dummyrunner settings module
@ -51,8 +59,10 @@ if not DUMMYRUNNER_SETTINGS:
"Error: Dummyrunner could not find settings file at %s"
% settings.DUMMYRUNNER_SETTINGS_MODULE
)
IDMAPPER_CACHE_MAXSIZE = settings.IDMAPPER_CACHE_MAXSIZE
DATESTRING = "%Y%m%d%H%M%S"
CLIENTS = []
# Settings
@ -71,18 +81,37 @@ 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
NCONNECTED = 0 # client has received a connection
NLOGIN_SCREEN = 0 # client has seen the login screen (server responded)
NLOGGING_IN = 0 # client starting login procedure
NLOGGED_IN = 0 # client has authenticated and logged in
# Messages
# time when all clients have logged_in
TIME_ALL_LOGIN = 0
# actions since all logged in
TOTAL_ACTIONS = 0
TOTAL_LAG_MEASURES = 0
# lag per 30s for all logged in
TOTAL_LAG = 0
TOTAL_LAG_IN = 0
TOTAL_LAG_OUT = 0
INFO_STARTING = """
Dummyrunner starting using {N} dummy account(s). If you don't see
Dummyrunner starting using {nclients} dummy account(s). If you don't see
any connection messages, make sure that the Evennia server is
running.
Use Ctrl-C to stop/disconnect clients.
TELNET_PORT = {port}
IDMAPPER_CACHE_MAXSIZE = {idmapper_cache_size} MB
TIMESTEP = {timestep} (rate {rate}/s)
CHANCE_OF_LOGIN = {chance_of_login}% per time step
CHANCE_OF_ACTION = {chance_of_action}% per time step
-> avg rate (per client, after login): {avg_rate} cmds/s
-> total avg rate (after login): {avg_rate_total} cmds/s
Use Ctrl-C (or Cmd-C) to stop/disconnect all clients.
"""
ERROR_NO_MIXIN = """
@ -97,6 +126,7 @@ ERROR_NO_MIXIN = """
to test all commands
- change PASSWORD_HASHERS to use a faster (but less safe) algorithm
when creating large numbers of accounts at the same time
- set LOGIN_THROTTLE/CREATION_THROTTLE=None to disable it
If you don't want to use the custom settings of the mixin for some
reason, you can change their values manually after the import, or
@ -168,6 +198,39 @@ until you see the initial login slows things too much.
"""
class CmdDummyRunnerEchoResponse(Command):
"""
Dummyrunner command measuring the round-about response time
from sending to receiving a result.
Usage:
dummyrunner_echo_response <timestamp>
Responds with
dummyrunner_echo_response:<timestamp>,<current_time>
The dummyrunner will send this and then compare the send time
with the receive time on both ends.
"""
key = "dummyrunner_echo_response"
def func(self):
# returns (dummy_client_timestamp,current_time)
self.msg(f"dummyrunner_echo_response:{self.args},{time.time()}")
if self.caller.account.is_superuser:
print(f"cmddummyrunner lag in: {time.time() - float(self.args)}s")
class DummyRunnerCmdSet(CmdSet):
"""
Dummyrunner injected cmdset.
"""
def at_cmdset_creation(self):
self.add(CmdDummyRunnerEchoResponse())
# ------------------------------------------------------------
# Helper functions
# ------------------------------------------------------------
@ -181,12 +244,12 @@ def idcounter():
Makes unique ids.
Returns:
count (int): A globally unique counter.
str: A globally unique id.
"""
global ICOUNT
ICOUNT += 1
return str(ICOUNT)
return str("{:03d}".format(ICOUNT))
GCOUNT = 0
@ -202,7 +265,7 @@ def gidcounter():
"""
global GCOUNT
GCOUNT += 1
return "%s-%s" % (time.strftime(DATESTRING), GCOUNT)
return "%s_%s" % (time.strftime(DATESTRING), GCOUNT)
def makeiter(obj):
@ -222,7 +285,6 @@ def makeiter(obj):
# Client classes
# ------------------------------------------------------------
class DummyClient(telnet.StatefulTelnetProtocol):
"""
Handles connection to a running Evennia server,
@ -231,22 +293,36 @@ class DummyClient(telnet.StatefulTelnetProtocol):
"""
def report(self, text, clientkey):
pad = " " * (25 - len(text))
tim = round(time.time() - self.connection_timestamp)
print(f"{text} {clientkey}{pad}\t"
f"conn: {NCONNECTED} -> "
f"welcome screen: {NLOGIN_SCREEN} -> "
f"authing: {NLOGGING_IN} -> "
f"loggedin/tot: {NLOGGED_IN}/{NCLIENTS} (after {tim}s)")
def connectionMade(self):
"""
Called when connection is first established.
"""
global NCONNECTED
# public properties
self.cid = idcounter()
self.key = "Dummy-%s" % self.cid
self.gid = "%s-%s" % (time.strftime(DATESTRING), self.cid)
self.key = f"Dummy-{self.cid}"
self.gid = f"{time.strftime(DATESTRING)}_{self.cid}"
self.istep = 0
self.exits = [] # exit names created
self.objs = [] # obj names created
self.connection_timestamp = time.time()
self.connection_attempt = 0
self.action_started = 0
self._connected = False
self._loggedin = False
self._logging_out = False
self._ready = False
self._report = ""
self._cmdlist = [] # already stepping in a cmd definition
self._login = self.factory.actions[0]
@ -255,6 +331,43 @@ class DummyClient(telnet.StatefulTelnetProtocol):
reactor.addSystemEventTrigger("before", "shutdown", self.logout)
NCONNECTED += 1
self.report("-> connected", self.key)
reactor.callLater(30, self._retry_welcome_screen)
def _retry_welcome_screen(self):
if not self._connected and not self._ready:
# we have connected but not received anything for 30s.
# (unclear why this would be - overload?)
# try sending a look to get something to start with
self.report("?? retrying welcome screen", self.key)
self.sendLine(bytes("look", 'utf-8'))
# make sure to check again later
reactor.callLater(30, self._retry_welcome_screen)
def _print_statistics(self):
global TIME_ALL_LOGIN, TOTAL_ACTIONS
global TOTAL_LAG, TOTAL_LAG_MEASURES, TOTAL_LAG_IN, TOTAL_LAG_OUT
tim = time.time() - TIME_ALL_LOGIN
avgrate = round(TOTAL_ACTIONS / tim)
lag = TOTAL_LAG / (TOTAL_LAG_MEASURES or 1)
lag_in = TOTAL_LAG_IN / (TOTAL_LAG_MEASURES or 1)
lag_out = TOTAL_LAG_OUT / (TOTAL_LAG_MEASURES or 1)
TOTAL_ACTIONS = 0
TOTAL_LAG = 0
TOTAL_LAG_IN = 0
TOTAL_LAG_OUT = 0
TOTAL_LAG_MEASURES = 0
TIME_ALL_LOGIN = time.time()
print(f".. running 30s average: ~{avgrate} actions/s "
f"lag: {lag:.2}s (in: {lag_in:.2}s, out: {lag_out:.2}s)")
reactor.callLater(30, self._print_statistics)
def dataReceived(self, data):
"""
Called when data comes in over the protocol. We wait to start
@ -264,15 +377,67 @@ class DummyClient(telnet.StatefulTelnetProtocol):
data (str): Incoming data.
"""
if not self._connected and not data.startswith(chr(255)):
# wait until we actually get text back (not just telnet
# negotiation)
self._connected = True
# 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)
global NLOGIN_SCREEN, NLOGGED_IN, NLOGGING_IN, NCONNECTED
global TOTAL_ACTIONS, TIME_ALL_LOGIN
global TOTAL_LAG, TOTAL_LAG_MEASURES, TOTAL_LAG_IN, TOTAL_LAG_OUT
if not data.startswith(b"\xff"):
# regular text, not a telnet command
if NCLIENTS == 1:
print("dummy-client sees:", str(data, "utf-8"))
if not self._connected:
# waiting for connection
# wait until we actually get text back (not just telnet
# negotiation)
# start client tick
d = LoopingCall(self.step)
df = max(abs(TIMESTEP * 0.001), min(TIMESTEP/10, 0.5))
# dither next attempt with random time
timestep = TIMESTEP + (-df + (random.random() * df))
d.start(timestep, now=True).addErrback(self.error)
self.connection_attempt += 1
self._connected = True
NLOGIN_SCREEN += 1
NCONNECTED -= 1
self.report("<- server sent login screen", self.key)
elif self._loggedin:
if not self._ready:
# logged in, ready to run
NLOGGED_IN += 1
NLOGGING_IN -= 1
self._ready = True
self.report("== logged in", self.key)
if NLOGGED_IN == NCLIENTS and not TIME_ALL_LOGIN:
# all are logged in! We can start collecting statistics
print(".. All clients connected and logged in!")
TIME_ALL_LOGIN = time.time()
reactor.callLater(30, self._print_statistics)
elif TIME_ALL_LOGIN:
TOTAL_ACTIONS += 1
try:
data = strip_ansi(str(data, "utf-8").strip())
if data.startswith("dummyrunner_echo_response:"):
# handle special lag-measuring command. This returns
# dummyrunner_echo_response:<starttime>,<midpointtime>
now = time.time()
_, data = data.split(":", 1)
start_time, mid_time = (float(part) for part in data.split(",", 1))
lag_in = mid_time - start_time
lag_out = now - mid_time
total_lag = now - start_time # full round-about time
TOTAL_LAG += total_lag
TOTAL_LAG_IN += lag_in
TOTAL_LAG_OUT += lag_out
TOTAL_LAG_MEASURES += 1
except Exception:
pass
def connectionLost(self, reason):
"""
@ -283,7 +448,7 @@ class DummyClient(telnet.StatefulTelnetProtocol):
"""
if not self._logging_out:
print("client %s(%s) lost connection (%s)" % (self.key, self.cid, reason))
self.report("XX lost connection", self.key)
def error(self, err):
"""
@ -310,9 +475,9 @@ class DummyClient(telnet.StatefulTelnetProtocol):
"""
self._logging_out = True
cmd = self._logout(self)
print("client %s(%s) logout (%s actions)" % (self.key, self.cid, self.istep))
self.sendLine(cmd)
cmd = self._logout(self)[0]
self.report(f"-> logout/disconnect ({self.istep} actions)", self.key)
self.sendLine(bytes(cmd, 'utf-8'))
def step(self):
"""
@ -321,7 +486,7 @@ class DummyClient(telnet.StatefulTelnetProtocol):
all "intelligence" of the dummy client.
"""
global NLOGGED_IN
global NLOGGING_IN, NLOGIN_SCREEN
rand = random.random()
@ -329,11 +494,13 @@ class DummyClient(telnet.StatefulTelnetProtocol):
# no commands ready. Load some.
if not self._loggedin:
if rand < CHANCE_OF_LOGIN:
if rand < CHANCE_OF_LOGIN or NLOGGING_IN < 10:
# lower rate of logins, but not below 1 / s
# 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))
NLOGGING_IN += 1 # this is for book-keeping
NLOGIN_SCREEN -= 1
self.report("-> create/login", self.key)
self._loggedin = True
else:
# no login yet, so cmdlist not yet set
@ -347,12 +514,26 @@ class DummyClient(telnet.StatefulTelnetProtocol):
# 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)))
cmd = str(self._cmdlist.pop(0))
if cmd.startswith("dummyrunner_echo_response"):
# we need to set the timer element as close to
# the send as possible
cmd = cmd.format(timestamp=time.time())
self.sendLine(bytes(cmd, 'utf-8'))
self.action_started = time.time()
self.istep += 1
if NCLIENTS == 1:
print(f"dummy-client sent: {cmd}")
class DummyFactory(protocol.ClientFactory):
class DummyFactory(protocol.ReconnectingClientFactory):
protocol = DummyClient
initialDelay = 1
maxDelay = 1
noisy = False
def __init__(self, actions):
"Setup the factory base (shared by all clients)"
@ -397,7 +578,7 @@ def start_all_dummy_clients(nclients):
# setting up all clients (they are automatically started)
factory = DummyFactory(actions)
for i in range(NCLIENTS):
reactor.connectTCP("localhost", TELNET_PORT, factory)
reactor.connectTCP("127.0.0.1", TELNET_PORT, factory)
# start reactor
reactor.run()
@ -422,12 +603,23 @@ if __name__ == "__main__":
)
args = parser.parse_args()
nclients = int(args.nclients[0])
print(INFO_STARTING.format(N=args.nclients[0]))
print(INFO_STARTING.format(
nclients=nclients,
port=TELNET_PORT,
idmapper_cache_size=IDMAPPER_CACHE_MAXSIZE,
timestep=TIMESTEP,
rate=1/TIMESTEP,
chance_of_login=CHANCE_OF_LOGIN * 100,
chance_of_action=CHANCE_OF_ACTION * 100,
avg_rate=(1 / TIMESTEP) * CHANCE_OF_ACTION,
avg_rate_total=(1 / TIMESTEP) * CHANCE_OF_ACTION * nclients
))
# run the dummyrunner
t0 = time.time()
start_all_dummy_clients(nclients=args.nclients[0])
TIME_START = t0 = time.time()
start_all_dummy_clients(nclients=nclients)
ttot = time.time() - t0
# output runtime

View file

@ -6,9 +6,9 @@ the actions available to dummy accounts.
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
- TIMESTEP - time in seconds between each 'tick'. 1 is a good start.
- CHANCE_OF_ACTION - chance 0-1 of action happening. Default is 0.5.
- CHANCE_OF_LOGIN - chance 0-1 of login happening. 0.01 is a good number.
- TELNET_PORT - port to use, defaults to settings.TELNET_PORT
- ACTIONS - see below
@ -16,23 +16,25 @@ ACTIONS is a tuple
```python
(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 account. 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,
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.
The PROFILE variable define pre-made ACTION tuples for convenience.
Each function should return an iterable of one or more command-call
strings (like "look here"), so each can group multiple command 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.
reference to the dummy client currently performing the action.
The client object has the following relevant properties and methods:
@ -55,11 +57,15 @@ commands (such as creating an account and logging in).
----
"""
import random
import string
# Dummy runner settings
# Time between each dummyrunner "tick", in seconds. Each dummy
# will be called with this frequency.
TIMESTEP = 2
TIMESTEP = 1
# TIMESTEP = 0.025 # 40/s
# Chance of a dummy actually performing an action on a given tick.
# This spreads out usage randomly, like it would be in reality.
@ -68,7 +74,7 @@ CHANCE_OF_ACTION = 0.5
# Chance of a currently unlogged-in dummy performing its login
# action every tick. This emulates not all accounts logging in
# at exactly the same time.
CHANCE_OF_LOGIN = 1.0
CHANCE_OF_LOGIN = 0.01
# Which telnet port to connect to. If set to None, uses the first
# default telnet port of the running server.
@ -79,9 +85,10 @@ TELNET_PORT = None
# some convenient templates
DUMMY_NAME = "Dummy-%s"
DUMMY_PWD = "password-%s"
START_ROOM = "testing_room_start_%s"
DUMMY_NAME = "Dummy_{gid}"
DUMMY_PWD = (''.join(random.choice(string.ascii_letters + string.digits)
for _ in range(20)) + "-{gid}")
START_ROOM = "testing_room_start_{gid}"
ROOM_TEMPLATE = "testing_room_%s"
EXIT_TEMPLATE = "exit_%s"
OBJ_TEMPLATE = "testing_obj_%s"
@ -94,26 +101,27 @@ TOBJ_TYPECLASS = "contrib.tutorial_examples.red_button.RedButton"
# 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
cname = DUMMY_NAME.format(gid=client.gid)
cpwd = DUMMY_PWD.format(gid=client.gid)
room_name = START_ROOM.format(gid=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])
# we assign the dummyrunner cmdsert to ourselves so # we can use special commands
add_cmdset = (
"py from evennia.server.profiling.dummyrunner import DummyRunnerCmdSet;"
"self.cmdset.add(DummyRunnerCmdSet, persistent=False)"
)
# create character, log in, then immediately dig a new location and
# teleport it (to keep the login room clean)
cmds = (
"create %s %s" % (cname, cpwd),
"connect %s %s" % (cname, cpwd),
"@dig %s" % START_ROOM % client.gid,
"@teleport %s" % START_ROOM % client.gid,
"@dig %s = %s, %s" % (roomname, exitname1, exitname2),
f"create {cname} {cpwd}",
f"connect {cname} {cpwd}",
f"dig {room_name}",
f"teleport {room_name}",
add_cmdset,
)
return cmds
@ -122,14 +130,16 @@ 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))
cmds = (
f"create {cname} {cpwd}",
f"connect {cname} {cpwd}"
)
return cmds
def c_logout(client):
"logouts of the game"
return "@quit"
return ("quit",)
# random commands
@ -141,7 +151,7 @@ def c_looks(client):
if not cmds:
cmds = ["look %s" % exi for exi in client.exits]
if not cmds:
cmds = "look"
cmds = ("look",)
return cmds
@ -151,7 +161,7 @@ def c_examines(client):
if not cmds:
cmds = ["examine %s" % exi for exi in client.exits]
if not cmds:
cmds = "examine me"
cmds = ("examine me",)
return cmds
@ -163,7 +173,7 @@ def c_idles(client):
def c_help(client):
"reads help files"
cmds = ("help", "help @teleport", "help look", "help @tunnel", "help @dig")
cmds = ("help", "dummyrunner_echo_response",)
return cmds
@ -173,7 +183,7 @@ def c_digs(client):
exitname1 = EXIT_TEMPLATE % client.counter()
exitname2 = EXIT_TEMPLATE % client.counter()
client.exits.extend([exitname1, exitname2])
return "@dig/tel %s = %s, %s" % (roomname, exitname1, exitname2)
return ("@dig/tel %s = %s, %s" % (roomname, exitname1, exitname2),)
def c_creates_obj(client):
@ -200,9 +210,7 @@ def c_creates_button(client):
def c_socialize(client):
"socializechats on channel"
cmds = (
"ooc Hello!",
"ooc Testing ...",
"ooc Testing ... times 2",
"pub Hello!",
"say Yo!",
"emote stands looking around.",
)
@ -212,81 +220,117 @@ def c_socialize(client):
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
return ("look",) if not cmds else cmds
def c_moves_n(client):
"move through north exit if available"
return "north"
return ("north",)
def c_moves_s(client):
"move through south exit if available"
return "south"
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.
def c_measure_lag(client):
"""
Special dummyrunner command, injected in c_login. It measures
response time. Including this in the ACTION tuple will give more
dummyrunner output about just how fast commands are being processed.
The dummyrunner will treat this special and inject the
{timestamp} just before sending.
"""
return ("dummyrunner_echo_response {timestamp}",)
# Action profile (required)
# Some pre-made profiles to test. To make your own, just assign a tuple to ACTIONS.
#
# idler - does nothing after logging in
# looker - just looks around
# normal_player - moves around, reads help, looks around (digs rarely) (spammy)
# normal_builder - digs now and then, examines, creates objects, moves
# heavy_builder - digs and creates a lot, moves and examines
# socializing_builder - builds a lot, creates help entries, moves, chat (spammy)
# only_digger - extreme builder that only digs room after room
PROFILE = "normal_player"
# "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 account" 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))
# "inactive account" definition
# ACTIONS = (c_login_nodig,
# c_logout,
# (1.0, c_idles))
# "normal account" definition
ACTIONS = (c_login, c_logout, (0.01, c_digs), (0.39, c_looks), (0.2, c_help), (0.4, c_moves))
# walking tester. This requires a pre-made
# "loop" of multiple rooms that ties back
# to limbo (using @tunnel and @open)
# 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))
if PROFILE == 'idler':
ACTIONS = (
c_login,
c_logout,
(0.9, c_idles),
(0.1, c_measure_lag),
)
elif PROFILE == 'looker':
ACTIONS = (
c_login,
c_logout,
(0.8, c_looks),
(0.2, c_measure_lag)
)
elif PROFILE == 'normal_player':
ACTIONS = (
c_login,
c_logout,
(0.01, c_digs),
(0.29, c_looks),
(0.2, c_help),
(0.3, c_moves),
(0.2, c_socialize),
(0.1, c_measure_lag)
)
elif PROFILE == 'normal_builder':
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.2, c_moves)
(0.1, c_measure_lag)
)
elif PROFILE == 'heavy_builder':
ACTIONS = (
c_login,
c_logout,
(0.1, c_looks),
(0.1, c_examines),
(0.2, c_help),
(0.1, c_digs),
(0.1, c_creates_obj),
(0.2, c_moves),
(0.1, c_measure_lag)
)
elif PROFILE == 'socializing_builder':
ACTIONS = (
c_login,
c_logout,
(0.1, c_socialize),
(0.1, c_looks),
(0.1, c_help),
(0.1, c_creates_obj),
(0.2, c_digs),
(0.3, c_moves),
(0.1, c_measure_lag)
)
elif PROFILE == 'only_digger':
ACTIONS = (
c_login,
c_logout,
(0.9, c_digs),
(0.1, c_measure_lag)
)
else:
print("No dummyrunner ACTION profile defined.")
import sys
sys.exit()

View file

@ -6,6 +6,7 @@ running dummyrunner, like this:
Note that these mixin-settings are not suitable for production
servers!
"""
# the dummyrunner will check this variable to make sure
@ -17,3 +18,25 @@ DUMMYRUNNER_MIXIN = True
PASSWORD_HASHERS = ("django.contrib.auth.hashers.MD5PasswordHasher",)
# make dummy clients able to test all commands
PERMISSION_ACCOUNT_DEFAULT = "Developer"
# disable throttles which would otherwise block the runner
CREATION_THROTTLE_LIMIT = None
CREATION_THROTTLE_TIMEOUT = None
LOGIN_THROTTLE_LIMIT = None
LOGIN_THROTTLE_TIMEOUT = None
MAX_COMMAND_RATE = 100000
MAX_CONNECTION_RATE = 100000
MAX_CHAR_LIMIT = 100000
print("""
Dummyrunner settings_mixin added (ONLY FOR PROFILING, NOT FOR PRODUCTION!)
PASSWORD_HASHERS = ("django.contrib.auth.hashers.MD5PasswordHasher",)
PERMISSION_ACCOUNT_DEFAULT = "Developer"
CREATION_THROTTLE_LIMIT = None
CREATION_THROTTLE_TIMEOUT = None
LOGIN_THROTTLE_LIMIT = None
LOGIN_THROTTLE_TIMEOUT = None
MAX_COMMAND_RATE = 100000
MAX_CONNECTION_RATE = 100000
MAX_CHAR_LIMIT = 100000
""")

View file

@ -26,7 +26,8 @@ class Throttle:
Keyword Args:
name (str): Name of this throttle.
limit (int): Max number of failures before imposing limiter
limit (int): Max number of failures before imposing limiter. If `None`,
the throttle is disabled.
timeout (int): number of timeout seconds after
max number of tries has been reached.
cache_size (int): Max number of attempts to record per IP within a
@ -197,6 +198,10 @@ class Throttle:
False otherwise.
"""
if self.limit is None:
# throttle is disabled
return False
now = time.time()
ip = str(ip)

View file

@ -697,7 +697,8 @@ PERMISSION_ACCOUNT_DEFAULT = "Player"
CLIENT_DEFAULT_WIDTH = 78
# telnet standard height is 24; does anyone use such low-res displays anymore?
CLIENT_DEFAULT_HEIGHT = 45
# Set rate limits per-IP on account creations and login attempts
# Set rate limits per-IP on account creations and login attempts. Set limits
# to None to disable.
CREATION_THROTTLE_LIMIT = 2
CREATION_THROTTLE_TIMEOUT = 10 * 60
LOGIN_THROTTLE_LIMIT = 5