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