From 934bf9a14a85b2af478d7ac05640a8bcb78aa658 Mon Sep 17 00:00:00 2001 From: Jared Goodwin Date: Fri, 2 Jun 2023 13:31:01 -0700 Subject: [PATCH] Adjust load tester. Made AddOrUpdateDevice async. --- Server/Hubs/AgentHub.cs | 29 +++-- Server/Services/DataService.cs | 60 +++++----- Tests/LoadTester/Program.cs | 157 +++++++++++++++---------- Tests/Server.Tests/DataServiceTests.cs | 5 +- Tests/Server.Tests/TestData.cs | 4 +- 5 files changed, 145 insertions(+), 110 deletions(-) diff --git a/Server/Hubs/AgentHub.cs b/Server/Hubs/AgentHub.cs index 0658a714..b02bc4bd 100644 --- a/Server/Hubs/AgentHub.cs +++ b/Server/Hubs/AgentHub.cs @@ -87,13 +87,13 @@ namespace Remotely.Server.Hubs } } - public Task DeviceCameOnline(Device device) + public async Task DeviceCameOnline(Device device) { try { if (CheckForDeviceBan(device.ID, device.DeviceName)) { - return Task.FromResult(false); + return false; } var ip = Context.GetHttpContext()?.Connection?.RemoteIpAddress; @@ -105,12 +105,13 @@ namespace Remotely.Server.Hubs if (CheckForDeviceBan(device.PublicIP)) { - return Task.FromResult(false); + return false; } - if (_dataService.AddOrUpdateDevice(device, out var updatedDevice)) + var result = await _dataService.AddOrUpdateDevice(device); + if (result.IsSuccess) { - Device = updatedDevice; + Device = result.Value; _serviceSessionCache.AddOrUpdateByConnectionId(Context.ConnectionId, Device); @@ -124,14 +125,14 @@ namespace Remotely.Server.Hubs foreach (var connection in connections) { - connection.InvokeCircuitEvent(CircuitEventName.DeviceUpdate, Device); + await connection.InvokeCircuitEvent(CircuitEventName.DeviceUpdate, Device); } - return Task.FromResult(true); + return true; } else { // Organization wasn't found. - return Task.FromResult(false); + return false; } } catch (Exception ex) @@ -140,7 +141,7 @@ namespace Remotely.Server.Hubs } Context.Abort(); - return Task.FromResult(false); + return false; } public async Task DeviceHeartbeat(Device device) @@ -163,8 +164,14 @@ namespace Remotely.Server.Hubs } - _dataService.AddOrUpdateDevice(device, out var updatedDevice); - Device = updatedDevice; + var result = await _dataService.AddOrUpdateDevice(device); + + if (result.IsSuccess) + { + return; + } + + Device = result.Value; _serviceSessionCache.AddOrUpdateByConnectionId(Context.ConnectionId, Device); diff --git a/Server/Services/DataService.cs b/Server/Services/DataService.cs index 7d24dbae..0c02c039 100644 --- a/Server/Services/DataService.cs +++ b/Server/Services/DataService.cs @@ -1,4 +1,5 @@ -using Immense.RemoteControl.Shared.Models; +using Immense.RemoteControl.Shared; +using Immense.RemoteControl.Shared.Models; using Microsoft.AspNetCore.Components.Forms; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; @@ -29,7 +30,7 @@ namespace Remotely.Server.Services InviteLink AddInvite(string orgID, InviteViewModel invite); - bool AddOrUpdateDevice(Device device, out Device updatedDevice); + Task> AddOrUpdateDevice(Device device); Task AddOrUpdateSavedScript(SavedScript script, string userId); @@ -322,45 +323,44 @@ namespace Remotely.Server.Services return inviteLink; } - public bool AddOrUpdateDevice(Device device, out Device updatedDevice) + public async Task> AddOrUpdateDevice(Device device) { using var dbContext = _appDbFactory.GetContext(); - var existingDevice = dbContext.Devices.Find(device.ID); - if (existingDevice != null) + var resultDevice = await dbContext.Devices.FindAsync(device.ID); + if (resultDevice != null) { - existingDevice.CurrentUser = device.CurrentUser; - existingDevice.DeviceName = device.DeviceName; - existingDevice.Drives = device.Drives; - existingDevice.CpuUtilization = device.CpuUtilization; - existingDevice.UsedMemory = device.UsedMemory; - existingDevice.UsedStorage = device.UsedStorage; - existingDevice.Is64Bit = device.Is64Bit; - existingDevice.IsOnline = true; - existingDevice.OSArchitecture = device.OSArchitecture; - existingDevice.OSDescription = device.OSDescription; - existingDevice.Platform = device.Platform; - existingDevice.ProcessorCount = device.ProcessorCount; - existingDevice.PublicIP = device.PublicIP; - existingDevice.TotalMemory = device.TotalMemory; - existingDevice.TotalStorage = device.TotalStorage; - existingDevice.AgentVersion = device.AgentVersion; - existingDevice.LastOnline = DateTimeOffset.Now; - updatedDevice = existingDevice; + resultDevice.CurrentUser = device.CurrentUser; + resultDevice.DeviceName = device.DeviceName; + resultDevice.Drives = device.Drives; + resultDevice.CpuUtilization = device.CpuUtilization; + resultDevice.UsedMemory = device.UsedMemory; + resultDevice.UsedStorage = device.UsedStorage; + resultDevice.Is64Bit = device.Is64Bit; + resultDevice.IsOnline = true; + resultDevice.OSArchitecture = device.OSArchitecture; + resultDevice.OSDescription = device.OSDescription; + resultDevice.Platform = device.Platform; + resultDevice.ProcessorCount = device.ProcessorCount; + resultDevice.PublicIP = device.PublicIP; + resultDevice.TotalMemory = device.TotalMemory; + resultDevice.TotalStorage = device.TotalStorage; + resultDevice.AgentVersion = device.AgentVersion; + resultDevice.LastOnline = DateTimeOffset.Now; } else { device.LastOnline = DateTimeOffset.Now; if (_hostEnvironment.IsDevelopment() && dbContext.Organizations.Any()) { - var org = dbContext.Organizations.FirstOrDefault(); + var org = await dbContext.Organizations.FirstOrDefaultAsync(); device.Organization = org; device.OrganizationID = org?.ID; } - updatedDevice = device; + resultDevice = device; - if (!dbContext.Organizations.Any(x => x.ID == device.OrganizationID)) + if (!await dbContext.Organizations.AnyAsync(x => x.ID == device.OrganizationID)) { _logger.LogInformation( "Unable to add device {deviceName} because organization {organizationID}" + @@ -369,12 +369,12 @@ namespace Remotely.Server.Services device.OrganizationID, device.ID); - return false; + return Result.Fail("Organization does not exist."); } - dbContext.Devices.Add(device); + await dbContext.Devices.AddAsync(device); } - dbContext.SaveChanges(); - return true; + await dbContext.SaveChangesAsync(); + return Result.Ok(resultDevice); } public async Task AddOrUpdateSavedScript(SavedScript script, string userId) diff --git a/Tests/LoadTester/Program.cs b/Tests/LoadTester/Program.cs index 45832972..3b341c0c 100644 --- a/Tests/LoadTester/Program.cs +++ b/Tests/LoadTester/Program.cs @@ -1,4 +1,5 @@ using Castle.Core.Logging; +using Microsoft.AspNetCore.Http.Connections; using Microsoft.AspNetCore.SignalR.Client; using Microsoft.Extensions.Logging; using Moq; @@ -6,85 +7,93 @@ using Remotely.Agent.Services; using Remotely.Agent.Services.Windows; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Threading; using System.Threading.Tasks; -namespace Remotely.Tests.LoadTester +namespace Remotely.Tests.LoadTester; + +internal class Program { - internal class Program + private static readonly double _heartbeatMs = TimeSpan.FromMinutes(1).TotalMilliseconds; + private static int _agentCount; + private static string _organizationId; + private static string _serverurl; + private static Mock _cpuSampler; + private static Mock> _logger; + private static DeviceInfoGeneratorWin _deviceInfo; + private static Stopwatch _stopwatch; + private static int _connectedCount; + + private static void Main(string[] args) { - private static readonly SemaphoreSlim _lock = new(10, 10); - private static readonly double _heartbeatMs = TimeSpan.FromMinutes(1).TotalMilliseconds; - private static int _agentCount; - private static string _organizationId; - private static string _serverurl; - private static Mock _cpuSampler; - private static Mock> _logger; - private static DeviceInfoGeneratorWin _deviceInfo; + _cpuSampler = new Mock(); + _cpuSampler.Setup(x => x.CurrentUtilization).Returns(0); + _logger = new Mock>(); - private static void Main(string[] args) + _deviceInfo = new DeviceInfoGeneratorWin(_cpuSampler.Object, _logger.Object); + + _stopwatch = Stopwatch.StartNew(); + ConnectAgents(); + + Console.Write("Press Enter to exit..."); + Console.ReadLine(); + } + + private static void ConnectAgents() + { + try { - _cpuSampler = new Mock(); - _cpuSampler.Setup(x => x.CurrentUtilization).Returns(0); - _logger = new Mock>(); - - _deviceInfo = new DeviceInfoGeneratorWin(_cpuSampler.Object, _logger.Object); - ConnectAgents(); - - Console.Write("Press Enter to exit..."); - Console.ReadLine(); - } - - private static void ConnectAgents() - { - try + if (!CommandLineParser.CommandLineArgs.ContainsKey("serverurl") || + !CommandLineParser.CommandLineArgs.ContainsKey("organizationid") || + !CommandLineParser.CommandLineArgs.ContainsKey("agentcount")) { - if (!CommandLineParser.CommandLineArgs.ContainsKey("serverurl") || - !CommandLineParser.CommandLineArgs.ContainsKey("organizationid") || - !CommandLineParser.CommandLineArgs.ContainsKey("agentcount")) - { - Console.WriteLine("Command line arguments must include all of the following: "); - Console.WriteLine(); - Console.WriteLine("-serverurl [full URL of the Remotely server]"); - Console.WriteLine(); - Console.WriteLine("-organizationid [organization ID that the device will belong to]"); - Console.WriteLine(); - Console.WriteLine("-agentcount [the number of agent connections to simulate]"); - Console.WriteLine(); - Console.WriteLine("Press Enter to exit..."); - Environment.Exit(0); - } - - _agentCount = int.Parse(CommandLineParser.CommandLineArgs["agentcount"]); - _organizationId = CommandLineParser.CommandLineArgs["organizationid"]; - _serverurl = CommandLineParser.CommandLineArgs["serverurl"]; - - for (var i = 0; i < _agentCount; i++) - { - _ = StartAgent(i); - } - } - catch (Exception ex) - { - Console.WriteLine("An error occurred. Check your syntex. Error: "); + Console.WriteLine("Command line arguments must include all of the following: "); Console.WriteLine(); - Console.WriteLine(ex.Message); + Console.WriteLine("-serverurl [full URL of the Remotely server]"); + Console.WriteLine(); + Console.WriteLine("-organizationid [organization ID that the device will belong to]"); + Console.WriteLine(); + Console.WriteLine("-agentcount [the number of agent connections to simulate]"); + Console.WriteLine(); + Console.WriteLine("Press Enter to exit..."); + Environment.Exit(0); + } + + _agentCount = int.Parse(CommandLineParser.CommandLineArgs["agentcount"]); + _organizationId = CommandLineParser.CommandLineArgs["organizationid"]; + _serverurl = CommandLineParser.CommandLineArgs["serverurl"]; + + for (var i = 0; i < _agentCount; i++) + { + _ = StartAgent(i); } } + catch (Exception ex) + { + Console.WriteLine("An error occurred. Check your syntex. Error: "); + Console.WriteLine(); + Console.WriteLine(ex.Message); + } + } - private static async Task StartAgent(int i) + private static async Task StartAgent(int i) + { + while (true) { try { - await _lock.WaitAsync(); + var waitSeconds = Math.Max(3, Random.Shared.NextDouble() * 10); + await Task.Delay(TimeSpan.FromSeconds(waitSeconds)); var deviceId = Guid.NewGuid().ToString(); var hubConnection = new HubConnectionBuilder() .WithUrl(_serverurl + "/hubs/service") + .WithAutomaticReconnect(new RetryPolicy()) .Build(); - Console.WriteLine("Connecting device number " + i.ToString()); + Console.WriteLine($"Connecting device number {i}"); await hubConnection.StartAsync(); var device = await _deviceInfo.CreateDevice(deviceId, _organizationId); @@ -98,29 +107,47 @@ namespace Remotely.Tests.LoadTester return; } - + _ = Task.Run(async () => { await Task.Delay(new Random().Next(1, (int)_heartbeatMs)); var heartbeatTimer = new System.Timers.Timer(_heartbeatMs); heartbeatTimer.Elapsed += async (sender, args) => { - var currentInfo = await _deviceInfo.CreateDevice(device.ID, _organizationId); - currentInfo.DeviceName = device.DeviceName; - await hubConnection.SendAsync("DeviceHeartbeat", currentInfo); + try + { + var currentInfo = await _deviceInfo.CreateDevice(device.ID, _organizationId); + currentInfo.DeviceName = device.DeviceName; + await hubConnection.SendAsync("DeviceHeartbeat", currentInfo); + } + catch { } }; heartbeatTimer.Start(); }); + + Console.WriteLine($"Connected device number {i}"); + + Interlocked.Increment(ref _connectedCount); + if (_connectedCount == _agentCount) + { + Console.WriteLine($"Finished connecting all devices. Elapsed: {_stopwatch.Elapsed}"); + } + + break; } catch (Exception ex) { Console.WriteLine($"Device {i} failed to connect."); Console.WriteLine(ex.Message); } - finally - { - _lock.Release(); - } + } + } + + private class RetryPolicy : IRetryPolicy + { + public TimeSpan? NextRetryDelay(RetryContext retryContext) + { + return TimeSpan.FromSeconds(3); } } } \ No newline at end of file diff --git a/Tests/Server.Tests/DataServiceTests.cs b/Tests/Server.Tests/DataServiceTests.cs index 2e68cd13..dc944d94 100644 --- a/Tests/Server.Tests/DataServiceTests.cs +++ b/Tests/Server.Tests/DataServiceTests.cs @@ -29,7 +29,7 @@ namespace Remotely.Tests } [TestMethod] - public void AddOrUpdateDevice() + public async Task AddOrUpdateDevice() { var storedDevice = _dataService.GetDevice(_newDeviceID); @@ -43,7 +43,8 @@ namespace Remotely.Tests Is64Bit = Environment.Is64BitOperatingSystem }; - Assert.IsTrue(_dataService.AddOrUpdateDevice(newDevice, out _)); + var result = await _dataService.AddOrUpdateDevice(newDevice); + Assert.IsTrue(result.IsSuccess); storedDevice = _dataService.GetDevice(_newDeviceID); diff --git a/Tests/Server.Tests/TestData.cs b/Tests/Server.Tests/TestData.cs index 8f27718f..22120046 100644 --- a/Tests/Server.Tests/TestData.cs +++ b/Tests/Server.Tests/TestData.cs @@ -94,9 +94,9 @@ namespace Remotely.Tests User2 = dataService.GetUserByNameWithOrg("testuser2@test.com"); Device1.OrganizationID = Admin1.OrganizationID; - dataService.AddOrUpdateDevice(Device1, out _); + await dataService.AddOrUpdateDevice(Device1); ; Device2.OrganizationID = Admin1.OrganizationID; - dataService.AddOrUpdateDevice(Device2, out _); + await dataService.AddOrUpdateDevice(Device2); dataService.AddDeviceGroup(Admin1.OrganizationID, Group1, out _, out _); dataService.AddDeviceGroup(Admin1.OrganizationID, Group2, out _, out _);