Skip to content

Commit

Permalink
Merge pull request #1637 from rloomans/save-html-report
Browse files Browse the repository at this point in the history
Added --html option to save HTML report
  • Loading branch information
cyberw authored Nov 24, 2020
2 parents 03b6df4 + f9854dd commit 0e362af
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 50 deletions.
6 changes: 6 additions & 0 deletions locust/argument_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,12 @@ def setup_parser_arguments(parser):
help="Reset statistics once spawning has been completed. Should be set on both master and workers when running in distributed mode",
env_var="LOCUST_RESET_STATS",
)
stats_group.add_argument(
"--html",
dest="html_file",
help="Store HTML report file",
env_var="LOCUST_HTML",
)

log_group = parser.add_argument_group("Logging options")
log_group.add_argument(
Expand Down
68 changes: 68 additions & 0 deletions locust/html.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
from jinja2 import Environment, FileSystemLoader
import os
import pathlib
import datetime
from itertools import chain
from .stats import sort_stats


def render_template(file, **kwargs):
templates_path = os.path.join(pathlib.Path(__file__).parent.absolute(), "templates")
env = Environment(loader=FileSystemLoader(templates_path))
template = env.get_template(file)
return template.render(**kwargs)


def get_html_report(environment):
stats = environment.runner.stats

start_ts = stats.start_time
start_time = datetime.datetime.fromtimestamp(start_ts)
start_time = start_time.strftime("%Y-%m-%d %H:%M:%S")

end_ts = stats.last_request_timestamp
end_time = datetime.datetime.fromtimestamp(end_ts)
end_time = end_time.strftime("%Y-%m-%d %H:%M:%S")

host = None
if environment.host:
host = environment.host
elif environment.runner.user_classes:
all_hosts = set([l.host for l in environment.runner.user_classes])
if len(all_hosts) == 1:
host = list(all_hosts)[0]

requests_statistics = list(chain(sort_stats(stats.entries), [stats.total]))
failures_statistics = sort_stats(stats.errors)
exceptions_statistics = []
for exc in environment.runner.exceptions.values():
exc["nodes"] = ", ".join(exc["nodes"])
exceptions_statistics.append(exc)

history = stats.history

static_js = ""
js_files = ["jquery-1.11.3.min.js", "echarts.common.min.js", "vintage.js", "chart.js"]
for js_file in js_files:
path = os.path.join(os.path.dirname(__file__), "static", js_file)
with open(path, encoding="utf8") as f:
content = f.read()
static_js += "// " + js_file + "\n"
static_js += content
static_js += "\n\n\n"

res = render_template(
"report.html",
int=int,
round=round,
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=static_js,
)

return res
5 changes: 5 additions & 0 deletions locust/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from .exception import AuthCredentialsError
from .shape import LoadTestShape
from .input_events import input_listener
from .html import get_html_report


version = locust.__version__
Expand Down Expand Up @@ -423,6 +424,10 @@ def sig_term_handler():
try:
logger.info("Starting Locust %s" % version)
main_greenlet.join()
if options.html_file:
html_report = get_html_report(environment)
with open(options.html_file, "w+") as file:
file.write(html_report)
shutdown()
except KeyboardInterrupt:
shutdown()
41 changes: 41 additions & 0 deletions locust/test/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -368,3 +368,44 @@ def t(self):
self.assertIn("1 Users have been stopped", output)
self.assertIn("10 Users have been stopped", output)
self.assertIn("Test task is running", output)

def test_html_report_option(self):
with mock_locustfile() as mocked:
with temporary_file("", suffix=".html") as html_report_file_path:
try:
output = (
subprocess.check_output(
[
"locust",
"-f",
mocked.file_path,
"--host",
"https://test.com/",
"--run-time",
"5s",
"--headless",
"--html",
html_report_file_path,
],
stderr=subprocess.STDOUT,
timeout=10,
)
.decode("utf-8")
.strip()
)
except subprocess.CalledProcessError as e:
raise AssertionError(
"Running locust command failed. Output was:\n\n%s" % e.stdout.decode("utf-8")
) from e

with open(html_report_file_path, encoding="utf-8") as f:
html_report_content = f.read()

# make sure title appears in the report
self.assertIn("<title>Test Report</title>", html_report_content)

# make sure host appears in the report
self.assertIn("https://test.com/", html_report_content)

# make sure the charts container appears in the report
self.assertIn("charts-container", html_report_content)
52 changes: 2 additions & 50 deletions locust/web.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from .util.cache import memoize
from .util.rounding import proper_round
from .util.timespan import parse_timespan
from .html import get_html_report


logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -168,56 +169,7 @@ def reset_stats():
@app.route("/stats/report")
@self.auth_required_if_enabled
def stats_report():
stats = self.environment.runner.stats

start_ts = stats.start_time
start_time = datetime.datetime.fromtimestamp(start_ts)
start_time = start_time.strftime("%Y-%m-%d %H:%M:%S")

end_ts = stats.last_request_timestamp
end_time = datetime.datetime.fromtimestamp(end_ts)
end_time = end_time.strftime("%Y-%m-%d %H:%M:%S")

host = None
if environment.host:
host = environment.host
elif environment.runner.user_classes:
all_hosts = set([l.host for l in environment.runner.user_classes])
if len(all_hosts) == 1:
host = list(all_hosts)[0]

requests_statistics = list(chain(sort_stats(stats.entries), [stats.total]))
failures_statistics = sort_stats(stats.errors)
exceptions_statistics = []
for exc in environment.runner.exceptions.values():
exc["nodes"] = ", ".join(exc["nodes"])
exceptions_statistics.append(exc)

history = stats.history

static_js = ""
js_files = ["jquery-1.11.3.min.js", "echarts.common.min.js", "vintage.js", "chart.js"]
for js_file in js_files:
path = os.path.join(os.path.dirname(__file__), "static", js_file)
with open(path, encoding="utf8") as f:
content = f.read()
static_js += "// " + js_file + "\n"
static_js += content
static_js += "\n\n\n"

res = render_template(
"report.html",
int=int,
round=round,
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=static_js,
)
res = get_html_report(self.environment)
if request.args.get("download"):
res = app.make_response(res)
res.headers["Content-Disposition"] = "attachment;filename=report_%s.html" % time()
Expand Down

0 comments on commit 0e362af

Please sign in to comment.