diff --git a/src/Billing/Billing.csproj b/src/Billing/Billing.csproj index 02cdf58..cb894b6 100644 --- a/src/Billing/Billing.csproj +++ b/src/Billing/Billing.csproj @@ -15,9 +15,9 @@ - - - + + + \ No newline at end of file diff --git a/src/Billing/Program.cs b/src/Billing/Program.cs index e6fcadd..383bf26 100644 --- a/src/Billing/Program.cs +++ b/src/Billing/Program.cs @@ -4,9 +4,6 @@ using Microsoft.Extensions.DependencyInjection; using Shared; -Console.Title = "Failure rate (Billing)"; -Console.SetWindowSize(65, 15); - LoggingUtils.ConfigureLogging("Billing"); var endpointConfiguration = new EndpointConfiguration("Billing"); @@ -29,10 +26,13 @@ endpointConfiguration.AuditProcessedMessagesTo("audit"); endpointConfiguration.SendHeartbeatTo("Particular.ServiceControl"); +var instanceId = "1C62248E-2681-45A4-B44D-5CF93584BAD6"; endpointConfiguration.UniquelyIdentifyRunningInstance() - .UsingCustomIdentifier(new Guid("1C62248E-2681-45A4-B44D-5CF93584BAD6")) + .UsingCustomIdentifier(new Guid(instanceId)) .UsingCustomDisplayName("original-instance"); +endpointConfiguration.ConfigureOpenTelemetry("Sales", instanceId, 9120); + var metrics = endpointConfiguration.EnableMetrics(); metrics.SendMetricDataToServiceControl( "Particular.Monitoring", @@ -41,38 +41,17 @@ var simulationEffects = new SimulationEffects(); endpointConfiguration.RegisterComponents(cc => cc.AddSingleton(simulationEffects)); +endpointConfiguration.Recoverability().OnConsecutiveFailures(5, new RateLimitSettings(TimeSpan.FromSeconds(5))); var endpointInstance = await Endpoint.Start(endpointConfiguration); -RunUserInterfaceLoop(simulationEffects); +var nonInteractive = args.Length > 1 && args[1] == bool.FalseString; +var interactive = !nonInteractive; -await endpointInstance.Stop(); - -void RunUserInterfaceLoop(SimulationEffects state) +UserInterface.RunLoop("Failure rate (Billing)", new Dictionary { - while (true) - { - Console.Clear(); - Console.WriteLine("Billing Endpoint"); - Console.WriteLine("Press F to increase the simulated failure rate"); - Console.WriteLine("Press S to decrease the simulated failure rate"); - Console.WriteLine("Press ESC to quit"); - Console.WriteLine(); - - state.WriteState(Console.Out); + ['w'] = ("increase the simulated failure rate", () => simulationEffects.IncreaseFailureRate()), + ['s'] = ("decrease the simulated failure rate", () => simulationEffects.DecreaseFailureRate()) +}, writer => simulationEffects.WriteState(writer), interactive); - var input = Console.ReadKey(true); - - switch (input.Key) - { - case ConsoleKey.F: - state.IncreaseFailureRate(); - break; - case ConsoleKey.S: - state.DecreaseFailureRate(); - break; - case ConsoleKey.Escape: - return; - } - } -} +await endpointInstance.Stop(); \ No newline at end of file diff --git a/src/ClientUI/ClientUI.csproj b/src/ClientUI/ClientUI.csproj index b459ab7..9b0fa58 100644 --- a/src/ClientUI/ClientUI.csproj +++ b/src/ClientUI/ClientUI.csproj @@ -15,9 +15,9 @@ - - - + + + \ No newline at end of file diff --git a/src/ClientUI/Program.cs b/src/ClientUI/Program.cs index 06830cb..21b03dd 100644 --- a/src/ClientUI/Program.cs +++ b/src/ClientUI/Program.cs @@ -3,9 +3,6 @@ using Messages; using Shared; -Console.Title = "Load (ClientUI)"; -Console.SetWindowSize(65, 15); - LoggingUtils.ConfigureLogging("ClientUI"); var endpointConfiguration = new EndpointConfiguration("ClientUI"); @@ -24,10 +21,13 @@ endpointConfiguration.AuditProcessedMessagesTo("audit"); endpointConfiguration.SendHeartbeatTo("Particular.ServiceControl"); +var instanceId = "EA3E7D1B-8171-4098-B160-1FEA975CCB2C"; endpointConfiguration.UniquelyIdentifyRunningInstance() - .UsingCustomIdentifier(new Guid("EA3E7D1B-8171-4098-B160-1FEA975CCB2C")) + .UsingCustomIdentifier(new Guid(instanceId)) .UsingCustomDisplayName("original-instance"); +endpointConfiguration.ConfigureOpenTelemetry("Sales", instanceId, 9130); + var metrics = endpointConfiguration.EnableMetrics(); metrics.SendMetricDataToServiceControl( "Particular.Monitoring", @@ -43,35 +43,16 @@ var cancellation = new CancellationTokenSource(); var simulatedWork = simulatedCustomers.Run(cancellation.Token); -RunUserInterfaceLoop(simulatedCustomers); +var nonInteractive = args.Length > 1 && args[1] == bool.FalseString; +var interactive = !nonInteractive; + +UserInterface.RunLoop("Load (ClientUI)", new Dictionary +{ + ['c'] = ("toggle High/Low traffic mode", () => simulatedCustomers.ToggleTrafficMode()), +}, writer => simulatedCustomers.WriteState(writer), interactive); cancellation.Cancel(); await simulatedWork; await endpointInstance.Stop(); - -void RunUserInterfaceLoop(SimulatedCustomers simulatedCustomers) -{ - while (true) - { - Console.Clear(); - Console.WriteLine("Simulating customers placing orders on a website"); - Console.WriteLine("Press T to toggle High/Low traffic mode"); - Console.WriteLine("Press ESC to quit"); - Console.WriteLine(); - - simulatedCustomers.WriteState(Console.Out); - - var input = Console.ReadKey(true); - - switch (input.Key) - { - case ConsoleKey.T: - simulatedCustomers.ToggleTrafficMode(); - break; - case ConsoleKey.Escape: - return; - } - } -} diff --git a/src/MonitoringDemo/DemoLauncher.cs b/src/MonitoringDemo/DemoLauncher.cs index 6716868..fe93c2f 100644 --- a/src/MonitoringDemo/DemoLauncher.cs +++ b/src/MonitoringDemo/DemoLauncher.cs @@ -2,9 +2,12 @@ sealed class DemoLauncher : IDisposable { - public DemoLauncher() + readonly bool remoteControlMode; + + public DemoLauncher(bool remoteControlMode) { - demoJob = new Job("Particular.MonitoringDemo"); + this.remoteControlMode = remoteControlMode; + demoJob = new Job("Particular.MonitoringDemo", remoteControlMode); File.WriteAllText(@".\Marker.sln", string.Empty); } @@ -28,6 +31,14 @@ public void Dispose() DirectoryEx.ForceDeleteReadonly(".audit-db"); } + public void Send(string value) + { + demoJob.Send(billingPath, 0, value); + demoJob.Send(shippingPath, 0, value); + demoJob.Send(clientPath, 0, value); + demoJob.Send(salesPath, 0, value); + } + public void Platform() { if (disposed) @@ -45,7 +56,7 @@ public void Billing() return; } - demoJob.AddProcess(Path.Combine("Billing", "Billing.exe")); + demoJob.AddProcess(billingPath); } public void Shipping() @@ -55,7 +66,7 @@ public void Shipping() return; } - demoJob.AddProcess(Path.Combine("Shipping", "Shipping.exe")); + demoJob.AddProcess(shippingPath); } public void ScaleOutSales() @@ -65,7 +76,7 @@ public void ScaleOutSales() return; } - demoJob.AddProcess(Path.Combine("Sales", "Sales.exe")); + demoJob.AddProcess(salesPath); } public void ScaleInSales() @@ -75,7 +86,7 @@ public void ScaleInSales() return; } - demoJob.KillProcess(Path.Combine("Sales", "Sales.exe")); + demoJob.KillProcess(salesPath); } public void ClientUI() @@ -85,9 +96,13 @@ public void ClientUI() return; } - demoJob.AddProcess(Path.Combine("ClientUI", "ClientUI.exe")); + demoJob.AddProcess(clientPath); } readonly Job demoJob; private bool disposed; + private static readonly string billingPath = Path.Combine("Billing", "Billing.exe"); + private static readonly string shippingPath = Path.Combine("Shipping", "Shipping.exe"); + private static readonly string salesPath = Path.Combine("Sales", "Sales.exe"); + private static readonly string clientPath = Path.Combine("ClientUI", "ClientUI.exe"); } \ No newline at end of file diff --git a/src/MonitoringDemo/Job.cs b/src/MonitoringDemo/Job.cs index 9ae5f42..256042b 100644 --- a/src/MonitoringDemo/Job.cs +++ b/src/MonitoringDemo/Job.cs @@ -1,209 +1,240 @@ -using System.Diagnostics; -using System.Runtime.InteropServices; - -namespace MonitoringDemo; - -partial class Job : IDisposable -{ - public Job(string jobName) - { - handle = CreateJobObject(nint.Zero, jobName); - - var info = new JOBOBJECT_BASIC_LIMIT_INFORMATION - { - LimitFlags = 0x2000 - }; - - var extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION - { - BasicLimitInformation = info - }; - - var length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION)); - var extendedInfoPtr = Marshal.AllocHGlobal(length); - Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false); - - if (!SetInformationJobObject(handle, JobObjectInfoType.ExtendedLimitInformation, extendedInfoPtr, (uint)length)) - { - throw new Exception($"Unable to set information. Error: {Marshal.GetLastWin32Error()}"); - } - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - public bool AddProcess(string relativeExePath) - { - if (!processesByExec.TryGetValue(relativeExePath, out var processes)) - { - processes = []; - processesByExec[relativeExePath] = processes; - } - - var processesCount = processes.Count; - var instanceId = processesCount == 0 ? null : $"instance-{processesCount}"; - - var process = StartProcess(relativeExePath, instanceId); - - if (process is null) - { - return false; - } - - processes.Push(process); - - return AddProcess(process); - } - - public void KillProcess(string relativeExePath) - { - if (!processesByExec.TryGetValue(relativeExePath, out var processes)) - { - return; - } - - while (processes.TryPop(out var victim)) - { - try - { - victim.Kill(true); - return; - } - catch (Exception) - { - //The process has died or has been killed by the user. Let's try to kill another one by doing at - // least another iteration - } - finally - { - victim.Dispose(); - } - } - } - - bool AddProcess(Process process) => AddProcess(process.Handle); - - bool AddProcess(nint processHandle) => AssignProcessToJobObject(handle, processHandle); - - static Process? StartProcess(string relativeExePath, string? arguments = null) - { - var fullExePath = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, relativeExePath)); - var workingDirectory = Path.GetDirectoryName(fullExePath); - - var startInfo = new ProcessStartInfo(fullExePath) - { - WorkingDirectory = workingDirectory, - UseShellExecute = true - }; - - if (arguments is not null) - { - startInfo.Arguments = arguments; - } - - return Process.Start(startInfo); - } - - void Dispose(bool disposing) - { - if (disposed) - { - return; - } - - if (!disposing) - { - return; - } - - CloseHandle(handle); - handle = nint.Zero; - processesByExec.Clear(); - disposed = true; - } - - [LibraryImport("kernel32.dll", EntryPoint = "CreateJobObjectW", StringMarshalling = StringMarshalling.Utf16)] - private static partial nint CreateJobObject(nint a, string lpName); - - [LibraryImport("kernel32.dll")] - [return: MarshalAs(UnmanagedType.Bool)] - private static partial bool SetInformationJobObject(nint hJob, JobObjectInfoType infoType, nint lpJobObjectInfo, uint cbJobObjectInfoLength); - - [LibraryImport("kernel32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - private static partial bool AssignProcessToJobObject(nint job, nint process); - - [LibraryImport("kernel32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - private static partial bool CloseHandle(nint hObject); - - readonly Dictionary> processesByExec = []; - - nint handle; - bool disposed; -} - -#region Helper classes - -[StructLayout(LayoutKind.Sequential)] -#pragma warning disable PS0024 // A non-interface type should not be prefixed with I -struct IO_COUNTERS -#pragma warning restore PS0024 // A non-interface type should not be prefixed with I -{ - public ulong ReadOperationCount; - public ulong WriteOperationCount; - public ulong OtherOperationCount; - public ulong ReadTransferCount; - public ulong WriteTransferCount; - public ulong OtherTransferCount; -} - - -[StructLayout(LayoutKind.Sequential)] -struct JOBOBJECT_BASIC_LIMIT_INFORMATION -{ - public long PerProcessUserTimeLimit; - public long PerJobUserTimeLimit; - public uint LimitFlags; - public nuint MinimumWorkingSetSize; - public nuint MaximumWorkingSetSize; - public uint ActiveProcessLimit; - public nuint Affinity; - public uint PriorityClass; - public uint SchedulingClass; -} - -[StructLayout(LayoutKind.Sequential)] -struct SECURITY_ATTRIBUTES -{ - public uint nLength; - public nint lpSecurityDescriptor; - public int bInheritHandle; -} - -[StructLayout(LayoutKind.Sequential)] -struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION -{ - public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation; - public IO_COUNTERS IoInfo; - public nuint ProcessMemoryLimit; - public nuint JobMemoryLimit; - public nuint PeakProcessMemoryUsed; - public nuint PeakJobMemoryUsed; -} - -enum JobObjectInfoType -{ - AssociateCompletionPortInformation = 7, - BasicLimitInformation = 2, - BasicUIRestrictions = 4, - EndOfJobTimeInformation = 6, - ExtendedLimitInformation = 9, - SecurityLimitInformation = 5, - GroupInformation = 11 -} - -#endregion +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text; + +namespace MonitoringDemo; + +partial class Job : IDisposable +{ + readonly bool redirectInputAndOutput; + + public Job(string jobName, bool redirectInputAndOutput) + { + this.redirectInputAndOutput = redirectInputAndOutput; + handle = CreateJobObject(nint.Zero, jobName); + + var info = new JOBOBJECT_BASIC_LIMIT_INFORMATION + { + LimitFlags = 0x2000 + }; + + var extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION + { + BasicLimitInformation = info + }; + + var length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION)); + var extendedInfoPtr = Marshal.AllocHGlobal(length); + Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false); + + if (!SetInformationJobObject(handle, JobObjectInfoType.ExtendedLimitInformation, extendedInfoPtr, (uint)length)) + { + throw new Exception($"Unable to set information. Error: {Marshal.GetLastWin32Error()}"); + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + public void Send(string relativeExePath, int index, string value) + { + if (!redirectInputAndOutput) + { + return; + } + if (processesByExec.TryGetValue(relativeExePath, out var processes)) + { + if (processes.Count > index) + { + processes[index].StandardInput.WriteLine(value); + } + } + } + + public bool AddProcess(string relativeExePath) + { + if (!processesByExec.TryGetValue(relativeExePath, out var processes)) + { + processes = []; + processesByExec[relativeExePath] = processes; + } + + var processesCount = processes.Count; + var instanceId = $"instance-{processesCount}"; + + var process = StartProcess(relativeExePath, instanceId); + + if (process is null) + { + return false; + } + + if (redirectInputAndOutput) + { + process.OutputDataReceived += Process_OutputDataReceived; + process.BeginOutputReadLine(); + } + + processes.Add(process); + + return AddProcess(process); + } + + private void Process_OutputDataReceived(object sender, DataReceivedEventArgs e) + { + Console.WriteLine(e.Data); + } + + public void KillProcess(string relativeExePath) + { + if (!processesByExec.TryGetValue(relativeExePath, out var processes)) + { + return; + } + + while (processes.Count > 0) + { + var victim = processes.Last(); + processes.Remove(victim); + try + { + victim.Kill(true); + return; + } + catch (Exception) + { + //The process has died or has been killed by the user. Let's try to kill another one by doing at + // least another iteration + } + finally + { + victim.Dispose(); + } + } + } + + bool AddProcess(Process process) => AddProcess(process.Handle); + + bool AddProcess(nint processHandle) => AssignProcessToJobObject(handle, processHandle); + + Process? StartProcess(string relativeExePath, string? arguments = null) + { + var fullExePath = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, relativeExePath)); + var workingDirectory = Path.GetDirectoryName(fullExePath); + + var startInfo = new ProcessStartInfo(fullExePath) + { + WorkingDirectory = workingDirectory, + UseShellExecute = !redirectInputAndOutput, + RedirectStandardInput = redirectInputAndOutput, + RedirectStandardOutput = redirectInputAndOutput, + }; + + startInfo.Arguments = (arguments ?? "") + " False"; + return Process.Start(startInfo); + } + + void Dispose(bool disposing) + { + if (disposed) + { + return; + } + + if (!disposing) + { + return; + } + + CloseHandle(handle); + handle = nint.Zero; + processesByExec.Clear(); + disposed = true; + } + + [LibraryImport("kernel32.dll", EntryPoint = "CreateJobObjectW", StringMarshalling = StringMarshalling.Utf16)] + private static partial nint CreateJobObject(nint a, string lpName); + + [LibraryImport("kernel32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + private static partial bool SetInformationJobObject(nint hJob, JobObjectInfoType infoType, nint lpJobObjectInfo, uint cbJobObjectInfoLength); + + [LibraryImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static partial bool AssignProcessToJobObject(nint job, nint process); + + [LibraryImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static partial bool CloseHandle(nint hObject); + + readonly Dictionary> processesByExec = []; + + nint handle; + bool disposed; +} + +#region Helper classes + +[StructLayout(LayoutKind.Sequential)] +#pragma warning disable PS0024 // A non-interface type should not be prefixed with I +struct IO_COUNTERS +#pragma warning restore PS0024 // A non-interface type should not be prefixed with I +{ + public ulong ReadOperationCount; + public ulong WriteOperationCount; + public ulong OtherOperationCount; + public ulong ReadTransferCount; + public ulong WriteTransferCount; + public ulong OtherTransferCount; +} + + +[StructLayout(LayoutKind.Sequential)] +struct JOBOBJECT_BASIC_LIMIT_INFORMATION +{ + public long PerProcessUserTimeLimit; + public long PerJobUserTimeLimit; + public uint LimitFlags; + public nuint MinimumWorkingSetSize; + public nuint MaximumWorkingSetSize; + public uint ActiveProcessLimit; + public nuint Affinity; + public uint PriorityClass; + public uint SchedulingClass; +} + +[StructLayout(LayoutKind.Sequential)] +struct SECURITY_ATTRIBUTES +{ + public uint nLength; + public nint lpSecurityDescriptor; + public int bInheritHandle; +} + +[StructLayout(LayoutKind.Sequential)] +struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION +{ + public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation; + public IO_COUNTERS IoInfo; + public nuint ProcessMemoryLimit; + public nuint JobMemoryLimit; + public nuint PeakProcessMemoryUsed; + public nuint PeakJobMemoryUsed; +} + +enum JobObjectInfoType +{ + AssociateCompletionPortInformation = 7, + BasicLimitInformation = 2, + BasicUIRestrictions = 4, + EndOfJobTimeInformation = 6, + ExtendedLimitInformation = 9, + SecurityLimitInformation = 5, + GroupInformation = 11 +} + +#endregion diff --git a/src/MonitoringDemo/Program.cs b/src/MonitoringDemo/Program.cs index 937cb73..328ea9b 100644 --- a/src/MonitoringDemo/Program.cs +++ b/src/MonitoringDemo/Program.cs @@ -1,4 +1,5 @@ -using MonitoringDemo; +using System.Diagnostics; +using MonitoringDemo; CancellationTokenSource tokenSource = new(); Console.Title = "MonitoringDemo"; @@ -11,9 +12,12 @@ syncEvent.TrySetResult(true); }; +//Debugger.Launch(); +var remoteControlMode = args.Length > 0 && string.Equals(args[0], bool.TrueString, StringComparison.InvariantCultureIgnoreCase); + try { - using var launcher = new DemoLauncher(); + using var launcher = new DemoLauncher(remoteControlMode); Console.WriteLine("Starting the Particular Platform"); launcher.Platform(); @@ -79,15 +83,17 @@ void ScaleSalesEndpointIfRequired(DemoLauncher launcher, TaskCompletionSource 1 && args[1] == bool.FalseString; +var interactive = !nonInteractive; -await endpointInstance.Stop(); - -void RunUserInterfaceLoop(SimulationEffects state, string instanceName) +UserInterface.RunLoop(title, new Dictionary { - while (true) - { - Console.Clear(); - Console.WriteLine($"Sales Endpoint - {instanceName}"); - Console.WriteLine("Press F to process messages faster"); - Console.WriteLine("Press S to process messages slower"); - - Console.WriteLine("Press ESC to quit"); - Console.WriteLine(); + ['r'] = ("process messages faster", () => simulationEffects.ProcessMessagesFaster()), + ['f'] = ("process messages slower", () => simulationEffects.ProcessMessagesSlower()) +}, writer => simulationEffects.WriteState(writer), interactive); - state.WriteState(Console.Out); - - var input = Console.ReadKey(true); - - switch (input.Key) - { - case ConsoleKey.F: - state.ProcessMessagesFaster(); - break; - case ConsoleKey.S: - state.ProcessMessagesSlower(); - break; - case ConsoleKey.Escape: - return; - } - } -} +await endpointInstance.Stop(); static class DeterministicGuid { diff --git a/src/Sales/Sales.csproj b/src/Sales/Sales.csproj index 5e01df9..2b6460b 100644 --- a/src/Sales/Sales.csproj +++ b/src/Sales/Sales.csproj @@ -15,9 +15,9 @@ - - - + + + \ No newline at end of file diff --git a/src/Shared/LoggingUtils.cs b/src/Shared/LoggingUtils.cs index 42be799..f385552 100644 --- a/src/Shared/LoggingUtils.cs +++ b/src/Shared/LoggingUtils.cs @@ -1,4 +1,5 @@  +using System.Diagnostics; using System.Reflection; using NServiceBus.Extensions.Logging; using NServiceBus.Logging; @@ -49,8 +50,8 @@ public static void ConfigureLogging(string endpointName) return null; } - var logsFolders = currentDir.GetDirectories("logs", SearchOption.TopDirectoryOnly); + var logsFolders = currentDir.GetDirectories(".logs", SearchOption.TopDirectoryOnly); return logsFolders.FirstOrDefault() ?? FindLogFolder(currentDir.Parent); } -} \ No newline at end of file +} diff --git a/src/Shared/OpenTelemetryUtils.cs b/src/Shared/OpenTelemetryUtils.cs new file mode 100644 index 0000000..c79747d --- /dev/null +++ b/src/Shared/OpenTelemetryUtils.cs @@ -0,0 +1,31 @@ +using OpenTelemetry; +using OpenTelemetry.Metrics; +using OpenTelemetry.Resources; + +namespace Shared; + +public static class OpenTelemetryUtils +{ + public static IDisposable ConfigureOpenTelemetry(this EndpointConfiguration endpointConfig, string name, string id, int port) + { + var attributes = new Dictionary + { + ["service.name"] = name, + ["service.instance.id"] = id, + }; + + var resourceBuilder = ResourceBuilder.CreateDefault().AddAttributes(attributes); + + var meterProviderBuilder = Sdk.CreateMeterProviderBuilder() + .SetResourceBuilder(resourceBuilder) + .AddMeter("NServiceBus.Core*"); + + meterProviderBuilder.AddPrometheusHttpListener(options => options.UriPrefixes = new[] { $"http://127.0.0.1:{port}" }); + + var meterProvider = meterProviderBuilder.Build(); + + endpointConfig.EnableOpenTelemetry(); + + return meterProvider; + } +} \ No newline at end of file diff --git a/src/Shared/Shared.csproj b/src/Shared/Shared.csproj index e13cbe2..8c74161 100644 --- a/src/Shared/Shared.csproj +++ b/src/Shared/Shared.csproj @@ -7,9 +7,10 @@ - + + diff --git a/src/Shared/UserInterface.cs b/src/Shared/UserInterface.cs new file mode 100644 index 0000000..585d6df --- /dev/null +++ b/src/Shared/UserInterface.cs @@ -0,0 +1,70 @@ +namespace Shared; + +public class UserInterface +{ + public static void RunLoop(string title, Dictionary controls, Action reportState, bool interactive) + { + if (interactive) + { + RunInteractiveLoop(title, controls, reportState); + } + else + { + RunNonInteractiveLoop(title, controls, reportState); + } + } + + static void RunInteractiveLoop(string title, Dictionary controls, Action reportState) + { + Console.Title = title; + Console.SetWindowSize(65, 15); + + while (true) + { + Console.Clear(); + foreach (var kvp in controls) + { + Console.WriteLine($"Press {char.ToUpperInvariant(kvp.Key)} to {kvp.Value.Item1}"); + } + Console.WriteLine("Press ESC to quit"); + Console.WriteLine(); + + reportState(Console.Out); + + var input = Console.ReadKey(true); + + if (controls.TryGetValue(char.ToLowerInvariant(input.KeyChar), out var action)) + { + action.Item2(); + } + else if (input.Key == ConsoleKey.Escape) + { + return; + } + } + } + + static void RunNonInteractiveLoop(string title, Dictionary controls, Action reportState) + { + Console.Title = title; + + reportState(Console.Out); + + while (true) + { + var input = Console.ReadLine(); + if (string.IsNullOrWhiteSpace(input)) + { + return; + } + + var key = input[0]; + + if (controls.TryGetValue(char.ToLowerInvariant(key), out var action)) + { + action.Item2(); + reportState(Console.Out); + } + } + } +} \ No newline at end of file diff --git a/src/Shipping/Program.cs b/src/Shipping/Program.cs index a8ac51f..1015824 100644 --- a/src/Shipping/Program.cs +++ b/src/Shipping/Program.cs @@ -4,10 +4,8 @@ using Shared; using Shipping; -Console.Title = "Processing (Shipping)"; -Console.SetWindowSize(65, 15); - LoggingUtils.ConfigureLogging("Shipping"); +var instanceId = "BB8A8BAF-4187-455E-AAD2-211CD43267CB"; var endpointConfiguration = new EndpointConfiguration("Shipping"); endpointConfiguration.LimitMessageProcessingConcurrencyTo(4); @@ -26,8 +24,10 @@ endpointConfiguration.AuditProcessedMessagesTo("audit"); endpointConfiguration.SendHeartbeatTo("Particular.ServiceControl"); +endpointConfiguration.ConfigureOpenTelemetry("Sales", instanceId, 9110); + endpointConfiguration.UniquelyIdentifyRunningInstance() - .UsingCustomIdentifier(new Guid("BB8A8BAF-4187-455E-AAD2-211CD43267CB")) + .UsingCustomIdentifier(new Guid(instanceId)) .UsingCustomDisplayName("original-instance"); var metrics = endpointConfiguration.EnableMetrics(); @@ -41,39 +41,15 @@ var endpointInstance = await Endpoint.Start(endpointConfiguration); -RunUserInterfaceLoop(simulationEffects); - -await endpointInstance.Stop(); +var nonInteractive = args.Length > 1 && args[1] == bool.FalseString; +var interactive = !nonInteractive; -void RunUserInterfaceLoop(SimulationEffects state) +UserInterface.RunLoop("Processing (Shipping)", new Dictionary { - while (true) - { - Console.Clear(); - Console.WriteLine("Shipping Endpoint"); - Console.WriteLine("Press D to toggle resource degradation simulation"); - Console.WriteLine("Press F to process OrderBilled events faster"); - Console.WriteLine("Press S to process OrderBilled events slower"); - Console.WriteLine("Press ESC to quit"); - Console.WriteLine(); + ['z'] = ("toggle resource degradation simulation", () => simulationEffects.ToggleDegradationSimulation()), + ['q'] = ("process OrderBilled events faster", () => simulationEffects.ProcessMessagesFaster()), + ['a'] = ("process OrderBilled events slower", () => simulationEffects.ProcessMessagesSlower()) +}, writer => simulationEffects.WriteState(writer), interactive); - state.WriteState(Console.Out); - - var input = Console.ReadKey(true); +await endpointInstance.Stop(); - switch (input.Key) - { - case ConsoleKey.D: - state.ToggleDegradationSimulation(); - break; - case ConsoleKey.F: - state.ProcessMessagesFaster(); - break; - case ConsoleKey.S: - state.ProcessMessagesSlower(); - break; - case ConsoleKey.Escape: - return; - } - } -} diff --git a/src/Shipping/Shipping.csproj b/src/Shipping/Shipping.csproj index 08ec165..3099985 100644 --- a/src/Shipping/Shipping.csproj +++ b/src/Shipping/Shipping.csproj @@ -15,9 +15,9 @@ - - - + + + \ No newline at end of file