Skip to content

Commit

Permalink
Merge pull request #2947 from obriat/report-name
Browse files Browse the repository at this point in the history
Report name
  • Loading branch information
cyberw authored Nov 4, 2024
2 parents eba774c + 15166af commit 903122a
Show file tree
Hide file tree
Showing 14 changed files with 242 additions and 15 deletions.
4 changes: 2 additions & 2 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"gevent": true
},
{
"name": "Run locust headless",
"name": "Run current locust scenario headless",
"type": "python",
"request": "launch",
"module": "locust",
Expand All @@ -24,7 +24,7 @@
"gevent": true
},
{
"name": "Run locust, autostart",
"name": "Run current locust scenario, autostart",
"type": "python",
"request": "launch",
"module": "locust",
Expand Down
7 changes: 7 additions & 0 deletions docs/developing-locust.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ If you install `pre-commit <https://pre-commit.com/>`_, linting and format check

Before you open a pull request, make sure all the tests work. And if you are adding a feature, make sure it is documented (in ``docs/*.rst``).

If you're in a hurry or don't have access to a development environment, you can simply use `Codespaces <https://github.com/features/codespaces>`_, the github cloud development environment. On your fork page, just click on *Code* then on *Create codespace on <branch name>*, and voila, your ready to code and test.

Testing your changes
====================

Expand All @@ -51,6 +53,11 @@ To only run a specific suite or specific test you can call `pytest <https://docs
$ pytest locust/test/test_main.py::DistributedIntegrationTests::test_distributed_tags
Debugging
=========

See: :ref:`running-in-debugger`.

Formatting and linting
======================

Expand Down
12 changes: 11 additions & 1 deletion docs/running-in-debugger.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,17 @@ It implicitly registers an event handler for the :ref:`request <extending_locust
You can configure exactly what is printed by specifying parameters to :py:func:`run_single_user <locust.debug.run_single_user>`.

Make sure you have enabled gevent in your debugger settings. In VS Code's ``launch.json`` it looks like this:
Make sure you have enabled gevent in your debugger settings.

Debugging Locust is quite easy with Vscode:

- Place breakpoints
- Select a python file or a scenario (ex: ```examples/basic.py``)
- Check that the Poetry virtualenv is correctly detected (bottom right)
- Open the action *Debug using launch.json*. You will have the choice between debugging the python file, the scenario with WebUI or in headless mode
- It could be rerun with the F5 shortkey

VS Code's ``launch.json`` looks like this:

.. literalinclude:: ../.vscode/launch.json
:language: json
Expand Down
4 changes: 3 additions & 1 deletion locust/html.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from .runners import STATE_STOPPED, STATE_STOPPING, MasterRunner
from .stats import sort_stats, update_stats_history
from .user.inspectuser import get_ratio
from .util.date import format_utc_timestamp
from .util.date import format_duration, format_utc_timestamp

PERCENTILES_FOR_HTML_REPORT = [0.50, 0.6, 0.7, 0.8, 0.9, 0.95, 0.99, 1.0]
DEFAULT_BUILD_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "webui", "dist")
Expand All @@ -36,6 +36,7 @@ def get_html_report(
if end_ts := stats.last_request_timestamp:
end_time = format_utc_timestamp(end_ts)
else:
end_ts = stats.start_time
end_time = start_time

host = None
Expand Down Expand Up @@ -88,6 +89,7 @@ def get_html_report(
],
"start_time": start_time,
"end_time": end_time,
"duration": format_duration(stats.start_time, end_ts),
"host": escape(str(host)),
"history": history,
"show_download_link": show_download_link,
Expand Down
86 changes: 86 additions & 0 deletions locust/test/test_date.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
from locust.util.date import format_duration, format_safe_timestamp, format_utc_timestamp

from datetime import datetime

import pytest

dates_checks = [
{
"datetime": datetime(2023, 10, 1, 12, 0, 0),
"utc_timestamp": "2023-10-01T10:00:00Z",
"safe_timestamp": "2023-10-01-12h00",
"duration": "0 seconds",
},
{
"datetime": datetime(2023, 10, 1, 12, 0, 30),
"utc_timestamp": "2023-10-01T10:00:30Z",
"safe_timestamp": "2023-10-01-12h00",
"duration": "30 seconds",
},
{
"datetime": datetime(2023, 10, 1, 12, 45, 0),
"utc_timestamp": "2023-10-01T10:45:00Z",
"safe_timestamp": "2023-10-01-12h45",
"duration": "45 minutes",
},
{
"datetime": datetime(2023, 10, 1, 15, 0, 0),
"utc_timestamp": "2023-10-01T13:00:00Z",
"safe_timestamp": "2023-10-01-15h00",
"duration": "3 hours",
},
{
"datetime": datetime(2023, 10, 4, 12, 0, 0),
"utc_timestamp": "2023-10-04T10:00:00Z",
"safe_timestamp": "2023-10-04-12h00",
"duration": "3 days",
},
{
"datetime": datetime(2023, 10, 3, 15, 45, 30),
"utc_timestamp": "2023-10-03T13:45:30Z",
"safe_timestamp": "2023-10-03-15h45",
"duration": "2 days, 3 hours, 45 minutes and 30 seconds",
},
{
"datetime": datetime(2023, 10, 2, 13, 1, 1),
"utc_timestamp": "2023-10-02T11:01:01Z",
"safe_timestamp": "2023-10-02-13h01",
"duration": "1 day, 1 hour, 1 minute and 1 second",
},
{
"datetime": datetime(2023, 10, 1, 15, 30, 45),
"utc_timestamp": "2023-10-01T13:30:45Z",
"safe_timestamp": "2023-10-01-15h30",
"duration": "3 hours, 30 minutes and 45 seconds",
},
{
"datetime": datetime(2023, 10, 2, 12, 30, 45),
"utc_timestamp": "2023-10-02T10:30:45Z",
"safe_timestamp": "2023-10-02-12h30",
"duration": "1 day, 30 minutes and 45 seconds",
},
{
"datetime": datetime(2023, 10, 2, 12, 0, 45),
"utc_timestamp": "2023-10-02T10:00:45Z",
"safe_timestamp": "2023-10-02-12h00",
"duration": "1 day and 45 seconds",
},
]


@pytest.mark.parametrize("check", dates_checks)
def test_format_utc_timestamp(check):
assert format_utc_timestamp(check["datetime"].timestamp()) == check["utc_timestamp"]


@pytest.mark.parametrize("check", dates_checks)
def test_format_safe_timestamp(check):
assert format_safe_timestamp(check["datetime"].timestamp()) == check["safe_timestamp"]


@pytest.mark.parametrize("check", dates_checks)
def test_format_duration(check):
global dates_checks
start_time = dates_checks[0]["datetime"].timestamp()
end_time = check["datetime"].timestamp()
assert format_duration(start_time, end_time) == check["duration"]
3 changes: 3 additions & 0 deletions locust/test/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -1181,6 +1181,9 @@ def test_html_report_option(self):
# make sure host appears in the report
self.assertIn("https://test.com/", html_report_content)
self.assertIn('"show_download_link": false', html_report_content)
self.assertRegex(html_report_content, r'"start_time": "\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z"')
self.assertRegex(html_report_content, r'"end_time": "\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z"')
self.assertRegex(html_report_content, r'"duration": "\d* seconds?"')

def test_run_with_userclass_picker(self):
with temporary_file(content=MOCK_LOCUSTFILE_CONTENT_A) as file1:
Expand Down
20 changes: 19 additions & 1 deletion locust/util/date.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,22 @@


def format_utc_timestamp(unix_timestamp):
return datetime.fromtimestamp(unix_timestamp, timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
return datetime.fromtimestamp(int(unix_timestamp), timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")


def format_safe_timestamp(unix_timestamp):
return datetime.fromtimestamp(int(unix_timestamp)).strftime("%Y-%m-%d-%Hh%M")


def format_duration(start_time, end_time):
seconds = int(end_time) - int(start_time)
days = seconds // 86400
hours = (seconds % 86400) // 3600
minutes = (seconds % 3600) // 60
seconds = seconds % 60

time_parts = [(days, "day"), (hours, "hour"), (minutes, "minute"), (seconds, "second")]

parts = [f"{value} {label}{'s' if value != 1 else ''}" for value, label in time_parts if value > 0]

return " and ".join(filter(None, [", ".join(parts[:-1])] + parts[-1:])) if parts else "0 seconds"
18 changes: 13 additions & 5 deletions locust/web.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
from .stats import StatsCSV, StatsCSVFileWriter, StatsErrorDict, sort_stats
from .user.inspectuser import get_ratio
from .util.cache import memoize
from .util.date import format_utc_timestamp
from .util.date import format_safe_timestamp
from .util.timespan import parse_timespan

if TYPE_CHECKING:
Expand Down Expand Up @@ -347,17 +347,25 @@ def stats_report() -> Response:
)
if request.args.get("download"):
res = app.make_response(res)
res.headers["Content-Disposition"] = f"attachment;filename=report_{time()}.html"
host = f"_{self.environment.host}" if self.environment.host else ""
res.headers["Content-Disposition"] = (
f"attachment;filename=Locust_{format_safe_timestamp(self.environment.stats.start_time)}_"
+ f"{self.environment.locustfile}{host}.html"
)
return res

def _download_csv_suggest_file_name(suggest_filename_prefix: str) -> str:
"""Generate csv file download attachment filename suggestion.
Arguments:
suggest_filename_prefix: Prefix of the filename to suggest for saving the download. Will be appended with timestamp.
suggest_filename_prefix: Prefix of the filename to suggest for saving the download.
Will be appended with timestamp.
"""

return f"{suggest_filename_prefix}_{time()}.csv"
host = f"_{self.environment.host}" if self.environment.host else ""
return (
f"Locust_{format_safe_timestamp(self.environment.stats.start_time)}_"
+ f"{self.environment.locustfile}{host}_{suggest_filename_prefix}.csv"
)

def _download_csv_response(csv_data: str, filename_prefix: str) -> Response:
"""Generate csv file download response with 'csv_data'.
Expand Down
3 changes: 2 additions & 1 deletion locust/webui/src/pages/HtmlReport.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export default function HtmlReport({
showDownloadLink,
startTime,
endTime,
duration,
charts,
host,
exceptionsStatistics,
Expand Down Expand Up @@ -75,7 +76,7 @@ export default function HtmlReport({
<Box sx={{ display: 'flex', columnGap: 0.5 }}>
<Typography fontWeight={600}>During:</Typography>
<Typography>
{formatLocaleString(startTime)} - {formatLocaleString(endTime)}
{formatLocaleString(startTime)} - {formatLocaleString(endTime)} ({duration})
</Typography>
</Box>

Expand Down
2 changes: 1 addition & 1 deletion locust/webui/src/pages/tests/HtmlReport.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ describe('HtmlReport', () => {
getByText(
`${formatLocaleString(swarmReportMock.startTime)} - ${formatLocaleString(
swarmReportMock.endTime,
)}`,
)} (${swarmReportMock.duration})`,
),
).toBeTruthy();
});
Expand Down
3 changes: 2 additions & 1 deletion locust/webui/src/test/mocks/swarmState.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ export const swarmReportMock: IReport = {
locustfile: 'locustfile.py',
showDownloadLink: true,
startTime: '2024-02-26 12:13:26',
endTime: '2024-02-26 12:13:26',
endTime: '2024-02-26 13:27:14',
duration: '1 hour, 13 minutes and 48 seconds',
host: 'http://0.0.0.0:8089/',
exceptionsStatistics: [],
requestsStatistics: [],
Expand Down
1 change: 1 addition & 0 deletions locust/webui/src/types/swarm.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export interface IReport {
showDownloadLink: boolean;
startTime: string;
endTime: string;
duration: string;
host: string;
charts: ICharts;
requestsStatistics: ISwarmStat[];
Expand Down
Loading

0 comments on commit 903122a

Please sign in to comment.