Skip to content

Commit

Permalink
Merge pull request #2844 from jeff-phillips-18/kserve-metrics
Browse files Browse the repository at this point in the history
[RHOAIENG-6522] Create model metrics kserve page
  • Loading branch information
openshift-merge-bot[bot] authored Jun 7, 2024
2 parents 34187a9 + bc32c9d commit 10d0ccd
Show file tree
Hide file tree
Showing 9 changed files with 175 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,7 @@ describe('Model Serving Global', () => {
modelServingGlobal.getModelRow('Test Inference Service').should('have.length', 1);
modelServingGlobal.getModelMetricLink('Test Inference Service').should('be.visible');
modelServingGlobal.getModelMetricLink('Test Inference Service').click();
cy.findByTestId('kserve-metrics-page').should('be.visible');
cy.findByTestId('app-page-title').should('have.text', 'Test Inference Service metrics');
});

describe('Table filter and pagination', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ describe('Project Details', () => {
projectDetails.visitSection('test-project', 'model-server');
projectDetails.getKserveModelMetricLink('Test Inference Service').should('be.visible');
projectDetails.getKserveModelMetricLink('Test Inference Service').click();
cy.findByTestId('kserve-metrics-page').should('be.visible');
cy.findByTestId('app-page-title').should('have.text', 'Test Inference Service metrics');
});
it('Multi model serving platform is enabled', () => {
initIntercepts({ templates: true, disableKServeConfig: true, disableModelConfig: false });
Expand Down
37 changes: 37 additions & 0 deletions frontend/src/pages/UnknownError.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React from 'react';
import {
EmptyState,
EmptyStateVariant,
EmptyStateIcon,
EmptyStateBody,
PageSection,
PageSectionVariants,
EmptyStateHeader,
} from '@patternfly/react-core';
import { ErrorCircleOIcon } from '@patternfly/react-icons';

type UnauthorizedErrorProps = {
variant?: PageSectionVariants;
titleText: string;
error: Error;
testId?: string;
};
const UnknownError: React.FC<UnauthorizedErrorProps> = ({
variant = PageSectionVariants.default,
titleText,
error,
testId,
}) => (
<PageSection isFilled variant={variant} data-testid={testId}>
<EmptyState variant={EmptyStateVariant.lg}>
<EmptyStateHeader
titleText={titleText}
icon={<EmptyStateIcon icon={ErrorCircleOIcon} />}
headingLevel="h5"
/>
<EmptyStateBody>{error.message}</EmptyStateBody>
</EmptyState>
</PageSection>
);

export default UnknownError;
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react';
import { Bullseye, PageSectionVariants, Spinner } from '@patternfly/react-core';
import { AxiosError } from 'axios';
import UnauthorizedError from '~/pages/UnauthorizedError';
import UnknownError from '~/pages/UnknownError';
import {
ModelMetricType,
ModelServingMetricsContext,
Expand Down Expand Up @@ -37,8 +38,18 @@ const EnsureMetricsAvailable: React.FC<EnsureMetricsAvailableProps> = ({

// Check for errors first as `loaded` prop will always be false when there is an error. If you check
// for loaded first, you'll get an infinite spinner.
if (error?.response?.status === 403) {
return <UnauthorizedError variant={PageSectionVariants.light} accessDomain={accessDomain} />;
if (error) {
if (error.response?.status === 403) {
return <UnauthorizedError variant={PageSectionVariants.light} accessDomain={accessDomain} />;
}
return (
<UnknownError
titleText="Error retrieving metrics"
error={error}
variant={PageSectionVariants.light}
data-testid="metrics-error"
/>
);
}

if (readyCount !== metrics.length) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import * as React from 'react';
import { Stack, StackItem } from '@patternfly/react-core';
import MetricsPlaceHolder from '~/pages/modelServing/screens/metrics/performance/MetricsPlaceHolder';

const KserveMetrics: React.FC = () => (
<Stack hasGutter>
<StackItem>
<MetricsPlaceHolder title="HTTP requests per 5 minutes" />
</StackItem>
<StackItem>
<MetricsPlaceHolder title="Average response time (ms)" />
</StackItem>
<StackItem>
<MetricsPlaceHolder title="CPU utilization %" />
</StackItem>
<StackItem>
<MetricsPlaceHolder title="Memory utilization %" />
</StackItem>
</Stack>
);

export default KserveMetrics;
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import * as React from 'react';
import {
Card,
CardBody,
CardHeader,
CardTitle,
EmptyState,
EmptyStateIcon,
Title,
} from '@patternfly/react-core';
import { CubesIcon } from '@patternfly/react-icons';

type MetricsPlaceHolderProps = {
title: string;
};
const MetricsPlaceHolder: React.FC<MetricsPlaceHolderProps> = ({ title }) => (
<Card data-testid={`metrics-card-${title}`}>
<CardHeader>
<CardTitle>{title}</CardTitle>
</CardHeader>
<CardBody style={{ height: 200, padding: 0 }}>
<EmptyState>
<EmptyStateIcon icon={CubesIcon} />
<Title headingLevel="h4" size="lg" data-testid="metrics-chart-place-holder">
Metrics coming soon
</Title>
</EmptyState>
</CardBody>
</Card>
);

export default MetricsPlaceHolder;
Original file line number Diff line number Diff line change
@@ -1,40 +1,14 @@
import * as React from 'react';
import { Stack, StackItem } from '@patternfly/react-core';
import MetricsChart from '~/pages/modelServing/screens/metrics/MetricsChart';
import {
ModelMetricType,
ModelServingMetricsContext,
} from '~/pages/modelServing/screens/metrics/ModelServingMetricsContext';
import { ContextResourceData, PrometheusQueryRangeResultValue } from '~/types';
import { InferenceServiceKind } from '~/k8sTypes';
import { isModelMesh } from '~/pages/modelServing/utils';
import ModelMeshMetrics from '~/pages/modelServing/screens/metrics/performance/ModelMeshMetrics';
import KserveMetrics from '~/pages/modelServing/screens/metrics/performance/KserveMetrics';

const ModelGraphs: React.FC = () => {
const { data } = React.useContext(ModelServingMetricsContext);

return (
<Stack hasGutter>
<StackItem>
<MetricsChart
metrics={[
{
name: 'Successful',
metric: data[
ModelMetricType.REQUEST_COUNT_SUCCESS
] as ContextResourceData<PrometheusQueryRangeResultValue>,
},
{
name: 'Failed',
metric: data[
ModelMetricType.REQUEST_COUNT_FAILED
] as ContextResourceData<PrometheusQueryRangeResultValue>,
},
]}
color="blue"
title="HTTP requests per 5 minutes"
isStack
/>
</StackItem>
</Stack>
);
type ModelGraphProps = {
model: InferenceServiceKind;
};

const ModelGraphs: React.FC<ModelGraphProps> = ({ model }) =>
isModelMesh(model) ? <ModelMeshMetrics /> : <KserveMetrics />;

export default ModelGraphs;
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import * as React from 'react';
import { Stack, StackItem } from '@patternfly/react-core';
import MetricsChart from '~/pages/modelServing/screens/metrics/MetricsChart';
import {
ModelMetricType,
ModelServingMetricsContext,
} from '~/pages/modelServing/screens/metrics/ModelServingMetricsContext';
import { ContextResourceData, PrometheusQueryRangeResultValue } from '~/types';
import EnsureMetricsAvailable from '~/pages/modelServing/screens/metrics/EnsureMetricsAvailable';

const ModelMeshMetrics: React.FC = () => {
const { data } = React.useContext(ModelServingMetricsContext);

return (
<Stack hasGutter>
<EnsureMetricsAvailable
metrics={[ModelMetricType.REQUEST_COUNT_SUCCESS, ModelMetricType.REQUEST_COUNT_FAILED]}
accessDomain="model metrics"
>
<StackItem>
<MetricsChart
metrics={[
{
name: 'Successful',
metric: data[
ModelMetricType.REQUEST_COUNT_SUCCESS
] as ContextResourceData<PrometheusQueryRangeResultValue>,
},
{
name: 'Failed',
metric: data[
ModelMetricType.REQUEST_COUNT_FAILED
] as ContextResourceData<PrometheusQueryRangeResultValue>,
},
]}
color="blue"
title="HTTP requests per 5 minutes"
isStack
/>
</StackItem>
</EnsureMetricsAvailable>
</Stack>
);
};

export default ModelMeshMetrics;
Original file line number Diff line number Diff line change
Expand Up @@ -7,50 +7,43 @@ import {
Stack,
StackItem,
} from '@patternfly/react-core';
import { PendingIcon } from '@patternfly/react-icons';
import { WarningTriangleIcon } from '@patternfly/react-icons';
import { InferenceServiceKind } from '~/k8sTypes';
import { SupportedArea, useIsAreaAvailable } from '~/concepts/areas';
import MetricsPageToolbar from '~/concepts/metrics/MetricsPageToolbar';
import { isModelMesh } from '~/pages/modelServing/utils';
import ModelGraphs from '~/pages/modelServing/screens/metrics/performance/ModelGraphs';
import { ModelMetricType } from '~/pages/modelServing/screens/metrics/ModelServingMetricsContext';
import EnsureMetricsAvailable from '~/pages/modelServing/screens/metrics/EnsureMetricsAvailable';

type PerformanceTabsProps = {
model: InferenceServiceKind;
};

const PerformanceTab: React.FC<PerformanceTabsProps> = ({ model }) => {
const kserve = !isModelMesh(model);
const modelMesh = isModelMesh(model);
const kserveMetricsEnabled = useIsAreaAvailable(SupportedArea.K_SERVE_METRICS).status;

if (kserve && kserveMetricsEnabled) {
if (!modelMesh && !kserveMetricsEnabled) {
return (
<EmptyState variant="full" data-testid="kserve-metrics-page">
<EmptyState variant="full">
<EmptyStateHeader
titleText="Single-model serving platform model metrics coming soon."
titleText="Single-model serving platform model metrics are not enabled."
headingLevel="h4"
icon={<EmptyStateIcon icon={PendingIcon} />}
icon={<EmptyStateIcon icon={WarningTriangleIcon} />}
alt=""
/>
</EmptyState>
);
}

return (
<EnsureMetricsAvailable
metrics={[ModelMetricType.REQUEST_COUNT_SUCCESS, ModelMetricType.REQUEST_COUNT_FAILED]}
accessDomain="model metrics"
>
<Stack data-testid="performance-metrics-loaded">
<StackItem>
<MetricsPageToolbar />
</StackItem>
<PageSection isFilled>
<ModelGraphs />
</PageSection>
</Stack>
</EnsureMetricsAvailable>
<Stack data-testid="performance-metrics-loaded">
<StackItem>
<MetricsPageToolbar />
</StackItem>
<PageSection isFilled>
<ModelGraphs model={model} />
</PageSection>
</Stack>
);
};

Expand Down

0 comments on commit 10d0ccd

Please sign in to comment.