Repository: locustio/locust

Issue:
More information in csv reports
### Problem
I am going to run tests with locust without web ui and it would be great to have much more detail information in locust csv report to create some charts on my own.

### What I need
I would like to have in `*_stats_history.csv` file information about:
- how many users are currently running (especially for step mode)
- statistics grouped by type and name (currently they are always set to `None,Aggregated`)

### Alternatives
As an alternative I have considered console output, but it is not very convienient for parsing.



Discussion:
Hi, I also would agree with alien11689's request.
It would certainly but great to have how many users running per time stamp or something like that so it can be shown on a graph.
I have run some tests on current master with option '--csv=result --csv-full-history' then I receive error `StatsEntry.use_response_times_cache must be set to True if we should be able to calculate the _current_ response time percentile`

When I change property from the error:

```diff
-    def __init__(self, stats, name, method, use_response_times_cache=False):
+    def __init__(self, stats, name, method, use_response_times_cache=True):
```

Specific entries for tasks are collected, but after a few seconds percentiles are set to 0:

```csv
"Type","Name","Timestamp","# requests","# failures","Requests/s","Requests Failed/s","Median response time","Average response time","Min response time","Max response time","Average Content Size","50%","66%","75%","80%","90%","95%","98%","99%","99.9%","99.99%","99.999","100%"
"None","Aggregated","1585134213",0,0,0.00,0.00,0,0,0,0.00,0.00,"N/A","N/A","N/A","N/A","N/A","N/A","N/A","N/A","N/A","N/A","N/A","N/A"
"GET cached client","Get 1","1585134215",776,0,0.00,0.00,20,28,11,144.00,3154.00,0,0,0,0,0,0,0,0,0,0,0,0
"GET new client","Get 1","1585134215",714,0,0.00,0.00,35,44,25,144.00,3154.00,0,0,0,0,0,0,0,0,0,0,0,0
"None","Aggregated","1585134215",1490,0,0.00,0.00,31,36,11,144.00,3154.00,31,36,41,48,66,75,130,130,140,140,140,140
"GET cached client","Get 1","1585134217",886,0,0.00,0.00,21,35,11,402.00,3154.00,21,33,41,47,69,130,140,150,400,400,400,400
"GET new client","Get 1","1585134217",814,0,0.00,0.00,37,51,25,402.00,3154.00,37,52,64,68,83,130,140,150,400,400,400,400
"None","Aggregated","1585134217",1700,0,0.00,0.00,33,42,11,402.00,3154.00,33,40,52,61,76,130,140,150,400,400,400,400
"GET cached client","Get 1","1585134219",3107,0,586.00,0.00,61,74,11,3546.00,3154.00,61,73,87,91,110,160,210,300,3500,3500,3500,3500
"GET new client","Get 1","1585134219",2978,0,563.33,0.00,110,120,25,3546.00,3154.00,110,130,160,170,200,210,230,480,3500,3500,3500,3500
"None","Aggregated","1585134219",6085,0,1149.33,0.00,70,96,11,3546.00,3154.00,70,97,130,130,180,200,220,380,3500,3500,3500,3500
"GET cached client","Get 1","1585134221",4930,0,632.20,0.00,90,99,11,3546.00,3154.00,90,110,110,120,150,200,360,440,3500,3500,3500,3500
"GET new client","Get 1","1585134221",4689,0,601.60,0.00,170,173,25,3546.00,3154.00,170,200,220,230,260,300,540,1200,1300,3500,3500,3500
"None","Aggregated","1585134221",9619,0,1233.80,0.00,110,135,11,3546.00,3154.00,110,140,180,200,230,270,410,600,1700,3500,3500,3500
"GET cached client","Get 1","1585134223",5338,0,654.83,0.00,95,108,11,3546.00,3154.00,95,110,120,130,160,260,410,510,3500,3500,3500,3500
"GET new client","Get 1","1585134223",5045,0,628.17,0.00,180,185,25,3546.00,3154.00,180,210,230,240,280,310,680,1200,1300,3500,3500,3500
"None","Aggregated","1585134223",10383,0,1283.00,0.00,110,146,11,3546.00,3154.00,110,150,190,210,250,300,500,750,1700,3500,3500,3500
"GET cached client","Get 1","1585134225",7505,0,682.11,0.00,120,144,11,3546.00,3154.00,120,150,150,150,250,440,590,790,1800,3500,3500,3500
"GET new client","Get 1","1585134225",7209,0,650.56,0.00,220,266,25,3546.00,3154.00,220,280,300,300,310,770,1300,1300,3300,3500,3500,3500
"None","Aggregated","1585134225",14714,0,1332.67,0.00,150,204,11,3546.00,3154.00,150,220,280,300,310,600,1300,1300,3300,3500,3500,3500
"GET cached client","Get 1","1585134227",9140,0,723.40,0.00,140,162,11,4007.00,3154.00,140,150,150,160,300,500,670,1000,3300,4000,4000,4000
"GET new client","Get 1","1585134227",8870,0,698.10,0.00,270,322,25,4149.00,3154.00,270,300,300,310,700,1300,1300,1800,3500,4100,4100,4100
"None","Aggregated","1585134227",18010,0,1421.50,0.00,150,241,11,4149.00,3154.00,220,300,300,310,540,830,1300,1800,3300,4000,4100,4100
"GET cached client","Get 1","1585134229",9631,0,722.40,0.00,140,168,11,4049.00,3154.00,140,150,150,160,300,510,750,1100,3300,4000,4000,4000
"GET new client","Get 1","1585134229",9381,0,700.30,0.00,280,334,25,4149.00,3154.00,280,300,310,310,760,1300,1300,1800,3500,4100,4100,4100
"None","Aggregated","1585134229",19012,0,1422.70,0.00,150,250,11,4149.00,3154.00,230,300,300,310,600,1200,1300,1800,3300,4000,4100,4100
"GET cached client","Get 1","1585134231",11765,0,724.50,0.00,150,180,11,7442.00,3154.00,150,150,160,160,310,520,770,1100,3500,6100,7400,7400
"GET new client","Get 1","1585134231",11564,0,714.90,0.00,300,394,25,7960.00,3154.00,300,300,310,310,790,1300,1800,3300,7400,7600,8000,8000
"None","Aggregated","1585134231",23329,0,1439.40,0.00,160,286,11,7960.00,3154.00,300,310,310,320,790,1300,1800,3300,7400,7600,8000,8000
"GET cached client","Get 1","1585134233",13370,0,710.80,0.00,150,186,11,8464.00,3154.00,0,0,0,0,0,0,0,0,0,0,0,0
"GET new client","Get 1","1585134233",13220,0,717.80,0.00,300,427,25,7960.00,3154.00,0,0,0,0,0,0,0,0,0,0,0,0
"None","Aggregated","1585134233",26590,0,1428.60,0.00,160,306,11,8464.00,3154.00,300,310,310,470,810,1300,2200,3300,7400,8000,8500,8500
"GET cached client","Get 1","1585134235",13883,0,709.60,0.00,150,187,11,8464.00,3154.00,0,0,0,0,0,0,0,0,0,0,0,0
"GET new client","Get 1","1585134235",13742,0,718.30,0.00,300,434,25,7960.00,3154.00,0,0,0,0,0,0,0,0,0,0,0,0
"None","Aggregated","1585134235",27625,0,1427.90,0.00,160,310,11,8464.00,3154.00,300,310,310,460,810,1300,2300,3300,7500,8000,8500,8500

```
I have found that the problem is with `response_times_cache` of class `class StatsEntry`. It keeps only one one entry instead of last N stats. I added prints in method `get_current_response_time_percentile`:

