# Buggy code
def setup_distributed_stats_event_listeners(events, stats):
    def on_report_to_master(client_id, data):
        data["stats"] = stats.serialize_stats()
        data["stats_total"] = stats.total.get_stripped_report()
        data["errors"] = stats.serialize_errors()
        stats.errors = {}

    def on_worker_report(client_id, data):
        for stats_data in data["stats"]:
            entry = StatsEntry.unserialize(stats_data)
            request_key = (entry.name, entry.method)
            if not request_key in stats.entries:
                stats.entries[request_key] = StatsEntry(stats, entry.name, entry.method)
            stats.entries[request_key].extend(entry)

        for error_key, error in data["errors"].items():
            if error_key not in stats.errors:
                stats.errors[error_key] = StatsError.from_dict(error)
            else:
                stats.errors[error_key].occurrences += error["occurrences"]

        # save the old last_request_timestamp, to see if we should store a new copy
        # of the response times in the response times cache
        old_last_request_timestamp = stats.total.last_request_timestamp
        # update the total StatsEntry
        stats.total.extend(StatsEntry.unserialize(data["stats_total"]))
        if stats.total.last_request_timestamp and stats.total.last_request_timestamp > (
            old_last_request_timestamp or 0
        ):
            # If we've entered a new second, we'll cache the response times. Note that there
            # might still be reports from other worker nodes - that contains requests for the same
            # time periods - that hasn't been received/accounted for yet. This will cause the cache to
            # lag behind a second or two, but since StatsEntry.current_response_time_percentile()
            # (which is what the response times cache is used for) uses an approximation of the
            # last 10 seconds anyway, it should be fine to ignore this.
            stats.total._cache_response_times(int(stats.total.last_request_timestamp))

    events.report_to_master.add_listener(on_report_to_master)
    events.worker_report.add_listener(on_worker_report)


# Traceback (ValueError)
[2020-04-06 13:01:45,700] Jonatans-Air.localdomain/ERROR/stderr: Traceback (most recent call last):
[2020-04-06 13:01:45,700] Jonatans-Air.localdomain/ERROR/stderr: File "/Users/heyman/virtualenvs/locust/bin/locust", line 11, in <module>
[2020-04-06 13:01:45,702] Jonatans-Air.localdomain/ERROR/stderr:
[2020-04-06 13:01:45,702] Jonatans-Air.localdomain/ERROR/stderr: load_entry_point('locustio', 'console_scripts', 'locust')()
[2020-04-06 13:01:45,702] Jonatans-Air.localdomain/ERROR/stderr:
[2020-04-06 13:01:45,702] Jonatans-Air.localdomain/ERROR/stderr: File "/Users/heyman/projects/locust/locust/main.py", line 297, in main
[2020-04-06 13:01:45,703] Jonatans-Air.localdomain/ERROR/stderr:
[2020-04-06 13:01:45,703] Jonatans-Air.localdomain/ERROR/stderr: shutdown(code=code)
[2020-04-06 13:01:45,703] Jonatans-Air.localdomain/ERROR/stderr:
[2020-04-06 13:01:45,704] Jonatans-Air.localdomain/ERROR/stderr: File "/Users/heyman/projects/locust/locust/main.py", line 281, in shutdown
[2020-04-06 13:01:45,705] Jonatans-Air.localdomain/ERROR/stderr:
[2020-04-06 13:01:45,705] Jonatans-Air.localdomain/ERROR/stderr: write_stat_csvs(runner.stats, options.csvfilebase, options.stats_history_enabled)
[2020-04-06 13:01:45,705] Jonatans-Air.localdomain/ERROR/stderr:
[2020-04-06 13:01:45,706] Jonatans-Air.localdomain/ERROR/stderr: File "/Users/heyman/projects/locust/locust/stats.py", line 757, in write_stat_csvs
[2020-04-06 13:01:45,706] Jonatans-Air.localdomain/ERROR/stderr:
[2020-04-06 13:01:45,706] Jonatans-Air.localdomain/ERROR/stderr: f.write(stats_history_csv(stats, stats_history_enabled) + "\n")
[2020-04-06 13:01:45,707] Jonatans-Air.localdomain/ERROR/stderr:
[2020-04-06 13:01:45,707] Jonatans-Air.localdomain/ERROR/stderr: File "/Users/heyman/projects/locust/locust/stats.py", line 871, in stats_history_csv
[2020-04-06 13:01:45,708] Jonatans-Air.localdomain/ERROR/stderr:
[2020-04-06 13:01:45,708] Jonatans-Air.localdomain/ERROR/stderr: percentile_str = ','.join([
[2020-04-06 13:01:45,708] Jonatans-Air.localdomain/ERROR/stderr:
[2020-04-06 13:01:45,708] Jonatans-Air.localdomain/ERROR/stderr: File "/Users/heyman/projects/locust/locust/stats.py", line 872, in <listcomp>
[2020-04-06 13:01:45,709] Jonatans-Air.localdomain/ERROR/stderr:
[2020-04-06 13:01:45,710] Jonatans-Air.localdomain/ERROR/stderr: str(int(s.get_current_response_time_percentile(x) or 0)) for x in PERCENTILES_TO_REPORT])
[2020-04-06 13:01:45,710] Jonatans-Air.localdomain/ERROR/stderr:
[2020-04-06 13:01:45,710] Jonatans-Air.localdomain/ERROR/stderr: File "/Users/heyman/projects/locust/locust/stats.py", line 508, in get_current_response_time_percentile
[2020-04-06 13:01:45,711] Jonatans-Air.localdomain/ERROR/stderr:
[2020-04-06 13:01:45,711] Jonatans-Air.localdomain/ERROR/stderr: raise ValueError("StatsEntry.use_response_times_cache must be set to True if we should be able to calculate the _current_ response time percentile")
[2020-04-06 13:01:45,711] Jonatans-Air.localdomain/ERROR/stderr:
[2020-04-06 13:01:45,711] Jonatans-Air.localdomain/ERROR/stderr: ValueError
[2020-04-06 13:01:45,711] Jonatans-Air.localdomain/ERROR/stderr: :
[2020-04-06 13:01:45,711] Jonatans-Air.localdomain/ERROR/stderr: StatsEntry.use_response_times_cache must be set to True if we should be able to calculate the _current_ response time percentile
[2020-04-06 13:01:45,711] Jonatans-Air.localdomain/ERROR/stderr:

# Fixed code
def setup_distributed_stats_event_listeners(events, stats):
    def on_report_to_master(client_id, data):
        data["stats"] = stats.serialize_stats()
        data["stats_total"] = stats.total.get_stripped_report()
        data["errors"] = stats.serialize_errors()
        stats.errors = {}

    def on_worker_report(client_id, data):
        for stats_data in data["stats"]:
            entry = StatsEntry.unserialize(stats_data)
            request_key = (entry.name, entry.method)
            if not request_key in stats.entries:
                stats.entries[request_key] = StatsEntry(
                    stats, entry.name, entry.method, use_response_times_cache=True
                )
            stats.entries[request_key].extend(entry)

        for error_key, error in data["errors"].items():
            if error_key not in stats.errors:
                stats.errors[error_key] = StatsError.from_dict(error)
            else:
                stats.errors[error_key].occurrences += error["occurrences"]

        stats.total.extend(StatsEntry.unserialize(data["stats_total"]))

    events.report_to_master.add_listener(on_report_to_master)
    events.worker_report.add_listener(on_worker_report)