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;