mirror of
https://github.com/NecroticBamboo/QRBee.git
synced 2025-12-21 12:11:53 +00:00
DEEP-37 Base class for anomalies created
This commit is contained in:
parent
58b7565597
commit
c5895adaa5
71
QRBee.Load.Generator/AnomalyBase.cs
Normal file
71
QRBee.Load.Generator/AnomalyBase.cs
Normal file
@ -0,0 +1,71 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace QRBee.Load.Generator;
|
||||
|
||||
internal class AnomalyBase
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly IAnomalyReporter _anomalyReporter;
|
||||
protected ThreadSafeRandom _rng = new();
|
||||
private bool _anomalyActive;
|
||||
private DateTime _anomalyStart = DateTime.MinValue;
|
||||
private DateTime _anomalyEnd = DateTime.MinValue;
|
||||
private double _anomalyProbability;
|
||||
private TimeSpan _anomalyDuration;
|
||||
private readonly object _lock = new();
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public AnomalyBase(string name, Anomaly settings, ILogger logger, IAnomalyReporter anomalyReporter)
|
||||
{
|
||||
Name = name;
|
||||
_logger = logger;
|
||||
_anomalyReporter = anomalyReporter;
|
||||
|
||||
_anomalyProbability = settings.Probability;
|
||||
_anomalyDuration = settings.Duration;
|
||||
if (IsEnabled)
|
||||
_logger.LogDebug($"{Name} configured: Probability={_anomalyProbability}, Duration={_anomalyDuration}");
|
||||
}
|
||||
|
||||
public bool IsEnabled => _anomalyProbability > 0.0;
|
||||
|
||||
protected bool IsActive()
|
||||
{
|
||||
if (DateTime.UtcNow < _anomalyEnd)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (_anomalyActive)
|
||||
{
|
||||
// double locking
|
||||
lock (_lock)
|
||||
{
|
||||
if (_anomalyActive)
|
||||
{
|
||||
_anomalyActive = false;
|
||||
_anomalyReporter.Report(_anomalyStart, _anomalyEnd, "Unconfirmed transaction");
|
||||
_logger.LogWarning($"Anomaly:{Name} ended");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var dice = _rng.NextDouble();
|
||||
if (dice < _anomalyProbability)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (!_anomalyActive)
|
||||
{
|
||||
_anomalyStart = DateTime.Now;
|
||||
_anomalyEnd = _anomalyStart + _anomalyDuration;
|
||||
_anomalyActive = true;
|
||||
_logger.LogWarning($"Anomaly: {Name}. Dice={dice} Ends=\"{_anomalyEnd.ToLocalTime():s}\"");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
64
QRBee.Load.Generator/AnomalyReporter.cs
Normal file
64
QRBee.Load.Generator/AnomalyReporter.cs
Normal file
@ -0,0 +1,64 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace QRBee.Load.Generator
|
||||
{
|
||||
internal class AnomalyReporter : IDisposable, IAnomalyReporter
|
||||
{
|
||||
private readonly StreamWriter _writer;
|
||||
private bool disposedValue;
|
||||
private readonly object _lock = new();
|
||||
|
||||
public AnomalyReporter()
|
||||
{
|
||||
const string fileName = "anomalies.csv";
|
||||
var writeHeader = !File.Exists(fileName);
|
||||
|
||||
var file = new FileStream(fileName, writeHeader ? FileMode.Create : FileMode.Append, FileAccess.Write);
|
||||
_writer = new StreamWriter(file);
|
||||
|
||||
if (writeHeader)
|
||||
{
|
||||
_writer.WriteLine("Start,End,Label");
|
||||
}
|
||||
}
|
||||
|
||||
public void Report(DateTime start, DateTime end, string description)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_writer.WriteLine($"\"{start:O}\",\"{end:O}\",\"{description}\"");
|
||||
_writer.Flush();
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_writer.Dispose();
|
||||
}
|
||||
|
||||
disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
~AnomalyReporter()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3,6 +3,7 @@
|
||||
internal class Anomaly
|
||||
{
|
||||
public double Probability { get; set; }
|
||||
public TimeSpan Duration { get; set; }
|
||||
public Dictionary<string,string> Parameters { get; set; } = new();
|
||||
}
|
||||
|
||||
|
||||
7
QRBee.Load.Generator/IAnomalyReporter.cs
Normal file
7
QRBee.Load.Generator/IAnomalyReporter.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace QRBee.Load.Generator
|
||||
{
|
||||
internal interface IAnomalyReporter
|
||||
{
|
||||
void Report(DateTime start, DateTime end, string description);
|
||||
}
|
||||
}
|
||||
27
QRBee.Load.Generator/LargeAmount.cs
Normal file
27
QRBee.Load.Generator/LargeAmount.cs
Normal file
@ -0,0 +1,27 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace QRBee.Load.Generator;
|
||||
|
||||
internal class LargeAmount : AnomalyBase
|
||||
{
|
||||
private double _minAmount = 1;
|
||||
private double _maxAmount = 100;
|
||||
private decimal _largeAmountValue = 1000;
|
||||
|
||||
public LargeAmount(IOptions<GeneratorSettings> settings, ILogger<UnconfirmedTransactions> logger, IAnomalyReporter anomalyReporter)
|
||||
: base("Large amount", settings.Value.LargeAmount, logger, anomalyReporter)
|
||||
{
|
||||
_minAmount = settings.Value.MinAmount;
|
||||
_maxAmount = settings.Value.MaxAmount;
|
||||
if (settings.Value.LargeAmount.Parameters.TryGetValue("Value", out var s))
|
||||
_largeAmountValue = decimal.Parse(s);
|
||||
}
|
||||
|
||||
public decimal GetAmount()
|
||||
{
|
||||
if (IsActive())
|
||||
return _largeAmountValue;
|
||||
return Convert.ToDecimal(_rng.NextDoubleInRange(_minAmount, _maxAmount));
|
||||
}
|
||||
}
|
||||
@ -1,91 +1,42 @@
|
||||
using log4net.Core;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace QRBee.Load.Generator
|
||||
namespace QRBee.Load.Generator;
|
||||
|
||||
internal class LoadSpike : AnomalyBase
|
||||
{
|
||||
internal class LoadSpike
|
||||
{
|
||||
private readonly IOptions<GeneratorSettings> _settings;
|
||||
private readonly ILogger<LoadSpike> _logger;
|
||||
|
||||
private TimeSpan _spikeDuration;
|
||||
private TimeSpan _spikeDelay;
|
||||
private double _spikeProbability;
|
||||
private bool _spikeActive;
|
||||
private int _delayBetweenMessagesMSec;
|
||||
private int _delayJitterMSec;
|
||||
|
||||
private ThreadSafeRandom _rng = new();
|
||||
private DateTime _spikeEnd = DateTime.MinValue;
|
||||
|
||||
public LoadSpike( IOptions<GeneratorSettings> settings, ILogger<LoadSpike> logger )
|
||||
public LoadSpike(IOptions<GeneratorSettings> settings, ILogger<LoadSpike> logger, IAnomalyReporter anomalyReporter)
|
||||
: base("Load spike", settings.Value.LoadSpike, logger, anomalyReporter)
|
||||
{
|
||||
_settings = settings;
|
||||
_logger = logger;
|
||||
|
||||
var loadSpike = settings.Value.LoadSpike;
|
||||
_spikeDuration = TimeSpan.Zero;
|
||||
_spikeDelay = TimeSpan.Zero;
|
||||
_spikeProbability = loadSpike?.Probability ?? 0.0;
|
||||
_spikeDelay = TimeSpan.FromSeconds(15);
|
||||
|
||||
if (loadSpike != null && loadSpike.Probability > 0.0)
|
||||
_delayBetweenMessagesMSec = settings.Value.DelayBetweenMessagesMSec;
|
||||
_delayJitterMSec = settings.Value.DelayJitterMSec;
|
||||
|
||||
if (IsEnabled)
|
||||
{
|
||||
if (!loadSpike.Parameters.TryGetValue("Duration", out var duration)
|
||||
|| !TimeSpan.TryParse(duration, out _spikeDuration))
|
||||
{
|
||||
_spikeProbability = 0.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (loadSpike.Parameters.TryGetValue("Delay", out duration)
|
||||
if (loadSpike.Parameters.TryGetValue("Delay", out var duration)
|
||||
&& TimeSpan.TryParse(duration, out _spikeDelay))
|
||||
{
|
||||
_spikeDelay = TimeSpan.FromMilliseconds(10);
|
||||
_logger.LogDebug($"Load spike configured. Probablility={_spikeProbability} Duration=\"{_spikeDuration:g}\" Delay=\"{_spikeDelay:g}\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public async Task Delay()
|
||||
{
|
||||
if (DateTime.Now > _spikeEnd)
|
||||
{
|
||||
if (_spikeActive)
|
||||
{
|
||||
_spikeActive = false;
|
||||
_logger.LogWarning($"Anomaly: Load spike ended");
|
||||
}
|
||||
|
||||
var dice = _rng.NextDouble();
|
||||
if (dice < _spikeProbability)
|
||||
{
|
||||
// start load spike
|
||||
_spikeEnd = DateTime.Now + _spikeDuration;
|
||||
_spikeActive = true;
|
||||
|
||||
_logger.LogWarning($"Anomaly: Load spike until {_spikeEnd} Dice={dice}");
|
||||
|
||||
if (IsActive())
|
||||
await Task.Delay(_spikeDelay);
|
||||
}
|
||||
else
|
||||
{
|
||||
await Task.Delay(_rng.NextInRange(
|
||||
_settings.Value.DelayBetweenMessagesMSec,
|
||||
_settings.Value.DelayBetweenMessagesMSec + _settings.Value.DelayJitterMSec
|
||||
_delayBetweenMessagesMSec,
|
||||
_delayBetweenMessagesMSec + _delayJitterMSec
|
||||
));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await Task.Delay(_spikeDelay);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,31 +9,13 @@ internal class PaymentRequestGenerator
|
||||
{
|
||||
private readonly ClientPool _clientPool;
|
||||
private readonly ILogger<PaymentRequestGenerator> _logger;
|
||||
private ThreadSafeRandom _rng = new ThreadSafeRandom();
|
||||
private double _minAmount = 1;
|
||||
private double _maxAmount = 100;
|
||||
private double _largeAmountProbability;
|
||||
private double _largeAmountValue;
|
||||
private readonly LargeAmount _largeAmount;
|
||||
|
||||
public PaymentRequestGenerator(ClientPool clientPool, IOptions<GeneratorSettings> settings, ILogger<PaymentRequestGenerator> logger)
|
||||
public PaymentRequestGenerator(ClientPool clientPool, IOptions<GeneratorSettings> settings, ILogger<PaymentRequestGenerator> logger, LargeAmount largeAmount)
|
||||
{
|
||||
_clientPool = clientPool;
|
||||
_logger = logger;
|
||||
_minAmount = settings.Value.MinAmount;
|
||||
_maxAmount = settings.Value.MaxAmount;
|
||||
|
||||
var largeAmount = settings.Value.LargeAmount;
|
||||
_largeAmountProbability = largeAmount.Probability;
|
||||
if (_largeAmountProbability > 0)
|
||||
{
|
||||
if ( largeAmount.Parameters.TryGetValue("Value", out var s))
|
||||
_largeAmountValue = Double.Parse(s);
|
||||
}
|
||||
|
||||
if (_largeAmountValue <= 0.0)
|
||||
_largeAmountProbability = 0.0;
|
||||
else
|
||||
_logger.LogDebug($"Large amount spike configured: Probability={_largeAmountProbability} Value=\"{_largeAmountValue}\"");
|
||||
_largeAmount = largeAmount;
|
||||
}
|
||||
|
||||
public async Task<PaymentRequest> GeneratePaymentRequest(int clientId, int merchantId)
|
||||
@ -79,16 +61,7 @@ internal class PaymentRequestGenerator
|
||||
return response;
|
||||
}
|
||||
|
||||
private decimal GetAmount()
|
||||
{
|
||||
var dice = _rng.NextDouble();
|
||||
if (dice < _largeAmountProbability)
|
||||
{
|
||||
_logger.LogWarning($"Anomaly: Large amount Dice={dice}");
|
||||
return Convert.ToDecimal(_rng.NextDoubleInRange(_largeAmountValue, _largeAmountValue* 1.10));
|
||||
}
|
||||
return Convert.ToDecimal(_rng.NextDoubleInRange(_minAmount, _maxAmount));
|
||||
}
|
||||
private decimal GetAmount() => _largeAmount.GetAmount();
|
||||
|
||||
private Task<ClientSettings> GetMerchant(int id) => _clientPool.GetMerchant(id);
|
||||
|
||||
|
||||
@ -39,15 +39,18 @@ builder.ConfigureServices((context, services) =>
|
||||
.AddSingleton<TransactionDefiler>()
|
||||
.AddSingleton<UnconfirmedTransactions>()
|
||||
.AddSingleton<LoadSpike>()
|
||||
.AddSingleton<LargeAmount>()
|
||||
.AddSingleton<IAnomalyReporter, AnomalyReporter>()
|
||||
.AddSingleton<PrivateKeyHandlerFactory>(x => no => new PrivateKeyHandler(x.GetRequiredService<ILogger<ServerPrivateKeyHandler>>(), x.GetRequiredService<IConfiguration>(), no))
|
||||
.AddSingleton<SecurityServiceFactory>(x => no => new AndroidSecurityService(x.GetRequiredService<PrivateKeyHandlerFactory>()(no)))
|
||||
.AddHostedService<LoadGenerator>()
|
||||
;
|
||||
});
|
||||
|
||||
ServicePointManager.DefaultConnectionLimit = 10;
|
||||
// ServicePointManager.DefaultConnectionLimit = 500;
|
||||
ServicePointManager.ReusePort = true;
|
||||
ServicePointManager.CheckCertificateRevocationList = false;
|
||||
|
||||
|
||||
var host = builder.Build();
|
||||
host.Run();
|
||||
|
||||
@ -4,57 +4,23 @@ using QRBee.Core.Data;
|
||||
|
||||
namespace QRBee.Load.Generator
|
||||
{
|
||||
internal class TransactionDefiler
|
||||
internal class TransactionDefiler : AnomalyBase
|
||||
{
|
||||
private readonly ILogger<TransactionDefiler> _logger;
|
||||
|
||||
private ThreadSafeRandom _rng = new();
|
||||
|
||||
private double _corruptionProbability;
|
||||
private int _sequenceLengthMin;
|
||||
private int _sequenceLengthMax;
|
||||
private int _corruptionCounter;
|
||||
|
||||
public TransactionDefiler(IOptions<GeneratorSettings> settings, ILogger<TransactionDefiler> logger)
|
||||
public TransactionDefiler(IOptions<GeneratorSettings> settings, ILogger<TransactionDefiler> logger, IAnomalyReporter anomalyReporter)
|
||||
: base("Corrupted transaction", settings.Value.TransactionCorruption, logger, anomalyReporter)
|
||||
{
|
||||
_logger = logger;
|
||||
|
||||
var transactionCorruption = settings.Value.TransactionCorruption;
|
||||
_corruptionProbability = transactionCorruption.Probability;
|
||||
if (_corruptionProbability > 0)
|
||||
{
|
||||
if (transactionCorruption.Parameters.TryGetValue("SequenceLengthMin", out var s))
|
||||
_sequenceLengthMin = int.Parse(s);
|
||||
if (transactionCorruption.Parameters.TryGetValue("SequenceLengthMax", out s))
|
||||
_sequenceLengthMax = int.Parse(s);
|
||||
_logger.LogDebug($"Transaction corruption configured: Probability={_corruptionProbability}, SequenceLengthMin={_sequenceLengthMin}, SequenceLengthMax={_sequenceLengthMax}");
|
||||
}
|
||||
}
|
||||
|
||||
public void CorruptPaymentRequest(PaymentRequest paymentRequest)
|
||||
{
|
||||
if(Interlocked.Decrement(ref _corruptionCounter) > 0 )
|
||||
{
|
||||
if ( IsActive() )
|
||||
paymentRequest.ClientResponse.MerchantRequest.Amount += 0.01M;
|
||||
return;
|
||||
}
|
||||
|
||||
var dice = _rng.NextDouble();
|
||||
if (dice < _corruptionProbability)
|
||||
{
|
||||
paymentRequest.ClientResponse.MerchantRequest.Amount += 10M;
|
||||
var cc = _rng.NextInRange(_sequenceLengthMin, _sequenceLengthMax);
|
||||
_corruptionCounter = cc;
|
||||
_logger.LogWarning($"Anomaly: Corrupted transaction. Dice={dice} SequenceLength={cc}");
|
||||
}
|
||||
}
|
||||
|
||||
public void CorruptPaymentConfirmation(PaymentConfirmation paymentConfirmation)
|
||||
{
|
||||
if (Interlocked.Decrement(ref _corruptionCounter) > 0)
|
||||
{
|
||||
if (IsActive())
|
||||
paymentConfirmation.GatewayTransactionId = "BadGatewayTransactionId";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,50 +3,12 @@ using Microsoft.Extensions.Options;
|
||||
|
||||
namespace QRBee.Load.Generator;
|
||||
|
||||
internal class UnconfirmedTransactions
|
||||
internal class UnconfirmedTransactions : AnomalyBase
|
||||
{
|
||||
private readonly ILogger<UnconfirmedTransactions> _logger;
|
||||
|
||||
private double _unconfirmedProbability;
|
||||
private TimeSpan _unconfirmedDuration;
|
||||
private DateTime _anomalyEnd = DateTime.MinValue;
|
||||
private ThreadSafeRandom _rng = new();
|
||||
|
||||
public UnconfirmedTransactions(IOptions<GeneratorSettings> settings, ILogger<UnconfirmedTransactions> logger)
|
||||
public UnconfirmedTransactions(IOptions<GeneratorSettings> settings, ILogger<UnconfirmedTransactions> logger, IAnomalyReporter anomalyReporter)
|
||||
: base("Unconfirmed transaction", settings.Value.UnconfirmedTransaction, logger, anomalyReporter)
|
||||
{
|
||||
_logger = logger;
|
||||
_unconfirmedProbability = settings.Value.UnconfirmedTransaction.Probability;
|
||||
_unconfirmedDuration = TimeSpan.Zero;
|
||||
|
||||
if (_unconfirmedProbability > 0.0)
|
||||
{
|
||||
if (settings.Value.UnconfirmedTransaction.Parameters.TryGetValue("Duration", out var duration)
|
||||
|| TimeSpan.TryParse(duration, out _unconfirmedDuration))
|
||||
{
|
||||
_logger.LogInformation($"Unconfirmed transactions configured. Probablility={_unconfirmedProbability}");
|
||||
}
|
||||
else
|
||||
{
|
||||
_unconfirmedProbability = 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool ShouldConfirm()
|
||||
{
|
||||
if ( DateTime.UtcNow < _anomalyEnd )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var dice = _rng.NextDouble();
|
||||
if (dice < _unconfirmedProbability)
|
||||
{
|
||||
_anomalyEnd = DateTime.UtcNow + _unconfirmedDuration;
|
||||
_logger.LogWarning($"Anomaly: Unconfirmed transaction. Dice={dice} Ends=\"{_anomalyEnd.ToLocalTime():s}\"");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
public bool ShouldConfirm() => IsActive();
|
||||
}
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
"QRBeeURL": "http://localhost:5000/",
|
||||
"NumberOfClients": 100,
|
||||
"NumberOfMerchants": 10,
|
||||
"NumberOfThreads": 10,
|
||||
"NumberOfThreads": 6,
|
||||
"DelayBetweenMessagesMSec": 500,
|
||||
"DelayJitterMSec": 50,
|
||||
"MinAmount": 10,
|
||||
@ -24,8 +24,8 @@
|
||||
//0.004
|
||||
"LoadSpike": {
|
||||
"Probability": 0.002,
|
||||
"Parameters": {
|
||||
"Duration": "00:00:15",
|
||||
"Parameters": {
|
||||
"Delay": "00:00:00.0100000"
|
||||
}
|
||||
},
|
||||
@ -33,15 +33,15 @@
|
||||
//0.002
|
||||
"TransactionCorruption": {
|
||||
"Probability": 0,
|
||||
"Duration": "00:00:15",
|
||||
"Parameters": {
|
||||
"SequenceLengthMin": 2,
|
||||
"SequenceLengthMax": 10
|
||||
}
|
||||
},
|
||||
|
||||
//0.003
|
||||
"LargeAmount": {
|
||||
"Probability": 0,
|
||||
"Duration": "00:00:15",
|
||||
"Parameters": {
|
||||
"Value": "1000"
|
||||
}
|
||||
@ -50,6 +50,7 @@
|
||||
//0.003
|
||||
"UnconfirmedTransaction": {
|
||||
"Probability": 0,
|
||||
"Duration": "00:00:15",
|
||||
"Parameters": {
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user