locust-cloud 1.14.3__py3-none-any.whl → 1.15.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- locust_cloud/cloud.py +22 -9
- locust_cloud/input_events.py +120 -0
- locust_cloud/websocket.py +3 -2
- {locust_cloud-1.14.3.dist-info → locust_cloud-1.15.1.dist-info}/METADATA +1 -1
- locust_cloud-1.15.1.dist-info/RECORD +11 -0
- locust_cloud-1.14.3.dist-info/RECORD +0 -10
- {locust_cloud-1.14.3.dist-info → locust_cloud-1.15.1.dist-info}/WHEEL +0 -0
- {locust_cloud-1.14.3.dist-info → locust_cloud-1.15.1.dist-info}/entry_points.txt +0 -0
locust_cloud/cloud.py
CHANGED
@@ -1,11 +1,14 @@
|
|
1
1
|
import logging
|
2
2
|
import os
|
3
3
|
import sys
|
4
|
+
import webbrowser
|
5
|
+
from threading import Thread
|
4
6
|
|
5
7
|
import requests
|
6
8
|
from locust_cloud.apisession import ApiSession
|
7
9
|
from locust_cloud.args import parse_known_args
|
8
10
|
from locust_cloud.common import __version__
|
11
|
+
from locust_cloud.input_events import input_listener
|
9
12
|
from locust_cloud.web_login import web_login
|
10
13
|
from locust_cloud.websocket import SessionMismatchError, Websocket, WebsocketTimeout
|
11
14
|
|
@@ -13,11 +16,13 @@ logger = logging.getLogger(__name__)
|
|
13
16
|
|
14
17
|
|
15
18
|
def configure_logging(loglevel: str) -> None:
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
+
format = (
|
20
|
+
"[%(asctime)s] %(levelname)s: %(message)s"
|
21
|
+
if loglevel == "INFO"
|
22
|
+
else "[%(asctime)s] %(levelname)s/%(module)s: %(message)s"
|
19
23
|
)
|
20
|
-
|
24
|
+
logging.basicConfig(format=format, level=loglevel)
|
25
|
+
# Restore log level for other libs. Yes, this can probably be done more nicely
|
21
26
|
logging.getLogger("requests").setLevel(logging.INFO)
|
22
27
|
logging.getLogger("urllib3").setLevel(logging.INFO)
|
23
28
|
|
@@ -88,22 +93,30 @@ def main() -> None:
|
|
88
93
|
|
89
94
|
try:
|
90
95
|
response = session.post("/deploy", json=payload)
|
96
|
+
js = response.json()
|
91
97
|
except requests.exceptions.RequestException as e:
|
92
98
|
logger.error(f"Failed to deploy the load generators: {e}")
|
93
99
|
sys.exit(1)
|
94
100
|
|
95
101
|
if response.status_code != 200:
|
96
102
|
try:
|
97
|
-
logger.error(f"{
|
103
|
+
logger.error(f"{js['Message']} (HTTP {response.status_code}/{response.reason})")
|
98
104
|
except Exception:
|
99
105
|
logger.error(
|
100
106
|
f"HTTP {response.status_code}/{response.reason} - Response: {response.text} - URL: {response.request.url}"
|
101
107
|
)
|
102
108
|
sys.exit(1)
|
103
109
|
|
104
|
-
log_ws_url =
|
105
|
-
session_id =
|
106
|
-
|
110
|
+
log_ws_url = js["log_ws_url"]
|
111
|
+
session_id = js["session_id"]
|
112
|
+
webui_url = log_ws_url.replace("/socket-logs", "")
|
113
|
+
|
114
|
+
def open_ui():
|
115
|
+
webbrowser.open_new_tab(webui_url)
|
116
|
+
|
117
|
+
Thread(target=input_listener({"\r": open_ui, "\n": open_ui}), daemon=True).start()
|
118
|
+
|
119
|
+
# logger.debug(f"Session ID is {session_id}")
|
107
120
|
|
108
121
|
logger.info("Waiting for load generators to be ready...")
|
109
122
|
websocket.connect(
|
@@ -147,7 +160,7 @@ def delete(session):
|
|
147
160
|
)
|
148
161
|
|
149
162
|
if response.status_code == 200:
|
150
|
-
logger.debug(response.json()[
|
163
|
+
logger.debug(f"Response message from teardown: {response.json()['message']}")
|
151
164
|
else:
|
152
165
|
logger.info(
|
153
166
|
f"Could not automatically tear down Locust Cloud: HTTP {response.status_code}/{response.reason} - Response: {response.text} - URL: {response.request.url}"
|
@@ -0,0 +1,120 @@
|
|
1
|
+
# mostly copied from locust core
|
2
|
+
from __future__ import annotations
|
3
|
+
|
4
|
+
import collections
|
5
|
+
import logging
|
6
|
+
import os
|
7
|
+
import sys
|
8
|
+
from collections.abc import Callable
|
9
|
+
|
10
|
+
import gevent
|
11
|
+
|
12
|
+
if os.name == "nt":
|
13
|
+
import pywintypes
|
14
|
+
from win32api import STD_INPUT_HANDLE
|
15
|
+
from win32console import (
|
16
|
+
ENABLE_ECHO_INPUT,
|
17
|
+
ENABLE_LINE_INPUT,
|
18
|
+
ENABLE_PROCESSED_INPUT,
|
19
|
+
KEY_EVENT,
|
20
|
+
GetStdHandle,
|
21
|
+
)
|
22
|
+
else:
|
23
|
+
import select
|
24
|
+
import termios
|
25
|
+
import tty
|
26
|
+
|
27
|
+
|
28
|
+
class InitError(Exception):
|
29
|
+
pass
|
30
|
+
|
31
|
+
|
32
|
+
class UnixKeyPoller:
|
33
|
+
def __init__(self):
|
34
|
+
if sys.stdin.isatty():
|
35
|
+
self.stdin = sys.stdin.fileno()
|
36
|
+
self.tattr = termios.tcgetattr(self.stdin)
|
37
|
+
tty.setcbreak(self.stdin, termios.TCSANOW)
|
38
|
+
else:
|
39
|
+
raise InitError("Terminal was not a tty. Keyboard input disabled")
|
40
|
+
|
41
|
+
def cleanup(self):
|
42
|
+
termios.tcsetattr(self.stdin, termios.TCSANOW, self.tattr)
|
43
|
+
|
44
|
+
def poll(_self):
|
45
|
+
dr, dw, de = select.select([sys.stdin], [], [], 0)
|
46
|
+
if not dr == []:
|
47
|
+
return sys.stdin.read(1)
|
48
|
+
return None
|
49
|
+
|
50
|
+
|
51
|
+
class WindowsKeyPoller:
|
52
|
+
def __init__(self):
|
53
|
+
if sys.stdin.isatty():
|
54
|
+
try:
|
55
|
+
self.read_handle = GetStdHandle(STD_INPUT_HANDLE) # type: ignore
|
56
|
+
self.read_handle.SetConsoleMode(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT) # type: ignore
|
57
|
+
self.cur_event_length = 0
|
58
|
+
self.cur_keys_length = 0
|
59
|
+
self.captured_chars = collections.deque()
|
60
|
+
except pywintypes.error: # type: ignore
|
61
|
+
raise InitError("Terminal says its a tty but we couldn't enable line input. Keyboard input disabled.")
|
62
|
+
else:
|
63
|
+
raise InitError("Terminal was not a tty. Keyboard input disabled")
|
64
|
+
|
65
|
+
def cleanup(self):
|
66
|
+
pass
|
67
|
+
|
68
|
+
def poll(self):
|
69
|
+
if self.captured_chars:
|
70
|
+
return self.captured_chars.popleft()
|
71
|
+
|
72
|
+
events_peek = self.read_handle.PeekConsoleInput(10000)
|
73
|
+
|
74
|
+
if not events_peek:
|
75
|
+
return None
|
76
|
+
|
77
|
+
if not len(events_peek) == self.cur_event_length:
|
78
|
+
for cur_event in events_peek[self.cur_event_length :]:
|
79
|
+
if cur_event.EventType == KEY_EVENT: # type: ignore
|
80
|
+
if ord(cur_event.Char) and cur_event.KeyDown:
|
81
|
+
cur_char = str(cur_event.Char)
|
82
|
+
self.captured_chars.append(cur_char)
|
83
|
+
|
84
|
+
self.cur_event_length = len(events_peek)
|
85
|
+
|
86
|
+
if self.captured_chars:
|
87
|
+
return self.captured_chars.popleft()
|
88
|
+
else:
|
89
|
+
return None
|
90
|
+
|
91
|
+
|
92
|
+
def get_poller():
|
93
|
+
if os.name == "nt":
|
94
|
+
return WindowsKeyPoller()
|
95
|
+
else:
|
96
|
+
return UnixKeyPoller()
|
97
|
+
|
98
|
+
|
99
|
+
def input_listener(key_to_func_map: dict[str, Callable]):
|
100
|
+
def input_listener_func():
|
101
|
+
try:
|
102
|
+
poller = get_poller()
|
103
|
+
except InitError as e:
|
104
|
+
logging.debug(e)
|
105
|
+
return
|
106
|
+
|
107
|
+
try:
|
108
|
+
while True:
|
109
|
+
if input := poller.poll():
|
110
|
+
for key in key_to_func_map:
|
111
|
+
if input == key:
|
112
|
+
key_to_func_map[key]()
|
113
|
+
else:
|
114
|
+
gevent.sleep(0.2)
|
115
|
+
except Exception as e:
|
116
|
+
logging.warning(f"Exception in keyboard input poller: {e}")
|
117
|
+
finally:
|
118
|
+
poller.cleanup()
|
119
|
+
|
120
|
+
return input_listener_func
|
locust_cloud/websocket.py
CHANGED
@@ -64,7 +64,7 @@ class Websocket:
|
|
64
64
|
|
65
65
|
self.__connect_timeout_timer = threading.Timer(timeout, _timeout)
|
66
66
|
self.__connect_timeout_timer.daemon = True
|
67
|
-
logger.debug(f"Setting websocket connection timeout to {timeout} seconds")
|
67
|
+
# logger.debug(f"Setting websocket connection timeout to {timeout} seconds")
|
68
68
|
self.__connect_timeout_timer.start()
|
69
69
|
|
70
70
|
def connect(self, url, *, auth) -> None:
|
@@ -110,7 +110,8 @@ class Websocket:
|
|
110
110
|
__on_connect_error method), raise it.
|
111
111
|
"""
|
112
112
|
timeout = self.wait_timeout if timeout else None
|
113
|
-
|
113
|
+
if timeout: # not worth even debug logging if we dont have a timeout
|
114
|
+
logger.debug(f"Waiting for shutdown for {str(timeout) + 's' if timeout else 'ever'}")
|
114
115
|
res = self.__shutdown_allowed.wait(timeout)
|
115
116
|
if self.exception:
|
116
117
|
raise self.exception
|
@@ -0,0 +1,11 @@
|
|
1
|
+
locust_cloud/apisession.py,sha256=AoU0FGQbyH2qbaTmdyoIMBd_lwZimLtihK0gnpAV6c0,4091
|
2
|
+
locust_cloud/args.py,sha256=1SaMQ16f_3vaNqnAbjFiBL-6ietp1-qfV2LUjBk6b8k,7100
|
3
|
+
locust_cloud/cloud.py,sha256=OaUE0bnlRGgLTKhdDCMnonaT-h2pop_cAfcOFOLcwng,5581
|
4
|
+
locust_cloud/common.py,sha256=cFrDVKpi9OEmH6giOuj9HoIUFSBArixNtNHzZIgDvPE,992
|
5
|
+
locust_cloud/input_events.py,sha256=MyxccgboHByICuK6VpQCCJhZQqTZAacNmkSpw-gxBEw,3420
|
6
|
+
locust_cloud/web_login.py,sha256=1j2AQoEM6XVSDtE1q0Ryrs4jFEx07r9IQfZCoFAQXJg,2400
|
7
|
+
locust_cloud/websocket.py,sha256=EMTDBFu11zNM7o2lyQxhAflzluRcKErh2CY3duyjKxc,8857
|
8
|
+
locust_cloud-1.15.1.dist-info/METADATA,sha256=GPuXVVfeIOck8W781AoYfWITccCas_MJLuJwfpAH7JA,497
|
9
|
+
locust_cloud-1.15.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
10
|
+
locust_cloud-1.15.1.dist-info/entry_points.txt,sha256=PGyAb4e3aTsGS3N3VGShDl6VzJaXy7QwsEgsLOC7V00,57
|
11
|
+
locust_cloud-1.15.1.dist-info/RECORD,,
|
@@ -1,10 +0,0 @@
|
|
1
|
-
locust_cloud/apisession.py,sha256=AoU0FGQbyH2qbaTmdyoIMBd_lwZimLtihK0gnpAV6c0,4091
|
2
|
-
locust_cloud/args.py,sha256=1SaMQ16f_3vaNqnAbjFiBL-6ietp1-qfV2LUjBk6b8k,7100
|
3
|
-
locust_cloud/cloud.py,sha256=Piq6J9pS6Tom1aoYwNmmt4Q9LrLFPKFZC6EqgOen3OE,5107
|
4
|
-
locust_cloud/common.py,sha256=cFrDVKpi9OEmH6giOuj9HoIUFSBArixNtNHzZIgDvPE,992
|
5
|
-
locust_cloud/web_login.py,sha256=1j2AQoEM6XVSDtE1q0Ryrs4jFEx07r9IQfZCoFAQXJg,2400
|
6
|
-
locust_cloud/websocket.py,sha256=lnVRsk0goAHIDNz9cT5xkYAjHSB5aqFyjR_m3X48qRM,8771
|
7
|
-
locust_cloud-1.14.3.dist-info/METADATA,sha256=zV3RuzeK3GU2qoW6TR7h38QfaAb2E-r28s9u-hXXYJY,497
|
8
|
-
locust_cloud-1.14.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
9
|
-
locust_cloud-1.14.3.dist-info/entry_points.txt,sha256=PGyAb4e3aTsGS3N3VGShDl6VzJaXy7QwsEgsLOC7V00,57
|
10
|
-
locust_cloud-1.14.3.dist-info/RECORD,,
|
File without changes
|
File without changes
|