This project is for testing load generation scenarios against Temporal. This is primarily used by the Temporal team to benchmark features and situations. Backwards compatibility may not be maintained.
Omes (pronounced oh-mess) is the Hebrew word for "load" (עומס).
- Go 1.25+
protoc
+protoc-gen-go
or mise for Kitchen Sink Workflow
- Java 8+
- TypeScript: Node 16+
- Python: uv
- .NET
And if you're running the fuzzer (see below)
SDK and tool versions are defined in versions.env
.
This (simplified) diagram shows the main components of Omes:
flowchart TD
subgraph "CLI"
RunWorker["run-worker"]
RunScenario["run-scenario"]
RunScenarioWithWorker["run-scenario-with-worker"]
end
Workers["Worker(s)"]
WorkflowsAndActivities["Workflows and Activities"]
RunScenarioWithWorker --> RunWorker
RunScenarioWithWorker --> RunScenario
RunWorker --> |"start"| Workers
RunScenario --> |"start"| Scenario
Scenario --> |"start"| Executor
Workers --> |"consume"| WorkflowsAndActivities
Executor --> |"produce"| WorkflowsAndActivities
- Scenario: starts an Executor to run a particular load configuration
- Executor: produces concurrent executions of workflows and activities requested by the Scenario
- Workers: consumes the workflows and activities started by the Executor
Scenarios are defined using plain Go code. They are located in the scenarios folder. There are already multiple defined that can be used.
A scenario must select an Executor
. The most common is the KitchenSinkExecutor
which is a wrapper on the
GenericExecutor
specific for executing the Kitchen Sink workflow. The Kitchen Sink workflow accepts
actions and is implemented in every worker language.
For example, here is scenarios/workflow_with_single_noop_activity.go:
func init() {
loadgen.MustRegisterScenario(loadgen.Scenario{
Description: "Each iteration executes a single workflow with a noop activity.",
Executor: loadgen.KitchenSinkExecutor{
WorkflowParams: kitchensink.NewWorkflowParams(kitchensink.NopActionExecuteActivity),
},
})
}
NOTE: The file name where the
Register
function is called, will be used as the name of the scenario.
- Use snake case for scenario file names.
- Use
KitchenSinkExecutor
for most basic scenarios, adding common/generic actions as need, but for unique scenarios useGenericExecutor
. - When using
GenericExecutor
, use methods of*loadgen.Run
in yourExecute
as much as possible. - Liberally add helpers to the
loadgen
package that will be useful to other scenario authors.
During local development it's typically easiest to run both the worker and the scenario together.
You can do that like follows. If you want an embedded server rather than one you've already started,
pass --embedded-server
.
go run ./cmd run-scenario-with-worker --scenario workflow_with_single_noop_activity --language go
Notes:
- Cleanup is not automatically performed here
- Accepts combined flags for
run-worker
andrun-scenario
commands
go run ./cmd run-worker --run-id local-test-run --language go
Notes:
--embedded-server
can be passed here to start an embedded localhost server--task-queue-suffix-index-start
and--task-queue-suffix-index-end
represent an inclusive range for running the worker on multiple task queues. The process will create a worker for every task queue from<task-queue>-<start>
through<task-queue>-end
. This only applies to multi-task-queue scenarios.
go run ./cmd run-scenario --scenario workflow_with_single_noop_activity --run-id local-test-run
Notes:
- Run ID is used to derive ID prefixes and the task queue name, it should be used to start a worker on the correct task queue and by the cleanup script.
- By default the number of iterations or duration is specified in the scenario config. They can be overridden with CLI flags.
- See help output for available flags.
go run ./cmd cleanup-scenario --scenario workflow_with_single_noop_activity --run-id local-test-run
The --version
flag can be used to specify a version of the SDK to use, it accepts either
a version number like v1.24.0
or you can also pass a local path to use a local SDK version.
This is useful while testing unreleased or in-development versions of the SDK.
go run ./cmd run-scenario-with-worker --scenario workflow_with_single_noop_activity --language go --version /path/to/go-sdk
For example, to build a go worker image using v1.24.0 of the Temporal Go SDK:
go run ./cmd/dev build-worker-image --language go --version v1.24.0
This will produce an image tagged like <current git commit hash>-go-v1.24.0
.
If version is not specified, the SDK version specified in versions.env
will be used.
Publishing images is done via CI, using the build-push-worker-image
command.
See the GHA workflows for more information.
The throughput_stress scenario can be configured to run "sleep" activities with different configurations.
The configuration is done via a JSON file, which is passed to the scenario with the
--option sleep-activity-per-priority-json=@<file>
flag. Example:
echo '{"count":{"type":"fixed","value":5},"groups":{"high":{"weight":2,"sleepDuration":{"type":"uniform","min":"2s","max":"4s"}},"low":{"weight":3,"sleepDuration":{"type":"discrete","weights":{"5s":3,"10s":1}}}}}' > sleep.json
go run ./cmd run-scenario-with-worker --scenario throughput_stress --language go --option [email protected] --run-id default-run-id
This runs 5 sleep activities per iteration, where "high" has a weight of 2 and sleeps for a random duration between 2-4s, and "low" has a weight of 3 and sleeps for either 5s or 10s.
Look at DistributionField
to learn more about different kinds of distrbutions.
The throughput_stress scenario can generate Nexus load if the scenario is started with --option nexus-endpoint=my-nexus-endpoint
:
temporal operator nexus endpoint create \
--name my-nexus-endpoint \
--target-namespace default \ # Change if needed
--target-task-queue throughput_stress:default-run-id
- Start the scenario with the given run-id:
go run ./cmd run-scenario-with-worker --scenario throughput_stress --language go --option nexus-endpoint=my-nexus-endpoint --run-id default-run-id
The fuzzer scenario makes use of the kitchen sink workflow (see below) to exercise a wide
range of possible actions. Actions are pre-generated by the kitchen-sink-gen
tool, written in
Rust, and are some combination of actions provided to the workflow as input, and actions to be
run by a client inside the scenario executor.
You can run the fuzzer with new random actions like so:
go run ./cmd run-scenario-with-worker --scenario fuzzer --iterations 1 --language cs
By default, the scenario will spit out a last_fuzz_run.proto
binary file containing the generated
actions. To re-run the same set of actions, you can pass in such a file like so:
go run ./cmd run-scenario-with-worker --scenario fuzzer --iterations 1 --language cs --option input-file=last_fuzz_run.proto
Or you can run with a specific seed (seeds are printed at the start of the scenario):
go run ./cmd run-scenario-with-worker --scenario fuzzer --iterations 1 --language cs --option seed=131962944538087455
However, the fuzzer is also sensitive to its configuration, and thus the seed will only produce the exact same set of actions if the config has also not changed. Thus you should prefer to save binary files rather than seeds.
Please do collect interesting fuzz cases in the scenarios/fuzz_cases.yaml
file. This file
currently has seeds, but could also easily reference stored binary files instead.
The Kitchen Sink workflows accepts a DSL generated by the kitchen-sink-gen
Rust tool, allowing us
to test a wide variety of scenarios without having to imagine all possible edge cases that could
come up in workflows. Input may be saved for regression testing, or hand written for specific cases.
Build by running go run ./cmd/dev build kitchensink
.
Test by running go test -v ./loadgen -run TestKitchenSink
.
Prefix with env variable SDK=<sdk>
to test a specific SDK only.
A scenario can only fail if an Execute
method returns an error, that means the control is fully in the scenario
authors's hands. For enforcing a timeout for a scenario, use options like workflow execution timeouts or write a
workflow that waits for a signal for a configurable amount of time.
- Nicer output that includes resource utilization for the worker (when running all-in-one)
- Ruby worker
Use the dev command for development tasks:
go run ./cmd/dev install # Install tools (default: all)
go run ./cmd/dev lint-and-format # Lint and format workers (default: all)
go run ./cmd/dev test # Test workers (default: all)
go run ./cmd/dev build # Build worker images (default: all)
go run ./cmd/dev clean # Clean worker artifacts (default: all)
go run ./cmd/dev build-proto # Build kitchen-sink proto
Or target specific languages: go run ./cmd/dev build go java python
All versions are defined in versions.env
.