Skip to main content

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 it's 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": 2.986971459302512,
"value": {
"action": "start"
},
"headers": null
},
{
"topic": "customers",
"key": 4.7136671026463155,
"value": {
"action": "running"
},
"headers": null
},
{
"topic": "customers",
"key": 4.149928501758961,
"value": {
"action": "stopped"
},
"headers": null
},
{
"topic": "customers",
"key": 1.7995021909293258,
"value": {
"action": "start"
},
"headers": null
},
{
"topic": "customers",
"key": 7.46775678915361,
"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": 3
},
{
"x": 3
},
{
"x": 3
},
{
"x": 2
}
]

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": 1
},
{
"type": "object",
"properties": {
"_gen": {
"type": "string"
}
},
"required": [
"_gen"
]
}
]
}
},
"required": [
"state",
"times"
]
}
]
}
}
}
}
]
}