Skip to content

Commit

Permalink
feat: Session usage monitor in react
Browse files Browse the repository at this point in the history
  • Loading branch information
ironAiken2 committed Jan 2, 2025
1 parent 84ed0b5 commit 22a1850
Show file tree
Hide file tree
Showing 3 changed files with 288 additions and 3 deletions.
8 changes: 6 additions & 2 deletions react/src/components/BAIProgressWithLabel.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import Flex from './Flex';
import { Typography, theme } from 'antd';
import { ProgressProps, Typography, theme } from 'antd';
import _ from 'lodash';
import React from 'react';

export interface BAIProgressWithLabelProps {
export interface BAIProgressWithLabelProps
extends Omit<ProgressProps, 'width' | 'size'> {
title?: React.ReactNode;
valueLabel?: React.ReactNode;
percent?: number;
width?: React.CSSProperties['width'];
strokeColor?: string;
labelStyle?: React.CSSProperties;
progressStyle?: React.CSSProperties;
size?: 'small' | 'middle' | 'large';
}
const BAIProgressWithLabel: React.FC<BAIProgressWithLabelProps> = ({
Expand All @@ -19,6 +21,7 @@ const BAIProgressWithLabel: React.FC<BAIProgressWithLabelProps> = ({
width,
strokeColor,
labelStyle,
progressStyle,
size = 'small',
}) => {
const { token } = theme.useToken();
Expand All @@ -39,6 +42,7 @@ const BAIProgressWithLabel: React.FC<BAIProgressWithLabelProps> = ({
...(_.isNumber(width) || _.isString(width)
? { width: width }
: { flex: 1 }),
...progressStyle,
}}
direction="column"
align="stretch"
Expand Down
7 changes: 6 additions & 1 deletion react/src/components/SessionDetailContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import SessionStatusTag from './ComputeSessionNodeItems/SessionStatusTag';
import SessionTypeTag from './ComputeSessionNodeItems/SessionTypeTag';
import Flex from './Flex';
import ImageMetaIcon from './ImageMetaIcon';
import SessionUsageMonitor from './SessionUsageMonitor';
import { SessionDetailContentLegacyQuery } from './__generated__/SessionDetailContentLegacyQuery.graphql';
import { SessionDetailContentQuery } from './__generated__/SessionDetailContentQuery.graphql';
import {
Expand Down Expand Up @@ -100,6 +101,7 @@ const SessionDetailContent: React.FC<{
# fix: This fragment is not used in this component, but it is required by the SessionActionButtonsFragment.
# It might be a bug in relay
...ContainerLogModalFragment
...SessionUsageMonitorFragment
}
legacy_session: compute_session(id: $uuid) {
image
Expand Down Expand Up @@ -201,11 +203,14 @@ const SessionDetailContent: React.FC<{
<Descriptions.Item label={t('session.Agent')}>
{session.agent_ids || '-'}
</Descriptions.Item>
<Descriptions.Item label={t('session.Reservation')}>
<Descriptions.Item label={t('session.Reservation')} span={md ? 2 : 1}>
<Flex gap={'xs'} wrap={'wrap'}>
<SessionReservation sessionFrgmt={session} />
</Flex>
</Descriptions.Item>
<Descriptions.Item label={'Resource Usage'} span={md ? 2 : 1}>
<SessionUsageMonitor sessionFrgmt={session} />
</Descriptions.Item>
</Descriptions>
</Flex>
) : (
Expand Down
276 changes: 276 additions & 0 deletions react/src/components/SessionUsageMonitor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,276 @@
import {
convertBinarySizeUnit,
convertDecimalSizeUnit,
toFixedFloorWithoutTrailingZeros,
} from '../helper';
import { useResourceSlotsDetails } from '../hooks/backendai';
import BAIProgressWithLabel from './BAIProgressWithLabel';
import Flex from './Flex';
import { SessionUsageMonitorFragment$key } from './__generated__/SessionUsageMonitorFragment.graphql';
import { Progress, ProgressProps, Tooltip, Typography, theme } from 'antd';
import graphql from 'babel-plugin-relay/macro';
import _ from 'lodash';
import { useMemo } from 'react';
import { useFragment } from 'react-relay';

interface SessionUsageMonitorProps extends ProgressProps {
sessionFrgmt: SessionUsageMonitorFragment$key | null;
size?: 'small' | 'default';
}

const SessionUsageMonitor: React.FC<SessionUsageMonitorProps> = ({
sessionFrgmt,
size = 'default',
}) => {
const { token } = theme.useToken();
const { mergedResourceSlots } = useResourceSlotsDetails();

const kernel_nodes = useFragment(
graphql`
fragment SessionUsageMonitorFragment on ComputeSessionNode {
kernel_nodes {
edges {
node {
live_stat
occupied_slots
}
}
}
}
`,
sessionFrgmt,
);

const resourceSlotNames = _.keysIn(
JSON.parse(
_.get(kernel_nodes, 'kernel_nodes.edges[0].node.occupied_slots') ?? '{}',
),
);
const liveStat = JSON.parse(
_.get(kernel_nodes, 'kernel_nodes.edges[0].node.live_stat') ?? '{}',
);

// to display util first, mem second
const sortedLiveStat = useMemo(
() =>
Object.keys(liveStat)
.sort((a, b) => {
const aUtil = a.includes('_util');
const bUtil = b.includes('_util');
const aMem = a.includes('_mem');
const bMem = b.includes('_mem');

if (aUtil && !bUtil) return -1;
if (!aUtil && bUtil) return 1;
if (aMem && !bMem) return -1;
if (!aMem && bMem) return 1;

return 0;
})
.reduce((acc: { [key: string]: any }, key) => {
acc[key] = liveStat[key];
return acc;
}, {}),
[liveStat],
);

const displayDeviceUsage = (
capacity: string,
current: string,
decimalSize: number = 2,
) => {
return `${convertBinarySizeUnit(current, 'g', decimalSize)?.numberUnit ?? '-'}iB / ${
convertBinarySizeUnit(capacity, 'g', decimalSize)?.numberUnit ?? '-'
}iB`;
};

return (
<Flex direction="column" align="stretch">
{sortedLiveStat?.cpu_util ? (
<Flex direction="column" align="stretch">
{size === 'default' ? (
<>
<Typography.Text>
{mergedResourceSlots?.['cpu']?.human_readable_name}
</Typography.Text>
<BAIProgressWithLabel
percent={sortedLiveStat?.cpu_util?.pct || 0}
valueLabel={
toFixedFloorWithoutTrailingZeros(
liveStat?.cpu_util?.pct || 0,
1,
) + '%'
}
strokeColor="#BFBFBF"
progressStyle={{ border: 'none' }}
/>
</>
) : (
<Tooltip title={mergedResourceSlots?.['cpu']?.human_readable_name}>
<Progress
format={(percent) => (
<Typography.Text style={{ fontSize: token.fontSizeSM }}>
{percent + '%'}
</Typography.Text>
)}
percent={
_.toNumber(
toFixedFloorWithoutTrailingZeros(
sortedLiveStat?.cpu_util?.pct,
1,
),
) || 0
}
strokeColor="#BFBFBF"
strokeLinecap="butt"
/>
</Tooltip>
)}
</Flex>
) : null}
{sortedLiveStat?.mem ? (
<Flex direction="column" align="stretch">
{size === 'default' ? (
<>
<Typography.Text>
{mergedResourceSlots?.['mem']?.human_readable_name}
</Typography.Text>
<BAIProgressWithLabel
percent={sortedLiveStat?.mem?.pct || 0}
valueLabel={
toFixedFloorWithoutTrailingZeros(liveStat?.mem?.pct || 0, 1) +
'%'
}
strokeColor="#BFBFBF"
progressStyle={{ border: 'none' }}
/>
<Flex justify="end">
<Typography.Text
type="secondary"
style={{ fontSize: token.fontSizeSM }}
>
{displayDeviceUsage(
sortedLiveStat?.mem?.capacity,
sortedLiveStat?.mem?.current,
)}
</Typography.Text>
</Flex>
</>
) : (
<Tooltip
title={
<Flex direction="column" align="stretch">
{mergedResourceSlots?.['mem']?.human_readable_name}
<br />
{displayDeviceUsage(
sortedLiveStat?.mem?.capacity,
sortedLiveStat?.mem?.current,
)}
</Flex>
}
>
<Progress
format={(percent) => (
<Typography.Text style={{ fontSize: token.fontSizeSM }}>
{percent + '%'}
</Typography.Text>
)}
percent={
_.toNumber(
toFixedFloorWithoutTrailingZeros(
sortedLiveStat?.mem?.pct,
1,
),
) || 0
}
strokeColor="#BFBFBF"
strokeLinecap="butt"
/>
</Tooltip>
)}
</Flex>
) : null}
{_.map(
_.omit(sortedLiveStat, 'cpu_util', 'cpu_used', 'mem'),
(value, key) => {
const deviceName = _.split(key, '_')[0];
const deviceKey = _.find(resourceSlotNames, (name) =>
_.includes(name, deviceName),
);

return deviceKey ? (
<Flex direction="column" align="stretch">
{size === 'default' ? (
<>
<Typography.Text>
{mergedResourceSlots?.[deviceKey]?.human_readable_name}
<Typography.Text type="secondary">
{_.includes(key, 'mem') && '(mem)'}
</Typography.Text>
</Typography.Text>
<BAIProgressWithLabel
percent={value.pct || 0}
valueLabel={
toFixedFloorWithoutTrailingZeros(value.pct || 0, 1) + '%'
}
strokeColor="#BFBFBF"
progressStyle={{ border: 'none' }}
/>
{size === 'default' ? (
<Flex justify="end">
{_.includes(key, 'mem') ? (
<Typography.Text
type="secondary"
style={{ fontSize: token.fontSizeSM }}
>
{displayDeviceUsage(value?.capacity, value?.current)}
</Typography.Text>
) : null}
</Flex>
) : null}
</>
) : (
<Tooltip
title={
<Flex direction="column" align="stretch">
{mergedResourceSlots?.[deviceKey]?.human_readable_name}
{_.includes(key, 'mem') && (
<>
(mem)
<br />
{displayDeviceUsage(value?.capacity, value?.current)}
</>
)}
</Flex>
}
>
<Progress
format={(percent) => (
<Typography.Text style={{ fontSize: token.fontSizeSM }}>
{percent + '%'}
</Typography.Text>
)}
percent={
_.toNumber(
toFixedFloorWithoutTrailingZeros(value?.pct, 1),
) || 0
}
strokeColor="#BFBFBF"
strokeLinecap="butt"
/>
</Tooltip>
)}
</Flex>
) : null;
},
)}
<Flex justify="end">
<Typography.Text>
{`I/O Read: ${convertDecimalSizeUnit(sortedLiveStat?.io_read?.current, 'm')?.numberUnit ?? '-'}B / Write: ${convertDecimalSizeUnit(sortedLiveStat?.io_write?.current, 'm')?.numberUnit ?? '-'}B`}
</Typography.Text>
</Flex>
</Flex>
);
};

export default SessionUsageMonitor;

0 comments on commit 22a1850

Please sign in to comment.