git: cadda769f93f - main - benchmarks/py-locust: Upgrade to 2.37.10

From: Jesús Daniel Colmenares Oviedo <dtxdf_at_FreeBSD.org>
Date: Fri, 13 Jun 2025 00:22:03 UTC
The branch main has been updated by dtxdf:

URL: https://cgit.FreeBSD.org/ports/commit/?id=cadda769f93f6743398bbeb88b8c3c2df0423535

commit cadda769f93f6743398bbeb88b8c3c2df0423535
Author:     Jesús Daniel Colmenares Oviedo <dtxdf@FreeBSD.org>
AuthorDate: 2025-06-12 22:51:22 +0000
Commit:     Jesús Daniel Colmenares Oviedo <dtxdf@FreeBSD.org>
CommitDate: 2025-06-13 00:21:40 +0000

    benchmarks/py-locust: Upgrade to 2.37.10
    
    * Pet portclippy/portfmt.
    * Add distribution file for examples and test scripts (the release
      file does not include them, but includes the web UI which the
      repository does not have).
    * Remove examples (they are now incompatible with the new version of
      Locust).
    * Include updated examples from the repository.
    * Prefer to generate pkg-plist manually instead of relying on autoplist
      (since the sample scripts are included manually, it is preferable to
      manually generate the pkg-plist).
    * Change distutils in favor of pep517.
    * Fix binding in all interfaces when --web-host is set to '*'.
    
    ChangeLog: https://github.com/locustio/locust/compare/0.11.0...2.37.10
    
    PR:             287027
    Approved by:    acm (mentor)
    Approved by:    maintainer timeout (20 days)
---
 benchmarks/py-locust/Makefile                      |  59 +++++--
 benchmarks/py-locust/distinfo                      |   8 +-
 benchmarks/py-locust/files/extra-EXAMPLES-basic.py |  26 ---
 .../extra-EXAMPLES-browse_docs_sequence_test.py    |  50 ------
 .../files/extra-EXAMPLES-browse_docs_test.py       |  49 ------
 .../files/extra-EXAMPLES-custom_wait_function.py   |  51 ------
 .../files/extra-EXAMPLES-custom_xmlrpc_client.py   |  54 ------
 .../extra-EXAMPLES-dynamice_user_credentials.py    |  25 ---
 .../py-locust/files/extra-EXAMPLES-events.py       |  69 --------
 .../files/extra-EXAMPLES-multiple_hosts.py         |  31 ----
 .../files/extra-EXAMPLES-semaphore_wait.py         |  25 ---
 benchmarks/py-locust/files/patch-locust_main.py    |  11 ++
 benchmarks/py-locust/pkg-plist                     | 182 ++++++++++++++++++++-
 13 files changed, 232 insertions(+), 408 deletions(-)

diff --git a/benchmarks/py-locust/Makefile b/benchmarks/py-locust/Makefile
index c5f1d2725067..02004755443e 100644
--- a/benchmarks/py-locust/Makefile
+++ b/benchmarks/py-locust/Makefile
@@ -1,10 +1,13 @@
 PORTNAME=	locust
-PORTVERSION=	0.11.0
-PORTREVISION=	2
+DISTVERSION=	2.37.10
 CATEGORIES=	benchmarks www python
-MASTER_SITES=	PYPI
+MASTER_SITES=	PYPI \
+		https://github.com/locustio/locust/archive/refs/tags/:EXTRA
 PKGNAMEPREFIX=	${PYTHON_PKGNAMEPREFIX}
-DISTNAME=	locustio-${PORTVERSION}
+DISTFILES=	${DISTNAME}${EXTRACT_SUFX} \
+		${PORTVERSION}${EXTRACT_SUFX}:EXTRA
+DIST_SUBDIR=	${PORTNAME}
+EXTRACT_ONLY=	${DISTNAME}${EXTRACT_SUFX}
 
 MAINTAINER=	egypcio@FreeBSD.org
 COMMENT=	Python utility for doing easy, distributed load testing
@@ -13,26 +16,50 @@ WWW=		https://locust.io/
 LICENSE=	MIT
 LICENSE_FILE=	${WRKSRC}/LICENSE
 
-RUN_DEPENDS=	${PYTHON_PKGNAMEPREFIX}flask>=0.10.1:www/py-flask@${PY_FLAVOR} \
-		${PYTHON_PKGNAMEPREFIX}gevent>=1.2.2:devel/py-gevent@${PY_FLAVOR} \
-		${PYTHON_PKGNAMEPREFIX}msgpack>=0.4.2:devel/py-msgpack@${PY_FLAVOR} \
-		${PYTHON_PKGNAMEPREFIX}pyzmq>=16.0.2:net/py-pyzmq@${PY_FLAVOR} \
-		${PYTHON_PKGNAMEPREFIX}requests>=2.9.1:www/py-requests@${PY_FLAVOR} \
-		${PYTHON_PKGNAMEPREFIX}six>=1.10.0:devel/py-six@${PY_FLAVOR}
-TEST_DEPENDS=	${PYTHON_PKGNAMEPREFIX}mock>=0:devel/py-mock@${PY_FLAVOR}
+BUILD_DEPENDS=	${PY_SETUPTOOLS} \
+		${PYTHON_PKGNAMEPREFIX}hatch-vcs>=0:devel/py-hatch-vcs@${PY_FLAVOR} \
+		${PYTHON_PKGNAMEPREFIX}hatchling>=0:devel/py-hatchling@${PY_FLAVOR}
+RUN_DEPENDS=	${PY_SETUPTOOLS} \
+		${PYTHON_PKGNAMEPREFIX}configargparse>=0:devel/py-configargparse@${PY_FLAVOR} \
+		${PYTHON_PKGNAMEPREFIX}Flask-Cors>=0:www/py-flask-cors@${PY_FLAVOR} \
+		${PYTHON_PKGNAMEPREFIX}Flask-Login>=0:www/py-flask-login@${PY_FLAVOR} \
+		${PYTHON_PKGNAMEPREFIX}flask>=0:www/py-flask@${PY_FLAVOR} \
+		${PYTHON_PKGNAMEPREFIX}gevent>=0:devel/py-gevent@${PY_FLAVOR} \
+		${PYTHON_PKGNAMEPREFIX}geventhttpclient>=0:devel/py-geventhttpclient@${PY_FLAVOR} \
+		${PYTHON_PKGNAMEPREFIX}locust-cloud>=0:benchmarks/py-locust-cloud@${PY_FLAVOR} \
+		${PYTHON_PKGNAMEPREFIX}msgpack>=0:devel/py-msgpack@${PY_FLAVOR} \
+		${PYTHON_PKGNAMEPREFIX}psutil>=0:sysutils/py-psutil@${PY_FLAVOR} \
+		${PYTHON_PKGNAMEPREFIX}pyzmq>=0:net/py-pyzmq@${PY_FLAVOR} \
+		${PYTHON_PKGNAMEPREFIX}requests>=0:www/py-requests@${PY_FLAVOR} \
+		${PYTHON_PKGNAMEPREFIX}tomli>=0:textproc/py-tomli@${PY_FLAVOR} \
+		${PYTHON_PKGNAMEPREFIX}typing-extensions>=0:devel/py-typing-extensions@${PY_FLAVOR} \
+		${PYTHON_PKGNAMEPREFIX}werkzeug>=0:www/py-werkzeug@${PY_FLAVOR}
+TEST_DEPENDS=	${PYTHON_PKGNAMEPREFIX}mock>=0:devel/py-mock@${PY_FLAVOR} \
+		${PYTHON_PKGNAMEPREFIX}pyquery>=0:devel/py-pyquery@${PY_FLAVOR} \
+		${PYTHON_PKGNAMEPREFIX}pytest>0:devel/py-pytest@${PY_FLAVOR} \
+		${PYTHON_PKGNAMEPREFIX}retry2>=0:devel/py-retry2@${PY_FLAVOR}
 
 USES=		python
-USE_PYTHON=	autoplist concurrent distutils
+USE_PYTHON=	cryptography_test pep517
 
 NO_ARCH=	yes
 
 OPTIONS_DEFINE=	EXAMPLES
 
-do-test:
-	@cd ${WRKSRC} && ${PYTHON_CMD} ${PYDISTUTILS_SETUP} test
+EXTRA_DIRNAME=	${PORTNAME}-${PORTVERSION}-extra
+
+post-extract:
+	@${MKDIR} ${WRKDIR}/${EXTRA_DIRNAME}
+	@${TAR} --strip-components=1 -xf ${_DISTDIR}/${PORTVERSION}${EXTRACT_SUFX} -C ${WRKDIR}/${EXTRA_DIRNAME}
 
 post-install-EXAMPLES-on:
-	${MKDIR} ${STAGEDIR}${EXAMPLESDIR}
-	${INSTALL_DATA} ${FILESDIR}/extra-EXAMPLES* ${STAGEDIR}${EXAMPLESDIR}
+	@${MKDIR} ${STAGEDIR}${EXAMPLESDIR}
+	${INSTALL_DATA} ${WRKDIR}/${EXTRA_DIRNAME}/examples/*.py ${STAGEDIR}${EXAMPLESDIR}
+
+do-test:
+	@cd ${WRKDIR}/${EXTRA_DIRNAME}/locust/test && ${COPYTREE_SHARE} \
+		. ${WRKSRC}/locust/test
+	@cd ${WRKSRC}/locust/test && ${SETENVI} ${WRK_ENV} ${TEST_ENV} ${PYTHON_CMD} -m pytest \
+		-v -rs -o addopts=
 
 .include <bsd.port.mk>
diff --git a/benchmarks/py-locust/distinfo b/benchmarks/py-locust/distinfo
index 2d5d7be1a1e3..547e80b05734 100644
--- a/benchmarks/py-locust/distinfo
+++ b/benchmarks/py-locust/distinfo
@@ -1,3 +1,5 @@
-TIMESTAMP = 1555083697
-SHA256 (locustio-0.11.0.tar.gz) = 93404f831114791b0756325c53b08bff73f048eeb69688be657629feaa62b507
-SIZE (locustio-0.11.0.tar.gz) = 229879
+TIMESTAMP = 1749763850
+SHA256 (locust/locust-2.37.10.tar.gz) = 4ae1df1f5155cc7df5aa3eee333c1fa87c1a3ac4815a47f7e62d8cdeadc1fd45
+SIZE (locust/locust-2.37.10.tar.gz) = 2252603
+SHA256 (locust/2.37.10.tar.gz) = 683d072ad675ba9a03cbd70146d1433e40a1eabee4d1d8af45fdf05fe31a42f2
+SIZE (locust/2.37.10.tar.gz) = 5706321
diff --git a/benchmarks/py-locust/files/extra-EXAMPLES-basic.py b/benchmarks/py-locust/files/extra-EXAMPLES-basic.py
deleted file mode 100644
index c34610e8df8e..000000000000
--- a/benchmarks/py-locust/files/extra-EXAMPLES-basic.py
+++ /dev/null
@@ -1,26 +0,0 @@
-from locust import HttpLocust, TaskSet, task
-
-
-def index(l):
-    l.client.get("/")
-
-def stats(l):
-    l.client.get("/stats/requests")
-
-class UserTasks(TaskSet):
-    # one can specify tasks like this
-    tasks = [index, stats]
-    
-    # but it might be convenient to use the @task decorator
-    @task
-    def page404(self):
-        self.client.get("/does_not_exist")
-    
-class WebsiteUser(HttpLocust):
-    """
-    Locust user class that does requests to the locust web server running on localhost
-    """
-    host = "http://127.0.0.1:8089"
-    min_wait = 2000
-    max_wait = 5000
-    task_set = UserTasks
diff --git a/benchmarks/py-locust/files/extra-EXAMPLES-browse_docs_sequence_test.py b/benchmarks/py-locust/files/extra-EXAMPLES-browse_docs_sequence_test.py
deleted file mode 100644
index b780c67e9879..000000000000
--- a/benchmarks/py-locust/files/extra-EXAMPLES-browse_docs_sequence_test.py
+++ /dev/null
@@ -1,50 +0,0 @@
-# This locust test script example will simulate a user
-# browsing the Locust documentation on https://docs.locust.io/
-
-import random
-from locust import HttpLocust, TaskSquence, seq_task, task
-from pyquery import PyQuery
-
-
-class BrowseDocumentationSequence(TaskSquence):
-    def on_start(self):
-        self.urls_on_current_page = self.toc_urls
-
-    # assume all users arrive at the index page
-    @seq_task(1)
-    def index_page(self):
-        r = self.client.get("/")
-        pq = PyQuery(r.content)
-        link_elements = pq(".toctree-wrapper a.internal")
-        self.toc_urls = [
-            l.attrib["href"] for l in link_elements
-        ]
-
-    @seq_task(2)
-    @task(50)
-    def load_page(self, url=None):
-        url = random.choice(self.toc_urls)
-        r = self.client.get(url)
-        pq = PyQuery(r.content)
-        link_elements = pq("a.internal")
-        self.urls_on_current_page = [
-            l.attrib["href"] for l in link_elements
-        ]
-
-    @seq_task(3)
-    @task(30)
-    def load_sub_page(self):
-        url = random.choice(self.urls_on_current_page)
-        r = self.client.get(url)
-
-
-class AwesomeUser(HttpLocust):
-    task_set = BrowseDocumentationSequence
-    host = "https://docs.locust.io/en/latest/"
-
-    # we assume someone who is browsing the Locust docs,
-    # generally has a quite long waiting time (between
-    # 20 and 600 seconds), since there's a bunch of text
-    # on each page
-    min_wait = 20 * 1000
-    max_wait = 600 * 1000
diff --git a/benchmarks/py-locust/files/extra-EXAMPLES-browse_docs_test.py b/benchmarks/py-locust/files/extra-EXAMPLES-browse_docs_test.py
deleted file mode 100644
index b8a42a7c2bd1..000000000000
--- a/benchmarks/py-locust/files/extra-EXAMPLES-browse_docs_test.py
+++ /dev/null
@@ -1,49 +0,0 @@
-# This locust test script example will simulate a user 
-# browsing the Locust documentation on https://docs.locust.io/
-
-import random
-from locust import HttpLocust, TaskSet, task
-from pyquery import PyQuery
-
-
-class BrowseDocumentation(TaskSet):
-    def on_start(self):
-        # assume all users arrive at the index page
-        self.index_page()
-        self.urls_on_current_page = self.toc_urls
-    
-    @task(10)
-    def index_page(self):
-        r = self.client.get("/")
-        pq = PyQuery(r.content)
-        link_elements = pq(".toctree-wrapper a.internal")
-        self.toc_urls = [
-            l.attrib["href"] for l in link_elements
-        ]
-    
-    @task(50)
-    def load_page(self, url=None):
-        url = random.choice(self.toc_urls)
-        r = self.client.get(url)
-        pq = PyQuery(r.content)
-        link_elements = pq("a.internal")
-        self.urls_on_current_page = [
-            l.attrib["href"] for l in link_elements
-        ]
-    
-    @task(30)
-    def load_sub_page(self):
-        url = random.choice(self.urls_on_current_page)
-        r = self.client.get(url)
-
-
-class AwesomeUser(HttpLocust):
-    task_set = BrowseDocumentation
-    host = "https://docs.locust.io/en/latest/"
-    
-    # we assume someone who is browsing the Locust docs, 
-    # generally has a quite long waiting time (between 
-    # 20 and 600 seconds), since there's a bunch of text 
-    # on each page
-    min_wait = 20  * 1000
-    max_wait = 600 * 1000
diff --git a/benchmarks/py-locust/files/extra-EXAMPLES-custom_wait_function.py b/benchmarks/py-locust/files/extra-EXAMPLES-custom_wait_function.py
deleted file mode 100644
index fad47c3174a6..000000000000
--- a/benchmarks/py-locust/files/extra-EXAMPLES-custom_wait_function.py
+++ /dev/null
@@ -1,51 +0,0 @@
-from locust import HttpLocust, TaskSet, task
-import random
-
-def index(l):
-    l.client.get("/")
-
-def stats(l):
-    l.client.get("/stats/requests")
-
-class UserTasks(TaskSet):
-    # one can specify tasks like this
-    tasks = [index, stats]
-    
-    # but it might be convenient to use the @task decorator
-    @task
-    def page404(self):
-        self.client.get("/does_not_exist")
-    
-class WebsiteUser(HttpLocust):
-    """
-    Locust user class that does requests to the locust web server running on localhost
-    """
-    host = "http://127.0.0.1:8089"
-    # Most task inter-arrival times approximate to exponential distributions
-    # We will model this wait time as exponentially distributed with a mean of 1 second
-    wait_function = lambda self: random.expovariate(1)*1000 # *1000 to convert to milliseconds
-    task_set = UserTasks
-
-def strictExp(min_wait,max_wait,mu=1):
-    """
-    Returns an exponentially distributed time strictly between two bounds.
-    """
-    while True:
-        x = random.expovariate(mu)
-        increment = (max_wait-min_wait)/(mu*6.0)
-        result = min_wait + (x*increment)
-        if result<max_wait:
-            break
-    return result
-
-class StrictWebsiteUser(HttpLocust):
-    """
-    Locust user class that makes exponential requests but strictly between two bounds.
-    """
-    host = "http://127.0.0.1:8089"
-    wait_function = lambda self: strictExp(self.min_wait, self.max_wait)*1000
-    task_set = UserTasks
-
-
-
-
diff --git a/benchmarks/py-locust/files/extra-EXAMPLES-custom_xmlrpc_client.py b/benchmarks/py-locust/files/extra-EXAMPLES-custom_xmlrpc_client.py
deleted file mode 100644
index f117acd7790a..000000000000
--- a/benchmarks/py-locust/files/extra-EXAMPLES-custom_xmlrpc_client.py
+++ /dev/null
@@ -1,54 +0,0 @@
-import time
-import xmlrpclib
-
-from locust import Locust, TaskSet, events, task
-
-
-class XmlRpcClient(xmlrpclib.ServerProxy):
-    """
-    Simple, sample XML RPC client implementation that wraps xmlrpclib.ServerProxy and 
-    fires locust events on request_success and request_failure, so that all requests 
-    gets tracked in locust's statistics.
-    """
-    def __getattr__(self, name):
-        func = xmlrpclib.ServerProxy.__getattr__(self, name)
-        def wrapper(*args, **kwargs):
-            start_time = time.time()
-            try:
-                result = func(*args, **kwargs)
-            except xmlrpclib.Fault as e:
-                total_time = int((time.time() - start_time) * 1000)
-                events.request_failure.fire(request_type="xmlrpc", name=name, response_time=total_time, exception=e)
-            else:
-                total_time = int((time.time() - start_time) * 1000)
-                events.request_success.fire(request_type="xmlrpc", name=name, response_time=total_time, response_length=0)
-                # In this example, I've hardcoded response_length=0. If we would want the response length to be 
-                # reported correctly in the statistics, we would probably need to hook in at a lower level
-        
-        return wrapper
-
-
-class XmlRpcLocust(Locust):
-    """
-    This is the abstract Locust class which should be subclassed. It provides an XML-RPC client
-    that can be used to make XML-RPC requests that will be tracked in Locust's statistics.
-    """
-    def __init__(self, *args, **kwargs):
-        super(XmlRpcLocust, self).__init__(*args, **kwargs)
-        self.client = XmlRpcClient(self.host)
-
-
-class ApiUser(XmlRpcLocust):
-    
-    host = "http://127.0.0.1:8877/"
-    min_wait = 100
-    max_wait = 1000
-    
-    class task_set(TaskSet):
-        @task(10)
-        def get_time(self):
-            self.client.get_time()
-        
-        @task(5)
-        def get_random_number(self):
-            self.client.get_random_number(0, 100)
diff --git a/benchmarks/py-locust/files/extra-EXAMPLES-dynamice_user_credentials.py b/benchmarks/py-locust/files/extra-EXAMPLES-dynamice_user_credentials.py
deleted file mode 100644
index 6f8f66baa5ca..000000000000
--- a/benchmarks/py-locust/files/extra-EXAMPLES-dynamice_user_credentials.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# locustfile.py
-
-from locust import HttpLocust, TaskSet, task
-
-USER_CREDENTIALS = [
-    ("user1", "password"),
-    ("user2", "password"),
-    ("user3", "password"),
-]
-
-class UserBehaviour(TaskSet):
-    def on_start(self):
-        if len(USER_CREDENTIALS) > 0:
-            user, passw = USER_CREDENTIALS.pop()
-            self.client.post("/login", {"username":user, "password":passw})
-    
-    @task
-    def some_task(self):
-        # user should be logged in here (unless the USER_CREDENTIALS ran out)
-        self.client.get("/protected/resource")
-
-class User(HttpLocust):
-    task_set = UserBehaviour
-    min_wait = 5000
-    max_wait = 60000
diff --git a/benchmarks/py-locust/files/extra-EXAMPLES-events.py b/benchmarks/py-locust/files/extra-EXAMPLES-events.py
deleted file mode 100644
index 7b1de7fafee2..000000000000
--- a/benchmarks/py-locust/files/extra-EXAMPLES-events.py
+++ /dev/null
@@ -1,69 +0,0 @@
-# -*- coding: utf-8 -*-
-
-"""
-This is an example of a locustfile that uses Locust's built in event hooks to 
-track the sum of the content-length header in all successful HTTP responses
-"""
-
-from locust import HttpLocust, TaskSet, events, task, web
-
-
-class MyTaskSet(TaskSet):
-    @task(2)
-    def index(l):
-        l.client.get("/")
-    
-    @task(1)
-    def stats(l):
-        l.client.get("/stats/requests")
-
-class WebsiteUser(HttpLocust):
-    host = "http://127.0.0.1:8089"
-    min_wait = 2000
-    max_wait = 5000
-    task_set = MyTaskSet
-    
-
-"""
-We need somewhere to store the stats.
-
-On the master node stats will contain the aggregated sum of all content-lengths,
-while on the slave nodes this will be the sum of the content-lengths since the 
-last stats report was sent to the master
-"""
-stats = {"content-length":0}
-
-def on_request_success(request_type, name, response_time, response_length):
-    """
-    Event handler that get triggered on every successful request
-    """
-    stats["content-length"] += response_length
-
-def on_report_to_master(client_id, data):
-    """
-    This event is triggered on the slave instances every time a stats report is
-    to be sent to the locust master. It will allow us to add our extra content-length
-    data to the dict that is being sent, and then we clear the local stats in the slave.
-    """
-    data["content-length"] = stats["content-length"]
-    stats["content-length"] = 0
-
-def on_slave_report(client_id, data):
-    """
-    This event is triggered on the master instance when a new stats report arrives
-    from a slave. Here we just add the content-length to the master's aggregated
-    stats dict.
-    """
-    stats["content-length"] += data["content-length"]
-
-# Hook up the event listeners
-events.request_success += on_request_success
-events.report_to_master += on_report_to_master
-events.slave_report += on_slave_report
-
-@web.app.route("/content-length")
-def total_content_length():
-    """
-    Add a route to the Locust web app, where we can see the total content-length
-    """
-    return "Total content-length recieved: %i" % stats["content-length"]
diff --git a/benchmarks/py-locust/files/extra-EXAMPLES-multiple_hosts.py b/benchmarks/py-locust/files/extra-EXAMPLES-multiple_hosts.py
deleted file mode 100644
index b30585b37ce1..000000000000
--- a/benchmarks/py-locust/files/extra-EXAMPLES-multiple_hosts.py
+++ /dev/null
@@ -1,31 +0,0 @@
-import os
-
-from locust import HttpLocust, TaskSet, task
-from locust.clients import HttpSession
-
-class MultipleHostsLocust(HttpLocust):
-    abstract = True
-    
-    def __init__(self, *args, **kwargs):
-        super(MultipleHostsLocust, self).__init__(*args, **kwargs)
-        self.api_client = HttpSession(base_url=os.environ["API_HOST"])
-    
-
-class UserTasks(TaskSet):
-    # but it might be convenient to use the @task decorator
-    @task
-    def index(self):
-        self.locust.client.get("/")
-    
-    @task
-    def index_other_host(self):
-        self.locust.api_client.get("/stats/requests")
-    
-class WebsiteUser(MultipleHostsLocust):
-    """
-    Locust user class that does requests to the locust web server running on localhost
-    """
-    host = "http://127.0.0.1:8089"
-    min_wait = 2000
-    max_wait = 5000
-    task_set = UserTasks
diff --git a/benchmarks/py-locust/files/extra-EXAMPLES-semaphore_wait.py b/benchmarks/py-locust/files/extra-EXAMPLES-semaphore_wait.py
deleted file mode 100644
index 563b89c95d63..000000000000
--- a/benchmarks/py-locust/files/extra-EXAMPLES-semaphore_wait.py
+++ /dev/null
@@ -1,25 +0,0 @@
-from locust import HttpLocust, TaskSet, task, events
-
-from gevent.coros import Semaphore
-all_locusts_spawned = Semaphore()
-all_locusts_spawned.acquire()
-
-def on_hatch_complete(**kw):
-    all_locusts_spawned.release()
-
-events.hatch_complete += on_hatch_complete
-
-class UserTasks(TaskSet):
-    def on_start(self):
-        all_locusts_spawned.wait()
-        self.wait()
-    
-    @task
-    def index(self):
-        self.client.get("/")
-    
-class WebsiteUser(HttpLocust):
-    host = "http://127.0.0.1:8089"
-    min_wait = 2000
-    max_wait = 5000
-    task_set = UserTasks
diff --git a/benchmarks/py-locust/files/patch-locust_main.py b/benchmarks/py-locust/files/patch-locust_main.py
new file mode 100644
index 000000000000..fc7fc9d27587
--- /dev/null
+++ b/benchmarks/py-locust/files/patch-locust_main.py
@@ -0,0 +1,11 @@
+--- locust/main.py.orig	2025-06-12 22:34:12 UTC
++++ locust/main.py
+@@ -454,7 +454,7 @@ See https://github.com/locustio/locust/wiki/Installati
+             sys.exit(1)
+         if options.web_host == "*":
+             # special check for "*" so that we're consistent with --master-bind-host
+-            web_host = ""
++            web_host = "0.0.0.0"
+         else:
+             web_host = options.web_host
+         if web_host:
diff --git a/benchmarks/py-locust/pkg-plist b/benchmarks/py-locust/pkg-plist
index a8d3b5f7e5f4..02aabb6816ab 100644
--- a/benchmarks/py-locust/pkg-plist
+++ b/benchmarks/py-locust/pkg-plist
@@ -1,9 +1,173 @@
-%%PORTEXAMPLES%%%%EXAMPLESDIR%%/extra-EXAMPLES-basic.py
-%%PORTEXAMPLES%%%%EXAMPLESDIR%%/extra-EXAMPLES-browse_docs_sequence_test.py
-%%PORTEXAMPLES%%%%EXAMPLESDIR%%/extra-EXAMPLES-browse_docs_test.py
-%%PORTEXAMPLES%%%%EXAMPLESDIR%%/extra-EXAMPLES-custom_wait_function.py
-%%PORTEXAMPLES%%%%EXAMPLESDIR%%/extra-EXAMPLES-custom_xmlrpc_client.py
-%%PORTEXAMPLES%%%%EXAMPLESDIR%%/extra-EXAMPLES-dynamice_user_credentials.py
-%%PORTEXAMPLES%%%%EXAMPLESDIR%%/extra-EXAMPLES-events.py
-%%PORTEXAMPLES%%%%EXAMPLESDIR%%/extra-EXAMPLES-multiple_hosts.py
-%%PORTEXAMPLES%%%%EXAMPLESDIR%%/extra-EXAMPLES-semaphore_wait.py
+bin/locust
+%%PYTHON_SITELIBDIR%%/locust-2.37.10.dist-info/METADATA
+%%PYTHON_SITELIBDIR%%/locust-2.37.10.dist-info/RECORD
+%%PYTHON_SITELIBDIR%%/locust-2.37.10.dist-info/WHEEL
+%%PYTHON_SITELIBDIR%%/locust-2.37.10.dist-info/entry_points.txt
+%%PYTHON_SITELIBDIR%%/locust-2.37.10.dist-info/licenses/LICENSE
+%%PYTHON_SITELIBDIR%%/locust/__init__.py
+%%PYTHON_SITELIBDIR%%/locust/__main__.py
+%%PYTHON_SITELIBDIR%%/locust/__pycache__/__init__%%PYTHON_EXT_SUFFIX%%.opt-1.pyc
+%%PYTHON_SITELIBDIR%%/locust/__pycache__/__init__%%PYTHON_EXT_SUFFIX%%.pyc
+%%PYTHON_SITELIBDIR%%/locust/__pycache__/__main__%%PYTHON_EXT_SUFFIX%%.opt-1.pyc
+%%PYTHON_SITELIBDIR%%/locust/__pycache__/__main__%%PYTHON_EXT_SUFFIX%%.pyc
+%%PYTHON_SITELIBDIR%%/locust/__pycache__/_version%%PYTHON_EXT_SUFFIX%%.opt-1.pyc
+%%PYTHON_SITELIBDIR%%/locust/__pycache__/_version%%PYTHON_EXT_SUFFIX%%.pyc
+%%PYTHON_SITELIBDIR%%/locust/__pycache__/argument_parser%%PYTHON_EXT_SUFFIX%%.opt-1.pyc
+%%PYTHON_SITELIBDIR%%/locust/__pycache__/argument_parser%%PYTHON_EXT_SUFFIX%%.pyc
+%%PYTHON_SITELIBDIR%%/locust/__pycache__/clients%%PYTHON_EXT_SUFFIX%%.opt-1.pyc
+%%PYTHON_SITELIBDIR%%/locust/__pycache__/clients%%PYTHON_EXT_SUFFIX%%.pyc
+%%PYTHON_SITELIBDIR%%/locust/__pycache__/debug%%PYTHON_EXT_SUFFIX%%.opt-1.pyc
+%%PYTHON_SITELIBDIR%%/locust/__pycache__/debug%%PYTHON_EXT_SUFFIX%%.pyc
+%%PYTHON_SITELIBDIR%%/locust/__pycache__/dispatch%%PYTHON_EXT_SUFFIX%%.opt-1.pyc
+%%PYTHON_SITELIBDIR%%/locust/__pycache__/dispatch%%PYTHON_EXT_SUFFIX%%.pyc
+%%PYTHON_SITELIBDIR%%/locust/__pycache__/env%%PYTHON_EXT_SUFFIX%%.opt-1.pyc
+%%PYTHON_SITELIBDIR%%/locust/__pycache__/env%%PYTHON_EXT_SUFFIX%%.pyc
+%%PYTHON_SITELIBDIR%%/locust/__pycache__/event%%PYTHON_EXT_SUFFIX%%.opt-1.pyc
+%%PYTHON_SITELIBDIR%%/locust/__pycache__/event%%PYTHON_EXT_SUFFIX%%.pyc
+%%PYTHON_SITELIBDIR%%/locust/__pycache__/exception%%PYTHON_EXT_SUFFIX%%.opt-1.pyc
+%%PYTHON_SITELIBDIR%%/locust/__pycache__/exception%%PYTHON_EXT_SUFFIX%%.pyc
+%%PYTHON_SITELIBDIR%%/locust/__pycache__/html%%PYTHON_EXT_SUFFIX%%.opt-1.pyc
+%%PYTHON_SITELIBDIR%%/locust/__pycache__/html%%PYTHON_EXT_SUFFIX%%.pyc
+%%PYTHON_SITELIBDIR%%/locust/__pycache__/input_events%%PYTHON_EXT_SUFFIX%%.opt-1.pyc
+%%PYTHON_SITELIBDIR%%/locust/__pycache__/input_events%%PYTHON_EXT_SUFFIX%%.pyc
+%%PYTHON_SITELIBDIR%%/locust/__pycache__/log%%PYTHON_EXT_SUFFIX%%.opt-1.pyc
+%%PYTHON_SITELIBDIR%%/locust/__pycache__/log%%PYTHON_EXT_SUFFIX%%.pyc
+%%PYTHON_SITELIBDIR%%/locust/__pycache__/main%%PYTHON_EXT_SUFFIX%%.opt-1.pyc
+%%PYTHON_SITELIBDIR%%/locust/__pycache__/main%%PYTHON_EXT_SUFFIX%%.pyc
+%%PYTHON_SITELIBDIR%%/locust/__pycache__/runners%%PYTHON_EXT_SUFFIX%%.opt-1.pyc
+%%PYTHON_SITELIBDIR%%/locust/__pycache__/runners%%PYTHON_EXT_SUFFIX%%.pyc
+%%PYTHON_SITELIBDIR%%/locust/__pycache__/shape%%PYTHON_EXT_SUFFIX%%.opt-1.pyc
+%%PYTHON_SITELIBDIR%%/locust/__pycache__/shape%%PYTHON_EXT_SUFFIX%%.pyc
+%%PYTHON_SITELIBDIR%%/locust/__pycache__/stats%%PYTHON_EXT_SUFFIX%%.opt-1.pyc
+%%PYTHON_SITELIBDIR%%/locust/__pycache__/stats%%PYTHON_EXT_SUFFIX%%.pyc
+%%PYTHON_SITELIBDIR%%/locust/__pycache__/web%%PYTHON_EXT_SUFFIX%%.opt-1.pyc
+%%PYTHON_SITELIBDIR%%/locust/__pycache__/web%%PYTHON_EXT_SUFFIX%%.pyc
+%%PYTHON_SITELIBDIR%%/locust/_version.py
+%%PYTHON_SITELIBDIR%%/locust/argument_parser.py
+%%PYTHON_SITELIBDIR%%/locust/clients.py
+%%PYTHON_SITELIBDIR%%/locust/contrib/__init__.py
+%%PYTHON_SITELIBDIR%%/locust/contrib/__pycache__/__init__%%PYTHON_EXT_SUFFIX%%.opt-1.pyc
+%%PYTHON_SITELIBDIR%%/locust/contrib/__pycache__/__init__%%PYTHON_EXT_SUFFIX%%.pyc
+%%PYTHON_SITELIBDIR%%/locust/contrib/__pycache__/fasthttp%%PYTHON_EXT_SUFFIX%%.opt-1.pyc
+%%PYTHON_SITELIBDIR%%/locust/contrib/__pycache__/fasthttp%%PYTHON_EXT_SUFFIX%%.pyc
+%%PYTHON_SITELIBDIR%%/locust/contrib/__pycache__/mongodb%%PYTHON_EXT_SUFFIX%%.opt-1.pyc
+%%PYTHON_SITELIBDIR%%/locust/contrib/__pycache__/mongodb%%PYTHON_EXT_SUFFIX%%.pyc
+%%PYTHON_SITELIBDIR%%/locust/contrib/__pycache__/oai%%PYTHON_EXT_SUFFIX%%.opt-1.pyc
+%%PYTHON_SITELIBDIR%%/locust/contrib/__pycache__/oai%%PYTHON_EXT_SUFFIX%%.pyc
+%%PYTHON_SITELIBDIR%%/locust/contrib/__pycache__/postgres%%PYTHON_EXT_SUFFIX%%.opt-1.pyc
+%%PYTHON_SITELIBDIR%%/locust/contrib/__pycache__/postgres%%PYTHON_EXT_SUFFIX%%.pyc
+%%PYTHON_SITELIBDIR%%/locust/contrib/fasthttp.py
+%%PYTHON_SITELIBDIR%%/locust/contrib/mongodb.py
+%%PYTHON_SITELIBDIR%%/locust/contrib/oai.py
+%%PYTHON_SITELIBDIR%%/locust/contrib/postgres.py
+%%PYTHON_SITELIBDIR%%/locust/debug.py
+%%PYTHON_SITELIBDIR%%/locust/dispatch.py
+%%PYTHON_SITELIBDIR%%/locust/env.py
+%%PYTHON_SITELIBDIR%%/locust/event.py
+%%PYTHON_SITELIBDIR%%/locust/exception.py
+%%PYTHON_SITELIBDIR%%/locust/html.py
+%%PYTHON_SITELIBDIR%%/locust/input_events.py
+%%PYTHON_SITELIBDIR%%/locust/log.py
+%%PYTHON_SITELIBDIR%%/locust/main.py
+%%PYTHON_SITELIBDIR%%/locust/py.typed
+%%PYTHON_SITELIBDIR%%/locust/rpc/__init__.py
+%%PYTHON_SITELIBDIR%%/locust/rpc/__pycache__/__init__%%PYTHON_EXT_SUFFIX%%.opt-1.pyc
+%%PYTHON_SITELIBDIR%%/locust/rpc/__pycache__/__init__%%PYTHON_EXT_SUFFIX%%.pyc
+%%PYTHON_SITELIBDIR%%/locust/rpc/__pycache__/protocol%%PYTHON_EXT_SUFFIX%%.opt-1.pyc
+%%PYTHON_SITELIBDIR%%/locust/rpc/__pycache__/protocol%%PYTHON_EXT_SUFFIX%%.pyc
+%%PYTHON_SITELIBDIR%%/locust/rpc/__pycache__/zmqrpc%%PYTHON_EXT_SUFFIX%%.opt-1.pyc
+%%PYTHON_SITELIBDIR%%/locust/rpc/__pycache__/zmqrpc%%PYTHON_EXT_SUFFIX%%.pyc
+%%PYTHON_SITELIBDIR%%/locust/rpc/protocol.py
+%%PYTHON_SITELIBDIR%%/locust/rpc/zmqrpc.py
+%%PYTHON_SITELIBDIR%%/locust/runners.py
+%%PYTHON_SITELIBDIR%%/locust/shape.py
+%%PYTHON_SITELIBDIR%%/locust/stats.py
+%%PYTHON_SITELIBDIR%%/locust/user/__init__.py
+%%PYTHON_SITELIBDIR%%/locust/user/__pycache__/__init__%%PYTHON_EXT_SUFFIX%%.opt-1.pyc
+%%PYTHON_SITELIBDIR%%/locust/user/__pycache__/__init__%%PYTHON_EXT_SUFFIX%%.pyc
+%%PYTHON_SITELIBDIR%%/locust/user/__pycache__/inspectuser%%PYTHON_EXT_SUFFIX%%.opt-1.pyc
+%%PYTHON_SITELIBDIR%%/locust/user/__pycache__/inspectuser%%PYTHON_EXT_SUFFIX%%.pyc
+%%PYTHON_SITELIBDIR%%/locust/user/__pycache__/sequential_taskset%%PYTHON_EXT_SUFFIX%%.opt-1.pyc
+%%PYTHON_SITELIBDIR%%/locust/user/__pycache__/sequential_taskset%%PYTHON_EXT_SUFFIX%%.pyc
+%%PYTHON_SITELIBDIR%%/locust/user/__pycache__/task%%PYTHON_EXT_SUFFIX%%.opt-1.pyc
+%%PYTHON_SITELIBDIR%%/locust/user/__pycache__/task%%PYTHON_EXT_SUFFIX%%.pyc
+%%PYTHON_SITELIBDIR%%/locust/user/__pycache__/users%%PYTHON_EXT_SUFFIX%%.opt-1.pyc
+%%PYTHON_SITELIBDIR%%/locust/user/__pycache__/users%%PYTHON_EXT_SUFFIX%%.pyc
+%%PYTHON_SITELIBDIR%%/locust/user/__pycache__/wait_time%%PYTHON_EXT_SUFFIX%%.opt-1.pyc
+%%PYTHON_SITELIBDIR%%/locust/user/__pycache__/wait_time%%PYTHON_EXT_SUFFIX%%.pyc
+%%PYTHON_SITELIBDIR%%/locust/user/inspectuser.py
+%%PYTHON_SITELIBDIR%%/locust/user/sequential_taskset.py
+%%PYTHON_SITELIBDIR%%/locust/user/task.py
+%%PYTHON_SITELIBDIR%%/locust/user/users.py
+%%PYTHON_SITELIBDIR%%/locust/user/wait_time.py
+%%PYTHON_SITELIBDIR%%/locust/util/__init__.py
+%%PYTHON_SITELIBDIR%%/locust/util/__pycache__/__init__%%PYTHON_EXT_SUFFIX%%.opt-1.pyc
+%%PYTHON_SITELIBDIR%%/locust/util/__pycache__/__init__%%PYTHON_EXT_SUFFIX%%.pyc
+%%PYTHON_SITELIBDIR%%/locust/util/__pycache__/cache%%PYTHON_EXT_SUFFIX%%.opt-1.pyc
+%%PYTHON_SITELIBDIR%%/locust/util/__pycache__/cache%%PYTHON_EXT_SUFFIX%%.pyc
+%%PYTHON_SITELIBDIR%%/locust/util/__pycache__/date%%PYTHON_EXT_SUFFIX%%.opt-1.pyc
+%%PYTHON_SITELIBDIR%%/locust/util/__pycache__/date%%PYTHON_EXT_SUFFIX%%.pyc
+%%PYTHON_SITELIBDIR%%/locust/util/__pycache__/deprecation%%PYTHON_EXT_SUFFIX%%.opt-1.pyc
+%%PYTHON_SITELIBDIR%%/locust/util/__pycache__/deprecation%%PYTHON_EXT_SUFFIX%%.pyc
+%%PYTHON_SITELIBDIR%%/locust/util/__pycache__/directory%%PYTHON_EXT_SUFFIX%%.opt-1.pyc
+%%PYTHON_SITELIBDIR%%/locust/util/__pycache__/directory%%PYTHON_EXT_SUFFIX%%.pyc
+%%PYTHON_SITELIBDIR%%/locust/util/__pycache__/exception_handler%%PYTHON_EXT_SUFFIX%%.opt-1.pyc
+%%PYTHON_SITELIBDIR%%/locust/util/__pycache__/exception_handler%%PYTHON_EXT_SUFFIX%%.pyc
+%%PYTHON_SITELIBDIR%%/locust/util/__pycache__/load_locustfile%%PYTHON_EXT_SUFFIX%%.opt-1.pyc
+%%PYTHON_SITELIBDIR%%/locust/util/__pycache__/load_locustfile%%PYTHON_EXT_SUFFIX%%.pyc
+%%PYTHON_SITELIBDIR%%/locust/util/__pycache__/rounding%%PYTHON_EXT_SUFFIX%%.opt-1.pyc
+%%PYTHON_SITELIBDIR%%/locust/util/__pycache__/rounding%%PYTHON_EXT_SUFFIX%%.pyc
+%%PYTHON_SITELIBDIR%%/locust/util/__pycache__/timespan%%PYTHON_EXT_SUFFIX%%.opt-1.pyc
+%%PYTHON_SITELIBDIR%%/locust/util/__pycache__/timespan%%PYTHON_EXT_SUFFIX%%.pyc
+%%PYTHON_SITELIBDIR%%/locust/util/__pycache__/url%%PYTHON_EXT_SUFFIX%%.opt-1.pyc
+%%PYTHON_SITELIBDIR%%/locust/util/__pycache__/url%%PYTHON_EXT_SUFFIX%%.pyc
+%%PYTHON_SITELIBDIR%%/locust/util/cache.py
+%%PYTHON_SITELIBDIR%%/locust/util/date.py
+%%PYTHON_SITELIBDIR%%/locust/util/deprecation.py
+%%PYTHON_SITELIBDIR%%/locust/util/directory.py
+%%PYTHON_SITELIBDIR%%/locust/util/exception_handler.py
+%%PYTHON_SITELIBDIR%%/locust/util/load_locustfile.py
+%%PYTHON_SITELIBDIR%%/locust/util/rounding.py
+%%PYTHON_SITELIBDIR%%/locust/util/timespan.py
+%%PYTHON_SITELIBDIR%%/locust/util/url.py
+%%PYTHON_SITELIBDIR%%/locust/web.py
+%%PYTHON_SITELIBDIR%%/locust/webui/dist/assets/favicon-dark.png
+%%PYTHON_SITELIBDIR%%/locust/webui/dist/assets/favicon-light.png
+%%PYTHON_SITELIBDIR%%/locust/webui/dist/assets/graphs-dark.png
+%%PYTHON_SITELIBDIR%%/locust/webui/dist/assets/graphs-light.png
+%%PYTHON_SITELIBDIR%%/locust/webui/dist/assets/index-4Ae7-bG8.js
+%%PYTHON_SITELIBDIR%%/locust/webui/dist/assets/testruns-dark.png
+%%PYTHON_SITELIBDIR%%/locust/webui/dist/assets/testruns-light.png
+%%PYTHON_SITELIBDIR%%/locust/webui/dist/auth.html
+%%PYTHON_SITELIBDIR%%/locust/webui/dist/index.html
+%%PYTHON_SITELIBDIR%%/locust/webui/dist/report.html
+%%PORTEXAMPLES%%%%EXAMPLESDIR%%/add_command_line_argument.py
+%%PORTEXAMPLES%%%%EXAMPLESDIR%%/basic.py
+%%PORTEXAMPLES%%%%EXAMPLESDIR%%/bottlenecked_server.py
+%%PORTEXAMPLES%%%%EXAMPLESDIR%%/browse_docs_sequence_test.py
+%%PORTEXAMPLES%%%%EXAMPLESDIR%%/browse_docs_test.py
+%%PORTEXAMPLES%%%%EXAMPLESDIR%%/csrf_form_authentication.py
+%%PORTEXAMPLES%%%%EXAMPLESDIR%%/custom_messages.py
+%%PORTEXAMPLES%%%%EXAMPLESDIR%%/custom_wait_function.py
+%%PORTEXAMPLES%%%%EXAMPLESDIR%%/debugging.py
+%%PORTEXAMPLES%%%%EXAMPLESDIR%%/debugging_advanced.py
+%%PORTEXAMPLES%%%%EXAMPLESDIR%%/dynamic_user_credentials.py
+%%PORTEXAMPLES%%%%EXAMPLESDIR%%/events.py
+%%PORTEXAMPLES%%%%EXAMPLESDIR%%/extend_web_ui.py
+%%PORTEXAMPLES%%%%EXAMPLESDIR%%/fast_http_locust.py
+%%PORTEXAMPLES%%%%EXAMPLESDIR%%/locustfile.py
+%%PORTEXAMPLES%%%%EXAMPLESDIR%%/manual_stats_reporting.py
+%%PORTEXAMPLES%%%%EXAMPLESDIR%%/multiple_hosts.py
+%%PORTEXAMPLES%%%%EXAMPLESDIR%%/nested_inline_tasksets.py
+%%PORTEXAMPLES%%%%EXAMPLESDIR%%/open_closed_workload.py
+%%PORTEXAMPLES%%%%EXAMPLESDIR%%/openai_ex.py
+%%PORTEXAMPLES%%%%EXAMPLESDIR%%/response_validations.py
+%%PORTEXAMPLES%%%%EXAMPLESDIR%%/rest.py
+%%PORTEXAMPLES%%%%EXAMPLESDIR%%/semaphore_wait.py
+%%PORTEXAMPLES%%%%EXAMPLESDIR%%/stop_on_threshold.py
+%%PORTEXAMPLES%%%%EXAMPLESDIR%%/test_data_management.py
+%%PORTEXAMPLES%%%%EXAMPLESDIR%%/testdata_from_csv.py
+%%PORTEXAMPLES%%%%EXAMPLESDIR%%/use_as_lib.py
+%%PORTEXAMPLES%%%%EXAMPLESDIR%%/web_ui_cache_stats.py
+%%PORTEXAMPLES%%%%EXAMPLESDIR%%/worker_index.py
+%%PORTEXAMPLES%%%%EXAMPLESDIR%%/x-forwarded-for.py