Add a script to simulate heavy lobby traffic

Plus a bunch of changes which were necessary for the script to work:
* The "simulate lobby activity" plugin now exits when the server is shut
  down.
* The plugin now uses wesnoth.random() for random number generation.
  Math.random() uses a fixed seed, which would make all the clients
  perform the exact same actions.
* Exposed wesnoth.random() to plugins to allow the change above.
* --nogui no longer implies --wconsole on Windows. With implied --wconsole
  the clients attached themselves to the standard output of the Python
  script, which made it impossible to see the output of the script itself.
This commit is contained in:
Jyrki Vesterinen 2016-10-23 16:52:36 +03:00
parent 5d14459594
commit e7c2105c48
6 changed files with 113 additions and 37 deletions

View file

@ -21,10 +21,22 @@ local function create_game(context)
context.create({})
end
local function exit_game(context)
local events, info
repeat
context.quit({})
events, context, info = coroutine.yield()
until info.name == "titlescreen"
context.exit({code = 0})
coroutine.yield()
end
return function()
local events, context, info
wesnoth.preferences.new_lobby = true
wesnoth.preferences.new_mp_ui = true
repeat
events, context, info = coroutine.yield()
@ -42,17 +54,22 @@ return function()
-- Reached the lobby. Random delay before we start actually simulating activity.
-- This is here to avoid a situation where activity arrives in bursts after a script
-- has launched, say, 100 copies of Wesnoth at the same time.
wesnoth.delay(math.random(15000))
wesnoth.delay(wesnoth.random(15000))
events, context, info = coroutine.yield()
local in_staging = false
while true do
if math.random() > 0.1 then
if info.name ~= "Multiplayer Lobby" and info.name ~= "Multiplayer Staging" then
-- most often, this means that the server was terminated -> stop generating traffic
exit_game(context)
end
if wesnoth.random() > 0.1 then
-- chat message
local messages = {"asdf", "qwerty", "zxc"}
context.chat({message = messages[math.random(#messages)]})
context.chat({message = messages[wesnoth.random(#messages)]})
else
-- toggle between creating a game and leaving it
if not in_staging then

View file

@ -654,36 +654,6 @@ int game_lua_kernel::intf_set_variable(lua_State *L)
return 0;
}
/**
* Returns a random numer, same interface as math.random.
*/
int game_lua_kernel::intf_random(lua_State *L)
{
if(lua_isnoneornil(L, 1)) {
double r = double (random_new::generator->next_random());
double r_max = double (std::numeric_limits<uint32_t>::max());
lua_push(L, r / (r_max + 1));
return 1;
}
else {
int32_t min;
int32_t max;
if(lua_isnumber(L, 2)) {
min = lua_check<int32_t>(L, 1);
max = lua_check<int32_t>(L, 2);
}
else {
min = 1;
max = lua_check<int32_t>(L, 1);
}
if(min > max) {
return luaL_argerror(L, 1, "min > max");
}
lua_push(L, random_new::generator->get_random_int(min, max));
return 1;
}
}
int game_lua_kernel::intf_set_menu_item(lua_State *L)
{
game_state_.get_wml_menu_items().set_item(luaL_checkstring(L, 1), luaW_checkvconfig(L,2));
@ -4160,7 +4130,6 @@ game_lua_kernel::game_lua_kernel(game_state & gs, play_controller & pc, reports
{ "print", &dispatch<&game_lua_kernel::intf_print > },
{ "put_recall_unit", &dispatch<&game_lua_kernel::intf_put_recall_unit > },
{ "put_unit", &dispatch<&game_lua_kernel::intf_put_unit > },
{ "random", &dispatch<&game_lua_kernel::intf_random > },
{ "redraw", &dispatch<&game_lua_kernel::intf_redraw > },
{ "remove_event_handler", &dispatch<&game_lua_kernel::intf_remove_event > },
{ "remove_fog", &dispatch2<&game_lua_kernel::intf_toggle_fog, true > },

View file

@ -84,7 +84,6 @@ class game_lua_kernel : public lua_kernel_base
int intf_get_recall_units(lua_State *L);
int intf_get_variable(lua_State *L);
int intf_get_side_variable(lua_State *L);
int intf_random(lua_State *L);
int intf_set_variable(lua_State *L);
int intf_set_side_variable(lua_State *L);
int intf_highlight_hex(lua_State *L);

View file

@ -21,6 +21,7 @@
#include "game_errors.hpp"
#include "log.hpp"
#include "lua_jailbreak_exception.hpp" // for tlua_jailbreak_exception
#include "random_new.hpp"
#include "seed_rng.hpp"
#ifdef DEBUG_LUA
@ -213,6 +214,36 @@ static int intf_name_generator(lua_State *L)
return 1;
}
/**
* Returns a random numer, same interface as math.random.
*/
static int intf_random(lua_State *L)
{
if (lua_isnoneornil(L, 1)) {
double r = double(random_new::generator->next_random());
double r_max = double(std::numeric_limits<uint32_t>::max());
lua_push(L, r / (r_max + 1));
return 1;
}
else {
int32_t min;
int32_t max;
if (lua_isnumber(L, 2)) {
min = lua_check<int32_t>(L, 1);
max = lua_check<int32_t>(L, 2);
}
else {
min = 1;
max = lua_check<int32_t>(L, 1);
}
if (min > max) {
return luaL_argerror(L, 1, "min > max");
}
lua_push(L, random_new::generator->get_random_int(min, max));
return 1;
}
}
// End Callback implementations
// Template which allows to push member functions to the lua kernel base into lua as C functions, using a shim
@ -332,6 +363,7 @@ lua_kernel_base::lua_kernel_base()
{ "compile_formula", &lua_formula_bridge::intf_compile_formula},
{ "eval_formula", &lua_formula_bridge::intf_eval_formula},
{ "name_generator", &intf_name_generator },
{ "random", &intf_random },
{ nullptr, nullptr }
};

View file

@ -956,7 +956,7 @@ int main(int argc, char** argv)
// running before then if requested, so just perform a trivial search
// here and let program_options ignore the switch later.
for(size_t k = 0; k < args.size(); ++k) {
if(args[k] == "--wconsole" || args[k] == "--help" || args[k] == "--nogui" || args[k] == "--logdomains" || args[k] == "--path" || args[k] == "--render-image" || args[k] == "--screenshot" || args[k] == "--data-path" || args[k] == "--userdata-path" || args[k] == "--userconfig-path" || args[k] == "--version") {
if(args[k] == "--wconsole" || args[k] == "--help" || args[k] == "--logdomains" || args[k] == "--path" || args[k] == "--render-image" || args[k] == "--screenshot" || args[k] == "--data-path" || args[k] == "--userdata-path" || args[k] == "--userconfig-path" || args[k] == "--version") {
lg::enable_native_console_output();
break;
}

View file

@ -0,0 +1,59 @@
# This script launches wesnothd, followed by 20 copies of wesnoth running the simulate-lobby-activity.lua plugin.
# The idea is to use the script to simulate a high amount of lobby traffic, e.g. for performance testing.
import subprocess
from subprocess import DEVNULL
import os
import os.path
import sys
import time
PORT = 56321
NUM_CLIENTS = 20
EXIT_WAIT_TIME = 20.0
def is_in_path(filename):
for d in os.get_exec_path():
if os.path.exists(os.path.join(d, filename)):
return True
return False
if (os.name == "nt" and not is_in_path("SDL2.dll")):
# Launching Wesnoth is not going to succeed
sys.exit("Error: SDL2.dll is not in PATH. This suggests that you haven't added the external\\dll directory to your PATH.")
print("Launching processes... ", end="")
# Change the working directory to the parent directory of the directory where this script resides
os.chdir(os.path.dirname(os.path.dirname(__file__)))
# Wesnoth restarts itself on launch if OMP_WAIT_POLICY isn't set. That's problematic because it makes it impossible to poll
# when the client processes terminate. Thus, set OMP_WAIT_POLICY.
os.environ["OMP_WAIT_POLICY"] = "PASSIVE"
# Launch the server
server = subprocess.Popen(("wesnothd", "-p", str(PORT)), -1, None, DEVNULL, DEVNULL, DEVNULL)
# Launch the clients
clients = set()
for i in range(NUM_CLIENTS):
clients.add(subprocess.Popen(("wesnoth", "--plugin=simulate-lobby-activity.lua", "--server=localhost:%d" % PORT, "--username=%d" % i, "--nogui"),
-1, None, DEVNULL, DEVNULL, DEVNULL))
input("done.\nPress Enter when you want to terminate all processes.")
server.terminate()
print("Waiting for clients to terminate...")
waiting_start_time = time.monotonic()
while len(clients) > 0 and time.monotonic() < waiting_start_time + EXIT_WAIT_TIME:
time.sleep(1.0)
clients_copy = list(clients)
for c in clients_copy:
if c.poll() != None:
# The process has terminated, remove it from the set.
clients.remove(c)
# Make sure that we get rid of the remaining processes
for c in clients:
c.kill()