mirror of https://github.com/locustio/locust.git
209 lines
6.8 KiB
Python
209 lines
6.8 KiB
Python
"""
|
|
This is an example of a locustfile that uses Locust's built in event and web
|
|
UI extension hooks to track the sum of Varnish cache hit/miss headers
|
|
and display them in the web UI.
|
|
"""
|
|
|
|
from locust import HttpUser, TaskSet, between, events, task
|
|
|
|
import json
|
|
import os
|
|
from html import escape
|
|
from time import time
|
|
|
|
from flask import Blueprint, make_response, render_template, request
|
|
|
|
|
|
class MyTaskSet(TaskSet):
|
|
@task(1)
|
|
def miss(l):
|
|
"""MISS X-Cache header"""
|
|
l.client.get("/response-headers?X-Cache=MISS")
|
|
|
|
@task(2)
|
|
def hit(l):
|
|
"""HIT X-Cache header"""
|
|
l.client.get("/response-headers?X-Cache=HIT")
|
|
|
|
@task(1)
|
|
def noinfo(l):
|
|
"""No X-Cache header (noinfo counter)"""
|
|
l.client.get("/")
|
|
|
|
|
|
class WebsiteUser(HttpUser):
|
|
host = "http://httpbin.org"
|
|
wait_time = between(2, 5)
|
|
tasks = [MyTaskSet]
|
|
|
|
|
|
# This example is based on the Varnish hit/miss headers (https://docs.varnish-software.com/tutorials/hit-miss-logging/).
|
|
# It could easily be customised for matching other caching systems, CDN or custom headers.
|
|
CACHE_HEADER = "X-Cache"
|
|
|
|
cache_stats = {}
|
|
|
|
page_stats = {"hit": 0, "miss": 0, "noinfo": 0}
|
|
|
|
path = os.path.dirname(os.path.abspath(__file__))
|
|
extend = Blueprint(
|
|
"extend",
|
|
"extend_web_ui",
|
|
static_folder=f"{path}/static/",
|
|
static_url_path="/extend/static/",
|
|
template_folder=f"{path}/templates/",
|
|
)
|
|
|
|
|
|
@events.init.add_listener
|
|
def locust_init(environment, **kwargs):
|
|
"""
|
|
Load data on locust init.
|
|
:param environment:
|
|
:param kwargs:
|
|
:return:
|
|
"""
|
|
|
|
if environment.web_ui:
|
|
# this code is only run on the master node (the web_ui instance doesn't exist on workers)
|
|
|
|
def get_cache_stats():
|
|
"""
|
|
This is used by the Cache tab in the
|
|
extended web UI to show the stats.
|
|
"""
|
|
if cache_stats:
|
|
stats_tmp = []
|
|
|
|
for name, inner_stats in cache_stats.items():
|
|
stats_tmp.append(
|
|
{
|
|
"name": name,
|
|
"safe_name": escape(name, quote=False),
|
|
"hit": inner_stats["hit"],
|
|
"miss": inner_stats["miss"],
|
|
"noinfo": inner_stats["noinfo"],
|
|
}
|
|
)
|
|
|
|
# Truncate the total number of stats and errors displayed since a large number
|
|
# of rows will cause the app to render extremely slowly.
|
|
return stats_tmp[:500]
|
|
return cache_stats
|
|
|
|
@environment.web_ui.app.after_request
|
|
def extend_stats_response(response):
|
|
if request.path != "/stats/requests":
|
|
return response
|
|
|
|
# extended_stats contains the data where extended_tables looks for its data: "cache-statistics"
|
|
response.set_data(
|
|
json.dumps(
|
|
{**response.json, "extended_stats": [{"key": "cache-statistics", "data": get_cache_stats()}]}
|
|
)
|
|
)
|
|
|
|
return response
|
|
|
|
@extend.route("/extend")
|
|
def extend_web_ui():
|
|
"""
|
|
Add route to access the extended web UI with our new tab.
|
|
"""
|
|
# ensure the template_args are up to date before using them
|
|
environment.web_ui.update_template_args()
|
|
|
|
return render_template(
|
|
"index.html",
|
|
template_args={
|
|
**environment.web_ui.template_args,
|
|
# extended_tabs and extended_tables keys must match.
|
|
"extended_tabs": [{"title": "Cache statistics", "key": "cache-statistics"}],
|
|
"extended_tables": [
|
|
{
|
|
"key": "cache-statistics",
|
|
"structure": [
|
|
{"key": "name", "title": "Name"},
|
|
{"key": "hit", "title": "Hit"},
|
|
{"key": "miss", "title": "Miss"},
|
|
{"key": "noinfo", "title": "No Info"},
|
|
],
|
|
}
|
|
],
|
|
"extended_csv_files": [{"href": "/cache/csv", "title": "Download Cache statistics CSV"}],
|
|
},
|
|
)
|
|
|
|
@extend.route("/cache/csv")
|
|
def request_cache_csv():
|
|
"""
|
|
Add route to enable downloading of cache stats as CSV
|
|
"""
|
|
response = make_response(cache_csv())
|
|
file_name = f"cache-{time()}.csv"
|
|
disposition = f"attachment;filename={file_name}"
|
|
response.headers["Content-type"] = "text/csv"
|
|
response.headers["Content-disposition"] = disposition
|
|
return response
|
|
|
|
def cache_csv():
|
|
"""Returns the cache stats as CSV."""
|
|
rows = [",".join(['"Name"', '"hit"', '"miss"', '"noinfo"'])]
|
|
|
|
if cache_stats:
|
|
for name, stats in cache_stats.items():
|
|
rows.append(f'"{name}",' + ",".join(str(v) for v in stats.values()))
|
|
return "\n".join(rows)
|
|
|
|
# register our new routes and extended UI with the Locust web UI
|
|
environment.web_ui.app.register_blueprint(extend)
|
|
|
|
|
|
@events.request.add_listener
|
|
def on_request(name, response, exception, **kwargs):
|
|
"""
|
|
Event handler that get triggered on every request
|
|
"""
|
|
|
|
cache_stats.setdefault(name, page_stats.copy())
|
|
|
|
if CACHE_HEADER not in response.headers:
|
|
cache_stats[name]["noinfo"] += 1
|
|
elif response.headers[CACHE_HEADER] == "HIT":
|
|
cache_stats[name]["hit"] += 1
|
|
elif response.headers[CACHE_HEADER] == "MISS":
|
|
cache_stats[name]["miss"] += 1
|
|
|
|
|
|
@events.report_to_master.add_listener
|
|
def on_report_to_master(client_id, data):
|
|
"""
|
|
This event is triggered on the worker instances every time a stats report is
|
|
to be sent to the locust master. It will allow us to add our extra cache
|
|
data to the dict that is being sent, and then we clear the local stats in the worker.
|
|
"""
|
|
global cache_stats
|
|
data["cache_stats"] = cache_stats
|
|
cache_stats = {}
|
|
|
|
|
|
@events.worker_report.add_listener
|
|
def on_worker_report(client_id, data):
|
|
"""
|
|
This event is triggered on the master instance when a new stats report arrives
|
|
from a worker. Here we just add the cache to the master's aggregated stats dict.
|
|
"""
|
|
for name in data["cache_stats"]:
|
|
cache_stats.setdefault(name, page_stats.copy())
|
|
for stat_name, value in data["cache_stats"][name].items():
|
|
cache_stats[name][stat_name] += value
|
|
|
|
|
|
@events.reset_stats.add_listener
|
|
def on_reset_stats():
|
|
"""
|
|
Event handler that get triggered on click of web UI Reset Stats button
|
|
"""
|
|
global cache_stats
|
|
cache_stats = {}
|