Skip to content

revert(load-generator): replace k6 with Locust - #3873

Merged
julianocosta89 merged 12 commits into
open-telemetry:mainfrom
mwimpelberg28:revert-load-generator-to-locust
Sep 1, 2026
Merged

revert(load-generator): replace k6 with Locust#3873
julianocosta89 merged 12 commits into
open-telemetry:mainfrom
mwimpelberg28:revert-load-generator-to-locust

Conversation

@mwimpelberg28

Copy link
Copy Markdown
Contributor

Reverts #3564. Restores the pre-k6 Locust-based load generator (locustfile.py, requirements, Envoy /loadgen/ route, compose service config) while keeping everything unrelated added since that commit.

This reverts commit 18b36c7.

Assisted-by: Claude Sonnet 5

Changes

Please provide a brief description of the changes here.

Merge Requirements

For new features contributions, please make sure you have completed the following
essential items:

  • CHANGELOG.md updated to document new feature additions
  • Appropriate documentation updates in the docs
  • Appropriate Helm chart updates in the helm-charts

Maintainers will not merge until the above have been completed. If you're unsure
which docs need to be changed ping the
@open-telemetry/demo-approvers.

Reverts open-telemetry#3564. Restores the pre-k6 Locust-based load generator
(locustfile.py, requirements, Envoy /loadgen/ route, compose service
config) while keeping everything unrelated added since that commit.

This reverts commit 18b36c7.

Assisted-by: Claude Sonnet 5
@mwimpelberg28
mwimpelberg28 requested a review from a team as a code owner August 21, 2026 16:10
@github-actions github-actions Bot added docs-update-required Requires documentation update helm-update-required Requires an update to the Helm chart when released labels Aug 21, 2026
@ps48
ps48 requested a balanced review from Copilot August 21, 2026 18:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Restores the Locust-based load generator and removes the k6 implementation.

Changes:

  • Restores Locust, Playwright browser traffic, Python dependencies, and container configuration.
  • Restores the Locust UI route, environment settings, and documentation links.
  • Removes k6-specific code, flags, dependencies, and changelog entries.

Reviewed changes

Copilot reviewed 19 out of 20 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
.env Restores Locust environment settings.
.github/dependabot.yml Switches load-generator updates to pip.
CHANGELOG.md Removes k6 feature entries.
CONTRIBUTING.md Restores the Locust UI link.
Makefile Prints the Locust UI URL.
compose.yaml Restores Locust deployment configuration.
src/flagd/demo.flagd.json Removes k6 load-control flags.
src/frontend-proxy/envoy.tmpl.yaml Restores the /loadgen/ route.
src/load-generator/Dockerfile Restores the Python/Locust image.
src/load-generator/README.md Restores Locust usage documentation.
src/load-generator/entrypoint.sh Removes the k6 wrapper.
src/load-generator/locustfile.py Restores HTTP and browser workloads.
src/load-generator/requirements.in Defines direct Python dependencies.
src/load-generator/requirements.txt Locks Python dependencies.
src/load-generator/script.js Removes the k6 workload.
src/load-generator/xk6-otel/go.mod Removes the extension module definition.
src/load-generator/xk6-otel/go.sum Removes extension dependency checksums.
src/load-generator/xk6-otel/otel.go Removes the custom k6 OTel extension.
src/otel-collector/otelcol-config.yml Reverts collector CORS headers.
test/telemetry/services.py Updates the frontend-web exclusion rationale.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/otel-collector/otelcol-config.yml
Comment thread src/load-generator/locustfile.py Outdated
Comment thread src/flagd/demo.flagd.json
Comment thread test/telemetry/services.py Outdated
Comment thread src/load-generator/README.md Outdated
Comment thread compose.yaml Outdated
- otel-collector: restore allowed_headers (content-type, traceparent,
  baggage) on the OTLP HTTP receiver's CORS config, otherwise the
  browser's baggage-bearing preflight requests are rejected and
  frontend-web telemetry disappears.
- locustfile.py: register the RoofBinoculars response listener before
  navigating instead of after, so a fast image response isn't missed.
