Skip to content

Extract response time bucketing into an overridable function#3373

Merged
cyberw merged 3 commits intolocustio:masterfrom
thessem:james.littlejohn/extract-bucket-response-time
Mar 13, 2026
Merged

Extract response time bucketing into an overridable function#3373
cyberw merged 3 commits intolocustio:masterfrom
thessem:james.littlejohn/extract-bucket-response-time

Conversation

@thessem
Copy link
Copy Markdown
Contributor

@thessem thessem commented Mar 13, 2026

Locust rounds response times into histogram buckets in StatsEntry._log_response_time. The rounding logic is not overridable, which means users who need different bucketing granularity have no clean way to change it. This PR extracts it into a module-level function so it can be replaced at runtime, the same way PERCENTILES_TO_CHART can be.

Our (MongoDB) use-case has us sometimes measuring things in the high microseconds/very low milliseconds range all the way up to the low seconds range. To get the needed range we report microseconds to Locust (I'm aware this is a horrible hack, sorry). For tests that need to cover both extremes we end up with too many buckets because the rounding cuts off at round(response_time, -3). Extracting the function gives us an avenue to fix that.

I expect usage to look something like:

import locust.stats
from math import floor, log10

def sigfig_bucket(response_time):
    """Bucket to 3 significant figures."""
    if response_time == 0:
        return 0
    return int(round(response_time, -int(floor(log10(abs(response_time)))) + 2))

locust.stats.bucket_response_time = sigfig_bucket

I annotated the argument as int | float since this is now a public interface and it is easier to later narrow the arg type than to broaden it without breaking user implementations. Happy to drop the float if you'd prefer to keep it strict. I'd like to discuss plumbing float through the stack at some point separately.

The docs update might be more than needed and I haven't tested how it renders. Happy to trim that.

Fixes #2005 (closed as stale). Related to #255.


import locust.stats

def my_bucket_function(response_time):
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add type hint to the example maybe? my_bucket_function(response_time: int | float) -> int:

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, also made the nested import a little more normal.

@thessem thessem requested a review from cyberw March 13, 2026 12:37
self.assertEqual(bucket_response_time(response_time), expected, f"response_time={response_time}")

def test_custom_bucket_function_override(self):
original = locust.stats.bucket_response_time
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tiny improvement, but maybe use @mock.patch instead? like here:

@mock.patch.dict(os.environ, {"LOCUST_HOST": "http://localhost"})

@cyberw
Copy link
Copy Markdown
Collaborator

cyberw commented Mar 13, 2026

Looks great. Made some minor comments, happy to merge if you can have a look at those.

@cyberw cyberw merged commit 35e0309 into locustio:master Mar 13, 2026
18 checks passed
@cyberw
Copy link
Copy Markdown
Collaborator

cyberw commented Mar 13, 2026

Nice!

@thessem thessem deleted the james.littlejohn/extract-bucket-response-time branch March 13, 2026 23:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow to customize rounded_response_time

2 participants