diff --git a/.nuget/packages.config b/.nuget/packages.config
index 7025a72..a7df95c 100644
--- a/.nuget/packages.config
+++ b/.nuget/packages.config
@@ -1,4 +1,4 @@

-
+
\ No newline at end of file
diff --git a/TimberWinR.ServiceHost/TimberWinR.ServiceHost.csproj b/TimberWinR.ServiceHost/TimberWinR.ServiceHost.csproj
index 7ff089c..f789e38 100644
--- a/TimberWinR.ServiceHost/TimberWinR.ServiceHost.csproj
+++ b/TimberWinR.ServiceHost/TimberWinR.ServiceHost.csproj
@@ -49,9 +49,9 @@
-
+
False
- ..\packages\Topshelf.3.1.3\lib\net40-full\Topshelf.dll
+ ..\packages\Topshelf.3.1.4\lib\net40-full\Topshelf.dll
diff --git a/TimberWinR.ServiceHost/default.json b/TimberWinR.ServiceHost/default.json
index 34665b7..12dfc62 100644
--- a/TimberWinR.ServiceHost/default.json
+++ b/TimberWinR.ServiceHost/default.json
@@ -18,7 +18,7 @@
"Filters": [
{
"grok": {
- "condition": "[EventTypeName] == \"Information Event\"",
+ "condition": "\"[EventTypeName]\" == \"Information Event\"",
"match": [
"Text",
""
diff --git a/TimberWinR.TestGenerator/CommandLineOptions.cs b/TimberWinR.TestGenerator/CommandLineOptions.cs
new file mode 100644
index 0000000..81fc217
--- /dev/null
+++ b/TimberWinR.TestGenerator/CommandLineOptions.cs
@@ -0,0 +1,82 @@
+using System.ComponentModel;
+using CommandLine;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using CommandLine.Text;
+
+namespace TimberWinR.TestGenerator
+{
+ class CommandLineOptions
+ {
+ // [Option('r', "read", Required = true, HelpText = "Input file to be processed.")]
+ // public string InputFile { get; set; }
+ [Option("timberWinRConfig", DefaultValue = "default.json", HelpText = "Config file/directory to use")]
+ public string TimberWinRConfigFile { get; set; }
+
+ [Option("testDir", DefaultValue = ".", HelpText = "Test directory to use (created if necessary)")]
+ public string TestDir { get; set; }
+
+ [Option("testFile", DefaultValue = "", HelpText = "Config file/directory to use")]
+ public string TestFile { get; set; }
+
+ [Option("resultsFile", HelpText = "Expected results Results json file")]
+ public string ExpectedResultsFile { get; set; }
+
+ [Option('n', "numMessages", DefaultValue = 1000, HelpText = "The number of messages to send to the output(s)")]
+ public int NumMessages { get; set; }
+
+ [Option('l', "logLevel", DefaultValue = "debug", HelpText = "Logging Level Debug|Error|Fatal|Info|Off|Trace|Warn")]
+ public string LogLevel { get; set; }
+
+ [Option('v', "verbose", DefaultValue = true, HelpText = "Prints all messages to standard output.")]
+ public bool Verbose { get; set; }
+
+ [Option("jsonLogDir", DefaultValue = ".", HelpText = "Json LogGenerator Log directory")]
+ public string JsonLogDir { get; set; }
+
+ [OptionArray('j', "json", DefaultValue = new string[] {})]
+ public string[] JsonLogFiles { get; set; }
+
+ [OptionArray("jroll", DefaultValue = new string[] { })]
+ public string[] JsonRollingLogFiles { get; set; }
+
+ [Option("jsonRate", DefaultValue = 30, HelpText = "Json Rate in Milliseconds between generation of log lines")]
+ public int JsonRate { get; set; }
+
+ [Option('u', "udp", DefaultValue = 0, HelpText = "Enable UDP generator on this Port")]
+ public int Udp { get; set; }
+
+ [Option("udp-host", DefaultValue = "localhost", HelpText = "Host to send Udp data to")]
+ public string UdpHost { get; set; }
+
+ [Option("udp-rate", DefaultValue = 10, HelpText = "Udp Rate in Milliseconds between generation of log lines")]
+ public int UdpRate { get; set; }
+
+ [Option('t', "tcp", DefaultValue = 0, HelpText = "Enable Tcp generator on this Port")]
+ public int Tcp { get; set; }
+
+ [Option("tcp-host", DefaultValue = "localhost", HelpText = "Host to send Tcp data to")]
+ public string TcpHost { get; set; }
+
+ [Option("tcp-rate", DefaultValue = 10, HelpText = "Tcp Rate in Milliseconds between generation of log lines")]
+ public int TcpRate { get; set; }
+
+ [Option('r', "redis", DefaultValue = 0, HelpText = "Enable Redis generator on this Port")]
+ public int Redis { get; set; }
+
+ [Option("redis-host", DefaultValue = "", HelpText = "Host to send Redis data to")]
+ public string RedisHost { get; set; }
+
+ [ParserState]
+ public IParserState LastParserState { get; set; }
+
+ [HelpOption]
+ public string GetUsage()
+ {
+ return HelpText.AutoBuild(this,
+ (HelpText current) => HelpText.DefaultParsingErrorsHandler(this, current));
+ }
+ }
+}
diff --git a/TimberWinR.TestGenerator/Dynamic.cs b/TimberWinR.TestGenerator/Dynamic.cs
new file mode 100644
index 0000000..3bde75c
--- /dev/null
+++ b/TimberWinR.TestGenerator/Dynamic.cs
@@ -0,0 +1,2407 @@
+//Copyright (C) Microsoft Corporation. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Reflection;
+using System.Reflection.Emit;
+using System.Threading;
+using System.Runtime.CompilerServices;
+using System.Collections;
+using Newtonsoft.Json.Linq;
+
+namespace System.Linq.Dynamic
+{
+ public static class DynamicQueryable
+ {
+ public static IQueryable Where(this IQueryable source, string predicate, params object[] values)
+ {
+ return (IQueryable)Where((IQueryable)source, predicate, values);
+ }
+
+ public static IQueryable Where(this IQueryable source, string predicate, params object[] values)
+ {
+ if (source == null) throw new ArgumentNullException("source");
+ if (predicate == null) throw new ArgumentNullException("predicate");
+ LambdaExpression lambda = DynamicExpression.ParseLambda(source.ElementType, typeof(bool), predicate, values);
+ return source.Provider.CreateQuery(
+ Expression.Call(
+ typeof(Queryable), "Where",
+ new Type[] { source.ElementType },
+ source.Expression, Expression.Quote(lambda)));
+ }
+
+ public static IQueryable Select(this IQueryable source, string selector, params object[] values)
+ {
+ if (source == null) throw new ArgumentNullException("source");
+ if (selector == null) throw new ArgumentNullException("selector");
+ LambdaExpression lambda = DynamicExpression.ParseLambda(source.ElementType, null, selector, values);
+ return source.Provider.CreateQuery(
+ Expression.Call(
+ typeof(Queryable), "Select",
+ new Type[] { source.ElementType, lambda.Body.Type },
+ source.Expression, Expression.Quote(lambda)));
+ }
+
+ public static IQueryable OrderBy(this IQueryable source, string ordering, params object[] values)
+ {
+ return (IQueryable)OrderBy((IQueryable)source, ordering, values);
+ }
+
+ public static IQueryable OrderBy(this IQueryable source, string ordering, params object[] values)
+ {
+ if (source == null) throw new ArgumentNullException("source");
+ if (ordering == null) throw new ArgumentNullException("ordering");
+ ParameterExpression[] parameters = new ParameterExpression[] {
+ Expression.Parameter(source.ElementType, "") };
+ ExpressionParser parser = new ExpressionParser(parameters, ordering, values);
+ IEnumerable orderings = parser.ParseOrdering();
+ Expression queryExpr = source.Expression;
+ string methodAsc = "OrderBy";
+ string methodDesc = "OrderByDescending";
+ foreach (DynamicOrdering o in orderings)
+ {
+ queryExpr = Expression.Call(
+ typeof(Queryable), o.Ascending ? methodAsc : methodDesc,
+ new Type[] { source.ElementType, o.Selector.Type },
+ queryExpr, Expression.Quote(Expression.Lambda(o.Selector, parameters)));
+ methodAsc = "ThenBy";
+ methodDesc = "ThenByDescending";
+ }
+ return source.Provider.CreateQuery(queryExpr);
+ }
+
+ public static IQueryable Take(this IQueryable source, int count)
+ {
+ if (source == null) throw new ArgumentNullException("source");
+ return source.Provider.CreateQuery(
+ Expression.Call(
+ typeof(Queryable), "Take",
+ new Type[] { source.ElementType },
+ source.Expression, Expression.Constant(count)));
+ }
+
+ public static IQueryable Union(this IQueryable source, IQueryable other)
+ {
+ if (source == null) throw new ArgumentNullException("source");
+ return source.Provider.CreateQuery(
+ Expression.Call(
+ typeof(Queryable), "Union",
+ new Type[] { source.ElementType },
+ source.Expression, other.Expression));
+ }
+
+ public static IQueryable Skip(this IQueryable source, int count)
+ {
+ if (source == null) throw new ArgumentNullException("source");
+ return source.Provider.CreateQuery(
+ Expression.Call(
+ typeof(Queryable), "Skip",
+ new Type[] { source.ElementType },
+ source.Expression, Expression.Constant(count)));
+ }
+
+ public static IQueryable GroupBy(this IQueryable source, string keySelector, string elementSelector, params object[] values)
+ {
+ if (source == null) throw new ArgumentNullException("source");
+ if (keySelector == null) throw new ArgumentNullException("keySelector");
+ if (elementSelector == null) throw new ArgumentNullException("elementSelector");
+ LambdaExpression keyLambda = DynamicExpression.ParseLambda(source.ElementType, null, keySelector, values);
+ LambdaExpression elementLambda = DynamicExpression.ParseLambda(source.ElementType, null, elementSelector, values);
+ return source.Provider.CreateQuery(
+ Expression.Call(
+ typeof(Queryable), "GroupBy",
+ new Type[] { source.ElementType, keyLambda.Body.Type, elementLambda.Body.Type },
+ source.Expression, Expression.Quote(keyLambda), Expression.Quote(elementLambda)));
+ }
+
+ public static bool Any(this IQueryable source)
+ {
+ if (source == null) throw new ArgumentNullException("source");
+ return (bool)source.Provider.Execute(
+ Expression.Call(
+ typeof(Queryable), "Any",
+ new Type[] { source.ElementType }, source.Expression));
+ }
+
+ public static int Count(this IQueryable source)
+ {
+ if (source == null) throw new ArgumentNullException("source");
+ return (int)source.Provider.Execute(
+ Expression.Call(
+ typeof(Queryable), "Count",
+ new Type[] { source.ElementType }, source.Expression));
+ }
+ }
+
+ public abstract class DynamicClass
+ {
+ public override string ToString()
+ {
+ PropertyInfo[] props = this.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);
+ StringBuilder sb = new StringBuilder();
+ sb.Append("{");
+ for (int i = 0; i < props.Length; i++)
+ {
+ if (i > 0) sb.Append(", ");
+ sb.Append(props[i].Name);
+ sb.Append("=");
+ sb.Append(props[i].GetValue(this, null));
+ }
+ sb.Append("}");
+ return sb.ToString();
+ }
+ }
+
+ public class DynamicProperty
+ {
+ string name;
+ Type type;
+
+ public DynamicProperty(string name, Type type)
+ {
+ if (name == null) throw new ArgumentNullException("name");
+ if (type == null) throw new ArgumentNullException("type");
+ this.name = name;
+ this.type = type;
+ }
+
+ public string Name
+ {
+ get { return name; }
+ }
+
+ public Type Type
+ {
+ get { return type; }
+ }
+ }
+
+ public static class DynamicExpression
+ {
+ public static Expression Parse(Type resultType, string expression, params object[] values)
+ {
+ ExpressionParser parser = new ExpressionParser(null, expression, values);
+ return parser.Parse(resultType);
+ }
+
+ public static LambdaExpression ParseLambda(Type itType, Type resultType, string expression, params object[] values)
+ {
+ return ParseLambda(new ParameterExpression[] { Expression.Parameter(itType, "") }, resultType, expression, values);
+ }
+
+ public static LambdaExpression ParseLambda(ParameterExpression[] parameters, Type resultType, string expression, params object[] values)
+ {
+ ExpressionParser parser = new ExpressionParser(parameters, expression, values);
+ return Expression.Lambda(parser.Parse(resultType), parameters);
+ }
+
+ public static Expression> ParseLambda(string expression, params object[] values)
+ {
+ return (Expression>)ParseLambda(typeof(T), typeof(S), expression, values);
+ }
+
+ public static Type CreateClass(params DynamicProperty[] properties)
+ {
+ return ClassFactory.Instance.GetDynamicClass(properties);
+ }
+
+ public static Type CreateClass(IEnumerable properties)
+ {
+ return ClassFactory.Instance.GetDynamicClass(properties);
+ }
+ }
+
+ internal class DynamicOrdering
+ {
+ public Expression Selector;
+ public bool Ascending;
+ }
+
+ internal class Signature : IEquatable
+ {
+ public DynamicProperty[] properties;
+ public int hashCode;
+
+ public Signature(IEnumerable properties)
+ {
+ this.properties = properties.ToArray();
+ hashCode = 0;
+ foreach (DynamicProperty p in properties)
+ {
+ hashCode ^= p.Name.GetHashCode() ^ p.Type.GetHashCode();
+ }
+ }
+
+ public override int GetHashCode()
+ {
+ return hashCode;
+ }
+
+ public override bool Equals(object obj)
+ {
+ return obj is Signature ? Equals((Signature)obj) : false;
+ }
+
+ public bool Equals(Signature other)
+ {
+ if (properties.Length != other.properties.Length) return false;
+ for (int i = 0; i < properties.Length; i++)
+ {
+ if (properties[i].Name != other.properties[i].Name ||
+ properties[i].Type != other.properties[i].Type) return false;
+ }
+ return true;
+ }
+ }
+
+ internal class ClassFactory
+ {
+ public static readonly ClassFactory Instance = new ClassFactory();
+
+ static ClassFactory() { } // Trigger lazy initialization of static fields
+
+ ModuleBuilder module;
+ Dictionary classes;
+ int classCount;
+ ReaderWriterLock rwLock;
+
+ private ClassFactory()
+ {
+ AssemblyName name = new AssemblyName("DynamicClasses");
+ AssemblyBuilder assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(name, AssemblyBuilderAccess.Run);
+#if ENABLE_LINQ_PARTIAL_TRUST
+ new ReflectionPermission(PermissionState.Unrestricted).Assert();
+#endif
+ try
+ {
+ module = assembly.DefineDynamicModule("Module");
+ }
+ finally
+ {
+#if ENABLE_LINQ_PARTIAL_TRUST
+ PermissionSet.RevertAssert();
+#endif
+ }
+ classes = new Dictionary();
+ rwLock = new ReaderWriterLock();
+ }
+
+ public Type GetDynamicClass(IEnumerable properties)
+ {
+ rwLock.AcquireReaderLock(Timeout.Infinite);
+ try
+ {
+ Signature signature = new Signature(properties);
+ Type type;
+ if (!classes.TryGetValue(signature, out type))
+ {
+ type = CreateDynamicClass(signature.properties);
+ classes.Add(signature, type);
+ }
+ return type;
+ }
+ finally
+ {
+ rwLock.ReleaseReaderLock();
+ }
+ }
+
+ Type CreateDynamicClass(DynamicProperty[] properties)
+ {
+ LockCookie cookie = rwLock.UpgradeToWriterLock(Timeout.Infinite);
+ try
+ {
+ string typeName = "DynamicClass" + (classCount + 1);
+#if ENABLE_LINQ_PARTIAL_TRUST
+ new ReflectionPermission(PermissionState.Unrestricted).Assert();
+#endif
+ try
+ {
+ TypeBuilder tb = this.module.DefineType(typeName, TypeAttributes.Class |
+ TypeAttributes.Public, typeof(DynamicClass));
+ FieldInfo[] fields = GenerateProperties(tb, properties);
+ GenerateEquals(tb, fields);
+ GenerateGetHashCode(tb, fields);
+ Type result = tb.CreateType();
+ classCount++;
+ return result;
+ }
+ finally
+ {
+#if ENABLE_LINQ_PARTIAL_TRUST
+ PermissionSet.RevertAssert();
+#endif
+ }
+ }
+ finally
+ {
+ rwLock.DowngradeFromWriterLock(ref cookie);
+ }
+ }
+
+ FieldInfo[] GenerateProperties(TypeBuilder tb, DynamicProperty[] properties)
+ {
+ FieldInfo[] fields = new FieldBuilder[properties.Length];
+ for (int i = 0; i < properties.Length; i++)
+ {
+ DynamicProperty dp = properties[i];
+ FieldBuilder fb = tb.DefineField("_" + dp.Name, dp.Type, FieldAttributes.Private);
+ PropertyBuilder pb = tb.DefineProperty(dp.Name, PropertyAttributes.HasDefault, dp.Type, null);
+ MethodBuilder mbGet = tb.DefineMethod("get_" + dp.Name,
+ MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig,
+ dp.Type, Type.EmptyTypes);
+ ILGenerator genGet = mbGet.GetILGenerator();
+ genGet.Emit(OpCodes.Ldarg_0);
+ genGet.Emit(OpCodes.Ldfld, fb);
+ genGet.Emit(OpCodes.Ret);
+ MethodBuilder mbSet = tb.DefineMethod("set_" + dp.Name,
+ MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig,
+ null, new Type[] { dp.Type });
+ ILGenerator genSet = mbSet.GetILGenerator();
+ genSet.Emit(OpCodes.Ldarg_0);
+ genSet.Emit(OpCodes.Ldarg_1);
+ genSet.Emit(OpCodes.Stfld, fb);
+ genSet.Emit(OpCodes.Ret);
+ pb.SetGetMethod(mbGet);
+ pb.SetSetMethod(mbSet);
+ fields[i] = fb;
+ }
+ return fields;
+ }
+
+ void GenerateEquals(TypeBuilder tb, FieldInfo[] fields)
+ {
+ MethodBuilder mb = tb.DefineMethod("Equals",
+ MethodAttributes.Public | MethodAttributes.ReuseSlot |
+ MethodAttributes.Virtual | MethodAttributes.HideBySig,
+ typeof(bool), new Type[] { typeof(object) });
+ ILGenerator gen = mb.GetILGenerator();
+ LocalBuilder other = gen.DeclareLocal(tb);
+ Label next = gen.DefineLabel();
+ gen.Emit(OpCodes.Ldarg_1);
+ gen.Emit(OpCodes.Isinst, tb);
+ gen.Emit(OpCodes.Stloc, other);
+ gen.Emit(OpCodes.Ldloc, other);
+ gen.Emit(OpCodes.Brtrue_S, next);
+ gen.Emit(OpCodes.Ldc_I4_0);
+ gen.Emit(OpCodes.Ret);
+ gen.MarkLabel(next);
+ foreach (FieldInfo field in fields)
+ {
+ Type ft = field.FieldType;
+ Type ct = typeof(EqualityComparer<>).MakeGenericType(ft);
+ next = gen.DefineLabel();
+ gen.EmitCall(OpCodes.Call, ct.GetMethod("get_Default"), null);
+ gen.Emit(OpCodes.Ldarg_0);
+ gen.Emit(OpCodes.Ldfld, field);
+ gen.Emit(OpCodes.Ldloc, other);
+ gen.Emit(OpCodes.Ldfld, field);
+ gen.EmitCall(OpCodes.Callvirt, ct.GetMethod("Equals", new Type[] { ft, ft }), null);
+ gen.Emit(OpCodes.Brtrue_S, next);
+ gen.Emit(OpCodes.Ldc_I4_0);
+ gen.Emit(OpCodes.Ret);
+ gen.MarkLabel(next);
+ }
+ gen.Emit(OpCodes.Ldc_I4_1);
+ gen.Emit(OpCodes.Ret);
+ }
+
+ void GenerateGetHashCode(TypeBuilder tb, FieldInfo[] fields)
+ {
+ MethodBuilder mb = tb.DefineMethod("GetHashCode",
+ MethodAttributes.Public | MethodAttributes.ReuseSlot |
+ MethodAttributes.Virtual | MethodAttributes.HideBySig,
+ typeof(int), Type.EmptyTypes);
+ ILGenerator gen = mb.GetILGenerator();
+ gen.Emit(OpCodes.Ldc_I4_0);
+ foreach (FieldInfo field in fields)
+ {
+ Type ft = field.FieldType;
+ Type ct = typeof(EqualityComparer<>).MakeGenericType(ft);
+ gen.EmitCall(OpCodes.Call, ct.GetMethod("get_Default"), null);
+ gen.Emit(OpCodes.Ldarg_0);
+ gen.Emit(OpCodes.Ldfld, field);
+ gen.EmitCall(OpCodes.Callvirt, ct.GetMethod("GetHashCode", new Type[] { ft }), null);
+ gen.Emit(OpCodes.Xor);
+ }
+ gen.Emit(OpCodes.Ret);
+ }
+ }
+
+ public sealed class ParseException : Exception
+ {
+ int position;
+
+ public ParseException(string message, int position)
+ : base(message)
+ {
+ this.position = position;
+ }
+
+ public int Position
+ {
+ get { return position; }
+ }
+
+ public override string ToString()
+ {
+ return string.Format(Res.ParseExceptionFormat, Message, position);
+ }
+ }
+
+ internal class ExpressionParser
+ {
+ struct Token
+ {
+ public TokenId id;
+ public string text;
+ public int pos;
+ }
+
+ enum TokenId
+ {
+ Unknown,
+ End,
+ Identifier,
+ StringLiteral,
+ IntegerLiteral,
+ RealLiteral,
+ Exclamation,
+ Percent,
+ Amphersand,
+ OpenParen,
+ CloseParen,
+ Asterisk,
+ Plus,
+ Comma,
+ Minus,
+ Dot,
+ Slash,
+ Colon,
+ LessThan,
+ Equal,
+ GreaterThan,
+ Question,
+ OpenBracket,
+ CloseBracket,
+ Bar,
+ ExclamationEqual,
+ DoubleAmphersand,
+ LessThanEqual,
+ LessGreater,
+ DoubleEqual,
+ GreaterThanEqual,
+ DoubleBar,
+ Lambda
+ }
+
+ interface ILogicalSignatures
+ {
+ void F(bool x, bool y);
+ void F(bool? x, bool? y);
+ }
+
+ interface IArithmeticSignatures
+ {
+ void F(int x, int y);
+ void F(uint x, uint y);
+ void F(long x, long y);
+ void F(ulong x, ulong y);
+ void F(float x, float y);
+ void F(double x, double y);
+ void F(decimal x, decimal y);
+ void F(int? x, int? y);
+ void F(uint? x, uint? y);
+ void F(long? x, long? y);
+ void F(ulong? x, ulong? y);
+ void F(float? x, float? y);
+ void F(double? x, double? y);
+ void F(decimal? x, decimal? y);
+ }
+
+ interface IRelationalSignatures : IArithmeticSignatures
+ {
+ void F(string x, string y);
+ void F(char x, char y);
+ void F(DateTime x, DateTime y);
+ void F(TimeSpan x, TimeSpan y);
+ void F(char? x, char? y);
+ void F(DateTime? x, DateTime? y);
+ void F(TimeSpan? x, TimeSpan? y);
+ }
+
+ interface IEqualitySignatures : IRelationalSignatures
+ {
+ void F(bool x, bool y);
+ void F(bool? x, bool? y);
+ }
+
+ interface IAddSignatures : IArithmeticSignatures
+ {
+ void F(DateTime x, TimeSpan y);
+ void F(TimeSpan x, TimeSpan y);
+ void F(DateTime? x, TimeSpan? y);
+ void F(TimeSpan? x, TimeSpan? y);
+ }
+
+ interface ISubtractSignatures : IAddSignatures
+ {
+ void F(DateTime x, DateTime y);
+ void F(DateTime? x, DateTime? y);
+ }
+
+ interface INegationSignatures
+ {
+ void F(int x);
+ void F(long x);
+ void F(float x);
+ void F(double x);
+ void F(decimal x);
+ void F(int? x);
+ void F(long? x);
+ void F(float? x);
+ void F(double? x);
+ void F(decimal? x);
+ }
+
+ interface INotSignatures
+ {
+ void F(bool x);
+ void F(bool? x);
+ }
+
+ interface IEnumerableSignatures
+ {
+ void Where(bool predicate);
+ void Any();
+ void Any(bool predicate);
+ void All(bool predicate);
+ void Count();
+ void Count(bool predicate);
+ void Min(object selector);
+ void Max(object selector);
+ void Sum(int selector);
+ void Sum(int? selector);
+ void Sum(long selector);
+ void Sum(long? selector);
+ void Sum(float selector);
+ void Sum(float? selector);
+ void Sum(double selector);
+ void Sum(double? selector);
+ void Sum(decimal selector);
+ void Sum(decimal? selector);
+ void Average(int selector);
+ void Average(int? selector);
+ void Average(long selector);
+ void Average(long? selector);
+ void Average(float selector);
+ void Average(float? selector);
+ void Average(double selector);
+ void Average(double? selector);
+ void Average(decimal selector);
+ void Average(decimal? selector);
+ void Take(int count);
+ void Union(IQueryable right);
+ void Select(LambdaExpression exp);
+ void OrderBy(LambdaExpression exp);
+ void OrderByDescending(LambdaExpression exp);
+ }
+
+ static readonly Type[] predefinedTypes = {
+ typeof(Object),
+ typeof(Boolean),
+ typeof(Char),
+ typeof(String),
+ typeof(SByte),
+ typeof(Byte),
+ typeof(Int16),
+ typeof(UInt16),
+ typeof(Int32),
+ typeof(UInt32),
+ typeof(Int64),
+ typeof(UInt64),
+ typeof(Single),
+ typeof(Double),
+ typeof(Decimal),
+ typeof(DateTime),
+ typeof(TimeSpan),
+ typeof(Guid),
+ typeof(Math),
+ typeof(Convert),
+ typeof(JToken),
+ typeof(JObject),
+ };
+
+ static readonly Expression trueLiteral = Expression.Constant(true);
+ static readonly Expression falseLiteral = Expression.Constant(false);
+ static readonly Expression nullLiteral = Expression.Constant(null);
+
+ static readonly string keywordIt = "it";
+ static readonly string keywordIif = "iif";
+ static readonly string keywordNew = "new";
+
+ static Dictionary keywords;
+
+ Dictionary symbols;
+ IDictionary externals;
+ IDictionary internals = new Dictionary();
+ Dictionary literals;
+ ParameterExpression it;
+ string text;
+ int textPos;
+ int textLen;
+ char ch;
+ Token token;
+
+ public ExpressionParser(ParameterExpression[] parameters, string expression, object[] values)
+ {
+ if (expression == null) throw new ArgumentNullException("expression");
+ if (keywords == null) keywords = CreateKeywords();
+ symbols = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ literals = new Dictionary();
+ if (parameters != null) ProcessParameters(parameters);
+ if (values != null) ProcessValues(values);
+ text = expression;
+ textLen = text.Length;
+ SetTextPos(0);
+ NextToken();
+ }
+
+ void ProcessParameters(ParameterExpression[] parameters)
+ {
+ foreach (ParameterExpression pe in parameters)
+ if (!String.IsNullOrEmpty(pe.Name))
+ AddSymbol(pe.Name, pe);
+ if (parameters.Length == 1 && String.IsNullOrEmpty(parameters[0].Name))
+ it = parameters[0];
+ }
+
+ void ProcessValues(object[] values)
+ {
+ for (int i = 0; i < values.Length; i++)
+ {
+ object value = values[i];
+ if (i == values.Length - 1 && value is IDictionary)
+ {
+ externals = (IDictionary)value;
+ }
+ else
+ {
+ AddSymbol("@" + i.ToString(System.Globalization.CultureInfo.InvariantCulture), value);
+ }
+ }
+ }
+
+ void AddSymbol(string name, object value)
+ {
+ if (symbols.ContainsKey(name))
+ throw ParseError(Res.DuplicateIdentifier, name);
+ symbols.Add(name, value);
+ }
+
+ public Expression Parse(Type resultType)
+ {
+ int exprPos = token.pos;
+ Expression expr = ParseExpression();
+ if (resultType != null)
+ if ((expr = PromoteExpression(expr, resultType, true)) == null)
+ throw ParseError(exprPos, Res.ExpressionTypeMismatch, GetTypeName(resultType));
+ ValidateToken(TokenId.End, Res.SyntaxError);
+ return expr;
+ }
+
+#pragma warning disable 0219
+ public IEnumerable ParseOrdering()
+ {
+ List orderings = new List();
+ while (true)
+ {
+ Expression expr = ParseExpression();
+ bool ascending = true;
+ if (TokenIdentifierIs("asc") || TokenIdentifierIs("ascending"))
+ {
+ NextToken();
+ }
+ else if (TokenIdentifierIs("desc") || TokenIdentifierIs("descending"))
+ {
+ NextToken();
+ ascending = false;
+ }
+ orderings.Add(new DynamicOrdering { Selector = expr, Ascending = ascending });
+ if (token.id != TokenId.Comma) break;
+ NextToken();
+ }
+ ValidateToken(TokenId.End, Res.SyntaxError);
+ return orderings;
+ }
+#pragma warning restore 0219
+
+ // ?: operator
+ Expression ParseExpression()
+ {
+ int errorPos = token.pos;
+ Expression expr = ParseLambda();
+ if (token.id == TokenId.Question)
+ {
+ NextToken();
+ Expression expr1 = ParseExpression();
+ ValidateToken(TokenId.Colon, Res.ColonExpected);
+ NextToken();
+ Expression expr2 = ParseExpression();
+ expr = GenerateConditional(expr, expr1, expr2, errorPos);
+ }
+ return expr;
+ }
+
+ ///
+ /// => operator
+ /// Added Support for projection operator
+ ///
+ ///
+ Expression ParseLambda()
+ {
+ int errorPos = token.pos;
+ Expression expr = ParseLogicalOr();
+ if (token.id == TokenId.Lambda)
+ {
+ if (token.id == TokenId.Lambda && it.Type == expr.Type)
+ {
+ NextToken();
+ if (token.id == TokenId.Identifier || token.id == TokenId.OpenParen)
+ {
+ var right = ParseExpression();
+ return Expression.Lambda(right, new[] { (ParameterExpression)expr });
+ }
+ ValidateToken(TokenId.OpenParen, Res.OpenParenExpected);
+ }
+ }
+ return expr;
+ }
+
+ // ||, or operator
+ Expression ParseLogicalOr()
+ {
+ Expression left = ParseLogicalAnd();
+ while (token.id == TokenId.DoubleBar || TokenIdentifierIs("or"))
+ {
+ Token op = token;
+ NextToken();
+ Expression right = ParseLogicalAnd();
+ CheckAndPromoteOperands(typeof(ILogicalSignatures), op.text, ref left, ref right, op.pos);
+ left = Expression.OrElse(left, right);
+ }
+ return left;
+ }
+
+ // &&, and operator
+ Expression ParseLogicalAnd()
+ {
+ Expression left = ParseComparison();
+ while (token.id == TokenId.DoubleAmphersand || TokenIdentifierIs("and"))
+ {
+ Token op = token;
+ NextToken();
+ Expression right = ParseComparison();
+ CheckAndPromoteOperands(typeof(ILogicalSignatures), op.text, ref left, ref right, op.pos);
+ left = Expression.AndAlso(left, right);
+ }
+ return left;
+ }
+
+ // =, ==, !=, <>, >, >=, <, <= operators
+ Expression ParseComparison()
+ {
+ Expression left = ParseAdditive();
+ while (token.id == TokenId.Equal || token.id == TokenId.DoubleEqual ||
+ token.id == TokenId.ExclamationEqual || token.id == TokenId.LessGreater ||
+ token.id == TokenId.GreaterThan || token.id == TokenId.GreaterThanEqual ||
+ token.id == TokenId.LessThan || token.id == TokenId.LessThanEqual)
+ {
+ Token op = token;
+ NextToken();
+ Expression right = ParseAdditive();
+ bool isEquality = op.id == TokenId.Equal || op.id == TokenId.DoubleEqual ||
+ op.id == TokenId.ExclamationEqual || op.id == TokenId.LessGreater;
+ if (isEquality && !left.Type.IsValueType && !right.Type.IsValueType)
+ {
+ if (left.Type != right.Type)
+ {
+ if (left.Type.IsAssignableFrom(right.Type))
+ {
+ right = Expression.Convert(right, left.Type);
+ }
+ else if (right.Type.IsAssignableFrom(left.Type))
+ {
+ left = Expression.Convert(left, right.Type);
+ }
+ else
+ {
+ throw IncompatibleOperandsError(op.text, left, right, op.pos);
+ }
+ }
+ }
+ else if (IsEnumType(left.Type) || IsEnumType(right.Type))
+ {
+ if (left.Type != right.Type)
+ {
+ Expression e;
+ if ((e = PromoteExpression(right, left.Type, true)) != null)
+ {
+ right = e;
+ }
+ else if ((e = PromoteExpression(left, right.Type, true)) != null)
+ {
+ left = e;
+ }
+ else
+ {
+ throw IncompatibleOperandsError(op.text, left, right, op.pos);
+ }
+ }
+ }
+ else
+ {
+ CheckAndPromoteOperands(isEquality ? typeof(IEqualitySignatures) : typeof(IRelationalSignatures),
+ op.text, ref left, ref right, op.pos);
+ }
+ switch (op.id)
+ {
+ case TokenId.Equal:
+ case TokenId.DoubleEqual:
+ left = GenerateEqual(left, right);
+ break;
+ case TokenId.ExclamationEqual:
+ case TokenId.LessGreater:
+ left = GenerateNotEqual(left, right);
+ break;
+ case TokenId.GreaterThan:
+ left = GenerateGreaterThan(left, right);
+ break;
+ case TokenId.GreaterThanEqual:
+ left = GenerateGreaterThanEqual(left, right);
+ break;
+ case TokenId.LessThan:
+ left = GenerateLessThan(left, right);
+ break;
+ case TokenId.LessThanEqual:
+ left = GenerateLessThanEqual(left, right);
+ break;
+ }
+ }
+ return left;
+ }
+
+ // +, -, & operators
+ Expression ParseAdditive()
+ {
+ Expression left = ParseMultiplicative();
+ while (token.id == TokenId.Plus || token.id == TokenId.Minus ||
+ token.id == TokenId.Amphersand)
+ {
+ Token op = token;
+ NextToken();
+ Expression right = ParseMultiplicative();
+ switch (op.id)
+ {
+ case TokenId.Plus:
+ if (left.Type == typeof(string) || right.Type == typeof(string))
+ goto case TokenId.Amphersand;
+ CheckAndPromoteOperands(typeof(IAddSignatures), op.text, ref left, ref right, op.pos);
+ left = GenerateAdd(left, right);
+ break;
+ case TokenId.Minus:
+ CheckAndPromoteOperands(typeof(ISubtractSignatures), op.text, ref left, ref right, op.pos);
+ left = GenerateSubtract(left, right);
+ break;
+ case TokenId.Amphersand:
+ left = GenerateStringConcat(left, right);
+ break;
+ }
+ }
+ return left;
+ }
+
+ // *, /, %, mod operators
+ Expression ParseMultiplicative()
+ {
+ Expression left = ParseUnary();
+ while (token.id == TokenId.Asterisk || token.id == TokenId.Slash ||
+ token.id == TokenId.Percent || TokenIdentifierIs("mod"))
+ {
+ Token op = token;
+ NextToken();
+ Expression right = ParseUnary();
+ CheckAndPromoteOperands(typeof(IArithmeticSignatures), op.text, ref left, ref right, op.pos);
+ switch (op.id)
+ {
+ case TokenId.Asterisk:
+ left = Expression.Multiply(left, right);
+ break;
+ case TokenId.Slash:
+ left = Expression.Divide(left, right);
+ break;
+ case TokenId.Percent:
+ case TokenId.Identifier:
+ left = Expression.Modulo(left, right);
+ break;
+ }
+ }
+ return left;
+ }
+
+ // -, !, not unary operators
+ Expression ParseUnary()
+ {
+ if (token.id == TokenId.Minus || token.id == TokenId.Exclamation ||
+ TokenIdentifierIs("not"))
+ {
+ Token op = token;
+ NextToken();
+ if (op.id == TokenId.Minus && (token.id == TokenId.IntegerLiteral ||
+ token.id == TokenId.RealLiteral))
+ {
+ token.text = "-" + token.text;
+ token.pos = op.pos;
+ return ParsePrimary();
+ }
+ Expression expr = ParseUnary();
+ if (op.id == TokenId.Minus)
+ {
+ CheckAndPromoteOperand(typeof(INegationSignatures), op.text, ref expr, op.pos);
+ expr = Expression.Negate(expr);
+ }
+ else
+ {
+ CheckAndPromoteOperand(typeof(INotSignatures), op.text, ref expr, op.pos);
+ expr = Expression.Not(expr);
+ }
+ return expr;
+ }
+ return ParsePrimary();
+ }
+
+ Expression ParsePrimary()
+ {
+ Expression expr = ParsePrimaryStart();
+ while (true)
+ {
+ if (token.id == TokenId.Dot)
+ {
+ NextToken();
+ expr = ParseMemberAccess(null, expr);
+ }
+ else if (token.id == TokenId.OpenBracket)
+ {
+ expr = ParseElementAccess(expr);
+ }
+ else
+ {
+ break;
+ }
+ }
+ return expr;
+ }
+
+ Expression ParsePrimaryStart()
+ {
+ switch (token.id)
+ {
+ case TokenId.Identifier:
+ return ParseIdentifier();
+ case TokenId.StringLiteral:
+ return ParseStringLiteral();
+ case TokenId.IntegerLiteral:
+ return ParseIntegerLiteral();
+ case TokenId.RealLiteral:
+ return ParseRealLiteral();
+ case TokenId.OpenParen:
+ return ParseParenExpression();
+ default:
+ throw ParseError(Res.ExpressionExpected);
+ }
+ }
+
+ Expression ParseStringLiteral()
+ {
+ ValidateToken(TokenId.StringLiteral);
+ char quote = token.text[0];
+ string s = token.text.Substring(1, token.text.Length - 2);
+ int start = 0;
+ while (true)
+ {
+ int i = s.IndexOf(quote, start);
+ if (i < 0) break;
+ s = s.Remove(i, 1);
+ start = i + 1;
+ }
+ if (quote == '\'')
+ {
+ if (s.Length != 1)
+ throw ParseError(Res.InvalidCharacterLiteral);
+ NextToken();
+ return CreateLiteral(s[0], s);
+ }
+ NextToken();
+ return CreateLiteral(s, s);
+ }
+
+ Expression ParseIntegerLiteral()
+ {
+ ValidateToken(TokenId.IntegerLiteral);
+ string text = token.text;
+ if (text[0] != '-')
+ {
+ ulong value;
+ if (!UInt64.TryParse(text, out value))
+ throw ParseError(Res.InvalidIntegerLiteral, text);
+ NextToken();
+ if (value <= (ulong)Int32.MaxValue) return CreateLiteral((int)value, text);
+ if (value <= (ulong)UInt32.MaxValue) return CreateLiteral((uint)value, text);
+ if (value <= (ulong)Int64.MaxValue) return CreateLiteral((long)value, text);
+ return CreateLiteral(value, text);
+ }
+ else
+ {
+ long value;
+ if (!Int64.TryParse(text, out value))
+ throw ParseError(Res.InvalidIntegerLiteral, text);
+ NextToken();
+ if (value >= Int32.MinValue && value <= Int32.MaxValue)
+ return CreateLiteral((int)value, text);
+ return CreateLiteral(value, text);
+ }
+ }
+
+ Expression ParseRealLiteral()
+ {
+ ValidateToken(TokenId.RealLiteral);
+ string text = token.text;
+ object value = null;
+ char last = text[text.Length - 1];
+ if (last == 'F' || last == 'f')
+ {
+ float f;
+ if (Single.TryParse(text.Substring(0, text.Length - 1), out f)) value = f;
+ }
+ else
+ {
+ double d;
+ if (Double.TryParse(text, out d)) value = d;
+ }
+ if (value == null) throw ParseError(Res.InvalidRealLiteral, text);
+ NextToken();
+ return CreateLiteral(value, text);
+ }
+
+ Expression CreateLiteral(object value, string text)
+ {
+ ConstantExpression expr = Expression.Constant(value);
+ literals.Add(expr, text);
+ return expr;
+ }
+
+ Expression ParseParenExpression()
+ {
+ ValidateToken(TokenId.OpenParen, Res.OpenParenExpected);
+ NextToken();
+ Expression e = ParseExpression();
+ ValidateToken(TokenId.CloseParen, Res.CloseParenOrOperatorExpected);
+ NextToken();
+ return e;
+ }
+
+ Expression ParseIdentifier()
+ {
+ ValidateToken(TokenId.Identifier);
+ object value;
+ if (keywords.TryGetValue(token.text, out value))
+ {
+ if (value is Type) return ParseTypeAccess((Type)value);
+ if (value == (object)keywordIt) return ParseIt();
+ if (value == (object)keywordIif) return ParseIif();
+ if (value == (object)keywordNew) return ParseNew();
+ NextToken();
+ return (Expression)value;
+ }
+ //if (symbols.TryGetValue(token.text, out value) ||
+ // externals != null && externals.TryGetValue(token.text, out value)) {
+ if (symbols.TryGetValue(token.text, out value) ||
+ externals != null && externals.TryGetValue(token.text, out value) || internals.TryGetValue(token.text, out value))
+ {
+ Expression expr = value as Expression;
+ if (expr == null)
+ {
+ expr = Expression.Constant(value);
+ }
+ else
+ {
+ LambdaExpression lambda = expr as LambdaExpression;
+ if (lambda != null) return ParseLambdaInvocation(lambda);
+ }
+ NextToken();
+ return expr;
+ }
+ if (it != null) return ParseMemberAccess(null, it);
+ throw ParseError(Res.UnknownIdentifier, token.text);
+ }
+
+ Expression ParseIt()
+ {
+ if (it == null)
+ throw ParseError(Res.NoItInScope);
+ NextToken();
+ return it;
+ }
+
+ Expression ParseIif()
+ {
+ int errorPos = token.pos;
+ NextToken();
+ Expression[] args = ParseArgumentList();
+ if (args.Length != 3)
+ throw ParseError(errorPos, Res.IifRequiresThreeArgs);
+ return GenerateConditional(args[0], args[1], args[2], errorPos);
+ }
+
+ Expression GenerateConditional(Expression test, Expression expr1, Expression expr2, int errorPos)
+ {
+ if (test.Type != typeof(bool))
+ throw ParseError(errorPos, Res.FirstExprMustBeBool);
+ if (expr1.Type != expr2.Type)
+ {
+ Expression expr1as2 = expr2 != nullLiteral ? PromoteExpression(expr1, expr2.Type, true) : null;
+ Expression expr2as1 = expr1 != nullLiteral ? PromoteExpression(expr2, expr1.Type, true) : null;
+ if (expr1as2 != null && expr2as1 == null)
+ {
+ expr1 = expr1as2;
+ }
+ else if (expr2as1 != null && expr1as2 == null)
+ {
+ expr2 = expr2as1;
+ }
+ else
+ {
+ string type1 = expr1 != nullLiteral ? expr1.Type.Name : "null";
+ string type2 = expr2 != nullLiteral ? expr2.Type.Name : "null";
+ if (expr1as2 != null && expr2as1 != null)
+ throw ParseError(errorPos, Res.BothTypesConvertToOther, type1, type2);
+ throw ParseError(errorPos, Res.NeitherTypeConvertsToOther, type1, type2);
+ }
+ }
+ return Expression.Condition(test, expr1, expr2);
+ }
+
+ Expression ParseNew()
+ {
+ NextToken();
+ ValidateToken(TokenId.OpenParen, Res.OpenParenExpected);
+ NextToken();
+ List properties = new List();
+ List expressions = new List();
+ while (true)
+ {
+ int exprPos = token.pos;
+ Expression expr = ParseExpression();
+ string propName;
+ if (TokenIdentifierIs("as"))
+ {
+ NextToken();
+ propName = GetIdentifier();
+ NextToken();
+ }
+ else
+ {
+ MemberExpression me = expr as MemberExpression;
+ if (me == null) throw ParseError(exprPos, Res.MissingAsClause);
+ propName = me.Member.Name;
+ }
+ expressions.Add(expr);
+ properties.Add(new DynamicProperty(propName, expr.Type));
+ if (token.id != TokenId.Comma) break;
+ NextToken();
+ }
+ ValidateToken(TokenId.CloseParen, Res.CloseParenOrCommaExpected);
+ NextToken();
+ Type type = DynamicExpression.CreateClass(properties);
+ MemberBinding[] bindings = new MemberBinding[properties.Count];
+ for (int i = 0; i < bindings.Length; i++)
+ bindings[i] = Expression.Bind(type.GetProperty(properties[i].Name), expressions[i]);
+ return Expression.MemberInit(Expression.New(type), bindings);
+ }
+
+ Expression ParseLambdaInvocation(LambdaExpression lambda)
+ {
+ int errorPos = token.pos;
+ NextToken();
+ Expression[] args = ParseArgumentList();
+ MethodBase method;
+ if (FindMethod(lambda.Type, "Invoke", false, args, out method) != 1)
+ throw ParseError(errorPos, Res.ArgsIncompatibleWithLambda);
+ return Expression.Invoke(lambda, args);
+ }
+
+ Expression ParseTypeAccess(Type type)
+ {
+ int errorPos = token.pos;
+ NextToken();
+ if (token.id == TokenId.Question)
+ {
+ if (!type.IsValueType || IsNullableType(type))
+ throw ParseError(errorPos, Res.TypeHasNoNullableForm, GetTypeName(type));
+ type = typeof(Nullable<>).MakeGenericType(type);
+ NextToken();
+ }
+ if (token.id == TokenId.OpenParen)
+ {
+ Expression[] args = ParseArgumentList();
+ MethodBase method;
+ switch (FindBestMethod(type.GetConstructors(), args, out method))
+ {
+ case 0:
+ if (args.Length == 1)
+ return GenerateConversion(args[0], type, errorPos);
+ throw ParseError(errorPos, Res.NoMatchingConstructor, GetTypeName(type));
+ case 1:
+ return Expression.New((ConstructorInfo)method, args);
+ default:
+ throw ParseError(errorPos, Res.AmbiguousConstructorInvocation, GetTypeName(type));
+ }
+ }
+ ValidateToken(TokenId.Dot, Res.DotOrOpenParenExpected);
+ NextToken();
+ return ParseMemberAccess(type, null);
+ }
+
+ Expression GenerateConversion(Expression expr, Type type, int errorPos)
+ {
+ Type exprType = expr.Type;
+ if (exprType == type) return expr;
+ if (exprType.IsValueType && type.IsValueType)
+ {
+ if ((IsNullableType(exprType) || IsNullableType(type)) &&
+ GetNonNullableType(exprType) == GetNonNullableType(type))
+ return Expression.Convert(expr, type);
+ if ((IsNumericType(exprType) || IsEnumType(exprType)) &&
+ (IsNumericType(type)) || IsEnumType(type))
+ return Expression.ConvertChecked(expr, type);
+ }
+ if (exprType.IsAssignableFrom(type) || type.IsAssignableFrom(exprType) ||
+ exprType.IsInterface || type.IsInterface)
+ return Expression.Convert(expr, type);
+ throw ParseError(errorPos, Res.CannotConvertValue,
+ GetTypeName(exprType), GetTypeName(type));
+ }
+
+ ///
+ /// Parsing begins here
+ ///
+ ///
+ ///
+ ///
+ Expression ParseMemberAccess(Type type, Expression instance)
+ {
+ if (instance != null) type = instance.Type;
+ int errorPos = token.pos;
+ string id = GetIdentifier();
+ NextToken();
+ if (token.id == TokenId.OpenParen)
+ {
+ if (instance != null && type != typeof(string))
+ {
+ Type enumerableType = FindGenericType(typeof(IQueryable<>), type);
+ if (enumerableType != null)
+ {
+ Type elementType = enumerableType.GetGenericArguments()[0];
+ return ParseAggregate(instance, elementType, id, errorPos);
+ }
+ }
+ Expression[] args = ParseArgumentList();
+ MethodBase mb;
+ switch (FindMethod(type, id, instance == null, args, out mb))
+ {
+ case 0:
+ throw ParseError(errorPos, Res.NoApplicableMethod,
+ id, GetTypeName(type));
+ case 1:
+ MethodInfo method = (MethodInfo)mb;
+ if (!IsPredefinedType(method.DeclaringType))
+ throw ParseError(errorPos, Res.MethodsAreInaccessible, GetTypeName(method.DeclaringType));
+ if (method.ReturnType == typeof(void))
+ throw ParseError(errorPos, Res.MethodIsVoid,
+ id, GetTypeName(method.DeclaringType));
+ return Expression.Call(instance, (MethodInfo)method, args);
+ default:
+ throw ParseError(errorPos, Res.AmbiguousMethodInvocation,
+ id, GetTypeName(type));
+ }
+ }
+ else
+ {
+ MemberInfo member = FindPropertyOrField(type, id, instance == null);
+ //if (member == null)
+ // throw ParseError(errorPos, Res.UnknownPropertyOrField,
+ // id, GetTypeName(type));
+ if (member == null)
+ {
+ if (token.id == TokenId.Lambda && it.Type == type)
+ {
+ // This might be an internal variable for use within a lambda expression, so store it as such
+ internals.Add(id, it);
+ NextToken();
+ var right = ParseExpression();
+ return right;
+ }
+ else
+ {
+ throw ParseError(errorPos, Res.UnknownPropertyOrField,
+ id, GetTypeName(type));
+ }
+ }
+ return member is PropertyInfo ?
+ Expression.Property(instance, (PropertyInfo)member) :
+ Expression.Field(instance, (FieldInfo)member);
+ }
+ }
+
+ static Type FindGenericType(Type generic, Type type)
+ {
+ while (type != null && type != typeof(object))
+ {
+ if (type.IsGenericType && type.GetGenericTypeDefinition() == generic) return type;
+ if (generic.IsInterface)
+ {
+ foreach (Type intfType in type.GetInterfaces())
+ {
+ Type found = FindGenericType(generic, intfType);
+ if (found != null) return found;
+ }
+ }
+ type = type.BaseType;
+ }
+ return null;
+ }
+
+ Expression ParseAggregate(Expression instance, Type elementType, string methodName, int errorPos)
+ {
+ ParameterExpression outerIt = it;
+ ParameterExpression innerIt = it == null ? Expression.Parameter(elementType, "") : Expression.Parameter(elementType, it.ToString());
+ it = innerIt;
+ Expression[] args = ParseArgumentList();
+ it = outerIt;
+ MethodBase signature;
+ if (FindMethod(typeof(IEnumerableSignatures), methodName, false, args, out signature) != 1)
+ throw ParseError(errorPos, Res.NoApplicableAggregate, methodName);
+ Type[] typeArgs;
+
+ switch (signature.Name)
+ {
+ case "Min":
+ case "Max":
+ typeArgs = new Type[] { elementType, args[0].Type };
+ break;
+ case "Select":
+ typeArgs = new Type[] { elementType, args[0].Type.GetGenericArguments().Last() };
+ break;
+ case "OrderBy":
+ typeArgs = new Type[] { elementType, args[0].Type.GetGenericArguments().Last() };
+ break;
+ case "OrderByDescending":
+ typeArgs = new Type[] { elementType, args[0].Type.GetGenericArguments().Last() };
+ break;
+ default:
+ typeArgs = new Type[] { elementType };
+ break;
+ }
+ if (args.Length == 0)
+ {
+ args = new Expression[] { instance };
+ }
+ else
+ {
+ if (args[0].NodeType == ExpressionType.Constant)
+ {
+ args = new Expression[] { instance, args[0] };
+ }
+ else
+ {
+ if (signature.GetParameters().Last().ParameterType == typeof(System.Linq.IQueryable)
+ || signature.GetParameters().Last().ParameterType == typeof(IEnumerable)
+ || args[0] is LambdaExpression)
+ {
+ args = new Expression[] { instance, args[0] };
+ }
+ else
+ {
+ args = new Expression[] { instance, Expression.Lambda(args[0], innerIt) };
+ }
+ }
+ }
+
+ return Expression.Call(typeof(Queryable), signature.Name, typeArgs, args);
+ }
+
+ Expression[] ParseArgumentList()
+ {
+ ValidateToken(TokenId.OpenParen, Res.OpenParenExpected);
+ NextToken();
+ Expression[] args = token.id != TokenId.CloseParen ? ParseArguments() : new Expression[0];
+ ValidateToken(TokenId.CloseParen, Res.CloseParenOrCommaExpected);
+ NextToken();
+ return args;
+ }
+
+ Expression[] ParseArguments()
+ {
+ List argList = new List();
+ while (true)
+ {
+ argList.Add(ParseExpression());
+ if (token.id != TokenId.Comma) break;
+ NextToken();
+ }
+ return argList.ToArray();
+ }
+
+ Expression ParseElementAccess(Expression expr)
+ {
+ int errorPos = token.pos;
+ ValidateToken(TokenId.OpenBracket, Res.OpenParenExpected);
+ NextToken();
+ Expression[] args = ParseArguments();
+ ValidateToken(TokenId.CloseBracket, Res.CloseBracketOrCommaExpected);
+ NextToken();
+ if (expr.Type.IsArray)
+ {
+ if (expr.Type.GetArrayRank() != 1 || args.Length != 1)
+ throw ParseError(errorPos, Res.CannotIndexMultiDimArray);
+ Expression index = PromoteExpression(args[0], typeof(int), true);
+ if (index == null)
+ throw ParseError(errorPos, Res.InvalidIndex);
+ return Expression.ArrayIndex(expr, index);
+ }
+ else
+ {
+ MethodBase mb;
+ switch (FindIndexer(expr.Type, args, out mb))
+ {
+ case 0:
+ throw ParseError(errorPos, Res.NoApplicableIndexer,
+ GetTypeName(expr.Type));
+ case 1:
+ return Expression.Call(expr, (MethodInfo)mb, args);
+ default:
+ throw ParseError(errorPos, Res.AmbiguousIndexerInvocation,
+ GetTypeName(expr.Type));
+ }
+ }
+ }
+
+ static bool IsPredefinedType(Type type)
+ {
+ foreach (Type t in predefinedTypes) if (t == type) return true;
+ return false;
+ }
+
+ static bool IsNullableType(Type type)
+ {
+ return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
+ }
+
+ static Type GetNonNullableType(Type type)
+ {
+ return IsNullableType(type) ? type.GetGenericArguments()[0] : type;
+ }
+
+ static string GetTypeName(Type type)
+ {
+ Type baseType = GetNonNullableType(type);
+ string s = baseType.Name;
+ if (type != baseType) s += '?';
+ return s;
+ }
+
+ static bool IsNumericType(Type type)
+ {
+ return GetNumericTypeKind(type) != 0;
+ }
+
+ static bool IsSignedIntegralType(Type type)
+ {
+ return GetNumericTypeKind(type) == 2;
+ }
+
+ static bool IsUnsignedIntegralType(Type type)
+ {
+ return GetNumericTypeKind(type) == 3;
+ }
+
+ static int GetNumericTypeKind(Type type)
+ {
+ type = GetNonNullableType(type);
+ if (type.IsEnum) return 0;
+ switch (Type.GetTypeCode(type))
+ {
+ case TypeCode.Char:
+ case TypeCode.Single:
+ case TypeCode.Double:
+ case TypeCode.Decimal:
+ return 1;
+ case TypeCode.SByte:
+ case TypeCode.Int16:
+ case TypeCode.Int32:
+ case TypeCode.Int64:
+ return 2;
+ case TypeCode.Byte:
+ case TypeCode.UInt16:
+ case TypeCode.UInt32:
+ case TypeCode.UInt64:
+ return 3;
+ default:
+ return 0;
+ }
+ }
+
+ static bool IsEnumType(Type type)
+ {
+ return GetNonNullableType(type).IsEnum;
+ }
+
+ void CheckAndPromoteOperand(Type signatures, string opName, ref Expression expr, int errorPos)
+ {
+ Expression[] args = new Expression[] { expr };
+ MethodBase method;
+ if (FindMethod(signatures, "F", false, args, out method) != 1)
+ throw ParseError(errorPos, Res.IncompatibleOperand,
+ opName, GetTypeName(args[0].Type));
+ expr = args[0];
+ }
+
+ void CheckAndPromoteOperands(Type signatures, string opName, ref Expression left, ref Expression right, int errorPos)
+ {
+ Expression[] args = new Expression[] { left, right };
+ MethodBase method;
+ if (FindMethod(signatures, "F", false, args, out method) != 1)
+ throw IncompatibleOperandsError(opName, left, right, errorPos);
+ left = args[0];
+ right = args[1];
+ }
+
+ Exception IncompatibleOperandsError(string opName, Expression left, Expression right, int pos)
+ {
+ return ParseError(pos, Res.IncompatibleOperands,
+ opName, GetTypeName(left.Type), GetTypeName(right.Type));
+ }
+
+ MemberInfo FindPropertyOrField(Type type, string memberName, bool staticAccess)
+ {
+ BindingFlags flags = BindingFlags.Public | BindingFlags.DeclaredOnly |
+ (staticAccess ? BindingFlags.Static : BindingFlags.Instance);
+ foreach (Type t in SelfAndBaseTypes(type))
+ {
+ MemberInfo[] members = t.FindMembers(MemberTypes.Property | MemberTypes.Field,
+ flags, Type.FilterNameIgnoreCase, memberName);
+ if (members.Length != 0) return members[0];
+ }
+ return null;
+ }
+
+ int FindMethod(Type type, string methodName, bool staticAccess, Expression[] args, out MethodBase method)
+ {
+ BindingFlags flags = BindingFlags.Public | BindingFlags.DeclaredOnly |
+ (staticAccess ? BindingFlags.Static : BindingFlags.Instance);
+ foreach (Type t in SelfAndBaseTypes(type))
+ {
+ MemberInfo[] members = t.FindMembers(MemberTypes.Method,
+ flags, Type.FilterNameIgnoreCase, methodName).ToArray();//GetExtensionMethods
+ int count = FindBestMethod(members.Cast(), args, out method);
+ if (count != 0) return count;
+ }
+ method = null;
+ return 0;
+ }
+
+
+ //UNDONE: Extract extension methods dynamically instead of relying on statically defined subset in code see IEnumerableSignatures
+ IEnumerable GetExtensionMethods(Type extendedType, string methodName)
+ {
+ //var query = from type in typeof(System.Linq.Enumerable).
+ // where type.IsSealed && !type.IsGenericType && !type.IsNested
+ var query = from method in typeof(System.Linq.Enumerable).GetMethods(BindingFlags.Static
+ | BindingFlags.Public | BindingFlags.NonPublic)
+ where method.IsDefined(typeof(ExtensionAttribute), false)
+ where method.Name == methodName
+ //where method.GetParameters()[0].ParameterType == extendedType
+ select method;
+ return query;
+ }
+
+
+ int FindIndexer(Type type, Expression[] args, out MethodBase method)
+ {
+ foreach (Type t in SelfAndBaseTypes(type))
+ {
+ MemberInfo[] members = t.GetDefaultMembers();
+ if (members.Length != 0)
+ {
+ IEnumerable methods = members.
+ OfType().
+ Select(p => (MethodBase)p.GetGetMethod()).
+ Where(m => m != null);
+ int count = FindBestMethod(methods, args, out method);
+ if (count != 0) return count;
+ }
+ }
+ method = null;
+ return 0;
+ }
+
+ static IEnumerable SelfAndBaseTypes(Type type)
+ {
+ if (type.IsInterface)
+ {
+ List types = new List();
+ AddInterface(types, type);
+ return types;
+ }
+ return SelfAndBaseClasses(type);
+ }
+
+ static IEnumerable SelfAndBaseClasses(Type type)
+ {
+ while (type != null)
+ {
+ yield return type;
+ type = type.BaseType;
+ }
+ }
+
+ static void AddInterface(List types, Type type)
+ {
+ if (!types.Contains(type))
+ {
+ types.Add(type);
+ foreach (Type t in type.GetInterfaces()) AddInterface(types, t);
+ }
+ }
+
+ class MethodData
+ {
+ public MethodBase MethodBase;
+ public ParameterInfo[] Parameters;
+ public Expression[] Args;
+ }
+
+ int FindBestMethod(IEnumerable methods, Expression[] args, out MethodBase method)
+ {
+ MethodData[] applicable = methods.
+ Select(m => new MethodData { MethodBase = m, Parameters = m.GetParameters().Where(p => p.ParameterType != ((MethodInfo)m).ReturnType).ToArray() }).
+ Where(m => IsApplicable(m, args)).
+ ToArray();
+ if (applicable.Length > 1)
+ {
+ applicable = applicable.
+ Where(m => applicable.All(n => m == n || IsBetterThan(args, m, n))).
+ ToArray();
+ }
+ if (applicable.Length == 1)
+ {
+ MethodData md = applicable[0];
+ for (int i = 0; i < args.Length; i++) args[i] = md.Args[i];
+ method = md.MethodBase;
+ }
+ else
+ {
+ method = null;
+ }
+ return applicable.Length;
+ }
+
+ bool IsApplicable(MethodData method, Expression[] args)
+ {
+ if (method.Parameters.Length != args.Length) return false;
+ Expression[] promotedArgs = new Expression[args.Length];
+ for (int i = 0; i < args.Length; i++)
+ {
+ ParameterInfo pi = method.Parameters[i];
+ if (pi.IsOut) return false;
+ Expression promoted = PromoteExpression(args[i], pi.ParameterType, false);
+ if (promoted == null) return false;
+ promotedArgs[i] = promoted;
+ }
+ method.Args = promotedArgs;
+ return true;
+ }
+
+ Expression PromoteExpression(Expression expr, Type type, bool exact)
+ {
+ if (expr.Type == type || ((expr is LambdaExpression) && ((LambdaExpression)expr).ReturnType == type) || (expr is LambdaExpression) && type == (typeof(LambdaExpression))) return expr;
+ //if (expr.Type == type || ((expr is LambdaExpression) && (type is typeof(LambdaExpression))) return expr;
+ if (expr is ConstantExpression)
+ {
+ ConstantExpression ce = (ConstantExpression)expr;
+ if (ce == nullLiteral)
+ {
+ if (!type.IsValueType || IsNullableType(type))
+ return Expression.Constant(null, type);
+ }
+ else
+ {
+ string text;
+ if (literals.TryGetValue(ce, out text))
+ {
+ Type target = GetNonNullableType(type);
+ Object value = null;
+ switch (Type.GetTypeCode(ce.Type))
+ {
+ case TypeCode.Int32:
+ case TypeCode.UInt32:
+ case TypeCode.Int64:
+ case TypeCode.UInt64:
+ value = ParseNumber(text, target);
+ break;
+ case TypeCode.Double:
+ if (target == typeof(decimal)) value = ParseNumber(text, target);
+ break;
+ case TypeCode.String:
+ value = ParseEnum(text, target);
+ break;
+ }
+ if (value != null)
+ return Expression.Constant(value, type);
+ }
+ }
+ }
+ if (IsCompatibleWith(expr.Type, type))
+ {
+ if (type.IsValueType || exact) return Expression.Convert(expr, type);
+ return expr;
+ }
+ return null;
+ }
+
+ static object ParseNumber(string text, Type type)
+ {
+ switch (Type.GetTypeCode(GetNonNullableType(type)))
+ {
+ case TypeCode.SByte:
+ sbyte sb;
+ if (sbyte.TryParse(text, out sb)) return sb;
+ break;
+ case TypeCode.Byte:
+ byte b;
+ if (byte.TryParse(text, out b)) return b;
+ break;
+ case TypeCode.Int16:
+ short s;
+ if (short.TryParse(text, out s)) return s;
+ break;
+ case TypeCode.UInt16:
+ ushort us;
+ if (ushort.TryParse(text, out us)) return us;
+ break;
+ case TypeCode.Int32:
+ int i;
+ if (int.TryParse(text, out i)) return i;
+ break;
+ case TypeCode.UInt32:
+ uint ui;
+ if (uint.TryParse(text, out ui)) return ui;
+ break;
+ case TypeCode.Int64:
+ long l;
+ if (long.TryParse(text, out l)) return l;
+ break;
+ case TypeCode.UInt64:
+ ulong ul;
+ if (ulong.TryParse(text, out ul)) return ul;
+ break;
+ case TypeCode.Single:
+ float f;
+ if (float.TryParse(text, out f)) return f;
+ break;
+ case TypeCode.Double:
+ double d;
+ if (double.TryParse(text, out d)) return d;
+ break;
+ case TypeCode.Decimal:
+ decimal e;
+ if (decimal.TryParse(text, out e)) return e;
+ break;
+ }
+ return null;
+ }
+
+ static object ParseEnum(string name, Type type)
+ {
+ if (type.IsEnum)
+ {
+ MemberInfo[] memberInfos = type.FindMembers(MemberTypes.Field,
+ BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Static,
+ Type.FilterNameIgnoreCase, name);
+ if (memberInfos.Length != 0) return ((FieldInfo)memberInfos[0]).GetValue(null);
+ }
+ return null;
+ }
+
+ static bool IsCompatibleWith(Type source, Type target)
+ {
+ if (source == target) return true;
+ if (!target.IsValueType) return target.IsAssignableFrom(source);
+ Type st = GetNonNullableType(source);
+ Type tt = GetNonNullableType(target);
+ if (st != source && tt == target) return false;
+ TypeCode sc = st.IsEnum ? TypeCode.Object : Type.GetTypeCode(st);
+ TypeCode tc = tt.IsEnum ? TypeCode.Object : Type.GetTypeCode(tt);
+ switch (sc)
+ {
+ case TypeCode.SByte:
+ switch (tc)
+ {
+ case TypeCode.SByte:
+ case TypeCode.Int16:
+ case TypeCode.Int32:
+ case TypeCode.Int64:
+ case TypeCode.Single:
+ case TypeCode.Double:
+ case TypeCode.Decimal:
+ return true;
+ }
+ break;
+ case TypeCode.Byte:
+ switch (tc)
+ {
+ case TypeCode.Byte:
+ case TypeCode.Int16:
+ case TypeCode.UInt16:
+ case TypeCode.Int32:
+ case TypeCode.UInt32:
+ case TypeCode.Int64:
+ case TypeCode.UInt64:
+ case TypeCode.Single:
+ case TypeCode.Double:
+ case TypeCode.Decimal:
+ return true;
+ }
+ break;
+ case TypeCode.Int16:
+ switch (tc)
+ {
+ case TypeCode.Int16:
+ case TypeCode.Int32:
+ case TypeCode.Int64:
+ case TypeCode.Single:
+ case TypeCode.Double:
+ case TypeCode.Decimal:
+ return true;
+ }
+ break;
+ case TypeCode.UInt16:
+ switch (tc)
+ {
+ case TypeCode.UInt16:
+ case TypeCode.Int32:
+ case TypeCode.UInt32:
+ case TypeCode.Int64:
+ case TypeCode.UInt64:
+ case TypeCode.Single:
+ case TypeCode.Double:
+ case TypeCode.Decimal:
+ return true;
+ }
+ break;
+ case TypeCode.Int32:
+ switch (tc)
+ {
+ case TypeCode.Int32:
+ case TypeCode.Int64:
+ case TypeCode.Single:
+ case TypeCode.Double:
+ case TypeCode.Decimal:
+ return true;
+ }
+ break;
+ case TypeCode.UInt32:
+ switch (tc)
+ {
+ case TypeCode.UInt32:
+ case TypeCode.Int64:
+ case TypeCode.UInt64:
+ case TypeCode.Single:
+ case TypeCode.Double:
+ case TypeCode.Decimal:
+ return true;
+ }
+ break;
+ case TypeCode.Int64:
+ switch (tc)
+ {
+ case TypeCode.Int64:
+ case TypeCode.Single:
+ case TypeCode.Double:
+ case TypeCode.Decimal:
+ return true;
+ }
+ break;
+ case TypeCode.UInt64:
+ switch (tc)
+ {
+ case TypeCode.UInt64:
+ case TypeCode.Single:
+ case TypeCode.Double:
+ case TypeCode.Decimal:
+ return true;
+ }
+ break;
+ case TypeCode.Single:
+ switch (tc)
+ {
+ case TypeCode.Single:
+ case TypeCode.Double:
+ return true;
+ }
+ break;
+ default:
+ if (st == tt) return true;
+ break;
+ }
+ return false;
+ }
+
+ static bool IsBetterThan(Expression[] args, MethodData m1, MethodData m2)
+ {
+ bool better = false;
+ for (int i = 0; i < args.Length; i++)
+ {
+ int c = CompareConversions(args[i].Type,
+ m1.Parameters[i].ParameterType,
+ m2.Parameters[i].ParameterType);
+ if (c < 0) return false;
+ if (c > 0) better = true;
+ }
+ return better;
+ }
+
+ // Return 1 if s -> t1 is a better conversion than s -> t2
+ // Return -1 if s -> t2 is a better conversion than s -> t1
+ // Return 0 if neither conversion is better
+ static int CompareConversions(Type s, Type t1, Type t2)
+ {
+ if (t1 == t2) return 0;
+ if (s == t1) return 1;
+ if (s == t2) return -1;
+ bool t1t2 = IsCompatibleWith(t1, t2);
+ bool t2t1 = IsCompatibleWith(t2, t1);
+ if (t1t2 && !t2t1) return 1;
+ if (t2t1 && !t1t2) return -1;
+ if (IsSignedIntegralType(t1) && IsUnsignedIntegralType(t2)) return 1;
+ if (IsSignedIntegralType(t2) && IsUnsignedIntegralType(t1)) return -1;
+ return 0;
+ }
+
+ Expression GenerateEqual(Expression left, Expression right)
+ {
+ return Expression.Equal(left, right);
+ }
+
+ Expression GenerateNotEqual(Expression left, Expression right)
+ {
+ return Expression.NotEqual(left, right);
+ }
+
+ Expression GenerateGreaterThan(Expression left, Expression right)
+ {
+ if (left.Type == typeof(string))
+ {
+ return Expression.GreaterThan(
+ GenerateStaticMethodCall("Compare", left, right),
+ Expression.Constant(0)
+ );
+ }
+ return Expression.GreaterThan(left, right);
+ }
+
+ Expression GenerateGreaterThanEqual(Expression left, Expression right)
+ {
+ if (left.Type == typeof(string))
+ {
+ return Expression.GreaterThanOrEqual(
+ GenerateStaticMethodCall("Compare", left, right),
+ Expression.Constant(0)
+ );
+ }
+ return Expression.GreaterThanOrEqual(left, right);
+ }
+
+ Expression GenerateLessThan(Expression left, Expression right)
+ {
+ if (left.Type == typeof(string))
+ {
+ return Expression.LessThan(
+ GenerateStaticMethodCall("Compare", left, right),
+ Expression.Constant(0)
+ );
+ }
+ return Expression.LessThan(left, right);
+ }
+
+ Expression GenerateLessThanEqual(Expression left, Expression right)
+ {
+ if (left.Type == typeof(string))
+ {
+ return Expression.LessThanOrEqual(
+ GenerateStaticMethodCall("Compare", left, right),
+ Expression.Constant(0)
+ );
+ }
+ return Expression.LessThanOrEqual(left, right);
+ }
+
+ Expression GenerateAdd(Expression left, Expression right)
+ {
+ if (left.Type == typeof(string) && right.Type == typeof(string))
+ {
+ return GenerateStaticMethodCall("Concat", left, right);
+ }
+ return Expression.Add(left, right);
+ }
+
+ Expression GenerateSubtract(Expression left, Expression right)
+ {
+ return Expression.Subtract(left, right);
+ }
+
+ Expression GenerateStringConcat(Expression left, Expression right)
+ {
+ return Expression.Call(
+ null,
+ typeof(string).GetMethod("Concat", new[] { typeof(object), typeof(object) }),
+ new[] { left, right });
+ }
+
+ MethodInfo GetStaticMethod(string methodName, Expression left, Expression right)
+ {
+ return left.Type.GetMethod(methodName, new[] { left.Type, right.Type });
+ }
+
+ Expression GenerateStaticMethodCall(string methodName, Expression left, Expression right)
+ {
+ return Expression.Call(null, GetStaticMethod(methodName, left, right), new[] { left, right });
+ }
+
+ void SetTextPos(int pos)
+ {
+ textPos = pos;
+ ch = textPos < textLen ? text[textPos] : '\0';
+ }
+
+ void NextChar()
+ {
+ if (textPos < textLen) textPos++;
+ ch = textPos < textLen ? text[textPos] : '\0';
+ }
+
+ void NextToken()
+ {
+ while (Char.IsWhiteSpace(ch)) NextChar();
+ TokenId t;
+ int tokenPos = textPos;
+ switch (ch)
+ {
+ case '!':
+ NextChar();
+ if (ch == '=')
+ {
+ NextChar();
+ t = TokenId.ExclamationEqual;
+ }
+ else
+ {
+ t = TokenId.Exclamation;
+ }
+ break;
+ case '%':
+ NextChar();
+ t = TokenId.Percent;
+ break;
+ case '&':
+ NextChar();
+ if (ch == '&')
+ {
+ NextChar();
+ t = TokenId.DoubleAmphersand;
+ }
+ else
+ {
+ t = TokenId.Amphersand;
+ }
+ break;
+ case '(':
+ NextChar();
+ t = TokenId.OpenParen;
+ break;
+ case ')':
+ NextChar();
+ t = TokenId.CloseParen;
+ break;
+ case '*':
+ NextChar();
+ t = TokenId.Asterisk;
+ break;
+ case '+':
+ NextChar();
+ t = TokenId.Plus;
+ break;
+ case ',':
+ NextChar();
+ t = TokenId.Comma;
+ break;
+ case '-':
+ NextChar();
+ t = TokenId.Minus;
+ break;
+ case '.':
+ NextChar();
+ t = TokenId.Dot;
+ break;
+ case '/':
+ NextChar();
+ t = TokenId.Slash;
+ break;
+ case ':':
+ NextChar();
+ t = TokenId.Colon;
+ break;
+ case '<':
+ NextChar();
+ if (ch == '=')
+ {
+ NextChar();
+ t = TokenId.LessThanEqual;
+ }
+ else
+ {
+ t = TokenId.LessThan;
+ }
+ break;
+ case '=':
+ NextChar();
+ if (ch == '=')
+ {
+ NextChar();
+ t = TokenId.DoubleEqual;
+ }
+ else if (ch == '>')
+ {
+ NextChar();
+ t = TokenId.Lambda;
+ }
+ else
+ {
+ t = TokenId.Equal;
+ }
+ break;
+ case '>':
+ NextChar();
+ if (ch == '=')
+ {
+ NextChar();
+ t = TokenId.GreaterThanEqual;
+ }
+ else
+ {
+ t = TokenId.GreaterThan;
+ }
+ break;
+ case '?':
+ NextChar();
+ t = TokenId.Question;
+ break;
+ case '[':
+ NextChar();
+ t = TokenId.OpenBracket;
+ break;
+ case ']':
+ NextChar();
+ t = TokenId.CloseBracket;
+ break;
+ case '|':
+ NextChar();
+ if (ch == '|')
+ {
+ NextChar();
+ t = TokenId.DoubleBar;
+ }
+ else
+ {
+ t = TokenId.Bar;
+ }
+ break;
+ case '"':
+ case '\'':
+ char quote = ch;
+ do
+ {
+ NextChar();
+ while (textPos < textLen && ch != quote) NextChar();
+ if (textPos == textLen)
+ throw ParseError(textPos, Res.UnterminatedStringLiteral);
+ NextChar();
+ } while (ch == quote);
+ t = TokenId.StringLiteral;
+ break;
+ default:
+ if (Char.IsLetter(ch) || ch == '@' || ch == '_')
+ {
+ do
+ {
+ NextChar();
+ } while (Char.IsLetterOrDigit(ch) || ch == '_');
+ t = TokenId.Identifier;
+ break;
+ }
+ if (Char.IsDigit(ch))
+ {
+ t = TokenId.IntegerLiteral;
+ do
+ {
+ NextChar();
+ } while (Char.IsDigit(ch));
+ if (ch == '.')
+ {
+ t = TokenId.RealLiteral;
+ NextChar();
+ ValidateDigit();
+ do
+ {
+ NextChar();
+ } while (Char.IsDigit(ch));
+ }
+ if (ch == 'E' || ch == 'e')
+ {
+ t = TokenId.RealLiteral;
+ NextChar();
+ if (ch == '+' || ch == '-') NextChar();
+ ValidateDigit();
+ do
+ {
+ NextChar();
+ } while (Char.IsDigit(ch));
+ }
+ if (ch == 'F' || ch == 'f') NextChar();
+ break;
+ }
+ if (textPos == textLen)
+ {
+ t = TokenId.End;
+ break;
+ }
+ throw ParseError(textPos, Res.InvalidCharacter, ch);
+ }
+ token.id = t;
+ token.text = text.Substring(tokenPos, textPos - tokenPos);
+ token.pos = tokenPos;
+ }
+
+ bool TokenIdentifierIs(string id)
+ {
+ return token.id == TokenId.Identifier && String.Equals(id, token.text, StringComparison.OrdinalIgnoreCase);
+ }
+
+ string GetIdentifier()
+ {
+ ValidateToken(TokenId.Identifier, Res.IdentifierExpected);
+ string id = token.text;
+ if (id.Length > 1 && id[0] == '@') id = id.Substring(1);
+ return id;
+ }
+
+ void ValidateDigit()
+ {
+ if (!Char.IsDigit(ch)) throw ParseError(textPos, Res.DigitExpected);
+ }
+
+ void ValidateToken(TokenId t, string errorMessage)
+ {
+ if (token.id != t) throw ParseError(errorMessage);
+ }
+
+ void ValidateToken(TokenId t)
+ {
+ if (token.id != t) throw ParseError(Res.SyntaxError);
+ }
+
+ Exception ParseError(string format, params object[] args)
+ {
+ return ParseError(token.pos, format, args);
+ }
+
+ Exception ParseError(int pos, string format, params object[] args)
+ {
+ return new ParseException(string.Format(System.Globalization.CultureInfo.CurrentCulture, format, args), pos);
+ }
+
+ static Dictionary CreateKeywords()
+ {
+ Dictionary d = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ d.Add("true", trueLiteral);
+ d.Add("false", falseLiteral);
+ d.Add("null", nullLiteral);
+ d.Add(keywordIt, keywordIt);
+ d.Add(keywordIif, keywordIif);
+ d.Add(keywordNew, keywordNew);
+ foreach (Type type in predefinedTypes) d.Add(type.Name, type);
+ return d;
+ }
+ }
+
+ static class Res
+ {
+ public const string DuplicateIdentifier = "The identifier '{0}' was defined more than once";
+ public const string ExpressionTypeMismatch = "Expression of type '{0}' expected";
+ public const string ExpressionExpected = "Expression expected";
+ public const string InvalidCharacterLiteral = "Character literal must contain exactly one character";
+ public const string InvalidIntegerLiteral = "Invalid integer literal '{0}'";
+ public const string InvalidRealLiteral = "Invalid real literal '{0}'";
+ public const string UnknownIdentifier = "Unknown identifier '{0}'";
+ public const string NoItInScope = "No 'it' is in scope";
+ public const string IifRequiresThreeArgs = "The 'iif' function requires three arguments";
+ public const string FirstExprMustBeBool = "The first expression must be of type 'Boolean'";
+ public const string BothTypesConvertToOther = "Both of the types '{0}' and '{1}' convert to the other";
+ public const string NeitherTypeConvertsToOther = "Neither of the types '{0}' and '{1}' converts to the other";
+ public const string MissingAsClause = "Expression is missing an 'as' clause";
+ public const string ArgsIncompatibleWithLambda = "Argument list incompatible with lambda expression";
+ public const string TypeHasNoNullableForm = "Type '{0}' has no nullable form";
+ public const string NoMatchingConstructor = "No matching constructor in type '{0}'";
+ public const string AmbiguousConstructorInvocation = "Ambiguous invocation of '{0}' constructor";
+ public const string CannotConvertValue = "A value of type '{0}' cannot be converted to type '{1}'";
+ public const string NoApplicableMethod = "No applicable method '{0}' exists in type '{1}'";
+ public const string MethodsAreInaccessible = "Methods on type '{0}' are not accessible";
+ public const string MethodIsVoid = "Method '{0}' in type '{1}' does not return a value";
+ public const string AmbiguousMethodInvocation = "Ambiguous invocation of method '{0}' in type '{1}'";
+ public const string UnknownPropertyOrField = "No property or field '{0}' exists in type '{1}'";
+ public const string NoApplicableAggregate = "No applicable aggregate method '{0}' exists";
+ public const string CannotIndexMultiDimArray = "Indexing of multi-dimensional arrays is not supported";
+ public const string InvalidIndex = "Array index must be an integer expression";
+ public const string NoApplicableIndexer = "No applicable indexer exists in type '{0}'";
+ public const string AmbiguousIndexerInvocation = "Ambiguous invocation of indexer in type '{0}'";
+ public const string IncompatibleOperand = "Operator '{0}' incompatible with operand type '{1}'";
+ public const string IncompatibleOperands = "Operator '{0}' incompatible with operand types '{1}' and '{2}'";
+ public const string UnterminatedStringLiteral = "Unterminated string literal";
+ public const string InvalidCharacter = "Syntax error '{0}'";
+ public const string DigitExpected = "Digit expected";
+ public const string SyntaxError = "Syntax error";
+ public const string TokenExpected = "{0} expected";
+ public const string ParseExceptionFormat = "{0} (at index {1})";
+ public const string ColonExpected = "':' expected";
+ public const string OpenParenExpected = "'(' expected";
+ public const string CloseParenOrOperatorExpected = "')' or operator expected";
+ public const string CloseParenOrCommaExpected = "')' or ',' expected";
+ public const string DotOrOpenParenExpected = "'.' or '(' expected";
+ public const string OpenBracketExpected = "'[' expected";
+ public const string CloseBracketOrCommaExpected = "']' or ',' expected";
+ public const string IdentifierExpected = "Identifier expected";
+ }
+}
diff --git a/TimberWinR.TestGenerator/JsonLogFileGenerator.cs b/TimberWinR.TestGenerator/JsonLogFileGenerator.cs
new file mode 100644
index 0000000..cd85417
--- /dev/null
+++ b/TimberWinR.TestGenerator/JsonLogFileGenerator.cs
@@ -0,0 +1,252 @@
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Runtime.Remoting.Messaging;
+using System.Threading;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using NLog;
+using NLog.Config;
+using NLog.Targets;
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+using System.IO;
+
+namespace TimberWinR.TestGenerator
+{
+ class JsonLogFileTestParameters
+ {
+ public int NumMessages { get; set; }
+ public string LogFileDir { get; set; }
+ public string LogFileName { get; set; }
+ public int SleepTimeMilliseconds { get; set; }
+ public JsonLogFileTestParameters()
+ {
+ SleepTimeMilliseconds = 30;
+ LogFileDir = ".";
+ NumMessages = 10;
+ }
+ }
+
+ class JsonLogFileGenerator
+ {
+ public static int Generate(JsonLogFileTestParameters parms)
+ {
+ LogManager.GetCurrentClassLogger().Info("Start JSON LogFile Generation for: {0} on Thread: {1}", Path.GetFullPath(parms.LogFileName), Thread.CurrentThread.ManagedThreadId);
+
+ var logFilePath = Path.Combine(parms.LogFileDir, parms.LogFileName);
+
+ try
+ {
+ if (File.Exists(logFilePath))
+ {
+ LogManager.GetCurrentClassLogger().Info("Deleting file: {0}", logFilePath);
+ File.Delete(logFilePath);
+ }
+ }
+ catch (Exception ex)
+ {
+ LogManager.GetCurrentClassLogger().Error(ex);
+ }
+
+
+ var hostName = System.Environment.MachineName + "." +
+ Microsoft.Win32.Registry.LocalMachine.OpenSubKey(
+ "SYSTEM\\CurrentControlSet\\services\\Tcpip\\Parameters").GetValue("Domain", "").ToString();
+
+ var watch = Stopwatch.StartNew();
+
+ // This text is always added, making the file longer over time
+ // if it is not deleted.
+ using (StreamWriter sw = File.AppendText(logFilePath))
+ {
+ sw.AutoFlush = true;
+ for (int i = 0; i < parms.NumMessages; i++)
+ {
+ JObject o = new JObject
+ {
+ {"LineNumber", i+1},
+ {"Application", "jsonlogfile-generator"},
+ {"Host", hostName},
+ {"UtcTimestamp", DateTime.UtcNow.ToString("o")},
+ {"Type", "jsonlog"},
+ {"Message", string.Format("{0}: Testgenerator jsonlogfile message {1}", i+1, DateTime.UtcNow.ToString("o"))},
+ {"Index", "logstash"}
+ };
+ sw.WriteLine(o.ToString(Formatting.None));
+
+ Thread.Sleep(parms.SleepTimeMilliseconds);
+ }
+ LogManager.GetCurrentClassLogger().Info("Elapsed Time for {0} was {1} seconds", Path.GetFullPath(parms.LogFileName), watch.Elapsed);
+ watch.Reset();
+ }
+
+ LogManager.GetCurrentClassLogger().Info("Finished JSON Log File Generation for: {0} elapsed: {1}", Path.GetFullPath(parms.LogFileName), watch.Elapsed);
+
+ return parms.NumMessages;
+ }
+ }
+
+ class JsonRollingLogFileGenerator
+ {
+ public static int Generate(JsonLogFileTestParameters parms)
+ {
+ LogManager.GetCurrentClassLogger().Info("Start JSON RollingLogFile Generation for: {0} on Thread: {1}", Path.GetFullPath(parms.LogFileName), Thread.CurrentThread.ManagedThreadId);
+
+ var logFilePath = Path.Combine(parms.LogFileDir, parms.LogFileName);
+
+ try
+ {
+ if (File.Exists(logFilePath))
+ File.Delete(logFilePath);
+
+ if (File.Exists(logFilePath + ".rolled"))
+ File.Delete(logFilePath + ".rolled");
+ }
+ catch (Exception ex)
+ {
+ LogManager.GetCurrentClassLogger().Error(ex);
+ }
+
+
+ var hostName = System.Environment.MachineName + "." +
+ Microsoft.Win32.Registry.LocalMachine.OpenSubKey(
+ "SYSTEM\\CurrentControlSet\\services\\Tcpip\\Parameters").GetValue("Domain", "").ToString();
+
+
+ int quarters = parms.NumMessages/4;
+
+ int[] segments = new int[] {quarters, quarters, quarters, quarters + parms.NumMessages%4};
+ var watch = Stopwatch.StartNew();
+
+
+ int recordNumber = 0;
+ int currentTotal = 0;
+ for (int segment = 0; segment < 4; segment++)
+ {
+ currentTotal += segments[segment];
+
+ // This text is always added, making the file longer over time
+ // if it is not deleted.
+ using (StreamWriter sw = File.AppendText(logFilePath))
+ {
+ sw.AutoFlush = true;
+
+ var lwatch = Stopwatch.StartNew();
+
+ // The Rolling Generator will roll 1/2 way through the log
+ for (int i = 0; i < segments[segment]; i++)
+ {
+ JObject o = new JObject
+ {
+ {"LineNumber", recordNumber + 1},
+ {"Application", "jsonrollinglogfile-generator"},
+ {"Host", hostName},
+ {"UtcTimestamp", DateTime.UtcNow.ToString("o")},
+ {"Type", "jsonrollinglog"},
+ {
+ "Message",
+ string.Format("{0}: Testgenerator jsonrollinglogfile message {1}", recordNumber + 1,
+ DateTime.UtcNow.ToString("o"))
+ },
+ {"Index", "logstash"}
+ };
+ sw.WriteLine(o.ToString(Formatting.None));
+ recordNumber++;
+ Thread.Sleep(parms.SleepTimeMilliseconds);
+ }
+ LogManager.GetCurrentClassLogger().Info("Elapsed Time for {0} was {1} seconds for {2} logs", Path.GetFullPath(parms.LogFileName), lwatch.Elapsed, segments[segment]);
+
+ }
+
+ //
+ // We might not have yet processed all the lines from the first file, so wait till
+ // we catch up before rolling the log file.
+ //
+ LogManager.GetCurrentClassLogger().Info("{0}: Waiting for output to catch up: {1} {2}", Thread.CurrentThread.ManagedThreadId, logFilePath, currentTotal);
+ WaitOutputToCatchUp(logFilePath, currentTotal);
+
+ //
+ // Roll the log + wait for the reader to catch up.
+ //
+
+ LogManager.GetCurrentClassLogger().Info("{0}: Rolling Log File: {1} {2}", Thread.CurrentThread.ManagedThreadId, logFilePath, File.GetCreationTimeUtc(logFilePath));
+
+ RollLogFile(logFilePath);
+
+ LogManager.GetCurrentClassLogger().Info("{0}: Finished Rolling Log File: {1}", Thread.CurrentThread.ManagedThreadId, logFilePath);
+ }
+
+ watch.Stop();
+
+ LogManager.GetCurrentClassLogger().Info("Finished JSON RollingLogFile File Generation for: {0} elapsed: {1}", Path.GetFullPath(parms.LogFileName), watch.Elapsed);
+
+ return parms.NumMessages;
+ }
+
+ private static void WaitOutputToCatchUp(string logFilePath, int firstPart)
+ {
+ bool caughtUp = false;
+ do
+ {
+ var json = Program.Diagnostics.DiagnosticsOutput();
+
+ IList inputs = json["timberwinr"]["inputs"].Children().ToList();
+ foreach (JToken t in inputs)
+ {
+ JProperty inputProp = t.First as JProperty;
+ if (inputProp.Name == "taillog" || inputProp.Name == "log")
+ {
+ var files = inputProp.Value["filedb"].Children().ToList();
+ foreach (var file in files)
+ {
+ var fileName = file["FileName"].ToString();
+ FileInfo fi1 = new FileInfo(fileName);
+ FileInfo fi2 = new FileInfo(logFilePath);
+ if (fi1.FullName == fi2.FullName)
+ {
+ var linesProcessed = file["LinesProcessed"].Value();
+ if (linesProcessed >= firstPart)
+ {
+ caughtUp = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ Thread.Sleep(300);
+ } while (!caughtUp);
+
+ LogManager.GetCurrentClassLogger().Info("{0}: Finished Waiting for output to catch up: {1} {2}", Thread.CurrentThread.ManagedThreadId, logFilePath, firstPart);
+
+ }
+
+ private static void RollLogFile(string logFilePath)
+ {
+ bool moved = false;
+ do
+ {
+ try
+ {
+ if (File.Exists(logFilePath + ".rolled"))
+ File.Delete(logFilePath + ".rolled");
+
+ File.Move(logFilePath, logFilePath + ".rolled");
+ moved = true;
+ }
+ catch (Exception)
+ {
+ Thread.Sleep(100);
+ }
+ } while (!moved);
+ Thread.Sleep(1000);
+ }
+ }
+
+}
diff --git a/TimberWinR.TestGenerator/LogFileGenerator.cs b/TimberWinR.TestGenerator/LogFileGenerator.cs
new file mode 100644
index 0000000..f1b1936
--- /dev/null
+++ b/TimberWinR.TestGenerator/LogFileGenerator.cs
@@ -0,0 +1,94 @@
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Runtime.Remoting.Messaging;
+using System.Threading;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using NLog;
+using NLog.Config;
+using NLog.Targets;
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+using System.IO;
+
+
+namespace TimberWinR.TestGenerator
+{
+ class LogFileTestParameters
+ {
+ public int NumMessages { get; set; }
+ public string LogFileDir { get; set; }
+ public string LogFileName { get; set; }
+ public int SleepTimeMilliseconds { get; set; }
+ public LogFileTestParameters()
+ {
+ SleepTimeMilliseconds = 30;
+ LogFileDir = ".";
+ NumMessages = 10;
+ }
+ }
+
+ class LogFileGenerator
+ {
+ public static int Generate(JsonLogFileTestParameters parms)
+ {
+ LogManager.GetCurrentClassLogger().Info("Start LogFile Generation for: {0} on Thread: {1}", Path.GetFullPath(parms.LogFileName), Thread.CurrentThread.ManagedThreadId);
+
+ var logFilePath = Path.Combine(parms.LogFileDir, parms.LogFileName);
+
+ try
+ {
+ if (File.Exists(logFilePath))
+ {
+ LogManager.GetCurrentClassLogger().Info("Deleting file: {0}", logFilePath);
+ File.Delete(logFilePath);
+ }
+ }
+ catch (Exception ex)
+ {
+ LogManager.GetCurrentClassLogger().Error(ex);
+ }
+
+
+ var hostName = System.Environment.MachineName + "." +
+ Microsoft.Win32.Registry.LocalMachine.OpenSubKey(
+ "SYSTEM\\CurrentControlSet\\services\\Tcpip\\Parameters").GetValue("Domain", "").ToString();
+
+ var watch = Stopwatch.StartNew();
+
+ // This text is always added, making the file longer over time
+ // if it is not deleted.
+ using (StreamWriter sw = File.AppendText(logFilePath))
+ {
+ sw.AutoFlush = true;
+ for (int i = 0; i < parms.NumMessages; i++)
+ {
+ JObject o = new JObject
+ {
+ {"LineNumber", i+1},
+ {"Application", "logfile-generator"},
+ {"Host", hostName},
+ {"UtcTimestamp", DateTime.UtcNow.ToString("o")},
+ {"Type", "log"},
+ {"Message", string.Format("{0}: Testgenerator logfile message {1}", i+1, DateTime.UtcNow.ToString("o"))},
+ {"Index", "logstash"}
+ };
+ sw.WriteLine(o.ToString(Formatting.None));
+
+ Thread.Sleep(parms.SleepTimeMilliseconds);
+ }
+ LogManager.GetCurrentClassLogger().Info("Elapsed Time for {0} was {1} seconds", Path.GetFullPath(parms.LogFileName), watch.Elapsed);
+ watch.Reset();
+ }
+
+ LogManager.GetCurrentClassLogger().Info("Finished LogFile Generation for: {0} elapsed: {1}", Path.GetFullPath(parms.LogFileName), watch.Elapsed);
+
+ return parms.NumMessages;
+ }
+ }
+}
diff --git a/TimberWinR.TestGenerator/Program.cs b/TimberWinR.TestGenerator/Program.cs
new file mode 100644
index 0000000..18cc313
--- /dev/null
+++ b/TimberWinR.TestGenerator/Program.cs
@@ -0,0 +1,579 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Linq.Expressions;
+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;
+
+
+namespace TimberWinR.TestGenerator
+{
+ public class Program
+ {
+ private static List _tasks = new List();
+ 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
+ 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
+ var jsonTimberWinr = ShutdownTimberWinR();
+
+ 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;
+ }
+
+ 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 inputs = jtest["arguments"].Children().ToList();
+ List testargs = new List();
+ 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 inputs = json["timberwinr"]["inputs"].Children().ToList();
+ foreach (JToken t in inputs)
+ {
+ JProperty inputProp = t.First as JProperty;
+ switch (inputProp.Name)
+ {
+ case "udp":
+ return VerifyConditions(json, new string[] { "udp" }, inputProp, jresult);
+
+ case "log":
+ case "taillog":
+ return VerifyConditions(json, new string[] { "log", "taillog" }, inputProp, jresult);
+ }
+ }
+
+ 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 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;
+ }
+
+ // Wait till all output has been transmitted.
+ private static void WaitForOutputTransmission()
+ {
+ bool completed = false;
+ do
+ {
+ var json = Diagnostics.DiagnosticsOutput();
+
+ //Console.WriteLine(json.ToString(Formatting.Indented));
+
+ IList inputs = json["timberwinr"]["inputs"].Children().ToList();
+ foreach (var so in inputs.Children())
+ {
+ var token = so.First;
+ var messages = token["messages"].Value();
+ // Console.WriteLine("{0} messages", messages);
+ }
+
+
+ IList outputs = json["timberwinr"]["outputs"].Children().ToList();
+ foreach (var so in outputs.Children())
+ {
+ var outputToken = so.First;
+
+ var mbc = outputToken["queuedMessageCount"].Value();
+ var smc = outputToken["sentMessageCount"].Value();
+
+ // LogManager.GetCurrentClassLogger().Info("Queued: {0}, Sent: {1}", mbc, smc);
+
+ 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()
+ {
+ _timberWinR.Shutdown();
+
+ // Cancel any/all other threads
+ _cancellationTokenSource.Cancel();
+
+ 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;
+ }
+
+ 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)
+ {
+ Console.WriteLine("Processed Config: {0}", configuration.GetHashCode());
+
+ 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)
+ {
+ _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);
+ }));
+ }
+ }
+
+ }
+}
diff --git a/TimberWinR.TestGenerator/Properties/AssemblyInfo.cs b/TimberWinR.TestGenerator/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..99e0400
--- /dev/null
+++ b/TimberWinR.TestGenerator/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("TimberWinR.TestGenerator")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("TimberWinR.TestGenerator")]
+[assembly: AssemblyCopyright("Copyright © 2015")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("a56bf91c-c5f8-4771-8ef8-ab9ad28179c4")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/TimberWinR.TestGenerator/RedisTestGenerator.cs b/TimberWinR.TestGenerator/RedisTestGenerator.cs
new file mode 100644
index 0000000..6af0cd9
--- /dev/null
+++ b/TimberWinR.TestGenerator/RedisTestGenerator.cs
@@ -0,0 +1,49 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Newtonsoft.Json.Linq;
+using ServiceStack.Redis;
+
+namespace TimberWinR.TestGenerator
+{
+ class RedisTestParameters
+ {
+ public int Port { get; set; }
+ public string Host { get; set; }
+ public int NumMessages { get; set; }
+ public RedisTestParameters()
+ {
+ NumMessages = 100;
+ Port = 6379;
+ Host = "localhost";
+ }
+ }
+
+ class RedisTestGenerator
+ {
+ public static void Generate(RedisTestParameters parms)
+ {
+ var hostName = System.Environment.MachineName + "." +
+ Microsoft.Win32.Registry.LocalMachine.OpenSubKey(
+ "SYSTEM\\CurrentControlSet\\services\\Tcpip\\Parameters").GetValue("Domain", "").ToString();
+
+ var rc = new RedisClient(parms.Host, parms.Port);
+
+ for (int i = 0; i < parms.NumMessages; i++)
+ {
+ JObject o = new JObject
+ {
+ {"Application", "redis-generator"},
+ {"Host", hostName},
+ {"UtcTimestamp", DateTime.UtcNow.ToString("o")},
+ {"Type", "redis"},
+ {"Message", "redis message " + DateTime.UtcNow.ToString("o")},
+ {"Index", "logstash"}
+ };
+ byte[] bytes = System.Text.Encoding.UTF8.GetBytes(o.ToString());
+ var restult = rc.RPush("logstash", bytes);
+ }
+ }
+ }
+}
diff --git a/TimberWinR.TestGenerator/TcpTestGenerator.cs b/TimberWinR.TestGenerator/TcpTestGenerator.cs
new file mode 100644
index 0000000..7037a27
--- /dev/null
+++ b/TimberWinR.TestGenerator/TcpTestGenerator.cs
@@ -0,0 +1,62 @@
+using System.Threading;
+using Newtonsoft.Json.Linq;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+using ServiceStack.Text;
+
+namespace TimberWinR.TestGenerator
+{
+ class TcpTestParameters
+ {
+ public int Port { get; set; }
+ public string Host { get; set; }
+ public int NumMessages { get; set; }
+ public int SleepTimeMilliseconds { get; set; }
+ public TcpTestParameters()
+ {
+ NumMessages = 100;
+ Port = 5140;
+ Host = "localhost";
+ SleepTimeMilliseconds = 10;
+ }
+ }
+
+ class TcpTestGenerator
+ {
+ public static int Generate(TcpTestParameters parms)
+ {
+ TcpClient server = new TcpClient(parms.Host, parms.Port);
+
+ var hostName = System.Environment.MachineName + "." +
+ Microsoft.Win32.Registry.LocalMachine.OpenSubKey(
+ "SYSTEM\\CurrentControlSet\\services\\Tcpip\\Parameters").GetValue("Domain", "").ToString();
+
+
+ using (NetworkStream stream = server.GetStream())
+ {
+ for (int i = 0; i < parms.NumMessages; i++)
+ {
+ JObject o = new JObject
+ {
+ {"Application", "tcp-generator"},
+ {"Host", hostName},
+ {"UtcTimestamp", DateTime.UtcNow.ToString("o")},
+ {"Type", "tcp"},
+ {"Message", "tcp message " + DateTime.UtcNow.ToString("o")},
+ {"Index", "logstash"}
+ };
+ byte[] data = Encoding.UTF8.GetBytes(string.Format("{0}\n", o.ToString()));
+ stream.Write(data, 0, data.Length);
+ Thread.Sleep(parms.SleepTimeMilliseconds);
+ }
+ }
+
+ return parms.NumMessages;
+ }
+
+ }
+}
diff --git a/TimberWinR.TestGenerator/TimberWinR.TestGenerator.csproj b/TimberWinR.TestGenerator/TimberWinR.TestGenerator.csproj
new file mode 100644
index 0000000..f1191e4
--- /dev/null
+++ b/TimberWinR.TestGenerator/TimberWinR.TestGenerator.csproj
@@ -0,0 +1,115 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {F3960D6E-1EA0-4F4E-8F08-82FC185A0D29}
+ Exe
+ Properties
+ TimberWinR.TestGenerator
+ TimberWinR.TestGenerator
+ v4.0
+ 512
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ ..\packages\CommandLineParser.1.9.71\lib\net40\CommandLine.dll
+
+
+ False
+ ..\packages\Newtonsoft.Json.6.0.8\lib\net40\Newtonsoft.Json.dll
+
+
+ False
+ ..\packages\NLog.3.2.0.0\lib\net40\NLog.dll
+
+
+ ..\packages\ServiceStack.Common.Signed.4.0.38\lib\net40\ServiceStack.Common.dll
+
+
+ ..\packages\ServiceStack.Interfaces.4.0.38\lib\portable-wp80+sl5+net40+win8+monotouch+monoandroid\ServiceStack.Interfaces.dll
+
+
+ ..\packages\ServiceStack.Redis.Signed.4.0.38\lib\net40\ServiceStack.Redis.dll
+
+
+ ..\packages\ServiceStack.Text.Signed.4.0.38\lib\net40\ServiceStack.Text.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+
+
+ {4ef96a08-21db-4178-be44-70dae594632c}
+ TimberWinR
+
+
+
+
+
\ No newline at end of file
diff --git a/TimberWinR.TestGenerator/UdpTestGenerator.cs b/TimberWinR.TestGenerator/UdpTestGenerator.cs
new file mode 100644
index 0000000..cf2644f
--- /dev/null
+++ b/TimberWinR.TestGenerator/UdpTestGenerator.cs
@@ -0,0 +1,70 @@
+using System.Threading;
+using Newtonsoft.Json.Linq;
+using NLog;
+using NLog.Config;
+using NLog.Targets;
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+
+namespace TimberWinR.TestGenerator
+{
+ class UdpTestParameters
+ {
+ public int Port { get; set; }
+ public string Host { get; set; }
+ public int NumMessages { get; set; }
+ public int SleepTimeMilliseconds { get; set; }
+ public UdpTestParameters()
+ {
+ NumMessages = 100;
+ Port = 6379;
+ Host = "localhost";
+ SleepTimeMilliseconds = 10;
+ }
+ }
+
+ class UdpTestGenerator
+ {
+ public static int Generate(UdpTestParameters parms)
+ {
+ var hostName = System.Environment.MachineName + "." +
+ Microsoft.Win32.Registry.LocalMachine.OpenSubKey(
+ "SYSTEM\\CurrentControlSet\\services\\Tcpip\\Parameters").GetValue("Domain", "").ToString();
+
+ IPAddress broadcast;
+ if (!IPAddress.TryParse(parms.Host, out broadcast))
+ broadcast = Dns.GetHostEntry(parms.Host).AddressList[0];
+
+ Socket s = new Socket(broadcast.AddressFamily, SocketType.Dgram, ProtocolType.Udp);
+
+ LogManager.GetCurrentClassLogger().Info("Start UDP Generation");
+
+ for (int i = 0; i < parms.NumMessages; i++)
+ {
+ JObject o = new JObject
+ {
+ {"Application", "udp-generator"},
+ {"Host", hostName},
+ {"UtcTimestamp", DateTime.UtcNow.ToString("o")},
+ {"Type", "udp"},
+ {"Message", "Testgenerator udp message " + DateTime.UtcNow.ToString("o")},
+ {"Index", "logstash"}
+ };
+ byte[] sendbuf = Encoding.UTF8.GetBytes(o.ToString());
+ IPEndPoint ep = new IPEndPoint(broadcast, parms.Port);
+ s.SendTo(sendbuf, ep);
+ Thread.Sleep(parms.SleepTimeMilliseconds);
+ }
+
+ LogManager.GetCurrentClassLogger().Info("Finished UDP Generation");
+
+ return parms.NumMessages;
+ }
+
+ }
+}
diff --git a/TimberWinR.TestGenerator/default.json b/TimberWinR.TestGenerator/default.json
new file mode 100644
index 0000000..9740666
--- /dev/null
+++ b/TimberWinR.TestGenerator/default.json
@@ -0,0 +1,45 @@
+{
+ "TimberWinR": {
+ "Inputs": {
+ "Udp": [
+ {
+ "_comment": "Output from NLog",
+ "port": 5140
+ }
+ ],
+ "TailFiles": [
+ {
+ "interval": 5,
+ "logSource": "log files",
+ "location": "*.jlog",
+ "recurse": -1
+ }
+ ]
+ },
+ "Filters": [
+ {
+ "grok": {
+ "condition": "\"[EventTypeName]\" == \"Information Event\"",
+ "match": [
+ "Text",
+ ""
+ ],
+ "drop": "true"
+ }
+ }
+ ],
+ "Outputs": {
+ "Redis": [
+ {
+ "_comment": "Change the host to your Redis instance",
+ "port": 6379,
+ "batch_count": 500,
+ "threads": 2,
+ "host": [
+ "tstlexiceapp006.vistaprint.svc"
+ ]
+ }
+ ]
+ }
+ }
+}
diff --git a/TimberWinR.TestGenerator/packages.config b/TimberWinR.TestGenerator/packages.config
new file mode 100644
index 0000000..fdebb31
--- /dev/null
+++ b/TimberWinR.TestGenerator/packages.config
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/TimberWinR.TestGenerator/results1.json b/TimberWinR.TestGenerator/results1.json
new file mode 100644
index 0000000..0e887d1
--- /dev/null
+++ b/TimberWinR.TestGenerator/results1.json
@@ -0,0 +1,20 @@
+{
+ "Results": {
+ "Inputs": [
+ {
+ "taillog": {
+ "test1: message sent count": "[messages] == 7404",
+ "test2: average cpu": "[avgCpuUsage] <= 30",
+ "test3: maximum memory": "[maxMemUsage] <= 20"
+ }
+ },
+ {
+ "udp": {
+ "test1: message sent count": "[messages] == 1234",
+ "test2: average cpu": "[avgCpuUsage] <= 30",
+ "test3: maximum memory": "[maxMemUsage] <= 20"
+ }
+ }
+ ]
+ }
+}
diff --git a/TimberWinR.TestGenerator/results2.json b/TimberWinR.TestGenerator/results2.json
new file mode 100644
index 0000000..e20ac02
--- /dev/null
+++ b/TimberWinR.TestGenerator/results2.json
@@ -0,0 +1,20 @@
+{
+ "Results": {
+ "Inputs": [
+ {
+ "taillog": {
+ "test1: message sent count": "[messages] == 7404",
+ "test2: average cpu": "[avgCpuUsage] <= 30",
+ "test3: maximum memory": "[maxMemUsage] <= 15"
+ }
+ },
+ {
+ "udp": {
+ "test1: message sent count": "[messages] == 1234",
+ "test2: average cpu": "[avgCpuUsage] <= 30",
+ "test3: maximum memory": "[maxMemUsage] <= 15"
+ }
+ }
+ ]
+ }
+}
diff --git a/TimberWinR.TestGenerator/test1-twconfig.json b/TimberWinR.TestGenerator/test1-twconfig.json
new file mode 100644
index 0000000..9740666
--- /dev/null
+++ b/TimberWinR.TestGenerator/test1-twconfig.json
@@ -0,0 +1,45 @@
+{
+ "TimberWinR": {
+ "Inputs": {
+ "Udp": [
+ {
+ "_comment": "Output from NLog",
+ "port": 5140
+ }
+ ],
+ "TailFiles": [
+ {
+ "interval": 5,
+ "logSource": "log files",
+ "location": "*.jlog",
+ "recurse": -1
+ }
+ ]
+ },
+ "Filters": [
+ {
+ "grok": {
+ "condition": "\"[EventTypeName]\" == \"Information Event\"",
+ "match": [
+ "Text",
+ ""
+ ],
+ "drop": "true"
+ }
+ }
+ ],
+ "Outputs": {
+ "Redis": [
+ {
+ "_comment": "Change the host to your Redis instance",
+ "port": 6379,
+ "batch_count": 500,
+ "threads": 2,
+ "host": [
+ "tstlexiceapp006.vistaprint.svc"
+ ]
+ }
+ ]
+ }
+ }
+}
diff --git a/TimberWinR.TestGenerator/test1.json b/TimberWinR.TestGenerator/test1.json
new file mode 100644
index 0000000..241a50c
--- /dev/null
+++ b/TimberWinR.TestGenerator/test1.json
@@ -0,0 +1,15 @@
+{
+ "test": "Test 1",
+ "arguments": {
+ "--testFile": "test1.json",
+ "--testDir": "test1",
+ "--timberWinRConfig": "test1-twconfig.json",
+ "--numMessages": 1234,
+ "--logLevel": "debug",
+ "--udp-host": "::1",
+ "--udp": "5140",
+ "--jroll": ["r1.jlog", "r2.jlog"],
+ "--json": ["1.jlog", "2.jlog", "3.jlog", "4.jlog"],
+ "--resultsFile": "results1.json"
+ }
+}
diff --git a/TimberWinR.TestGenerator/test2-tw.json b/TimberWinR.TestGenerator/test2-tw.json
new file mode 100644
index 0000000..a321b18
--- /dev/null
+++ b/TimberWinR.TestGenerator/test2-tw.json
@@ -0,0 +1,45 @@
+{
+ "TimberWinR": {
+ "Inputs": {
+ "Udp": [
+ {
+ "_comment": "Output from NLog",
+ "port": 5140
+ }
+ ],
+ "Logs": [
+ {
+ "interval": 5,
+ "logSource": "log files",
+ "location": "*.jlog",
+ "recurse": -1
+ }
+ ]
+ },
+ "Filters": [
+ {
+ "grok": {
+ "condition": "\"[EventTypeName]\" == \"Information Event\"",
+ "match": [
+ "Text",
+ ""
+ ],
+ "drop": "true"
+ }
+ }
+ ],
+ "Outputs": {
+ "Redis": [
+ {
+ "_comment": "Change the host to your Redis instance",
+ "port": 6379,
+ "batch_count": 500,
+ "threads": 2,
+ "host": [
+ "tstlexiceapp006.vistaprint.svc"
+ ]
+ }
+ ]
+ }
+ }
+}
diff --git a/TimberWinR.TestGenerator/test2.json b/TimberWinR.TestGenerator/test2.json
new file mode 100644
index 0000000..223da98
--- /dev/null
+++ b/TimberWinR.TestGenerator/test2.json
@@ -0,0 +1,14 @@
+{
+ "test": "Test 2",
+ "arguments": {
+ "--testFile": "test2.json",
+ "--testDir": "test2",
+ "--timberWinRConfig": "test2-tw.json",
+ "--numMessages": 1234,
+ "--logLevel": "debug",
+ "--udp": "5140",
+ "--jroll": ["r1.jlog", "r2.jlog"],
+ "--json": ["1.jlog", "2.jlog", "3.jlog", "4.jlog"],
+ "--resultsFile": "results2.json"
+ }
+}
diff --git a/TimberWinR.UnitTests/GrokFilterTests.cs b/TimberWinR.UnitTests/GrokFilterTests.cs
index c1e4398..5aecd96 100644
--- a/TimberWinR.UnitTests/GrokFilterTests.cs
+++ b/TimberWinR.UnitTests/GrokFilterTests.cs
@@ -223,6 +223,7 @@ namespace TimberWinR.UnitTests
}
},
{"type", "Win32-FileLog"},
+ {"Type", "Win32-MyType"},
{"ComputerName", "dev.mycompany.net"}
};
@@ -281,11 +282,35 @@ namespace TimberWinR.UnitTests
}
}";
- // Positive Tests
- Configuration c = Configuration.FromString(grokJson1);
+ string grokJson4 = @"{
+ ""TimberWinR"":{
+ ""Filters"":[
+ {
+ ""grok"":{
+ ""condition"": ""!\""[Type]\"".StartsWith(\""[\"") && !\""[Type]\"".EndsWith(\""]\"") && (\""[type]\"" == \""Win32-FileLog\"")"",
+ ""match"":[
+ ""Text"",
+ """"
+ ],
+ ""remove_tag"":[
+ ""tag1""
+ ]
+ }
+ }]
+ }
+ }";
+
+
+ Configuration c = Configuration.FromString(grokJson4);
Grok grok = c.Filters.First() as Grok;
Assert.IsTrue(grok.Apply(json));
+
+ // Positive Tests
+ c = Configuration.FromString(grokJson1);
+ grok = c.Filters.First() as Grok;
+ Assert.IsTrue(grok.Apply(json));
+
c = Configuration.FromString(grokJson2);
grok = c.Filters.First() as Grok;
Assert.IsTrue(grok.Apply(json));
diff --git a/TimberWinR.UnitTests/TailFileTests.cs b/TimberWinR.UnitTests/TailFileTests.cs
index 12a044e..89388a1 100644
--- a/TimberWinR.UnitTests/TailFileTests.cs
+++ b/TimberWinR.UnitTests/TailFileTests.cs
@@ -28,7 +28,7 @@ namespace TimberWinR.UnitTests
var mgr = new Manager();
mgr.LogfileDir = ".";
- var tf = new TailFile();
+ var tf = new TailFileArguments();
var cancelTokenSource = new CancellationTokenSource();
tf.Location = "TestTailFile1.log";
diff --git a/TimberWinR.UnitTests/TimberWinR.UnitTests.csproj b/TimberWinR.UnitTests/TimberWinR.UnitTests.csproj
index b927454..70a7674 100644
--- a/TimberWinR.UnitTests/TimberWinR.UnitTests.csproj
+++ b/TimberWinR.UnitTests/TimberWinR.UnitTests.csproj
@@ -39,15 +39,17 @@
..\TimberWinR\lib\com-logparser\Interop.MSUtil.dll
- ..\packages\Moq.4.2.1409.1722\lib\net40\Moq.dll
+ ..\packages\Moq.4.2.1502.0911\lib\net40\Moq.dll
+ True
False
- ..\packages\Newtonsoft.Json.6.0.4\lib\net40\Newtonsoft.Json.dll
+ ..\packages\Newtonsoft.Json.6.0.8\lib\net40\Newtonsoft.Json.dll
+ True
-
+
False
- ..\packages\NUnit.2.6.3\lib\nunit.framework.dll
+ ..\packages\NUnit.2.6.4\lib\nunit.framework.dll
@@ -82,6 +84,7 @@
+
Designer
diff --git a/TimberWinR.UnitTests/app.config b/TimberWinR.UnitTests/app.config
new file mode 100644
index 0000000..a01ef9c
--- /dev/null
+++ b/TimberWinR.UnitTests/app.config
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/TimberWinR.sln b/TimberWinR.sln
index 321b5ff..b32d06e 100644
--- a/TimberWinR.sln
+++ b/TimberWinR.sln
@@ -35,6 +35,8 @@ Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "TimberWinR.Wix", "TimberWix
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TimberWinR.ExtractID", "TimberWinR.ExtractID\TimberWinR.ExtractID.csproj", "{99096939-E9DD-4499-883D-4726745A5843}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TimberWinR.TestGenerator", "TimberWinR.TestGenerator\TimberWinR.TestGenerator.csproj", "{F3960D6E-1EA0-4F4E-8F08-82FC185A0D29}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -97,6 +99,16 @@ Global
{99096939-E9DD-4499-883D-4726745A5843}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{99096939-E9DD-4499-883D-4726745A5843}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{99096939-E9DD-4499-883D-4726745A5843}.Release|x86.ActiveCfg = Release|Any CPU
+ {F3960D6E-1EA0-4F4E-8F08-82FC185A0D29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F3960D6E-1EA0-4F4E-8F08-82FC185A0D29}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F3960D6E-1EA0-4F4E-8F08-82FC185A0D29}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {F3960D6E-1EA0-4F4E-8F08-82FC185A0D29}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {F3960D6E-1EA0-4F4E-8F08-82FC185A0D29}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {F3960D6E-1EA0-4F4E-8F08-82FC185A0D29}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F3960D6E-1EA0-4F4E-8F08-82FC185A0D29}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F3960D6E-1EA0-4F4E-8F08-82FC185A0D29}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {F3960D6E-1EA0-4F4E-8F08-82FC185A0D29}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {F3960D6E-1EA0-4F4E-8F08-82FC185A0D29}.Release|x86.ActiveCfg = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/TimberWinR/Configuration.cs b/TimberWinR/Configuration.cs
index 78fb08f..3dfe485 100644
--- a/TimberWinR/Configuration.cs
+++ b/TimberWinR/Configuration.cs
@@ -44,7 +44,7 @@ namespace TimberWinR
{
get { return _redisOutputs; }
}
-
+
private List _elasticsearchOutputs = new List();
public IEnumerable ElasticsearchOutputs
@@ -76,8 +76,8 @@ namespace TimberWinR
get { return _logs; }
}
- private List _tails = new List();
- public IEnumerable TailFiles
+ private List _tails = new List();
+ public IEnumerable TailFiles
{
get { return _tails; }
}
@@ -241,8 +241,8 @@ namespace TimberWinR
c._stdins.AddRange(x.TimberWinR.Inputs.Stdins.ToList());
if (x.TimberWinR.Inputs.Logs != null)
c._logs.AddRange(x.TimberWinR.Inputs.Logs.ToList());
- if (x.TimberWinR.Inputs.TailFiles != null)
- c._tails.AddRange(x.TimberWinR.Inputs.TailFiles.ToList());
+ if (x.TimberWinR.Inputs.TailFilesArguments != null)
+ c._tails.AddRange(x.TimberWinR.Inputs.TailFilesArguments.ToList());
if (x.TimberWinR.Inputs.Tcps != null)
c._tcps.AddRange(x.TimberWinR.Inputs.Tcps.ToList());
if (x.TimberWinR.Inputs.Udps != null)
diff --git a/TimberWinR/Diagnostics/Diagnostics.cs b/TimberWinR/Diagnostics/Diagnostics.cs
index 9596429..f009047 100644
--- a/TimberWinR/Diagnostics/Diagnostics.cs
+++ b/TimberWinR/Diagnostics/Diagnostics.cs
@@ -21,7 +21,7 @@ namespace TimberWinR.Diagnostics
private CancellationToken CancelToken { get; set; }
public int Port { get; set; }
public Manager Manager { get; set; }
-
+ public bool Stop { get; set; }
private HttpListener web;
public Diagnostics(Manager manager, CancellationToken cancelToken, int port = 5141)
@@ -49,44 +49,60 @@ namespace TimberWinR.Diagnostics
}
- private void DiagnosticCallback(IAsyncResult result)
- {
- if (web == null)
- return;
-
- var context = web.EndGetContext(result);
- var response = context.Response;
-
+ public JObject DiagnosticsOutput()
+ {
JObject json = new JObject(
- new JProperty("timberwinr",
- new JObject(
- new JProperty("version", GetAssemblyByName("TimberWinR.ServiceHost").GetName().Version.ToString()),
- new JProperty("messages", Manager.NumMessages),
- new JProperty("startedon", Manager.StartedOn),
- new JProperty("configfile", Manager.JsonConfig),
- new JProperty("logdir", Manager.LogfileDir),
- new JProperty("logginglevel", LogManager.GlobalThreshold.ToString()),
- new JProperty("inputs",
- new JArray(
- from i in Manager.Listeners
- select new JObject(i.ToJson()))),
- new JProperty("filters",
- new JArray(
- from f in Manager.Config.Filters
- select new JObject(f.ToJson()))),
- new JProperty("outputs",
- new JArray(
- from o in Manager.Outputs
- select new JObject(o.ToJson()))))));
-
- response.StatusCode = (int)HttpStatusCode.OK;
- response.StatusDescription = HttpStatusCode.OK.ToString();
- byte[] buffer = Encoding.UTF8.GetBytes(json.ToString());
- response.ContentLength64 = buffer.Length;
- response.OutputStream.Write(buffer, 0, buffer.Length);
- response.OutputStream.Close();
+ new JProperty("timberwinr",
+ new JObject(
+ new JProperty("version", Assembly.GetEntryAssembly().GetName().Version.ToString()),
+ new JProperty("messages", Manager.NumMessages),
+ new JProperty("startedon", Manager.StartedOn),
+ new JProperty("configfile", Manager.JsonConfig),
+ new JProperty("logdir", Manager.LogfileDir),
+ new JProperty("logginglevel", LogManager.GlobalThreshold.ToString()),
+ new JProperty("inputs",
+ new JArray(
+ from i in Manager.Listeners
+ select new JObject(i.ToJson()))),
+ new JProperty("filters",
+ new JArray(
+ from f in Manager.Config.Filters
+ select new JObject(f.ToJson()))),
+ new JProperty("outputs",
+ new JArray(
+ from o in Manager.Outputs
+ select new JObject(o.ToJson()))))));
+ return json;
}
+ private void DiagnosticCallback(IAsyncResult result)
+ {
+ if (web == null)
+ return;
+
+ try
+ {
+ var context = web.EndGetContext(result);
+ var response = context.Response;
+ var json = DiagnosticsOutput();
+
+ response.StatusCode = (int) HttpStatusCode.OK;
+ response.StatusDescription = HttpStatusCode.OK.ToString();
+ byte[] buffer = Encoding.UTF8.GetBytes(json.ToString());
+ response.ContentLength64 = buffer.Length;
+ response.OutputStream.Write(buffer, 0, buffer.Length);
+ response.OutputStream.Close();
+ }
+ catch (SocketException)
+ {
+ // Shutdown
+ }
+ catch (Exception ex)
+ {
+ if (!Stop)
+ LogManager.GetCurrentClassLogger().Error(ex);
+ }
+ }
private void HttpListen(object o)
{
@@ -97,14 +113,19 @@ namespace TimberWinR.Diagnostics
web.Start();
while (web != null && web.IsListening)
- {
- processRequest();
- }
+ {
+ processRequest();
+ }
+ }
+ catch (SocketException)
+ {
+ // Shutdown
}
catch (Exception ex)
{
+ if (!Stop)
LogManager.GetCurrentClassLogger().Error("Diagnostic Listener Error: {0}", ex.ToString());
- }
+ }
}
private void ListenForClients(object olistener)
@@ -140,7 +161,7 @@ namespace TimberWinR.Diagnostics
var tcpClient = (TcpClient)client;
NetworkStream clientStream = null;
- Console.WriteLine("Handle new diag client: {0}, {1}", tcpClient.Connected, tcpClient.Client.RemoteEndPoint.ToString());
+ // Console.WriteLine("Handle new diag client: {0}, {1}", tcpClient.Connected, tcpClient.Client.RemoteEndPoint.ToString());
try
{
using (clientStream = tcpClient.GetStream())
@@ -183,7 +204,7 @@ namespace TimberWinR.Diagnostics
public void Shutdown()
{
-
+ Stop = true;
try
{
if (web != null && web.IsListening)
@@ -193,11 +214,9 @@ namespace TimberWinR.Diagnostics
web = null;
}
}
- catch (Exception ex)
+ catch (Exception)
{
- LogManager.GetCurrentClassLogger().Error(ex);
}
}
-
}
}
diff --git a/TimberWinR/Filters/GrokFilter.cs b/TimberWinR/Filters/GrokFilter.cs
index 9d10820..7f88d22 100644
--- a/TimberWinR/Filters/GrokFilter.cs
+++ b/TimberWinR/Filters/GrokFilter.cs
@@ -47,6 +47,7 @@ namespace TimberWinR.Parser
new JProperty("condition", Condition),
new JProperty("addfields", AddField),
new JProperty("addtags", AddTag),
+ new JProperty("drop", DropIfMatch),
new JProperty("type", Type),
new JProperty("removefields", RemoveField),
new JProperty("removetag", RemoveTag)
diff --git a/TimberWinR/Inputs/InputListener.cs b/TimberWinR/Inputs/InputListener.cs
index 527c824..c89abff 100644
--- a/TimberWinR/Inputs/InputListener.cs
+++ b/TimberWinR/Inputs/InputListener.cs
@@ -1,5 +1,6 @@
using System.IO;
using System.Runtime.InteropServices;
+using Microsoft.Win32;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
@@ -18,7 +19,9 @@ namespace TimberWinR.Inputs
private string _typeName;
public AutoResetEvent FinishedEvent { get; set; }
public string CheckpointFileName { get; set; }
-
+ private object _locker = new object();
+ public List Files { get; set; }
+
public string InputType
{
get { return _typeName; }
@@ -28,6 +31,7 @@ namespace TimberWinR.Inputs
public InputListener(CancellationToken token, string typeName)
{
+ Files = new List();
CheckpointFileName = Path.Combine(System.IO.Path.GetTempPath(), string.Format("{0}.lpc", Guid.NewGuid().ToString()));
this.FinishedEvent = new AutoResetEvent(false);
@@ -40,6 +44,19 @@ namespace TimberWinR.Inputs
.ToString();
}
+ public bool HaveSeenFile(string fileName)
+ {
+ return Files.Contains(fileName);
+ }
+
+ protected void SaveVisitedFileName(string fileName)
+ {
+ lock (_locker)
+ {
+ if (!HaveSeenFile(fileName))
+ Files.Add(fileName);
+ }
+ }
protected string ToPrintable(string inputString)
{
string asAscii = Encoding.ASCII.GetString(
@@ -58,17 +75,17 @@ namespace TimberWinR.Inputs
public void Finished()
{
- LogManager.GetCurrentClassLogger().Info("Signaling Event Shutdown {0}", InputType);
+ LogManager.GetCurrentClassLogger().Info("{0}: Signalling Event Shutdown {1}", Thread.CurrentThread.ManagedThreadId, InputType);
FinishedEvent.Set();
- LogManager.GetCurrentClassLogger().Info("Finished signaling Shutdown {0}", InputType);
+ LogManager.GetCurrentClassLogger().Info("{0}: Finished signalling Shutdown {1}", Thread.CurrentThread.ManagedThreadId, InputType);
}
+
public virtual void Shutdown()
{
- LogManager.GetCurrentClassLogger().Info("Shutting Down {0}", InputType);
+ LogManager.GetCurrentClassLogger().Info("{0}: Shutting Down {1}", Thread.CurrentThread.ManagedThreadId, InputType);
FinishedEvent.WaitOne();
-
- LogManager.GetCurrentClassLogger().Info("Finished Wait For {0}", InputType);
+
try
{
if (File.Exists(CheckpointFileName))
@@ -80,6 +97,32 @@ namespace TimberWinR.Inputs
}
}
+ protected void EnsureRollingCaught()
+ {
+ try
+ {
+ const string mteKey = @"SYSTEM\CurrentControlSet\Control\FileSystem";
+
+ var mte = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(mteKey).GetValue("MaximumTunnelEntries");
+ if (mte == null || (int)mte != 0)
+ {
+ LogManager.GetCurrentClassLogger()
+ .Error(
+ "HKLM\\{0}\\MaximumTunnelEntries is not set to accurately detect log rolling, a DWORD value of 0 is required.",
+ mteKey);
+ Microsoft.Win32.Registry.LocalMachine.CreateSubKey(mteKey).SetValue("MaximumTunnelEntries", 0, RegistryValueKind.DWord);
+ LogManager.GetCurrentClassLogger()
+ .Error(
+ "HKLM\\{0}\\MaximumTunnelEntries is now set to 0, A reboot is now required to fix this issue. See http://support.microsoft.com/en-us/kb/172190 for details",
+ mteKey);
+ }
+ }
+ catch (Exception ex)
+ {
+ LogManager.GetCurrentClassLogger().Error(ex);
+ }
+ }
+
public virtual void AddDefaultFields(JObject json)
{
if (json["type"] == null)
diff --git a/TimberWinR/Inputs/LogsFileDatabase.cs b/TimberWinR/Inputs/LogsFileDatabase.cs
index 2010c4b..8fe5116 100644
--- a/TimberWinR/Inputs/LogsFileDatabase.cs
+++ b/TimberWinR/Inputs/LogsFileDatabase.cs
@@ -1,9 +1,12 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
+using System.Threading;
using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
using NLog;
using TimberWinR.Parser;
@@ -34,7 +37,10 @@ namespace TimberWinR.Inputs
return ExistingFileTest(logName);
}
}
-
+
+ //
+ // Lookup the database entry for this log file, returns null if there isnt one.
+ //
private LogsFileDatabaseEntry FindFile(string logName)
{
lock (_locker)
@@ -43,6 +49,7 @@ namespace TimberWinR.Inputs
return existingEntry;
}
}
+
private bool ExistingFileTest(string logName)
{
var existingEntry = (from e in Entries where e.FileName == logName select e).FirstOrDefault();
@@ -67,45 +74,82 @@ namespace TimberWinR.Inputs
var de = new LogsFileDatabaseEntry();
lock (_locker)
{
- de.NewFile = true;
- var fi = new FileInfo(logName);
+ var fi = new FileInfo(logName);
de.FileName = logName;
- de.Size = fi.Length;
+ de.LogFileExists = fi.Exists;
+ de.NewFile = true;
+ de.ProcessedFile = false;
+ de.LastPosition = fi.Length;
de.SampleTime = DateTime.UtcNow;
- de.CreationTimeUtc = fi.CreationTimeUtc;
+ de.CreationTimeUtc = fi.CreationTimeUtc;
+
Entries.Add(de);
WriteDatabaseFileNoLock();
}
return de;
- }
+ }
public static LogsFileDatabaseEntry LookupLogFile(string logName)
{
LogsFileDatabaseEntry dbe = Instance.FindFile(logName);
if (dbe == null)
dbe = Instance.AddFileEntry(logName);
- else
- dbe.NewFile = false;
-
+
+ FileInfo fi = new FileInfo(logName);
+
+ dbe.LogFileExists = fi.Exists;
+ var creationTime = fi.CreationTimeUtc;
+
+ if (dbe.LogFileExists && creationTime != dbe.CreationTimeUtc)
+ dbe.NewFile = true;
+
+ dbe.CreationTimeUtc = creationTime;
+
return dbe;
}
- public static void Update(LogsFileDatabaseEntry dbe)
+ // Find all the non-existent entries and remove them.
+ private void PruneFiles()
{
- Instance.UpdateEntry(dbe);
- }
-
- private void UpdateEntry(LogsFileDatabaseEntry dbe)
- {
- lock(_locker)
+ lock (_locker)
{
- var fi = new FileInfo(dbe.FileName);
- dbe.CreationTimeUtc = fi.CreationTimeUtc;
- dbe.SampleTime = DateTime.UtcNow;
- dbe.Size = fi.Length;
+ foreach(var entry in Entries.ToList())
+ {
+ FileInfo fi = new FileInfo(entry.FileName);
+ if (!fi.Exists)
+ Entries.Remove(entry);
+ }
+ WriteDatabaseFileNoLock();
+ }
+ }
+
+ public static void Update(LogsFileDatabaseEntry dbe, bool processedFile, long lastOffset)
+ {
+ dbe.ProcessedFile = processedFile;
+ dbe.LogFileExists = File.Exists(dbe.FileName);
+ Instance.UpdateEntry(dbe, lastOffset);
+ }
+
+ public static void Roll(LogsFileDatabaseEntry dbe)
+ {
+ dbe.ProcessedFile = false;
+ dbe.LastPosition = 0;
+ Instance.UpdateEntry(dbe, 0);
+ dbe.NewFile = true;
+ }
+
+ private void UpdateEntry(LogsFileDatabaseEntry dbe, long lastOffset)
+ {
+ lock (_locker)
+ {
+ var fi = new FileInfo(dbe.FileName);
+ dbe.NewFile = !fi.Exists;
+ dbe.CreationTimeUtc = fi.CreationTimeUtc;
+ dbe.SampleTime = DateTime.UtcNow;
+ dbe.LastPosition = lastOffset;
+
WriteDatabaseFileNoLock();
}
-
}
public static LogsFileDatabase Instance
{
@@ -123,12 +167,19 @@ namespace TimberWinR.Inputs
instance.ReadDatabaseNoLock();
else
instance.WriteDatabaseFileNoLock();
+
+ if (instance.Entries == null)
+ instance.Entries = new List();
+
+ instance.PruneFiles();
}
}
return instance;
}
}
+
+ // Serialize in the Database
private void ReadDatabaseNoLock()
{
try
@@ -152,7 +203,7 @@ namespace TimberWinR.Inputs
catch (Exception ex2)
{
LogManager.GetCurrentClassLogger().Info("Error Creating New Database '{0}': {1}", DatabaseFileName, ex2.ToString());
- }
+ }
}
}
private void WriteDatabaseFileNoLock()
@@ -193,15 +244,30 @@ namespace TimberWinR.Inputs
}
+
+ //
+ // Represents a log file to be tailed
+ //
public class LogsFileDatabaseEntry
{
[JsonIgnore]
public bool NewFile { get; set; }
- public string FileName { get; set; }
- public Int64 MaxRecords { get; set; }
+ public bool ProcessedFile { get; set; }
+ public bool LogFileExists { get; set; }
+ public string FileName { get; set; }
public DateTime CreationTimeUtc { get; set; }
public DateTime SampleTime { get; set; }
- public long Size { get; set; }
+ public long LastPosition { get; set; }
+ public long LinesProcessed
+ {
+ get { return _linesProcessed; }
+ }
+
+ private int _linesProcessed;
+ public void IncrementLineCount()
+ {
+ Interlocked.Increment(ref _linesProcessed);
+ }
}
}
diff --git a/TimberWinR/Inputs/LogsListener.cs b/TimberWinR/Inputs/LogsListener.cs
index 35aba05..d1372f0 100644
--- a/TimberWinR/Inputs/LogsListener.cs
+++ b/TimberWinR/Inputs/LogsListener.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Linq;
using System.Net.Configuration;
using System.Runtime.InteropServices;
@@ -9,6 +10,7 @@ using System.Threading;
using System.Threading.Tasks;
using System.IO;
using Interop.MSUtil;
+using Microsoft.Win32;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
@@ -27,42 +29,45 @@ namespace TimberWinR.Inputs
///
public class LogsListener : InputListener
{
+ private object _locker = new object();
private int _pollingIntervalInSeconds;
private TimberWinR.Parser.LogParameters _arguments;
private long _receivedMessages;
- private Dictionary _logFileMaxRecords;
- private Dictionary _logFileCreationTimes;
- private Dictionary _logFileSampleTimes;
- private Dictionary _logFileSizes;
- private CodecArguments _codecArguments;
- private ICodec _codec;
-
+ private CodecArguments _codecArguments;
+ private ICodec _codec;
+
public bool Stop { get; set; }
+ public bool IsWildcardFilePattern { get; set; }
+
public LogsListener(TimberWinR.Parser.LogParameters arguments, CancellationToken cancelToken)
: base(cancelToken, "Win32-FileLog")
{
Stop = false;
+
+ EnsureRollingCaught();
- _codecArguments = arguments.CodecArguments;
+ _codecArguments = arguments.CodecArguments;
_codecArguments = arguments.CodecArguments;
if (_codecArguments != null && _codecArguments.Type == CodecArguments.CodecType.multiline)
_codec = new Multiline(_codecArguments);
- _logFileMaxRecords = new Dictionary();
- _logFileCreationTimes = new Dictionary();
- _logFileSampleTimes = new Dictionary();
- _logFileSizes = new Dictionary();
-
_receivedMessages = 0;
_arguments = arguments;
_pollingIntervalInSeconds = arguments.Interval;
+ IsWildcardFilePattern = arguments.Location.Contains('*');
+
foreach (string srcFile in _arguments.Location.Split(','))
{
string file = srcFile.Trim();
- Task.Factory.StartNew(() => FileWatcher(file));
+ string dir = Path.GetDirectoryName(file);
+ if (string.IsNullOrEmpty(dir))
+ dir = Environment.CurrentDirectory;
+ string fileSpec = Path.Combine(dir, file);
+
+ Task.Factory.StartNew(() => FileWatcher(fileSpec));
}
}
@@ -73,9 +78,9 @@ namespace TimberWinR.Inputs
base.Shutdown();
}
+
public override JObject ToJson()
{
-
JObject json = new JObject(
new JProperty("log",
new JObject(
@@ -86,21 +91,11 @@ namespace TimberWinR.Inputs
new JProperty("codepage", _arguments.CodePage),
new JProperty("splitLongLines", _arguments.SplitLongLines),
new JProperty("recurse", _arguments.Recurse),
-
+ new JProperty("filedb",
+ new JArray(from f in Files.ToList()
+ select JObject.FromObject(LogsFileDatabase.LookupLogFile(f)))),
new JProperty("files",
- new JArray(from f in _logFileMaxRecords.Keys
- select new JValue(f))),
- new JProperty("fileSampleTimes",
- new JArray(from f in _logFileSampleTimes.Values
- select new JValue(f))),
- new JProperty("fileSizes",
- new JArray(from f in _logFileSizes.Values
- select new JValue(f))),
- new JProperty("fileIndices",
- new JArray(from f in _logFileMaxRecords.Values
- select new JValue(f))),
- new JProperty("fileCreationDates",
- new JArray(from f in _logFileCreationTimes.Values
+ new JArray(from f in Files.ToList()
select new JValue(f)))
)));
@@ -120,7 +115,7 @@ namespace TimberWinR.Inputs
return json;
- }
+ }
private void FileWatcher(string fileToWatch)
{
@@ -149,46 +144,34 @@ namespace TimberWinR.Inputs
{
var record = rsfiles.getRecord();
string logName = record.getValue("LogFilename") as string;
- FileInfo fi = new FileInfo(logName);
+ FileInfo fi = new FileInfo(logName);
- if (!fi.Exists)
- {
- _logFileCreationTimes.Remove(logName);
- _logFileMaxRecords.Remove(logName);
- _logFileSizes.Remove(logName);
- }
-
- _logFileSampleTimes[logName] = DateTime.UtcNow;
+ var dbe = LogsFileDatabase.LookupLogFile(logName);
+
+ SaveVisitedFileName(dbe.FileName);
DateTime creationTime = fi.CreationTimeUtc;
- bool logHasRolled = (_logFileCreationTimes.ContainsKey(logName) &&
- creationTime > _logFileCreationTimes[logName]) ||
- (_logFileSizes.ContainsKey(logName) &&
- fi.Length < _logFileSizes[logName]);
+ bool logHasRolled = dbe.NewFile || (creationTime != dbe.CreationTimeUtc || fi.Length < dbe.LastPosition);
-
- if (!_logFileMaxRecords.ContainsKey(logName) || logHasRolled)
+ if (logHasRolled)
{
- _logFileCreationTimes[logName] = creationTime;
- _logFileSizes[logName] = fi.Length;
- var qcount = string.Format("SELECT max(Index) as MaxRecordNumber FROM {0}", logName);
- var rcount = oLogQuery.Execute(qcount, iFmt);
- var qr = rcount.getRecord();
- var lrn = (Int64)qr.getValueEx("MaxRecordNumber");
- if (logHasRolled)
- {
- LogManager.GetCurrentClassLogger().Info("Log {0} has rolled", logName);
- lrn = 0;
- }
- _logFileMaxRecords[logName] = lrn;
+ LogManager.GetCurrentClassLogger().Info("Log {0} has rolled", logName);
+ LogsFileDatabase.Roll(dbe);
}
- _logFileSizes[logName] = fi.Length;
+ // Log has rolled or this is a new file, or we haven't processed yet.
+ bool processWholeFile = logHasRolled || !dbe.ProcessedFile;
+
+ if (processWholeFile)
+ LogsFileDatabase.Update(dbe, true, 0);
+
}
rsfiles.close();
- foreach (string fileName in _logFileMaxRecords.Keys.ToList())
+ foreach (string fileName in Files.ToList())
{
- var lastRecordNumber = _logFileMaxRecords[fileName];
+ var dbe = LogsFileDatabase.LookupLogFile(fileName);
+
+ var lastRecordNumber = dbe.LastPosition;
var query = string.Format("SELECT * FROM {0} where Index > {1}", fileName,
lastRecordNumber);
@@ -231,21 +214,22 @@ namespace TimberWinR.Inputs
string msg = json["Text"].ToString();
if (!string.IsNullOrEmpty(msg))
{
- if (_codecArguments != null &&
- _codecArguments.Type == CodecArguments.CodecType.multiline)
+ if (_codecArguments != null && _codecArguments.Type == CodecArguments.CodecType.multiline)
{
_codec.Apply(msg, this);
_receivedMessages++;
+ dbe.IncrementLineCount();
}
else
{
ProcessJson(json);
- _receivedMessages++;
+ dbe.IncrementLineCount();
+ _receivedMessages++;
}
}
var lrn = (Int64)record.getValueEx("Index");
- _logFileMaxRecords[fileName] = lrn;
+ LogsFileDatabase.Update(dbe, true, lrn);
GC.Collect();
}
@@ -254,16 +238,18 @@ namespace TimberWinR.Inputs
rs.close();
rs = null;
GC.Collect();
+
}
}
catch (FileNotFoundException fnfex)
{
string fn = fnfex.FileName;
- if (!_fnfmap.ContainsKey(fn))
+ if (!string.IsNullOrEmpty(fn) && !_fnfmap.ContainsKey(fn))
+ {
LogManager.GetCurrentClassLogger().Warn(fnfex.Message);
-
- _fnfmap[fn] = fn;
+ _fnfmap[fn] = fn;
+ }
}
catch (OperationCanceledException)
{
@@ -283,7 +269,7 @@ namespace TimberWinR.Inputs
syncHandle.Wait(TimeSpan.FromSeconds(_pollingIntervalInSeconds), CancelToken);
}
catch (OperationCanceledException)
- {
+ {
}
catch (Exception ex1)
{
diff --git a/TimberWinR/Inputs/StdinListener.cs b/TimberWinR/Inputs/StdinListener.cs
index c94798f..d54799b 100644
--- a/TimberWinR/Inputs/StdinListener.cs
+++ b/TimberWinR/Inputs/StdinListener.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
+using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
@@ -15,10 +16,15 @@ namespace TimberWinR.Inputs
{
public class StdinListener : InputListener
{
+ [DllImport("User32.Dll", EntryPoint = "PostMessageA")]
+ private static extern bool PostMessage(IntPtr hWnd, uint msg, int wParam, int lParam);
+
private Thread _listenThread;
private CodecArguments _codecArguments;
- private ICodec _codec;
-
+ private ICodec _codec;
+ const int VK_RETURN = 0x0D;
+ const int WM_KEYDOWN = 0x100;
+
public StdinListener(TimberWinR.Parser.Stdin arguments, CancellationToken cancelToken)
: base(cancelToken, "Win32-Console")
{
@@ -54,7 +60,14 @@ namespace TimberWinR.Inputs
public override void Shutdown()
{
- LogManager.GetCurrentClassLogger().Info("Shutting Down {0}", InputType);
+ LogManager.GetCurrentClassLogger().Info("Shutting Down {0}", InputType);
+ // This must come from another thread.
+ ThreadPool.QueueUserWorkItem((o) =>
+ {
+ Thread.Sleep(100);
+ var hWnd = System.Diagnostics.Process.GetCurrentProcess().MainWindowHandle;
+ PostMessage(hWnd, WM_KEYDOWN, VK_RETURN, 0);
+ });
base.Shutdown();
}
diff --git a/TimberWinR/Inputs/TailFileListener.cs b/TimberWinR/Inputs/TailFileListener.cs
index dc49418..e4390a8 100644
--- a/TimberWinR/Inputs/TailFileListener.cs
+++ b/TimberWinR/Inputs/TailFileListener.cs
@@ -26,33 +26,27 @@ namespace TimberWinR.Inputs
///
public class TailFileListener : InputListener
{
+ private object _locker = new object();
private int _pollingIntervalInSeconds;
- private TimberWinR.Parser.TailFile _arguments;
+ private TimberWinR.Parser.TailFileArguments _arguments;
private long _receivedMessages;
- private Dictionary _logFileMaxRecords;
- private Dictionary _logFileCreationTimes;
- private Dictionary _logFileSampleTimes;
- private Dictionary _logFileSizes;
- private CodecArguments _codecArguments;
- private ICodec _codec;
+ private CodecArguments _codecArguments;
+ private ICodec _codec;
+
public bool Stop { get; set; }
- public TailFileListener(TimberWinR.Parser.TailFile arguments, CancellationToken cancelToken)
+ public TailFileListener(TimberWinR.Parser.TailFileArguments arguments, CancellationToken cancelToken)
: base(cancelToken, "Win32-TailLog")
{
Stop = false;
+
+ EnsureRollingCaught();
_codecArguments = arguments.CodecArguments;
if (_codecArguments != null && _codecArguments.Type == CodecArguments.CodecType.multiline)
_codec = new Multiline(_codecArguments);
-
- _logFileMaxRecords = new Dictionary();
- _logFileCreationTimes = new Dictionary();
- _logFileSampleTimes = new Dictionary();
- _logFileSizes = new Dictionary();
-
_receivedMessages = 0;
_arguments = arguments;
_pollingIntervalInSeconds = arguments.Interval;
@@ -66,7 +60,7 @@ namespace TimberWinR.Inputs
public override void Shutdown()
{
- LogManager.GetCurrentClassLogger().Info("Shutting Down {0}", InputType);
+ LogManager.GetCurrentClassLogger().Info("{0}: Shutting Down {1} for {2}", Thread.CurrentThread.ManagedThreadId, InputType, _arguments.Location);
Stop = true;
base.Shutdown();
}
@@ -74,29 +68,19 @@ namespace TimberWinR.Inputs
public override JObject ToJson()
{
JObject json = new JObject(
- new JProperty("log",
+ new JProperty("taillog",
new JObject(
new JProperty("messages", _receivedMessages),
new JProperty("type", InputType),
new JProperty("location", _arguments.Location),
new JProperty("logSource", _arguments.LogSource),
new JProperty("recurse", _arguments.Recurse),
-
new JProperty("files",
- new JArray(from f in _logFileMaxRecords.Keys
+ new JArray(from f in Files
select new JValue(f))),
- new JProperty("fileSampleTimes",
- new JArray(from f in _logFileSampleTimes.Values
- select new JValue(f))),
- new JProperty("fileSizes",
- new JArray(from f in _logFileSizes.Values
- select new JValue(f))),
- new JProperty("fileIndices",
- new JArray(from f in _logFileMaxRecords.Values
- select new JValue(f))),
- new JProperty("fileCreationDates",
- new JArray(from f in _logFileCreationTimes.Values
- select new JValue(f)))
+ new JProperty("filedb",
+ new JArray(from f in Files
+ select JObject.FromObject(LogsFileDatabase.LookupLogFile(f))))
)));
@@ -117,10 +101,9 @@ namespace TimberWinR.Inputs
return json;
}
- private void TailFileContents(string fileName, long offset)
+ private void TailFileContents(string fileName, long offset, LogsFileDatabaseEntry dbe)
{
- using (StreamReader reader = new StreamReader(new FileStream(fileName,
- FileMode.Open, FileAccess.Read, FileShare.ReadWrite)))
+ using (StreamReader reader = new StreamReader(new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)))
{
//start at the end of the file
long lastMaxOffset = offset;
@@ -130,6 +113,8 @@ namespace TimberWinR.Inputs
return;
//seek to the last max offset
+ LogManager.GetCurrentClassLogger().Trace("{0}: File: {1} Seek to: {2}", Thread.CurrentThread.ManagedThreadId, fileName, lastMaxOffset);
+
reader.BaseStream.Seek(lastMaxOffset, SeekOrigin.Begin);
//read out of the file until the EOF
@@ -152,24 +137,31 @@ namespace TimberWinR.Inputs
else
json.Add(new JProperty("logSource", _arguments.LogSource));
}
+
json["Text"] = line;
json["Index"] = index;
json["LogFileName"] = fileName;
+
if (_codecArguments != null && _codecArguments.Type == CodecArguments.CodecType.multiline)
{
_codec.Apply(line, this);
Interlocked.Increment(ref _receivedMessages);
+ dbe.IncrementLineCount();
}
else
{
ProcessJson(json);
Interlocked.Increment(ref _receivedMessages);
- }
- lineOffset += line.Length;
+ dbe.IncrementLineCount();
+ //LogManager.GetCurrentClassLogger().Info("{0}: File: {1} {2} {3}", Thread.CurrentThread.ManagedThreadId, fileName, dbe.LinesProcessed, line);
+ }
+
+ lineOffset += line.Length;
}
//update the last max offset
lastMaxOffset = reader.BaseStream.Position;
+ LogsFileDatabase.Update(dbe, true, lastMaxOffset);
}
}
// One thread for each kind of file to watch, i.e. "*.log,*.txt" would be two separate
@@ -187,11 +179,14 @@ namespace TimberWinR.Inputs
{
if (!CancelToken.IsCancellationRequested)
{
+ var isWildcardPattern = fileToWatch.Contains('*');
string path = Path.GetDirectoryName(fileToWatch);
string name = Path.GetFileName(fileToWatch);
if (string.IsNullOrEmpty(path))
path = ".";
+ LogManager.GetCurrentClassLogger().Trace(":{0} Tailing File: {1}", Thread.CurrentThread.ManagedThreadId, Path.Combine(path, name));
+
// Ok, we have a potential file filter here as 'fileToWatch' could be foo.log or *.log
SearchOption so = SearchOption.TopDirectoryOnly;
@@ -201,26 +196,41 @@ namespace TimberWinR.Inputs
foreach (string fileName in Directory.GetFiles(path, name, so))
{
var dbe = LogsFileDatabase.LookupLogFile(fileName);
- FileInfo fi = new FileInfo(dbe.FileName);
- //LogManager.GetCurrentClassLogger().Info("Located File: {0}, New: {1}", dbe.FileName, dbe.NewFile);
- long length = fi.Length;
- bool logHasRolled = false;
- if (fi.Length < dbe.Size || fi.CreationTimeUtc != dbe.CreationTimeUtc)
+
+ // We only spin up 1 thread for a file we haven't yet seen.
+ if (isWildcardPattern && !HaveSeenFile(fileName) && dbe.NewFile)
{
- LogManager.GetCurrentClassLogger().Info("Log has Rolled: {0}", dbe.FileName);
- logHasRolled = true;
+ LogManager.GetCurrentClassLogger().Debug(":{0} Starting Thread Tailing File: {1}", Thread.CurrentThread.ManagedThreadId, dbe.FileName);
+ LogsFileDatabase.Update(dbe, false, dbe.LastPosition);
+ SaveVisitedFileName(fileName);
+ Task.Factory.StartNew(() => TailFileWatcher(fileName));
}
- bool processWholeFile = logHasRolled || dbe.NewFile;
- if (processWholeFile)
+ else if (!isWildcardPattern)
{
- LogManager.GetCurrentClassLogger().Info("Process Whole File: {0}", dbe.FileName);
- TailFileContents(dbe.FileName, 0);
+ FileInfo fi = new FileInfo(dbe.FileName);
+
+ //LogManager.GetCurrentClassLogger().Info("Located File: {0}, New: {1}", dbe.FileName, dbe.NewFile);
+ long length = fi.Length;
+ bool logHasRolled = false;
+ if (fi.Length < dbe.LastPosition || fi.CreationTimeUtc != dbe.CreationTimeUtc)
+ {
+ LogManager.GetCurrentClassLogger().Info("{0}: Log has Rolled: {1}", Thread.CurrentThread.ManagedThreadId, dbe.FileName);
+ logHasRolled = true;
+ LogsFileDatabase.Roll(dbe);
+ }
+ // Log has rolled or this is a file we are seeing for the first time.
+ bool processWholeFile = logHasRolled || !dbe.ProcessedFile;
+ if (processWholeFile)
+ {
+ LogsFileDatabase.Update(dbe, true, 0);
+ LogManager.GetCurrentClassLogger().Debug("{0}: Process Whole File: {1}", Thread.CurrentThread.ManagedThreadId, dbe.FileName);
+ TailFileContents(dbe.FileName, 0, dbe);
+ }
+ else
+ {
+ TailFileContents(dbe.FileName, dbe.LastPosition, dbe);
+ }
}
- else
- {
- TailFileContents(dbe.FileName, dbe.Size);
- }
- LogsFileDatabase.Update(dbe);
}
}
}
@@ -232,6 +242,10 @@ namespace TimberWinR.Inputs
LogManager.GetCurrentClassLogger().Warn(fnfex.Message);
_fnfmap[fn] = fn;
}
+ catch (IOException ioex)
+ {
+ LogManager.GetCurrentClassLogger().Debug("Log has rolled: {0}", ioex.Message);
+ }
catch (OperationCanceledException)
{
break;
@@ -249,16 +263,17 @@ namespace TimberWinR.Inputs
}
catch (OperationCanceledException)
{
+ Stop = true;
}
catch (Exception ex1)
{
LogManager.GetCurrentClassLogger().Warn(ex1);
- }
+ }
}
}
}
Finished();
- }
+ }
}
}
diff --git a/TimberWinR/Inputs/TcpInputListener.cs b/TimberWinR/Inputs/TcpInputListener.cs
index dd5e0e2..64db158 100644
--- a/TimberWinR/Inputs/TcpInputListener.cs
+++ b/TimberWinR/Inputs/TcpInputListener.cs
@@ -52,7 +52,7 @@ namespace TimberWinR.Inputs
public override void Shutdown()
{
- LogManager.GetCurrentClassLogger().Info("Shutting Down {0}", InputType);
+ LogManager.GetCurrentClassLogger().Info("{0}: Shutting Down {1}", Thread.CurrentThread.ManagedThreadId, InputType);
this._tcpListenerV4.Stop();
this._tcpListenerV6.Stop();
diff --git a/TimberWinR/Inputs/UdpInputListener.cs b/TimberWinR/Inputs/UdpInputListener.cs
index fcff71d..615611c 100644
--- a/TimberWinR/Inputs/UdpInputListener.cs
+++ b/TimberWinR/Inputs/UdpInputListener.cs
@@ -1,6 +1,7 @@
using System;
using System.IO;
using System.Text;
+using System.Text.RegularExpressions;
using System.Threading;
using System.Net;
using System.Net.Sockets;
@@ -11,24 +12,14 @@ using NLog;
namespace TimberWinR.Inputs
{
public class UdpInputListener : InputListener
- {
- private readonly System.Net.Sockets.UdpClient _udpListener;
- private readonly IPEndPoint groupV4;
- private readonly IPEndPoint groupV6;
-
- private Thread _listenThreadV4;
- private Thread _listenThreadV6;
+ {
+ private UdpClient _udpListenerV6;
+ private readonly Thread _listenThreadV6;
private readonly int _port;
private long _receivedMessages;
private long _parsedErrors;
-
- private struct listenProfile
- {
- public IPEndPoint endPoint;
- public UdpClient client;
- }
-
+
public override JObject ToJson()
{
JObject json = new JObject(
@@ -46,36 +37,47 @@ namespace TimberWinR.Inputs
: base(cancelToken, "Win32-Udp")
{
_port = port;
-
- groupV4 = new IPEndPoint(IPAddress.Any, 0);
- groupV6 = new IPEndPoint(IPAddress.IPv6Any, 0);
-
+
LogManager.GetCurrentClassLogger().Info("Udp Input on Port {0} Ready", _port);
_receivedMessages = 0;
- _udpListener = new System.Net.Sockets.UdpClient(port);
-
- _listenThreadV4 = new Thread(new ParameterizedThreadStart(StartListener));
- _listenThreadV4.Start(new listenProfile() { endPoint = groupV4, client = _udpListener });
-
- _listenThreadV6 = new Thread(new ParameterizedThreadStart(StartListener));
- _listenThreadV6.Start(new listenProfile() { endPoint = groupV6, client = _udpListener });
+ _listenThreadV6 = new Thread(StartListener);
+ _listenThreadV6.Start();
}
public override void Shutdown()
{
LogManager.GetCurrentClassLogger().Info("Shutting Down {0}", InputType);
- _udpListener.Close();
+
+ // close UDP listeners, which will end the listener threads
+ _udpListenerV6.Close();
+
+ // wait for completion of the threads
+ _listenThreadV6.Join();
+
Finished();
+
base.Shutdown();
}
+ private void StartListener()
+ {
+ var groupV6 = new IPEndPoint(IPAddress.IPv6Any, _port);
+ // Create the socket as IPv6
+ var dualModeSocket = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp);
+
+ //
+ // Now, disable the IPV6only flag to make it compatable with both ipv4 and ipv6
+ // See: http://blogs.msdn.com/b/malarch/archive/2005/11/18/494769.aspx
+ //
+ dualModeSocket.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, 0);
+ dualModeSocket.Bind(groupV6);
+
+ _udpListenerV6 = new UdpClient();
+ _udpListenerV6.Client = dualModeSocket;
- private void StartListener(object useProfile)
- {
- var profile = (listenProfile)useProfile;
string lastMessage = "";
try
{
@@ -83,21 +85,25 @@ namespace TimberWinR.Inputs
{
try
{
- byte[] bytes = profile.client.Receive(ref profile.endPoint);
- var data = Encoding.UTF8.GetString(bytes, 0, bytes.Length);
+ byte[] bytes = _udpListenerV6.Receive(ref groupV6);
+ var data = Encoding.UTF8.GetString(bytes, 0, bytes.Length);
lastMessage = data;
- JObject json = JObject.Parse(data);
+ var json = JObject.Parse(data);
ProcessJson(json);
- _receivedMessages++;
+ Interlocked.Increment(ref _receivedMessages);
}
- catch (Exception ex1)
+ catch(SocketException)
+ {
+ break;
+ }
+ catch (Exception ex)
{
LogManager.GetCurrentClassLogger().Warn("Bad JSON: {0}", lastMessage);
- LogManager.GetCurrentClassLogger().Warn(ex1);
- _parsedErrors++;
+ LogManager.GetCurrentClassLogger().Warn(ex);
+ Interlocked.Increment(ref _parsedErrors);
}
}
- _udpListener.Close();
+ _udpListenerV6.Close();
}
catch (Exception ex)
{
diff --git a/TimberWinR/Manager.cs b/TimberWinR/Manager.cs
index d8dd550..3417691 100644
--- a/TimberWinR/Manager.cs
+++ b/TimberWinR/Manager.cs
@@ -28,6 +28,8 @@ namespace TimberWinR
public List Listeners { get; set; }
public bool LiveMonitor { get; set; }
+ public event Action OnConfigurationProcessed;
+
public DateTime StartedOn { get; set; }
public string JsonConfig { get; set; }
public string LogfileDir { get; set; }
@@ -67,7 +69,7 @@ namespace TimberWinR
LogsFileDatabase.Manager = this;
}
- public Manager(string jsonConfigFile, string logLevel, string logfileDir, bool liveMonitor, CancellationToken cancelToken)
+ public Manager(string jsonConfigFile, string logLevel, string logfileDir, bool liveMonitor, CancellationToken cancelToken, bool processConfiguration = true)
{
LogsFileDatabase.Manager = this;
@@ -106,12 +108,14 @@ namespace TimberWinR
LogManager.GlobalThreshold = LogLevel.FromString(logLevel);
- LogManager.GetCurrentClassLogger()
- .Info("TimberWinR Version {0}", GetAssemblyByName("TimberWinR.ServiceHost").GetName().Version.ToString());
-
+ //LogManager.GetCurrentClassLogger()
+ // .Info("TimberWinR Version {0}", GetAssemblyByName("TimberWinR.ServiceHost").GetName().Version.ToString());
LogManager.GetCurrentClassLogger()
- .Info("Database Directory: {0}", LogsFileDatabase.Instance.DatabaseFileName);
+ .Info("TimberWinR Version {0}", Assembly.GetEntryAssembly().GetName().Version.ToString());
+
+ LogManager.GetCurrentClassLogger()
+ .Info("Database Filename: {0}", LogsFileDatabase.Instance.DatabaseFileName);
try
{
@@ -146,7 +150,16 @@ namespace TimberWinR
LogManager.GetCurrentClassLogger().Info("Log Directory {0}", logfileDir);
LogManager.GetCurrentClassLogger().Info("Logging Level: {0}", LogManager.GlobalThreshold);
- ProcessConfiguration(cancelToken, Config);
+ if (processConfiguration)
+ {
+ ProcessConfiguration(cancelToken, Config);
+ Start(cancelToken);
+ }
+ }
+
+ public void Start(CancellationToken cancelToken)
+ {
+ ProcessConfiguration(cancelToken, Config);
}
public void ProcessConfiguration(CancellationToken cancelToken, Configuration config)
@@ -154,6 +167,9 @@ namespace TimberWinR
// Read the Configuration file
if (config != null)
{
+ if (OnConfigurationProcessed != null)
+ OnConfigurationProcessed(config);
+
if (config.RedisOutputs != null)
{
foreach (var ro in config.RedisOutputs)
@@ -256,7 +272,8 @@ namespace TimberWinR
new JProperty("TimberWinR",
new JObject(
new JProperty("version",
- GetAssemblyByName("TimberWinR.ServiceHost").GetName().Version.ToString()),
+ Assembly.GetEntryAssembly().GetName().Version.ToString()),
+ //GetAssemblyByName("TimberWinR.ServiceHost").GetName().Version.ToString()),
new JProperty("host", computerName),
new JProperty("output", output.Name),
new JProperty("initialized", DateTime.UtcNow)
@@ -264,7 +281,7 @@ namespace TimberWinR
json.Add(new JProperty("type", "Win32-TimberWinR"));
json.Add(new JProperty("host", computerName));
output.Startup(json);
- }
+ }
}
}
diff --git a/TimberWinR/Outputs/Elasticsearch.cs b/TimberWinR/Outputs/Elasticsearch.cs
index 9dae391..2b21880 100644
--- a/TimberWinR/Outputs/Elasticsearch.cs
+++ b/TimberWinR/Outputs/Elasticsearch.cs
@@ -268,7 +268,7 @@ namespace TimberWinR.Outputs
ApplyFilters(jsonMessage);
var message = jsonMessage.ToString();
- LogManager.GetCurrentClassLogger().Debug(message);
+ LogManager.GetCurrentClassLogger().Trace(message);
lock (_locker)
{
diff --git a/TimberWinR/Outputs/Redis.cs b/TimberWinR/Outputs/Redis.cs
index e192083..f0d21e3 100644
--- a/TimberWinR/Outputs/Redis.cs
+++ b/TimberWinR/Outputs/Redis.cs
@@ -28,7 +28,7 @@ namespace TimberWinR.Outputs
private const int QUEUE_SAMPLE_SIZE = 30; // 30 samples over 2.5 minutes (default)
private object _locker = new object();
private bool _warnedReachedMax;
-
+
private readonly int _maxBatchCount;
private readonly int _batchCount;
private int _totalSamples;
@@ -53,7 +53,7 @@ namespace TimberWinR.Outputs
{
if (_totalSamples < QUEUE_SAMPLE_SIZE)
_totalSamples++;
-
+
// Take a sample of the queue depth
if (_sampleCountIndex >= QUEUE_SAMPLE_SIZE)
_sampleCountIndex = 0;
@@ -69,7 +69,7 @@ namespace TimberWinR.Outputs
if (_totalSamples > 0)
{
var samples = _sampleQueueDepths.Take(_totalSamples);
- int avg = (int) samples.Average();
+ int avg = (int)samples.Average();
return avg;
}
return 0;
@@ -81,7 +81,7 @@ namespace TimberWinR.Outputs
{
if (currentBatchCount < _maxBatchCount && currentBatchCount < queueSize && AverageQueueDepth() > currentBatchCount)
{
- currentBatchCount += Math.Max(_maxBatchCount/_batchCount, 1);
+ currentBatchCount += Math.Max(_maxBatchCount / _batchCount, 1);
if (currentBatchCount >= _maxBatchCount && !_warnedReachedMax)
{
LogManager.GetCurrentClassLogger().Warn("Maximum Batch Count of {0} reached.", currentBatchCount);
@@ -93,7 +93,7 @@ namespace TimberWinR.Outputs
else // Reset to default
{
currentBatchCount = _batchCount;
- _warnedReachedMax = false;
+ _warnedReachedMax = false;
}
return currentBatchCount;
@@ -117,21 +117,21 @@ namespace TimberWinR.Outputs
private readonly int _port;
private readonly int _timeout;
private readonly object _locker = new object();
- private readonly List _jsonQueue;
+ private readonly List _jsonQueue;
private readonly string[] _redisHosts;
private int _redisHostIndex;
private TimberWinR.Manager _manager;
private readonly int _batchCount;
private int _currentBatchCount;
- private readonly int _maxBatchCount;
+ private readonly int _maxBatchCount;
private readonly int _interval;
- private readonly int _numThreads;
+ private readonly int _numThreads;
private long _sentMessages;
private long _errorCount;
private long _redisDepth;
- private DateTime? _lastErrorTimeUTC;
+ private DateTime? _lastErrorTimeUTC;
private readonly int _maxQueueSize;
- private readonly bool _queueOverflowDiscardOldest;
+ private readonly bool _queueOverflowDiscardOldest;
private BatchCounter _batchCounter;
public bool Stop { get; set; }
@@ -186,9 +186,9 @@ namespace TimberWinR.Outputs
new JProperty("threads", _numThreads),
new JProperty("batchcount", _batchCount),
new JProperty("currentBatchCount", _currentBatchCount),
- new JProperty("reachedMaxBatchCountTimes", _batchCounter.ReachedMaxBatchCountTimes),
+ new JProperty("reachedMaxBatchCountTimes", _batchCounter.ReachedMaxBatchCountTimes),
new JProperty("maxBatchCount", _maxBatchCount),
- new JProperty("averageQueueDepth", _batchCounter.AverageQueueDepth()),
+ new JProperty("averageQueueDepth", _batchCounter.AverageQueueDepth()),
new JProperty("queueSamples", new JArray(_batchCounter.Samples())),
new JProperty("index", _logstashIndexName),
new JProperty("hosts",
@@ -201,17 +201,17 @@ namespace TimberWinR.Outputs
public RedisOutput(TimberWinR.Manager manager, Parser.RedisOutputParameters parameters, CancellationToken cancelToken)
: base(cancelToken, "Redis")
- {
+ {
_redisDepth = 0;
_batchCount = parameters.BatchCount;
_maxBatchCount = parameters.MaxBatchCount;
// Make sure maxBatchCount is larger than batchCount
- if (_maxBatchCount < _batchCount)
- _maxBatchCount = _batchCount*10;
-
+ if (_maxBatchCount <= _batchCount)
+ _maxBatchCount = _batchCount * 10;
+
_manager = manager;
_redisHostIndex = 0;
- _redisHosts = parameters.Host;
+ _redisHosts = parameters.Host;
_jsonQueue = new List();
_port = parameters.Port;
_timeout = parameters.Timeout;
@@ -224,7 +224,7 @@ namespace TimberWinR.Outputs
_queueOverflowDiscardOldest = parameters.QueueOverflowDiscardOldest;
_batchCounter = new BatchCounter(_batchCount, _maxBatchCount);
_currentBatchCount = _batchCount;
-
+
for (int i = 0; i < parameters.NumThreads; i++)
{
var redisThread = new Task(RedisSender, cancelToken);
@@ -250,7 +250,7 @@ namespace TimberWinR.Outputs
}
var message = jsonMessage.ToString();
- LogManager.GetCurrentClassLogger().Debug(message);
+ LogManager.GetCurrentClassLogger().Trace(message);
lock (_locker)
{
@@ -285,13 +285,13 @@ namespace TimberWinR.Outputs
foreach (var filter in _manager.Config.Filters)
{
if (!filter.Apply(json))
- {
- LogManager.GetCurrentClassLogger().Debug("Dropping: {0}", json.ToString());
+ {
+ LogManager.GetCurrentClassLogger().Debug("{0}: Dropping: {1}", Thread.CurrentThread.ManagedThreadId, json.ToString());
drop = true;
- }
+ }
}
return drop;
- }
+ }
//
// Pull off messages from the Queue, batch them up and send them all across
//
@@ -313,11 +313,9 @@ namespace TimberWinR.Outputs
_batchCounter.SampleQueueDepth(_jsonQueue.Count);
// Re-compute current batch size
_currentBatchCount = _batchCounter.UpdateCurrentBatchCount(_jsonQueue.Count, _currentBatchCount);
-
+
messages = _jsonQueue.Take(_currentBatchCount).ToArray();
_jsonQueue.RemoveRange(0, messages.Length);
-
-
}
if (messages.Length > 0)
@@ -335,12 +333,12 @@ namespace TimberWinR.Outputs
{
client.StartPipe();
LogManager.GetCurrentClassLogger()
- .Debug("Sending {0} Messages to {1}", messages.Length, client.Host);
+ .Debug("{0}: Sending {1} Messages to {2}", Thread.CurrentThread.ManagedThreadId, messages.Length, client.Host);
try
{
_redisDepth = client.RPush(_logstashIndexName, messages);
- _sentMessages += messages.Length;
+ Interlocked.Add(ref _sentMessages, messages.Length);
client.EndPipe();
sentSuccessfully = true;
if (messages.Length > 0)
@@ -357,7 +355,7 @@ namespace TimberWinR.Outputs
LogManager.GetCurrentClassLogger().Error(ex);
Interlocked.Increment(ref _errorCount);
_lastErrorTimeUTC = DateTime.UtcNow;
- }
+ }
break;
}
else
@@ -374,20 +372,19 @@ namespace TimberWinR.Outputs
{
LogManager.GetCurrentClassLogger().Error(ex);
Interlocked.Increment(ref _errorCount);
- _lastErrorTimeUTC = DateTime.UtcNow;
+ _lastErrorTimeUTC = DateTime.UtcNow;
}
} // No more hosts to try.
-
- if (!sentSuccessfully)
+ // Couldn't send, put it back into the queue.
+ if (!sentSuccessfully)
{
lock (_locker)
{
_jsonQueue.InsertRange(0, messages);
}
}
- }
- // GC.Collect();
+ }
if (!Stop)
syncHandle.Wait(TimeSpan.FromMilliseconds(_interval), CancelToken);
}
@@ -395,7 +392,7 @@ namespace TimberWinR.Outputs
{
break;
}
- catch(ThreadAbortException)
+ catch (ThreadAbortException)
{
break;
}
@@ -403,11 +400,11 @@ namespace TimberWinR.Outputs
{
_lastErrorTimeUTC = DateTime.UtcNow;
Interlocked.Increment(ref _errorCount);
- LogManager.GetCurrentClassLogger().Error(ex);
+ LogManager.GetCurrentClassLogger().Error(ex);
}
}
}
}
- }
+ }
}
}
diff --git a/TimberWinR/Parser.cs b/TimberWinR/Parser.cs
index 3eb03ac..9fd69f1 100644
--- a/TimberWinR/Parser.cs
+++ b/TimberWinR/Parser.cs
@@ -297,7 +297,7 @@ namespace TimberWinR.Parser
}
}
- public class TailFile : IValidateSchema
+ public class TailFileArguments : IValidateSchema
{
[JsonProperty(PropertyName = "location")]
public string Location { get; set; }
@@ -312,7 +312,7 @@ namespace TimberWinR.Parser
[JsonProperty(PropertyName = "codec")]
public CodecArguments CodecArguments { get; set; }
- public TailFile()
+ public TailFileArguments()
{
Fields = new List();
Fields.Add(new Field("LogFilename", "string"));
@@ -605,7 +605,7 @@ namespace TimberWinR.Parser
Index = "logstash";
Host = new string[] { "localhost" };
Timeout = 10000;
- BatchCount = 10;
+ BatchCount = 50;
MaxBatchCount = BatchCount*10;
NumThreads = 1;
Interval = 5000;
@@ -646,7 +646,7 @@ namespace TimberWinR.Parser
public LogParameters[] Logs { get; set; }
[JsonProperty("TailFiles")]
- public TailFile[] TailFiles { get; set; }
+ public TailFileArguments[] TailFilesArguments { get; set; }
[JsonProperty("Tcp")]
public TcpParameters[] Tcps { get; set; }
diff --git a/TimberWinR/ReleaseNotes.md b/TimberWinR/ReleaseNotes.md
index b7c76f4..be90ac6 100644
--- a/TimberWinR/ReleaseNotes.md
+++ b/TimberWinR/ReleaseNotes.md
@@ -2,7 +2,16 @@
==================================
A Native Windows to Redis/Elasticsearch Logstash Agent which runs as a service.
-Version History
+Version / Date
+
+### 1.4.0.0 - 04/03/2015
+1. A re-factoring of Logs and TailLogs to be more efficient and detect log rolling correctly,
+ this requires http://support.microsoft.com/en-us/kb/172190 which will be detected and
+ set by TimberWinR, however, requires a reboot.
+2. Fixed issue [#38](https://github.com/Cimpress-MCP/TimberWinR/issues/38) diagnostic output not showing drop flag for Grok filter.
+3. Created TimberWinR.TestGenerator for complete testing of TimberWinR
+4. Fixed ipv4/ipv6 thread-safe issue with UdpInputListener which might lead to corrupted input data.
+
### 1.3.19.1 - 03/03/2015
1. Added new Redis parameter _max\_batch\_count_ which increases the _batch\_count_ dynamically over time
to handle input flooding. Default is _batch\_count_ * 10
diff --git a/TimberWinR/TimberWinR.csproj b/TimberWinR/TimberWinR.csproj
index eef68f3..4ad3c76 100644
--- a/TimberWinR/TimberWinR.csproj
+++ b/TimberWinR/TimberWinR.csproj
@@ -32,10 +32,12 @@
- ..\packages\csredis.1.4.7.1\lib\net40\csredis.dll
+ ..\packages\csredis.3.2.1\lib\net40\csredis.dll
+ True
- ..\packages\Elasticsearch.Net.1.3.1\lib\Elasticsearch.Net.dll
+ ..\packages\Elasticsearch.Net.1.4.2\lib\Elasticsearch.Net.dll
+ True
False
@@ -43,33 +45,35 @@
lib\com-logparser\Interop.MSUtil.dll
- ..\packages\MaxMind.Db.0.2.3.0\lib\net40\MaxMind.Db.dll
+ ..\packages\MaxMind.Db.1.0.0.0\lib\net40\MaxMind.Db.dll
True
- ..\packages\MaxMind.GeoIP2.0.4.0.0\lib\net40\MaxMind.GeoIP2.dll
+ ..\packages\MaxMind.GeoIP2.2.1.0.0\lib\net40\MaxMind.GeoIP2.dll
True
- ..\packages\NEST.1.3.1\lib\Nest.dll
+ ..\packages\NEST.1.4.2\lib\Nest.dll
+ True
- False
- ..\packages\Newtonsoft.Json.6.0.4\lib\net40\Newtonsoft.Json.dll
+ ..\packages\Newtonsoft.Json.6.0.8\lib\net40\Newtonsoft.Json.dll
-
- ..\packages\NLog.3.1.0.0\lib\net40\NLog.dll
+
+ False
+ ..\packages\NLog.3.2.0.0\lib\net40\NLog.dll
..\packages\RapidRegex.Core.1.0.0.2\lib\net40\RapidRegex.Core.dll
- ..\packages\RestSharp.104.4.0\lib\net4\RestSharp.dll
+ ..\packages\RestSharp.105.0.1\lib\net4\RestSharp.dll
+ True
- ..\packages\System.Linq.Dynamic.1.0.3\lib\net40\System.Linq.Dynamic.dll
+ ..\packages\System.Linq.Dynamic.1.0.4\lib\net40\System.Linq.Dynamic.dll
@@ -149,7 +153,9 @@
-
+
+ Designer
+
diff --git a/TimberWinR/packages.config b/TimberWinR/packages.config
index 9a46d62..1bfdc1f 100644
--- a/TimberWinR/packages.config
+++ b/TimberWinR/packages.config
@@ -9,5 +9,5 @@
-
+
\ No newline at end of file
diff --git a/TimberWix/TimberWinR.Wix.wixproj b/TimberWix/TimberWinR.Wix.wixproj
index a187d94..3c7c4d3 100644
--- a/TimberWix/TimberWinR.Wix.wixproj
+++ b/TimberWix/TimberWinR.Wix.wixproj
@@ -63,9 +63,6 @@
-
-
-
@@ -103,6 +100,9 @@
$(SolutionDir)\TimberWinR.ExtractID\$(OutDir)\TimberWinR.ExtractID.exe $(TargetDir) $(SolutionDir)chocolateyUninstall.ps1.guid $(SolutionDir)chocolateyUninstall.ps1.template
+
+ cmd.exe /c copy $(SolutionDir)chocolateyUninstall.ps1.template.orig $(SolutionDir)chocolateyUninstall.ps1.template
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/packages/NUnit.Runners.2.6.3/tools/launcher.log.conf b/packages/NUnit.Runners.2.6.3/tools/launcher.log.conf
deleted file mode 100644
index b5bcd9d..0000000
--- a/packages/NUnit.Runners.2.6.3/tools/launcher.log.conf
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Circles/Failure.jpg b/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Circles/Failure.jpg
deleted file mode 100644
index c245548..0000000
Binary files a/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Circles/Failure.jpg and /dev/null differ
diff --git a/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Circles/Ignored.jpg b/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Circles/Ignored.jpg
deleted file mode 100644
index 0549b70..0000000
Binary files a/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Circles/Ignored.jpg and /dev/null differ
diff --git a/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Circles/Inconclusive.jpg b/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Circles/Inconclusive.jpg
deleted file mode 100644
index 8d36153..0000000
Binary files a/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Circles/Inconclusive.jpg and /dev/null differ
diff --git a/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Circles/Skipped.jpg b/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Circles/Skipped.jpg
deleted file mode 100644
index 3d84255..0000000
Binary files a/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Circles/Skipped.jpg and /dev/null differ
diff --git a/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Circles/Success.jpg b/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Circles/Success.jpg
deleted file mode 100644
index 15ec1b7..0000000
Binary files a/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Circles/Success.jpg and /dev/null differ
diff --git a/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Classic/Failure.jpg b/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Classic/Failure.jpg
deleted file mode 100644
index 658905f..0000000
Binary files a/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Classic/Failure.jpg and /dev/null differ
diff --git a/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Classic/Ignored.jpg b/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Classic/Ignored.jpg
deleted file mode 100644
index 95b7fdb..0000000
Binary files a/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Classic/Ignored.jpg and /dev/null differ
diff --git a/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Classic/Inconclusive.jpg b/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Classic/Inconclusive.jpg
deleted file mode 100644
index 32a0ff7..0000000
Binary files a/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Classic/Inconclusive.jpg and /dev/null differ
diff --git a/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Classic/Skipped.jpg b/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Classic/Skipped.jpg
deleted file mode 100644
index 3d84255..0000000
Binary files a/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Classic/Skipped.jpg and /dev/null differ
diff --git a/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Classic/Success.jpg b/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Classic/Success.jpg
deleted file mode 100644
index 3d8e760..0000000
Binary files a/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Classic/Success.jpg and /dev/null differ
diff --git a/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Default/Failure.png b/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Default/Failure.png
deleted file mode 100644
index 2e400b2..0000000
Binary files a/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Default/Failure.png and /dev/null differ
diff --git a/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Default/Ignored.png b/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Default/Ignored.png
deleted file mode 100644
index 05715cb..0000000
Binary files a/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Default/Ignored.png and /dev/null differ
diff --git a/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Default/Inconclusive.png b/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Default/Inconclusive.png
deleted file mode 100644
index 4807b7c..0000000
Binary files a/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Default/Inconclusive.png and /dev/null differ
diff --git a/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Default/Skipped.png b/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Default/Skipped.png
deleted file mode 100644
index 7c9fc64..0000000
Binary files a/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Default/Skipped.png and /dev/null differ
diff --git a/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Default/Success.png b/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Default/Success.png
deleted file mode 100644
index 2a30150..0000000
Binary files a/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Default/Success.png and /dev/null differ
diff --git a/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Visual Studio/Failure.png b/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Visual Studio/Failure.png
deleted file mode 100644
index ba03e84..0000000
Binary files a/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Visual Studio/Failure.png and /dev/null differ
diff --git a/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Visual Studio/Ignored.png b/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Visual Studio/Ignored.png
deleted file mode 100644
index 9271d6e..0000000
Binary files a/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Visual Studio/Ignored.png and /dev/null differ
diff --git a/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Visual Studio/Inconclusive.png b/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Visual Studio/Inconclusive.png
deleted file mode 100644
index 76219b5..0000000
Binary files a/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Visual Studio/Inconclusive.png and /dev/null differ
diff --git a/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Visual Studio/SeriousWarning.png b/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Visual Studio/SeriousWarning.png
deleted file mode 100644
index 6a578cc..0000000
Binary files a/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Visual Studio/SeriousWarning.png and /dev/null differ
diff --git a/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Visual Studio/Skipped.png b/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Visual Studio/Skipped.png
deleted file mode 100644
index 7c9fc64..0000000
Binary files a/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Visual Studio/Skipped.png and /dev/null differ
diff --git a/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Visual Studio/Success.png b/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Visual Studio/Success.png
deleted file mode 100644
index 346fe8f..0000000
Binary files a/packages/NUnit.Runners.2.6.3/tools/lib/Images/Tree/Visual Studio/Success.png and /dev/null differ
diff --git a/packages/NUnit.Runners.2.6.3/tools/lib/log4net.dll b/packages/NUnit.Runners.2.6.3/tools/lib/log4net.dll
deleted file mode 100644
index 20a2e1c..0000000
Binary files a/packages/NUnit.Runners.2.6.3/tools/lib/log4net.dll and /dev/null differ
diff --git a/packages/NUnit.Runners.2.6.3/tools/lib/nunit-console-runner.dll b/packages/NUnit.Runners.2.6.3/tools/lib/nunit-console-runner.dll
deleted file mode 100644
index a2a21ce..0000000
Binary files a/packages/NUnit.Runners.2.6.3/tools/lib/nunit-console-runner.dll and /dev/null differ
diff --git a/packages/NUnit.Runners.2.6.3/tools/lib/nunit-gui-runner.dll b/packages/NUnit.Runners.2.6.3/tools/lib/nunit-gui-runner.dll
deleted file mode 100644
index 7161b97..0000000
Binary files a/packages/NUnit.Runners.2.6.3/tools/lib/nunit-gui-runner.dll and /dev/null differ
diff --git a/packages/NUnit.Runners.2.6.3/tools/lib/nunit.core.dll b/packages/NUnit.Runners.2.6.3/tools/lib/nunit.core.dll
deleted file mode 100644
index b306fae..0000000
Binary files a/packages/NUnit.Runners.2.6.3/tools/lib/nunit.core.dll and /dev/null differ
diff --git a/packages/NUnit.Runners.2.6.3/tools/lib/nunit.core.interfaces.dll b/packages/NUnit.Runners.2.6.3/tools/lib/nunit.core.interfaces.dll
deleted file mode 100644
index 4053b0d..0000000
Binary files a/packages/NUnit.Runners.2.6.3/tools/lib/nunit.core.interfaces.dll and /dev/null differ
diff --git a/packages/NUnit.Runners.2.6.3/tools/lib/nunit.uiexception.dll b/packages/NUnit.Runners.2.6.3/tools/lib/nunit.uiexception.dll
deleted file mode 100644
index 34f2f4e..0000000
Binary files a/packages/NUnit.Runners.2.6.3/tools/lib/nunit.uiexception.dll and /dev/null differ
diff --git a/packages/NUnit.Runners.2.6.3/tools/lib/nunit.uikit.dll b/packages/NUnit.Runners.2.6.3/tools/lib/nunit.uikit.dll
deleted file mode 100644
index d93d8ca..0000000
Binary files a/packages/NUnit.Runners.2.6.3/tools/lib/nunit.uikit.dll and /dev/null differ
diff --git a/packages/NUnit.Runners.2.6.3/tools/lib/nunit.util.dll b/packages/NUnit.Runners.2.6.3/tools/lib/nunit.util.dll
deleted file mode 100644
index 122eff4..0000000
Binary files a/packages/NUnit.Runners.2.6.3/tools/lib/nunit.util.dll and /dev/null differ
diff --git a/packages/NUnit.Runners.2.6.3/tools/nunit-agent-x86.exe b/packages/NUnit.Runners.2.6.3/tools/nunit-agent-x86.exe
deleted file mode 100644
index fe0d719..0000000
Binary files a/packages/NUnit.Runners.2.6.3/tools/nunit-agent-x86.exe and /dev/null differ
diff --git a/packages/NUnit.Runners.2.6.3/tools/nunit-agent-x86.exe.config b/packages/NUnit.Runners.2.6.3/tools/nunit-agent-x86.exe.config
deleted file mode 100644
index de2caf6..0000000
--- a/packages/NUnit.Runners.2.6.3/tools/nunit-agent-x86.exe.config
+++ /dev/null
@@ -1,37 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/packages/NUnit.Runners.2.6.3/tools/nunit-agent.exe b/packages/NUnit.Runners.2.6.3/tools/nunit-agent.exe
deleted file mode 100644
index 6f057bc..0000000
Binary files a/packages/NUnit.Runners.2.6.3/tools/nunit-agent.exe and /dev/null differ
diff --git a/packages/NUnit.Runners.2.6.3/tools/nunit-agent.exe.config b/packages/NUnit.Runners.2.6.3/tools/nunit-agent.exe.config
deleted file mode 100644
index de2caf6..0000000
--- a/packages/NUnit.Runners.2.6.3/tools/nunit-agent.exe.config
+++ /dev/null
@@ -1,37 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/packages/NUnit.Runners.2.6.3/tools/nunit-console-x86.exe b/packages/NUnit.Runners.2.6.3/tools/nunit-console-x86.exe
deleted file mode 100644
index c71d21f..0000000
Binary files a/packages/NUnit.Runners.2.6.3/tools/nunit-console-x86.exe and /dev/null differ
diff --git a/packages/NUnit.Runners.2.6.3/tools/nunit-console-x86.exe.config b/packages/NUnit.Runners.2.6.3/tools/nunit-console-x86.exe.config
deleted file mode 100644
index 81e5346..0000000
--- a/packages/NUnit.Runners.2.6.3/tools/nunit-console-x86.exe.config
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/packages/NUnit.Runners.2.6.3/tools/nunit-console.exe b/packages/NUnit.Runners.2.6.3/tools/nunit-console.exe
deleted file mode 100644
index 8d65c82..0000000
Binary files a/packages/NUnit.Runners.2.6.3/tools/nunit-console.exe and /dev/null differ
diff --git a/packages/NUnit.Runners.2.6.3/tools/nunit-console.exe.config b/packages/NUnit.Runners.2.6.3/tools/nunit-console.exe.config
deleted file mode 100644
index 81e5346..0000000
--- a/packages/NUnit.Runners.2.6.3/tools/nunit-console.exe.config
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/packages/NUnit.Runners.2.6.3/tools/nunit-editor.exe b/packages/NUnit.Runners.2.6.3/tools/nunit-editor.exe
deleted file mode 100644
index 640a253..0000000
Binary files a/packages/NUnit.Runners.2.6.3/tools/nunit-editor.exe and /dev/null differ
diff --git a/packages/NUnit.Runners.2.6.3/tools/nunit-x86.exe b/packages/NUnit.Runners.2.6.3/tools/nunit-x86.exe
deleted file mode 100644
index bd77b81..0000000
Binary files a/packages/NUnit.Runners.2.6.3/tools/nunit-x86.exe and /dev/null differ
diff --git a/packages/NUnit.Runners.2.6.3/tools/nunit-x86.exe.config b/packages/NUnit.Runners.2.6.3/tools/nunit-x86.exe.config
deleted file mode 100644
index 9301f94..0000000
--- a/packages/NUnit.Runners.2.6.3/tools/nunit-x86.exe.config
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/packages/NUnit.Runners.2.6.3/tools/nunit.exe b/packages/NUnit.Runners.2.6.3/tools/nunit.exe
deleted file mode 100644
index 5cd35b9..0000000
Binary files a/packages/NUnit.Runners.2.6.3/tools/nunit.exe and /dev/null differ
diff --git a/packages/NUnit.Runners.2.6.3/tools/nunit.exe.config b/packages/NUnit.Runners.2.6.3/tools/nunit.exe.config
deleted file mode 100644
index 9301f94..0000000
--- a/packages/NUnit.Runners.2.6.3/tools/nunit.exe.config
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/packages/NUnit.Runners.2.6.3/tools/nunit.framework.dll b/packages/NUnit.Runners.2.6.3/tools/nunit.framework.dll
deleted file mode 100644
index 780727f..0000000
Binary files a/packages/NUnit.Runners.2.6.3/tools/nunit.framework.dll and /dev/null differ
diff --git a/packages/NUnit.Runners.2.6.3/tools/pnunit-agent.exe b/packages/NUnit.Runners.2.6.3/tools/pnunit-agent.exe
deleted file mode 100644
index 9ec9da0..0000000
Binary files a/packages/NUnit.Runners.2.6.3/tools/pnunit-agent.exe and /dev/null differ
diff --git a/packages/NUnit.Runners.2.6.3/tools/pnunit-agent.exe.config b/packages/NUnit.Runners.2.6.3/tools/pnunit-agent.exe.config
deleted file mode 100644
index c1516ef..0000000
--- a/packages/NUnit.Runners.2.6.3/tools/pnunit-agent.exe.config
+++ /dev/null
@@ -1,77 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/packages/NUnit.Runners.2.6.3/tools/pnunit-launcher.exe b/packages/NUnit.Runners.2.6.3/tools/pnunit-launcher.exe
deleted file mode 100644
index edc56d3..0000000
Binary files a/packages/NUnit.Runners.2.6.3/tools/pnunit-launcher.exe and /dev/null differ
diff --git a/packages/NUnit.Runners.2.6.3/tools/pnunit-launcher.exe.config b/packages/NUnit.Runners.2.6.3/tools/pnunit-launcher.exe.config
deleted file mode 100644
index c1516ef..0000000
--- a/packages/NUnit.Runners.2.6.3/tools/pnunit-launcher.exe.config
+++ /dev/null
@@ -1,77 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/packages/NUnit.Runners.2.6.3/tools/pnunit.framework.dll b/packages/NUnit.Runners.2.6.3/tools/pnunit.framework.dll
deleted file mode 100644
index 573b9fc..0000000
Binary files a/packages/NUnit.Runners.2.6.3/tools/pnunit.framework.dll and /dev/null differ
diff --git a/packages/NUnit.Runners.2.6.3/tools/pnunit.tests.dll b/packages/NUnit.Runners.2.6.3/tools/pnunit.tests.dll
deleted file mode 100644
index 7051add..0000000
Binary files a/packages/NUnit.Runners.2.6.3/tools/pnunit.tests.dll and /dev/null differ
diff --git a/packages/NUnit.Runners.2.6.3/tools/runpnunit.bat b/packages/NUnit.Runners.2.6.3/tools/runpnunit.bat
deleted file mode 100644
index 43b3a69..0000000
--- a/packages/NUnit.Runners.2.6.3/tools/runpnunit.bat
+++ /dev/null
@@ -1,3 +0,0 @@
-start pnunit-agent 8080 .
-start pnunit-agent 8081 .
-pnunit-launcher test.conf
diff --git a/packages/NUnit.Runners.2.6.3/tools/test.conf b/packages/NUnit.Runners.2.6.3/tools/test.conf
deleted file mode 100644
index ce825eb..0000000
--- a/packages/NUnit.Runners.2.6.3/tools/test.conf
+++ /dev/null
@@ -1,82 +0,0 @@
-
-
-
-
-
-
-
-
-
-
- Testing
-
-
- Testing
- pnunit.tests.dll
- TestLibraries.Testing.EqualTo19
- $agent_host:8080
-
-
-
-
-
-
- Parallel_Tests
-
-
- ParallelTest_A_Test
- pnunit.tests.dll
- TestLibraries.ParallelExample.ParallelTest_A
- $agent_host:8080
-
-
- 2
-
-
-
- ParallelTest_B_Test
- pnunit.tests.dll
- TestLibraries.ParallelExample.ParallelTest_B
- $agent_host:8080
-
- 1
-
-
-
-
-
-
-
-
- Parallel_Barriers
-
-
- Parallel_Barriers_TestA
- pnunit.tests.dll
- TestLibraries.ParallelExampleWithBarriers.ParallelTestWithBarriersA
- $agent_host:8080
-
-
-
- START_BARRIER
- WAIT_BARRIER
-
-
-
- Parallel_Barriers_TestB
- pnunit.tests.dll
- TestLibraries.ParallelExampleWithBarriers.ParallelTestWithBarriersB
- $agent_host:8081
-
-
-
- START_BARRIER
- WAIT_BARRIER
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/packages/repositories.config b/packages/repositories.config
index c216dd1..f1c5939 100644
--- a/packages/repositories.config
+++ b/packages/repositories.config
@@ -1,6 +1,7 @@

+
\ No newline at end of file