locust-cloud 1.5.4__py3-none-any.whl → 1.5.6__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 +1 -1
- locust_cloud/auth.py +8 -3
- locust_cloud/cloud.py +22 -34
- locust_cloud/constants.py +1 -1
- locust_cloud/credential_manager.py +1 -8
- locust_cloud/timescale/queries.py +9 -8
- locust_cloud/timescale/query.py +0 -1
- {locust_cloud-1.5.4.dist-info → locust_cloud-1.5.6.dist-info}/METADATA +1 -1
- {locust_cloud-1.5.4.dist-info → locust_cloud-1.5.6.dist-info}/RECORD +11 -11
- {locust_cloud-1.5.4.dist-info → locust_cloud-1.5.6.dist-info}/WHEEL +0 -0
- {locust_cloud-1.5.4.dist-info → locust_cloud-1.5.6.dist-info}/entry_points.txt +0 -0
locust_cloud/__init__.py
CHANGED
locust_cloud/auth.py
CHANGED
@@ -7,9 +7,11 @@ import requests
|
|
7
7
|
import werkzeug
|
8
8
|
from flask import redirect, request, url_for
|
9
9
|
from flask_login import UserMixin, login_user
|
10
|
-
from locust_cloud.constants import
|
10
|
+
from locust_cloud.constants import (
|
11
|
+
DEFAULT_DEPLOYER_URL,
|
12
|
+
)
|
11
13
|
|
12
|
-
|
14
|
+
DEPLOYER_URL = os.environ.get("LOCUSTCLOUD_DEPLOYER_URL", DEFAULT_DEPLOYER_URL)
|
13
15
|
|
14
16
|
|
15
17
|
class Credentials(TypedDict):
|
@@ -64,7 +66,10 @@ def register_auth(environment: locust.env.Environment):
|
|
64
66
|
password = request.form.get("password")
|
65
67
|
|
66
68
|
try:
|
67
|
-
auth_response = requests.post(
|
69
|
+
auth_response = requests.post(
|
70
|
+
f"{DEPLOYER_URL}/auth/login",
|
71
|
+
json={"username": username, "password": password},
|
72
|
+
)
|
68
73
|
|
69
74
|
if auth_response.status_code == 200:
|
70
75
|
credentials = auth_response.json()
|
locust_cloud/cloud.py
CHANGED
@@ -16,14 +16,13 @@ import requests
|
|
16
16
|
from botocore.exceptions import ClientError
|
17
17
|
from locust_cloud.constants import (
|
18
18
|
DEFAULT_CLUSTER_NAME,
|
19
|
-
|
19
|
+
DEFAULT_DEPLOYER_URL,
|
20
20
|
DEFAULT_NAMESPACE,
|
21
21
|
DEFAULT_REGION_NAME,
|
22
22
|
USERS_PER_WORKER,
|
23
23
|
)
|
24
24
|
from locust_cloud.credential_manager import CredentialError, CredentialManager
|
25
25
|
|
26
|
-
LOCUST_ENV_VARIABLE_IGNORE_LIST = ["LOCUST_BUILD_PATH", "LOCUST_SKIP_MONKEY_PATCH"]
|
27
26
|
__version__ = importlib.metadata.version("locust-cloud")
|
28
27
|
|
29
28
|
|
@@ -57,7 +56,7 @@ parser = configargparse.ArgumentParser(
|
|
57
56
|
"~/.cloud.conf",
|
58
57
|
"cloud.conf",
|
59
58
|
],
|
60
|
-
auto_env_var_prefix="
|
59
|
+
auto_env_var_prefix="LOCUSTCLOUD_",
|
61
60
|
formatter_class=configargparse.RawDescriptionHelpFormatter,
|
62
61
|
config_file_parser_class=configargparse.CompositeConfigParser(
|
63
62
|
[
|
@@ -67,7 +66,7 @@ parser = configargparse.ArgumentParser(
|
|
67
66
|
),
|
68
67
|
description="""Launches distributed Locust runs on locust.cloud infrastructure.
|
69
68
|
|
70
|
-
Example: locust-cloud -f my_locustfile.py --
|
69
|
+
Example: locust-cloud -f my_locustfile.py --region us-east-1 --users 2000""",
|
71
70
|
epilog="""Any parameters not listed here are forwarded to locust master unmodified, so go ahead and use things like --users, --host, --run-time, ...
|
72
71
|
Locust config can also be set using config file (~/.locust.conf, locust.conf, pyproject.toml, ~/.cloud.conf or cloud.conf).
|
73
72
|
Parameters specified on command line override env vars, which in turn override config files.""",
|
@@ -88,21 +87,19 @@ parser.add_argument(
|
|
88
87
|
"--users",
|
89
88
|
type=int,
|
90
89
|
default=1,
|
91
|
-
help="Number of users to launch. This is the same as the regular Locust argument, but also
|
90
|
+
help="Number of users to launch. This is the same as the regular Locust argument, but also affects how many workers to launch.",
|
92
91
|
env_var="LOCUST_USERS",
|
93
92
|
)
|
94
93
|
parser.add_argument(
|
95
94
|
"--requirements",
|
96
95
|
type=str,
|
97
96
|
help="Optional requirements.txt file that contains your external libraries.",
|
98
|
-
env_var="LOCUST_REQUIREMENTS",
|
99
97
|
)
|
100
98
|
parser.add_argument(
|
101
|
-
"--
|
99
|
+
"--region",
|
102
100
|
type=str,
|
103
|
-
default=DEFAULT_REGION_NAME,
|
104
|
-
help="Sets the region to use for the deployed cluster",
|
105
|
-
env_var="AWS_REGION_NAME",
|
101
|
+
default=os.environ.get("AWS_DEFAULT_REGION", DEFAULT_REGION_NAME),
|
102
|
+
help="Sets the AWS region to use for the deployed cluster, e.g. us-east-1. It defaults to use AWS_DEFAULT_REGION env var, like AWS tools.",
|
106
103
|
)
|
107
104
|
parser.add_argument(
|
108
105
|
"--kube-cluster-name",
|
@@ -119,11 +116,10 @@ parser.add_argument(
|
|
119
116
|
env_var="KUBE_NAMESPACE",
|
120
117
|
)
|
121
118
|
parser.add_argument(
|
122
|
-
"--
|
119
|
+
"--deployer-url",
|
123
120
|
type=str,
|
124
|
-
default=
|
125
|
-
help=
|
126
|
-
env_var="LOCUST_API_BASE_URL",
|
121
|
+
default=DEFAULT_DEPLOYER_URL,
|
122
|
+
help=configargparse.SUPPRESS,
|
127
123
|
)
|
128
124
|
parser.add_argument(
|
129
125
|
"--aws-access-key-id",
|
@@ -143,29 +139,25 @@ parser.add_argument(
|
|
143
139
|
"--username",
|
144
140
|
type=str,
|
145
141
|
help="Authentication for deploying with Locust Cloud",
|
146
|
-
|
147
|
-
default=None,
|
142
|
+
default=os.getenv("LOCUST_CLOUD_USERNAME", None), # backwards compatitibility for dmdb
|
148
143
|
)
|
149
144
|
parser.add_argument(
|
150
145
|
"--password",
|
151
146
|
type=str,
|
152
147
|
help="Authentication for deploying with Locust Cloud",
|
153
|
-
|
154
|
-
default=None,
|
148
|
+
default=os.getenv("LOCUST_CLOUD_PASSWORD", None), # backwards compatitibility for dmdb
|
155
149
|
)
|
156
150
|
parser.add_argument(
|
157
151
|
"--loglevel",
|
158
152
|
"-L",
|
159
153
|
type=str,
|
160
154
|
help="Log level",
|
161
|
-
env_var="LOCUST_CLOUD_LOGLEVEL",
|
162
155
|
default="INFO",
|
163
156
|
)
|
164
157
|
parser.add_argument(
|
165
158
|
"--workers",
|
166
159
|
type=int,
|
167
|
-
help="Number of workers to use for the deployment",
|
168
|
-
env_var="LOCUST_CLOUD_WORKERS",
|
160
|
+
help=f"Number of workers to use for the deployment. Defaults to number of users divided by {USERS_PER_WORKER}",
|
169
161
|
default=None,
|
170
162
|
)
|
171
163
|
parser.add_argument(
|
@@ -176,7 +168,6 @@ parser.add_argument(
|
|
176
168
|
parser.add_argument(
|
177
169
|
"--image-tag",
|
178
170
|
type=str,
|
179
|
-
env_var="LOCUST_CLOUD_IMAGE_TAG",
|
180
171
|
default="latest",
|
181
172
|
help=configargparse.SUPPRESS, # overrides the locust-cloud docker image tag. for internal use
|
182
173
|
)
|
@@ -202,6 +193,7 @@ def main() -> None:
|
|
202
193
|
s3_bucket = f"{options.kube_cluster_name}-{options.kube_namespace}"
|
203
194
|
deployed_pods: list[Any] = []
|
204
195
|
worker_count: int = max(options.workers or math.ceil(options.users / USERS_PER_WORKER), 2)
|
196
|
+
os.environ["AWS_DEFAULT_REGION"] = options.region
|
205
197
|
if options.users > 10000:
|
206
198
|
logger.error("You asked for more than 10000 Users, that isn't allowed.")
|
207
199
|
sys.exit(1)
|
@@ -217,15 +209,14 @@ def main() -> None:
|
|
217
209
|
)
|
218
210
|
sys.exit(1)
|
219
211
|
|
220
|
-
logger.info(f"Authenticating ({
|
221
|
-
logger.debug(f"Lambda url: {options.
|
212
|
+
logger.info(f"Authenticating ({os.environ['AWS_DEFAULT_REGION']}, v{__version__})")
|
213
|
+
logger.debug(f"Lambda url: {options.deployer_url}")
|
222
214
|
credential_manager = CredentialManager(
|
223
|
-
lambda_url=options.
|
215
|
+
lambda_url=options.deployer_url,
|
224
216
|
access_key=options.aws_access_key_id,
|
225
217
|
secret_key=options.aws_secret_access_key,
|
226
218
|
username=options.username,
|
227
219
|
password=options.password,
|
228
|
-
region_name=options.aws_region_name,
|
229
220
|
)
|
230
221
|
|
231
222
|
credentials = credential_manager.get_current_credentials()
|
@@ -278,26 +269,23 @@ def main() -> None:
|
|
278
269
|
{"name": env_variable, "value": str(os.environ[env_variable])}
|
279
270
|
for env_variable in os.environ
|
280
271
|
if env_variable.startswith("LOCUST_")
|
281
|
-
and not env_variable.startswith("LOCUST_CLOUD")
|
282
272
|
and not env_variable
|
283
273
|
in [
|
284
274
|
"LOCUST_LOCUSTFILE",
|
285
275
|
"LOCUST_USERS",
|
286
276
|
"LOCUST_WEB_HOST_DISPLAY_NAME",
|
287
|
-
"LOCUST_API_BASE_URL",
|
288
277
|
"LOCUST_SKIP_MONKEY_PATCH",
|
289
|
-
"LOCUST_REQUIREMENTS_URL",
|
290
278
|
]
|
291
279
|
and os.environ[env_variable]
|
292
280
|
]
|
293
|
-
deploy_endpoint = f"{options.
|
281
|
+
deploy_endpoint = f"{options.deployer_url}/{options.kube_cluster_name}"
|
294
282
|
payload = {
|
295
283
|
"locust_args": [
|
296
284
|
{"name": "LOCUST_LOCUSTFILE", "value": locustfile_url},
|
297
285
|
{"name": "LOCUST_USERS", "value": str(options.users)},
|
298
|
-
{"name": "LOCUST_REQUIREMENTS_URL", "value": requirements_url},
|
299
286
|
{"name": "LOCUST_FLAGS", "value": " ".join(locust_options)},
|
300
|
-
{"name": "
|
287
|
+
{"name": "LOCUSTCLOUD_REQUIREMENTS_URL", "value": requirements_url},
|
288
|
+
{"name": "LOCUSTCLOUD_DEPLOYER_URL", "value": options.deployer_url},
|
301
289
|
*locust_env_variables,
|
302
290
|
],
|
303
291
|
"worker_count": worker_count,
|
@@ -416,7 +404,7 @@ def delete(s3_bucket, credential_manager):
|
|
416
404
|
headers["AWS_SESSION_TOKEN"] = token
|
417
405
|
|
418
406
|
response = requests.delete(
|
419
|
-
f"{options.
|
407
|
+
f"{options.deployer_url}/{options.kube_cluster_name}",
|
420
408
|
headers=headers,
|
421
409
|
params={"namespace": options.kube_namespace} if options.kube_namespace else {},
|
422
410
|
)
|
@@ -426,7 +414,7 @@ def delete(s3_bucket, credential_manager):
|
|
426
414
|
f"Could not automatically tear down Locust Cloud: HTTP {response.status_code}/{response.reason} - Response: {response.text} - URL: {response.request.url}"
|
427
415
|
)
|
428
416
|
except Exception as e:
|
429
|
-
logger.error(f"Could not automatically tear down Locust Cloud: {e}")
|
417
|
+
logger.error(f"Could not automatically tear down Locust Cloud: {e.__class__.__name__}:{e}")
|
430
418
|
|
431
419
|
try:
|
432
420
|
logger.debug("Cleaning up locustfiles")
|
locust_cloud/constants.py
CHANGED
@@ -8,7 +8,6 @@ import jwt
|
|
8
8
|
import requests
|
9
9
|
from botocore.credentials import RefreshableCredentials
|
10
10
|
from botocore.session import Session as BotocoreSession
|
11
|
-
from locust_cloud.constants import DEFAULT_REGION_NAME
|
12
11
|
|
13
12
|
logger = logging.getLogger(__name__)
|
14
13
|
|
@@ -27,7 +26,6 @@ class CredentialManager:
|
|
27
26
|
password: str | None = None,
|
28
27
|
user_sub_id: str | None = None,
|
29
28
|
refresh_token: str | None = None,
|
30
|
-
region_name: str = DEFAULT_REGION_NAME,
|
31
29
|
access_key: str | None = None,
|
32
30
|
secret_key: str | None = None,
|
33
31
|
) -> None:
|
@@ -36,7 +34,6 @@ class CredentialManager:
|
|
36
34
|
self.password = password
|
37
35
|
self.user_sub_id = user_sub_id
|
38
36
|
self.refresh_token = refresh_token
|
39
|
-
self.region_name = region_name
|
40
37
|
|
41
38
|
self.credentials = {
|
42
39
|
"access_key": access_key,
|
@@ -56,12 +53,8 @@ class CredentialManager:
|
|
56
53
|
botocore_session = BotocoreSession()
|
57
54
|
botocore_session._credentials = self.refreshable_credentials # type: ignore
|
58
55
|
botocore_session.set_config_variable("signature_version", "v4")
|
59
|
-
botocore_session.set_config_variable("region", self.region_name)
|
60
56
|
|
61
|
-
self.session = boto3.Session(
|
62
|
-
botocore_session=botocore_session,
|
63
|
-
region_name=self.region_name,
|
64
|
-
)
|
57
|
+
self.session = boto3.Session(botocore_session=botocore_session)
|
65
58
|
logger.debug("Boto3 session created with RefreshableCredentials.")
|
66
59
|
|
67
60
|
def obtain_credentials(self) -> None:
|
@@ -33,7 +33,7 @@ GROUP BY "name",left(exception,300)
|
|
33
33
|
requests_per_second = """
|
34
34
|
WITH request_count_agg AS (
|
35
35
|
SELECT
|
36
|
-
time_bucket_gapfill(
|
36
|
+
time_bucket_gapfill(%(resolution)s * interval '1 second', bucket) AS time,
|
37
37
|
COALESCE(SUM(count)/%(resolution)s, 0) as rps
|
38
38
|
FROM requests_summary
|
39
39
|
WHERE bucket BETWEEN %(start)s AND %(end)s
|
@@ -43,7 +43,7 @@ WITH request_count_agg AS (
|
|
43
43
|
),
|
44
44
|
user_count_agg AS (
|
45
45
|
SELECT
|
46
|
-
time_bucket_gapfill(
|
46
|
+
time_bucket_gapfill(%(resolution)s * interval '1 second', time) AS time,
|
47
47
|
COALESCE(avg(user_count), 0) as users
|
48
48
|
FROM number_of_users
|
49
49
|
WHERE time BETWEEN %(start)s AND %(end)s
|
@@ -53,7 +53,7 @@ user_count_agg AS (
|
|
53
53
|
),
|
54
54
|
errors_per_s_agg AS (
|
55
55
|
SELECT
|
56
|
-
time_bucket_gapfill(
|
56
|
+
time_bucket_gapfill(%(resolution)s * interval '1 second', bucket) AS time,
|
57
57
|
COALESCE(SUM(failed_count)/%(resolution)s, 0) as error_rate
|
58
58
|
FROM requests_summary
|
59
59
|
WHERE bucket BETWEEN %(start)s AND %(end)s
|
@@ -101,7 +101,7 @@ AND run_id = %(testrun)s
|
|
101
101
|
|
102
102
|
rps_per_request = """
|
103
103
|
SELECT
|
104
|
-
time_bucket_gapfill(
|
104
|
+
time_bucket_gapfill(%(resolution)s * interval '1 second', bucket) AS time,
|
105
105
|
name,
|
106
106
|
COALESCE(SUM(count)/%(resolution)s, 0) as throughput
|
107
107
|
FROM requests_summary
|
@@ -114,7 +114,7 @@ ORDER BY 1,2
|
|
114
114
|
|
115
115
|
avg_response_times = """
|
116
116
|
SELECT
|
117
|
-
time_bucket_gapfill(
|
117
|
+
time_bucket_gapfill(%(resolution)s * interval '1 second', bucket) as time,
|
118
118
|
name,
|
119
119
|
avg(average) as "responseTime"
|
120
120
|
FROM requests_summary
|
@@ -126,7 +126,7 @@ ORDER BY 1, 2
|
|
126
126
|
|
127
127
|
errors_per_request = """
|
128
128
|
SELECT
|
129
|
-
time_bucket_gapfill(
|
129
|
+
time_bucket_gapfill(%(resolution)s * interval '1 second', bucket) AS time,
|
130
130
|
name,
|
131
131
|
SUM(failed_count)/%(resolution)s as "errorRate"
|
132
132
|
FROM requests_summary
|
@@ -138,19 +138,20 @@ ORDER BY 1
|
|
138
138
|
|
139
139
|
|
140
140
|
perc99_response_times = """
|
141
|
-
SELECT time_bucket(
|
141
|
+
SELECT time_bucket(%(resolution)s * interval '1 second', bucket) AS time,
|
142
142
|
name,
|
143
143
|
perc99
|
144
144
|
FROM requests_summary
|
145
145
|
WHERE bucket BETWEEN %(start)s AND %(end)s
|
146
146
|
AND run_id = %(testrun)s
|
147
147
|
GROUP BY 1, name, perc99
|
148
|
+
ORDER BY 1
|
148
149
|
"""
|
149
150
|
|
150
151
|
|
151
152
|
response_length = """
|
152
153
|
SELECT
|
153
|
-
time_bucket(
|
154
|
+
time_bucket(%(resolution)s * interval '1 second', bucket) as time,
|
154
155
|
response_length as "responseLength",
|
155
156
|
name
|
156
157
|
FROM requests_summary
|
locust_cloud/timescale/query.py
CHANGED
@@ -59,6 +59,5 @@ def register_query(environment, pool):
|
|
59
59
|
logger.warning(f"Received invalid query key: '{query}'")
|
60
60
|
return make_response("Invalid query key", 401)
|
61
61
|
except Exception as e:
|
62
|
-
print(e)
|
63
62
|
logger.error(f"Error executing query '{query}': {e}", exc_info=True)
|
64
63
|
return make_response("Error executing query", 401)
|
@@ -1,11 +1,11 @@
|
|
1
|
-
locust_cloud/__init__.py,sha256=
|
2
|
-
locust_cloud/auth.py,sha256=
|
3
|
-
locust_cloud/cloud.py,sha256=
|
4
|
-
locust_cloud/constants.py,sha256=
|
5
|
-
locust_cloud/credential_manager.py,sha256=
|
1
|
+
locust_cloud/__init__.py,sha256=wH_r-2Up1Wd8uVVffQmVdSxsr479Q2pAro-NA8GzQs8,3082
|
2
|
+
locust_cloud/auth.py,sha256=PFox9bsu6m-jCgvXcdICWvx47bGe8fQ1OU4NSLEzuvg,2975
|
3
|
+
locust_cloud/cloud.py,sha256=O6yMDFksrVtlc29XRs6cfA3QIwZgNpquZ_ouJJQs5XM,16074
|
4
|
+
locust_cloud/constants.py,sha256=pYmV-msUnEej8FsPYZZyJ3rNG6utHHIQEcSNCCD8rSg,169
|
5
|
+
locust_cloud/credential_manager.py,sha256=KFvoThBz7fMhgZiDqId0uQ7qIoOSnDACT4yXWgSy6X8,5235
|
6
6
|
locust_cloud/timescale/exporter.py,sha256=AgwXvdYykHpVwcPz04Ygi0Rx4cjH6_oBT661Ji8g52Y,10999
|
7
|
-
locust_cloud/timescale/queries.py,sha256=
|
8
|
-
locust_cloud/timescale/query.py,sha256=
|
7
|
+
locust_cloud/timescale/queries.py,sha256=KHgZ3l5K0-A4tQp9DqiwnD7kWD7WZPW-v0fUGgzPRfw,6196
|
8
|
+
locust_cloud/timescale/query.py,sha256=GEtuZIdUwJxCH_RK9IxMsnKonMJ_Y2eTfpbZWNPoTJw,2768
|
9
9
|
locust_cloud/webui/.eslintrc,sha256=huuuTZNwbBGkhfelEOMhLQ-tgC9OYnnX0DRYgBn83Ig,1146
|
10
10
|
locust_cloud/webui/.gitignore,sha256=i8EHIqDlVm1-Dkvf_GTZmP_Wu99GE7ABfbYzHXOU734,54
|
11
11
|
locust_cloud/webui/.prettierrc,sha256=qZpzAJPlHUQ3--NHYK8WXgc_TlIqq-eoz4pbkTKK3nk,167
|
@@ -17,7 +17,7 @@ locust_cloud/webui/vite.config.ts,sha256=cqxPMkbwEA3H9mGGbuPulQUhIHCosUqm_1usxzs
|
|
17
17
|
locust_cloud/webui/yarn.lock,sha256=6BC6sHV1ufQeg1s3HoLHC2SwYs6zEVE-NHVg_1kSuVI,227118
|
18
18
|
locust_cloud/webui/dist/index.html,sha256=O4blyt2S59DAyM2UiC5Fecx6UX4I_MqmfH2IL7hWvrs,664
|
19
19
|
locust_cloud/webui/dist/assets/index-DUDp7J7F.js,sha256=XMzzKiL5l1GIzdKXEZ8v6go18bJAGb4fRWJoUWymfnw,2807563
|
20
|
-
locust_cloud-1.5.
|
21
|
-
locust_cloud-1.5.
|
22
|
-
locust_cloud-1.5.
|
23
|
-
locust_cloud-1.5.
|
20
|
+
locust_cloud-1.5.6.dist-info/METADATA,sha256=GMbsoaFAvpRG65lzLxsGo98Zzp_D0Tdn7DOs6IikmaQ,1663
|
21
|
+
locust_cloud-1.5.6.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
|
22
|
+
locust_cloud-1.5.6.dist-info/entry_points.txt,sha256=PGyAb4e3aTsGS3N3VGShDl6VzJaXy7QwsEgsLOC7V00,57
|
23
|
+
locust_cloud-1.5.6.dist-info/RECORD,,
|
File without changes
|
File without changes
|