Skip to content

Commit

Permalink
Merge pull request #32 from fbeltrao/pattern-simulation-rebase
Browse files Browse the repository at this point in the history
Add support to variable sequences and file based configuration
  • Loading branch information
fbeltrao authored Sep 29, 2021
2 parents 605ebfe + f0ac745 commit 65c0de7
Show file tree
Hide file tree
Showing 23 changed files with 923 additions and 50 deletions.
67 changes: 62 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ A single AMQP connection can handle approximately 995 devices.
The quickest way to generate telemetry is using Docker with the following command:

```bash
docker run -it -e "IotHubConnectionString=HostName=your-iothub-name.azure-devices.net;SharedAccessKeyName=device;SharedAccessKey=your-iothub-key" mcr.microsoft.com/oss/azure-samples/azureiot-telemetrysimulator:latest
docker run -it -e "IotHubConnectionString=HostName=your-iothub-name.azure-devices.net;SharedAccessKeyName=device;SharedAccessKey=your-iothub-key" mcr.microsoft.com/oss/azure-samples/azureiot-telemetrysimulator
```

**The simulator expects the devices to already exist in Azure IoT Hub**. If you need help creating simulation devices in an Azure IoT Hub use the included project IotSimulatorDeviceProvisioning or the Docker image:

```bash
docker run -it -e "IotHubConnectionString=HostName=your-iothub-name.azure-devices.net;SharedAccessKeyName=registryReadWrite;SharedAccessKey=your-iothub-key" -e DeviceCount=1000 mcr.microsoft.com/oss/azure-samples/azureiot-simulatordeviceprovisioning:latest
docker run -it -e "IotHubConnectionString=HostName=your-iothub-name.azure-devices.net;SharedAccessKeyName=registryReadWrite;SharedAccessKey=your-iothub-key" -e DeviceCount=1000 mcr.microsoft.com/oss/azure-samples/azureiot-simulatordeviceprovisioning
```

