Selection functions
stateMachine
Commentary
added in 0.0.5
stateful function
Generates data according to a set of states and transitions.
State machines can be used in two ways: either as regular functions, or as a top-level field that controls the entire generator.
For the former, when a state is triggered, its value is used in place. 1 For the latter, its value is merged into the generator to override its existing behavior. 2
For many use cases, it's common that each successive state needs to merge its previously generated event. Top-level state machines may be configured with a merge
property to enable this behavior. 3
Examples
Basic state machine
Use a state machine like any other function. ShadowTraffic keeps track of the current state and advances it over time. States can transition indefinitely, or terminate, in which case the generator will go dormant and stop generating new events.
{
"_gen": "stateMachine",
"initial": "a",
"transitions": {
"a": "b",
"b": "c",
"c": "a"
},
"states": {
"a": 1,
"b": 2,
"c": 3
}
}
[
1,
2,
3,
1,
2
]
Top-level state machine
An optional top-level state machine may be defined for the generator. When present, the value of its state is merged into the entire generator output. This is useful when modeling change that affects many attributes, even including the output collection.
{
"generators": [
{
"topic": "customers",
"vars": {
"id": {
"_gen": "uniformDistribution",
"bounds": [
1,
10
]
}
},
"key": {
"_gen": "var",
"var": "id"
},
"value": {},
"stateMachine": {
"_gen": "stateMachine",
"initial": "a",
"transitions": {
"a": "b",
"b": "c",
"c": "a"
},
"states": {
"a": {
"value": {
"action": "start"
}
},
"b": {
"value": {
"action": "running"
}
},
"c": {
"value": {
"action": "stopped"
}
}
}
}
}
],
"connections": {
"kafka": {
"kind": "kafka",
"producerConfigs": {
"bootstrap.servers": "localhost:9092",
"key.serializer": "io.shadowtraffic.kafka.serdes.JsonSerializer",
"value.serializer": "io.shadowtraffic.kafka.serdes.JsonSerializer"
}
}
}
}
[
{
"topic": "customers",
"key": 6.55112060472371,
"value": {
"action": "start"
},
"headers": null
},
{
"topic": "customers",
"key": 9.034051322967441,
"value": {
"action": "running"
},
"headers": null
},
{
"topic": "customers",
"key": 3.3974979657991153,
"value": {
"action": "stopped"
},
"headers": null
},
{
"topic": "customers",
"key": 7.00265245344972,
"value": {
"action": "start"
},
"headers": null
},
{
"topic": "customers",
"key": 1.4764676766760236,
"value": {
"action": "running"
},
"headers": null
}
]
Merge previous events
Top-level state machines can force their previously generated events into their body.
{
"topic": "sandbox",
"value": {
"a": 1
},
"stateMachine": {
"_gen": "stateMachine",
"initial": "s1",
"merge": {
"previous": true
},
"transitions": {
"s1": "s2",
"s2": "s3"
},
"states": {
"s1": {
"value": {
"b": 2
}
},
"s2": {
"value": {
"c": 3
}
},
"s3": {
"value": {
"d": 4
}
}
}
}
}
[
{
"a": 1,
"b": 2
},
{
"a": 1,
"b": 2,
"c": 3
},
{
"a": 1,
"b": 2,
"c": 3,
"d": 4
}
]
Sequential transitions
Instead of a map, transitions
can also be specified as an array, describing the exact path to take through the state machine.
When specified in this form, you don't need to specify an initial
state since it's obvious where to begin.
{
"_gen": "stateMachine",
"transitions": [
"s1",
"s2",
"s2",
"s3"
],
"states": {
"s1": {
"value": {
"a": 2
}
},
"s2": {
"value": {
"b": 3
}
},
"s3": {
"value": {
"c": 4
}
}
}
}
[
{
"a": 2
},
{
"b": 3
},
{
"b": 3
},
{
"c": 4
}
]
Repeated transitions
Optionally use state
and times
in an array of transitions to repeat states.
{
"_gen": "stateMachine",
"transitions": [
"s1",
{
"state": "s2",
"times": 3
},
"s3"
],
"states": {
"s1": {
"value": {
"a": 2
}
},
"s2": {
"value": {
"b": 3
}
},
"s3": {
"value": {
"c": 4
}
}
}
}
[
{
"a": 2
},
{
"b": 3
},
{
"b": 3
},
{
"b": 3
},
{
"c": 4
}
]
Functions in transitions
Both state
and times
can be functions. times
is evaluated only once, and its value locked for the lifetime of the state machine. state
is evaluated on each execution.
In this example, state s1
runs once, and then either s2
or s3
are visited as many as 4 times total.
{
"topic": "sandbox",
"varsOnce": {
"n": {
"_gen": "uniformDistribution",
"bounds": [
1,
5
]
}
},
"value": {},
"stateMachine": {
"_gen": "stateMachine",
"transitions": [
"s1",
{
"state": {
"_gen": "oneOf",
"choices": [
"s2",
"s3"
]
},
"times": {
"_gen": "var",
"var": "n"
}
}
],
"states": {
"s1": {
"value": {
"x": 1
}
},
"s2": {
"value": {
"x": 2
}
},
"s3": {
"value": {
"x": 3
}
}
}
}
}
[
{
"x": 1
},
{
"x": 2
}
]
Terminating state machines
Transition to the value null
to terminate a state machine, and thus the generator.
{
"topic": "sandbox",
"value": {},
"stateMachine": {
"_gen": "stateMachine",
"initial": "s1",
"transitions": {
"s1": "s2",
"s2": null,
"s3": "s4"
},
"states": {
"s1": {
"value": {
"x": 1
}
},
"s2": {
"value": {
"x": 2
}
},
"s3": {
"value": {
"x": 3
}
},
"s4": {
"value": {
"x": 4
}
}
}
}
}
[
{
"x": 1
},
{
"x": 2
}
]
Overriding throttle values
Use localConfigs
with throttleMs
inside of a state to override the generator's default throttle value. This is useful if you want to make a particular state wait longer or shorter before transitioning to the next.
In this example, state s1
throttles for 2000
ms, the default, and s2
throttles for 500
ms.
{
"topic": "sandbox",
"value": {},
"stateMachine": {
"_gen": "stateMachine",
"initial": "s1",
"transitions": {
"s1": "s2",
"s2": "s1"
},
"states": {
"s1": {
"value": {
"x": 1
}
},
"s2": {
"value": {
"x": 2
},
"localConfigs": {
"throttleMs": 500
}
}
}
},
"localConfigs": {
"throttleMs": 2000
}
}
Specification
JSON schema
{
"type": "object",
"properties": {
"merge": {
"type": "object",
"properties": {
"previous": {
"type": "boolean"
}
},
"required": [
"previous"
]
},
"states": {
"type": "object"
}
},
"required": [
"transitions",
"states"
],
"anyOf": [
{
"properties": {
"initial": {
"type": "string"
},
"transitions": {
"type": "object"
}
},
"required": [
"initial"
]
},
{
"properties": {
"transitions": {
"type": "array",
"minItems": 1,
"items": {
"oneOf": [
{
"type": "string"
},
{
"type": "object",
"properties": {
"state": {
"oneOf": [
{
"type": "string"
},
{
"type": "object",
"properties": {
"_gen": {
"type": "string"
}
},
"required": [
"_gen"
]
}
]
},
"times": {
"oneOf": [
{
"type": "integer",
"minimum": 0
},
{
"type": "object",
"properties": {
"_gen": {
"type": "string"
}
},
"required": [
"_gen"
]
}
]
}
},
"required": [
"state",
"times"
]
}
]
}
}
}
}
]
}