```python
    if percent == 0.5:
            print("Acceptable timestamps %s" % (acceptable_timestamps))
            print("Response time cache %s" % (self.response_times_cache.keys()))
```

and in master logs I saw only at the beginning:

```
[2020-03-25 13:46:50,422] dpr-asus/INFO/stdout: Acceptable timestamps [1585140400, 1585140400, 1585140399, 1585140401, 1585140398, 1585140402, 1585140397, 1585140403, 1585140396, 1585140404, 1585140395, 1585140405, 1585140394, 1585140406, 1585140393, 1585140407, 1585140392, 1585140408]
[2020-03-25 13:46:50,423] dpr-asus/INFO/stdout: 
[2020-03-25 13:46:50,423] dpr-asus/INFO/stdout: Response time cache odict_keys([1585140407])
```
and at the end:

```
[2020-03-25 13:47:37,922] dpr-asus/INFO/stdout: Acceptable timestamps [1585140447, 1585140447, 1585140446, 1585140448, 1585140445, 1585140449, 1585140444, 1585140450, 1585140443, 1585140451, 1585140442, 1585140452, 1585140441, 1585140453, 1585140440, 1585140454, 1585140439, 1585140455]
[2020-03-25 13:47:37,922] dpr-asus/INFO/stdout: 
[2020-03-25 13:47:37,922] dpr-asus/INFO/stdout: Response time cache odict_keys([1585140407])
```
I have created PR: https://github.com/locustio/locust/pull/1297
I added total_users count for detailed stats.
> I would like to have in *_stats_history.csv file information about:
> * how many users are currently running (especially for step mode)

