mirror of
https://github.com/evennia/evennia.git
synced 2026-03-16 21:06:30 +01:00
Cleaned up dummyrunner and fixed a lot of issues
This commit is contained in:
parent
d7b66eecca
commit
677a34d06e
6 changed files with 408 additions and 142 deletions
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
""")
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue