From owner-svn-ports-head@freebsd.org Sun Jan 20 12:54:34 2019 Return-Path: Delivered-To: svn-ports-head@mailman.ysv.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mailman.ysv.freebsd.org (Postfix) with ESMTP id 6EEAF148E98E; Sun, 20 Jan 2019 12:54:34 +0000 (UTC) (envelope-from egypcio@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) server-signature RSA-PSS (4096 bits) client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "Let's Encrypt Authority X3" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id 21401950B2; Sun, 20 Jan 2019 12:54:34 +0000 (UTC) (envelope-from egypcio@FreeBSD.org) Received: from repo.freebsd.org (repo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:0]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client did not present a certificate) by mxrelay.nyi.freebsd.org (Postfix) with ESMTPS id BD7FA2750; Sun, 20 Jan 2019 12:54:33 +0000 (UTC) (envelope-from egypcio@FreeBSD.org) Received: from repo.freebsd.org ([127.0.1.37]) by repo.freebsd.org (8.15.2/8.15.2) with ESMTP id x0KCsXat081859; Sun, 20 Jan 2019 12:54:33 GMT (envelope-from egypcio@FreeBSD.org) Received: (from egypcio@localhost) by repo.freebsd.org (8.15.2/8.15.2/Submit) id x0KCsVif081845; Sun, 20 Jan 2019 12:54:31 GMT (envelope-from egypcio@FreeBSD.org) Message-Id: <201901201254.x0KCsVif081845@repo.freebsd.org> X-Authentication-Warning: repo.freebsd.org: egypcio set sender to egypcio@FreeBSD.org using -f From: =?UTF-8?Q?Vin=c3=adcius_Zavam?= Date: Sun, 20 Jan 2019 12:54:31 +0000 (UTC) To: ports-committers@freebsd.org, svn-ports-all@freebsd.org, svn-ports-head@freebsd.org Subject: svn commit: r490786 - in head/benchmarks: . py-locust py-locust/files X-SVN-Group: ports-head X-SVN-Commit-Author: egypcio X-SVN-Commit-Paths: in head/benchmarks: . py-locust py-locust/files X-SVN-Commit-Revision: 490786 X-SVN-Commit-Repository: ports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-Rspamd-Queue-Id: 21401950B2 X-Spamd-Bar: -- Authentication-Results: mx1.freebsd.org X-Spamd-Result: default: False [-2.97 / 15.00]; local_wl_from(0.00)[FreeBSD.org]; NEURAL_HAM_MEDIUM(-1.00)[-1.000,0]; NEURAL_HAM_SHORT(-0.97)[-0.973,0]; NEURAL_HAM_LONG(-1.00)[-0.999,0] X-BeenThere: svn-ports-head@freebsd.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: SVN commit messages for the ports tree for head List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Sun, 20 Jan 2019 12:54:34 -0000 Author: egypcio Date: Sun Jan 20 12:54:30 2019 New Revision: 490786 URL: https://svnweb.freebsd.org/changeset/ports/490786 Log: [NEW] benchmarks/py-locust: Python utility for doing distributed load tests Locust is an easy-to-use, distributed, user load testing tool. It is intended for load-testing web sites (or other systems) and figuring out how many concurrent users a system can handle. The behavior of each locust (or test user if you will) is defined by you and the swarming process is monitored from a web UI in real-time. This will help you battle test and identify bottlenecks in your code before letting real users in. WWW: https://locust.io/ Approved by: araujo (mentor), rene (mentor) Sponsored by: cleverbridge AG Differential Revision: https://reviews.freebsd.org/D18895 Added: head/benchmarks/py-locust/ head/benchmarks/py-locust/Makefile (contents, props changed) head/benchmarks/py-locust/distinfo (contents, props changed) head/benchmarks/py-locust/files/ head/benchmarks/py-locust/files/extra-EXAMPLES-basic.py (contents, props changed) head/benchmarks/py-locust/files/extra-EXAMPLES-browse_docs_sequence_test.py (contents, props changed) head/benchmarks/py-locust/files/extra-EXAMPLES-browse_docs_test.py (contents, props changed) head/benchmarks/py-locust/files/extra-EXAMPLES-custom_wait_function.py (contents, props changed) head/benchmarks/py-locust/files/extra-EXAMPLES-custom_xmlrpc_client.py (contents, props changed) head/benchmarks/py-locust/files/extra-EXAMPLES-dynamice_user_credentials.py (contents, props changed) head/benchmarks/py-locust/files/extra-EXAMPLES-events.py (contents, props changed) head/benchmarks/py-locust/files/extra-EXAMPLES-multiple_hosts.py (contents, props changed) head/benchmarks/py-locust/files/extra-EXAMPLES-semaphore_wait.py (contents, props changed) head/benchmarks/py-locust/pkg-descr (contents, props changed) head/benchmarks/py-locust/pkg-plist (contents, props changed) Modified: head/benchmarks/Makefile Modified: head/benchmarks/Makefile ============================================================================== --- head/benchmarks/Makefile Sun Jan 20 12:49:04 2019 (r490785) +++ head/benchmarks/Makefile Sun Jan 20 12:54:30 2019 (r490786) @@ -65,6 +65,7 @@ SUBDIR += polygraph SUBDIR += postal SUBDIR += postmark + SUBDIR += py-locust SUBDIR += py-naarad SUBDIR += py-throughpy SUBDIR += py-zopkio Added: head/benchmarks/py-locust/Makefile ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/benchmarks/py-locust/Makefile Sun Jan 20 12:54:30 2019 (r490786) @@ -0,0 +1,34 @@ +# $FreeBSD$ + +PORTNAME= locust +PORTVERSION= 0.9.0 +CATEGORIES= benchmarks www python +MASTER_SITES= CHEESESHOP +PKGNAMEPREFIX= ${PYTHON_PKGNAMEPREFIX} +DISTNAME= locustio-${PORTVERSION} + +MAINTAINER= egypcio@FreeBSD.org +COMMENT= Python utility for doing easy, distributed load testing + +LICENSE= MIT + +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} + +USES= python +USE_PYTHON= autoplist distutils + +NO_ARCH= yes + +OPTIONS_DEFINE= EXAMPLES + +post-install-EXAMPLES-on: + ${MKDIR} ${STAGEDIR}${EXAMPLESDIR} + ${INSTALL_DATA} ${FILESDIR}/extra-EXAMPLES* ${STAGEDIR}${EXAMPLESDIR} + +.include Added: head/benchmarks/py-locust/distinfo ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/benchmarks/py-locust/distinfo Sun Jan 20 12:54:30 2019 (r490786) @@ -0,0 +1,3 @@ +TIMESTAMP = 1547833536 +SHA256 (locustio-0.9.0.tar.gz) = c77b471e0e08e215c93a7af9a95b79193268072873fbbc0effca40f3d9b58be4 +SIZE (locustio-0.9.0.tar.gz) = 226870 Added: head/benchmarks/py-locust/files/extra-EXAMPLES-basic.py ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/benchmarks/py-locust/files/extra-EXAMPLES-basic.py Sun Jan 20 12:54:30 2019 (r490786) @@ -0,0 +1,26 @@ +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 Added: head/benchmarks/py-locust/files/extra-EXAMPLES-browse_docs_sequence_test.py ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/benchmarks/py-locust/files/extra-EXAMPLES-browse_docs_sequence_test.py Sun Jan 20 12:54:30 2019 (r490786) @@ -0,0 +1,50 @@ +# 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 Added: head/benchmarks/py-locust/files/extra-EXAMPLES-browse_docs_test.py ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/benchmarks/py-locust/files/extra-EXAMPLES-browse_docs_test.py Sun Jan 20 12:54:30 2019 (r490786) @@ -0,0 +1,49 @@ +# 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 Added: head/benchmarks/py-locust/files/extra-EXAMPLES-custom_wait_function.py ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/benchmarks/py-locust/files/extra-EXAMPLES-custom_wait_function.py Sun Jan 20 12:54:30 2019 (r490786) @@ -0,0 +1,51 @@ +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 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 Added: head/benchmarks/py-locust/files/extra-EXAMPLES-events.py ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/benchmarks/py-locust/files/extra-EXAMPLES-events.py Sun Jan 20 12:54:30 2019 (r490786) @@ -0,0 +1,69 @@ +# -*- 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"] Added: head/benchmarks/py-locust/files/extra-EXAMPLES-multiple_hosts.py ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/benchmarks/py-locust/files/extra-EXAMPLES-multiple_hosts.py Sun Jan 20 12:54:30 2019 (r490786) @@ -0,0 +1,31 @@ +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 Added: head/benchmarks/py-locust/files/extra-EXAMPLES-semaphore_wait.py ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/benchmarks/py-locust/files/extra-EXAMPLES-semaphore_wait.py Sun Jan 20 12:54:30 2019 (r490786) @@ -0,0 +1,25 @@ +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 Added: head/benchmarks/py-locust/pkg-descr ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/benchmarks/py-locust/pkg-descr Sun Jan 20 12:54:30 2019 (r490786) @@ -0,0 +1,9 @@ +Locust is an easy-to-use, distributed, user load testing tool. It is intended +for load-testing web sites (or other systems) and figuring out how many +concurrent users a system can handle. + +The behavior of each locust (or test user if you will) is defined by you and the +swarming process is monitored from a web UI in real-time. This will help you +battle test and identify bottlenecks in your code before letting real users in. + +WWW: https://locust.io/ Added: head/benchmarks/py-locust/pkg-plist ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/benchmarks/py-locust/pkg-plist Sun Jan 20 12:54:30 2019 (r490786) @@ -0,0 +1,9 @@ +%%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