Skip to content

Commit

Permalink
Show cpu and disk usage in instance list. distinguish memory cached a…
Browse files Browse the repository at this point in the history
…nd used (#1075)

## Done

- Show memory and disk usage in instance list
- Distinguish memory cached and used

Fixes #928 #1062

## QA

1. Run the LXD-UI:
- On the demo server via the link posted by @webteam-app below. This is
only available for PRs created by collaborators of the repo. Ask
@mas-who or @edlerd for access.
- With a local copy of this branch, [build and run as described in the
docs](../CONTRIBUTING.md#setting-up-for-development).
2. Perform the following QA steps:
    - check instance list, enable disk and memory columns
    - check instance detail page with metrics
  • Loading branch information
edlerd authored Jan 29, 2025
2 parents 029ccc8 + 4de5655 commit d27ba0b
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 d27ba0b

Please sign in to comment.