style: detail layout

This commit is contained in:
jialin
2026-01-30 18:48:55 +08:00
parent 667751dc5f
commit 5747cae87a
23 changed files with 506 additions and 234 deletions
+1 -1
View File
@@ -21,7 +21,7 @@ export default {
'benchmark.form.profile.longContext': 'Long Context', 'benchmark.form.profile.longContext': 'Long Context',
'benchmark.form.profile.heavy': 'Generation Heavy', 'benchmark.form.profile.heavy': 'Generation Heavy',
'benchmark.form.profile.custom': 'Custom', 'benchmark.form.profile.custom': 'Custom',
'benchmark.table.inputTokenLength': 'Input Token Length', 'benchmark.table.inputTokenLength': 'Prompt Token Length',
'benchmark.table.outputTokenLength': 'Output Token Length', 'benchmark.table.outputTokenLength': 'Output Token Length',
'benchmark.detail.summary.title': 'Summary', 'benchmark.detail.summary.title': 'Summary',
'benchmark.detail.configure.title': 'Configure', 'benchmark.detail.configure.title': 'Configure',
+1 -1
View File
@@ -21,7 +21,7 @@ export default {
'benchmark.form.profile.longContext': 'Long Context', 'benchmark.form.profile.longContext': 'Long Context',
'benchmark.form.profile.heavy': 'Generation Heavy', 'benchmark.form.profile.heavy': 'Generation Heavy',
'benchmark.form.profile.custom': 'Custom', 'benchmark.form.profile.custom': 'Custom',
'benchmark.table.inputTokenLength': 'Input Token Length', 'benchmark.table.inputTokenLength': 'Prompt Token Length',
'benchmark.table.outputTokenLength': 'Output Token Length', 'benchmark.table.outputTokenLength': 'Output Token Length',
'benchmark.detail.summary.title': 'Summary', 'benchmark.detail.summary.title': 'Summary',
'benchmark.detail.configure.title': 'Configure', 'benchmark.detail.configure.title': 'Configure',
+1 -1
View File
@@ -21,7 +21,7 @@ export default {
'benchmark.form.profile.longContext': 'Long Context', 'benchmark.form.profile.longContext': 'Long Context',
'benchmark.form.profile.heavy': 'Generation Heavy', 'benchmark.form.profile.heavy': 'Generation Heavy',
'benchmark.form.profile.custom': 'Custom', 'benchmark.form.profile.custom': 'Custom',
'benchmark.table.inputTokenLength': 'Input Token Length', 'benchmark.table.inputTokenLength': 'Prompt Token Length',
'benchmark.table.outputTokenLength': 'Output Token Length', 'benchmark.table.outputTokenLength': 'Output Token Length',
'benchmark.detail.summary.title': 'Summary', 'benchmark.detail.summary.title': 'Summary',
'benchmark.detail.configure.title': 'Configure', 'benchmark.detail.configure.title': 'Configure',
+1 -1
View File
@@ -21,7 +21,7 @@ export default {
'benchmark.form.profile.longContext': '长上下文', 'benchmark.form.profile.longContext': '长上下文',
'benchmark.form.profile.heavy': '高生成量', 'benchmark.form.profile.heavy': '高生成量',
'benchmark.form.profile.custom': '自定义', 'benchmark.form.profile.custom': '自定义',
'benchmark.table.inputTokenLength': '输入 Token 长度', 'benchmark.table.inputTokenLength': '提示词 Token 长度',
'benchmark.table.outputTokenLength': '输出 Token 长度', 'benchmark.table.outputTokenLength': '输出 Token 长度',
'benchmark.detail.summary.title': '摘要', 'benchmark.detail.summary.title': '摘要',
'benchmark.detail.configure.title': '配置', 'benchmark.detail.configure.title': '配置',
+17
View File
@@ -10,6 +10,7 @@ import {
export const BENCHMARKS_API = '/benchmarks'; export const BENCHMARKS_API = '/benchmarks';
export const DATASETS_API = '/datasets'; export const DATASETS_API = '/datasets';
export const PROFILES_CONFIG_API = '/benchmark-profiles/default-config'; export const PROFILES_CONFIG_API = '/benchmark-profiles/default-config';
export const EXPORT_BENCHMARK_LIST = '/benchmarks/export';
export async function queryBenchmarkList( export async function queryBenchmarkList(
params: Global.SearchParams, params: Global.SearchParams,
@@ -103,3 +104,19 @@ export async function queryProfiles(
cancelToken: options?.token cancelToken: options?.token
}); });
} }
export async function exportBenchmarkList(
params: {
ids?: number[];
},
options?: {
token?: CancelToken;
}
) {
return request(`${BENCHMARKS_API}/export`, {
method: 'POST',
data: params.ids,
responseType: 'blob',
cancelToken: options?.token
});
}
@@ -4,7 +4,8 @@ import SealTable from '@/components/seal-table/index';
import useExpandedRowKeys from '@/hooks/use-expanded-row-keys'; import useExpandedRowKeys from '@/hooks/use-expanded-row-keys';
import { convertFileSize } from '@/utils'; import { convertFileSize } from '@/utils';
import useMemoizedFn from 'ahooks/lib/useMemoizedFn'; import useMemoizedFn from 'ahooks/lib/useMemoizedFn';
import { Col, Row } from 'antd'; import { Col, Row, Tag } from 'antd';
import _ from 'lodash';
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { useDetailContext } from '../../config/detail-context'; import { useDetailContext } from '../../config/detail-context';
@@ -32,30 +33,31 @@ const Environment: React.FC = () => {
return { return {
...workerInfo, ...workerInfo,
..._.pick(gpuData?.[0], ['driver_version', 'runtime_version']),
isMain: true, isMain: true,
children: gpuData children: gpuData
}; };
}, [snapshot]); }, [snapshot]);
// const subWorkers = useMemo(() => { const subWorkers = useMemo(() => {
// const [[mainWorkerName, mainWorkerInfo]] = Object.entries(snapshot.workers); const [[mainWorkerName, mainWorkerInfo]] = Object.entries(snapshot.workers);
// const subOrdinaryWorkers = Object.values(snapshot.instances).filter( const subOrdinaryWorkers = Object.values(snapshot.instances).filter(
// (instance) => instance.worker_name === mainWorkerName (instance) => instance.worker_name === mainWorkerName
// ); );
// return subOrdinaryWorkers.map((worker) => { return subOrdinaryWorkers.map((worker) => {
// const gpuData = Object.values(snapshot.gpus).filter( const gpuData = Object.values(snapshot.gpus).filter(
// (gpu) => gpu.worker_name === worker.worker_name (gpu) => gpu.worker_name === worker.worker_name
// ); );
// return { return {
// ...worker, ...worker,
// isMain: false, isMain: false,
// children: gpuData children: gpuData
// }; };
// }); });
// }, [snapshot, mainWorker]); }, [snapshot, mainWorker]);
const GPUColumns = [ const GPUColumns = [
{ {
@@ -68,19 +70,26 @@ const Environment: React.FC = () => {
<AutoTooltip ghost>{value}</AutoTooltip> <AutoTooltip ghost>{value}</AutoTooltip>
) )
}, },
{
title: 'Index',
dataIndex: 'index',
key: 'index',
span: 4,
colStyle: { paddingLeft: 48 }
},
{ {
title: 'Vendor', title: 'Vendor',
dataIndex: 'vendor', dataIndex: 'vendor',
key: 'vendor', key: 'vendor',
span: 4, span: 6,
colStyle: { paddingLeft: 46 } colStyle: { paddingLeft: 110 }
}, },
{ {
title: 'VRAM', title: 'VRAM',
dataIndex: 'memory_total', dataIndex: 'memory_total',
key: 'memory_total', key: 'memory_total',
label: 'VRAM', label: 'VRAM',
span: 3, span: 4,
render: (value: number, record: any) => convertFileSize(value) render: (value: number, record: any) => convertFileSize(value)
}, },
{ {
@@ -88,20 +97,8 @@ const Environment: React.FC = () => {
dataIndex: 'core_total', dataIndex: 'core_total',
key: 'core_total', key: 'core_total',
label: 'Cores', label: 'Cores',
span: 3
},
{
title: 'Runtime Version',
dataIndex: 'runtime_version',
key: 'runtime_version',
span: 4
},
{
title: 'Driver Version',
dataIndex: 'driver_version',
key: 'driver_version',
span: 4, span: 4,
colStyle: { paddingLeft: 35 } colStyle: { paddingLeft: 36 }
} }
]; ];
@@ -110,31 +107,56 @@ const Environment: React.FC = () => {
title: 'Worker Name', title: 'Worker Name',
dataIndex: 'name', dataIndex: 'name',
key: 'name', key: 'name',
span: 6 span: 6,
render: (value: string, record: any) => {
return (
<>
<AutoTooltip ghost>{value}</AutoTooltip>
{record.isMain && (
<Tag color="geekblue" style={{ marginLeft: 8 }}>
Main
</Tag>
)}
</>
);
}
}, },
{ {
title: 'System', title: 'System',
dataIndex: 'os', dataIndex: 'os',
key: 'system', key: 'system',
span: 4, span: 5,
render: (os: { name: string; version: string }, record: any) => { render: (os: { name: string; version: string }, record: any) => {
return `${record.os.name}`; return (
<AutoTooltip
ghost
>{`${record.os.name} (${record.os.version})`}</AutoTooltip>
);
} }
}, },
{ {
title: 'Version', title: 'Runtime Version',
dataIndex: 'version', dataIndex: 'runtime_version',
key: 'version', key: 'runtime_version',
span: 6, span: 3,
render: (val: any, record: any) => { render: (val: any, record: any) => {
return <AutoTooltip ghost>{record.os.version}</AutoTooltip>; return <AutoTooltip ghost>{record.runtime_version}</AutoTooltip>;
}
},
{
title: 'Driver Version',
dataIndex: 'driver_version',
key: 'driver_version',
span: 3,
render: (val: any, record: any) => {
return <AutoTooltip ghost>{record.driver_version}</AutoTooltip>;
} }
}, },
{ {
title: 'CPU Count', title: 'CPU Count',
dataIndex: 'cpu_total', dataIndex: 'cpu_total',
key: 'cpu_total', key: 'cpu_total',
span: 4 span: 3
}, },
{ {
title: 'Memory', title: 'Memory',
@@ -146,8 +168,8 @@ const Environment: React.FC = () => {
]; ];
const dataList = useMemo(() => { const dataList = useMemo(() => {
return [mainWorker]; return [mainWorker, ...subWorkers];
}, [mainWorker]); }, [mainWorker, subWorkers]);
const handleToggleExpandAll = useMemoizedFn((expanded: boolean) => { const handleToggleExpandAll = useMemoizedFn((expanded: boolean) => {
const keys = dataList?.map((item) => item.id); const keys = dataList?.map((item) => item.id);
@@ -161,6 +183,29 @@ const Environment: React.FC = () => {
const renderChildren = useMemoizedFn((list: any[]) => { const renderChildren = useMemoizedFn((list: any[]) => {
return ( return (
<div style={{ borderRadius: 'var(--ant-table-header-border-radius)' }}> <div style={{ borderRadius: 'var(--ant-table-header-border-radius)' }}>
<Row style={{ width: '100%' }} align="middle">
{GPUColumns.map((col) => (
<Col key={col.key} span={col.span} style={{ ...col.colStyle }}>
<span
style={{
display: 'flex',
alignItems: 'flex-start',
gap: 4,
flexDirection: 'column'
}}
>
<span
style={{
color: 'var(--ant-color-text-tertiary)',
fontSize: 12
}}
>
{col.title}
</span>
</span>
</Col>
))}
</Row>
<RowChildren> <RowChildren>
{list.map((gpu) => ( {list.map((gpu) => (
<Row key={gpu.id} style={{ width: '100%' }} align="middle"> <Row key={gpu.id} style={{ width: '100%' }} align="middle">
@@ -174,14 +219,6 @@ const Environment: React.FC = () => {
flexDirection: 'column' flexDirection: 'column'
}} }}
> >
<span
style={{
color: 'var(--ant-color-text-tertiary)',
fontSize: 12
}}
>
{col.title}
</span>
{col.render {col.render
? col.render((gpu as any)[col.dataIndex], gpu) ? col.render((gpu as any)[col.dataIndex], gpu)
: (gpu as any)[col.dataIndex]} : (gpu as any)[col.dataIndex]}
@@ -1,7 +1,7 @@
import { Descriptions, DescriptionsProps } from 'antd'; import { Descriptions, DescriptionsProps } from 'antd';
import React from 'react'; import React from 'react';
import { useDetailContext } from '../../config/detail-context'; import { useDetailContext } from '../../config/detail-context';
import Section from './section'; import Title from './title';
const Benchmark: React.FC = () => { const Benchmark: React.FC = () => {
const { detailData } = useDetailContext(); const { detailData } = useDetailContext();
@@ -19,7 +19,7 @@ const Benchmark: React.FC = () => {
}, },
{ {
key: '3', key: '3',
label: 'Token Length (Prompt/Out)', label: 'Token Length (Prompt/Output)',
children: ( children: (
<span> <span>
{detailData?.dataset_prompt_tokens || '-'} /{' '} {detailData?.dataset_prompt_tokens || '-'} /{' '}
@@ -45,19 +45,19 @@ const Benchmark: React.FC = () => {
]; ];
return ( return (
<Section title="Benchmark Parameters" minHeight={132}> <div>
<Title>Parameters</Title>
<Descriptions <Descriptions
items={items} items={items}
colon={false} colon={false}
column={3} column={3}
layout="vertical"
styles={{ styles={{
content: { content: {
justifyContent: 'flex-start' justifyContent: 'flex-start'
} }
}} }}
></Descriptions> ></Descriptions>
</Section> </div>
); );
}; };
@@ -1,9 +1,11 @@
import { Divider } from 'antd';
import React from 'react'; import React from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import BenchMark from './benchmark'; import BenchMark from './benchmark';
import Instance from './instance'; import Instance from './instance';
import MetricsResult from './metrics-result'; import MetricsResult from './metrics-result';
import PercentileResult from './percentile-result'; import PercentileResult from './percentile-result';
import Section from './section';
const Container = styled.div` const Container = styled.div`
display: flex; display: flex;
@@ -14,10 +16,16 @@ const Container = styled.div`
const Summary: React.FC = () => { const Summary: React.FC = () => {
return ( return (
<Container> <Container>
<Instance /> <Section title="Results">
<BenchMark /> <MetricsResult />
<MetricsResult /> <Divider />
<PercentileResult /> <PercentileResult />
</Section>
<Section title="Benchmark Details">
<Instance />
<Divider />
<BenchMark />
</Section>
</Container> </Container>
); );
}; };
@@ -1,8 +1,9 @@
import AutoTooltip from '@/components/auto-tooltip';
import { Descriptions, Flex, Tag } from 'antd'; import { Descriptions, Flex, Tag } from 'antd';
import _ from 'lodash'; import _ from 'lodash';
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { useDetailContext } from '../../config/detail-context'; import { useDetailContext } from '../../config/detail-context';
import Section from './section'; import Title from './title';
const calcTotalVram = (vram: Record<string, number>) => { const calcTotalVram = (vram: Record<string, number>) => {
return _.sum(_.values(vram)); return _.sum(_.values(vram));
@@ -10,6 +11,8 @@ const calcTotalVram = (vram: Record<string, number>) => {
const Instance: React.FC = () => { const Instance: React.FC = () => {
const { detailData } = useDetailContext(); const { detailData } = useDetailContext();
const [, instanceData] =
Object.entries(detailData?.snapshot?.instances || {})[0] || [];
const items = useMemo(() => { const items = useMemo(() => {
const { snapshot } = detailData; const { snapshot } = detailData;
@@ -18,25 +21,21 @@ const Instance: React.FC = () => {
return [ return [
{ {
key: '1', key: '1',
label: 'Instance Name', label: 'Model Name',
children: ( children: (
<div className="flex-center gap-8"> <AutoTooltip ghost>{detailData?.model_name || '-'}</AutoTooltip>
<span>
{detailData?.model_name}/{detailData?.model_instance_name}
</span>
</div>
) )
}, },
{ {
key: '2', key: '2',
label: 'Worker', label: 'Instance Name',
children: instanceData?.worker_name || '-' children: (
}, <AutoTooltip ghost>
{ {detailData?.model_instance_name || '-'}
key: '6', </AutoTooltip>
label: 'GPU Type', )
children: instanceData?.gpu_type || '-'
}, },
{ {
key: '5', key: '5',
label: 'Backend', label: 'Backend',
@@ -45,6 +44,13 @@ const Instance: React.FC = () => {
? `(${instanceData?.backend_version})` ? `(${instanceData?.backend_version})`
: '' : ''
}` }`
},
{
key: '6',
label: 'Model File',
children: (
<AutoTooltip ghost>{instanceData?.resolved_path || '-'}</AutoTooltip>
)
} }
]; ];
}, [detailData]); }, [detailData]);
@@ -144,7 +150,8 @@ const Instance: React.FC = () => {
}, [detailData]); }, [detailData]);
return ( return (
<Section title="Model Instance" minHeight={220}> <div>
<Title>Instance</Title>
<Descriptions <Descriptions
items={items} items={items}
colon={false} colon={false}
@@ -168,7 +175,7 @@ const Instance: React.FC = () => {
} }
}} }}
></Descriptions> ></Descriptions>
</Section> </div>
); );
}; };
@@ -1,25 +1,46 @@
import { Descriptions } from 'antd'; import { Descriptions } from 'antd';
import _, { round } from 'lodash'; import _, { round } from 'lodash';
import React from 'react'; import React from 'react';
import styled from 'styled-components';
import { useDetailContext } from '../../config/detail-context'; import { useDetailContext } from '../../config/detail-context';
import Section from './section'; import Title from './title';
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 1fr;
gap: 16px;
`;
const DescWrapper = styled.div`
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 24px;
`;
const columns = [ const columns = [
{ // {
title: 'Duration (s)', // title: 'Duration',
dataIndex: 'duration', // dataIndex: 'duration',
path: ['raw_metrics', 'benchmarks', '0', 'duration'], // path: ['raw_metrics', 'benchmarks', '0', 'duration'],
unit: 's', // unit: 's',
render: (value: number) => round(value, 2) // render: (value: number) => round(value, 2)
}, // },
{ // {
title: 'Total Requests', // title: 'Total Requests',
dataIndex: 'total_requests', // dataIndex: 'total_requests',
path: 'total_requests', // path: 'total_requests',
unit: '', // unit: '',
render: (value: number) => round(value, 0) // render: (value: number) => round(value, 0)
}, // },
{ {
title: 'Total token throughput', title: 'Total token throughput',
dataIndex: 'tokens_per_second_mean', dataIndex: 'tokens_per_second_mean',
@@ -28,18 +49,25 @@ const columns = [
render: (value: number) => round(value, 2) render: (value: number) => round(value, 2)
}, },
{ {
title: 'Success Requests', title: 'Prompt token throughput ',
dataIndex: 'successful_requests', dataIndex: 'prompt_tokens_per_second_mean',
path: ['raw_metrics', 'benchmarks', '0'], path: 'prompt_tokens_per_second_mean',
unit: '', unit: 'Tokens/s',
render: (value: number) => { render: (value: number) => round(value, 2)
return (
<span style={{ color: 'var(--ant-color-success)' }}>
{_.get(value, 'metrics.request_totals.successful')}
</span>
);
}
}, },
// {
// title: 'Success Requests',
// dataIndex: 'successful_requests',
// path: ['raw_metrics', 'benchmarks', '0'],
// unit: '',
// render: (value: number) => {
// return (
// <span style={{ color: 'var(--ant-color-success)' }}>
// {_.get(value, 'metrics.request_totals.successful')}
// </span>
// );
// }
// },
{ {
title: 'Output token throughput', title: 'Output token throughput',
@@ -51,65 +79,103 @@ const columns = [
]; ];
const columnsSub = [ const columnsSub = [
// {
// title: 'Failed Requests',
// dataIndex: 'failed_requests',
// path: ['raw_metrics', 'benchmarks', '0'],
// unit: '',
// render: (value: number) => {
// return (
// <span style={{ color: 'var(--ant-color-error)' }}>
// {_.get(value, 'metrics.request_totals.errored')}
// </span>
// );
// }
// },
// {
// title: 'Concurrency',
// dataIndex: 'request_concurrency',
// path: ['raw_metrics', 'benchmarks', '0'],
// unit: '',
// render: (value: number) => {
// return round(
// _.get(value, 'metrics.request_concurrency.successful.mean'),
// 0
// );
// }
// },
{ {
title: 'Failed Requests', title: 'Average Request Latency',
dataIndex: 'failed_requests',
path: ['raw_metrics', 'benchmarks', '0'],
unit: '',
render: (value: number) => {
return (
<span style={{ color: 'var(--ant-color-error)' }}>
{_.get(value, 'metrics.request_totals.errored')}
</span>
);
}
},
{
title: 'Concurrency',
dataIndex: 'request_concurrency',
path: ['raw_metrics', 'benchmarks', '0'],
unit: '',
render: (value: number) => {
return round(
_.get(value, 'metrics.request_concurrency.successful.mean'),
0
);
}
},
{
title: 'Request token throughput ',
dataIndex: 'prompt_tokens_per_second_mean',
path: 'prompt_tokens_per_second_mean',
unit: 'Tokens/s',
render: (value: number) => round(value, 2)
},
{
title: 'Average Request Latency (ms)',
dataIndex: 'request_latency_mean', dataIndex: 'request_latency_mean',
path: 'request_latency_mean', path: 'request_latency_mean',
unit: 'ms', unit: 'ms',
render: (value: number) => round(value, 2) render: (value: number) => round(value, 2)
}, },
{ {
title: 'Average Time To First Token (ms)', title: 'Average Time To First Token',
dataIndex: 'time_to_first_token_mean', dataIndex: 'time_to_first_token_mean',
path: 'time_to_first_token_mean', path: 'time_to_first_token_mean',
unit: 'ms', unit: 'ms',
render: (value: number) => round(value, 2) render: (value: number) => round(value, 2)
}, },
{ {
title: 'Average Time Per Output Token (ms)', title: 'Average Time Per Output Token',
dataIndex: 'time_per_output_token_mean', dataIndex: 'time_per_output_token_mean',
path: 'time_per_output_token_mean', path: 'time_per_output_token_mean',
unit: 'ms', unit: 'ms',
render: (value: number) => round(value, 2) render: (value: number) => round(value, 2)
}, },
{ {
title: '', title: 'Average Inter Token Latency',
dataIndex: '', dataIndex: 'inter_token_latency_mean',
path: '', path: 'inter_token_latency_mean',
unit: '', unit: 'ms',
render: (_: number) => '' render: (value: number) => round(value, 2)
}
];
const requestFields = [
{
label: 'Total Requests',
key: 'total_requests',
dataIndex: 'total_requests',
path: 'total_requests',
precision: 0,
render: (value: number) => round(value, 0),
unit: ''
},
{
label: 'Success Requests',
key: 'total_requests',
dataIndex: 'successful_requests',
path: ['raw_metrics', 'benchmarks', '0'],
render: (value: number) =>
round(_.get(value, ['metrics', 'request_totals', 'successful']), 0),
precision: 0,
color: 'var(--ant-color-success)',
unit: ''
},
{
label: 'Failed Requests',
key: 'total_requests',
dataIndex: 'failed_requests',
path: ['raw_metrics', 'benchmarks', '0'],
render: (value: number) =>
round(_.get(value, ['metrics', 'request_totals', 'errored']), 0),
precision: 0,
color: 'var(--ant-color-error)',
unit: ''
},
{
label: 'Concurrency',
key: 'request_concurrency',
dataIndex: 'request_concurrency',
path: ['raw_metrics', 'benchmarks', '0'],
render: (value: number) =>
round(_.get(value, 'metrics.request_concurrency.successful.mean'), 0),
precision: 0,
unit: ''
} }
]; ];
@@ -148,39 +214,94 @@ const PercentileResult: React.FC = () => {
}) })
); );
const throughputItems = columns.map(
({ title, dataIndex, path, render, unit }) => ({
key: dataIndex,
label: title,
children: unit ? (
<span className="flex-center">
{render(_.get(detailData, path) ?? 0)}{' '}
<span className="m-l-4">({unit})</span>
</span>
) : (
render(_.get(detailData, path) ?? 0)
)
})
);
const latencyItems = columnsSub.map(
({ title, dataIndex, path, render, unit }) => ({
key: dataIndex,
label: title,
children: unit ? (
<span className="flex-center">
{render(_.get(detailData, path) ?? 0)}{' '}
<span className="m-l-4">({unit})</span>
</span>
) : (
render(_.get(detailData, path) ?? 0)
)
})
);
const requestItems = requestFields.map(
({ label, dataIndex, path, render, unit, color }) => ({
key: dataIndex,
label: label,
children: unit ? (
<span className="flex-center" style={{ color: color }}>
{render(_.get(detailData, path) ?? 0)}{' '}
<span className="m-l-4">({unit})</span>
</span>
) : (
<span style={{ color: color }}>
{render(_.get(detailData, path) ?? 0)}
</span>
)
})
);
const descriptionStyles = {
header: {
marginBottom: 8
},
title: {
fontWeight: 500,
color: 'var(--ant-color-text-secondary)',
fontSize: 14
},
content: {
justifyContent: 'flex-start'
}
};
return ( return (
<Section title="Metrics Result" minHeight={210}> <div>
{/* <Table <Title>Metrics</Title>
size="small" <Box>
columns={[ <Descriptions
{ styles={descriptionStyles}
title: 'Metrics', title="Throughput"
dataIndex: 'metrics' items={throughputItems}
}, colon={false}
...columns column={1}
]} ></Descriptions>
dataSource={buildPercentileTable(metrics)} <Descriptions
rowKey="percentile" styles={descriptionStyles}
pagination={false} title="Latency"
styles={{ items={latencyItems}
body: { colon={false}
cell: { column={1}
height: 54 ></Descriptions>
} <Descriptions
} styles={descriptionStyles}
}} title="Requests"
></Table> */} items={requestItems}
<Descriptions colon={false}
items={items} column={1}
colon={false} ></Descriptions>
column={2} </Box>
styles={{ </div>
content: {
justifyContent: 'flex-end'
}
}}
></Descriptions>
</Section>
); );
}; };
@@ -2,7 +2,7 @@ import { Table } from 'antd';
import { round } from 'lodash'; import { round } from 'lodash';
import React from 'react'; import React from 'react';
import { useDetailContext } from '../../config/detail-context'; import { useDetailContext } from '../../config/detail-context';
import Section from './section'; import Title from './title';
const columns = [ const columns = [
{ {
@@ -76,13 +76,17 @@ const PercentileResult: React.FC = () => {
}; };
return ( return (
<Section title="Percentiles Result" minHeight={240}> <div>
<Title>Percentile</Title>
<Table <Table
size="small" size="small"
columns={[ columns={[
{ {
title: 'Percentile', title: 'Percentile',
dataIndex: 'percentile' dataIndex: 'percentile',
render: (value: string) => (
<span style={{ fontWeight: 500 }}>{value}</span>
)
}, },
...columns ...columns
]} ]}
@@ -90,17 +94,15 @@ const PercentileResult: React.FC = () => {
rowKey="percentile" rowKey="percentile"
pagination={false} pagination={false}
styles={{ styles={{
// header: { header: {
// row: { row: {
// background: 'none', backgroundColor: 'transparent'
// borderBottom: '1px solid var(--ant-color-split)' },
// }, cell: {
// cell: { fontWeight: 400,
// background: 'none', borderBottom: '1px solid var(--ant-color-split)'
// paddingBottom: 0, }
// borderBottom: '1px solid var(--ant-color-split)' },
// }
// },
body: { body: {
cell: { cell: {
height: 54 height: 54
@@ -108,7 +110,7 @@ const PercentileResult: React.FC = () => {
} }
}} }}
></Table> ></Table>
</Section> </div>
); );
}; };
@@ -0,0 +1,21 @@
import { BulbOutlined } from '@ant-design/icons';
import styled from 'styled-components';
const Content = styled.div`
font-weight: 500;
font-size: 14px;
margin-bottom: 16px;
`;
const Title: React.FC<{ children: React.ReactNode }> = ({ children }) => {
return (
<Content>
<BulbOutlined
style={{ marginRight: 8, color: 'var(--ant-color-text-tertiary)' }}
/>
{children}
</Content>
);
};
export default Title;
+4 -1
View File
@@ -7,6 +7,7 @@ export const BenchmarkStatusValueMap = {
Running: 'running', Running: 'running',
Completed: 'completed', Completed: 'completed',
Error: 'error', Error: 'error',
Stopped: 'stopped',
Unreachable: 'unreachable' Unreachable: 'unreachable'
}; };
@@ -16,6 +17,7 @@ export const BenchmarkStatusLabelMap = {
[BenchmarkStatusValueMap.Running]: 'Running', [BenchmarkStatusValueMap.Running]: 'Running',
[BenchmarkStatusValueMap.Completed]: 'Completed', [BenchmarkStatusValueMap.Completed]: 'Completed',
[BenchmarkStatusValueMap.Error]: 'Error', [BenchmarkStatusValueMap.Error]: 'Error',
[BenchmarkStatusValueMap.Stopped]: 'Stopped',
[BenchmarkStatusValueMap.Unreachable]: 'Unreachable' [BenchmarkStatusValueMap.Unreachable]: 'Unreachable'
}; };
@@ -25,7 +27,8 @@ export const BenchmarkStatus: Record<string, StatusType> = {
[BenchmarkStatusValueMap.Running]: StatusMaps.success, [BenchmarkStatusValueMap.Running]: StatusMaps.success,
[BenchmarkStatusValueMap.Completed]: StatusMaps.success, [BenchmarkStatusValueMap.Completed]: StatusMaps.success,
[BenchmarkStatusValueMap.Error]: StatusMaps.error, [BenchmarkStatusValueMap.Error]: StatusMaps.error,
[BenchmarkStatusValueMap.Unreachable]: StatusMaps.error [BenchmarkStatusValueMap.Unreachable]: StatusMaps.error,
[BenchmarkStatusValueMap.Stopped]: StatusMaps.warning
}; };
export const ProfileValueMap = { export const ProfileValueMap = {
+1
View File
@@ -91,6 +91,7 @@ export interface BenchmarkListItem extends FormData {
updated_at: string; updated_at: string;
state: string; state: string;
state_message: string; state_message: string;
progress: number;
instance_snapshot: InstanceSnapshot; instance_snapshot: InstanceSnapshot;
gpu_snapshot: GPUSnapshot[]; gpu_snapshot: GPUSnapshot[];
} }
+1
View File
@@ -77,6 +77,7 @@ const BasicForm: React.FC = () => {
]} ]}
> >
<SealSelect <SealSelect
disabled={action === PageAction.EDIT}
loading={clusterLoading} loading={clusterLoading}
options={clusterList} options={clusterList}
label={intl.formatMessage({ id: 'clusters.title' })} label={intl.formatMessage({ id: 'clusters.title' })}
+5 -7
View File
@@ -87,13 +87,11 @@ const DatasetForm: React.FC = () => {
<Form.Item<FormData> hidden name="dataset_id"> <Form.Item<FormData> hidden name="dataset_id">
<SealInput.Input></SealInput.Input> <SealInput.Input></SealInput.Input>
</Form.Item> </Form.Item>
{profile === 'Custom' && ( <RandomSettingsForm
<RandomSettingsForm datasetList={datasetList}
datasetList={datasetList} datasetLoading={datasetLoading}
datasetLoading={datasetLoading} handleOnDataSetChange={handleOnDataSetChange}
handleOnDataSetChange={handleOnDataSetChange} ></RandomSettingsForm>
></RandomSettingsForm>
)}
</> </>
); );
}; };
+11 -13
View File
@@ -17,7 +17,6 @@ import FormContext from '../config/form-context';
import { FormData, BenchmarkListItem as ListItem } from '../config/types'; import { FormData, BenchmarkListItem as ListItem } from '../config/types';
import Basic from './basic'; import Basic from './basic';
import DatasetForm from './dataset'; import DatasetForm from './dataset';
import RandomSettingsForm from './random-settings';
interface ProviderFormProps { interface ProviderFormProps {
ref?: any; ref?: any;
@@ -90,16 +89,16 @@ const ProviderForm: React.FC<ProviderFormProps> = forwardRef((props, ref) => {
} }
}, [showAdvanced, activeKey]); }, [showAdvanced, activeKey]);
const advancedItems = showAdvanced // const advancedItems = showAdvanced
? [ // ? [
{ // {
key: TABKeysMap.ADVANCED, // key: TABKeysMap.ADVANCED,
label: intl.formatMessage({ id: 'resources.form.advanced' }), // label: intl.formatMessage({ id: 'resources.form.advanced' }),
forceRender: true, // forceRender: true,
children: <RandomSettingsForm /> // children: <RandomSettingsForm />
} // }
] // ]
: []; // : [];
console.log('render form with profile:', showAdvanced); console.log('render form with profile:', showAdvanced);
@@ -144,8 +143,7 @@ const ProviderForm: React.FC<ProviderFormProps> = forwardRef((props, ref) => {
label: intl.formatMessage({ id: 'common.title.config' }), label: intl.formatMessage({ id: 'common.title.config' }),
forceRender: true, forceRender: true,
children: <DatasetForm /> children: <DatasetForm />
}, }
...advancedItems
]} ]}
></CollapsePanel> ></CollapsePanel>
</Form> </Form>
@@ -3,7 +3,7 @@ import AutoTooltip from '@/components/auto-tooltip';
import StatusTag from '@/components/status-tag'; import StatusTag from '@/components/status-tag';
import { tableSorter } from '@/config/settings'; import { tableSorter } from '@/config/settings';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Typography } from 'antd'; import { Progress, Typography } from 'antd';
import { ColumnsType } from 'antd/es/table'; import { ColumnsType } from 'antd/es/table';
import _ from 'lodash'; import _ from 'lodash';
import { useMemo } from 'react'; import { useMemo } from 'react';
@@ -58,15 +58,24 @@ const useBenchmarkColumns = (
ellipsis: { ellipsis: {
showTitle: false showTitle: false
}, },
width: 100, width: 120,
render: (value: number, record: ListItem) => ( render: (value: number, record: ListItem) => (
<StatusTag <span className="flex-center gap-8">
statusValue={{ <StatusTag
status: BenchmarkStatus[value], statusValue={{
text: BenchmarkStatusLabelMap[value], status: BenchmarkStatus[value],
message: record.state_message || undefined text: BenchmarkStatusLabelMap[value],
}} message: record.state_message || undefined
/> }}
/>
{record.progress !== undefined && record.progress < 100 && (
<Progress
type="circle"
percent={_.round(record.progress, 0)}
size={20}
/>
)}
</span>
) )
}, },
// { // {
+3 -7
View File
@@ -27,8 +27,8 @@ import { FormData, BenchmarkListItem as ListItem } from './config/types';
import useBenchmarkColumns from './hooks/use-benchmark-columns'; import useBenchmarkColumns from './hooks/use-benchmark-columns';
import useColumnSettings from './hooks/use-column-settings'; import useColumnSettings from './hooks/use-column-settings';
import useCreateBenchmark from './hooks/use-create-benchmark'; import useCreateBenchmark from './hooks/use-create-benchmark';
import useExportData from './hooks/use-export-data';
import useViewLogs from './hooks/use-view-logs'; import useViewLogs from './hooks/use-view-logs';
import { useExportBenchmark } from './services/use-export-benchmark';
import useQueryDataset from './services/use-query-dataset'; import useQueryDataset from './services/use-query-dataset';
const Benchmark: React.FC = () => { const Benchmark: React.FC = () => {
@@ -65,6 +65,7 @@ const Benchmark: React.FC = () => {
const { SettingsButton, selectedColumns } = useColumnSettings(); const { SettingsButton, selectedColumns } = useColumnSettings();
const { datasetList, fetchDatasetData } = useQueryDataset(); const { datasetList, fetchDatasetData } = useQueryDataset();
const { exportData } = useExportBenchmark();
useEffect(() => { useEffect(() => {
fetchModelList({ page: -1 }); fetchModelList({ page: -1 });
@@ -154,13 +155,8 @@ const Benchmark: React.FC = () => {
handleOnCellClick handleOnCellClick
); );
const { exportData } = useExportData({ columns: columns });
const handleExportData = () => { const handleExportData = () => {
const list = dataSource.dataList.filter((item) => exportData(rowSelection.selectedRowKeys);
rowSelection.selectedRowKeys.includes(item.id)
);
exportData(list);
}; };
return ( return (
@@ -0,0 +1,45 @@
import { GPUSTACK_API_BASE_URL } from '@/config/settings';
import { downloadFile } from '@/utils/download-stream';
import { message } from 'antd';
import { EXPORT_BENCHMARK_LIST } from '../apis';
const matchFilename = (disposition: string | null): string | undefined => {
if (!disposition) return '';
const match = disposition.match(/filename="?([^"]+)"?/);
const filename = match ? match[1] : '';
return filename;
};
export function useExportBenchmark() {
const exportData = async (data: any[]) => {
try {
const res = await fetch(
`${GPUSTACK_API_BASE_URL}${EXPORT_BENCHMARK_LIST}`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
}
);
// header
const contentDispostion = res.headers.get('content-Disposition');
const filename =
matchFilename(contentDispostion) || `benchmark-export.yml`;
if (res.ok) {
const blob = await res.blob();
downloadFile(blob, filename);
} else {
message.error('Download failed');
}
} catch (error) {
message.error('Download failed');
}
};
return {
exportData
};
}
@@ -36,6 +36,7 @@ const useStyles = createStyles(({ token, css }) => {
interface LocalUserFormProps { interface LocalUserFormProps {
handleLogin: (values: any) => void; handleLogin: (values: any) => void;
form: FormInstance; form: FormInstance;
loading?: boolean;
loginOption: { loginOption: {
saml: boolean; saml: boolean;
oidc: boolean; oidc: boolean;
@@ -144,6 +145,7 @@ const LocalUserForm: React.FC<LocalUserFormProps> = (props) => {
htmlType="submit" htmlType="submit"
type="primary" type="primary"
block block
loading={props.loading}
style={{ height: '48px', fontSize: '14px' }} style={{ height: '48px', fontSize: '14px' }}
> >
{intl.formatMessage({ id: 'common.button.login' })} {intl.formatMessage({ id: 'common.button.login' })}
+2 -1
View File
@@ -148,7 +148,7 @@ const LoginForm = () => {
}; };
// local user authentication // local user authentication
const { handleLogin } = useLocalAuth({ const { handleLogin, submitLoading } = useLocalAuth({
fetchUserInfo, fetchUserInfo,
form, form,
onSuccess: async (userInfo) => { onSuccess: async (userInfo) => {
@@ -254,6 +254,7 @@ const LoginForm = () => {
<LocalUserForm <LocalUserForm
handleLogin={handleLogin} handleLogin={handleLogin}
form={form} form={form}
loading={submitLoading}
loginOption={SSOAuth.options} loginOption={SSOAuth.options}
/> />
)} )}
+7 -2
View File
@@ -10,7 +10,7 @@ import {
import { FormInstance } from 'antd'; import { FormInstance } from 'antd';
import CryptoJS from 'crypto-js'; import CryptoJS from 'crypto-js';
import { useAtom } from 'jotai'; import { useAtom } from 'jotai';
import { useEffect } from 'react'; import { useEffect, useState } from 'react';
import { login } from '../apis'; import { login } from '../apis';
interface UseLocalAuthOptions { interface UseLocalAuthOptions {
@@ -27,6 +27,7 @@ export const useLocalAuth = ({
form form
}: UseLocalAuthOptions) => { }: UseLocalAuthOptions) => {
const [initialPassword, setInitialPassword] = useAtom(initialPasswordAtom); const [initialPassword, setInitialPassword] = useAtom(initialPasswordAtom);
const [submitLoading, setSubmitLoading] = useState<boolean>(false);
// Encrypt password before storing // Encrypt password before storing
const encryptPassword = (password: string) => { const encryptPassword = (password: string) => {
@@ -66,6 +67,7 @@ export const useLocalAuth = ({
// click login button // click login button
const handleLogin = async (values: any) => { const handleLogin = async (values: any) => {
setSubmitLoading(true);
try { try {
await login({ await login({
username: values.username, username: values.username,
@@ -86,6 +88,8 @@ export const useLocalAuth = ({
onSuccess?.(userInfo); onSuccess?.(userInfo);
} catch (error: any) { } catch (error: any) {
onError?.(error); onError?.(error);
} finally {
setSubmitLoading(false);
} }
}; };
@@ -94,6 +98,7 @@ export const useLocalAuth = ({
}, []); }, []);
return { return {
handleLogin handleLogin,
submitLoading
}; };
}; };