From 8671767353f8ae9e08a5dd1ae540932e217cb449 Mon Sep 17 00:00:00 2001 From: Vladimir Ivanov Date: Wed, 2 Mar 2022 16:37:47 +0300 Subject: [PATCH] counters --- .gitignore | 1 + gui/Optick/Controls/FrameCapture.xaml | 18 +- gui/Optick/Controls/FrameCapture.xaml.cs | 164 ++++++++- gui/Optick/Controls/FunctionSearch.xaml.cs | 2 +- gui/Optick/Controls/Settings.cs | 4 +- gui/Optick/Converters/IsNotEmptyConverter.cs | 21 ++ gui/Optick/Converters/IsNotNullConverter.cs | 19 ++ gui/Optick/Optick.csproj | 31 ++ gui/Optick/PlotPanelsSettingsStorage.cs | 88 +++++ .../Plots/PlotPanelsSettingsViewModel.cs | 23 ++ gui/Optick/Plots/PlotsViewModel.cs | 322 ++++++++++++++++++ gui/Optick/Plots/SelectCounterViewModel.cs | 74 ++++ gui/Optick/Plots/SelectedCounterViewModel.cs | 153 +++++++++ gui/Optick/Plots/Units.cs | 14 + gui/Optick/Plots/UnitsViewModel.cs | 91 +++++ gui/Optick/TimeLine/Icons/draw.png | Bin 0 -> 816 bytes gui/Optick/TimeLine/TimeLine.xaml.cs | 48 ++- .../ViewModels/FunctionSummaryViewModel.cs | 306 ++++++++--------- gui/Optick/Views/PlotPanelsSettingsView.xaml | 37 ++ .../Views/PlotPanelsSettingsView.xaml.cs | 66 ++++ gui/Optick/Views/PlotView.xaml | 187 ++++++++++ gui/Optick/Views/PlotView.xaml.cs | 89 +++++ gui/Optick/counters.json | 1 + gui/Optick/packages.config | 2 + gui/Profiler.Controls/FunctionSearch.xaml.cs | 2 +- gui/Profiler.Controls/Icons/Icons.xaml | 14 + gui/Profiler.Controls/RoutedEvents.cs | 8 +- .../ThreadView/EventThreadView.xaml.cs | 18 +- gui/Profiler.Data/Communication/Message.cs | 5 +- gui/Profiler.Data/FrameCollection.cs | 42 ++- gui/Profiler.Data/Profiler.Data.csproj | 1 + gui/Profiler.Data/RawCounterData.cs | 97 ++++++ samples/Common/TestEngine/TestEngine.cpp | 9 + src/optick.h | 14 + src/optick_core.cpp | 29 ++ src/optick_core.h | 11 + src/optick_message.h | 4 +- 37 files changed, 1816 insertions(+), 199 deletions(-) create mode 100644 gui/Optick/Converters/IsNotEmptyConverter.cs create mode 100644 gui/Optick/Converters/IsNotNullConverter.cs create mode 100644 gui/Optick/PlotPanelsSettingsStorage.cs create mode 100644 gui/Optick/Plots/PlotPanelsSettingsViewModel.cs create mode 100644 gui/Optick/Plots/PlotsViewModel.cs create mode 100644 gui/Optick/Plots/SelectCounterViewModel.cs create mode 100644 gui/Optick/Plots/SelectedCounterViewModel.cs create mode 100644 gui/Optick/Plots/Units.cs create mode 100644 gui/Optick/Plots/UnitsViewModel.cs create mode 100644 gui/Optick/TimeLine/Icons/draw.png create mode 100644 gui/Optick/Views/PlotPanelsSettingsView.xaml create mode 100644 gui/Optick/Views/PlotPanelsSettingsView.xaml.cs create mode 100644 gui/Optick/Views/PlotView.xaml create mode 100644 gui/Optick/Views/PlotView.xaml.cs create mode 100644 gui/Optick/counters.json create mode 100644 gui/Profiler.Data/RawCounterData.cs diff --git a/.gitignore b/.gitignore index 5bd85daa..436437e9 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,4 @@ Publish/ /Samples/UnrealEnginePlugin/Binaries /.vs /samples/UnrealEnginePlugin/GUI/Optick.exe +/**/.idea \ No newline at end of file diff --git a/gui/Optick/Controls/FrameCapture.xaml b/gui/Optick/Controls/FrameCapture.xaml index 98d2fe44..449a75ab 100644 --- a/gui/Optick/Controls/FrameCapture.xaml +++ b/gui/Optick/Controls/FrameCapture.xaml @@ -25,6 +25,7 @@ + @@ -36,7 +37,7 @@ - + @@ -83,6 +84,9 @@ + + + @@ -126,7 +130,7 @@ - + @@ -175,6 +179,10 @@ + + + + @@ -198,5 +206,11 @@ + + + + + + diff --git a/gui/Optick/Controls/FrameCapture.xaml.cs b/gui/Optick/Controls/FrameCapture.xaml.cs index 29ae52b0..b20614a9 100644 --- a/gui/Optick/Controls/FrameCapture.xaml.cs +++ b/gui/Optick/Controls/FrameCapture.xaml.cs @@ -23,6 +23,10 @@ using Profiler.InfrastructureMvvm; using Autofac; using Profiler.Controls.ViewModels; +using Profiler.ViewModels.Plots; +using Profiler.Views; +using Xceed.Wpf.AvalonDock.Layout; +using Frame = Profiler.Data.Frame; namespace Profiler.Controls { @@ -31,9 +35,10 @@ namespace Profiler.Controls /// public partial class FrameCapture : UserControl, INotifyPropertyChanged { - private string _captureName; - - public event PropertyChangedEventHandler PropertyChanged; + private readonly List _plotPanels = new List(); + private string _captureName; + + public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { @@ -58,7 +63,7 @@ public SummaryViewerModel SummaryVM public FrameCapture() { - using (var scope = BootStrapperBase.Container.BeginLifetimeScope()) + using (var scope = BootStrapperBase.Container.BeginLifetimeScope()) { SummaryVM = scope.Resolve(); } @@ -77,6 +82,7 @@ public FrameCapture() timeLine.NewConnection += TimeLine_NewConnection; timeLine.CancelConnection += TimeLine_CancelConnection; timeLine.ShowWarning += TimeLine_ShowWarning; + timeLine.DataLoaded += TimeLine_DataLoaded; warningBlock.Visibility = Visibility.Collapsed; AddressBarVM = (AddressBarViewModel)FindResource("AddressBarVM"); @@ -84,6 +90,14 @@ public FrameCapture() FunctionInstanceVM = (FunctionInstanceViewModel)FindResource("FunctionInstanceVM"); CaptureSettingsVM = (CaptureSettingsViewModel)FindResource("CaptureSettingsVM"); + var plotPanelsSettingsViewModel = (PlotPanelsSettingsViewModel)FindResource("CountersSettingsVM"); + plotPanelsSettingsViewModel.CustomPanels = _plotPanels; + if (string.IsNullOrEmpty(plotPanelsSettingsViewModel.CurrentPath)) + plotPanelsSettingsViewModel.CurrentPath = "counters.json"; + + var plotPanelsSettings = PlotPanelsSettingsStorage.Load(plotPanelsSettingsViewModel.CurrentPath); + CreatePlotPanels(plotPanelsSettings); + FunctionSamplingVM = (SamplingViewModel)FindResource("FunctionSamplingVM"); SysCallsSamplingVM = (SamplingViewModel)FindResource("SysCallsSamplingVM"); @@ -143,17 +157,34 @@ private void TimeLine_NewConnection(object sender, RoutedEventArgs e) AddressBarVM?.Update(args.Connection); } + private void TimeLine_DataLoaded(object sender, RoutedEventArgs e) + { + foreach (var plotPanel in _plotPanels) + plotPanel.SetModel(timeLine.Counters); + } + private void OpenFrame(object source, FocusFrameEventArgs args) { Data.Frame frame = args.Frame; - - if (frame is EventFrame) + + if (frame is EventFrame eventFrame) + { + var group = eventFrame.Group; + EventThreadViewControl.Highlight(frame as EventFrame, null); - if (frame is EventFrame) - { - EventFrame eventFrame = frame as EventFrame; - FrameGroup group = eventFrame.Group; + if (args.FocusPlot) + { + var interval = (IDurable)frame; + + var startRelative = interval.Start - frame.Group.Board.TimeSlice.Start; + var startRelativeMs = group.Board.TimeSettings.TicksToMs * startRelative; + + foreach (var plotPanel in _plotPanels) + { + plotPanel.Zoom(startRelativeMs, interval.Duration); + } + } if (eventFrame.RootEntry != null) { @@ -235,6 +266,11 @@ private void ClearButton_Click(object sender, System.Windows.RoutedEventArgs e) SysCallInfoControl.DataContext = null; //SamplingTreeControl.SetDescription(null, null); + + foreach (var plotPanel in _plotPanels) + plotPanel.Clear(); + + timeLine.Counters.Clear(); MainViewModel vm = DataContext as MainViewModel; if (vm.IsCapturing) @@ -301,5 +337,113 @@ private void OnShowDebugInfoCommandExecuted(object sender, ExecutedRoutedEventAr DebugInfoPopup.DataContext = vm; DebugInfoPopup.IsOpen = true; } + + /// + /// Gets called when a different counters layout is loaded + /// + private void OnPlotPanelsSettingsLoaded(List plotPanelsSerialized) + { + CreatePlotPanels(plotPanelsSerialized); + } + + private void CreatePlotPanels(List plotPanelsSerialized) + { + PlotsPane.Children.Clear(); + _plotPanels.Clear(); + foreach (var plotPanelSerialized in plotPanelsSerialized) + AddPlotPanel(plotPanelSerialized.Title, plotPanelSerialized.Counters); + } + + /// + /// Gets called when "Create panel" button is being pressed + /// + private void OnCreatePanel(string title) + { + AddPlotPanel(title, null); + PlotsPane.SelectedContentIndex = PlotsPane.Children.Count - 1; + } + + /// + /// Create a new panel. + /// + /// Panel's title + /// Optional, pre selected counters + private void AddPlotPanel(string title, List selectedCounters) + { + var viewModel = new PlotsViewModel(title, timeLine.Counters); + var view = new PlotView + { + DataContext = viewModel + }; + view.PlotClicked += (s, e) => + { + var frame = GetFrame(timeLine.Frames, e.Time); + if (frame != null) + RaiseEvent(new FocusFrameEventArgs(GlobalEvents.FocusFrameEvent, frame, focusPlot: false)); + }; + if (selectedCounters != null) + { + foreach (var selectedCounter in selectedCounters) + { + var color = Color.FromArgb(selectedCounter.Color.A, selectedCounter.Color.R, selectedCounter.Color.G, selectedCounter.Color.B); + viewModel.SelectCounter(selectedCounter.Key, selectedCounter.Name, new SolidColorBrush(color), selectedCounter.DataUnits, selectedCounter.ViewUnits); + } + } + + var anchorable = new LayoutAnchorable + { + CanClose = true, + Content = view, + CanHide = false + }; + + var titleBinding = new Binding(nameof(viewModel.Title)) + { + Mode = BindingMode.OneWay, + Source = viewModel + }; + BindingOperations.SetBinding(anchorable, LayoutContent.TitleProperty, titleBinding); + + // Restoring layout because when an user closes a tab this elements get removed from the layout + if (PlotsPaneGroup.Parent == null) + mainPanel.Children.Add(PlotsPaneGroup); + if (PlotsPane.Parent == null) + PlotsPaneGroup.Children.Add(PlotsPane); + + PlotsPane.Children.Add(anchorable); + + anchorable.Closed += (s,e) => + { + _plotPanels.Remove(viewModel); + }; + + _plotPanels.Add(viewModel); + } + + private static Frame GetFrame(FrameCollection frameCollection, double time) + { + // binary search + var left = 0; + var right = frameCollection.Count - 1; + while (left <= right) + { + var middle = (left + right) / 2; + var frame = frameCollection[middle]; + + var interval = (IDurable)frame; + var startRelative = interval.Start - frame.Group.Board.TimeSlice.Start; + var startRelativeMs = frame.Group.Board.TimeSettings.TicksToMs * startRelative; + + if (startRelativeMs <= time && time <= startRelativeMs + interval.Duration) + return frame; + + if (startRelativeMs < time) + left = middle + 1; + else + right = middle - 1; + } + + return null; + } } } diff --git a/gui/Optick/Controls/FunctionSearch.xaml.cs b/gui/Optick/Controls/FunctionSearch.xaml.cs index bee33455..d4f715ee 100644 --- a/gui/Optick/Controls/FunctionSearch.xaml.cs +++ b/gui/Optick/Controls/FunctionSearch.xaml.cs @@ -97,7 +97,7 @@ private void Flush() if (maxFrame != null && maxEntry != null) { EventNode maxNode = maxFrame.Root.FindNode(maxEntry); - RaiseEvent(new FocusFrameEventArgs(GlobalEvents.FocusFrameEvent, new EventFrame(maxFrame, maxNode), null)); + RaiseEvent(new FocusFrameEventArgs(GlobalEvents.FocusFrameEvent, new EventFrame(maxFrame, maxNode))); } } } diff --git a/gui/Optick/Controls/Settings.cs b/gui/Optick/Controls/Settings.cs index c37ba426..883d474d 100644 --- a/gui/Optick/Controls/Settings.cs +++ b/gui/Optick/Controls/Settings.cs @@ -47,7 +47,9 @@ public class LocalSettings public Platform.Connection LastConnection { get; set; } public ThreadViewSettings ThreadSettings { get; set; } = new ThreadViewSettings(); - public string TempDirectoryPath => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Optick\\Temp\\"); + public string PlotPanelsSettingsFile { get; set; } + + public string TempDirectoryPath => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Optick\\Temp\\"); public LocalSettings() { diff --git a/gui/Optick/Converters/IsNotEmptyConverter.cs b/gui/Optick/Converters/IsNotEmptyConverter.cs new file mode 100644 index 00000000..836b4b52 --- /dev/null +++ b/gui/Optick/Converters/IsNotEmptyConverter.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections; +using System.Globalization; +using System.Windows; +using System.Windows.Data; + +namespace Profiler +{ + public class IsNotEmptyConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return ((ICollection)value).Count > 0 ? Visibility.Visible : Visibility.Collapsed; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/gui/Optick/Converters/IsNotNullConverter.cs b/gui/Optick/Converters/IsNotNullConverter.cs new file mode 100644 index 00000000..542d29ac --- /dev/null +++ b/gui/Optick/Converters/IsNotNullConverter.cs @@ -0,0 +1,19 @@ +using System; +using System.Globalization; +using System.Windows.Data; + +namespace Profiler +{ + public class IsNotNullConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return value != null; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/gui/Optick/Optick.csproj b/gui/Optick/Optick.csproj index f6666088..7e545a17 100644 --- a/gui/Optick/Optick.csproj +++ b/gui/Optick/Optick.csproj @@ -133,6 +133,9 @@ ..\packages\Autofac.4.8.1\lib\net45\Autofac.dll + + ..\packages\PixiEditor.ColorPicker.3.2.0\lib\net451\ColorPicker.dll + ..\packages\ControlzEx.3.0.2.4\lib\net45\ControlzEx.dll @@ -171,6 +174,9 @@ ..\packages\MahApps.Metro.1.6.5\lib\net46\MahApps.Metro.dll + + ..\packages\Microsoft.Xaml.Behaviors.Wpf.1.1.31\lib\net45\Microsoft.Xaml.Behaviors.dll + ..\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll @@ -281,6 +287,15 @@ TagsControl.xaml + + + + + + + + + SearchBox.xaml @@ -298,6 +313,12 @@ CaptureSettingsView.xaml + + PlotPanelsSettingsView.xaml + + + PlotView.xaml + FunctionDescriptionView.xaml @@ -397,6 +418,8 @@ Designer MSBuild:Compile + + Designer MSBuild:Compile @@ -575,6 +598,14 @@ + + + + + + PreserveNewest + + {2d34544c-2df2-4b20-a43a-6c8d2df3dd82} diff --git a/gui/Optick/PlotPanelsSettingsStorage.cs b/gui/Optick/PlotPanelsSettingsStorage.cs new file mode 100644 index 00000000..815d4108 --- /dev/null +++ b/gui/Optick/PlotPanelsSettingsStorage.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Windows.Media; +using Newtonsoft.Json; +using Profiler.ViewModels; +using Profiler.ViewModels.Plots; + +namespace Profiler +{ + public static class PlotPanelsSettingsStorage + { + public static List Load(string path) + { + if (!File.Exists(path)) + return new List(); + + using (var file = File.Open(path, FileMode.Open, FileAccess.Read)) + using (var reader = new StreamReader(file)) + { + return JsonConvert.DeserializeObject>(reader.ReadToEnd()) ?? new List(); + } + } + + public static void Save(List plotPanels, string path) + { + var customPlanesStorage = new List(); + + foreach (var customPanel in plotPanels) + { + var serializedPlane = new PlotPanelSerialized + { + Title = customPanel.Title, + Counters = new List(), + }; + foreach (var selectedCounterViewModel in customPanel.SelectedCounterViewModels) + { + var color = ((SolidColorBrush)selectedCounterViewModel.Color).Color; + serializedPlane.Counters.Add(new PlotPanelSerialized.CounterSerialized + { + Key = selectedCounterViewModel.Key, + Name = selectedCounterViewModel.Name, + Color = new PlotPanelSerialized.CounterSerialized.ColorSerialized + { + A = color.A, + R = color.R, + G = color.G, + B = color.B, + } , + DataUnits = selectedCounterViewModel.DataUnits, + ViewUnits = selectedCounterViewModel.ViewUnits + }); + } + + customPlanesStorage.Add(serializedPlane); + } + + var serializedSettings = JsonConvert.SerializeObject(customPlanesStorage); + using (var file = File.Open(path, FileMode.Create)) + using (var writer = new StreamWriter(file)) + writer.Write(serializedSettings); + } + } + + public class PlotPanelSerialized + { + public string Title { get; set; } + + public List Counters { get; set; } + + public class CounterSerialized + { + public string Key { get; set; } + public string Name { get; set; } + public ColorSerialized Color { get; set; } + public Units DataUnits { get; set; } + public Units ViewUnits { get; set; } + + public class ColorSerialized + { + public byte A { get; set; } + public byte R { get; set; } + public byte G { get; set; } + public byte B { get; set; } + } + } + } +} \ No newline at end of file diff --git a/gui/Optick/Plots/PlotPanelsSettingsViewModel.cs b/gui/Optick/Plots/PlotPanelsSettingsViewModel.cs new file mode 100644 index 00000000..c6417705 --- /dev/null +++ b/gui/Optick/Plots/PlotPanelsSettingsViewModel.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using Profiler.Controls; +using Profiler.InfrastructureMvvm; +using Profiler.ViewModels.Plots; + +namespace Profiler.ViewModels +{ + public class PlotPanelsSettingsViewModel : BaseViewModel + { + public List CustomPanels { get; set; } + + public string CurrentPath + { + get => Settings.LocalSettings.Data.PlotPanelsSettingsFile; + set + { + Settings.LocalSettings.Data.PlotPanelsSettingsFile = value; + Settings.LocalSettings.Save(); + OnPropertyChanged(); + } + } + } +} \ No newline at end of file diff --git a/gui/Optick/Plots/PlotsViewModel.cs b/gui/Optick/Plots/PlotsViewModel.cs new file mode 100644 index 00000000..ed8992ea --- /dev/null +++ b/gui/Optick/Plots/PlotsViewModel.cs @@ -0,0 +1,322 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Windows; +using System.Windows.Media; +using Profiler.Data; +using Profiler.InfrastructureMvvm; + +namespace Profiler.ViewModels.Plots +{ + public class PlotsViewModel : BaseViewModel + { + private readonly Random _rnd = new Random(); + + private Dictionary _model; + private SelectCounterViewModel _selectCounterViewModel; + private ObservableCollection _selectedCounterViewModels; + private double _mousePositionX; + private double _mousePositionY; + private Visibility _hoverVisibility; + private double _yMin; + private double _yMax; + private double _height; + private string _title; + private bool _isAutoFitEnabled; + private double _xMin; + private double _width; + + + public PlotsViewModel(string title, Dictionary model) + { + Title = title; + _selectCounterViewModel = new SelectCounterViewModel + { + Color = Color.FromRgb((byte)_rnd.Next(0, 255), (byte)_rnd.Next(0, 255), (byte)_rnd.Next(0, 255)) + }; + _selectedCounterViewModels = new ObservableCollection(); + + SetModel(model); + } + + public HashSet SelectedCounterKeys { get; } = new HashSet(); + + public string Title + { + get => _title; + set => SetProperty(ref _title, value); + } + + public SelectCounterViewModel SelectCounterViewModel + { + get => _selectCounterViewModel; + set => SetProperty(ref _selectCounterViewModel, value); + } + + public double MousePositionX + { + get => _mousePositionX; + set => SetProperty(ref _mousePositionX, value); + } + + public double MousePositionY + { + get => _mousePositionY; + set => SetProperty(ref _mousePositionY, value); + } + + public ObservableCollection SelectedCounterViewModels + { + get => _selectedCounterViewModels; + set => SetProperty(ref _selectedCounterViewModels, value); + } + + public Visibility HoverVisibility + { + get => _hoverVisibility; + set => SetProperty(ref _hoverVisibility, value); + } + + public double YMin + { + get => _yMin; + set => SetAxis(nameof(YMin), false, ref _yMin, value); + } + + public double YMax + { + get => _yMax; + set => SetAxis(nameof(YMax), false, ref _yMax, value); + } + + public double Height + { + get => _height; + set + { + SetProperty(ref _height, value); + SetAxis(nameof(YMax), true, ref _yMax, _yMin + value); + } + } + + public bool IsAutoFitEnabled + { + get => _isAutoFitEnabled; + set => SetProperty(ref _isAutoFitEnabled, value); + } + + public double XMin + { + get => _xMin; + set => SetProperty(ref _xMin, value); + } + + public double Width + { + get => _width; + set => SetProperty(ref _width, value); + } + + public void SetModel(Dictionary model) + { + _model = model; + SelectCounterViewModel.AvailableCounters.Clear(); + + var availableCounter = new List(); + foreach (var counterPair in _model.Where(c => !SelectedCounterKeys.Contains(c.Key))) + availableCounter.Add(new SelectCounterViewModel.AvailableCounterViewModel(counterPair.Key, counterPair.Value.DisplayName)); + + SelectCounterViewModel.AvailableCounters = new ObservableCollection(availableCounter.OrderBy(c => c.Key)); + SelectCounterViewModel.TrySelectFirstCounter(); + + foreach (var counterViewModel in SelectedCounterViewModels) + { + if (!_model.TryGetValue(counterViewModel.Key, out var counterModel)) + continue; + + counterViewModel.Model = counterModel; + } + + UpdatePoints(); + IsAutoFitEnabled = true; + } + + public void SelectCurrentCounter() + { + var counterToAdd = _model[SelectCounterViewModel.SelectedCounter]; + SelectCounter(counterToAdd.Name, counterToAdd.Name, new SolidColorBrush(SelectCounterViewModel.Color), SelectCounterViewModel.UnitsViewModel.SelectedDataUnits, SelectCounterViewModel.UnitsViewModel.SelectedViewUnits); + } + + public void SelectCounter(string counterKey, string name, SolidColorBrush brush, Units dataUnits, Units viewUnits) + { + _model.TryGetValue(counterKey, out var counterModel); + + var counterVM = new SelectedCounterViewModel( + counterModel, + counterKey, + name, + brush, + RemoveSelectedCounter, + default, + default, + dataUnits, + viewUnits); + + SelectedCounterViewModels.Add(counterVM); + SelectedCounterKeys.Add(counterKey); + + SelectCounterViewModel.RemoveFromAvailabile(counterKey); + SelectCounterViewModel.TrySelectFirstCounter(); + + SelectCounterViewModel.Color = Color.FromRgb((byte)_rnd.Next(0, 255), (byte)_rnd.Next(0, 255), (byte)_rnd.Next(0, 255)); + IsAutoFitEnabled = true; + } + + private void RemoveSelectedCounter(SelectedCounterViewModel counter) + { + SelectedCounterViewModels.Remove(counter); + + // if we have only layout without data + if (!_model.TryGetValue(counter.Key, out var removedCounter)) + return; + SelectCounterViewModel.AvailableCounters.Add(new SelectCounterViewModel.AvailableCounterViewModel(removedCounter.Name, removedCounter.DisplayName)); + SelectCounterViewModel.AvailableCounters = new ObservableCollection(SelectCounterViewModel.AvailableCounters.OrderBy(c => c.Key)); + if (SelectCounterViewModel.SelectedCounter == null) + SelectCounterViewModel.TrySelectFirstCounter(); + } + + public void Clear() + { + foreach (var selectedCounterViewModel in _selectedCounterViewModels) + { + selectedCounterViewModel.Points.Clear(); + selectedCounterViewModel.HoverPoint = new Point(); + selectedCounterViewModel.HoverPointInScreenSpace = new Point(); + } + + _selectCounterViewModel.Clear(); + } + + /// + /// Refresh counters points to current model + /// + private void UpdatePoints() + { + foreach (var counterViewModel in SelectedCounterViewModels) + counterViewModel.UpdatePoints(); + } + + public void Zoom(double start, double duration) + { + var yMin = double.MaxValue; + var yMax = double.MinValue; + var yCalculated = false; + foreach (var selectedCounterViewModel in SelectedCounterViewModels) + { + if (selectedCounterViewModel.Points.Count == 0) + continue; + + var xIndexLeft = GetPointIndex(selectedCounterViewModel.Points, start); + var xIndexRight = GetPointIndex(selectedCounterViewModel.Points, start + duration); + + if (selectedCounterViewModel.Points.Count - 1 == xIndexLeft || xIndexRight == 0) + { + // zoom area is after/before points + // i am not sure what to do in this situation + } + else + { + var leftY = Interpolate(xIndexLeft, start, selectedCounterViewModel.Points); + yMin = Math.Min(yMin, leftY); + yMax = Math.Max(yMax, leftY); + + var rightY = selectedCounterViewModel.Points.Count - 1 != xIndexRight + ? Interpolate(xIndexRight, start + duration, selectedCounterViewModel.Points) + : selectedCounterViewModel.Points[xIndexRight].Y; + yMin = Math.Min(yMin, rightY); + yMax = Math.Max(yMax, rightY); + + // this points are inside interval + for (int xIndex = xIndexLeft + 1; xIndex <= xIndexRight; xIndex++) + { + var y = selectedCounterViewModel.Points[xIndex].Y; + + yMin = Math.Min(yMin, y); + yMax = Math.Max(yMax, y); + } + + yCalculated = true; + } + } + + XMin = start; + Width = duration; + + if (yCalculated) + { + YMin = yMin; + Height = yMax - yMin; + } + } + + private void SetAxis(string propertyName, bool skipHeight, ref double field, double value) + { + if (field == value) + return; + + field = value; + OnPropertyChanged(propertyName); + + if (!skipHeight) + { + var oldHeight = _height; + _height = YMax - YMin; + if (oldHeight != _height) + OnPropertyChanged(nameof(Height)); + } + } + + private double Interpolate(int closestLeftIndex, double value, PointCollection points) + { + var startLeftPoint = points[closestLeftIndex]; + var startRightPoint = points[closestLeftIndex + 1]; + var startT = (value - startLeftPoint.X) / (startRightPoint.X - startLeftPoint.X); + return startLeftPoint.Y + startT * (startRightPoint.Y - startLeftPoint.Y); + } + + public static int GetPointIndex(PointCollection points, double plotX) + { + var minIntervalIndex = 0; + var maxIntervalIndex = points.Count - 2; + while (true) + { + if (maxIntervalIndex < 0) + return 0; + + // we are out of last interval bounds + if (minIntervalIndex == points.Count - 1) + return points.Count - 1; + + var checkIntervalIndex = minIntervalIndex + (maxIntervalIndex - minIntervalIndex) / 2; + + var leftPoint = points[checkIntervalIndex]; + var rightPoint = points[checkIntervalIndex + 1]; + if (leftPoint.X <= plotX && plotX <= rightPoint.X) + // we take a left point as data + return checkIntervalIndex; + + if (leftPoint.X < plotX) + { + // right point index + minIntervalIndex = checkIntervalIndex + 1; + } + else if (leftPoint.X > plotX) + { + // left point index + maxIntervalIndex = checkIntervalIndex - 1; + } + } + } + } +} \ No newline at end of file diff --git a/gui/Optick/Plots/SelectCounterViewModel.cs b/gui/Optick/Plots/SelectCounterViewModel.cs new file mode 100644 index 00000000..2c9f9e5f --- /dev/null +++ b/gui/Optick/Plots/SelectCounterViewModel.cs @@ -0,0 +1,74 @@ +using System.Collections.ObjectModel; +using System.Windows.Media; +using Profiler.InfrastructureMvvm; + +namespace Profiler.ViewModels +{ + public class SelectCounterViewModel : BaseViewModel + { + private ObservableCollection _availableCounters = new ObservableCollection(); + private string _selectedCounter; + private Color _color; + + public SelectCounterViewModel() + { + UnitsViewModel = new UnitsViewModel(); + } + + public ObservableCollection AvailableCounters + { + get => _availableCounters; + set => SetProperty(ref _availableCounters, value); + } + + public Color Color + { + get => _color; + set => SetProperty(ref _color, value); + } + + public string SelectedCounter + { + get => _selectedCounter; + set => SetProperty(ref _selectedCounter, value); + } + + public UnitsViewModel UnitsViewModel { get; } + + public void Clear() + { + _availableCounters.Clear(); + _selectedCounter = null; + } + + public void TrySelectFirstCounter() + { + SelectedCounter = AvailableCounters.Count > 0 ? AvailableCounters[0].Key : null; + } + + public void RemoveFromAvailabile(string counterKeyToRemove) + { + for (var index = 0; index < AvailableCounters.Count; index++) + { + if (AvailableCounters[index].Key == counterKeyToRemove) + { + AvailableCounters.RemoveAt(index); + return; + } + } + } + + public class AvailableCounterViewModel + { + public AvailableCounterViewModel(string key, string name) + { + Key = key; + Name = name; + } + + public string Key { get; } + + public string Name { get; } + } + } +} \ No newline at end of file diff --git a/gui/Optick/Plots/SelectedCounterViewModel.cs b/gui/Optick/Plots/SelectedCounterViewModel.cs new file mode 100644 index 00000000..edba2e9d --- /dev/null +++ b/gui/Optick/Plots/SelectedCounterViewModel.cs @@ -0,0 +1,153 @@ +using System; +using System.Linq; +using System.Windows; +using System.Windows.Input; +using System.Windows.Media; +using Profiler.Data; +using Profiler.InfrastructureMvvm; + +namespace Profiler.ViewModels +{ + public class SelectedCounterViewModel : BaseViewModel + { + public const double SelectedPointEllipseSize = 9; + + private Point _hoverPoint; + private string _key; + private string _name; + private Brush _color; + private ICommand _removeCommand; + private PointCollection _points; + + public SelectedCounterViewModel(CounterModel model, string key, string name, Brush color, Action onRemove, + Point hoverPoint, Point hoverPointInScreenSpace, Units dataUnits, Units viewUnits) + { + Key = key; + Name = name; + Color = color; + Model = model; + _hoverPoint = hoverPoint; + HoverPointInScreenSpace = hoverPointInScreenSpace; + RemoveCommand = new RelayCommand(onRemove); + DataUnits = dataUnits; + ViewUnits = viewUnits; + UpdatePoints(); + } + + public CounterModel Model { get; set; } + + public string Key + { + get => _key; + set => _key = value; + } + + public string Name + { + get => _name; + set => _name = value; + } + + public Brush Color + { + get => _color; + set => _color = value; + } + + public PointCollection Points + { + get => _points; + set => SetProperty(ref _points, value); + } + + public Point HoverPoint + { + get => _hoverPoint; + set => SetProperty(ref _hoverPoint, value); + } + + public Point HoverPointInScreenSpace + { + get => _hoverPoint; + set => SetProperty(ref _hoverPoint, value); + } + + public Units DataUnits { get; } + + public Units ViewUnits { get; } + + public double EllipseSize => SelectedPointEllipseSize; + + public ICommand RemoveCommand + { + get => _removeCommand; + set => _removeCommand = value; + } + + public void UpdatePoints() + { + if (Model == null) + { + Points = new PointCollection(Array.Empty()); + return; + } + + var measurements = Model.Measurements; + var points = measurements.Select(m => + { + double simpleUnits = 0; + switch (DataUnits) + { + case Units.Byte: + simpleUnits = m.Value; + break; + case Units.KB: + simpleUnits = m.Value * 1024; + break; + case Units.MB: + simpleUnits = m.Value * 1024 * 1024; + break; + case Units.Nanosecond: + simpleUnits = m.Value; + break; + case Units.Millisecond: + simpleUnits = m.Value * 1000000; + break; + case Units.Second: + simpleUnits = m.Value * 1000000 * 1000; + break; + } + + double convertedValue = 0; + switch (ViewUnits) + { + case Units.Byte: + convertedValue = simpleUnits; + break; + case Units.KB: + convertedValue = simpleUnits / 1024; + break; + case Units.MB: + convertedValue = simpleUnits / 1024 / 1024; + break; + case Units.Nanosecond: + convertedValue = simpleUnits; + break; + case Units.Millisecond: + convertedValue = simpleUnits / 1000000; + break; + case Units.Second: + convertedValue = simpleUnits / 1000000 / 1000; + break; + default: + // Do not convert + convertedValue = m.Value; + break; + } + + return new Point(m.RelativeMSec, convertedValue); + }); + Points = new PointCollection(points); + } + } +} \ No newline at end of file diff --git a/gui/Optick/Plots/Units.cs b/gui/Optick/Plots/Units.cs new file mode 100644 index 00000000..def44717 --- /dev/null +++ b/gui/Optick/Plots/Units.cs @@ -0,0 +1,14 @@ +namespace Profiler.ViewModels +{ + public enum Units + { + Byte, + KB, + MB, + Nanosecond, + Millisecond, + Second, + Count, + Percent + } +} \ No newline at end of file diff --git a/gui/Optick/Plots/UnitsViewModel.cs b/gui/Optick/Plots/UnitsViewModel.cs new file mode 100644 index 00000000..4106d6df --- /dev/null +++ b/gui/Optick/Plots/UnitsViewModel.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using Profiler.InfrastructureMvvm; + +namespace Profiler.ViewModels +{ + public class UnitsViewModel : BaseViewModel + { + private Units _selectedDataUnits; + private Units _selectedViewUnits; + private List _viewUnits; + + public UnitsViewModel() + { + SelectedDataUnits = Units.Byte; + } + + private static readonly List SizeUnits = new List + { + Units.Byte, + Units.KB, + Units.MB + }; + + private static readonly List TimeUnits = new List + { + Units.Nanosecond, + Units.Millisecond, + Units.Second + }; + + public List DataUnits => new List + { + Units.Count, + Units.Percent, + Units.Byte, + Units.KB, + Units.MB, + Units.Nanosecond, + Units.Millisecond, + Units.Second, + }; + + public Units SelectedDataUnits + { + get => _selectedDataUnits; + set + { + var oldUnits = ViewUnits; + switch (value) + { + case Units.Byte: + case Units.KB: + case Units.MB: + ViewUnits = SizeUnits; + break; + case Units.Nanosecond: + case Units.Millisecond: + case Units.Second: + ViewUnits = TimeUnits; + break; + default: + ViewUnits = new List + { + value + }; + break; + } + + if (oldUnits != ViewUnits) + { + SelectedViewUnits = ViewUnits[0]; + } + + SetProperty(ref _selectedDataUnits, value); + } + } + + public List ViewUnits + { + get => _viewUnits; + set => SetProperty(ref _viewUnits, value); + } + + public Units SelectedViewUnits + { + get => _selectedViewUnits; + set => SetProperty(ref _selectedViewUnits, value); + } + } +} \ No newline at end of file diff --git a/gui/Optick/TimeLine/Icons/draw.png b/gui/Optick/TimeLine/Icons/draw.png new file mode 100644 index 0000000000000000000000000000000000000000..d34e1f09a8c4da5e9573755055073fbae8b945dc GIT binary patch literal 816 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTCmUKs7M+SzC{oH>NS%G|oWRD45dJguM!v-tY$DUh!@P+6=(yLU`z6LcLCBs@Y8vBJ&@uo z@Q5r1(g|SvA=~LZ0|S$hr;B4q#=W;U{5yODWe$Age|%nNLQt@p>4^zbkFF8%_3i!B zIdwzo!}DR08q#K)5*35I+SGD%bVS0$CW(j%Npq`m8%r7gG@o24_I=--^5=KV-}9cW zEZSfCs`UKJfBW{A*FB$Ct)~hCTem#qYTKZ@?TpWp^@}HMlfNMuy-M=e2jj^OtoQ#i z8((aj_vL$|gV6jWZa;Nnw4L~oi`v+r5?(%RpbRq_(!axNVnERLA`;*=1Abo@1dh$5u(6xE{S>>X!fO zZF9HH+9jNJTSe|6huHR6Ha2tA66(6`J5$#x@gLr)Aa7{adm>%t=$|vSi%uN=9kal6 zMz`;d#!joC^MB$x0-rog)4L&i=H!?1hdOS|N$Ez9fvS!tYt3s#Xn{M6f z8gF;#?9o`C7?^X=&9Y^EqsqMm=bW8xk^RpbRgPVd2dceQs5#+?`IlqUvfG-M?F-{s zY`ra_qp#_;^#|4IM}&31sU|YUJ(Rn~_UpIdJntm`oR>PsjglVkXgGIgM(|FScgLo! zyQKN&nq}kd8@iTB>q4r#q`P$EQg=S#G0@yIQFQCZ8;>f@9v2F8+H~l~xU65Y@AcUe zE5!ckL@bp)uIj&xf0FZAYtNl)yLH2Zf`W{y|1cG>c+TZ{=WrdEj8scpBT7;dOH!?p zi&B9UgOP!ek*LrImrXwt<0_fq~5AmfI*Aa`RI%(<*Um$USG14%EQl M>FVdQ&MBb@09MRUEdT%j literal 0 HcmV?d00001 diff --git a/gui/Optick/TimeLine/TimeLine.xaml.cs b/gui/Optick/TimeLine/TimeLine.xaml.cs index 9847113b..79f59505 100644 --- a/gui/Optick/TimeLine/TimeLine.xaml.cs +++ b/gui/Optick/TimeLine/TimeLine.xaml.cs @@ -17,19 +17,11 @@ using System.IO; using System.Windows.Threading; using Profiler.Data; -using Frame = Profiler.Data.Frame; -using Microsoft.Win32; -using System.Xml; -using System.Net.Cache; -using System.Reflection; -using System.Diagnostics; -using System.Web; -using System.Net.NetworkInformation; -using System.ComponentModel; -using System.IO.Compression; using System.Threading.Tasks; using System.Security; +using Microsoft.Win32; using Profiler.Controls; +using Frame = Profiler.Data.Frame; namespace Profiler { @@ -40,6 +32,8 @@ namespace Profiler /// public partial class TimeLine : UserControl { + private bool _scrollingToEnd; + FrameCollection frames = new FrameCollection(); Thread socketThread = null; @@ -52,6 +46,8 @@ public FrameCollection Frames return frames; } } + + public Dictionary Counters { get; private set; } = new Dictionary(); public TimeLine() { @@ -127,7 +123,10 @@ private bool Open(String name, Stream stream) } frames.UpdateName(name); - frames.Flush(); + Counters = frames.Flush(); + if (frames.Count > 0) + RaiseEvent(new DataLoadedEventArgs()); + ScrollToEnd(); return true; @@ -206,7 +205,10 @@ private bool ApplyResponse(DataResponse response) StatusText.Visibility = System.Windows.Visibility.Collapsed; lock (frames) { - frames.Flush(); + Counters = frames.Flush(); + if (frames.Count > 0) + RaiseEvent(new DataLoadedEventArgs()); + ScrollToEnd(); } break; @@ -257,8 +259,10 @@ private void ScrollToEnd() { if (frames.Count > 0) { + _scrollingToEnd = true; frameList.SelectedItem = frames[frames.Count - 1]; frameList.ScrollIntoView(frames[frames.Count - 1]); + _scrollingToEnd = false; } } @@ -278,7 +282,7 @@ public void RecieveMessage() #region FocusFrame private void FocusOnFrame(Data.Frame frame) { - FocusFrameEventArgs args = new FocusFrameEventArgs(GlobalEvents.FocusFrameEvent, frame); + FocusFrameEventArgs args = new FocusFrameEventArgs(GlobalEvents.FocusFrameEvent, frame, focusPlot: !_scrollingToEnd); RaiseEvent(args); } @@ -310,14 +314,23 @@ public CancelConnectionEventArgs() : base(CancelConnectionEvent) { } } + + public class DataLoadedEventArgs : RoutedEventArgs + { + public DataLoadedEventArgs() : base(DataLoadedEvent) + { + } + } public delegate void ShowWarningEventHandler(object sender, ShowWarningEventArgs e); public delegate void NewConnectionEventHandler(object sender, NewConnectionEventArgs e); public delegate void CancelConnectionEventHandler(object sender, CancelConnectionEventArgs e); + public delegate void DataLoadedEventHandler(object sender, DataLoadedEventArgs e); public static readonly RoutedEvent ShowWarningEvent = EventManager.RegisterRoutedEvent("ShowWarning", RoutingStrategy.Bubble, typeof(ShowWarningEventArgs), typeof(TimeLine)); public static readonly RoutedEvent NewConnectionEvent = EventManager.RegisterRoutedEvent("NewConnection", RoutingStrategy.Bubble, typeof(NewConnectionEventHandler), typeof(TimeLine)); public static readonly RoutedEvent CancelConnectionEvent = EventManager.RegisterRoutedEvent("CancelConnection", RoutingStrategy.Bubble, typeof(CancelConnectionEventHandler), typeof(TimeLine)); + public static readonly RoutedEvent DataLoadedEvent = EventManager.RegisterRoutedEvent("DataLoaded", RoutingStrategy.Bubble, typeof(DataLoadedEventHandler), typeof(TimeLine)); public event RoutedEventHandler FocusFrame { @@ -342,6 +355,13 @@ public event RoutedEventHandler CancelConnection add { AddHandler(CancelConnectionEvent, value); } remove { RemoveHandler(CancelConnectionEvent, value); } } + + public event RoutedEventHandler DataLoaded + { + add { AddHandler(DataLoadedEvent, value); } + remove { RemoveHandler(DataLoadedEvent, value); } + } + #endregion private void frameList_SelectionChanged(object sender, SelectionChangedEventArgs e) @@ -388,7 +408,7 @@ public void Save(Stream stream) public String Save() { - SaveFileDialog dlg = new SaveFileDialog(); + var dlg = new SaveFileDialog(); dlg.Filter = "Optick Performance Capture (*.opt)|*.opt"; dlg.Title = "Where should I save profiler results?"; diff --git a/gui/Optick/ViewModels/FunctionSummaryViewModel.cs b/gui/Optick/ViewModels/FunctionSummaryViewModel.cs index 46e424b7..d5d62764 100644 --- a/gui/Optick/ViewModels/FunctionSummaryViewModel.cs +++ b/gui/Optick/ViewModels/FunctionSummaryViewModel.cs @@ -1,91 +1,91 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Media; -using LiveCharts; -using LiveCharts.Wpf; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Media; +using LiveCharts; +using LiveCharts.Wpf; using Profiler.Controls; -using Profiler.Data; -using Profiler.InfrastructureMvvm; +using Profiler.Data; +using Profiler.InfrastructureMvvm; using Profiler.Views; -namespace Profiler.ViewModels -{ - public class FunctionViewModel : BaseViewModel - { - private FrameGroup Group { get; set; } - private EventDescription Description { get; set; } - - private String _title; - public String Title - { - get { return _title; } - set { SetProperty(ref _title, value); } - } - - private bool _isLoading = false; - public bool IsLoading - { - get { return _isLoading; } - set { SetProperty(ref _isLoading, value); } - } - - private FunctionStats _stats; - public FunctionStats Stats - { - get { return _stats; } - set { SetProperty(ref _stats, value); } - } - - private FunctionStats.Sample _hoverSample; +namespace Profiler.ViewModels +{ + public class FunctionViewModel : BaseViewModel + { + private FrameGroup Group { get; set; } + private EventDescription Description { get; set; } + + private String _title; + public String Title + { + get { return _title; } + set { SetProperty(ref _title, value); } + } + + private bool _isLoading = false; + public bool IsLoading + { + get { return _isLoading; } + set { SetProperty(ref _isLoading, value); } + } + + private FunctionStats _stats; + public FunctionStats Stats + { + get { return _stats; } + set { SetProperty(ref _stats, value); } + } + + private FunctionStats.Sample _hoverSample; public FunctionStats.Sample HoverSample { get { return _hoverSample; } set { SetProperty(ref _hoverSample, value); } } - public FunctionStats.Origin Origin { get; set; } - - public virtual void OnLoaded(FunctionStats stats) - { - Application.Current.Dispatcher.BeginInvoke(new Action(() => - { - Stats = stats; - IsLoading = false; - OnChanged?.Invoke(); - })); - } - - public void Load(FrameGroup group, EventDescription desc) - { - if (Group == group && Description == desc) - return; - - Group = group; - Description = desc; - - Task.Run(() => - { - Application.Current.Dispatcher.BeginInvoke(new Action(() => - { - IsLoading = true; - })); - - FunctionStats frameStats = null; - - if (group != null && desc != null) - { - frameStats = new FunctionStats(group, desc); - frameStats.Load(Origin); - } - - OnLoaded(frameStats); - }); + public FunctionStats.Origin Origin { get; set; } + + public virtual void OnLoaded(FunctionStats stats) + { + Application.Current.Dispatcher.BeginInvoke(new Action(() => + { + Stats = stats; + IsLoading = false; + OnChanged?.Invoke(); + })); + } + + public void Load(FrameGroup group, EventDescription desc) + { + if (Group == group && Description == desc) + return; + + Group = group; + Description = desc; + + Task.Run(() => + { + Application.Current.Dispatcher.BeginInvoke(new Action(() => + { + IsLoading = true; + })); + + FunctionStats frameStats = null; + + if (group != null && desc != null) + { + frameStats = new FunctionStats(group, desc); + frameStats.Load(Origin); + } + + OnLoaded(frameStats); + }); } public void OnDataClick(FrameworkElement parent, List indices) @@ -103,7 +103,7 @@ public void OnDataClick(FrameworkElement parent, List indices) if (maxEntry != null) { EventNode maxNode = maxEntry.Frame.Root.FindNode(maxEntry); - parent.RaiseEvent(new FocusFrameEventArgs(GlobalEvents.FocusFrameEvent, new EventFrame(maxEntry.Frame, maxNode), null)); + parent.RaiseEvent(new FocusFrameEventArgs(GlobalEvents.FocusFrameEvent, new EventFrame(maxEntry.Frame, maxNode))); } } } @@ -123,61 +123,61 @@ public void OnDataHover(FrameworkElement parent, int index) public delegate void OnChangedHandler(); public event OnChangedHandler OnChanged; - } - - public class FunctionInstanceViewModel : FunctionViewModel - { - } - - public class FunctionSummaryViewModel : FunctionViewModel - { + } + + public class FunctionInstanceViewModel : FunctionViewModel + { + } + + public class FunctionSummaryViewModel : FunctionViewModel + { public FunctionSummaryViewModel() { } - - public class FunctionSummaryItem : BaseViewModel - { - public Style Icon { get; set; } - public String Name { get; set; } - public String Description { get; set; } - } - - public class MinMaxFunctionSummaryItem : FunctionSummaryItem - { - public Brush Foreground { get; set; } - public double MaxValue { get; set; } - public double MinValue { get; set; } - public double AvgValue { get; set; } - - public MinMaxFunctionSummaryItem(IEnumerable values) - { - int count = values.Count(); - - if (count > 0) - { - MinValue = values.Min(); - MaxValue = values.Max(); - AvgValue = values.Sum() / count; - } - } - } - - public class HyperlinkFunctionSummaryItem : FunctionSummaryItem - { - public FileLine Path { get; set; } - } - - private ObservableCollection _summaryItems = new ObservableCollection(); - public ObservableCollection SummaryItems - { - get { return _summaryItems; } - set { SetProperty(ref _summaryItems, value); } - } - - private ObservableCollection GenerateSummaryItems(FunctionStats frameStats) - { - ObservableCollection items = new ObservableCollection(); - + + public class FunctionSummaryItem : BaseViewModel + { + public Style Icon { get; set; } + public String Name { get; set; } + public String Description { get; set; } + } + + public class MinMaxFunctionSummaryItem : FunctionSummaryItem + { + public Brush Foreground { get; set; } + public double MaxValue { get; set; } + public double MinValue { get; set; } + public double AvgValue { get; set; } + + public MinMaxFunctionSummaryItem(IEnumerable values) + { + int count = values.Count(); + + if (count > 0) + { + MinValue = values.Min(); + MaxValue = values.Max(); + AvgValue = values.Sum() / count; + } + } + } + + public class HyperlinkFunctionSummaryItem : FunctionSummaryItem + { + public FileLine Path { get; set; } + } + + private ObservableCollection _summaryItems = new ObservableCollection(); + public ObservableCollection SummaryItems + { + get { return _summaryItems; } + set { SetProperty(ref _summaryItems, value); } + } + + private ObservableCollection GenerateSummaryItems(FunctionStats frameStats) + { + ObservableCollection items = new ObservableCollection(); + if (frameStats != null) { items.Add(new MinMaxFunctionSummaryItem(frameStats.Samples.Select(s => s.Total)) @@ -221,24 +221,24 @@ private ObservableCollection GenerateSummaryItems(FunctionS Description = "Open Source Code", Path = frameStats.Description.Path, }); - } - - return items; - } - - public override void OnLoaded(FunctionStats frameStats) - { - ObservableCollection items = GenerateSummaryItems(frameStats); - - Application.Current.Dispatcher.BeginInvoke(new Action(() => - { - SummaryItems = items; - })); - - base.OnLoaded(frameStats); - } - - public double StrokeOpacity { get; set; } = 1.0; - public double StrokeThickness { get; set; } = 1.0; - } -} + } + + return items; + } + + public override void OnLoaded(FunctionStats frameStats) + { + ObservableCollection items = GenerateSummaryItems(frameStats); + + Application.Current.Dispatcher.BeginInvoke(new Action(() => + { + SummaryItems = items; + })); + + base.OnLoaded(frameStats); + } + + public double StrokeOpacity { get; set; } = 1.0; + public double StrokeThickness { get; set; } = 1.0; + } +} diff --git a/gui/Optick/Views/PlotPanelsSettingsView.xaml b/gui/Optick/Views/PlotPanelsSettingsView.xaml new file mode 100644 index 00000000..2514110b --- /dev/null +++ b/gui/Optick/Views/PlotPanelsSettingsView.xaml @@ -0,0 +1,37 @@ + + + + + Plots panels settings + + + + + + Custom Panel + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/gui/Optick/Views/PlotPanelsSettingsView.xaml.cs b/gui/Optick/Views/PlotPanelsSettingsView.xaml.cs new file mode 100644 index 00000000..9ce8f495 --- /dev/null +++ b/gui/Optick/Views/PlotPanelsSettingsView.xaml.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Windows; +using System.Windows.Controls; +using Microsoft.Win32; +using Profiler.ViewModels; + +namespace Profiler.Views +{ + public partial class PlotPanelsSettingsView : UserControl + { + public PlotPanelsSettingsView() + { + InitializeComponent(); + } + + public event Action> PlotPanelsSettingsLoaded; + public event Action CreatePanel; + + private void SaveCurrent_OnClick(object sender, RoutedEventArgs e) + { + var vm = (PlotPanelsSettingsViewModel)DataContext; + PlotPanelsSettingsStorage.Save(vm.CustomPanels, vm.CurrentPath); + } + + private void SaveAs_OnClick(object sender, RoutedEventArgs e) + { + var vm = (PlotPanelsSettingsViewModel)DataContext; + + var saveFileDialog = new SaveFileDialog + { + AddExtension = true, + Filter = "Plot panels settings (*.json)|*.json" + }; + var result = saveFileDialog.ShowDialog(); + if (result != true) + return; + + vm.CurrentPath = saveFileDialog.FileName; + PlotPanelsSettingsStorage.Save(vm.CustomPanels, vm.CurrentPath); + } + + private void Load_OnClick(object sender, RoutedEventArgs e) + { + var vm = (PlotPanelsSettingsViewModel)DataContext; + + var openFileDialog = new OpenFileDialog + { + Filter = "(*.json)|*.json" + }; + var result = openFileDialog.ShowDialog(); + if (result != true) + return; + + vm.CurrentPath = openFileDialog.FileName; + var plotsSettings = PlotPanelsSettingsStorage.Load(vm.CurrentPath); + PlotPanelsSettingsLoaded?.Invoke(plotsSettings); + + } + + private void CreatePanel_OnClick(object sender, RoutedEventArgs e) + { + CreatePanel?.Invoke(txtBxTitle.Text); + } + } +} \ No newline at end of file diff --git a/gui/Optick/Views/PlotView.xaml b/gui/Optick/Views/PlotView.xaml new file mode 100644 index 00000000..6e8bee3c --- /dev/null +++ b/gui/Optick/Views/PlotView.xaml @@ -0,0 +1,187 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + #FF545454 + #FF575757 + #FF2E2E2E + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Title: + + + Y Min: + + Y Max: + + + + + + + + Data Units: + + + View Units: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/gui/Optick/Views/PlotView.xaml.cs b/gui/Optick/Views/PlotView.xaml.cs new file mode 100644 index 00000000..f2f4b614 --- /dev/null +++ b/gui/Optick/Views/PlotView.xaml.cs @@ -0,0 +1,89 @@ +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using Profiler.ViewModels; +using Profiler.ViewModels.Plots; + +namespace Profiler.Views +{ + public partial class PlotView : UserControl + { + public static readonly RoutedEvent PlotClickEvent = EventManager.RegisterRoutedEvent("PlotClick", RoutingStrategy.Bubble, typeof(PlotClickedEventHandler), typeof(TimeLine)); + + public PlotView() + { + InitializeComponent(); + Chart.PlotTransformChanged += UpdateMouse; + MouseMove += UpdateMouse; + } + + public event PlotClickedEventHandler PlotClicked + { + add => AddHandler(PlotClickEvent, value); + remove => RemoveHandler(PlotClickEvent, value); + } + + private void UpdateMouse(Object s, EventArgs e) + { + var vm = (PlotsViewModel)DataContext; + var pos = Mouse.GetPosition(PART_axisGrid); + var cursorInside = !(pos.X < 0 || pos.Y < 0 || pos.X > PART_axisGrid.ActualWidth || pos.Y > PART_axisGrid.ActualHeight); + + vm.HoverVisibility = cursorInside ? Visibility.Visible : Visibility.Hidden; + + if (cursorInside) + { + double plotX = Chart.XFromLeft(pos.X); + + foreach (var selectedCounterViewModel in vm.SelectedCounterViewModels) + { + if (selectedCounterViewModel.Points.Count == 0) + continue; + + var pointIndex = PlotsViewModel.GetPointIndex(selectedCounterViewModel.Points, plotX); + selectedCounterViewModel.HoverPoint = new Point(selectedCounterViewModel.Points[pointIndex].X, selectedCounterViewModel.Points[pointIndex].Y); + var offset = SelectedCounterViewModel.SelectedPointEllipseSize / 2; + selectedCounterViewModel.HoverPointInScreenSpace = new Point(Chart.LeftFromX(selectedCounterViewModel.HoverPoint.X) - offset, Chart.TopFromY(selectedCounterViewModel.HoverPoint.Y) - offset); + } + } + + vm.MousePositionX = pos.X; + vm.MousePositionY = pos.Y; + } + + private void BtnAddCounter_OnClick(object sender, RoutedEventArgs e) + { + var vm = (PlotsViewModel)DataContext; + vm.SelectCurrentCounter(); + } + + private void BtnAutofit_OnClick(object sender, RoutedEventArgs e) + { + Chart.IsAutoFitEnabled = true; + } + + private void MouseNav_OnPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) + { + var pos = Mouse.GetPosition(PART_axisGrid); + var cursorInside = !(pos.X < 0 || pos.Y < 0 || pos.X > PART_axisGrid.ActualWidth || pos.Y > PART_axisGrid.ActualHeight); + if (cursorInside) + { + double plotX = Chart.XFromLeft(pos.X); + RaiseEvent(new PlotClickedEventArgs(plotX)); + } + } + + public delegate void PlotClickedEventHandler(object sender, PlotClickedEventArgs e); + + public class PlotClickedEventArgs : RoutedEventArgs + { + public double Time { get; } + + public PlotClickedEventArgs(double time) : base(PlotClickEvent) + { + Time = time; + } + } + } +} \ No newline at end of file diff --git a/gui/Optick/counters.json b/gui/Optick/counters.json new file mode 100644 index 00000000..dbd1a717 --- /dev/null +++ b/gui/Optick/counters.json @@ -0,0 +1 @@ +[{"Title":"General","Counters":[{"Key":"Render/Triangles Count","Name":"Render/Triangles Count","Color":{"A":255,"R":4,"G":117,"B":225},"DataUnits":6,"ViewUnits":6},{"Key":"Network/Message count","Name":"Network/Message count","Color":{"A":255,"R":226,"G":1,"B":216},"DataUnits":6,"ViewUnits":6}]},{"Title":"Network","Counters":[{"Key":"Network/AVG Message size","Name":"Network/AVG Message size","Color":{"A":255,"R":111,"G":4,"B":230},"DataUnits":0,"ViewUnits":1}]}] \ No newline at end of file diff --git a/gui/Optick/packages.config b/gui/Optick/packages.config index 35c5530b..f2547904 100644 --- a/gui/Optick/packages.config +++ b/gui/Optick/packages.config @@ -13,7 +13,9 @@ + + diff --git a/gui/Profiler.Controls/FunctionSearch.xaml.cs b/gui/Profiler.Controls/FunctionSearch.xaml.cs index bee33455..d4f715ee 100644 --- a/gui/Profiler.Controls/FunctionSearch.xaml.cs +++ b/gui/Profiler.Controls/FunctionSearch.xaml.cs @@ -97,7 +97,7 @@ private void Flush() if (maxFrame != null && maxEntry != null) { EventNode maxNode = maxFrame.Root.FindNode(maxEntry); - RaiseEvent(new FocusFrameEventArgs(GlobalEvents.FocusFrameEvent, new EventFrame(maxFrame, maxNode), null)); + RaiseEvent(new FocusFrameEventArgs(GlobalEvents.FocusFrameEvent, new EventFrame(maxFrame, maxNode))); } } } diff --git a/gui/Profiler.Controls/Icons/Icons.xaml b/gui/Profiler.Controls/Icons/Icons.xaml index 6e47baeb..a3b4ea7c 100644 --- a/gui/Profiler.Controls/Icons/Icons.xaml +++ b/gui/Profiler.Controls/Icons/Icons.xaml @@ -12078,6 +12078,20 @@ + +