Skip to content
ConsoleMonster

Container status

Watch a container host refresh every two seconds: who is restarting, who is near its memory limit, and what just happened.

Demos Dashboards Showcase

Use it when a container keeps restarting and you want to see memory pressure build before the next OOM kill.

  • Live
  • 100×34
  • PowerShell 7.4+
  • .NET 8.0+
Container status 100×3424-bitFull

How it's built

The whole program in one file, with its sample data at the top. It drew the recording above; all three styles produce the same frame bytes.

PowerShellcontainer-status.ps1View on GitHub
# Watch a container host: state, restarts, CPU and memory against each limit, with an event feed.
Import-Module -Name ConsoleMonster

# Data: four fictional samples, two seconds apart; no container engine is contacted.
$containers = @(
    [pscustomobject] @{ Name = 'web'; Image = 'nginx:1.27'; Limit = 256; Cpu = @(8, 9, 12, 10)
        Memory = @(96, 98, 101, 99); Restarts = @(0, 0, 0, 0) }
    [pscustomobject] @{ Name = 'api'; Image = 'adatum/api:4.2'; Limit = 512; Cpu = @(34, 41, 3, 22)
        Memory = @(410, 468, 120, 190); Restarts = @(2, 2, 3, 3) }
    [pscustomobject] @{ Name = 'worker'; Image = 'adatum/worker:4.2'; Limit = 896
        Cpu = @(55, 61, 72, 78); Memory = @(690, 742, 801, 846); Restarts = @(0, 0, 0, 0) }
    [pscustomobject] @{ Name = 'db'; Image = 'postgres:16'; Limit = 2048; Cpu = @(18, 22, 25, 21)
        Memory = @(1210, 1225, 1240, 1236); Restarts = @(0, 0, 0, 0) }
    [pscustomobject] @{ Name = 'cache'; Image = 'redis:7.4'; Limit = 512; Cpu = @(3, 4, 4, 3)
        Memory = @(152, 153, 153, 154); Restarts = @(0, 0, 0, 0) }
    [pscustomobject] @{ Name = 'queue'; Image = 'rabbitmq:3.13'; Limit = 1024; Cpu = @(6, 7, 9, 8)
        Memory = @(301, 305, 309, 312); Restarts = @(1, 1, 1, 1) }
)
$events = @()

# Layout: one complete screen per sample; restarts and memory pressure also go to the event feed.
function New-ContainerFrame {
    param(
        [int] $Tick
    )

    $clock = '10:00:{0:00}' -f ($Tick * 2)
    $rows = foreach ($container in $containers) {
        $memoryPercent = [math]::Round(100 * $container.Memory[$Tick] / $container.Limit)
        $restarted = $Tick -gt 0 -and $container.Restarts[$Tick] -gt $container.Restarts[$Tick - 1]
        $status = if ($restarted -or $memoryPercent -ge 90) { 'Fail' }
        elseif ($memoryPercent -ge 80) { 'Warn' }
        else { 'Pass' }
        if ($restarted) {
            $script:events = @([pscustomobject] @{ Label = "$clock $($container.Name)"
                Status = 'Fail'
                Detail = "restarted after exit 137 · restart $($container.Restarts[$Tick])" }) + $script:events
        }
        elseif ($memoryPercent -ge 90) {
            $script:events = @([pscustomobject] @{ Label = "$clock $($container.Name)"
                Status = 'Warn'
                Detail = "memory at $memoryPercent% of its $($container.Limit) MiB limit" }) + $script:events
        }
        [pscustomobject] @{
            Container = $container.Name
            Image = $container.Image
            State = $(if ($restarted) { 'restarting' } else { 'running' })
            Restarts = $container.Restarts[$Tick]
            'CPU %' = $container.Cpu[$Tick]
            'Memory MiB' = $container.Memory[$Tick]
            'Memory %' = $memoryPercent
            Status = $status
        }
    }
    $restarting = @($rows | Where-Object -Property State -EQ -Value 'restarting').Count
    $memoryTotal = ($rows | Measure-Object -Property 'Memory MiB' -Sum).Sum
    $limitTotal = ($containers | Measure-Object -Property Limit -Sum).Sum
    $feed = @($script:events | Select-Object -First 4)

    New-ConsoleRows -Gap 1 -Content {
        New-ConsoleRule -Title "[bold]Containers[/] [muted]· docker-01.adatum.example.test · refresh 2s · $clock[/]"
        ("[pass]$($rows.Count - $restarting) running[/]   [warn]$restarting restarting[/]   " +
            "[muted]memory $('{0:N0}' -f $memoryTotal) of $('{0:N0}' -f $limitTotal) MiB reserved[/]")
        New-ConsoleTable -InputObject $rows -Property Container, Image, State, Restarts, 'CPU %',
            'Memory MiB', 'Memory %', Status -Expand
        New-ConsoleColumns -Content {
            New-ConsolePanel -Title 'CPU %' -Content {
                $chartParameters = @{
                    WidgetId = 'cpu'
                    Type = 'Bar'
                    Maximum = 100
                    Warning = 70
                    Critical = 90
                }
                $rows |
                    ForEach-Object -Process {
                        [pscustomobject] @{ Label = $_.Container; Value = $_.'CPU %' }
                    } |
                        New-ConsoleChart @chartParameters
            }
            New-ConsolePanel -Title 'Memory % of limit' -Content {
                $chartParameters = @{
                    WidgetId = 'memory'
                    Type = 'Bar'
                    Maximum = 100
                    Warning = 80
                    Critical = 90
                }
                $rows |
                    ForEach-Object -Process {
                        [pscustomobject] @{ Label = $_.Container; Value = $_.'Memory %' }
                    } |
                        New-ConsoleChart @chartParameters
            }
        }
        New-ConsolePanel -Title 'Events' -Content {
            if ($feed.Count -eq 0) {
                '[muted]No restarts or memory warnings yet.[/]'
            }
            else {
                New-ConsoleStatusList -Content { $feed }
            }
        }
    }
}