- demo.flagd.json: restore loadGeneratorTraffic and loadGeneratorVUs,
  which the flagd-ui scheduler tests still assert exist.
- services.py: correct the frontend-web exclusion comment; browser
  traffic is enabled in every scope, it's just low-weighted relative
  to HTTP traffic, not disabled.
- README.md: point to the stable Locust docs instead of the 2.16
  archive, since the restored lockfile pins 2.44.4.

Assisted-by: Claude Sonnet 5
Comment thread compose.yaml Outdated
@julianocosta89

Copy link
Copy Markdown
Member

When testing this PR I'm failing to build flagd-ui.
I believe you need to update some tests. Could you take a look?

 > [flagd-ui test 8/8] RUN mix test   && touch /tmp/flagd-ui-tests-passed:
2.090 ==> file_system
2.096 Compiling 7 files (.ex)
2.543 Generated file_system app
2.599 ==> floki
2.599 Compiling 1 file (.xrl)
2.688 Compiling 2 files (.erl)
3.115 Compiling 30 files (.ex)
4.690 Generated floki app
4.715 ==> mime
4.717 Compiling 1 file (.ex)
4.800 Generated mime app
4.825 ==> nimble_options
4.826 Compiling 3 files (.ex)
5.058 Generated nimble_options app
5.083 ==> fine
5.083 Compiling 1 file (.ex)
5.097 Generated fine app
5.126 ==> bunt
5.126 Compiling 2 files (.ex)
5.380 Generated bunt app
5.402 ==> jason
5.402 Compiling 10 files (.ex)
6.353 Generated jason app
6.367 ==> esbuild
6.367 Compiling 4 files (.ex)
6.430 Generated esbuild app
6.459 ==> plug_crypto
6.459 Compiling 5 files (.ex)
6.519 Generated plug_crypto app
6.532 ==> hpax
6.532 Compiling 4 files (.ex)
6.808 Generated hpax app
6.834 ==> mint
6.834 Compiling 1 file (.erl)
6.882 Compiling 19 files (.ex)
7.584 Generated mint app
7.612 ==> ssl_verify_fun
7.612 Compiling 7 files (.erl)
7.641 Generated ssl_verify_fun app
7.924 ==> flagd_ui
7.925 ===> Analyzing applications...
7.955 ===> Compiling ctx
8.170 ==> elixir_make
8.170 Compiling 8 files (.ex)
8.237 Generated elixir_make app
8.263 ==> opentelemetry_semantic_conventions
8.263 Compiling 2 files (.erl)
8.276 Compiling 79 files (.ex)
8.514 Generated opentelemetry_semantic_conventions app
9.153 ==> flagd_ui
9.156 ===> Fetching rebar3_ex_doc v0.3.0
9.581 ===> Analyzing applications...
9.598 ===> Compiling rebar3_ex_doc
9.765 ===> Analyzing applications...
9.769 ===> Compiling otel_http
10.12 ===> Analyzing applications...
10.58 ===> Compiling idna
15.40 ===> Analyzing applications...
15.44 ===> Compiling gproc
16.24 ===> Analyzing applications...
16.31 ===> Compiling telemetry
16.54 ==> telemetry_metrics
16.54 Compiling 7 files (.ex)
16.59      warning: unused require Logger
16.59      │
16.59  392 │   require Logger
16.59      │   ~
16.59      │
16.59      └─ lib/telemetry_metrics.ex:392:3
16.59 
16.61 Generated telemetry_metrics app
17.01 ==> flagd_ui
17.01 ===> Analyzing applications...
17.05 ===> Compiling telemetry_poller
17.22 ==> thousand_island
17.22 Compiling 18 files (.ex)
17.36 Generated thousand_island app
17.38 ==> phoenix_html
17.38 Compiling 6 files (.ex)
17.55 Generated phoenix_html app
17.56 ==> phoenix_template
17.56 Compiling 4 files (.ex)
17.57      warning: the variable "prefix_size" is accessed inside size(...) of a bitstring but it was defined outside of the match. You must precede it with the pin operator
17.57      │
17.57  505 │       <<prefix::binary-size(prefix_size), ^suffix::binary>> -> prefix
17.57      │                             ~
17.57      │
17.57      └─ lib/phoenix/template.ex:505:29: Phoenix.Template.unsuffix/2
17.57 
17.61 Generated phoenix_template app
17.63 ==> expo
17.63 Compiling 2 files (.erl)
17.84 Compiling 22 files (.ex)
17.99 Generated expo app
18.01 ==> gettext
18.01 Compiling 18 files (.ex)
18.16      warning: this clause in cond will never match:
18.16 
18.16          not is_binary(locale)
18.16 
18.16      since it has type:
18.16 
18.16          dynamic(false)
18.16 
18.16      where "locale" was given the types:
18.16 
18.16          # type: binary()
18.16          # from: lib/gettext.ex:806:28
18.16          locale
18.16 
18.16          # type: binary()
18.16          # from: lib/gettext.ex:806:41
18.16          is_binary(locale)
18.16 
18.16      type warning found at:
18.16      │
18.16  808 │       not is_binary(locale) ->
18.16      │                             ~
18.16      │
18.16      └─ lib/gettext.ex:808:29: Gettext.put_locale!/2
18.16 
18.16      warning: Kernel.ParallelCompiler.async/1 is deprecated. Use `pmap/2` instead
18.16      │
18.16  426 │               Kernel.ParallelCompiler.async(fn ->
18.16      │                                       ~
18.16      │
18.16      └─ lib/gettext/compiler.ex:426:39: Gettext.Compiler.compile_po_files/3
18.16 
18.16      warning: the following clause is redundant:
18.16 
18.16          defp ensure_empty_msgstr!(%Expo.Message.Plural{} = message)
18.16 
18.16      it has type:
18.16 
18.16          dynamic(%Expo.Message.Plural{})
18.16 
18.16      previous clauses have already matched on the following types:
18.16 
18.16          %Expo.Message.Singular{}
18.16          %Expo.Message.Plural{}
18.16 
18.16      │
18.16  391 │   defp ensure_empty_msgstr!(%Message.Plural{} = message) do
18.16      │        ~
18.16      │
18.16      └─ lib/gettext/extractor.ex:391:8: Gettext.Extractor.ensure_empty_msgstr!/1
18.16 
18.17 Generated gettext app
18.18 ==> phoenix_pubsub
18.18 Compiling 12 files (.ex)
18.38 Generated phoenix_pubsub app
18.72 ==> flagd_ui
18.72 ===> Analyzing applications...
18.74 ===> Compiling acceptor_pool
18.85 ==> dns_cluster
18.85 Compiling 1 file (.ex)
18.92      warning: comparison between distinct types found:
18.92 
18.92          net_state.started != :no
18.92 
18.92      given types:
18.92 
18.92          dynamic(not :no) != :no
18.92 
18.92      where "net_state" was given the types:
18.92 
18.92          # type: dynamic()
18.92          # from: lib/dns_cluster.ex:207:8
18.92          net_state
18.92 
18.92          # type: dynamic(not false and not nil)
18.92          # from: lib/dns_cluster.ex:207:8
18.92          net_state
18.92 
18.92          # type: dynamic(%{..., started: term()})
18.92          # from: lib/dns_cluster.ex:210:25
18.92          net_state.started == :no
18.92 
18.92          # type: dynamic(%{..., started: not :no})
18.92          # from: lib/dns_cluster.ex:220:25
18.92          net_state.started == :no
18.92 
18.92      While Elixir can compare across all types, you are comparing across types which are always disjoint, and the result is either always true or always false
18.92 
18.92      type warning found at:
18.92      │
18.92  221 │           (!release? and net_state.started != :no and net_state[:name_domain] != :longnames) ->
18.92      │                                            ~
18.92      │
18.92      └─ lib/dns_cluster.ex:221:44: DNSCluster.warn_on_invalid_dist/0
18.92 
18.92 Generated dns_cluster app
19.23 ==> flagd_ui
19.23 ===> Analyzing applications...
19.27 ===> Compiling hpack
20.04 ===> Analyzing applications...
20.12 ===> Compiling tls_certificate_check
20.51 ==> credo
20.51 Compiling 257 files (.ex)
22.18 Generated credo app
22.20 ==> plug
22.20 Compiling 1 file (.erl)
22.28 Compiling 42 files (.ex)
23.49 Generated plug app
23.51 ==> nimble_pool
23.51 Compiling 2 files (.ex)
23.64 Generated nimble_pool app
23.66 ==> finch
23.66 Compiling 23 files (.ex)
24.08 Generated finch app
24.11 ==> req
24.11 Compiling 24 files (.ex)
24.91 Generated req app
24.94 ==> cc_precompiler
24.94 Compiling 3 files (.ex)
25.07 Generated cc_precompiler app
25.28 ==> lazy_html
25.28 Downloading precompiled NIF to /root/.cache/elixir_make/lazy_html-nif-2.16-aarch64-linux-gnu-0.1.12.tar.gz
26.33 Compiling 3 files (.ex)
26.56 Generated lazy_html app
26.62 ==> tailwind
26.62 Compiling 3 files (.ex)
26.86 Generated tailwind app
27.72 ==> flagd_ui
27.72 ===> Analyzing applications...
28.19 ===> Compiling chatterbox
29.31 ===> Analyzing applications...
29.40 ===> Compiling grpcbox
30.22 ==> websock
30.22 Compiling 1 file (.ex)
30.25 Generated websock app
30.27 ==> bandit
30.27 Compiling 54 files (.ex)
30.89 Generated bandit app
30.91 ==> swoosh
30.91 Compiling 61 files (.ex)
31.40 Generated swoosh app
31.41 ==> websock_adapter
31.41 Compiling 4 files (.ex)
31.43 Generated websock_adapter app
31.46 ==> phoenix
31.46 Compiling 74 files (.ex)
32.28 Generated phoenix app
32.30 ==> phoenix_live_view
32.30 Compiling 55 files (.ex)
33.90 Generated phoenix_live_view app
33.96 ==> phoenix_live_dashboard
33.96 Compiling 36 files (.ex)
35.21      warning: Postgrex.Interval.to_string/1 is undefined (module Postgrex.Interval is not available or is yet to be defined)
35.21      │
35.21  321 │     do: struct.to_string(value)
35.21      │                ~
35.21      │
35.21      └─ lib/phoenix/live_dashboard/pages/ecto_stats_page.ex:321:16: Phoenix.LiveDashboard.EctoStatsPage.format/2
35.21 
35.22 Generated phoenix_live_dashboard app
35.27 ==> opentelemetry_api
35.27 Compiling 19 files (.erl)
35.39 Compiling 5 files (.ex)
35.43 Generated opentelemetry_api app
35.91 ==> flagd_ui
35.91 ===> Analyzing applications...
36.16 ===> Compiling opentelemetry
37.76 ===> Analyzing applications...
38.02 ===> Compiling opentelemetry_exporter
40.74 ==> opentelemetry_bandit
40.74 Compiling 1 file (.ex)
40.85 Generated opentelemetry_bandit app
40.87 ==> opentelemetry_process_propagator
40.87 Compiling 1 file (.erl)
40.88 Compiling 4 files (.ex)
40.94 Generated opentelemetry_process_propagator app
40.96 ==> opentelemetry_telemetry
40.96 Compiling 1 file (.erl)
40.98 Compiling 1 file (.ex)
41.00 Generated opentelemetry_telemetry app
41.01 ==> opentelemetry_phoenix
41.01 Compiling 1 file (.ex)
41.04     warning: unused require OpenTelemetry.Tracer
41.04     │
41.04  60 │   require OpenTelemetry.Tracer
41.04     │   ~
41.04     │
41.04     └─ lib/opentelemetry_phoenix.ex:60:3
41.04 
41.05 Generated opentelemetry_phoenix app
41.10 ==> flagd_ui
41.10 Compiling 21 files (.ex)
41.98 Generated flagd_ui app
42.43 Running ExUnit with seed: 57952, max_cases: 20
42.43 
42.45 ......11:00:11.413 [warning] Ignoring replace with invalid JSON
42.73 .........................
46.01 
46.01   1) test running the schedule reverts every concurrently held flag when stopped (FlagdUi.SchedulerTest)
46.01      test/flagd_ui/scheduler_test.exs:367
46.01      Expected truthy, got false
46.01      code: assert eventually(fn -> variant_of(flag) == "off" end)
46.01      arguments:
46.01 
46.01          # 1
46.01          #Function<33.125993540/0 in FlagdUi.SchedulerTest."test running the schedule reverts every concurrently held flag when stopped"/1>
46.01 
46.01      stacktrace:
46.01        test/flagd_ui/scheduler_test.exs:381: anonymous fn/1 in FlagdUi.SchedulerTest."test running the schedule reverts every concurrently held flag when stopped"/1
46.01        (elixir 1.20.1) lib/enum.ex:1725: Enum."-map/2-lists^map/1-1-"/2
46.01        (elixir 1.20.1) lib/enum.ex:1725: Enum."-map/2-lists^map/1-1-"/2
46.01        test/flagd_ui/scheduler_test.exs:380: (test)
46.01 
46.01 .....
54.78 
54.78   2) test running the schedule turns the flag back off when the hold expires (FlagdUi.SchedulerTest)
54.78      test/flagd_ui/scheduler_test.exs:165
54.78      Expected truthy, got false
54.78      code: assert eventually(fn -> variant_of(flag) == "off" end, 2000)
54.78      arguments:
54.78 
54.78          # 1
54.78          #Function<30.125993540/0 in FlagdUi.SchedulerTest."test running the schedule turns the flag back off when the hold expires"/1>
54.78 
54.78          # 2
54.78          2000
54.78 
54.78      stacktrace:
54.78        test/flagd_ui/scheduler_test.exs:175: (test)
54.78 
54.79 .11:00:23.473 [warning] Flag scheduler was asked for 99 concurrent flags but only 1 are selected
54.82 ..........
60.08 
60.08   3) test running the schedule stopping reverts a flag that is still active (FlagdUi.SchedulerTest)
60.08      test/flagd_ui/scheduler_test.exs:245
60.08      Expected truthy, got false
60.08      code: assert eventually(fn -> variant_of(flag) == "off" end)
60.08      arguments:
60.08 
60.08          # 1
60.08          #Function<26.125993540/0 in FlagdUi.SchedulerTest."test running the schedule stopping reverts a flag that is still active"/1>
60.08 
60.08      stacktrace:
60.08        test/flagd_ui/scheduler_test.exs:266: (test)
60.08 
60.08 
60.08 Finished in 17.6 seconds (0.1s async, 17.4s sync)
60.08 
60.08 Result: 47/50 passed
60.08 Failed: 3 tests
------

