diff --git a/src/Artemis.Core/Artemis.Core.csproj.DotSettings b/src/Artemis.Core/Artemis.Core.csproj.DotSettings
index abd028c5b..daa70a5ef 100644
--- a/src/Artemis.Core/Artemis.Core.csproj.DotSettings
+++ b/src/Artemis.Core/Artemis.Core.csproj.DotSettings
@@ -58,6 +58,7 @@
True
True
True
+ True
True
True
True
diff --git a/src/Artemis.Core/Constants.cs b/src/Artemis.Core/Constants.cs
index 4c22e8728..6f8f1c27d 100644
--- a/src/Artemis.Core/Constants.cs
+++ b/src/Artemis.Core/Constants.cs
@@ -62,8 +62,8 @@ namespace Artemis.Core
///
public static readonly Plugin CorePlugin = new(CorePluginInfo, new DirectoryInfo(ApplicationFolder), null);
- internal static readonly CorePluginFeature CorePluginFeature = new() {Plugin = CorePlugin};
- internal static readonly EffectPlaceholderPlugin EffectPlaceholderPlugin = new() {Plugin = CorePlugin};
+ internal static readonly CorePluginFeature CorePluginFeature = new() {Plugin = CorePlugin, Profiler = CorePlugin.GetProfiler("Feature - Core")};
+ internal static readonly EffectPlaceholderPlugin EffectPlaceholderPlugin = new() {Plugin = CorePlugin, Profiler = CorePlugin.GetProfiler("Feature - Effect Placeholder")};
internal static JsonSerializerSettings JsonConvertSettings = new()
{
diff --git a/src/Artemis.Core/Plugins/Plugin.cs b/src/Artemis.Core/Plugins/Plugin.cs
index ddd090bfd..4d226640a 100644
--- a/src/Artemis.Core/Plugins/Plugin.cs
+++ b/src/Artemis.Core/Plugins/Plugin.cs
@@ -17,6 +17,7 @@ namespace Artemis.Core
public class Plugin : CorePropertyChanged, IDisposable
{
private readonly List _features;
+ private readonly List _profilers;
private bool _isEnabled;
@@ -28,6 +29,7 @@ namespace Artemis.Core
Info.Plugin = this;
_features = new List();
+ _profilers = new List();
}
///
@@ -64,6 +66,8 @@ namespace Artemis.Core
///
public ReadOnlyCollection Features => _features.AsReadOnly();
+ public ReadOnlyCollection Profilers => _profilers.AsReadOnly();
+
///
/// The assembly the plugin code lives in
///
@@ -114,7 +118,7 @@ namespace Artemis.Core
{
return _features.FirstOrDefault(i => i.Instance is T)?.Instance as T;
}
-
+
///
/// Looks up the feature info the feature of type
///
@@ -126,6 +130,22 @@ namespace Artemis.Core
return _features.First(i => i.FeatureType == typeof(T));
}
+ ///
+ /// Gets a profiler with the provided , if it does not yet exist it will be created.
+ ///
+ /// The name of the profiler
+ /// A new or existing profiler with the provided
+ public Profiler GetProfiler(string name)
+ {
+ Profiler? profiler = _profilers.FirstOrDefault(p => p.Name == name);
+ if (profiler != null)
+ return profiler;
+
+ profiler = new Profiler(this, name);
+ _profilers.Add(profiler);
+ return profiler;
+ }
+
///
public override string ToString()
{
diff --git a/src/Artemis.Core/Plugins/PluginFeature.cs b/src/Artemis.Core/Plugins/PluginFeature.cs
index aa661c951..2fd931ebf 100644
--- a/src/Artemis.Core/Plugins/PluginFeature.cs
+++ b/src/Artemis.Core/Plugins/PluginFeature.cs
@@ -1,6 +1,4 @@
using System;
-using System.Collections.Generic;
-using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
using Artemis.Storage.Entities.Plugins;
@@ -12,11 +10,9 @@ namespace Artemis.Core
///
public abstract class PluginFeature : CorePropertyChanged, IDisposable
{
- private readonly Stopwatch _renderStopwatch = new();
- private readonly Stopwatch _updateStopwatch = new();
private bool _isEnabled;
private Exception? _loadException;
-
+
///
/// Gets the plugin feature info related to this feature
///
@@ -27,6 +23,11 @@ namespace Artemis.Core
///
public Plugin Plugin { get; internal set; } = null!; // Will be set right after construction
+ ///
+ /// Gets the profiler that can be used to take profiling measurements
+ ///
+ public Profiler Profiler { get; internal set; } = null!; // Will be set right after construction
+
///
/// Gets whether the plugin is enabled
///
@@ -112,24 +113,22 @@ namespace Artemis.Core
internal void StartUpdateMeasure()
{
- _updateStopwatch.Start();
+ Profiler.StartMeasurement("Update");
}
internal void StopUpdateMeasure()
{
- UpdateTime = _updateStopwatch.Elapsed;
- _updateStopwatch.Reset();
+ Profiler.StopMeasurement("Update");
}
internal void StartRenderMeasure()
{
- _renderStopwatch.Start();
+ Profiler.StartMeasurement("Render");
}
internal void StopRenderMeasure()
{
- RenderTime = _renderStopwatch.Elapsed;
- _renderStopwatch.Reset();
+ Profiler.StopMeasurement("Render");
}
internal void SetEnabled(bool enable, bool isAutoEnable = false)
diff --git a/src/Artemis.Core/Plugins/PluginInfo.cs b/src/Artemis.Core/Plugins/PluginInfo.cs
index de8a77e7f..1a6af3a88 100644
--- a/src/Artemis.Core/Plugins/PluginInfo.cs
+++ b/src/Artemis.Core/Plugins/PluginInfo.cs
@@ -58,8 +58,7 @@ namespace Artemis.Core
///
/// The plugins display icon that's shown in the settings see for
- /// available
- /// icons
+ /// available icons
///
[JsonProperty]
public string? Icon
diff --git a/src/Artemis.Core/Plugins/Profiling/Profiler.cs b/src/Artemis.Core/Plugins/Profiling/Profiler.cs
new file mode 100644
index 000000000..a544bc2f0
--- /dev/null
+++ b/src/Artemis.Core/Plugins/Profiling/Profiler.cs
@@ -0,0 +1,72 @@
+using System.Collections.Generic;
+
+namespace Artemis.Core
+{
+ ///
+ /// Represents a profiler that can measure time between calls distinguished by identifiers
+ ///
+ public class Profiler
+ {
+ internal Profiler(Plugin plugin, string name)
+ {
+ Plugin = plugin;
+ Name = name;
+ }
+
+ ///
+ /// Gets the plugin this profiler belongs to
+ ///
+ public Plugin Plugin { get; }
+
+ ///
+ /// Gets the name of this profiler
+ ///
+ public string Name { get; }
+
+
+ ///
+ /// Gets a dictionary containing measurements by their identifiers
+ ///
+ public Dictionary Measurements { get; set; } = new();
+
+ ///
+ /// Starts measuring time for the provided
+ ///
+ /// A unique identifier for this measurement
+ public void StartMeasurement(string identifier)
+ {
+ if (!Measurements.TryGetValue(identifier, out ProfilingMeasurement? measurement))
+ {
+ measurement = new ProfilingMeasurement(identifier);
+ Measurements.Add(identifier, measurement);
+ }
+
+ measurement.Start();
+ }
+
+ ///
+ /// Stops measuring time for the provided
+ ///
+ /// A unique identifier for this measurement
+ /// The number of ticks that passed since the call with the same identifier
+ public long StopMeasurement(string identifier)
+ {
+ if (!Measurements.TryGetValue(identifier, out ProfilingMeasurement? measurement))
+ {
+ measurement = new ProfilingMeasurement(identifier);
+ Measurements.Add(identifier, measurement);
+ }
+
+ return measurement.Stop();
+ }
+
+ ///
+ /// Clears measurements with the the provided
+ ///
+ ///
+ public void ClearMeasurements(string identifier)
+ {
+ Measurements.Remove(identifier);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.Core/Plugins/Profiling/ProfilingMeasurement.cs b/src/Artemis.Core/Plugins/Profiling/ProfilingMeasurement.cs
new file mode 100644
index 000000000..4c8aeaae9
--- /dev/null
+++ b/src/Artemis.Core/Plugins/Profiling/ProfilingMeasurement.cs
@@ -0,0 +1,110 @@
+using System;
+using System.Linq;
+
+namespace Artemis.Core
+{
+ ///
+ /// Represents a set of profiling measurements
+ ///
+ public class ProfilingMeasurement
+ {
+ private bool _filledArray;
+ private int _index;
+ private long _last;
+ private DateTime? _start;
+
+ internal ProfilingMeasurement(string identifier)
+ {
+ Identifier = identifier;
+ }
+
+ ///
+ /// Gets the unique identifier of this measurement
+ ///
+ public string Identifier { get; }
+
+ ///
+ /// Gets the last 1000 measurements
+ ///
+ public long[] Measurements { get; } = new long[1000];
+
+ ///
+ /// Starts measuring time until is called
+ ///
+ public void Start()
+ {
+ _start = DateTime.UtcNow;
+ }
+
+ ///
+ /// Stops measuring time and stores the time passed in the list
+ ///
+ /// The time passed since the last call
+ public long Stop()
+ {
+ if (_start == null)
+ return 0;
+
+ long difference = (DateTime.UtcNow - _start.Value).Ticks;
+ Measurements[_index] = difference;
+ _start = null;
+
+ _index++;
+ if (_index >= 1000)
+ {
+ _filledArray = true;
+ _index = 0;
+ }
+
+ _last = difference;
+ return difference;
+ }
+
+ ///
+ /// Gets the last measured time
+ ///
+ public TimeSpan GetLast()
+ {
+ return new(_last);
+ }
+
+ ///
+ /// Gets the average time of the last 1000 measurements
+ ///
+ public TimeSpan GetAverage()
+ {
+ if (!_filledArray && _index == 0)
+ return TimeSpan.Zero;
+
+ return _filledArray
+ ? new TimeSpan((long) Measurements.Average(m => m))
+ : new TimeSpan((long) Measurements.Take(_index).Average(m => m));
+ }
+
+ ///
+ /// Gets the min time of the last 1000 measurements
+ ///
+ public TimeSpan GetMin()
+ {
+ if (!_filledArray && _index == 0)
+ return TimeSpan.Zero;
+
+ return _filledArray
+ ? new TimeSpan(Measurements.Min())
+ : new TimeSpan(Measurements.Take(_index).Min());
+ }
+
+ ///
+ /// Gets the max time of the last 1000 measurements
+ ///
+ public TimeSpan GetMax()
+ {
+ if (!_filledArray && _index == 0)
+ return TimeSpan.Zero;
+
+ return _filledArray
+ ? new TimeSpan(Measurements.Max())
+ : new TimeSpan(Measurements.Take(_index).Max());
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.Core/Services/PluginManagementService.cs b/src/Artemis.Core/Services/PluginManagementService.cs
index 7b3e6dea1..0d312a262 100644
--- a/src/Artemis.Core/Services/PluginManagementService.cs
+++ b/src/Artemis.Core/Services/PluginManagementService.cs
@@ -411,6 +411,7 @@ namespace Artemis.Core.Services
featureInfo.Instance = instance;
instance.Info = featureInfo;
instance.Plugin = plugin;
+ instance.Profiler = plugin.GetProfiler("Feature - " + featureInfo.Name);
instance.Entity = featureInfo.Entity;
}
catch (Exception e)
diff --git a/src/Artemis.UI/Artemis.UI.csproj b/src/Artemis.UI/Artemis.UI.csproj
index ce31292e3..5ae4e77bb 100644
--- a/src/Artemis.UI/Artemis.UI.csproj
+++ b/src/Artemis.UI/Artemis.UI.csproj
@@ -372,5 +372,8 @@
$(DefaultXamlRuntime)
+
+ $(DefaultXamlRuntime)
+
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineSegmentViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineSegmentViewModel.cs
index 8d8c52ee9..804e39dee 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineSegmentViewModel.cs
+++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Timeline/TimelineSegmentViewModel.cs
@@ -190,6 +190,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
ShowRepeatButton = SegmentWidth > 45 && IsMainSegment;
ShowDisableButton = SegmentWidth > 25;
+
if (Segment == SegmentViewModelType.Main)
NotifyOfPropertyChange(nameof(RepeatSegment));
}
diff --git a/src/Artemis.UI/Screens/Settings/Debug/DebugViewModel.cs b/src/Artemis.UI/Screens/Settings/Debug/DebugViewModel.cs
index e661158ea..55939628d 100644
--- a/src/Artemis.UI/Screens/Settings/Debug/DebugViewModel.cs
+++ b/src/Artemis.UI/Screens/Settings/Debug/DebugViewModel.cs
@@ -1,8 +1,8 @@
using System;
-using System.Collections.Generic;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Screens.Settings.Debug.Tabs;
+using Artemis.UI.Screens.Settings.Debug.Tabs.Performance;
using Stylet;
namespace Artemis.UI.Screens.Settings.Debug
@@ -16,12 +16,14 @@ namespace Artemis.UI.Screens.Settings.Debug
ICoreService coreService,
RenderDebugViewModel renderDebugViewModel,
DataModelDebugViewModel dataModelDebugViewModel,
- LogsDebugViewModel logsDebugViewModel)
+ LogsDebugViewModel logsDebugViewModel,
+ PerformanceDebugViewModel performanceDebugViewModel)
{
_coreService = coreService;
Items.Add(renderDebugViewModel);
Items.Add(dataModelDebugViewModel);
Items.Add(logsDebugViewModel);
+ Items.Add(performanceDebugViewModel);
ActiveItem = renderDebugViewModel;
StayOnTopSetting = settingsService.GetSetting("Debugger.StayOnTop", false);
diff --git a/src/Artemis.UI/Screens/Settings/Debug/Tabs/Performance/PerformanceDebugMeasurementViewModel.cs b/src/Artemis.UI/Screens/Settings/Debug/Tabs/Performance/PerformanceDebugMeasurementViewModel.cs
new file mode 100644
index 000000000..7bec70c87
--- /dev/null
+++ b/src/Artemis.UI/Screens/Settings/Debug/Tabs/Performance/PerformanceDebugMeasurementViewModel.cs
@@ -0,0 +1,52 @@
+using Artemis.Core;
+using Stylet;
+
+namespace Artemis.UI.Screens.Settings.Debug.Tabs.Performance
+{
+ public class PerformanceDebugMeasurementViewModel : PropertyChangedBase
+ {
+ private string _average;
+ private string _last;
+ private string _max;
+ private string _min;
+
+ public PerformanceDebugMeasurementViewModel(ProfilingMeasurement measurement)
+ {
+ Measurement = measurement;
+ }
+
+ public ProfilingMeasurement Measurement { get; }
+
+ public string Last
+ {
+ get => _last;
+ set => SetAndNotify(ref _last, value);
+ }
+
+ public string Average
+ {
+ get => _average;
+ set => SetAndNotify(ref _average, value);
+ }
+
+ public string Min
+ {
+ get => _min;
+ set => SetAndNotify(ref _min, value);
+ }
+
+ public string Max
+ {
+ get => _max;
+ set => SetAndNotify(ref _max, value);
+ }
+
+ public void Update()
+ {
+ Last = Measurement.GetLast().TotalMilliseconds + " ms";
+ Average = Measurement.GetAverage().TotalMilliseconds + " ms";
+ Min = Measurement.GetMin().TotalMilliseconds + " ms";
+ Max = Measurement.GetMax().TotalMilliseconds + " ms";
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/Settings/Debug/Tabs/Performance/PerformanceDebugPluginView.xaml b/src/Artemis.UI/Screens/Settings/Debug/Tabs/Performance/PerformanceDebugPluginView.xaml
new file mode 100644
index 000000000..4a3d11955
--- /dev/null
+++ b/src/Artemis.UI/Screens/Settings/Debug/Tabs/Performance/PerformanceDebugPluginView.xaml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/Settings/Debug/Tabs/Performance/PerformanceDebugPluginViewModel.cs b/src/Artemis.UI/Screens/Settings/Debug/Tabs/Performance/PerformanceDebugPluginViewModel.cs
new file mode 100644
index 000000000..719eff26b
--- /dev/null
+++ b/src/Artemis.UI/Screens/Settings/Debug/Tabs/Performance/PerformanceDebugPluginViewModel.cs
@@ -0,0 +1,32 @@
+using System.Linq;
+using Artemis.Core;
+using Artemis.UI.Shared;
+using Stylet;
+
+namespace Artemis.UI.Screens.Settings.Debug.Tabs.Performance
+{
+ public class PerformanceDebugPluginViewModel : Screen
+ {
+ public Plugin Plugin { get; }
+ public object Icon { get; }
+
+ public PerformanceDebugPluginViewModel(Plugin plugin)
+ {
+ Plugin = plugin;
+ Icon = PluginUtilities.GetPluginIcon(Plugin, Plugin.Info.Icon);
+ }
+
+ public BindableCollection Profilers { get; } = new();
+ public void Update()
+ {
+ foreach (Profiler pluginProfiler in Plugin.Profilers.Where(p => p.Measurements.Any()))
+ {
+ if (Profilers.All(p => p.Profiler != pluginProfiler))
+ Profilers.Add(new PerformanceDebugProfilerViewModel(pluginProfiler));
+ }
+
+ foreach (PerformanceDebugProfilerViewModel profilerViewModel in Profilers)
+ profilerViewModel.Update();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/Settings/Debug/Tabs/Performance/PerformanceDebugProfilerView.xaml b/src/Artemis.UI/Screens/Settings/Debug/Tabs/Performance/PerformanceDebugProfilerView.xaml
new file mode 100644
index 000000000..f8e4cad6a
--- /dev/null
+++ b/src/Artemis.UI/Screens/Settings/Debug/Tabs/Performance/PerformanceDebugProfilerView.xaml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Artemis.UI/Screens/Settings/Debug/Tabs/Performance/PerformanceDebugProfilerViewModel.cs b/src/Artemis.UI/Screens/Settings/Debug/Tabs/Performance/PerformanceDebugProfilerViewModel.cs
new file mode 100644
index 000000000..c0921b87a
--- /dev/null
+++ b/src/Artemis.UI/Screens/Settings/Debug/Tabs/Performance/PerformanceDebugProfilerViewModel.cs
@@ -0,0 +1,30 @@
+using System.Linq;
+using Artemis.Core;
+using Stylet;
+
+namespace Artemis.UI.Screens.Settings.Debug.Tabs.Performance
+{
+ public class PerformanceDebugProfilerViewModel : Screen
+ {
+ public Profiler Profiler { get; }
+
+ public PerformanceDebugProfilerViewModel(Profiler profiler)
+ {
+ Profiler = profiler;
+ }
+
+ public BindableCollection Measurements { get; } = new();
+
+ public void Update()
+ {
+ foreach ((string _, ProfilingMeasurement measurement) in Profiler.Measurements)
+ {
+ if (Measurements.All(m => m.Measurement != measurement))
+ Measurements.Add(new PerformanceDebugMeasurementViewModel(measurement));
+ }
+
+ foreach (PerformanceDebugMeasurementViewModel profilingMeasurementViewModel in Measurements)
+ profilingMeasurementViewModel.Update();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/Settings/Debug/Tabs/Performance/PerformanceDebugView.xaml b/src/Artemis.UI/Screens/Settings/Debug/Tabs/Performance/PerformanceDebugView.xaml
new file mode 100644
index 000000000..cdc24de90
--- /dev/null
+++ b/src/Artemis.UI/Screens/Settings/Debug/Tabs/Performance/PerformanceDebugView.xaml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+ In this window you can see how much CPU time different plugin features are taking.
+ If you are having performance issues, below you can find out which plugin might be the culprit.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/Settings/Debug/Tabs/Performance/PerformanceDebugViewModel.cs b/src/Artemis.UI/Screens/Settings/Debug/Tabs/Performance/PerformanceDebugViewModel.cs
new file mode 100644
index 000000000..b30ebe72e
--- /dev/null
+++ b/src/Artemis.UI/Screens/Settings/Debug/Tabs/Performance/PerformanceDebugViewModel.cs
@@ -0,0 +1,77 @@
+using System.Linq;
+using System.Timers;
+using Artemis.Core;
+using Artemis.Core.Services;
+using Stylet;
+
+namespace Artemis.UI.Screens.Settings.Debug.Tabs.Performance
+{
+ public class PerformanceDebugViewModel : Conductor.Collection.AllActive
+ {
+ private readonly IPluginManagementService _pluginManagementService;
+ private readonly Timer _updateTimer;
+
+ public PerformanceDebugViewModel(IPluginManagementService pluginManagementService)
+ {
+ _pluginManagementService = pluginManagementService;
+ _updateTimer = new Timer(500);
+
+ DisplayName = "PERFORMANCE";
+ _updateTimer.Elapsed += UpdateTimerOnElapsed;
+ }
+
+ private void UpdateTimerOnElapsed(object sender, ElapsedEventArgs e)
+ {
+ foreach (PerformanceDebugPluginViewModel viewModel in Items)
+ viewModel.Update();
+ }
+
+ private void FeatureToggled(object? sender, PluginFeatureEventArgs e)
+ {
+ Items.Clear();
+ PopulateItems();
+ }
+
+ private void PluginToggled(object? sender, PluginEventArgs e)
+ {
+ Items.Clear();
+ PopulateItems();
+ }
+
+ private void PopulateItems()
+ {
+ Items.AddRange(_pluginManagementService.GetAllPlugins()
+ .Where(p => p.IsEnabled && p.Profilers.Any(pr => pr.Measurements.Any()))
+ .OrderBy(p => p.Info.Name)
+ .Select(p => new PerformanceDebugPluginViewModel(p)));
+ }
+
+ #region Overrides of Screen
+
+ ///
+ protected override void OnActivate()
+ {
+ PopulateItems();
+ _updateTimer.Start();
+ _pluginManagementService.PluginDisabled += PluginToggled;
+ _pluginManagementService.PluginDisabled += PluginToggled;
+ _pluginManagementService.PluginFeatureEnabled += FeatureToggled;
+ _pluginManagementService.PluginFeatureDisabled += FeatureToggled;
+ base.OnActivate();
+ }
+
+ ///
+ protected override void OnDeactivate()
+ {
+ _updateTimer.Stop();
+ _pluginManagementService.PluginDisabled -= PluginToggled;
+ _pluginManagementService.PluginDisabled -= PluginToggled;
+ _pluginManagementService.PluginFeatureEnabled -= FeatureToggled;
+ _pluginManagementService.PluginFeatureDisabled -= FeatureToggled;
+ Items.Clear();
+ base.OnDeactivate();
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file