Skip to content

Commit

Permalink
Merge pull request #2 from CriggerMarg/feature/web-socket
Browse files Browse the repository at this point in the history
implement web socket data refresh
  • Loading branch information
CriggerMarg authored May 24, 2020
2 parents 71a4ab4 + 9fcb75c commit ae667ff
Show file tree
Hide file tree
Showing 32 changed files with 808 additions and 68 deletions.
24 changes: 24 additions & 0 deletions Tasklist.Background/Extensions/ConfigurationExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@

using Microsoft.Extensions.Configuration;

namespace Tasklist.Background.Extensions
{
public static class ConfigurationExtensions
{
public static int ReadIntConfigValue(this IConfiguration configuration, string key, int defaultValue)
{
var value = configuration[key];
if (value == null)
{
return defaultValue;
}
int number;
if(int.TryParse(value, out number))
{
return number;
}
return defaultValue;
}

}
}
8 changes: 7 additions & 1 deletion Tasklist.Background/IProcessRepository.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
using System.Collections.Generic;
using System.Net.WebSockets;
using System.Threading.Tasks;

namespace Tasklist.Background
{
public interface IProcessRepository
{
IReadOnlyCollection<ProcessInformation> ProcessInformation { get; set; }
IReadOnlyCollection<ProcessInformation> ProcessInformation { get; }
Task SetProcessInfo(IReadOnlyCollection<ProcessInformation> info);
bool IsCpuHigh { get; set; }
bool IsMemoryLow { get; set; }
void AddSocket(WebSocket socket, TaskCompletionSource<object> tcs);
}
}
65 changes: 61 additions & 4 deletions Tasklist.Background/InMemoryProcessRepository.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Net.WebSockets;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;

namespace Tasklist.Background
{
public class InMemoryProcessRepository : IProcessRepository
{
private IReadOnlyCollection<ProcessInformation> _processes = new List<ProcessInformation>();

private WebSocket _socket;
private static readonly object _locker = new object();

public InMemoryProcessRepository()
{
IsCpuHigh = true;
IsMemoryLow = true;
}

public IReadOnlyCollection<ProcessInformation> ProcessInformation
{
Expand All @@ -16,7 +27,53 @@ public IReadOnlyCollection<ProcessInformation> ProcessInformation
lock (_locker)
{ return _processes; }
}
set { lock (_locker) { _processes = value; } }
private set
{
lock (_locker)
{
_processes = value;
}
}
}

public async Task SetProcessInfo(IReadOnlyCollection<ProcessInformation> info)
{
ProcessInformation = info;
await Send();
}

public bool IsCpuHigh
{
get;
set;
}

public bool IsMemoryLow
{
get;
set;
}


private async Task Send()
{
if (_socket == null)
{
return;
}
if (_socket.State != WebSocketState.Open)
return;

var message = JsonSerializer.Serialize(ProcessInformation);
var buffer = new ArraySegment<byte>(Encoding.ASCII.GetBytes(message), 0, message.Length);

await _socket.SendAsync(buffer, WebSocketMessageType.Text, true, CancellationToken.None);
}

public void AddSocket(WebSocket socket, TaskCompletionSource<object> tcs)
{
_socket = socket;
tcs.SetResult(true);
}
}
}
}
16 changes: 11 additions & 5 deletions Tasklist.Background/ProcessListHostedService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ private async Task BackgroundProcessing(CancellationToken stoppingToken)
{
try
{
_processRepository.ProcessInformation = ReadProcessInfo();
await _processRepository.SetProcessInfo(ReadProcessInfo());

await Task.Delay(TimeSpan.FromMilliseconds(_refreshRateInMS), stoppingToken);
}
Expand Down Expand Up @@ -94,18 +94,24 @@ private IReadOnlyCollection<ProcessInformation> ReadProcessInfo()
}
var els = entry.Trim().Split(' ');
var name = els.First();
if (name == "_total")
{

}
if (name == "_total" || name == "idle" || name == "system")
{
continue;
}
float cpu = 0;
float.TryParse(els.Last(), out cpu);
list.Add(new ProcessInformation()
if (cpu > 0)
{
Name = els.First(),
CPULoad = cpu
});
list.Add(new ProcessInformation()
{
Name = els.First(),
CPULoad = cpu
});
}
}
}
catch (Exception e)
Expand Down
25 changes: 25 additions & 0 deletions Tasklist.Background/SysInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;

