Repository: locustio/locust

Issue:
No longer logging interval stats when using LoadTestShape after 2.1.x
### Describe the bug
If I use any LoadTestShape test in 2.1.0 it will log interval statistics (CSV) every X seconds depending on CSV_STATS_INTERVAL_SEC.

However, from 2.2 (up to and including 2.4) it will not log the interval stats. 

### Expected behavior
Should see output like so: 
```
 Aggregated                                                                            48     0(0.00%)  |     331      75    1089      99  |    2.10    0.00
----------------------------------------------------------------------------------------------------------------------------------------------------------------
 GET /                                                                                 48     0(0.00%)  |     331      75    1089      99  |    2.10    0.00
----------------------------------------------------------------------------------------------------------------------------------------------------------------
 Name                                                                              # reqs      # fails  |     Avg     Min     Max  Median  |   req/s failures/s

 Aggregated                                                                            44     0(0.00%)  |     353      75    1089     100  |    2.10    0.00
----------------------------------------------------------------------------------------------------------------------------------------------------------------
 GET /                                                                                 44     0(0.00%)  |     353      75    1089     100  |    2.10    0.00
----------------------------------------------------------------------------------------------------------------------------------------------------------------
```

### Actual behavior
You see the shape launching debug info, but no aggregated stats etc.

### Steps to reproduce
Use any LoadTestShape test, for example: 
```
import math
from locust import HttpUser, TaskSet, task, between
from locust import LoadTestShape

class QuickstartUser(HttpUser):
    wait_time = between(5, 9)

    @task(1)
    def index_page(self):
        self.client.get("/")


class DoubleWave(LoadTestShape):
    """
    A shape to imitate some specific user behavior. In this example, midday
    and evening meal times. First peak of users appear at time_limit/3 and
    second peak appears at 2*time_limit/3
    Settings:
        min_users -- minimum users
        peak_one_users -- users in first peak
        peak_two_users -- users in second peak
        time_limit -- total length of test
    """

    min_users = 20
    peak_one_users = 60
    peak_two_users = 40
    time_limit = 300

    def tick(self):
        run_time = round(self.get_run_time())

        if run_time < self.time_limit:
            user_count = (
                (self.peak_one_users - self.min_users)
                * math.e ** -(((run_time / (self.time_limit / 10 * 2 / 3)) - 5) ** 2)
                + (self.peak_two_users - self.min_users)
                * math.e ** -(((run_time / (self.time_limit / 10 * 2 / 3)) - 10) ** 2)
                + self.min_users
            )
            return (round(user_count), round(user_count))
        else:
            return None    
```

### Environment

- OS: Linux
- Python version: 3
- Locust version: 2.2, 2.4
- Locust command line that you ran: ```/usr/local/bin/locust -f ./shape.py --host https://testsite:443 --headless --csv=mycsv --csv-full-history -LDEBUG```
- Locust file contents (anonymized if necessary): above


Discussion:
Probably my fault (when refactoring/introducing —autostart). I’ll have a look in the coming week. 

Resolving patch:
diff --git a/locust/main.py b/locust/main.py
--- a/locust/main.py
+++ b/locust/main.py
@@ -335,6 +335,11 @@ def spawn_run_time_quit_greenlet():
         gevent.spawn_later(options.run_time, stop_and_optionally_quit).link_exception(greenlet_exception_handler)
 
     headless_master_greenlet = None
+    stats_printer_greenlet = None
+    if not options.only_summary and (options.print_stats or (options.headless and not options.worker)):
+        # spawn stats printing greenlet
+        stats_printer_greenlet = gevent.spawn(stats_printer(runner.stats))
+        stats_printer_greenlet.link_exception(greenlet_exception_handler)
 
     def start_automatic_run():
         if options.master:
@@ -400,12 +405,6 @@ def start_automatic_run():
         )
         input_listener_greenlet.link_exception(greenlet_exception_handler)
 
-    stats_printer_greenlet = None
-    if not options.only_summary and (options.print_stats or (options.headless and not options.worker)):
-        # spawn stats printing greenlet
-        stats_printer_greenlet = gevent.spawn(stats_printer(runner.stats))
-        stats_printer_greenlet.link_exception(greenlet_exception_handler)
-
     if options.csv_prefix:
         gevent.spawn(stats_csv_writer.stats_writer).link_exception(greenlet_exception_handler)
 


Test patch:
diff --git a/locust/test/test_main.py b/locust/test/test_main.py
--- a/locust/test/test_main.py
+++ b/locust/test/test_main.py
@@ -394,9 +394,11 @@ def tick(self):
             _, stderr = proc.communicate()
             stderr = stderr.decode("utf-8")
             self.assertIn("Shape test updating to 10 users at 1.00 spawn rate", stderr)
-            self.assertIn("Cleaning up runner...", stderr)
+            self.assertTrue(success, "Got timeout and had to kill the process")
+            # ensure stats printer printed at least one report before shutting down and that there was a final report printed as well
+            self.assertRegex(stderr, r".*Aggregated[\S\s]*Shutting down[\S\s]*Aggregated.*")
+            self.assertIn("Shutting down (exit code 0)", stderr)
             self.assertEqual(0, proc.returncode)
-            self.assertTrue(success, "got timeout and had to kill the process")
 
     def test_autostart_wo_run_time(self):
         port = get_free_tcp_port()