diff --git a/locust/html.py b/locust/html.py index 7ba8e0b6fe..ef5d595f5f 100644 --- a/locust/html.py +++ b/locust/html.py @@ -1,5 +1,6 @@ from jinja2 import Environment, FileSystemLoader import os +import glob import pathlib import datetime from itertools import chain @@ -9,6 +10,10 @@ from html import escape from json import dumps from .runners import MasterRunner, STATE_STOPPED, STATE_STOPPING +from flask import render_template as flask_render_template + + +PERCENTILES_FOR_HTML_REPORT = [0.50, 0.6, 0.7, 0.8, 0.9, 0.95, 0.99, 1.0] def render_template(file, **kwargs): @@ -18,7 +23,7 @@ def render_template(file, **kwargs): return template.render(**kwargs) -def get_html_report(environment, show_download_link=True): +def get_html_report(environment, static_path=os.path.join(os.path.dirname(__file__), "static"), show_download_link=True, use_modern_ui=False, theme=""): stats = environment.runner.stats start_ts = stats.start_time @@ -47,22 +52,27 @@ def get_html_report(environment, show_download_link=True): history = stats.history static_js = [] - js_files = ["jquery-1.11.3.min.js", "echarts.common.min.js", "vintage.js", "chart.js", "tasks.js"] + if use_modern_ui: + js_files = [os.path.basename(filepath) for filepath in glob.glob(os.path.join(static_path, "*.js"))] + else: + js_files = ["jquery-1.11.3.min.js", "echarts.common.min.js", "vintage.js", "chart.js", "tasks.js"] + for js_file in js_files: - path = os.path.join(os.path.dirname(__file__), "static", js_file) - static_js.append("// " + js_file) + path = os.path.join(static_path, js_file) + static_js.append("// " + js_file + "\n") with open(path, encoding="utf8") as f: static_js.append(f.read()) static_js.extend(["", ""]) - static_css = [] - css_files = ["tables.css"] - for css_file in css_files: - path = os.path.join(os.path.dirname(__file__), "static", "css", css_file) - static_css.append("/* " + css_file + " */") - with open(path, encoding="utf8") as f: - static_css.append(f.read()) - static_css.extend(["", ""]) + if not use_modern_ui: + static_css = [] + css_files = ["tables.css"] + for css_file in css_files: + path = os.path.join(static_path, "css", css_file) + static_css.append("/* " + css_file + " */") + with open(path, encoding="utf8") as f: + static_css.append(f.read()) + static_css.extend(["", ""]) is_distributed = isinstance(environment.runner, MasterRunner) user_spawned = ( @@ -77,26 +87,59 @@ def get_html_report(environment, show_download_link=True): "total": get_ratio(environment.user_classes, user_spawned, True), } - res = render_template( - "report.html", - int=int, - round=round, - escape=escape, - str=str, - requests_statistics=requests_statistics, - failures_statistics=failures_statistics, - exceptions_statistics=exceptions_statistics, - start_time=start_time, - end_time=end_time, - host=host, - history=history, - static_js="\n".join(static_js), - static_css="\n".join(static_css), - show_download_link=show_download_link, - locustfile=environment.locustfile, - tasks=dumps(task_data), - percentile1=stats_module.PERCENTILES_TO_CHART[0], - percentile2=stats_module.PERCENTILES_TO_CHART[1], - ) + if use_modern_ui: + res = flask_render_template( + "report.html", + template_args={ + "is_report": True, + "requests_statistics": [stat.to_dict(escape_string_values=True) for stat in requests_statistics], + "failures_statistics": [stat.to_dict() for stat in failures_statistics], + "exceptions_statistics": [stat for stat in exceptions_statistics], + "response_time_statistics": [ + { + "name": escape(stat.name), + "method": escape(stat.method), + **{ + str(percentile): stat.get_response_time_percentile(percentile) + for percentile in PERCENTILES_FOR_HTML_REPORT + }, + } + for stat in requests_statistics + ], + "start_time": start_time, + "end_time": end_time, + "host": escape(str(host)), + "history": history, + "show_download_link": show_download_link, + "locustfile": escape(str(environment.locustfile)), + "tasks": task_data, + "percentile1": stats_module.PERCENTILES_TO_CHART[0], + "percentile2": stats_module.PERCENTILES_TO_CHART[1], + }, + theme=theme, + static_js="\n".join(static_js), + ) + else: + res = render_template( + "report.html", + int=int, + round=round, + escape=escape, + str=str, + requests_statistics=requests_statistics, + failures_statistics=failures_statistics, + exceptions_statistics=exceptions_statistics, + start_time=start_time, + end_time=end_time, + host=host, + history=history, + static_js="\n".join(static_js), + static_css="\n".join(static_css), + show_download_link=show_download_link, + locustfile=environment.locustfile, + tasks=dumps(task_data), + percentile1=stats_module.PERCENTILES_TO_CHART[0], + percentile2=stats_module.PERCENTILES_TO_CHART[1], + ) return res diff --git a/locust/web.py b/locust/web.py index 4d6dc5cd22..83c528fc35 100644 --- a/locust/web.py +++ b/locust/web.py @@ -277,7 +277,20 @@ def reset_stats() -> str: @app.route("/stats/report") @self.auth_required_if_enabled def stats_report() -> Response: - res = get_html_report(self.environment, show_download_link=not request.args.get("download")) + if self.modern_ui: + self.set_static_modern_ui() + static_path = f"{self.webui_build_path}/assets" + else: + static_path = f"{self.app.root_path}/static" + + theme = request.args.get("theme", "") + res = get_html_report( + self.environment, + static_path=static_path, + show_download_link=not request.args.get("download"), + use_modern_ui=self.modern_ui, + theme=theme, + ) if request.args.get("download"): res = app.make_response(res) res.headers["Content-Disposition"] = f"attachment;filename=report_{time()}.html"