This feature makes sense.

> I have run some tests on current master with option '--csv=result --csv-full-history' then I receive error StatsEntry.use_response_times_cache must be set to True if we should be able to calculate the _current_ response time percentile

Ah, you've discovered a bug. I've opened up a new issue for this: #1315 

Resolving patch:
diff --git a/locust/main.py b/locust/main.py
--- a/locust/main.py
+++ b/locust/main.py
@@ -260,7 +260,7 @@ def timelimit_stop():
         stats_printer_greenlet = gevent.spawn(stats_printer(runner.stats))
 
     if options.csvfilebase:
-        gevent.spawn(stats_writer, runner.stats, options.csvfilebase, full_history=options.stats_history_enabled)
+        gevent.spawn(stats_writer, environment, options.csvfilebase, full_history=options.stats_history_enabled)
 
     
     def shutdown(code=0):
@@ -278,7 +278,7 @@ def shutdown(code=0):
         print_stats(runner.stats, current=False)
         print_percentile_stats(runner.stats)
         if options.csvfilebase:
-            write_csv_files(runner.stats, options.csvfilebase, options.stats_history_enabled)
+            write_csv_files(environment, options.csvfilebase, full_history=options.stats_history_enabled)
         print_error_report(runner.stats)
         sys.exit(code)
     
diff --git a/locust/stats.py b/locust/stats.py
--- a/locust/stats.py
+++ b/locust/stats.py
@@ -751,25 +751,25 @@ def stats_printer_func():
             gevent.sleep(CONSOLE_STATS_INTERVAL_SEC)
     return stats_printer_func
 
-def stats_writer(stats, base_filepath, full_history=False):
+def stats_writer(environment, base_filepath, full_history=False):
     """Writes the csv files for the locust run."""
     with open(base_filepath + '_stats_history.csv', 'w') as f:
         f.write(stats_history_csv_header())
     while True:
-        write_csv_files(stats, base_filepath, full_history)
+        write_csv_files(environment, base_filepath, full_history)
         gevent.sleep(CSV_STATS_INTERVAL_SEC)
 
 
-def write_csv_files(stats, base_filepath, full_history=False):
+def write_csv_files(environment, base_filepath, full_history=False):
     """Writes the requests, distribution, and failures csvs."""
     with open(base_filepath + '_stats.csv', 'w') as f:
-        f.write(requests_csv(stats))
+        f.write(requests_csv(environment.runner.stats))
 
     with open(base_filepath + '_stats_history.csv', 'a') as f:
-        f.write(stats_history_csv(stats, full_history) + "\n")
+        f.write(stats_history_csv(environment, full_history) + "\n")
 
     with open(base_filepath + '_failures.csv', 'w') as f:
-        f.write(failures_csv(stats))
+        f.write(failures_csv(environment.runner.stats))
 
 
 def sort_stats(stats):
@@ -831,13 +831,15 @@ def requests_csv(stats):
         ))
     return "\n".join(rows)
 
+
 def stats_history_csv_header():
     """Headers for the stats history CSV"""
 
     return ','.join((
+        '"Timestamp"',
+        '"User count"',
         '"Type"',
         '"Name"',
-        '"Timestamp"',
         '"# requests"',
         '"# failures"',
         '"Requests/s"',
@@ -861,34 +863,31 @@ def stats_history_csv_header():
         '"100%"'
     )) + '\n'
 
-def stats_history_csv(stats, stats_history_enabled=False, csv_for_web_ui=False):
-    """Returns the Aggregated stats entry every interval"""
-    # csv_for_web_ui boolean returns the header along with the stats history row so that
-    # it can be returned as a csv for download on the web ui. Otherwise when run with
-    # the '--headless' option we write the header first and then append the file with stats
-    # entries every interval.
-    if csv_for_web_ui:
-        rows = [stats_history_csv_header()]
-    else:
-        rows = []
-
+def stats_history_csv(environment, all_entries=False):
+    """
+    Return a string of CSV rows with the *current* stats. By default only includes the 
+    Aggregated stats entry, but if all_entries is set to True, a row for each entry will 
+    will be included.
+    """
+    stats = environment.runner.stats
     timestamp = int(time.time())
