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