From 4a837a143df93d2fc473cf1c3efc71d6ada24e2b Mon Sep 17 00:00:00 2001 From: Nice3point Date: Wed, 8 May 2024 16:18:34 +0300 Subject: [PATCH] Memory diagnostic support --- .../Core/Diagnosers/ClockDiagnoser.cs | 46 +++++++++++ .../Core/Diagnosers/MemoryDiagnoser.cs | 57 ++++++++++++++ .../DescriptorBuilder.Api.cs | 2 +- .../DescriptorBuilder.Build.cs | 26 ++++--- .../DescriptorBuilder.Enumeration.cs | 2 +- .../DescriptorBuilder.Events.cs | 2 +- .../DescriptorBuilder.Extensions.cs | 24 ++++-- .../DescriptorBuilder.Fields.cs | 19 +++-- .../DescriptorBuilder.Methods.cs | 72 +++++++++++------ .../DescriptorBuilder.Properties.cs | 78 ++++++++++++------- .../DescriptorBuilder.Write.cs | 18 ++--- .../{Metadata => Engine}/DescriptorBuilder.cs | 8 +- source/RevitLookup/Core/Objects/Descriptor.cs | 1 + .../Core/Objects/SnoopableObject.cs | 2 +- .../Services/Contracts/ISettingsService.cs | 3 +- .../RevitLookup/Services/SettingsService.cs | 9 ++- .../Converters/BytesToStringConverter.cs | 50 ++++++++++++ .../Abstraction/SnoopViewBase.ContextMenu.cs | 13 +++- .../Abstraction/SnoopViewBase.ToolTips.cs | 6 ++ .../Pages/Abstraction/SnoopViewBase.xaml.cs | 59 ++++++++------ .../RevitLookup/Views/Pages/EventsView.xaml | 11 ++- source/RevitLookup/Views/Pages/SnoopView.xaml | 23 +++--- 22 files changed, 401 insertions(+), 130 deletions(-) create mode 100644 source/RevitLookup/Core/Diagnosers/ClockDiagnoser.cs create mode 100644 source/RevitLookup/Core/Diagnosers/MemoryDiagnoser.cs rename source/RevitLookup/Core/{Metadata => Engine}/DescriptorBuilder.Api.cs (98%) rename source/RevitLookup/Core/{Metadata => Engine}/DescriptorBuilder.Build.cs (93%) rename source/RevitLookup/Core/{Metadata => Engine}/DescriptorBuilder.Enumeration.cs (97%) rename source/RevitLookup/Core/{Metadata => Engine}/DescriptorBuilder.Events.cs (92%) rename source/RevitLookup/Core/{Metadata => Engine}/DescriptorBuilder.Extensions.cs (81%) rename source/RevitLookup/Core/{Metadata => Engine}/DescriptorBuilder.Fields.cs (79%) rename source/RevitLookup/Core/{Metadata => Engine}/DescriptorBuilder.Methods.cs (62%) rename source/RevitLookup/Core/{Metadata => Engine}/DescriptorBuilder.Properties.cs (61%) rename source/RevitLookup/Core/{Metadata => Engine}/DescriptorBuilder.Write.cs (92%) rename source/RevitLookup/Core/{Metadata => Engine}/DescriptorBuilder.cs (88%) create mode 100644 source/RevitLookup/Views/Converters/BytesToStringConverter.cs diff --git a/source/RevitLookup/Core/Diagnosers/ClockDiagnoser.cs b/source/RevitLookup/Core/Diagnosers/ClockDiagnoser.cs new file mode 100644 index 000000000..b5f0012c7 --- /dev/null +++ b/source/RevitLookup/Core/Diagnosers/ClockDiagnoser.cs @@ -0,0 +1,46 @@ +// Copyright 2003-2024 by Autodesk, Inc. +// +// Permission to use, copy, modify, and distribute this software in +// object code form for any purpose and without fee is hereby granted, +// provided that the above copyright notice appears in all copies and +// that both that copyright notice and the limited warranty and +// restricted rights notice below appear in all supporting +// documentation. +// +// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. +// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF +// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. +// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE +// UNINTERRUPTED OR ERROR FREE. +// +// Use, duplication, or disclosure by the U.S. Government is subject to +// restrictions set forth in FAR 52.227-19 (Commercial Computer +// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) +// (Rights in Technical Data and Computer Software), as applicable. + +using System.Diagnostics; + +namespace RevitLookup.Core.Diagnosers; + +public class ClockDiagnoser +{ + private readonly Stopwatch _clock = new(); + + public void Start() + { + _clock.Start(); + } + + public void Stop() + { + _clock.Stop(); + } + + public TimeSpan GetElapsed() + { + var elapsed = _clock.Elapsed; + _clock.Reset(); + + return elapsed; + } +} \ No newline at end of file diff --git a/source/RevitLookup/Core/Diagnosers/MemoryDiagnoser.cs b/source/RevitLookup/Core/Diagnosers/MemoryDiagnoser.cs new file mode 100644 index 000000000..3170b305c --- /dev/null +++ b/source/RevitLookup/Core/Diagnosers/MemoryDiagnoser.cs @@ -0,0 +1,57 @@ +// Copyright 2003-2024 by Autodesk, Inc. +// +// Permission to use, copy, modify, and distribute this software in +// object code form for any purpose and without fee is hereby granted, +// provided that the above copyright notice appears in all copies and +// that both that copyright notice and the limited warranty and +// restricted rights notice below appear in all supporting +// documentation. +// +// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. +// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF +// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. +// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE +// UNINTERRUPTED OR ERROR FREE. +// +// Use, duplication, or disclosure by the U.S. Government is subject to +// restrictions set forth in FAR 52.227-19 (Commercial Computer +// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) +// (Rights in Technical Data and Computer Software), as applicable. + +namespace RevitLookup.Core.Diagnosers; + +public class MemoryDiagnoser +{ + private long _initialAllocatedBytes; + private long _finalAllocatedBytes; + + public void Start() + { + _initialAllocatedBytes = GetTotalAllocatedBytes(); + } + + public void Stop() + { + _finalAllocatedBytes = GetTotalAllocatedBytes(); + } + + public long GetAllocatedBytes() + { + var allocatedBytes = _finalAllocatedBytes - _initialAllocatedBytes; + + _finalAllocatedBytes = 0; + _initialAllocatedBytes = 0; + + return allocatedBytes; + } + + private static long GetTotalAllocatedBytes() + { + // We don't need to run GC.Collect(); + // GC.GetTotalAllocatedBytes() is not valid for this case. + // AppDomain.MonitoringIsEnabled is not valid for this case. + // Ref: https://github.com/dotnet/BenchmarkDotNet/blob/master/src/BenchmarkDotNet/Engines/GcStats.cs + + return GC.GetAllocatedBytesForCurrentThread(); + } +} \ No newline at end of file diff --git a/source/RevitLookup/Core/Metadata/DescriptorBuilder.Api.cs b/source/RevitLookup/Core/Engine/DescriptorBuilder.Api.cs similarity index 98% rename from source/RevitLookup/Core/Metadata/DescriptorBuilder.Api.cs rename to source/RevitLookup/Core/Engine/DescriptorBuilder.Api.cs index e7889f3e1..2d327017a 100644 --- a/source/RevitLookup/Core/Metadata/DescriptorBuilder.Api.cs +++ b/source/RevitLookup/Core/Engine/DescriptorBuilder.Api.cs @@ -20,7 +20,7 @@ using RevitLookup.Core.Objects; -namespace RevitLookup.Core.Metadata; +namespace RevitLookup.Core.Engine; public sealed partial class DescriptorBuilder { diff --git a/source/RevitLookup/Core/Metadata/DescriptorBuilder.Build.cs b/source/RevitLookup/Core/Engine/DescriptorBuilder.Build.cs similarity index 93% rename from source/RevitLookup/Core/Metadata/DescriptorBuilder.Build.cs rename to source/RevitLookup/Core/Engine/DescriptorBuilder.Build.cs index f4261f7ff..69e76a887 100644 --- a/source/RevitLookup/Core/Metadata/DescriptorBuilder.Build.cs +++ b/source/RevitLookup/Core/Engine/DescriptorBuilder.Build.cs @@ -19,21 +19,23 @@ // (Rights in Technical Data and Computer Software), as applicable. using System.Reflection; +using System.Runtime; using RevitLookup.Core.Objects; using RevitLookup.Core.Utils; -namespace RevitLookup.Core.Metadata; +namespace RevitLookup.Core.Engine; public sealed partial class DescriptorBuilder { private IList BuildInstanceObject(Type type) { var types = GetTypeHierarchy(type); + for (var i = types.Count - 1; i >= 0; i--) { _type = types[i]; _currentDescriptor = DescriptorUtils.FindSuitableDescriptor(_obj, _type); - + var flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly; if (_settings.IncludeStatic) flags |= BindingFlags.Static; if (_settings.IncludePrivate) flags |= BindingFlags.NonPublic; @@ -43,15 +45,15 @@ private IList BuildInstanceObject(Type type) AddFields(flags); AddEvents(flags); AddExtensions(); - + _depth--; } - + AddEnumerableItems(); - + return _descriptors; } - + private List GetTypeHierarchy(Type type) { var types = new List(); @@ -60,23 +62,23 @@ private List GetTypeHierarchy(Type type) types.Add(type); type = type.BaseType; } - + if (_settings.IncludeRootHierarchy) types.Add(type); - + return types; } - + private IList BuildStaticObject(Type type) { _type = type; - + var flags = BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly; if (_settings.IncludePrivate) flags |= BindingFlags.NonPublic; - + AddProperties(flags); AddMethods(flags); AddFields(flags); - + return _descriptors; } } \ No newline at end of file diff --git a/source/RevitLookup/Core/Metadata/DescriptorBuilder.Enumeration.cs b/source/RevitLookup/Core/Engine/DescriptorBuilder.Enumeration.cs similarity index 97% rename from source/RevitLookup/Core/Metadata/DescriptorBuilder.Enumeration.cs rename to source/RevitLookup/Core/Engine/DescriptorBuilder.Enumeration.cs index 5d1b7708f..f1c89087c 100644 --- a/source/RevitLookup/Core/Metadata/DescriptorBuilder.Enumeration.cs +++ b/source/RevitLookup/Core/Engine/DescriptorBuilder.Enumeration.cs @@ -20,7 +20,7 @@ using System.Collections; -namespace RevitLookup.Core.Metadata; +namespace RevitLookup.Core.Engine; public sealed partial class DescriptorBuilder { diff --git a/source/RevitLookup/Core/Metadata/DescriptorBuilder.Events.cs b/source/RevitLookup/Core/Engine/DescriptorBuilder.Events.cs similarity index 92% rename from source/RevitLookup/Core/Metadata/DescriptorBuilder.Events.cs rename to source/RevitLookup/Core/Engine/DescriptorBuilder.Events.cs index 9422bdebc..845b17109 100644 --- a/source/RevitLookup/Core/Metadata/DescriptorBuilder.Events.cs +++ b/source/RevitLookup/Core/Engine/DescriptorBuilder.Events.cs @@ -1,7 +1,7 @@ using System.Reflection; using RevitLookup.Core.Utils; -namespace RevitLookup.Core.Metadata; +namespace RevitLookup.Core.Engine; public sealed partial class DescriptorBuilder { diff --git a/source/RevitLookup/Core/Metadata/DescriptorBuilder.Extensions.cs b/source/RevitLookup/Core/Engine/DescriptorBuilder.Extensions.cs similarity index 81% rename from source/RevitLookup/Core/Metadata/DescriptorBuilder.Extensions.cs rename to source/RevitLookup/Core/Engine/DescriptorBuilder.Extensions.cs index 29a0800db..aacf6bce1 100644 --- a/source/RevitLookup/Core/Metadata/DescriptorBuilder.Extensions.cs +++ b/source/RevitLookup/Core/Engine/DescriptorBuilder.Extensions.cs @@ -21,7 +21,7 @@ using RevitLookup.Core.Contracts; using RevitLookup.Core.Objects; -namespace RevitLookup.Core.Metadata; +namespace RevitLookup.Core.Engine; public sealed partial class DescriptorBuilder : IExtensionManager { @@ -29,10 +29,10 @@ private void AddExtensions() { if (!_settings.IncludeExtensions) return; if (_currentDescriptor is not IDescriptorExtension extension) return; - + extension.RegisterExtensions(this); } - + public void Register(T value, Action> extension) { var descriptorExtension = new DescriptorExtension @@ -40,20 +40,30 @@ public void Register(T value, Action> extension) Value = value, Context = Context }; - - _tracker.Start(); + try { - extension.Invoke(descriptorExtension); + Evaluate(descriptorExtension, extension); WriteDescriptor(descriptorExtension.Name, descriptorExtension.Result); } catch (Exception exception) { WriteDescriptor(descriptorExtension.Name, exception); } + } + + private void Evaluate(DescriptorExtension descriptorExtension, Action> extension) + { + try + { + _clockDiagnoser.Start(); + _memoryDiagnoser.Start(); + extension.Invoke(descriptorExtension); + } finally { - _tracker.Stop(); + _memoryDiagnoser.Stop(); + _clockDiagnoser.Stop(); } } } \ No newline at end of file diff --git a/source/RevitLookup/Core/Metadata/DescriptorBuilder.Fields.cs b/source/RevitLookup/Core/Engine/DescriptorBuilder.Fields.cs similarity index 79% rename from source/RevitLookup/Core/Metadata/DescriptorBuilder.Fields.cs rename to source/RevitLookup/Core/Engine/DescriptorBuilder.Fields.cs index c7ec727f7..682db9780 100644 --- a/source/RevitLookup/Core/Metadata/DescriptorBuilder.Fields.cs +++ b/source/RevitLookup/Core/Engine/DescriptorBuilder.Fields.cs @@ -20,22 +20,31 @@ using System.Reflection; -namespace RevitLookup.Core.Metadata; +namespace RevitLookup.Core.Engine; public sealed partial class DescriptorBuilder { private void AddFields(BindingFlags bindingFlags) { if (!_settings.IncludeFields) return; - + var members = _type.GetFields(bindingFlags); foreach (var member in members) { if (member.IsSpecialName) continue; - _tracker.Start(); - var value = member.GetValue(_obj); - _tracker.Stop(); + + var value = Evaluate(member); WriteDescriptor(member, value, null); } } + + private object Evaluate(FieldInfo member) + { + _clockDiagnoser.Start(); + _memoryDiagnoser.Start(); + var value = member.GetValue(_obj); + _memoryDiagnoser.Stop(); + _clockDiagnoser.Stop(); + return value; + } } \ No newline at end of file diff --git a/source/RevitLookup/Core/Metadata/DescriptorBuilder.Methods.cs b/source/RevitLookup/Core/Engine/DescriptorBuilder.Methods.cs similarity index 62% rename from source/RevitLookup/Core/Metadata/DescriptorBuilder.Methods.cs rename to source/RevitLookup/Core/Engine/DescriptorBuilder.Methods.cs index 9ac502f21..f6123101b 100644 --- a/source/RevitLookup/Core/Metadata/DescriptorBuilder.Methods.cs +++ b/source/RevitLookup/Core/Engine/DescriptorBuilder.Methods.cs @@ -21,7 +21,7 @@ using System.Reflection; using RevitLookup.Core.Contracts; -namespace RevitLookup.Core.Metadata; +namespace RevitLookup.Core.Engine; public sealed partial class DescriptorBuilder { @@ -31,16 +31,20 @@ private void AddMethods(BindingFlags bindingFlags) foreach (var member in members) { if (member.IsSpecialName) continue; - + object value; - ParameterInfo[] parameters = null; - _tracker.Start(); + var parameters = member.GetParameters(); + try { - if (!TryEvaluate(member, out value, out parameters)) + if (!TryResolve(member, parameters, out value)) { - _tracker.Reset(); - continue; + if (!IsMethodSupported(member, parameters, out value)) continue; + + if (value is null) + { + Evaluate(member, out value); + } } } catch (TargetInvocationException exception) @@ -51,43 +55,65 @@ private void AddMethods(BindingFlags bindingFlags) { value = exception; } - finally - { - _tracker.Stop(); - } - + WriteDescriptor(member, value, parameters); } } - - private bool TryEvaluate(MethodInfo member, out object value, out ParameterInfo[] parameters) + + private bool TryResolve(MethodInfo member, ParameterInfo[] parameters, out object value) { value = null; - parameters = member.GetParameters(); - - if (_currentDescriptor is IDescriptorResolver resolver) + if (_currentDescriptor is not IDescriptorResolver resolver) return false; + + try { + _clockDiagnoser.Start(); + _memoryDiagnoser.Start(); value = resolver.Resolve(Context, member.Name, parameters); - if (value is not null) return true; + } + finally + { + _memoryDiagnoser.Stop(); + _clockDiagnoser.Stop(); } + return value is not null; + } + + private void Evaluate(MethodInfo member, out object value) + { + try + { + _clockDiagnoser.Start(); + _memoryDiagnoser.Start(); + value = member.Invoke(_obj, null); + } + finally + { + _memoryDiagnoser.Stop(); + _clockDiagnoser.Stop(); + } + } + + private bool IsMethodSupported(MethodInfo member, ParameterInfo[] parameters, out object value) + { + value = null; if (member.ReturnType.Name == "Void") { if (!_settings.IncludeUnsupported) return false; value = new NotSupportedException("Method doesn't return a value"); return true; - } - + } + if (parameters.Length > 0) { if (!_settings.IncludeUnsupported) return false; - + value = new NotSupportedException("Unsupported method overload"); return true; } - - value = member.Invoke(_obj, null); + return true; } } \ No newline at end of file diff --git a/source/RevitLookup/Core/Metadata/DescriptorBuilder.Properties.cs b/source/RevitLookup/Core/Engine/DescriptorBuilder.Properties.cs similarity index 61% rename from source/RevitLookup/Core/Metadata/DescriptorBuilder.Properties.cs rename to source/RevitLookup/Core/Engine/DescriptorBuilder.Properties.cs index 36586c06a..c81eeae49 100644 --- a/source/RevitLookup/Core/Metadata/DescriptorBuilder.Properties.cs +++ b/source/RevitLookup/Core/Engine/DescriptorBuilder.Properties.cs @@ -21,7 +21,7 @@ using System.Reflection; using RevitLookup.Core.Contracts; -namespace RevitLookup.Core.Metadata; +namespace RevitLookup.Core.Engine; public sealed partial class DescriptorBuilder { @@ -31,16 +31,20 @@ private void AddProperties(BindingFlags bindingFlags) foreach (var member in members) { if (member.IsSpecialName) continue; - + object value; - ParameterInfo[] parameters = null; - _tracker.Start(); + var parameters = member.GetMethod!.GetParameters(); + try { - if (!TryEvaluate(member, out value, out parameters)) + if (!TryResolve(member, parameters, out value)) { - _tracker.Reset(); - continue; + if (!IsPropertySupported(member, parameters, out value)) continue; + + if (value is null) + { + Evaluate(member, out value); + } } } catch (TargetInvocationException exception) @@ -51,42 +55,64 @@ private void AddProperties(BindingFlags bindingFlags) { value = exception; } - finally - { - _tracker.Stop(); - } - + WriteDescriptor(member, value, parameters); } } - - private bool TryEvaluate(PropertyInfo member, out object value, out ParameterInfo[] parameters) + + private bool TryResolve(PropertyInfo member, ParameterInfo[] parameters, out object value) { value = null; - parameters = null; - + if (_currentDescriptor is not IDescriptorResolver resolver) return false; + + try + { + _clockDiagnoser.Start(); + _memoryDiagnoser.Start(); + value = resolver.Resolve(Context, member.Name, parameters); + } + finally + { + _memoryDiagnoser.Stop(); + _clockDiagnoser.Stop(); + } + + return value is not null; + } + + private void Evaluate(PropertyInfo member, out object value) + { + try + { + _clockDiagnoser.Start(); + _memoryDiagnoser.Start(); + value = member.GetValue(_obj); + } + finally + { + _memoryDiagnoser.Stop(); + _clockDiagnoser.Stop(); + } + } + + private bool IsPropertySupported(PropertyInfo member, ParameterInfo[] parameters, out object value) + { + value = null; + if (!member.CanRead) { value = new NotSupportedException("Property does not have a get accessor, it cannot be read"); return true; } - parameters = member.GetMethod!.GetParameters(); - if (_currentDescriptor is IDescriptorResolver resolver) - { - value = resolver.Resolve(Context, member.Name, parameters); - if (value is not null) return true; - } - if (parameters.Length > 0) { if (!_settings.IncludeUnsupported) return false; - + value = new NotSupportedException("Unsupported property overload"); return true; } - - value = member.GetValue(_obj); + return true; } } \ No newline at end of file diff --git a/source/RevitLookup/Core/Metadata/DescriptorBuilder.Write.cs b/source/RevitLookup/Core/Engine/DescriptorBuilder.Write.cs similarity index 92% rename from source/RevitLookup/Core/Metadata/DescriptorBuilder.Write.cs rename to source/RevitLookup/Core/Engine/DescriptorBuilder.Write.cs index 44ccd56ea..cb80261b1 100644 --- a/source/RevitLookup/Core/Metadata/DescriptorBuilder.Write.cs +++ b/source/RevitLookup/Core/Engine/DescriptorBuilder.Write.cs @@ -24,7 +24,7 @@ using RevitLookup.Core.Objects; using RevitLookup.Core.Utils; -namespace RevitLookup.Core.Metadata; +namespace RevitLookup.Core.Engine; public sealed partial class DescriptorBuilder { @@ -53,11 +53,11 @@ private void WriteDescriptor(string name, object value) TypeFullName = DescriptorUtils.MakeGenericFullTypeName(_type), MemberAttributes = MemberAttributes.Extension, Type = DescriptorUtils.MakeGenericTypeName(_type), - ComputationTime = _tracker.Elapsed.TotalMilliseconds + ComputationTime = _clockDiagnoser.GetElapsed().TotalMilliseconds, + AllocatedBytes = _memoryDiagnoser.GetAllocatedBytes() }; _descriptors.Add(descriptor); - _tracker.Reset(); } private void WriteDescriptor(MemberInfo member, object value, ParameterInfo[] parameters) @@ -70,11 +70,11 @@ private void WriteDescriptor(MemberInfo member, object value, ParameterInfo[] pa Name = EvaluateName(member, parameters), MemberAttributes = EvaluateAttributes(member), Type = DescriptorUtils.MakeGenericTypeName(_type), - ComputationTime = _tracker.Elapsed.TotalMilliseconds + ComputationTime = _clockDiagnoser.GetElapsed().TotalMilliseconds, + AllocatedBytes = _memoryDiagnoser.GetAllocatedBytes() }; - + _descriptors.Add(descriptor); - _tracker.Reset(); } private SnoopableObject EvaluateValue(MemberInfo member, object value) @@ -116,9 +116,9 @@ private static MemberAttributes EvaluateAttributes(MemberInfo member) return member switch { MethodInfo info => GetModifiers(MemberAttributes.Method, info.Attributes), - PropertyInfo info => GetModifiers(MemberAttributes.Property, info.CanRead ? info.GetMethod.Attributes : info.SetMethod.Attributes), + PropertyInfo info => GetModifiers(MemberAttributes.Property, info.CanRead ? info.GetMethod!.Attributes : info.SetMethod!.Attributes), FieldInfo info => GetModifiers(MemberAttributes.Field, info.Attributes), - EventInfo info => GetModifiers(MemberAttributes.Event, info.AddMethod.Attributes), + EventInfo info => GetModifiers(MemberAttributes.Event, info.AddMethod!.Attributes), _ => throw new ArgumentOutOfRangeException(nameof(member)) }; } @@ -155,7 +155,7 @@ private static void RestoreSetDescription(MemberInfo member, object value, Snoop var name = member switch { - PropertyInfo property => DescriptorUtils.MakeGenericTypeName(property.GetMethod.ReturnType), + PropertyInfo property => DescriptorUtils.MakeGenericTypeName(property.GetMethod!.ReturnType), MethodInfo method => DescriptorUtils.MakeGenericTypeName(method.ReturnType), _ => snoopableObject.Descriptor.Name }; diff --git a/source/RevitLookup/Core/Metadata/DescriptorBuilder.cs b/source/RevitLookup/Core/Engine/DescriptorBuilder.cs similarity index 88% rename from source/RevitLookup/Core/Metadata/DescriptorBuilder.cs rename to source/RevitLookup/Core/Engine/DescriptorBuilder.cs index 4d6bca70c..2211242b1 100644 --- a/source/RevitLookup/Core/Metadata/DescriptorBuilder.cs +++ b/source/RevitLookup/Core/Engine/DescriptorBuilder.cs @@ -18,21 +18,23 @@ // Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) // (Rights in Technical Data and Computer Software), as applicable. -using System.Diagnostics; +using RevitLookup.Core.Diagnosers; using RevitLookup.Core.Objects; using RevitLookup.Services.Contracts; -namespace RevitLookup.Core.Metadata; +namespace RevitLookup.Core.Engine; public sealed partial class DescriptorBuilder { private readonly List _descriptors; - private readonly Stopwatch _tracker = new(); private readonly ISettingsService _settings; private Descriptor _currentDescriptor; private object _obj; private Type _type; private int _depth; + + private readonly ClockDiagnoser _clockDiagnoser = new(); + private readonly MemoryDiagnoser _memoryDiagnoser = new(); private DescriptorBuilder() { diff --git a/source/RevitLookup/Core/Objects/Descriptor.cs b/source/RevitLookup/Core/Objects/Descriptor.cs index 920f3c689..4d9e286c0 100644 --- a/source/RevitLookup/Core/Objects/Descriptor.cs +++ b/source/RevitLookup/Core/Objects/Descriptor.cs @@ -30,6 +30,7 @@ public abstract class Descriptor public string Name { get; set; } public string Description { get; set; } public double ComputationTime { get; set; } + public long AllocatedBytes { get; set; } public MemberAttributes MemberAttributes { get; set; } public SnoopableObject Value { get; set; } } \ No newline at end of file diff --git a/source/RevitLookup/Core/Objects/SnoopableObject.cs b/source/RevitLookup/Core/Objects/SnoopableObject.cs index 05d0fc9d9..d4bb2cf47 100644 --- a/source/RevitLookup/Core/Objects/SnoopableObject.cs +++ b/source/RevitLookup/Core/Objects/SnoopableObject.cs @@ -19,7 +19,7 @@ // Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) // (Rights in Technical Data and Computer Software), as applicable. -using RevitLookup.Core.Metadata; +using RevitLookup.Core.Engine; using RevitLookup.Core.Utils; namespace RevitLookup.Core.Objects; diff --git a/source/RevitLookup/Services/Contracts/ISettingsService.cs b/source/RevitLookup/Services/Contracts/ISettingsService.cs index f8c0de364..a0dad0191 100644 --- a/source/RevitLookup/Services/Contracts/ISettingsService.cs +++ b/source/RevitLookup/Services/Contracts/ISettingsService.cs @@ -31,6 +31,7 @@ public interface ISettingsService int TransitionDuration { get; } bool UseHardwareRendering { get; set; } bool ShowTimeColumn { get; set; } + bool ShowMemoryColumn { get; set; } //Window bool UseSizeRestoring { get; set; } @@ -48,7 +49,7 @@ public interface ISettingsService // Ribbon bool UseModifyTab { get; set; } - + int ApplyTransition(bool value); void Save(); } \ No newline at end of file diff --git a/source/RevitLookup/Services/SettingsService.cs b/source/RevitLookup/Services/SettingsService.cs index b47879752..f26dccbf4 100644 --- a/source/RevitLookup/Services/SettingsService.cs +++ b/source/RevitLookup/Services/SettingsService.cs @@ -41,6 +41,7 @@ internal sealed class Settings [JsonPropertyName("TransitionDuration")] public int TransitionDuration { get; set; } //= SettingsService.DefaultTransitionDuration; [JsonPropertyName("IsHardwareRenderingAllowed")] public bool UseHardwareRendering { get; set; } = true; [JsonPropertyName("IsTimeColumnAllowed")] public bool ShowTimeColumn { get; set; } + [JsonPropertyName("ShowMemoryColumn")] public bool ShowMemoryColumn { get; set; } [JsonPropertyName("UseSizeRestoring")] public bool UseSizeRestoring { get; set; } [JsonPropertyName("WindowWidth")] public double WindowWidth { get; set; } [JsonPropertyName("WindowHeight")] public double WindowHeight { get; set; } @@ -57,7 +58,7 @@ internal sealed class Settings public sealed class SettingsService : ISettingsService { private const int DefaultTransitionDuration = 200; - + private readonly FolderLocations _folderLocations; private readonly JsonSerializerOptions _jsonSerializerOptions; private readonly ILogger _logger; @@ -101,6 +102,12 @@ public bool ShowTimeColumn set => _settings.ShowTimeColumn = value; } + public bool ShowMemoryColumn + { + get => _settings.ShowMemoryColumn; + set => _settings.ShowMemoryColumn = value; + } + public bool UseModifyTab { get => _settings.UseModifyTab; diff --git a/source/RevitLookup/Views/Converters/BytesToStringConverter.cs b/source/RevitLookup/Views/Converters/BytesToStringConverter.cs new file mode 100644 index 000000000..9044b705a --- /dev/null +++ b/source/RevitLookup/Views/Converters/BytesToStringConverter.cs @@ -0,0 +1,50 @@ +// Copyright 2003-2024 by Autodesk, Inc. +// +// Permission to use, copy, modify, and distribute this software in +// object code form for any purpose and without fee is hereby granted, +// provided that the above copyright notice appears in all copies and +// that both that copyright notice and the limited warranty and +// restricted rights notice below appear in all supporting +// documentation. +// +// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. +// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF +// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. +// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE +// UNINTERRUPTED OR ERROR FREE. +// +// Use, duplication, or disclosure by the U.S. Government is subject to +// restrictions set forth in FAR 52.227-19 (Commercial Computer +// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) +// (Rights in Technical Data and Computer Software), as applicable. + +using System.Globalization; +using System.Windows.Data; +using System.Windows.Markup; + +namespace RevitLookup.Views.Converters; + +public sealed class BytesToStringConverter : MarkupExtension, IValueConverter +{ + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + var bytes = (long) value!; + return bytes switch + { + 0 => string.Empty, + < 1_000 => $"{value} B", + < 1_000_000 => $"{(bytes / 1000d):F3} KB", + _ => $"{(bytes / 1_000_000d):F3} MB" + }; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotSupportedException(); + } + + public override object ProvideValue(IServiceProvider serviceProvider) + { + return this; + } +} \ No newline at end of file diff --git a/source/RevitLookup/Views/Pages/Abstraction/SnoopViewBase.ContextMenu.cs b/source/RevitLookup/Views/Pages/Abstraction/SnoopViewBase.ContextMenu.cs index 197421e74..c8dd25aa2 100644 --- a/source/RevitLookup/Views/Pages/Abstraction/SnoopViewBase.ContextMenu.cs +++ b/source/RevitLookup/Views/Pages/Abstraction/SnoopViewBase.ContextMenu.cs @@ -42,7 +42,7 @@ private void CreateTreeContextMenu(Descriptor descriptor, FrameworkElement row) PlacementTarget = row, DataContext = ViewModel }; - + row.ContextMenu = contextMenu; contextMenu.AddMenuItem("CopyMenuItem") @@ -85,6 +85,15 @@ private void CreateGridContextMenu(DataGrid dataGrid) parameter.Visibility = _settingsService.ShowTimeColumn ? Visibility.Visible : Visibility.Collapsed; }); + contextMenu.AddMenuItem() + .SetHeader("Memory") + .SetChecked(dataGrid.Columns[3].Visibility == Visibility.Visible) + .SetCommand(dataGrid.Columns[3], parameter => + { + _settingsService.ShowMemoryColumn = parameter.Visibility != Visibility.Visible; + parameter.Visibility = _settingsService.ShowMemoryColumn ? Visibility.Visible : Visibility.Collapsed; + }); + contextMenu.AddSeparator(); contextMenu.AddLabel("Show"); @@ -174,7 +183,7 @@ private void CreateGridRowContextMenu(Descriptor descriptor, FrameworkElement ro contextMenu.AddMenuItem("HelpMenuItem") .SetCommand(descriptor, parameter => HelpUtils.ShowHelp(parameter.TypeFullName, parameter.Name)) .SetShortcut(Key.F1); - + if (descriptor.Value.Descriptor is IDescriptorConnector connector) connector.RegisterMenu(contextMenu); } } \ No newline at end of file diff --git a/source/RevitLookup/Views/Pages/Abstraction/SnoopViewBase.ToolTips.cs b/source/RevitLookup/Views/Pages/Abstraction/SnoopViewBase.ToolTips.cs index 7d84783fe..354dce29e 100644 --- a/source/RevitLookup/Views/Pages/Abstraction/SnoopViewBase.ToolTips.cs +++ b/source/RevitLookup/Views/Pages/Abstraction/SnoopViewBase.ToolTips.cs @@ -75,6 +75,12 @@ private void CreateGridRowTooltip(Descriptor descriptor, FrameworkElement row) .Append("Time: ") .Append(descriptor.ComputationTime) .Append(" ms"); + + if (descriptor.AllocatedBytes > 0) + builder.AppendLine() + .Append("Allocated: ") + .Append(descriptor.AllocatedBytes) + .Append(" bytes"); row.ToolTip = new ToolTip { diff --git a/source/RevitLookup/Views/Pages/Abstraction/SnoopViewBase.xaml.cs b/source/RevitLookup/Views/Pages/Abstraction/SnoopViewBase.xaml.cs index a943aa6e0..1bddfcc50 100644 --- a/source/RevitLookup/Views/Pages/Abstraction/SnoopViewBase.xaml.cs +++ b/source/RevitLookup/Views/Pages/Abstraction/SnoopViewBase.xaml.cs @@ -40,13 +40,13 @@ public partial class SnoopViewBase : Page, INavigableView private readonly TreeView _treeViewControl; private readonly DataGrid _dataGridControl; private readonly ISettingsService _settingsService; - + protected SnoopViewBase(ISettingsService settingsService) { _settingsService = settingsService; AddShortcuts(); } - + protected TreeView TreeViewControl { get => _treeViewControl; @@ -56,7 +56,7 @@ protected TreeView TreeViewControl OnTreeChanged(value); } } - + protected DataGrid DataGridControl { get => _dataGridControl; @@ -66,10 +66,10 @@ protected DataGrid DataGridControl OnGridChanged(value); } } - + protected UIElement SearchBoxControl { get; init; } public ISnoopViewModel ViewModel { get; protected init; } - + /// /// Handle data grid reference changed event /// @@ -80,7 +80,7 @@ private void OnTreeChanged(TreeView control) { control.ItemContainerGenerator.StatusChanged += OnTreeViewItemGenerated; } - + /// /// Handle tree view source changed /// @@ -94,17 +94,17 @@ protected void OnTreeSourceChanged(object sender, IEnumerable enumerable) SetupTreeView(); return; } - + Loaded += OnLoaded; return; - + void OnLoaded(object o, RoutedEventArgs args) { Loaded -= OnLoaded; SetupTreeView(); } } - + /// /// Handle tree view item loaded /// @@ -120,13 +120,13 @@ private void OnTreeViewItemGenerated(object sender, EventArgs _) { var treeItem = (ItemsControl) generator.ContainerFromItem(item); if (treeItem is null) continue; - + treeItem.Loaded -= OnTreeItemLoaded; treeItem.PreviewMouseLeftButtonUp -= OnTreeItemClicked; - + treeItem.Loaded += OnTreeItemLoaded; treeItem.PreviewMouseLeftButtonUp += OnTreeItemClicked; - + if (treeItem.Items.Count > 0) { treeItem.ItemContainerGenerator.StatusChanged -= OnTreeViewItemGenerated; @@ -135,7 +135,7 @@ private void OnTreeViewItemGenerated(object sender, EventArgs _) } } } - + /// /// Handle tree view loaded event /// @@ -153,7 +153,7 @@ private void OnTreeItemLoaded(object sender, RoutedEventArgs args) break; } } - + /// /// Handle tree view reference changed event /// @@ -164,10 +164,10 @@ private async void SetupTreeView() { // Await Frame transition. GetMembers freezes the thread and breaks the animation await Task.Delay(_settingsService.TransitionDuration); - + ExpandFirstTreeGroup(); } - + /// /// Handle data grid reference changed event /// @@ -180,11 +180,12 @@ private void OnGridChanged(DataGrid control) { var dataGrid = (DataGrid) sender; ValidateTimeColumn(dataGrid); + ValidateAllocatedColumn(dataGrid); CreateGridContextMenu(dataGrid); }; control.ItemsSourceChanged += OnGridItemsSourceChanged; } - + /// /// Handle data grid source changed /// @@ -194,7 +195,7 @@ private void OnGridChanged(DataGrid control) private void OnGridItemsSourceChanged(object sender, EventArgs _) { var dataGrid = (DataGrid) sender; - + //Clear shapingStorage for remove duplications. WpfBug? dataGrid.Items.GroupDescriptions!.Clear(); dataGrid.Items.SortDescriptions.Add(new SortDescription(nameof(Descriptor.Depth), ListSortDirection.Descending)); @@ -202,7 +203,7 @@ private void OnGridItemsSourceChanged(object sender, EventArgs _) dataGrid.Items.SortDescriptions.Add(new SortDescription(nameof(Descriptor.Name), ListSortDirection.Ascending)); dataGrid.Items.GroupDescriptions.Add(new PropertyGroupDescription(nameof(Descriptor.Type))); } - + /// /// Handle data grid row loading event /// @@ -216,7 +217,7 @@ protected void OnGridRowLoading(object sender, DataGridRowEventArgs args) row.PreviewMouseLeftButtonUp += OnGridRowClicked; SelectDataGridRowStyle(row); } - + /// /// Handle data grid row loaded event /// @@ -230,23 +231,23 @@ protected void OnGridRowLoaded(object sender, RoutedEventArgs args) CreateGridRowTooltip(context, element); CreateGridRowContextMenu(context, element); } - + /// /// Expand first tree view group after navigation /// private void ExpandFirstTreeGroup() { if (TreeViewControl.Items.Count > 3) return; - + var rootItem = VisualUtils.GetTreeViewItem(TreeViewControl, 0); if (rootItem is null) return; - + var nestedItem = VisualUtils.GetTreeViewItem(rootItem, 0); if (nestedItem is null) return; - + nestedItem.IsSelected = true; } - + /// /// Show/hide time column /// @@ -254,4 +255,12 @@ private void ValidateTimeColumn(System.Windows.Controls.DataGrid control) { control.Columns[2].Visibility = _settingsService.ShowTimeColumn ? Visibility.Visible : Visibility.Collapsed; } + + /// + /// Show/hide allocated column + /// + private void ValidateAllocatedColumn(System.Windows.Controls.DataGrid control) + { + control.Columns[3].Visibility = _settingsService.ShowMemoryColumn ? Visibility.Visible : Visibility.Collapsed; + } } \ No newline at end of file diff --git a/source/RevitLookup/Views/Pages/EventsView.xaml b/source/RevitLookup/Views/Pages/EventsView.xaml index 02c6a13f3..841fcf8ef 100644 --- a/source/RevitLookup/Views/Pages/EventsView.xaml +++ b/source/RevitLookup/Views/Pages/EventsView.xaml @@ -126,8 +126,15 @@ Binding="{Binding ComputationTime, Converter={converters:TimeToStringConverter}, Mode=OneTime}" - Header="Time"> - + Header="Time" /> + diff --git a/source/RevitLookup/Views/Pages/SnoopView.xaml b/source/RevitLookup/Views/Pages/SnoopView.xaml index 074cf8aaa..4bdb56105 100644 --- a/source/RevitLookup/Views/Pages/SnoopView.xaml +++ b/source/RevitLookup/Views/Pages/SnoopView.xaml @@ -31,10 +31,8 @@ - - + + @@ -43,10 +41,8 @@ - - + + @@ -132,8 +128,15 @@ Binding="{Binding ComputationTime, Converter={converters:TimeToStringConverter}, Mode=OneTime}" - Header="Time"> - + Header="Time" /> +