diff options
Diffstat (limited to 'SystemdCtl.Client/Pages')
-rw-r--r-- | SystemdCtl.Client/Pages/Graphs/TimelineGraph.razor | 70 | ||||
-rw-r--r-- | SystemdCtl.Client/Pages/ServiceManage.razor | 50 |
2 files changed, 110 insertions, 10 deletions
diff --git a/SystemdCtl.Client/Pages/Graphs/TimelineGraph.razor b/SystemdCtl.Client/Pages/Graphs/TimelineGraph.razor new file mode 100644 index 0000000..9c53986 --- /dev/null +++ b/SystemdCtl.Client/Pages/Graphs/TimelineGraph.razor @@ -0,0 +1,70 @@ +@using ArcaneLibs +<svg width="@Width" height="@Height"> + @if (Data.Count > 0) { + <polyline points="@string.Join(" ", _points.Select(x => $"{x.Key},{x.Value}"))" style="fill:none;stroke:black;stroke-width:3"/> + @* Y min/max labels *@ + <text> + <text x="0" y="@Height" fill="black">@(ValueFormatter.Invoke(_minValue))</text> + <text x="0" y="15" fill="black">@(ValueFormatter.Invoke(_maxValue))</text> + </text> + @* outline *@ + <rect x="0" y="0" width="@Width" height="@Height" style="fill:none;stroke:black;stroke-width:1"/> + } +</svg> + +@code { + + [Parameter] + public Dictionary<DateTime, double> Data { get; set; } + + [Parameter] + public int Width { get; set; } = 100; + + [Parameter] + public int Height { get; set; } = 100; + + [Parameter] + public double? MinValue { get; set; } + + [Parameter] + public double? MaxValue { get; set; } + + //value formatter + [Parameter] + public Func<double, string> ValueFormatter { get; set; } = x => x.ToString("X2"); + + private double _minValue => MinValue ?? (Data.Count > 0 ? Data.Values.Min() : 0); + private double _maxValue => MaxValue ?? (Data.Count > 0 ? Data.Values.Max() : 0); + + private Dictionary<double, double> _points = []; + + protected override async Task OnParametersSetAsync() { + await RebuildGraph(); + await base.OnParametersSetAsync(); + } + + private async Task RebuildGraph() { + if (Data.Count == 0) return; + _points.Clear(); + var startTime = Data.Keys.Min(x => x).Ticks; + var endTime = Data.Keys.Max(x => x).Ticks; + var minValue = _minValue; + var maxValue = _maxValue; + Console.WriteLine($"Start: {startTime}, End: {endTime}, Min: {minValue}, Max: {maxValue}"); + foreach (var item in Data) { + _points.Add(Map(item.Key.Ticks, startTime, endTime, 0, Width), + Map(item.Value, minValue, maxValue, Height, 0)); + } + } + + public static double Map( + double value, + double originalStart, + double originalEnd, + double newStart, + double newEnd) { + double num = (newEnd - newStart) / (originalEnd - originalStart); + return newStart + (value - originalStart) * num; + } + +} \ No newline at end of file diff --git a/SystemdCtl.Client/Pages/ServiceManage.razor b/SystemdCtl.Client/Pages/ServiceManage.razor index 851f1b6..39be89e 100644 --- a/SystemdCtl.Client/Pages/ServiceManage.razor +++ b/SystemdCtl.Client/Pages/ServiceManage.razor @@ -6,6 +6,8 @@ @using SystemdCtl.Client.Abstractions @using System.Diagnostics.Metrics @using System.Text.Json +@using ArcaneLibs +@using SystemdCtl.Client.Pages.Graphs @* @attribute [StreamRendering] *@ @rendermode InteractiveWebAssembly @inject NavigationManager NavigationManager @@ -33,6 +35,19 @@ </div> </div> +<div class="row"> + <div class="col-12"> + <h3>Statistics</h3> + <div class="card"> + <div class="card-body"> + <p>CPU usage: @ServiceInfo?.CpuUsagePercent.ToString("P2")</p> + <TimelineGraph Data="@CpuUsageHistory" Height="50" Width="500" MinValue="0" ValueFormatter="@(value => value.ToString("P2"))"/> + <p>Memory usage: @Util.SI_BytesToString(ServiceInfo?.MemoryCurrent ?? 0) (peak: @Util.SI_BytesToString(ServiceInfo?.MemoryPeak ?? 0))</p> + <TimelineGraph Data="@MemoryUsageHistory" Height="50" Width="500" ValueFormatter="@(value => Util.SI_BytesToString((long)value))"/> + </div> + </div> + </div> +</div> @* //simple log view *@ <div class="row"> @@ -72,17 +87,22 @@ private async Task Run() { if (!IsClient) return; - int history = 100; - LogLines.Clear(); GetServiceDataTask(); + GetLogs(); + } + + private JsonSerializerOptions jso = new() { + DefaultBufferSize = 1, + AllowTrailingCommas = true + }; + + private async Task GetLogs() { + int history = 100; var Http = new StreamingHttpClient() { BaseAddress = new Uri(NavigationManager.BaseUri) }; - var jso = new JsonSerializerOptions() { - DefaultBufferSize = 1, - AllowTrailingCommas = true - }; + + LogLines.Clear(); var _items = Http.GetAsyncEnumerableFromJsonAsync<SystemdJournalLogItem>($"/api/unit/{ServiceName}/logs?contextLines={history}", jso); await foreach (var item in _items) { - // LogLines.RemoveAll(x=>x.SystemdInvocationId != item.SystemdInvocationId); LogLines.Add(item); if (LogLines.Count > history) { LogLines.RemoveAt(0); @@ -95,10 +115,17 @@ private async Task GetServiceDataTask() { if (!IsClient) return; var Http = new StreamingHttpClient() { BaseAddress = new Uri(NavigationManager.BaseUri) }; - while (true) { - ServiceInfo = await Http.GetFromJsonAsync<SystemdServiceData>($"/api/unit/{ServiceName}/data"); + + var stream = Http.GetAsyncEnumerableFromJsonAsync<SystemdServiceData>($"/api/unit/{ServiceName}/dataStream", jso); + await foreach (var item in stream) { + ServiceInfo = item; + CpuUsageHistory.Add(ServiceInfo.Timestamp, ServiceInfo.CpuUsagePercent); + MemoryUsageHistory.Add(ServiceInfo.Timestamp, ServiceInfo.MemoryCurrent); + CpuUsageHistory = CpuUsageHistory.Where((x, y) => x.Key > DateTime.Now.AddMinutes(-1)) + .OrderBy(x => x.Key).ToDictionary(); + MemoryUsageHistory = MemoryUsageHistory.Where((x, y) => x.Key > DateTime.Now.AddMinutes(-1)).OrderBy(x => x.Key).ToDictionary(); + StateHasChanged(); - await Task.Delay(TimeSpan.FromSeconds(5)); } } @@ -130,4 +157,7 @@ await Http.GetAsync($"/api/unit/{ServiceName}/kill", null); } + public Dictionary<DateTime, double> CpuUsageHistory { get; set; } = []; + public Dictionary<DateTime, double> MemoryUsageHistory { get; set; } = []; + } \ No newline at end of file |