loadGeneratorTraffic rests at "on" and loadGeneratorVUs rests at its own
default variant, not "off". With the fixed seed used by these tests, the
scheduler deterministically picked one of them, so the generic "reverts
to off" assertions never passed. Restrict those tests to off-resting
flags, matching the pattern already used elsewhere in this file.

Assisted-by: Claude Sonnet 5
@mwimpelberg28

Copy link
Copy Markdown
Contributor Author

Thanks for catching that! The 3 failing tests (variant_of(flag) == "off" assertions in scheduler_test.exs:165,245,367) were assuming every flag rests at "off" after a revert, but loadGeneratorTraffic and loadGeneratorVUs don't — they're covered by their own revert tests instead. Fixed in b98d030 by excluding those two from the generic off-resting assertions. Rebuilt the flagd-ui test stage locally and confirmed 50/50 tests pass now.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 19 out of 20 changed files in this pull request and generated 6 comments.

Suppressed comments (2)

src/flagd/demo.flagd.json:128

  • This flag is now disconnected from concurrency control. The deleted wrapper consumed loadGeneratorVUs, but the restored service starts Locust with the static LOCUST_USERS environment value and no code observes this flag. Remove the stale flag and its flagd-ui coverage/docs, or wire it to Locust's runner so changing it actually resizes users.
    "loadGeneratorVUs": {
      "description": "Number of concurrent virtual users the load generator's HTTP scenario runs.",

src/load-generator/locustfile.py:137

  • This second product span also uses the unregistered product.id key. Rename it to the existing demo.product.id attribute and add that attribute to the load-generator service declaration in the telemetry registry.
        with self.tracer.start_as_current_span("user_get_recommendations", context=context.get_current(), attributes={"product.id": product}):

Comment thread src/flagd/demo.flagd.json Outdated
Comment thread src/load-generator/locustfile.py Outdated
Comment thread src/load-generator/locustfile.py Outdated
Comment thread src/load-generator/locustfile.py Outdated
Comment thread src/load-generator/locustfile.py Outdated
Comment thread src/load-generator/locustfile.py Outdated
@julianocosta89

Copy link
Copy Markdown
Member

@mwimpelberg28 I think copilot found some inconsistencies with attributes naming.
Could you take a look?

loadGeneratorTraffic and loadGeneratorVUs were introduced by the k6
migration (open-telemetry#3564) and consumed only by its entrypoint.sh wrapper; the
restored Locust code never observed them. Remove both flags along
with the flagd-ui scheduler test cases and README section that
existed only to accommodate their non-off resting state, restoring
the pre-k6 flag set.

Assisted-by: Claude Sonnet 5
Replace unregistered span attributes (product.id, quantity, category,
item.count, flood.count) with their existing demo.* registry
equivalents, adding a new demo.request.flood.count attribute for the
one case with no existing match, and declare all of them for
service.load_generator per the telemetry-schema conventions.

Assisted-by: Claude Sonnet 5
@mwimpelberg28

Copy link
Copy Markdown
Contributor Author

@mwimpelberg28 I think copilot found some inconsistencies with attributes naming. Could you take a look?

Good catch, I fixed those and the checks are passing now.

- Restore the open-telemetry#3564 changelog bullets under 3.0.0, which was already
  released with them, and add a new Unreleased entry for this revert
  instead of rewriting released history.
- Fix the service.load_generator attribute group: its brief claimed
  "baggage attributes" but most of its members are plain span
  attributes, and it was missing user.id, which locustfile.py also
  emits as a span attribute.

Assisted-by: Claude Sonnet 5
@Kielek

Kielek commented Aug 31, 2026

Copy link
Copy Markdown
Member

@mwimpelberg28, we potentially keep k6 as a replacement, but it requires Grafana to re-license package from AGPL to something more permissive, the best option from OTel/CNCF persective is Apache 2.0.

It is not a blocking comment, just highlighting possibility on the table. I suppose this change is extremely unlikely based on the historical Grafanas' decisions.

@mwimpelberg28

Copy link
Copy Markdown
Contributor Author

@mwimpelberg28, we potentially keep k6 as a replacement, but it requires Grafana to re-license package from AGPL to something more permissive, the best option from OTel/CNCF persective is Apache 2.0.

It is not a blocking comment, just highlighting possibility on the table. I suppose this change is extremely unlikely based on the historical Grafanas' decisions.

Can an exception be granted in the AGPL just for the demo app?

@Kielek

Kielek commented Aug 31, 2026

Copy link
Copy Markdown
Member

Can an exception be granted in the AGPL just for the demo app?

Unfortunately no, see cncf/foundation#1482

@julianocosta89

Copy link
Copy Markdown
Member

@mwimpelberg28 would you be able to take care of the docs revert as well?

@julianocosta89
julianocosta89 added this pull request to the merge queue Sep 1, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Sep 1, 2026
@julianocosta89
julianocosta89 added this pull request to the merge queue Sep 1, 2026
Merged via the queue into open-telemetry:main with commit 7459c1a Sep 1, 2026
67 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs-update-required Requires documentation update helm-update-required Requires an update to the Helm chart when released

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants