diff --git a/react/src/components/SessionDetailContent.tsx b/react/src/components/SessionDetailContent.tsx
index fb7d4cfd6..e8e83eaf8 100644
--- a/react/src/components/SessionDetailContent.tsx
+++ b/react/src/components/SessionDetailContent.tsx
@@ -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 {
@@ -99,6 +100,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
@@ -200,11 +202,14 @@ const SessionDetailContent: React.FC<{
{session.agent_ids || '-'}
-
+
+
+
+
) : (
diff --git a/react/src/components/SessionUsageMonitor.tsx b/react/src/components/SessionUsageMonitor.tsx
new file mode 100644
index 000000000..0f5b50c53
--- /dev/null
+++ b/react/src/components/SessionUsageMonitor.tsx
@@ -0,0 +1,242 @@
+import {
+ convertBinarySizeUnit,
+ convertDecimalSizeUnit,
+ toFixedFloorWithoutTrailingZeros,
+} from '../helper';
+import { knownAcceleratorResourceSlotNames } from '../hooks/backendai';
+import Flex from './Flex';
+import { ResourceTypeIcon } from './ResourceNumber';
+import { SessionUsageMonitorFragment$key } from './__generated__/SessionUsageMonitorFragment.graphql';
+import { Col, Progress, ProgressProps, Row, Typography, theme } from 'antd';
+import { createStyles } from 'antd-style';
+import graphql from 'babel-plugin-relay/macro';
+import _ from 'lodash';
+import { useMemo } from 'react';
+import { useFragment } from 'react-relay';
+
+const useStyles = createStyles(({ css, token }) => ({
+ progress: css`
+ .ant-progress-text {
+ font-size: ${token.fontSizeSM} !important;
+ min-width: 50px;
+ }
+ .ant-typography {
+ color: ${token.colorTextSecondary} !important;
+ font-size: ${token.fontSizeSM}px !important;
+ }
+ `,
+}));
+
+interface SessionUsageMonitorProps extends ProgressProps {
+ sessionFrgmt: SessionUsageMonitorFragment$key | null;
+ col?: 1 | 2 | 3 | 4;
+ showIcon?: boolean;
+}
+
+const gridColSpanMap = {
+ 1: 24,
+ 2: 12,
+ 3: 8,
+ 4: 6,
+};
+
+const SessionUsageMonitor: React.FC = ({
+ sessionFrgmt,
+ col = 1,
+ showIcon = true,
+ ...progressProps
+}) => {
+ const { token } = theme.useToken();
+ const { styles } = useStyles();
+
+ const kernel_nodes = useFragment(
+ graphql`
+ fragment SessionUsageMonitorFragment on ComputeSessionNode {
+ kernel_nodes {
+ edges {
+ node {
+ live_stat
+ }
+ }
+ }
+ }
+ `,
+ sessionFrgmt,
+ );
+
+ 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],
+ );
+
+ console.log(sortedLiveStat);
+
+ const mapDeviceToResourceSlotName = (device: string) => {
+ const deviceName = device.split('_')[0];
+ const resourceSlotName = _.find(
+ knownAcceleratorResourceSlotNames,
+ (name) => {
+ return _.includes(name, deviceName);
+ },
+ );
+ return resourceSlotName;
+ };
+
+ return (
+ <>
+
+ {sortedLiveStat?.cpu_util ? (
+
+
+
+ {showIcon && (
+
+ )}
+
+
+
+
+ ) : null}
+ {sortedLiveStat?.mem ? (
+
+
+
+ {showIcon && (
+
+ )}
+
+
+
+ {`(mem) ${
+ convertBinarySizeUnit(sortedLiveStat?.mem?.current, 'g')
+ ?.numberUnit
+ }iB
+ /
+ ${
+ convertBinarySizeUnit(sortedLiveStat?.mem?.capacity, 'g')
+ ?.numberUnit
+ }iB`}
+
+
+
+ ) : null}
+ {_.map(_.omit(sortedLiveStat, 'cpu_util', 'mem'), (value, key) => {
+ const resourceSlotName = mapDeviceToResourceSlotName(key);
+ return resourceSlotName ? (
+
+
+
+ {showIcon && (
+
+ )}
+
+
+
+ {_.includes(key, 'mem') &&
+ `${key.split('_')[0]} (mem) ${convertBinarySizeUnit(sortedLiveStat?.[key]?.current, 'g')?.numberUnit}iB /
+ ${convertBinarySizeUnit(sortedLiveStat?.[key]?.capacity, 'g')?.numberUnit}iB`}
+
+
+
+ ) : null;
+ })}
+
+
+
+ {`I/O Read: ${convertDecimalSizeUnit(sortedLiveStat?.io_read?.current, 'm')?.numberUnit ?? '-'}B / Write: ${convertDecimalSizeUnit(sortedLiveStat?.io_write?.current, 'm')?.numberUnit ?? '-'}B`}
+
+
+ >
+ );
+};
+
+export default SessionUsageMonitor;