Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[*.{csproj,props,targets,xml}]
indent_style = space
indent_size = 2
xml_space_inside_empty_tag = true
1 change: 1 addition & 0 deletions src/Billing/Billing.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

<ItemGroup>
<PackageReference Include="NServiceBus" Version="9.*" />
<PackageReference Include="NServiceBus.Persistence.NonDurable" Version="2.*" />
<PackageReference Include="NServiceBus.Heartbeat" Version="5.*" />
<PackageReference Include="NServiceBus.Metrics.ServiceControl" Version="5.*" />
</ItemGroup>
Expand Down
26 changes: 26 additions & 0 deletions src/Billing/DispatchingProgressBehavior.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using NServiceBus.Pipeline;
using NServiceBus.Transport;

namespace Billing;

public class DispatchingProgressBehavior : Behavior<IBatchDispatchContext>
{
private FailureSimulator failureSimulator = new();

public override async Task Invoke(IBatchDispatchContext context, Func<Task> next)
{
var incomingMessage = context.Extensions.Get<IncomingMessage>();
if (incomingMessage.Headers.ContainsKey("MonitoringDemo.SlowMotion"))
{
Console.WriteLine($"Dispatching outgoing messages {incomingMessage.MessageId}...");
await failureSimulator.RunInteractive(context.CancellationToken);
}

await next().ConfigureAwait(false);
}

public void Failure()
{
failureSimulator.Trigger();
}
}
32 changes: 32 additions & 0 deletions src/Billing/FailureSimulation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
namespace Billing;

public class FailureSimulation
{
private RetrievingMessageProgressBehavior retrievingMessageProgressBehavior = new RetrievingMessageProgressBehavior();
private ProcessingMessageProgressBehavior processingMessageProgressBehavior = new ProcessingMessageProgressBehavior();
private DispatchingProgressBehavior dispatchingMessageProgressBehavior = new DispatchingProgressBehavior();

public void Register(EndpointConfiguration endpointConfiguration)
{
endpointConfiguration.Pipeline.Register(retrievingMessageProgressBehavior, "Shows progress of retrieving messages");

endpointConfiguration.Pipeline.Register(processingMessageProgressBehavior, "Shows progress of processing messages");

endpointConfiguration.Pipeline.Register(dispatchingMessageProgressBehavior, "Shows progress of dispatching messages");
}

public void TriggerFailureReceiving()
{
retrievingMessageProgressBehavior.Failure();
}

public void TriggerFailureProcessing()
{
processingMessageProgressBehavior.Failure();
}

public void TriggerFailureDispatching()
{
dispatchingMessageProgressBehavior.Failure();
}
}
30 changes: 30 additions & 0 deletions src/Billing/FailureSimulator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
namespace Billing;

public class FailureSimulator
{
private bool failureTriggered = false;

#pragma warning disable PS0003
public async Task RunInteractive(CancellationToken cancellationToken)
#pragma warning restore PS0003
{
using var progressBar = new ProgressBar();

for (var i = 0; i <= 100; i++)
{
if (failureTriggered)
{
failureTriggered = false;
throw new Exception("Simulated failure");
}
progressBar.Update(i);
await Task.Delay(25, cancellationToken).ConfigureAwait(false);
}
}

public void Trigger()
{
//TODO: Use Interlocked
failureTriggered = true;
}
}
24 changes: 24 additions & 0 deletions src/Billing/ProcessingMessageProgressBehavior.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using NServiceBus.Pipeline;

namespace Billing;

public class ProcessingMessageProgressBehavior : Behavior<IIncomingLogicalMessageContext>
{
private FailureSimulator failureSimulator = new();

public override async Task Invoke(IIncomingLogicalMessageContext context, Func<Task> next)
{
if (context.Headers.ContainsKey("MonitoringDemo.SlowMotion"))
{
Console.WriteLine($"Processing message {context.MessageId}...");
await failureSimulator.RunInteractive(context.CancellationToken);
}

await next().ConfigureAwait(false);
}

public void Failure()
{
failureSimulator.Trigger();
}
}
21 changes: 15 additions & 6 deletions src/Billing/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text.Json;
using System.Reflection;
using System.Text.Json;
using Billing;
using Messages;
using Microsoft.Extensions.DependencyInjection;
Expand All @@ -16,7 +17,12 @@
}
});

endpointConfiguration.UseTransport<LearningTransport>();
var transport = new LearningTransport
{
StorageDirectory = Path.Combine(Directory.GetParent(Assembly.GetExecutingAssembly().Location)!.Parent!.FullName, ".learningtransport"),
TransportTransactionMode = TransportTransactionMode.ReceiveOnly
};
endpointConfiguration.UseTransport(transport);

endpointConfiguration.Recoverability()
.Delayed(delayed => delayed.NumberOfRetries(0));
Expand All @@ -34,18 +40,21 @@
TimeSpan.FromMilliseconds(500)
);

endpointConfiguration.UsePersistence<NonDurablePersistence>();
endpointConfiguration.EnableOutbox();

var failureSimulation = new FailureSimulation();
failureSimulation.Register(endpointConfiguration);

var simulationEffects = new SimulationEffects();
endpointConfiguration.RegisterComponents(cc => cc.AddSingleton(simulationEffects));

var endpointInstance = await Endpoint.Start(endpointConfiguration);

var nonInteractive = args.Length > 1 && bool.TryParse(args[1], out var isInteractive) && !isInteractive;
var interactive = !nonInteractive;

UserInterface.RunLoop("Failure rate (Billing)", new Dictionary<char, (string, Action)>
{
['w'] = ("increase the simulated failure rate", () => simulationEffects.IncreaseFailureRate()),
['s'] = ("decrease the simulated failure rate", () => simulationEffects.DecreaseFailureRate())
}, writer => simulationEffects.WriteState(writer), false /* for now*/);
}, writer => simulationEffects.WriteState(writer));

await endpointInstance.Stop();
21 changes: 21 additions & 0 deletions src/Billing/ProgressBar.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace Billing;

public class ProgressBar : IDisposable
{
private readonly string widgetId = Guid.NewGuid().ToString("N");

public ProgressBar()
{
Console.WriteLine($"!BeginWidget Progress {widgetId}");
}

public void Update(int percent)
{
Console.WriteLine($"!Widget {widgetId} {percent}");
}

public void Dispose()
{
Console.WriteLine($"!EndWidget {widgetId}");
}
}
25 changes: 25 additions & 0 deletions src/Billing/RetrievingMessageProgressBehavior.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using NServiceBus.Pipeline;
using Shared;

namespace Billing;

public class RetrievingMessageProgressBehavior : Behavior<ITransportReceiveContext>
{
private FailureSimulator failureSimulator = new();

public override async Task Invoke(ITransportReceiveContext context, Func<Task> next)
{
if (context.Message.Headers.ContainsKey("MonitoringDemo.SlowMotion"))
{
Console.WriteLine($"Retrieving message {context.Message.MessageId}...");
await failureSimulator.RunInteractive(context.CancellationToken);
}

await next().ConfigureAwait(false);
}

public void Failure()
{
failureSimulator.Trigger();
}
}
16 changes: 10 additions & 6 deletions src/ClientUI/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text.Json;
using System.Reflection;
using System.Text.Json;
using ClientUI;
using Messages;
using Shared;
Expand All @@ -14,7 +15,11 @@
}
});

var transport = endpointConfiguration.UseTransport<LearningTransport>();
var transport = new LearningTransport
{
StorageDirectory = Path.Combine(Directory.GetParent(Assembly.GetExecutingAssembly().Location)!.Parent!.FullName, ".learningtransport")
};
var routing = endpointConfiguration.UseTransport(transport);

