Update charts only after render, avoid registering multiple timers. Format axes

dashboard
Pat Hartl 2023-03-03 17:32:16 -06:00
parent d24063b545
commit d09ecb3efb
7 changed files with 243 additions and 62 deletions

View File

@ -1,45 +1,66 @@
@using System.Diagnostics; @using System.Diagnostics;
@using LANCommander.Extensions; @using LANCommander.Extensions;
@using AntDesign.Charts; @using AntDesign.Charts;
<Line @ref="Chart" Config="Config" /> @using System.Collections.Concurrent;
<Area @ref="Chart" Config="Config" />
@code { @code {
[Parameter] public int TimerHistory { get; set; } [Parameter] public int TimerHistory { get; set; }
[Parameter] public int TimerInterval { get; set; } [Parameter] public int TimerInterval { get; set; }
IChartComponent? Chart; IChartComponent? Chart;
System.Timers.Timer Timer;
Dictionary<string, double[]> Data = new Dictionary<string, double[]>(); Dictionary<string, double[]> Data = new Dictionary<string, double[]>();
Dictionary<string, PerformanceCounter> PerformanceCounters = new Dictionary<string, PerformanceCounter>(); ConcurrentDictionary<string, PerformanceCounter> PerformanceCounters = new ConcurrentDictionary<string, PerformanceCounter>();
LineConfig Config = new LineConfig string JsConfig = @"{
meta: {
value: {
alias: 'Speed',
formatter: (v) => humanFileSize(v, true) + '/s'
}
}
}";
AreaConfig Config = new AreaConfig
{ {
Name = "Network Download Rate", Name = "Network Download Rate",
Padding = "auto", Padding = "auto",
SeriesField = "Series", SeriesField = "series",
YField = "Value", YField = "value",
XField = "Index", XField = "index",
Animation = false,
XAxis = new ValueCatTimeAxis XAxis = new ValueCatTimeAxis
{ {
Type = "dateTime", Visible = false
TickCount = 1
} }
}; };
protected override void OnInitialized() protected override async Task OnInitializedAsync()
{ {
var timer = new System.Timers.Timer(); if (Timer == null)
{
Timer = new System.Timers.Timer();
timer.Interval = TimerInterval; Timer.Interval = TimerInterval;
timer.Elapsed += async (s, e) => Timer.Elapsed += async (s, e) =>
{ {
await RefreshData(); await RefreshData();
await InvokeAsync(StateHasChanged);
}; };
}
}
timer.Start(); protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await Chart.UpdateChart(Config, null, null, JsConfig);
Timer.Start();
}
} }
private async Task RefreshData() private async Task RefreshData()
@ -57,6 +78,6 @@
Data[instance] = Data[instance].ShiftArrayAndInsert((double)PerformanceCounters[instance].NextValue(), TimerHistory); Data[instance] = Data[instance].ShiftArrayAndInsert((double)PerformanceCounters[instance].NextValue(), TimerHistory);
} }
await Chart.ChangeData(Data.SelectMany(x => x.Value.Select((y, i) => new { Value = y, Index = i, Series = x.Key })), true); await Chart.ChangeData(Data.SelectMany(x => x.Value.Select((y, i) => new { value = y, index = i, series = x.Key })), true);
} }
} }

View File

