Skip to content

Commit

Permalink
Minor fixes
Browse files Browse the repository at this point in the history
Fix build loading from GWCA
Introduce persistence toggle for logging to reduce disk usage
Improve performance of memory usage and processor usage counters
Add disk usage counters
Closes #462
Closes #461
  • Loading branch information
AlexMacocian committed Nov 4, 2023
1 parent 97d2d09 commit 85921c6
Show file tree
Hide file tree
Showing 10 changed files with 165 additions and 31 deletions.
5 changes: 5 additions & 0 deletions Daybreak/Configuration/Options/LauncherOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Daybreak.Views;
using Newtonsoft.Json;
using System;
using System.ComponentModel;

namespace Daybreak.Configuration.Options;

Expand Down Expand Up @@ -45,4 +46,8 @@ public sealed class LauncherOptions
[OptionName(Name = "Mod Startup Timeout", Description = "Amount of seconds that Daybreak will wait for each mod to start-up before cancelling the tasks")]
[OptionRange<double>(MinValue = 30, MaxValue = 300)]
public double ModStartupTimeout { get; set; } = 30;

[JsonProperty(nameof(PersistentLogging))]
[OptionName(Name = "Persistent Logging", Description = "If true, the launcher will save logs in the local database. Otherwise, the launcher will only keep logs in a memory cache")]
public bool PersistentLogging { get; set; } = false;
}
1 change: 1 addition & 0 deletions Daybreak/Configuration/ProjectConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ public override void RegisterServices(IServiceCollection services)
services.AddSingleton<ViewManager>();
services.AddSingleton<ProcessorUsageMonitor>();
services.AddSingleton<MemoryUsageMonitor>();
services.AddSingleton<DiskUsageMonitor>();
services.AddSingleton<IViewManager, ViewManager>(sp => sp.GetRequiredService<ViewManager>());
services.AddSingleton<IViewProducer, ViewManager>(sp => sp.GetRequiredService<ViewManager>());
services.AddSingleton<PostUpdateActionManager>();
Expand Down
2 changes: 1 addition & 1 deletion Daybreak/Daybreak.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<LangVersion>preview</LangVersion>
<ApplicationIcon>Daybreak.ico</ApplicationIcon>
<IncludePackageReferencesDuringMarkupCompilation>true</IncludePackageReferencesDuringMarkupCompilation>
<Version>0.9.8.136</Version>
<Version>0.9.8.137</Version>
<EnableWindowsTargeting>true</EnableWindowsTargeting>
<UserSecretsId>cfb2a489-db80-448d-a969-80270f314c46</UserSecretsId>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
Expand Down
9 changes: 8 additions & 1 deletion Daybreak/Services/BuildTemplates/BuildTemplateManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -229,10 +229,17 @@ private Result<Build, Exception> DecodeTemplateInner(string template)
for(int i = 0; i < buildMetadata.AttributeCount; i++)
{
var attributeId = buildMetadata.AttributesIds[i];
if (attributeId == 0)
{
continue;
}

var maybeAttribute = build.Attributes.FirstOrDefault(a => a.Attribute!.Id == attributeId);
if (maybeAttribute is null)
{
this.logger.LogError($"Failed to parse attribute with id {attributeId} for professions {primaryProfession.Name}/{secondaryProfession.Name}");
var msg = $"Failed to parse attribute with id {attributeId} for professions {primaryProfession.Name}/{secondaryProfession.Name}";
this.logger.LogError(msg);
return new InvalidOperationException(msg);
}

maybeAttribute!.Points = buildMetadata.AttributePoints[i];
Expand Down
45 changes: 40 additions & 5 deletions Daybreak/Services/Logging/JsonLogsManager.cs
Original file line number Diff line number Diff line change
@@ -1,31 +1,41 @@
using LiteDB;
using Daybreak.Configuration.Options;
using LiteDB;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Core.Extensions;
using System.Extensions;
using System.Linq;
using System.Linq.Expressions;
using System.Logging;

namespace Daybreak.Services.Logging;

public sealed class JsonLogsManager : ILogsManager
{
private const int MemoryCacheMaxSize = 5000;

private readonly List<Models.Log> memoryCache = new();
private readonly ILiteCollection<Models.Log> collection;
private readonly ILiveOptions<LauncherOptions> liveOptions;

public event EventHandler<Models.Log>? ReceivedLog;

public JsonLogsManager(ILiteCollection<Models.Log> collection)
public JsonLogsManager(
ILiteCollection<Models.Log> collection,
ILiveOptions<LauncherOptions> liveOptions)
{
this.collection = collection.ThrowIfNull();
this.liveOptions = liveOptions.ThrowIfNull();
}

public IEnumerable<Models.Log> GetLogs(Expression<Func<Models.Log, bool>> filter)
{
return this.collection.Find(filter);
return this.collection.Find(filter).Concat(this.memoryCache.Where(filter.Compile()));
}
public IEnumerable<Models.Log> GetLogs()
{
return this.collection.FindAll();
return this.collection.FindAll().Concat(this.memoryCache);
}
public void WriteLog(Log log)
{
Expand All @@ -39,7 +49,32 @@ public void WriteLog(Log log)
CorrelationVector = log.CorrelationVector
};

this.collection.Insert(dbLog);
if (this.liveOptions.Value.PersistentLogging)
{
lock (this.memoryCache)
{
if (this.memoryCache.Count > 0)
{
this.collection.InsertBulk(this.memoryCache, this.memoryCache.Count);
this.memoryCache.Clear();
}
}

this.collection.Insert(dbLog);
}
else
{
lock(this.memoryCache)
{
if (this.memoryCache.Count >= MemoryCacheMaxSize)
{
this.memoryCache.RemoveAt(this.memoryCache.Count - 1);
}

this.memoryCache.Add(dbLog);
}
}

this.ReceivedLog?.Invoke(this, dbLog);
}
public int DeleteLogs()
Expand Down
68 changes: 68 additions & 0 deletions Daybreak/Services/Monitoring/DiskUsageMonitor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using Daybreak.Services.Metrics;
using System.Diagnostics.Metrics;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Windows.Extensions.Services;
using System.Core.Extensions;
using System.Extensions;
using System.Threading;

namespace Daybreak.Services.Monitoring;

public sealed class DiskUsageMonitor : IApplicationLifetimeService
{
private const string WriteDiskUsage = "Write Disk Usage";
private const string WriteDiskUsageUnit = "MBs/s";
private const string WriteDiskUsageDescription = "MBs/s written by Daybreak";
private const string ReadDiskUsage = "Read Disk Usage";
private const string ReadDiskUsageUnit = "MBs/s";
private const string ReadDiskUsageDescription = "MBs/s read by Daybreak";

private readonly Histogram<double> writeDiskUsageHistogram;
private readonly Histogram<double> readDiskUsageHistogram;
private readonly CancellationTokenSource cancellationTokenSource = new();

private PerformanceCounter? readPerformanceCounter;
private PerformanceCounter? writePerformanceCounter;

public DiskUsageMonitor(
IMetricsService metricsService)
{
this.writeDiskUsageHistogram = metricsService.ThrowIfNull().CreateHistogram<double>(WriteDiskUsage, WriteDiskUsageUnit, WriteDiskUsageDescription, Models.Metrics.AggregationTypes.NoAggregate);
this.readDiskUsageHistogram = metricsService.ThrowIfNull().CreateHistogram<double>(ReadDiskUsage, ReadDiskUsageUnit, ReadDiskUsageDescription, Models.Metrics.AggregationTypes.NoAggregate);
}

public void OnClosing()
{
this.cancellationTokenSource.Cancel();
}

public void OnStartup()
{
_ = Task.Run(this.StartupAndPeriodicallyReadDiskUsage, this.cancellationTokenSource.Token);
}

private async Task StartupAndPeriodicallyReadDiskUsage()
{
var processName = Process.GetCurrentProcess().ProcessName;
this.readPerformanceCounter = new PerformanceCounter("Process", "IO Read Bytes/sec", processName);
this.writePerformanceCounter = new PerformanceCounter("Process", "IO Write Bytes/sec", processName);
while (!this.cancellationTokenSource.Token.IsCancellationRequested)
{
try
{
this.PeriodicallyCheckMemoryUsagePerfCounterBased();
await Task.Delay(1000, this.cancellationTokenSource.Token);
}
catch
{
}
}
}

private void PeriodicallyCheckMemoryUsagePerfCounterBased()
{
this.readDiskUsageHistogram.Record(this.readPerformanceCounter?.NextValue() / 1024 / 1024 ?? 0);
this.writeDiskUsageHistogram.Record(this.writePerformanceCounter?.NextValue() / 1024 / 1024 ?? 0);
}
}
29 changes: 18 additions & 11 deletions Daybreak/Services/Monitoring/MemoryUsageMonitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,19 @@
using System.Windows.Extensions.Services;
using System.Core.Extensions;
using System.Extensions;
using System.Threading;

namespace Daybreak.Services.Monitoring;

public sealed class MemoryUsageMonitor : IApplicationLifetimeService
{
private const string MemoryUsage = "Memory Usage";
private const string MemoryUsageUnit = "MBs";
private const string MemoryUsageDescription = "MBs used by the launcher";
private const string MemoryUsageDescription = "MBs used by Daybreak";

private readonly Histogram<long> memoryUsageHistogram;
private readonly Process currentProcess;
private readonly CancellationTokenSource cancellationTokenSource = new();

private PerformanceCounter? memoryPerformanceCounter;

Expand All @@ -28,11 +30,12 @@ public MemoryUsageMonitor(

public void OnClosing()
{
this.cancellationTokenSource.Cancel();
}

public void OnStartup()
{
_ = Task.Run(this.StartupAndPeriodicallyReadMemoryUsage);
_ = Task.Run(this.StartupAndPeriodicallyReadMemoryUsage, this.cancellationTokenSource.Token);
}

private async Task StartupAndPeriodicallyReadMemoryUsage()
Expand All @@ -51,20 +54,24 @@ private async Task StartupAndPeriodicallyReadMemoryUsage()

private async Task PeriodicallyCheckMemoryUsagePerfCounterBased()
{
if (this.memoryPerformanceCounter is not null)
while (!this.cancellationTokenSource.IsCancellationRequested)
{
this.memoryUsageHistogram.Record(this.memoryPerformanceCounter.RawValue / 1024);
}
if (this.memoryPerformanceCounter is not null)
{
this.memoryUsageHistogram.Record(this.memoryPerformanceCounter.RawValue / 1024);
}

await Task.Delay(1000);
_ = Task.Run(this.PeriodicallyCheckMemoryUsagePerfCounterBased);
await Task.Delay(1000);
}
}

private async Task PeriodicallyCheckMemoryUsageProcessBased()
{
this.currentProcess.Refresh();
this.memoryUsageHistogram.Record(this.currentProcess.PrivateMemorySize64 / 1000000);
await Task.Delay(1000);
_ = Task.Run(this.PeriodicallyCheckMemoryUsageProcessBased);
while (!this.cancellationTokenSource.IsCancellationRequested)
{
this.currentProcess.Refresh();
this.memoryUsageHistogram.Record(this.currentProcess.PrivateMemorySize64 / 1000000);
await Task.Delay(1000);
}
}
}
29 changes: 17 additions & 12 deletions Daybreak/Services/Monitoring/ProcessorUsageMonitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Core.Extensions;
using System.Diagnostics;
using System.Diagnostics.Metrics;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Extensions.Services;

Expand All @@ -12,11 +13,12 @@ public sealed class ProcessorUsageMonitor : IApplicationLifetimeService
{
private const string ProcessorTime = "Processor Usage";
private const string ProcessorTimeUnit = "% CPU";
private const string ProcessorTimeDescription = "Percentage of CPU used by the launcher";
private const string ProcessorTimeDescription = "Percentage of CPU used by Daybreak";

private readonly Histogram<double> processorTimeHistogram;
private readonly Process currentProcess;
private readonly int processorCount;
private readonly CancellationTokenSource cancellationTokenSource = new();

public ProcessorUsageMonitor(
IMetricsService metricsService)
Expand All @@ -28,24 +30,27 @@ public ProcessorUsageMonitor(

public void OnClosing()
{
this.cancellationTokenSource.Cancel();
}

public void OnStartup()
{
_ = Task.Run(this.PeriodicallyCheckCPU);
_ = Task.Run(this.PeriodicallyCheckCPU, this.cancellationTokenSource.Token);
}

private async Task PeriodicallyCheckCPU()
{
var stopwatch = Stopwatch.StartNew();
var startCpuUsage = this.currentProcess.TotalProcessorTime;
await Task.Delay(1000);

var endCpuUsage = this.currentProcess.TotalProcessorTime;
var elapsedTicks = stopwatch.ElapsedTicks;
var usage = (double)(endCpuUsage - startCpuUsage).Ticks / (double)elapsedTicks / this.processorCount * 100d;

this.processorTimeHistogram.Record(usage);
_ = Task.Run(this.PeriodicallyCheckCPU);
while (!this.cancellationTokenSource.IsCancellationRequested)
{
var stopwatch = Stopwatch.StartNew();
var startCpuUsage = this.currentProcess.TotalProcessorTime;
await Task.Delay(1000, this.cancellationTokenSource.Token);

var endCpuUsage = this.currentProcess.TotalProcessorTime;
var elapsedTicks = stopwatch.ElapsedTicks;
var usage = (double)(endCpuUsage - startCpuUsage).Ticks / (double)elapsedTicks / this.processorCount * 100d;

this.processorTimeHistogram.Record(usage);
}
}
}
5 changes: 5 additions & 0 deletions Daybreak/Services/Scanner/GWCAMemoryReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -777,6 +777,11 @@ private static PlayerInformation ParsePayload(PartyPlayerPayload partyPlayerPayl
return default;
}

if (a.ActualLevel == 64)
{
return default;
}

return new AttributeEntry
{
Attribute = attribute,
Expand Down
3 changes: 2 additions & 1 deletion Daybreak/Views/LauncherView.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,12 +111,13 @@ private async void SplitButton_Click(object sender, RoutedEventArgs e)
if (this.applicationLauncher.GetGuildwarsProcess(this.LatestConfiguration) is GuildWarsApplicationLaunchContext context)
{
// Detected already running guildwars process
await this.Dispatcher.InvokeAsync(() => this.Loading = false);
if (this.focusViewOptions.Value.Enabled)
{
this.menuService.CloseMenu();
this.viewManager.ShowView<FocusView>(context);
}

await this.Dispatcher.InvokeAsync(() => this.Loading = false);
return;
}

Expand Down

0 comments on commit 85921c6

Please sign in to comment.