Repository: locustio/locust

Issue:
The Charts presentation in the report downloaded by locust is problematic
### Prerequisites

- [X] I am using [the latest version of Locust](https://github.com/locustio/locust/releases/)
- [X] I am reporting a bug, not asking a question

### Description

When I used the V1.0.0 version, the downloaded charts appeared fine
![brznKr7O6H](https://github.com/locustio/locust/assets/149132232/c76671ee-7123-4a40-ad08-3b5e19b85a83)

When I used the newer version (2.27, 2.25), the downloaded chart was not fully displayed, and I tried the lower version 2.19, which had the same problem
![WisXLzPws4](https://github.com/locustio/locust/assets/149132232/3806fd58-62bf-42c2-a242-446180d5a03c)
![image](https://github.com/locustio/locust/assets/149132232/88e36e6d-bdd0-405b-91c8-e63d2960bf80)


### Command line

 locust --tag post/pipelines

### Locustfile contents

```python3
class QuickstartUser(HttpUser):
    wait_time = constant(1)

    @task
    @tag("post/pipelines")
    def create_pipeline(self):
        self.client.post("/pipelines", json={
            "name": f"pressure_test_pipeline_{int(time.time())}",
            "version": "1.0.0",
            "title": "pressure_test_pipeline_title",
            "detailLink": "",
            "maintainer": "tester",
            "dag": "pipeline-bidsapp-bidsinfo",
            "isPublic": True
        })
```


### Python version

3.9

### Locust version

2.27

### Operating system

Windows11


Discussion:
Hi!

Is this an issue with the downloaded report or also the live view?
Hi~
When I click on "Download Report" and then preview the html report I have a problem with Charts, and of course the same thing happens after the download.

I think it's caused by the run time being too short, because I only ran it for 5 seconds, and when I ran it for 10s the charts were displayed properly

[run time: 5s]
![image](https://github.com/locustio/locust/assets/149132232/9375ce8d-5eca-4c5d-bbb6-ddd1107931c9)

[run time: 10s]
![image](https://github.com/locustio/locust/assets/149132232/8d75e739-dddc-4e79-acc4-bdda43ead111)

@andrewbaldwin44 if you have time, can you have a look at what happens in the RPS graph with short run times? Super-short run times arent that common I think, but the line/dot should not be missing completely or be zero.

Resolving patch:
diff --git a/locust/html.py b/locust/html.py
--- a/locust/html.py
+++ b/locust/html.py
@@ -10,7 +10,7 @@
 
 from . import stats as stats_module
 from .runners import STATE_STOPPED, STATE_STOPPING, MasterRunner
-from .stats import sort_stats
+from .stats import sort_stats, update_stats_history
 from .user.inspectuser import get_ratio
 
 PERCENTILES_FOR_HTML_REPORT = [0.50, 0.6, 0.7, 0.8, 0.9, 0.95, 0.99, 1.0]
@@ -54,6 +54,7 @@ def get_html_report(
         {**exc, "nodes": ", ".join(exc["nodes"])} for exc in environment.runner.exceptions.values()
     ]
 
+    update_stats_history(environment.runner)
     history = stats.history
 
     static_js = []
diff --git a/locust/stats.py b/locust/stats.py
--- a/locust/stats.py
+++ b/locust/stats.py
@@ -907,28 +907,32 @@ def sort_stats(stats: dict[Any, S]) -> list[S]:
     return [stats[key] for key in sorted(stats.keys())]
 
 
+def update_stats_history(runner: Runner) -> None:
+    stats = runner.stats
+    current_response_time_percentiles = {
+        f"response_time_percentile_{percentile}": stats.total.get_current_response_time_percentile(percentile) or 0
+        for percentile in PERCENTILES_TO_CHART
+    }
+
+    r = {
+        **current_response_time_percentiles,
+        "time": datetime.datetime.now(tz=datetime.timezone.utc).strftime("%H:%M:%S"),
+        "current_rps": stats.total.current_rps or 0,
+        "current_fail_per_sec": stats.total.current_fail_per_sec or 0,
+        "total_avg_response_time": stats.total.avg_response_time,
+        "user_count": runner.user_count or 0,
+    }
+    stats.history.append(r)
+
+
 def stats_history(runner: Runner) -> None:
     """Save current stats info to history for charts of report."""
     while True:
-        stats = runner.stats
-        if not stats.total.use_response_times_cache:
+        if not runner.stats.total.use_response_times_cache:
             break
         if runner.state != "stopped":
-            current_response_time_percentiles = {
-                f"response_time_percentile_{percentile}": stats.total.get_current_response_time_percentile(percentile)
-                or 0
-                for percentile in PERCENTILES_TO_CHART
-            }
-
-            r = {
-                **current_response_time_percentiles,
-                "time": datetime.datetime.now(tz=datetime.timezone.utc).strftime("%H:%M:%S"),
-                "current_rps": stats.total.current_rps or 0,
-                "current_fail_per_sec": stats.total.current_fail_per_sec or 0,
-                "total_avg_response_time": stats.total.avg_response_time,
-                "user_count": runner.user_count or 0,
-            }
-            stats.history.append(r)
+            update_stats_history(runner)
+
         gevent.sleep(HISTORY_STATS_INTERVAL_SEC)
 
 


Test patch: