/* Copyright 2026 Marimo. All rights reserved. */
import { useAtomValue } from "jotai";
import { CpuIcon, MemoryStickIcon, MicrochipIcon } from "lucide-react";
import type React from "react";
import { useState } from "react";
import { useNumberFormatter } from "react-aria";
import { Tooltip } from "@/components/ui/tooltip";
import { connectionAtom } from "@/core/network/connection";
import { useRequestClient } from "@/core/network/requests";
import type { UsageResponse } from "@/core/network/types";
import { isWasm } from "@/core/wasm/utils";
import { WebSocketState } from "@/core/websocket/types";
import { useAsyncData } from "@/hooks/useAsyncData";
import { useInterval } from "@/hooks/useInterval";
import { cn } from "@/utils/cn";
export const MachineStats: React.FC = () => {
const [nonce, setNonce] = useState(0);
const connection = useAtomValue(connectionAtom);
const { getUsageStats } = useRequestClient();
useInterval(
() => setNonce((nonce) => nonce + 1),
// Refresh every 10 seconds, or when the document becomes visible
{ delayMs: 10_000, whenVisible: true },
);
const { data } = useAsyncData(async () => {
if (isWasm()) {
return null;
}
if (connection.state !== WebSocketState.OPEN) {
return null;
}
return getUsageStats();
}, [nonce, connection.state]);
return (
{data?.gpu && data.gpu.length > 0 && }
{data && (
)}
{data && }
);
};
const MemoryUsageBar: React.FC<{
memory: UsageResponse["memory"];
kernel: UsageResponse["kernel"];
server: UsageResponse["server"];
}> = ({ memory, kernel, server }) => {
const { percent, total, available, has_cgroup_mem_limit } = memory;
const roundedPercent = Math.round(percent);
const memoryLabel = has_cgroup_mem_limit
? "container memory"
: "computer memory";
const gbFormatter = useNumberFormatter({
maximumFractionDigits: 2,
});
const mbFormatter = useNumberFormatter({
maximumFractionDigits: 0,
});
const formatBytes = (bytes: number): string => {
if (bytes > 1024 * 1024 * 1024) {
return `${gbFormatter.format(bytes / (1024 * 1024 * 1024))} GB`;
}
return `${mbFormatter.format(bytes / (1024 * 1024))} MB`;
};
const formatGB = (bytes: number): string => {
return gbFormatter.format(bytes / (1024 * 1024 * 1024));
};
return (
{memoryLabel}: {formatGB(total - available)} /{" "}
{formatGB(total)} GB ({roundedPercent}%)
{server?.memory && (
marimo server: {formatBytes(server.memory)}
)}
{kernel?.memory && (
kernel: {formatBytes(kernel.memory)}
)}
}
>
);
};
const CPUBar: React.FC<{ cpu: UsageResponse["cpu"] }> = ({ cpu }) => {
const { percent } = cpu;
const roundedPercent = Math.round(percent);
return (
CPU: {roundedPercent}%
}
>
);
};
interface GPU {
index: number;
name: string;
memory: {
used: number;
total: number;
percent: number;
};
}
const GPUBar: React.FC<{ gpus: GPU[] }> = ({ gpus }) => {
const avgPercent = Math.round(
gpus.reduce((sum: number, gpu: GPU) => sum + gpu.memory.percent, 0) /
gpus.length,
);
const gbFormatter = useNumberFormatter({
maximumFractionDigits: 2,
});
const mbFormatter = useNumberFormatter({
maximumFractionDigits: 0,
});
const formatBytes = (bytes: number): string => {
if (bytes > 1024 * 1024 * 1024) {
return `${gbFormatter.format(bytes / (1024 * 1024 * 1024))} GB`;
}
return `${mbFormatter.format(bytes / (1024 * 1024))} MB`;
};
return (
{gpus.map((gpu) => (
GPU {gpu.index} ({gpu.name}):
{" "}
{formatBytes(gpu.memory.used)} / {formatBytes(gpu.memory.total)} (
{Math.round(gpu.memory.percent)}%)
))}
}
>
);
};
const Bar: React.FC<{ percent: number; colorClassName?: string }> = ({
percent,
colorClassName,
}) => {
return (
);
};