diff --git a/Source/AssemblyInfo.cs b/Source/AssemblyInfo.cs index fe68f3f..c83e8ca 100644 --- a/Source/AssemblyInfo.cs +++ b/Source/AssemblyInfo.cs @@ -5,5 +5,5 @@ using System.Reflection; [assembly: AssemblyCompany("MS Open Tech")] [assembly: AssemblyProduct("Tx (LINQ to Logs and Traces)")] [assembly: AssemblyCopyright("Copyright © MS Open Tech 2012")] -[assembly: AssemblyVersion("1.0.50825.0")] -[assembly: AssemblyFileVersion("1.0.50825.0")] \ No newline at end of file +[assembly: AssemblyVersion("1.0.50917.0")] +[assembly: AssemblyFileVersion("1.0.50917.0")] \ No newline at end of file diff --git a/Source/Tx.Bond/BondEtwObserver.cs b/Source/Tx.Bond/BondEtwObserver.cs index 020d276..662f4fb 100644 --- a/Source/Tx.Bond/BondEtwObserver.cs +++ b/Source/Tx.Bond/BondEtwObserver.cs @@ -6,7 +6,9 @@ namespace Tx.Bond using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; + using System.Text; using System.Threading; + using System.Web.Script.Serialization; using global::Bond; using global::Bond.IO.Safe; @@ -16,7 +18,13 @@ namespace Tx.Bond public sealed class BondEtwObserver : IObserver, IDisposable { - private IDictionary manifestMap; + private readonly IDictionary manifestMap; + + private readonly OutputBuffer outputBuffer = new OutputBuffer(); + + private readonly CompactBinaryWriter writer; + + private readonly JavaScriptSerializer javaScriptSerializer = new JavaScriptSerializer(); private TimeSpan interval = TimeSpan.FromMinutes(20); @@ -26,17 +34,23 @@ namespace Tx.Bond private ConcurrentBag knownManifest; - private readonly OutputBuffer outputBuffer = new OutputBuffer(); - - private readonly CompactBinaryWriter writer; - + /// + /// Initializes a new instance of the class. + /// public BondEtwObserver() { this.writer = new CompactBinaryWriter(this.outputBuffer); } + /// + /// Initializes a new instance of the class. + /// + /// The map of manifests per types. + /// The time period between writing manifests into ETW. + /// manifestMap + /// interval public BondEtwObserver( - IDictionary manifestMap, + IDictionary manifestMap, TimeSpan interval) { if (manifestMap == null) @@ -56,7 +70,7 @@ namespace Tx.Bond this.Initialize(); } - + public void Initialize() { if (this.bondTypeMap == null) @@ -82,6 +96,10 @@ namespace Tx.Bond } } + /// + /// Provides the observer with new data. + /// + /// The current notification information. public void OnNext(object value) { if (value == null) @@ -96,21 +114,34 @@ namespace Tx.Bond BondTypeInfo manifestData; if (!this.bondTypeMap.TryGetValue(type, out manifestData)) { - if (!type.IsBondType()) + string manifest; + + if (type.IsBondStruct()) { - throw new NotSupportedException(); + manifest = type.TryGetManifestData(); + + manifestData = new BondTypeInfo + { + ManifestId = type.GetBondManifestIdentifier(), + ManifestData = manifest, + Serializer = new Serializer>(type) + }; + + this.bondTypeMap.Add(type, manifestData); } - - var manifest = type.TryGetManifestData(); - - manifestData = new BondTypeInfo + else { - ManifestId = type.GetBondManifestIdentifier(), - ManifestData = manifest, - Serializer = new Serializer>(type) - }; + manifest = null; - this.bondTypeMap.Add(type, manifestData); + manifestData = new BondTypeInfo + { + ManifestId = type.GetBondManifestIdentifier(), + ManifestData = null, + Serializer = null + }; + + this.bondTypeMap.Add(type, manifestData); + } if (manifest != null) { @@ -123,21 +154,43 @@ namespace Tx.Bond } } - this.outputBuffer.Position = 0; - - manifestData.Serializer.Serialize(value, this.writer); - var now = DateTime.UtcNow; - BinaryEventSource.Log.Write(now, now, BondProtocol.CompactBinaryV1, "Tx.Bond", this.outputBuffer.Data.ToByteArray(), manifestData.ManifestId); + + if (manifestData.Serializer != null) + { + this.outputBuffer.Position = 0; + + manifestData.Serializer.Serialize(value, this.writer); + + BinaryEventSource.Log.Write(now, now, BondProtocol.CompactBinaryV1, @"Tx.Bond", this.outputBuffer.Data.ToByteArray(), manifestData.ManifestId); + } + else + { + var json = this.javaScriptSerializer.Serialize(value); + BinaryEventSource.Log.Write( + now, + now, + "JSON", + @"Tx.Bond", + Encoding.UTF8.GetBytes(json), + manifestData.ManifestId); + } } + /// + /// Notifies the observer that the provider has finished sending push-based notifications. + /// public void OnCompleted() { } + /// + /// Notifies the observer that the provider has experienced an error condition. + /// + /// An object that provides additional information about the error. public void OnError(Exception error) { - throw error; + BinaryEventSource.Log.Error(error.ToString()); } private void InitializeTimer() @@ -163,11 +216,15 @@ namespace Tx.Bond manfiestInfo.ManifestData); } } + catch(Exception error) + { + BinaryEventSource.Log.Error(error.ToString()); + } finally { if (this.logManifestTimer != null) { - this.logManifestTimer.Change(this.interval.Ticks, Timeout.Infinite); + this.logManifestTimer.Change((int)this.interval.TotalMilliseconds, Timeout.Infinite); } } } diff --git a/Source/Tx.Bond/BondIdentifierHelpers.cs b/Source/Tx.Bond/BondIdentifierHelpers.cs index bb1f73e..ac228dc 100644 --- a/Source/Tx.Bond/BondIdentifierHelpers.cs +++ b/Source/Tx.Bond/BondIdentifierHelpers.cs @@ -4,7 +4,6 @@ namespace Tx.Bond { using System; using System.Linq; - using System.Reflection; using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Text; @@ -31,19 +30,14 @@ namespace Tx.Bond (byte) 251 }; - public static string GetBondManifestIdentifier(this object bondSerializable) + public static string GetBondManifestIdentifier(this object instance) { - if (bondSerializable == null) + if (instance == null) { - throw new ArgumentNullException("bondSerializable"); + throw new ArgumentNullException("instance"); } - var type = bondSerializable.GetType(); - - if (!type.IsBondType()) - { - throw new NotSupportedException(); - } + var type = instance.GetType(); var manifestId = type.GetBondManifestIdentifier(); @@ -57,11 +51,6 @@ namespace Tx.Bond throw new ArgumentNullException("type"); } - if (!type.IsBondType()) - { - return null; - } - var bondMapAttribute = ((GuidAttribute[])type.GetCustomAttributes(typeof(GuidAttribute), false)) .FirstOrDefault(); diff --git a/Source/Tx.Bond/GeneralPartitionableTypeMap.cs b/Source/Tx.Bond/GeneralPartitionableTypeMap.cs new file mode 100644 index 0000000..068020d --- /dev/null +++ b/Source/Tx.Bond/GeneralPartitionableTypeMap.cs @@ -0,0 +1,153 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Tx.Bond +{ + using System; + using System.Collections.Generic; + using System.Reactive; + using System.Text; + using System.Web.Script.Serialization; + + using global::Bond; + using global::Bond.IO.Safe; + using global::Bond.Protocols; + + using Tx.Binary; + + public sealed class GeneralPartitionableTypeMap : IPartitionableTypeMap + { + private static readonly IEqualityComparer comparer = StringComparer.OrdinalIgnoreCase; + + // Objects returned by transform call should not be null and be of System.Object type. + private static readonly object defaultInstance = new { }; + + private static readonly JavaScriptSerializer javaScriptSerializer = new JavaScriptSerializer(); + + private readonly Dictionary> transforms = new Dictionary>(); + + public IEqualityComparer Comparer + { + get { return comparer; } + } + + public string GetTypeKey(Type outputType) + { + string manifestId; + + try + { + manifestId = outputType.GetBondManifestIdentifier(); + } + catch + { + manifestId = string.Empty; + } + + return manifestId; + } + + public Func GetTransform(Type outputType) + { + Func transform; + this.transforms.TryGetValue(outputType, out transform); + + if (transform != null) + { + return transform; + } + + Func jsonDeserializer = e => DeserializeJson(e.EventPayload, outputType); + + var deserializerMap = new Dictionary>(StringComparer.OrdinalIgnoreCase) + { + { "JSON", jsonDeserializer } + }; + + if (outputType.IsBondStruct()) + { + var deserializer = new Deserializer>(outputType); + + deserializerMap.Add("BOND_V1", e => DeserializeCompactBinary(1, e.EventPayload, deserializer)); + deserializerMap.Add("BOND", e => DeserializeCompactBinary(2, e.EventPayload, deserializer)); + } + + transform = e => Transform(e, deserializerMap); + + this.transforms.Add(outputType, transform); + + return transform; + } + + public Func TimeFunction + { + get + { + return GetTime; + } + } + + public string GetInputKey(BinaryEnvelope envelope) + { + return envelope.PayloadId; + } + + private static DateTimeOffset GetTime(BinaryEnvelope envelope) + { + var time = DateTimeOffset.FromFileTime(envelope.ReceiveFileTimeUtc); + + return time; + } + + private static object Transform( + BinaryEnvelope envelope, + IDictionary> deserializerMap) + { + if (envelope.EventPayload == null) + { + return defaultInstance; + } + + object deserializedObject; + + try + { + Func transform; + if (deserializerMap.TryGetValue(envelope.Protocol, out transform)) + { + deserializedObject = transform(envelope) ?? defaultInstance; + } + else + { + deserializedObject = defaultInstance; + } + } + catch + { + deserializedObject = defaultInstance; + } + + return deserializedObject; + } + + private static object DeserializeCompactBinary( + ushort version, + byte[] data, + Deserializer> deserializer) + { + var inputStream = new InputBuffer(data); + + var reader = new CompactBinaryReader(inputStream, version); + + var outputObject = deserializer.Deserialize(reader); + + return outputObject; + } + + private static object DeserializeJson(byte[] data, Type outputType) + { + var json = Encoding.UTF8.GetString(data); + + return javaScriptSerializer.Deserialize(json, outputType); + } + } +} diff --git a/Source/Tx.Bond/Tx.Bond.csproj b/Source/Tx.Bond/Tx.Bond.csproj index 6601550..f2937e4 100644 --- a/Source/Tx.Bond/Tx.Bond.csproj +++ b/Source/Tx.Bond/Tx.Bond.csproj @@ -50,6 +50,8 @@ $(CPReferencePath)\System.Reactive.PlatformServices.dll + + @@ -68,6 +70,7 @@ + diff --git a/Test/UnitTests/BinaryEtw/BondEtwObserverTests.cs b/Test/UnitTests/BinaryEtw/BondEtwObserverTests.cs new file mode 100644 index 0000000..87ed0c1 --- /dev/null +++ b/Test/UnitTests/BinaryEtw/BondEtwObserverTests.cs @@ -0,0 +1,44 @@ +namespace Tests.Tx.BinaryEtw +{ + using System; + using System.Collections.Generic; + + using global::Tx.Bond; + + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class BondEtwObserverTests + { + [TestMethod] + public void WriteToBinaryEtw() + { + using (var observer = new BondEtwObserver()) + { + observer.OnNext(new GeneralPartitionableTypeMapTests.TestBondClass{ EventId = "A" }); + observer.OnNext("A"); + observer.OnNext(null); + + observer.OnCompleted(); + } + } + + [TestMethod] + public void WriteToBinaryEtw_2() + { + using (var observer = new BondEtwObserver( + new Dictionary + { + { typeof(GeneralPartitionableTypeMapTests.TestBondClass), "Manifest" } + }, + TimeSpan.FromMinutes(1))) + { + observer.OnNext(new GeneralPartitionableTypeMapTests.TestBondClass { EventId = "A" }); + observer.OnNext("A"); + observer.OnNext(null); + + observer.OnCompleted(); + } + } + } +} diff --git a/Test/UnitTests/BinaryEtw/GeneralPartitionableTypeMapTests.cs b/Test/UnitTests/BinaryEtw/GeneralPartitionableTypeMapTests.cs new file mode 100644 index 0000000..e40fb9a --- /dev/null +++ b/Test/UnitTests/BinaryEtw/GeneralPartitionableTypeMapTests.cs @@ -0,0 +1,114 @@ +namespace Tests.Tx.BinaryEtw +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reactive; + using System.Reactive.Linq; + using System.Runtime.InteropServices; + using System.Text; + + using global::Tx.Binary; + using global::Tx.Bond; + + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class GeneralPartitionableTypeMapTests + { + [TestMethod] + public void DeserializeJson() + { + var typeMap = new GeneralPartitionableTypeMap(); + + var typekey = typeMap.GetTypeKey(typeof(string)); + var envelope = new BinaryEnvelope + { + EventPayload = Encoding.UTF8.GetBytes(@"""A"""), + PayloadId = "daf0be6e-da1e-5a6a-0d49-69782745c885", + Protocol = "JSON" + }; + + var typekey2 = typeMap.GetInputKey(envelope); + Assert.AreEqual(typekey, typekey2); + + var result = typeMap.GetTransform(typeof(string))(envelope); + Assert.AreEqual("A", result); + } + + [TestMethod] + public void DeserializeBondV1() + { + var typeMap = new GeneralPartitionableTypeMap(); + + var typekey = typeMap.GetTypeKey(typeof(TestBondClass)); + + var envelope = new BinaryEnvelope + { + EventPayload = new byte[] { 41, 1, 65, 0 }, + PayloadId = "daf0be6e-da1e-5a6a-0d49-69782745c886", + Protocol = "BOND_V1" + }; + + var typekey2 = typeMap.GetInputKey(envelope); + Assert.AreEqual(typekey, typekey2); + + var result = typeMap.GetTransform(typeof(TestBondClass))(envelope); + } + + [TestMethod] + public void DeserializeMixedStream() + { + var input = new[] + { + new SimpleEnvelope("JSON", Guid.Parse("daf0be6e-da1e-5a6a-0d49-69782745c885"), Encoding.UTF8.GetBytes(@"""A""")), + new SimpleEnvelope("JSON", Guid.Parse("daf0be6e-da1e-5a6a-0d49-69782745c885"), new byte[0]), + new SimpleEnvelope("BOND_V1", new Guid("daf0be6e-da1e-5a6a-0d49-69782745c886"), new byte[] { 41, 1, 65, 0 }), + }; + + IEnumerable testBondClassCollection; + IEnumerable stringCollection; + + using (var playback = new Playback()) + { + ((IPlaybackConfiguration)playback).AddInput( + () => input.ToObservable(), + typeof(GeneralPartitionableTypeMap)); + + var testBondClassStream = playback.GetObservable(); + var stringStream = playback.GetObservable(); + + testBondClassCollection = playback.BufferOutput(testBondClassStream); + stringCollection = playback.BufferOutput(stringStream); + + playback.Run(); + } + + var bondObjects = testBondClassCollection.ToArray(); + Assert.IsNotNull(bondObjects); + Assert.AreEqual(1, bondObjects.Length); + + var strings = stringCollection.ToArray(); + Assert.IsNotNull(strings); + Assert.AreEqual(1, strings.Length); + } + + private class SimpleEnvelope : BinaryEnvelope + { + public SimpleEnvelope(string protocol, Guid manifestId, byte[] data) + { + this.Protocol = protocol; + this.PayloadId = manifestId.ToString(); + this.EventPayload = data; + } + } + + [global::Bond.Schema] + [Guid("daf0be6e-da1e-5a6a-0d49-69782745c886")] + public partial class TestBondClass + { + [global::Bond.Id(1)] + public string EventId { get; set; } + } + } +} diff --git a/Test/UnitTests/Tests.Tx.csproj b/Test/UnitTests/Tests.Tx.csproj index 0eeda19..27ea53d 100644 --- a/Test/UnitTests/Tests.Tx.csproj +++ b/Test/UnitTests/Tests.Tx.csproj @@ -25,6 +25,16 @@ ..\..\Source\key.snk + + $(CPReferencePath)\Bond.dll + + + $(CPReferencePath)\Bond.Attributes.dll + + + False + $(CPReferencePath)\Microsoft.Diagnostics.Tracing.EventSource.dll + $(CPReferencePath)\Microsoft.Reactive.Testing.dll @@ -56,6 +66,8 @@ Microsoft_Windows_Kernel_Process.cs + + @@ -99,6 +111,10 @@ + + {6dce4a40-4946-41b0-abe5-ce1700b598e9} + Tx.Bond + {c5cc33b0-1684-4dd4-83a5-5da4a9a25a7f} Tx.Core diff --git a/Test/UnitTests/Tests.Tx.sln b/Test/UnitTests/Tests.Tx.sln index 53ebf7c..a5da838 100644 --- a/Test/UnitTests/Tests.Tx.sln +++ b/Test/UnitTests/Tests.Tx.sln @@ -1,12 +1,16 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2012 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.Tx", "Tests.Tx.csproj", "{E88FF544-1342-4ADC-B88A-1BDCE0057DA6}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tx.Core", "..\..\Source\Tx.Core\Tx.Core.csproj", "{C5CC33B0-1684-4DD4-83A5-5DA4A9A25A7F}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tx.Windows", "..\..\Source\Tx.Windows\Tx.Windows.csproj", "{C4043ABB-EC40-4194-B15B-C0D13C2CF5C8}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tx.Bond", "..\..\Source\Tx.Bond\Tx.Bond.csproj", "{6DCE4A40-4946-41B0-ABE5-CE1700B598E9}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug40|Any CPU = Debug40|Any CPU @@ -39,6 +43,14 @@ Global {C4043ABB-EC40-4194-B15B-C0D13C2CF5C8}.Release40|Any CPU.Build.0 = Release40|Any CPU {C4043ABB-EC40-4194-B15B-C0D13C2CF5C8}.Release45|Any CPU.ActiveCfg = Release45|Any CPU {C4043ABB-EC40-4194-B15B-C0D13C2CF5C8}.Release45|Any CPU.Build.0 = Release45|Any CPU + {6DCE4A40-4946-41B0-ABE5-CE1700B598E9}.Debug40|Any CPU.ActiveCfg = Debug40|Any CPU + {6DCE4A40-4946-41B0-ABE5-CE1700B598E9}.Debug40|Any CPU.Build.0 = Debug40|Any CPU + {6DCE4A40-4946-41B0-ABE5-CE1700B598E9}.Debug45|Any CPU.ActiveCfg = Debug45|Any CPU + {6DCE4A40-4946-41B0-ABE5-CE1700B598E9}.Debug45|Any CPU.Build.0 = Debug45|Any CPU + {6DCE4A40-4946-41B0-ABE5-CE1700B598E9}.Release40|Any CPU.ActiveCfg = Release40|Any CPU + {6DCE4A40-4946-41B0-ABE5-CE1700B598E9}.Release40|Any CPU.Build.0 = Release40|Any CPU + {6DCE4A40-4946-41B0-ABE5-CE1700B598E9}.Release45|Any CPU.ActiveCfg = Release45|Any CPU + {6DCE4A40-4946-41B0-ABE5-CE1700B598E9}.Release45|Any CPU.Build.0 = Release45|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE