diff --git a/TuneUp/ProfiledNodeViewModel.cs b/TuneUp/ProfiledNodeViewModel.cs index 87aca8b..281643a 100644 --- a/TuneUp/ProfiledNodeViewModel.cs +++ b/TuneUp/ProfiledNodeViewModel.cs @@ -28,7 +28,7 @@ public class ProfiledNodeViewModel : NotificationObject /// public string Name { - get + get { // For virtual row, do not attempt to grab node name if (!name.Contains(ExecutionTimelString) && !name.StartsWith(GroupNodePrefix)) @@ -37,7 +37,7 @@ public string Name } internal set { name = value; } } - + /// /// The order number of this node in the most recent graph run. /// Returns null if the node was not executed during the most recent graph run. @@ -133,6 +133,20 @@ public ProfiledNodeState State } private ProfiledNodeState state; + /// + /// The current hotspot state based on execution time and configured hotspot values + /// + public ProfiledNodeHotspotState HotspotState + { + get => hotspotState; + set + { + hotspotState = value; + RaisePropertyChanged(nameof(HotspotState)); + } + } + private ProfiledNodeHotspotState hotspotState; + /// /// The GUID of the group to which this node belongs /// diff --git a/TuneUp/TuneUpWindow.xaml b/TuneUp/TuneUpWindow.xaml index cbecbd3..1aa40b8 100644 --- a/TuneUp/TuneUpWindow.xaml +++ b/TuneUp/TuneUpWindow.xaml @@ -17,7 +17,8 @@ - + + @@ -133,7 +134,14 @@ + + + + + - + + + + 0) { // Select - var command = new DynamoModel.SelectModelCommand(selectedNodes.Select(nm => nm.GUID), ModifierKeys.None); + var command = new DynamoModel.SelectModelCommand(selectedNodes.Select(nm => nm.GUID), Dynamo.Utilities.ModifierKeys.None); commandExecutive.ExecuteCommand(command, uniqueId, "TuneUp"); // Focus on selected @@ -154,10 +157,35 @@ private void ExportTimes_Click(object sender, RoutedEventArgs e) { (NodeAnalysisTable.DataContext as TuneUpWindowViewModel).ExportToCsv(); } + + private void HotspotToggleButton_Checked(object sender, RoutedEventArgs e) + { + var viewModel = NodeAnalysisTable.DataContext as TuneUpWindowViewModel; + viewModel.ShowHotspotsEnabled = (sender as ToggleButton).IsChecked == true; + } + + private void NumberValidationTextBox(object sender, TextCompositionEventArgs e) + { + Regex regex = new Regex("[^0-9]+"); + e.Handled = regex.IsMatch(e.Text); + } } #region Converters + public class BoolToVisibilityConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + return (bool)value ? Visibility.Visible : Visibility.Hidden; + } + + public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + throw new NotSupportedException(); + } + } + public class IsGroupToMarginMultiConverter : IMultiValueConverter { private static readonly Guid DefaultGuid = Guid.Empty; @@ -179,17 +207,25 @@ public object[] ConvertBack(object value, Type[] targetTypes, object parameter, } } - public class IsGroupToBrushConverter : IValueConverter + public class IsGroupToBrushMultiConverter : IMultiValueConverter { private static readonly SolidColorBrush GroupBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#333333")); private static readonly SolidColorBrush DefaultBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#AAAAAA")); + private static readonly SolidColorBrush MinHotspotBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#B7D78C")); + private static readonly SolidColorBrush MaxHotspotBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#EB5555")); - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { - return value is bool isGroup && isGroup ? GroupBrush : DefaultBrush; + if (values.Length == 2 && values[0] is bool isGroup && values[1] is ProfiledNodeHotspotState hotspotState) + { + if (isGroup) return GroupBrush; + else if (hotspotState == ProfiledNodeHotspotState.Low) return MinHotspotBrush; + else if (hotspotState == ProfiledNodeHotspotState.High) return MaxHotspotBrush; + } + return DefaultBrush; } + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } diff --git a/TuneUp/TuneUpWindowViewModel.cs b/TuneUp/TuneUpWindowViewModel.cs index b9c85ef..ecacfaa 100644 --- a/TuneUp/TuneUpWindowViewModel.cs +++ b/TuneUp/TuneUpWindowViewModel.cs @@ -7,7 +7,6 @@ using System.Linq; using System.Threading; using System.Windows.Data; -using System.Windows.Media; using Dynamo.Core; using Dynamo.Engine.Profiling; using Dynamo.Graph.Annotations; @@ -43,6 +42,21 @@ public enum ProfiledNodeState NotExecuted = 5, } + /// + /// Enum of possible states of node depending on execution time and hotspot values + /// + public enum ProfiledNodeHotspotState + { + [Display(Name = "Mid")] + Mid = 0, + + [Display(Name = "Low")] + Low = 1, + + [Display(Name = "High")] + High = 2 + } + /// /// ViewModel for TuneUp. /// Handles profiling setup, workspace events, execution events, etc. @@ -63,6 +77,9 @@ public class TuneUpWindowViewModel : NotificationObject, IDisposable private bool isTuneUpChecked = false; private ListSortDirection sortDirection; private string sortingOrder = "number"; + private bool showHotspotsEnabled; + private int hotspotMinValue; + private int hotspotMaxValue; /// /// Name of the row to display current execution time @@ -190,6 +207,54 @@ public bool IsTuneUpChecked } } + /// + /// Gets or sets a value indicating whether hotspot highlighting is enabled. + /// + public bool ShowHotspotsEnabled + { + get => showHotspotsEnabled; + set + { + showHotspotsEnabled = value; + SetNodeHotspotState(); + RaisePropertyChanged(nameof(ShowHotspotsEnabled)); + } + } + + /// + /// Gets or sets the minimum value for hotspot highlighting. + /// + public int HotspotMinValue + { + get => hotspotMinValue; + set + { + if (hotspotMinValue != value) + { + hotspotMinValue = value; + RaisePropertyChanged(nameof(HotspotMinValue)); + SetNodeHotspotState(); + } + } + } + + /// + /// Gets or sets the maximum value for hotspot highlighting. + /// + public int HotspotMaxValue + { + get => hotspotMaxValue; + set + { + if (hotspotMaxValue != value) + { + hotspotMaxValue = value; + RaisePropertyChanged(nameof(HotspotMaxValue)); + SetNodeHotspotState(); + } + } + } + /// /// Collection of profiling data for nodes in the current workspace /// @@ -215,7 +280,7 @@ public string TotalGraphExecutiontime return (PreviousExecutionTimeRow?.ExecutionMilliseconds + CurrentExecutionTimeRow?.ExecutionMilliseconds).ToString() + "ms"; } } - + #endregion #region Constructor @@ -485,6 +550,34 @@ private void CalculateGroupNodes() } } + /// + /// Updates the hotspot state of all nodes based on execution time and configured hotspot values. + /// + private void SetNodeHotspotState() + { + foreach (var node in nodeDictionary.Values) + { + if (node.State == ProfiledNodeState.NotExecuted || !ShowHotspotsEnabled) + { + node.HotspotState = ProfiledNodeHotspotState.Mid; + continue; + } + + if (node.ExecutionMilliseconds <= HotspotMinValue) + { + node.HotspotState = ProfiledNodeHotspotState.Low; + } + else if (node.ExecutionMilliseconds >= HotspotMaxValue) + { + node.HotspotState = ProfiledNodeHotspotState.High; + } + else + { + node.HotspotState = ProfiledNodeHotspotState.Mid; + } + } + } + /// /// Applies the sorting logic to the ProfiledNodesCollection. /// @@ -602,7 +695,7 @@ private void CurrentWorkspaceModel_GroupAdded(AnnotationModel group) profiledNode.GroupExecutionOrderNumber = profiledGroup.GroupExecutionOrderNumber; groupDictionary[group.GUID].Add(profiledNode); - } + } } // Executes for each group when a graph with groups is open while TuneUp is enabled // Ensures that group nodes are sorted properly and do not appear at the bottom of the DataGrid