mirror of
https://github.com/Artemis-RGB/Artemis
synced 2026-11-11 11:26:27 +00:00
UI - Avoid opening the main window multiple times from tray
Profiling - Thread safety and use high precision counters Profiling - Profile timed updates Timed updates - Added argument to give timed updates a name Plugins - Affix plugin folders with a part of the plugin GUID Debugger - Added 95th percentile column to profiling Debugger - Fix scrolling in performance profile tab when hovering over datagrids
This commit is contained in:
1 parent
4ab8459927
commit
1660519bee
14 files changed
+315
-185
No files matched your search
@@ -8,40 +8,6 @@ namespace Artemis.Core
|
||||
/// </summary>
|
||||
public abstract class DataModelPluginFeature : PluginFeature
|
||||
{
|
||||
/// <summary>
|
||||
/// Registers a timed update that whenever the plugin is enabled calls the provided <paramref name="action" /> at the
|
||||
/// provided
|
||||
/// <paramref name="interval" />
|
||||
/// </summary>
|
||||
/// <param name="interval">The interval at which the update should occur</param>
|
||||
/// <param name="action">
|
||||
/// The action to call every time the interval has passed. The delta time parameter represents the
|
||||
/// time passed since the last update in seconds
|
||||
/// </param>
|
||||
/// <returns>The resulting plugin update registration which can be used to stop the update</returns>
|
||||
public TimedUpdateRegistration AddTimedUpdate(TimeSpan interval, Action<double> action)
|
||||
{
|
||||
if (action == null)
|
||||
throw new ArgumentNullException(nameof(action));
|
||||
return new TimedUpdateRegistration(this, interval, action);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a timed update that whenever the plugin is enabled calls the provided <paramref name="asyncAction" /> at the
|
||||
/// provided
|
||||
/// <paramref name="interval" />
|
||||
/// </summary>
|
||||
/// <param name="interval">The interval at which the update should occur</param>
|
||||
/// <param name="asyncAction">
|
||||
/// The async action to call every time the interval has passed. The delta time parameter
|
||||
/// represents the time passed since the last update in seconds
|
||||
/// </param>
|
||||
/// <returns>The resulting plugin update registration</returns>
|
||||
public TimedUpdateRegistration AddTimedUpdate(TimeSpan interval, Func<double, Task> asyncAction)
|
||||
{
|
||||
if (asyncAction == null)
|
||||
throw new ArgumentNullException(nameof(asyncAction));
|
||||
return new TimedUpdateRegistration(this, interval, asyncAction);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -146,6 +146,15 @@ namespace Artemis.Core
|
||||
return profiler;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a profiler from the plugin
|
||||
/// </summary>
|
||||
/// <param name="profiler">The profiler to remove</param>
|
||||
public void RemoveProfiler(Profiler profiler)
|
||||
{
|
||||
_profilers.Remove(profiler);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
|
||||
@@ -51,16 +51,6 @@ namespace Artemis.Core
|
||||
/// </summary>
|
||||
public string Id => $"{GetType().FullName}-{Plugin.Guid.ToString().Substring(0, 8)}"; // Not as unique as a GUID but good enough and stays readable
|
||||
|
||||
/// <summary>
|
||||
/// Gets the last measured update time of the feature
|
||||
/// </summary>
|
||||
public TimeSpan UpdateTime { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the last measured render time of the feature
|
||||
/// </summary>
|
||||
public TimeSpan RenderTime { get; private set; }
|
||||
|
||||
internal PluginFeatureEntity Entity { get; set; } = null!; // Will be set right after construction
|
||||
|
||||
/// <summary>
|
||||
@@ -241,5 +231,47 @@ namespace Artemis.Core
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Timed updates
|
||||
|
||||
/// <summary>
|
||||
/// Registers a timed update that whenever the plugin is enabled calls the provided <paramref name="action" /> at the
|
||||
/// provided
|
||||
/// <paramref name="interval" />
|
||||
/// </summary>
|
||||
/// <param name="interval">The interval at which the update should occur</param>
|
||||
/// <param name="action">
|
||||
/// The action to call every time the interval has passed. The delta time parameter represents the
|
||||
/// time passed since the last update in seconds
|
||||
/// </param>
|
||||
/// <param name="name">An optional name used in exceptions and profiling</param>
|
||||
/// <returns>The resulting plugin update registration which can be used to stop the update</returns>
|
||||
public TimedUpdateRegistration AddTimedUpdate(TimeSpan interval, Action<double> action, string? name = null)
|
||||
{
|
||||
if (action == null)
|
||||
throw new ArgumentNullException(nameof(action));
|
||||
return new TimedUpdateRegistration(this, interval, action, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a timed update that whenever the plugin is enabled calls the provided <paramref name="asyncAction" /> at the
|
||||
/// provided
|
||||
/// <paramref name="interval" />
|
||||
/// </summary>
|
||||
/// <param name="interval">The interval at which the update should occur</param>
|
||||
/// <param name="asyncAction">
|
||||
/// The async action to call every time the interval has passed. The delta time parameter
|
||||
/// represents the time passed since the last update in seconds
|
||||
/// </param>
|
||||
/// <param name="name">An optional name used in exceptions and profiling</param>
|
||||
/// <returns>The resulting plugin update registration</returns>
|
||||
public TimedUpdateRegistration AddTimedUpdate(TimeSpan interval, Func<double, Task> asyncAction, string? name = null)
|
||||
{
|
||||
if (asyncAction == null)
|
||||
throw new ArgumentNullException(nameof(asyncAction));
|
||||
return new TimedUpdateRegistration(this, interval, asyncAction, name);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -124,6 +124,8 @@ namespace Artemis.Core
|
||||
/// <inheritdoc />
|
||||
public bool ArePrerequisitesMet() => Prerequisites.All(p => p.IsMet());
|
||||
|
||||
internal string PreferredPluginDirectory => $"{Main.Split(".dll")[0].Replace("/", "").Replace("\\", "")}-{Guid.ToString().Substring(0, 8)}";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
@@ -35,13 +37,16 @@ namespace Artemis.Core
|
||||
/// <param name="identifier">A unique identifier for this measurement</param>
|
||||
public void StartMeasurement(string identifier)
|
||||
{
|
||||
if (!Measurements.TryGetValue(identifier, out ProfilingMeasurement? measurement))
|
||||
lock (Measurements)
|
||||
{
|
||||
measurement = new ProfilingMeasurement(identifier);
|
||||
Measurements.Add(identifier, measurement);
|
||||
}
|
||||
if (!Measurements.TryGetValue(identifier, out ProfilingMeasurement? measurement))
|
||||
{
|
||||
measurement = new ProfilingMeasurement(identifier);
|
||||
Measurements.Add(identifier, measurement);
|
||||
}
|
||||
|
||||
measurement.Start();
|
||||
measurement.Start();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -51,13 +56,17 @@ namespace Artemis.Core
|
||||
/// <returns>The number of ticks that passed since the <see cref="StartMeasurement" /> call with the same identifier</returns>
|
||||
public long StopMeasurement(string identifier)
|
||||
{
|
||||
if (!Measurements.TryGetValue(identifier, out ProfilingMeasurement? measurement))
|
||||
long lockRequestedAt = Stopwatch.GetTimestamp();
|
||||
lock (Measurements)
|
||||
{
|
||||
measurement = new ProfilingMeasurement(identifier);
|
||||
Measurements.Add(identifier, measurement);
|
||||
}
|
||||
if (!Measurements.TryGetValue(identifier, out ProfilingMeasurement? measurement))
|
||||
{
|
||||
measurement = new ProfilingMeasurement(identifier);
|
||||
Measurements.Add(identifier, measurement);
|
||||
}
|
||||
|
||||
return measurement.Stop();
|
||||
return measurement.Stop(Stopwatch.GetTimestamp() - lockRequestedAt);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -66,7 +75,10 @@ namespace Artemis.Core
|
||||
/// <param name="identifier"></param>
|
||||
public void ClearMeasurements(string identifier)
|
||||
{
|
||||
Measurements.Remove(identifier);
|
||||
lock (Measurements)
|
||||
{
|
||||
Measurements.Remove(identifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
|
||||
namespace Artemis.Core
|
||||
@@ -11,7 +13,8 @@ namespace Artemis.Core
|
||||
private bool _filledArray;
|
||||
private int _index;
|
||||
private long _last;
|
||||
private DateTime? _start;
|
||||
private bool _open;
|
||||
private long _start;
|
||||
|
||||
internal ProfilingMeasurement(string identifier)
|
||||
{
|
||||
@@ -33,22 +36,24 @@ namespace Artemis.Core
|
||||
/// </summary>
|
||||
public void Start()
|
||||
{
|
||||
_start = DateTime.UtcNow;
|
||||
_start = Stopwatch.GetTimestamp();
|
||||
_open = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops measuring time and stores the time passed in the <see cref="Measurements" /> list
|
||||
/// </summary>
|
||||
/// <param name="correction">An optional correction in ticks to subtract from the measurement</param>
|
||||
/// <returns>The time passed since the last <see cref="Start" /> call</returns>
|
||||
public long Stop()
|
||||
public long Stop(long correction = 0)
|
||||
{
|
||||
if (_start == null)
|
||||
if (!_open)
|
||||
return 0;
|
||||
|
||||
long difference = (DateTime.UtcNow - _start.Value).Ticks;
|
||||
|
||||
long difference = Stopwatch.GetTimestamp() - _start - correction;
|
||||
_open = false;
|
||||
Measurements[_index] = difference;
|
||||
_start = null;
|
||||
|
||||
|
||||
_index++;
|
||||
if (_index >= 1000)
|
||||
{
|
||||
@@ -106,5 +111,31 @@ namespace Artemis.Core
|
||||
? new TimeSpan(Measurements.Max())
|
||||
: new TimeSpan(Measurements.Take(_index).Max());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the nth percentile of the last 1000 measurements
|
||||
/// </summary>
|
||||
public TimeSpan GetPercentile(double percentile)
|
||||
{
|
||||
if (!_filledArray && _index == 0)
|
||||
return TimeSpan.Zero;
|
||||
|
||||
long[] collection = _filledArray
|
||||
? Measurements.OrderBy(l => l).ToArray()
|
||||
: Measurements.Take(_index).OrderBy(l => l).ToArray();
|
||||
|
||||
return new TimeSpan((long) Percentile(collection, percentile));
|
||||
}
|
||||
|
||||
private static double Percentile(long[] elements, double percentile)
|
||||
{
|
||||
Array.Sort(elements);
|
||||
double realIndex = percentile * (elements.Length - 1);
|
||||
int index = (int) realIndex;
|
||||
double frac = realIndex - index;
|
||||
if (index + 1 < elements.Length)
|
||||
return elements[index] * (1 - frac) + elements[index + 1] * frac;
|
||||
return elements[index];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ using System.Threading.Tasks;
|
||||
using System.Timers;
|
||||
using Artemis.Core.Modules;
|
||||
using Artemis.Core.Services;
|
||||
using Humanizer;
|
||||
using Ninject;
|
||||
using Serilog;
|
||||
|
||||
@@ -13,19 +14,20 @@ namespace Artemis.Core
|
||||
/// </summary>
|
||||
public class TimedUpdateRegistration : IDisposable
|
||||
{
|
||||
private DateTime _lastEvent;
|
||||
private Timer? _timer;
|
||||
private bool _disposed;
|
||||
private readonly object _lock = new();
|
||||
private ILogger _logger;
|
||||
private bool _disposed;
|
||||
private DateTime _lastEvent;
|
||||
private readonly ILogger _logger;
|
||||
private Timer? _timer;
|
||||
|
||||
internal TimedUpdateRegistration(PluginFeature feature, TimeSpan interval, Action<double> action)
|
||||
internal TimedUpdateRegistration(PluginFeature feature, TimeSpan interval, Action<double> action, string? name)
|
||||
{
|
||||
_logger = CoreService.Kernel.Get<ILogger>();
|
||||
|
||||
Feature = feature;
|
||||
Interval = interval;
|
||||
Action = action;
|
||||
Name = name ?? $"TimedUpdate-{Guid.NewGuid().ToString().Substring(0, 8)}";
|
||||
|
||||
Feature.Enabled += FeatureOnEnabled;
|
||||
Feature.Disabled += FeatureOnDisabled;
|
||||
@@ -33,13 +35,14 @@ namespace Artemis.Core
|
||||
Start();
|
||||
}
|
||||
|
||||
internal TimedUpdateRegistration(PluginFeature feature, TimeSpan interval, Func<double, Task> asyncAction)
|
||||
internal TimedUpdateRegistration(PluginFeature feature, TimeSpan interval, Func<double, Task> asyncAction, string? name)
|
||||
{
|
||||
_logger = CoreService.Kernel.Get<ILogger>();
|
||||
|
||||
|
||||
Feature = feature;
|
||||
Interval = interval;
|
||||
AsyncAction = asyncAction;
|
||||
Name = name ?? $"TimedUpdate-{Guid.NewGuid().ToString().Substring(0, 8)}";
|
||||
|
||||
Feature.Enabled += FeatureOnEnabled;
|
||||
Feature.Disabled += FeatureOnDisabled;
|
||||
@@ -69,7 +72,12 @@ namespace Artemis.Core
|
||||
public Func<double, Task>? AsyncAction { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Starts calling the <see cref="Action" /> or <see cref="AsyncAction"/> at the configured <see cref="Interval" />
|
||||
/// Gets the name of this timed update
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Starts calling the <see cref="Action" /> or <see cref="AsyncAction" /> at the configured <see cref="Interval" />
|
||||
/// <para>Note: Called automatically when the plugin enables</para>
|
||||
/// </summary>
|
||||
public void Start()
|
||||
@@ -93,7 +101,7 @@ namespace Artemis.Core
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops calling the <see cref="Action" /> or <see cref="AsyncAction"/> at the configured <see cref="Interval" />
|
||||
/// Stops calling the <see cref="Action" /> or <see cref="AsyncAction" /> at the configured <see cref="Interval" />
|
||||
/// <para>Note: Called automatically when the plugin disables</para>
|
||||
/// </summary>
|
||||
public void Stop()
|
||||
@@ -113,49 +121,6 @@ namespace Artemis.Core
|
||||
}
|
||||
}
|
||||
|
||||
private void TimerOnElapsed(object? sender, ElapsedEventArgs e)
|
||||
{
|
||||
if (!Feature.IsEnabled)
|
||||
return;
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
TimeSpan interval = DateTime.Now - _lastEvent;
|
||||
_lastEvent = DateTime.Now;
|
||||
|
||||
// Modules don't always want to update, honor that
|
||||
if (Feature is Module module && !module.IsUpdateAllowed)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
if (Action != null)
|
||||
Action(interval.TotalSeconds);
|
||||
else if (AsyncAction != null)
|
||||
{
|
||||
Task task = AsyncAction(interval.TotalSeconds);
|
||||
task.Wait();
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error(exception, "Timed update uncaught exception in plugin {plugin}", Feature.Plugin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void FeatureOnEnabled(object? sender, EventArgs e)
|
||||
{
|
||||
Start();
|
||||
}
|
||||
|
||||
private void FeatureOnDisabled(object? sender, EventArgs e)
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
|
||||
#region IDisposable
|
||||
|
||||
/// <summary>
|
||||
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
|
||||
/// </summary>
|
||||
@@ -176,6 +141,55 @@ namespace Artemis.Core
|
||||
}
|
||||
}
|
||||
|
||||
private void TimerOnElapsed(object? sender, ElapsedEventArgs e)
|
||||
{
|
||||
if (!Feature.IsEnabled)
|
||||
return;
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
Feature.Profiler.StartMeasurement(ToString());
|
||||
|
||||
TimeSpan interval = DateTime.Now - _lastEvent;
|
||||
_lastEvent = DateTime.Now;
|
||||
|
||||
// Modules don't always want to update, honor that
|
||||
if (Feature is Module module && !module.IsUpdateAllowed)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
if (Action != null)
|
||||
{
|
||||
Action(interval.TotalSeconds);
|
||||
}
|
||||
else if (AsyncAction != null)
|
||||
{
|
||||
Task task = AsyncAction(interval.TotalSeconds);
|
||||
task.Wait();
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error(exception, "{timedUpdate} uncaught exception in plugin {plugin}", this, Feature.Plugin);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Feature.Profiler.StopMeasurement(ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void FeatureOnEnabled(object? sender, EventArgs e)
|
||||
{
|
||||
Start();
|
||||
}
|
||||
|
||||
private void FeatureOnDisabled(object? sender, EventArgs e)
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
@@ -183,6 +197,12 @@ namespace Artemis.Core
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
/// <inheritdoc />
|
||||
public sealed override string ToString()
|
||||
{
|
||||
if (Interval.TotalSeconds >= 1)
|
||||
return $"{Name} ({Interval.TotalSeconds} sec)";
|
||||
return $"{Name} ({Interval.TotalMilliseconds} ms)";
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user