## Simulator input parameters
Expand All @@ -60,6 +60,8 @@ The amount of devices, their names and telemetry generated can be customized usi
|PartitionKey|optional [partition key](https://docs.microsoft.com/en-us/azure/event-hubs/event-hubs-features#partitions) template for Event Hubs (see telemetry template and [Advanced options](#advanced-options))|
|Variables|telemetry variables (see telemetry template)|
|DuplicateEveryNEvents|if > 0, send duplicates of the given fraction of messages. See [Advanced options](#advanced-options) (default = 0)|
|File|Defines a json file where templates, variables and device based intervals can be defined. File and environment variable configuration can be used in conjunction.|
|Intervals|Allows customizing the message interval per device|

## Telemetry template

Expand Down Expand Up @@ -99,6 +101,8 @@ Customizable variables can be created with the following properties:
|max|The maximum value generated|
|values|Defines an array of possible values. Example ["on", "off"]|
|customlengthstring|Creates a random string of n bytes. Provide n as parameter|
|sequence|Create a sequence of values as defined in `values` property, producing one after the other. Values can reference other non-sequence variables|


#### Example 1: Telemetry with temperature between 23 and 25 and a counter starting from 100

Expand All @@ -125,13 +129,13 @@ Output:
Running with Docker:

```powershell
docker run -it -e "IotHubConnectionString=HostName=your-iothub-name.azure-devices.net;SharedAccessKeyName=device;SharedAccessKey=your-iothub-key" -e Template="{ \"deviceId\": \"$.DeviceId\", \"temp\": $.Temp, \"Ticks\": $.Ticks, \"Counter\": $.Counter, \"time\": \"$.Time\" }" -e Variables="[{name: \"Temp\", \"random\": true, \"max\": 25, \"min\": 23}, {\"name\":\"Counter\", \"min\":100} ]" mcr.microsoft.com/oss/azure-samples/azureiot-telemetrysimulator:latest
docker run -it -e "IotHubConnectionString=HostName=your-iothub-name.azure-devices.net;SharedAccessKeyName=device;SharedAccessKey=your-iothub-key" -e Template="{ \"deviceId\": \"$.DeviceId\", \"temp\": $.Temp, \"Ticks\": $.Ticks, \"Counter\": $.Counter, \"time\": \"$.Time\" }" -e Variables="[{name: \"Temp\", \"random\": true, \"max\": 25, \"min\": 23}, {\"name\":\"Counter\", \"min\":100} ]" mcr.microsoft.com/oss/azure-samples/azureiot-telemetrysimulator
```

calling from PowerShell:

```powershell
docker run -it -e "IotHubConnectionString=HostName=your-iothub-name.azure-devices.net;SharedAccessKeyName=iothubowner;SharedAccessKey=your-iothub-key" -e Template="{ \"""deviceId\""": \"""$.DeviceId\""", \"""temp\""": $.Temp, \"""Ticks\""": $.Ticks, \"""Counter\""": $.Counter, \"""time\""": \"""$.Time\""", \"""engine\""": \"""$.Engine\""" }" -e Variables="[{name: \"""Temp\""", \"""random\""": true, \"""max\""": 25, \"""min\""": 23}, {\"""name\""":\"""Counter\""", \"""min\""":100}, {name:\"""Engine\""", values: [\"""on\""", \"""off\"""]}]" -e DeviceCount=1 -e MessageCount=3 mcr.microsoft.com/oss/azure-samples/azureiot-telemetrysimulator:latest
docker run -it -e "IotHubConnectionString=HostName=your-iothub-name.azure-devices.net;SharedAccessKeyName=iothubowner;SharedAccessKey=your-iothub-key" -e Template="{ \"""deviceId\""": \"""$.DeviceId\""", \"""temp\""": $.Temp, \"""Ticks\""": $.Ticks, \"""Counter\""": $.Counter, \"""time\""": \"""$.Time\""", \"""engine\""": \"""$.Engine\""" }" -e Variables="[{name: \"""Temp\""", \"""random\""": true, \"""max\""": 25, \"""min\""": 23}, {\"""name\""":\"""Counter\""", \"""min\""":100}, {name:\"""Engine\""", values: [\"""on\""", \"""off\"""]}]" -e DeviceCount=1 -e MessageCount=3 mcr.microsoft.com/oss/azure-samples/azureiot-telemetrysimulator
```

#### Example 2: Adding the engine status ("on" or "off") to the telemetry
Expand All @@ -158,7 +162,60 @@ Output:
Running with Docker:

```bash
docker run -it -e "IotHubConnectionString=HostName=your-iothub-name.azure-devices.net;SharedAccessKeyName=device;SharedAccessKey=your-iothub-key" -e Template="{ \"deviceId\": \"$.DeviceId\", \"temp\": $.Temp, \"Ticks\": $.Ticks, \"Counter\": $.Counter, \"time\": \"$.Time\", \"engine\": \"$.Engine\" }" -e Variables="[{name: \"Temp\", \"random\": true, \"max\": 25, \"min\": 23}, {\"name\":\"Counter\", \"min\":100}, {name:\"Engine\", values: [\"on\", \"off\"]}]" mcr.microsoft.com/oss/azure-samples/azureiot-telemetrysimulator:latest
docker run -it -e "IotHubConnectionString=HostName=your-iothub-name.azure-devices.net;SharedAccessKeyName=device;SharedAccessKey=your-iothub-key" -e Template="{ \"deviceId\": \"$.DeviceId\", \"temp\": $.Temp, \"Ticks\": $.Ticks, \"Counter\": $.Counter, \"time\": \"$.Time\", \"engine\": \"$.Engine\" }" -e Variables="[{name: \"Temp\", \"random\": true, \"max\": 25, \"min\": 23}, {\"name\":\"Counter\", \"min\":100}, {name:\"Engine\", values: [\"on\", \"off\"]}]" mcr.microsoft.com/oss/azure-samples/azureiot-telemetrysimulator
```

#### Example 3: Using a configuration file to customize simulation

```bash
docker run -it -e "IotHubConnectionString=HostName=your-iothub-name.azure-devices.net;SharedAccessKeyName=device;SharedAccessKey=your-iothub-key" -e "File=/config_files/test4-config-multiple-internals-per-device.json" -e DeviceCount=3 --mount type=bind,source=$pwd\test\IotTelemetrySimulator.Test\test_files,target=/config_files,readonly mcr.microsoft.com/oss/azure-samples/azureiot-telemetrysimulator
```

Where the file content is:

```plain
{
"Variables": [
{
"name": "DeviceSequenceValue1",
"sequence": true,
"values": [ "$.Counter", "$.Counter", "$.Counter", "$.Counter", "$.Counter", "true", "false", "$.Counter" ]
},
{
"name": "Device1Tags",
"sequence": true,
"values": [ "['ProducedPartCount']", "['ProducedPartCount']", "['ProducedPartCount']", "['ProducedPartCount']", "['ProducedPartCount']", "['Downtime']", "['Downtime']", "['ProducedPartCount']" ]
},
{
"name": "Device1Downtime",
"values": [ "true", "true", "true", "true", "false" ]
},
{
"name": "Counter"
}
],
"Intervals": {
"sim000001": 10000,
"sim000002": 100
},
"Payloads": [
{
"type": "template",
"deviceId": "sim000001",
"template": "{\"device\":\"$.DeviceId\",\"value\":\"$.DeviceSequenceValue1\",\"tags\": $.Device1Tags}"
},
{
"type": "fix",
"deviceId": "sim000002",
"value": "{\"value\":\"myfixvalue\"}"
},
{
"type": "template",
"deviceId": "sim000003",
"template": "{\"device\":\"$.DeviceId\",\"a\":\"b\",\"value\":\"$.DeviceSequenceValue1\"}"
}
]
}
```

## Generating high volume of telemetry
Expand Down
3 changes: 2 additions & 1 deletion src/IotTelemetrySimulator/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@ public static class Constants
public const string DeviceIdValueName = "DeviceId";
public const string GuidValueName = "Guid";
public const string MachineNameValueName = "MachineName";
public const string IterationNumberValueName = "IterationNumber";
public static readonly string[] AllSpecialValueNames =
{
TimeValueName, LocalTimeValueName, EpochValueName, TicksValueName,
DeviceIdValueName, GuidValueName, MachineNameValueName,
DeviceIdValueName, GuidValueName, MachineNameValueName, IterationNumberValueName
};
}
}
7 changes: 6 additions & 1 deletion src/IotTelemetrySimulator/FixPayload.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ public class FixPayload : PayloadBase
public byte[] Payload { get; }