namespace Tasklist.Background
{
public struct SysInfo : IEquatable<SysInfo>
{
public bool HighCpu { get; set; }
public bool LowMemory { get; set; }

public bool Equals(SysInfo other)
{
return HighCpu == other.HighCpu && LowMemory == other.LowMemory;
}

public override bool Equals(object obj)
{
return obj is SysInfo other && Equals(other);
}

public override int GetHashCode()
{
return HashCode.Combine(HighCpu, LowMemory);
}
}
}
121 changes: 121 additions & 0 deletions Tasklist.Background/SysInfoHostedService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

using Tasklist.Background.Extensions;

namespace Tasklist.Background
{
public class SysInfoHostedService : BackgroundService
{
private readonly ILogger<SysInfoHostedService> _logger;
private readonly IProcessRepository _processRepository;
private readonly int _refreshRateInMs;
private readonly int _cpuHighValue;
private readonly int _memoryLowValue;
public SysInfoHostedService(ILogger<SysInfoHostedService> logger, IProcessRepository processRepository, IConfiguration configuration)
{
_logger = logger;
_processRepository = processRepository;
_refreshRateInMs = configuration.ReadIntConfigValue("TasklistRefreshRateMS", 50);
_cpuHighValue = configuration.ReadIntConfigValue("cpuHighValue", 90);
_memoryLowValue = configuration.ReadIntConfigValue("memoryLowValue", 1024);
}

public override async Task StopAsync(CancellationToken stoppingToken)
{
_logger.LogInformation($"{GetType().Name} is stopping.");

await base.StopAsync(stoppingToken);
}

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await BackgroundProcessing(stoppingToken);
}

private async Task BackgroundProcessing(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
var info = ReadSysInfo();
_processRepository.IsMemoryLow = info.LowMemory;
_processRepository.IsCpuHigh = info.HighCpu;

await Task.Delay(TimeSpan.FromMilliseconds(_refreshRateInMs), stoppingToken);
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error occurred executing {GetType().Name}");
}
}
}

private SysInfo ReadSysInfo()
{
var query = "(Get-Counter -Counter '\\Memory\\Available MBytes','\\Processor(_Total)\\% Processor Time').CounterSamples.CookedValue";
// powershell may not work on machine where it would be ran. So consider yo use WMI too
var stringData = string.Empty;
var errorData = string.Empty;
using (var process = new Process())
{
process.StartInfo.FileName = "powershell.exe";
process.StartInfo.Arguments = $"-NoProfile -ExecutionPolicy unrestricted \"{query }\"";
process.StartInfo.CreateNoWindow = true;
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;

process.OutputDataReceived += (sender, data) =>
{
if (!string.IsNullOrEmpty(data.Data))
{
stringData += data.Data + Environment.NewLine;
}
};
process.ErrorDataReceived += (sender, data) => errorData += data.Data;
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.WaitForExit(1000 * 10);
}
if (!string.IsNullOrEmpty(errorData))
{
_logger.LogError(errorData);
}
try
{
if (!string.IsNullOrEmpty(stringData))
{
var data = stringData.Split(Environment.NewLine);
float memory;
float.TryParse(data[0], out memory);

float cpu;
float.TryParse(data[1], out cpu);

return new SysInfo
{
HighCpu = cpu > _cpuHighValue,
LowMemory = memory < _memoryLowValue
};
}
}
catch (Exception e)
{
_logger.LogError(e, "oops");
}

return new SysInfo();
}
}
}
12 changes: 12 additions & 0 deletions Tasklist.Middleware/Tasklist.Middleware.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.1.4" />
</ItemGroup>

</Project>
33 changes: 33 additions & 0 deletions Tasklist.Middleware/Websocket/Extensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;

using System.Reflection;

namespace Tasklist.Middleware.Websocket
{
public static class WebSocketExtensions
{
public static IServiceCollection AddWebSocketManager(this IServiceCollection services)
{
services.AddTransient<WebSocketConnectionManager>();

foreach (var type in Assembly.GetEntryAssembly().ExportedTypes)
{
if (type.GetTypeInfo().BaseType == typeof(WebSocketHandler))
{
services.AddSingleton(type);
}
}

return services;
}

public static IApplicationBuilder MapWebSocket(this IApplicationBuilder app,
PathString path,
WebSocketHandler handler)
{
return app.Map(path, (_app) => _app.UseMiddleware<WebSocketMiddleware>(handler));
}
}
}
1 change: 1 addition & 0 deletions Tasklist.Middleware/Websocket/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Idea taken from https://radu-matei.com/blog/aspnet-core-websockets-middleware/
Loading

0 comments on commit ae667ff

Please sign in to comment.