Skip to content

Commit

Permalink
Minor fixes (#463)
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 authored Nov 4, 2023
1 parent 97d2d09 commit 32efe42
Show file tree
Hide file tree
Showing 11 changed files with 173 additions and 33 deletions.
10 changes: 8 additions & 2 deletions Daybreak.Tests/Services/JsonLoggerProviderTests.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
using Daybreak.Services.Logging;
using Daybreak.Configuration.Options;
using Daybreak.Services.Logging;
using FluentAssertions;
using LiteDB;
using Microsoft.Extensions.Logging;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NSubstitute;
using NSubstitute.Extensions;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Logging;
Expand All @@ -21,7 +25,9 @@ public void InitializeProvider()
{
File.Delete("Daybreak.db");
this.liteDatabase = new LiteDatabase("Daybreak.db");
this.logsManager = new JsonLogsManager(this.liteDatabase.GetCollection<Daybreak.Models.Log>());
var options = Substitute.For<ILiveOptions<LauncherOptions>>();
options.Value.Returns(new LauncherOptions { PersistentLogging = true });
this.logsManager = new JsonLogsManager(this.liteDatabase.GetCollection<Daybreak.Models.Log>(), options);
this.loggerProvider = new CVLoggerProvider(this.logsManager);
}

Expand Down
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 32efe42

Please sign in to comment.