├── .gitattributes ├── .gitignore ├── BuildAndDeployment ├── Legacy │ ├── Build.Project.Pro.bat │ ├── Build.Project.bat │ ├── EditNuspec.msbuild │ └── Viki.LoadRunner.Tools.Legacy.nuspec └── nuget.exe ├── LICENSE ├── README.md ├── Viki.LoadRunner.sln ├── demo ├── App.config ├── Common │ ├── SleepingScenario.cs │ └── SleepingScenarioFactory.cs ├── DemoResults.xlsx ├── Guides │ ├── Aggregation │ │ ├── HistogramAggregatorDemo.cs │ │ └── RawDataMeasurementsDemo.cs │ ├── QuickStart │ │ └── QuickStartDemo.cs │ └── StrategyBuilderFeatures │ │ └── ScenarioFactoryDemo.cs ├── Legacy │ ├── Detailed │ │ ├── Aggregation.cs │ │ ├── DetailedDemo.cs │ │ ├── Scenario.cs │ │ └── Strategy.cs │ └── Features │ │ └── RawDataAggregation.cs ├── LoadRunner.Demo.csproj ├── LoadRunner.Demo.sln ├── Program.cs ├── Properties │ └── AssemblyInfo.cs ├── Theoretical │ ├── AggregationImpactDemo.cs │ ├── CountingScenario.cs │ ├── CountingScenarioFactory.cs │ └── TheoreticalSpeedDemo.cs └── packages.config ├── diagrams └── Architecture.png └── src ├── Viki.LoadRunner.Engine ├── Aggregators │ ├── Dimensions │ │ ├── FuncDimension.cs │ │ └── TimeDimension.cs │ ├── FileStreamAggregatorBase.cs │ ├── HistogramAggregator.cs │ ├── Interfaces │ │ ├── IDimension.cs │ │ └── IMetric.cs │ ├── Metrics │ │ ├── AvgDurationMetric.cs │ │ ├── BreakByMetric.cs │ │ ├── CheckpointValuesMetric.cs │ │ ├── CountMetric.cs │ │ ├── ErrorCountMetric.cs │ │ ├── ErrorRatioMetric.cs │ │ ├── FuncMetric.cs │ │ ├── FuncMultiMetric.cs │ │ ├── GlobalTimerMaxValueMetric.cs │ │ ├── GlobalTimerMinValueMetric.cs │ │ ├── GlobalTimerPeriodMetric.cs │ │ ├── MaxDurationMetric.cs │ │ ├── MetricBase.cs │ │ ├── MinDurationMetric.cs │ │ ├── MultiMetricBase.cs │ │ ├── PercentileMetric.cs │ │ └── TransactionsPerSecMetric.cs │ ├── Result │ │ └── HistogramResult.cs │ ├── StreamAggregator.cs │ ├── StreamAggregatorBase.cs │ └── Utils │ │ ├── OrderLearner.cs │ │ ├── ReplayResult.cs │ │ ├── ResultExtensions.cs │ │ └── UnixDateTimeExtensions.cs ├── Analytics │ ├── DimensionKey.cs │ ├── Dimensions │ │ ├── FuncDimension.cs │ │ ├── ThresholdDimension.cs │ │ └── TimeDimension.cs │ ├── DimensionsHandler.cs │ ├── Extensions │ │ └── HistogramExtensions.cs │ ├── FlexiRow.cs │ ├── HistogramBase.cs │ ├── HistogramBuilderExtensions.cs │ ├── Interfaces │ │ ├── IDimension.cs │ │ ├── IHistogramBuilder.cs │ │ └── IMetric.cs │ ├── Metrics │ │ ├── AverageMetric.cs │ │ ├── Calculators │ │ │ ├── AverageCalculator.cs │ │ │ └── RatioCalculator.cs │ │ ├── CountMetric.cs │ │ ├── Delegates.cs │ │ ├── DistinctCountMetric.cs │ │ ├── DistinctListCountMetric.cs │ │ ├── DistinctListMetric.cs │ │ ├── FilterMetric.cs │ │ ├── FuncMetric.cs │ │ ├── MaxMetric.cs │ │ ├── MinMetric.cs │ │ ├── PercentileMetric.cs │ │ ├── RatioMetric.cs │ │ ├── SubDimension.cs │ │ ├── SubMetric.cs │ │ ├── SumMetric.cs │ │ └── ValuesMetric.cs │ ├── MetricsHandler.cs │ └── MetricsTemplate.cs ├── Core │ ├── Collector │ │ ├── AggregatorException.cs │ │ ├── Interfaces │ │ │ ├── IAggregator.cs │ │ │ ├── IDataCollector.cs │ │ │ └── IResult.cs │ │ ├── IterationResult.cs │ │ ├── NullDataCollector.cs │ │ ├── PipeDataCollector.cs │ │ ├── Pipeline │ │ │ ├── BatchingPipe.cs │ │ │ ├── Extensions │ │ │ │ ├── ConsumerExtensions.cs │ │ │ │ └── ProducerExtensions.cs │ │ │ ├── Interfaces │ │ │ │ ├── IConsumer.cs │ │ │ │ ├── IPipeFactory.cs │ │ │ │ └── IProducer.cs │ │ │ ├── PipeFactory.cs │ │ │ ├── PipeMultiplexer.cs │ │ │ ├── PipeMultiplier.cs │ │ │ └── PipeMuxer.cs │ │ └── PipelineDataAggregator.cs │ ├── Counter │ │ ├── Interfaces │ │ │ ├── ICounter.cs │ │ │ └── IThreadPoolCounter.cs │ │ ├── ThreadPoolCounter.cs │ │ └── ThreadSafeCounter.cs │ ├── Factory │ │ ├── Interfaces │ │ │ ├── FuncScenarioFactory.cs │ │ │ ├── IDataCollectorFactory.cs │ │ │ ├── IFactory.cs │ │ │ ├── IIterationContextFactory.cs │ │ │ ├── IScenarioFactory.cs │ │ │ ├── IScenarioHandlerFactory.cs │ │ │ ├── IScenarioThreadFactory.cs │ │ │ └── ISchedulerFactory.cs │ │ ├── IterationContextFactory.cs │ │ ├── NullDataCollectorFactory.cs │ │ ├── NullSchedulerFactory.cs │ │ ├── PipeDataCollectorFactory.cs │ │ ├── ReflectionFactory.cs │ │ ├── ScenarioFactory.cs │ │ ├── ScenarioHandlerFactory.cs │ │ ├── ScenarioThreadFactory.cs │ │ ├── SchedulerFactory.cs │ │ └── ThreadFactory.cs │ ├── Generator │ │ ├── Interfaces │ │ │ └── IUniqueIdGenerator.cs │ │ ├── MockedIdGenerator.cs │ │ ├── NotThreadSafeIdGenerator.cs │ │ └── ThreadSafeIdGenerator.cs │ ├── Pool │ │ ├── Interfaces │ │ │ ├── IThreadFactory.cs │ │ │ ├── IThreadPool.cs │ │ │ └── IThreadPoolStats.cs │ │ ├── ThreadPool.cs │ │ └── WorkerThreadStats.cs │ ├── Scenario │ │ ├── Checkpoint.cs │ │ ├── CheckpointExtensions.cs │ │ ├── GlobalCounters.cs │ │ ├── Interfaces │ │ │ ├── ICheckpoint.cs │ │ │ ├── IGlobalCounters.cs │ │ │ ├── IGlobalCountersControl.cs │ │ │ ├── IIteration.cs │ │ │ ├── IIterationControl.cs │ │ │ ├── IIterationId.cs │ │ │ ├── IIterationMetadata.cs │ │ │ ├── IIterationResult.cs │ │ │ ├── IScenario.cs │ │ │ └── IScenarioHandler.cs │ │ ├── IterationContext.cs │ │ ├── ScenarioBase.cs │ │ └── ScenarioHandler.cs │ ├── Scheduler │ │ ├── Interfaces │ │ │ ├── IScheduler.cs │ │ │ └── IWait.cs │ │ ├── NullScheduler.cs │ │ ├── Scheduler.cs │ │ └── SemiWait.cs │ ├── State │ │ ├── Interfaces │ │ │ └── ITestState.cs │ │ └── TestState.cs │ ├── Timer │ │ ├── ExecutionTimer.cs │ │ ├── Interfaces │ │ │ ├── ITimer.cs │ │ │ └── ITimerControl.cs │ │ └── StopWatchEx.cs │ └── Worker │ │ ├── ErrorHandler.cs │ │ ├── Interfaces │ │ ├── IErrorHandler.cs │ │ ├── IPrewait.cs │ │ ├── IThread.cs │ │ └── IWork.cs │ │ ├── NullPrewait.cs │ │ ├── ScenarioWork.cs │ │ ├── TimerBasedPrewait.cs │ │ ├── WorkerException.cs │ │ └── WorkerThread.cs ├── Interfaces │ ├── IStrategyExecutor.cs │ └── IStrategyExecutorAsync.cs ├── LoadRunnerEngine.cs ├── Strategies │ ├── Custom │ │ ├── Adapter │ │ │ ├── Limit │ │ │ │ └── LimitsHandler.cs │ │ │ └── Speed │ │ │ │ ├── ScheduleTable.cs │ │ │ │ └── SlowestSpeedStrategy.cs │ │ ├── CustomStrategy.cs │ │ ├── Factory │ │ │ └── PriorityStrategyFactory.cs │ │ ├── Interfaces │ │ │ └── ICustomStrategySettings.cs │ │ └── Strategies │ │ │ ├── Interfaces │ │ │ ├── ILimitStrategy.cs │ │ │ ├── ISpeedStrategy.cs │ │ │ └── IThreadingStrategy.cs │ │ │ ├── Limit │ │ │ ├── ErrorLimit.cs │ │ │ ├── IterationLimit.cs │ │ │ └── TimeLimit.cs │ │ │ ├── Speed │ │ │ ├── BatchBySlowestSpeed.cs │ │ │ ├── BatchByTimeIntervalSpeed.cs │ │ │ ├── ClockedListOfSpeed.cs │ │ │ ├── FixedSpeed.cs │ │ │ ├── IncrementalLimitWorkingThreads.cs │ │ │ ├── IncrementalSpeed.cs │ │ │ ├── LimitWorkingThreads.cs │ │ │ ├── ListOfSpeed.cs │ │ │ └── MaxSpeed.cs │ │ │ └── Threading │ │ │ ├── FixedThreadCount.cs │ │ │ ├── IncrementalThreadCount.cs │ │ │ └── ListOfCounts.cs │ ├── Extensions │ │ ├── FeatureExtensions.cs │ │ ├── ReplayDataReaderExtensions.cs │ │ └── StrategyBuilderExtensions.cs │ ├── Interfaces │ │ ├── IAggregatorFeature.cs │ │ ├── IStrategy.cs │ │ ├── IStrategyBuilder.cs │ │ ├── ITimeoutFeature.cs │ │ └── IUserDataFeature.cs │ ├── Replay │ │ ├── Data │ │ │ ├── DataItem.cs │ │ │ ├── Interfaces │ │ │ │ └── IReplayDataReader.cs │ │ │ └── Readers │ │ │ │ ├── PartitionedDataReader.cs │ │ │ │ └── ReplayDataReader.cs │ │ ├── Factory │ │ │ ├── Interfaces │ │ │ │ ├── IReplayScenarioFactory.cs │ │ │ │ ├── IReplayScenarioHandlerFactory.cs │ │ │ │ └── IReplaySchedulerFactory.cs │ │ │ ├── ReplayScenarioFactory.cs │ │ │ ├── ReplayScenarioHandlerFactory.cs │ │ │ ├── ReplayScenarioThreadFactory.cs │ │ │ └── ReplaySchedulerFactory.cs │ │ ├── Interfaces │ │ │ ├── IReplayScenario.cs │ │ │ └── IReplayStrategySettings.cs │ │ ├── ReplayStrategy.cs │ │ ├── Scenario │ │ │ ├── Interfaces │ │ │ │ └── IReplayScenarioHandler.cs │ │ │ └── ReplayScenarioHandler.cs │ │ └── Scheduler │ │ │ ├── DataContext.cs │ │ │ ├── Interfaces │ │ │ └── IDataContext.cs │ │ │ └── ReplayScheduler.cs │ ├── ReplayStrategyBuilder.cs │ └── StrategyBuilder.cs ├── Utils │ ├── ArrayExtensions.cs │ ├── EnumerableExtensions.cs │ └── StringExtensions.cs ├── Validators │ ├── IValidator.cs │ ├── ReplayScenarioValidator.cs │ ├── ReplayScenarioValidatorExtensions.cs │ ├── ScenarioValidator.cs │ └── ScenarioValidatorExtensions.cs └── Viki.LoadRunner.Engine.csproj ├── Viki.LoadRunner.Playground ├── App.config ├── AssertPipeline.cs ├── BatchAndWaitDemo.cs ├── BatchStrategy.cs ├── BlankScenario.cs ├── BlankStressScenarioJsonStream.cs ├── BlankStressScenarioMemoryStream.cs ├── DemoSetup.cs ├── FaultyAggregator.cs ├── LimitConcurrencyAndTpsDemo.cs ├── Program.cs ├── Properties │ └── AssemblyInfo.cs ├── Replay │ ├── DataGenerator.cs │ ├── ReplayDemo.cs │ └── ReplayScenario.cs ├── RnD │ └── PercentileMetricWithCount.cs ├── TheoreticalSpeedDemo.cs ├── Tools │ ├── AssertIterationIdsAggregator.cs │ ├── CountingScenario.cs │ └── CountingScenarioFactory.cs ├── Viki.LoadRunner.Playground.csproj └── packages.config ├── Viki.LoadRunner.Tools.Legacy ├── Extensions │ ├── ControlExtensions.cs │ └── StrategyBuilderExtensions.cs ├── Properties │ └── AssemblyInfo.cs ├── Viki.LoadRunner.Tools.Legacy.csproj ├── Windows │ ├── LoadRunnerUi.Designer.cs │ ├── LoadRunnerUi.cs │ └── LoadRunnerUi.resx └── packages.config └── Viki.LoadRunner.Tools ├── Aggregators ├── BsonStreamAggregator.cs ├── FileStreamAggregator.cs └── JsonStreamAggregator.cs ├── Analytics └── Histogram.cs ├── ConsoleUi └── KpiPrinterAggregator.cs ├── Extensions ├── CsvStream.cs ├── DictionaryExtensions.cs └── JsonStream.cs ├── Strategy ├── GenericWork.cs └── StrategyBase.cs └── Viki.LoadRunner.Tools.csproj /BuildAndDeployment/Legacy/Build.Project.Pro.bat: -------------------------------------------------------------------------------- 1 | Build.Project.bat Professional -------------------------------------------------------------------------------- /BuildAndDeployment/Legacy/EditNuspec.msbuild: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | @(FileLines,'%0d%0a') 11 | $(FileContents.Replace('version%3E%3C', 'version%3E$(majorVersion).$(minorVersion)%3C')) 12 | $(FileContents.Replace('id%3E%3C', 'id%3E$(projectID).$(majorVersion)%3C')) 13 | 14 | 15 | 16 | 17 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /BuildAndDeployment/Legacy/Viki.LoadRunner.Tools.Legacy.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | LoadRunner Tools Legacy 5 | Viki.LoadRunner.Tools.Legacy 6 | 7 | Vytautas Klumbys 8 | 9 | This library only contains Winforms BuildUi() as it is unsupported in .NET Standard 10 | Legacy features cut from Tools nuget, as they not work under .NET Standard 11 | false 12 | 13 | https://github.com/Vycka/LoadRunner/blob/master/LICENSE 14 | https://github.com/Vycka/LoadRunner 15 | 2019 16 | Load Stress Performance Test Testing c# Runner Parallel Thread Library Framework Tool Addin .NET 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /BuildAndDeployment/nuget.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vycka/LoadRunner/ccca980dd144c729f025d5c1b2125b62a64a0d48/BuildAndDeployment/nuget.exe -------------------------------------------------------------------------------- /demo/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /demo/Common/SleepingScenario.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using Viki.LoadRunner.Engine.Core.Scenario.Interfaces; 4 | 5 | namespace LoadRunner.Demo.Common 6 | { 7 | public class SleepingScenario : IScenario 8 | { 9 | private readonly TimeSpan _sleepTime; 10 | 11 | public SleepingScenario(TimeSpan sleepTime) 12 | { 13 | _sleepTime = sleepTime; 14 | } 15 | 16 | public void ScenarioSetup(IIteration context) 17 | { 18 | } 19 | 20 | public void IterationSetup(IIteration context) 21 | { 22 | } 23 | 24 | public void ExecuteScenario(IIteration context) 25 | { 26 | Thread.Sleep(_sleepTime); 27 | } 28 | 29 | public void IterationTearDown(IIteration context) 30 | { 31 | } 32 | 33 | public void ScenarioTearDown(IIteration context) 34 | { 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /demo/Common/SleepingScenarioFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Viki.LoadRunner.Engine.Core.Factory.Interfaces; 3 | using Viki.LoadRunner.Engine.Core.Scenario.Interfaces; 4 | 5 | namespace LoadRunner.Demo.Common 6 | { 7 | public class SleepingScenarioFactory : IScenarioFactory 8 | { 9 | private readonly TimeSpan _sleepTime; 10 | 11 | public SleepingScenarioFactory(TimeSpan sleepTime) 12 | { 13 | _sleepTime = sleepTime; 14 | } 15 | 16 | public IScenario Create(int threadId) 17 | { 18 | return new SleepingScenario(_sleepTime); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /demo/DemoResults.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vycka/LoadRunner/ccca980dd144c729f025d5c1b2125b62a64a0d48/demo/DemoResults.xlsx -------------------------------------------------------------------------------- /demo/LoadRunner.Demo.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27130.2024 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LoadRunner.Demo", "LoadRunner.Demo.csproj", "{E3DA725E-873C-4CDC-9DCD-272A8D6FF7A3}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {E3DA725E-873C-4CDC-9DCD-272A8D6FF7A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {E3DA725E-873C-4CDC-9DCD-272A8D6FF7A3}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {E3DA725E-873C-4CDC-9DCD-272A8D6FF7A3}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {E3DA725E-873C-4CDC-9DCD-272A8D6FF7A3}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {E900CC96-4402-4B65-9B9F-E8337AB0A14E} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /demo/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("LoadRunner.Demo")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("LoadRunner.Demo")] 12 | [assembly: AssemblyCopyright("Copyright © 2015")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("e3da725e-873c-4cdc-9dcd-272a8d6ff7a3")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] 36 | -------------------------------------------------------------------------------- /demo/Theoretical/CountingScenario.cs: -------------------------------------------------------------------------------- 1 | using Viki.LoadRunner.Engine.Core.Scenario.Interfaces; 2 | 3 | namespace LoadRunner.Demo.Theoretical 4 | { 5 | public class CountingScenario : IScenario 6 | { 7 | public int Count = 0; 8 | public int ThreadId; 9 | 10 | public void ScenarioSetup(IIteration context) 11 | { 12 | ThreadId = context.ThreadId; 13 | } 14 | 15 | public void IterationSetup(IIteration context) 16 | { 17 | 18 | } 19 | 20 | public void ExecuteScenario(IIteration context) 21 | { 22 | Count = Count + 1; 23 | } 24 | 25 | public void IterationTearDown(IIteration context) 26 | { 27 | 28 | } 29 | 30 | public void ScenarioTearDown(IIteration context) 31 | { 32 | 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /demo/Theoretical/CountingScenarioFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Viki.LoadRunner.Engine.Core.Factory.Interfaces; 5 | using Viki.LoadRunner.Engine.Core.Scenario.Interfaces; 6 | 7 | namespace LoadRunner.Demo.Theoretical 8 | { 9 | public class CountingScenarioFactory : IScenarioFactory 10 | { 11 | List _instances = new List(); 12 | 13 | public IScenario Create(int threadId) 14 | { 15 | CountingScenario scenario = new CountingScenario(); 16 | _instances.Add(scenario); 17 | 18 | return scenario; 19 | } 20 | 21 | public void PrintSum() 22 | { 23 | string perThread = String.Join(Environment.NewLine, _instances.Select(i => $"{i.ThreadId} {i.Count}")); 24 | int sum = GetSum(); 25 | 26 | Console.WriteLine(perThread); 27 | Console.WriteLine(@"-------"); 28 | Console.WriteLine(sum); 29 | } 30 | 31 | 32 | public int GetSum() 33 | { 34 | return _instances.Sum(i => i.Count); 35 | } 36 | 37 | } 38 | } -------------------------------------------------------------------------------- /demo/Theoretical/TheoreticalSpeedDemo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using Viki.LoadRunner.Engine; 4 | using Viki.LoadRunner.Engine.Strategies; 5 | using Viki.LoadRunner.Engine.Strategies.Custom.Strategies.Limit; 6 | using Viki.LoadRunner.Engine.Strategies.Custom.Strategies.Threading; 7 | using Viki.LoadRunner.Engine.Strategies.Extensions; 8 | 9 | namespace LoadRunner.Demo.Theoretical 10 | { 11 | 12 | // No aggregation is used here, worker threads them selves will count how much iterations they have done and all will be just summed up. 13 | public static class TheoreticalSpeedDemo 14 | { 15 | public static void Run() 16 | { 17 | CountingScenarioFactory scenarioFactory = new CountingScenarioFactory(); 18 | 19 | StrategyBuilder strategy = new StrategyBuilder() 20 | .SetScenario(scenarioFactory) 21 | .SetThreading(new FixedThreadCount(4)) 22 | .SetLimit(new TimeLimit(TimeSpan.FromSeconds(10))); 23 | 24 | // Increase TimeLimit precision 25 | LoadRunnerEngine engine = strategy.Build(); 26 | engine.HeartBeatMs = 1; 27 | 28 | Stopwatch watch = new Stopwatch(); 29 | watch.Start(); 30 | engine.Run(); 31 | watch.Stop(); 32 | 33 | scenarioFactory.PrintSum(); 34 | Console.WriteLine($@"TPS {scenarioFactory.GetSum() / watch.Elapsed.TotalSeconds:N}"); 35 | Console.WriteLine(watch.Elapsed.ToString("g")); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /demo/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /diagrams/Architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vycka/LoadRunner/ccca980dd144c729f025d5c1b2125b62a64a0d48/diagrams/Architecture.png -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Aggregators/Dimensions/FuncDimension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Viki.LoadRunner.Engine.Aggregators.Interfaces; 3 | using Viki.LoadRunner.Engine.Analytics.Interfaces; 4 | using Viki.LoadRunner.Engine.Core.Collector.Interfaces; 5 | 6 | namespace Viki.LoadRunner.Engine.Aggregators.Dimensions 7 | { 8 | /// 9 | /// Split results by provided Func 10 | /// 11 | public class FuncDimension : IDimension 12 | { 13 | private readonly Func _dimensionValueSelector; 14 | 15 | /// Name/Key of custom dimension 16 | /// Dimension value selector 17 | public FuncDimension(string dimensionName, Func dimensionValueSelector) 18 | { 19 | if (dimensionName == null) 20 | throw new ArgumentNullException(nameof(dimensionName)); 21 | if (dimensionValueSelector == null) 22 | throw new ArgumentNullException(nameof(dimensionValueSelector)); 23 | 24 | _dimensionValueSelector = dimensionValueSelector; 25 | DimensionName = dimensionName; 26 | } 27 | 28 | public string DimensionName { get; } 29 | 30 | string IDimension.GetKey(IResult result) 31 | { 32 | return _dimensionValueSelector(result); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Aggregators/Dimensions/TimeDimension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Viki.LoadRunner.Engine.Aggregators.Interfaces; 3 | using Viki.LoadRunner.Engine.Analytics.Interfaces; 4 | using Viki.LoadRunner.Engine.Core.Collector.Interfaces; 5 | 6 | namespace Viki.LoadRunner.Engine.Aggregators.Dimensions 7 | { 8 | /// 9 | /// Split results in provided time intervals 10 | /// 11 | public class TimeDimension : IDimension 12 | { 13 | public readonly TimeSpan Interval; 14 | 15 | public Func TimeSelector = r => r.IterationFinished; 16 | public Func Formatter = t => ((long) t.TotalSeconds).ToString(); 17 | 18 | /// interval timespan 19 | /// Custom name for dimension 20 | public TimeDimension(TimeSpan interval, string dimensionName = "Time (s)") 21 | { 22 | Interval = interval; 23 | DimensionName = dimensionName; 24 | } 25 | 26 | public string DimensionName { get; } 27 | 28 | string IDimension.GetKey(IResult result) 29 | { 30 | TimeSpan resultTimeSlot = Calculate(Interval, TimeSelector(result)); 31 | 32 | return Formatter(resultTimeSlot); 33 | } 34 | 35 | /// 36 | /// Calculates TimeSpan value for dimension key. 37 | /// 38 | public static TimeSpan Calculate(TimeSpan interval, TimeSpan time) 39 | { 40 | return TimeSpan.FromTicks(((int) (time.Ticks / interval.Ticks)) * interval.Ticks); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Aggregators/FileStreamAggregatorBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Viki.LoadRunner.Engine.Core.Collector.Interfaces; 4 | 5 | namespace Viki.LoadRunner.Engine.Aggregators 6 | { 7 | public abstract class FileStreamAggregatorBase : StreamAggregatorBase 8 | { 9 | #region Fields 10 | 11 | private readonly Func _outFileNameFunc; 12 | 13 | #endregion 14 | 15 | #region Constructors 16 | 17 | public FileStreamAggregatorBase(string jsonOutputfile) : this(() => jsonOutputfile) 18 | { 19 | } 20 | 21 | public FileStreamAggregatorBase(Func dynamicJsonOutputFile) 22 | { 23 | _outFileNameFunc = dynamicJsonOutputFile; 24 | 25 | } 26 | 27 | #endregion 28 | 29 | #region FileStreamAggregatorBase 30 | 31 | protected override void Process(IEnumerable stream) 32 | { 33 | Write(_outFileNameFunc(), stream); 34 | } 35 | 36 | #endregion 37 | 38 | #region FileStreamAggregatorBase abstracts 39 | 40 | public abstract void Write(string filePath, IEnumerable stream); 41 | 42 | #endregion 43 | } 44 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Aggregators/HistogramAggregator.cs: -------------------------------------------------------------------------------- 1 | using Viki.LoadRunner.Engine.Analytics; 2 | using Viki.LoadRunner.Engine.Core.Collector.Interfaces; 3 | 4 | namespace Viki.LoadRunner.Engine.Aggregators 5 | { 6 | /// 7 | /// Modular 2D grid histogram aggregator/builder. Use Add() method to register concrete IDiminension's and IMetric's 8 | /// 9 | public class HistogramAggregator : HistogramBase, IAggregator 10 | { 11 | public void End() 12 | { 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Aggregators/Interfaces/IDimension.cs: -------------------------------------------------------------------------------- 1 | using Viki.LoadRunner.Engine.Analytics.Interfaces; 2 | using Viki.LoadRunner.Engine.Core.Collector.Interfaces; 3 | 4 | namespace Viki.LoadRunner.Engine.Aggregators.Interfaces 5 | { 6 | public interface IDimension : IDimension 7 | { 8 | } 9 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Aggregators/Interfaces/IMetric.cs: -------------------------------------------------------------------------------- 1 | using Viki.LoadRunner.Engine.Analytics.Interfaces; 2 | using Viki.LoadRunner.Engine.Core.Collector.Interfaces; 3 | 4 | namespace Viki.LoadRunner.Engine.Aggregators.Interfaces 5 | { 6 | public interface IMetric : IMetric 7 | { 8 | } 9 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Aggregators/Metrics/BreakByMetric.cs: -------------------------------------------------------------------------------- 1 | using Viki.LoadRunner.Engine.Analytics.Interfaces; 2 | using Viki.LoadRunner.Engine.Analytics.Metrics; 3 | using Viki.LoadRunner.Engine.Core.Collector.Interfaces; 4 | 5 | namespace Viki.LoadRunner.Engine.Aggregators.Metrics 6 | { 7 | /// 8 | /// BreakByMetric allows additional data slicing by provided sub-dimension. 9 | /// 10 | public class BreakByMetric : SubDimension 11 | { 12 | public BreakByMetric(IDimension subDimension, params IMetric[] actualMetrics) 13 | : base(subDimension, actualMetrics) 14 | { 15 | } 16 | 17 | public BreakByMetric(IDimension subDimension, ColumnNameDelegate columnNameSelector, params IMetric[] actualMetrics) 18 | : base(subDimension, columnNameSelector, actualMetrics) 19 | { 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Aggregators/Metrics/ErrorCountMetric.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Viki.LoadRunner.Engine.Aggregators.Interfaces; 3 | using Viki.LoadRunner.Engine.Analytics; 4 | using Viki.LoadRunner.Engine.Analytics.Interfaces; 5 | using Viki.LoadRunner.Engine.Core.Collector.Interfaces; 6 | using Viki.LoadRunner.Engine.Core.Scenario.Interfaces; 7 | 8 | namespace Viki.LoadRunner.Engine.Aggregators.Metrics 9 | { 10 | public class ErrorCountMetric : IMetric 11 | { 12 | private readonly bool _includeTotals; 13 | private readonly FlexiRow _row = new FlexiRow(() => default(int)); 14 | 15 | public ErrorCountMetric(bool includeTotals = true) 16 | { 17 | _includeTotals = includeTotals; 18 | 19 | if (_includeTotals) 20 | _row.Touch("Errors: Totals"); 21 | } 22 | 23 | IMetric IMetric.CreateNew() 24 | { 25 | return new ErrorCountMetric(_includeTotals); 26 | } 27 | 28 | void IMetric.Add(IResult result) 29 | { 30 | 31 | ICheckpoint[] checkpoints = result.Checkpoints; 32 | for (int i = 0, j = checkpoints.Length; i < j; i++) 33 | { 34 | ICheckpoint checkpoint = checkpoints[i]; 35 | if (checkpoint.Error != null) 36 | { 37 | string key = "Errors: " + checkpoint.Name; 38 | 39 | _row[key]++; 40 | 41 | if (_includeTotals) 42 | _row["Errors: Totals"]++; 43 | } 44 | } 45 | } 46 | 47 | string[] IMetric.ColumnNames => _row.Keys.ToArray(); 48 | object[] IMetric.Values => _row.Values.Select(v => (object)v).ToArray(); 49 | } 50 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Aggregators/Metrics/FuncMetric.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Viki.LoadRunner.Engine.Analytics.Interfaces; 3 | using Viki.LoadRunner.Engine.Core.Collector.Interfaces; 4 | 5 | namespace Viki.LoadRunner.Engine.Aggregators.Metrics 6 | { 7 | public class FuncMetric : MetricBase 8 | { 9 | private readonly Func _metricFunc; 10 | 11 | public FuncMetric(string keyName, TValue initialValue, Func metricFunc) 12 | : base(keyName, initialValue) 13 | { 14 | _metricFunc = metricFunc; 15 | } 16 | 17 | protected override IMetric CreateNewMetric() 18 | { 19 | return new FuncMetric(_keyName, _initialValue, _metricFunc); 20 | } 21 | 22 | protected override void AddResult(IResult result) 23 | { 24 | _value = _metricFunc(_value, result); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Aggregators/Metrics/FuncMultiMetric.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Viki.LoadRunner.Engine.Analytics; 3 | using Viki.LoadRunner.Engine.Analytics.Interfaces; 4 | using Viki.LoadRunner.Engine.Core.Collector.Interfaces; 5 | 6 | namespace Viki.LoadRunner.Engine.Aggregators.Metrics 7 | { 8 | public class FuncMultiMetric : MultiMetricBase 9 | { 10 | private readonly Action, IResult> _metricProcedure; 11 | private readonly Func _cellBuilderFunc; 12 | 13 | public FuncMultiMetric(Action, IResult> metricProcedure, Func cellBuilderFunc) 14 | : base(cellBuilderFunc) 15 | { 16 | if (metricProcedure == null) throw new ArgumentNullException(nameof(metricProcedure)); 17 | if (cellBuilderFunc == null) throw new ArgumentNullException(nameof(cellBuilderFunc)); 18 | 19 | _metricProcedure = metricProcedure; 20 | _cellBuilderFunc = cellBuilderFunc; 21 | } 22 | 23 | protected override IMetric CreateNewMetric() 24 | { 25 | return new FuncMultiMetric(_metricProcedure, _cellBuilderFunc); 26 | } 27 | 28 | protected override void AddResult(IResult result) 29 | { 30 | _metricProcedure(_row, result); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Aggregators/Metrics/GlobalTimerMaxValueMetric.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Viki.LoadRunner.Engine.Aggregators.Interfaces; 3 | using Viki.LoadRunner.Engine.Analytics.Interfaces; 4 | using Viki.LoadRunner.Engine.Core.Collector.Interfaces; 5 | 6 | namespace Viki.LoadRunner.Engine.Aggregators.Metrics 7 | { 8 | public class GlobalTimerMaxValueMetric : IMetric 9 | { 10 | private TimeSpan _maxValue = TimeSpan.MinValue; 11 | 12 | public GlobalTimerMaxValueMetric(string name = "Timer Max") 13 | { 14 | if (name == null) 15 | throw new ArgumentNullException(nameof(name)); 16 | 17 | ColumnNames = new[] { name }; 18 | } 19 | 20 | public IMetric CreateNew() 21 | { 22 | return new GlobalTimerMaxValueMetric(ColumnNames[0]); 23 | } 24 | 25 | public void Add(IResult data) 26 | { 27 | if (data.IterationFinished > _maxValue) 28 | _maxValue = data.IterationFinished; 29 | } 30 | 31 | public string[] ColumnNames { get; } 32 | public object[] Values => new object[] { _maxValue }; 33 | } 34 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Aggregators/Metrics/GlobalTimerMinValueMetric.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Viki.LoadRunner.Engine.Aggregators.Interfaces; 3 | using Viki.LoadRunner.Engine.Analytics.Interfaces; 4 | using Viki.LoadRunner.Engine.Core.Collector.Interfaces; 5 | 6 | namespace Viki.LoadRunner.Engine.Aggregators.Metrics 7 | { 8 | public class GlobalTimerMinValueMetric : IMetric 9 | { 10 | private TimeSpan _minValue = TimeSpan.MaxValue; 11 | 12 | public GlobalTimerMinValueMetric(string name = "Timer Min") 13 | { 14 | if (name == null) 15 | throw new ArgumentNullException(nameof(name)); 16 | 17 | ColumnNames = new[] { name }; 18 | } 19 | 20 | public IMetric CreateNew() 21 | { 22 | return new GlobalTimerMinValueMetric(ColumnNames[0]); 23 | } 24 | 25 | public void Add(IResult data) 26 | { 27 | if (data.IterationStarted < _minValue) 28 | _minValue = data.IterationStarted; 29 | } 30 | 31 | public string[] ColumnNames { get; } 32 | public object[] Values => new object[] { _minValue }; 33 | } 34 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Aggregators/Metrics/GlobalTimerPeriodMetric.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using Viki.LoadRunner.Engine.Aggregators.Interfaces; 4 | using Viki.LoadRunner.Engine.Analytics.Interfaces; 5 | using Viki.LoadRunner.Engine.Core.Collector.Interfaces; 6 | 7 | namespace Viki.LoadRunner.Engine.Aggregators.Metrics 8 | { 9 | public class GlobalTimerPeriodMetric : IMetric 10 | { 11 | TimeSpan _min = TimeSpan.MaxValue; 12 | TimeSpan _max = TimeSpan.MinValue; 13 | 14 | public Func Formatter = (t) => t.TotalMilliseconds.ToString(CultureInfo.InvariantCulture); 15 | 16 | public GlobalTimerPeriodMetric(string name = "Timer period (ms)") 17 | { 18 | ColumnNames = new[] { name }; 19 | } 20 | 21 | public IMetric CreateNew() 22 | { 23 | return new GlobalTimerPeriodMetric(ColumnNames[0]) 24 | { 25 | Formatter = Formatter 26 | }; 27 | } 28 | 29 | public void Add(IResult data) 30 | { 31 | if (_min > data.IterationStarted) 32 | _min = data.IterationStarted; 33 | 34 | if (_max < data.IterationFinished) 35 | _max = data.IterationFinished; 36 | } 37 | 38 | public string[] ColumnNames { get; } 39 | public object[] Values => new [] { Formatter(_max - _min) }; 40 | } 41 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Aggregators/Metrics/MaxDurationMetric.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Viki.LoadRunner.Engine.Aggregators.Interfaces; 4 | using Viki.LoadRunner.Engine.Analytics; 5 | using Viki.LoadRunner.Engine.Analytics.Interfaces; 6 | using Viki.LoadRunner.Engine.Core.Collector.Interfaces; 7 | using Viki.LoadRunner.Engine.Core.Scenario; 8 | using Viki.LoadRunner.Engine.Core.Scenario.Interfaces; 9 | 10 | namespace Viki.LoadRunner.Engine.Aggregators.Metrics 11 | { 12 | public class MaxDurationMetric : IMetric 13 | { 14 | private readonly FlexiRow _row = new FlexiRow(() => long.MinValue); 15 | private readonly string[] _ignoredCheckpoints; 16 | 17 | public MaxDurationMetric(params string[] ignoredCheckpoints) 18 | { 19 | if (ignoredCheckpoints == null) 20 | throw new ArgumentNullException(nameof(ignoredCheckpoints)); 21 | 22 | _ignoredCheckpoints = ignoredCheckpoints.Union(Checkpoint.NotMeassuredCheckpoints).ToArray(); 23 | } 24 | 25 | public IMetric CreateNew() 26 | { 27 | return new MaxDurationMetric(_ignoredCheckpoints); 28 | } 29 | 30 | public void Add(IResult result) 31 | { 32 | ICheckpoint[] checkpoints = result.Checkpoints; 33 | for (int i = 0, j = checkpoints.Length - 1; i < j; i++) 34 | { 35 | ICheckpoint checkpoint = checkpoints[i]; 36 | if (checkpoint.Error == null && _ignoredCheckpoints.All(name => name != checkpoint.Name)) 37 | { 38 | string key = "Max: " + checkpoint.Name; 39 | TimeSpan momentDiff = checkpoint.Diff(checkpoints[i + 1]); 40 | 41 | if (_row[key] < momentDiff.TotalMilliseconds) 42 | _row[key] = Convert.ToInt64(momentDiff.TotalMilliseconds); 43 | 44 | } 45 | } 46 | } 47 | 48 | public string[] ColumnNames => _row.Keys.ToArray(); 49 | public object[] Values => _row.Values.Select(v => (object)v).ToArray(); 50 | 51 | } 52 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Aggregators/Metrics/MetricBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Viki.LoadRunner.Engine.Aggregators.Interfaces; 3 | using Viki.LoadRunner.Engine.Analytics.Interfaces; 4 | using Viki.LoadRunner.Engine.Core.Collector.Interfaces; 5 | 6 | namespace Viki.LoadRunner.Engine.Aggregators.Metrics 7 | { 8 | public abstract class MetricBase : IMetric 9 | { 10 | protected readonly string _keyName; 11 | protected readonly TValue _initialValue; 12 | protected TValue _value; 13 | 14 | protected MetricBase(string keyName, TValue initialValue) 15 | { 16 | if (keyName == null) 17 | throw new ArgumentNullException(nameof(keyName)); 18 | 19 | _keyName = keyName; 20 | _value = initialValue; 21 | _initialValue = initialValue; 22 | } 23 | 24 | protected abstract IMetric CreateNewMetric(); 25 | 26 | IMetric IMetric.CreateNew() 27 | { 28 | return CreateNewMetric(); 29 | } 30 | 31 | protected abstract void AddResult(IResult result); 32 | 33 | void IMetric.Add(IResult result) 34 | { 35 | AddResult(result); 36 | } 37 | 38 | string[] IMetric.ColumnNames => new[] { _keyName }; 39 | object[] IMetric.Values => new[] { (object)_value }; 40 | } 41 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Aggregators/Metrics/MinDurationMetric.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Viki.LoadRunner.Engine.Analytics.Interfaces; 4 | using Viki.LoadRunner.Engine.Core.Collector.Interfaces; 5 | using Viki.LoadRunner.Engine.Core.Scenario; 6 | using Viki.LoadRunner.Engine.Core.Scenario.Interfaces; 7 | 8 | namespace Viki.LoadRunner.Engine.Aggregators.Metrics 9 | { 10 | public class MinDurationMetric : MultiMetricBase 11 | { 12 | private readonly string[] _ignoredCheckpoints; 13 | 14 | public MinDurationMetric(params string[] ignoredCheckpoints) 15 | : base(() => long.MaxValue) 16 | { 17 | if (ignoredCheckpoints == null) 18 | throw new ArgumentNullException(nameof(ignoredCheckpoints)); 19 | 20 | _ignoredCheckpoints = ignoredCheckpoints.Union(Checkpoint.NotMeassuredCheckpoints).ToArray(); 21 | } 22 | 23 | protected override IMetric CreateNewMetric() 24 | { 25 | return new MinDurationMetric(_ignoredCheckpoints); 26 | } 27 | 28 | protected override void AddResult(IResult result) 29 | { 30 | ICheckpoint[] checkpoints = result.Checkpoints; 31 | for (int i = 0, j = checkpoints.Length - 1; i < j; i++) 32 | { 33 | ICheckpoint checkpoint = checkpoints[i]; 34 | if (checkpoint.Error == null && _ignoredCheckpoints.All(name => name != checkpoint.Name)) 35 | { 36 | string key = "Min: " + checkpoint.Name; 37 | TimeSpan momentDiff = checkpoint.Diff(checkpoints[i + 1]); 38 | 39 | if (_row[key] > momentDiff.TotalMilliseconds) 40 | _row[key] = Convert.ToInt64(momentDiff.TotalMilliseconds); 41 | } 42 | } 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Aggregators/Metrics/MultiMetricBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Viki.LoadRunner.Engine.Aggregators.Interfaces; 4 | using Viki.LoadRunner.Engine.Analytics; 5 | using Viki.LoadRunner.Engine.Analytics.Interfaces; 6 | using Viki.LoadRunner.Engine.Core.Collector.Interfaces; 7 | 8 | namespace Viki.LoadRunner.Engine.Aggregators.Metrics 9 | { 10 | public abstract class MultiMetricBase : IMetric 11 | { 12 | protected readonly FlexiRow _row; 13 | 14 | protected MultiMetricBase(Func cellBuilderFunc) 15 | { 16 | _row = new FlexiRow(cellBuilderFunc); 17 | } 18 | 19 | protected abstract IMetric CreateNewMetric(); 20 | 21 | IMetric IMetric.CreateNew() 22 | { 23 | return CreateNewMetric(); 24 | } 25 | 26 | protected abstract void AddResult(IResult result); 27 | 28 | void IMetric.Add(IResult result) 29 | { 30 | AddResult(result); 31 | } 32 | 33 | string[] IMetric.ColumnNames => _row.Keys.ToArray(); 34 | object[] IMetric.Values => _row.Values.Select(v => (object) v).ToArray(); 35 | } 36 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Aggregators/Metrics/TransactionsPerSecMetric.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Viki.LoadRunner.Engine.Aggregators.Interfaces; 4 | using Viki.LoadRunner.Engine.Analytics.Interfaces; 5 | using Viki.LoadRunner.Engine.Core.Collector.Interfaces; 6 | 7 | namespace Viki.LoadRunner.Engine.Aggregators.Metrics 8 | { 9 | /// 10 | /// Counts successful transactions per second 11 | /// 12 | public class TransactionsPerSecMetric : IMetric 13 | { 14 | private TimeSpan _iterationStartedMin; 15 | private TimeSpan _iterationFinishedMax; 16 | 17 | private uint _count; 18 | 19 | public TransactionsPerSecMetric() 20 | { 21 | _count = 0; 22 | 23 | _iterationStartedMin = TimeSpan.MaxValue; 24 | _iterationFinishedMax = TimeSpan.MinValue; 25 | } 26 | 27 | IMetric IMetric.CreateNew() 28 | { 29 | return new TransactionsPerSecMetric(); 30 | } 31 | 32 | void IMetric.Add(IResult result) 33 | { 34 | if (result.IterationStarted < _iterationStartedMin) 35 | _iterationStartedMin = result.IterationStarted; 36 | 37 | if (result.IterationFinished > _iterationFinishedMax) 38 | _iterationFinishedMax = result.IterationFinished; 39 | 40 | if (result.Checkpoints.All(c => c.Error == null)) 41 | { 42 | _count++; 43 | } 44 | } 45 | 46 | private TimeSpan GetDurationDelta() 47 | { 48 | if (_count == 0) 49 | return TimeSpan.Zero; 50 | 51 | return _iterationFinishedMax - _iterationStartedMin; 52 | } 53 | 54 | string[] IMetric.ColumnNames { get; } = { "TPS" }; 55 | 56 | object[] IMetric.Values 57 | { 58 | get 59 | { 60 | TimeSpan delta = GetDurationDelta(); 61 | 62 | if (delta == TimeSpan.Zero) 63 | return new object[] {0.0}; 64 | else 65 | return new object[] {(double)_count / delta.TotalSeconds }; 66 | } 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Aggregators/StreamAggregatorBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using Viki.LoadRunner.Engine.Core.Collector.Interfaces; 5 | using Viki.LoadRunner.Engine.Core.Collector.Pipeline; 6 | using Viki.LoadRunner.Engine.Core.Collector.Pipeline.Extensions; 7 | 8 | namespace Viki.LoadRunner.Engine.Aggregators 9 | { 10 | /// 11 | /// StreamAggregator provides loadtest raw/masterdata (IResult) IEnumerable stream 12 | /// 13 | public abstract class StreamAggregatorBase : IAggregator, IDisposable 14 | { 15 | #region Fields 16 | 17 | private BatchingPipe _queue; 18 | private Task _writerTask; 19 | 20 | #endregion 21 | 22 | #region Abstract 23 | 24 | protected abstract void Process(IEnumerable stream); 25 | 26 | #endregion 27 | 28 | #region IResultsAggregator 29 | 30 | public void Begin() 31 | { 32 | _queue = new BatchingPipe(); 33 | 34 | _writerTask = _queue.ToEnumerableAsync(Process); 35 | } 36 | 37 | public void Aggregate(IResult result) 38 | { 39 | AssertTask(); 40 | 41 | _queue.Produce(result); 42 | } 43 | 44 | public void End() 45 | { 46 | if (_queue != null) 47 | { 48 | _queue.ProducingCompleted(); 49 | _queue = null; 50 | 51 | _writerTask.Wait(); 52 | 53 | AssertTask(); 54 | } 55 | } 56 | 57 | #endregion 58 | 59 | #region IDisposable 60 | 61 | ~StreamAggregatorBase() 62 | { 63 | Dispose(); 64 | } 65 | 66 | public void Dispose() 67 | { 68 | ((IAggregator)this).End(); 69 | } 70 | 71 | #endregion 72 | 73 | #region Misc 74 | 75 | private void AssertTask() 76 | { 77 | if (_writerTask.Exception != null) 78 | throw _writerTask.Exception; 79 | } 80 | 81 | #endregion 82 | } 83 | 84 | 85 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Aggregators/Utils/OrderLearner.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Viki.LoadRunner.Engine.Aggregators.Utils 4 | { 5 | /// 6 | /// Tool for learning sort order of columns 7 | /// 8 | public class OrderLearner 9 | { 10 | private List _nameOrder; 11 | public IReadOnlyList LearnedOrder => _nameOrder; 12 | 13 | public OrderLearner() 14 | { 15 | Reset(); 16 | } 17 | 18 | public void Reset() 19 | { 20 | 21 | _nameOrder = new List(); 22 | } 23 | 24 | public void Learn(IEnumerable names) 25 | { 26 | 27 | string previousName = ""; 28 | foreach (string name in names) 29 | { 30 | if (!_nameOrder.Contains(name)) 31 | { 32 | if (_nameOrder.Count == 0) 33 | { 34 | _nameOrder.Add(name); 35 | } 36 | else 37 | { 38 | int insertPosition = _nameOrder.FindIndex(s => s == previousName) + 1; 39 | 40 | if (insertPosition == 0 || insertPosition == _nameOrder.Count) 41 | _nameOrder.Add(name); 42 | else 43 | _nameOrder.Insert(insertPosition, name); 44 | } 45 | } 46 | 47 | previousName = name; 48 | } 49 | 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Aggregators/Utils/ReplayResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Linq; 4 | using Viki.LoadRunner.Engine.Core.Collector; 5 | using Viki.LoadRunner.Engine.Core.Scenario; 6 | using Viki.LoadRunner.Engine.Core.Scenario.Interfaces; 7 | 8 | namespace Viki.LoadRunner.Engine.Aggregators.Utils 9 | { 10 | [DebuggerDisplay("T:{ThreadIterationId} G:{GlobalIterationId} L:{ThreadIterationId} TS:{(int)(IterationStarted.TotalMilliseconds)}")] 11 | public class ReplayResult : IterationResult 12 | { 13 | private Checkpoint[] _realCheckpoints; 14 | 15 | // This helps deserializers know what implementation of Checkpoints to use. 16 | public new Checkpoint[] Checkpoints 17 | { 18 | get { return _realCheckpoints; } 19 | set 20 | { 21 | base.Checkpoints = value.Cast().ToArray(); 22 | _realCheckpoints = value; 23 | } 24 | } 25 | 26 | public new TUserData UserData 27 | { 28 | get { return (TUserData)base.UserData; } 29 | set { base.UserData = value; } 30 | } 31 | 32 | /// 33 | /// offsets IterationStarted and IterationFinished values by provided offset 34 | /// 35 | /// 36 | public void Offset(TimeSpan offset) 37 | { 38 | IterationStarted += offset; 39 | IterationFinished += offset; 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Aggregators/Utils/ResultExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Viki.LoadRunner.Engine.Core.Collector.Interfaces; 3 | 4 | namespace Viki.LoadRunner.Engine.Aggregators.Utils 5 | { 6 | public static class ResultExtensions 7 | { 8 | public static void Replay(this IEnumerable results, params IAggregator[] agregators) 9 | { 10 | StreamAggregator.Replay(results, agregators); 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Aggregators/Utils/UnixDateTimeExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Viki.LoadRunner.Engine.Aggregators.Utils 4 | { 5 | public static class UnixDateTimeExtensions 6 | { 7 | public static DateTime UnixTimeStart = new DateTime(1970, 1, 1, 0, 0, 0, 0); 8 | 9 | public static long ToUnixTimeMs(this DateTime currentDateTime) 10 | { 11 | return (long)(currentDateTime - UnixTimeStart).TotalMilliseconds; 12 | } 13 | 14 | public static int ToUnixTime(this DateTime currentDateTime) 15 | { 16 | return (int)(currentDateTime - UnixTimeStart).TotalSeconds; 17 | } 18 | 19 | public static DateTime UnixTimeToDateTime(int unixTime) 20 | { 21 | return UnixTimeStart.AddSeconds(unixTime); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Analytics/DimensionKey.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable 1591 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | namespace Viki.LoadRunner.Engine.Analytics 7 | { 8 | public class DimensionKey 9 | { 10 | private readonly string[] _dimensionValues; 11 | private readonly string _cachedKey; 12 | 13 | public IReadOnlyList Values => _dimensionValues; 14 | 15 | public DimensionKey(string[] dimensionValues) 16 | { 17 | if (dimensionValues == null) 18 | throw new ArgumentNullException(nameof(dimensionValues)); 19 | 20 | _dimensionValues = dimensionValues; 21 | _cachedKey = String.Join(" ", dimensionValues); 22 | } 23 | 24 | 25 | public override bool Equals(object obj) 26 | { 27 | return (obj as DimensionKey)?._cachedKey == _cachedKey; 28 | } 29 | 30 | public override int GetHashCode() 31 | { 32 | return _cachedKey.GetHashCode(); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Analytics/Dimensions/FuncDimension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Viki.LoadRunner.Engine.Analytics.Interfaces; 3 | 4 | namespace Viki.LoadRunner.Engine.Analytics.Dimensions 5 | { 6 | public class FuncDimension : IDimension 7 | { 8 | private readonly Func _dimensionValueSelector; 9 | 10 | /// Name/Key of custom dimension 11 | /// Dimension value selector 12 | public FuncDimension(string dimensionName, Func dimensionValueSelector) 13 | { 14 | if (dimensionName == null) 15 | throw new ArgumentNullException(nameof(dimensionName)); 16 | if (dimensionValueSelector == null) 17 | throw new ArgumentNullException(nameof(dimensionValueSelector)); 18 | 19 | _dimensionValueSelector = dimensionValueSelector; 20 | DimensionName = dimensionName; 21 | } 22 | 23 | public string DimensionName { get; } 24 | 25 | string IDimension.GetKey(T result) 26 | { 27 | return _dimensionValueSelector(result); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Analytics/Dimensions/ThresholdDimension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Viki.LoadRunner.Engine.Analytics.Interfaces; 4 | 5 | namespace Viki.LoadRunner.Engine.Analytics.Dimensions 6 | { 7 | public class ThresholdDimension : IDimension 8 | where TDuration : IComparable 9 | { 10 | private readonly ValueSelector _selector; 11 | 12 | private readonly TDuration[] _thresholds; 13 | private readonly string[] _thresholdStrings; 14 | 15 | public string DefaultOutOfRange = "OutOfRange"; 16 | 17 | public ThresholdDimension(ValueSelector selector, TDuration[] thresholds) 18 | : this(selector, value => value.ToString(), thresholds) 19 | { 20 | } 21 | 22 | public ThresholdDimension(ValueSelector selector, DimensionFormatter formatter, params TDuration[] thresholds) 23 | { 24 | if (formatter == null) throw new ArgumentNullException(nameof(formatter)); 25 | _selector = selector ?? throw new ArgumentNullException(nameof(selector)); 26 | _thresholds = thresholds ?? throw new ArgumentNullException(nameof(thresholds)); 27 | 28 | Array.Sort(_thresholds); 29 | _thresholdStrings = _thresholds.Select(t => formatter(t)).ToArray(); 30 | } 31 | 32 | public string GetKey(TData data) 33 | { 34 | TDuration value = _selector(data); 35 | 36 | for (int i = 0; i < _thresholds.Length; i++) 37 | { 38 | if (value.CompareTo(_thresholds[i]) == -1) 39 | return _thresholdStrings[i]; 40 | } 41 | 42 | return DefaultOutOfRange; 43 | } 44 | 45 | public string DimensionName { get; set; } = "Threshold"; 46 | 47 | public delegate TDuration ValueSelector(TData data); 48 | public delegate string DimensionFormatter(TDuration value); 49 | } 50 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Analytics/Dimensions/TimeDimension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Viki.LoadRunner.Engine.Analytics.Interfaces; 3 | 4 | namespace Viki.LoadRunner.Engine.Analytics.Dimensions 5 | { 6 | /// 7 | /// Split results in provided time intervals 8 | /// 9 | public class TimeDimension : IDimension 10 | { 11 | public readonly TimeSpan Interval; 12 | 13 | private readonly TimeSelectorDelegate _timeSelector; 14 | 15 | public Func Formatter = t => ((long)t.TotalSeconds).ToString(); 16 | 17 | /// interval timespan 18 | /// TimeSpan selector for which dimension key will be calculated 19 | /// Custom name for dimension 20 | public TimeDimension(TimeSpan interval, TimeSelectorDelegate timeSelector, string dimensionName = "Time (s)") 21 | { 22 | Interval = interval; 23 | _timeSelector = timeSelector ?? throw new ArgumentNullException(nameof(timeSelector)); 24 | DimensionName = dimensionName; 25 | } 26 | 27 | public string DimensionName { get; } 28 | 29 | string IDimension.GetKey(T data) 30 | { 31 | TimeSpan resultTimeSlot = Calculate(Interval, _timeSelector(data)); 32 | 33 | return Formatter(resultTimeSlot); 34 | } 35 | 36 | /// 37 | /// Calculates TimeSpan value for dimension key. 38 | /// 39 | public static TimeSpan Calculate(TimeSpan interval, TimeSpan time) 40 | { 41 | return TimeSpan.FromTicks(((int)(time.Ticks / interval.Ticks)) * interval.Ticks); 42 | } 43 | 44 | public delegate TimeSpan TimeSelectorDelegate(TData data); 45 | } 46 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Analytics/DimensionsHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Viki.LoadRunner.Engine.Analytics.Interfaces; 4 | 5 | namespace Viki.LoadRunner.Engine.Analytics 6 | { 7 | public class DimensionsHandler 8 | { 9 | private readonly IDimension[] _dimensions; 10 | 11 | public DimensionsHandler(IEnumerable> dimensions) 12 | { 13 | _dimensions = dimensions.ToArray(); 14 | } 15 | 16 | public DimensionKey GetValue(T result) 17 | { 18 | string[] dimensionValues = _dimensions.Select(d => d.GetKey(result)).ToArray(); 19 | 20 | DimensionKey resultKey = new DimensionKey(dimensionValues); 21 | 22 | return resultKey; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Analytics/Extensions/HistogramExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Viki.LoadRunner.Engine.Analytics.Interfaces; 3 | 4 | namespace Viki.LoadRunner.Engine.Analytics.Extensions 5 | { 6 | public static class HistogramExtensions 7 | { 8 | /// 9 | /// Copies histogram setup from source to target, overwriting all preexisting configuration in target 10 | /// 11 | public static void CopySettings(this IHistogramBuilder source, IHistogramBuilder target) 12 | { 13 | target.Dimensions.Clear(); 14 | target.Dimensions.AddRange(source.Dimensions); 15 | 16 | target.Metrics.Clear(); 17 | target.Metrics.AddRange(source.Metrics); 18 | 19 | target.ColumnAliases.Clear(); 20 | foreach (KeyValuePair item in source.ColumnAliases) 21 | { 22 | target.ColumnAliases.Add(item.Key, item.Value); 23 | } 24 | 25 | target.ColumnIgnoreNames.Clear(); 26 | target.ColumnIgnoreNames.AddRange(source.ColumnIgnoreNames); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Analytics/FlexiRow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | namespace Viki.LoadRunner.Engine.Analytics 6 | { 7 | public class FlexiRow : IEnumerable> 8 | { 9 | #region Fields 10 | 11 | private readonly Func _valueBuilderFunc; 12 | private readonly Dictionary _row = new Dictionary(); 13 | 14 | #endregion 15 | 16 | #region Constructor 17 | 18 | public FlexiRow(Func valueBuilderFunc) 19 | { 20 | if (valueBuilderFunc == null) 21 | throw new ArgumentNullException(nameof(valueBuilderFunc)); 22 | 23 | _valueBuilderFunc = valueBuilderFunc; 24 | } 25 | 26 | #endregion 27 | 28 | #region public functionality 29 | 30 | public void Touch(TKey key) 31 | { 32 | if (!_row.ContainsKey(key)) 33 | _row.Add(key, _valueBuilderFunc()); 34 | } 35 | 36 | public TValue this[TKey key] 37 | { 38 | get 39 | { 40 | TValue value; 41 | if (!_row.TryGetValue(key, out value)) 42 | { 43 | value = _valueBuilderFunc(); 44 | _row.Add(key, value); 45 | } 46 | 47 | return value; 48 | } 49 | set { _row[key] = value; } 50 | } 51 | 52 | public int Count => _row.Count; 53 | 54 | public void Clear() 55 | { 56 | _row.Clear(); 57 | } 58 | 59 | public IEnumerable Keys => _row.Keys; 60 | public IEnumerable Values => _row.Values; 61 | 62 | #endregion 63 | 64 | #region IEnumerable 65 | 66 | IEnumerator> IEnumerable>.GetEnumerator() 67 | { 68 | return _row.GetEnumerator(); 69 | } 70 | 71 | IEnumerator IEnumerable.GetEnumerator() 72 | { 73 | return ((IEnumerable>)this).GetEnumerator(); 74 | } 75 | 76 | #endregion 77 | } 78 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Analytics/Interfaces/IDimension.cs: -------------------------------------------------------------------------------- 1 | namespace Viki.LoadRunner.Engine.Analytics.Interfaces 2 | { 3 | public interface IDimension 4 | { 5 | /// 6 | /// DisplayName/Key of the column 7 | /// 8 | string DimensionName { get; } 9 | 10 | /// 11 | /// Build dimension key by provided row of raw data 12 | /// 13 | /// row of raw data 14 | /// String dimension key 15 | string GetKey(T data); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Analytics/Interfaces/IHistogramBuilder.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Viki.LoadRunner.Engine.Analytics.Interfaces 4 | { 5 | public interface IHistogramBuilder 6 | { 7 | List> Dimensions { get; } 8 | List> Metrics { get; } 9 | 10 | Dictionary ColumnAliases { get; } 11 | List ColumnIgnoreNames { get; } 12 | 13 | Dictionary MetricsPostProcess { get; } 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Analytics/Interfaces/IMetric.cs: -------------------------------------------------------------------------------- 1 | namespace Viki.LoadRunner.Engine.Analytics.Interfaces 2 | { 3 | public interface IMetric 4 | { 5 | /// 6 | /// Create new blank IMetric instance based on current instance settings (e.g. settings passed in the constructor in histogram setup) 7 | /// 8 | /// 9 | IMetric CreateNew(); 10 | 11 | /// 12 | /// Aggregate row of raw data 13 | /// 14 | /// row of raw data 15 | void Add(T data); 16 | 17 | /// 18 | /// Names of columns produced by this metric (order must match [Values] order) 19 | /// 20 | string[] ColumnNames { get; } 21 | /// 22 | /// Values produced by this metric (order must match [ColumnNames] order) 23 | /// 24 | object[] Values { get; } 25 | } 26 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Analytics/Metrics/AverageMetric.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Viki.LoadRunner.Engine.Analytics.Interfaces; 3 | using Viki.LoadRunner.Engine.Analytics.Metrics.Calculators; 4 | 5 | namespace Viki.LoadRunner.Engine.Analytics.Metrics 6 | { 7 | public class AverageMetric : IMetric 8 | { 9 | private readonly string _name; 10 | private readonly DoubleSelectorDelegate _selector; 11 | 12 | private readonly AverageCalculator _calculator = new AverageCalculator(); 13 | 14 | public AverageMetric(DoubleSelectorDelegate selector) 15 | : this("Average", selector) 16 | { 17 | } 18 | 19 | public AverageMetric(string name, DoubleSelectorDelegate selector) 20 | { 21 | _name = name; 22 | _selector = selector; 23 | } 24 | 25 | IMetric IMetric.CreateNew() 26 | { 27 | return new AverageMetric(_name, _selector); 28 | } 29 | 30 | void IMetric.Add(T result) 31 | { 32 | _calculator.Add(_selector(result)); 33 | } 34 | 35 | string[] IMetric.ColumnNames => _calculator.SampleCount > 0 ? new[] { _name } : Array.Empty(); 36 | 37 | object[] IMetric.Values => _calculator.SampleCount > 0 38 | ? new object[] { _calculator.GetAverage() } 39 | : Array.Empty(); 40 | } 41 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Analytics/Metrics/Calculators/AverageCalculator.cs: -------------------------------------------------------------------------------- 1 | namespace Viki.LoadRunner.Engine.Analytics.Metrics.Calculators 2 | { 3 | public class AverageCalculator 4 | { 5 | public int SampleCount { get; private set; } = 0; 6 | public double Sum { get; private set; } = 0; 7 | 8 | public void Add(double duration) 9 | { 10 | SampleCount++; 11 | Sum += duration; 12 | } 13 | 14 | public double GetAverage() 15 | { 16 | return Sum / SampleCount; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Analytics/Metrics/Calculators/RatioCalculator.cs: -------------------------------------------------------------------------------- 1 | namespace Viki.LoadRunner.Engine.Analytics.Metrics.Calculators 2 | { 3 | public class RatioCalculator 4 | { 5 | public void Add(bool increaseRatio) 6 | { 7 | TotalCount++; 8 | 9 | if (increaseRatio) 10 | RatioCount++; 11 | } 12 | 13 | public int TotalCount { get; private set; } = 0; 14 | public int RatioCount { get; private set; } = 0; 15 | 16 | public double Ratio => (double)RatioCount / TotalCount; 17 | } 18 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Analytics/Metrics/CountMetric.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Viki.LoadRunner.Engine.Analytics.Interfaces; 3 | 4 | namespace Viki.LoadRunner.Engine.Analytics.Metrics 5 | { 6 | public class CountMetric : IMetric 7 | { 8 | private readonly BoolSelectorDelegate _canCountSelector; 9 | private int _count = 0; 10 | 11 | public CountMetric(string name = "Count") 12 | : this((i) => true, name) 13 | { 14 | } 15 | 16 | public CountMetric(BoolSelectorDelegate canCountSelector, string name = "Count") 17 | { 18 | _canCountSelector = canCountSelector; 19 | ColumnNames = new[] { name }; 20 | } 21 | 22 | public IMetric CreateNew() 23 | { 24 | return new CountMetric(_canCountSelector, ColumnNames[0]); 25 | } 26 | 27 | public void Add(T data) 28 | { 29 | if (_canCountSelector(data)) 30 | { 31 | _count++; 32 | } 33 | } 34 | 35 | public string[] ColumnNames { get; } 36 | public object[] Values => new object[] { _count }; 37 | } 38 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Analytics/Metrics/Delegates.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Viki.LoadRunner.Engine.Analytics.Metrics 4 | { 5 | public delegate Val ValSelectorDelegate(TData data); 6 | public delegate IEnumerable ValuesSelectorDelegate(TData data); 7 | public delegate object ObjectSelectorDelegate(TData data); 8 | public delegate long LongSelectorDelegate(TData data); 9 | public delegate double DoubleSelectorDelegate(TData data); 10 | public delegate bool BoolSelectorDelegate(TData data); 11 | public delegate bool BoolSelectorDelegate(TBase data, TSub sub); 12 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Analytics/Metrics/DistinctCountMetric.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Viki.LoadRunner.Engine.Analytics.Interfaces; 4 | 5 | namespace Viki.LoadRunner.Engine.Analytics.Metrics 6 | { 7 | public class DistinctCountMetric : IMetric 8 | { 9 | protected readonly string _name; 10 | protected readonly Func _selector; 11 | 12 | private readonly HashSet _elements; 13 | 14 | public DistinctCountMetric(Func selector, string name = "Distinct (Count)") 15 | { 16 | _name = name; 17 | _selector = selector; 18 | 19 | _elements = new HashSet(); 20 | } 21 | 22 | public IMetric CreateNew() 23 | { 24 | return new DistinctCountMetric(_selector, _name); 25 | } 26 | 27 | public void Add(TIn result) 28 | { 29 | TVal item = _selector(result); 30 | 31 | if (item != null && _elements.Contains(item) == false) 32 | { 33 | _elements.Add(item); 34 | } 35 | } 36 | 37 | public string[] ColumnNames => new[] { _name }; 38 | public object[] Values => new object[] { _elements.Count }; 39 | } 40 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Analytics/Metrics/DistinctListCountMetric.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Viki.LoadRunner.Engine.Analytics.Interfaces; 5 | 6 | namespace Viki.LoadRunner.Engine.Analytics.Metrics 7 | { 8 | public class DistinctListCountMetric : IMetric 9 | { 10 | protected readonly string _name; 11 | protected readonly Func _selector; 12 | 13 | private readonly FlexiRow _elements; 14 | 15 | public DistinctListCountMetric(string name, Func selector) 16 | { 17 | _name = name; 18 | _selector = selector; 19 | 20 | _elements = new FlexiRow(() => 0); 21 | } 22 | 23 | public IMetric CreateNew() 24 | { 25 | return new DistinctListCountMetric(_name, _selector) 26 | { 27 | Sort = Sort 28 | }; 29 | } 30 | 31 | public void Add(TIn result) 32 | { 33 | TOut item = _selector(result); 34 | 35 | if (item != null) 36 | { 37 | _elements[item]++; 38 | } 39 | } 40 | 41 | public Func>, IEnumerable>> Sort { get; set; } = input => input.OrderBy(e => e.Key); 42 | 43 | public string[] ColumnNames => new[] { _name }; 44 | public object[] Values => new object[] { String.Join(", ", Sort(_elements).Select(e => String.Concat(e.Key, "[", e.Value, "]"))) }; 45 | } 46 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Analytics/Metrics/DistinctListMetric.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Viki.LoadRunner.Engine.Analytics.Interfaces; 5 | 6 | namespace Viki.LoadRunner.Engine.Analytics.Metrics 7 | { 8 | public class DistinctListMetric : IMetric 9 | { 10 | private readonly string _name; 11 | private readonly Func _selector; 12 | private readonly string _separator; 13 | 14 | private readonly HashSet _elements; 15 | 16 | public DistinctListMetric(string name, Func selector, string separator = ", ") 17 | { 18 | _name = name; 19 | _selector = selector; 20 | _separator = separator; 21 | 22 | _elements = new HashSet(); 23 | } 24 | 25 | public IMetric CreateNew() 26 | { 27 | return new DistinctListMetric(_name, _selector, _separator) 28 | { 29 | Sort = Sort, 30 | Limit = Limit 31 | }; 32 | } 33 | 34 | public void Add(TIn result) 35 | { 36 | TOut item = _selector(result); 37 | 38 | if (item != null && _elements.Contains(item) == false) 39 | { 40 | _elements.Add(item); 41 | } 42 | } 43 | 44 | public Func, IEnumerable> Sort { get; set; } = input => input.OrderBy(e => e); 45 | 46 | public int Limit { get; set; } = Int32.MaxValue; 47 | 48 | public string[] ColumnNames => new[] { _name }; 49 | public object[] Values => new object[] { String.Join(_separator, Sort(_elements).Take(Limit)) + (_elements.Count > Limit ? $"... +{_elements.Count - Limit} more items." : "") }; 50 | } 51 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Analytics/Metrics/FilterMetric.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Viki.LoadRunner.Engine.Analytics.Interfaces; 3 | using Viki.LoadRunner.Engine.Analytics.Viki.LoadRunner.Engine.Aggregators.Utils; 4 | 5 | namespace Viki.LoadRunner.Engine.Analytics.Metrics 6 | { 7 | /// 8 | /// Select only subset of rows which will be used to aggregate with provided metrics. 9 | /// 10 | public class FilterMetric : IMetric 11 | { 12 | private readonly BoolSelectorDelegate _selector; 13 | private readonly MetricsTemplate _template; 14 | private readonly IMetric _metric; 15 | 16 | /// 17 | /// Select only subset of rows which will be used to aggregate with provided metrics. 18 | /// 19 | /// Return true for the data which should be aggregated 20 | /// Metrics to be used for filtered aggregation 21 | public FilterMetric(BoolSelectorDelegate selector, params IMetric[] metrics) 22 | : this(selector, new MetricsTemplate(metrics)) 23 | { 24 | } 25 | 26 | private FilterMetric(BoolSelectorDelegate selector, MetricsTemplate template) 27 | { 28 | _selector = selector ?? throw new ArgumentNullException(nameof(selector)); 29 | _template = template ?? throw new ArgumentNullException(nameof(template)); 30 | 31 | _metric = _template.Create(); 32 | } 33 | 34 | IMetric IMetric.CreateNew() 35 | { 36 | return new FilterMetric(_selector, _template); 37 | } 38 | 39 | void IMetric.Add(T data) 40 | { 41 | if (_selector(data)) 42 | _metric.Add(data); 43 | } 44 | 45 | string[] IMetric.ColumnNames => _metric.ColumnNames; 46 | object[] IMetric.Values => _metric.Values; 47 | } 48 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Analytics/Metrics/FuncMetric.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Viki.LoadRunner.Engine.Analytics.Interfaces; 3 | 4 | namespace Viki.LoadRunner.Engine.Analytics.Metrics 5 | { 6 | public class FuncMetric : IMetric 7 | { 8 | private readonly Func _metricFunc; 9 | private readonly TValue _initialValue; 10 | 11 | private TValue _value; 12 | 13 | private readonly OutputFormatter _outputFormatter; 14 | 15 | public FuncMetric(string keyName, TValue initialValue, Func metricFunc) 16 | : this(keyName, initialValue, metricFunc, DefaultOutputFormatter) 17 | { 18 | } 19 | 20 | public FuncMetric(string keyName, TValue initialValue, Func metricFunc, OutputFormatter formatter) 21 | { 22 | ColumnNames = new[] { keyName }; 23 | _value = initialValue; 24 | _initialValue = initialValue; 25 | _metricFunc = metricFunc; 26 | _outputFormatter = formatter; 27 | } 28 | 29 | public IMetric CreateNew() 30 | { 31 | return new FuncMetric(ColumnNames[0], _initialValue, _metricFunc, _outputFormatter); 32 | } 33 | 34 | public void Add(T data) 35 | { 36 | _value = _metricFunc(data, _value); 37 | } 38 | 39 | public string[] ColumnNames { get; } 40 | public object[] Values => new [] { _outputFormatter(_value) }; 41 | 42 | private static object DefaultOutputFormatter(TValue value) => value; 43 | 44 | public delegate object OutputFormatter(TValue value); 45 | } 46 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Analytics/Metrics/MaxMetric.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Viki.LoadRunner.Engine.Analytics.Interfaces; 3 | 4 | namespace Viki.LoadRunner.Engine.Analytics.Metrics 5 | { 6 | public class MaxMetric : IMetric 7 | { 8 | private readonly LongSelectorDelegate _selector; 9 | private readonly long _initialValue; 10 | private long _max; 11 | 12 | public MaxMetric(LongSelectorDelegate selector, string name = "Max", long initialValue = long.MinValue) 13 | { 14 | _selector = selector ?? throw new ArgumentNullException(nameof(selector)); 15 | 16 | _initialValue = initialValue; 17 | _max = initialValue; 18 | 19 | ColumnNames = new[] { name }; 20 | } 21 | 22 | 23 | public IMetric CreateNew() 24 | { 25 | return new MaxMetric(_selector, ColumnNames[0], _initialValue); 26 | } 27 | 28 | public void Add(T data) 29 | { 30 | long current = _selector(data); 31 | if (current > _max) 32 | _max = current; 33 | } 34 | 35 | public string[] ColumnNames { get; } 36 | public object[] Values => new object[] { _max }; 37 | } 38 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Analytics/Metrics/MinMetric.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Viki.LoadRunner.Engine.Analytics.Interfaces; 3 | 4 | namespace Viki.LoadRunner.Engine.Analytics.Metrics 5 | { 6 | public class MinMetric : IMetric 7 | { 8 | private readonly LongSelectorDelegate _selector; 9 | private readonly long _initialValue; 10 | private long _min; 11 | 12 | public MinMetric(LongSelectorDelegate selector, string name = "Min", long initialValue = long.MaxValue) 13 | { 14 | _selector = selector ?? throw new ArgumentNullException(nameof(selector)); 15 | 16 | _initialValue = initialValue; 17 | _min = initialValue; 18 | 19 | ColumnNames = new[] { name }; 20 | } 21 | 22 | 23 | public IMetric CreateNew() 24 | { 25 | return new MinMetric(_selector, ColumnNames[0], _initialValue); 26 | } 27 | 28 | public void Add(T data) 29 | { 30 | long current = _selector(data); 31 | if (current < _min) 32 | _min = current; 33 | } 34 | 35 | public string[] ColumnNames { get; } 36 | public object[] Values => new object[] { _min }; 37 | } 38 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Analytics/Metrics/RatioMetric.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Viki.LoadRunner.Engine.Analytics.Interfaces; 3 | using Viki.LoadRunner.Engine.Analytics.Metrics.Calculators; 4 | 5 | namespace Viki.LoadRunner.Engine.Analytics.Metrics 6 | { 7 | /// 8 | /// Calculate ratio between of included_count[selector returns true] / total_count 9 | /// 10 | public class RatioMetric : IMetric 11 | { 12 | private readonly string _name; 13 | private readonly BoolSelectorDelegate _selector; 14 | private readonly double _multiplier; 15 | 16 | private readonly RatioCalculator _calculator = new RatioCalculator(); 17 | 18 | public RatioMetric(BoolSelectorDelegate include, double multiplier = 1.0) 19 | : this("Ratio", include, multiplier) 20 | { 21 | } 22 | 23 | public RatioMetric(string name, BoolSelectorDelegate include, double multiplier = 1.0) 24 | { 25 | _name = name ?? throw new ArgumentNullException(nameof(name)); 26 | _selector = include ?? throw new ArgumentNullException(nameof(include)); 27 | _multiplier = multiplier; 28 | } 29 | 30 | IMetric IMetric.CreateNew() 31 | { 32 | return new RatioMetric(_name, _selector, _multiplier); 33 | } 34 | 35 | void IMetric.Add(T data) 36 | { 37 | _calculator.Add(_selector(data)); 38 | } 39 | 40 | string[] IMetric.ColumnNames => _calculator.TotalCount > 0 ? new[] { _name } : Array.Empty(); 41 | 42 | object[] IMetric.Values => _calculator.TotalCount > 0 43 | ? new object[] {_calculator.Ratio * _multiplier} 44 | : Array.Empty(); 45 | } 46 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Analytics/Metrics/SubMetric.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Viki.LoadRunner.Engine.Analytics.Interfaces; 5 | 6 | namespace Viki.LoadRunner.Engine.Analytics.Metrics 7 | { 8 | public class SubMetric : IMetric 9 | { 10 | private readonly SubSelectorDelegate _selector; 11 | private readonly BoolSelectorDelegate _filter; 12 | private readonly MetricsTemplate _template; 13 | private readonly IMetric _metric; 14 | 15 | 16 | 17 | public SubMetric(SubSelectorDelegate selector, params IMetric[] metrics) 18 | : this(selector, (data, sub) => true, new MetricsTemplate(metrics)) 19 | { 20 | } 21 | 22 | public SubMetric(SubSelectorDelegate selector, BoolSelectorDelegate filter, params IMetric[] metrics) 23 | : this(selector, filter, new MetricsTemplate(metrics)) 24 | { 25 | } 26 | 27 | public SubMetric(SubSelectorDelegate selector, BoolSelectorDelegate filter, MetricsTemplate template) 28 | { 29 | _selector = selector ?? throw new ArgumentNullException(nameof(selector)); 30 | _filter = filter ?? throw new ArgumentNullException(nameof(filter)); 31 | _template = template ?? throw new ArgumentNullException(nameof(template)); 32 | 33 | _metric = _template.Create(); 34 | } 35 | 36 | public IMetric CreateNew() 37 | { 38 | return new SubMetric(_selector, _filter, _template); 39 | } 40 | 41 | public void Add(TBase data) 42 | { 43 | IEnumerable subInput = _selector(data).Where(s => _filter(data, s)); 44 | foreach (TSub subItem in subInput) 45 | { 46 | _metric.Add(subItem); 47 | } 48 | } 49 | 50 | public string[] ColumnNames => _metric.ColumnNames; 51 | public object[] Values => _metric.Values; 52 | } 53 | 54 | public delegate IEnumerable SubSelectorDelegate(TBase input); 55 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Analytics/MetricsTemplate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Viki.LoadRunner.Engine.Analytics.Interfaces; 5 | 6 | namespace Viki.LoadRunner.Engine.Analytics 7 | { 8 | public class MetricsTemplate 9 | { 10 | private readonly IMetric[] _templates; 11 | 12 | public MetricsTemplate(IEnumerable> templates) 13 | { 14 | if (templates == null) 15 | throw new ArgumentNullException(nameof(templates)); 16 | 17 | _templates = templates.ToArray(); 18 | } 19 | 20 | public IMetric Create() 21 | { 22 | return new MetricsMuxer(_templates); 23 | } 24 | 25 | private class MetricsMuxer : IMetric 26 | { 27 | private readonly IMetric[] _metrics; 28 | private readonly int _length; 29 | 30 | public MetricsMuxer(IMetric[] templates) 31 | { 32 | _metrics = templates 33 | .Select(t => t.CreateNew()) 34 | .ToArray(); 35 | 36 | _length = _metrics.Length; 37 | } 38 | 39 | public IMetric CreateNew() 40 | { 41 | return new MetricsMuxer(_metrics); 42 | } 43 | 44 | public void Add(T data) 45 | { 46 | for (int i = 0; i < _length; i++) 47 | { 48 | _metrics[i].Add(data); 49 | } 50 | } 51 | 52 | string[] IMetric.ColumnNames => _metrics.SelectMany(m => m.ColumnNames).ToArray(); 53 | 54 | object[] IMetric.Values => _metrics.SelectMany(m => m.Values).ToArray(); 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Collector/AggregatorException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Viki.LoadRunner.Engine.Core.Collector.Interfaces; 3 | 4 | namespace Viki.LoadRunner.Engine.Core.Collector 5 | { 6 | public class AggregatorException : Exception 7 | { 8 | public IAggregator Aggregator { get; } 9 | 10 | public IResult Data { get; } 11 | 12 | public AggregatorException(string message, IAggregator sender, IResult data, Exception innerException) : base(message, innerException) 13 | { 14 | Aggregator = sender ?? throw new ArgumentNullException(nameof(sender)); 15 | Data = data; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Collector/Interfaces/IAggregator.cs: -------------------------------------------------------------------------------- 1 | namespace Viki.LoadRunner.Engine.Core.Collector.Interfaces 2 | { 3 | /// 4 | /// This interface defines handling of raw results received from ongoing test. 5 | /// 6 | /// Aggregators are expected to contain their errors. 7 | /// Thrown exceptions will break test execution. 8 | public interface IAggregator 9 | { 10 | /// 11 | /// Signals aggregator, that new test execution is about to begin 12 | /// Aggregator can reset stats here if needed. 13 | /// 14 | void Begin(); 15 | 16 | /// 17 | /// Results from all running threads will be poured into this one. 18 | /// 19 | void Aggregate(IResult result); 20 | 21 | /// 22 | /// Signals aggregator, that test execion has ended and all meassurements have been delivered. 23 | /// 24 | void End(); 25 | } 26 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Collector/Interfaces/IDataCollector.cs: -------------------------------------------------------------------------------- 1 | namespace Viki.LoadRunner.Engine.Core.Collector.Interfaces 2 | { 3 | public interface IDataCollector 4 | { 5 | void Collect(); 6 | 7 | void Complete(); 8 | } 9 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Collector/IterationResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Viki.LoadRunner.Engine.Core.Collector.Interfaces; 3 | using Viki.LoadRunner.Engine.Core.Pool.Interfaces; 4 | using Viki.LoadRunner.Engine.Core.Scenario.Interfaces; 5 | 6 | namespace Viki.LoadRunner.Engine.Core.Collector 7 | { 8 | public class IterationResult : IResult 9 | { 10 | public IterationResult() 11 | { 12 | } 13 | 14 | public ICheckpoint[] Checkpoints { get; set; } 15 | 16 | public int GlobalIterationId { get; set; } 17 | public int ThreadIterationId { get; set; } 18 | public int ThreadId { get; set; } 19 | public object UserData { get; set; } 20 | 21 | public TimeSpan IterationStarted { get; set; } 22 | public TimeSpan IterationFinished { get; set; } 23 | 24 | public int CreatedThreads { get; set; } 25 | public int IdleThreads { get; set; } 26 | 27 | public IterationResult(IIterationResult iteration, IThreadPoolStats threadPoolContext) 28 | { 29 | ThreadId = iteration.ThreadId; 30 | GlobalIterationId = iteration.GlobalIterationId; 31 | ThreadIterationId = iteration.ThreadIterationId; 32 | UserData = iteration.UserData; 33 | 34 | 35 | IterationStarted = iteration.IterationStarted; 36 | IterationFinished = iteration.IterationFinished; 37 | 38 | CreatedThreads = threadPoolContext.InitializedThreadCount; 39 | IdleThreads = threadPoolContext.IdleThreadCount; 40 | 41 | Checkpoints = iteration.CopyCheckpoints(); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Collector/NullDataCollector.cs: -------------------------------------------------------------------------------- 1 | using Viki.LoadRunner.Engine.Core.Collector.Interfaces; 2 | 3 | namespace Viki.LoadRunner.Engine.Core.Collector 4 | { 5 | public class NullDataCollector : IDataCollector 6 | { 7 | public void Collect() 8 | { 9 | } 10 | 11 | public void Complete() 12 | { 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Collector/PipeDataCollector.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Viki.LoadRunner.Engine.Core.Collector.Interfaces; 3 | using Viki.LoadRunner.Engine.Core.Collector.Pipeline.Interfaces; 4 | using Viki.LoadRunner.Engine.Core.Counter.Interfaces; 5 | using Viki.LoadRunner.Engine.Core.Pool.Interfaces; 6 | using Viki.LoadRunner.Engine.Core.Scenario.Interfaces; 7 | 8 | namespace Viki.LoadRunner.Engine.Core.Collector 9 | { 10 | public class PipeDataCollector : IDataCollector 11 | { 12 | private readonly IProducer _producer; 13 | 14 | private readonly IIterationResult _context; 15 | private readonly IThreadPoolStats _poolStats; 16 | 17 | public PipeDataCollector(IProducer producer, IIterationResult context, IThreadPoolStats poolStats) 18 | { 19 | _producer = producer ?? throw new ArgumentNullException(nameof(producer)); 20 | _context = context ?? throw new ArgumentNullException(nameof(context)); 21 | _poolStats = poolStats ?? throw new ArgumentNullException(nameof(poolStats)); 22 | } 23 | 24 | public void Collect() 25 | { 26 | _producer.Produce(new IterationResult(_context, _poolStats)); 27 | } 28 | 29 | public void Complete() 30 | { 31 | _producer.ProducingCompleted(); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Collector/Pipeline/Extensions/ProducerExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Viki.LoadRunner.Engine.Core.Collector.Pipeline.Interfaces; 5 | 6 | namespace Viki.LoadRunner.Engine.Core.Collector.Pipeline.Extensions 7 | { 8 | public static class ProducerExtensions 9 | { 10 | public static Task ProduceCompleteAsync(this IProducer producer, IEnumerable items, bool start = true) 11 | { 12 | return ProduceCompleteAsync(producer, items, CancellationToken.None, start); 13 | } 14 | 15 | public static Task ProduceCompleteAsync(this IProducer producer, IEnumerable items, CancellationToken token, bool start = true) 16 | { 17 | Task task = new Task(() => ProduceAndComplete(producer, items, token), token, TaskCreationOptions.LongRunning); 18 | 19 | if (start) 20 | task.Start(); 21 | 22 | return task; 23 | } 24 | 25 | private static void ProduceAndComplete(IProducer producer, IEnumerable items, CancellationToken token) 26 | { 27 | try 28 | { 29 | foreach (T item in items) 30 | { 31 | if (token.IsCancellationRequested) 32 | break; 33 | 34 | producer.Produce(item); 35 | } 36 | } 37 | finally 38 | { 39 | producer.ProducingCompleted(); 40 | } 41 | } 42 | 43 | public static void Produce(this IProducer producer, IEnumerable items) 44 | { 45 | foreach (T item in items) 46 | { 47 | producer.Produce(item); 48 | } 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Collector/Pipeline/Interfaces/IConsumer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Viki.LoadRunner.Engine.Core.Collector.Pipeline.Interfaces 4 | { 5 | public interface IConsumer 6 | { 7 | bool TryLockBatch(out IReadOnlyList batch); 8 | 9 | void ReleaseBatch(); 10 | 11 | bool Available { get; } 12 | } 13 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Collector/Pipeline/Interfaces/IPipeFactory.cs: -------------------------------------------------------------------------------- 1 | namespace Viki.LoadRunner.Engine.Core.Collector.Pipeline.Interfaces 2 | { 3 | public interface IPipeFactory : IPipeProvider 4 | { 5 | BatchingPipe Create(); 6 | } 7 | 8 | public interface IPipeProvider 9 | { 10 | event PipeCreatedEventDelegate PipeCreatedEvent; 11 | } 12 | 13 | public delegate void PipeCreatedEventDelegate(IPipeFactory sender, BatchingPipe pipe); 14 | 15 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Collector/Pipeline/Interfaces/IProducer.cs: -------------------------------------------------------------------------------- 1 | namespace Viki.LoadRunner.Engine.Core.Collector.Pipeline.Interfaces 2 | { 3 | public interface IProducer 4 | { 5 | void Produce(T item); 6 | 7 | void ProducingCompleted(); 8 | } 9 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Collector/Pipeline/PipeFactory.cs: -------------------------------------------------------------------------------- 1 | using Viki.LoadRunner.Engine.Core.Collector.Pipeline.Interfaces; 2 | 3 | namespace Viki.LoadRunner.Engine.Core.Collector.Pipeline 4 | { 5 | public class PipeFactory : IPipeFactory 6 | { 7 | public BatchingPipe Create() 8 | { 9 | BatchingPipe pipe = new BatchingPipe(); 10 | 11 | PipeCreatedEvent?.Invoke(this, pipe); 12 | 13 | return pipe; 14 | } 15 | 16 | public event PipeCreatedEventDelegate PipeCreatedEvent; 17 | } 18 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Collector/Pipeline/PipeMultiplexer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using Viki.LoadRunner.Engine.Core.Collector.Pipeline.Interfaces; 6 | 7 | namespace Viki.LoadRunner.Engine.Core.Collector.Pipeline 8 | { 9 | public class PipeMultiplexer : IProducer, IEnumerable> 10 | { 11 | private readonly Func _partitionSelector; 12 | private readonly BatchingPipe[] _pipes; 13 | 14 | public int Count => _pipes.Length; 15 | 16 | public PipeMultiplexer(int consumerCount, Func partitionSelector) 17 | { 18 | if (consumerCount <= 0) 19 | throw new ArgumentOutOfRangeException(nameof(consumerCount)); 20 | _partitionSelector = partitionSelector ?? throw new ArgumentNullException(nameof(partitionSelector)); 21 | 22 | _pipes = new BatchingPipe[consumerCount]; 23 | for (int i = 0; i < consumerCount; i++) 24 | { 25 | _pipes[i] = new BatchingPipe(); 26 | } 27 | } 28 | 29 | public void Produce(T item) 30 | { 31 | int partition = _partitionSelector(item) % _pipes.Length; 32 | 33 | _pipes[partition].Produce(item); 34 | } 35 | 36 | public void ProducingCompleted() 37 | { 38 | for (int i = 0; i < _pipes.Length; i++) 39 | { 40 | _pipes[i].ProducingCompleted(); 41 | } 42 | } 43 | 44 | public IConsumer this[int index] => _pipes[index]; 45 | public IEnumerator> GetEnumerator() 46 | { 47 | return _pipes.Cast>().GetEnumerator(); 48 | } 49 | 50 | IEnumerator IEnumerable.GetEnumerator() 51 | { 52 | return _pipes.GetEnumerator(); 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Collector/Pipeline/PipeMultiplier.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using Viki.LoadRunner.Engine.Core.Collector.Pipeline.Interfaces; 6 | 7 | namespace Viki.LoadRunner.Engine.Core.Collector.Pipeline 8 | { 9 | public class PipeMultiplier : IProducer, IEnumerable> 10 | { 11 | private readonly BatchingPipe[] _pipes; 12 | 13 | public int Count => _pipes.Length; 14 | 15 | public PipeMultiplier(int consumerCount) 16 | { 17 | if (consumerCount <= 0) 18 | throw new ArgumentOutOfRangeException(nameof(consumerCount)); 19 | 20 | _pipes = new BatchingPipe[consumerCount]; 21 | for (int i = 0; i < consumerCount; i++) 22 | { 23 | _pipes[i] = new BatchingPipe(); 24 | } 25 | } 26 | 27 | public void Produce(T item) 28 | { 29 | for (int i = 0; i < _pipes.Length; i++) 30 | { 31 | _pipes[i].Produce(item); 32 | } 33 | } 34 | 35 | public void ProducingCompleted() 36 | { 37 | for (int i = 0; i < _pipes.Length; i++) 38 | { 39 | _pipes[i].ProducingCompleted(); 40 | } 41 | } 42 | 43 | public IConsumer this[int index] => _pipes[index]; 44 | 45 | public IEnumerator> GetEnumerator() 46 | { 47 | return _pipes.Cast>().GetEnumerator(); 48 | } 49 | 50 | IEnumerator IEnumerable.GetEnumerator() 51 | { 52 | return _pipes.GetEnumerator(); 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Counter/Interfaces/ICounter.cs: -------------------------------------------------------------------------------- 1 | namespace Viki.LoadRunner.Engine.Core.Counter.Interfaces 2 | { 3 | public interface ICounter 4 | { 5 | int Value { get; } 6 | 7 | int Add(int count); 8 | } 9 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Counter/Interfaces/IThreadPoolCounter.cs: -------------------------------------------------------------------------------- 1 | using Viki.LoadRunner.Engine.Core.Pool.Interfaces; 2 | 3 | namespace Viki.LoadRunner.Engine.Core.Counter.Interfaces 4 | { 5 | public interface IThreadPoolCounter : IThreadPoolStats 6 | { 7 | void AddIdle(int count); 8 | 9 | void AddInitialized(int count); 10 | 11 | void AddCreated(int count); 12 | } 13 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Counter/ThreadPoolCounter.cs: -------------------------------------------------------------------------------- 1 | using Viki.LoadRunner.Engine.Core.Counter.Interfaces; 2 | 3 | namespace Viki.LoadRunner.Engine.Core.Counter 4 | { 5 | public class ThreadPoolCounter : IThreadPoolCounter 6 | { 7 | public int CreatedThreadCount => _created.Value; 8 | public int InitializedThreadCount => _initialized.Value; 9 | public int IdleThreadCount => _idle.Value; 10 | 11 | private readonly ICounter _created, _initialized, _idle; 12 | 13 | public ThreadPoolCounter() 14 | { 15 | _created = new ThreadSafeCounter(); 16 | _initialized = new ThreadSafeCounter(); 17 | _idle = new ThreadSafeCounter(); 18 | } 19 | 20 | public void AddIdle(int count) 21 | { 22 | _idle.Add(count); 23 | } 24 | 25 | public void AddInitialized(int count) 26 | { 27 | _initialized.Add(count); 28 | } 29 | 30 | public void AddCreated(int count) 31 | { 32 | 33 | _created.Add(count); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Counter/ThreadSafeCounter.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using Viki.LoadRunner.Engine.Core.Counter.Interfaces; 3 | 4 | namespace Viki.LoadRunner.Engine.Core.Counter 5 | { 6 | public class ThreadSafeCounter : ICounter 7 | { 8 | public int Value => _value; 9 | 10 | private int _value = 0; 11 | 12 | public int Add(int count) 13 | { 14 | return Interlocked.Add(ref _value, count); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Factory/Interfaces/FuncScenarioFactory.cs: -------------------------------------------------------------------------------- 1 | using Viki.LoadRunner.Engine.Core.Scenario.Interfaces; 2 | 3 | namespace Viki.LoadRunner.Engine.Core.Factory.Interfaces 4 | { 5 | public delegate IScenario CreateScenarioDelegate(int threadId); 6 | 7 | public class FuncScenarioFactory : IScenarioFactory 8 | { 9 | private readonly CreateScenarioDelegate _factoryDelegate; 10 | 11 | public FuncScenarioFactory(CreateScenarioDelegate factoryDelegate) 12 | { 13 | _factoryDelegate = factoryDelegate; 14 | } 15 | 16 | public IScenario Create(int threadId) 17 | { 18 | return _factoryDelegate(threadId); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Factory/Interfaces/IDataCollectorFactory.cs: -------------------------------------------------------------------------------- 1 | using Viki.LoadRunner.Engine.Core.Collector.Interfaces; 2 | using Viki.LoadRunner.Engine.Core.Scenario.Interfaces; 3 | 4 | namespace Viki.LoadRunner.Engine.Core.Factory.Interfaces 5 | { 6 | public interface IDataCollectorFactory 7 | { 8 | IDataCollector Create(IIterationResult iterationContext); 9 | } 10 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Factory/Interfaces/IFactory.cs: -------------------------------------------------------------------------------- 1 | namespace Viki.LoadRunner.Engine.Core.Factory.Interfaces 2 | { 3 | public interface IFactory 4 | { 5 | T Create(int threadId); 6 | } 7 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Factory/Interfaces/IIterationContextFactory.cs: -------------------------------------------------------------------------------- 1 | using Viki.LoadRunner.Engine.Core.Scenario.Interfaces; 2 | 3 | namespace Viki.LoadRunner.Engine.Core.Factory.Interfaces 4 | { 5 | public interface IIterationContextFactory 6 | { 7 | IIterationControl Create(); 8 | } 9 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Factory/Interfaces/IScenarioFactory.cs: -------------------------------------------------------------------------------- 1 |  2 | using Viki.LoadRunner.Engine.Core.Scenario.Interfaces; 3 | 4 | namespace Viki.LoadRunner.Engine.Core.Factory.Interfaces 5 | { 6 | public interface IScenarioFactory : IFactory 7 | { 8 | } 9 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Factory/Interfaces/IScenarioHandlerFactory.cs: -------------------------------------------------------------------------------- 1 | using Viki.LoadRunner.Engine.Core.Scenario.Interfaces; 2 | 3 | namespace Viki.LoadRunner.Engine.Core.Factory.Interfaces 4 | { 5 | public interface IScenarioHandlerFactory 6 | { 7 | IScenarioHandler Create(IIterationControl iterationContext); 8 | } 9 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Factory/Interfaces/IScenarioThreadFactory.cs: -------------------------------------------------------------------------------- 1 | using Viki.LoadRunner.Engine.Core.Collector.Interfaces; 2 | using Viki.LoadRunner.Engine.Core.Scenario.Interfaces; 3 | using Viki.LoadRunner.Engine.Core.Scheduler.Interfaces; 4 | using Viki.LoadRunner.Engine.Core.Worker.Interfaces; 5 | 6 | namespace Viki.LoadRunner.Engine.Core.Factory.Interfaces 7 | { 8 | public interface IScenarioThreadFactory 9 | { 10 | IThread Create(IScheduler scheduler, IScenarioHandler handler, IDataCollector collector); 11 | } 12 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Factory/Interfaces/ISchedulerFactory.cs: -------------------------------------------------------------------------------- 1 | using Viki.LoadRunner.Engine.Core.Scenario.Interfaces; 2 | using Viki.LoadRunner.Engine.Core.Scheduler.Interfaces; 3 | 4 | namespace Viki.LoadRunner.Engine.Core.Factory.Interfaces 5 | { 6 | public interface ISchedulerFactory 7 | { 8 | IScheduler Create(IIterationId iterationContext); 9 | } 10 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Factory/IterationContextFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Viki.LoadRunner.Engine.Core.Factory.Interfaces; 3 | using Viki.LoadRunner.Engine.Core.Generator; 4 | using Viki.LoadRunner.Engine.Core.Generator.Interfaces; 5 | using Viki.LoadRunner.Engine.Core.Scenario; 6 | using Viki.LoadRunner.Engine.Core.Scenario.Interfaces; 7 | using Viki.LoadRunner.Engine.Core.Timer.Interfaces; 8 | 9 | namespace Viki.LoadRunner.Engine.Core.Factory 10 | { 11 | public class IterationContextFactory : IIterationContextFactory 12 | { 13 | private readonly ITimer _timer; 14 | private readonly object _initialUserData; 15 | 16 | private readonly IUniqueIdGenerator _threadIdGenerator; 17 | 18 | public IterationContextFactory(ITimer timer, object initialUserData) 19 | { 20 | if (timer == null) 21 | throw new ArgumentNullException(nameof(timer)); 22 | 23 | _timer = timer; 24 | _initialUserData = initialUserData; 25 | 26 | _threadIdGenerator = new ThreadSafeIdGenerator(); 27 | } 28 | 29 | public IIterationControl Create() 30 | { 31 | int newThreadId = _threadIdGenerator.Next(); 32 | 33 | return new IterationContext(newThreadId, _timer, _initialUserData); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Factory/NullDataCollectorFactory.cs: -------------------------------------------------------------------------------- 1 | using Viki.LoadRunner.Engine.Core.Collector; 2 | using Viki.LoadRunner.Engine.Core.Collector.Interfaces; 3 | using Viki.LoadRunner.Engine.Core.Factory.Interfaces; 4 | using Viki.LoadRunner.Engine.Core.Scenario.Interfaces; 5 | 6 | namespace Viki.LoadRunner.Engine.Core.Factory 7 | { 8 | public class NullDataCollectorFactory : IDataCollectorFactory 9 | { 10 | public IDataCollector Create(IIterationResult iterationContext) 11 | { 12 | return new NullDataCollector(); 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Factory/NullSchedulerFactory.cs: -------------------------------------------------------------------------------- 1 | using Viki.LoadRunner.Engine.Core.Factory.Interfaces; 2 | using Viki.LoadRunner.Engine.Core.Scenario.Interfaces; 3 | using Viki.LoadRunner.Engine.Core.Scheduler; 4 | using Viki.LoadRunner.Engine.Core.Scheduler.Interfaces; 5 | 6 | namespace Viki.LoadRunner.Engine.Core.Factory 7 | { 8 | public class NullSchedulerFactory : ISchedulerFactory 9 | { 10 | private readonly NullScheduler _scheduler = new NullScheduler(); 11 | 12 | public IScheduler Create(IIterationId iterationContext) 13 | { 14 | return _scheduler; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Factory/PipeDataCollectorFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Viki.LoadRunner.Engine.Core.Collector; 3 | using Viki.LoadRunner.Engine.Core.Collector.Interfaces; 4 | using Viki.LoadRunner.Engine.Core.Collector.Pipeline.Interfaces; 5 | using Viki.LoadRunner.Engine.Core.Counter.Interfaces; 6 | using Viki.LoadRunner.Engine.Core.Factory.Interfaces; 7 | using Viki.LoadRunner.Engine.Core.Pool.Interfaces; 8 | using Viki.LoadRunner.Engine.Core.Scenario.Interfaces; 9 | 10 | namespace Viki.LoadRunner.Engine.Core.Factory 11 | { 12 | public class PipeDataCollectorFactory : IDataCollectorFactory 13 | { 14 | private readonly IPipeFactory _pipeFactory; 15 | private readonly IThreadPoolStats _counter; 16 | 17 | public PipeDataCollectorFactory(IPipeFactory pipeFactory, IThreadPoolStats counter) 18 | { 19 | _pipeFactory = pipeFactory ?? throw new ArgumentNullException(nameof(pipeFactory)); 20 | _counter = counter ?? throw new ArgumentNullException(nameof(counter)); 21 | } 22 | 23 | public IDataCollector Create(IIterationResult iterationContext) 24 | { 25 | IProducer pipe = _pipeFactory.Create(); 26 | 27 | IDataCollector collector = new PipeDataCollector(pipe, iterationContext, _counter); 28 | 29 | return collector; 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Factory/ReflectionFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Viki.LoadRunner.Engine.Core.Factory.Interfaces; 4 | 5 | namespace Viki.LoadRunner.Engine.Core.Factory 6 | { 7 | public class ReflectionFactory : IFactory 8 | { 9 | private readonly Type _type; 10 | 11 | public ReflectionFactory(Type createType) 12 | { 13 | if (createType == null) 14 | throw new ArgumentNullException(nameof(createType)); 15 | 16 | if (!createType.GetInterfaces().Contains(typeof(T))) 17 | throw new ArgumentException($"Provided {nameof(createType)} must implement {typeof(T)}", nameof(createType)); 18 | 19 | _type = createType; 20 | } 21 | 22 | public T Create(int threadId) 23 | { 24 | T instance = (T)Activator.CreateInstance(_type); 25 | 26 | return instance; 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Factory/ScenarioFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Viki.LoadRunner.Engine.Core.Factory.Interfaces; 3 | using Viki.LoadRunner.Engine.Core.Scenario.Interfaces; 4 | 5 | namespace Viki.LoadRunner.Engine.Core.Factory 6 | { 7 | public class ScenarioFactory : ReflectionFactory, IScenarioFactory 8 | { 9 | public ScenarioFactory(Type createType) : base(createType) 10 | { 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Factory/ScenarioHandlerFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Viki.LoadRunner.Engine.Core.Factory.Interfaces; 3 | using Viki.LoadRunner.Engine.Core.Generator; 4 | using Viki.LoadRunner.Engine.Core.Scenario; 5 | using Viki.LoadRunner.Engine.Core.Scenario.Interfaces; 6 | 7 | 8 | namespace Viki.LoadRunner.Engine.Core.Factory 9 | { 10 | public class ScenarioHandlerFactory : IScenarioHandlerFactory 11 | { 12 | private readonly IFactory _factory; 13 | private readonly IGlobalCountersControl _globalCounters; 14 | 15 | public ScenarioHandlerFactory(IFactory factory, IGlobalCountersControl globalCounters) 16 | { 17 | if (factory == null) 18 | throw new ArgumentNullException(nameof(factory)); 19 | if (globalCounters == null) 20 | throw new ArgumentNullException(nameof(globalCounters)); 21 | 22 | _factory = factory; 23 | _globalCounters = globalCounters; 24 | } 25 | 26 | public IScenarioHandler Create(IIterationControl iterationContext) 27 | { 28 | IScenario scenarioInstance = _factory.Create(iterationContext.ThreadId); 29 | IScenarioHandler scenarioHandler = new ScenarioHandler(_globalCounters, new NotThreadSafeIdGenerator(), scenarioInstance, iterationContext); 30 | 31 | return scenarioHandler; 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Factory/SchedulerFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Viki.LoadRunner.Engine.Core.Counter.Interfaces; 3 | using Viki.LoadRunner.Engine.Core.Factory.Interfaces; 4 | using Viki.LoadRunner.Engine.Core.Scenario.Interfaces; 5 | using Viki.LoadRunner.Engine.Core.Scheduler.Interfaces; 6 | using Viki.LoadRunner.Engine.Core.Timer.Interfaces; 7 | using Viki.LoadRunner.Engine.Strategies.Custom.Strategies.Interfaces; 8 | 9 | namespace Viki.LoadRunner.Engine.Core.Factory 10 | { 11 | public class SchedulerFactory : ISchedulerFactory 12 | { 13 | private readonly ITimer _timer; 14 | private readonly ISpeedStrategy _speedStrategy; 15 | private readonly IThreadPoolCounter _counter; 16 | 17 | public SchedulerFactory(ITimer timer, ISpeedStrategy speedStrategy, IThreadPoolCounter counter) 18 | { 19 | if (timer == null) 20 | throw new ArgumentNullException(nameof(timer)); 21 | if (speedStrategy == null) 22 | throw new ArgumentNullException(nameof(speedStrategy)); 23 | if (counter == null) 24 | throw new ArgumentNullException(nameof(counter)); 25 | 26 | _timer = timer; 27 | _speedStrategy = speedStrategy; 28 | _counter = counter; 29 | } 30 | 31 | public IScheduler Create(IIterationId iterationContext) 32 | { 33 | IScheduler scheduler = new Scheduler.Scheduler(_speedStrategy, _counter, _timer, iterationContext); 34 | 35 | return scheduler; 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Factory/ThreadFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Viki.LoadRunner.Engine.Core.Collector.Interfaces; 3 | using Viki.LoadRunner.Engine.Core.Factory.Interfaces; 4 | using Viki.LoadRunner.Engine.Core.Scenario.Interfaces; 5 | using Viki.LoadRunner.Engine.Core.Scheduler.Interfaces; 6 | using Viki.LoadRunner.Engine.Core.Worker; 7 | using Viki.LoadRunner.Engine.Core.Worker.Interfaces; 8 | 9 | namespace Viki.LoadRunner.Engine.Core.Factory 10 | { 11 | public class ThreadFactory : IScenarioThreadFactory 12 | { 13 | private readonly IErrorHandler _errorHandler; 14 | private readonly IPrewait _prewait; 15 | 16 | public ThreadFactory(IPrewait prewait, IErrorHandler errorHandler) 17 | { 18 | if (prewait == null) 19 | throw new ArgumentNullException(nameof(prewait)); 20 | if (errorHandler == null) 21 | throw new ArgumentNullException(nameof(errorHandler)); 22 | 23 | _prewait = prewait; 24 | _errorHandler = errorHandler; 25 | } 26 | 27 | public IThread Create(IScheduler scheduler, IScenarioHandler handler, IDataCollector collector) 28 | { 29 | IWork scenarioWork = new ScenarioWork(scheduler, handler, collector); 30 | IThread thread = new WorkerThread(scenarioWork, _prewait); 31 | _errorHandler.Register(thread); 32 | 33 | return thread; 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Generator/Interfaces/IUniqueIdGenerator.cs: -------------------------------------------------------------------------------- 1 | namespace Viki.LoadRunner.Engine.Core.Generator.Interfaces 2 | { 3 | public interface IUniqueIdGenerator 4 | { 5 | T Next(); 6 | 7 | T Current { get; } 8 | } 9 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Generator/MockedIdGenerator.cs: -------------------------------------------------------------------------------- 1 | using Viki.LoadRunner.Engine.Core.Generator.Interfaces; 2 | 3 | namespace Viki.LoadRunner.Engine.Core.Generator 4 | { 5 | public class MockedIdGenerator : IUniqueIdGenerator 6 | { 7 | public MockedIdGenerator(int id) 8 | { 9 | Current = id; 10 | } 11 | 12 | public int Next() 13 | { 14 | return Current; 15 | } 16 | 17 | public int Current { get; } 18 | } 19 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Generator/NotThreadSafeIdGenerator.cs: -------------------------------------------------------------------------------- 1 | using Viki.LoadRunner.Engine.Core.Generator.Interfaces; 2 | 3 | namespace Viki.LoadRunner.Engine.Core.Generator 4 | { 5 | public class NotThreadSafeIdGenerator : IUniqueIdGenerator 6 | { 7 | public int Next() 8 | { 9 | Current = Current + 1; 10 | 11 | return Current; 12 | } 13 | 14 | public int Current { get; private set; } = -1; 15 | } 16 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Generator/ThreadSafeIdGenerator.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using Viki.LoadRunner.Engine.Core.Generator.Interfaces; 3 | 4 | namespace Viki.LoadRunner.Engine.Core.Generator 5 | { 6 | public class ThreadSafeIdGenerator : IUniqueIdGenerator 7 | { 8 | private int _id = -1; 9 | 10 | public int Next() 11 | { 12 | int result = Interlocked.Increment(ref _id); 13 | 14 | return result; 15 | } 16 | 17 | public int Current => _id; 18 | } 19 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Pool/Interfaces/IThreadFactory.cs: -------------------------------------------------------------------------------- 1 | using Viki.LoadRunner.Engine.Core.Worker.Interfaces; 2 | namespace Viki.LoadRunner.Engine.Core.Pool.Interfaces 3 | { 4 | public interface IThreadFactory 5 | { 6 | IThread Create(); 7 | } 8 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Pool/Interfaces/IThreadPool.cs: -------------------------------------------------------------------------------- 1 | using Viki.LoadRunner.Engine.Core.State.Interfaces; 2 | 3 | namespace Viki.LoadRunner.Engine.Core.Pool.Interfaces 4 | { 5 | public interface IThreadPool 6 | { 7 | void StartWorkersAsync(int count); 8 | void StopWorkersAsync(int count); 9 | } 10 | 11 | public static class ThreadPoolExtensions 12 | { 13 | public static void SetWorkerCountAsync(this IThreadPool context, ITestState state, int threadCount) 14 | { 15 | int delta = threadCount - state.ThreadPool.CreatedThreadCount; 16 | if (delta != 0) 17 | { 18 | if (delta > 0) 19 | context.StartWorkersAsync(delta); 20 | else 21 | context.StopWorkersAsync(-delta); 22 | } 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Pool/Interfaces/IThreadPoolStats.cs: -------------------------------------------------------------------------------- 1 | namespace Viki.LoadRunner.Engine.Core.Pool.Interfaces 2 | { 3 | public interface IThreadPoolStats 4 | { 5 | int CreatedThreadCount { get; } 6 | int InitializedThreadCount { get; } 7 | int IdleThreadCount { get; } 8 | } 9 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Pool/WorkerThreadStats.cs: -------------------------------------------------------------------------------- 1 | using Viki.LoadRunner.Engine.Core.Pool.Interfaces; 2 | 3 | namespace Viki.LoadRunner.Engine.Core.Pool 4 | { 5 | public struct WorkerThreadStats : IThreadPoolStats 6 | { 7 | private readonly int _createdThreadCount; 8 | private readonly int _initializedTheadCount; 9 | private readonly int _idleThreadCount; 10 | 11 | public int CreatedThreadCount => _createdThreadCount; 12 | public int InitializedThreadCount => _initializedTheadCount; 13 | public int IdleThreadCount => _idleThreadCount; 14 | 15 | public WorkerThreadStats(IThreadPoolStats reference) 16 | { 17 | _createdThreadCount = reference.CreatedThreadCount; 18 | _initializedTheadCount = reference.InitializedThreadCount; 19 | _idleThreadCount = reference.IdleThreadCount; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Scenario/CheckpointExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Viki.LoadRunner.Engine.Core.Scenario.Interfaces; 3 | 4 | namespace Viki.LoadRunner.Engine.Core.Scenario 5 | { 6 | public static class CheckpointExtensions 7 | { 8 | public static TimeSpan Diff(this ICheckpoint checkpoint, ICheckpoint next) 9 | { 10 | return next.TimePoint - checkpoint.TimePoint; 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Scenario/GlobalCounters.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Viki.LoadRunner.Engine.Core.Counter; 3 | using Viki.LoadRunner.Engine.Core.Counter.Interfaces; 4 | using Viki.LoadRunner.Engine.Core.Generator; 5 | using Viki.LoadRunner.Engine.Core.Generator.Interfaces; 6 | using Viki.LoadRunner.Engine.Core.Scenario.Interfaces; 7 | 8 | namespace Viki.LoadRunner.Engine.Core.Scenario 9 | { 10 | public class GlobalCounters : IGlobalCountersControl, IGlobalCounters 11 | { 12 | public IUniqueIdGenerator IterationId { get; } 13 | public ICounter Errors { get; } 14 | 15 | public int ErrorCount => Errors.Value; 16 | public int LastGlobalIterationId => IterationId.Current; 17 | 18 | public static GlobalCounters CreateDefault() 19 | { 20 | return new GlobalCounters(new ThreadSafeCounter(), new ThreadSafeIdGenerator()); 21 | } 22 | 23 | public GlobalCounters(ICounter errorsCounter, IUniqueIdGenerator iterationIdCounter) 24 | { 25 | Errors = errorsCounter ?? throw new ArgumentNullException(nameof(errorsCounter)); 26 | IterationId = iterationIdCounter ?? throw new ArgumentNullException(nameof(iterationIdCounter)); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Scenario/Interfaces/ICheckpoint.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Viki.LoadRunner.Engine.Core.Scenario.Interfaces 4 | { 5 | /// 6 | /// Checkpoint acts as meassurement point in test iteration 7 | /// In successful iteration - ResultContext will contain 4 system checkpoints: 8 | /// 9 | /// Checkpoint.IterationSetupCheckpointName 10 | /// * Created before calling ScenarioSetup() 11 | /// Checkpoint.IterationStartCheckpointName 12 | /// * Created before starting ExecuteScenario() 13 | /// * Also at this time the timer will get started 14 | /// Checkpoint.IterationEndCheckpointName 15 | /// * Created after successful ExecuteScenario() execution 16 | /// * It will contain total ExecuteScenario() execution time 17 | /// * Failed iterations won't have this checkpoint. 18 | /// * Timer will also stop here 19 | /// Checkpoint.IterationTearDownCheckpointName 20 | /// * Created before calling IterationTearDown() 21 | /// 22 | /// If there is unhandled exception in Setup, Execute or Teardown steps, it will get logged to the last created checkpoint. 23 | /// * E.g. if test fails in the middle of ExecuteScenario() and there are no custom checkpoints defined. Error will get logged to [Checkpoint.IterationStartCheckpointName] checkpoint. 24 | /// 25 | public interface ICheckpoint 26 | { 27 | /// 28 | /// Name of checkpoint 29 | /// 30 | string Name { get; } 31 | 32 | /// 33 | /// Timepint of iteration when checkpoint was taken 34 | /// 35 | TimeSpan TimePoint { get; } 36 | 37 | /// 38 | /// If executed code below checkpoint creation throws error. 39 | /// Last previously created checkpoint will have this property set with thrown exception. 40 | /// 41 | object Error { get; } 42 | } 43 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Scenario/Interfaces/IGlobalCounters.cs: -------------------------------------------------------------------------------- 1 | namespace Viki.LoadRunner.Engine.Core.Scenario.Interfaces 2 | { 3 | public interface IGlobalCounters 4 | { 5 | int LastGlobalIterationId { get; } 6 | int ErrorCount { get; } 7 | } 8 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Scenario/Interfaces/IGlobalCountersControl.cs: -------------------------------------------------------------------------------- 1 | using Viki.LoadRunner.Engine.Core.Counter.Interfaces; 2 | using Viki.LoadRunner.Engine.Core.Generator.Interfaces; 3 | 4 | namespace Viki.LoadRunner.Engine.Core.Scenario.Interfaces 5 | { 6 | public interface IGlobalCountersControl 7 | { 8 | IUniqueIdGenerator IterationId { get; } 9 | ICounter Errors { get; } 10 | } 11 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Scenario/Interfaces/IIteration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Viki.LoadRunner.Engine.Core.Timer.Interfaces; 3 | 4 | namespace Viki.LoadRunner.Engine.Core.Scenario.Interfaces 5 | { 6 | public interface IIteration : IIterationMetadata 7 | { 8 | /// 9 | /// Marks time checkpoint for current scenario itaration 10 | /// 11 | /// Checkpoint name 12 | void Checkpoint(string checkpointName = null); 13 | 14 | /// 15 | /// Set error onto current checkpoint. 16 | /// Engine will also uses this to set exception if one is thrown and will overwrite existing data. 17 | /// 18 | /// error object to set (setting to null clears it) 19 | void SetError(object error); 20 | 21 | /// 22 | /// Current timer value of the currently going on iteration. 23 | /// 24 | TimeSpan IterationElapsedTime { get; } 25 | 26 | /// 27 | /// Root test timer. 28 | /// 29 | ITimer Timer { get; } 30 | } 31 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Scenario/Interfaces/IIterationControl.cs: -------------------------------------------------------------------------------- 1 | namespace Viki.LoadRunner.Engine.Core.Scenario.Interfaces 2 | { 3 | public interface IIterationControl : IIteration, IIterationResult 4 | { 5 | void Start(); 6 | void Stop(); 7 | void Skip(); 8 | void Reset(int threadIterationId, int globalIterationId); 9 | } 10 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Scenario/Interfaces/IIterationId.cs: -------------------------------------------------------------------------------- 1 | namespace Viki.LoadRunner.Engine.Core.Scenario.Interfaces 2 | { 3 | public interface IIterationId 4 | { 5 | /// 6 | /// Unique Iteration ID within all worker-threads (Starts from zero) 7 | /// [Tip: If scenario fits - this reference ID can be used as index for test-data datasources] 8 | /// 9 | int GlobalIterationId { get; } 10 | 11 | /// 12 | /// Unique Iteration ID withing current instance of ILoadTestScenario (Starts from zero) 13 | /// [Tip: If scenario fits - this reference ID can be used as index for test-data datasources] 14 | /// 15 | int ThreadIterationId { get; } 16 | 17 | /// 18 | /// Unique worker-thread ID. It will stay the same throughout all ILoadTestScenario instance lifetime (Starts from zero) 19 | /// [Tip: If scenario fits - this reference ID can be used as index for test-data datasources] 20 | /// 21 | int ThreadId { get; } 22 | } 23 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Scenario/Interfaces/IIterationMetadata.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace Viki.LoadRunner.Engine.Core.Scenario.Interfaces 3 | { 4 | /// 5 | /// Only iteration metadata containing values (no meassurements) 6 | /// 7 | /// type of UserData it will carry 8 | public interface IIterationMetadata : IIterationId 9 | { 10 | /// 11 | /// Field mainly used for passing data from test iteration to custom aggregation. 12 | /// 13 | TUserData UserData { get; set; } 14 | } 15 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Scenario/Interfaces/IIterationResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Viki.LoadRunner.Engine.Core.Scenario.Interfaces 4 | { 5 | public interface IIterationResult : IIterationMetadata 6 | { 7 | /// 8 | /// All checkpoints containing meassurements from whole iteration 9 | /// 10 | ICheckpoint[] CopyCheckpoints(); 11 | 12 | /// 13 | /// It contains value when this iteration started (relative to LoadTest start) 14 | /// 15 | TimeSpan IterationStarted { get; } 16 | 17 | /// 18 | /// It contains value when this iteration ended (relative to LoadTest start) 19 | /// 20 | TimeSpan IterationFinished { get; } 21 | } 22 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Scenario/Interfaces/IScenarioHandler.cs: -------------------------------------------------------------------------------- 1 | namespace Viki.LoadRunner.Engine.Core.Scenario.Interfaces 2 | { 3 | public interface IScenarioHandler 4 | { 5 | /// 6 | /// Initial setup 7 | /// 8 | /// Called 1st (Initialize) 9 | void Init(); 10 | 11 | /// 12 | /// Prepares context for next iteration 13 | /// 14 | ///Called 2nd (Before each Execute) 15 | void PrepareNext(); 16 | 17 | /// 18 | /// Executes iteration 19 | /// 20 | ///Called 3rd (After each Prepare) 21 | void Execute(); 22 | 23 | /// 24 | /// Final cleanup 25 | /// 26 | /// Called last (Cleanup) 27 | void Cleanup(); 28 | } 29 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Scenario/ScenarioBase.cs: -------------------------------------------------------------------------------- 1 | using Viki.LoadRunner.Engine.Core.Scenario.Interfaces; 2 | 3 | namespace Viki.LoadRunner.Engine.Core.Scenario 4 | { 5 | public abstract class ScenarioBase : IScenario 6 | { 7 | public virtual void ScenarioSetup(IIteration context) 8 | { 9 | } 10 | 11 | public virtual void IterationSetup(IIteration context) 12 | { 13 | } 14 | 15 | public abstract void ExecuteScenario(IIteration context); 16 | 17 | public virtual void IterationTearDown(IIteration context) 18 | { 19 | } 20 | 21 | public virtual void ScenarioTearDown(IIteration context) 22 | { 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Scheduler/Interfaces/IWait.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Viki.LoadRunner.Engine.Core.Scheduler.Interfaces 4 | { 5 | public interface IWait 6 | { 7 | void Wait(TimeSpan target, ref bool cencellationToken); 8 | } 9 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Scheduler/NullScheduler.cs: -------------------------------------------------------------------------------- 1 | using Viki.LoadRunner.Engine.Core.Scheduler.Interfaces; 2 | 3 | namespace Viki.LoadRunner.Engine.Core.Scheduler 4 | { 5 | public class NullScheduler : IScheduler 6 | { 7 | public void WaitNext(ref bool stop) 8 | { 9 | } 10 | 11 | public void ThreadStarted() 12 | { 13 | } 14 | 15 | public void ThreadFinished() 16 | { 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Scheduler/SemiWait.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using Viki.LoadRunner.Engine.Core.Scheduler.Interfaces; 4 | using Viki.LoadRunner.Engine.Core.Timer.Interfaces; 5 | 6 | namespace Viki.LoadRunner.Engine.Core.Scheduler 7 | { 8 | public class SemiWait : IWait 9 | { 10 | private readonly TimeSpan _oneSecond = TimeSpan.FromSeconds(1); 11 | 12 | private readonly ITimer _timer; 13 | 14 | public SemiWait(ITimer timer) 15 | { 16 | if (timer == null) 17 | throw new ArgumentNullException(nameof(timer)); 18 | 19 | _timer = timer; 20 | } 21 | 22 | public void Wait(TimeSpan target, ref bool stop) 23 | { 24 | TimeSpan delay = CalculateDelay(target); 25 | while (delay > _oneSecond && stop == false) 26 | { 27 | Thread.Sleep(_oneSecond); 28 | delay = CalculateDelay(target); 29 | } 30 | if (delay > TimeSpan.Zero && stop == false) 31 | Thread.Sleep(delay); 32 | } 33 | 34 | private TimeSpan CalculateDelay(TimeSpan target) 35 | { 36 | return target - _timer.Value; 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/State/Interfaces/ITestState.cs: -------------------------------------------------------------------------------- 1 | using Viki.LoadRunner.Engine.Core.Pool.Interfaces; 2 | using Viki.LoadRunner.Engine.Core.Scenario.Interfaces; 3 | using Viki.LoadRunner.Engine.Core.Timer.Interfaces; 4 | 5 | namespace Viki.LoadRunner.Engine.Core.State.Interfaces 6 | { 7 | public interface ITestState : IStrategyState 8 | { 9 | IGlobalCounters Counters { get; } 10 | } 11 | 12 | public interface IStrategyState 13 | { 14 | ITimer Timer { get; } 15 | IThreadPoolStats ThreadPool { get; } 16 | } 17 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/State/TestState.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Viki.LoadRunner.Engine.Core.Pool.Interfaces; 3 | using Viki.LoadRunner.Engine.Core.Scenario.Interfaces; 4 | using Viki.LoadRunner.Engine.Core.State.Interfaces; 5 | using Viki.LoadRunner.Engine.Core.Timer.Interfaces; 6 | 7 | namespace Viki.LoadRunner.Engine.Core.State 8 | { 9 | public class TestState : ITestState 10 | { 11 | public ITimer Timer { get; } 12 | public IGlobalCounters Counters { get; } 13 | public IThreadPoolStats ThreadPool { get; } 14 | 15 | public TestState(ITimer timer, IGlobalCounters counters, IThreadPoolStats threadPool) 16 | { 17 | if (timer == null) 18 | throw new ArgumentNullException(nameof(timer)); 19 | if (counters == null) 20 | throw new ArgumentNullException(nameof(counters)); 21 | if (threadPool == null) 22 | throw new ArgumentNullException(nameof(threadPool)); 23 | 24 | Timer = timer; 25 | Counters = counters; 26 | ThreadPool = threadPool; 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Timer/ExecutionTimer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using Viki.LoadRunner.Engine.Core.Timer.Interfaces; 4 | 5 | #pragma warning disable 1591 6 | 7 | namespace Viki.LoadRunner.Engine.Core.Timer 8 | { 9 | public class ExecutionTimer : ITimerControl 10 | { 11 | private readonly Stopwatch _stopwatch = new Stopwatch(); 12 | private DateTime _beginTime; 13 | 14 | public void Start() 15 | { 16 | _beginTime = DateTime.UtcNow; 17 | 18 | _stopwatch.Reset(); 19 | _stopwatch.Start(); 20 | } 21 | 22 | public void Stop() 23 | { 24 | _stopwatch.Stop(); 25 | } 26 | 27 | public TimeSpan Value => _stopwatch.Elapsed; 28 | public bool IsRunning => _stopwatch.IsRunning; 29 | public DateTime BeginTime => _beginTime; 30 | } 31 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Timer/Interfaces/ITimer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Viki.LoadRunner.Engine.Core.Timer.Interfaces 4 | { 5 | /// 6 | /// Timer interface used to pass read-only timer to TestContext from LoadRunner engine 7 | /// 8 | public interface ITimer 9 | { 10 | /// 11 | /// Time passed since the start of the execution 12 | /// 13 | TimeSpan Value { get; } 14 | 15 | /// 16 | /// Indicates whether timer is running 17 | /// 18 | bool IsRunning { get; } 19 | 20 | /// 21 | /// Real-world utc time of when load test was started 22 | /// 23 | DateTime BeginTime { get; } 24 | } 25 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Timer/Interfaces/ITimerControl.cs: -------------------------------------------------------------------------------- 1 | namespace Viki.LoadRunner.Engine.Core.Timer.Interfaces 2 | { 3 | public interface ITimerControl : ITimer 4 | { 5 | void Start(); 6 | void Stop(); 7 | } 8 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Worker/ErrorHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Threading; 4 | using Viki.LoadRunner.Engine.Core.Worker.Interfaces; 5 | 6 | namespace Viki.LoadRunner.Engine.Core.Worker 7 | { 8 | public class ErrorHandler : IErrorHandler 9 | { 10 | private readonly ConcurrentBag _errors = new ConcurrentBag(); 11 | 12 | public void Register(IThread worker) 13 | { 14 | worker.ThreadError += OnThreadError; 15 | worker.ThreadStopped += OnThreadStopped; 16 | } 17 | 18 | private void OnThreadStopped(IThread sender) 19 | { 20 | sender.ThreadError -= OnThreadError; 21 | sender.ThreadStopped -= OnThreadStopped; 22 | } 23 | 24 | private void OnThreadError(IThread sender, Exception ex) 25 | { 26 | if (ex.GetType() != typeof(ThreadAbortException)) 27 | { 28 | _errors.Add(new WorkerException(ex.Message, sender, ex)); 29 | } 30 | } 31 | 32 | public void Assert() 33 | { 34 | if (_errors.Count != 0) 35 | { 36 | WorkerException resultError; 37 | _errors.TryTake(out resultError); 38 | 39 | if (resultError != null) 40 | throw resultError; 41 | } 42 | 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Worker/Interfaces/IErrorHandler.cs: -------------------------------------------------------------------------------- 1 | namespace Viki.LoadRunner.Engine.Core.Worker.Interfaces 2 | { 3 | public interface IErrorHandler 4 | { 5 | void Assert(); 6 | 7 | void Register(IThread thread); 8 | } 9 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Worker/Interfaces/IPrewait.cs: -------------------------------------------------------------------------------- 1 | namespace Viki.LoadRunner.Engine.Core.Worker.Interfaces 2 | { 3 | public interface IPrewait 4 | { 5 | void Wait(ref bool stop); 6 | } 7 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Worker/Interfaces/IThread.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Viki.LoadRunner.Engine.Core.Worker.Interfaces 4 | { 5 | public interface IThread : IDisposable 6 | { 7 | void StartThread(); 8 | void QueueStopThreadAsync(); 9 | void StopThread(int timeoutMilliseconds); 10 | 11 | bool Initialized { get; } 12 | 13 | event ThreadInitializedEventDelegate ThreadInitialized; 14 | event ThreadErrorEventDelegate ThreadError; 15 | event ThreadStoppedEventDelegate ThreadStopped; 16 | } 17 | 18 | public delegate void ThreadInitializedEventDelegate(IThread sender); 19 | 20 | public delegate void ThreadErrorEventDelegate(IThread sender, Exception ex); 21 | 22 | public delegate void ThreadStoppedEventDelegate(IThread sender); 23 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Worker/Interfaces/IWork.cs: -------------------------------------------------------------------------------- 1 | namespace Viki.LoadRunner.Engine.Core.Worker.Interfaces 2 | { 3 | public interface IWork 4 | { 5 | void Init(); 6 | void Execute(ref bool stop); 7 | void Cleanup(); 8 | } 9 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Worker/NullPrewait.cs: -------------------------------------------------------------------------------- 1 | using Viki.LoadRunner.Engine.Core.Worker.Interfaces; 2 | 3 | namespace Viki.LoadRunner.Engine.Core.Worker 4 | { 5 | public class NullPrewait : IPrewait 6 | { 7 | public void Wait(ref bool stop) 8 | { 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Worker/ScenarioWork.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Viki.LoadRunner.Engine.Core.Collector.Interfaces; 3 | using Viki.LoadRunner.Engine.Core.Scenario.Interfaces; 4 | using Viki.LoadRunner.Engine.Core.Scheduler.Interfaces; 5 | using Viki.LoadRunner.Engine.Core.Worker.Interfaces; 6 | 7 | namespace Viki.LoadRunner.Engine.Core.Worker 8 | { 9 | public class ScenarioWork : IWork 10 | { 11 | private readonly IScheduler _scheduler; 12 | private readonly IScenarioHandler _handler; 13 | 14 | private readonly IDataCollector _collector; 15 | 16 | public ScenarioWork(IScheduler scheduler, IScenarioHandler handler, IDataCollector collector) 17 | { 18 | if (scheduler == null) 19 | throw new ArgumentNullException(nameof(scheduler)); 20 | if (handler == null) 21 | throw new ArgumentNullException(nameof(handler)); 22 | if (collector == null) 23 | throw new ArgumentNullException(nameof(collector)); 24 | 25 | _scheduler = scheduler; 26 | _handler = handler; 27 | _collector = collector; 28 | } 29 | 30 | public void Init() 31 | { 32 | _handler.Init(); 33 | _scheduler.ThreadStarted(); 34 | } 35 | 36 | public void Execute(ref bool stop) 37 | { 38 | while (!stop) 39 | { 40 | _handler.PrepareNext(); 41 | 42 | _scheduler.WaitNext(ref stop); 43 | 44 | if (!stop) 45 | { 46 | _handler.Execute(); 47 | 48 | _collector.Collect(); 49 | } 50 | } 51 | } 52 | 53 | public void Cleanup() 54 | { 55 | _collector.Complete(); 56 | _scheduler.ThreadFinished(); 57 | _handler.Cleanup(); 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Worker/TimerBasedPrewait.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using Viki.LoadRunner.Engine.Core.Timer.Interfaces; 4 | using Viki.LoadRunner.Engine.Core.Worker.Interfaces; 5 | 6 | namespace Viki.LoadRunner.Engine.Core.Worker 7 | { 8 | public class TimerBasedPrewait : IPrewait 9 | { 10 | private readonly ITimer _timer; 11 | 12 | public TimerBasedPrewait(ITimer timer) 13 | { 14 | if (timer == null) 15 | throw new ArgumentNullException(nameof(timer)); 16 | 17 | _timer = timer; 18 | } 19 | 20 | public void Wait(ref bool stop) 21 | { 22 | // Wait for ITimer to start. 23 | while (_timer.IsRunning == false && stop == false) 24 | Thread.Sleep(1); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Core/Worker/WorkerException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Viki.LoadRunner.Engine.Core.Worker.Interfaces; 3 | 4 | namespace Viki.LoadRunner.Engine.Core.Worker 5 | { 6 | public class WorkerException : Exception 7 | { 8 | public IThread Sender { get; } 9 | 10 | public WorkerException(string message, IThread sender, Exception innerException) 11 | : base(message, innerException) 12 | { 13 | Sender = sender; 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Interfaces/IStrategyExecutor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Transactions; 3 | using Viki.LoadRunner.Engine.Core.State.Interfaces; 4 | 5 | namespace Viki.LoadRunner.Engine.Interfaces 6 | { 7 | public interface IStrategyExecutor 8 | { 9 | /// 10 | /// Start LoadTest execution on main thread. This blocks until test execution is finished by defined rules if any. 11 | /// 12 | void Run(); 13 | 14 | /// 15 | /// Started event is triggered only if executor succeeds initialization, but moments before starting the strategy. 16 | /// If strategy fails at start or in the middle of execution, Stopped event will be triggered and it will contain related exception. 17 | /// 18 | event ExecutorStartedEventDelegate Started; 19 | 20 | /// 21 | /// Ended event is triggered after test is stopped. 22 | /// If passed exception is not not null, test has stopped abnormally and passed exception will be the reason why execution was stopped. 23 | /// 24 | event ExecutorStoppedEventDelegate Stopped; 25 | } 26 | 27 | public delegate void ExecutorStartedEventDelegate(IStrategyExecutor sender, ITestState state); 28 | 29 | public delegate void ExecutorStoppedEventDelegate(IStrategyExecutor sender, Exception exception); 30 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Interfaces/IStrategyExecutorAsync.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Viki.LoadRunner.Engine.Interfaces 4 | { 5 | public interface IStrategyExecutorAsync : IStrategyExecutor 6 | { 7 | /// 8 | /// If execution failed due to unhandled exception, it will be set here. 9 | /// 10 | Exception Exception { get; } 11 | 12 | /// 13 | /// Is test running 14 | /// 15 | bool Running { get; } 16 | 17 | 18 | /// 19 | /// Is test is in stopping state 20 | /// 21 | bool Stopping { get; } 22 | 23 | /// 24 | /// Executes test in separate thread (non-blocking call) 25 | /// 26 | void RunAsync(); 27 | 28 | /// 29 | /// Cancels Async test execution. 30 | /// Stops exeucion gracefully with time-out handling. 31 | /// Aggregated data up to this point won't be lost. 32 | /// 33 | /// Use [Running] property to check when it is finished 34 | void CancelAsync(bool blocking = false); 35 | 36 | /// 37 | /// Waits, till execution is finished gracefully with graceful waiting for ExecutionStrategy and FinishTimeout. 38 | /// 39 | /// timeout time period to wait before returning 40 | /// if execution won't finish within desired timeout, should it be terminated prematurely? 41 | /// true - if test execution is stopped (either before timeout or aborted due to [abortOnTimeOut]) 42 | bool Wait(TimeSpan timeOut, bool abortOnTimeOut = false); 43 | 44 | /// 45 | /// Waits Infinitely until loadtest execution is finished 46 | /// 47 | void Wait(); 48 | } 49 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Strategies/Custom/Adapter/Limit/LimitsHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Viki.LoadRunner.Engine.Core.State.Interfaces; 3 | using Viki.LoadRunner.Engine.Strategies.Custom.Strategies.Interfaces; 4 | 5 | namespace Viki.LoadRunner.Engine.Strategies.Custom.Adapter.Limit 6 | { 7 | public class LimitsHandler 8 | { 9 | private readonly ILimitStrategy[] _limits; 10 | 11 | public LimitsHandler(ILimitStrategy[] limits) 12 | { 13 | if (limits == null) 14 | throw new ArgumentNullException(nameof(limits)); 15 | 16 | _limits = limits; 17 | } 18 | 19 | public bool StopTest(ITestState state) 20 | { 21 | for (int i = 0; i < _limits.Length; i++) 22 | { 23 | if (_limits[i].StopTest(state)) 24 | return true; 25 | } 26 | 27 | return false; 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Strategies/Custom/Adapter/Speed/ScheduleTable.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Viki.LoadRunner.Engine.Core.Scheduler.Interfaces; 3 | using Viki.LoadRunner.Engine.Core.Timer.Interfaces; 4 | 5 | namespace Viki.LoadRunner.Engine.Strategies.Custom.Adapter.Speed 6 | { 7 | public class ScheduleTable 8 | { 9 | public ISchedule[] Schedules; 10 | public int ReadPosition; 11 | 12 | public ScheduleTable(ITimer timer, int size) 13 | { 14 | Schedules = Enumerable.Range(0, size).Select(i => new Schedule(timer)).Cast().ToArray(); 15 | ReadPosition = 0; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Strategies/Custom/Factory/PriorityStrategyFactory.cs: -------------------------------------------------------------------------------- 1 | using Viki.LoadRunner.Engine.Core.Timer.Interfaces; 2 | using Viki.LoadRunner.Engine.Strategies.Custom.Adapter.Speed; 3 | using Viki.LoadRunner.Engine.Strategies.Custom.Strategies.Interfaces; 4 | using Viki.LoadRunner.Engine.Strategies.Custom.Strategies.Speed; 5 | using Viki.LoadRunner.Engine.Utils; 6 | 7 | namespace Viki.LoadRunner.Engine.Strategies.Custom.Factory 8 | { 9 | public static class PriorityStrategyFactory 10 | { 11 | public static ISpeedStrategy Create(ISpeedStrategy[] strategies, ITimer timer) 12 | { 13 | if (strategies.IsNullOrEmpty()) 14 | return new MaxSpeed(); 15 | 16 | if (strategies.Length == 1) 17 | return strategies[0]; 18 | 19 | return SlowestSpeedStrategy.Create(timer, strategies); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Strategies/Custom/Interfaces/ICustomStrategySettings.cs: -------------------------------------------------------------------------------- 1 | using Viki.LoadRunner.Engine.Core.Factory.Interfaces; 2 | using Viki.LoadRunner.Engine.Strategies.Custom.Strategies.Interfaces; 3 | using Viki.LoadRunner.Engine.Strategies.Interfaces; 4 | 5 | namespace Viki.LoadRunner.Engine.Strategies.Custom.Interfaces 6 | { 7 | /// 8 | /// LoadRunner engine configuration root 9 | /// 10 | public interface ICustomStrategySettings : IAggregatorFeature, IUserDataFeature, ITimeoutFeature 11 | { 12 | /// 13 | /// Limits define when test execution will be scheduled to stop. 14 | /// Keep in mind that limits can't enforce stopping precisely how defined. E.g. you can't make it stop at exactly at 2000 iterations, but it will be close to it. 15 | /// Limits precision correlates to LoadRunnerEngine.HeartBeatMs 16 | /// 17 | ILimitStrategy[] Limits { get; } 18 | 19 | /// 20 | /// Speed strategies will limit executed iteration per second. 21 | /// See this.SpeedPriority for prioritization. 22 | /// 23 | ISpeedStrategy[] Speeds { get; } 24 | 25 | /// 26 | /// Threading strategy controls created thread count throughout the LoadTest. 27 | /// 28 | IThreadingStrategy Threading { get; } 29 | 30 | /// 31 | /// Factory for creating IScenario instances. 32 | /// 33 | IScenarioFactory ScenarioFactory { get; } 34 | } 35 | } 36 | 37 | -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Strategies/Custom/Strategies/Interfaces/ILimitStrategy.cs: -------------------------------------------------------------------------------- 1 | using Viki.LoadRunner.Engine.Core.State.Interfaces; 2 | 3 | namespace Viki.LoadRunner.Engine.Strategies.Custom.Strategies.Interfaces 4 | { 5 | /// 6 | /// Limit-Strategy tells when test execution should be stopped. 7 | /// 8 | public interface ILimitStrategy 9 | { 10 | /// 11 | /// StopTest() gets called at every HeartBeat from executor (by default every 100ms.) 12 | /// once returned value is [true] engine will initiate graceful shutdown respecting [FinishTimeout] value. 13 | /// 14 | bool StopTest(ITestState state); 15 | } 16 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Strategies/Custom/Strategies/Interfaces/IThreadingStrategy.cs: -------------------------------------------------------------------------------- 1 | using Viki.LoadRunner.Engine.Core.Pool.Interfaces; 2 | using Viki.LoadRunner.Engine.Core.State.Interfaces; 3 | 4 | namespace Viki.LoadRunner.Engine.Strategies.Custom.Strategies.Interfaces 5 | { 6 | public interface IThreadingStrategy 7 | { 8 | /// 9 | /// Will be called within load-test initialization. This is a good place to tell how many worker-threads to create before starting the test 10 | /// 11 | /// ThreadPool controller interface 12 | void Setup(IThreadPool pool); 13 | 14 | /// 15 | /// IStrategyExecutor fires HeartBeat from its own root thread. 16 | /// This can be used to adjust thread count t 17 | /// 18 | /// ThreadPool controller interface 19 | /// Global test state instance which is used throughout the whole test 20 | void HeartBeat(IThreadPool pool, ITestState state); 21 | } 22 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Strategies/Custom/Strategies/Limit/ErrorLimit.cs: -------------------------------------------------------------------------------- 1 | using Viki.LoadRunner.Engine.Core.State.Interfaces; 2 | using Viki.LoadRunner.Engine.Strategies.Custom.Strategies.Interfaces; 3 | 4 | namespace Viki.LoadRunner.Engine.Strategies.Custom.Strategies.Limit 5 | { 6 | public class ErrorLimit : ILimitStrategy 7 | { 8 | private readonly int _errorsThreshold; 9 | 10 | public ErrorLimit(int errorsThreshold) 11 | { 12 | _errorsThreshold = errorsThreshold; 13 | } 14 | 15 | public bool StopTest(ITestState state) 16 | { 17 | return state.Counters.ErrorCount >= _errorsThreshold; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Strategies/Custom/Strategies/Limit/IterationLimit.cs: -------------------------------------------------------------------------------- 1 | using Viki.LoadRunner.Engine.Core.State.Interfaces; 2 | using Viki.LoadRunner.Engine.Strategies.Custom.Strategies.Interfaces; 3 | 4 | namespace Viki.LoadRunner.Engine.Strategies.Custom.Strategies.Limit 5 | { 6 | /// 7 | /// Stops test execution after executing defined count of iterations. 8 | /// 9 | public class IterationLimit : ILimitStrategy 10 | { 11 | private readonly int _iterationsLimit; 12 | 13 | /// 14 | /// Inits test execution stop after executing defined count of iterations. 15 | /// 16 | /// Count of iterations after which to schedule stop 17 | public IterationLimit(int iterationsLimit) 18 | { 19 | _iterationsLimit = iterationsLimit; 20 | } 21 | 22 | bool ILimitStrategy.StopTest(ITestState state) 23 | { 24 | return _iterationsLimit <= state.Counters.LastGlobalIterationId - state.ThreadPool.IdleThreadCount; 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Strategies/Custom/Strategies/Limit/TimeLimit.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Viki.LoadRunner.Engine.Core.State.Interfaces; 3 | using Viki.LoadRunner.Engine.Strategies.Custom.Strategies.Interfaces; 4 | 5 | namespace Viki.LoadRunner.Engine.Strategies.Custom.Strategies.Limit 6 | { 7 | /// 8 | /// Inits test execution stop once execution time reaches defined limit. 9 | /// 10 | public class TimeLimit : ILimitStrategy 11 | { 12 | private readonly TimeSpan _timeLimit; 13 | 14 | /// 15 | /// Inits test execution stop once execution time reaches defined limit. 16 | /// 17 | /// How long tests should be executed 18 | public TimeLimit(TimeSpan timeLimit) 19 | { 20 | _timeLimit = timeLimit; 21 | } 22 | 23 | public bool StopTest(ITestState state) 24 | { 25 | return _timeLimit <= state.Timer.Value; 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Strategies/Custom/Strategies/Speed/ClockedListOfSpeed.cs: -------------------------------------------------------------------------------- 1 | namespace Viki.LoadRunner.Engine.Strategies.Custom.Strategies.Speed 2 | { 3 | // TODO: Rework it using new IThreadingStrategy 4 | /// 5 | /// WiP Strategy, not yet fully finallized. 6 | /// 7 | //public class ClockedListOfSpeed 8 | //{ 9 | // private readonly DateTime _initialTime; 10 | // private readonly TimeSpan _minSpeed; 11 | 12 | // public Func ClockSelector = () => DateTime.UtcNow; 13 | 14 | //public ClockedListOfSpeed(double minIterationsPerSec, DateTime initialTime, TimeSpan period, params double[] iterationPerSecValues) 15 | // : base(period, iterationPerSecValues) 16 | //{ 17 | // _initialTime = initialTime.ToUniversalTime(); 18 | 19 | // _minSpeed = TimeSpan.FromTicks((long)(OneSecond.Ticks / minIterationsPerSec)); 20 | //} 21 | 22 | //public override TimeSpan GetDelayBetweenIterations(TimeSpan testExecutionTime) 23 | //{ 24 | // TimeSpan fakeExecutionTime = ClockSelector() - _initialTime; 25 | 26 | // if (fakeExecutionTime > TimeSpan.Zero) 27 | // return base.GetIndex(fakeExecutionTime); 28 | // else 29 | // return _minSpeed; 30 | 31 | //} 32 | //} 33 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Strategies/Custom/Strategies/Speed/MaxSpeed.cs: -------------------------------------------------------------------------------- 1 | using Viki.LoadRunner.Engine.Core.Scenario.Interfaces; 2 | using Viki.LoadRunner.Engine.Core.Scheduler.Interfaces; 3 | using Viki.LoadRunner.Engine.Core.State.Interfaces; 4 | using Viki.LoadRunner.Engine.Strategies.Custom.Strategies.Interfaces; 5 | 6 | namespace Viki.LoadRunner.Engine.Strategies.Custom.Strategies.Speed 7 | { 8 | public class MaxSpeed : ISpeedStrategy 9 | { 10 | public void Setup(ITestState state) 11 | { 12 | } 13 | 14 | public void Next(IIterationId id, ISchedule scheduler) 15 | { 16 | } 17 | 18 | public void HeartBeat(ITestState state) 19 | { 20 | } 21 | 22 | public void ThreadStarted(IIterationId id, ISchedule scheduler) 23 | { 24 | } 25 | 26 | public void ThreadFinished(IIterationId id, ISchedule scheduler) 27 | { 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Strategies/Custom/Strategies/Threading/FixedThreadCount.cs: -------------------------------------------------------------------------------- 1 | using Viki.LoadRunner.Engine.Core.Pool.Interfaces; 2 | using Viki.LoadRunner.Engine.Core.State.Interfaces; 3 | using Viki.LoadRunner.Engine.Strategies.Custom.Strategies.Interfaces; 4 | 5 | namespace Viki.LoadRunner.Engine.Strategies.Custom.Strategies.Threading 6 | { 7 | public class FixedThreadCount : IThreadingStrategy 8 | { 9 | protected int ThreadCount; 10 | 11 | public FixedThreadCount(int threadCount) 12 | { 13 | ThreadCount = threadCount; 14 | } 15 | 16 | public void Setup(IThreadPool pool) 17 | { 18 | pool.StartWorkersAsync(ThreadCount); 19 | } 20 | 21 | public void HeartBeat(IThreadPool pool, ITestState state) 22 | { 23 | pool.SetWorkerCountAsync(state, ThreadCount); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Strategies/Custom/Strategies/Threading/ListOfCounts.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Viki.LoadRunner.Engine.Core.Pool.Interfaces; 3 | using Viki.LoadRunner.Engine.Core.State.Interfaces; 4 | using Viki.LoadRunner.Engine.Strategies.Custom.Strategies.Interfaces; 5 | 6 | namespace Viki.LoadRunner.Engine.Strategies.Custom.Strategies.Threading 7 | { 8 | public class ListOfCounts : IThreadingStrategy 9 | { 10 | private readonly TimeSpan _period; 11 | private readonly int[] _threadCountValues; 12 | 13 | private readonly int _lastValue; 14 | 15 | public ListOfCounts(TimeSpan period, params int[] threadCountValues) 16 | { 17 | if (threadCountValues == null) 18 | throw new ArgumentNullException(nameof(threadCountValues)); 19 | if (threadCountValues.Length == 0) 20 | throw new ArgumentException("At least one value must be provided", nameof(threadCountValues)); 21 | 22 | _period = period; 23 | _threadCountValues = threadCountValues; 24 | 25 | _lastValue = threadCountValues[threadCountValues.Length - 1]; 26 | } 27 | 28 | public int InitialThreadCount => _threadCountValues[0]; 29 | 30 | public void Setup(IThreadPool pool) 31 | { 32 | pool.StartWorkersAsync(InitialThreadCount); 33 | } 34 | 35 | public void HeartBeat(IThreadPool pool, ITestState state) 36 | { 37 | long index = state.Timer.Value.Ticks / _period.Ticks; 38 | int result; 39 | 40 | if (index < _threadCountValues.Length) 41 | result = _threadCountValues[index]; 42 | else 43 | result = _lastValue; 44 | 45 | pool.SetWorkerCountAsync(state, result); 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Strategies/Extensions/ReplayDataReaderExtensions.cs: -------------------------------------------------------------------------------- 1 | using Viki.LoadRunner.Engine.Strategies.Replay.Data; 2 | using Viki.LoadRunner.Engine.Strategies.Replay.Data.Interfaces; 3 | 4 | namespace Viki.LoadRunner.Engine.Strategies.Extensions 5 | { 6 | public static class ReplayDataReaderExtensions 7 | { 8 | public static DataItem Next(this IReplayDataReader reader, int threadId) 9 | { 10 | bool fakeRefStop = false; 11 | return reader.Next(threadId, ref fakeRefStop); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Strategies/Extensions/StrategyBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using Viki.LoadRunner.Engine.Strategies.Interfaces; 2 | 3 | namespace Viki.LoadRunner.Engine.Strategies.Extensions 4 | { 5 | public static class StrategyBuilderExtensions 6 | { 7 | /// 8 | /// Initialize IStrategy from this configuration and then LoadRunnerEngine it self using it. 9 | /// 10 | /// Strategy builder 11 | public static LoadRunnerEngine Build(this IStrategyBuilder builder) 12 | { 13 | LoadRunnerEngine engine = new LoadRunnerEngine(builder.BuildStrategy()); 14 | 15 | return engine; 16 | } 17 | 18 | } 19 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Strategies/Interfaces/IAggregatorFeature.cs: -------------------------------------------------------------------------------- 1 | using Viki.LoadRunner.Engine.Core.Collector.Interfaces; 2 | 3 | namespace Viki.LoadRunner.Engine.Strategies.Interfaces 4 | { 5 | public interface IAggregatorFeature : IStrategyBuilder 6 | { 7 | /// 8 | /// Aggregators to collect the data. 9 | /// 10 | IAggregator[] Aggregators { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Strategies/Interfaces/IStrategy.cs: -------------------------------------------------------------------------------- 1 | using Viki.LoadRunner.Engine.Core.State.Interfaces; 2 | 3 | namespace Viki.LoadRunner.Engine.Strategies.Interfaces 4 | { 5 | public interface IStrategy 6 | { 7 | ITestState Start(); 8 | 9 | bool HeartBeat(); 10 | 11 | void Stop(); 12 | } 13 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Strategies/Interfaces/IStrategyBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace Viki.LoadRunner.Engine.Strategies.Interfaces 2 | { 3 | public interface IStrategyBuilder 4 | { 5 | IStrategy BuildStrategy(); 6 | 7 | /// 8 | /// Duplicates configuration builder having own configuration lists. But registered configuration instances will still be the same. 9 | /// 10 | /// New instance of IStrategyBuilder 11 | IStrategyBuilder ShallowCopy(); 12 | } 13 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Strategies/Interfaces/ITimeoutFeature.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Viki.LoadRunner.Engine.Strategies.Interfaces 4 | { 5 | public interface ITimeoutFeature : IStrategyBuilder 6 | { 7 | /// 8 | /// Timeout for scenario execution threads to gracefully stop. 9 | /// Failed to complete threads will get Thread.Abort() 10 | /// 11 | TimeSpan FinishTimeout { get; set; } 12 | } 13 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Strategies/Interfaces/IUserDataFeature.cs: -------------------------------------------------------------------------------- 1 | namespace Viki.LoadRunner.Engine.Strategies.Interfaces 2 | { 3 | public interface IUserDataFeature : IStrategyBuilder 4 | { 5 | /// 6 | /// Initial user data which will be passed to created thread contexts. (context.UserData) 7 | /// 8 | object InitialUserData { get; set; } 9 | } 10 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Strategies/Replay/Data/DataItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Viki.LoadRunner.Engine.Strategies.Replay.Data 4 | { 5 | public class DataItem 6 | { 7 | public TimeSpan TimeStamp; 8 | 9 | public object Value; 10 | 11 | public DataItem(TimeSpan timeStamp, object value) 12 | { 13 | if (value == null) 14 | throw new ArgumentNullException(nameof(value)); 15 | 16 | TimeStamp = timeStamp; 17 | Value = value; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Strategies/Replay/Data/Interfaces/IReplayDataReader.cs: -------------------------------------------------------------------------------- 1 | using Viki.LoadRunner.Engine.Core.State.Interfaces; 2 | 3 | namespace Viki.LoadRunner.Engine.Strategies.Replay.Data.Interfaces 4 | { 5 | /// 6 | /// Data provider for ReplayStrategy. 7 | /// 8 | public interface IReplayDataReader 9 | { 10 | /// 11 | /// Gives the signal that execution is about to begin. 12 | /// 13 | void Begin(ITestState testState); 14 | 15 | /// 16 | /// Get next item to be executed in its corresponding order 17 | /// E.g. provided DataItem's must be already sorted by TimeStamp 18 | /// 19 | /// If implementation decides to block, then it must occasionally poll stop value to see if one needs to cancel execution (one can use SemiWait to easier handle this) 20 | /// after the stop, one can still return one more item to be executed (although its recommended to just return null to indicate end) 21 | /// 22 | /// Must be thread safe 23 | /// 24 | DataItem Next(int threadId, ref bool stop); 25 | 26 | /// 27 | /// Gives the signal that test is ending, no more Next() calls after this. 28 | /// 29 | void End(); 30 | } 31 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Strategies/Replay/Data/Readers/PartitionedDataReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Viki.LoadRunner.Engine.Core.State.Interfaces; 5 | using Viki.LoadRunner.Engine.Strategies.Replay.Data.Interfaces; 6 | 7 | namespace Viki.LoadRunner.Engine.Strategies.Replay.Data.Readers 8 | { 9 | public class PartitionedDataReader : IReplayDataReader 10 | { 11 | private readonly IReplayDataReader[] _readers; 12 | 13 | public PartitionedDataReader(ICollection readers) 14 | { 15 | _readers = readers.ToArray(); 16 | } 17 | 18 | public void Begin(ITestState testState) 19 | { 20 | Array.ForEach(_readers, r => r.Begin(testState)); 21 | } 22 | 23 | public DataItem Next(int threadId, ref bool stop) 24 | { 25 | return _readers[threadId % _readers.Length].Next(threadId, ref stop); 26 | } 27 | 28 | public void End() 29 | { 30 | Array.ForEach(_readers, r => r.End()); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Strategies/Replay/Data/Readers/ReplayDataReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading; 5 | using Viki.LoadRunner.Engine.Core.State.Interfaces; 6 | using Viki.LoadRunner.Engine.Strategies.Custom.Strategies.Interfaces; 7 | using Viki.LoadRunner.Engine.Strategies.Replay.Data.Interfaces; 8 | 9 | namespace Viki.LoadRunner.Engine.Strategies.Replay.Data.Readers 10 | { 11 | public class ReplayDataReader : IReplayDataReader, ILimitStrategy 12 | { 13 | private readonly DataItem[] _data; 14 | private int _readIndex; 15 | 16 | public ReplayDataReader(ICollection data) 17 | { 18 | if (data == null) 19 | throw new ArgumentNullException(nameof(data)); 20 | 21 | _data = data.ToArray(); 22 | } 23 | 24 | public void Begin(ITestState testState) 25 | { 26 | _readIndex = -1; 27 | } 28 | 29 | public DataItem Next(int threadId, ref bool stop) 30 | { 31 | int current = Interlocked.Increment(ref _readIndex); 32 | 33 | DataItem result = null; 34 | if (current < _data.Length) 35 | result = _data[current]; 36 | 37 | return result; 38 | } 39 | 40 | public void End() 41 | { 42 | } 43 | 44 | public bool StopTest(ITestState state) 45 | { 46 | return _readIndex + 1 >= _data.Length; 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Strategies/Replay/Factory/Interfaces/IReplayScenarioFactory.cs: -------------------------------------------------------------------------------- 1 | using Viki.LoadRunner.Engine.Core.Factory.Interfaces; 2 | using Viki.LoadRunner.Engine.Strategies.Replay.Interfaces; 3 | 4 | namespace Viki.LoadRunner.Engine.Strategies.Replay.Factory.Interfaces 5 | { 6 | public interface IReplayScenarioFactory : IFactory> 7 | { 8 | } 9 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Strategies/Replay/Factory/Interfaces/IReplayScenarioHandlerFactory.cs: -------------------------------------------------------------------------------- 1 | using Viki.LoadRunner.Engine.Core.Scenario.Interfaces; 2 | using Viki.LoadRunner.Engine.Strategies.Replay.Scenario.Interfaces; 3 | 4 | namespace Viki.LoadRunner.Engine.Strategies.Replay.Factory.Interfaces 5 | { 6 | public interface IReplayScenarioHandlerFactory 7 | { 8 | IReplayScenarioHandler Create(IIterationControl iterationContext); 9 | } 10 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Strategies/Replay/Factory/Interfaces/IReplaySchedulerFactory.cs: -------------------------------------------------------------------------------- 1 | using Viki.LoadRunner.Engine.Core.Scheduler.Interfaces; 2 | using Viki.LoadRunner.Engine.Strategies.Replay.Scenario.Interfaces; 3 | 4 | namespace Viki.LoadRunner.Engine.Strategies.Replay.Factory.Interfaces 5 | { 6 | public interface IReplaySchedulerFactory 7 | { 8 | IScheduler Create(IReplayScenarioHandler scenarioHandler, int threadId); 9 | } 10 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Strategies/Replay/Factory/ReplayScenarioFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Viki.LoadRunner.Engine.Core.Factory; 3 | using Viki.LoadRunner.Engine.Strategies.Replay.Factory.Interfaces; 4 | using Viki.LoadRunner.Engine.Strategies.Replay.Interfaces; 5 | 6 | namespace Viki.LoadRunner.Engine.Strategies.Replay.Factory 7 | { 8 | public class ReplayScenarioFactory : ReflectionFactory>, IReplayScenarioFactory 9 | { 10 | public ReplayScenarioFactory(Type replayScenarioType) : base(replayScenarioType) 11 | { 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Strategies/Replay/Factory/ReplayScenarioHandlerFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Viki.LoadRunner.Engine.Core.Factory.Interfaces; 3 | using Viki.LoadRunner.Engine.Core.Generator; 4 | using Viki.LoadRunner.Engine.Core.Scenario.Interfaces; 5 | using Viki.LoadRunner.Engine.Strategies.Replay.Factory.Interfaces; 6 | using Viki.LoadRunner.Engine.Strategies.Replay.Interfaces; 7 | using Viki.LoadRunner.Engine.Strategies.Replay.Scenario; 8 | using Viki.LoadRunner.Engine.Strategies.Replay.Scenario.Interfaces; 9 | 10 | namespace Viki.LoadRunner.Engine.Strategies.Replay.Factory 11 | { 12 | public class ReplayScenarioHandlerFactory : IReplayScenarioHandlerFactory 13 | { 14 | private readonly IGlobalCountersControl _globalCounters; 15 | private readonly IFactory> _scenarioFactory; 16 | 17 | 18 | public ReplayScenarioHandlerFactory(IFactory> scenarioFactory, IGlobalCountersControl globalCounters) 19 | { 20 | if (scenarioFactory == null) 21 | throw new ArgumentNullException(nameof(scenarioFactory)); 22 | if (globalCounters == null) 23 | throw new ArgumentNullException(nameof(globalCounters)); 24 | 25 | _scenarioFactory = scenarioFactory; 26 | _globalCounters = globalCounters; 27 | } 28 | 29 | public IReplayScenarioHandler Create(IIterationControl iterationContext) 30 | { 31 | IReplayScenario scenarioInstance = _scenarioFactory.Create(iterationContext.ThreadId); 32 | IReplayScenarioHandler scenarioHandler = new ReplayScenarioHandler(_globalCounters, new NotThreadSafeIdGenerator(), scenarioInstance, iterationContext); 33 | 34 | return scenarioHandler; 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Strategies/Replay/Factory/ReplaySchedulerFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Viki.LoadRunner.Engine.Core.Counter.Interfaces; 3 | using Viki.LoadRunner.Engine.Core.Scheduler.Interfaces; 4 | using Viki.LoadRunner.Engine.Core.Timer.Interfaces; 5 | using Viki.LoadRunner.Engine.Strategies.Replay.Data.Interfaces; 6 | using Viki.LoadRunner.Engine.Strategies.Replay.Factory.Interfaces; 7 | using Viki.LoadRunner.Engine.Strategies.Replay.Scenario.Interfaces; 8 | using Viki.LoadRunner.Engine.Strategies.Replay.Scheduler; 9 | 10 | namespace Viki.LoadRunner.Engine.Strategies.Replay.Factory 11 | { 12 | public class ReplaySchedulerFactory : IReplaySchedulerFactory 13 | { 14 | private readonly ITimer _timer; 15 | private readonly IReplayDataReader _dataReader; 16 | private readonly IThreadPoolCounter _counter; 17 | private readonly double _speedMultiplier; 18 | 19 | 20 | public ReplaySchedulerFactory(ITimer timer, IReplayDataReader dataReader, IThreadPoolCounter counter, double speedMultiplier) 21 | { 22 | if (timer == null) 23 | throw new ArgumentNullException(nameof(timer)); 24 | if (dataReader == null) 25 | throw new ArgumentNullException(nameof(dataReader)); 26 | if (counter == null) 27 | throw new ArgumentNullException(nameof(counter)); 28 | 29 | _timer = timer; 30 | _dataReader = dataReader; 31 | _counter = counter; 32 | _speedMultiplier = speedMultiplier; 33 | } 34 | 35 | public IScheduler Create(IReplayScenarioHandler scenarioHandler, int threadId) 36 | { 37 | IScheduler scheduler = new ReplayScheduler(_timer, scenarioHandler, _dataReader, _counter, _speedMultiplier, threadId); 38 | 39 | return scheduler; 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Strategies/Replay/Interfaces/IReplayScenario.cs: -------------------------------------------------------------------------------- 1 | using Viki.LoadRunner.Engine.Core.Scenario.Interfaces; 2 | using Viki.LoadRunner.Engine.Strategies.Replay.Scheduler.Interfaces; 3 | 4 | namespace Viki.LoadRunner.Engine.Strategies.Replay.Interfaces 5 | { 6 | /// 7 | /// IReplayScenario is extended version IScenario 8 | /// all logic is the same, except there is extra step before iteration to set the data given by IReplayDataReader 9 | /// 10 | /// 11 | public interface IReplayScenario : IScenario 12 | { 13 | /// 14 | /// Set the data which which will be used for next iteration 15 | /// Call chain will be like this SetData() -> IterationSetup() -> ExecuteScenario() -> IterationTearDown(). 16 | /// 17 | /// 18 | /// * SetData() will get called as early as thread is free, and then it will wait for data.TargetTime to execute next three steps. 19 | /// * if data.TargetTime is bigger than data.Timer.Value. It means that execution is falling behing timeline. 20 | /// * It must not fail or it will stop the whole test execution. 21 | /// 22 | /// Structure containing replay metadata related to next iteration 23 | void SetData(IData data); 24 | } 25 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Strategies/Replay/Interfaces/IReplayStrategySettings.cs: -------------------------------------------------------------------------------- 1 | using Viki.LoadRunner.Engine.Strategies.Interfaces; 2 | using Viki.LoadRunner.Engine.Strategies.Replay.Data.Interfaces; 3 | using Viki.LoadRunner.Engine.Strategies.Replay.Factory.Interfaces; 4 | 5 | namespace Viki.LoadRunner.Engine.Strategies.Replay.Interfaces 6 | { 7 | public interface IReplayStrategySettings : IAggregatorFeature, IUserDataFeature, ITimeoutFeature 8 | { 9 | /// 10 | /// Fixed count of threads to use 11 | /// 12 | int ThreadCount { get; } 13 | 14 | /// 15 | /// Datasource which defines timeline of replay execution 16 | /// 17 | IReplayDataReader DataReader { get; } 18 | 19 | /// 20 | /// Speed multiplier at which Replay strategy will run 21 | /// 22 | double SpeedMultiplier { get; } 23 | 24 | /// 25 | /// Factory for creating IReplayScenario instances. 26 | /// 27 | IReplayScenarioFactory ScenarioFactory { get; } 28 | } 29 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Strategies/Replay/Scenario/Interfaces/IReplayScenarioHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Viki.LoadRunner.Engine.Core.Scenario.Interfaces; 3 | 4 | namespace Viki.LoadRunner.Engine.Strategies.Replay.Scenario.Interfaces 5 | { 6 | public interface IReplayScenarioHandler : IScenarioHandler 7 | { 8 | bool SetData(object data, TimeSpan target); 9 | } 10 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Strategies/Replay/Scenario/ReplayScenarioHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Viki.LoadRunner.Engine.Core.Generator.Interfaces; 3 | using Viki.LoadRunner.Engine.Core.Scenario; 4 | using Viki.LoadRunner.Engine.Core.Scenario.Interfaces; 5 | using Viki.LoadRunner.Engine.Strategies.Replay.Interfaces; 6 | using Viki.LoadRunner.Engine.Strategies.Replay.Scenario.Interfaces; 7 | using Viki.LoadRunner.Engine.Strategies.Replay.Scheduler; 8 | 9 | namespace Viki.LoadRunner.Engine.Strategies.Replay.Scenario 10 | { 11 | public class ReplayScenarioHandler : ScenarioHandler, IReplayScenarioHandler 12 | { 13 | private readonly IReplayScenario _scenario; 14 | 15 | private readonly DataContext _dataContext; 16 | 17 | public ReplayScenarioHandler(IGlobalCountersControl globalCounters, IUniqueIdGenerator threadIterationIdGenerator, IReplayScenario scenario, IIterationControl context) 18 | : base(globalCounters, threadIterationIdGenerator, scenario, context) 19 | { 20 | _scenario = scenario; 21 | 22 | _dataContext = new DataContext 23 | { 24 | Timer = context.Timer, 25 | Execute = true, 26 | Context = context 27 | }; 28 | } 29 | 30 | public bool SetData(object data, TimeSpan target) 31 | { 32 | _dataContext.Set((TData)data, target); 33 | 34 | _scenario.SetData(_dataContext); 35 | 36 | return _dataContext.Execute; 37 | } 38 | 39 | public new void Execute() 40 | { 41 | if (_dataContext.Execute) 42 | { 43 | base.Execute(); 44 | } 45 | else 46 | { 47 | _context.Skip(); 48 | _context.Checkpoint(Checkpoint.Names.Skip); 49 | } 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Strategies/Replay/Scheduler/DataContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Viki.LoadRunner.Engine.Core.Scenario.Interfaces; 3 | using Viki.LoadRunner.Engine.Core.Timer.Interfaces; 4 | using Viki.LoadRunner.Engine.Strategies.Replay.Scheduler.Interfaces; 5 | 6 | namespace Viki.LoadRunner.Engine.Strategies.Replay.Scheduler 7 | { 8 | public class DataContext : IData 9 | { 10 | /// 11 | /// Set information about upcomming iteration. 12 | /// Both passed values are only use for passing it to scenario it self 13 | /// 14 | /// data value 15 | /// target time 16 | public void Set(TData value, TimeSpan target) 17 | { 18 | Value = value; 19 | TargetTime = target; 20 | Execute = true; 21 | } 22 | 23 | /// 24 | /// Global test timer 25 | /// 26 | public ITimer Timer { get; set; } 27 | 28 | /// 29 | /// Adjusted target time based on provided speed multiplier 30 | /// if (TargetTime < Timer.Value) it means that scenario is falling behind the timeline 31 | /// 32 | public TimeSpan TargetTime { get; set; } 33 | 34 | /// 35 | /// Test data asociated with this iteration 36 | /// 37 | public TData Value { get; set; } 38 | 39 | /// 40 | /// Setting to false will skip this iteration Setup/Execute/Teardown steps 41 | /// Iteration result will only contain (Checkpoint.Names.Skip [default value "ITERATION_SKIP"]) checkpoint 42 | /// 43 | /// It can be used to handle execution timeline falling behind and skip few requests 44 | public bool Execute { get; set; } 45 | 46 | 47 | /// 48 | /// Iteration context 49 | /// 50 | public IIteration Context { get; set; } 51 | } 52 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Strategies/Replay/Scheduler/Interfaces/IDataContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Viki.LoadRunner.Engine.Core.Scenario.Interfaces; 3 | using Viki.LoadRunner.Engine.Core.Timer.Interfaces; 4 | 5 | namespace Viki.LoadRunner.Engine.Strategies.Replay.Scheduler.Interfaces 6 | { 7 | /// 8 | /// Contains information used for seting up upcomming replay scenario iteration. 9 | /// 10 | /// scenario data value type 11 | public interface IData 12 | { 13 | /// 14 | /// Global test timer 15 | /// 16 | ITimer Timer { get; } 17 | 18 | /// 19 | /// Adjusted target time based on provided speed multiplier 20 | /// if (TargetTime < Timer.Value) it means that scenario is falling behind the timeline 21 | /// 22 | TimeSpan TargetTime { get; } 23 | 24 | /// 25 | /// Test data asociated with this iteration 26 | /// 27 | TData Value { get; } 28 | 29 | /// 30 | /// Setting to false will skip this iteration Setup/Execute/Teardown steps 31 | /// Iteration result will only contain (Checkpoint.Names.Skip [default value "ITERATION_SKIP"]) checkpoint 32 | /// 33 | /// It can be used to handle execution timeline falling behind and skip few requests 34 | bool Execute { get; set; } 35 | 36 | /// 37 | /// Iteration context 38 | /// 39 | IIteration Context { get; } 40 | } 41 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Utils/ArrayExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Viki.LoadRunner.Engine.Utils 2 | { 3 | public static class ArrayExtensions 4 | { 5 | public static bool IsNullOrEmpty(this T[] reference) 6 | { 7 | return reference == null || reference.Length == 0; 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Utils/EnumerableExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Threading.Tasks; 5 | 6 | namespace Viki.LoadRunner.Engine.Utils 7 | { 8 | public static class EnumerableExtensions 9 | { 10 | public static IEnumerable ForEachReturn(this IEnumerable enumeration, Action action) 11 | { 12 | foreach (T item in enumeration) 13 | { 14 | action(item); 15 | 16 | yield return item; 17 | } 18 | } 19 | 20 | public static void ForEach(this IEnumerable enumeration, Action action) 21 | { 22 | foreach (T item in enumeration) 23 | { 24 | action(item); 25 | } 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Utils/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Viki.LoadRunner.Engine.Utils 4 | { 5 | public static class StringExtensions 6 | { 7 | public static string SubstringSafe(this string input, int startIndex, int length) 8 | { 9 | if (input == null) 10 | { 11 | return null; 12 | } 13 | 14 | if (input.Length - 1 < startIndex) 15 | { 16 | return String.Empty; 17 | } 18 | 19 | int safeLength = input.Length - startIndex; 20 | if (input.Length - startIndex < length) 21 | { 22 | length = safeLength; 23 | } 24 | 25 | return input.Substring(startIndex, length); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Validators/IValidator.cs: -------------------------------------------------------------------------------- 1 | using Viki.LoadRunner.Engine.Core.Collector; 2 | 3 | namespace Viki.LoadRunner.Engine.Validators 4 | { 5 | public interface IValidator 6 | { 7 | IterationResult Validate(int threadId = 0, int threadIterationId = 0, int globalIterationId = 0); 8 | } 9 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Validators/ReplayScenarioValidator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Viki.LoadRunner.Engine.Core.Collector; 3 | using Viki.LoadRunner.Engine.Strategies.Replay.Data; 4 | using Viki.LoadRunner.Engine.Strategies.Replay.Factory.Interfaces; 5 | 6 | namespace Viki.LoadRunner.Engine.Validators 7 | { 8 | public class ReplayScenarioValidator : IValidator 9 | { 10 | private readonly IReplayScenarioFactory _factory; 11 | private readonly DataItem _data; 12 | 13 | public ReplayScenarioValidator(IReplayScenarioFactory factory, DataItem data) 14 | { 15 | if (factory == null) 16 | throw new ArgumentNullException(nameof(factory)); 17 | if (data == null) 18 | throw new ArgumentNullException(nameof(data)); 19 | 20 | _factory = factory; 21 | _data = data; 22 | } 23 | 24 | public IterationResult Validate(int threadId = 0, int threadIterationId = 0, int globalIterationId = 0) 25 | { 26 | return _factory.Create(threadId).Validate(_data, threadId, threadIterationId, globalIterationId); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Validators/ScenarioValidator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Viki.LoadRunner.Engine.Core.Collector; 3 | using Viki.LoadRunner.Engine.Core.Factory.Interfaces; 4 | 5 | namespace Viki.LoadRunner.Engine.Validators 6 | { 7 | public class ScenarioValidator : IValidator 8 | { 9 | private readonly IScenarioFactory _factory; 10 | 11 | public ScenarioValidator(IScenarioFactory factory) 12 | { 13 | if (factory == null) 14 | throw new ArgumentNullException(nameof(factory)); 15 | _factory = factory; 16 | } 17 | 18 | public IterationResult Validate(int threadId = 0, int threadIterationId = 0, int globalIterationId = 0) 19 | { 20 | // TODO: Move all logic to here 21 | return _factory.Create(threadId).Validate(threadId, threadIterationId, globalIterationId); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Engine/Viki.LoadRunner.Engine.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | true 6 | 0.8.86 7 | Viki.LoadRunner 8 | Vytautas Klumbys 9 | Generic performance testing library for executing load-tests written in .NET c# 10 | 11 | Visit project website for examples. 12 | https://github.com/Vycka/LoadRunner 13 | https://github.com/Vycka/LoadRunner 14 | Load Stress Performance Test Testing c# Runner Parallel Thread Library Framework Tool Addin .NET 15 | 2019 - 2024 16 | true 17 | true 18 | snupkg 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | true 27 | LICENSE 28 | Func Scenario factory support integrated into StrategyBuilder 29 | 30 | 31 | 32 | 33 | 34 | True 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Playground/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Playground/BlankStressScenarioJsonStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json; 3 | using Viki.LoadRunner.Engine.Aggregators; 4 | using Viki.LoadRunner.Engine.Aggregators.Dimensions; 5 | using Viki.LoadRunner.Engine.Aggregators.Metrics; 6 | using Viki.LoadRunner.Engine.Analytics; 7 | using Viki.LoadRunner.Engine.Strategies; 8 | using Viki.LoadRunner.Engine.Strategies.Custom.Strategies.Limit; 9 | using Viki.LoadRunner.Engine.Strategies.Custom.Strategies.Threading; 10 | using Viki.LoadRunner.Engine.Strategies.Extensions; 11 | using Viki.LoadRunner.Tools.Aggregators; 12 | 13 | namespace Viki.LoadRunner.Playground 14 | { 15 | public class BlankStressScenarioJsonStream 16 | { 17 | // HDD/SSD intensive, beware 18 | public static void Run() 19 | { 20 | HistogramAggregator aggregator = new HistogramAggregator() 21 | .Add(new TimeDimension(TimeSpan.FromSeconds(2))) 22 | .Add(new CountMetric()) 23 | .Add(new TransactionsPerSecMetric()); 24 | 25 | HistogramAggregator aggregagorOriginal = new HistogramAggregator() 26 | .Add(new TimeDimension(TimeSpan.FromSeconds(2))) 27 | .Add(new CountMetric()) 28 | .Add(new TransactionsPerSecMetric()); 29 | 30 | StrategyBuilder strategy = new StrategyBuilder() 31 | .SetScenario() 32 | .SetThreading(new FixedThreadCount(3)) 33 | .SetLimit(new TimeLimit(TimeSpan.FromSeconds(5))) 34 | .SetAggregator(new JsonStreamAggregator("Raw.json"), aggregagorOriginal); 35 | 36 | strategy.Build().Run(); 37 | 38 | JsonStreamAggregator.Replay("Raw.json", aggregator); 39 | 40 | Console.WriteLine(JsonConvert.SerializeObject(aggregagorOriginal.BuildResultsObjects(), Formatting.Indented)); 41 | Console.WriteLine(JsonConvert.SerializeObject(aggregator.BuildResultsObjects(), Formatting.Indented)); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Playground/FaultyAggregator.cs: -------------------------------------------------------------------------------- 1 | using Viki.LoadRunner.Engine.Core.Collector.Interfaces; 2 | 3 | namespace Viki.LoadRunner.Playground 4 | { 5 | public class FaultyAggregator : IAggregator 6 | { 7 | private readonly bool _begin, _receive, _end; 8 | 9 | public FaultyAggregator(bool begin, bool receive, bool end) 10 | { 11 | _begin = begin; 12 | _receive = receive; 13 | _end = end; 14 | } 15 | 16 | public void Begin() 17 | { 18 | if (_begin) 19 | throw new System.NotImplementedException(); 20 | } 21 | 22 | public void Aggregate(IResult result) 23 | { 24 | if (_receive) 25 | throw new System.NotImplementedException(); 26 | } 27 | 28 | public void End() 29 | { 30 | if (_end) 31 | throw new System.NotImplementedException(); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Playground/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Viki.LoadRunner.Playground.Replay; 3 | 4 | namespace Viki.LoadRunner.Playground 5 | { 6 | class Program 7 | { 8 | static void Main() 9 | { 10 | BatchAndWaitDemo.Run(); 11 | 12 | BlankScenario.Run(); 13 | 14 | AssertPipeline.Run(); 15 | 16 | TheoreticalSpeedDemo.Run(); 17 | 18 | BlankStressScenarioMemoryStream.Run(); 19 | 20 | ReplayDemo.Run(); 21 | 22 | BatchStrategyDemo.Run(); 23 | 24 | DemoSetup.Run(); 25 | 26 | LimitConcurrencyAndTpsDemo.Run(); 27 | 28 | // TODO: Validate ThreadId, ThreadIterartionId, GlobalIterationId counters 29 | // TODO: Validate validators 30 | 31 | // Warning! Hdd/sdd intensive usage with this one 32 | //BlankStressScenarioJsonStream.Run(); 33 | 34 | Console.ReadKey(); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Playground/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Viki.LoadRunner.Playground")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Viki.LoadRunner.Playground")] 13 | [assembly: AssemblyCopyright("Copyright © 2019")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("54ab3500-d553-448c-bc79-ed7886d395be")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Playground/Replay/DataGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Viki.LoadRunner.Engine.Strategies.Replay.Data; 4 | 5 | namespace Viki.LoadRunner.Playground.Replay 6 | { 7 | public class DataGenerator 8 | { 9 | public static IEnumerable Create(int bigStep, int smallStep, int bigSteps, int smallSteps) 10 | { 11 | for (int i = 0; i < bigSteps; i++) 12 | { 13 | int offset = i * bigStep; 14 | for (int j = 0; j < smallSteps; j++) 15 | { 16 | int delaySeconds = offset + smallStep * j; 17 | yield return new DataItem(TimeSpan.FromSeconds(delaySeconds), $"i:{i} j:{j} t:{delaySeconds}"); 18 | } 19 | } 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Playground/Replay/ReplayDemo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Linq; 4 | using Newtonsoft.Json; 5 | using Viki.LoadRunner.Engine; 6 | using Viki.LoadRunner.Engine.Aggregators; 7 | using Viki.LoadRunner.Engine.Aggregators.Dimensions; 8 | using Viki.LoadRunner.Engine.Aggregators.Metrics; 9 | using Viki.LoadRunner.Engine.Analytics; 10 | using Viki.LoadRunner.Engine.Strategies; 11 | using Viki.LoadRunner.Engine.Strategies.Extensions; 12 | 13 | namespace Viki.LoadRunner.Playground.Replay 14 | { 15 | public class ReplayDemo 16 | { 17 | public static void Run() 18 | { 19 | HistogramAggregator aggregator = new HistogramAggregator() 20 | .Add(new FuncDimension("Iteration", result => result.GlobalIterationId.ToString())) 21 | .Add(new FuncDimension("Time", result => result.IterationStarted.TotalSeconds.ToString(CultureInfo.InvariantCulture))) 22 | .Add(new FuncDimension("Data", result => result.UserData?.ToString())) 23 | .Add(new FuncMetric("CThreads", 0, (i, r) => r.CreatedThreads)) 24 | .Add(new FuncMetric("IThreads", 0, (i, r) => r.IdleThreads)); 25 | 26 | ReplayStrategyBuilder settings = new ReplayStrategyBuilder() 27 | .SetAggregator(aggregator) 28 | .SetData(DataGenerator.Create(5, 1, 3, 3).ToArray()) 29 | .SetScenario() 30 | .SetThreadCount(30) 31 | .SetSpeed(1); 32 | 33 | // UI 34 | //LoadRunnerUi engineUi = settings.BuildUi(new DataItem(TimeSpan.Zero, "Validation demo")); 35 | //engineUi.StartWindow(); 36 | 37 | // Non UI blocking 38 | LoadRunnerEngine engine = settings.Build(); 39 | //engine.Run(); 40 | 41 | // Non UI Async 42 | engine.RunAsync(); 43 | engine.Wait(); 44 | 45 | object defaultResults = aggregator.BuildResultsObjects(); 46 | Console.WriteLine(JsonConvert.SerializeObject(defaultResults, Formatting.Indented)); 47 | 48 | //Console.ReadKey(); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Playground/TheoreticalSpeedDemo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using Viki.LoadRunner.Engine; 4 | using Viki.LoadRunner.Engine.Strategies; 5 | using Viki.LoadRunner.Engine.Strategies.Custom.Strategies.Limit; 6 | using Viki.LoadRunner.Engine.Strategies.Custom.Strategies.Threading; 7 | using Viki.LoadRunner.Engine.Strategies.Extensions; 8 | using Viki.LoadRunner.Playground.Tools; 9 | 10 | namespace Viki.LoadRunner.Playground 11 | { 12 | public class TheoreticalSpeedDemo 13 | { 14 | public static void Run() 15 | { 16 | CountingScenarioFactory scenarioFactory = new CountingScenarioFactory(); 17 | 18 | StrategyBuilder strategy = new StrategyBuilder() 19 | .SetScenario(scenarioFactory) 20 | .SetThreading(new FixedThreadCount(4)) 21 | .SetLimit(new TimeLimit(TimeSpan.FromSeconds(10))); 22 | 23 | 24 | // Increase TimeLimit precision 25 | LoadRunnerEngine engine = strategy.Build(); 26 | engine.HeartBeatMs = 1; 27 | 28 | Stopwatch watch = new Stopwatch(); 29 | watch.Start(); 30 | engine.Run(); 31 | watch.Stop(); 32 | 33 | scenarioFactory.PrintSum(); 34 | Console.WriteLine($@"TPS {scenarioFactory.GetSum() / watch.Elapsed.TotalSeconds:N}"); 35 | Console.WriteLine(watch.Elapsed.ToString("g")); 36 | 37 | /* 38 | i5-4670 39 | 40 | Unoptimized build 41 | 0 15887088 42 | 1 13794690 43 | 2 16120714 44 | 3 13965244 45 | ------- 46 | 59767736 47 | TPS 5 873 963,09 48 | 49 | Optimized build 50 | 0:00:10,0204332 51 | 0 20196083 52 | 1 20441812 53 | 2 20276545 54 | 3 20205667 55 | ------- 56 | 81120107 57 | TPS 8 095 469,07 58 | */ 59 | } 60 | 61 | 62 | } 63 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Playground/Tools/AssertIterationIdsAggregator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Viki.LoadRunner.Engine.Core.Collector.Interfaces; 4 | 5 | namespace Viki.LoadRunner.Playground.Tools 6 | { 7 | public class AssertIterationIdsAggregator : IAggregator 8 | { 9 | 10 | private readonly SortedSet _missedOrderIds = new SortedSet(); 11 | private int _nextId; 12 | 13 | public void Begin() 14 | { 15 | _missedOrderIds.Clear(); 16 | _nextId = 0; 17 | } 18 | 19 | public void Aggregate(IResult result) 20 | { 21 | if (_nextId == result.GlobalIterationId) 22 | _nextId++; 23 | else 24 | { 25 | _missedOrderIds.Add(result.GlobalIterationId); 26 | } 27 | } 28 | 29 | public void End() 30 | { 31 | while (_missedOrderIds.Contains(_nextId)) 32 | { 33 | _missedOrderIds.Remove(_nextId); 34 | _nextId++; 35 | } 36 | } 37 | 38 | public void PrintResults() 39 | { 40 | Console.WriteLine($@"IdsValidator: _nextId: {_nextId}, _missedOrderIds:{_missedOrderIds.Count}: {String.Join(", ", _missedOrderIds)}"); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Playground/Tools/CountingScenario.cs: -------------------------------------------------------------------------------- 1 | using Viki.LoadRunner.Engine.Core.Scenario.Interfaces; 2 | 3 | namespace Viki.LoadRunner.Playground.Tools 4 | { 5 | public class CountingScenario : IScenario 6 | { 7 | public int Count = 0; 8 | public int ThreadId; 9 | 10 | public void ScenarioSetup(IIteration context) 11 | { 12 | ThreadId = context.ThreadId; 13 | } 14 | 15 | public void IterationSetup(IIteration context) 16 | { 17 | 18 | } 19 | 20 | public void ExecuteScenario(IIteration context) 21 | { 22 | Count = Count + 1; 23 | } 24 | 25 | public void IterationTearDown(IIteration context) 26 | { 27 | 28 | } 29 | 30 | public void ScenarioTearDown(IIteration context) 31 | { 32 | 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Playground/Tools/CountingScenarioFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Viki.LoadRunner.Engine.Core.Factory.Interfaces; 5 | using Viki.LoadRunner.Engine.Core.Scenario.Interfaces; 6 | 7 | namespace Viki.LoadRunner.Playground.Tools 8 | { 9 | public class CountingScenarioFactory : IScenarioFactory 10 | { 11 | List _instances = new List(); 12 | 13 | public IScenario Create(int threadId) 14 | { 15 | CountingScenario scenario = new CountingScenario(); 16 | _instances.Add(scenario); 17 | 18 | return scenario; 19 | } 20 | 21 | public void PrintSum() 22 | { 23 | string perThread = String.Join(Environment.NewLine, _instances.Select(i => $"{i.ThreadId} {i.Count}")); 24 | int sum = GetSum(); 25 | 26 | Console.WriteLine(perThread); 27 | Console.WriteLine(@"-------"); 28 | Console.WriteLine(sum); 29 | } 30 | 31 | 32 | public int GetSum() 33 | { 34 | return _instances.Sum(i => i.Count); 35 | } 36 | 37 | } 38 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Playground/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Tools.Legacy/Extensions/ControlExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using System.Reflection; 4 | using System.Windows.Forms; 5 | 6 | namespace Viki.LoadRunner.Tools.Legacy.Extensions 7 | { 8 | public static class ControlExtensions 9 | { 10 | private delegate void SetPropertyThreadSafeDelegate( 11 | Control @this, 12 | Expression> property, 13 | TResult value 14 | ); 15 | 16 | public static void SetPropertyThreadSafe( 17 | this Control @this, 18 | Expression> property, 19 | TResult value 20 | ) 21 | { 22 | var propertyInfo = (property.Body as MemberExpression).Member 23 | as PropertyInfo; 24 | 25 | if (propertyInfo == null || 26 | !@this.GetType().IsSubclassOf(propertyInfo.ReflectedType) || 27 | @this.GetType().GetProperty( 28 | propertyInfo.Name, 29 | propertyInfo.PropertyType) == null) 30 | { 31 | throw new ArgumentException("The lambda expression 'property' must reference a valid property on this Control."); 32 | } 33 | 34 | if (@this.InvokeRequired) 35 | { 36 | @this.Invoke(new SetPropertyThreadSafeDelegate 37 | (SetPropertyThreadSafe), 38 | new object[] { @this, property, value }); 39 | } 40 | else 41 | { 42 | @this.GetType().InvokeMember( 43 | propertyInfo.Name, 44 | BindingFlags.SetProperty, 45 | null, 46 | @this, 47 | new object[] { value }); 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Tools.Legacy/Extensions/StrategyBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Viki.LoadRunner.Engine.Strategies.Extensions; 3 | using Viki.LoadRunner.Engine.Strategies.Interfaces; 4 | using Viki.LoadRunner.Engine.Validators; 5 | using Viki.LoadRunner.Tools.Legacy.Windows; 6 | 7 | namespace Viki.LoadRunner.Tools.Legacy.Extensions 8 | { 9 | public static class StrategyBuilderExtensions 10 | { 11 | public static LoadRunnerUi BuildUi(this IStrategyBuilder builder, IValidator validator = null) 12 | { 13 | if (!(builder is IAggregatorFeature)) 14 | throw new ArgumentException("Strategy builder must support IAggrregatorFeature"); 15 | 16 | LoadRunnerUi ui = new LoadRunnerUi(); 17 | IAggregatorFeature localBuilder = (IAggregatorFeature)builder.ShallowCopy(); 18 | 19 | localBuilder.AddAggregator(ui); 20 | 21 | ui.Setup(localBuilder.BuildStrategy(), validator); 22 | 23 | return ui; 24 | } 25 | 26 | 27 | } 28 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Tools.Legacy/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Viki.LoadRunner.Tools.Legacy")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Viki.LoadRunner.Tools.Legacy")] 13 | [assembly: AssemblyCopyright("Copyright © 2019")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("d4d5978e-a6ef-4947-8ad3-d465d3295eb5")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Tools.Legacy/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Tools/Aggregators/BsonStreamAggregator.cs: -------------------------------------------------------------------------------- 1 | namespace Viki.LoadRunner.Tools.Aggregators 2 | { 3 | //public class BsonStreamAggregator : FileStreamAggregatorBase 4 | //{ 5 | // #region Constructors 6 | 7 | // public BsonStreamAggregator(string jsonOutputfile) : base(jsonOutputfile) 8 | // { 9 | // } 10 | 11 | // public BsonStreamAggregator(Func dynamicJsonOutputFile) : base(dynamicJsonOutputFile) 12 | // { 13 | // } 14 | 15 | // #endregion 16 | 17 | // #region FileStreamAggregatorBase Write() 18 | 19 | // public override void Write(string filePath, IEnumerable stream) 20 | // { 21 | // stream.SerializeToBson(filePath); 22 | // } 23 | 24 | // #endregion 25 | 26 | // #region Replay functions 27 | 28 | // public static void Replay(string jsonResultsFile, params IAggregator[] targetAggregators) 29 | // { 30 | // Replay(jsonResultsFile, targetAggregators); 31 | // } 32 | 33 | // public static void Replay(string jsonResultsFile, params IAggregator[] targetAggregators) 34 | // { 35 | // IEnumerable resultsStream = Load(jsonResultsFile); 36 | 37 | // StreamAggregator.Replay(resultsStream, targetAggregators); 38 | // } 39 | 40 | // public static IEnumerable> Load(string jsonResultsFile) 41 | // { 42 | // return JsonStream.DeserializeFromBson>(jsonResultsFile); 43 | // } 44 | 45 | // #endregion 46 | //} 47 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Tools/Aggregators/FileStreamAggregator.cs: -------------------------------------------------------------------------------- 1 | namespace Viki.LoadRunner.Tools.Aggregators 2 | { 3 | //public abstract class FileStreamAggregator : StreamAggregator 4 | //{ 5 | // #region Fields 6 | 7 | // private readonly Func _outFileNameFunc; 8 | 9 | // #endregion 10 | 11 | // #region Constructors 12 | 13 | // protected FileStreamAggregator(string filePath) 14 | // : this(() => filePath) 15 | // { 16 | // } 17 | 18 | // protected FileStreamAggregator(Func filePathSelector) 19 | // : base(results => Write( results, filePathSelector)) 20 | // { 21 | // _outFileNameFunc = filePathSelector; 22 | // } 23 | 24 | // #endregion 25 | 26 | // private static void Write(FileStreamAggregator aggregator, IEnumerable results, Func filePathSelector) 27 | // { 28 | // string filePath = filePathSelector(); 29 | 30 | // aggregator.Write(results, filePath); 31 | // } 32 | 33 | // #region Default write function 34 | 35 | // private void StreamWriterFunction(IEnumerable results) 36 | // { 37 | // string fileName = _outFileNameFunc(); 38 | 39 | // results.SerializeToBson(fileName); 40 | // } 41 | 42 | // #endregion 43 | 44 | // #region Read/Write abstracts 45 | 46 | // protected abstract IEnumerable Read(string filePath); 47 | 48 | // protected abstract void Write(IEnumerable items, string filePath); 49 | 50 | // #endregion 51 | //} 52 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Tools/Aggregators/JsonStreamAggregator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Viki.LoadRunner.Engine.Aggregators; 4 | using Viki.LoadRunner.Engine.Aggregators.Utils; 5 | using Viki.LoadRunner.Engine.Core.Collector.Interfaces; 6 | using Viki.LoadRunner.Tools.Extensions; 7 | 8 | namespace Viki.LoadRunner.Tools.Aggregators 9 | { 10 | public class JsonStreamAggregator : FileStreamAggregatorBase 11 | { 12 | #region Constructors 13 | 14 | public JsonStreamAggregator(string jsonOutputfile) : base(jsonOutputfile) 15 | { 16 | } 17 | 18 | public JsonStreamAggregator(Func dynamicJsonOutputFile) : base(dynamicJsonOutputFile) 19 | { 20 | } 21 | 22 | #endregion 23 | 24 | #region FileStreamAggregatorBase Write() 25 | 26 | public override void Write(string filePath, IEnumerable stream) 27 | { 28 | stream.SerializeToJson(filePath); 29 | } 30 | 31 | #endregion 32 | 33 | #region Replay functions 34 | 35 | public static void Replay(string jsonResultsFile, params IAggregator[] targetAggregators) 36 | { 37 | Replay(jsonResultsFile, targetAggregators); 38 | } 39 | 40 | public static void Replay(string jsonResultsFile, params IAggregator[] targetAggregators) 41 | { 42 | IEnumerable resultsStream = Load(jsonResultsFile); 43 | 44 | StreamAggregator.Replay(resultsStream, targetAggregators); 45 | } 46 | 47 | public static IEnumerable> Load(string jsonResultsFile) 48 | { 49 | return JsonStream.DeserializeFromJson>(jsonResultsFile); 50 | } 51 | 52 | #endregion 53 | } 54 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Tools/ConsoleUi/KpiPrinterAggregator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Timers; 5 | using Viki.LoadRunner.Engine.Analytics; 6 | using Viki.LoadRunner.Engine.Analytics.Interfaces; 7 | using Viki.LoadRunner.Engine.Analytics.Viki.LoadRunner.Engine.Aggregators.Utils; 8 | using Viki.LoadRunner.Engine.Core.Collector.Interfaces; 9 | 10 | namespace Viki.LoadRunner.Tools.ConsoleUi 11 | { 12 | public class KpiPrinterAggregator : IAggregator 13 | { 14 | private readonly TimeSpan _interval; 15 | 16 | private MetricsHandler _metrics; 17 | 18 | public Action> OutputAction = (r) => Console.Out.WriteLine( 19 | String.Join( 20 | Environment.NewLine, 21 | r.Select(kv => String.Concat(" * ", kv.Key, ": ", kv.Value)) 22 | ) + Environment.NewLine 23 | ); 24 | 25 | private Timer _timer; 26 | 27 | public KpiPrinterAggregator(TimeSpan interval, params IMetric[] metrics) 28 | { 29 | _metrics = new MetricsHandler(metrics); 30 | _interval = interval; 31 | } 32 | 33 | 34 | public void Begin() 35 | { 36 | _metrics = _metrics.Create(); 37 | 38 | _timer = new Timer(_interval.TotalMilliseconds); 39 | _timer.AutoReset = true; 40 | _timer.Elapsed += (sender, args) => Output(); 41 | 42 | _timer.Start(); 43 | } 44 | 45 | public void Aggregate(IResult result) 46 | { 47 | _metrics.Add(result); 48 | } 49 | 50 | public void End() 51 | { 52 | _timer.Stop(); 53 | _timer.Dispose(); 54 | _timer = null; 55 | } 56 | 57 | private void Output() 58 | { 59 | MetricsHandler current = _metrics; 60 | _metrics = _metrics.Create(); 61 | 62 | 63 | OutputAction(current.Export()); 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Tools/Extensions/DictionaryExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Viki.LoadRunner.Tools.Extensions 4 | { 5 | public static class DictionaryExtensions 6 | { 7 | public static TValue GetOrNull(this IDictionary dictionary, TKey key) 8 | { 9 | dictionary.TryGetValue(key, out var result); 10 | 11 | return result; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Tools/Strategy/GenericWork.cs: -------------------------------------------------------------------------------- 1 | //using System; 2 | //using Viki.LoadRunner.Engine.Core.Worker.Interfaces; 3 | 4 | //namespace Viki.LoadRunner.Tools.Strategy 5 | //{ 6 | // public class GenericWork : IWork 7 | // { 8 | // public void Init() 9 | // { 10 | // throw new NotImplementedException(); 11 | // } 12 | 13 | // public void Execute(ref bool stop) 14 | // { 15 | 16 | // // 2 Delegates F() and F(ref stop) 17 | // while (!stop) 18 | // { 19 | 20 | // } 21 | // } 22 | 23 | // public void Cleanup() 24 | // { 25 | // throw new NotImplementedException(); 26 | // } 27 | // } 28 | //} -------------------------------------------------------------------------------- /src/Viki.LoadRunner.Tools/Viki.LoadRunner.Tools.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | 0.8.50 6 | true 7 | Vytautas Klumbys 8 | Load performance Test Tester c# Runner Parallel Thread Client Library Framework Tool Addin .NET utils 9 | Additional utils for Viki.LoadRunner (WIP) 10 | Since .NET Standard switch, BuildUi() feature was cut from this nuget and placed into Viki.LoadRunner.Tools.Legacy 11 | 2019 - 2024 12 | https://github.com/Vycka/LoadRunner 13 | https://github.com/Vycka/LoadRunner 14 | true 15 | true 16 | snupkg 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | true 25 | LICENSE 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | True 43 | 44 | 45 | 46 | 47 | 48 | --------------------------------------------------------------------------------