endpointConfiguration.AuditProcessedMessagesTo("audit");
endpointConfiguration.SendHeartbeatTo("Particular.ServiceControl");
Expand All @@ -29,7 +34,6 @@
TimeSpan.FromMilliseconds(500)
);

var routing = transport.Routing();
routing.RouteToEndpoint(typeof(PlaceOrder), "Sales");

var endpointInstance = await Endpoint.Start(endpointConfiguration);
Expand All @@ -38,13 +42,13 @@
var cancellation = new CancellationTokenSource();
var simulatedWork = simulatedCustomers.Run(cancellation.Token);

var nonInteractive = args.Length > 1 && bool.TryParse(args[1], out var isInteractive) && !isInteractive;
var interactive = !nonInteractive;

UserInterface.RunLoop("Load (ClientUI)", new Dictionary<char, (string, Action)>
{
['c'] = ("toggle High/Low traffic mode", () => simulatedCustomers.ToggleTrafficMode()),
}, writer => simulatedCustomers.WriteState(writer), interactive);
['v'] = ("toggle manual mode", () => simulatedCustomers.ToggleManualMode()),
['b'] = ("send message manually", () => simulatedCustomers.SendManually()),
}, writer => simulatedCustomers.WriteState(writer));

cancellation.Cancel();

Expand Down
37 changes: 34 additions & 3 deletions src/ClientUI/SimulatedCustomers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@ namespace ClientUI;

class SimulatedCustomers(IEndpointInstance endpointInstance)
{
private const string Letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

public void WriteState(TextWriter output)
{
var trafficMode = highTrafficMode ? "High" : "Low";
output.WriteLine($"{trafficMode} traffic mode - sending {rate} orders / second");
var trafficMode = manualMode
? "Manual sending mode"
: highTrafficMode ? $"High traffic mode - sending {rate} orders / second" : $"Low traffic mode - sending {rate} orders / second";
output.WriteLine(trafficMode);
}

public void ToggleTrafficMode()
Expand All @@ -16,6 +20,16 @@ public void ToggleTrafficMode()
rate = highTrafficMode ? 8 : 1;
}

public void ToggleManualMode()
{
manualMode = !manualMode;
}

public void SendManually()
{
manualModeSemaphore.Release();
}

public async Task Run(CancellationToken cancellationToken = default)
{
nextReset = DateTime.UtcNow.AddSeconds(1);
Expand All @@ -30,6 +44,11 @@ public async Task Run(CancellationToken cancellationToken = default)
nextReset = now.AddSeconds(1);
}

if (manualMode)
{
await manualModeSemaphore.WaitAsync();
}

await PlaceSingleOrder(cancellationToken);
currentIntervalCount++;

Expand All @@ -53,17 +72,29 @@ public async Task Run(CancellationToken cancellationToken = default)

Task PlaceSingleOrder(CancellationToken cancellationToken)
{
var messageId = new string(Enumerable.Range(0, 4).Select(x => Letters[Random.Shared.Next(Letters.Length)]).ToArray());

var placeOrderCommand = new PlaceOrder
{
OrderId = Guid.NewGuid().ToString()
};

return endpointInstance.Send(placeOrderCommand, cancellationToken);
var sendOptions = new SendOptions();

if (manualMode)
{
sendOptions.SetHeader("MonitoringDemo.SlowMotion", "True");
}

sendOptions.SetMessageId(messageId);
return endpointInstance.Send(placeOrderCommand, sendOptions, cancellationToken);
}

bool highTrafficMode;

DateTime nextReset;
int currentIntervalCount;
int rate = 1;
private bool manualMode;
private SemaphoreSlim manualModeSemaphore = new SemaphoreSlim(0);
}
Empty file removed src/MonitoringDemo/.\Marker.sln
Empty file.
20 changes: 0 additions & 20 deletions src/MonitoringDemo/ColoredConsole.cs

This file was deleted.

Loading