# Behaviour: build each sample's screen, then replay them in a bounded live dashboard.
$frames = @(0..3 | ForEach-Object -Process { New-ContainerFrame -Tick $_ })
$screen = New-ConsoleRegion -WidgetId containers -Content $frames[0]
Invoke-ConsoleDashboard -Dashboard $screen -ArgumentList (, $frames) -ScriptBlock {
    param($Frames)
    foreach ($frame in $Frames) {
        if ($ConsoleSession.Cancellation.IsCancellationRequested) {
            break
        }
        Update-Console -WidgetId containers -Content $frame
        Wait-ConsoleDashboard -Seconds 2
    }
}
C#Program.csView on GitHub
// Watch a container host: state, restarts, CPU and memory against each limit, with an event feed.
using ConsoleMonster;
using ConsoleMonster.Layout;
using ConsoleMonster.Live;
using ConsoleMonster.Presenters;
using ConsoleMonster.Styling;
using ConsoleMonster.Widgets;

// Data: four fictional samples, two seconds apart; no container engine is contacted.
var containers = new Container[]
{
    new(
        "web",
        "nginx:1.27",
        256,
        new[] { 8, 9, 12, 10 },
        new[] { 96, 98, 101, 99 },
        new[] { 0, 0, 0, 0 }),
    new(
        "api",
        "adatum/api:4.2",
        512,
        new[] { 34, 41, 3, 22 },
        new[] { 410, 468, 120, 190 },
        new[] { 2, 2, 3, 3 }),
    new(
        "worker",
        "adatum/worker:4.2",
        896,
        new[] { 55, 61, 72, 78 },
        new[] { 690, 742, 801, 846 },
        new[] { 0, 0, 0, 0 }),
    new(
        "db",
        "postgres:16",
        2048,
        new[] { 18, 22, 25, 21 },
        new[] { 1210, 1225, 1240, 1236 },
        new[] { 0, 0, 0, 0 }),
    new(
        "cache",
        "redis:7.4",
        512,
        new[] { 3, 4, 4, 3 },
        new[] { 152, 153, 153, 154 },
        new[] { 0, 0, 0, 0 }),
    new(
        "queue",
        "rabbitmq:3.13",
        1024,
        new[] { 6, 7, 9, 8 },
        new[] { 301, 305, 309, 312 },
        new[] { 1, 1, 1, 1 })
};
var events = new List<Event>();

