Skip to content

Commit

Permalink
feat(metrics) show cpu and disk usage in instance list. distinguish m…
Browse files Browse the repository at this point in the history
…emory cached and used. fixes #928 #1062

Signed-off-by: David Edler <[email protected]>
  • Loading branch information
edlerd committed Jan 28, 2025
1 parent cf5e5d2 commit 4de5655
Show file tree
Hide file tree
Showing 8 changed files with 189 additions and 43 deletions.
25 changes: 22 additions & 3 deletions src/components/Meter.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,36 @@
import { FC } from "react";
import classnames from "classnames";

interface Props {
percentage: number;
secondaryPercentage?: number;
text: string;
}

const Meter: FC<Props> = ({ percentage, text }: Props) => {
const Meter: FC<Props> = ({
percentage,
secondaryPercentage = 0,
text,
}: Props) => {
return (
<>
<div className="p-meter u-no-margin--bottom">
<div style={{ width: `${percentage}%` }} />
<div
style={{ width: `max(${percentage}%, 5px)` }}
className={classnames({
"has-next-sibling": secondaryPercentage > 0,
})}
/>
{secondaryPercentage ? (
<div
className="has-previous-sibling"
style={{ width: `${secondaryPercentage}%` }}
/>
) : null}
</div>
<div className="p-text--small u-no-margin--bottom u-text--muted">
{text}
</div>
<div className="p-text--small u-no-margin--bottom">{text}</div>
</>
);
};
Expand Down
48 changes: 48 additions & 0 deletions src/pages/instances/InstanceDisk.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { FC } from "react";
import { useQuery } from "@tanstack/react-query";
import { queryKeys } from "util/queryKeys";
import { fetchMetrics } from "api/metrics";
import { humanFileSize } from "util/helpers";
import { getInstanceMetrics } from "util/metricSelectors";
import Meter from "components/Meter";
import type { LxdInstance } from "types/instance";
import { useAuth } from "context/auth";

interface Props {
instance: LxdInstance;
}

const InstanceUsageDisk: FC<Props> = ({ instance }) => {
const { isRestricted } = useAuth();

const { data: metrics = [] } = useQuery({
queryKey: [queryKeys.metrics],
queryFn: fetchMetrics,
refetchInterval: 15 * 1000, // 15 seconds
enabled: !isRestricted,
});

const instanceMetrics = getInstanceMetrics(metrics, instance);

return instanceMetrics.disk ? (
<div>
<Meter
percentage={
(100 / instanceMetrics.disk.total) *
(instanceMetrics.disk.total - instanceMetrics.disk.free)
}
text={
humanFileSize(
instanceMetrics.disk.total - instanceMetrics.disk.free,
) +
" of " +
humanFileSize(instanceMetrics.disk.total)
}
/>
</div>
) : (
""
);
};

export default InstanceUsageDisk;
32 changes: 31 additions & 1 deletion src/pages/instances/InstanceList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ import {
SNAPSHOTS,
STATUS,
TYPE,
MEMORY,
DISK,
} from "util/instanceTable";
import { getInstanceName } from "util/operations";
import ScrollableTable from "components/ScrollableTable";
Expand All @@ -61,10 +63,12 @@ import InstanceDetailPanel from "./InstanceDetailPanel";
import { useSmallScreen } from "context/useSmallScreen";
import { useSettings } from "context/useSettings";
import { isClusteredServer } from "util/settings";
import InstanceUsageMemory from "pages/instances/InstanceUsageMemory";
import InstanceUsageDisk from "pages/instances/InstanceDisk";

const loadHidden = () => {
const saved = localStorage.getItem("instanceListHiddenColumns");
return saved ? (JSON.parse(saved) as string[]) : [];
return saved ? (JSON.parse(saved) as string[]) : [MEMORY, DISK];
};

const saveHidden = (columns: string[]) => {
Expand Down Expand Up @@ -231,6 +235,14 @@ const InstanceList: FC = () => {
},
]
: []),
{
content: MEMORY,
style: { width: `${COLUMN_WIDTHS[MEMORY]}px` },
},
{
content: DISK,
style: { width: `${COLUMN_WIDTHS[DISK]}px` },
},
{
content: DESCRIPTION,
sortKey: "description",
Expand Down Expand Up @@ -393,6 +405,22 @@ const InstanceList: FC = () => {
},
]
: []),
{
content: <InstanceUsageMemory instance={instance} />,
role: "cell",
"aria-label": MEMORY,
onClick: openSummary,
className: "clickable-cell",
style: { width: `${COLUMN_WIDTHS[MEMORY]}px` },
},
{
content: <InstanceUsageDisk instance={instance} />,
role: "cell",
"aria-label": DISK,
onClick: openSummary,
className: "clickable-cell",
style: { width: `${COLUMN_WIDTHS[DISK]}px` },
},
{
content: (
<div className="u-truncate" title={instance.description}>
Expand Down Expand Up @@ -616,6 +644,8 @@ const InstanceList: FC = () => {
<TableColumnsSelect
columns={[
TYPE,
MEMORY,
DISK,
CLUSTER_MEMBER,
DESCRIPTION,
IPV4,
Expand Down
41 changes: 4 additions & 37 deletions src/pages/instances/InstanceOverviewMetrics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import { FC } from "react";
import { useQuery } from "@tanstack/react-query";
import { queryKeys } from "util/queryKeys";
import { fetchMetrics } from "api/metrics";
import { humanFileSize } from "util/helpers";
import { getInstanceMetrics } from "util/metricSelectors";
import Meter from "components/Meter";
import Loader from "components/Loader";
import type { LxdInstance } from "types/instance";
import { useAuth } from "context/auth";
import InstanceUsageMemory from "pages/instances/InstanceUsageMemory";
import InstanceUsageDisk from "pages/instances/InstanceDisk";

interface Props {
instance: LxdInstance;
Expand Down Expand Up @@ -53,24 +53,7 @@ const InstanceOverviewMetrics: FC<Props> = ({ instance, onFailure }) => {
<th className="u-text--muted">Memory</th>
<td>
{instanceMetrics.memory ? (
<div>
<Meter
percentage={
(100 / instanceMetrics.memory.total) *
(instanceMetrics.memory.total -
instanceMetrics.memory.free)
}
text={
humanFileSize(
instanceMetrics.memory.total -
instanceMetrics.memory.free,
) +
" of " +
humanFileSize(instanceMetrics.memory.total) +
" memory used"
}
/>
</div>
<InstanceUsageMemory instance={instance} />
) : (
"-"
)}
Expand All @@ -80,23 +63,7 @@ const InstanceOverviewMetrics: FC<Props> = ({ instance, onFailure }) => {
<th className="u-text--muted">Disk</th>
<td>
{instanceMetrics.disk ? (
<div>
<Meter
percentage={
(100 / instanceMetrics.disk.total) *
(instanceMetrics.disk.total - instanceMetrics.disk.free)
}
text={
humanFileSize(
instanceMetrics.disk.total -
instanceMetrics.disk.free,
) +
" of " +
humanFileSize(instanceMetrics.disk.total) +
" disk used"
}
/>
</div>
<InstanceUsageDisk instance={instance} />
) : (
"-"
)}
Expand Down
53 changes: 53 additions & 0 deletions src/pages/instances/InstanceUsageMemory.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { FC } from "react";
import { useQuery } from "@tanstack/react-query";
import { queryKeys } from "util/queryKeys";
import { fetchMetrics } from "api/metrics";
import { humanFileSize } from "util/helpers";
import { getInstanceMetrics } from "util/metricSelectors";
import Meter from "components/Meter";
import type { LxdInstance } from "types/instance";
import { useAuth } from "context/auth";

interface Props {
instance: LxdInstance;
}

const InstanceUsageMemory: FC<Props> = ({ instance }) => {
const { isRestricted } = useAuth();

const { data: metrics = [] } = useQuery({
queryKey: [queryKeys.metrics],
queryFn: fetchMetrics,
refetchInterval: 15 * 1000, // 15 seconds
enabled: !isRestricted,
});

const instanceMetrics = getInstanceMetrics(metrics, instance);

return instanceMetrics.memory ? (
<div>
<Meter
percentage={
(100 / instanceMetrics.memory.total) *
(instanceMetrics.memory.total -
instanceMetrics.memory.free -
instanceMetrics.memory.cached)
}
secondaryPercentage={
(100 / instanceMetrics.memory.total) * instanceMetrics.memory.cached
}
text={
humanFileSize(
instanceMetrics.memory.total - instanceMetrics.memory.free,
) +
" of " +
humanFileSize(instanceMetrics.memory.total)
}
/>
</div>
) : (
""
);
};

export default InstanceUsageMemory;
13 changes: 13 additions & 0 deletions src/sass/_meter.scss
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
.p-meter {
background-color: #d3e4ed;
border-radius: 0.75rem;
display: flex;
height: 0.75rem;
margin-bottom: 0.375rem;
width: 100%;
Expand All @@ -11,4 +12,16 @@
border-right: 1px solid #f7f7f7;
height: 100%;
}

.has-next-sibling {
border-bottom-right-radius: 0;
border-right: 0;
border-top-right-radius: 0;
}

.has-previous-sibling {
border-bottom-left-radius: 0;
border-top-left-radius: 0;
opacity: 0.3;
}
}
15 changes: 14 additions & 1 deletion src/util/instanceTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ export const NAME = "Name";
export const TYPE = "Type";
export const CLUSTER_MEMBER = "Cluster member";
export const DESCRIPTION = "Description";
export const MEMORY = "Memory";
export const DISK = "Disk";
export const IPV4 = "IPv4";
export const IPV6 = "IPv6";
export const SNAPSHOTS = "Snapshots";
Expand All @@ -12,6 +14,8 @@ export const COLUMN_WIDTHS: Record<string, number> = {
[NAME]: 170,
[TYPE]: 130,
[CLUSTER_MEMBER]: 150,
[MEMORY]: 150,
[DISK]: 150,
[DESCRIPTION]: 150,
[IPV4]: 150,
[IPV6]: 330,
Expand All @@ -25,8 +29,17 @@ export const SIZE_HIDEABLE_COLUMNS = [
IPV6,
IPV4,
DESCRIPTION,
MEMORY,
DISK,
TYPE,
STATUS,
];

export const CREATION_SPAN_COLUMNS = [TYPE, DESCRIPTION, IPV4, IPV6, SNAPSHOTS];
export const CREATION_SPAN_COLUMNS = [
TYPE,
MEMORY,
DISK,
IPV4,
IPV6,
SNAPSHOTS,
];
5 changes: 4 additions & 1 deletion src/util/metricSelectors.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ interface MemoryReport {
| {
free: number;
total: number;
cached: number;
}
| undefined;
disk:
Expand All @@ -26,12 +27,14 @@ export const getInstanceMetrics = (
?.metrics.find((item) => item.labels.name === instance.name)?.value;

const memFree = memValue("lxd_memory_MemFree_bytes");
const memCached = memValue("lxd_memory_Cached_bytes");
const memTotal = memValue("lxd_memory_MemTotal_bytes");
const memory =
memFree && memTotal
memFree && memTotal && memCached
? {
free: memFree,
total: memTotal,
cached: memCached,
}
: undefined;

Expand Down

0 comments on commit 4de5655

Please sign in to comment.