647 lines
23 KiB
C#
647 lines
23 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Linq.Expressions;
|
|
using System.Net;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using System.CodeDom.Compiler;
|
|
using Newtonsoft.Json;
|
|
using Newtonsoft.Json.Linq;
|
|
|
|
using NLog;
|
|
using NLog.Config;
|
|
using NLog.Targets;
|
|
using ServiceStack.Text.Jsv;
|
|
using TimberWinR.Parser;
|
|
|
|
|
|
namespace TimberWinR.TestGenerator
|
|
{
|
|
public class Program
|
|
{
|
|
private static List<Task> _tasks = new List<Task>();
|
|
private static CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
|
|
private static Manager _timberWinR;
|
|
|
|
public static Diagnostics.Diagnostics Diagnostics { get; set; }
|
|
|
|
private static PerformanceCounter cpuCounter = new PerformanceCounter();
|
|
private static PerformanceCounter ramCounter = new PerformanceCounter();
|
|
private static Task _monitorTask;
|
|
|
|
private static int _totalMessagesToSend;
|
|
private static int _cpuSampleCount;
|
|
private static double _avgCpuUsage;
|
|
private static double _totalCpuUsage;
|
|
private static double _maxCpuUsage;
|
|
|
|
private static int _memSampleCount;
|
|
private static double _avgMemUsage;
|
|
private static double _totalMemUsage;
|
|
private static double _maxMemUsage;
|
|
|
|
private static CommandLineOptions Options;
|
|
|
|
static int Main(string[] args)
|
|
{
|
|
_totalMessagesToSend = 0;
|
|
|
|
cpuCounter.CategoryName = "Processor";
|
|
cpuCounter.CounterName = "% Processor Time";
|
|
cpuCounter.InstanceName = "_Total";
|
|
|
|
ramCounter.CategoryName = "Memory";
|
|
ramCounter.CounterName = "% Committed Bytes In Use";
|
|
|
|
Options = new CommandLineOptions();
|
|
|
|
if (CommandLine.Parser.Default.ParseArguments(args, Options))
|
|
{
|
|
var testFile = Options.TestFile;
|
|
if (!string.IsNullOrEmpty(testFile))
|
|
{
|
|
if (!File.Exists(Options.TestFile))
|
|
throw new Exception(string.Format("No such test file: {0} found", Options.TestFile));
|
|
|
|
var fargs = ParseTestArguments(testFile, ref Options);
|
|
if (!CommandLine.Parser.Default.ParseArguments(fargs, Options))
|
|
return 2;
|
|
}
|
|
|
|
SetupTestDirectory(Options);
|
|
|
|
var swOverall = Stopwatch.StartNew();
|
|
swOverall.Start();
|
|
|
|
InitializeLogging(Options.LogLevel);
|
|
|
|
LogManager.GetCurrentClassLogger().Info("Starting CPU Usage: {0}, RAM Usage: {1}", getCurrentCpuUsage(), getAvailableRAM());
|
|
|
|
// Reset the tests.
|
|
ResetTests(Options);
|
|
|
|
var sw = Stopwatch.StartNew();
|
|
|
|
// Startup TimberWinR
|
|
if (Options.StartTimberWinR)
|
|
StartTimberWinR(Options.TimberWinRConfigFile, Options.LogLevel, ".", false);
|
|
|
|
// Run the Generators
|
|
var arrayOfTasks = RunGenerators(Options);
|
|
|
|
// Wait for all Generators to finish
|
|
try
|
|
{
|
|
Task.WaitAll(arrayOfTasks);
|
|
}
|
|
catch (AggregateException aex)
|
|
{
|
|
LogManager.GetCurrentClassLogger().Error(aex);
|
|
}
|
|
|
|
|
|
LogManager.GetCurrentClassLogger().Info("Generation Finished: " + sw.Elapsed);
|
|
sw.Reset();
|
|
sw.Start();
|
|
|
|
// All generators are finished, wait till senders are done.
|
|
WaitForOutputTransmission();
|
|
|
|
LogManager.GetCurrentClassLogger().Info("Finished Transmission: " + sw.Elapsed);
|
|
sw.Reset();
|
|
sw.Start();
|
|
|
|
// Get all the stats
|
|
JObject jsonTimberWinr;
|
|
|
|
if (Options.StartTimberWinR)
|
|
jsonTimberWinr = ShutdownTimberWinR();
|
|
else
|
|
{
|
|
jsonTimberWinr = GetDiagnosticsOutput();
|
|
if (jsonTimberWinr == null)
|
|
return 3;
|
|
}
|
|
|
|
LogManager.GetCurrentClassLogger().Info("Finished Shutdown: " + sw.Elapsed);
|
|
sw.Stop();
|
|
|
|
swOverall.Stop();
|
|
LogManager.GetCurrentClassLogger().Info("Total Elapsed Time: {0}", swOverall.Elapsed);
|
|
|
|
int results = VerifyResults(Options, jsonTimberWinr);
|
|
|
|
Console.ReadKey();
|
|
return results;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
public static string GET(string url)
|
|
{
|
|
try
|
|
{
|
|
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
|
|
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
|
|
Stream stream = response.GetResponseStream();
|
|
StreamReader reader = new StreamReader(stream);
|
|
|
|
string data = reader.ReadToEnd();
|
|
|
|
reader.Close();
|
|
stream.Close();
|
|
|
|
return data;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
LogManager.GetCurrentClassLogger().ErrorException("Error in GET", e);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static void CopySourceFile(string fileName, string outputDir)
|
|
{
|
|
FileInfo fi = new FileInfo(fileName);
|
|
if (fi.Exists)
|
|
File.Copy(fileName, Path.Combine(outputDir, fi.Name));
|
|
}
|
|
|
|
private static void SetupTestDirectory(CommandLineOptions options)
|
|
{
|
|
if (options.TestDir != "." && Directory.Exists(options.TestDir))
|
|
Directory.Delete(options.TestDir, true);
|
|
|
|
if (!Directory.Exists(options.TestDir))
|
|
Directory.CreateDirectory(options.TestDir);
|
|
|
|
CopySourceFile(options.TestFile, options.TestDir);
|
|
CopySourceFile(options.TimberWinRConfigFile, options.TestDir);
|
|
CopySourceFile(options.ExpectedResultsFile, options.TestDir);
|
|
|
|
Directory.SetCurrentDirectory(options.TestDir);
|
|
}
|
|
|
|
private static string[] ParseTestArguments(string testFile, ref CommandLineOptions options)
|
|
{
|
|
options = new CommandLineOptions();
|
|
JObject jtest = JObject.Parse(File.ReadAllText(testFile));
|
|
IList<JToken> inputs = jtest["arguments"].Children().ToList();
|
|
List<string> testargs = new List<string>();
|
|
foreach (JProperty it in inputs)
|
|
{
|
|
testargs.Add(it.Name);
|
|
|
|
var cc = it.Value.Children().Count();
|
|
if (cc > 0)
|
|
{
|
|
for (int i = 0; i < cc; i++)
|
|
{
|
|
testargs.Add(it.Value[i].ToString());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
testargs.Add(it.Value.ToString());
|
|
}
|
|
}
|
|
var fargs = testargs.ToArray();
|
|
return fargs;
|
|
}
|
|
|
|
private static int VerifyResults(CommandLineOptions options, JObject json)
|
|
{
|
|
var jresult = JObject.Parse(File.ReadAllText(options.ExpectedResultsFile));
|
|
|
|
json["maxCpuUsage"] = _maxCpuUsage;
|
|
json["avgCpuUsage"] = _avgCpuUsage;
|
|
|
|
json["maxMemUsage"] = _maxMemUsage;
|
|
json["avgMemUsage"] = _avgMemUsage;
|
|
|
|
// TailLogs
|
|
|
|
IList<JToken> inputs = json["timberwinr"]["inputs"].Children().ToList();
|
|
foreach (JToken t in inputs)
|
|
{
|
|
JProperty inputProp = t.First as JProperty;
|
|
switch (inputProp.Name)
|
|
{
|
|
case "udp":
|
|
if (VerifyConditions(json, new string[] { "udp" }, inputProp, jresult) != 0)
|
|
return 1;
|
|
break;
|
|
case "tcp":
|
|
if (VerifyConditions(json, new string[] { "tcp" }, inputProp, jresult) != 0)
|
|
return 1;
|
|
break;
|
|
case "log":
|
|
case "taillog":
|
|
if (VerifyConditions(json, new string[] { "log", "taillog" }, inputProp, jresult) != 0)
|
|
return 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
private static int VerifyConditions(JObject json, string[] logTypes, JProperty inputProp, JObject jresult)
|
|
{
|
|
var ttail = inputProp.Value as JObject;
|
|
foreach (var resultInput in jresult["Results"]["Inputs"].Children().ToList())
|
|
{
|
|
JProperty rinputProp = resultInput.First as JProperty;
|
|
if (logTypes.Contains(rinputProp.Name))
|
|
{
|
|
foreach (JProperty testProp in rinputProp.Value)
|
|
{
|
|
try
|
|
{
|
|
var cond1 = testProp.Value.ToString();
|
|
IList<string> tkeys = ttail.Properties().Select(pn => pn.Name).ToList();
|
|
foreach (string tkey in tkeys)
|
|
cond1 = cond1.Replace(string.Format("[{0}]", tkey), string.Format("{0}", ttail[tkey].ToString()));
|
|
|
|
// Add builtins
|
|
cond1 = cond1.Replace("[avgCpuUsage]", json["avgCpuUsage"].ToString());
|
|
cond1 = cond1.Replace("[maxCpuUsage]", json["maxCpuUsage"].ToString());
|
|
cond1 = cond1.Replace("[avgMemUsage]", json["avgMemUsage"].ToString());
|
|
cond1 = cond1.Replace("[maxMemUsage]", json["maxMemUsage"].ToString());
|
|
|
|
var p1 = Expression.Parameter(typeof(JObject), "json");
|
|
var e1 = System.Linq.Dynamic.DynamicExpression.ParseLambda(new[] { p1 },
|
|
typeof(bool), cond1);
|
|
bool r1 = (bool)e1.Compile().DynamicInvoke(ttail);
|
|
if (!r1)
|
|
{
|
|
LogManager.GetCurrentClassLogger().Error("Test Failed: '{0}: ({1})'", testProp.Name, cond1);
|
|
return 1;
|
|
|
|
}
|
|
else
|
|
{
|
|
LogManager.GetCurrentClassLogger()
|
|
.Info("PASSED({0}): '{1}: ({2})'", inputProp.Name, testProp.Name, cond1);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogManager.GetCurrentClassLogger()
|
|
.Error("Error parsing expression '{0}': {1}", testProp.Value.ToString(),
|
|
ex.Message);
|
|
return 2;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
private static JObject GetDiagnosticsOutput()
|
|
{
|
|
if (Diagnostics != null)
|
|
return Diagnostics.DiagnosticsOutput();
|
|
else
|
|
{
|
|
var jsonDiag = GET("http://localhost:5141");
|
|
if (jsonDiag == null)
|
|
{
|
|
LogManager.GetCurrentClassLogger().Error("TimberWinR diagnostics port not responding.");
|
|
return null;
|
|
}
|
|
return JObject.Parse(jsonDiag);
|
|
}
|
|
}
|
|
|
|
// Wait till all output has been transmitted.
|
|
private static void WaitForOutputTransmission()
|
|
{
|
|
bool completed = false;
|
|
do
|
|
{
|
|
var json = GetDiagnosticsOutput();
|
|
if (json == null)
|
|
return;
|
|
|
|
//Console.WriteLine(json.ToString(Formatting.Indented));
|
|
|
|
IList<JToken> inputs = json["timberwinr"]["inputs"].Children().ToList();
|
|
foreach (var so in inputs.Children())
|
|
{
|
|
var token = so.First;
|
|
var messages = token["messages"].Value<int>();
|
|
// Console.WriteLine("{0} messages", messages);
|
|
}
|
|
|
|
|
|
IList<JToken> outputs = json["timberwinr"]["outputs"].Children().ToList();
|
|
foreach (var so in outputs.Children())
|
|
{
|
|
var outputToken = so.First;
|
|
|
|
var mbc = outputToken["queuedMessageCount"].Value<int>();
|
|
var smc = outputToken["sentMessageCount"].Value<int>();
|
|
|
|
//LogManager.GetCurrentClassLogger().Info("Output: {2} Queued: {0}, Sent: {1}", mbc, smc, outputToken.ToString());
|
|
|
|
completed = mbc == 0 && smc >= _totalMessagesToSend;
|
|
}
|
|
Thread.Sleep(250);
|
|
} while (!completed);
|
|
}
|
|
|
|
private static void sampleUsages()
|
|
{
|
|
getCurrentCpuUsage();
|
|
getAvailableRAM();
|
|
}
|
|
|
|
private static string getCurrentCpuUsage()
|
|
{
|
|
_cpuSampleCount++;
|
|
var v = cpuCounter.NextValue();
|
|
if (v > _maxCpuUsage)
|
|
_maxCpuUsage = v;
|
|
|
|
_totalCpuUsage += v;
|
|
_avgCpuUsage = _totalCpuUsage / _cpuSampleCount;
|
|
|
|
return v + "%";
|
|
}
|
|
|
|
private static string getAvailableRAM()
|
|
{
|
|
_memSampleCount++;
|
|
var v = ramCounter.NextValue();
|
|
if (v > _maxMemUsage)
|
|
_maxMemUsage = v;
|
|
|
|
_totalMemUsage += v;
|
|
_avgMemUsage = _totalMemUsage / _memSampleCount;
|
|
return v + "MB";
|
|
}
|
|
|
|
private static JObject ShutdownTimberWinR()
|
|
{
|
|
if (_timberWinR != null)
|
|
{
|
|
// Cancel any/all other threads
|
|
_cancellationTokenSource.Cancel();
|
|
|
|
_timberWinR.Shutdown();
|
|
|
|
var json = Diagnostics.DiagnosticsOutput();
|
|
|
|
LogManager.GetCurrentClassLogger()
|
|
.Info("Average CPU Usage: {0}%, Average RAM Usage: {1}MB, Max CPU: {2}%, Max Mem: {3}MB",
|
|
_avgCpuUsage, _avgMemUsage, _maxCpuUsage, _maxMemUsage);
|
|
|
|
LogManager.GetCurrentClassLogger().Info(json.ToString());
|
|
|
|
Diagnostics.Shutdown();
|
|
|
|
return json;
|
|
}
|
|
|
|
return new JObject();
|
|
}
|
|
|
|
static void StartTimberWinR(string configFile, string logLevel, string logFileDir, bool enableLiveMonitor)
|
|
{
|
|
_timberWinR = new TimberWinR.Manager(configFile, logLevel, logFileDir, enableLiveMonitor, _cancellationTokenSource.Token, false);
|
|
_timberWinR.OnConfigurationProcessed += TimberWinROnOnConfigurationProcessed;
|
|
_timberWinR.Start(_cancellationTokenSource.Token);
|
|
Diagnostics = new Diagnostics.Diagnostics(_timberWinR, _cancellationTokenSource.Token, 5141);
|
|
}
|
|
|
|
private static void TimberWinROnOnConfigurationProcessed(Configuration configuration)
|
|
{
|
|
if (!string.IsNullOrEmpty(Options.RedisHost) && configuration.RedisOutputs != null && configuration.RedisOutputs.Count() > 0)
|
|
{
|
|
foreach (var ro in configuration.RedisOutputs)
|
|
{
|
|
ro.Host = new string[] { Options.RedisHost };
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
static void InitializeLogging(string logLevel)
|
|
{
|
|
var loggingConfiguration = new LoggingConfiguration();
|
|
|
|
// Create our default targets
|
|
var coloredConsoleTarget = new ColoredConsoleTarget();
|
|
|
|
var logFileDir = ".";
|
|
|
|
Target fileTarget = CreateDefaultFileTarget(logFileDir);
|
|
|
|
loggingConfiguration.AddTarget("Console", coloredConsoleTarget);
|
|
loggingConfiguration.AddTarget("DailyFile", fileTarget);
|
|
|
|
// The LogLevel.Trace means has to be at least Trace to show up on console
|
|
loggingConfiguration.LoggingRules.Add(new LoggingRule("*", LogLevel.Trace, coloredConsoleTarget));
|
|
// LogLevel.Debug means has to be at least Debug to show up in logfile
|
|
loggingConfiguration.LoggingRules.Add(new LoggingRule("*", LogLevel.Debug, fileTarget));
|
|
|
|
LogManager.Configuration = loggingConfiguration;
|
|
LogManager.EnableLogging();
|
|
|
|
LogManager.GlobalThreshold = LogLevel.FromString(logLevel);
|
|
}
|
|
|
|
static FileTarget CreateDefaultFileTarget(string logPath)
|
|
{
|
|
return new FileTarget
|
|
{
|
|
ArchiveEvery = FileArchivePeriod.None,
|
|
ArchiveAboveSize = 5 * 1024 * 1024,
|
|
MaxArchiveFiles = 5,
|
|
BufferSize = 10,
|
|
FileName = Path.Combine(logPath, "TimberWinR.TestGenerator", "TimberWinRTestGen.log"),
|
|
ArchiveFileName = Path.Combine(logPath, "TimberWinR-TestGenerator_log-{#######}.log"),
|
|
};
|
|
}
|
|
|
|
static void ResetTests(CommandLineOptions options)
|
|
{
|
|
if (File.Exists(".timberwinrdb"))
|
|
File.Delete(".timberwinrdb");
|
|
|
|
if (File.Exists("TimberWinR.TestGenerator\\TimberWinRTestGen.log"))
|
|
File.Delete("TimberWinR.TestGenerator\\TimberWinRTestGen.log");
|
|
|
|
if (File.Exists("TimberWinR\\TimberWinR.log"))
|
|
File.Delete("TimberWinR\\TimberWinR.log");
|
|
|
|
if (options.JsonLogFiles.Length > 0)
|
|
{
|
|
foreach (var logFile in options.JsonLogFiles)
|
|
{
|
|
if (File.Exists(logFile))
|
|
File.Delete(logFile);
|
|
}
|
|
}
|
|
|
|
if (options.JsonRollingLogFiles.Length > 0)
|
|
{
|
|
foreach (var logFile in options.JsonRollingLogFiles)
|
|
{
|
|
if (File.Exists(logFile))
|
|
File.Delete(logFile);
|
|
}
|
|
}
|
|
}
|
|
|
|
static Task[] RunGenerators(CommandLineOptions options)
|
|
{
|
|
_totalMessagesToSend = options.TotalMessages;
|
|
|
|
_monitorTask = Task.Factory.StartNew(() =>
|
|
{
|
|
using (var syncHandle = new ManualResetEventSlim())
|
|
{
|
|
try
|
|
{
|
|
// Execute the query
|
|
while (!_cancellationTokenSource.Token.IsCancellationRequested)
|
|
{
|
|
sampleUsages();
|
|
// LogManager.GetCurrentClassLogger().Info("Starting CPU Usage: {0}, RAM Usage: {1}", getCurrentCpuUsage(), getAvailableRAM());
|
|
syncHandle.Wait(TimeSpan.FromMilliseconds(options.JsonRate), _cancellationTokenSource.Token);
|
|
}
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogManager.GetCurrentClassLogger().Error(ex);
|
|
}
|
|
}
|
|
}, _cancellationTokenSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Current);
|
|
|
|
StartJson(options);
|
|
StartJsonRolling(options);
|
|
StartUdp(options);
|
|
StartTcp(options);
|
|
|
|
return _tasks.ToArray();
|
|
}
|
|
|
|
static void StartJson(CommandLineOptions options)
|
|
{
|
|
if (options.JsonLogFiles.Length > 0)
|
|
{
|
|
foreach (var logFile in options.JsonLogFiles)
|
|
{
|
|
_totalMessagesToSend += options.NumMessages;
|
|
|
|
if (options.Verbose)
|
|
LogManager.GetCurrentClassLogger()
|
|
.Info("Starting LogFile Generator for {0}",
|
|
Path.GetFullPath(Path.Combine(options.JsonLogDir, logFile)));
|
|
_tasks.Add(Task.Factory.StartNew(() =>
|
|
{
|
|
var p = new JsonLogFileTestParameters()
|
|
{
|
|
NumMessages = options.NumMessages,
|
|
LogFileDir = options.JsonLogDir,
|
|
LogFileName = logFile,
|
|
SleepTimeMilliseconds = options.JsonRate
|
|
};
|
|
JsonLogFileGenerator.Generate(p);
|
|
Thread.Sleep(250);
|
|
}));
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void StartJsonRolling(CommandLineOptions options)
|
|
{
|
|
if (options.JsonRollingLogFiles.Length > 0)
|
|
{
|
|
foreach (var logFile in options.JsonRollingLogFiles)
|
|
{
|
|
_totalMessagesToSend += options.NumMessages;
|
|
|
|
if (options.Verbose)
|
|
LogManager.GetCurrentClassLogger()
|
|
.Info("Starting RollingLogFile Generator for {0}",
|
|
Path.GetFullPath(Path.Combine(options.JsonLogDir, logFile)));
|
|
_tasks.Add(Task.Factory.StartNew(() =>
|
|
{
|
|
var p = new JsonLogFileTestParameters()
|
|
{
|
|
NumMessages = options.NumMessages,
|
|
LogFileDir = options.JsonLogDir,
|
|
LogFileName = logFile,
|
|
SleepTimeMilliseconds = options.JsonRate
|
|
};
|
|
JsonRollingLogFileGenerator.Generate(p);
|
|
Thread.Sleep(250);
|
|
}));
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
static void StartUdp(CommandLineOptions options)
|
|
{
|
|
if (options.Udp > 0)
|
|
{
|
|
if (options.Verbose)
|
|
LogManager.GetCurrentClassLogger()
|
|
.Info("Starting UDP Generator for {0}:{1}", options.UdpHost, options.Udp);
|
|
|
|
_tasks.Add(Task.Factory.StartNew(() =>
|
|
{
|
|
var p = new UdpTestParameters()
|
|
{
|
|
Port = options.Udp,
|
|
Host = options.UdpHost,
|
|
NumMessages = options.NumMessages,
|
|
SleepTimeMilliseconds = options.UdpRate
|
|
};
|
|
_totalMessagesToSend += UdpTestGenerator.Generate(p);
|
|
}));
|
|
}
|
|
}
|
|
|
|
static void StartTcp(CommandLineOptions options)
|
|
{
|
|
if (options.Tcp > 0)
|
|
{
|
|
if (options.Verbose)
|
|
LogManager.GetCurrentClassLogger()
|
|
.Info("Starting Tcp Generator for {0}:{1}", options.TcpHost, options.Tcp);
|
|
|
|
_totalMessagesToSend += options.NumMessages;
|
|
|
|
_tasks.Add(Task.Factory.StartNew(() =>
|
|
{
|
|
var p = new TcpTestParameters()
|
|
{
|
|
Port = options.Tcp,
|
|
Host = options.TcpHost,
|
|
NumMessages = options.NumMessages,
|
|
SleepTimeMilliseconds = options.TcpRate
|
|
};
|
|
TcpTestGenerator.Generate(p);
|
|
}));
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|