diff --git a/Services.Test/Concurrency/LoopSettingsTest.cs b/Services.Test/Concurrency/LoopSettingsTest.cs new file mode 100644 index 00000000..fc978af5 --- /dev/null +++ b/Services.Test/Concurrency/LoopSettingsTest.cs @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Concurrency; +using Moq; +using Services.Test.helpers; +using Xunit; +using Xunit.Abstractions; + +namespace Services.Test.Concurrency +{ + public class LoopSettingsTest + { + private readonly ITestOutputHelper log; + private readonly Mock rateLimitingConfig; + private readonly PropertiesLoopSettings propertiesTarget; + + private const int TWIN_WRITES_PER_SECOND = 10; + + public LoopSettingsTest(ITestOutputHelper logger) + { + this.log = logger; + this.rateLimitingConfig = new Mock(); + this.propertiesTarget = new PropertiesLoopSettings(this.rateLimitingConfig.Object); + } + + [Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)] + public void Should_SetTaggingLimit_When_NewLoopCreated() + { + // Arrange + this.SetupRateLimitingConfig(); + + // Act + this.propertiesTarget.NewLoop(); + + // Assert + // In order for other threads to be able to schedule twin opertations, + // value should be at least 1 but less than the limit per second. + Assert.True(this.propertiesTarget.SchedulableTaggings >= 1); + Assert.True(this.propertiesTarget.SchedulableTaggings < TWIN_WRITES_PER_SECOND); + } + + [Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)] + public void Should_UseTwinWrites_When_NewLoopCreated() + { + // Arrange + this.SetupRateLimitingConfig(); + + // Act + this.propertiesTarget.NewLoop(); + + // Assert + // ensure twin writes were accessed and no other + // config values to calculate properties limits + this.rateLimitingConfig.VerifyGet(x => x.TwinWritesPerSecond); + this.rateLimitingConfig.VerifyNoOtherCalls(); + } + + private void SetupRateLimitingConfig() + { + this.rateLimitingConfig.Setup(x => x.TwinWritesPerSecond).Returns(TWIN_WRITES_PER_SECOND); + } + } +} diff --git a/Services/Concurrency/LoopSettings.cs b/Services/Concurrency/LoopSettings.cs index 1b07220b..5f4c632f 100644 --- a/Services/Concurrency/LoopSettings.cs +++ b/Services/Concurrency/LoopSettings.cs @@ -22,7 +22,6 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Concurrency public double SchedulableFetches { get; set; } public double SchedulableRegistrations { get; set; } - public double SchedulableTaggings { get; set; } public ConnectionLoopSettings(IRateLimitingConfig ratingConfig) { @@ -35,7 +34,31 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Concurrency // Prioritize connections and registrations, so that devices connect as soon as possible this.SchedulableFetches = Math.Max(1, this.ratingConfig.RegistryOperationsPerMinute / 25); this.SchedulableRegistrations = Math.Max(1, this.ratingConfig.RegistryOperationsPerMinute / 10); - this.SchedulableTaggings = Math.Max(1, this.ratingConfig.RegistryOperationsPerMinute / 25); + } + } + + public class PropertiesLoopSettings + { + private const int SHARED_TWIN_WRITES_ALLOCATION = 2; + + public const int MIN_LOOP_DURATION = 500; + + private readonly IRateLimitingConfig ratingConfig; + + public double SchedulableTaggings { get; set; } + + public PropertiesLoopSettings(IRateLimitingConfig ratingConfig) + { + this.ratingConfig = ratingConfig; + this.NewLoop(); + } + + public void NewLoop() + { + // In order for other threads to be able to schedule twin operations, + // divide by a constant value to prevent the tagging thread from having + // first priority over twin writes all of the time. + this.SchedulableTaggings = Math.Max(1, this.ratingConfig.TwinWritesPerSecond / SHARED_TWIN_WRITES_ALLOCATION); } } } \ No newline at end of file diff --git a/Services/Diagnostics/ActorsLogger.cs b/Services/Diagnostics/ActorsLogger.cs index eb1bc8c0..b5a470be 100644 --- a/Services/Diagnostics/ActorsLogger.cs +++ b/Services/Diagnostics/ActorsLogger.cs @@ -22,10 +22,10 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Diagnostics void DeviceRegistered(); void DeviceRegistrationFailed(); - void DeviceTwinTaggingScheduled(long time); - void TaggingDeviceTwin(); - void DeviceTwinTagged(); - void DeviceTwinTaggingFailed(); + void DeviceTaggingScheduled(long time); + void TaggingDevice(); + void DeviceTagged(); + void DeviceTaggingFailed(); void DeviceConnectionScheduled(long time); void ConnectingDevice(); @@ -184,37 +184,37 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Diagnostics this.LogRegistry("Registration FAILED"); } - public void DeviceTwinTaggingScheduled(long time) + public void DeviceTaggingScheduled(long time) { if (!this.enabled) return; var msg = DateTimeOffset.FromUnixTimeMilliseconds(time).ToString(DATE_FORMAT); - this.Log("Twin tagging scheduled at: " + msg); - this.LogProperties("Twin tagging scheduled at: " + msg); + this.Log("Device tagging scheduled at: " + msg); + this.LogProperties("Device tagging scheduled at: " + msg); } - public void TaggingDeviceTwin() + public void TaggingDevice() { if (!this.enabled) return; - this.Log("Tagging twin"); + this.Log("Tagging device"); this.LogProperties("Tagging"); } - public void DeviceTwinTagged() + public void DeviceTagged() { if (!this.enabled) return; - this.Log("Twin tagged"); - this.LogProperties("Twin tagged"); + this.Log("Device tagged"); + this.LogProperties("Device tagged"); } - public void DeviceTwinTaggingFailed() + public void DeviceTaggingFailed() { if (!this.enabled) return; - this.Log("Twin tagging FAILED"); - this.LogProperties("Twin tag FAILED"); + this.Log("Device tagging FAILED"); + this.LogProperties("Device tag FAILED"); } public void DeviceConnectionScheduled(long time) diff --git a/SimulationAgent.Test/DeviceConnection/DeviceConnectionActorTest.cs b/SimulationAgent.Test/DeviceConnection/DeviceConnectionActorTest.cs index 6d2209d7..c54cc6ca 100644 --- a/SimulationAgent.Test/DeviceConnection/DeviceConnectionActorTest.cs +++ b/SimulationAgent.Test/DeviceConnection/DeviceConnectionActorTest.cs @@ -20,7 +20,6 @@ namespace SimulationAgent.Test.DeviceConnection private readonly Mock rateLimiting; private readonly Mock fetchLogic; private readonly Mock registerLogic; - private readonly Mock deviceTwinTagLogic; private readonly Mock devices; private readonly Mock connectLogic; private readonly Mock deviceStateActor; @@ -41,9 +40,6 @@ namespace SimulationAgent.Test.DeviceConnection this.registerLogic = new Mock( this.devices.Object, this.logger.Object); - this.deviceTwinTagLogic = new Mock( - this.devices.Object, - this.logger.Object); this.connectLogic = new Mock( this.devices.Object, this.scriptInterpreter.Object, @@ -61,7 +57,6 @@ namespace SimulationAgent.Test.DeviceConnection this.rateLimiting.Object, this.fetchLogic.Object, this.registerLogic.Object, - this.deviceTwinTagLogic.Object, this.connectLogic.Object); } @@ -98,39 +93,6 @@ namespace SimulationAgent.Test.DeviceConnection Assert.Equal(FAILED_DEVICE_CONNECTIONS_COUNT, failedDeviceConnectionCount); } - [Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)] - public void TheNumberOfFailedTwinUpdatesIsZeroAtStart() - { - // Arrange - this.SetupDeviceConnectionActor(); - - // Act - long failedTwinUpdateCount = this.target.FailedTwinUpdatesCount; - - // Assert - Assert.Equal(0, failedTwinUpdateCount); - } - - [Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)] - public void ItReturnsTheNumberOfFailedTwinUpdates() - { - // Arrange - const int FAILED_DEVICE_TWIN_UPDATES_COUNT = 5; - this.SetupDeviceConnectionActor(); - DeviceConnectionActor.ActorEvents deviceTwinTaggingFailed = DeviceConnectionActor.ActorEvents.DeviceTwinTaggingFailed; - - // Act - for (int i = 0; i < FAILED_DEVICE_TWIN_UPDATES_COUNT; i++) - { - this.target.HandleEvent(deviceTwinTaggingFailed); - } - - long failedTwinUpdateCount = this.target.FailedTwinUpdatesCount; - - // Assert - Assert.Equal(FAILED_DEVICE_TWIN_UPDATES_COUNT, failedTwinUpdateCount); - } - private void SetupDeviceConnectionActor() { string DEVICE_ID = "01"; diff --git a/SimulationAgent.Test/DeviceProperties/DevicePropertiesActorTest.cs b/SimulationAgent.Test/DeviceProperties/DevicePropertiesActorTest.cs index da404de5..118ce77a 100644 --- a/SimulationAgent.Test/DeviceProperties/DevicePropertiesActorTest.cs +++ b/SimulationAgent.Test/DeviceProperties/DevicePropertiesActorTest.cs @@ -1,8 +1,10 @@ // Copyright (c) Microsoft. All rights reserved. using System; +using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services; using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Concurrency; using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Diagnostics; +using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Simulation; using Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DeviceConnection; using Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DeviceProperties; using Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DeviceState; @@ -18,13 +20,18 @@ namespace SimulationAgent.Test.DeviceProperties public class DevicePropertiesActorTest { private readonly Mock logger; - - private readonly Mock actorsLogger; private readonly Mock rateLimiting; - private readonly Mock updatePropertiesLogic; + private readonly Mock rateLimitingConfig; + private readonly Mock devices; + private readonly Mock updatePropertiesLogic; + private readonly Mock deviceTagLogic; private readonly Mock deviceConnectionActor; private readonly Mock deviceStateActor; + private readonly Mock loopSettings; + + private const string DEVICE_ID = "01"; + private const int TWIN_WRITES_PER_SECOND = 10; private DevicePropertiesActor target; @@ -32,12 +39,14 @@ namespace SimulationAgent.Test.DeviceProperties { this.logger = new Mock(); this.actorsLogger = new Mock(); - this.rateLimiting = new Mock(); - + this.rateLimitingConfig = new Mock(); this.deviceConnectionActor = new Mock(); this.deviceStateActor = new Mock(); - this.updatePropertiesLogic = new Mock(); + this.devices = new Mock(); + this.loopSettings = new Mock(this.rateLimitingConfig.Object); + this.updatePropertiesLogic = new Mock(this.logger.Object); + this.deviceTagLogic = new Mock(devices.Object, this.logger.Object); this.CreateNewDevicePropertiesActor(); } @@ -46,7 +55,7 @@ namespace SimulationAgent.Test.DeviceProperties public void Setup_Called_Twice_Should_Throw_Already_Initialized_Exception() { // Arrange - CreateNewDevicePropertiesActor(); + this.CreateNewDevicePropertiesActor(); // Act this.SetupDevicePropertiesActor(); @@ -62,7 +71,7 @@ namespace SimulationAgent.Test.DeviceProperties { // Arrange const ActorEvents OUT_OF_RANGE_EVENT = (ActorEvents) 123; - CreateNewDevicePropertiesActor(); + this.CreateNewDevicePropertiesActor(); // Act this.SetupDevicePropertiesActor(); @@ -72,22 +81,89 @@ namespace SimulationAgent.Test.DeviceProperties () => this.target.HandleEvent(OUT_OF_RANGE_EVENT)); } + [Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)] + public void Should_Return_CountOfFailedTwinUpdates_When_TwinUpdateFails() + { + // Arrange + const int FAILED_DEVICE_TWIN_UPDATES_COUNT = 3; + this.CreateNewDevicePropertiesActor(); + this.SetupDevicePropertiesActor(); + this.SetupRateLimitingConfig(); + this.loopSettings.Object.NewLoop(); // resets SchedulableTaggings + + // The constructor should initialize count to zero. + Assert.Equal(0, this.target.FailedTwinUpdatesCount); + + ActorEvents deviceTwinTaggingFailed = ActorEvents.DeviceTaggingFailed; + + // Act + for (int i = 0; i < FAILED_DEVICE_TWIN_UPDATES_COUNT; i++) + { + this.target.HandleEvent(deviceTwinTaggingFailed); + } + + long failedTwinUpdateCount = this.target.FailedTwinUpdatesCount; + + // Assert + Assert.Equal(FAILED_DEVICE_TWIN_UPDATES_COUNT, failedTwinUpdateCount); + } + + [Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)] + public void Should_ReturnZeroForFailedTwinUpdates_When_Started() + { + // Arrange + this.CreateNewDevicePropertiesActor(); + + // Act + long failedTwinUpdateCount = this.target.FailedTwinUpdatesCount; + + // Assert + Assert.Equal(0, failedTwinUpdateCount); + } + private void CreateNewDevicePropertiesActor() { this.target = new DevicePropertiesActor( this.logger.Object, this.actorsLogger.Object, this.rateLimiting.Object, - this.updatePropertiesLogic.Object); + this.updatePropertiesLogic.Object, + this.deviceTagLogic.Object); } private void SetupDevicePropertiesActor() { - string DEVICE_ID = "01"; - this.target.Setup(DEVICE_ID, this.deviceStateActor.Object, - this.deviceConnectionActor.Object); + this.deviceConnectionActor.Object, + this.loopSettings.Object); + } + + private void SetupRateLimitingConfig() + { + this.rateLimitingConfig.SetupGet(x => x.TwinWritesPerSecond).Returns(TWIN_WRITES_PER_SECOND); + } + + private DeviceConnectionActor GetDeviceConnectionActor() + { + Mock scriptInterpreter = new Mock(); + Mock fetchLogic = new Mock( + this.devices.Object, + this.logger.Object); + Mock registerLogic = new Mock( + this.devices.Object, + this.logger.Object); + Mock connectLogic = new Mock( + this.devices.Object, + scriptInterpreter.Object, + this.logger.Object); + return new DeviceConnectionActor( + this.logger.Object, + this.actorsLogger.Object, + this.rateLimiting.Object, + fetchLogic.Object, + registerLogic.Object, + connectLogic.Object); } } } diff --git a/SimulationAgent.Test/DeviceProperties/TagTest.cs b/SimulationAgent.Test/DeviceProperties/TagTest.cs new file mode 100644 index 00000000..7deb7864 --- /dev/null +++ b/SimulationAgent.Test/DeviceProperties/TagTest.cs @@ -0,0 +1,66 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services; +using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Concurrency; +using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Diagnostics; +using Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DeviceConnection; +using Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DeviceProperties; +using Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DeviceState; +using Moq; +using SimulationAgent.Test.helpers; +using Xunit; +using Xunit.Abstractions; + +namespace SimulationAgent.Test.DeviceProperties +{ + public class TagTest + { + private const string DEVICE_ID = "01"; + + private readonly Mock logger; + private Mock devices; + private readonly Mock devicePropertiesActor; + private readonly Mock deviceStateActor; + private readonly Mock deviceConnectionActor; + private readonly Mock rateLimitingConfig; + private readonly Mock loopSettings; + + private SetDeviceTag target; + + public TagTest(ITestOutputHelper log) + { + this.logger = new Mock(); + this.devices = new Mock(); + this.rateLimitingConfig = new Mock(); + this.devicePropertiesActor = new Mock(); + this.deviceStateActor = new Mock(); + this.deviceConnectionActor = new Mock(); + this.loopSettings = new Mock(this.rateLimitingConfig.Object); + + this.target = new SetDeviceTag(this.devices.Object, this.logger.Object); + } + + [Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)] + public void Should_Call_DeviceTagged_When_Succeeded() + { + // Arrange + this.SetupPropertiesActor(); + this.target.Setup(this.devicePropertiesActor.Object, DEVICE_ID); + + // Act + this.target.Run(); + + // Assert + this.devicePropertiesActor.Verify(x => x.HandleEvent(DevicePropertiesActor.ActorEvents.DeviceTagged)); + } + + private void SetupPropertiesActor() + { + this.devicePropertiesActor.Object.Setup( + DEVICE_ID, + this.deviceStateActor.Object, + this.deviceConnectionActor.Object, + this.loopSettings.Object); + } + } +} diff --git a/SimulationAgent.Test/DeviceProperties/UpdateReportedPropertiesTest.cs b/SimulationAgent.Test/DeviceProperties/UpdateReportedPropertiesTest.cs index 1a052fa9..ef7fd4e6 100644 --- a/SimulationAgent.Test/DeviceProperties/UpdateReportedPropertiesTest.cs +++ b/SimulationAgent.Test/DeviceProperties/UpdateReportedPropertiesTest.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services; +using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Concurrency; using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Diagnostics; using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models; using Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DeviceConnection; @@ -20,22 +21,27 @@ namespace SimulationAgent.Test.DeviceProperties { private const string DEVICE_ID = "01"; private readonly Mock logger; + private readonly Mock rateLimitingConfig; private readonly Mock devicePropertiesActor; private readonly Mock deviceStateActor; private readonly Mock deviceConnectionActor; private readonly Mock properties; private readonly Mock client; + private readonly Mock loopSettings; private UpdateReportedProperties target; public UpdateReportedPropertiesTest(ITestOutputHelper log) { this.logger = new Mock(); + this.rateLimitingConfig = new Mock(); this.devicePropertiesActor = new Mock(); this.deviceStateActor = new Mock(); this.deviceConnectionActor = new Mock(); this.properties = new Mock(); this.client = new Mock(); + this.loopSettings = new Mock( + this.rateLimitingConfig.Object); this.target = new UpdateReportedProperties(logger.Object); } @@ -79,7 +85,8 @@ namespace SimulationAgent.Test.DeviceProperties this.devicePropertiesActor.Object.Setup( DEVICE_ID, this.deviceStateActor.Object, - this.deviceConnectionActor.Object); + this.deviceConnectionActor.Object, + this.loopSettings.Object); } private void SetupPropertiesActorProperties() diff --git a/SimulationAgent.Test/SimulationRunnerTest.cs b/SimulationAgent.Test/SimulationRunnerTest.cs index 0dd866f3..2ffc87d4 100644 --- a/SimulationAgent.Test/SimulationRunnerTest.cs +++ b/SimulationAgent.Test/SimulationRunnerTest.cs @@ -217,7 +217,7 @@ namespace SimulationAgent.Test this.SetupSimulationReadyToStart(); - this.deviceConnectionActor + this.devicePropertiesActor .Setup(x => x.FailedTwinUpdatesCount) .Returns(FAILED_DEVICE_TWIN_UPDATES_COUNT); diff --git a/SimulationAgent/DeviceConnection/DeviceConnectionActor.cs b/SimulationAgent/DeviceConnection/DeviceConnectionActor.cs index 5f77bfe2..5f1cd7ce 100644 --- a/SimulationAgent/DeviceConnection/DeviceConnectionActor.cs +++ b/SimulationAgent/DeviceConnection/DeviceConnectionActor.cs @@ -18,7 +18,6 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DeviceCo Device Device { get; set; } bool Connected { get; } long FailedDeviceConnectionsCount { get; } - long FailedTwinUpdatesCount { get; } long SimulationErrorsCount { get; } void Setup(string deviceId, DeviceModel deviceModel, IDeviceStateActor deviceStateActor, ConnectionLoopSettings loopSettings); @@ -27,9 +26,6 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DeviceCo void Stop(); } - /** - * TODO: when the device exists already, check whether it is tagged - */ public class DeviceConnectionActor : IDeviceConnectionActor { private enum ActorStatus @@ -40,8 +36,6 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DeviceCo Fetching, ReadyToRegister, Registering, - ReadyToTagDeviceTwin, - TaggingDeviceTwin, ReadyToConnect, Connecting, Done, @@ -56,8 +50,6 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DeviceCo FetchCompleted, RegistrationFailed, DeviceRegistered, - DeviceTwinTaggingFailed, - DeviceTwinTagged, Connected, ConnectionFailed } @@ -67,7 +59,6 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DeviceCo private readonly IRateLimiting rateLimiting; private readonly IDeviceConnectionLogic fetchLogic; private readonly IDeviceConnectionLogic registerLogic; - private readonly IDeviceConnectionLogic deviceTwinTagLogic; private readonly IDeviceConnectionLogic connectLogic; private ActorStatus status; @@ -76,7 +67,6 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DeviceCo private long whenToRun; private ConnectionLoopSettings loopSettings; private long failedDeviceConnectionsCount; - private long failedTwinUpdatesCount; private long failedRegistrationsCount; private long failedFetchCount; @@ -121,17 +111,11 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DeviceCo /// public long FailedDeviceConnectionsCount => this.failedDeviceConnectionsCount; - /// - /// Failed device twin updates counter - /// - public long FailedTwinUpdatesCount => this.failedTwinUpdatesCount; - /// /// Simulation error counter in DeviceConnectionActor /// public long SimulationErrorsCount => this.failedRegistrationsCount + this.failedFetchCount + - this.FailedTwinUpdatesCount + this.FailedDeviceConnectionsCount; public DeviceConnectionActor( @@ -140,7 +124,6 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DeviceCo IRateLimiting rateLimiting, Fetch fetchLogic, Register registerLogic, - DeviceTwinTag deviceTwinTagLogic, Connect connectLogic) { this.log = logger; @@ -149,7 +132,6 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DeviceCo this.fetchLogic = fetchLogic; this.registerLogic = registerLogic; - this.deviceTwinTagLogic = deviceTwinTagLogic; this.connectLogic = connectLogic; this.Message = null; @@ -162,10 +144,9 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DeviceCo this.deviceStateActor = null; this.failedDeviceConnectionsCount = 0; - this.failedTwinUpdatesCount = 0; this.failedRegistrationsCount = 0; this.failedFetchCount = 0; - } + } /// /// Invoke this method before calling Execute(), to initialize the actor @@ -188,7 +169,6 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DeviceCo this.fetchLogic.Setup(this, this.deviceId, this.deviceModel); this.registerLogic.Setup(this, this.deviceId, this.deviceModel); - this.deviceTwinTagLogic.Setup(this, this.deviceId, this.deviceModel); this.connectLogic.Setup(this, this.deviceId, this.deviceModel); this.actorLogger.Setup(deviceId, "Connection"); @@ -239,11 +219,8 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DeviceCo break; case ActorEvents.DeviceRegistered: - if (this.loopSettings.SchedulableTaggings <= 0) return; - this.loopSettings.SchedulableTaggings--; - this.actorLogger.DeviceRegistered(); - this.ScheduleDeviceTagging(); + this.ScheduleConnection(); break; case ActorEvents.RegistrationFailed: @@ -255,25 +232,11 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DeviceCo this.ScheduleRegistration(); break; - case ActorEvents.DeviceTwinTaggingFailed: - if (this.loopSettings.SchedulableTaggings <= 0) return; - this.loopSettings.SchedulableTaggings--; - - this.failedTwinUpdatesCount++; - this.actorLogger.DeviceTwinTaggingFailed(); - this.ScheduleDeviceTagging(); - break; - case ActorEvents.FetchCompleted: this.actorLogger.DeviceFetched(); this.ScheduleConnection(); break; - case ActorEvents.DeviceTwinTagged: - this.actorLogger.DeviceTwinTagged(); - this.ScheduleConnection(); - break; - case ActorEvents.ConnectionFailed: this.failedDeviceConnectionsCount++; this.actorLogger.DeviceConnectionFailed(); @@ -321,12 +284,6 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DeviceCo this.actorLogger.ConnectingDevice(); this.connectLogic.RunAsync(); return; - - case ActorStatus.ReadyToTagDeviceTwin: - this.status = ActorStatus.TaggingDeviceTwin; - this.actorLogger.TaggingDeviceTwin(); - this.deviceTwinTagLogic.RunAsync(); - return; } } @@ -364,24 +321,6 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DeviceCo }); } - private void ScheduleDeviceTagging() - { - var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); - // note: we overwrite the twin, so no Read operation is needed - var pauseMsec = this.rateLimiting.GetPauseForNextTwinWrite(); - this.whenToRun = now + pauseMsec; - this.status = ActorStatus.ReadyToTagDeviceTwin; - - this.actorLogger.DeviceTwinTaggingScheduled(this.whenToRun); - this.log.Debug("Device twin tagging scheduled", - () => new - { - this.deviceId, - Status = this.status.ToString(), - When = this.log.FormatDate(this.whenToRun) - }); - } - private void ScheduleConnection() { var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); diff --git a/SimulationAgent/DeviceConnection/DeviceTwinTag.cs b/SimulationAgent/DeviceConnection/DeviceTwinTag.cs deleted file mode 100644 index 8d41c68c..00000000 --- a/SimulationAgent/DeviceConnection/DeviceTwinTag.cs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Threading.Tasks; -using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services; -using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Diagnostics; -using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models; - -namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DeviceConnection -{ - /// - /// Add twin information to the new device twin - /// - public class DeviceTwinTag : IDeviceConnectionLogic - { - private readonly IDevices devices; - private readonly ILogger log; - private string deviceId; - private IDeviceConnectionActor context; - - public DeviceTwinTag(IDevices devices, ILogger logger) - { - this.log = logger; - this.devices = devices; - } - - public void Setup(IDeviceConnectionActor context, string deviceId, DeviceModel deviceModel) - { - this.context = context; - this.deviceId = deviceId; - } - - public async Task RunAsync() - { - this.log.Debug("Adding tag to device twin...", () => new { this.deviceId }); - try - { - var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); - await this.devices.AddTagAsync(this.deviceId); - - var timeSpent = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() - now; - this.log.Debug("Device tag added", () => new { this.deviceId, timeSpent }); - - this.context.HandleEvent(DeviceConnectionActor.ActorEvents.DeviceTwinTagged); - } - catch (Exception e) - { - this.log.Error("Error while tagging the device twin", () => new { this.deviceId, e }); - this.context.HandleEvent(DeviceConnectionActor.ActorEvents.DeviceTwinTaggingFailed); - } - } - } -} diff --git a/SimulationAgent/DeviceProperties/DevicePropertiesActor.cs b/SimulationAgent/DeviceProperties/DevicePropertiesActor.cs index 16b8bbb3..50214316 100644 --- a/SimulationAgent/DeviceProperties/DevicePropertiesActor.cs +++ b/SimulationAgent/DeviceProperties/DevicePropertiesActor.cs @@ -16,23 +16,33 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DevicePr ISmartDictionary DeviceProperties { get; } ISmartDictionary DeviceState { get; } IDeviceClient Client { get; } + long FailedTwinUpdatesCount { get; } + long SimulationErrorsCount { get; } void Setup( string deviceId, IDeviceStateActor deviceStateActor, - IDeviceConnectionActor deviceConnectionActor); + IDeviceConnectionActor deviceConnectionActor, + PropertiesLoopSettings loopSettings); string Run(); void HandleEvent(DevicePropertiesActor.ActorEvents e); void Stop(); } + /// + /// The Device Properties Actor is responsible for sending updates + /// to the IoT Hub Device Twin. This includes adding an initial tag to + /// the device twin, and pushing changes to the device properties. + /// public class DevicePropertiesActor : IDevicePropertiesActor { private enum ActorStatus { None, ReadyToStart, + ReadyToTagDevice, + TaggingDevice, WaitingForChanges, ReadyToUpdate, Updating, @@ -42,6 +52,8 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DevicePr public enum ActorEvents { Started, + DeviceTaggingFailed, + DeviceTagged, PropertiesUpdateFailed, PropertiesUpdated, } @@ -50,10 +62,13 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DevicePr private readonly IActorsLogger actorLogger; private readonly IRateLimiting rateLimiting; private readonly IDevicePropertiesLogic updatePropertiesLogic; + private readonly IDevicePropertiesLogic deviceSetDeviceTagLogic; private ActorStatus status; private string deviceId; private long whenToRun; + private PropertiesLoopSettings loopSettings; + private long failedTwinUpdatesCount; /// /// Reference to the actor managing the device state, used @@ -81,21 +96,35 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DevicePr /// public IDeviceClient Client => this.deviceConnectionActor.Client; + /// + /// Failed device twin updates counter + /// + public long FailedTwinUpdatesCount => this.failedTwinUpdatesCount; + + /// + /// Simulation error counter in DeviceConnectionActor + /// + public long SimulationErrorsCount => this.FailedTwinUpdatesCount; + public DevicePropertiesActor( ILogger logger, IActorsLogger actorLogger, IRateLimiting rateLimiting, - IDevicePropertiesLogic updatePropertiesLogic) + UpdateReportedProperties updatePropertiesLogic, + SetDeviceTag deviceSetDeviceTagLogic) { this.log = logger; this.actorLogger = actorLogger; this.rateLimiting = rateLimiting; this.updatePropertiesLogic = updatePropertiesLogic; + this.deviceSetDeviceTagLogic = deviceSetDeviceTagLogic; this.status = ActorStatus.None; this.deviceId = null; this.deviceStateActor = null; this.deviceConnectionActor = null; + + this.failedTwinUpdatesCount = 0; } /// @@ -105,7 +134,8 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DevicePr public void Setup( string deviceId, IDeviceStateActor deviceStateActor, - IDeviceConnectionActor deviceConnectionActor) + IDeviceConnectionActor deviceConnectionActor, + PropertiesLoopSettings loopSettings) { if (this.status != ActorStatus.None) { @@ -117,6 +147,10 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DevicePr this.deviceId = deviceId; this.deviceStateActor = deviceStateActor; this.deviceConnectionActor = deviceConnectionActor; + this.loopSettings = loopSettings; + + this.updatePropertiesLogic.Setup(this, this.deviceId); + this.deviceSetDeviceTagLogic.Setup(this, this.deviceId); this.updatePropertiesLogic.Setup(this, this.deviceId); this.actorLogger.Setup(deviceId, "Properties"); @@ -130,15 +164,34 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DevicePr { case ActorEvents.Started: this.actorLogger.ActorStarted(); + this.status = ActorStatus.ReadyToTagDevice; break; + + case ActorEvents.DeviceTagged: + this.actorLogger.DeviceTagged(); + this.status = ActorStatus.WaitingForChanges; + break; + + case ActorEvents.DeviceTaggingFailed: + if (this.loopSettings.SchedulableTaggings <= 0) return; + this.loopSettings.SchedulableTaggings--; + + this.failedTwinUpdatesCount++; + this.actorLogger.DeviceTaggingFailed(); + this.ScheduleDeviceTagging(); + break; + case ActorEvents.PropertiesUpdated: this.actorLogger.DevicePropertiesUpdated(); this.status = ActorStatus.WaitingForChanges; break; + case ActorEvents.PropertiesUpdateFailed: + this.failedTwinUpdatesCount++; this.actorLogger.DevicePropertiesUpdateFailed(); this.SchedulePropertiesUpdate(isRetry: true); break; + default: throw new ArgumentOutOfRangeException(nameof(e), e, null); } @@ -160,6 +213,12 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DevicePr this.HandleEvent(ActorEvents.Started); return "started"; + case ActorStatus.ReadyToTagDevice: + this.status = ActorStatus.TaggingDevice; + this.actorLogger.TaggingDevice(); + this.deviceSetDeviceTagLogic.Run(); + return "device tag scheduled"; + case ActorStatus.WaitingForChanges: if (!this.DeviceProperties.Changed) return "no properties to update"; this.SchedulePropertiesUpdate(); @@ -201,5 +260,23 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DevicePr isRetry }); } + + private void ScheduleDeviceTagging() + { + var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + // note: we overwrite the twin, so no Read operation is needed + var pauseMsec = this.rateLimiting.GetPauseForNextTwinWrite(); + this.whenToRun = now + pauseMsec; + this.status = ActorStatus.ReadyToTagDevice; + + this.actorLogger.DeviceTaggingScheduled(this.whenToRun); + this.log.Debug("Device twin tagging scheduled", + () => new + { + this.deviceId, + Status = this.status.ToString(), + When = this.log.FormatDate(this.whenToRun) + }); + } } } diff --git a/SimulationAgent/DeviceProperties/SetDeviceTag.cs b/SimulationAgent/DeviceProperties/SetDeviceTag.cs new file mode 100644 index 00000000..07086f54 --- /dev/null +++ b/SimulationAgent/DeviceProperties/SetDeviceTag.cs @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services; +using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Diagnostics; + +namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DeviceProperties +{ + /// + /// Add twin information to the new device twin + /// + public class SetDeviceTag : IDevicePropertiesLogic + { + private readonly IDevices devices; + private readonly ILogger log; + private string deviceId; + private IDevicePropertiesActor context; + + public SetDeviceTag(IDevices devices, ILogger logger) + { + this.log = logger; + this.devices = devices; + } + + public void Setup(IDevicePropertiesActor context, string deviceId) + { + this.context = context; + this.deviceId = deviceId; + } + + public void Run() + { + this.log.Debug("Adding tag to device twin...", () => new { this.deviceId }); + + var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + + try + { + /* + * ContinueWith allows to easily manage the exceptions here, with the ability to change + * the code to synchronous or asynchronous, via TaskContinuationOptions. + * + * Once the code successfully handle all the scenarios, with good throughput and low CPU usage + * we should see if the async/await syntax performs similarly/better. + */ + this.devices + .AddTagAsync(this.deviceId) + .ContinueWith(t => + { + var timeSpent = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() - now; + + if (t.IsCanceled) + { + this.log.Warn("The set device tag task has been cancelled", () => new { this.deviceId, t.Exception }); + } + else if (t.IsFaulted) + { + var exception = t.Exception.InnerExceptions.FirstOrDefault(); + this.log.Error(GetLogErrorMessage(exception), () => new { this.deviceId, exception }); + this.context.HandleEvent(DevicePropertiesActor.ActorEvents.DeviceTaggingFailed); + } + else if (t.IsCompleted) + { + this.log.Debug("Device tag set", () => new { this.deviceId, timeSpent }); + this.context.HandleEvent(DevicePropertiesActor.ActorEvents.DeviceTagged); + } + }, + TaskContinuationOptions.ExecuteSynchronously); + } + catch (Exception e) + { + this.log.Error("Unexpected error while tagging the device twin", () => new { this.deviceId, e }); + this.context.HandleEvent(DevicePropertiesActor.ActorEvents.DeviceTaggingFailed); + } + } + + private static string GetLogErrorMessage(Exception e) + { + return e != null ? "Set device tag error: " + e.Message : string.Empty; + } + } +} diff --git a/SimulationAgent/SimulationAgent.csproj b/SimulationAgent/SimulationAgent.csproj index 59923ead..1d420abd 100644 --- a/SimulationAgent/SimulationAgent.csproj +++ b/SimulationAgent/SimulationAgent.csproj @@ -8,4 +8,4 @@ - \ No newline at end of file + diff --git a/SimulationAgent/SimulationRunner.cs b/SimulationAgent/SimulationRunner.cs index 753a8bde..e5ca6c8f 100644 --- a/SimulationAgent/SimulationRunner.cs +++ b/SimulationAgent/SimulationRunner.cs @@ -38,9 +38,12 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent // Application logger private readonly ILogger log; - // Set of counter to optimize scheduling + // Settings to optimize scheduling private readonly ConnectionLoopSettings connectionLoopSettings; + // Settings to optimize scheduling + private readonly PropertiesLoopSettings propertiesLoopSettings; + // Service used to load device models details private readonly IDeviceModels deviceModels; @@ -106,6 +109,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent IFactory factory) { this.connectionLoopSettings = new ConnectionLoopSettings(ratingConfig); + this.propertiesLoopSettings = new PropertiesLoopSettings(ratingConfig); this.log = logger; this.deviceModels = deviceModels; @@ -277,13 +281,14 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent public long FailedDeviceConnectionsCount => this.deviceConnectionActors.Sum(a => a.Value.FailedDeviceConnectionsCount); // Method to return the count of twin update failed devices - public long FailedDeviceTwinUpdatesCount => this.deviceConnectionActors.Sum(a => a.Value.FailedTwinUpdatesCount); + public long FailedDeviceTwinUpdatesCount => this.devicePropertiesActors.Sum(a => a.Value.FailedTwinUpdatesCount); // Method to return the count of simulation errors public long SimulationErrorsCount => this.simulationErrors + this.deviceConnectionActors.Sum(a => a.Value.SimulationErrorsCount) + this.deviceStateActors.Sum(a => a.Value.SimulationErrorsCount) + - this.deviceTelemetryActors.Sum(a => a.Value.FailedMessagesCount); + this.deviceTelemetryActors.Sum(a => a.Value.FailedMessagesCount) + + this.devicePropertiesActors.Sum(a => a.Value.SimulationErrorsCount); private DeviceModel GetDeviceModel(string id, Services.Models.Simulation.DeviceModelOverride overrides) { @@ -339,6 +344,8 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent { while (this.running) { + this.propertiesLoopSettings.NewLoop(); + var before = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); foreach (var device in this.devicePropertiesActors) { @@ -425,7 +432,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent // Create one device properties actor for each device var devicePropertiesActor = this.factory.Resolve(); - devicePropertiesActor.Setup(deviceId, deviceStateActor, deviceConnectionActor); + devicePropertiesActor.Setup(deviceId, deviceStateActor, deviceConnectionActor, this.propertiesLoopSettings); this.devicePropertiesActors.Add(key, devicePropertiesActor); // Create one telemetry actor for each telemetry message to be sent diff --git a/WebService/DependencyResolution.cs b/WebService/DependencyResolution.cs index ec06b5ec..c1494894 100644 --- a/WebService/DependencyResolution.cs +++ b/WebService/DependencyResolution.cs @@ -9,6 +9,7 @@ using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Diagnostics; using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Runtime; using Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent; using Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DeviceConnection; +using Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DeviceProperties; using Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DeviceState; using Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DeviceTelemetry; using Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.Runtime; @@ -98,11 +99,12 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService // Registrations required by Autofac, these classes implement the same interface builder.RegisterType().As(); - builder.RegisterType().As(); + builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); + builder.RegisterType().As(); } private static void RegisterFactory(IContainer container)