Locust's startup path carries the highest activity risk — 5 functions to address first

A single function, main in locust/main.py, has cyclomatic complexity 89 and sits in the fire quadrant, while the other four top hotspots range from CC 12 to 25.

Stephen Collins ·
Generated by hotspots · free & open source
pip
$ pip install hotspots-cli
Activity Risk18.19Low
Hottest Functionmain

Antipatterns Detected

complex_branching8exit_heavy5god_function4long_function4deeply_nested1

Run this on your own codebase

See if your own repo has a main-style hotspot — run this in any local git repo:

pip
$ pip install hotspots-cli
npm
$ npm install -g @stephencollinstech/hotspots
Run in any repo
$ hotspots analyze .
★ Star on GitHub

Key Points

What is a god function and why does it matter in locust?

A god function is one that does too many unrelated jobs, usually showing up as very high fan-out (the count of distinct functions it calls) plus long length. It matters because every job inside it shares one test surface and one blast radius: a change to option parsing can break process forking. In locust, three of the top five functions carry this pattern (`main`, `__init__` in `locust/web.py`, and `setup_opentelemetry`), and `main` calls 110 distinct functions while `__init__` in `locust/web.py` calls 93.

How do I reduce cyclomatic complexity in python?

Use extract-method and decompose-conditional: pull cohesive blocks of branching into named helper functions. A CC above 15 warrants splitting, and above 30 warrants immediate attention. For `main` in `locust/main.py` (CC 89), a good first step is moving the chain of option-combination checks into a single validation function, which should remove a large share of its branches and can be tested without starting any processes.

Is locust actively maintained?

The data points that way. The web server constructor in `locust/web.py` was touched 5 times in the last year, most recently 3 days before the snapshot. `main` was last changed 6 days before the snapshot and `stop_users` 5 days before. Overall, 23 functions are in the fire quadrant, while 95 are in the debt quadrant, including `_get_section_groups`, which hasn't changed in 113 days (0 touches in the last year). Active development and structural debt coexist here, and the live risk is concentrated in a few large orchestration functions.

How do I reproduce this analysis?

Use the hotspots CLI from GitHub on commit 66279c9. Run `git checkout 66279c9`, then `hotspots analyze . --mode snapshot --explain-patterns --force`.

What does activity-weighted risk mean?

Activity-weighted risk combines structural complexity (cyclomatic complexity, nesting depth, and fan-out) with how often a function has been changed recently, so functions that are both hard to understand and actively changing score highest. A function with CC 80 that hasn't been touched in two years scores lower than one with CC 20 that changes every week, because the dormant one has lower near-term regression risk. This points refactoring effort at the code most likely to have bugs introduced right now, not just the code that looks complicated in the abstract.

The function main in locust/main.py has a cyclomatic complexity of 89. The fifth-ranked function, setup_opentelemetry, scores 12. That 77-point gap makes main a different category of problem from everything else on the list. It sits in the fire quadrant with an activity-weighted risk score of 18.19, so it is both structurally extreme and still changing (6 days since last change). Across the 864 functions I analyzed in locust at commit 66279c9, 28 are in the critical band, and I would start with main before anything else.

The table below ranks functions by activity-weighted risk — a score that multiplies structural complexity by recent commit frequency. A function that is both hard to understand (high cyclomatic complexity) and actively changing is a higher priority than one that is complex but untouched. CC = cyclomatic complexity (independent execution paths); ND = max nesting depth; FO = fan-out (distinct callees).

Top 5 Hotspots

FunctionFileRiskCCNDFO
mainlocust/main.py18.2897110
__init__locust/web.py16.825493
stop_userslocust/runners.py14.418419
_get_section_groupsdocs/_ext/llms_txt.py13.118411
setup_opentelemetrylocust/opentelemetry.py12.512227
Triage Band Distribution
Fire23Debt95Watch26OK720

864 functions analyzed

Detected Antipatterns
Complex Branching×8Complex Branching
High cyclomatic complexity — many independent execution paths, each a potential bug surface and required test case.
Exit Heavy×5Exit Heavy
Multiple return or throw paths dispersed through the body — each exit needs separate test coverage.
God Function×4God Function
Calls an unusually large number of distinct functions (high fan-out), making it the structural centre of gravity for a subsystem.
Long Function×4Long Function
Function body is too long to review in a single pass; likely contains multiple distinct responsibilities.
Deeply Nested×1Deeply Nested
Control structures nested 4+ levels deep, making it hard to reason about the full execution state at inner branches.

The distribution is worth reading before the individual functions. Of 864 functions, 23 are in the fire quadrant (complex and recently changed) and 95 are in the debt quadrant (complex but quiet). Four of my top five are fire (main, __init__, stop_users, and setup_opentelemetry) and one is debt (_get_section_groups). Most of the risk is concentrated in the startup and orchestration path: locust/main.py, locust/web.py, locust/runners.py, and locust/opentelemetry.py account for four of the five entries.

main — main.py

main
locust/main.py
18.19
fire
CC 89
ND 7
FO 110
touches/yr 2
Cyclomatic Complexity 89
threshold: 10

I would start with main because it combines CC 89, nesting depth 7, and fan-out of 110 with an activity-weighted risk score of 18.19. That CC means roughly 89 independent paths that each need a test case to cover properly. From the source excerpt, it handles locustfile discovery, argument parsing, option validation, logging setup, OpenTelemetry setup, and a multi-process fork path that adjusts options for child workers. Those are separable concerns living in one function body. The excerpt also shows long chains of option checks that each end in sys.exit(1), which is the exit-heavy pattern: every early exit is another path to test.