-    stats_entries_per_iteration = []
-
-    if stats_history_enabled:
-        stats_entries_per_iteration = sort_stats(stats.entries)
-
-    for s in chain(stats_entries_per_iteration, [stats.total]):
+    stats_entries = []
+    if all_entries:
+        stats_entries = sort_stats(stats.entries)
+    
+    rows = []
+    for s in chain(stats_entries, [stats.total]):
         if s.num_requests:
             percentile_str = ','.join([
                 str(int(s.get_current_response_time_percentile(x) or 0)) for x in PERCENTILES_TO_REPORT])
         else:
             percentile_str = ','.join(['"N/A"'] * len(PERCENTILES_TO_REPORT))
 
-        rows.append('"%s","%s","%s",%i,%i,%.2f,%.2f,%i,%i,%i,%.2f,%.2f,%s' % (
+        rows.append('"%i","%i","%s","%s",%i,%i,%.2f,%.2f,%i,%i,%i,%.2f,%.2f,%s' % (
+            timestamp,
+            environment.runner.user_count,
             s.method,
             s.name,
-            timestamp,
             s.num_requests,
             s.num_failures,
             s.current_rps,
diff --git a/locust/web.py b/locust/web.py
--- a/locust/web.py
+++ b/locust/web.py
@@ -23,7 +23,7 @@
 
 from . import runners
 from .runners import MasterLocustRunner
-from .stats import failures_csv, median_from_dict, requests_csv, sort_stats, stats_history_csv
+from .stats import failures_csv, median_from_dict, requests_csv, sort_stats
 from .util.cache import memoize
 from .util.rounding import proper_round
 from .util.timespan import parse_timespan
@@ -120,15 +120,6 @@ def request_stats_csv():
             response.headers["Content-disposition"] = disposition
             return response
         
-        @app.route("/stats/stats_history/csv")
-        def stats_history_stats_csv():
-            response = make_response(stats_history_csv(self.environment.runner.stats, False, True))
-            file_name = "stats_history_{0}.csv".format(time())
-            disposition = "attachment;filename={0}".format(file_name)
-            response.headers["Content-type"] = "text/csv"
-            response.headers["Content-disposition"] = disposition
-            return response
-        
         @app.route("/stats/failures/csv")
         def failures_stats_csv():
             response = make_response(failures_csv(self.environment.runner.stats))


Test patch:
diff --git a/locust/test/test_stats.py b/locust/test/test_stats.py
--- a/locust/test/test_stats.py
+++ b/locust/test/test_stats.py
@@ -10,10 +10,11 @@
 from locust.core import HttpLocust, TaskSet, task, Locust
 from locust.env import Environment
 from locust.inspectlocust import get_task_ratio_dict
-from locust.runners import MasterLocustRunner
+from locust.runners import LocalLocustRunner, MasterLocustRunner
 from locust.rpc.protocol import Message
 from locust.stats import CachedResponseTimes, RequestStats, StatsEntry, diff_response_time_dicts, stats_writer
 from locust.test.testcases import LocustTestCase
+from locust.wait_time import constant
 
 from .testcases import WebserverTestCase
 from .test_runners import mocked_options, mocked_rpc
@@ -286,7 +287,7 @@ def test_print_percentile_stats(self):
         self.assertEqual(len(headlines), len(info[5].split()))
 
 
-class TestWriteStatCSVs(LocustTestCase):
+class TestCsvStats(LocustTestCase):
     STATS_BASE_NAME = "test"
     STATS_FILENAME = "{}_stats.csv".format(STATS_BASE_NAME)
     STATS_HISTORY_FILENAME = "{}_stats_history.csv".format(STATS_BASE_NAME)
@@ -294,23 +295,11 @@ class TestWriteStatCSVs(LocustTestCase):
 
     def setUp(self):
         super().setUp()
-        class User(Locust):
-            setup_run_count = 0
-            task_run_count = 0
-            locust_error_count = 0
-            wait_time = locust.wait_time.constant(1)
-            @task
-            def my_task(self):
-                User.task_run_count += 1
-        
-        self.environment = Environment(options=mocked_options())
-        locust.runners.locust_runner = locust.runners.LocalLocustRunner(self.environment, [User])
         self.remove_file_if_exists(self.STATS_FILENAME)
         self.remove_file_if_exists(self.STATS_HISTORY_FILENAME)
         self.remove_file_if_exists(self.STATS_FAILURES_FILENAME)
 
     def tearDown(self):
-        locust.runners.locust_runner.quit()
         self.remove_file_if_exists(self.STATS_FILENAME)
         self.remove_file_if_exists(self.STATS_HISTORY_FILENAME)
         self.remove_file_if_exists(self.STATS_FAILURES_FILENAME)
@@ -320,53 +309,53 @@ def remove_file_if_exists(self, filename):
             os.remove(filename)
 
     def test_write_csv_files(self):
-        locust.stats.write_csv_files(self.runner.stats, self.STATS_BASE_NAME)
+        locust.stats.write_csv_files(self.environment, self.STATS_BASE_NAME)
         self.assertTrue(os.path.exists(self.STATS_FILENAME))
         self.assertTrue(os.path.exists(self.STATS_HISTORY_FILENAME))
         self.assertTrue(os.path.exists(self.STATS_FAILURES_FILENAME))
     
     def test_write_csv_files_full_history(self):
-        locust.stats.write_csv_files(self.runner.stats, self.STATS_BASE_NAME, full_history=True)
+        locust.stats.write_csv_files(self.environment, self.STATS_BASE_NAME, full_history=True)
         self.assertTrue(os.path.exists(self.STATS_FILENAME))
         self.assertTrue(os.path.exists(self.STATS_HISTORY_FILENAME))
         self.assertTrue(os.path.exists(self.STATS_FAILURES_FILENAME))
     
+    @mock.patch("locust.stats.CSV_STATS_INTERVAL_SEC", new=0.2)
     def test_csv_stats_writer(self):
-        with mock.patch("locust.stats.CSV_STATS_INTERVAL_SEC", new=0.2):
-            greenlet = gevent.spawn(stats_writer, self.runner.stats, self.STATS_BASE_NAME)
-            gevent.sleep(0.21)
-            gevent.kill(greenlet)
-            self.assertTrue(os.path.exists(self.STATS_FILENAME))
-            self.assertTrue(os.path.exists(self.STATS_HISTORY_FILENAME))
-            self.assertTrue(os.path.exists(self.STATS_FAILURES_FILENAME))
-            
-            with open(self.STATS_HISTORY_FILENAME) as f:
-                reader = csv.DictReader(f)
-                rows = [r for r in reader]
-            
-            self.assertEqual(2, len(rows))
-            self.assertEqual("Aggregated", rows[0]["Name"])
-            self.assertEqual("Aggregated", rows[1]["Name"])
+        greenlet = gevent.spawn(stats_writer, self.environment, self.STATS_BASE_NAME)
+        gevent.sleep(0.21)
+        gevent.kill(greenlet)
+        self.assertTrue(os.path.exists(self.STATS_FILENAME))
+        self.assertTrue(os.path.exists(self.STATS_HISTORY_FILENAME))
+        self.assertTrue(os.path.exists(self.STATS_FAILURES_FILENAME))
+        
+        with open(self.STATS_HISTORY_FILENAME) as f:
+            reader = csv.DictReader(f)
+            rows = [r for r in reader]
+        
+        self.assertEqual(2, len(rows))
+        self.assertEqual("Aggregated", rows[0]["Name"])
+        self.assertEqual("Aggregated", rows[1]["Name"])
     
+    @mock.patch("locust.stats.CSV_STATS_INTERVAL_SEC", new=0.2)
     def test_csv_stats_writer_full_history(self):
         self.runner.stats.log_request("GET", "/", 10, content_length=666)
-        with mock.patch("locust.stats.CSV_STATS_INTERVAL_SEC", new=0.2):
-            greenlet = gevent.spawn(stats_writer, self.runner.stats, self.STATS_BASE_NAME, full_history=True)
-            gevent.sleep(0.21)
-            gevent.kill(greenlet)
-            self.assertTrue(os.path.exists(self.STATS_FILENAME))
-            self.assertTrue(os.path.exists(self.STATS_HISTORY_FILENAME))
-            self.assertTrue(os.path.exists(self.STATS_FAILURES_FILENAME))
-            
-            with open(self.STATS_HISTORY_FILENAME) as f:
-                reader = csv.DictReader(f)
-                rows = [r for r in reader]
-            
-            self.assertEqual(4, len(rows))
-            self.assertEqual("/", rows[0]["Name"])
-            self.assertEqual("Aggregated", rows[1]["Name"])
-            self.assertEqual("/", rows[2]["Name"])
-            self.assertEqual("Aggregated", rows[3]["Name"])
+        greenlet = gevent.spawn(stats_writer, self.environment, self.STATS_BASE_NAME, full_history=True)
+        gevent.sleep(0.21)
+        gevent.kill(greenlet)
+        self.assertTrue(os.path.exists(self.STATS_FILENAME))
+        self.assertTrue(os.path.exists(self.STATS_HISTORY_FILENAME))
+        self.assertTrue(os.path.exists(self.STATS_FAILURES_FILENAME))
+        
+        with open(self.STATS_HISTORY_FILENAME) as f:
+            reader = csv.DictReader(f)
+            rows = [r for r in reader]
+        
+        self.assertEqual(4, len(rows))
+        self.assertEqual("/", rows[0]["Name"])
+        self.assertEqual("Aggregated", rows[1]["Name"])
+        self.assertEqual("/", rows[2]["Name"])
+        self.assertEqual("Aggregated", rows[3]["Name"])
     
     def test_csv_stats_on_master_from_aggregated_stats(self):
         # Failing test for: https://github.com/locustio/locust/issues/1315
@@ -386,10 +375,45 @@ def test_csv_stats_on_master_from_aggregated_stats(self):
             s = master.stats.get("/", "GET")
             self.assertEqual(700, s.median_response_time)
             
-            locust.stats.write_csv_files(master.stats, self.STATS_BASE_NAME, full_history=True)
+            locust.stats.write_csv_files(self.environment, self.STATS_BASE_NAME, full_history=True)
             self.assertTrue(os.path.exists(self.STATS_FILENAME))
             self.assertTrue(os.path.exists(self.STATS_HISTORY_FILENAME))
             self.assertTrue(os.path.exists(self.STATS_FAILURES_FILENAME))
+    
+    @mock.patch("locust.stats.CSV_STATS_INTERVAL_SEC", new=0.2)
+    def test_user_count_in_csv_history_stats(self):
+        start_time = int(time.time())
+        class TestUser(Locust):
+            wait_time = constant(10)
+            @task
+            def t(self):
+                self.environment.runner.stats.log_request("GET", "/", 10, 10)
+        runner = LocalLocustRunner(self.environment, [TestUser])
+        runner.start(3, 5) # spawn a user every 0.2 second
+        gevent.sleep(0.1)
+        
+        greenlet = gevent.spawn(stats_writer, self.environment, self.STATS_BASE_NAME, full_history=True)
+        gevent.sleep(0.6)
+        gevent.kill(greenlet)
+        
+        runner.stop()
+        
+        with open(self.STATS_HISTORY_FILENAME) as f:
+            reader = csv.DictReader(f)
+            rows = [r for r in reader]
+        
+        self.assertEqual(6, len(rows))
+        for i in range(3):
+            row = rows.pop(0)
+            self.assertEqual("%i" % (i + 1), row["User count"])
+            self.assertEqual("/", row["Name"])
+            self.assertEqual("%i" % (i + 1), row["# requests"])
+            self.assertGreaterEqual(int(row["Timestamp"]), start_time)
+            row = rows.pop(0)
+            self.assertEqual("%i" % (i + 1), row["User count"])
+            self.assertEqual("Aggregated", row["Name"])
+            self.assertEqual("%i" % (i + 1), row["# requests"])
+            self.assertGreaterEqual(int(row["Timestamp"]), start_time)
 
 
 class TestStatsEntryResponseTimesCache(unittest.TestCase):
diff --git a/locust/test/test_web.py b/locust/test/test_web.py
--- a/locust/test/test_web.py
+++ b/locust/test/test_web.py
@@ -108,11 +108,6 @@ def test_request_stats_csv(self):
         response = requests.get("http://127.0.0.1:%i/stats/requests/csv" % self.web_port)
         self.assertEqual(200, response.status_code)
 
-    def test_request_stats_history_csv(self):
-        self.stats.log_request("GET", "/test2", 120, 5612)
-        response = requests.get("http://127.0.0.1:%i/stats/stats_history/csv" % self.web_port)
-        self.assertEqual(200, response.status_code)
-
     def test_failure_stats_csv(self):
         self.stats.log_error("GET", "/", Exception("Error1337"))
         response = requests.get("http://127.0.0.1:%i/stats/failures/csv" % self.web_port)