Pause/Delay sending of new batch of users from swarm - python-3.x
I have a test case where I need to spawn 1000 websocket connections and sustain a conversation over them through a Locust task (It has a prefedined send/receive process for the websocket connections). I can successfully do it by the following setup in locust:
Max Number of Users: 1000
Hatch rate: 1000
However, this setup opens up 1000 connection every second. Even if i lower down the hatch rate, it will come to a time where it will continue to spawn 1000 websocket connections per second. Is there a way to spawn 1000 users instantly and halt/delay the swarm in sending new 1000 connections for quite some time?
I am trying to test if a my server can handle 1000 users sending and receiving messages from my server through a websocket connection. I have tried multiprocessing approach in python but I'm having a hard time to spawn connections as fast as I can with Locust.
class UserBehavior(TaskSet):
statements = [
"Do you like coffee?",
"What's your favorite book?",
"Do you invest in crypto?",
"Who will host the Superbowl next year?",
"Have you listened to the new Adele?",
"Coldplay released a new album",
"I watched the premiere of Succession season 3 last night",
"Who is your favorite team in the NBA?",
"I want to buy the new Travis Scott x Jordan shoes",
"I want a Lamborghini Urus",
"Have you been to the Philippines?",
"Did you sign up for a Netflix account?"
]
def on_start(self):
pass
def on_quit(self):
pass
#task
def send_convo(self):
end = False
ws_url = "ws://xx.xx.xx.xx:8080/websocket"
self.ws = create_connection(ws_url)
body = json.dumps({"text": "start blender"})
self.ws.send(body)
while True:
#print("Waiting for response..")
response = self.ws.recv()
if response != None:
if "Sorry, this world closed" in response:
end = True
break
if not end:
body = json.dumps({"text": "begin"})
self.ws.send(body)
while True:
#print("Waiting for response..")
response = self.ws.recv()
if response != None:
# print("[BOT]: ", response)
if "Sorry, this world closed" in response:
end = True
self.ws.close()
break
if not end:
body = json.dumps({"text": random.choice(self.statements)})
start_at = time.time()
self.ws.send(body)
while True:
response = self.ws.recv()
if response != None:
if "Sorry, this world closed" not in response:
response_time = int((time.time() - start_at)*1000)
print(f"[BOT]Response: {response}")
response_length = len(response)
events.request_success.fire(
request_type='Websocker Recv',
name='test/ws/echo',
response_time=response_time,
response_length=response_length,
)
else:
end = True
self.ws.close()
break
if not end:
body = json.dumps({"text": "[DONE]"})
self.ws.send(body)
while True:
response = self.ws.recv()
if response != None:
if "Sorry, this world closed" in response:
end = True
self.ws.close()
break
if not end:
time.sleep(1)
body = json.dumps({"text": "EXIT"})
self.ws.send(body)
time.sleep(1)
self.ws.close()
class WebsiteUser(HttpUser):
tasks = [UserBehavior]
wait_time = constant(2)
host = "ws://xx.xx.xx.xx:8080/websocket"
For this particular test, I set the maximum users to 1 and the hatch rate to 1 and clearly, locust keeps on sending 1 request per second as seen on the following responsees:
[BOT]Response: {"text": "No, I don't have a netflix account. I do have a Hulu account, though.", "quick_replies": null}
enter code here
[BOT]Response: {"text": "I have not, but I would love to go. I have always wanted to visit the Philippines.", "quick_replies": null
[BOT]Response: {"text": "No, I don't have a netflix account. I do have a Hulu account, though.", "quick_replies": null}
[BOT]Response: {"text": "I think it's going to be New Orleans. _POTENTIALLY_UNSAFE__", "quick_replies": null}
My expectation is after I set the maximum user to 1, and a hatch rate of 1, there would instantly be 1 websocket connection sending a random message, and receiving 1 main response from the websocket server. but what's happening is it keeps on repeating the task per second until i explicitly hit the stop button on the locust dashboard.
I would debug your logic. Put more print statements in each if block at various places and between each block. When dealing with a long list of decisions, it's easy to get things tripped up.
In this case, you are only wanting to sleep in a very specific situation but it's not happening. Most likely you're setting end = True when you're not expecting it so you're not sleeping and are immediately going to get a new user.
EDIT:
Reviewing your question and issue description again, it sounds like you expect Locust to send a single request and then never send another one. That's not how Locust works. Locust will run your task code for a user. When it's done, that user goes away and it waits for a certain amount of time (looks like you have it set to 2 seconds) and then it spawns another user and starts the task over again. The idea is it will try to keep a near constant number of users you tell it to. It will not only run 1000 users and then end the test, by default.
If you want to keep all 1000 users running, you need to make them continue to execute code. For example, you could put everything in your task in another while loop with another way to break out and end. That way even after making your socket connection and sending the single message you expect, the user will stay alive in the loop and won't end because it ran out of things to do. Doing it this way requires a lot more work and coordination but is possible. There may be other questions on SO about different approaches if this isn't exactly what you're looking for.
Related
Discord connection lost Pycord
I'm working on a chat-revive bot that waits for a message (for an hour) before sending an embed with a chat revive ping. It does this correctly with a small waiting time. i.e if I put 5 - 10 minutes (300 - 600 seconds) on the wait_for section. But anything higher than about 30 minutes and the bot doesn't respond after the time is up. I've been told it's most likely from a reconnection and it cleared the task. I was wondering if anyone knew how to either keep the socket alive long enough to finish the said task or how to resume from where it left off after it disconnects then reconnects #bot.event async def on_message(ctx): if ctx.channel.id == 991806371815243836: pass else: return if ctx.author.id != bot.user.id: pass if not ctx.author.bot: pass print("yes") else: return #everything above is just for specifications guild = bot.get_guild(920057625255747674) chn = guild.get_channel(991806371815243836) times = [10, 5, 15] async def wait(): await bot.wait_for("message", timeout=3600) # Waits an hour for someone to send message try: loop = asyncio.get_event_loop() task = loop.create_task(wait()) shields = shield(task) shields.cancel() await wait() except asyncio.TimeoutError: # If returned with a timeout error, it would send the chat revive embed async with chn.typing(): await asyncio.sleep(random.choice(times)) embed = discord.Embed(title="Come Chat!", description="Aether is quiet,, it must be asleep :o Let's wake it up! Come talk to us!", color=0xBEE0FA) await chn.send("<#&982394594727718938>", embed=embed) bot.run(token)
Message repeating even after explicitly checking for history in discord
I'm trying to make a discord bot and I've added functionality of the tasks repeating after 24 hours using tasks.loop after at an interval of 24 hours( 64800 seconds). Since the code is run on repl.it, I've used uptime robot to continuously send a ping after 5 minutes to make it running always in the repl.it. My issue is that even after checking historyin the channel the message is getting send again as you can see in the below image. ( by checking here I mean the explanation which is the explanation that comes with the API). I'm using NASA api here. Code snippet def get_APOD(): image = requests.get("https://api.nasa.gov/planetary/apod?api_key="+apod) json_data = json.loads(image.text) description = json_data["url"] explanation = "'" + json_data["explanation"]+ "'" title = json_data["title"] return description,explanation,title #client.event async def on_ready(): print('We have logged in as {0.user}'.format(client)) send_message.start() #tasks.loop(seconds=64800.0) async def send_message(): channel = client.get_channel(int(apod_channel)) messages = await channel.history(limit=1).flatten() description,explanation,title = get_APOD() if messages[0].content == explanation: print("Its the same") else: await channel.send(description) await channel.send(title) await channel.send(explanation) . Here you can see the messages repeating twice. It shouldn't by the code I've written here. Since it check for the history in the channel. What is the issue here? Appreciate any help. EDIT: When I restart the repl.it IDE the code will run alright and print "It's the same" as it should if the message is already sent but otherwise it's failing.
How can you reset or cancel asyncio.sleep events?
I am trying to set up a bot in discord that works on timers. One of them will work if one person types in the '!challenge' command, in which the bot will wait for 60 seconds to see if anyone types the '!accept' command in response. If it does not, it states 'Challenge was ignored. Resetting.' Or something along those lines. Another timer actually runs during the game itself, and is an hour long. However, the hour resets after a command has been put in. If the games is idle for an hour (One player DCs or quits) the bot resets the game itself. I had this working with threading: # Initiate a challenge to the room. Opponent is whoever uses the !accept command. This command should be # unavailable for use the moment someone !accepts, to ensure no one trolls during a fight. if message[:10] == "!challenge": msg, opponent, pOneInfo, new_game, bTimer, playerOne = message_10_challenge(channel, charFolder, message, unspoiledArena, character, self.game) if opponent is not "": self.opponent = opponent if pOneInfo is not None: self.pOneInfo = pOneInfo self.pOneTotalHP = self.pOneInfo['thp'] self.pOneCurrentHP = self.pOneInfo['thp'] self.pOneLevel = self.pOneInfo['level'] if new_game != 0: self.game = new_game if bTimer is True: timeout = 60 self.timer = Timer(timeout, self.challengeTimeOut) self.timer.start() if playerOne is not "": self.playerOne = playerOne super().MSG(channel, msg) and: # Response to use to accept a challenge. if message == "!accept": msg, pTwoInfo, new_game, playerTwo, bTimer, bGameTimer, new_oppenent, token = message_accept(channel, charFolder, unspoiledArena, character, self.game, self.opponent, self.pOneInfo) if not charFile.is_file(): super().MSG(channel, "You don't even have a character made to fight.") else: if new_game is not None: self.game = new_game if pTwoInfo is not None: self.pTwoInfo = pTwoInfo self.pTwoTotalHP = self.pTwoInfo['thp'] self.pTwoCurrentHP = self.pTwoInfo['thp'] self.pTwoLevel = self.pTwoInfo['level'] if bTimer: self.timer.cancel() if bGameTimer: gametimeout = 3600 self.gameTimer = Timer(gametimeout, self.combatTimeOut) self.gameTimer.start() if new_oppenent is not None: self.opponent = new_oppenent if playerTwo is not None: self.playerTwo = playerTwo if token is not None: self.token = token for msg_item in msg: super().MSG(channel, msg_item) with functions: def challengeTimeOut(self): super().MSG(unspoiledArena, "Challenge was not accepted. Challenge reset.") self.game = 0 def combatTimeOut(self): super().PRI('Unspoiled Desire', "!reset") The above is an example of the same game, but on a different chat platform, with threading to handle the timers. But threading and discord.py aren't friends I guess. So I am trying to get the above code to work with discord.py, which seems to use asyncio. The thought was to use asyncio.sleep() in the if bTimer is true: await asyncio.sleep(60) self.game = 0 await ctx.send("Challenge was not accepted. Challenge reset.") And this works...but it doesn't stop the timer, so even if someone !accepts, thus changing bTimer to False, which would cancel the timer: if bTimer: self.timer.cancel() it's still going to say "Challenge was not accepted. Challenge reset." The same problem will occure with bGameTimer if I try: if bGameTimer: await asyncio.sleep(3600) await ctx.send("!reset") the game will be hardwired to reset after 1 hours time, no matter if the game is done or not. Rather than resetting the 1 hour timer after every turn, and ONLY resetting if a full hour has passed in which no commands are made. Is there a way to easily cancel or reset sleep cycles?
The asyncio code equivalent to your threading code would be: ... if bTimer: self.timer = asyncio.create_task(self.challengeTimeout()) ... async def challengeTimeout(self): await asyncio.sleep(60) self.game = 0 await ctx.send("Challenge was not accepted. Challenge reset.") asyncio.create_task() creates a light-weight "task" object roughly equivalent to a thread. It runs "in parallel" to the your other coroutines, and you can cancel it by invoking its cancel() method: if bTimer: self.timer.cancel()
Why is this queue not working properly?
The following queue is not working properly somehow. Is there any obvious mistake I have made? Basically every incoming SMS message is put onto the queue, tries to send it and if it successful deletes from the queue. If its unsuccessful it sleeps for 2 seconds and tries sending it again. # initialize queue queue = queue.Queue() def messagePump(): while True: item = queue.get() if item is not None: status = sendText(item) if status == 'SUCCEEDED': queue.task_done() else: time.sleep(2) def sendText(item): response = getClient().send_message(item) response = response['messages'][0] if response['status'] == '0': return 'SUCCEEDED' else: return 'FAILED' #app.route('/webhooks/inbound-sms', methods=['POST']) def delivery_receipt(): data = dict(request.form) or dict(request.args) senderNumber = data['msisdn'][0] incomingMessage = data['text'][0] # came from customer service operator if (senderNumber == customerServiceNumber): try: split = incomingMessage.split(';') # get recipient phone number recipient = split[0] # get message content message = split[1] # check if target number is 10 digit long and there is a message if (len(message) > 0): # for confirmation send beginning string only successText = 'Message successfully sent to: '+recipient+' with text: '+message[:7] queue.put({'from': virtualNumber, 'to': recipient, 'text': message}) The above is running on a Flask server. So invoking messagePump: thread = threading.Thread(target=messagePump) thread.start()
The common in such cases is that Thread has completed execution before item started to be presented in the queue, please call thread.daemon = True before running thread.start(). Another thing which may happen here is that Thread was terminated due to exception. Make sure the messagePump handle all possible exceptions. That topic regarding tracing exceptions on threads may be useful for you: Catch a thread's exception in the caller thread in Python
Using Socket and Threads Why Is my Chat Server so Slow and CPU Usage so High?
So my code works fine except for when iterating through arrays and sending a response to multiple chat clients the latency between each client's reception of the response is nearly a second. I'm running the server and client on my computer so there shouldn't be any real latency, right? I know ruby isn't this slow. Also, why does my computer's fan spin up when running this? There's a bit more if it would be helpful to include it. # Creates a thread per client that listens for any messages and relays them to the server viewer and all the other clients. create_client_listener_threads = Thread.new do x = nil client_quantity = 0 # Loops indefinitely until x != nil #Checks to see if clients have joined since last check. if #client_join_order_array.size > client_quantity # Derives number of new arrivals. number_of_new_arrivals = #client_join_order_array.size - client_quantity # Updates number of clients in client_quantity. client_quantity = #client_join_order_array.size if number_of_new_arrivals != 0 # Passes new arrivals into client for their thread creation. #client_join_order_array[-1 * number_of_new_arrivals..-1].each do |client| # Creates thread to handle receiving of each client's text. client_thread = Thread.new do loop do text = client.acception.gets # Displays text for server viewer. puts "#{client.handle} # #{Time.now} said: #{text}" #client_hash.each_value do |value| if value.handle != client.handle # Displays text for everyone except server viewr and person who spoke. value.acception.puts "#{client.handle} # #{Time.now} said: #{text}" end end end end end end end end end
Instead of testing if #client_join_order_array.size > client_quantity, and doing nothing except smoke the CPU if it is false, you should be accepting the new connection at this point, and blocking until there is one. In other words move the code that accepts connections and adds them to the array here.