The history supports prioritizing it. The file has 6 bug-linked commits out of 12 total, a bug-fix rate of 58%, and 3 distinct authors in the last 90 days, with a review-comment density of 9.5 — the highest in my top five. That is file-level context, not proof that main itself is defective. The function was touched 2 times in the last year and last changed 6 days ago, so it is actively changing, not dormant.

My recommendation is extract-method, along the seams the code already shows: one function to validate option combinations (the --autoquit, --processes, --master checks), one to handle the fork-and-configure-children logic, and one for logging and telemetry setup. Pulling out just the option validation would remove a large share of the branches and make each check unit-testable without launching a process.

__init__ — web.py

__init__
locust/web.py
16.76
fire
CC 25
ND 4
FO 93
touches/yr 5

The WebUI constructor has a fan-out of 93, the second highest on the list, with CC 25 and nesting depth 4. It was touched 5 times in the last year, the most of any function in my top five, and last changed 3 days ago, which makes it the most frequently edited hotspot. In Python, a fan-out this high usually understates the coupling, since types resolve at runtime. The excerpt shows the constructor storing configuration, building a Flask app, enabling CORS, setting up login management, deciding whether to start the server, and then defining error handlers and routes as nested functions inside the constructor body. That makes it a god function and a long function: construction, configuration, and route registration are tangled together.

The file has 1 bug-linked commit out of 11 (a bug-fix rate of 45%) and 7 distinct authors in the last 90 days. Those author counts are file-level, but together with the 5 touches they suggest this constructor sees frequent change. I would move route and error-handler registration into separate methods or a dedicated blueprint-builder, leaving __init__ to assign state. Tests could then register routes without constructing a full server.

stop_users — runners.py

stop_users
locust/runners.py
14.43
fire
CC 18
ND 4
FO 19
touches/yr 1

stop_users has CC 18 and a modest fan-out of 19, so its risk comes from branching rather than coupling. It was touched once in the last year and changed 5 days ago. The excerpt shows nested loops that pick greenlets to stop per user class, then three different stop strategies depending on whether the user is the current greenlet, whether a stop timeout is configured, and whether the user is already stopping. There is also a while True loop with a break and an error path for greenlets with unexpected arguments. The multiple exits and the gevent timing logic make this hard to test deterministically.

This function carries the strongest historical signal in the list. The file shows 4 bug-linked commits out of 12, a bug-fix rate of 67%, and 7 distinct authors in the last 90 days. Again, that is file-level history covering all of runners.py, but combined with the complexity it justifies stronger review. I would split selection (which users to stop) from execution (how to stop them), so the three stop strategies can be tested without constructing greenlets for the selection logic.

_get_section_groups — llms_txt.py

_get_section_groups
docs/_ext/llms_txt.py
13.1
debt
CC 18
ND 4
FO 11
touches/yr 0

This is the only debt-quadrant function in my top five, and it is a documentation extension rather than runtime code. It has CC 18, nesting depth 4, and fan-out of 11, with 0 touches in the last year and 113 days since last change. It walks a Sphinx doctree to build section groups for an llms.txt index, nesting loops over child sections, toctree nodes, and entries. It is structural debt with a contained blast radius: the file has 3 commits total, no bug-linked commits, and 1 distinct author in the last 90 days. I would treat it as maintainability debt, not a regression risk. Next time someone touches the docs build, extracting the toctree-collection step into a helper would cut a nesting level and make the traversal easier to follow.

setup_opentelemetry — opentelemetry.py

setup_opentelemetry
locust/opentelemetry.py
12.52
fire
CC 12
ND 2
FO 27
touches/yr 4

setup_opentelemetry has the lowest CC of the five at 12 and shallow nesting of 2, but fan-out of 27 and 4 touches in the last year, with its last change 10 days ago. The source shows it parsing exporter environment variables for traces, metrics, and logs, building a resource, and wiring providers, and it defines event listeners inline (on_request, on_locust_init) inside the function body. That is the god-function and long-function pattern: setup, configuration parsing, and listener definitions all in one place.

The historical signal here is the weakest of the group. The file has no bug-linked commits out of 13 and a bug-fix rate of 15%, with 4 distinct authors in the last 90 days. I read this as an actively iterated feature that is growing, not one with a defect history. The remaining work is moving the inline listeners out of setup_opentelemetry, and I would start there.

Patterns Found

Antipatterns detected across the top functions in this snapshot:

PatternOccurrences
complex_branching8
exit_heavy5
god_function4
long_function4
deeply_nested1

These labels belong to two tiers — Tier 1 (structural): complex_branching, deeply_nested, exit_heavy, long_function, god_function. Tier 2 (relational/temporal): hub_function, cyclic_hub, middle_man, neighbor_risk, stale_complex, churn_magnet, shotgun_target, volatile_god.

See more analyses with these patterns: complex_branching, deeply_nested, exit_heavy, god_function, long_function.

Reproduce This Analysis

git clone https://github.com/locustio/locust
cd locust
git checkout 66279c936b4b69a8a7df3ac3b11f7e47d20780de
hotspots analyze . --mode snapshot --explain-patterns --force

To run the same analysis on your own codebase, run hotspots analyze . --mode snapshot in any local git repo — no configuration required.

I use Hotspots to highlight structural and activity risk — not “bad code.” I treat these findings as a prioritization aid, not a bug predictor. Editorial policy →

Was this useful? Let me know →

Related Analyses