DEEP-37 Base class for anomalies created

This commit is contained in:
Andrey Shabarshov 2023-07-22 12:55:36 +01:00
parent 58b7565597
commit c5895adaa5
11 changed files with 221 additions and 195 deletions

View 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;
}
}

View 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);
}
}
}

View File

@ -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();
}

View File

@ -0,0 +1,7 @@
namespace QRBee.Load.Generator
{
internal interface IAnomalyReporter
{
void Report(DateTime start, DateTime end, string description);
}
}

View 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));
}
}

View File

@ -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);
}
}
}
}

View File

@ -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);

View File

@ -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();

View File

@ -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";
}
}
}
}

View File

@ -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();
}

View File

@ -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": {
}
}