@ -1,45 +1,66 @@
@using System.Diagnostics; @using System.Diagnostics;
@using LANCommander.Extensions; @using LANCommander.Extensions;
@using AntDesign.Charts; @using AntDesign.Charts;
<Line @ref="Chart" Config="Config" /> @using System.Collections.Concurrent;
<Area @ref="Chart" Config="Config" />
@code { @code {
[Parameter] public int TimerHistory { get; set; } [Parameter] public int TimerHistory { get; set; }
[Parameter] public int TimerInterval { get; set; } [Parameter] public int TimerInterval { get; set; }
IChartComponent? Chart; IChartComponent? Chart;
System.Timers.Timer Timer;
Dictionary<string, double[]> Data = new Dictionary<string, double[]>(); Dictionary<string, double[]> Data = new Dictionary<string, double[]>();
Dictionary<string, PerformanceCounter> PerformanceCounters = new Dictionary<string, PerformanceCounter>(); ConcurrentDictionary<string, PerformanceCounter> PerformanceCounters = new ConcurrentDictionary<string, PerformanceCounter>();
LineConfig Config = new LineConfig string JsConfig = @"{
meta: {
value: {
alias: 'Speed',
formatter: (v) => humanFileSize(v, true) + '/s'
}
}
}";
AreaConfig Config = new AreaConfig
{ {
Name = "Network Upload Rate", Name = "Network Upload Rate",
Padding = "auto", Padding = "auto",
SeriesField = "Series", SeriesField = "series",
YField = "Value", YField = "value",
XField = "Index", XField = "index",
Animation = false,
XAxis = new ValueCatTimeAxis XAxis = new ValueCatTimeAxis
{ {
Type = "dateTime", Visible = false
TickCount = 1
} }
}; };
protected override void OnInitialized() protected override async Task OnInitializedAsync()
{ {
var timer = new System.Timers.Timer(); if (Timer == null)
{
Timer = new System.Timers.Timer();
timer.Interval = TimerInterval; Timer.Interval = TimerInterval;
timer.Elapsed += async (s, e) => Timer.Elapsed += async (s, e) =>
{ {
await RefreshData(); await RefreshData();
await InvokeAsync(StateHasChanged);
}; };
}
}
timer.Start(); protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await Chart.UpdateChart(Config, null, null, JsConfig);
Timer.Start();
}
} }
private async Task RefreshData() private async Task RefreshData()
@ -57,6 +78,6 @@
Data[instance] = Data[instance].ShiftArrayAndInsert((double)PerformanceCounters[instance].NextValue(), TimerHistory); Data[instance] = Data[instance].ShiftArrayAndInsert((double)PerformanceCounters[instance].NextValue(), TimerHistory);
} }
await Chart.ChangeData(Data.SelectMany(x => x.Value.Select((y, i) => new { Value = y, Index = i, Series = x.Key })), true); await Chart.ChangeData(Data.SelectMany(x => x.Value.Select((y, i) => new { value = y, index = i, series = x.Key })), true);
} }
} }

View File

@ -1,50 +1,75 @@
@using System.Diagnostics; @using System.Diagnostics;
@using LANCommander.Extensions; @using LANCommander.Extensions;
@using AntDesign.Charts; @using AntDesign.Charts;
<Line @ref="Chart" Config="Config" />
<Area @ref="Chart" Config="Config" />
@code { @code {
[Parameter] public int TimerHistory { get; set; } [Parameter] public int TimerHistory { get; set; }
[Parameter] public int TimerInterval { get; set; } [Parameter] public int TimerInterval { get; set; }
IChartComponent? Chart; IChartComponent? Chart;
System.Timers.Timer Timer;
double[] Data; double[] Data;
PerformanceCounter PerformanceCounter = new PerformanceCounter("Processor", "% Processor Time", "_Total"); PerformanceCounter PerformanceCounter = new PerformanceCounter("Processor", "% Processor Time", "_Total");
LineConfig Config = new LineConfig string JsConfig = @"{
meta: {
value: {
alias: '% Usage',
formatter: (v) => v + '%'
}
}
}";
AreaConfig Config = new AreaConfig
{ {
Name = "Processor Utilization", Name = "Processor Utilization",
Padding = "auto", Padding = "auto",
YField = "Value", YField = "value",
XField = "Index", XField = "index",
Animation = false,
IsPercent = true,
YAxis = new ValueAxis
{
Min = 0,
Max = 100
},
XAxis = new ValueCatTimeAxis XAxis = new ValueCatTimeAxis
{ {
Type = "dateTime", Visible = false
TickCount = 1
} }
}; };
protected override void OnInitialized() protected override async Task OnInitializedAsync()
{ {
var timer = new System.Timers.Timer(); if (Timer == null)
{
Timer = new System.Timers.Timer();
timer.Interval = TimerInterval; Timer.Interval = TimerInterval;
timer.Elapsed += async (s, e) => Timer.Elapsed += async (s, e) =>
{ {
await RefreshData(); await RefreshData();
await InvokeAsync(StateHasChanged);
}; };
}
}
timer.Start(); protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await Chart.UpdateChart(Config, null, null, JsConfig);
Timer.Start();
}
} }
private async Task RefreshData() private async Task RefreshData()
{ {
Data = Data.ShiftArrayAndInsert((double)PerformanceCounter.NextValue(), TimerHistory); Data = Data.ShiftArrayAndInsert((double)Math.Ceiling(PerformanceCounter.NextValue()), TimerHistory);
await Chart.ChangeData(Data.Select((x, i) => new { Value = x, Index = i }), true); await Chart.ChangeData(Data.Select((x, i) => new { value = x, index = i }), true);
} }
} }

