Skip to content

Commit

Permalink
Multi-metric display on model bias screen (#1273) (#1349)
Browse files Browse the repository at this point in the history
* Enhancements to model bias screen

  * Display of multiple bias charts simultaneously

  * Multi-select component, allowing free text, or select-from-list selection of chartst to display

  * Ability to collapse / expand individual charts

  * User selectable refresh rates of chart data

  * Chart selection and open / closed status is persisted to session cache for life of user's browser session

Display user defined threshold values on charts (#1163)

  * Clean up of bias chart logic

  * Displays thresholds chosen by user, or defaults if none.

  * Improves domain and threshold calculation based on user values or defaults
  • Loading branch information
alexcreasy authored Jun 12, 2023
1 parent 4aa8675 commit 694ada2
Show file tree
Hide file tree
Showing 21 changed files with 547 additions and 261 deletions.
15 changes: 14 additions & 1 deletion frontend/src/api/prometheus/serving.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ import {
InferenceMetricType,
RuntimeMetricType,
} from '~/pages/modelServing/screens/metrics/ModelServingMetricsContext';
import { MetricType, TimeframeTitle } from '~/pages/modelServing/screens/types';
import {
MetricType,
RefreshIntervalTitle,
TimeframeTitle,
} from '~/pages/modelServing/screens/types';
import useQueryRangeResourceData, {
useQueryRangeResourceDataTrusty,
} from './useQueryRangeResourceData';
Expand All @@ -19,6 +23,7 @@ export const useModelServingMetrics = (
timeframe: TimeframeTitle,
lastUpdateTime: number,
setLastUpdateTime: (time: number) => void,
refreshInterval: RefreshIntervalTitle,
): {
data: Record<
RuntimeMetricType | InferenceMetricType,
Expand All @@ -33,55 +38,63 @@ export const useModelServingMetrics = (
queries[RuntimeMetricType.REQUEST_COUNT],
end,
timeframe,
refreshInterval,
);

const runtimeAverageResponseTime = useQueryRangeResourceData(
type === 'runtime',
queries[RuntimeMetricType.AVG_RESPONSE_TIME],
end,
timeframe,
refreshInterval,
);

const runtimeCPUUtilization = useQueryRangeResourceData(
type === 'runtime',
queries[RuntimeMetricType.CPU_UTILIZATION],
end,
timeframe,
refreshInterval,
);

const runtimeMemoryUtilization = useQueryRangeResourceData(
type === 'runtime',
queries[RuntimeMetricType.MEMORY_UTILIZATION],
end,
timeframe,
refreshInterval,
);

const inferenceRequestSuccessCount = useQueryRangeResourceData(
type === 'inference',
queries[InferenceMetricType.REQUEST_COUNT_SUCCESS],
end,
timeframe,
refreshInterval,
);

const inferenceRequestFailedCount = useQueryRangeResourceData(
type === 'inference',
queries[InferenceMetricType.REQUEST_COUNT_FAILED],
end,
timeframe,
refreshInterval,
);

const inferenceTrustyAISPD = useQueryRangeResourceDataTrusty(
type === 'inference',
queries[InferenceMetricType.TRUSTY_AI_SPD],
end,
timeframe,
refreshInterval,
);

const inferenceTrustyAIDIR = useQueryRangeResourceDataTrusty(
type === 'inference',
queries[InferenceMetricType.TRUSTY_AI_DIR],
end,
timeframe,
refreshInterval,
);

React.useEffect(() => {
Expand Down
14 changes: 10 additions & 4 deletions frontend/src/api/prometheus/useQueryRangeResourceData.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import * as React from 'react';
import { TimeframeStep, TimeframeTimeRange } from '~/pages/modelServing/screens/const';
import { TimeframeTitle } from '~/pages/modelServing/screens/types';
import {
RefreshIntervalValue,
TimeframeStep,
TimeframeTimeRange,
} from '~/pages/modelServing/screens/const';
import { RefreshIntervalTitle, TimeframeTitle } from '~/pages/modelServing/screens/types';
import {
ContextResourceData,
PrometheusQueryRangeResponseDataResult,
Expand All @@ -15,6 +19,7 @@ const useQueryRangeResourceData = (
query: string,
end: number,
timeframe: TimeframeTitle,
refreshInterval: RefreshIntervalTitle,
): ContextResourceData<PrometheusQueryRangeResultValue> => {
const responsePredicate = React.useCallback<ResponsePredicate>(
(data) => data.result?.[0]?.values || [],
Expand All @@ -30,7 +35,7 @@ const useQueryRangeResourceData = (
TimeframeStep[timeframe],
responsePredicate,
),
5 * 60 * 1000,
RefreshIntervalValue[refreshInterval],
);
};

Expand All @@ -42,6 +47,7 @@ export const useQueryRangeResourceDataTrusty = (
query: string,
end: number,
timeframe: TimeframeTitle,
refreshInterval: RefreshIntervalTitle,
): ContextResourceData<TrustyData> => {
const responsePredicate = React.useCallback<ResponsePredicate<TrustyData>>(
(data) => data.result,
Expand All @@ -57,7 +63,7 @@ export const useQueryRangeResourceDataTrusty = (
TimeframeStep[timeframe],
responsePredicate,
),
5 * 60 * 1000,
RefreshIntervalValue[refreshInterval],
);
};
export default useQueryRangeResourceData;
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.dashboard-expandable-section-heading > .pf-c-expandable-section__toggle > * {
font-family: var(--pf-global--FontFamily--sans-serif) !important;
font-size: var(--pf-global--FontSize--2xl) !important;
color: var(--pf-global--icon--Color--light) !important;
}

.dashboard-expandable-section-heading > .pf-c-expandable-section__toggle:hover > * {
color: var(--pf-global--icon--Color--dark) !important;
}
32 changes: 32 additions & 0 deletions frontend/src/concepts/dashboard/DashboardExpandableSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from 'react';
import { ExpandableSection } from '@patternfly/react-core';
import { useBrowserStorage } from '~/components/browserStorage';

import './DashboardExpandableSection.scss';

type DashboardExpandableSectionProps = {
children: React.ReactNode;
title: string;
storageKey: string;
};

const DashboardExpandableSection: React.FC<DashboardExpandableSectionProps> = ({
children,
title,
storageKey,
}) => {
const [isExpanded, setIsExpanded] = useBrowserStorage(storageKey, true, true, true);

return (
<ExpandableSection
className="dashboard-expandable-section-heading"
toggleText={title}
onToggle={setIsExpanded}
isExpanded={isExpanded}
>
{children}
</ExpandableSection>
);
};

export default DashboardExpandableSection;
14 changes: 13 additions & 1 deletion frontend/src/pages/modelServing/screens/const.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { ServingRuntimeSize, TimeframeStepType, TimeframeTimeType, TimeframeTitle } from './types';
import {
RefreshIntervalTitle,
RefreshIntervalValueType,
ServingRuntimeSize,
TimeframeStepType,
TimeframeTimeType,
TimeframeTitle,
} from './types';

export const DEFAULT_MODEL_SERVER_SIZES: ServingRuntimeSize[] = [
{
Expand Down Expand Up @@ -84,3 +91,8 @@ export const TimeframeStep: TimeframeStepType = {
[TimeframeTitle.ONE_MONTH]: 30 * 7 * 24 * 12,
// [TimeframeTitle.UNLIMITED]: 30 * 7 * 24 * 12, // TODO: determine if we "zoom out" more
};

export const RefreshIntervalValue: RefreshIntervalValueType = {
[RefreshIntervalTitle.ONE_MINUTE]: 60 * 1000,
[RefreshIntervalTitle.FIVE_MINUTES]: 5 * 60 * 1000,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import React from 'react';
import { Select, SelectGroup, SelectOption, SelectVariant } from '@patternfly/react-core';
import { useExplainabilityModelData } from '~/concepts/explainability/useExplainabilityModelData';
import { BiasMetricConfig } from '~/concepts/explainability/types';
import { MetricTypes } from '~/api';
import {
byId,
byNotId,
createBiasSelectOption,
isBiasSelectOption,
} from '~/pages/modelServing/screens/metrics/utils';
import { BiasSelectOption } from '~/pages/modelServing/screens/metrics/types';

type BiasMetricConfigSelectorProps = {
onChange: (x: BiasMetricConfig[]) => void;
initialSelections: BiasMetricConfig[];
};

const BiasMetricConfigSelector: React.FC<BiasMetricConfigSelectorProps> = ({
onChange,
initialSelections,
}) => {
const { biasMetricConfigs, loaded } = useExplainabilityModelData();

const [isOpen, setIsOpen] = React.useState(false);
const [selected, setSelected] = React.useState<BiasSelectOption[]>(
initialSelections.map(createBiasSelectOption),
);

const elementId = React.useId();

const changeState = React.useCallback(
(options: BiasSelectOption[]) => {
setSelected(options);
onChange(options.map((x) => x.biasMetricConfig));
},
[onChange],
);

return (
<div style={{ maxWidth: '600px' }}>
<span id={elementId} hidden>
Select the metrics to display charts for
</span>
<Select
variant={SelectVariant.typeaheadMulti}
typeAheadAriaLabel="Select a metric"
onToggle={setIsOpen}
onSelect={(event, item) => {
if (isBiasSelectOption(item)) {
if (selected.find(byId(item))) {
// User has de-selected an item.
changeState(selected.filter(byNotId(item)));
} else {
// User has selected an item.
changeState([...selected, item]);
}
}
}}
onClear={() => {
changeState([]);
setIsOpen(false);
}}
selections={selected}
isOpen={isOpen}
aria-labelledby={elementId}
placeholderText="Select a metric"
isDisabled={!(loaded && biasMetricConfigs.length > 0)}
isGrouped
>
<SelectGroup label="SPD" key="SPD">
{biasMetricConfigs
.filter((x) => x.metricType === MetricTypes.SPD)
.map((x) => (
<SelectOption key={x.id} value={createBiasSelectOption(x)} />
))}
</SelectGroup>
<SelectGroup label="DIR" key="DIR">
{biasMetricConfigs
.filter((x) => x.metricType === MetricTypes.DIR)
.map((x) => (
<SelectOption key={x.id} value={createBiasSelectOption(x)} />
))}
</SelectGroup>
</Select>
</div>
);
};

export default BiasMetricConfigSelector;
Loading

0 comments on commit 694ada2

Please sign in to comment.