├── .nuget ├── NuGet.exe └── NuGet.Config ├── tools └── NuGet.exe ├── RxSpy.LiveView ├── Resources │ └── rxspy.ico ├── Properties │ ├── Settings.settings │ ├── Settings.Designer.cs │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ └── Resources.resx ├── Communication │ ├── IRxSpyClient.cs │ └── RxSpyHttpClient.cs ├── ViewModels │ ├── Graphs │ │ ├── ObservableGraph.cs │ │ ├── ObservableGraphLayout.cs │ │ ├── ObserveableEdge.cs │ │ └── ObservableVertex.cs │ ├── RxSpyObservablesGridViewModel.cs │ ├── ObservableGraphViewModel.cs │ ├── RxSpyObservedValueViewModel.cs │ ├── RxObservableDetailsViewModel.cs │ ├── MainViewModel.cs │ └── RxSpyObservableGridItemViewModel.cs ├── App.xaml.cs ├── MainWindow.xaml ├── App.xaml ├── Models │ ├── RxSpyErrorModel.cs │ ├── RxSpyObservedValueModel.cs │ ├── RxSpySubscriptionModel.cs │ ├── RxSpySessionModel.cs │ └── RxSpyObservableModel.cs ├── MainWindow.xaml.cs ├── packages.config ├── Styles │ ├── DataGrid.xaml │ └── ReactiveUI.xaml ├── App.config ├── Views │ ├── MainView.xaml │ ├── MainView.xaml.cs │ └── Controls │ │ ├── ObservableDetails.xaml │ │ ├── ObservableDetails.xaml.cs │ │ ├── TrackedObservablesGrid.xaml.cs │ │ └── TrackedObservablesGrid.xaml ├── GraphWindow.xaml.cs ├── AppStartup │ └── StartupSequence.cs └── GraphWindow.xaml ├── RxSpy.Shared ├── packages.config ├── Model │ └── Events │ │ ├── ConnectedEvent.cs │ │ ├── OnCompletedEvent.cs │ │ ├── UnsubscribeEvent.cs │ │ ├── DisconnectedEvent.cs │ │ ├── TypeInfo.cs │ │ ├── SubscribeEvent.cs │ │ ├── TagOperatorEvent.cs │ │ ├── Event.cs │ │ ├── MethodInfo.cs │ │ ├── OnNextEvent.cs │ │ ├── OnErrorEvent.cs │ │ ├── OperatorCreatedEvent.cs │ │ └── CallSite.cs ├── Properties │ └── AssemblyInfo.cs ├── Communication │ └── Serialization │ │ └── RxSpyJsonSerializerStrategy.cs └── RxSpy.Shared.csproj ├── RxSpy ├── Observables │ ├── IConnection.cs │ ├── IOperatorObservable.cs │ ├── ConnectableOperatorConnection.cs │ ├── ConnectableOperatorObservable.cs │ ├── OperatorConnection.cs │ └── OperatorObservable.cs ├── packages.config ├── Communication │ ├── IRxSpyServer.cs │ ├── Serialization │ │ └── RxSpyJsonSerializerStrategy.cs │ └── RxSpyHttpServer.cs ├── Events │ ├── OnCompletedEvent.cs │ ├── UnsubscribeEvent.cs │ ├── EventType.cs │ ├── ConnectedEvent.cs │ ├── TypeInfo.cs │ ├── DisconnectedEvent.cs │ ├── SubscribeEvent.cs │ ├── TagOperatorEvent.cs │ ├── OperatorCreatedEvent.cs │ ├── OnNextEvent.cs │ ├── OnErrorEvent.cs │ ├── CallSite.cs │ ├── MethodInfo.cs │ ├── Interfaces.cs │ └── Event.cs ├── Extensions │ ├── SpyObservableExtensions.cs │ └── EventHandlerExtensions.cs ├── IRxSpyEventHandler.cs ├── Utils │ ├── OperatorFactory.cs │ ├── MethodInvoker.cs │ ├── TypeUtils.cs │ ├── ValueFormatter.cs │ ├── DebuggerDisplayFormatter.cs │ ├── CallSiteCache.cs │ └── ConnectionFactory.cs ├── OperatorInfo.cs ├── Properties │ └── AssemblyInfo.cs ├── RxSpyStreamWriter.cs └── RxSpy.csproj ├── RxSpy.StressTest ├── packages.config ├── CustomObjectWithDebuggerDisplay.cs ├── App.config ├── StressTestStream.cs ├── Properties │ └── AssemblyInfo.cs ├── StressTestEventHandler.cs ├── RxSpy.StressTest.csproj └── Program.cs ├── RxSpy.TestConsole ├── packages.config ├── App.config ├── Properties │ └── AssemblyInfo.cs ├── Program.cs └── RxSpy.TestConsole.csproj ├── .gitattributes ├── RxSpy.nuspec ├── LICENSE ├── RxSpy-LiveView.nuspec ├── RxSpy.sln ├── Readme.md └── .gitignore /.nuget/NuGet.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niik/RxSpy/HEAD/.nuget/NuGet.exe -------------------------------------------------------------------------------- /tools/NuGet.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niik/RxSpy/HEAD/tools/NuGet.exe -------------------------------------------------------------------------------- /RxSpy.LiveView/Resources/rxspy.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niik/RxSpy/HEAD/RxSpy.LiveView/Resources/rxspy.ico -------------------------------------------------------------------------------- /RxSpy.Shared/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.nuget/NuGet.Config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /RxSpy.LiveView/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /RxSpy/Observables/IConnection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace RxSpy.Observables 8 | { 9 | interface IConnection: IOperatorObservable 10 | { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /RxSpy/Observables/IOperatorObservable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace RxSpy.Observables 8 | { 9 | public interface IOperatorObservable 10 | { 11 | OperatorInfo OperatorInfo { get; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /RxSpy/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /RxSpy.Shared/Model/Events/ConnectedEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using RxSpy.Events; 7 | 8 | namespace RxSpy.Models.Events 9 | { 10 | public class ConnectedEvent: Event, IConnectedEvent 11 | { 12 | public long OperatorId { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /RxSpy.StressTest/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /RxSpy.Shared/Model/Events/OnCompletedEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using RxSpy.Events; 7 | 8 | namespace RxSpy.Models.Events 9 | { 10 | public class OnCompletedEvent: Event, IOnCompletedEvent 11 | { 12 | public long OperatorId { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /RxSpy.Shared/Model/Events/UnsubscribeEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using RxSpy.Events; 7 | 8 | namespace RxSpy.Models.Events 9 | { 10 | public class UnsubscribeEvent: Event, IUnsubscribeEvent 11 | { 12 | public long SubscriptionId { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /RxSpy.TestConsole/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /RxSpy.Shared/Model/Events/DisconnectedEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using RxSpy.Events; 7 | 8 | namespace RxSpy.Models.Events 9 | { 10 | public class DisconnectedEvent : Event, IDisconnectedEvent 11 | { 12 | public long ConnectionId { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /RxSpy.LiveView/Communication/IRxSpyClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using ReactiveUI; 7 | using RxSpy.Events; 8 | 9 | namespace RxSpy.Communication 10 | { 11 | public interface IRxSpyClient 12 | { 13 | IObservable Connect(Uri address, TimeSpan timeout); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /RxSpy.Shared/Model/Events/TypeInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using RxSpy.Events; 7 | 8 | namespace RxSpy.Models.Events 9 | { 10 | public class TypeInfo: ITypeInfo 11 | { 12 | public string Name { get; set; } 13 | public string Namespace { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /RxSpy.LiveView/ViewModels/Graphs/ObservableGraph.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using GraphSharp; 7 | using QuickGraph; 8 | using RxSpy.Models; 9 | 10 | namespace RxSpy.ViewModels.Graphs 11 | { 12 | public class ObservableGraph : BidirectionalGraph 13 | { 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /RxSpy.LiveView/ViewModels/Graphs/ObservableGraphLayout.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using GraphSharp.Controls; 7 | using RxSpy.Models; 8 | 9 | namespace RxSpy.ViewModels.Graphs 10 | { 11 | public class ObservableGraphLayout: GraphLayout 12 | { 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /RxSpy.Shared/Model/Events/SubscribeEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using RxSpy.Events; 7 | 8 | namespace RxSpy.Models.Events 9 | { 10 | public class SubscribeEvent : Event, ISubscribeEvent 11 | { 12 | public long ChildId { get; set; } 13 | public long ParentId { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /RxSpy.Shared/Model/Events/TagOperatorEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using RxSpy.Events; 7 | 8 | namespace RxSpy.Models.Events 9 | { 10 | public class TagOperatorEvent: Event, ITagOperatorEvent 11 | { 12 | public long OperatorId { get; set; } 13 | public string Tag { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /RxSpy.Shared/Model/Events/Event.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using RxSpy.Events; 7 | 8 | namespace RxSpy.Models.Events 9 | { 10 | public class Event: IEvent 11 | { 12 | public EventType EventType { get; set; } 13 | public long EventId { get; set; } 14 | public long EventTime { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /RxSpy/Communication/IRxSpyServer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using RxSpy.Events; 7 | 8 | namespace RxSpy.Communication 9 | { 10 | internal interface IRxSpyServer: IDisposable 11 | { 12 | Uri Address { get; } 13 | 14 | void WaitForConnection(TimeSpan timeout); 15 | void EnqueueEvent(IEvent ev); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /RxSpy.StressTest/CustomObjectWithDebuggerDisplay.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace RxSpy.StressTest 9 | { 10 | [DebuggerDisplay("{Name} {Value,nq}")] 11 | public class CustomObjectWithDebuggerDisplay 12 | { 13 | public string Name { get; set; } 14 | public string Value { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /RxSpy/Communication/Serialization/RxSpyJsonSerializerStrategy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace RxSpy.Communication.Serialization 8 | { 9 | public class RxSpyJsonSerializerStrategy: PocoJsonSerializerStrategy 10 | { 11 | protected override object SerializeEnum(Enum p) 12 | { 13 | return Enum.GetName(p.GetType(), p); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /RxSpy.Shared/Model/Events/MethodInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using RxSpy.Events; 7 | 8 | namespace RxSpy.Models.Events 9 | { 10 | public class MethodInfo: IMethodInfo 11 | { 12 | public string DeclaringType { get; set; } 13 | public string Name { get; set; } 14 | public string Namespace { get; set; } 15 | public string Signature { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /RxSpy.Shared/Model/Events/OnNextEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using RxSpy.Events; 7 | 8 | namespace RxSpy.Models.Events 9 | { 10 | public class OnNextEvent: Event, IOnNextEvent 11 | { 12 | public long OperatorId { get; set; } 13 | public string ValueType { get; set; } 14 | public string Value { get; set; } 15 | public int Thread { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /RxSpy/Events/OnCompletedEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace RxSpy.Events 7 | { 8 | internal class OnCompletedEvent : Event, IOnCompletedEvent 9 | { 10 | public long OperatorId { get; private set; } 11 | 12 | public OnCompletedEvent(OperatorInfo operatorInfo) 13 | : base(EventType.OnCompleted) 14 | { 15 | OperatorId = operatorInfo.Id; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /RxSpy/Events/UnsubscribeEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace RxSpy.Events 7 | { 8 | internal class UnsubscribeEvent : Event, IUnsubscribeEvent 9 | { 10 | public long SubscriptionId { get; private set; } 11 | 12 | public UnsubscribeEvent(long subscriptionId) 13 | : base(EventType.Unsubscribe) 14 | { 15 | SubscriptionId = subscriptionId; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /RxSpy/Events/EventType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace RxSpy.Events 8 | { 9 | public enum EventType 10 | { 11 | OperatorCreated, 12 | OperatorCollected, 13 | Subscribe, 14 | Unsubscribe, 15 | 16 | OnNext, 17 | OnError, 18 | OnCompleted, 19 | 20 | TagOperator, 21 | 22 | Connected, 23 | Disconnected 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /RxSpy.Shared/Model/Events/OnErrorEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using RxSpy.Events; 7 | 8 | namespace RxSpy.Models.Events 9 | { 10 | public class OnErrorEvent: Event, IOnErrorEvent 11 | { 12 | public ITypeInfo ErrorType { get; set; } 13 | public string Message { get; set; } 14 | public string StackTrace { get; set; } 15 | public long OperatorId { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /RxSpy/Events/ConnectedEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace RxSpy.Events 8 | { 9 | internal class ConnectedEvent : Event, IConnectedEvent 10 | { 11 | public long OperatorId { get; set; } 12 | 13 | public ConnectedEvent(OperatorInfo operatorInfo) 14 | : base(EventType.Connected) 15 | { 16 | OperatorId = operatorInfo.Id; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /RxSpy/Events/TypeInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace RxSpy.Events 8 | { 9 | public class TypeInfo: ITypeInfo 10 | { 11 | public string Name { get; private set; } 12 | public string Namespace { get; private set; } 13 | 14 | public TypeInfo(Type type) 15 | { 16 | Name = type.Name; 17 | Namespace = type.Namespace; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /RxSpy/Events/DisconnectedEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace RxSpy.Events 8 | { 9 | internal class DisconnectedEvent : Event, IDisconnectedEvent 10 | { 11 | public long ConnectionId { get; set; } 12 | 13 | public DisconnectedEvent(long connectionId) 14 | : base(EventType.Disconnected) 15 | { 16 | ConnectionId = connectionId; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /RxSpy.Shared/Model/Events/OperatorCreatedEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using RxSpy.Events; 7 | 8 | namespace RxSpy.Models.Events 9 | { 10 | public class OperatorCreatedEvent : Event, IOperatorCreatedEvent 11 | { 12 | public long Id { get; set; } 13 | public string Name { get; set; } 14 | public ICallSite CallSite { get; set; } 15 | public IMethodInfo OperatorMethod { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /RxSpy.LiveView/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Data; 5 | using System.Diagnostics; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | using System.Windows; 9 | using RxSpy.AppStartup; 10 | 11 | namespace RxSpy 12 | { 13 | /// 14 | /// Interaction logic for App.xaml 15 | /// 16 | public partial class App : Application 17 | { 18 | public App() 19 | { 20 | StartupSequence.Start(); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /RxSpy/Events/SubscribeEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace RxSpy.Events 7 | { 8 | internal class SubscribeEvent : Event, ISubscribeEvent 9 | { 10 | public long ChildId { get; private set; } 11 | public long ParentId { get; private set; } 12 | 13 | public SubscribeEvent(OperatorInfo child, OperatorInfo parent) 14 | : base(EventType.Subscribe) 15 | { 16 | ChildId = child.Id; 17 | ParentId = parent.Id; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /RxSpy/Events/TagOperatorEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace RxSpy.Events 8 | { 9 | internal class TagOperatorEvent : Event, ITagOperatorEvent 10 | { 11 | public long OperatorId { get; set; } 12 | public string Tag { get; set; } 13 | 14 | public TagOperatorEvent(OperatorInfo info, string tag) 15 | : base(EventType.TagOperator) 16 | { 17 | OperatorId = info.Id; 18 | Tag = tag; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /RxSpy.LiveView/MainWindow.xaml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /RxSpy/Extensions/SpyObservableExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using RxSpy.Observables; 7 | 8 | namespace System.Reactive.Linq 9 | { 10 | public static class SpyObservableExtensions 11 | { 12 | public static IObservable SpyTag(this IObservable source, string tag) 13 | { 14 | var oobs = source as OperatorObservable; 15 | 16 | if (oobs != null) 17 | oobs.Tag(tag); 18 | 19 | return source; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /RxSpy.LiveView/App.xaml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /RxSpy.LiveView/Models/RxSpyErrorModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using RxSpy.Events; 6 | 7 | namespace RxSpy.Models 8 | { 9 | public class RxSpyErrorModel 10 | { 11 | public ITypeInfo ErrorType { get; set; } 12 | public string Message { get; set; } 13 | public TimeSpan Received { get; set; } 14 | public string StackTrace { get; set; } 15 | 16 | public RxSpyErrorModel(IOnErrorEvent onErrorEvent) 17 | { 18 | Received = TimeSpan.FromMilliseconds(onErrorEvent.EventTime); 19 | ErrorType = onErrorEvent.ErrorType; 20 | Message = onErrorEvent.Message; 21 | StackTrace = onErrorEvent.StackTrace; 22 | } 23 | 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /RxSpy.LiveView/Models/RxSpyObservedValueModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using RxSpy.Events; 7 | 8 | namespace RxSpy.Models 9 | { 10 | public class RxSpyObservedValueModel 11 | { 12 | public string ValueType { get; set; } 13 | public string Value { get; set; } 14 | public TimeSpan Received { get; set; } 15 | public int Thread { get; set; } 16 | 17 | public RxSpyObservedValueModel(IOnNextEvent onNextEvent) 18 | { 19 | ValueType = onNextEvent.ValueType; 20 | Value = onNextEvent.Value; 21 | Thread = onNextEvent.Thread; 22 | 23 | Received = TimeSpan.FromMilliseconds(onNextEvent.EventTime); 24 | } 25 | 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /RxSpy/IRxSpyEventHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using RxSpy.Events; 7 | 8 | namespace RxSpy 9 | { 10 | public interface IRxSpyEventHandler: IDisposable 11 | { 12 | void OnCreated(IOperatorCreatedEvent onCreatedEvent); 13 | void OnCompleted(IOnCompletedEvent onCompletedEvent); 14 | void OnError(IOnErrorEvent onErrorEvent); 15 | void OnNext(IOnNextEvent onNextEvent); 16 | void OnSubscribe(ISubscribeEvent subscribeEvent); 17 | void OnUnsubscribe(IUnsubscribeEvent unsubscribeEvent); 18 | void OnConnected(IConnectedEvent connectedEvent); 19 | void OnDisconnected(IDisconnectedEvent disconnectedEvent); 20 | void OnTag(ITagOperatorEvent tagEvent); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /RxSpy/Events/OperatorCreatedEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace RxSpy.Events 8 | { 9 | internal class OperatorCreatedEvent : Event, IOperatorCreatedEvent 10 | { 11 | readonly OperatorInfo _operatorInfo; 12 | 13 | public long Id { get { return _operatorInfo.Id; } } 14 | public string Name { get { return _operatorInfo.Name; } } 15 | public ICallSite CallSite { get { return _operatorInfo.CallSite; } } 16 | public IMethodInfo OperatorMethod { get { return _operatorInfo.OperatorMethod; } } 17 | 18 | public OperatorCreatedEvent(OperatorInfo operatorInfo) 19 | : base(EventType.OperatorCreated) 20 | { 21 | _operatorInfo = operatorInfo; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /RxSpy/Events/OnNextEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using RxSpy.Utils; 7 | 8 | namespace RxSpy.Events 9 | { 10 | internal class OnNextEvent : Event, IOnNextEvent 11 | { 12 | public long OperatorId { get; private set; } 13 | public string ValueType { get; private set; } 14 | public string Value { get; private set; } 15 | public int Thread { get; private set; } 16 | 17 | public OnNextEvent(OperatorInfo operatorInfo, Type valueType, object value, int thread) 18 | : base(EventType.OnNext) 19 | { 20 | OperatorId = operatorInfo.Id; 21 | ValueType = TypeUtils.ToFriendlyName(valueType); 22 | Value = ValueFormatter.ToString(value, valueType); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /RxSpy/Events/OnErrorEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace RxSpy.Events 7 | { 8 | internal class OnErrorEvent : Event, IOnErrorEvent 9 | { 10 | public ITypeInfo ErrorType { get; private set; } 11 | public string Message { get; private set; } 12 | public long OperatorId { get; private set; } 13 | public string StackTrace { get; private set; } 14 | 15 | public OnErrorEvent(OperatorInfo operatorInfo, Exception error) 16 | : base(EventType.OnError) 17 | { 18 | if (error == null) 19 | return; 20 | 21 | OperatorId = operatorInfo.Id; 22 | ErrorType = new TypeInfo(error.GetType()); 23 | Message = error.Message; 24 | StackTrace = error.StackTrace; 25 | } 26 | 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /RxSpy/Events/CallSite.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace RxSpy.Events 10 | { 11 | public class CallSite : ICallSite 12 | { 13 | public int Line { get; private set; } 14 | public string File { get; private set; } 15 | public int ILOffset { get; private set; } 16 | public IMethodInfo Method { get; private set; } 17 | 18 | public CallSite(StackFrame frame) 19 | { 20 | Line = frame.GetFileLineNumber(); 21 | File = frame.GetFileName(); 22 | ILOffset = frame.GetILOffset(); 23 | 24 | var method = frame.GetMethod(); 25 | 26 | if (method != null) 27 | Method = new MethodInfo(method); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /RxSpy.Shared/Model/Events/CallSite.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using RxSpy.Events; 8 | 9 | namespace RxSpy.Models.Events 10 | { 11 | public class CallSite : ICallSite 12 | { 13 | public string File { get; set; } 14 | public int ILOffset { get; set; } 15 | public int Line { get; set; } 16 | public IMethodInfo Method { get; set; } 17 | 18 | public override string ToString() 19 | { 20 | if (Method.Name == null) 21 | return ""; 22 | 23 | string typeAndMethod = Method.DeclaringType + "." + Method.Signature; 24 | 25 | if (File != null && Line != -1) 26 | return typeAndMethod + " in " + Path.GetFileName(File) + ":" + Line; 27 | 28 | return typeAndMethod; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /RxSpy.LiveView/ViewModels/Graphs/ObserveableEdge.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using QuickGraph; 8 | using RxSpy.Models; 9 | 10 | namespace RxSpy.ViewModels.Graphs 11 | { 12 | [DebuggerDisplay("{Source} -> {Target}")] 13 | public class ObserveableEdge : Edge, IEquatable 14 | { 15 | public ObserveableEdge(ObservableVertex child, ObservableVertex parent) 16 | : base(child, parent) 17 | { 18 | 19 | } 20 | 21 | public bool Equals(ObserveableEdge other) 22 | { 23 | if (other == null) return false; 24 | 25 | return other.Source.Equals(Source) && other.Target.Equals(Target); 26 | } 27 | 28 | public override int GetHashCode() 29 | { 30 | return Source.GetHashCode() ^ Target.GetHashCode(); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /RxSpy.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | RxSpy 5 | 0.1.5 6 | A library that provides logging/analysis of Reactive Extensions observables, events, errors and subscriptions in your application. 7 | Markus Olsson 8 | https://github.com/niik/RxSpy 9 | https://github.com/niik/RxSpy/blob/master/LICENSE 10 | en-us 11 | false 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /RxSpy.LiveView/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Windows; 7 | using System.Windows.Controls; 8 | using System.Windows.Data; 9 | using System.Windows.Documents; 10 | using System.Windows.Input; 11 | using System.Windows.Media; 12 | using System.Windows.Media.Imaging; 13 | using System.Windows.Navigation; 14 | using System.Windows.Shapes; 15 | using ReactiveUI; 16 | using RxSpy.ViewModels; 17 | using Splat; 18 | 19 | namespace RxSpy 20 | { 21 | /// 22 | /// Interaction logic for MainWindow.xaml 23 | /// 24 | public partial class MainWindow : Window 25 | { 26 | public MainWindow() 27 | { 28 | InitializeComponent(); 29 | 30 | viewHost.ViewModel = Locator.Current.GetService(); 31 | } 32 | 33 | protected override void OnClosed(EventArgs e) 34 | { 35 | base.OnClosed(e); 36 | Application.Current.Shutdown(); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /RxSpy.StressTest/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /RxSpy.TestConsole/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /RxSpy/Observables/ConnectableOperatorConnection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reactive.Disposables; 4 | using System.Reactive.Subjects; 5 | using RxSpy.Events; 6 | 7 | namespace RxSpy.Observables 8 | { 9 | internal class ConnectableOperatorConnection : OperatorConnection, IConnectableObservable 10 | { 11 | readonly IConnectableObservable _connectableObservable; 12 | 13 | public ConnectableOperatorConnection(RxSpySession session, IConnectableObservable parent, OperatorInfo childInfo) 14 | : base(session, parent, childInfo) 15 | { 16 | _connectableObservable = parent; 17 | } 18 | 19 | public IDisposable Connect() 20 | { 21 | var connectionId = Session.OnConnected(OperatorInfo); 22 | var disp = _connectableObservable.Connect(); 23 | 24 | return Disposable.Create(() => 25 | { 26 | disp.Dispose(); 27 | Session.OnDisconnected(Event.Disconnect(connectionId)); 28 | }); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Markus Olsson 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the 8 | Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | DEALINGS IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /RxSpy/Observables/ConnectableOperatorObservable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reactive.Disposables; 4 | using System.Reactive.Subjects; 5 | using RxSpy.Events; 6 | 7 | namespace RxSpy.Observables 8 | { 9 | internal class ConnectableOperatorObservable : OperatorObservable, IConnectableObservable 10 | { 11 | readonly IConnectableObservable _connectableObservable; 12 | 13 | public ConnectableOperatorObservable(RxSpySession session, IConnectableObservable parent, OperatorInfo operatorInfo) 14 | : base(session, parent, operatorInfo) 15 | { 16 | _connectableObservable = parent; 17 | } 18 | 19 | public IDisposable Connect() 20 | { 21 | var connectionId = Session.OnConnected(OperatorInfo); 22 | var disp = _connectableObservable.Connect(); 23 | 24 | return Disposable.Create(() => 25 | { 26 | disp.Dispose(); 27 | Session.OnDisconnected(Event.Disconnect(connectionId)); 28 | }); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /RxSpy.LiveView/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /RxSpy.LiveView/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.18449 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace RxSpy.Properties 12 | { 13 | 14 | 15 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 16 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] 17 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase 18 | { 19 | 20 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 21 | 22 | public static Settings Default 23 | { 24 | get 25 | { 26 | return defaultInstance; 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /RxSpy.LiveView/ViewModels/RxSpyObservablesGridViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Reactive.Linq; 6 | using System.Threading.Tasks; 7 | using ReactiveUI; 8 | using RxSpy.Models; 9 | 10 | namespace RxSpy.ViewModels 11 | { 12 | public class RxSpyObservablesGridViewModel : ReactiveObject 13 | { 14 | IReactiveDerivedList _observables; 15 | public IReactiveDerivedList Observables 16 | { 17 | get { return _observables; } 18 | set { this.RaiseAndSetIfChanged(ref _observables, value); } 19 | } 20 | 21 | RxSpyObservableGridItemViewModel _selectedItem; 22 | public RxSpyObservableGridItemViewModel SelectedItem 23 | { 24 | get { return _selectedItem; } 25 | set { this.RaiseAndSetIfChanged(ref _selectedItem, value); } 26 | } 27 | 28 | public RxSpyObservablesGridViewModel(IReadOnlyReactiveList model) 29 | { 30 | Observables = model.CreateDerivedCollection(x => new RxSpyObservableGridItemViewModel(x)); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /RxSpy.LiveView/Styles/DataGrid.xaml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 19 | 20 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /RxSpy.LiveView/ViewModels/Graphs/ObservableVertex.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Reactive.Concurrency; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using ReactiveUI; 9 | using RxSpy.Models; 10 | 11 | namespace RxSpy.ViewModels.Graphs 12 | { 13 | [DebuggerDisplay("{Id}")] 14 | public class ObservableVertex : ReactiveObject, IEquatable 15 | { 16 | public long Id { get; set; } 17 | 18 | readonly ObservableAsPropertyHelper _name; 19 | public string Name 20 | { 21 | get { return _name.Value; } 22 | } 23 | 24 | readonly ObservableAsPropertyHelper _hasError; 25 | public bool HasError 26 | { 27 | get { return _hasError.Value; } 28 | } 29 | 30 | readonly ObservableAsPropertyHelper _isActive; 31 | public bool IsActive 32 | { 33 | get { return _isActive.Value; } 34 | } 35 | 36 | readonly ObservableAsPropertyHelper _signalsCount; 37 | public long SignalsCount 38 | { 39 | get { return _signalsCount.Value; } 40 | } 41 | 42 | readonly ObservableAsPropertyHelper _tooltip; 43 | public string ToolTip 44 | { 45 | get { return _tooltip.Value; } 46 | } 47 | 48 | public ObservableVertex(RxSpyObservableModel model) 49 | { 50 | Id = model.Id; 51 | Model = model; 52 | 53 | this.WhenAnyValue(x => x.Model.Name) 54 | .ToProperty(this, x => x.Name, out _name, scheduler: Scheduler.Immediate); 55 | 56 | this.WhenAnyValue(x => x.Model.HasError) 57 | .ToProperty(this, x => x.HasError, out _hasError); 58 | 59 | this.WhenAnyValue(x => x.Model.IsActive) 60 | .ToProperty(this, x => x.IsActive, out _isActive); 61 | 62 | this.WhenAnyValue(x => x.Model.ValuesProduced) 63 | .ToProperty(this, x => x.SignalsCount, out _signalsCount); 64 | 65 | this.WhenAnyValue(x => x.Model.Id, x => x.Model.CallSite, (x, y) => { return "#" + Id + " " + Convert.ToString(y); }) 66 | .ToProperty(this, x => x.ToolTip, out _tooltip); 67 | } 68 | 69 | public bool Equals(ObservableVertex other) 70 | { 71 | if (other == null) return false; 72 | 73 | return other.Id == Id; 74 | } 75 | 76 | public RxSpyObservableModel Model { get; set; } 77 | 78 | public override int GetHashCode() 79 | { 80 | return Id.GetHashCode(); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /RxSpy.Shared/Communication/Serialization/RxSpyJsonSerializerStrategy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using RxSpy.Events; 7 | using RxSpy.Models.Events; 8 | 9 | namespace RxSpy.Communication.Serialization 10 | { 11 | public class RxSpyJsonSerializerStrategy: PocoJsonSerializerStrategy 12 | { 13 | public override object DeserializeObject(object value, Type type) 14 | { 15 | if (type.IsEnum) 16 | { 17 | return Enum.Parse(type, (string)value); 18 | } 19 | 20 | var obj = value as IDictionary; 21 | 22 | if (obj == null) 23 | return base.DeserializeObject(value, type); 24 | 25 | if (type == typeof(IEvent)) 26 | { 27 | var eventType = (EventType)DeserializeObject(obj["EventType"], typeof(EventType)); 28 | 29 | switch (eventType) 30 | { 31 | case EventType.OperatorCreated: return base.DeserializeObject(value, typeof(OperatorCreatedEvent)); 32 | case EventType.Subscribe: return base.DeserializeObject(value, typeof(SubscribeEvent)); 33 | case EventType.Unsubscribe: return base.DeserializeObject(value, typeof(UnsubscribeEvent)); 34 | case EventType.OnNext: return base.DeserializeObject(value, typeof(OnNextEvent)); 35 | case EventType.OnError: return base.DeserializeObject(value, typeof(OnErrorEvent)); 36 | case EventType.OnCompleted: return base.DeserializeObject(value, typeof(OnCompletedEvent)); 37 | case EventType.TagOperator: return base.DeserializeObject(value, typeof(TagOperatorEvent)); 38 | case EventType.Connected: return base.DeserializeObject(value, typeof(ConnectedEvent)); 39 | case EventType.Disconnected: return base.DeserializeObject(value, typeof(DisconnectedEvent)); 40 | default: throw new NotImplementedException(); 41 | } 42 | } 43 | 44 | if (type == typeof(ICallSite)) 45 | return base.DeserializeObject(value, typeof(CallSite)); 46 | 47 | if (type == typeof(IMethodInfo)) 48 | return base.DeserializeObject(value, typeof(MethodInfo)); 49 | 50 | if (type == typeof(ITypeInfo)) 51 | return base.DeserializeObject(value, typeof(TypeInfo)); 52 | 53 | return base.DeserializeObject(value, type); 54 | } 55 | 56 | protected override object SerializeEnum(Enum p) 57 | { 58 | return Enum.GetName(p.GetType(), p); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /RxSpy.LiveView/AppStartup/StartupSequence.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reactive.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Windows; 8 | using ReactiveUI; 9 | using RxSpy.Communication; 10 | using RxSpy.Events; 11 | using RxSpy.Models; 12 | using RxSpy.Models.Events; 13 | using RxSpy.ViewModels; 14 | using RxSpy.Views; 15 | using RxSpy.Views.Controls; 16 | using Splat; 17 | 18 | namespace RxSpy.AppStartup 19 | { 20 | public static class StartupSequence 21 | { 22 | public static void Start() 23 | { 24 | var args = Environment.GetCommandLineArgs(); 25 | 26 | var address = args.Length > 1 27 | ? new Uri(args[1]) 28 | : new Uri("http://localhost:65073/rxspy/"); 29 | 30 | var client = new RxSpyHttpClient(); 31 | 32 | var session = new RxSpySessionModel(); 33 | 34 | client.Connect(address, TimeSpan.FromSeconds(5)) 35 | .Where(x => x != null) 36 | .ObserveOn(RxApp.MainThreadScheduler) 37 | .Subscribe(session.OnEvent, ex => { MessageBox.Show(App.Current.MainWindow, "Lost connection to host", "Host disconnected", MessageBoxButton.OK, MessageBoxImage.Error); }); 38 | 39 | //session.OnEvent(new OperatorCreatedEvent 40 | //{ 41 | // EventType = EventType.OperatorCreated, 42 | // Name = "Dummy1", 43 | // Id = 0, 44 | //}); 45 | 46 | //session.OnEvent(new OperatorCreatedEvent 47 | //{ 48 | // EventType = EventType.OperatorCreated, 49 | // Name = "Dummy2", 50 | // Id = 1, 51 | //}); 52 | 53 | //session.OnEvent(new OperatorCreatedEvent 54 | //{ 55 | // EventType = EventType.OperatorCreated, 56 | // Name = "Dummy3", 57 | // Id = 2 58 | //}); 59 | 60 | //session.OnEvent(new SubscribeEvent { EventType = EventType.Subscribe, ChildId = 2, ParentId = 1 }); 61 | //session.OnEvent(new SubscribeEvent { EventType = EventType.Subscribe, ChildId = 1, ParentId = 0 }); 62 | 63 | var mainViewModel = new MainViewModel(session); 64 | 65 | Locator.CurrentMutable.RegisterConstant(mainViewModel, typeof(MainViewModel)); 66 | 67 | Locator.CurrentMutable.Register(() => new MainView(), typeof(IViewFor)); 68 | Locator.CurrentMutable.Register(() => new TrackedObservablesGrid(), typeof(IViewFor)); 69 | Locator.CurrentMutable.Register(() => new ObservableDetails(), typeof(IViewFor)); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /RxSpy.LiveView/Styles/ReactiveUI.xaml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 70 | -------------------------------------------------------------------------------- /RxSpy.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.30723.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{AD3DF912-0232-495D-9542-172C1B5AEB18}" 7 | ProjectSection(SolutionItems) = preProject 8 | .nuget\NuGet.Config = .nuget\NuGet.Config 9 | .nuget\NuGet.exe = .nuget\NuGet.exe 10 | .nuget\NuGet.targets = .nuget\NuGet.targets 11 | EndProjectSection 12 | EndProject 13 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RxSpy.StressTest", "RxSpy.StressTest\RxSpy.StressTest.csproj", "{AEDF8197-4672-45B8-B152-131C11439D90}" 14 | EndProject 15 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RxSpy.TestConsole", "RxSpy.TestConsole\RxSpy.TestConsole.csproj", "{C50D2810-7AE0-435E-B0DF-245820F26D08}" 16 | EndProject 17 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RxSpy.LiveView", "RxSpy.LiveView\RxSpy.LiveView.csproj", "{D7E752E7-411A-413B-B27D-482C653C45DC}" 18 | EndProject 19 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RxSpy", "RxSpy\RxSpy.csproj", "{AD01307E-2429-482A-9F7A-A8AA147BCFDA}" 20 | EndProject 21 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RxSpy.Shared", "RxSpy.Shared\RxSpy.Shared.csproj", "{1E10026E-8A90-46F1-8FB2-06BEDC21B4CC}" 22 | EndProject 23 | Global 24 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 25 | Debug|Any CPU = Debug|Any CPU 26 | Release|Any CPU = Release|Any CPU 27 | EndGlobalSection 28 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 29 | {AEDF8197-4672-45B8-B152-131C11439D90}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 30 | {AEDF8197-4672-45B8-B152-131C11439D90}.Debug|Any CPU.Build.0 = Debug|Any CPU 31 | {AEDF8197-4672-45B8-B152-131C11439D90}.Release|Any CPU.ActiveCfg = Release|Any CPU 32 | {AEDF8197-4672-45B8-B152-131C11439D90}.Release|Any CPU.Build.0 = Release|Any CPU 33 | {C50D2810-7AE0-435E-B0DF-245820F26D08}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 34 | {C50D2810-7AE0-435E-B0DF-245820F26D08}.Debug|Any CPU.Build.0 = Debug|Any CPU 35 | {C50D2810-7AE0-435E-B0DF-245820F26D08}.Release|Any CPU.ActiveCfg = Release|Any CPU 36 | {C50D2810-7AE0-435E-B0DF-245820F26D08}.Release|Any CPU.Build.0 = Release|Any CPU 37 | {D7E752E7-411A-413B-B27D-482C653C45DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 38 | {D7E752E7-411A-413B-B27D-482C653C45DC}.Debug|Any CPU.Build.0 = Debug|Any CPU 39 | {D7E752E7-411A-413B-B27D-482C653C45DC}.Release|Any CPU.ActiveCfg = Release|Any CPU 40 | {D7E752E7-411A-413B-B27D-482C653C45DC}.Release|Any CPU.Build.0 = Release|Any CPU 41 | {AD01307E-2429-482A-9F7A-A8AA147BCFDA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 42 | {AD01307E-2429-482A-9F7A-A8AA147BCFDA}.Debug|Any CPU.Build.0 = Debug|Any CPU 43 | {AD01307E-2429-482A-9F7A-A8AA147BCFDA}.Release|Any CPU.ActiveCfg = Release|Any CPU 44 | {AD01307E-2429-482A-9F7A-A8AA147BCFDA}.Release|Any CPU.Build.0 = Release|Any CPU 45 | {1E10026E-8A90-46F1-8FB2-06BEDC21B4CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 46 | {1E10026E-8A90-46F1-8FB2-06BEDC21B4CC}.Debug|Any CPU.Build.0 = Debug|Any CPU 47 | {1E10026E-8A90-46F1-8FB2-06BEDC21B4CC}.Release|Any CPU.ActiveCfg = Release|Any CPU 48 | {1E10026E-8A90-46F1-8FB2-06BEDC21B4CC}.Release|Any CPU.Build.0 = Release|Any CPU 49 | EndGlobalSection 50 | GlobalSection(SolutionProperties) = preSolution 51 | HideSolutionNode = FALSE 52 | EndGlobalSection 53 | EndGlobal 54 | -------------------------------------------------------------------------------- /RxSpy.LiveView/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.18449 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace RxSpy.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("RxSpy.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | 63 | /// 64 | /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). 65 | /// 66 | internal static System.Drawing.Icon rxspy { 67 | get { 68 | object obj = ResourceManager.GetObject("rxspy", resourceCulture); 69 | return ((System.Drawing.Icon)(obj)); 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /RxSpy.LiveView/ViewModels/RxObservableDetailsViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Reactive.Linq; 2 | using System.Text; 3 | using ReactiveUI; 4 | using RxSpy.Models; 5 | 6 | namespace RxSpy.ViewModels 7 | { 8 | public class RxSpyObservableDetailsViewModel : ReactiveObject 9 | { 10 | private RxSpyObservableModel _model; 11 | 12 | readonly ObservableAsPropertyHelper> _observedValues; 13 | public IReadOnlyReactiveList ObservedValues 14 | { 15 | get { return _observedValues.Value; } 16 | } 17 | 18 | readonly ObservableAsPropertyHelper _valuesGridIsEnabled; 19 | public bool ValuesGridIsEnabled 20 | { 21 | get { return _valuesGridIsEnabled.Value; } 22 | } 23 | 24 | readonly ObservableAsPropertyHelper _parents; 25 | public RxSpyObservablesGridViewModel Parents 26 | { 27 | get { return _parents.Value; } 28 | } 29 | 30 | readonly ObservableAsPropertyHelper _children; 31 | public RxSpyObservablesGridViewModel Children 32 | { 33 | get { return _children.Value; } 34 | } 35 | 36 | readonly ObservableAsPropertyHelper _showErrorTab; 37 | public bool ShowErrorTab 38 | { 39 | get { return _showErrorTab.Value; } 40 | } 41 | 42 | readonly ObservableAsPropertyHelper _errorText; 43 | public string ErrorText 44 | { 45 | get { return _errorText.Value; } 46 | } 47 | 48 | public RxSpyObservableDetailsViewModel(RxSpyObservableModel model) 49 | { 50 | _model = model; 51 | 52 | this.WhenAnyValue(x => x._model.ObservedValues) 53 | .Select(x => x.CreateDerivedCollection(m => new RxSpyObservedValueViewModel(m))) 54 | .Scan((prev, cur) => { using (prev) return cur; }) 55 | .ToProperty(this, x => x.ObservedValues, out _observedValues); 56 | 57 | this.WhenAnyValue(x => x._model.Parents, x => new RxSpyObservablesGridViewModel(x)) 58 | .ToProperty(this, x => x.Parents, out _parents); 59 | 60 | this.WhenAnyValue(x => x._model.Children, x => new RxSpyObservablesGridViewModel(x)) 61 | .ToProperty(this, x => x.Children, out _children); 62 | 63 | this.WhenAnyValue(x => x._model.HasError) 64 | .ToProperty(this, x => x.ShowErrorTab, out _showErrorTab); 65 | 66 | this.WhenAnyValue(x => x._model.Error, FormatErrorText) 67 | .ToProperty(this, x => x.ErrorText, out _errorText); 68 | } 69 | 70 | string FormatErrorText(RxSpyErrorModel err) 71 | { 72 | if (err == null) 73 | return "Nope, you're good"; 74 | 75 | var sb = new StringBuilder(); 76 | 77 | sb.AppendLine("Received: " + err.Received); 78 | sb.AppendLine(err.ErrorType.Namespace + err.ErrorType.Name); 79 | sb.AppendLine(); 80 | sb.AppendLine(err.Message); 81 | 82 | if (!string.IsNullOrEmpty(err.StackTrace)) 83 | { 84 | sb.AppendLine(); 85 | sb.AppendLine("Stacktrace: "); 86 | sb.AppendLine(err.StackTrace); 87 | } 88 | 89 | return sb.ToString(); 90 | } 91 | } 92 | } -------------------------------------------------------------------------------- /RxSpy.LiveView/GraphWindow.xaml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 51 | 52 | 53 | 54 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # RxSpy 2 | 3 | Debugging Reactive applications can be hard™. Chaining and joining observables right and left often produce an observable graph that can be hard to comprehend. 4 | 5 | RxSpy tries to solve or at least alleviate this problem by giving developers a live birds-eye view of the application, all observables within it and the signals they produce. By referencing a small library your app you can launch a reactive debugging session of sorts which gives you a visual interface through which you can see all observables in your app, the values they produced and where they where created from. 6 | 7 | ![rxspy-screenshot](https://cloud.githubusercontent.com/assets/634063/4343107/e3261bb6-404e-11e4-986f-adf6475db8d7.gif) 8 | 9 | 10 | RxSpy consists of two pieces. ```RxSpy``` which is the small library you include in your app and the visual tool RxSpy LiveView. ```RxSpy``` initiates a small server inside of your app which the visual tool can then connect to. Through that connection ```RxSpy.LiveView``` then streams all observable events that it can possibly get its hands on. You may also stream events to a file for analysis through some other tool. You do this by calling ```RxSpySession.Launch``` and passing in an instance of ```RxSpyStreamWriter```. 11 | 12 | ## WARNING WARNING BETA BETA 13 | 14 | RxSpy is extremely young still. While the LiveView tool is suitable for demos, teaching etc it struggles to handle any real world application load. The reason is twofold, it's not been optimized yet and it's also really hard/impossible to take all events from multiple threads and try to serialize them onto the UI thread of the live view app in real time. 15 | 16 | If you have a rought idea of what areas of your app you'd want to monitor you can use [explicit capture](https://github.com/niik/RxSpy/pull/24) to only look at a specific set of observables. 17 | 18 | The long term plan is to build a companion app to the Live View tool that does non-realtime analysis of a captured file. See [#25](https://github.com/niik/RxSpy/pull/25) for more info on that. 19 | 20 | ## Running it 21 | 22 | The easiest way to start playing with RxSpy is to install the NuGet package RxSpy.LiveView into your app. Once you've done that or included RxSpy through other means call ```RxSpySession.Launch``` at the entry point of your application. This call will block until the UI has had a chance to launch and connect. 23 | 24 | If you're not running it through nuget package you'll have to edit the ```RxSpySession.FindGuiPath``` and point it to the ```RxSpy.LiveView.exe```. 25 | 26 | You can also clone RxSpy and try the RxSpy.TestConsole project. 27 | 28 | ## Things currently trackable 29 | 30 | - All observables created through one of the standard Rx operators. 31 | - The class and method from which the operator was created and (if available) the file name and line. 32 | - Signals (values) produced by observables 33 | - The timestamp of when the signal was produced 34 | - A string representation of the value itself (using ```DebuggerDisplay``` if available, falls back to ```.ToString()```) 35 | - What thread the value was produced on (helpful for debugging UI thread issues) 36 | - If the observable completed in error and the full details of that error 37 | - Total number of observables observed (no pun intended) 38 | - Total number of signals so far in the app 39 | - Signals per second currently 40 | 41 | ## Nifty things 42 | 43 | - By double-clicking on an observable in the app you get a visual graph rendering of that observable, all of its ancestors and descendants. The graph is live-updating and 44 | - If you tack on ```.SpyTag("Foo")``` to one of the observables in your app that tag will show in the UI. Making it easy to locate the observable in the app. 45 | -------------------------------------------------------------------------------- /RxSpy/Utils/TypeUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | namespace RxSpy.Utils 10 | { 11 | public static class TypeUtils 12 | { 13 | static Dictionary csFriendlyTypeNames; 14 | readonly static ConcurrentDictionary> _typeNameCache = 15 | new ConcurrentDictionary>(); 16 | 17 | static TypeUtils() 18 | { 19 | csFriendlyTypeNames = new Dictionary(); 20 | 21 | csFriendlyTypeNames.Add(typeof(sbyte), "sbyte"); 22 | csFriendlyTypeNames.Add(typeof(byte), "byte"); 23 | csFriendlyTypeNames.Add(typeof(short), "short"); 24 | csFriendlyTypeNames.Add(typeof(ushort), "ushort"); 25 | csFriendlyTypeNames.Add(typeof(int), "int"); 26 | csFriendlyTypeNames.Add(typeof(uint), "uint"); 27 | csFriendlyTypeNames.Add(typeof(long), "long"); 28 | csFriendlyTypeNames.Add(typeof(ulong), "ulong"); 29 | csFriendlyTypeNames.Add(typeof(float), "float"); 30 | csFriendlyTypeNames.Add(typeof(double), "double"); 31 | csFriendlyTypeNames.Add(typeof(bool), "bool"); 32 | csFriendlyTypeNames.Add(typeof(char), "char"); 33 | csFriendlyTypeNames.Add(typeof(string), "string"); 34 | csFriendlyTypeNames.Add(typeof(object), "object"); 35 | csFriendlyTypeNames.Add(typeof(decimal), "decimal"); 36 | } 37 | 38 | public static string ToFriendlyName(Type type) 39 | { 40 | var lazy = _typeNameCache.GetOrAdd(type, _ => new Lazy( 41 | () => toFriendlyNameImpl(type), 42 | LazyThreadSafetyMode.ExecutionAndPublication)); 43 | 44 | return lazy.Value; 45 | } 46 | 47 | static string toFriendlyNameImpl(Type type) 48 | { 49 | if (type.IsGenericType) 50 | { 51 | if (type.GetGenericTypeDefinition() == typeof(Nullable<>)) 52 | { 53 | return ToFriendlyName(type.GetGenericArguments()[0]) + "?"; 54 | } 55 | else 56 | { 57 | var definition = type.GetGenericTypeDefinition(); 58 | 59 | return GetNameWithoutGenerics(definition) + "<" + string.Join(", ", type.GetGenericArguments().Select(ToFriendlyName)) + ">"; 60 | } 61 | } 62 | 63 | if (type.IsArray) 64 | { 65 | return ToFriendlyName(type.GetElementType()) + Repeat("[]", type.GetArrayRank()); 66 | } 67 | 68 | string name; 69 | 70 | if (csFriendlyTypeNames.TryGetValue(type, out name)) 71 | return name; 72 | 73 | return type.Name; 74 | } 75 | 76 | private static string GetNameWithoutGenerics(Type definition) 77 | { 78 | var n = definition.Name; 79 | var p = n.IndexOf('`'); 80 | 81 | if (p == -1) 82 | return n; 83 | 84 | return n.Substring(0, p); 85 | } 86 | 87 | private static string Repeat(string str, int count) 88 | { 89 | if (count == 1) 90 | return str; 91 | 92 | var arr = new string[count]; 93 | 94 | for (int i = 0; i < count; i++) 95 | arr[i] = str; 96 | 97 | return string.Concat(arr); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /RxSpy.LiveView/ViewModels/MainViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using ReactiveUI; 7 | using RxSpy.Models; 8 | using System.Reactive.Linq; 9 | using System.Diagnostics; 10 | 11 | namespace RxSpy.ViewModels 12 | { 13 | public class MainViewModel : ReactiveObject 14 | { 15 | public RxSpySessionModel Model { get; set; } 16 | 17 | readonly ObservableAsPropertyHelper> _trackedObservables; 18 | public IReadOnlyReactiveList TrackedObservables 19 | { 20 | get { return _trackedObservables.Value; } 21 | } 22 | 23 | RxSpyObservablesGridViewModel _gridViewModel; 24 | public RxSpyObservablesGridViewModel GridViewModel 25 | { 26 | get { return _gridViewModel; } 27 | set { this.RaiseAndSetIfChanged(ref _gridViewModel, value); } 28 | } 29 | 30 | readonly ObservableAsPropertyHelper _selectedObservable; 31 | public RxSpyObservableModel SelectedObservable 32 | { 33 | get { return _selectedObservable.Value; } 34 | } 35 | 36 | readonly ObservableAsPropertyHelper _detailViewModel; 37 | public RxSpyObservableDetailsViewModel DetailsViewModel 38 | { 39 | get { return _detailViewModel.Value; } 40 | } 41 | 42 | readonly ObservableAsPropertyHelper _signalsPerSecond; 43 | public double SignalsPerSecond 44 | { 45 | get { return _signalsPerSecond.Value; } 46 | } 47 | 48 | readonly ObservableAsPropertyHelper _signalCount; 49 | public long SignalCount 50 | { 51 | get { return _signalCount.Value; } 52 | } 53 | 54 | readonly ObservableAsPropertyHelper _errorCount; 55 | public long ErrorCount 56 | { 57 | get { return _errorCount.Value; } 58 | } 59 | 60 | 61 | public MainViewModel(RxSpySessionModel model) 62 | { 63 | Model = model; 64 | 65 | this.WhenAnyValue(x => x.Model.TrackedObservables) 66 | .ToProperty(this, x => x.TrackedObservables, out _trackedObservables); 67 | 68 | GridViewModel = new RxSpyObservablesGridViewModel(model.TrackedObservables); 69 | 70 | this.WhenAnyValue(x => x.GridViewModel.SelectedItem) 71 | .Select(x => x == null ? null : x.Model) 72 | .ToProperty(this, x => x.SelectedObservable, out _selectedObservable); 73 | 74 | this.WhenAnyValue(x => x.SelectedObservable) 75 | .Select(x => x == null ? null : new RxSpyObservableDetailsViewModel(x)) 76 | .ToProperty(this, x => x.DetailsViewModel, out _detailViewModel); 77 | 78 | var throttledSignalCount = this.WhenAnyValue(x => x.Model.SignalCount) 79 | .Throttle(TimeSpan.FromSeconds(1), RxApp.MainThreadScheduler) 80 | .Publish() 81 | .RefCount(); 82 | 83 | throttledSignalCount 84 | .Scan(Tuple.Create(0L, 0L), (acc, cur) => Tuple.Create(acc.Item2, cur)) 85 | .Select(x => (double)(x.Item2 - x.Item1)) 86 | .ToProperty(this, x => x.SignalsPerSecond, out _signalsPerSecond); 87 | 88 | throttledSignalCount.ToProperty(this, x => x.SignalCount, out _signalCount); 89 | 90 | this.WhenAnyValue(x => x.Model.ErrorCount) 91 | .Throttle(TimeSpan.FromSeconds(1), RxApp.MainThreadScheduler) 92 | .ToProperty(this, x => x.ErrorCount, out _errorCount); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /RxSpy/Utils/ValueFormatter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Linq; 6 | using System.Reflection; 7 | using System.Text; 8 | using System.Text.RegularExpressions; 9 | using System.Threading.Tasks; 10 | 11 | namespace RxSpy.Utils 12 | { 13 | public static class ValueFormatter 14 | { 15 | readonly static ConcurrentDictionary>> _cachedFormatters = 16 | new ConcurrentDictionary>>(); 17 | 18 | public static string ToString(object value) 19 | { 20 | if (value == null) 21 | return ""; 22 | 23 | return ToString(value, value.GetType()); 24 | } 25 | 26 | public static string ToString(object value, Type type) 27 | { 28 | var formatter = _cachedFormatters.GetOrAdd(type, CreateFormatter); 29 | 30 | return formatter.Value(value); 31 | } 32 | 33 | private static Lazy> CreateFormatter(Type type) 34 | { 35 | return new Lazy>(() => BuildFormatterDelegate(type)); 36 | } 37 | 38 | private static Func BuildFormatterDelegate(Type type) 39 | { 40 | if (type == typeof(string)) 41 | { 42 | return o => o == null ? "null" : ('"' + (string)o + '"'); 43 | } 44 | 45 | if (type.IsArray) 46 | { 47 | if (type.GetArrayRank() == 1) 48 | { 49 | string typeName = TypeUtils.ToFriendlyName(type); 50 | 51 | return o => 52 | { 53 | if (o == null) 54 | return typeName; 55 | 56 | var arr = (Array)o; 57 | 58 | if (arr.Length < 10) 59 | { 60 | var elements = new string[arr.Length]; 61 | 62 | for (int i = 0; i < arr.Length; i++) 63 | elements[i] = ToString(arr.GetValue(i)); 64 | 65 | return typeName + " {" + string.Join(", ", elements) + "}"; 66 | } 67 | else 68 | { 69 | return typeName + "[" + arr.Length + "]"; 70 | } 71 | }; 72 | } 73 | } 74 | 75 | if (typeof(System.Collections.IList).IsAssignableFrom(type)) 76 | { 77 | string typeName = TypeUtils.ToFriendlyName(type); 78 | return o => 79 | { 80 | if (o == null) 81 | return typeName; 82 | 83 | var list = o as System.Collections.IList; 84 | 85 | if (list != null && list.Count < 15) 86 | { 87 | return typeName + " {" + string.Join(", ", list.Cast().Select(ToString)) + "}"; 88 | } 89 | else 90 | { 91 | return typeName + "[" + list.Count + "]"; 92 | } 93 | }; 94 | } 95 | 96 | Func debuggerDisplayFormatter; 97 | 98 | if (DebuggerDisplayFormatter.TryGetDebuggerDisplayFormatter(type, out debuggerDisplayFormatter)) 99 | { 100 | return debuggerDisplayFormatter; 101 | } 102 | 103 | return o => Convert.ToString(o); 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Eclipse 3 | ################# 4 | 5 | *.pydevproject 6 | .project 7 | .metadata 8 | bin/ 9 | tmp/ 10 | *.tmp 11 | *.bak 12 | *.swp 13 | *~.nib 14 | local.properties 15 | .classpath 16 | .settings/ 17 | .loadpath 18 | 19 | # External tool builders 20 | .externalToolBuilders/ 21 | 22 | # Locally stored "Eclipse launch configurations" 23 | *.launch 24 | 25 | # CDT-specific 26 | .cproject 27 | 28 | # PDT-specific 29 | .buildpath 30 | 31 | 32 | ################# 33 | ## Visual Studio 34 | ################# 35 | 36 | ## Ignore Visual Studio temporary files, build results, and 37 | ## files generated by popular Visual Studio add-ons. 38 | 39 | # User-specific files 40 | *.suo 41 | *.user 42 | *.sln.docstates 43 | 44 | # Build results 45 | 46 | [Dd]ebug/ 47 | [Rr]elease/ 48 | x64/ 49 | build/ 50 | [Bb]in/ 51 | [Oo]bj/ 52 | 53 | # MSTest test Results 54 | [Tt]est[Rr]esult*/ 55 | [Bb]uild[Ll]og.* 56 | 57 | *_i.c 58 | *_p.c 59 | *.ilk 60 | *.meta 61 | *.obj 62 | *.pch 63 | *.pdb 64 | *.pgc 65 | *.pgd 66 | *.rsp 67 | *.sbr 68 | *.tlb 69 | *.tli 70 | *.tlh 71 | *.tmp 72 | *.tmp_proj 73 | *.log 74 | *.vspscc 75 | *.vssscc 76 | .builds 77 | *.pidb 78 | *.log 79 | *.scc 80 | 81 | # Visual C++ cache files 82 | ipch/ 83 | *.aps 84 | *.ncb 85 | *.opensdf 86 | *.sdf 87 | *.cachefile 88 | 89 | # Visual Studio profiler 90 | *.psess 91 | *.vsp 92 | *.vspx 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | 101 | # TeamCity is a build add-in 102 | _TeamCity* 103 | 104 | # DotCover is a Code Coverage Tool 105 | *.dotCover 106 | 107 | # NCrunch 108 | *.ncrunch* 109 | .*crunch*.local.xml 110 | 111 | # Installshield output folder 112 | [Ee]xpress/ 113 | 114 | # DocProject is a documentation generator add-in 115 | DocProject/buildhelp/ 116 | DocProject/Help/*.HxT 117 | DocProject/Help/*.HxC 118 | DocProject/Help/*.hhc 119 | DocProject/Help/*.hhk 120 | DocProject/Help/*.hhp 121 | DocProject/Help/Html2 122 | DocProject/Help/html 123 | 124 | # Click-Once directory 125 | publish/ 126 | 127 | # Publish Web Output 128 | *.Publish.xml 129 | *.pubxml 130 | 131 | # NuGet Packages Directory 132 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 133 | packages/ 134 | 135 | # Windows Azure Build Output 136 | csx 137 | *.build.csdef 138 | 139 | # Windows Store app package directory 140 | AppPackages/ 141 | 142 | # Others 143 | sql/ 144 | *.Cache 145 | ClientBin/ 146 | [Ss]tyle[Cc]op.* 147 | ~$* 148 | *~ 149 | *.dbmdl 150 | *.[Pp]ublish.xml 151 | *.pfx 152 | *.publishsettings 153 | 154 | # RIA/Silverlight projects 155 | Generated_Code/ 156 | 157 | # Backup & report files from converting an old project file to a newer 158 | # Visual Studio version. Backup files are not needed, because we have git ;-) 159 | _UpgradeReport_Files/ 160 | Backup*/ 161 | UpgradeLog*.XML 162 | UpgradeLog*.htm 163 | 164 | # SQL Server files 165 | App_Data/*.mdf 166 | App_Data/*.ldf 167 | 168 | ############# 169 | ## Windows detritus 170 | ############# 171 | 172 | # Windows image file caches 173 | Thumbs.db 174 | ehthumbs.db 175 | 176 | # Folder config file 177 | Desktop.ini 178 | 179 | # Recycle Bin used on file shares 180 | $RECYCLE.BIN/ 181 | 182 | # Mac crap 183 | .DS_Store 184 | 185 | 186 | ############# 187 | ## Python 188 | ############# 189 | 190 | *.py[co] 191 | 192 | # Packages 193 | *.egg 194 | *.egg-info 195 | dist/ 196 | build/ 197 | eggs/ 198 | parts/ 199 | var/ 200 | sdist/ 201 | develop-eggs/ 202 | .installed.cfg 203 | 204 | # Installer logs 205 | pip-log.txt 206 | 207 | # Unit test / coverage reports 208 | .coverage 209 | .tox 210 | 211 | #Translations 212 | *.mo 213 | 214 | #Mr Developer 215 | .mr.developer.cfg 216 | 217 | # Nuget packages, don't need these cluttering my view 218 | *.nupkg 219 | -------------------------------------------------------------------------------- /RxSpy/Observables/OperatorObservable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reactive.Disposables; 3 | using System.Reactive.Subjects; 4 | using System.Threading; 5 | using RxSpy.Events; 6 | using RxSpy.Utils; 7 | 8 | namespace RxSpy.Observables 9 | { 10 | internal class OperatorObservable : IObservable, IOperatorObservable 11 | { 12 | readonly OperatorInfo _operatorInfo; 13 | readonly RxSpySession _session; 14 | readonly IObservable _inner; 15 | 16 | protected RxSpySession Session { get { return _session; } } 17 | public OperatorInfo OperatorInfo { get { return _operatorInfo; } } 18 | 19 | // We can't subscribe an extra observer to out inner observable just for the event stuff 20 | // since that could potentially modify the behavior of the inner (like if it was designed 21 | // for one subscription only. So instead of doing that we wrap all incoming observers in 22 | // a private class which forwards all signals while keeping track of whether or not it's 23 | // the wrapper currently responsible for reporting events. 24 | Observer _currentlyReportingObserver; 25 | 26 | sealed class Observer: IObserver, IDisposable 27 | { 28 | readonly OperatorObservable _parent; 29 | readonly IObserver _inner; 30 | 31 | bool _isReporting = false; 32 | 33 | public Observer(OperatorObservable parent, IObserver inner) 34 | { 35 | _parent = parent; 36 | _inner = inner; 37 | } 38 | 39 | bool isReporting() 40 | { 41 | if (!_isReporting && Interlocked.CompareExchange(ref _parent._currentlyReportingObserver, this, null) == null) 42 | _isReporting = true; 43 | 44 | return _isReporting; 45 | } 46 | 47 | public void OnCompleted() 48 | { 49 | if (isReporting()) 50 | _parent._session.OnCompleted(Event.OnCompleted(_parent._operatorInfo)); 51 | 52 | _inner.OnCompleted(); 53 | } 54 | 55 | public void OnError(Exception error) 56 | { 57 | if (isReporting()) 58 | _parent._session.OnError(Event.OnError(_parent._operatorInfo, error)); 59 | 60 | _inner.OnError(error); 61 | } 62 | 63 | public void OnNext(T value) 64 | { 65 | if (isReporting()) 66 | _parent._session.OnNext(Event.OnNext(_parent._operatorInfo, typeof(T), value)); 67 | 68 | _inner.OnNext(value); 69 | } 70 | 71 | public void Dispose() 72 | { 73 | if (_isReporting) 74 | { 75 | Interlocked.CompareExchange(ref _parent._currentlyReportingObserver, null, this); 76 | _isReporting = false; 77 | } 78 | } 79 | } 80 | 81 | public OperatorObservable(RxSpySession session, IObservable inner, OperatorInfo operatorInfo) 82 | { 83 | if (inner == null) 84 | throw new ArgumentNullException("source"); 85 | 86 | _inner = inner; 87 | _session = session; 88 | _operatorInfo = operatorInfo; 89 | 90 | _session.OnCreated(Event.OperatorCreated(operatorInfo)); 91 | } 92 | 93 | public IDisposable Subscribe(IObserver observer) 94 | { 95 | var obs = new Observer(this, observer); 96 | var disp = _inner.Subscribe(obs); 97 | 98 | return new CompositeDisposable(disp, obs); 99 | } 100 | 101 | public override string ToString() 102 | { 103 | return _operatorInfo.ToString(); 104 | } 105 | 106 | internal void Tag(string tag) 107 | { 108 | _session.OnTag(Event.Tag(_operatorInfo, tag)); 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /RxSpy.LiveView/ViewModels/RxSpyObservableGridItemViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Reactive.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using ReactiveUI; 9 | using RxSpy.Events; 10 | using RxSpy.Models; 11 | 12 | namespace RxSpy.ViewModels 13 | { 14 | public class RxSpyObservableGridItemViewModel : ReactiveObject 15 | { 16 | public RxSpyObservableModel Model { get; private set; } 17 | 18 | readonly ObservableAsPropertyHelper _id; 19 | public long Id 20 | { 21 | get { return _id.Value; } 22 | } 23 | 24 | readonly ObservableAsPropertyHelper _name; 25 | public string Name 26 | { 27 | get { return _name.Value; } 28 | } 29 | 30 | readonly ObservableAsPropertyHelper _tag; 31 | public string Tag 32 | { 33 | get { return _tag.Value; } 34 | } 35 | 36 | readonly ObservableAsPropertyHelper _valuesProduced; 37 | public long ValuesProduced 38 | { 39 | get { return _valuesProduced.Value; } 40 | } 41 | 42 | readonly ObservableAsPropertyHelper _parents; 43 | public int Parents 44 | { 45 | get { return _parents.Value; } 46 | } 47 | 48 | readonly ObservableAsPropertyHelper _children; 49 | public int Children 50 | { 51 | get { return _children.Value; } 52 | } 53 | 54 | readonly ObservableAsPropertyHelper _ancestors; 55 | public int Ancestors 56 | { 57 | get { return _ancestors.Value; } 58 | } 59 | 60 | readonly ObservableAsPropertyHelper _descendants; 61 | public int Descendants 62 | { 63 | get { return _descendants.Value; } 64 | } 65 | 66 | readonly ObservableAsPropertyHelper _totalSubscriptions; 67 | public int TotalSubscriptions 68 | { 69 | get { return _totalSubscriptions.Value; } 70 | } 71 | 72 | readonly ObservableAsPropertyHelper _callSite; 73 | public string CallSite 74 | { 75 | get { return _callSite.Value; } 76 | } 77 | 78 | readonly ObservableAsPropertyHelper _status; 79 | public string Status 80 | { 81 | get { return _status.Value; } 82 | } 83 | 84 | public RxSpyObservableGridItemViewModel(RxSpyObservableModel model) 85 | { 86 | Model = model; 87 | 88 | this.WhenAnyValue(x => x.Model.Id) 89 | .ToProperty(this, x => x.Id, out _id); 90 | 91 | this.WhenAnyValue(x => x.Model.Name) 92 | .ToProperty(this, x => x.Name, out _name); 93 | 94 | this.WhenAnyValue(x => x.Model.Tag) 95 | .ToProperty(this, x => x.Tag, out _tag); 96 | 97 | this.WhenAnyValue(x => x.Model.ValuesProduced) 98 | .ToProperty(this, x => x.ValuesProduced, out _valuesProduced); 99 | 100 | this.WhenAnyValue(x => x.Model.Children.Count) 101 | .ToProperty(this, x => x.TotalSubscriptions, out _totalSubscriptions); 102 | 103 | this.WhenAnyValue(x => x.Model.Parents.Count) 104 | .ToProperty(this, x => x.Parents, out _parents); 105 | 106 | this.WhenAnyValue(x => x.Model.Children.Count) 107 | .ToProperty(this, x => x.Children, out _children); 108 | 109 | this.WhenAnyValue(x => x.Model.Ancestors) 110 | .ToProperty(this, x => x.Ancestors, out _ancestors); 111 | 112 | this.WhenAnyValue(x => x.Model.Descendants) 113 | .ToProperty(this, x => x.Descendants, out _descendants); 114 | 115 | this.WhenAnyValue(x => x.Model.CallSite) 116 | .Select(x => Convert.ToString(x)) 117 | .ToProperty(this, x => x.CallSite, out _callSite); 118 | 119 | this.WhenAnyValue(x => x.Model.Status) 120 | .ToProperty(this, x => x.Status, out _status); 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /RxSpy/RxSpyStreamWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | using RxSpy.Communication.Serialization; 11 | using RxSpy.Events; 12 | 13 | namespace RxSpy 14 | { 15 | public sealed class RxSpyStreamWriter : IRxSpyEventHandler 16 | { 17 | string _path; 18 | Stream _stream; 19 | RxSpyJsonSerializerStrategy _serializerStrategy; 20 | ConcurrentQueue _queue = new ConcurrentQueue(); 21 | CancellationTokenSource _cancellationTokenSource; 22 | 23 | public RxSpyStreamWriter(string path) 24 | { 25 | _path = path; 26 | _serializerStrategy = new RxSpyJsonSerializerStrategy(); 27 | _cancellationTokenSource = new CancellationTokenSource(); 28 | 29 | Task.Factory.StartNew(() => RunQueue(_cancellationTokenSource.Token), TaskCreationOptions.LongRunning); 30 | } 31 | 32 | public RxSpyStreamWriter(Stream stream) 33 | { 34 | _stream = stream; 35 | _serializerStrategy = new RxSpyJsonSerializerStrategy(); 36 | _cancellationTokenSource = new CancellationTokenSource(); 37 | 38 | Task.Factory.StartNew(() => RunQueue(_cancellationTokenSource.Token), TaskCreationOptions.LongRunning); 39 | } 40 | 41 | async Task RunQueue(CancellationToken ct) 42 | { 43 | using (var sw = GetStreamWriter()) 44 | { 45 | IEvent ev; 46 | 47 | while (!ct.IsCancellationRequested) 48 | { 49 | while (!ct.IsCancellationRequested && _queue.TryDequeue(out ev)) 50 | { 51 | sw.WriteLine(SimpleJson.SerializeObject(ev, _serializerStrategy)); 52 | } 53 | 54 | await Task.Delay(200, ct); 55 | } 56 | } 57 | } 58 | 59 | TextWriter GetStreamWriter() 60 | { 61 | if (_path != null) 62 | return new StreamWriter(_path, append: false, encoding: Encoding.UTF8); 63 | 64 | return new StreamWriter(_stream, Encoding.UTF8, 1024, leaveOpen: true); 65 | } 66 | 67 | void EnqueueEvent(IEvent ev) 68 | { 69 | _queue.Enqueue(ev); 70 | } 71 | 72 | public void Dispose() 73 | { 74 | GC.SuppressFinalize(this); 75 | IEvent ev; 76 | 77 | // Wait for up to half a second for the queue to clear 78 | for (int i = 0; i < 50; i++) 79 | { 80 | if (!_queue.TryPeek(out ev)) 81 | break; 82 | 83 | Thread.Sleep(10); 84 | } 85 | 86 | _cancellationTokenSource.Cancel(); 87 | } 88 | 89 | public void OnCreated(IOperatorCreatedEvent onCreatedEvent) 90 | { 91 | EnqueueEvent(onCreatedEvent); 92 | } 93 | 94 | public void OnCompleted(IOnCompletedEvent onCompletedEvent) 95 | { 96 | EnqueueEvent(onCompletedEvent); 97 | } 98 | 99 | public void OnError(IOnErrorEvent onErrorEvent) 100 | { 101 | EnqueueEvent(onErrorEvent); 102 | } 103 | 104 | public void OnNext(IOnNextEvent onNextEvent) 105 | { 106 | EnqueueEvent(onNextEvent); 107 | } 108 | 109 | public void OnSubscribe(ISubscribeEvent subscribeEvent) 110 | { 111 | EnqueueEvent(subscribeEvent); 112 | } 113 | 114 | public void OnUnsubscribe(IUnsubscribeEvent unsubscribeEvent) 115 | { 116 | EnqueueEvent(unsubscribeEvent); 117 | } 118 | 119 | public void OnConnected(IConnectedEvent connectedEvent) 120 | { 121 | EnqueueEvent(connectedEvent); 122 | } 123 | 124 | public void OnDisconnected(IDisconnectedEvent disconnectedEvent) 125 | { 126 | EnqueueEvent(disconnectedEvent); 127 | } 128 | 129 | public void OnTag(ITagOperatorEvent tagEvent) 130 | { 131 | EnqueueEvent(tagEvent); 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /RxSpy.TestConsole/RxSpy.TestConsole.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {C50D2810-7AE0-435E-B0DF-245820F26D08} 8 | Exe 9 | Properties 10 | RxSpy.TestConsole 11 | RxSpy.TestConsole 12 | v4.5 13 | 512 14 | ..\ 15 | true 16 | 17 | 18 | AnyCPU 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | 27 | 28 | AnyCPU 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | 36 | 37 | 38 | 39 | 40 | False 41 | ..\packages\Rx-Core.2.2.5\lib\net45\System.Reactive.Core.dll 42 | 43 | 44 | False 45 | ..\packages\Rx-Interfaces.2.2.5\lib\net45\System.Reactive.Interfaces.dll 46 | 47 | 48 | False 49 | ..\packages\Rx-Linq.2.2.5\lib\net45\System.Reactive.Linq.dll 50 | 51 | 52 | False 53 | ..\packages\Rx-PlatformServices.2.2.5\lib\net45\System.Reactive.PlatformServices.dll 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | {ad01307e-2429-482a-9f7a-a8aa147bcfda} 72 | RxSpy 73 | 74 | 75 | 76 | 77 | 84 | -------------------------------------------------------------------------------- /RxSpy.Shared/RxSpy.Shared.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {1E10026E-8A90-46F1-8FB2-06BEDC21B4CC} 8 | Library 9 | Properties 10 | RxSpy 11 | RxSpy.Shared 12 | v4.5 13 | 512 14 | ..\ 15 | true 16 | 17 | 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | Model\Events\EventType.cs 46 | 47 | 48 | Model\Events\Interfaces.cs 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 75 | 76 | 77 | 78 | 85 | -------------------------------------------------------------------------------- /RxSpy.StressTest/RxSpy.StressTest.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {AEDF8197-4672-45B8-B152-131C11439D90} 8 | Exe 9 | Properties 10 | RxSpy.StressTest 11 | RxSpy.StressTest 12 | v4.5 13 | 512 14 | ..\ 15 | true 16 | 17 | 18 | AnyCPU 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | 27 | 28 | AnyCPU 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | 36 | 37 | 38 | 39 | 40 | False 41 | ..\packages\Rx-Core.2.2.5\lib\net45\System.Reactive.Core.dll 42 | 43 | 44 | False 45 | ..\packages\Rx-Interfaces.2.2.5\lib\net45\System.Reactive.Interfaces.dll 46 | 47 | 48 | False 49 | ..\packages\Rx-Linq.2.2.5\lib\net45\System.Reactive.Linq.dll 50 | 51 | 52 | False 53 | ..\packages\Rx-PlatformServices.2.2.5\lib\net45\System.Reactive.PlatformServices.dll 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | {ad01307e-2429-482a-9f7a-a8aa147bcfda} 75 | RxSpy 76 | 77 | 78 | 79 | 80 | 87 | -------------------------------------------------------------------------------- /RxSpy.StressTest/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Reactive.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using System.Reactive.Threading.Tasks; 10 | using System.Reactive.Concurrency; 11 | using System.Reactive; 12 | using System.Reactive.Subjects; 13 | 14 | namespace RxSpy.StressTest 15 | { 16 | class Program 17 | { 18 | static void Main(string[] args) 19 | { 20 | // This app is meant as a tool for benchmarking performance when developing features. 21 | // Run it a few times before starting work on a new feature and then continually as you 22 | // make changes to monitor your progress. 23 | 24 | var sw = Stopwatch.StartNew(); 25 | 26 | Run(); 27 | 28 | var warmupRun = sw.Elapsed; 29 | Console.WriteLine("Warm up round without RxSpy took {0:N4}", warmupRun.TotalSeconds); 30 | 31 | sw.Restart(); 32 | Run(); 33 | 34 | var pureRun = sw.Elapsed; 35 | Console.WriteLine("Stress test without RxSpy took {0:N4}", pureRun.TotalSeconds); 36 | 37 | // Launching with the streamwriter will benchmark serialization performance but not 38 | // create a huge file on disk. 39 | var fakeStream = new StressTestStream(); 40 | var eventHandler = new StressTestEventHandler(new RxSpyStreamWriter(fakeStream)); 41 | 42 | sw.Restart(); 43 | 44 | using (RxSpySession.Launch(eventHandler)) 45 | { 46 | Run(); 47 | } 48 | 49 | var spyRun = sw.Elapsed; 50 | 51 | Log("Stress test run took {0:N4}, captured {1:N0} events, {2:N0} observables", spyRun.TotalSeconds, eventHandler.EventCount, eventHandler.ObservableCount); 52 | Log("Produced {0:N0} bytes of event output", fakeStream.Length); 53 | Log("RxSpy was {0:N2}x slower", spyRun.TotalSeconds / pureRun.TotalSeconds); 54 | 55 | if (!Debugger.IsAttached) 56 | Console.ReadLine(); 57 | } 58 | 59 | static void Log(string format, params object[] args) 60 | { 61 | var text = String.Format(format, args); 62 | 63 | Console.WriteLine(text); 64 | Debug.WriteLine(text); 65 | 66 | File.AppendAllText("out.log", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " " + text + "\r\n"); 67 | } 68 | 69 | private static void Run() 70 | { 71 | var waitForCompletion = new List(); 72 | 73 | for (int i = 0; i < 10; i++) 74 | { 75 | waitForCompletion.AddRange(CreateSimpleObservables()); 76 | waitForCompletion.AddRange(CreateConnectableObservables()); 77 | waitForCompletion.AddRange(CreateAsyncObservables()); 78 | waitForCompletion.AddRange(CreatLongObservableChain()); 79 | } 80 | 81 | Task.WaitAll(waitForCompletion.ToArray()); 82 | } 83 | 84 | private static IEnumerable CreateAsyncObservables() 85 | { 86 | for (int i = 0; i < 5; i++) 87 | { 88 | yield return Observable.Range(0, 1000) 89 | .ObserveOn(TaskPoolScheduler.Default) 90 | .Where(x => x % 2 == 0) 91 | .ToTask(); 92 | } 93 | } 94 | 95 | static IEnumerable CreateSimpleObservables() 96 | { 97 | yield return Observable.Range(0, 1000).Where(x => x % 2 == 0).ToTask(); 98 | 99 | yield return Observable.Range(0, 1000).Where(x => x % 2 == 0) 100 | .Select(x => new CustomObjectWithDebuggerDisplay { Name = x.ToString() }) 101 | .Select(x => x) 102 | .ToTask(); 103 | } 104 | 105 | static IEnumerable CreateConnectableObservables() 106 | { 107 | var subject = new AsyncSubject(); 108 | 109 | yield return Observable.Defer(() => Observable.Start(() => Unit.Default)) 110 | .Multicast(subject) 111 | .RefCount() 112 | .ToTask(); 113 | } 114 | 115 | static IEnumerable CreatLongObservableChain() 116 | { 117 | var obs = Observable.Range(0, 1000); 118 | 119 | for (int i = 0; i < 50; i++) 120 | { 121 | obs = obs.Select(x => x).Where(x => true); 122 | } 123 | 124 | yield return obs.ToTask(); 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /RxSpy/Utils/DebuggerDisplayFormatter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Linq; 6 | using System.Text.RegularExpressions; 7 | using System.Threading; 8 | 9 | namespace RxSpy.Utils 10 | { 11 | public class DebuggerDisplayFormatter 12 | { 13 | readonly static ConcurrentDictionary>> _cachedFormatters = 14 | new ConcurrentDictionary>>(); 15 | 16 | static readonly Regex DebuggerDisplayPropertyRe = new Regex(@"\{\s*(\w[\w\d]+)(,nq)?\s*\}"); 17 | 18 | public static bool TryFormat(Type type, object target, out string value) 19 | { 20 | Func formatter; 21 | 22 | if (!TryGetDebuggerDisplayFormatter(type, out formatter)) 23 | { 24 | value = null; 25 | return false; 26 | } 27 | 28 | value = formatter(target); 29 | return true; 30 | } 31 | 32 | public static bool TryGetDebuggerDisplayFormatter(Type type, out Func formatter) 33 | { 34 | var cacheEntry = _cachedFormatters.GetOrAdd(type, CreateFormatter); 35 | 36 | if (cacheEntry == null) 37 | { 38 | formatter = null; 39 | return false; 40 | } 41 | 42 | formatter = cacheEntry.Value; 43 | return true; 44 | } 45 | 46 | static Lazy> CreateFormatter(Type type) 47 | { 48 | var debuggerDisplayAttributes = type.GetCustomAttributes(typeof(DebuggerDisplayAttribute), false); 49 | 50 | if (debuggerDisplayAttributes == null || debuggerDisplayAttributes.Length == 0) 51 | { 52 | return null; 53 | } 54 | 55 | var attribute = (DebuggerDisplayAttribute)debuggerDisplayAttributes[0]; 56 | 57 | return new Lazy>( 58 | () => BuildFormatterDelegate(type, attribute.Value), 59 | LazyThreadSafetyMode.ExecutionAndPublication 60 | ); 61 | } 62 | 63 | static Func BuildFormatterDelegate(Type type, string format) 64 | { 65 | // We only support simple property getters for now, no method invocation 66 | 67 | try 68 | { 69 | int lastCharacterPosition = 0; 70 | 71 | var subs = new List>>(); 72 | var parts = new List(); 73 | 74 | var matches = DebuggerDisplayPropertyRe.Matches(format); 75 | 76 | foreach(Match m in matches) 77 | { 78 | if (lastCharacterPosition != m.Index) 79 | { 80 | parts.Add(format.Substring(lastCharacterPosition, m.Index - lastCharacterPosition)); 81 | lastCharacterPosition = m.Index + m.Length; 82 | } 83 | 84 | var propertyName = m.Groups[1].Value; 85 | 86 | var sub = CreatePropertyValueDelegate(type, propertyName, !m.Groups[2].Success); 87 | subs.Add(Tuple.Create(parts.Count, sub)); 88 | parts.Add(null); 89 | } 90 | 91 | if (lastCharacterPosition != format.Length) 92 | parts.Add(format.Substring(lastCharacterPosition)); 93 | 94 | return o => 95 | { 96 | var combine = parts.ToArray(); 97 | 98 | foreach (var sub in subs) 99 | combine[sub.Item1] = sub.Item2(o); 100 | 101 | return string.Concat(combine); 102 | }; 103 | } 104 | catch (Exception exc) 105 | { 106 | return new Func(_ => "Could not create debugger display formatter " + exc.Message); 107 | } 108 | } 109 | 110 | static Func CreatePropertyValueDelegate(Type type, string propertyName, bool quote) 111 | { 112 | var propertyInfo = type.GetProperty(propertyName); 113 | 114 | if (propertyInfo != null) 115 | { 116 | return o => Convert.ToString(propertyInfo.GetValue(o) ?? "null"); 117 | } 118 | 119 | var fieldInfo = type.GetField(propertyName); 120 | 121 | if (fieldInfo != null) 122 | { 123 | return o => Convert.ToString(fieldInfo.GetValue(o) ?? "null"); 124 | } 125 | 126 | return o => "No such property or field " + propertyName; 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /RxSpy/Utils/CallSiteCache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Diagnostics; 4 | using System.Linq.Expressions; 5 | using System.Threading; 6 | using RxSpy.Events; 7 | using bcl = System.Reflection; 8 | 9 | namespace RxSpy.Utils 10 | { 11 | public static class CallSiteCache 12 | { 13 | readonly static GetStackFrameInfo _stackFrameFast; 14 | 15 | readonly static ConcurrentDictionary, CallSite> _cache = 16 | new ConcurrentDictionary, CallSite>(); 17 | 18 | delegate Tuple GetStackFrameInfo(int skipFrames); 19 | 20 | static CallSiteCache() 21 | { 22 | try 23 | { 24 | _stackFrameFast = CreateInternalStackFrameInfoMethod(); 25 | } 26 | catch (Exception exc) 27 | { 28 | Debug.WriteLine("Could not create fast stack info method, things are going to get slooooow. Exception: " + exc); 29 | 30 | // If we end up here it's bad, the .NET framework authors has changed the private implementation that 31 | // we rely on (which is entirely within their rights to do). 32 | if (Debugger.IsAttached) 33 | Debugger.Break(); 34 | } 35 | } 36 | 37 | static GetStackFrameInfo CreateInternalStackFrameInfoMethod() 38 | { 39 | var mscorlib = typeof(object).Assembly; 40 | 41 | var sfhType = mscorlib.GetType("System.Diagnostics.StackFrameHelper"); 42 | var sfhCtor = sfhType.GetConstructor(new Type[] { typeof(bool), typeof(Thread) }); 43 | var sfhRgMethodHandle = sfhType.GetField("rgMethodHandle", bcl.BindingFlags.NonPublic | bcl.BindingFlags.Instance); 44 | var sfhRgiILOffsetField = sfhType.GetField("rgiILOffset", bcl.BindingFlags.NonPublic | bcl.BindingFlags.Instance); 45 | 46 | var sfhGetMethodBaseMethod = sfhType.GetMethod("GetMethodBase", 47 | bcl.BindingFlags.Instance | bcl.BindingFlags.Public); 48 | 49 | var getStackFramesInternalMethod = typeof(StackTrace).GetMethod("GetStackFramesInternal", 50 | bcl.BindingFlags.Static | bcl.BindingFlags.NonPublic); 51 | 52 | var calculateFramesToSkipMethod = typeof(StackTrace).GetMethod("CalculateFramesToSkip", 53 | bcl.BindingFlags.Static | bcl.BindingFlags.NonPublic); 54 | 55 | var currentThreadProperty = typeof(Thread).GetProperty("CurrentThread"); 56 | var tupleCtor = typeof(Tuple).GetConstructor(new Type[] { typeof(IntPtr), typeof(int) }); 57 | 58 | var skipParam = Expression.Parameter(typeof(int), "iSkip"); 59 | var sfhVariable = Expression.Variable(sfhType, "sfh"); 60 | var actualSkip = Expression.Variable(typeof(int), "iNumFrames"); 61 | 62 | var zero = Expression.Constant(0, typeof(int)); 63 | 64 | var methodLambda = Expression.Lambda( 65 | Expression.Block( 66 | new[] { sfhVariable }, 67 | 68 | Expression.Assign( 69 | sfhVariable, 70 | Expression.New(sfhCtor, 71 | Expression.Constant(false, typeof(bool)), // fNeedFileLineColInfo 72 | Expression.Constant(null, typeof(Thread)) // target (thread) 73 | ) 74 | ), 75 | 76 | Expression.Call(getStackFramesInternalMethod, sfhVariable, zero, Expression.Constant(null, typeof(Exception))), 77 | 78 | Expression.New(tupleCtor, 79 | Expression.ArrayIndex(Expression.Field(sfhVariable, sfhRgMethodHandle), skipParam), 80 | Expression.ArrayIndex(Expression.Field(sfhVariable, sfhRgiILOffsetField), skipParam) 81 | ) 82 | ), 83 | skipParam 84 | ).Compile(); 85 | 86 | return methodLambda; 87 | } 88 | 89 | public static CallSite Get(int skipFrames) 90 | { 91 | // Account for ourselves 92 | skipFrames++; 93 | 94 | if (_stackFrameFast == null) 95 | { 96 | // This is terribad, this session is going to be soooo sloooooooooooow. 97 | // This will eventually happen when the .NET framework authors 98 | // excercise their right to change the private implementation we're 99 | // depending on. 100 | // Fall back to expensive full frame 101 | 102 | return new CallSite(new StackFrame(skipFrames, true)); 103 | } 104 | 105 | // Don't exactly know why we need to skip 2 and not 1 here. 106 | // I suspect expression tree trickery. 107 | var key = _stackFrameFast(skipFrames + 2); 108 | 109 | CallSite cached; 110 | 111 | if (_cache.TryGetValue(key, out cached)) 112 | return cached; 113 | 114 | var frame = new StackFrame(skipFrames, true); 115 | 116 | var callSite = new CallSite(frame); 117 | 118 | return _cache.GetOrAdd(key, callSite); 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /RxSpy.LiveView/Models/RxSpySessionModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Diagnostics; 4 | using ReactiveUI; 5 | using RxSpy.Events; 6 | using System.Reactive.Linq; 7 | 8 | namespace RxSpy.Models 9 | { 10 | public class RxSpySessionModel : ReactiveObject 11 | { 12 | readonly ConcurrentDictionary observableRepository 13 | = new ConcurrentDictionary(); 14 | 15 | readonly ConcurrentDictionary subscriptionRepository 16 | = new ConcurrentDictionary(); 17 | 18 | public ReactiveList TrackedObservables { get; set; } 19 | 20 | long _signalCount; 21 | public long SignalCount 22 | { 23 | get { return _signalCount; } 24 | set { this.RaiseAndSetIfChanged(ref _signalCount, value); } 25 | } 26 | 27 | long _errorCount; 28 | public long ErrorCount 29 | { 30 | get { return _errorCount; } 31 | set { this.RaiseAndSetIfChanged(ref _errorCount, value); } 32 | } 33 | 34 | public RxSpySessionModel() 35 | { 36 | TrackedObservables = new ReactiveList(); 37 | } 38 | 39 | internal void OnEvent(IEvent ev) 40 | { 41 | switch (ev.EventType) 42 | { 43 | case EventType.OperatorCreated: 44 | OnOperatorCreated((IOperatorCreatedEvent)ev); 45 | break; 46 | 47 | case EventType.Subscribe: 48 | OnSubscribe((ISubscribeEvent)ev); 49 | break; 50 | 51 | case EventType.Unsubscribe: 52 | OnUnsubscribe((IUnsubscribeEvent)ev); 53 | break; 54 | 55 | case EventType.OnCompleted: 56 | OnCompleted((IOnCompletedEvent)ev); 57 | break; 58 | 59 | case EventType.OnNext: 60 | OnNext((IOnNextEvent)ev); 61 | break; 62 | 63 | case EventType.OnError: 64 | OnError((IOnErrorEvent)ev); 65 | break; 66 | 67 | case EventType.TagOperator: 68 | OnTag((ITagOperatorEvent)ev); 69 | break; 70 | } 71 | } 72 | 73 | void OnOperatorCreated(IOperatorCreatedEvent operatorCreatedEvent) 74 | { 75 | var operatorModel = new RxSpyObservableModel(operatorCreatedEvent); 76 | 77 | observableRepository.TryAdd(operatorCreatedEvent.Id, operatorModel); 78 | TrackedObservables.Add(operatorModel); 79 | } 80 | 81 | void OnSubscribe(ISubscribeEvent subscribeEvent) 82 | { 83 | RxSpyObservableModel child, parent; 84 | 85 | observableRepository.TryGetValue(subscribeEvent.ChildId, out child); 86 | observableRepository.TryGetValue(subscribeEvent.ParentId, out parent); 87 | 88 | var subscriptionModel = new RxSpySubscriptionModel(subscribeEvent, child, parent) 89 | { 90 | IsActive = true 91 | }; 92 | 93 | subscriptionRepository.TryAdd(subscribeEvent.EventId, subscriptionModel); 94 | 95 | parent.Subscriptions.Add(subscriptionModel); 96 | 97 | parent.Children.Add(child); 98 | child.Parents.Add(parent); 99 | } 100 | 101 | void OnUnsubscribe(IUnsubscribeEvent unsubscribeEvent) 102 | { 103 | RxSpySubscriptionModel subscriptionModel; 104 | 105 | subscriptionRepository.TryGetValue(unsubscribeEvent.SubscriptionId, out subscriptionModel); 106 | 107 | if (subscriptionModel != null) 108 | { 109 | subscriptionModel.IsActive = false; 110 | } 111 | } 112 | 113 | private void OnError(IOnErrorEvent onErrorEvent) 114 | { 115 | ErrorCount++; 116 | 117 | RxSpyObservableModel operatorModel; 118 | observableRepository.TryGetValue(onErrorEvent.OperatorId, out operatorModel); 119 | 120 | operatorModel.OnError(onErrorEvent); 121 | } 122 | 123 | private void OnNext(IOnNextEvent onNextEvent) 124 | { 125 | SignalCount++; 126 | 127 | RxSpyObservableModel operatorModel; 128 | observableRepository.TryGetValue(onNextEvent.OperatorId, out operatorModel); 129 | 130 | operatorModel.OnNext(onNextEvent); 131 | } 132 | 133 | void OnCompleted(IOnCompletedEvent onCompletedEvent) 134 | { 135 | RxSpyObservableModel operatorModel; 136 | observableRepository.TryGetValue(onCompletedEvent.OperatorId, out operatorModel); 137 | 138 | if (operatorModel != null) 139 | { 140 | operatorModel.OnCompleted(onCompletedEvent); 141 | } 142 | } 143 | 144 | private void OnTag(ITagOperatorEvent tagOperatorEvent) 145 | { 146 | RxSpyObservableModel operatorModel; 147 | observableRepository.TryGetValue(tagOperatorEvent.OperatorId, out operatorModel); 148 | 149 | if (operatorModel != null) 150 | { 151 | operatorModel.OnTag(tagOperatorEvent); 152 | } 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /RxSpy/RxSpy.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {AD01307E-2429-482A-9F7A-A8AA147BCFDA} 8 | Library 9 | Properties 10 | RxSpy 11 | RxSpy 12 | v4.5 13 | 512 14 | ..\ 15 | true 16 | 17 | 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | 37 | 38 | False 39 | ..\packages\Rx-Core.2.2.5\lib\net45\System.Reactive.Core.dll 40 | 41 | 42 | False 43 | ..\packages\Rx-Interfaces.2.2.5\lib\net45\System.Reactive.Interfaces.dll 44 | 45 | 46 | False 47 | ..\packages\Rx-Linq.2.2.5\lib\net45\System.Reactive.Linq.dll 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 110 | -------------------------------------------------------------------------------- /RxSpy.LiveView/Models/RxSpyObservableModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Reactive.Linq; 6 | using System.Threading.Tasks; 7 | using ReactiveUI; 8 | using RxSpy.Events; 9 | 10 | namespace RxSpy.Models 11 | { 12 | public class RxSpyObservableModel: ReactiveObject 13 | { 14 | public long Id { get; set; } 15 | public string Name { get; set; } 16 | 17 | public IMethodInfo OperatorMethod { get; private set; } 18 | public ICallSite CallSite { get; private set; } 19 | 20 | string _tag; 21 | public string Tag 22 | { 23 | get { return _tag; } 24 | set { this.RaiseAndSetIfChanged(ref _tag, value); } 25 | } 26 | 27 | public TimeSpan Created { get; private set; } 28 | 29 | ReactiveList _parents; 30 | public ReactiveList Parents 31 | { 32 | get { return _parents; } 33 | private set { this.RaiseAndSetIfChanged(ref _parents, value); } 34 | } 35 | 36 | ReactiveList _children; 37 | public ReactiveList Children 38 | { 39 | get { return _children; } 40 | private set { this.RaiseAndSetIfChanged(ref _children, value); } 41 | } 42 | 43 | ReactiveList _subscriptions; 44 | public ReactiveList Subscriptions 45 | { 46 | get { return _subscriptions; } 47 | private set { this.RaiseAndSetIfChanged(ref _subscriptions, value); } 48 | } 49 | 50 | ReactiveList _observedValues; 51 | public ReactiveList ObservedValues 52 | { 53 | get { return _observedValues; } 54 | private set { this.RaiseAndSetIfChanged(ref _observedValues, value); } 55 | } 56 | RxSpyErrorModel _error; 57 | public RxSpyErrorModel Error 58 | { 59 | get { return _error; } 60 | set { this.RaiseAndSetIfChanged(ref _error, value); } 61 | } 62 | 63 | readonly ObservableAsPropertyHelper _hasError; 64 | public bool HasError 65 | { 66 | get { return _hasError.Value; } 67 | } 68 | 69 | bool _isActive; 70 | public bool IsActive 71 | { 72 | get { return _isActive; } 73 | set { this.RaiseAndSetIfChanged(ref _isActive, value); } 74 | } 75 | 76 | long _valuesProduced; 77 | public long ValuesProduced 78 | { 79 | get { return _valuesProduced; } 80 | set { this.RaiseAndSetIfChanged(ref _valuesProduced, value); } 81 | } 82 | 83 | readonly ObservableAsPropertyHelper _descendants; 84 | public int Descendants 85 | { 86 | get { return _descendants.Value; } 87 | } 88 | 89 | readonly ObservableAsPropertyHelper _ancestors; 90 | public int Ancestors 91 | { 92 | get { return _ancestors.Value; } 93 | } 94 | 95 | string _status; 96 | public string Status 97 | { 98 | get { return _status; } 99 | private set { this.RaiseAndSetIfChanged(ref _status, value); } 100 | } 101 | 102 | public RxSpyObservableModel(IOperatorCreatedEvent createdEvent) 103 | { 104 | Id = createdEvent.Id; 105 | Name = createdEvent.Name; 106 | OperatorMethod = createdEvent.OperatorMethod; 107 | CallSite = createdEvent.CallSite; 108 | IsActive = true; 109 | 110 | Created = TimeSpan.FromMilliseconds(createdEvent.EventTime); 111 | 112 | Subscriptions = new ReactiveList(); 113 | Parents = new ReactiveList(); 114 | Children = new ReactiveList(); 115 | 116 | ObservedValues = new ReactiveList(); 117 | 118 | this.WhenAnyValue(x => x.Error) 119 | .Select(x => x == null ? false : true) 120 | .ToProperty(this, x => x.HasError, out _hasError); 121 | 122 | this.WhenAnyValue(x => x.Children.Count) 123 | .Select(_ => Observable.CombineLatest(Children.Select(c => c.WhenAnyValue(x => x.Descendants)))) 124 | .Switch() 125 | .Select(x => x.Sum() + Children.Count) 126 | .ToProperty(this, x => x.Descendants, out _descendants); 127 | 128 | this.WhenAnyValue(x => x.Parents.Count) 129 | .Select(_ => Observable.CombineLatest(Parents.Select(c => c.WhenAnyValue(x => x.Ancestors)))) 130 | .Switch() 131 | .Select(x => x.Sum() + Parents.Count) 132 | .ToProperty(this, x => x.Ancestors, out _ancestors); 133 | 134 | Status = "Active"; 135 | } 136 | 137 | public void OnNext(IOnNextEvent onNextEvent) 138 | { 139 | ObservedValues.Add(new RxSpyObservedValueModel(onNextEvent)); 140 | ValuesProduced++; 141 | } 142 | 143 | public void OnCompleted(IOnCompletedEvent onCompletedEvent) 144 | { 145 | IsActive = false; 146 | Status = "Completed"; 147 | } 148 | 149 | public void OnError(IOnErrorEvent onErrorEvent) 150 | { 151 | Error = new RxSpyErrorModel(onErrorEvent); 152 | IsActive = false; 153 | Status = "Error"; 154 | } 155 | 156 | public void OnTag(ITagOperatorEvent onTagEvent) 157 | { 158 | Tag = onTagEvent.Tag; 159 | } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /RxSpy/Utils/ConnectionFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Text; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using RxSpy.Observables; 10 | 11 | namespace RxSpy.Utils 12 | { 13 | public static class ConnectionFactory 14 | { 15 | readonly static ConcurrentDictionary>> _connectionFactoryCache = 16 | new ConcurrentDictionary>>(); 17 | 18 | readonly static ConcurrentDictionary> _connectionConstructorCache = 19 | new ConcurrentDictionary>(); 20 | 21 | public static bool TryCreateConnection(Type type, object value, OperatorInfo operatorInfo, out object connectionObject) 22 | { 23 | var factory = _connectionFactoryCache.GetOrAdd( 24 | type, 25 | _ => new Lazy>( 26 | () => CreateConnectionFactory(type), 27 | LazyThreadSafetyMode.ExecutionAndPublication) 28 | ); 29 | 30 | if (factory.Value == null) 31 | { 32 | connectionObject = null; 33 | return false; 34 | } 35 | 36 | connectionObject = factory.Value(value, operatorInfo); 37 | return true; 38 | } 39 | 40 | static Func CreateConnectionFactory(Type pt) 41 | { 42 | if (IsGenericTypeDefinition(pt, typeof(IObservable<>))) 43 | { 44 | var signalType = pt.GetGenericArguments()[0]; 45 | 46 | return (value, operatorInfo) => TryCreateObservableConnection(value, signalType, operatorInfo); 47 | } 48 | else if (pt.IsArray) 49 | { 50 | var observableType = pt.GetElementType(); 51 | 52 | if (!IsGenericTypeDefinition(observableType, typeof(IObservable<>))) 53 | { 54 | return null; 55 | } 56 | 57 | var signalType = observableType.GetGenericArguments()[0]; 58 | 59 | return (value, operatorInfo) => 60 | { 61 | var argArray = (Array)value; 62 | var newArray = Array.CreateInstance(observableType, argArray.Length); 63 | 64 | for (int i = 0; i < argArray.Length; i++) 65 | { 66 | newArray.SetValue(TryCreateObservableConnection(argArray.GetValue(i), signalType, operatorInfo), i); 67 | } 68 | 69 | return newArray; 70 | }; 71 | } 72 | else if (IsGenericTypeDefinition(pt, typeof(IEnumerable<>)) && 73 | IsGenericTypeDefinition(pt.GetGenericArguments()[0], typeof(IObservable<>))) 74 | { 75 | var observableType = pt.GetGenericArguments()[0]; 76 | var signalType = observableType.GetGenericArguments()[0]; 77 | 78 | var enumerableConnectionType = typeof(DeferredOperatorConnectionEnumerable<>) 79 | .MakeGenericType(observableType); 80 | 81 | return (value, operatorInfo) => 82 | { 83 | return Activator.CreateInstance( 84 | enumerableConnectionType, 85 | new object[] { 86 | value, 87 | new Func(o => TryCreateObservableConnection(o, signalType, operatorInfo)) 88 | }); 89 | }; 90 | } 91 | 92 | return null; 93 | } 94 | 95 | static object TryCreateObservableConnection(object source, Type signalType, OperatorInfo operatorInfo) 96 | { 97 | if (!(source is IOperatorObservable)) 98 | return source; 99 | 100 | var ctor = _connectionConstructorCache.GetOrAdd( 101 | signalType, 102 | _ => new Lazy(() => GetConnectionConstructor(signalType))); 103 | 104 | return ctor.Value.Invoke(new object[] { RxSpySession.Current, source, operatorInfo }); 105 | } 106 | 107 | static ConstructorInfo GetConnectionConstructor(Type signalType) 108 | { 109 | var operatorObservable = typeof(OperatorConnection<>).MakeGenericType(signalType); 110 | 111 | return operatorObservable.GetConstructor(new[] { typeof(RxSpySession), typeof(IObservable<>).MakeGenericType(signalType), typeof(OperatorInfo) }); 112 | } 113 | 114 | static bool IsGenericTypeDefinition(Type source, Type genericTypeComparand) 115 | { 116 | return source.IsGenericType && source.GetGenericTypeDefinition() == genericTypeComparand; 117 | } 118 | 119 | class DeferredOperatorConnectionEnumerable : IEnumerable 120 | { 121 | readonly IEnumerable _source; 122 | readonly Func _selector; 123 | 124 | public DeferredOperatorConnectionEnumerable(IEnumerable source, Func selector) 125 | { 126 | _source = source; 127 | _selector = selector; 128 | } 129 | 130 | public IEnumerator GetEnumerator() 131 | { 132 | foreach (var item in _source) 133 | yield return (T)_selector(item); 134 | } 135 | 136 | System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 137 | { 138 | return GetEnumerator(); 139 | } 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /RxSpy.LiveView/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 122 | ..\Resources\rxspy.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 123 | 124 | -------------------------------------------------------------------------------- /RxSpy/Communication/RxSpyHttpServer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Net; 6 | using System.Net.Sockets; 7 | using System.Text; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | using RxSpy.Communication.Serialization; 11 | using RxSpy.Events; 12 | 13 | namespace RxSpy.Communication 14 | { 15 | // I feel the need to appologize for the messy TPL code in here. This would have been so 16 | // clean if I could only use Rx but obviously I can't since I replaced the entire 17 | // Rx implementation and it would only add noise to the receiving clients. 18 | internal sealed class RxSpyHttpServer : IRxSpyServer, IRxSpyEventHandler 19 | { 20 | readonly HttpListener _server; 21 | readonly Task _serverTask; 22 | readonly CancellationTokenSource _cancellationTokenSource; 23 | readonly IJsonSerializerStrategy _serializerStrategy; 24 | 25 | readonly AutoResetEvent _connnectionSignal = new AutoResetEvent(false); 26 | bool _hasConnection = false; 27 | 28 | public Uri Address { get; private set; } 29 | 30 | ConcurrentQueue _queue = new ConcurrentQueue(); 31 | 32 | public RxSpyHttpServer() 33 | { 34 | _server = new HttpListener(); 35 | Address = new Uri("http://localhost:" + GetRandomTcpPort() + "/rxspy/"); 36 | 37 | _serializerStrategy = new RxSpyJsonSerializerStrategy(); 38 | 39 | _server.Prefixes.Add(Address.AbsoluteUri); 40 | _server.Start(); 41 | 42 | var cts = new CancellationTokenSource(); 43 | 44 | _serverTask = Task.Factory.StartNew(() => Run(cts.Token)); 45 | _cancellationTokenSource = cts; 46 | } 47 | 48 | public void WaitForConnection(TimeSpan timeout) 49 | { 50 | if (_hasConnection) 51 | return; 52 | 53 | if (!_connnectionSignal.WaitOne(timeout) || !_hasConnection) 54 | throw new TimeoutException("No connection received"); 55 | } 56 | 57 | public void EnqueueEvent(IEvent ev) 58 | { 59 | if (_hasConnection) 60 | _queue.Enqueue(ev); 61 | } 62 | 63 | async Task Run(CancellationToken ct) 64 | { 65 | do 66 | { 67 | var ctx = await _server.GetContextAsync(); 68 | 69 | Task.Factory.StartNew(() => RunRequest(ctx, ct)); 70 | 71 | } while (!ct.IsCancellationRequested); 72 | } 73 | 74 | async Task RunRequest(HttpListenerContext ctx, CancellationToken ct) 75 | { 76 | var requestPath = ctx.Request.Url.PathAndQuery; 77 | 78 | if (requestPath == "/rxspy/stream") 79 | { 80 | await RunStream(ctx, ct); 81 | } 82 | else 83 | { 84 | ctx.Response.StatusCode = 404; 85 | ctx.Response.StatusDescription = "Yeah I dunno what you're trying to do"; 86 | ctx.Response.ContentType = "text/plain"; 87 | 88 | using (var sw = new StreamWriter(ctx.Response.OutputStream)) 89 | { 90 | await sw.WriteLineAsync("No clue."); 91 | } 92 | } 93 | 94 | ctx.Response.Close(); 95 | } 96 | 97 | async Task RunStream(HttpListenerContext ctx, CancellationToken ct) 98 | { 99 | ctx.Response.ContentType = "application/json; charset=utf-8"; 100 | ctx.Response.SendChunked = true; 101 | 102 | try 103 | { 104 | _hasConnection = true; 105 | _connnectionSignal.Set(); 106 | 107 | using (var sw = new StreamWriter(ctx.Response.OutputStream, Encoding.UTF8)) 108 | { 109 | sw.AutoFlush = true; 110 | 111 | while (!ct.IsCancellationRequested) 112 | { 113 | try 114 | { 115 | IEvent ev; 116 | 117 | while (!ct.IsCancellationRequested) 118 | { 119 | while (!ct.IsCancellationRequested && _queue.TryDequeue(out ev)) 120 | { 121 | await sw.WriteLineAsync(SimpleJson.SerializeObject(ev, _serializerStrategy)); 122 | } 123 | 124 | await Task.Delay(200, ct); 125 | } 126 | } 127 | catch (Exception e) 128 | { 129 | Debug.WriteLine("Request failed: " + e.Message); 130 | return; 131 | } 132 | } 133 | } 134 | } 135 | finally 136 | { 137 | _hasConnection = false; 138 | } 139 | } 140 | 141 | // ugh... 142 | int GetRandomTcpPort() 143 | { 144 | var s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 145 | try 146 | { 147 | s.Bind(new IPEndPoint(IPAddress.Loopback, 0)); 148 | return ((IPEndPoint)s.LocalEndPoint).Port; 149 | } 150 | finally 151 | { 152 | s.Close(); 153 | } 154 | } 155 | 156 | public void OnCreated(IOperatorCreatedEvent onCreatedEvent) 157 | { 158 | EnqueueEvent(onCreatedEvent); 159 | } 160 | 161 | public void OnCompleted(IOnCompletedEvent onCompletedEvent) 162 | { 163 | EnqueueEvent(onCompletedEvent); 164 | } 165 | 166 | public void OnError(IOnErrorEvent onErrorEvent) 167 | { 168 | EnqueueEvent(onErrorEvent); 169 | } 170 | 171 | public void OnNext(IOnNextEvent onNextEvent) 172 | { 173 | EnqueueEvent(onNextEvent); 174 | } 175 | 176 | public void OnSubscribe(ISubscribeEvent subscribeEvent) 177 | { 178 | EnqueueEvent(subscribeEvent); 179 | } 180 | 181 | public void OnUnsubscribe(IUnsubscribeEvent unsubscribeEvent) 182 | { 183 | EnqueueEvent(unsubscribeEvent); 184 | } 185 | 186 | public void OnConnected(IConnectedEvent connectedEvent) 187 | { 188 | EnqueueEvent(connectedEvent); 189 | } 190 | 191 | public void OnDisconnected(IDisconnectedEvent disconnectedEvent) 192 | { 193 | EnqueueEvent(disconnectedEvent); 194 | } 195 | 196 | public void OnTag(ITagOperatorEvent tagEvent) 197 | { 198 | EnqueueEvent(tagEvent); 199 | } 200 | 201 | public void Dispose() 202 | { 203 | _cancellationTokenSource.Cancel(); 204 | GC.SuppressFinalize(this); 205 | } 206 | } 207 | } 208 | --------------------------------------------------------------------------------