locust-cloud 1.12.1__py3-none-any.whl → 1.12.3__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/__init__.py +11 -15
- locust_cloud/cloud.py +3 -2
- locust_cloud/socket_logging.py +33 -9
- locust_cloud/timescale/queries.py +0 -3
- locust_cloud/webui/dist/assets/{index-BoNYN-22.js → index-D3YieuNV.js} +76 -76
- locust_cloud/webui/dist/index.html +1 -1
- locust_cloud/webui/eslint.config.mjs +9 -8
- locust_cloud/webui/package.json +10 -2
- locust_cloud/webui/tsconfig.tsbuildinfo +1 -1
- locust_cloud/webui/vite.config.ts +3 -3
- locust_cloud/webui/vitest.config.ts +16 -0
- locust_cloud/webui/yarn.lock +1018 -18
- {locust_cloud-1.12.1.dist-info → locust_cloud-1.12.3.dist-info}/METADATA +3 -3
- locust_cloud-1.12.3.dist-info/RECORD +25 -0
- {locust_cloud-1.12.1.dist-info → locust_cloud-1.12.3.dist-info}/WHEEL +1 -1
- locust_cloud-1.12.1.dist-info/RECORD +0 -24
- {locust_cloud-1.12.1.dist-info → locust_cloud-1.12.3.dist-info}/entry_points.txt +0 -0
locust_cloud/__init__.py
CHANGED
@@ -105,21 +105,17 @@ def on_locust_init(environment: locust.env.Environment, **_args):
|
|
105
105
|
if not (os.environ.get("PGHOST")):
|
106
106
|
return
|
107
107
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
pool.wait()
|
120
|
-
except Exception as e:
|
121
|
-
logger.exception(e)
|
122
|
-
raise
|
108
|
+
conninfo = make_conninfo(
|
109
|
+
sslmode="require",
|
110
|
+
)
|
111
|
+
pool = ConnectionPool(
|
112
|
+
conninfo,
|
113
|
+
min_size=1,
|
114
|
+
max_size=20,
|
115
|
+
configure=set_autocommit,
|
116
|
+
check=ConnectionPool.check_connection,
|
117
|
+
)
|
118
|
+
pool.wait(timeout=10)
|
123
119
|
|
124
120
|
if not environment.parsed_options.graph_viewer:
|
125
121
|
IdleExit(environment)
|
locust_cloud/cloud.py
CHANGED
@@ -401,11 +401,12 @@ def delete(credential_manager):
|
|
401
401
|
headers=headers,
|
402
402
|
)
|
403
403
|
|
404
|
-
if response.status_code
|
404
|
+
if response.status_code == 200:
|
405
|
+
logger.debug(response.json()["message"])
|
406
|
+
else:
|
405
407
|
logger.info(
|
406
408
|
f"Could not automatically tear down Locust Cloud: HTTP {response.status_code}/{response.reason} - Response: {response.text} - URL: {response.request.url}"
|
407
409
|
)
|
408
|
-
logger.debug(response.json()["message"])
|
409
410
|
except Exception as e:
|
410
411
|
logger.error(f"Could not automatically tear down Locust Cloud: {e.__class__.__name__}:{e}")
|
411
412
|
|
locust_cloud/socket_logging.py
CHANGED
@@ -3,13 +3,13 @@ import logging
|
|
3
3
|
import os
|
4
4
|
import sys
|
5
5
|
from collections import deque
|
6
|
-
from contextlib import suppress
|
7
6
|
|
8
7
|
import flask
|
9
8
|
import gevent
|
10
9
|
import gevent.pywsgi
|
11
10
|
import geventwebsocket.handler
|
12
11
|
import socketio
|
12
|
+
import socketio.exceptions
|
13
13
|
|
14
14
|
|
15
15
|
def setup_socket_logging():
|
@@ -23,13 +23,13 @@ def setup_socket_logging():
|
|
23
23
|
server in addition to the websocket stuff.
|
24
24
|
"""
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
|
26
|
+
quiet_logger = logging.getLogger("be_quiet")
|
27
|
+
quiet_logger.propagate = False
|
28
|
+
quiet_logger.addHandler(logging.NullHandler())
|
29
29
|
|
30
30
|
# This app will use a logger with the same name as the application
|
31
31
|
# which means it will pick up the one set up above.
|
32
|
-
healthcheck_app = flask.Flask(
|
32
|
+
healthcheck_app = flask.Flask("be_quiet")
|
33
33
|
|
34
34
|
# /login is the health check endpoint currently configured for the
|
35
35
|
# ALB controller in kubernetes.
|
@@ -38,18 +38,33 @@ def setup_socket_logging():
|
|
38
38
|
def healthcheck():
|
39
39
|
return ""
|
40
40
|
|
41
|
+
logger = logging.getLogger(__name__)
|
42
|
+
socketio_path = f"/{os.environ['CUSTOMER_ID']}/socket-logs"
|
41
43
|
sio = socketio.Server(async_handlers=True, always_connect=True, async_mode="gevent", cors_allowed_origins="*")
|
42
|
-
sio_app = socketio.WSGIApp(sio, healthcheck_app, socketio_path=
|
44
|
+
sio_app = socketio.WSGIApp(sio, healthcheck_app, socketio_path=socketio_path)
|
43
45
|
message_queue = deque(maxlen=500)
|
44
46
|
|
47
|
+
@sio.event
|
48
|
+
def connect(sid, environ, auth): # noqa: ARG001
|
49
|
+
logger.debug("Client connected to socketio server")
|
50
|
+
|
51
|
+
@sio.event
|
52
|
+
def disconnect(sid): # noqa: ARG001
|
53
|
+
logger.debug("Client disconnected from socketio server")
|
54
|
+
|
45
55
|
class QueueCopyStream:
|
46
56
|
def __init__(self, name, original):
|
47
57
|
self.name = name
|
48
58
|
self.original = original
|
49
59
|
|
50
60
|
def write(self, message):
|
51
|
-
|
61
|
+
"""
|
62
|
+
Writes to the queue first since when running in gevent the write to
|
63
|
+
stdout/stderr can yield and we want to ensure the same ordering in
|
64
|
+
the queue as for the written messages.
|
65
|
+
"""
|
52
66
|
message_queue.append((self.name, message))
|
67
|
+
self.original.write(message)
|
53
68
|
|
54
69
|
def flush(self):
|
55
70
|
self.original.flush()
|
@@ -65,13 +80,20 @@ def setup_socket_logging():
|
|
65
80
|
while message_queue and (sid := connected_sid()):
|
66
81
|
name, message = message_queue[0]
|
67
82
|
|
68
|
-
|
83
|
+
try:
|
84
|
+
if name == "shutdown":
|
85
|
+
logger.debug("Sending websocket shutdown event")
|
86
|
+
|
69
87
|
sio.call(name, message, to=sid, timeout=5)
|
70
88
|
message_queue.popleft()
|
71
89
|
|
72
90
|
if name == "shutdown":
|
91
|
+
logger.debug("Websocket shutdown event acknowledged by client")
|
73
92
|
return
|
74
93
|
|
94
|
+
except socketio.exceptions.TimeoutError:
|
95
|
+
logger.debug("Timed out waiting for client to aknowledge websocket message")
|
96
|
+
|
75
97
|
gevent.sleep(1)
|
76
98
|
|
77
99
|
emitter_greenlet = gevent.spawn(emitter)
|
@@ -81,6 +103,7 @@ def setup_socket_logging():
|
|
81
103
|
|
82
104
|
@atexit.register
|
83
105
|
def notify_shutdown(*args, **kwargs):
|
106
|
+
logger.debug("Adding shutdown event to websocket queue")
|
84
107
|
message_queue.append(("shutdown", ""))
|
85
108
|
emitter_greenlet.join(timeout=30)
|
86
109
|
|
@@ -92,10 +115,11 @@ def setup_socket_logging():
|
|
92
115
|
|
93
116
|
@property
|
94
117
|
def logger(self):
|
95
|
-
return
|
118
|
+
return quiet_logger
|
96
119
|
|
97
120
|
@gevent.spawn
|
98
121
|
def start_websocket_server():
|
122
|
+
logger.debug(f"Starting socketio server on port 1095 with path {socketio_path}")
|
99
123
|
server = gevent.pywsgi.WSGIServer(
|
100
124
|
("", 1095), sio_app, log=None, error_log=None, handler_class=WebSocketHandlerWithoutLogging
|
101
125
|
)
|
@@ -4,10 +4,8 @@ requests_query = """
|
|
4
4
|
SELECT
|
5
5
|
name,
|
6
6
|
request_type as method,
|
7
|
-
SUM(average * count) / SUM(count) as average,
|
8
7
|
SUM(count) as requests,
|
9
8
|
SUM(failed_count) as failed,
|
10
|
-
MIN(min),
|
11
9
|
MAX(max),
|
12
10
|
SUM(failed_count) / SUM(count) * 100 as "errorPercentage"
|
13
11
|
FROM requests_summary_view
|
@@ -200,7 +198,6 @@ SELECT
|
|
200
198
|
fail_ratio as "failRatio",
|
201
199
|
requests,
|
202
200
|
date_trunc('second', end_time - id) AS "runTime",
|
203
|
-
description,
|
204
201
|
exit_code as "exitCode",
|
205
202
|
username,
|
206
203
|
worker_count as "workerCount",
|