| diff --git a/LibSystemdCli.Models/LibSystemdCli.Models.csproj b/LibSystemdCli.Models/LibSystemdCli.Models.csproj
index 3a63532..17b910f 100644
--- a/LibSystemdCli.Models/LibSystemdCli.Models.csproj
+++ b/LibSystemdCli.Models/LibSystemdCli.Models.csproj
@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
     <PropertyGroup>
-        <TargetFramework>net8.0</TargetFramework>
+        <TargetFramework>net9.0</TargetFramework>
         <ImplicitUsings>enable</ImplicitUsings>
         <Nullable>enable</Nullable>
     </PropertyGroup>
diff --git a/LibSystemdCli.Models/SystemdServiceData.cs b/LibSystemdCli.Models/SystemdServiceData.cs
 index 1efcfd7..8d35c26 100644
--- a/LibSystemdCli.Models/SystemdServiceData.cs
+++ b/LibSystemdCli.Models/SystemdServiceData.cs
@@ -9,14 +9,30 @@ public class SystemdServiceData {
     [JsonPropertyName("multiData")]
     public Dictionary<string, List<string>> MultiData { get; set; } = new();
 
-    public bool IsRunning => Status is "active" or "reloading";
+    public bool IsRunning => Status is "active" or "reloading" or "deactating" or "activating";
     public string Status => GetSingleData("ActiveState") ?? "unknown";
+    
+    public long MemoryCurrent {
+        get {
+            if (long.TryParse(GetSingleData("MemoryCurrent") ?? "0", out long mem)) return mem;
+            return 0;
+        }
+    }
+
+    public long MemoryPeak {
+        get {
+            if (long.TryParse(GetSingleData("MemoryPeak") ?? "0", out long mem)) return mem;
+            return 0;
+        }
+    }
+    
+    
     public void AddData(string key, string value) {
         if (MultiData.ContainsKey(key)) {
             MultiData[key].Add(value);
         }
         else if (SingleData.ContainsKey(key)) {
-            MultiData[key] = new List<string> { SingleData[key], value };
+            MultiData[key] = [SingleData[key], value];
             SingleData.Remove(key);
         }
         else {
@@ -43,4 +59,17 @@ public class SystemdServiceData {
 
         return null;
     }
+
+    public void SetOldData(SystemdServiceData oldData) {
+        var now = DateTime.Now;
+        
+        var cpuDiff = CpuUsageNs - oldData.CpuUsageNs;
+        var timeDiff = now - oldData.Timestamp;
+        CpuUsagePercent = cpuDiff / timeDiff.TotalMilliseconds / 1_000_000d;
+    }
+    
+    public DateTime Timestamp { get; set; } = DateTime.Now;
+    public long CpuUsageNs => long.Parse(GetSingleData("CPUUsageNSec") ?? "0");
+    
+    public double CpuUsagePercent { get; set; }
 }
\ No newline at end of file
diff --git a/LibSystemdCli/CommandExecutor.cs b/LibSystemdCli/CommandExecutor.cs
 index 096f1c1..8a21bc5 100644
--- a/LibSystemdCli/CommandExecutor.cs
+++ b/LibSystemdCli/CommandExecutor.cs
@@ -27,6 +27,16 @@ public class CommandExecutor
             throw new Exception($"Command {command} {args} failed with exit code {process.ExitCode} and error: {error}");
         }
         
+        if (string.IsNullOrWhiteSpace(output))
+        {
+            Console.WriteLine($"[{DateTime.Now:O}] Command {command} {args} produced no output.");
+        }
+        
+        if (!string.IsNullOrWhiteSpace(error))
+        {
+            Console.WriteLine($"[{DateTime.Now:O}] Command {command} {args} produced error output: {error}");
+        }
+        
         return output;
     }
     
diff --git a/LibSystemdCli/LibSystemdCli.csproj b/LibSystemdCli/LibSystemdCli.csproj
 index 1d1bffa..0c142de 100644
--- a/LibSystemdCli/LibSystemdCli.csproj
+++ b/LibSystemdCli/LibSystemdCli.csproj
@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
     <PropertyGroup>
-        <TargetFramework>net8.0</TargetFramework>
+        <TargetFramework>net9.0</TargetFramework>
         <ImplicitUsings>enable</ImplicitUsings>
         <Nullable>enable</Nullable>
     </PropertyGroup>