View File

@ -0,0 +1,63 @@
@using AntDesign.Charts
@using ByteSizeLib
<Pie Data="Data" Config="Config" JsConfig="@JsConfig" />
@code {
object[] Data;
string JsConfig = @"{
meta: {
value: {
alias: 'Data Usage',
formatter: (v) => humanFileSize(v, true)
}
},
label: {
visible: true,
type: 'outer-center'
}
}";
PieConfig Config = new PieConfig
{
Radius = 0.8,
AngleField = "value",
ColorField = "type",
};
protected override async Task OnInitializedAsync()
{
var drives = DriveInfo.GetDrives();
var root = Path.GetPathRoot(System.Reflection.Assembly.GetExecutingAssembly().Location);
var totalStorageSize = drives.Where(d => d.IsReady && d.Name == root).Sum(d => d.TotalSize);
var totalAvailableFreeSpace = drives.Where(d => d.IsReady && d.Name == root).Sum(d => d.AvailableFreeSpace);
var totalUploadDirectorySize = new DirectoryInfo("Upload").EnumerateFiles().Sum(f => f.Length);
var totalSaveDirectorySize = new DirectoryInfo("Save").EnumerateFiles().Sum(f => f.Length);
Data = new object[]
{
new {
type = "Free",
value = totalAvailableFreeSpace
},
new {
type = "Games",
value = totalUploadDirectorySize
},
new
{
type = "Saves",
value = totalSaveDirectorySize
},
new
{
type = "Other",
value = totalStorageSize - totalAvailableFreeSpace - totalUploadDirectorySize - totalSaveDirectorySize
}
};
StateHasChanged();
}
}

View File

@ -1,10 +1,39 @@
@page "/Dashboard" @page "/"
@page "/Dashboard"
@using LANCommander.Pages.Dashboard.Charts @using LANCommander.Pages.Dashboard.Charts
<PageHeader Title="Dashboard" Style="margin-bottom: 24px" />
<GridRow Gutter="(16, 16)">
<GridCol Sm="24" Md="12">
<Card Title="Network Upload Rate">
<Body>
<NetworkDownloadRate TimerHistory="60" TimerInterval="1000" /> <NetworkDownloadRate TimerHistory="60" TimerInterval="1000" />
</Body>
</Card>
</GridCol>
<GridCol Sm="24" Md="12">
<Card Title="Network Download Rate">
<Body>
<NetworkUploadRate TimerHistory="60" TimerInterval="1000" /> <NetworkUploadRate TimerHistory="60" TimerInterval="1000" />
</Body>
</Card>
</GridCol>
<GridCol Sm="24" Md="12">
<Card Title="CPU Usage (%)">
<Body>
<ProcessorUtilization TimerHistory="60" TimerInterval="1000" /> <ProcessorUtilization TimerHistory="60" TimerInterval="1000" />
</Body>
</Card>
</GridCol>
@code { <GridCol Sm="24" Md="12">
<Card Title="Storage Usage">
} <Body>
<StorageUsage />
</Body>
</Card>
</GridCol>
</GridRow>

View File

@ -29,5 +29,6 @@
<script src="~/_content/BlazorMonaco/jsInterop.js"></script> <script src="~/_content/BlazorMonaco/jsInterop.js"></script>
<script src="~/_content/BlazorMonaco/lib/monaco-editor/min/vs/loader.js"></script> <script src="~/_content/BlazorMonaco/lib/monaco-editor/min/vs/loader.js"></script>
<script src="~/_content/BlazorMonaco/lib/monaco-editor/min/vs/editor/editor.main.js"></script> <script src="~/_content/BlazorMonaco/lib/monaco-editor/min/vs/editor/editor.main.js"></script>
<script src="~/js/site.js"></script>
</body> </body>
</html> </html>

View File

@ -2,3 +2,24 @@
// for details on configuring this project to bundle and minify static web assets. // for details on configuring this project to bundle and minify static web assets.
// Write your JavaScript code. // Write your JavaScript code.
function humanFileSize(bytes, si = false, dp = 1) {
const thresh = si ? 1000 : 1024;
if (Math.abs(bytes) < thresh) {
return bytes + ' B';
}
const units = si
? ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
let u = -1;
const r = 10 ** dp;
do {
bytes /= thresh;
++u;
} while (Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1);
return bytes.toFixed(dp) + ' ' + units[u];
}