// Layout: one complete screen per sample; restarts and memory pressure also go to the event feed.
IWidget Frame(int tick)
{
    string clock = $"10:00:{tick * 2:00}";
    var rows = new List<ContainerRow>();
    foreach (var container in containers)
    {
        int memoryPercent = (int)Math.Round(100.0 * container.Memory[tick] / container.Limit);
        bool restarted = tick > 0 && container.Restarts[tick] > container.Restarts[tick - 1];
        string status = restarted || memoryPercent >= 90
            ? "Fail"
            : memoryPercent >= 80 ? "Warn" : "Pass";
        if (restarted)
            events.Insert(
                0,
                new Event($"{clock} {container.Name}", StatusKind.Fail,
                    $"restarted after exit 137 · restart {container.Restarts[tick]}"));
        else if (memoryPercent >= 90)
            events.Insert(
                0,
                new Event($"{clock} {container.Name}", StatusKind.Warn,
                    $"memory at {memoryPercent}% of its {container.Limit} MiB limit"));
        rows.Add(new ContainerRow(container.Name, container.Image,
            restarted ? "restarting" : "running", container.Restarts[tick], container.Cpu[tick],
            container.Memory[tick], memoryPercent, status));
    }
    int restarting = rows.Count(row => row.State == "restarting");
    int memoryTotal = rows.Sum(row => row.Memory);
    int limitTotal = containers.Sum(container => container.Limit);

    var table = Table.For(rows)
        .Column(row => row.Container)
        .Column(row => row.Image)
        .Column(row => row.State, column => column.Presenter = CellPresenters.StatusWord)
        .Column(row => row.Restarts)
        .Column("CPU %", row => row.Cpu)
        .Column("Memory MiB", row => row.Memory)
        .Column("Memory %", row => row.MemoryPercent)
        .Column(row => row.Status, column => column.Presenter = CellPresenters.StatusWord);
    table.Expand = true;
    var cpu = new BarChart("cpu") { Maximum = 100, Warning = 70, Critical = 90 };
    var memory = new BarChart("memory") { Maximum = 100, Warning = 80, Critical = 90 };
    foreach (var row in rows)
    {
        cpu.Add(row.Container, row.Cpu);
        memory.Add(row.Container, row.MemoryPercent);
    }
    IWidget feed = new TextBlock("[muted]No restarts or memory warnings yet.[/]");
    if (events.Count > 0)
    {
        var list = new StatusList();
        foreach (var item in events.Take(4))
            list.Add(item.Label, item.Status, item.Detail);
        feed = list;
    }

    return new Rows(
        new Rule(
            ($"[bold]Containers[/] [muted]· docker-01.adatum.example.test · refresh 2s · "
                + $"{clock}[/]")),
        new TextBlock(
            ($"[pass]{rows.Count - restarting} running[/]   [warn]{restarting} "
                + $"restarting[/]   [muted]memory {memoryTotal:N0} of {limitTotal:N0} MiB "
                + $"reserved[/]")),
        table,
        new Columns(new Panel(cpu, "CPU %"), new Panel(memory, "Memory % of limit")),
        new Panel(feed, "Events"))
    {
        Gap = 1
    };
}

// Behaviour: build each sample's screen, then replay them in a bounded live dashboard.
var frames = Enumerable.Range(0, 4).Select(Frame).ToArray();
var screen = new RegionWidget("containers", frames[0]);
using LiveSession session = screen.CreateSession();
try
{
    session.Run(live =>
    {
        foreach (var frame in frames)
        {
            if (live.Cancellation.IsCancellationRequested)
                break;
            screen.Handle(live).Set(frame);
            live.Delay(TimeSpan.FromMilliseconds(2000));
        }
    });
}
catch (OperationCanceledException cancelled)
    when (cancelled.CancellationToken == session.Cancellation)
{
    // Ctrl+C is a normal way to leave the dashboard, after the session restores the terminal.
}

record Container(string Name, string Image, int Limit, int[] Cpu, int[] Memory, int[] Restarts);

record ContainerRow(string Container, string Image, string State, int Restarts, int Cpu, int Memory,
    int MemoryPercent, string Status);