diff --git a/LibSystemdCli/SystemdExecutor.cs b/LibSystemdCli/SystemdExecutor.cs
 index ead11fd..f252891 100644
--- a/LibSystemdCli/SystemdExecutor.cs
+++ b/LibSystemdCli/SystemdExecutor.cs
@@ -8,7 +8,15 @@ public class SystemdExecutor {
     public static async IAsyncEnumerable<SystemdUnitListItem> GetUnits() {
         var output = await CommandExecutor.ExecuteCommand("systemctl", "list-units --all --no-legend --no-pager --no-legend -o json-pretty");
 
-        var data = JsonSerializer.Deserialize<List<SystemdUnitListItem>>(output);
+        List<SystemdUnitListItem>? data;
+        try {
+            data = JsonSerializer.Deserialize<List<SystemdUnitListItem>>(output);
+        }
+        catch (Exception ex) {
+            Console.WriteLine("Failed to parse systemctl output: " + ex);
+            Console.WriteLine("Output was: " + output);
+            yield break;
+        }
 
         foreach (var unit in data) {
             try {
@@ -16,7 +24,9 @@ public class SystemdExecutor {
                 // Console.WriteLine(fragmentOutput);
                 unit.FragmentPaths = fragmentOutput.Split('\n', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToList();
             }
-            catch { }
+            catch (Exception e) {
+                Console.WriteLine("Failed to get fragment path for unit " + unit.Unit + ": " + e);
+            }
 
             yield return unit;
             // await Task.Delay(100);
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
diff --git a/SystemdCtl.Client/SystemdCtl.Client.csproj b/SystemdCtl.Client/SystemdCtl.Client.csproj
 index e00a924..677fb9a 100644
--- a/SystemdCtl.Client/SystemdCtl.Client.csproj
+++ b/SystemdCtl.Client/SystemdCtl.Client.csproj
@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
 
   <PropertyGroup>
-    <TargetFramework>net8.0</TargetFramework>
+    <TargetFramework>net9.0</TargetFramework>
     <ImplicitUsings>enable</ImplicitUsings>
     <Nullable>enable</Nullable>
     <NoDefaultLaunchSettingsFile>true</NoDefaultLaunchSettingsFile>
diff --git a/SystemdCtl/Controllers/UnitController.cs b/SystemdCtl/Controllers/UnitController.cs
 index a128584..40ef46a 100644
--- a/SystemdCtl/Controllers/UnitController.cs
+++ b/SystemdCtl/Controllers/UnitController.cs
@@ -1,51 +1,92 @@
+using System.Runtime.InteropServices;
+using System.Text;
+using ArcaneLibs.Extensions;
+using ArcaneLibs.Extensions.Streams;
 using LibSystemdCli;
 using LibSystemdCli.Models;
 using Microsoft.AspNetCore.Mvc;
 
 namespace SystemdCtl.Controllers;
+
 [ApiController]
 [Route("/api/")]
-public class UnitController : ControllerBase
-{
+public class UnitController : ControllerBase {
     [HttpGet("listUnits")]
-    public async IAsyncEnumerable<SystemdUnitListItem> GetUnits()
-    {
-        await foreach (var unit in SystemdExecutor.GetUnits())
-        {
+    public async IAsyncEnumerable<SystemdUnitListItem> GetUnits() {
+        await foreach (var unit in SystemdExecutor.GetUnits()) {
             yield return unit;
-            await Response.Body.FlushAsync();
+
+            if (Response.HasStarted)
+                await Response.Body.FlushAsync();
         }
     }
-    
+
+    // [HttpGet("unit/{serviceName}/logs")]
+    // public async IAsyncEnumerable<SystemdJournalLogItem> GetUnitLogs(string serviceName, [FromQuery] int contextLines = 100)
+    // {
+    //     await foreach (var log in SystemdExecutor.GetUnitLogs(serviceName, contextLines: contextLines))
+    //     {
+    //         Console.WriteLine(log.Message);
+    //         yield return log;
+    //         await Response.Body.FlushAsync();
+    //     }
+    // }   
     [HttpGet("unit/{serviceName}/logs")]
-    public async IAsyncEnumerable<SystemdJournalLogItem> GetUnitLogs(string serviceName, [FromQuery] int contextLines = 100)
-    {
-        await foreach (var log in SystemdExecutor.GetUnitLogs(serviceName, contextLines: contextLines))
-        {
+    public async Task GetUnitLogs(string serviceName, [FromQuery] int contextLines = 100) {
+        Response.ContentType = "application/json";
+        await Response.StartAsync();
+        await Response.Body.WriteAsync("[\n"u8.ToArray());
+        await foreach (var log in SystemdExecutor.GetUnitLogs(serviceName, contextLines: contextLines)) {
             Console.WriteLine(log.Message);
-            yield return log;
+            var bytes = Encoding.UTF8.GetBytes($"  {log.ToJson(indent: false)},\n");
+            await Response.Body.WriteAsync(bytes);
             await Response.Body.FlushAsync();
         }
+
+        await Response.Body.WriteAsync("]\n"u8.ToArray());
+        await Response.Body.FlushAsync();
+        await Response.CompleteAsync();
     }
-    
+
+    [HttpGet("unit/{serviceName}/dataStream")]
+    public async Task GetUnitDataStream(string serviceName, [FromQuery] int contextLines = 100) {
+        Response.ContentType = "application/json";
+        await Response.StartAsync();
+        await Response.Body.WriteAsync("[\n"u8.ToArray());
+        var oldData = await SystemdExecutor.GetUnitData(serviceName);
+        while (true) {
+            var data = await SystemdExecutor.GetUnitData(serviceName);
+            data.SetOldData(oldData);
+            var bytes = Encoding.UTF8.GetBytes($"  {data.ToJson(indent: false)},\n");
+            await Response.Body.WriteAsync(bytes);
+            await Response.Body.FlushAsync();
+            oldData = data;
+            await Task.Delay(1000);
+        }
+
+        await Response.Body.WriteAsync("]\n"u8.ToArray());
+        await Response.Body.FlushAsync();
+        await Response.CompleteAsync();
+    }
+
     [HttpGet("unit/{serviceName}/data")]
     public Task<SystemdServiceData> GetUnitData(string serviceName) => SystemdExecutor.GetUnitData(serviceName);
-    
+
     [HttpGet("unit/{serviceName}/start")]
     public Task StartUnit(string serviceName) => CommandExecutor.ExecuteCommand("systemctl", $"start {serviceName}");
-    
+
     [HttpGet("unit/{serviceName}/stop")]
     public Task StopUnit(string serviceName) => CommandExecutor.ExecuteCommand("systemctl", $"stop {serviceName}");
-    
+
     [HttpGet("unit/{serviceName}/restart")]
     public Task RestartUnit(string serviceName) => CommandExecutor.ExecuteCommand("systemctl", $"restart {serviceName}");
-    
+
     [HttpGet("unit/{serviceName}/reload")]
     public Task ReloadUnit(string serviceName) => CommandExecutor.ExecuteCommand("systemctl", $"reload {serviceName}");
-    
+
     [HttpGet("unit/{serviceName}/enable")]
     public Task EnableUnit(string serviceName) => CommandExecutor.ExecuteCommand("systemctl", $"enable {serviceName}");
-    
+
     [HttpGet("unit/{serviceName}/disable")]
     public Task DisableUnit(string serviceName) => CommandExecutor.ExecuteCommand("systemctl", $"disable {serviceName}");
 }
\ No newline at end of file
diff --git a/SystemdCtl/SystemdCtl.csproj b/SystemdCtl/SystemdCtl.csproj
 index 7a4f701..4425832 100644
--- a/SystemdCtl/SystemdCtl.csproj
+++ b/SystemdCtl/SystemdCtl.csproj
@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk.Web">
 
   <PropertyGroup>
-    <TargetFramework>net8.0</TargetFramework>
+    <TargetFramework>net9.0</TargetFramework>
     <Nullable>enable</Nullable>
     <ImplicitUsings>enable</ImplicitUsings>
   </PropertyGroup>
@@ -9,7 +9,7 @@
   <ItemGroup>
     <ProjectReference Include="..\LibSystemdCli\LibSystemdCli.csproj" />
     <ProjectReference Include="..\SystemdCtl.Client\SystemdCtl.Client.csproj" />
-    <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="8.0.0" />
+    <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="9.0.8" />
   </ItemGroup>
 
   <ItemGroup>
 |