chore: dashboard api

main
jialin 2 years ago
parent 0fa4e21fa5
commit 1293f71bce

@ -6,38 +6,60 @@ interface BarChartProps {
yField: string;
title?: string;
height?: number;
group?: boolean;
colorField?: string;
seriesField?: string;
stack?: boolean;
legend?: any;
}
const BarChart: React.FC<BarChartProps> = (props) => {
const { data, xField, yField, title, height = 260 } = props;
const {
data,
xField,
yField,
title,
height = 260,
group,
colorField,
seriesField,
stack,
legend = undefined
} = props;
const config = {
data,
xField,
yField,
// colorField: 'name',
colorField: colorField || 'name',
direction: 'vertical',
stack,
seriesField,
height,
group: true,
group,
scale: {
x: {
type: 'band',
padding: 0.5
}
},
legend: {
color: {
position: 'top',
layout: {
justifyContent: 'center'
}
}
},
legend:
legend === 'undefined'
? {
color: {
position: 'top',
layout: {
justifyContent: 'center'
}
}
}
: legend,
axis: {
x: {
xAxis: true,
tick: false
},
y: {
tick: false
tick: false,
labelAutoWrap: true
}
},
title: {
@ -61,7 +83,12 @@ const BarChart: React.FC<BarChartProps> = (props) => {
},
style: {
fill: 'linear-gradient(90deg,rgba(84, 204, 152,0.8) 0%,rgb(0, 168, 143,.7) 100%)',
fill: (params: any) => {
return (
params.color ||
'linear-gradient(90deg,rgba(84, 204, 152,0.8) 0%,rgb(0, 168, 143,.7) 100%)'
);
},
radiusTopLeft: 12,
radiusTopRight: 12,
align: 'center',

@ -6,25 +6,46 @@ interface BarChartProps {
yField: string;
title?: string;
height?: number;
group?: boolean;
colorField?: string;
seriesField?: string;
stack?: boolean;
legend?: any;
}
const BarChart: React.FC<BarChartProps> = (props) => {
const { data, xField, yField, title, height } = props;
const {
data,
xField,
yField,
title,
height,
group,
colorField,
seriesField,
stack,
legend = undefined
} = props;
const config = {
data,
xField,
yField,
// colorField: 'name',
colorField: colorField || 'name',
direction: 'vertical',
seriesField,
height,
group: true,
legend: {
color: {
position: 'top',
layout: {
justifyContent: 'center'
}
}
},
group,
stack,
legend:
legend === 'undefined'
? {
color: {
position: 'top',
layout: {
justifyContent: 'center'
}
}
}
: legend,
scale: {
x: {
type: 'band',
@ -59,7 +80,12 @@ const BarChart: React.FC<BarChartProps> = (props) => {
},
markBackground: {},
style: {
fill: 'linear-gradient(180deg,rgba(84, 204, 152,0.8) 0%,rgb(0, 168, 143,.7) 100%)',
fill: (params: any) => {
return (
params.color ||
'linear-gradient(90deg,rgba(84, 204, 152,0.8) 0%,rgb(0, 168, 143,.7) 100%)'
);
},
radiusTopLeft: 12,
radiusTopRight: 12,
height: 20

@ -7,10 +7,12 @@ interface LineChartProps {
color?: string[];
xField?: string;
yField?: string;
slider?: boolean;
labelFormatter?: (v: any) => string;
slider?: any;
}
const LineChart: React.FC<LineChartProps> = (props) => {
const { data, title, color, xField, yField, slider, height } = props;
const { data, title, color, xField, yField, slider, height, labelFormatter } =
props;
const config = {
title,
height,
@ -28,7 +30,12 @@ const LineChart: React.FC<LineChartProps> = (props) => {
}
},
y: {
tick: false
tick: false,
// size: 14,
// title: '%',
titlePosition: 'top',
titleFontSize: 12,
labelFormatter
}
},
// point: {

@ -12,7 +12,7 @@ interface LiquidChartProps {
}
const LiquidChart: React.FC<LiquidChartProps> = (props) => {
const {
percent,
percent = 0,
color,
title,
width,

@ -289,6 +289,14 @@ body {
.ant-pro-sider {
.ant-menu {
.ant-menu-item {
border-radius: 16px;
&:active {
background-color: var(--ant-menu-item-selected-bg);
}
}
.ant-menu-item:not(.ant-menu-item-selected) {
color: var(--ant-color-text);
}
@ -300,6 +308,11 @@ body {
.ant-menu-item.ant-menu-item-selected:hover {
color: var(--ant-color-primary);
}
.ant-menu-item.ant-menu-item-selected {
color: var(--ant-color-primary);
background-color: unset;
}
}
}
}

@ -0,0 +1,7 @@
import { request } from '@umijs/max';
export const DASHBOARD_API = '/dashboard';
export async function queryDashboardData() {
return request(DASHBOARD_API);
}

@ -1,6 +1,9 @@
// import div from '@/components/content-wrapper';
import PageTools from '@/components/page-tools';
import ProgressBar from '@/components/progress-bar';
import { Col, Row, Table } from 'antd';
import _ from 'lodash';
import { useContext } from 'react';
import { DashboardContext } from '../config/dashboard-context';
const modelColumns = [
{
@ -9,28 +12,30 @@ const modelColumns = [
key: 'name'
},
{
title: 'Allocated GPUs',
dataIndex: 'allocated',
key: 'allocated',
render: (text: any, record: any) => <span>{record.gpu.allocated}</span>
title: 'GPU Utilization',
dataIndex: 'gpu_utilization',
key: 'gpu_utilization',
render: (text: any, record: any) => (
<ProgressBar percent={_.round(text, 2)}></ProgressBar>
)
},
{
title: 'GPU Utilization',
dataIndex: 'utilization',
key: 'utilization',
render: (text: any, record: any) => <span>{record.gpu.utilization}</span>
title: 'VRAM Utilization',
dataIndex: 'gpu_memory_utilization',
key: 'gpu_memory_utilization',
render: (text: any, record: any) => (
<ProgressBar percent={_.round(text, 2)}></ProgressBar>
)
},
{
title: 'Running Instances',
dataIndex: 'running',
key: 'running',
render: (text: any, record: any) => <span>{record.instances.running}</span>
dataIndex: 'instance_count',
key: 'instance_count'
},
{
title: 'Pending Instances',
dataIndex: 'pending',
key: 'pending',
render: (text: any, record: any) => <span>{record.instances.pending}</span>
title: 'Tokens',
dataIndex: 'token_count',
key: 'token_count'
}
];
@ -59,39 +64,6 @@ const projectColumns = [
}
];
const modelData = [
{
id: 1,
name: 'qwen2',
gpu: { allocated: 4, utilization: '50%' },
instances: { running: 1, pending: 0 }
},
{
id: 2,
name: 'llama3:70b',
gpu: { allocated: 3, utilization: '70%' },
instances: { running: 1, pending: 0 }
},
{
id: 3,
name: 'llama3',
gpu: { allocated: 5, utilization: '20%' },
instances: { running: 1, pending: 0 }
},
{
id: 4,
name: 'gemma',
gpu: { allocated: 1, utilization: '25%' },
instances: { running: 1, pending: 0 }
},
{
id: 5,
name: 'phi3',
gpu: { allocated: 2, utilization: '46%' },
instances: { running: 1, pending: 0 }
}
];
const projectData = [
{
id: 1,
@ -130,6 +102,7 @@ const projectData = [
}
];
const ActiveTable = () => {
const data = useContext(DashboardContext).active_models || [];
return (
<Row gutter={[20, 0]}>
<Col xs={24} sm={24} md={24} lg={24} xl={24}>
@ -147,7 +120,7 @@ const ActiveTable = () => {
<div>
<Table
columns={modelColumns}
dataSource={modelData}
dataSource={data}
pagination={false}
rowKey="id"
/>

@ -1,6 +1,8 @@
import { Card, Col, Row, Space } from 'antd';
import React from 'react';
import _ from 'lodash';
import React, { useContext } from 'react';
import { overviewConfigs } from '../config';
import { DashboardContext } from '../config/dashboard-context';
import '../styles/index.less';
import styles from './over-view.less';
@ -23,23 +25,8 @@ const renderCardItem = (data: {
</Card>
);
};
const Overview: React.FC = (props) => {
// const { data = {} } = props;
const data = {
workers: 8,
models: {
healthy: 10,
warning: 2,
error: 1
},
gpus: 30,
allocatedGpus: 12,
instances: {
healthy: 32,
warning: 3,
error: 2
}
};
const Overview: React.FC = () => {
const data = useContext(DashboardContext).resource_counts || {};
const renderValue = (
value:
@ -75,7 +62,7 @@ const Overview: React.FC = (props) => {
>
{renderCardItem({
label: config.label,
value: renderValue(data[config.key] || 0),
value: renderValue(_.get(data, config.key, 0)),
bgColor: config.backgroundColor
})}
</Col>

@ -1,68 +1,68 @@
import LineChart from '@/components/charts/line-chart';
import { generateFluctuatingData } from '@/utils';
import dayjs from 'dayjs';
import _ from 'lodash';
import { useContext, useEffect, useState } from 'react';
import { DashboardContext } from '../config/dashboard-context';
const mockData = {
GPU: generateFluctuatingData({
total: 17,
max: 80,
min: 20
}),
CPU: generateFluctuatingData({
total: 17,
max: 70,
min: 10
}),
Memory: generateFluctuatingData({
total: 17,
max: 60,
min: 20
}),
VRAM: generateFluctuatingData({
total: 17,
max: 90,
min: 15
})
const TypeKeyMap = {
cpu: 'CPU',
memory: 'Memory',
gpu: 'GPU',
gpu_memory: 'VRAM'
};
const UtilizationOvertime: React.FC = () => {
const timeList = [
'08:00:00',
'09:00:00',
'10:00:00',
'11:00:00',
'12:00:00',
'13:00:00',
'14:00:00',
'15:00:00',
'16:00:00',
'17:00:00',
'18:00:00',
'19:00:00',
'20:00:00',
'21:00:00',
'22:00:00',
'23:00:00',
'24:00:00'
];
const typeList = ['GPU', 'CPU', 'Memory', 'VRAM'];
const generateData = () => {
const data = [];
for (let i = 0; i < timeList.length; i++) {
for (let j = 0; j < typeList.length; j++) {
data.push({
time: timeList[i],
type: typeList[j],
value: _.get(mockData, typeList[j])[i]
});
}
const data = useContext(DashboardContext)?.system_load?.history || {};
const [result, setResult] = useState<
{ time: string; value: number; type: string }[]
>([]);
const typeList = ['gpu', 'cpu', 'memory', 'gpu_memory'];
const sliderConfig = {
y: false,
x: {
style: {
selectionFill: 'rgb(84, 204, 152)',
selectionFillOpacity: 0.1,
handleIconFill: 'rgb(84, 204, 152)',
handleIconFillOpacity: 0.15,
handleIconStrokeOpacity: 0,
sparklineType: 'line',
sparkline: true
},
sparkline: true
}
return data;
};
const data = generateData();
const labelFormatter = (value: any) => {
return `${value}%`;
};
const generateData = () => {
const list: { value: number; time: string; type: string }[] = [];
_.each(typeList, (type: any) => {
const dataList = _.map(_.get(data, type, []), (item: any) => {
return {
value: _.round(item.value, 2) || 0,
time: dayjs(item.timestamp * 1000).format('HH:mm:ss'),
type: _.get(TypeKeyMap, type, '')
};
});
list.push(...dataList);
});
setResult(list);
};
useEffect(() => {
generateData();
}, [data]);
return (
<>
<LineChart data={data} />
<LineChart
data={result}
labelFormatter={labelFormatter}
slider={sliderConfig}
/>
</>
);
};

@ -4,7 +4,9 @@ import PageTools from '@/components/page-tools';
import breakpoints from '@/config/breakpoints';
import useWindowResize from '@/hooks/use-window-resize';
import { Col, DatePicker, Row } from 'antd';
import { useEffect, useState } from 'react';
import _ from 'lodash';
import { useContext, useEffect, useState } from 'react';
import { DashboardContext } from '../config/dashboard-context';
import ResourceUtilization from './resource-utilization';
const SystemLoad = () => {
@ -13,7 +15,7 @@ const SystemLoad = () => {
'linear-gradient(90deg, rgba(255, 214, 102,.8) 0%, rgba(255, 214, 102,0.5) 50%, rgba(255, 214, 102,.8) 100%)',
'linear-gradient(90deg, rgba(255, 120, 117,.8) 0%, rgba(255, 120, 117,0.5) 50%, rgba(255, 120, 117,.8) 100%)'
];
const data = useContext(DashboardContext)?.system_load?.current || {};
const { size } = useWindowResize();
const [paddingRight, setPaddingRight] = useState<string>('20px');
const [smallChartHeight, setSmallChartHeight] = useState<number>(190);
@ -64,7 +66,7 @@ const SystemLoad = () => {
<Col span={12} style={{ height: smallChartHeight }}>
<LiquidChart
title="GPU Compute Utilization"
percent={0.2}
percent={_.round(data.gpu?.utilization_rate || 0, 2) / 100}
thresholds={thresholds}
rangColor={colors}
></LiquidChart>
@ -72,7 +74,9 @@ const SystemLoad = () => {
<Col span={12} style={{ height: smallChartHeight }}>
<LiquidChart
title="GPU Memory Utilization"
percent={0.3}
percent={
_.round(data.gpu_memory?.utilization_rate || 0, 2) / 100
}
thresholds={thresholds}
rangColor={colors}
></LiquidChart>
@ -80,7 +84,7 @@ const SystemLoad = () => {
<Col span={12} style={{ height: smallChartHeight }}>
<LiquidChart
title="CPU Compute Utilization"
percent={0.8}
percent={_.round(data.cpu?.utilization_rate || 0, 2) / 100}
thresholds={thresholds}
rangColor={colors}
></LiquidChart>
@ -88,7 +92,9 @@ const SystemLoad = () => {
<Col span={12} style={{ height: smallChartHeight }}>
<LiquidChart
title="CPU Memory Utilization"
percent={0.7}
percent={
_.round(data.memory?.utilization_rate || 0, 2) / 100
}
thresholds={thresholds}
rangColor={colors}
></LiquidChart>

@ -6,7 +6,10 @@ import breakpoints from '@/config/breakpoints';
import useWindowResize from '@/hooks/use-window-resize';
import { generateRandomArray } from '@/utils';
import { Col, DatePicker, Row } from 'antd';
import { useEffect, useState } from 'react';
import dayjs from 'dayjs';
import _ from 'lodash';
import { useContext, useEffect, useState } from 'react';
import { DashboardContext } from '../config/dashboard-context';
const { RangePicker } = DatePicker;
const times = [
@ -92,9 +95,82 @@ const tokenUsage = TokensData.map((val, i) => {
const Usage = () => {
const { size } = useWindowResize();
const [paddingRight, setPaddingRight] = useState<string>('20px');
const [requestData, setRequestData] = useState<
{ time: string; value: number }[]
>([]);
const [tokenData, setTokenData] = useState<{ time: string; value: number }[]>(
[]
);
const [userData, setUserData] = useState<{ name: string; value: number }[]>(
[]
);
const data = useContext(DashboardContext)?.model_usage || {};
const handleSelectDate = (dateString: string) => {};
const generateData = () => {
const requestList: { time: string; value: number }[] = [];
const tokenList: {
time: string;
value: number;
name: string;
color: string;
}[] = [];
const userList: {
name: string;
value: number;
type: string;
color: string;
}[] = [];
_.each(data.api_request_history, (item: any) => {
requestList.push({
time: dayjs(item.timestamp * 1000).format('YYYY-MM-DD'),
value: item.value
});
});
_.each(data.completion_token_history, (item: any) => {
tokenList.push({
time: dayjs(item.timestamp * 1000).format('YYYY-MM-DD'),
name: 'completion_token',
color:
'linear-gradient(90deg,rgba(84, 204, 152,0.8) 0%,rgb(0, 168, 143,.7) 100%)',
value: item.value
});
});
_.each(data.prompt_token_history, (item: any) => {
tokenList.push({
time: dayjs(item.timestamp * 1000).format('YYYY-MM-DD'),
name: 'prompt_token',
color:
'linear-gradient(90deg,rgba(0, 170, 173, 0.8) 0%,rgba(0, 109, 193, 0.7) 100%)',
value: item.value
});
});
_.each(data.top_users, (item: any) => {
userList.push({
name: item.username,
type: 'completion_token',
color:
'linear-gradient(90deg,rgba(84, 204, 152,0.8) 0%,rgb(0, 168, 143,.7) 100%)',
value: item.completion_token_count
});
userList.push({
name: item.username,
type: 'prompt_token',
color:
'linear-gradient(90deg,rgba(0, 170, 173, 0.8) 0%,rgba(0, 109, 193, 0.7) 100%)',
value: item.prompt_token_count
});
});
setRequestData(requestList);
setTokenData(tokenList);
setUserData(userList);
};
useEffect(() => {
if (size.width < breakpoints.xl) {
setPaddingRight('0');
@ -103,6 +179,10 @@ const Usage = () => {
}
}, [size.width]);
useEffect(() => {
generateData();
}, [data]);
return (
<>
<PageTools
@ -126,7 +206,7 @@ const Usage = () => {
<Col span={12}>
<ColumnBar
title="API Request"
data={dataList}
data={requestData}
xField="time"
yField="value"
height={360}
@ -135,8 +215,12 @@ const Usage = () => {
<Col span={12}>
<ColumnBar
title="Tokens"
data={tokenUsage}
data={tokenData}
group={false}
colorField="name"
stack={true}
xField="time"
legend={false}
yField="value"
height={360}
></ColumnBar>
@ -148,8 +232,11 @@ const Usage = () => {
<CardWrapper>
<HBar
title="Top Users"
data={userDataList}
xField="time"
data={userData}
colorField="type"
stack={true}
legend={false}
xField="name"
yField="value"
height={360}
></HBar>

@ -0,0 +1,8 @@
import { createContext } from 'react';
import { DashboardProps } from './types';
export const DashboardContext = createContext<DashboardProps>(
{} as DashboardProps
);
export default DashboardContext;

@ -1,12 +1,12 @@
export const overviewConfigs = [
{
key: 'workers',
key: 'workworker_count',
label: 'Workers',
// backgroundColor: 'linear-gradient(135deg, #ffffff, rgb(232 249 240 / 60%))'
backgroundColor: 'var(--color-white-1)'
},
{
key: 'gpus',
key: 'gpu_count',
label: 'Total GPUs',
backgroundColor: 'var(--color-white-1)'
// backgroundColor: 'linear-gradient(135deg, #ffffff, rgb(232 249 240 / 60%))'
@ -18,13 +18,13 @@ export const overviewConfigs = [
// backgroundColor: 'linear-gradient(135deg, #ffffff, rgb(232 249 240 / 60%))'
},
{
key: 'models',
key: 'model_count',
label: 'Models',
backgroundColor: 'var(--color-white-1)'
// backgroundColor: 'linear-gradient(135deg, #ffffff, rgb(232 249 240 / 60%))'
},
{
key: 'instances',
key: 'model_instance_count',
label: 'Instances',
backgroundColor: 'var(--color-white-1)'
// backgroundColor: 'linear-gradient(135deg, #ffffff, rgb(232 249 240 / 60%))'

@ -0,0 +1,62 @@
export interface DashboardProps {
resource_counts: {
worker_count: number;
gpu_count: number;
model_count: number;
model_instance_count: number;
};
system_load: {
current: {
cpu: {
total: number;
used: number;
utilization_rate: number;
};
memory: {
total: number;
used: number;
utilization_rate: number;
};
gpu: {
total: number;
used: number;
utilization_rate: number;
};
gpu_memory: {
total: number;
used: number;
utilization_rate: number;
};
};
history: {
cpu: {
timestamp: number;
value: number;
}[];
memory: {
timestamp: number;
value: number;
}[];
gpu: {
timestamp: number;
value: number;
}[];
gpu_memory: {
timestamp: number;
value: number;
}[];
};
};
model_usage: {
api_request_history: any[];
completion_token_history: any[];
prompt_token_history: any[];
top_users: {
user_id: number;
username: string;
prompt_token_count: number;
completion_token_count: number;
}[];
};
active_models: any[];
}

@ -1,16 +1,40 @@
import { Spin } from 'antd';
import { useEffect, useState } from 'react';
import { queryDashboardData } from './apis';
import ActiveTable from './components/active-table';
import Overview from './components/over-view';
import SystemLoad from './components/system-load';
import Usage from './components/usage';
import DashboardContext from './config/dashboard-context';
import { DashboardProps } from './config/types';
const Dashboard: React.FC = () => {
const [data, setData] = useState<DashboardProps>({} as DashboardProps);
const [loading, setLoading] = useState(false);
const getDashboardData = async () => {
try {
setLoading(true);
const res = await queryDashboardData();
setData(res);
setLoading(false);
} catch (error) {
setLoading(false);
setData({} as DashboardProps);
}
};
useEffect(() => {
getDashboardData();
}, []);
return (
<>
<Overview></Overview>
<SystemLoad></SystemLoad>
<Usage></Usage>
<ActiveTable></ActiveTable>
</>
<Spin spinning={loading}>
<DashboardContext.Provider value={{ ...data }}>
<Overview></Overview>
<SystemLoad></SystemLoad>
<Usage></Usage>
<ActiveTable></ActiveTable>
</DashboardContext.Provider>
</Spin>
);
};

@ -26,7 +26,7 @@ type AddModalProps = {
const sourceOptions = [
{ label: 'Huggingface', value: 'huggingface', key: 'huggingface' },
{ label: 'Ollama', value: 'ollama_library', key: 'ollama_library' }
{ label: 'Ollama Library', value: 'ollama_library', key: 'ollama_library' }
];
const AddModal: React.FC<AddModalProps> = (props) => {

@ -114,7 +114,7 @@ const Models: React.FC = () => {
clearInterval(timer.current);
timer.current = setInterval(() => {
fetchData(true);
}, 3000);
}, 5000);
};
const handleShowSizeChange = (page: number, size: number) => {

@ -18,11 +18,20 @@ interface ChatFooterProps {
onView: () => void;
disabled?: boolean;
feedback?: React.ReactNode;
hasTokenResult?: boolean;
}
const ChatFooter: React.FC<ChatFooterProps> = (props) => {
const intl = useIntl();
const { onSubmit, onClear, onNewMessage, onView, feedback, disabled } = props;
const {
onSubmit,
onClear,
onNewMessage,
onView,
feedback,
disabled,
hasTokenResult
} = props;
useHotkeys(
HotKeys.SUBMIT.join(','),
() => {
@ -34,7 +43,7 @@ const ChatFooter: React.FC<ChatFooterProps> = (props) => {
return (
<div className="chat-footer">
<Row style={{ width: '100%' }}>
<Col span={8}>
<Col span={hasTokenResult ? 8 : 12}>
<Space size={20}>
<Button
disabled={disabled}
@ -53,8 +62,8 @@ const ChatFooter: React.FC<ChatFooterProps> = (props) => {
</Button>
</Space>
</Col>
<Col span={8}>{feedback}</Col>
<Col span={8} style={{ textAlign: 'right' }}>
<Col span={hasTokenResult ? 8 : 0}>{feedback}</Col>
<Col span={hasTokenResult ? 8 : 12} style={{ textAlign: 'right' }}>
<Space size={20}>
<Button
icon={<CodeOutlined></CodeOutlined>}

@ -235,6 +235,7 @@ const MessageList: React.FC<MessageProps> = (props) => {
onSubmit={handleSubmit}
onView={handleView}
disabled={loading}
hasTokenResult={!!tokenResult}
feedback={<ReferenceParams usage={tokenResult}></ReferenceParams>}
></ChatFooter>
</div>

@ -6,6 +6,10 @@
.message-list-wrap {
max-height: calc(100vh - 152px);
overflow-y: auto;
.ant-pro-page-container-children-container {
padding-inline: 26px;
}
}
.ground-left-footer {

@ -201,14 +201,7 @@ const Models: React.FC = () => {
);
}}
/>
<Column
title={intl.formatMessage({ id: 'resources.table.disk' })}
dataIndex="storage"
key="storage"
render={(text, record: ListItem) => {
return <ProgressBar percent={0}></ProgressBar>;
}}
/>
<Column
title={intl.formatMessage({ id: 'resources.table.vram' })}
dataIndex="VRAM"
@ -240,6 +233,14 @@ const Models: React.FC = () => {
);
}}
/>
<Column
title={intl.formatMessage({ id: 'resources.table.disk' })}
dataIndex="storage"
key="storage"
render={(text, record: ListItem) => {
return <ProgressBar percent={0}></ProgressBar>;
}}
/>
</Table>
</>
);

Loading…
Cancel
Save