public FixPayload(int distribution, byte[] payload)
: base(distribution)
: this(distribution, null, payload)
{
}

public FixPayload(int distribution, string deviceId, byte[] payload)
: base(distribution, deviceId)
{
this.Payload = payload;
}
Expand Down
2 changes: 1 addition & 1 deletion src/IotTelemetrySimulator/IotHubSender.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ internal class IotHubSender : SenderBase<Message>
const string ApplicationJsonContentType = "application/json";
const string Utf8Encoding = "utf-8";

private DeviceClient deviceClient;
private readonly DeviceClient deviceClient;

public IotHubSender(DeviceClient deviceClient, string deviceId, RunnerConfiguration config)
: base(deviceId, config)
Expand Down
10 changes: 9 additions & 1 deletion src/IotTelemetrySimulator/PayloadBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,20 @@ public abstract class PayloadBase
{
public int Distribution { get; set; }

public PayloadBase(int distribution)
public string DeviceId { get; set; }

protected PayloadBase(int distribution)
: this(distribution, null)
{
}

protected PayloadBase(int distribution, string deviceId)
{
if (distribution < 1 || distribution > 100)
throw new ArgumentOutOfRangeException(nameof(distribution), "Distribution must be between 1 and 100");

this.Distribution = distribution;
this.DeviceId = deviceId;
}

public abstract (byte[], Dictionary<string, object>) Generate(Dictionary<string, object> variableValues);
Expand Down
10 changes: 9 additions & 1 deletion src/IotTelemetrySimulator/PayloadGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ public class PayloadGenerator

public PayloadBase[] Payloads { get; }

private readonly Dictionary<string, PayloadBase> payloadsPerDevice;

public PayloadGenerator(IEnumerable<PayloadBase> payloads, IRandomizer randomizer)
{
this.randomizer = randomizer ?? throw new ArgumentNullException(nameof(randomizer));
Expand All @@ -19,13 +21,19 @@ public PayloadGenerator(IEnumerable<PayloadBase> payloads, IRandomizer randomize
}

this.Payloads = payloads.OrderByDescending(x => x.Distribution).ToArray();
this.payloadsPerDevice = this.Payloads
.Where(x => !string.IsNullOrEmpty(x.DeviceId))
.ToDictionary(x => x.DeviceId);
}

public (byte[], Dictionary<string, object>) Generate(Dictionary<string, object> variableValues)
public (byte[], Dictionary<string, object>) Generate(string deviceId, Dictionary<string, object> variableValues)
{
if (this.Payloads.Length == 1)
return this.Payloads[0].Generate(variableValues);

if (deviceId != null && this.payloadsPerDevice.TryGetValue(deviceId, out var payloadForDevice))
return payloadForDevice.Generate(variableValues);

var random = this.randomizer.Next(1, 101);
var currentPercentage = 0;
foreach (var payload in this.Payloads)
Expand Down
20 changes: 15 additions & 5 deletions src/IotTelemetrySimulator/Program.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
namespace IotTelemetrySimulator
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

Expand All @@ -22,6 +18,20 @@ public static IHostBuilder CreateHostBuilder(string[] args)
#if DEBUG
.UseEnvironment("Development")
#endif
.ConfigureAppConfiguration(builder =>
{
var tempConfig = builder.Build();
var fileConfig = tempConfig["File"];
if (!string.IsNullOrEmpty(fileConfig))
{
builder.AddJsonFile(fileConfig, optional: false, reloadOnChange: false);
}

if (tempConfig is IDisposable diposableConfig)
{
diposableConfig.Dispose();
}
})
.ConfigureServices((hostContext, services) =>
{
services.AddSingleton<IDeviceSimulatorFactory, DefaultDeviceSimulatorFactory>();
Expand Down
Loading

0 comments on commit 65c0de7

Please sign in to comment.