chore: detail api
This commit is contained in:
@@ -49,6 +49,7 @@ const AddBenchmark: React.FC<AddModalProps> = ({
|
||||
<BenchmarkForm
|
||||
ref={form}
|
||||
action={action}
|
||||
open={open}
|
||||
currentData={currentData}
|
||||
onFinish={handleOk}
|
||||
/>
|
||||
|
||||
@@ -184,7 +184,7 @@ const CompareConditions: React.FC = () => {
|
||||
}}
|
||||
>
|
||||
<Container>
|
||||
<span className="holder">Compare Filter</span>
|
||||
<span className="holder">Export filter</span>
|
||||
</Container>
|
||||
</Popover>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
import IconFont from '@/components/icon-font';
|
||||
import { useQueryClusterList } from '@/pages/cluster-management/services/use-query-cluster-list';
|
||||
import { useIntl, useSearchParams } from '@umijs/max';
|
||||
import { Tabs, TabsProps } from 'antd';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import DetailContext from '../config/detail-context';
|
||||
import { BenchmarkListItem } from '../config/types';
|
||||
import useQueryDetail from '../services/use-query-detail';
|
||||
import Configure from './configure';
|
||||
import Environment from './environment';
|
||||
import Logs from './logs';
|
||||
import Summary from './summary';
|
||||
|
||||
const Details: React.FC<{ currentData?: BenchmarkListItem }> = ({
|
||||
currentData
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const [searchParams] = useSearchParams();
|
||||
const id = searchParams.get('id');
|
||||
const { loading, detailData, cancelRequest, fetchData } = useQueryDetail();
|
||||
const {
|
||||
clusterList,
|
||||
fetchClusterList,
|
||||
cancelRequest: cancelClusterRequest
|
||||
} = useQueryClusterList();
|
||||
const [activeKey, setActiveKey] = useState('summary');
|
||||
|
||||
const items: TabsProps['items'] = [
|
||||
{
|
||||
key: 'summary',
|
||||
label: intl.formatMessage({ id: 'benchmark.detail.summary.title' }),
|
||||
children: <Summary />,
|
||||
icon: <IconFont type="icon-basic" />
|
||||
},
|
||||
{
|
||||
key: 'configure',
|
||||
label: intl.formatMessage({ id: 'benchmark.detail.configure.title' }),
|
||||
children: <Configure />,
|
||||
icon: <IconFont type="icon-settings-02" />
|
||||
},
|
||||
{
|
||||
key: 'environment',
|
||||
label: intl.formatMessage({ id: 'benchmark.detail.environment.title' }),
|
||||
children: <Environment />,
|
||||
icon: <IconFont type="icon-server02" />
|
||||
},
|
||||
{
|
||||
key: 'logs',
|
||||
label: intl.formatMessage({ id: 'benchmark.detail.logs.title' }),
|
||||
children: <Logs />,
|
||||
icon: <IconFont type="icon-logs" />
|
||||
}
|
||||
];
|
||||
|
||||
const handleChangeTab = (key: string) => {
|
||||
setActiveKey(key);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (id) {
|
||||
fetchData(id);
|
||||
fetchClusterList({ page: -1 });
|
||||
} else {
|
||||
cancelRequest();
|
||||
cancelClusterRequest();
|
||||
}
|
||||
}, [id]);
|
||||
|
||||
return (
|
||||
<DetailContext.Provider
|
||||
value={{
|
||||
detailData: detailData || {},
|
||||
clusterList: clusterList,
|
||||
loading: loading,
|
||||
id: Number(id)
|
||||
}}
|
||||
>
|
||||
<Tabs
|
||||
size="small"
|
||||
activeKey={activeKey}
|
||||
onChange={handleChangeTab}
|
||||
items={items}
|
||||
type="card"
|
||||
/>
|
||||
</DetailContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default Details;
|
||||
@@ -0,0 +1,29 @@
|
||||
import FormDrawer from '@/pages/_components/form-drawer';
|
||||
import { BenchmarkListItem } from '../config/types';
|
||||
import DetailContent from './detail-content';
|
||||
|
||||
interface DetailModalProps {
|
||||
open: boolean;
|
||||
currentData?: BenchmarkListItem;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const DetailModal: React.FC<DetailModalProps> = ({
|
||||
open,
|
||||
currentData,
|
||||
onClose
|
||||
}) => {
|
||||
return (
|
||||
<FormDrawer
|
||||
title={currentData?.name}
|
||||
open={open}
|
||||
onCancel={onClose}
|
||||
width={700}
|
||||
footer={null}
|
||||
>
|
||||
<DetailContent currentData={currentData} />
|
||||
</FormDrawer>
|
||||
);
|
||||
};
|
||||
|
||||
export default DetailModal;
|
||||
@@ -0,0 +1,57 @@
|
||||
import { convertFileSize } from '@/utils';
|
||||
import { Descriptions, DescriptionsProps } from 'antd';
|
||||
import React from 'react';
|
||||
import { GPUData } from '../../config/detail-types';
|
||||
import Section from '../summary/section';
|
||||
|
||||
const Environment: React.FC<GPUData> = (props) => {
|
||||
const items: DescriptionsProps['items'] = [
|
||||
{
|
||||
key: '4',
|
||||
label: 'Name',
|
||||
children: props.name
|
||||
},
|
||||
{
|
||||
key: '1',
|
||||
label: 'VRAM',
|
||||
children: convertFileSize(props.memory_total)
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
label: 'Driver Version',
|
||||
children: props.driver_version
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
label: 'Runtime Version',
|
||||
children: props.runtime_version
|
||||
},
|
||||
{
|
||||
key: '6',
|
||||
label: 'Core',
|
||||
children: props.core_total
|
||||
},
|
||||
{
|
||||
key: '5',
|
||||
label: 'Vendor',
|
||||
children: props.vendor
|
||||
}
|
||||
];
|
||||
return (
|
||||
<Section title={`GPU ${props.index}`}>
|
||||
<Descriptions
|
||||
items={items}
|
||||
colon={false}
|
||||
column={3}
|
||||
layout="vertical"
|
||||
styles={{
|
||||
content: {
|
||||
justifyContent: 'flex-start'
|
||||
}
|
||||
}}
|
||||
></Descriptions>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
export default Environment;
|
||||
@@ -1,9 +1,82 @@
|
||||
import React from 'react';
|
||||
import { Tag } from 'antd';
|
||||
import React, { useMemo } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { useDetailContext } from '../../config/detail-context';
|
||||
import WorkerData from './worker-data';
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
`;
|
||||
|
||||
const Environment: React.FC = () => {
|
||||
const { detailData } = useDetailContext();
|
||||
return <div>Environment Content</div>;
|
||||
const { snapshot } = detailData;
|
||||
|
||||
const mainWorker = useMemo(() => {
|
||||
// get workers data
|
||||
const [[workerName, workerInfo]] = Object.entries(snapshot.workers);
|
||||
const gpuData = Object.values(snapshot.gpus).filter(
|
||||
(gpu) => gpu.worker_name === workerName
|
||||
);
|
||||
|
||||
return {
|
||||
workerData: workerInfo,
|
||||
gpuData: [...gpuData, ...gpuData]
|
||||
};
|
||||
}, [snapshot]);
|
||||
|
||||
const subWorkers = useMemo(() => {
|
||||
const [[mainWorkerName, mainWorkerInfo]] = Object.entries(snapshot.workers);
|
||||
|
||||
const subOrdinaryWorkers = Object.values(snapshot.instances).filter(
|
||||
(instance) => instance.worker_name === mainWorkerName
|
||||
);
|
||||
|
||||
return subOrdinaryWorkers.map((worker) => {
|
||||
const gpuData = Object.values(snapshot.gpus).filter(
|
||||
(gpu) => gpu.worker_name === worker.worker_name
|
||||
);
|
||||
|
||||
return {
|
||||
workerData: worker,
|
||||
gpuData: gpuData
|
||||
};
|
||||
});
|
||||
}, [snapshot, mainWorker]);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<WorkerData
|
||||
workerData={mainWorker.workerData}
|
||||
gpuData={mainWorker.gpuData}
|
||||
title={
|
||||
<div className="flex-center gap-8">
|
||||
<Tag color="geekblue">Main</Tag>
|
||||
<span>{mainWorker?.workerData?.name}</span>
|
||||
</div>
|
||||
}
|
||||
></WorkerData>
|
||||
<WorkerData
|
||||
workerData={mainWorker.workerData}
|
||||
gpuData={mainWorker.gpuData}
|
||||
title={
|
||||
<div className="flex-center gap-8">
|
||||
<Tag color="geekblue">Sub</Tag>
|
||||
<span>{mainWorker?.workerData?.name}</span>
|
||||
</div>
|
||||
}
|
||||
></WorkerData>
|
||||
{/* {subWorkers?.map?.((worker, index) => (
|
||||
<WorkerData
|
||||
key={index}
|
||||
workerData={worker.workerData}
|
||||
gpuData={worker.gpuData}
|
||||
></WorkerData>
|
||||
))} */}
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default Environment;
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { convertFileSize } from '@/utils';
|
||||
import { Descriptions, DescriptionsProps } from 'antd';
|
||||
import React from 'react';
|
||||
import { WorkerData } from '../../config/detail-types';
|
||||
|
||||
const Environment: React.FC<WorkerData> = (props) => {
|
||||
const { os, cpu_total, memory_total } = props;
|
||||
|
||||
const items: DescriptionsProps['items'] = [
|
||||
{
|
||||
key: '4',
|
||||
label: 'System',
|
||||
children: os.name
|
||||
},
|
||||
{
|
||||
key: '1',
|
||||
label: 'CPU Count',
|
||||
children: cpu_total
|
||||
},
|
||||
{
|
||||
key: '5',
|
||||
label: 'Memory Total',
|
||||
children: convertFileSize(memory_total)
|
||||
}
|
||||
];
|
||||
return (
|
||||
<div>
|
||||
<Descriptions
|
||||
items={items}
|
||||
colon={false}
|
||||
column={3}
|
||||
layout="vertical"
|
||||
styles={{
|
||||
content: {
|
||||
justifyContent: 'flex-start'
|
||||
}
|
||||
}}
|
||||
></Descriptions>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Environment;
|
||||
@@ -0,0 +1,57 @@
|
||||
import { Col, Row } from 'antd';
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { GPUData as GPUDataType, WorkerData } from '../../config/detail-types';
|
||||
import Section from '../summary/section';
|
||||
import GPUData from './gpu-data';
|
||||
import Metadata from './metadata';
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: var(--ant-border-radius);
|
||||
border: 1px solid var(--ant-color-border);
|
||||
`;
|
||||
|
||||
const Content = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
padding: 16px 16px;
|
||||
`;
|
||||
|
||||
const Environment: React.FC<{
|
||||
workerData: WorkerData;
|
||||
gpuData: GPUDataType[];
|
||||
title?: React.ReactNode;
|
||||
}> = (props) => {
|
||||
const { title } = props;
|
||||
return (
|
||||
<Container>
|
||||
<Section
|
||||
title={title}
|
||||
styles={{
|
||||
container: {
|
||||
border: 'none',
|
||||
paddingInline: 16,
|
||||
borderRadius: 0,
|
||||
borderBottom: '1px solid var(--ant-color-split)'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Metadata {...props.workerData} />
|
||||
</Section>
|
||||
<Content>
|
||||
<Row gutter={16}>
|
||||
{props.gpuData.map((gpu, index) => (
|
||||
<Col span={12} key={index}>
|
||||
<GPUData {...gpu} />
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
</Content>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default Environment;
|
||||
@@ -2,7 +2,6 @@ import { SearchOutlined, SyncOutlined } from '@ant-design/icons';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Button, Input, Space } from 'antd';
|
||||
import React from 'react';
|
||||
import CompareConditions from './compare-conditions';
|
||||
|
||||
export interface RightActionsProps {
|
||||
handleInputChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
@@ -30,7 +29,7 @@ const RightActions: React.FC<RightActionsProps> = ({
|
||||
allowClear
|
||||
onChange={handleInputChange}
|
||||
></Input>
|
||||
<CompareConditions></CompareConditions>
|
||||
{/* <CompareConditions></CompareConditions> */}
|
||||
<Button
|
||||
type="text"
|
||||
style={{ color: 'var(--ant-color-text-tertiary)' }}
|
||||
|
||||
@@ -1,9 +1,26 @@
|
||||
import React from 'react';
|
||||
import LogsViewer from '@/components/logs-viewer/virtual-log-list';
|
||||
import React, { useRef } from 'react';
|
||||
import { BENCHMARKS_API } from '../../apis';
|
||||
import { useDetailContext } from '../../config/detail-context';
|
||||
|
||||
const Logs: React.FC = () => {
|
||||
const { id } = useDetailContext();
|
||||
return <div>Logs Content</div>;
|
||||
const logsViewerRef = useRef<any>(null);
|
||||
return (
|
||||
<div>
|
||||
<LogsViewer
|
||||
ref={logsViewerRef}
|
||||
diffHeight={175}
|
||||
url={`${BENCHMARKS_API}/${id}/logs`}
|
||||
tail={undefined}
|
||||
enableScorllLoad={true}
|
||||
isDownloading={false}
|
||||
params={{
|
||||
follow: true
|
||||
}}
|
||||
></LogsViewer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Logs;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import DropdownButtons from '@/components/drop-down-buttons';
|
||||
import {
|
||||
DeleteOutlined,
|
||||
PlusOutlined,
|
||||
SettingOutlined
|
||||
DownloadOutlined,
|
||||
PlusOutlined
|
||||
} from '@ant-design/icons';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Button, Space } from 'antd';
|
||||
@@ -11,6 +12,7 @@ export interface RightActionsProps {
|
||||
handleDeleteByBatch: () => void;
|
||||
handleClickPrimary?: () => void;
|
||||
handleSettingFields?: () => void;
|
||||
settingButton?: React.ReactNode;
|
||||
buttonText?: string;
|
||||
rowSelection: {
|
||||
selectedRowKeys: React.Key[];
|
||||
@@ -21,14 +23,38 @@ const RightActions: React.FC<RightActionsProps> = ({
|
||||
handleDeleteByBatch,
|
||||
handleClickPrimary,
|
||||
handleSettingFields,
|
||||
settingButton,
|
||||
buttonText,
|
||||
rowSelection
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const ButtonList = [
|
||||
{
|
||||
label: 'common.button.export',
|
||||
key: 'export',
|
||||
icon: <DownloadOutlined />
|
||||
},
|
||||
{
|
||||
label: 'common.button.delete',
|
||||
key: 'start',
|
||||
props: {
|
||||
danger: true
|
||||
},
|
||||
icon: <DeleteOutlined />
|
||||
}
|
||||
];
|
||||
|
||||
const handleActionSelect = (val: string) => {};
|
||||
|
||||
return (
|
||||
<Space size={16}>
|
||||
<Button onClick={handleSettingFields} icon={<SettingOutlined />}></Button>
|
||||
{/* <Tooltip title="Column Settings">
|
||||
<Button
|
||||
onClick={handleSettingFields}
|
||||
icon={<SettingOutlined />}
|
||||
></Button>
|
||||
</Tooltip> */}
|
||||
{settingButton}
|
||||
<Button
|
||||
icon={<PlusOutlined></PlusOutlined>}
|
||||
type="primary"
|
||||
@@ -36,7 +62,7 @@ const RightActions: React.FC<RightActionsProps> = ({
|
||||
>
|
||||
{buttonText}
|
||||
</Button>
|
||||
<Button
|
||||
{/* <Button
|
||||
icon={<DeleteOutlined />}
|
||||
danger
|
||||
onClick={handleDeleteByBatch}
|
||||
@@ -48,7 +74,19 @@ const RightActions: React.FC<RightActionsProps> = ({
|
||||
<span>({rowSelection?.selectedRowKeys?.length})</span>
|
||||
)}
|
||||
</span>
|
||||
</Button>
|
||||
</Button> */}
|
||||
<DropdownButtons
|
||||
items={ButtonList}
|
||||
extra={
|
||||
rowSelection.selectedRowKeys.length > 0 && (
|
||||
<span>({rowSelection.selectedRowKeys.length})</span>
|
||||
)
|
||||
}
|
||||
size="large"
|
||||
showText={true}
|
||||
disabled={!rowSelection.selectedRowKeys.length}
|
||||
onSelect={handleActionSelect}
|
||||
/>
|
||||
</Space>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,244 @@
|
||||
import AutoTooltip from '@/components/auto-tooltip';
|
||||
import StatusTag from '@/components/status-tag';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Col, Descriptions, DescriptionsProps, Row, Statistic } from 'antd';
|
||||
import dayjs from 'dayjs';
|
||||
import { round } from 'lodash';
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { BenchmarkStatus, BenchmarkStatusLabelMap } from '../../config';
|
||||
import { useDetailContext } from '../../config/detail-context';
|
||||
import PercentileResult from './percentile-result';
|
||||
import Section from './section';
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
`;
|
||||
|
||||
const Card = styled.div`
|
||||
height: 78px;
|
||||
padding: 12px 16px;
|
||||
border: 1px solid var(--ant-color-border);
|
||||
border-radius: var(--ant-border-radius);
|
||||
background-color: var(--ant-color-bg-container);
|
||||
`;
|
||||
|
||||
const Box = styled.div`
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 16px;
|
||||
`;
|
||||
|
||||
const Summary: React.FC = () => {
|
||||
const { detailData, clusterList } = useDetailContext();
|
||||
const intl = useIntl();
|
||||
|
||||
const items: DescriptionsProps['items'] = [
|
||||
{
|
||||
key: '1',
|
||||
label: 'Model',
|
||||
children: <AutoTooltip ghost>{detailData.model_name}</AutoTooltip>
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
label: 'Cluster',
|
||||
children: (
|
||||
<AutoTooltip ghost>
|
||||
{clusterList?.find((item) => item.value === detailData.cluster_id)
|
||||
?.label || detailData.cluster_id}
|
||||
</AutoTooltip>
|
||||
)
|
||||
},
|
||||
{
|
||||
key: '4',
|
||||
label: 'Dataset',
|
||||
children: detailData.dataset_name
|
||||
},
|
||||
{
|
||||
key: '5',
|
||||
label: 'Concurrency',
|
||||
children: detailData.concurrency
|
||||
},
|
||||
{
|
||||
key: '5',
|
||||
label: 'Duration (s)',
|
||||
children: detailData.duration
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
label: 'Update Time',
|
||||
children: dayjs(detailData.updated_at).format('YYYY-MM-DD HH:mm:ss')
|
||||
}
|
||||
];
|
||||
const cardFields = [
|
||||
{
|
||||
label: 'Total Requests',
|
||||
key: 'total_requests',
|
||||
value: detailData.total_requests,
|
||||
unit: ''
|
||||
},
|
||||
{
|
||||
label: 'Success Rate',
|
||||
key: 'success_rate',
|
||||
value: detailData.success_rate,
|
||||
color: 'var(--ant-color-success)',
|
||||
unit: '%'
|
||||
},
|
||||
{
|
||||
label: 'TPS',
|
||||
key: 'tps',
|
||||
value: detailData.tokens_per_second_mean,
|
||||
unit: 't/s'
|
||||
},
|
||||
{
|
||||
label: 'RPS',
|
||||
key: 'rps',
|
||||
value: detailData.requests_per_second_mean,
|
||||
unit: 'req/s'
|
||||
},
|
||||
// {
|
||||
// label: 'ITL',
|
||||
// key: 'itl',
|
||||
// value: detailData.inter_token_latency_mean,
|
||||
// unit: 'ms'
|
||||
// },
|
||||
{
|
||||
label: 'TPOT',
|
||||
key: 'time_per_output_token_mean',
|
||||
value: detailData.time_per_output_token_mean,
|
||||
unit: 'ms'
|
||||
},
|
||||
{
|
||||
label: 'TTFT',
|
||||
key: 'time_to_first_token_mean',
|
||||
value: detailData.time_to_first_token_mean,
|
||||
unit: 'ms'
|
||||
}
|
||||
];
|
||||
|
||||
const throughputFields = [
|
||||
{
|
||||
key: '3',
|
||||
label: 'Total throughput',
|
||||
children: `${round(detailData.total_through || 0, 2)} t/s`
|
||||
},
|
||||
{
|
||||
key: '1',
|
||||
label: 'Request throughput',
|
||||
children: `${round(detailData.request_through || 0, 2)} t/s`
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
label: 'Output throughput',
|
||||
children: `${round(detailData.output_through || 0, 2)} t/s`
|
||||
}
|
||||
];
|
||||
|
||||
const latencyFields = [
|
||||
{
|
||||
key: '1',
|
||||
label: 'Request Latency',
|
||||
children: `${round(detailData.request_latency_mean, 2)} t/s`
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
label: 'Avg Time to First Token',
|
||||
children: `${round(detailData.time_to_first_token_mean, 2)} t/s`
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
label: 'Avg Time Per Output Token',
|
||||
children: `${round(detailData.time_per_output_token_mean, 2)} t/s`
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Row gutter={16}>
|
||||
{cardFields.map((field) => (
|
||||
<Col span={8} key={field.key} style={{ padding: '8px' }}>
|
||||
<Card>
|
||||
<Statistic
|
||||
title={field.label}
|
||||
value={field.value}
|
||||
precision={2}
|
||||
styles={{
|
||||
content: {
|
||||
color: field.color,
|
||||
fontSize: 16,
|
||||
fontWeight: 500
|
||||
},
|
||||
header: {
|
||||
paddingBottom: 4
|
||||
}
|
||||
}}
|
||||
suffix={field.unit}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
<Section>
|
||||
<div className="flex-center section-title gap-8">
|
||||
<span>{detailData.model_instance_name}</span>
|
||||
<StatusTag
|
||||
statusValue={{
|
||||
status: BenchmarkStatus[detailData.state],
|
||||
text: BenchmarkStatusLabelMap[detailData.state],
|
||||
message: detailData.state_message || undefined
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<Descriptions
|
||||
items={items}
|
||||
colon={false}
|
||||
column={3}
|
||||
layout="vertical"
|
||||
styles={{
|
||||
content: {
|
||||
justifyContent: 'flex-start'
|
||||
}
|
||||
}}
|
||||
></Descriptions>
|
||||
</Section>
|
||||
|
||||
<Box>
|
||||
<Section>
|
||||
<div className="flex-center section-title gap-8">
|
||||
<span>Throughput</span>
|
||||
</div>
|
||||
<Descriptions
|
||||
items={throughputFields}
|
||||
colon={true}
|
||||
column={1}
|
||||
styles={{
|
||||
content: {
|
||||
justifyContent: 'flex-end'
|
||||
}
|
||||
}}
|
||||
></Descriptions>
|
||||
</Section>
|
||||
<Section>
|
||||
<div className="flex-center section-title gap-8">
|
||||
<span>Latency</span>
|
||||
</div>
|
||||
<Descriptions
|
||||
items={latencyFields}
|
||||
colon={true}
|
||||
column={1}
|
||||
styles={{
|
||||
content: {
|
||||
justifyContent: 'flex-end'
|
||||
}
|
||||
}}
|
||||
></Descriptions>
|
||||
</Section>
|
||||
</Box>
|
||||
<PercentileResult />
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default Summary;
|
||||
@@ -1,9 +1,21 @@
|
||||
import React from 'react';
|
||||
import { useDetailContext } from '../../config/detail-context';
|
||||
import styled from 'styled-components';
|
||||
import BasicInfo from './basic';
|
||||
import MetricsInfo from './metrics';
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
`;
|
||||
|
||||
const Summary: React.FC = () => {
|
||||
const { detailData } = useDetailContext();
|
||||
return <div>Summary Content</div>;
|
||||
return (
|
||||
<Container>
|
||||
<BasicInfo />
|
||||
<MetricsInfo />
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default Summary;
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import _ from 'lodash';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useDetailContext } from '../../config/detail-context';
|
||||
import useQueryMtrics from '../../services/use-query-metrics';
|
||||
|
||||
const fields = [
|
||||
'requests_per_second_mean',
|
||||
'request_latency_mean',
|
||||
'time_per_output_token_mean',
|
||||
'inter_token_latency_mean',
|
||||
'time_to_first_token_mean',
|
||||
'tokens_per_second_mean',
|
||||
'output_tokens_per_second_mean',
|
||||
'prompt_tokens_per_second_mean'
|
||||
];
|
||||
|
||||
const Summary: React.FC = () => {
|
||||
const { detailData, id } = useDetailContext();
|
||||
const {
|
||||
detailData: metricsData,
|
||||
fetchData,
|
||||
cancelRequest
|
||||
} = useQueryMtrics();
|
||||
|
||||
useEffect(() => {
|
||||
if (id) {
|
||||
fetchData({
|
||||
id: id,
|
||||
data: {
|
||||
..._.pick(detailData, fields)
|
||||
}
|
||||
});
|
||||
} else {
|
||||
cancelRequest();
|
||||
}
|
||||
}, [id]);
|
||||
return <div></div>;
|
||||
};
|
||||
|
||||
export default Summary;
|
||||
@@ -0,0 +1,119 @@
|
||||
import { Table } from 'antd';
|
||||
import React from 'react';
|
||||
import { useDetailContext } from '../../config/detail-context';
|
||||
import Section from './section';
|
||||
|
||||
// Define table columns: Percentile、 TTFT (ms) 、 ITL (ms) 、TPOT (ms)、Latency (ms)、Input Tokens (t/s)、 Output Tokens (t/s)、 Output (t/s)、Total (t/s)
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: 'Percentile',
|
||||
dataIndex: 'percentile',
|
||||
key: 'percentile'
|
||||
},
|
||||
{
|
||||
title: 'TTFT (ms)',
|
||||
dataIndex: 'ttft',
|
||||
key: 'ttft'
|
||||
},
|
||||
{
|
||||
title: 'ITL (ms)',
|
||||
dataIndex: 'itl',
|
||||
key: 'itl'
|
||||
},
|
||||
{
|
||||
title: 'TPOT (ms)',
|
||||
dataIndex: 'tpot',
|
||||
key: 'tpot'
|
||||
},
|
||||
{
|
||||
title: 'Latency (ms)',
|
||||
dataIndex: 'latency',
|
||||
key: 'latency'
|
||||
},
|
||||
{
|
||||
title: 'Input Tokens (t/s)',
|
||||
dataIndex: 'inputTokens',
|
||||
key: 'inputTokens'
|
||||
},
|
||||
{
|
||||
title: 'Output Tokens (t/s)',
|
||||
dataIndex: 'outputTokens',
|
||||
key: 'outputTokens'
|
||||
},
|
||||
{
|
||||
title: 'Output (t/s)',
|
||||
dataIndex: 'output',
|
||||
key: 'output'
|
||||
},
|
||||
{
|
||||
title: 'Total (t/s)',
|
||||
dataIndex: 'total',
|
||||
key: 'total'
|
||||
}
|
||||
];
|
||||
|
||||
// mock data example: three rows of percentiles: 50th, 90th, 99th
|
||||
const data = [
|
||||
{
|
||||
key: '1',
|
||||
percentile: '50%',
|
||||
ttft: 100,
|
||||
itl: 50,
|
||||
tpot: 20,
|
||||
latency: 200,
|
||||
inputTokens: 300,
|
||||
outputTokens: 400,
|
||||
output: 500,
|
||||
total: 600
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
percentile: '90%',
|
||||
ttft: 150,
|
||||
itl: 70,
|
||||
tpot: 30,
|
||||
latency: 250,
|
||||
inputTokens: 350,
|
||||
outputTokens: 450,
|
||||
output: 550,
|
||||
total: 650
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
percentile: '99%',
|
||||
ttft: 200,
|
||||
itl: 90,
|
||||
tpot: 40,
|
||||
latency: 300,
|
||||
inputTokens: 400,
|
||||
outputTokens: 500,
|
||||
output: 600,
|
||||
total: 700
|
||||
}
|
||||
];
|
||||
|
||||
const PercentileResult: React.FC = () => {
|
||||
const { detailData, id } = useDetailContext();
|
||||
|
||||
return (
|
||||
<Section title="Percentiles Result">
|
||||
<Table
|
||||
size="small"
|
||||
columns={columns}
|
||||
dataSource={data}
|
||||
rowKey="percentile"
|
||||
pagination={false}
|
||||
styles={{
|
||||
body: {
|
||||
cell: {
|
||||
height: 54
|
||||
}
|
||||
}
|
||||
}}
|
||||
></Table>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
export default PercentileResult;
|
||||
@@ -0,0 +1,42 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Container = styled.div`
|
||||
padding: 16px;
|
||||
border: 1px solid var(--ant-color-border);
|
||||
border-radius: 4px;
|
||||
.section-title {
|
||||
font-weight: 500;
|
||||
margin-bottom: 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
th.ant-descriptions-item {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
const Title = styled.div`
|
||||
display: flex;
|
||||
font-weight: 500;
|
||||
margin-bottom: 12px;
|
||||
font-size: 14px;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const DetailSection: React.FC<{
|
||||
children: React.ReactNode;
|
||||
title?: React.ReactNode;
|
||||
styles?: {
|
||||
container?: React.CSSProperties;
|
||||
title?: React.CSSProperties;
|
||||
};
|
||||
}> = ({ children, title, styles }) => {
|
||||
return (
|
||||
<Container style={styles?.container}>
|
||||
{title && <Title style={styles?.title}>{title}</Title>}
|
||||
{children}
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default DetailSection;
|
||||
Reference in New Issue
Block a user