record Event(string Label, StatusKind Status, string Detail);
C# fluentProgram.Fluent.csView on GitHub
// Watch a container host: state, restarts, CPU and memory against each limit, with an event feed.
using ConsoleMonster;
using ConsoleMonster.Layout;
using ConsoleMonster.Live;
using ConsoleMonster.Presenters;
using ConsoleMonster.Styling;
using ConsoleMonster.Widgets;

// Data: four fictional samples, two seconds apart; no container engine is contacted.
var containers = new Container[]
{
    new(
        "web",
        "nginx:1.27",
        256,
        new[] { 8, 9, 12, 10 },
        new[] { 96, 98, 101, 99 },
        new[] { 0, 0, 0, 0 }),
    new(
        "api",
        "adatum/api:4.2",
        512,
        new[] { 34, 41, 3, 22 },
        new[] { 410, 468, 120, 190 },
        new[] { 2, 2, 3, 3 }),
    new(
        "worker",
        "adatum/worker:4.2",
        896,
        new[] { 55, 61, 72, 78 },
        new[] { 690, 742, 801, 846 },
        new[] { 0, 0, 0, 0 }),
    new(
        "db",
        "postgres:16",
        2048,
        new[] { 18, 22, 25, 21 },
        new[] { 1210, 1225, 1240, 1236 },
        new[] { 0, 0, 0, 0 }),
    new(
        "cache",
        "redis:7.4",
        512,
        new[] { 3, 4, 4, 3 },
        new[] { 152, 153, 153, 154 },
        new[] { 0, 0, 0, 0 }),
    new(
        "queue",
        "rabbitmq:3.13",
        1024,
        new[] { 6, 7, 9, 8 },
        new[] { 301, 305, 309, 312 },
        new[] { 1, 1, 1, 1 })
};
var events = new List<Event>();

// Layout: one complete screen per sample; restarts and memory pressure also go to the event feed.
IWidget Frame(int tick)
{
    string clock = $"10:00:{tick * 2:00}";
    var rows = new List<ContainerRow>();
    foreach (var container in containers)
    {
        int memoryPercent = (int)Math.Round(100.0 * container.Memory[tick] / container.Limit);
        bool restarted = tick > 0 && container.Restarts[tick] > container.Restarts[tick - 1];
        string status = restarted || memoryPercent >= 90
            ? "Fail"
            : memoryPercent >= 80 ? "Warn" : "Pass";
        if (restarted)
            events.Insert(
                0,
                new Event($"{clock} {container.Name}", StatusKind.Fail,
                    $"restarted after exit 137 · restart {container.Restarts[tick]}"));
        else if (memoryPercent >= 90)
            events.Insert(
                0,
                new Event($"{clock} {container.Name}", StatusKind.Warn,
                    $"memory at {memoryPercent}% of its {container.Limit} MiB limit"));
        rows.Add(new ContainerRow(container.Name, container.Image,
            restarted ? "restarting" : "running", container.Restarts[tick], container.Cpu[tick],
            container.Memory[tick], memoryPercent, status));
    }

    int restarting = rows.Count(row => row.State == "restarting");
    int memoryTotal = rows.Sum(row => row.Memory);
    int limitTotal = containers.Sum(container => container.Limit);
    var table = Ui.Table(rows)
        .Column(row => row.Container)
        .Column(row => row.Image)
        .Column(row => row.State, column => column.Presenter(CellPresenters.StatusWord))
        .Column(row => row.Restarts)
        .Column("CPU %", row => row.Cpu)
        .Column("Memory MiB", row => row.Memory)
        .Column("Memory %", row => row.MemoryPercent)
        .Column(row => row.Status, column => column.Presenter(CellPresenters.StatusWord))
        .Expand();
    var cpu = (BarChart)Ui.Chart()
        .Type(ChartType.Bar)
        .WidgetId("cpu")
        .Maximum(100)
        .Warning(70)
        .Critical(90)
        .Build();
    var memory = (BarChart)Ui.Chart()
        .Type(ChartType.Bar)
        .WidgetId("memory")
        .Maximum(100)
        .Warning(80)
        .Critical(90)
        .Build();
    foreach (var row in rows)
    {
        cpu.Add(row.Container, row.Cpu);
        memory.Add(row.Container, row.MemoryPercent);
    }

    IWidget feed = Ui.Markup("[muted]No restarts or memory warnings yet.[/]");
    if (events.Count > 0)
    {
        var list = Ui.StatusList();
        foreach (var item in events.Take(4))
            list.Add(item.Label, item.Status, item.Detail);
        feed = list;
    }

    return Ui.Rows().Gap(1).Content(c3 =>
    {
        c3.Rule(($"[bold]Containers[/] [muted]· docker-01.adatum.example.test · refresh 2s · "
            + $"{clock}[/]"));
        c3.Markup(($"[pass]{rows.Count - restarting} running[/]   [warn]{restarting} "
            + $"restarting[/]   [muted]memory {memoryTotal:N0} of {limitTotal:N0} MiB "
            + $"reserved[/]"));
        c3.Add(table);
        c3.Columns(columns => columns.Content(c2 =>
        {
            c2.Panel(p => p.Title("CPU %").Content(c =>
            {
                c.Add(cpu);
            }));
            c2.Panel(p => p.Title("Memory % of limit").Content(c =>
            {
                c.Add(memory);
            }));
        }));
        c3.Panel(p => p.Title("Events").Content(c =>
        {
            c.Add(feed);
        }));
    });
}

