Skip to content

Commit

Permalink
Merge pull request #2269 from AndersSpringborg/stats_in_json_to_stdout
Browse files Browse the repository at this point in the history
Stats in json to stdout
  • Loading branch information
cyberw committed Jan 3, 2023
2 parents f36e614 + a70b3b5 commit 468a67b
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 4 deletions.
6 changes: 6 additions & 0 deletions locust/argument_parser.py
Expand Up @@ -488,6 +488,12 @@ def setup_parser_arguments(parser):
help="Store HTML report to file path specified",
env_var="LOCUST_HTML",
)
stats_group.add_argument(
"--json",
default=False,
action="store_true",
help="Prints the final stats in JSON format to stdout. Useful for parsing the results in other programs/scripts. Use together with --headless and --skip-log for an output only with the json data.",
)

log_group = parser.add_argument_group("Logging options")
log_group.add_argument(
Expand Down
14 changes: 11 additions & 3 deletions locust/main.py
Expand Up @@ -13,7 +13,14 @@
from .env import Environment
from .log import setup_logging, greenlet_exception_logger
from . import stats
from .stats import print_error_report, print_percentile_stats, print_stats, stats_printer, stats_history
from .stats import (
print_error_report,
print_percentile_stats,
print_stats,
print_stats_json,
stats_printer,
stats_history,
)
from .stats import StatsCSV, StatsCSVFileWriter
from .user.inspectuser import print_task_ratio, print_task_ratio_json
from .util.timespan import parse_timespan
Expand Down Expand Up @@ -428,8 +435,9 @@ def shutdown():
logger.debug("Cleaning up runner...")
if runner is not None:
runner.quit()

if not isinstance(runner, locust.runners.WorkerRunner):
if options.json:
print_stats_json(runner.stats)
elif not isinstance(runner, locust.runners.WorkerRunner):
print_stats(runner.stats, current=False)
print_percentile_stats(runner.stats)
print_error_report(runner.stats)
Expand Down
5 changes: 5 additions & 0 deletions locust/stats.py
Expand Up @@ -2,6 +2,7 @@
from abc import abstractmethod
import datetime
import hashlib
import json
from tempfile import NamedTemporaryFile
import time
from collections import namedtuple, OrderedDict
Expand Down Expand Up @@ -787,6 +788,10 @@ def print_stats(stats: RequestStats, current=True) -> None:
console_logger.info("")


def print_stats_json(stats: RequestStats) -> None:
print(json.dumps(stats.serialize_stats(), indent=4))


def get_stats_summary(stats: RequestStats, current=True) -> List[str]:
"""
stats summary will be returned as list of string
Expand Down
86 changes: 85 additions & 1 deletion locust/test/test_main.py
@@ -1,3 +1,4 @@
import json
import os
import platform
import pty
Expand All @@ -6,7 +7,7 @@
import textwrap
from tempfile import TemporaryDirectory
from unittest import TestCase
from subprocess import PIPE, STDOUT
from subprocess import PIPE, STDOUT, DEVNULL

import gevent
import requests
Expand Down Expand Up @@ -1400,6 +1401,89 @@ def t(self):
self.assertEqual(0, proc.returncode)
self.assertEqual(0, proc_worker.returncode)

def test_json_can_be_parsed(self):
LOCUSTFILE_CONTENT = textwrap.dedent(
"""
from locust import User, task, constant
class User1(User):
wait_time = constant(1)
@task
def t(self):
pass
"""
)
with mock_locustfile(content=LOCUSTFILE_CONTENT) as mocked:
proc = subprocess.Popen(
["locust", "-f", mocked.file_path, "--headless", "-t", "5s", "--json"],
stderr=DEVNULL,
stdout=PIPE,
text=True,
)
stdout, stderr = proc.communicate()

try:
json.loads(stdout)
except json.JSONDecodeError:
self.fail(f"Trying to parse {stdout} as json failed")
self.assertEqual(0, proc.returncode)

def test_json_schema(self):
LOCUSTFILE_CONTENT = textwrap.dedent(
"""
from locust import HttpUser, task, constant
class QuickstartUser(HttpUser):
wait_time = constant(1)
@task
def hello_world(self):
self.client.get("/")
"""
)
with mock_locustfile(content=LOCUSTFILE_CONTENT) as mocked:
proc = subprocess.Popen(
[
"locust",
"-f",
mocked.file_path,
"--host",
"http://google.com",
"--headless",
"-u",
"1",
"-t",
"2s",
"--json",
],
stderr=DEVNULL,
stdout=PIPE,
text=True,
)
stdout, stderr = proc.communicate()

try:
data = json.loads(stdout)
except json.JSONDecodeError:
self.fail(f"Trying to parse {stdout} as json failed")

self.assertEqual(0, proc.returncode)

result = data[0]
self.assertEqual(float, type(result["last_request_timestamp"]))
self.assertEqual(float, type(result["start_time"]))
self.assertEqual(int, type(result["num_requests"]))
self.assertEqual(int, type(result["num_none_requests"]))
self.assertEqual(float, type(result["total_response_time"]))
self.assertEqual(float, type(result["max_response_time"]))
self.assertEqual(float, type(result["min_response_time"]))
self.assertEqual(int, type(result["total_content_length"]))
self.assertEqual(dict, type(result["response_times"]))
self.assertEqual(dict, type(result["num_reqs_per_sec"]))
self.assertEqual(dict, type(result["num_fail_per_sec"]))

def test_worker_indexes(self):
content = """
from locust import HttpUser, task, between
Expand Down

0 comments on commit 468a67b

Please sign in to comment.