Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support visualizing inference metrics #2876

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"theme-schema:update": "pnpm dlx typescript-json-schema \"./react/node_modules/antd/es/config-provider/context.d.ts\" ThemeConfig -o ./resources/antdThemeConfig.schema.json --esModuleInterop"
},
"dependencies": {
"@ant-design/charts": "^2.2.3",
"@iarna/toml": "^2.2.5",
"@lit/reactive-element": "^2.0.4",
"@material/mwc-button": "^0.27.0",
Expand Down
1,037 changes: 1,010 additions & 27 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions react/data/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,7 @@ type DomainNode implements Node {
scaling_groups(filter: String, order: String, offset: Int, before: String, after: String, first: Int, last: Int): ScalinGroupConnection
}

"""Added in 24.12.0."""
"""Added in 24.09.1."""
scalar Bytes

"""Added in 24.12.0."""
Expand Down Expand Up @@ -1373,6 +1373,7 @@ type Endpoint implements Item {
status: String
lifecycle_stage: String
errors: [InferenceSessionError!]!
live_stat: JSONString
}

"""Added in 24.03.5."""
Expand All @@ -1390,6 +1391,7 @@ type Routing implements Item {
traffic_ratio: Float
created_at: DateTime
error_data: JSONString
live_stat: JSONString
}

type InferenceSessionError {
Expand Down Expand Up @@ -2592,4 +2594,4 @@ type CheckAndTransitStatus {
input CheckAndTransitStatusInput {
ids: [GlobalIDField]!
client_mutation_id: String
}
}
186 changes: 186 additions & 0 deletions react/src/components/EndpointMetricsModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import BAIModal, { BAIModalProps } from './BAIModal';
import { EndpointMetricsModalQuery } from './__generated__/EndpointMetricsModalQuery.graphql';
import { Bar, Line } from '@ant-design/charts';

Check warning on line 3 in react/src/components/EndpointMetricsModal.tsx

View workflow job for this annotation

GitHub Actions / coverage

'Bar' is defined but never used
import { DownOutlined } from '@ant-design/icons';
import { Col, Dropdown, Row, Space } from 'antd';

Check warning on line 5 in react/src/components/EndpointMetricsModal.tsx

View workflow job for this annotation

GitHub Actions / coverage

'Col' is defined but never used

Check warning on line 5 in react/src/components/EndpointMetricsModal.tsx

View workflow job for this annotation

GitHub Actions / coverage

'Row' is defined but never used
import graphql from 'babel-plugin-relay/macro';
import _ from 'lodash';
import React, { useEffect, useMemo, useRef, useState } from 'react';

Check warning on line 8 in react/src/components/EndpointMetricsModal.tsx

View workflow job for this annotation

GitHub Actions / coverage

'useRef' is defined but never used
import { useTranslation } from 'react-i18next';
import {
fetchQuery,
useFragment,

Check warning on line 12 in react/src/components/EndpointMetricsModal.tsx

View workflow job for this annotation

GitHub Actions / coverage

'useFragment' is defined but never used
useLazyLoadQuery,

Check warning on line 13 in react/src/components/EndpointMetricsModal.tsx

View workflow job for this annotation

GitHub Actions / coverage

'useLazyLoadQuery' is defined but never used
useMutation,

Check warning on line 14 in react/src/components/EndpointMetricsModal.tsx

View workflow job for this annotation

GitHub Actions / coverage

'useMutation' is defined but never used
useRelayEnvironment,
} from 'react-relay';

interface EndpointMetricsModalProps extends BAIModalProps {
endpoint_id: string;
}

type LineChartData = {

Check warning on line 22 in react/src/components/EndpointMetricsModal.tsx

View workflow job for this annotation

GitHub Actions / coverage

'LineChartData' is defined but never used
label: string;
data: number;
};

const EndpointMetrics: React.FC<{ endpoint_id: string }> = ({
endpoint_id,
}: {
endpoint_id: string;
}) => {
const relayEvn = useRelayEnvironment();

const [dataSeries, setDataSeries] =
useState<{ liveStat: any; lastUpdatedAt: Date }[]>();
const [selectedMetric, setSelectedMetric] = useState<string>();
const [chartType, setChartType] = useState<string>();

const updateStatInfo = async () => {
const response = await fetchQuery<EndpointMetricsModalQuery>(
relayEvn,
graphql`
query EndpointMetricsModalQuery($endpointId: UUID!) {
endpoint(endpoint_id: $endpointId) {
name
live_stat @since(version: "24.03.11")
}
}
`,
{ endpointId: endpoint_id },
).toPromise();
const liveStatJSON = response?.endpoint?.live_stat;
if (liveStatJSON) {
setDataSeries((prev) =>
[
...(prev || []),
{ liveStat: JSON.parse(liveStatJSON), lastUpdatedAt: new Date() },
].slice(-30),
);
}
};

useEffect(() => {
const itv = setInterval(updateStatInfo, 1500);
updateStatInfo();

return () => clearInterval(itv);
}, []);

Check warning on line 68 in react/src/components/EndpointMetricsModal.tsx

View workflow job for this annotation

GitHub Actions / coverage

React Hook useEffect has a missing dependency: 'updateStatInfo'. Either include it or remove the dependency array

const chartData = useMemo(() => {
if (!dataSeries || !selectedMetric) return;

const dataPoints = dataSeries.map((data) => data.liveStat[selectedMetric]);
if (dataPoints.length === 0) return;
const dataType = dataPoints[0].__type;

setChartType(dataType);
switch (dataType) {
case 'HISTOGRAM':
const cdf = dataPoints[dataPoints.length - 1].current;
const max = parseFloat(cdf['+Inf']);
return Array.from(Object.keys(cdf)).map((label) => ({
label,
data: parseFloat(cdf[label]) / max,
}));
case 'GAUGE':
case 'ACCUMULATION':
return dataPoints.map((point, index) => {
const label = `${_.padStart(dataSeries[index].lastUpdatedAt.getHours().toString(), 2, '0')}:${_.padStart(dataSeries[index].lastUpdatedAt.getMinutes().toString(), 2, '0')}:${_.padStart(dataSeries[index].lastUpdatedAt.getSeconds().toString(), 2, '0')}`;
return { label, data: parseFloat(point.current) };
});
default:
return;
}
}, [dataSeries, selectedMetric]);

const chartStyles = useMemo(() => {
if (!chartType) return;

switch (chartType) {
case 'HISTOGRAM':
return {
point: {
sizeField: 4,
},
style: {
lineWidth: 2,
stroke: 'red',
},
animation: false,
animate: false,
autoFit: true,
};
case 'GAUGE':
case 'ACCUMULATION':
return {
style: {
lineWidth: 2,
stroke: 'blue',
},
animation: false,
animate: false,
autoFit: true,
};
}
}, [chartType]);

return (
<div>
<Dropdown
menu={{
items: dataSeries
? Array.from(Object.keys(dataSeries[0].liveStat)).map((key) => ({
key,
label: `${key} (${dataSeries[0].liveStat[key].__type})`,
}))
: [],
onClick: (info) => setSelectedMetric(info.key),
}}
disabled={dataSeries?.length === 0}
>
<a onClick={(e) => e.preventDefault()}>

Check warning on line 142 in react/src/components/EndpointMetricsModal.tsx

View workflow job for this annotation

GitHub Actions / coverage

The href attribute is required for an anchor to be keyboard accessible. Provide a valid, navigable address as the href value. If you cannot provide an href, but still need the element to resemble a link, use a button and change it with appropriate styles. Learn more: https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/HEAD/docs/rules/anchor-is-valid.md
<Space>
{selectedMetric
? `${selectedMetric} (${chartType})`
: 'Select Metric'}
<DownOutlined />
</Space>
</a>
</Dropdown>

<div style={{ height: '80%' }}>
{selectedMetric && chartData ? (
<Line
data={chartData}
xField="label"
yField="data"
title={selectedMetric}
{...(chartStyles || {})}
/>
) : null}
</div>
</div>
);
};

const EndpointMetricsModal: React.FC<EndpointMetricsModalProps> = ({
endpoint_id,
...modalProps
}) => {
const { t } = useTranslation();

return (
<BAIModal
{...modalProps}
footer={null}
title={t('button.ViewMetrics')}
destroyOnClose
width="90%"
>
<EndpointMetrics endpoint_id={endpoint_id} />
</BAIModal>
);
};

export default EndpointMetricsModal;
22 changes: 22 additions & 0 deletions react/src/pages/EndpointDetailPage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import CopyableCodeText from '../components/CopyableCodeText';
import EndpointMetricsModal from '../components/EndpointMetricsModal';
import EndpointOwnerInfo from '../components/EndpointOwnerInfo';
import EndpointStatusTag from '../components/EndpointStatusTag';
import EndpointTokenGenerationModal from '../components/EndpointTokenGenerationModal';
Expand Down Expand Up @@ -29,6 +30,7 @@ import {
CheckOutlined,
CloseOutlined,
FolderOutlined,
LineChartOutlined,
LoadingOutlined,
PlusOutlined,
QuestionCircleOutlined,
Expand Down Expand Up @@ -102,6 +104,8 @@ const EndpointDetailPage: React.FC<EndpointDetailPageProps> = () => {
const [isPendingClearError, startClearErrorTransition] = useTransition();
const [selectedSessionErrorForModal, setSelectedSessionErrorForModal] =
useState<InferenceSessionErrorModalFragment$key | null>(null);
const [openEndpointMetricsModal, setOpenEndpointMetricsModal] =
useState(false);
const [isOpenTokenGenerationModal, setIsOpenTokenGenerationModal] =
useState(false);
const [openChatModal, setOpenChatModal] = useState(false);
Expand Down Expand Up @@ -182,6 +186,7 @@ const EndpointDetailPage: React.FC<EndpointDetailPageProps> = () => {
status
}
created_user_email @since(version: "23.09.8")
live_stat @since(version: "23.09.11")
...EndpointOwnerInfoFragment
...EndpointStatusTagFragment
...ChatUIModalFragment
Expand Down Expand Up @@ -476,6 +481,16 @@ const EndpointDetailPage: React.FC<EndpointDetailPageProps> = () => {
) : (
<></>
)}
<Button
loading={isPendingRefetch}
disabled={!endpoint?.live_stat}
icon={<LineChartOutlined />}
onClick={() => {
setOpenEndpointMetricsModal(true);
}}
>
{t('button.ViewMetrics')}
</Button>
<Button
loading={isPendingRefetch}
icon={<ReloadOutlined />}
Expand Down Expand Up @@ -728,6 +743,13 @@ const EndpointDetailPage: React.FC<EndpointDetailPageProps> = () => {
setOpenChatModal(false);
}}
/>
<EndpointMetricsModal
endpoint_id={endpoint?.endpoint_id || ''}
open={openEndpointMetricsModal}
onCancel={() => {
setOpenEndpointMetricsModal(false);
}}
/>
<SessionDetailDrawer
open={!selectedSessionId}
sessionId={selectedSessionId}
Expand Down
3 changes: 2 additions & 1 deletion resources/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -563,7 +563,8 @@
"Expand": "Expand",
"Clear": "Clear",
"Apply": "Apply",
"CopySomething": "Copy {{name}}"
"CopySomething": "Copy {{name}}",
"ViewMetrics": "View Metrics"
},
"agent": {
"Endpoint": "Endpoint",
Expand Down
2 changes: 1 addition & 1 deletion version.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{ "package": "24.09.0-alpha.1", "buildNumber": "6111", "buildDate": "240703.130731", "revision": "d8cea1cb" }
{ "package": "24.09.0-alpha.1", "buildNumber": "6357", "buildDate": "241119.011126", "revision": "90496bcf" }
Loading