Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Report name #2947

Merged
merged 10 commits into from
Nov 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"
)
obriat marked this conversation as resolved.
Show resolved Hide resolved
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
Loading