// Behaviour: build each sample's screen, then replay them in a bounded live dashboard.
var frames = Enumerable.Range(0, 4).Select(Frame).ToArray();
var screen = Ui.Region("containers").Content(c =>
{
    c.Add(frames[0]);
});
using LiveSession session = screen.CreateSession();
try
{
    session.Run(live =>
    {
        foreach (var frame in frames)
        {
            if (live.Cancellation.IsCancellationRequested)
                break;
            screen.Handle(live).Set(frame);
            live.Delay(TimeSpan.FromMilliseconds(2000));
        }
    });
}
catch (OperationCanceledException cancelled)
    when (cancelled.CancellationToken == session.Cancellation)
{
// Ctrl+C is a normal way to leave the dashboard, after the session restores the terminal.
}

record Container(string Name, string Image, int Limit, int[] Cpu, int[] Memory, int[] Restarts);

record ContainerRow(string Container, string Image, string State, int Restarts, int Cpu, int Memory,
    int MemoryPercent, string Status);

record Event(string Label, StatusKind Status, string Detail);

Run it yourself

Three steps in PowerShell, four in C#. Every command copies with one click.

  1. Install the module

    Once. Windows PowerShell 5.1 or PowerShell 7 on Windows, macOS or Linux.

    Install-Module ConsoleMonster -Scope CurrentUser
  2. Save the program

    Download it, or copy it from above into container-status.ps1.

  3. Run it

    From the folder you saved it in.

    ./container-status.ps1
  1. Create a project

    A plain console app; .NET 8 or later.

    dotnet new console -n ContainerStatus; cd ContainerStatus
  2. Save the project

    The project selects one C# style at a time and restores ConsoleMonster.

  3. Save both programs

    Replace Program.cs and save Program.Fluent.cs beside it.

  4. Run it

    dotnet run
  1. Create a project

    A plain console app; .NET 8 or later.

    dotnet new console -n ContainerStatus; cd ContainerStatus
  2. Save the project

    The project selects one C# style at a time and restores ConsoleMonster.

  3. Save both programs

    Replace Program.cs and save Program.Fluent.cs beside it.

  4. Run it

    dotnet run --property:DemoStyle=Fluent

Or skip the copying. Every demo ships with the module and the project template.

Show-ConsoleDemo container-status
Export-ConsoleDemo container-status -Path ./container-status.ps1
dotnet new install ConsoleMonster.Templates
dotnet new consolemonster --demo container-status -n ContainerStatus
dotnet new install ConsoleMonster.Templates
dotnet new consolemonster --demo container-status --DemoStyle Fluent -n ContainerStatus

Make it yours

Start with these, then read the rest of the file.

  1. Replace the samples with docker stats --no-stream --format '{{json .}}' and docker inspect for restart counts, read once per refresh.

  2. Change the 80% and 90% memory thresholds and the CPU warning levels to fit your hosts.

  3. Keep the dashboard running until Ctrl+C instead of four samples, and log the event feed to a file.

More dashboards

All demos