mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[ML] Add a new memory usage by job and by model view (#149419)
Adds a new Memory usage page to the ML app. This contains the page which was originally called Nodes, under the Model Management section, and introduces a new tree map chart to display memory usage of jobs and trained models.  If kibana is running in a serverless environment, the Memory usage page will only show the overall memory usage chart. There will be no reference made to "nodes". **Refactoring** Organises related server side code under a `model_management` section. Moves routes to a new `model_management` file. Adds a new `isServerless` function to the client side to spoof information we should son get from kibana to tell whether we are running in a serverless environment. --------- Co-authored-by: István Zoltán Szabó <istvan.szabo@elastic.co>
This commit is contained in:
parent
c25da5920d
commit
ec192acac2
94 changed files with 1215 additions and 586 deletions
|
@ -15,7 +15,7 @@ export const ML_PAGES = {
|
|||
DATA_FRAME_ANALYTICS_SOURCE_SELECTION: 'data_frame_analytics/source_selection',
|
||||
DATA_FRAME_ANALYTICS_CREATE_JOB: 'data_frame_analytics/new_job',
|
||||
TRAINED_MODELS_MANAGE: 'trained_models',
|
||||
TRAINED_MODELS_NODES: 'trained_models/nodes',
|
||||
MEMORY_USAGE: 'memory_usage',
|
||||
DATA_FRAME_ANALYTICS_EXPLORATION: 'data_frame_analytics/exploration',
|
||||
DATA_FRAME_ANALYTICS_MAP: 'data_frame_analytics/map',
|
||||
/**
|
||||
|
|
|
@ -203,7 +203,7 @@ export interface TrainedModelsQueryState {
|
|||
modelId?: string;
|
||||
}
|
||||
|
||||
export interface TrainedModelsNodesQueryState {
|
||||
export interface MemoryUsageNodesQueryState {
|
||||
nodeId?: string;
|
||||
}
|
||||
|
||||
|
@ -276,7 +276,7 @@ export type MlLocatorState =
|
|||
| MlGenericUrlState
|
||||
| NotificationsUrlState
|
||||
| TrainedModelsUrlState
|
||||
| TrainedModelsNodesUrlState;
|
||||
| MemoryUsageUrlState;
|
||||
|
||||
export type MlLocatorParams = MlLocatorState & SerializableRecord;
|
||||
|
||||
|
@ -287,9 +287,9 @@ export type TrainedModelsUrlState = MLPageState<
|
|||
TrainedModelsQueryState | undefined
|
||||
>;
|
||||
|
||||
export type TrainedModelsNodesUrlState = MLPageState<
|
||||
typeof ML_PAGES.TRAINED_MODELS_NODES,
|
||||
TrainedModelsNodesQueryState | undefined
|
||||
export type MemoryUsageUrlState = MLPageState<
|
||||
typeof ML_PAGES.MEMORY_USAGE,
|
||||
MemoryUsageNodesQueryState | undefined
|
||||
>;
|
||||
|
||||
export interface NotificationsQueryState {
|
||||
|
|
|
@ -9,6 +9,7 @@ import type { DataFrameAnalyticsConfig } from './data_frame_analytics';
|
|||
import type { FeatureImportanceBaseline, TotalFeatureImportance } from './feature_importance';
|
||||
import type { XOR } from './common';
|
||||
import type { DeploymentState, TrainedModelType } from '../constants/trained_models';
|
||||
import type { MlSavedObjectType } from './saved_objects';
|
||||
|
||||
export interface IngestStats {
|
||||
count: number;
|
||||
|
@ -236,3 +237,47 @@ export interface NodesOverviewResponse {
|
|||
_nodes: { total: number; failed: number; successful: number };
|
||||
nodes: NodeDeploymentStatsResponse[];
|
||||
}
|
||||
|
||||
export interface MemoryUsageInfo {
|
||||
id: string;
|
||||
type: MlSavedObjectType;
|
||||
size: number;
|
||||
nodeNames: string[];
|
||||
}
|
||||
|
||||
export interface MemoryStatsResponse {
|
||||
_nodes: { total: number; failed: number; successful: number };
|
||||
cluster_name: string;
|
||||
nodes: Record<
|
||||
string,
|
||||
{
|
||||
jvm: {
|
||||
heap_max_in_bytes: number;
|
||||
java_inference_in_bytes: number;
|
||||
java_inference_max_in_bytes: number;
|
||||
};
|
||||
mem: {
|
||||
adjusted_total_in_bytes: number;
|
||||
total_in_bytes: number;
|
||||
ml: {
|
||||
data_frame_analytics_in_bytes: number;
|
||||
native_code_overhead_in_bytes: number;
|
||||
max_in_bytes: number;
|
||||
anomaly_detectors_in_bytes: number;
|
||||
native_inference_in_bytes: number;
|
||||
};
|
||||
};
|
||||
transport_address: string;
|
||||
roles: string[];
|
||||
name: string;
|
||||
attributes: Record<`${'ml.'}${string}`, string>;
|
||||
ephemeral_id: string;
|
||||
}
|
||||
>;
|
||||
}
|
||||
|
||||
// @ts-expect-error TrainedModelDeploymentStatsResponse missing properties from MlTrainedModelDeploymentStats
|
||||
export interface TrainedModelStatsResponse extends estypes.MlTrainedModelStats {
|
||||
deployment_stats?: Omit<TrainedModelDeploymentStatsResponse, 'model_id'>;
|
||||
model_size_stats?: TrainedModelModelSizeStats;
|
||||
}
|
||||
|
|
|
@ -45,6 +45,12 @@ interface AppProps {
|
|||
|
||||
const localStorage = new Storage(window.localStorage);
|
||||
|
||||
// temporary function to hardcode the serverless state
|
||||
// this will be replaced by the true serverless information from kibana
|
||||
export function isServerless() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides global services available across the entire ML app.
|
||||
*/
|
||||
|
@ -54,6 +60,7 @@ export function getMlGlobalServices(httpStart: HttpStart, usageCollection?: Usag
|
|||
httpService,
|
||||
mlApiServices: mlApiServicesProvider(httpService),
|
||||
mlUsageCollection: mlUsageCollectionProvider(usageCollection),
|
||||
isServerless,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -169,7 +169,7 @@ const CommonPageWrapper: FC<CommonPageWrapperProps> = React.memo(({ pageDeps, ro
|
|||
{routeList.map((route) => {
|
||||
return (
|
||||
<Route
|
||||
key={route.id}
|
||||
key={route.path}
|
||||
path={route.path}
|
||||
exact
|
||||
render={(props) => {
|
||||
|
|
|
@ -96,6 +96,15 @@ export function useSideNavItems(activeRoute: MlRoute | undefined) {
|
|||
disabled: disableLinks,
|
||||
testSubj: 'mlMainTab notifications',
|
||||
},
|
||||
{
|
||||
id: 'memory_usage',
|
||||
pathId: ML_PAGES.MEMORY_USAGE,
|
||||
name: i18n.translate('xpack.ml.navMenu.memoryUsageText', {
|
||||
defaultMessage: 'Memory Usage',
|
||||
}),
|
||||
disabled: disableLinks || !canViewMlNodes,
|
||||
testSubj: 'mlMainTab nodesOverview',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
@ -196,15 +205,6 @@ export function useSideNavItems(activeRoute: MlRoute | undefined) {
|
|||
disabled: disableLinks,
|
||||
testSubj: 'mlMainTab trainedModels',
|
||||
},
|
||||
{
|
||||
id: 'nodes_overview',
|
||||
pathId: ML_PAGES.TRAINED_MODELS_NODES,
|
||||
name: i18n.translate('xpack.ml.navMenu.nodesOverviewText', {
|
||||
defaultMessage: 'Nodes',
|
||||
}),
|
||||
disabled: disableLinks || !canViewMlNodes,
|
||||
testSubj: 'mlMainTab nodesOverview',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { useMemo } from 'react';
|
||||
import { useMlKibana } from './kibana_context';
|
||||
|
||||
export const useIsServerless = () => {
|
||||
const isServerless = useMlKibana().services.mlServices.isServerless;
|
||||
return useMemo(() => isServerless(), [isServerless]);
|
||||
};
|
|
@ -27,7 +27,7 @@ import { BUILT_IN_MODEL_TAG } from '../../../../../../common/constants/data_fram
|
|||
import { useTrainedModelsApiService } from '../../../../services/ml_api_service/trained_models';
|
||||
import { GetDataFrameAnalyticsResponse } from '../../../../services/ml_api_service/data_frame_analytics';
|
||||
import { useToastNotificationService } from '../../../../services/toast_notification_service';
|
||||
import { ModelsTableToConfigMapping } from '../../../../trained_models/models_management';
|
||||
import { ModelsTableToConfigMapping } from '../../../../model_management';
|
||||
import { DataFrameAnalyticsConfig } from '../../../common';
|
||||
import { useMlApiContext } from '../../../../contexts/kibana';
|
||||
import { TrainedModelConfigResponse } from '../../../../../../common/types/trained_models';
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiBasicTableColumn } from '@elastic/eui';
|
||||
import { TrainedModelLink } from '../../../../../trained_models/models_management';
|
||||
import { TrainedModelLink } from '../../../../../model_management';
|
||||
import type { MlSavedObjectType } from '../../../../../../../common/types/saved_objects';
|
||||
import type {
|
||||
AnalyticsManagementItems,
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export { MemoryUsagePage } from './memory_usage_page';
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import {
|
||||
euiPaletteComplimentary,
|
||||
euiPaletteForTemperature,
|
||||
euiPaletteGray,
|
||||
euiPalettePositive,
|
||||
euiPaletteWarm,
|
||||
} from '@elastic/eui';
|
||||
import { MlSavedObjectType } from '../../../common/types/saved_objects';
|
||||
|
||||
type MemoryItem = MlSavedObjectType | 'jvm-heap-size' | 'estimated-available-memory';
|
||||
|
||||
export function getMemoryItemColor(typeIn: MemoryItem) {
|
||||
switch (typeIn) {
|
||||
case 'anomaly-detector':
|
||||
return euiPaletteWarm(5)[1];
|
||||
case 'data-frame-analytics':
|
||||
return euiPalettePositive(5)[2];
|
||||
case 'trained-model':
|
||||
return euiPaletteForTemperature(5)[1];
|
||||
case 'estimated-available-memory':
|
||||
return euiPaletteGray(5)[0];
|
||||
case 'jvm-heap-size':
|
||||
return euiPaletteComplimentary(5)[4];
|
||||
default:
|
||||
return euiPaletteGray(5)[4];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export { JobMemoryTreeMap } from './tree_map';
|
||||
export { MemoryPage } from './memory_page';
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiCallOut, EuiSpacer } from '@elastic/eui';
|
||||
import React, { FC } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { JobMemoryTreeMap } from './tree_map';
|
||||
|
||||
export const MemoryPage: FC = () => {
|
||||
return (
|
||||
<>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiCallOut color="primary">
|
||||
<FormattedMessage
|
||||
id="xpack.ml.memoryUsage.treeMap.infoCallout"
|
||||
defaultMessage="Memory usage for active machine learning jobs and trained models."
|
||||
/>
|
||||
</EuiCallOut>
|
||||
<JobMemoryTreeMap />
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,201 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { FC, useEffect, useState, useCallback, useMemo } from 'react';
|
||||
import {
|
||||
Chart,
|
||||
Settings,
|
||||
Partition,
|
||||
PartitionLayout,
|
||||
ShapeTreeNode,
|
||||
LIGHT_THEME,
|
||||
DARK_THEME,
|
||||
} from '@elastic/charts';
|
||||
import { EUI_CHARTS_THEME_DARK, EUI_CHARTS_THEME_LIGHT } from '@elastic/eui/dist/eui_charts_theme';
|
||||
import { FIELD_FORMAT_IDS } from '@kbn/field-formats-plugin/common';
|
||||
import { EuiComboBox, EuiComboBoxOptionOption, EuiEmptyPrompt, EuiSpacer } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { MemoryUsageInfo } from '../../../../common/types/trained_models';
|
||||
import { JobType, MlSavedObjectType } from '../../../../common/types/saved_objects';
|
||||
import { useTrainedModelsApiService } from '../../services/ml_api_service/trained_models';
|
||||
import { LoadingWrapper } from '../../jobs/new_job/pages/components/charts/loading_wrapper';
|
||||
import { useFieldFormatter, useUiSettings } from '../../contexts/kibana';
|
||||
|
||||
import { useRefresh } from '../../routing/use_refresh';
|
||||
import { getMemoryItemColor } from '../memory_item_colors';
|
||||
import { useToastNotificationService } from '../../services/toast_notification_service';
|
||||
|
||||
interface Props {
|
||||
node?: string;
|
||||
type?: MlSavedObjectType;
|
||||
height?: string;
|
||||
}
|
||||
|
||||
const DEFAULT_CHART_HEIGHT = '400px';
|
||||
|
||||
const TYPE_LABELS: Record<string, MlSavedObjectType> = {
|
||||
[i18n.translate('xpack.ml.memoryUsage.treeMap.adLabel', {
|
||||
defaultMessage: 'Anomaly detection jobs',
|
||||
})]: 'anomaly-detector',
|
||||
[i18n.translate('xpack.ml.memoryUsage.treeMap.dfaLabel', {
|
||||
defaultMessage: 'Data frame analytics jobs',
|
||||
})]: 'data-frame-analytics',
|
||||
[i18n.translate('xpack.ml.memoryUsage.treeMap.modelsLabel', {
|
||||
defaultMessage: 'Trained models',
|
||||
})]: 'trained-model',
|
||||
} as const;
|
||||
|
||||
const TYPE_LABELS_INVERTED = Object.entries(TYPE_LABELS).reduce<Record<MlSavedObjectType, string>>(
|
||||
(acc, [label, type]) => {
|
||||
acc[type] = label;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<MlSavedObjectType, string>
|
||||
);
|
||||
|
||||
const TYPE_OPTIONS: EuiComboBoxOptionOption[] = Object.entries(TYPE_LABELS).map(
|
||||
([label, type]) => ({
|
||||
label,
|
||||
color: getMemoryItemColor(type),
|
||||
})
|
||||
);
|
||||
|
||||
export const JobMemoryTreeMap: FC<Props> = ({ node, type, height }) => {
|
||||
const isDarkTheme = useUiSettings().get('theme:darkMode');
|
||||
const { theme, baseTheme } = useMemo(
|
||||
() =>
|
||||
isDarkTheme
|
||||
? { theme: EUI_CHARTS_THEME_DARK, baseTheme: DARK_THEME }
|
||||
: { theme: EUI_CHARTS_THEME_LIGHT, baseTheme: LIGHT_THEME },
|
||||
[isDarkTheme]
|
||||
);
|
||||
|
||||
const bytesFormatter = useFieldFormatter(FIELD_FORMAT_IDS.BYTES);
|
||||
const { displayErrorToast } = useToastNotificationService();
|
||||
const refresh = useRefresh();
|
||||
const chartHeight = height ?? DEFAULT_CHART_HEIGHT;
|
||||
|
||||
const trainedModelsApiService = useTrainedModelsApiService();
|
||||
const [allData, setAllData] = useState<MemoryUsageInfo[]>([]);
|
||||
const [data, setData] = useState<MemoryUsageInfo[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [selectedOptions, setSelectedOptions] = useState(TYPE_OPTIONS);
|
||||
|
||||
const filterData = useCallback(
|
||||
(dataIn: MemoryUsageInfo[]) => {
|
||||
const types = selectedOptions.map((o) => TYPE_LABELS[o.label]);
|
||||
return dataIn.filter((d) => types.includes(d.type));
|
||||
},
|
||||
[selectedOptions]
|
||||
);
|
||||
|
||||
const loadJobMemorySize = useCallback(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const resp = await trainedModelsApiService.memoryUsage(type, node);
|
||||
setAllData(resp);
|
||||
} catch (error) {
|
||||
displayErrorToast(
|
||||
error,
|
||||
i18n.translate('xpack.ml.memoryUsage.treeMap.fetchFailedErrorMessage', {
|
||||
defaultMessage: 'Models memory usage fetch failed',
|
||||
})
|
||||
);
|
||||
}
|
||||
setLoading(false);
|
||||
}, [trainedModelsApiService, type, node, displayErrorToast]);
|
||||
|
||||
useEffect(
|
||||
function redrawOnFilterChange() {
|
||||
setData(filterData(allData));
|
||||
},
|
||||
[selectedOptions, allData, filterData]
|
||||
);
|
||||
|
||||
useEffect(
|
||||
function updateOnTimerRefresh() {
|
||||
loadJobMemorySize();
|
||||
},
|
||||
[loadJobMemorySize, refresh]
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{ height: chartHeight }}
|
||||
data-test-subj={`mlJobTreeMap ${data.length ? 'withData' : 'empty'}`}
|
||||
>
|
||||
<EuiSpacer size="s" />
|
||||
<LoadingWrapper height={chartHeight} hasData={data.length > 0} loading={loading}>
|
||||
<EuiComboBox
|
||||
fullWidth
|
||||
options={TYPE_OPTIONS}
|
||||
selectedOptions={selectedOptions}
|
||||
onChange={setSelectedOptions}
|
||||
isClearable={false}
|
||||
/>
|
||||
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
{data.length ? (
|
||||
<Chart>
|
||||
<Settings baseTheme={baseTheme} theme={theme.theme} />
|
||||
<Partition<MemoryUsageInfo>
|
||||
id="memoryUsageTreeMap"
|
||||
data={data}
|
||||
layout={PartitionLayout.treemap}
|
||||
valueAccessor={(d) => d.size}
|
||||
valueFormatter={(size: number) => bytesFormatter(size)}
|
||||
layers={[
|
||||
{
|
||||
groupByRollup: (d: MemoryUsageInfo) => d.type,
|
||||
nodeLabel: (d) => TYPE_LABELS_INVERTED[d as MlSavedObjectType],
|
||||
fillLabel: {
|
||||
valueFormatter: (size: number) => bytesFormatter(size),
|
||||
},
|
||||
shape: {
|
||||
fillColor: (d: ShapeTreeNode) => getMemoryItemColor(d.dataName as JobType),
|
||||
},
|
||||
},
|
||||
{
|
||||
groupByRollup: (d: MemoryUsageInfo) => d.id,
|
||||
nodeLabel: (d) => `${d}`,
|
||||
fillLabel: {
|
||||
valueFont: {
|
||||
fontWeight: 100,
|
||||
},
|
||||
},
|
||||
shape: {
|
||||
fillColor: (d: ShapeTreeNode) => {
|
||||
// color the shape the same as its parent.
|
||||
const parentId = d.parent.path[d.parent.path.length - 1].value as JobType;
|
||||
return getMemoryItemColor(parentId);
|
||||
},
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Chart>
|
||||
) : (
|
||||
<EuiEmptyPrompt
|
||||
titleSize="xs"
|
||||
iconType="alert"
|
||||
data-test-subj="mlEmptyMemoryUsageTreeMap"
|
||||
title={
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.memoryUsage.treeMap.emptyPrompt"
|
||||
defaultMessage="No open jobs or trained models match the current selection. "
|
||||
/>
|
||||
</h2>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</LoadingWrapper>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { FC, useCallback, useState } from 'react';
|
||||
import { mlTimefilterRefresh$, useTimefilter } from '@kbn/ml-date-picker';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiTabs, EuiTab } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { NodesList } from './nodes_overview';
|
||||
import { MlPageHeader } from '../components/page_header';
|
||||
import { MemoryPage, JobMemoryTreeMap } from './memory_tree_map';
|
||||
import { useIsServerless } from '../contexts/kibana/use_is_serverless';
|
||||
import { SavedObjectsWarning } from '../components/saved_objects_warning';
|
||||
|
||||
enum TAB {
|
||||
NODES,
|
||||
MEMORY_USAGE,
|
||||
}
|
||||
|
||||
export const MemoryUsagePage: FC = () => {
|
||||
const serverless = useIsServerless();
|
||||
const [selectedTab, setSelectedTab] = useState<TAB>(TAB.NODES);
|
||||
useTimefilter({ timeRangeSelector: false, autoRefreshSelector: true });
|
||||
|
||||
const refresh = useCallback(() => {
|
||||
mlTimefilterRefresh$.next({
|
||||
lastRefresh: Date.now(),
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<MlPageHeader>
|
||||
<EuiFlexGroup responsive={false} wrap={false} alignItems={'center'} gutterSize={'m'}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.memoryUsage.memoryUsageHeader"
|
||||
defaultMessage="Memory Usage"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</MlPageHeader>
|
||||
|
||||
<SavedObjectsWarning onCloseFlyout={refresh} />
|
||||
|
||||
{serverless ? (
|
||||
<JobMemoryTreeMap />
|
||||
) : (
|
||||
<>
|
||||
<EuiTabs>
|
||||
<EuiTab
|
||||
isSelected={selectedTab === TAB.NODES}
|
||||
onClick={() => setSelectedTab(TAB.NODES)}
|
||||
>
|
||||
<FormattedMessage id="xpack.ml.memoryUsage.nodesTab" defaultMessage="Nodes" />
|
||||
</EuiTab>
|
||||
<EuiTab
|
||||
isSelected={selectedTab === TAB.MEMORY_USAGE}
|
||||
onClick={() => setSelectedTab(TAB.MEMORY_USAGE)}
|
||||
>
|
||||
<FormattedMessage id="xpack.ml.memoryUsage.memoryTab" defaultMessage="Memory usage" />
|
||||
</EuiTab>
|
||||
</EuiTabs>
|
||||
{selectedTab === TAB.NODES ? <NodesList /> : <MemoryPage />}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,151 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { FC, useState } from 'react';
|
||||
import {
|
||||
EuiDescriptionList,
|
||||
EuiFlexGrid,
|
||||
EuiFlexItem,
|
||||
EuiPanel,
|
||||
EuiSpacer,
|
||||
EuiTab,
|
||||
EuiTabs,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { FIELD_FORMAT_IDS } from '@kbn/field-formats-plugin/common';
|
||||
import { css } from '@emotion/react';
|
||||
import { NodeItem } from './nodes_list';
|
||||
import { useListItemsFormatter } from '../../model_management/expanded_row';
|
||||
import { AllocatedModels } from './allocated_models';
|
||||
import { useFieldFormatter } from '../../contexts/kibana/use_field_formatter';
|
||||
import { JobMemoryTreeMap } from '../memory_tree_map';
|
||||
|
||||
interface ExpandedRowProps {
|
||||
item: NodeItem;
|
||||
}
|
||||
|
||||
enum TAB {
|
||||
DETAILS,
|
||||
MEMORY_USAGE,
|
||||
}
|
||||
|
||||
export const ExpandedRow: FC<ExpandedRowProps> = ({ item }) => {
|
||||
const bytesFormatter = useFieldFormatter(FIELD_FORMAT_IDS.BYTES);
|
||||
const [selectedTab, setSelectedTab] = useState<TAB>(TAB.DETAILS);
|
||||
|
||||
const formatToListItems = useListItemsFormatter();
|
||||
|
||||
const {
|
||||
allocated_models: allocatedModels,
|
||||
attributes,
|
||||
memory_overview: memoryOverview,
|
||||
id,
|
||||
...details
|
||||
} = cloneDeep(item);
|
||||
|
||||
// Process node attributes
|
||||
attributes['ml.machine_memory'] = bytesFormatter(attributes['ml.machine_memory']);
|
||||
attributes['ml.max_jvm_size'] = bytesFormatter(attributes['ml.max_jvm_size']);
|
||||
|
||||
return (
|
||||
<div
|
||||
css={css`
|
||||
width: 100%;
|
||||
`}
|
||||
>
|
||||
<EuiTabs>
|
||||
<EuiTab
|
||||
isSelected={selectedTab === TAB.DETAILS}
|
||||
onClick={() => setSelectedTab(TAB.DETAILS)}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.trainedModels.nodesList.expandedRow.detailsTabTitle"
|
||||
defaultMessage="Details"
|
||||
/>
|
||||
</EuiTab>
|
||||
<EuiTab
|
||||
isSelected={selectedTab === TAB.MEMORY_USAGE}
|
||||
onClick={() => setSelectedTab(TAB.MEMORY_USAGE)}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.trainedModels.nodesList.expandedRow.memoryTabTitle"
|
||||
defaultMessage="Memory usage"
|
||||
/>
|
||||
</EuiTab>
|
||||
</EuiTabs>
|
||||
|
||||
{selectedTab === TAB.DETAILS ? (
|
||||
<>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiFlexGrid columns={2} gutterSize={'s'}>
|
||||
<EuiFlexItem>
|
||||
<EuiPanel hasShadow={false}>
|
||||
<EuiTitle size={'xs'}>
|
||||
<h5>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.trainedModels.nodesList.expandedRow.detailsTitle"
|
||||
defaultMessage="Details"
|
||||
/>
|
||||
</h5>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size={'m'} />
|
||||
<EuiDescriptionList
|
||||
compressed={true}
|
||||
type="column"
|
||||
listItems={formatToListItems(details)}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem>
|
||||
<EuiPanel hasShadow={false}>
|
||||
<EuiTitle size={'xs'}>
|
||||
<h5>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.trainedModels.nodesList.expandedRow.attributesTitle"
|
||||
defaultMessage="Attributes"
|
||||
/>
|
||||
</h5>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size={'m'} />
|
||||
<EuiDescriptionList
|
||||
compressed={true}
|
||||
type="column"
|
||||
listItems={formatToListItems(attributes)}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGrid>
|
||||
{allocatedModels.length > 0 ? (
|
||||
<>
|
||||
<EuiSpacer size={'s'} />
|
||||
<EuiPanel hasShadow={false}>
|
||||
<EuiTitle size={'xs'}>
|
||||
<h5>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.trainedModels.nodesList.expandedRow.allocatedModelsTitle"
|
||||
defaultMessage="Allocated trained models"
|
||||
/>
|
||||
</h5>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size={'m'} />
|
||||
|
||||
<AllocatedModels models={allocatedModels} />
|
||||
</EuiPanel>
|
||||
</>
|
||||
) : null}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<JobMemoryTreeMap node={item.name} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -18,11 +18,11 @@ import {
|
|||
LineAnnotation,
|
||||
AnnotationDomainType,
|
||||
} from '@elastic/charts';
|
||||
import { EuiIcon, euiPaletteGray } from '@elastic/eui';
|
||||
import { EuiIcon } from '@elastic/eui';
|
||||
import { FIELD_FORMAT_IDS } from '@kbn/field-formats-plugin/common';
|
||||
import { NodeDeploymentStatsResponse } from '../../../../common/types/trained_models';
|
||||
import { useFieldFormatter } from '../../contexts/kibana/use_field_formatter';
|
||||
import { useCurrentEuiTheme } from '../../components/color_range_legend';
|
||||
import { getMemoryItemColor } from '../memory_item_colors';
|
||||
|
||||
interface MemoryPreviewChartProps {
|
||||
memoryOverview: NodeDeploymentStatsResponse['memory_overview'];
|
||||
|
@ -31,42 +31,39 @@ interface MemoryPreviewChartProps {
|
|||
export const MemoryPreviewChart: FC<MemoryPreviewChartProps> = ({ memoryOverview }) => {
|
||||
const bytesFormatter = useFieldFormatter(FIELD_FORMAT_IDS.BYTES);
|
||||
|
||||
const { euiTheme } = useCurrentEuiTheme();
|
||||
|
||||
const groups = useMemo(
|
||||
() => ({
|
||||
jvm: {
|
||||
name: i18n.translate('xpack.ml.trainedModels.nodesList.jvmHeapSIze', {
|
||||
defaultMessage: 'JVM heap size',
|
||||
}),
|
||||
colour: euiTheme.euiColorVis1,
|
||||
color: getMemoryItemColor('jvm-heap-size'),
|
||||
},
|
||||
trained_models: {
|
||||
name: i18n.translate('xpack.ml.trainedModels.nodesList.modelsMemoryUsage', {
|
||||
defaultMessage: 'Trained models',
|
||||
}),
|
||||
colour: euiTheme.euiColorVis2,
|
||||
color: getMemoryItemColor('trained-model'),
|
||||
},
|
||||
anomaly_detection: {
|
||||
name: i18n.translate('xpack.ml.trainedModels.nodesList.adMemoryUsage', {
|
||||
defaultMessage: 'Anomaly detection jobs',
|
||||
}),
|
||||
colour: euiTheme.euiColorVis6,
|
||||
color: getMemoryItemColor('anomaly-detector'),
|
||||
},
|
||||
dfa_training: {
|
||||
name: i18n.translate('xpack.ml.trainedModels.nodesList.dfaMemoryUsage', {
|
||||
defaultMessage: 'Data frame analytics jobs',
|
||||
}),
|
||||
colour: euiTheme.euiColorVis4,
|
||||
color: getMemoryItemColor('data-frame-analytics'),
|
||||
},
|
||||
available: {
|
||||
name: i18n.translate('xpack.ml.trainedModels.nodesList.availableMemory', {
|
||||
defaultMessage: 'Estimated available memory',
|
||||
}),
|
||||
colour: euiPaletteGray(5)[0],
|
||||
color: getMemoryItemColor('estimated-available-memory'),
|
||||
},
|
||||
}),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[]
|
||||
);
|
||||
|
||||
|
@ -106,7 +103,7 @@ export const MemoryPreviewChart: FC<MemoryPreviewChartProps> = ({ memoryOverview
|
|||
const barSeriesColorAccessor: SeriesColorAccessor = ({ specId, yAccessor, splitAccessors }) => {
|
||||
const group = splitAccessors.get('g');
|
||||
|
||||
return Object.values(groups).find((v) => v.name === group)!.colour;
|
||||
return Object.values(groups).find((v) => v.name === group)!.color;
|
||||
};
|
||||
|
||||
return (
|
|
@ -33,7 +33,7 @@ import { useRefresh } from '../../routing/use_refresh';
|
|||
export type NodeItem = NodeDeploymentStatsResponse;
|
||||
|
||||
interface PageUrlState {
|
||||
pageKey: typeof ML_PAGES.TRAINED_MODELS_NODES;
|
||||
pageKey: typeof ML_PAGES.MEMORY_USAGE;
|
||||
pageUrlState: ListingPageUrlState;
|
||||
}
|
||||
|
||||
|
@ -61,7 +61,7 @@ export const NodesList: FC<NodesListProps> = ({ compactView = false }) => {
|
|||
{}
|
||||
);
|
||||
const [pageState, updatePageState] = usePageUrlState<PageUrlState>(
|
||||
ML_PAGES.TRAINED_MODELS_NODES,
|
||||
ML_PAGES.MEMORY_USAGE,
|
||||
getDefaultNodesListState()
|
||||
);
|
||||
|
|
@ -16,9 +16,9 @@ import {
|
|||
EuiButton,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { useTrainedModelsApiService } from '../../services/ml_api_service/trained_models';
|
||||
import { useToastNotificationService } from '../../services/toast_notification_service';
|
||||
import { DeleteSpaceAwareItemCheckModal } from '../../components/delete_space_aware_item_check_modal';
|
||||
import { useTrainedModelsApiService } from '../services/ml_api_service/trained_models';
|
||||
import { useToastNotificationService } from '../services/toast_notification_service';
|
||||
import { DeleteSpaceAwareItemCheckModal } from '../components/delete_space_aware_item_check_modal';
|
||||
|
||||
interface DeleteModelsModalProps {
|
||||
modelIds: string[];
|
|
@ -30,8 +30,8 @@ import type { Observable } from 'rxjs';
|
|||
import type { CoreTheme, OverlayStart } from '@kbn/core/public';
|
||||
import { css } from '@emotion/react';
|
||||
import { numberValidator } from '@kbn/ml-agg-utils';
|
||||
import { isCloudTrial } from '../../services/ml_server_info';
|
||||
import { composeValidators, requiredValidator } from '../../../../common/util/validators';
|
||||
import { isCloudTrial } from '../services/ml_server_info';
|
||||
import { composeValidators, requiredValidator } from '../../../common/util/validators';
|
||||
|
||||
interface DeploymentSetupProps {
|
||||
config: ThreadingParams;
|
|
@ -20,6 +20,7 @@ import {
|
|||
EuiTabbedContent,
|
||||
EuiTabbedContentTab,
|
||||
EuiTitle,
|
||||
useEuiPaddingSize,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { FIELD_FORMAT_IDS } from '@kbn/field-formats-plugin/common';
|
||||
|
@ -27,30 +28,38 @@ import { isPopulatedObject } from '@kbn/ml-is-populated-object';
|
|||
import { isDefined } from '@kbn/ml-is-defined';
|
||||
import type { ModelItemFull } from './models_list';
|
||||
import { ModelPipelines } from './pipelines';
|
||||
import { AllocatedModels } from '../nodes_overview/allocated_models';
|
||||
import type { AllocatedModel } from '../../../../common/types/trained_models';
|
||||
import { useFieldFormatter } from '../../contexts/kibana/use_field_formatter';
|
||||
import { AllocatedModels } from '../memory_usage/nodes_overview/allocated_models';
|
||||
import type { AllocatedModel } from '../../../common/types/trained_models';
|
||||
import { useFieldFormatter } from '../contexts/kibana/use_field_formatter';
|
||||
|
||||
interface ExpandedRowProps {
|
||||
item: ModelItemFull;
|
||||
}
|
||||
|
||||
const badgeFormatter = (items: string[]) => {
|
||||
if (items.length === 0) return;
|
||||
return (
|
||||
<div>
|
||||
{items.map((item) => (
|
||||
<EuiBadge key={item} color="hollow">
|
||||
{item}
|
||||
</EuiBadge>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
const useBadgeFormatter = () => {
|
||||
const xs = useEuiPaddingSize('xs');
|
||||
|
||||
function badgeFormatter(items: string[]) {
|
||||
if (items.length === 0) return;
|
||||
return (
|
||||
<div>
|
||||
{items.map((item) => (
|
||||
<span css={{ marginRight: xs! }} key={item}>
|
||||
<EuiBadge color="hollow" css={{ marginRight: xs! }}>
|
||||
{item}
|
||||
</EuiBadge>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return { badgeFormatter };
|
||||
};
|
||||
|
||||
export function useListItemsFormatter() {
|
||||
const bytesFormatter = useFieldFormatter(FIELD_FORMAT_IDS.BYTES);
|
||||
const dateFormatter = useFieldFormatter(FIELD_FORMAT_IDS.DATE);
|
||||
const { badgeFormatter } = useBadgeFormatter();
|
||||
|
||||
const formatterDictionary: Record<string, (value: any) => JSX.Element | string | undefined> =
|
||||
useMemo(
|
|
@ -10,16 +10,16 @@ import { i18n } from '@kbn/i18n';
|
|||
import { isPopulatedObject } from '@kbn/ml-is-populated-object';
|
||||
import { EuiToolTip } from '@elastic/eui';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { BUILT_IN_MODEL_TAG } from '../../../../common/constants/data_frame_analytics';
|
||||
import { useTrainedModelsApiService } from '../../services/ml_api_service/trained_models';
|
||||
import { BUILT_IN_MODEL_TAG } from '../../../common/constants/data_frame_analytics';
|
||||
import { useTrainedModelsApiService } from '../services/ml_api_service/trained_models';
|
||||
import { getUserConfirmationProvider } from './force_stop_dialog';
|
||||
import { useToastNotificationService } from '../../services/toast_notification_service';
|
||||
import { useToastNotificationService } from '../services/toast_notification_service';
|
||||
import { getUserInputThreadingParamsProvider } from './deployment_setup';
|
||||
import { useMlKibana, useMlLocator, useNavigateToPath } from '../../contexts/kibana';
|
||||
import { getAnalysisType } from '../../../../common/util/analytics_utils';
|
||||
import { DataFrameAnalysisConfigType } from '../../../../common/types/data_frame_analytics';
|
||||
import { ML_PAGES } from '../../../../common/constants/locator';
|
||||
import { DEPLOYMENT_STATE, TRAINED_MODEL_TYPE } from '../../../../common/constants/trained_models';
|
||||
import { useMlKibana, useMlLocator, useNavigateToPath } from '../contexts/kibana';
|
||||
import { getAnalysisType } from '../../../common/util/analytics_utils';
|
||||
import { DataFrameAnalysisConfigType } from '../../../common/types/data_frame_analytics';
|
||||
import { ML_PAGES } from '../../../common/constants/locator';
|
||||
import { DEPLOYMENT_STATE, TRAINED_MODEL_TYPE } from '../../../common/constants/trained_models';
|
||||
import { isTestable } from './test_models';
|
||||
import { ModelItem } from './models_list';
|
||||
|
|
@ -7,8 +7,8 @@
|
|||
|
||||
import { EuiLink } from '@elastic/eui';
|
||||
import React, { FC } from 'react';
|
||||
import { useMlLink } from '../../contexts/kibana';
|
||||
import { ML_PAGES } from '../../../../common/constants/locator';
|
||||
import { useMlLink } from '../contexts/kibana';
|
||||
import { ML_PAGES } from '../../../common/constants/locator';
|
||||
|
||||
export interface TrainedModelLinkProps {
|
||||
id: string;
|
|
@ -29,25 +29,25 @@ import { usePageUrlState } from '@kbn/ml-url-state';
|
|||
import { useTimefilter } from '@kbn/ml-date-picker';
|
||||
import { useModelActions } from './model_actions';
|
||||
import { ModelsTableToConfigMapping } from '.';
|
||||
import { ModelsBarStats, StatsBar } from '../../components/stats_bar';
|
||||
import { useMlKibana } from '../../contexts/kibana';
|
||||
import { useTrainedModelsApiService } from '../../services/ml_api_service/trained_models';
|
||||
import { ModelsBarStats, StatsBar } from '../components/stats_bar';
|
||||
import { useMlKibana } from '../contexts/kibana';
|
||||
import { useTrainedModelsApiService } from '../services/ml_api_service/trained_models';
|
||||
import {
|
||||
ModelPipelines,
|
||||
TrainedModelConfigResponse,
|
||||
TrainedModelStat,
|
||||
} from '../../../../common/types/trained_models';
|
||||
import { BUILT_IN_MODEL_TAG } from '../../../../common/constants/data_frame_analytics';
|
||||
} from '../../../common/types/trained_models';
|
||||
import { BUILT_IN_MODEL_TAG } from '../../../common/constants/data_frame_analytics';
|
||||
import { DeleteModelsModal } from './delete_models_modal';
|
||||
import { ML_PAGES } from '../../../../common/constants/locator';
|
||||
import { ListingPageUrlState } from '../../../../common/types/common';
|
||||
import { ML_PAGES } from '../../../common/constants/locator';
|
||||
import { ListingPageUrlState } from '../../../common/types/common';
|
||||
import { ExpandedRow } from './expanded_row';
|
||||
import { useTableSettings } from '../../data_frame_analytics/pages/analytics_management/components/analytics_list/use_table_settings';
|
||||
import { useToastNotificationService } from '../../services/toast_notification_service';
|
||||
import { useFieldFormatter } from '../../contexts/kibana/use_field_formatter';
|
||||
import { useRefresh } from '../../routing/use_refresh';
|
||||
import { BUILT_IN_MODEL_TYPE } from '../../../../common/constants/trained_models';
|
||||
import { SavedObjectsWarning } from '../../components/saved_objects_warning';
|
||||
import { useTableSettings } from '../data_frame_analytics/pages/analytics_management/components/analytics_list/use_table_settings';
|
||||
import { useToastNotificationService } from '../services/toast_notification_service';
|
||||
import { useFieldFormatter } from '../contexts/kibana/use_field_formatter';
|
||||
import { useRefresh } from '../routing/use_refresh';
|
||||
import { BUILT_IN_MODEL_TYPE } from '../../../common/constants/trained_models';
|
||||
import { SavedObjectsWarning } from '../components/saved_objects_warning';
|
||||
import { TestTrainedModelFlyout } from './test_models';
|
||||
|
||||
type Stats = Omit<TrainedModelStat, 'model_id'>;
|
|
@ -12,9 +12,9 @@ import { FormattedMessage } from '@kbn/i18n-react';
|
|||
import { EuiBasicTableColumn } from '@elastic/eui/src/components/basic_table/basic_table';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FIELD_FORMAT_IDS } from '@kbn/field-formats-plugin/common';
|
||||
import { useFieldFormatter } from '../../../contexts/kibana/use_field_formatter';
|
||||
import { useFieldFormatter } from '../../contexts/kibana/use_field_formatter';
|
||||
import { IngestStatsResponse } from './pipelines';
|
||||
import { HelpIcon } from '../../../components/help_icon';
|
||||
import { HelpIcon } from '../../components/help_icon';
|
||||
|
||||
interface ProcessorsStatsProps {
|
||||
stats: Exclude<IngestStatsResponse, undefined>['pipelines'][string]['processors'];
|
|
@ -16,7 +16,7 @@ import {
|
|||
EuiAccordion,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { useMlKibana } from '../../../contexts/kibana';
|
||||
import { useMlKibana } from '../../contexts/kibana';
|
||||
import { ModelItem } from '../models_list';
|
||||
import { ProcessorsStats } from './expanded_row';
|
||||
|
|
@ -14,7 +14,7 @@ import { EuiSpacer, EuiSelect, EuiFormRow, EuiAccordion, EuiCodeBlock } from '@e
|
|||
|
||||
import { isPopulatedObject } from '@kbn/ml-is-populated-object';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useMlKibana } from '../../../../contexts/kibana';
|
||||
import { useMlKibana } from '../../../contexts/kibana';
|
||||
import { RUNNING_STATE } from './inference_base';
|
||||
import type { InferrerType } from '.';
|
||||
|
|
@ -10,9 +10,9 @@ import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { map } from 'rxjs/operators';
|
||||
import { MLHttpFetchError } from '../../../../../../common/util/errors';
|
||||
import { SupportedPytorchTasksType } from '../../../../../../common/constants/trained_models';
|
||||
import { trainedModelsApiProvider } from '../../../../services/ml_api_service/trained_models';
|
||||
import { MLHttpFetchError } from '../../../../../common/util/errors';
|
||||
import { SupportedPytorchTasksType } from '../../../../../common/constants/trained_models';
|
||||
import { trainedModelsApiProvider } from '../../../services/ml_api_service/trained_models';
|
||||
import { getInferenceInfoComponent } from './inference_info';
|
||||
|
||||
export type InferenceType =
|
|
@ -22,7 +22,7 @@ import {
|
|||
} from '@elastic/eui';
|
||||
|
||||
import { ErrorMessage } from '../../inference_error';
|
||||
import { extractErrorMessage } from '../../../../../../../common';
|
||||
import { extractErrorMessage } from '../../../../../../common';
|
||||
import type { InferrerType } from '..';
|
||||
import { useIndexInput, InferenceInputFormIndexControls } from '../index_input';
|
||||
import { RUNNING_STATE } from '../inference_base';
|
|
@ -12,7 +12,7 @@ import { FormattedMessage } from '@kbn/i18n-react';
|
|||
import { EuiSpacer, EuiButton, EuiTabs, EuiTab, EuiForm } from '@elastic/eui';
|
||||
|
||||
import { ErrorMessage } from '../../inference_error';
|
||||
import { extractErrorMessage } from '../../../../../../../common';
|
||||
import { extractErrorMessage } from '../../../../../../common';
|
||||
import type { InferrerType } from '..';
|
||||
import { OutputLoadingContent } from '../../output_loading';
|
||||
import { RUNNING_STATE } from '../inference_base';
|
|
@ -7,12 +7,12 @@
|
|||
|
||||
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { trainedModelsApiProvider } from '../../../../../services/ml_api_service/trained_models';
|
||||
import { trainedModelsApiProvider } from '../../../../services/ml_api_service/trained_models';
|
||||
import { InferenceBase, INPUT_TYPE } from '../inference_base';
|
||||
import type { InferResponse } from '../inference_base';
|
||||
import { getGeneralInputComponent } from '../text_input';
|
||||
import { getNerOutputComponent } from './ner_output';
|
||||
import { SUPPORTED_PYTORCH_TASKS } from '../../../../../../../common/constants/trained_models';
|
||||
import { SUPPORTED_PYTORCH_TASKS } from '../../../../../../common/constants/trained_models';
|
||||
|
||||
export type FormattedNerResponse = Array<{
|
||||
value: string;
|
|
@ -21,7 +21,7 @@ import {
|
|||
import {
|
||||
useCurrentEuiTheme,
|
||||
EuiThemeType,
|
||||
} from '../../../../../components/color_range_legend/use_color_range';
|
||||
} from '../../../../components/color_range_legend/use_color_range';
|
||||
import type { NerInference, NerResponse } from './ner_inference';
|
||||
import { INPUT_TYPE } from '../inference_base';
|
||||
|
|
@ -13,8 +13,8 @@ import { InferenceBase, INPUT_TYPE } from '../inference_base';
|
|||
import type { InferResponse } from '../inference_base';
|
||||
import { getQuestionAnsweringInput } from './question_answering_input';
|
||||
import { getQuestionAnsweringOutputComponent } from './question_answering_output';
|
||||
import { SUPPORTED_PYTORCH_TASKS } from '../../../../../../../common/constants/trained_models';
|
||||
import { trainedModelsApiProvider } from '../../../../../services/ml_api_service/trained_models';
|
||||
import { SUPPORTED_PYTORCH_TASKS } from '../../../../../../common/constants/trained_models';
|
||||
import { trainedModelsApiProvider } from '../../../../services/ml_api_service/trained_models';
|
||||
|
||||
export interface RawQuestionAnsweringResponse {
|
||||
inference_results: Array<{
|
|
@ -10,7 +10,7 @@ import useObservable from 'react-use/lib/useObservable';
|
|||
|
||||
import { EuiBadge, EuiHorizontalRule } from '@elastic/eui';
|
||||
|
||||
import { useCurrentEuiTheme } from '../../../../../components/color_range_legend/use_color_range';
|
||||
import { useCurrentEuiTheme } from '../../../../components/color_range_legend/use_color_range';
|
||||
|
||||
import type {
|
||||
QuestionAnsweringInference,
|
|
@ -9,7 +9,7 @@ import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
|||
import React, { FC } from 'react';
|
||||
import { Observable } from 'rxjs';
|
||||
import useObservable from 'react-use/lib/useObservable';
|
||||
import { MLJobEditor } from '../../../../jobs/jobs_list/components/ml_job_editor';
|
||||
import { MLJobEditor } from '../../../jobs/jobs_list/components/ml_job_editor';
|
||||
|
||||
import type { InferrerType } from '.';
|
||||
import { NerResponse } from './ner';
|
|
@ -13,8 +13,8 @@ import type { TextClassificationResponse, RawTextClassificationResponse } from '
|
|||
import { processResponse, processInferenceResult } from './common';
|
||||
import { getGeneralInputComponent } from '../text_input';
|
||||
import { getFillMaskOutputComponent } from './fill_mask_output';
|
||||
import { SUPPORTED_PYTORCH_TASKS } from '../../../../../../../common/constants/trained_models';
|
||||
import { trainedModelsApiProvider } from '../../../../../services/ml_api_service/trained_models';
|
||||
import { SUPPORTED_PYTORCH_TASKS } from '../../../../../../common/constants/trained_models';
|
||||
import { trainedModelsApiProvider } from '../../../../services/ml_api_service/trained_models';
|
||||
|
||||
const MASK = '[MASK]';
|
||||
|
|
@ -13,7 +13,7 @@ import { processInferenceResult, processResponse } from './common';
|
|||
import { getGeneralInputComponent } from '../text_input';
|
||||
import { getLangIdentOutputComponent } from './lang_ident_output';
|
||||
import type { TextClassificationResponse, RawTextClassificationResponse } from './common';
|
||||
import { trainedModelsApiProvider } from '../../../../../services/ml_api_service/trained_models';
|
||||
import { trainedModelsApiProvider } from '../../../../services/ml_api_service/trained_models';
|
||||
|
||||
export class LangIdentInference extends InferenceBase<TextClassificationResponse> {
|
||||
protected inferenceType: InferenceType = 'classification';
|
|
@ -12,8 +12,8 @@ import { processInferenceResult, processResponse } from './common';
|
|||
import type { TextClassificationResponse, RawTextClassificationResponse } from './common';
|
||||
import { getGeneralInputComponent } from '../text_input';
|
||||
import { getTextClassificationOutputComponent } from './text_classification_output';
|
||||
import { SUPPORTED_PYTORCH_TASKS } from '../../../../../../../common/constants/trained_models';
|
||||
import { trainedModelsApiProvider } from '../../../../../services/ml_api_service/trained_models';
|
||||
import { SUPPORTED_PYTORCH_TASKS } from '../../../../../../common/constants/trained_models';
|
||||
import { trainedModelsApiProvider } from '../../../../services/ml_api_service/trained_models';
|
||||
|
||||
export class TextClassificationInference extends InferenceBase<TextClassificationResponse> {
|
||||
protected inferenceType = SUPPORTED_PYTORCH_TASKS.TEXT_CLASSIFICATION;
|
|
@ -9,14 +9,14 @@ import { i18n } from '@kbn/i18n';
|
|||
import { BehaviorSubject } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { estypes } from '@elastic/elasticsearch';
|
||||
import { trainedModelsApiProvider } from '../../../../../services/ml_api_service/trained_models';
|
||||
import { trainedModelsApiProvider } from '../../../../services/ml_api_service/trained_models';
|
||||
import { InferenceBase, INPUT_TYPE } from '../inference_base';
|
||||
import { processInferenceResult, processResponse } from './common';
|
||||
import type { TextClassificationResponse, RawTextClassificationResponse } from './common';
|
||||
|
||||
import { getZeroShotClassificationInput } from './zero_shot_classification_input';
|
||||
import { getTextClassificationOutputComponent } from './text_classification_output';
|
||||
import { SUPPORTED_PYTORCH_TASKS } from '../../../../../../../common/constants/trained_models';
|
||||
import { SUPPORTED_PYTORCH_TASKS } from '../../../../../../common/constants/trained_models';
|
||||
|
||||
export class ZeroShotClassificationInference extends InferenceBase<TextClassificationResponse> {
|
||||
protected inferenceType = SUPPORTED_PYTORCH_TASKS.ZERO_SHOT_CLASSIFICATION;
|
|
@ -11,8 +11,8 @@ import { InferenceBase, INPUT_TYPE } from '../inference_base';
|
|||
import type { InferResponse } from '../inference_base';
|
||||
import { getGeneralInputComponent } from '../text_input';
|
||||
import { getTextEmbeddingOutputComponent } from './text_embedding_output';
|
||||
import { SUPPORTED_PYTORCH_TASKS } from '../../../../../../../common/constants/trained_models';
|
||||
import { trainedModelsApiProvider } from '../../../../../services/ml_api_service/trained_models';
|
||||
import { SUPPORTED_PYTORCH_TASKS } from '../../../../../../common/constants/trained_models';
|
||||
import { trainedModelsApiProvider } from '../../../../services/ml_api_service/trained_models';
|
||||
|
||||
export interface RawTextEmbeddingResponse {
|
||||
inference_results: Array<{ predicted_value: number[] }>;
|
|
@ -23,8 +23,8 @@ import { TextEmbeddingInference } from './models/text_embedding';
|
|||
import {
|
||||
TRAINED_MODEL_TYPE,
|
||||
SUPPORTED_PYTORCH_TASKS,
|
||||
} from '../../../../../common/constants/trained_models';
|
||||
import { useMlApiContext } from '../../../contexts/kibana';
|
||||
} from '../../../../common/constants/trained_models';
|
||||
import { useMlApiContext } from '../../contexts/kibana';
|
||||
import { InferenceInputForm } from './models/inference_input_form';
|
||||
import { InferrerType } from './models';
|
||||
import { INPUT_TYPE } from './models/inference_base';
|
|
@ -22,7 +22,7 @@ import {
|
|||
|
||||
import { SelectedModel } from './selected_model';
|
||||
import { INPUT_TYPE } from './models/inference_base';
|
||||
import { useTrainedModelsApiService } from '../../../services/ml_api_service/trained_models';
|
||||
import { useTrainedModelsApiService } from '../../services/ml_api_service/trained_models';
|
||||
|
||||
interface Props {
|
||||
modelId: string;
|
|
@ -9,8 +9,8 @@ import {
|
|||
TRAINED_MODEL_TYPE,
|
||||
DEPLOYMENT_STATE,
|
||||
SUPPORTED_PYTORCH_TASKS,
|
||||
} from '../../../../../common/constants/trained_models';
|
||||
import type { SupportedPytorchTasksType } from '../../../../../common/constants/trained_models';
|
||||
} from '../../../../common/constants/trained_models';
|
||||
import type { SupportedPytorchTasksType } from '../../../../common/constants/trained_models';
|
||||
import type { ModelItem } from '../models_list';
|
||||
|
||||
const PYTORCH_TYPES = Object.values(SUPPORTED_PYTORCH_TASKS);
|
|
@ -19,11 +19,13 @@ import { SavedObjectsWarning } from '../components/saved_objects_warning';
|
|||
import { UpgradeWarning } from '../components/upgrade';
|
||||
import { HelpMenu } from '../components/help_menu';
|
||||
import { useMlKibana } from '../contexts/kibana';
|
||||
import { NodesList } from '../trained_models/nodes_overview';
|
||||
import { NodesList } from '../memory_usage/nodes_overview';
|
||||
import { MlPageHeader } from '../components/page_header';
|
||||
import { PageTitle } from '../components/page_title';
|
||||
import { useIsServerless } from '../contexts/kibana/use_is_serverless';
|
||||
|
||||
export const OverviewPage: FC = () => {
|
||||
const serverless = useIsServerless();
|
||||
const canViewMlNodes = checkPermission('canViewMlNodes');
|
||||
|
||||
const disableCreateAnomalyDetectionJob = !checkPermission('canCreateJob') || !mlNodesAvailable();
|
||||
|
@ -62,7 +64,7 @@ export const OverviewPage: FC = () => {
|
|||
|
||||
<GettingStartedCallout />
|
||||
|
||||
{canViewMlNodes ? (
|
||||
{canViewMlNodes && serverless === false ? (
|
||||
<>
|
||||
<EuiPanel hasShadow={false} hasBorder>
|
||||
<NodesList compactView />
|
||||
|
|
|
@ -17,3 +17,4 @@ export * from './explorer';
|
|||
export * from './access_denied';
|
||||
export * from './trained_models';
|
||||
export * from './notifications';
|
||||
export * from './memory_usage';
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { FC } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { ML_PAGES } from '../../../locator';
|
||||
import { NavigateToPath } from '../../contexts/kibana';
|
||||
import { MlRoute, PageLoader, PageProps, createPath } from '../router';
|
||||
import { useResolver } from '../use_resolver';
|
||||
import { basicResolvers } from '../resolvers';
|
||||
import { getBreadcrumbWithUrlForApp } from '../breadcrumbs';
|
||||
import { MemoryUsagePage } from '../../memory_usage';
|
||||
|
||||
export const nodesListRouteFactory = (
|
||||
navigateToPath: NavigateToPath,
|
||||
basePath: string
|
||||
): MlRoute => ({
|
||||
path: createPath(ML_PAGES.MEMORY_USAGE),
|
||||
render: (props, deps) => <PageWrapper {...props} deps={deps} />,
|
||||
title: i18n.translate('xpack.ml.modelManagement.memoryUsage.docTitle', {
|
||||
defaultMessage: 'Memory Usage',
|
||||
}),
|
||||
breadcrumbs: [
|
||||
getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath),
|
||||
{
|
||||
text: i18n.translate('xpack.ml.trainedModelsBreadcrumbs.nodeOverviewLabel', {
|
||||
defaultMessage: 'Memory Usage',
|
||||
}),
|
||||
},
|
||||
],
|
||||
enableDatePicker: true,
|
||||
});
|
||||
|
||||
const PageWrapper: FC<PageProps> = ({ location, deps }) => {
|
||||
const { context } = useResolver(
|
||||
undefined,
|
||||
undefined,
|
||||
deps.config,
|
||||
deps.dataViewsContract,
|
||||
deps.getSavedSearchDeps,
|
||||
basicResolvers(deps)
|
||||
);
|
||||
|
||||
return (
|
||||
<PageLoader context={context}>
|
||||
<MemoryUsagePage />
|
||||
</PageLoader>
|
||||
);
|
||||
};
|
|
@ -6,4 +6,3 @@
|
|||
*/
|
||||
|
||||
export * from './models_list';
|
||||
export * from './nodes_list';
|
||||
|
|
|
@ -15,7 +15,7 @@ import { createPath, MlRoute, PageLoader, PageProps } from '../../router';
|
|||
import { useResolver } from '../../use_resolver';
|
||||
import { basicResolvers } from '../../resolvers';
|
||||
import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
|
||||
import { ModelsList } from '../../../trained_models/models_management';
|
||||
import { ModelsList } from '../../../model_management';
|
||||
import { MlPageHeader } from '../../../components/page_header';
|
||||
|
||||
export const modelsListRouteFactory = (
|
||||
|
@ -52,7 +52,6 @@ const PageWrapper: FC<PageProps> = ({ location, deps }) => {
|
|||
);
|
||||
return (
|
||||
<PageLoader context={context}>
|
||||
<ModelsList />
|
||||
<MlPageHeader>
|
||||
<EuiFlexGroup responsive={false} wrap={false} alignItems={'center'} gutterSize={'m'}>
|
||||
<EuiFlexItem grow={false}>
|
||||
|
@ -63,6 +62,7 @@ const PageWrapper: FC<PageProps> = ({ location, deps }) => {
|
|||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</MlPageHeader>
|
||||
<ModelsList />
|
||||
</PageLoader>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,68 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { FC } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { useTimefilter } from '@kbn/ml-date-picker';
|
||||
import { ML_PAGES } from '../../../../locator';
|
||||
import { NavigateToPath } from '../../../contexts/kibana';
|
||||
import { createPath, MlRoute, PageLoader, PageProps } from '../../router';
|
||||
import { useResolver } from '../../use_resolver';
|
||||
import { basicResolvers } from '../../resolvers';
|
||||
import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
|
||||
import { NodesList } from '../../../trained_models/nodes_overview';
|
||||
import { MlPageHeader } from '../../../components/page_header';
|
||||
|
||||
export const nodesListRouteFactory = (
|
||||
navigateToPath: NavigateToPath,
|
||||
basePath: string
|
||||
): MlRoute => ({
|
||||
path: createPath(ML_PAGES.TRAINED_MODELS_NODES),
|
||||
render: (props, deps) => <PageWrapper {...props} deps={deps} />,
|
||||
title: i18n.translate('xpack.ml.modelManagement.nodesOverview.docTitle', {
|
||||
defaultMessage: 'Nodes',
|
||||
}),
|
||||
breadcrumbs: [
|
||||
getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath),
|
||||
getBreadcrumbWithUrlForApp('TRAINED_MODELS', navigateToPath, basePath),
|
||||
{
|
||||
text: i18n.translate('xpack.ml.trainedModelsBreadcrumbs.nodeOverviewLabel', {
|
||||
defaultMessage: 'Nodes',
|
||||
}),
|
||||
},
|
||||
],
|
||||
enableDatePicker: true,
|
||||
});
|
||||
|
||||
const PageWrapper: FC<PageProps> = ({ location, deps }) => {
|
||||
const { context } = useResolver(
|
||||
undefined,
|
||||
undefined,
|
||||
deps.config,
|
||||
deps.dataViewsContract,
|
||||
deps.getSavedSearchDeps,
|
||||
basicResolvers(deps)
|
||||
);
|
||||
useTimefilter({ timeRangeSelector: false, autoRefreshSelector: true });
|
||||
return (
|
||||
<PageLoader context={context}>
|
||||
<MlPageHeader>
|
||||
<EuiFlexGroup responsive={false} wrap={false} alignItems={'center'} gutterSize={'m'}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.modelManagement.nodesOverviewHeader"
|
||||
defaultMessage="Nodes"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</MlPageHeader>
|
||||
<NodesList />
|
||||
</PageLoader>
|
||||
);
|
||||
};
|
|
@ -9,6 +9,7 @@ import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
|||
|
||||
import { useMemo } from 'react';
|
||||
import { HttpFetchQuery } from '@kbn/core/public';
|
||||
import { MlSavedObjectType } from '../../../../common/types/saved_objects';
|
||||
import { HttpService } from '../http_service';
|
||||
import { basePath } from '.';
|
||||
import { useMlKibana } from '../../contexts/kibana';
|
||||
|
@ -17,6 +18,7 @@ import type {
|
|||
ModelPipelines,
|
||||
TrainedModelStat,
|
||||
NodesOverviewResponse,
|
||||
MemoryUsageInfo,
|
||||
} from '../../../../common/types/trained_models';
|
||||
|
||||
export interface InferenceQueryParams {
|
||||
|
@ -119,7 +121,7 @@ export function trainedModelsApiProvider(httpService: HttpService) {
|
|||
|
||||
getTrainedModelsNodesOverview() {
|
||||
return httpService.http<NodesOverviewResponse>({
|
||||
path: `${apiBasePath}/trained_models/nodes_overview`,
|
||||
path: `${apiBasePath}/model_management/nodes_overview`,
|
||||
method: 'GET',
|
||||
});
|
||||
},
|
||||
|
@ -185,6 +187,14 @@ export function trainedModelsApiProvider(httpService: HttpService) {
|
|||
body,
|
||||
});
|
||||
},
|
||||
|
||||
memoryUsage(type?: MlSavedObjectType, node?: string, showClosedJobs = false) {
|
||||
return httpService.http<MemoryUsageInfo[]>({
|
||||
path: `${apiBasePath}/model_management/memory_usage`,
|
||||
method: 'GET',
|
||||
query: { type, node, showClosedJobs },
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,113 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { FC } from 'react';
|
||||
import {
|
||||
EuiDescriptionList,
|
||||
EuiFlexGrid,
|
||||
EuiFlexItem,
|
||||
EuiPanel,
|
||||
EuiSpacer,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { FIELD_FORMAT_IDS } from '@kbn/field-formats-plugin/common';
|
||||
import { css } from '@emotion/react';
|
||||
import { NodeItem } from './nodes_list';
|
||||
import { useListItemsFormatter } from '../models_management/expanded_row';
|
||||
import { AllocatedModels } from './allocated_models';
|
||||
import { useFieldFormatter } from '../../contexts/kibana/use_field_formatter';
|
||||
|
||||
interface ExpandedRowProps {
|
||||
item: NodeItem;
|
||||
}
|
||||
|
||||
export const ExpandedRow: FC<ExpandedRowProps> = ({ item }) => {
|
||||
const bytesFormatter = useFieldFormatter(FIELD_FORMAT_IDS.BYTES);
|
||||
|
||||
const formatToListItems = useListItemsFormatter();
|
||||
|
||||
const {
|
||||
allocated_models: allocatedModels,
|
||||
attributes,
|
||||
memory_overview: memoryOverview,
|
||||
id,
|
||||
...details
|
||||
} = cloneDeep(item);
|
||||
|
||||
// Process node attributes
|
||||
attributes['ml.machine_memory'] = bytesFormatter(attributes['ml.machine_memory']);
|
||||
attributes['ml.max_jvm_size'] = bytesFormatter(attributes['ml.max_jvm_size']);
|
||||
|
||||
return (
|
||||
<div
|
||||
css={css`
|
||||
width: 100%;
|
||||
`}
|
||||
>
|
||||
<EuiFlexGrid columns={2} gutterSize={'s'}>
|
||||
<EuiFlexItem>
|
||||
<EuiPanel>
|
||||
<EuiTitle size={'xs'}>
|
||||
<h5>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.trainedModels.nodesList.expandedRow.detailsTitle"
|
||||
defaultMessage="Details"
|
||||
/>
|
||||
</h5>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size={'m'} />
|
||||
<EuiDescriptionList
|
||||
compressed={true}
|
||||
type="column"
|
||||
listItems={formatToListItems(details)}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem>
|
||||
<EuiPanel>
|
||||
<EuiTitle size={'xs'}>
|
||||
<h5>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.trainedModels.nodesList.expandedRow.attributesTitle"
|
||||
defaultMessage="Attributes"
|
||||
/>
|
||||
</h5>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size={'m'} />
|
||||
<EuiDescriptionList
|
||||
compressed={true}
|
||||
type="column"
|
||||
listItems={formatToListItems(attributes)}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGrid>
|
||||
|
||||
{allocatedModels.length > 0 ? (
|
||||
<>
|
||||
<EuiSpacer size={'s'} />
|
||||
<EuiPanel>
|
||||
<EuiTitle size={'xs'}>
|
||||
<h5>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.trainedModels.nodesList.expandedRow.allocatedModelsTitle"
|
||||
defaultMessage="Allocated models"
|
||||
/>
|
||||
</h5>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size={'m'} />
|
||||
|
||||
<AllocatedModels models={allocatedModels} />
|
||||
</EuiPanel>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -6,10 +6,7 @@
|
|||
*/
|
||||
|
||||
import { setStateToKbnUrl } from '@kbn/kibana-utils-plugin/public';
|
||||
import type {
|
||||
TrainedModelsNodesUrlState,
|
||||
TrainedModelsUrlState,
|
||||
} from '../../../common/types/locator';
|
||||
import type { MemoryUsageUrlState, TrainedModelsUrlState } from '../../../common/types/locator';
|
||||
import { ML_PAGES } from '../../../common/constants/locator';
|
||||
import type { AppPageState, ListingPageUrlState } from '../../../common/types/common';
|
||||
|
||||
|
@ -41,11 +38,11 @@ export function formatTrainedModelsManagementUrl(
|
|||
return url;
|
||||
}
|
||||
|
||||
export function formatTrainedModelsNodesManagementUrl(
|
||||
export function formatMemoryUsageUrl(
|
||||
appBasePath: string,
|
||||
mlUrlGeneratorState: TrainedModelsNodesUrlState['pageState']
|
||||
mlUrlGeneratorState: MemoryUsageUrlState['pageState']
|
||||
): string {
|
||||
let url = `${appBasePath}/${ML_PAGES.TRAINED_MODELS_NODES}`;
|
||||
let url = `${appBasePath}/${ML_PAGES.MEMORY_USAGE}`;
|
||||
if (mlUrlGeneratorState) {
|
||||
const { nodeId } = mlUrlGeneratorState;
|
||||
if (nodeId) {
|
||||
|
@ -54,7 +51,7 @@ export function formatTrainedModelsNodesManagementUrl(
|
|||
};
|
||||
|
||||
const queryState: AppPageState<ListingPageUrlState> = {
|
||||
[ML_PAGES.TRAINED_MODELS_NODES]: nodesListState,
|
||||
[ML_PAGES.MEMORY_USAGE]: nodesListState,
|
||||
};
|
||||
|
||||
url = setStateToKbnUrl<AppPageState<ListingPageUrlState>>(
|
||||
|
|
|
@ -29,7 +29,7 @@ import {
|
|||
} from './formatters';
|
||||
import {
|
||||
formatTrainedModelsManagementUrl,
|
||||
formatTrainedModelsNodesManagementUrl,
|
||||
formatMemoryUsageUrl,
|
||||
} from './formatters/trained_models';
|
||||
|
||||
export type { MlLocatorParams, MlLocator };
|
||||
|
@ -74,8 +74,8 @@ export class MlLocatorDefinition implements LocatorDefinition<MlLocatorParams> {
|
|||
case ML_PAGES.TRAINED_MODELS_MANAGE:
|
||||
path = formatTrainedModelsManagementUrl('', params.pageState);
|
||||
break;
|
||||
case ML_PAGES.TRAINED_MODELS_NODES:
|
||||
path = formatTrainedModelsNodesManagementUrl('', params.pageState);
|
||||
case ML_PAGES.MEMORY_USAGE:
|
||||
path = formatMemoryUsageUrl('', params.pageState);
|
||||
break;
|
||||
case ML_PAGES.ANOMALY_DETECTION_CREATE_JOB:
|
||||
case ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_RECOGNIZER:
|
||||
|
|
|
@ -49,11 +49,11 @@ const MODEL_MANAGEMENT_DEEP_LINK: AppDeepLink = {
|
|||
path: `/${ML_PAGES.TRAINED_MODELS_MANAGE}`,
|
||||
},
|
||||
{
|
||||
id: 'mlNodesOverviewDeepLink',
|
||||
title: i18n.translate('xpack.ml.deepLink.nodesOverview', {
|
||||
defaultMessage: 'Nodes',
|
||||
id: 'mlMemoryUsageDeepLink',
|
||||
title: i18n.translate('xpack.ml.deepLink.memoryUsage', {
|
||||
defaultMessage: 'Memory usage',
|
||||
}),
|
||||
path: `/${ML_PAGES.TRAINED_MODELS_NODES}`,
|
||||
path: `/${ML_PAGES.MEMORY_USAGE}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
@ -6,5 +6,4 @@
|
|||
*/
|
||||
|
||||
export { analyticsAuditMessagesProvider } from './analytics_audit_messages';
|
||||
export { modelsProvider } from './models_provider';
|
||||
export { AnalyticsManager } from './analytics_manager';
|
||||
|
|
|
@ -1,220 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { IScopedClusterClient } from '@kbn/core/server';
|
||||
import { pick } from 'lodash';
|
||||
import {
|
||||
MlTrainedModelStats,
|
||||
NodesInfoNodeInfo,
|
||||
} from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import { isDefined } from '@kbn/ml-is-defined';
|
||||
import type {
|
||||
NodeDeploymentStatsResponse,
|
||||
PipelineDefinition,
|
||||
NodesOverviewResponse,
|
||||
} from '../../../common/types/trained_models';
|
||||
import type { MlClient } from '../../lib/ml_client';
|
||||
import {
|
||||
TrainedModelDeploymentStatsResponse,
|
||||
TrainedModelModelSizeStats,
|
||||
} from '../../../common/types/trained_models';
|
||||
|
||||
export type ModelService = ReturnType<typeof modelsProvider>;
|
||||
|
||||
const NODE_FIELDS = ['attributes', 'name', 'roles'] as const;
|
||||
|
||||
export type RequiredNodeFields = Pick<NodesInfoNodeInfo, typeof NODE_FIELDS[number]>;
|
||||
|
||||
// @ts-expect-error TrainedModelDeploymentStatsResponse missing properties from MlTrainedModelDeploymentStats
|
||||
interface TrainedModelStatsResponse extends MlTrainedModelStats {
|
||||
deployment_stats?: Omit<TrainedModelDeploymentStatsResponse, 'model_id'>;
|
||||
model_size_stats?: TrainedModelModelSizeStats;
|
||||
}
|
||||
|
||||
export interface MemoryStatsResponse {
|
||||
_nodes: { total: number; failed: number; successful: number };
|
||||
cluster_name: string;
|
||||
nodes: Record<
|
||||
string,
|
||||
{
|
||||
jvm: {
|
||||
heap_max_in_bytes: number;
|
||||
java_inference_in_bytes: number;
|
||||
java_inference_max_in_bytes: number;
|
||||
};
|
||||
mem: {
|
||||
adjusted_total_in_bytes: number;
|
||||
total_in_bytes: number;
|
||||
ml: {
|
||||
data_frame_analytics_in_bytes: number;
|
||||
native_code_overhead_in_bytes: number;
|
||||
max_in_bytes: number;
|
||||
anomaly_detectors_in_bytes: number;
|
||||
native_inference_in_bytes: number;
|
||||
};
|
||||
};
|
||||
transport_address: string;
|
||||
roles: string[];
|
||||
name: string;
|
||||
attributes: Record<`${'ml.'}${string}`, string>;
|
||||
ephemeral_id: string;
|
||||
}
|
||||
>;
|
||||
}
|
||||
|
||||
export function modelsProvider(client: IScopedClusterClient, mlClient: MlClient) {
|
||||
return {
|
||||
/**
|
||||
* Retrieves the map of model ids and aliases with associated pipelines.
|
||||
* @param modelIds - Array of models ids and model aliases.
|
||||
*/
|
||||
async getModelsPipelines(modelIds: string[]) {
|
||||
const modelIdsMap = new Map<string, Record<string, PipelineDefinition> | null>(
|
||||
modelIds.map((id: string) => [id, null])
|
||||
);
|
||||
|
||||
try {
|
||||
const body = await client.asCurrentUser.ingest.getPipeline();
|
||||
|
||||
for (const [pipelineName, pipelineDefinition] of Object.entries(body)) {
|
||||
const { processors } = pipelineDefinition as { processors: Array<Record<string, any>> };
|
||||
|
||||
for (const processor of processors) {
|
||||
const id = processor.inference?.model_id;
|
||||
if (modelIdsMap.has(id)) {
|
||||
const obj = modelIdsMap.get(id);
|
||||
if (obj === null) {
|
||||
modelIdsMap.set(id, { [pipelineName]: pipelineDefinition });
|
||||
} else {
|
||||
obj![pipelineName] = pipelineDefinition;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.statusCode === 404) {
|
||||
// ES returns 404 when there are no pipelines
|
||||
// Instead, we should return the modelIdsMap and a 200
|
||||
return modelIdsMap;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
return modelIdsMap;
|
||||
},
|
||||
|
||||
/**
|
||||
* Provides the ML nodes overview with allocated models.
|
||||
*/
|
||||
async getNodesOverview(): Promise<NodesOverviewResponse> {
|
||||
// TODO set node_id to ml:true when elasticsearch client is updated.
|
||||
const response = (await mlClient.getMemoryStats()) as MemoryStatsResponse;
|
||||
|
||||
const { trained_model_stats: trainedModelStats } = await mlClient.getTrainedModelsStats({
|
||||
size: 10000,
|
||||
});
|
||||
|
||||
const mlNodes = Object.entries(response.nodes).filter(([, node]) =>
|
||||
node.roles.includes('ml')
|
||||
);
|
||||
|
||||
const nodeDeploymentStatsResponses: NodeDeploymentStatsResponse[] = mlNodes.map(
|
||||
([nodeId, node]) => {
|
||||
const nodeFields = pick(node, NODE_FIELDS) as RequiredNodeFields;
|
||||
|
||||
nodeFields.attributes = nodeFields.attributes;
|
||||
|
||||
const allocatedModels = (trainedModelStats as TrainedModelStatsResponse[])
|
||||
.filter(
|
||||
(d) =>
|
||||
isDefined(d.deployment_stats) &&
|
||||
isDefined(d.deployment_stats.nodes) &&
|
||||
d.deployment_stats.nodes.some((n) => Object.keys(n.node)[0] === nodeId)
|
||||
)
|
||||
.map((d) => {
|
||||
const modelSizeState = d.model_size_stats;
|
||||
const deploymentStats = d.deployment_stats;
|
||||
|
||||
if (!deploymentStats || !modelSizeState) {
|
||||
throw new Error('deploymentStats or modelSizeState not defined');
|
||||
}
|
||||
|
||||
const { nodes, ...rest } = deploymentStats;
|
||||
|
||||
const { node: tempNode, ...nodeRest } = nodes.find(
|
||||
(v) => Object.keys(v.node)[0] === nodeId
|
||||
)!;
|
||||
return {
|
||||
model_id: d.model_id,
|
||||
...rest,
|
||||
...modelSizeState,
|
||||
node: nodeRest,
|
||||
};
|
||||
});
|
||||
|
||||
const modelsMemoryUsage = allocatedModels.map((v) => {
|
||||
return {
|
||||
model_id: v.model_id,
|
||||
model_size: v.required_native_memory_bytes,
|
||||
};
|
||||
});
|
||||
|
||||
const memoryRes = {
|
||||
adTotalMemory: node.mem.ml.anomaly_detectors_in_bytes,
|
||||
dfaTotalMemory: node.mem.ml.data_frame_analytics_in_bytes,
|
||||
trainedModelsTotalMemory: node.mem.ml.native_inference_in_bytes,
|
||||
};
|
||||
|
||||
for (const key of Object.keys(memoryRes)) {
|
||||
if (memoryRes[key as keyof typeof memoryRes] > 0) {
|
||||
/**
|
||||
* The amount of memory needed to load the ML native code shared libraries. The assumption is that the first
|
||||
* ML job to run on a given node will do this, and then subsequent ML jobs on the same node will reuse the
|
||||
* same already-loaded code.
|
||||
*/
|
||||
memoryRes[key as keyof typeof memoryRes] += node.mem.ml.native_code_overhead_in_bytes;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: nodeId,
|
||||
...nodeFields,
|
||||
allocated_models: allocatedModels,
|
||||
memory_overview: {
|
||||
machine_memory: {
|
||||
total: node.mem.adjusted_total_in_bytes,
|
||||
jvm: node.jvm.heap_max_in_bytes,
|
||||
},
|
||||
anomaly_detection: {
|
||||
total: memoryRes.adTotalMemory,
|
||||
},
|
||||
dfa_training: {
|
||||
total: memoryRes.dfaTotalMemory,
|
||||
},
|
||||
trained_models: {
|
||||
total: memoryRes.trainedModelsTotalMemory,
|
||||
by_model: modelsMemoryUsage,
|
||||
},
|
||||
ml_max_in_bytes: node.mem.ml.max_in_bytes,
|
||||
},
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
// TODO preserve _nodes from the response when getMemoryStats method is updated to support ml:true filter
|
||||
_nodes: {
|
||||
...response._nodes,
|
||||
total: mlNodes.length,
|
||||
successful: mlNodes.length,
|
||||
},
|
||||
nodes: nodeDeploymentStatsResponses,
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export { modelsProvider } from './models_provider';
|
||||
export { MemoryUsageService } from './memory_usage';
|
|
@ -5,16 +5,12 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { MemoryStatsResponse, ModelService, modelsProvider } from './models_provider';
|
||||
import { IScopedClusterClient } from '@kbn/core/server';
|
||||
import { MlClient } from '../../lib/ml_client';
|
||||
import { MemoryUsageService } from './memory_usage';
|
||||
import type { MlClient } from '../../lib/ml_client';
|
||||
import mockResponse from './__mocks__/mock_deployment_response.json';
|
||||
import type { MemoryStatsResponse } from '../../../common/types/trained_models';
|
||||
|
||||
describe('Model service', () => {
|
||||
const client = {
|
||||
asInternalUser: {},
|
||||
} as unknown as jest.Mocked<IScopedClusterClient>;
|
||||
|
||||
const mlClient = {
|
||||
getTrainedModelsStats: jest.fn(() => {
|
||||
return Promise.resolve({
|
||||
|
@ -137,10 +133,10 @@ describe('Model service', () => {
|
|||
}),
|
||||
} as unknown as jest.Mocked<MlClient>;
|
||||
|
||||
let service: ModelService;
|
||||
let service: MemoryUsageService;
|
||||
|
||||
beforeEach(() => {
|
||||
service = modelsProvider(client, mlClient);
|
||||
service = new MemoryUsageService(mlClient);
|
||||
});
|
||||
|
||||
afterEach(() => {});
|
277
x-pack/plugins/ml/server/models/model_management/memory_usage.ts
Normal file
277
x-pack/plugins/ml/server/models/model_management/memory_usage.ts
Normal file
|
@ -0,0 +1,277 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import numeral from '@elastic/numeral';
|
||||
import { pick } from 'lodash';
|
||||
import { isDefined } from '@kbn/ml-is-defined';
|
||||
import type {
|
||||
MemoryUsageInfo,
|
||||
TrainedModelStatsResponse,
|
||||
MemoryStatsResponse,
|
||||
} from '../../../common/types/trained_models';
|
||||
|
||||
import type { JobStats } from '../../../common/types/anomaly_detection_jobs';
|
||||
import type { MlSavedObjectType } from '../../../common/types/saved_objects';
|
||||
import type { MlClient } from '../../lib/ml_client';
|
||||
import type {
|
||||
NodeDeploymentStatsResponse,
|
||||
NodesOverviewResponse,
|
||||
} from '../../../common/types/trained_models';
|
||||
|
||||
// @ts-expect-error numeral missing value
|
||||
const AD_EXTRA_MEMORY = numeral('10MB').value();
|
||||
// @ts-expect-error numeral missing value
|
||||
const DFA_EXTRA_MEMORY = numeral('5MB').value();
|
||||
|
||||
const NODE_FIELDS = ['attributes', 'name', 'roles'] as const;
|
||||
|
||||
export type RequiredNodeFields = Pick<estypes.NodesInfoNodeInfo, typeof NODE_FIELDS[number]>;
|
||||
|
||||
export class MemoryUsageService {
|
||||
constructor(private readonly mlClient: MlClient) {}
|
||||
|
||||
public async getMemorySizes(itemType?: MlSavedObjectType, node?: string, showClosedJobs = false) {
|
||||
let memories: MemoryUsageInfo[] = [];
|
||||
|
||||
switch (itemType) {
|
||||
case 'anomaly-detector':
|
||||
memories = await this.getADJobsSizes();
|
||||
break;
|
||||
case 'data-frame-analytics':
|
||||
memories = await this.getDFAJobsSizes();
|
||||
break;
|
||||
case 'trained-model':
|
||||
memories = await this.getTrainedModelsSizes();
|
||||
break;
|
||||
default:
|
||||
memories = [
|
||||
...(await this.getADJobsSizes()),
|
||||
...(await this.getDFAJobsSizes()),
|
||||
...(await this.getTrainedModelsSizes()),
|
||||
];
|
||||
break;
|
||||
}
|
||||
return memories.filter((m) => nodeFilter(m, node, showClosedJobs));
|
||||
}
|
||||
|
||||
private async getADJobsSizes() {
|
||||
const jobs = await this.mlClient.getJobStats();
|
||||
return jobs.jobs.map(this.getADJobMemorySize);
|
||||
}
|
||||
|
||||
private async getTrainedModelsSizes() {
|
||||
const [models, stats] = await Promise.all([
|
||||
this.mlClient.getTrainedModels(),
|
||||
this.mlClient.getTrainedModelsStats(),
|
||||
]);
|
||||
const statsMap = stats.trained_model_stats.reduce<Record<string, estypes.MlTrainedModelStats>>(
|
||||
(acc, cur) => {
|
||||
acc[cur.model_id] = cur;
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
return models.trained_model_configs.map((m) =>
|
||||
this.getTrainedModelMemorySize(m, statsMap[m.model_id])
|
||||
);
|
||||
}
|
||||
|
||||
private async getDFAJobsSizes() {
|
||||
const [jobs, jobsStats] = await Promise.all([
|
||||
this.mlClient.getDataFrameAnalytics(),
|
||||
this.mlClient.getDataFrameAnalyticsStats(),
|
||||
]);
|
||||
const statsMap = jobsStats.data_frame_analytics.reduce<
|
||||
Record<string, estypes.MlDataframeAnalytics>
|
||||
>((acc, cur) => {
|
||||
acc[cur.id] = cur;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return jobs.data_frame_analytics.map((j) => this.getDFAJobMemorySize(j, statsMap[j.id]));
|
||||
}
|
||||
|
||||
private getADJobMemorySize(jobStats: JobStats): MemoryUsageInfo {
|
||||
let memory = 0;
|
||||
switch (jobStats.model_size_stats.assignment_memory_basis) {
|
||||
case 'model_memory_limit':
|
||||
memory = (jobStats.model_size_stats.model_bytes_memory_limit as number) ?? 0;
|
||||
break;
|
||||
case 'current_model_bytes':
|
||||
memory = jobStats.model_size_stats.model_bytes as number;
|
||||
break;
|
||||
case 'peak_model_bytes':
|
||||
memory = (jobStats.model_size_stats.peak_model_bytes as number) ?? 0;
|
||||
break;
|
||||
}
|
||||
|
||||
const size = memory + AD_EXTRA_MEMORY;
|
||||
const nodeName = jobStats.node?.name;
|
||||
return {
|
||||
id: jobStats.job_id,
|
||||
type: 'anomaly-detector',
|
||||
size,
|
||||
nodeNames: nodeName ? [nodeName] : [],
|
||||
};
|
||||
}
|
||||
|
||||
private getDFAJobMemorySize(
|
||||
job: estypes.MlDataframeAnalyticsSummary,
|
||||
jobStats: estypes.MlDataframeAnalytics
|
||||
): MemoryUsageInfo {
|
||||
const mml = job.model_memory_limit ?? '0mb';
|
||||
// @ts-expect-error numeral missing value
|
||||
const memory = numeral(mml.toUpperCase()).value();
|
||||
const size = memory + DFA_EXTRA_MEMORY;
|
||||
const nodeName = jobStats.node?.name;
|
||||
return {
|
||||
id: jobStats.id,
|
||||
type: 'data-frame-analytics',
|
||||
size,
|
||||
nodeNames: nodeName ? [nodeName] : [],
|
||||
};
|
||||
}
|
||||
|
||||
private getTrainedModelMemorySize(
|
||||
trainedModel: estypes.MlTrainedModelConfig,
|
||||
trainedModelStats: estypes.MlTrainedModelStats
|
||||
): MemoryUsageInfo {
|
||||
const memory = trainedModelStats.model_size_stats.required_native_memory_bytes;
|
||||
|
||||
const size = memory + AD_EXTRA_MEMORY;
|
||||
const nodes = (trainedModelStats.deployment_stats?.nodes ??
|
||||
[]) as estypes.MlTrainedModelDeploymentNodesStats[];
|
||||
return {
|
||||
id: trainedModelStats.model_id,
|
||||
type: 'trained-model',
|
||||
size,
|
||||
nodeNames: nodes.map((n) => Object.values(n.node)[0].name),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the ML nodes overview with allocated models.
|
||||
*/
|
||||
async getNodesOverview(): Promise<NodesOverviewResponse> {
|
||||
// TODO set node_id to ml:true when elasticsearch client is updated.
|
||||
const response = (await this.mlClient.getMemoryStats()) as MemoryStatsResponse;
|
||||
|
||||
const { trained_model_stats: trainedModelStats } = await this.mlClient.getTrainedModelsStats({
|
||||
size: 10000,
|
||||
});
|
||||
|
||||
const mlNodes = Object.entries(response.nodes).filter(([, node]) => node.roles.includes('ml'));
|
||||
|
||||
const nodeDeploymentStatsResponses: NodeDeploymentStatsResponse[] = mlNodes.map(
|
||||
([nodeId, node]) => {
|
||||
const nodeFields = pick(node, NODE_FIELDS) as RequiredNodeFields;
|
||||
|
||||
nodeFields.attributes = nodeFields.attributes;
|
||||
|
||||
const allocatedModels = (trainedModelStats as TrainedModelStatsResponse[])
|
||||
.filter(
|
||||
(d) =>
|
||||
isDefined(d.deployment_stats) &&
|
||||
isDefined(d.deployment_stats.nodes) &&
|
||||
d.deployment_stats.nodes.some((n) => Object.keys(n.node)[0] === nodeId)
|
||||
)
|
||||
.map((d) => {
|
||||
const modelSizeState = d.model_size_stats;
|
||||
const deploymentStats = d.deployment_stats;
|
||||
|
||||
if (!deploymentStats || !modelSizeState) {
|
||||
throw new Error('deploymentStats or modelSizeState not defined');
|
||||
}
|
||||
|
||||
const { nodes, ...rest } = deploymentStats;
|
||||
|
||||
const { node: tempNode, ...nodeRest } = nodes.find(
|
||||
(v) => Object.keys(v.node)[0] === nodeId
|
||||
)!;
|
||||
return {
|
||||
model_id: d.model_id,
|
||||
...rest,
|
||||
...modelSizeState,
|
||||
node: nodeRest,
|
||||
};
|
||||
});
|
||||
|
||||
const modelsMemoryUsage = allocatedModels.map((v) => {
|
||||
return {
|
||||
model_id: v.model_id,
|
||||
model_size: v.required_native_memory_bytes,
|
||||
};
|
||||
});
|
||||
|
||||
const memoryRes = {
|
||||
adTotalMemory: node.mem.ml.anomaly_detectors_in_bytes,
|
||||
dfaTotalMemory: node.mem.ml.data_frame_analytics_in_bytes,
|
||||
trainedModelsTotalMemory: node.mem.ml.native_inference_in_bytes,
|
||||
};
|
||||
|
||||
for (const key of Object.keys(memoryRes)) {
|
||||
if (memoryRes[key as keyof typeof memoryRes] > 0) {
|
||||
/**
|
||||
* The amount of memory needed to load the ML native code shared libraries. The assumption is that the first
|
||||
* ML job to run on a given node will do this, and then subsequent ML jobs on the same node will reuse the
|
||||
* same already-loaded code.
|
||||
*/
|
||||
memoryRes[key as keyof typeof memoryRes] += node.mem.ml.native_code_overhead_in_bytes;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: nodeId,
|
||||
...nodeFields,
|
||||
allocated_models: allocatedModels,
|
||||
memory_overview: {
|
||||
machine_memory: {
|
||||
total: node.mem.adjusted_total_in_bytes,
|
||||
jvm: node.jvm.heap_max_in_bytes,
|
||||
},
|
||||
anomaly_detection: {
|
||||
total: memoryRes.adTotalMemory,
|
||||
},
|
||||
dfa_training: {
|
||||
total: memoryRes.dfaTotalMemory,
|
||||
},
|
||||
trained_models: {
|
||||
total: memoryRes.trainedModelsTotalMemory,
|
||||
by_model: modelsMemoryUsage,
|
||||
},
|
||||
ml_max_in_bytes: node.mem.ml.max_in_bytes,
|
||||
},
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
// TODO preserve _nodes from the response when getMemoryStats method is updated to support ml:true filter
|
||||
_nodes: {
|
||||
...response._nodes,
|
||||
total: mlNodes.length,
|
||||
successful: mlNodes.length,
|
||||
},
|
||||
nodes: nodeDeploymentStatsResponses,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function nodeFilter(m: MemoryUsageInfo, node?: string, showClosedJobs = false) {
|
||||
if (m.nodeNames.length === 0) {
|
||||
return showClosedJobs;
|
||||
}
|
||||
|
||||
if (node === undefined) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return m.nodeNames.includes(node);
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { IScopedClusterClient } from '@kbn/core/server';
|
||||
|
||||
import type { PipelineDefinition } from '../../../common/types/trained_models';
|
||||
|
||||
export type ModelService = ReturnType<typeof modelsProvider>;
|
||||
|
||||
export function modelsProvider(client: IScopedClusterClient) {
|
||||
return {
|
||||
/**
|
||||
* Retrieves the map of model ids and aliases with associated pipelines.
|
||||
* @param modelIds - Array of models ids and model aliases.
|
||||
*/
|
||||
async getModelsPipelines(modelIds: string[]) {
|
||||
const modelIdsMap = new Map<string, Record<string, PipelineDefinition> | null>(
|
||||
modelIds.map((id: string) => [id, null])
|
||||
);
|
||||
|
||||
try {
|
||||
const body = await client.asCurrentUser.ingest.getPipeline();
|
||||
|
||||
for (const [pipelineName, pipelineDefinition] of Object.entries(body)) {
|
||||
const { processors } = pipelineDefinition as { processors: Array<Record<string, any>> };
|
||||
|
||||
for (const processor of processors) {
|
||||
const id = processor.inference?.model_id;
|
||||
if (modelIdsMap.has(id)) {
|
||||
const obj = modelIdsMap.get(id);
|
||||
if (obj === null) {
|
||||
modelIdsMap.set(id, { [pipelineName]: pipelineDefinition });
|
||||
} else {
|
||||
obj![pipelineName] = pipelineDefinition;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.statusCode === 404) {
|
||||
// ES returns 404 when there are no pipelines
|
||||
// Instead, we should return the modelIdsMap and a 200
|
||||
return modelIdsMap;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
return modelIdsMap;
|
||||
},
|
||||
};
|
||||
}
|
|
@ -47,6 +47,7 @@ import { jobServiceRoutes } from './routes/job_service';
|
|||
import { savedObjectsRoutes } from './routes/saved_objects';
|
||||
import { jobValidationRoutes } from './routes/job_validation';
|
||||
import { resultsServiceRoutes } from './routes/results_service';
|
||||
import { modelManagementRoutes } from './routes/model_management';
|
||||
import { systemRoutes } from './routes/system';
|
||||
import { MlLicense } from '../common/license';
|
||||
import { createSharedServices, SharedServices } from './shared_services';
|
||||
|
@ -217,6 +218,7 @@ export class MlServerPlugin
|
|||
jobRoutes(routeInit);
|
||||
jobServiceRoutes(routeInit);
|
||||
managementRoutes(routeInit);
|
||||
modelManagementRoutes(routeInit);
|
||||
resultsServiceRoutes(routeInit);
|
||||
jobValidationRoutes(routeInit);
|
||||
savedObjectsRoutes(routeInit, {
|
||||
|
|
|
@ -170,7 +170,6 @@
|
|||
"GetTrainedModel",
|
||||
"GetTrainedModelStats",
|
||||
"GetTrainedModelStatsById",
|
||||
"GetTrainedModelsNodesOverview",
|
||||
"GetTrainedModelPipelines",
|
||||
"StartTrainedModelDeployment",
|
||||
"UpdateTrainedModelDeployment",
|
||||
|
@ -184,6 +183,10 @@
|
|||
"PreviewAlert",
|
||||
|
||||
"Management",
|
||||
"ManagementList"
|
||||
"ManagementList",
|
||||
|
||||
"ModelManagement",
|
||||
"GetModelManagementNodesOverview",
|
||||
"GetModelManagementMemoryUsage"
|
||||
]
|
||||
}
|
||||
|
|
98
x-pack/plugins/ml/server/routes/model_management.ts
Normal file
98
x-pack/plugins/ml/server/routes/model_management.ts
Normal file
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { RouteInitialization } from '../types';
|
||||
import { wrapError } from '../client/error_wrapper';
|
||||
|
||||
import { MemoryUsageService } from '../models/model_management';
|
||||
import { itemTypeLiterals } from './schemas/saved_objects';
|
||||
|
||||
export function modelManagementRoutes({ router, routeGuard }: RouteInitialization) {
|
||||
/**
|
||||
* @apiGroup ModelManagement
|
||||
*
|
||||
* @api {get} /api/ml/model_management/nodes_overview Get node overview about the models allocation
|
||||
* @apiName GetModelManagementNodesOverview
|
||||
* @apiDescription Retrieves the list of ML nodes with memory breakdown and allocated models info
|
||||
*/
|
||||
router.get(
|
||||
{
|
||||
path: '/api/ml/model_management/nodes_overview',
|
||||
validate: {},
|
||||
options: {
|
||||
tags: [
|
||||
'access:ml:canViewMlNodes',
|
||||
'access:ml:canGetDataFrameAnalytics',
|
||||
'access:ml:canGetJobs',
|
||||
'access:ml:canGetTrainedModels',
|
||||
],
|
||||
},
|
||||
},
|
||||
routeGuard.fullLicenseAPIGuard(async ({ client, mlClient, response }) => {
|
||||
try {
|
||||
const memoryUsageService = new MemoryUsageService(mlClient);
|
||||
const result = await memoryUsageService.getNodesOverview();
|
||||
return response.ok({
|
||||
body: result,
|
||||
});
|
||||
} catch (e) {
|
||||
return response.customError(wrapError(e));
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* @apiGroup ModelManagement
|
||||
*
|
||||
* @api {get} /api/ml/model_management/memory_usage Memory usage for jobs and trained models
|
||||
* @apiName GetModelManagementMemoryUsage
|
||||
* @apiDescription Returns the memory usage for jobs and trained models
|
||||
*/
|
||||
router.get(
|
||||
{
|
||||
path: '/api/ml/model_management/memory_usage',
|
||||
validate: {
|
||||
query: schema.object({
|
||||
type: schema.maybe(itemTypeLiterals),
|
||||
node: schema.maybe(schema.string()),
|
||||
showClosedJobs: schema.maybe(schema.boolean()),
|
||||
}),
|
||||
},
|
||||
options: {
|
||||
tags: [
|
||||
'access:ml:canViewMlNodes',
|
||||
'access:ml:canGetDataFrameAnalytics',
|
||||
'access:ml:canGetJobs',
|
||||
'access:ml:canGetTrainedModels',
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
routeGuard.fullLicenseAPIGuard(async ({ mlClient, response, request }) => {
|
||||
try {
|
||||
const memoryUsageService = new MemoryUsageService(mlClient);
|
||||
return response.ok({
|
||||
body: await memoryUsageService.getMemorySizes(
|
||||
request.query.type,
|
||||
request.query.node,
|
||||
request.query.showClosedJobs
|
||||
),
|
||||
});
|
||||
} catch (e) {
|
||||
return response.customError(wrapError(e));
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
|
@ -19,6 +19,7 @@ export const itemTypeLiterals = schema.oneOf([
|
|||
]);
|
||||
|
||||
export const itemTypeSchema = schema.object({ jobType: itemTypeLiterals });
|
||||
export const jobTypeSchema = schema.object({ jobType: jobTypeLiterals });
|
||||
|
||||
export const updateJobsSpaces = schema.object({
|
||||
jobType: jobTypeLiterals,
|
||||
|
|
|
@ -19,10 +19,11 @@ import {
|
|||
pipelineSimulateBody,
|
||||
updateDeploymentParamsSchema,
|
||||
} from './schemas/inference_schema';
|
||||
import { modelsProvider } from '../models/data_frame_analytics';
|
||||
|
||||
import { TrainedModelConfigResponse } from '../../common/types/trained_models';
|
||||
import { mlLog } from '../lib/log';
|
||||
import { forceQuerySchema } from './schemas/anomaly_detectors_schema';
|
||||
import { modelsProvider } from '../models/model_management';
|
||||
|
||||
export function trainedModelsRoutes({ router, routeGuard }: RouteInitialization) {
|
||||
/**
|
||||
|
@ -68,7 +69,7 @@ export function trainedModelsRoutes({ router, routeGuard }: RouteInitialization)
|
|||
)
|
||||
);
|
||||
|
||||
const pipelinesResponse = await modelsProvider(client, mlClient).getModelsPipelines(
|
||||
const pipelinesResponse = await modelsProvider(client).getModelsPipelines(
|
||||
modelIdsAndAliases
|
||||
);
|
||||
for (const model of result) {
|
||||
|
@ -178,9 +179,7 @@ export function trainedModelsRoutes({ router, routeGuard }: RouteInitialization)
|
|||
routeGuard.fullLicenseAPIGuard(async ({ client, request, mlClient, response }) => {
|
||||
try {
|
||||
const { modelId } = request.params;
|
||||
const result = await modelsProvider(client, mlClient).getModelsPipelines(
|
||||
modelId.split(',')
|
||||
);
|
||||
const result = await modelsProvider(client).getModelsPipelines(modelId.split(','));
|
||||
return response.ok({
|
||||
body: [...result].map(([id, pipelines]) => ({ model_id: id, pipelines })),
|
||||
});
|
||||
|
@ -260,38 +259,6 @@ export function trainedModelsRoutes({ router, routeGuard }: RouteInitialization)
|
|||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* @apiGroup TrainedModels
|
||||
*
|
||||
* @api {get} /api/ml/trained_models/nodes_overview Get node overview about the models allocation
|
||||
* @apiName GetTrainedModelsNodesOverview
|
||||
* @apiDescription Retrieves the list of ML nodes with memory breakdown and allocated models info
|
||||
*/
|
||||
router.get(
|
||||
{
|
||||
path: '/api/ml/trained_models/nodes_overview',
|
||||
validate: {},
|
||||
options: {
|
||||
tags: [
|
||||
'access:ml:canViewMlNodes',
|
||||
'access:ml:canGetDataFrameAnalytics',
|
||||
'access:ml:canGetJobs',
|
||||
'access:ml:canGetTrainedModels',
|
||||
],
|
||||
},
|
||||
},
|
||||
routeGuard.fullLicenseAPIGuard(async ({ client, mlClient, request, response }) => {
|
||||
try {
|
||||
const result = await modelsProvider(client, mlClient).getNodesOverview();
|
||||
return response.ok({
|
||||
body: result,
|
||||
});
|
||||
} catch (e) {
|
||||
return response.customError(wrapError(e));
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* @apiGroup TrainedModels
|
||||
*
|
||||
|
|
|
@ -21232,7 +21232,6 @@
|
|||
"xpack.ml.deepLink.filterListsSettings": "Listes de filtres",
|
||||
"xpack.ml.deepLink.indexDataVisualizer": "Index Data Visualizer (Visualiseur de données pour les index)",
|
||||
"xpack.ml.deepLink.modelManagement": "Gestion des modèles",
|
||||
"xpack.ml.deepLink.nodesOverview": "Nœuds",
|
||||
"xpack.ml.deepLink.overview": "Aperçu",
|
||||
"xpack.ml.deepLink.settings": "Paramètres",
|
||||
"xpack.ml.deepLink.trainedModels": "Modèles entraînés",
|
||||
|
@ -21772,8 +21771,6 @@
|
|||
"xpack.ml.mlEntitySelector.dfaOptionsLabel": "Analyse du cadre de données",
|
||||
"xpack.ml.mlEntitySelector.fetchError": "Impossible de récupérer les entités de ML",
|
||||
"xpack.ml.mlEntitySelector.trainedModelsLabel": "Modèles entraînés",
|
||||
"xpack.ml.modelManagement.nodesOverview.docTitle": "Nœuds",
|
||||
"xpack.ml.modelManagement.nodesOverviewHeader": "Nœuds",
|
||||
"xpack.ml.modelManagement.trainedModels.docTitle": "Modèles entraînés",
|
||||
"xpack.ml.modelManagement.trainedModelsHeader": "Modèles entraînés",
|
||||
"xpack.ml.modelManagementLabel": "Gestion des modèles",
|
||||
|
@ -21884,7 +21881,6 @@
|
|||
"xpack.ml.navMenu.logCategorizationLinkText": "Analyse du modèle de log",
|
||||
"xpack.ml.navMenu.mlAppNameText": "Machine Learning",
|
||||
"xpack.ml.navMenu.modelManagementText": "Gestion des modèles",
|
||||
"xpack.ml.navMenu.nodesOverviewText": "Nœuds",
|
||||
"xpack.ml.navMenu.notificationsTabLinkText": "Notifications",
|
||||
"xpack.ml.navMenu.overviewTabLinkText": "Aperçu",
|
||||
"xpack.ml.navMenu.settingsTabLinkText": "Paramètres",
|
||||
|
|
|
@ -21212,7 +21212,6 @@
|
|||
"xpack.ml.deepLink.filterListsSettings": "フィルターリスト",
|
||||
"xpack.ml.deepLink.indexDataVisualizer": "インデックスデータビジュアライザー",
|
||||
"xpack.ml.deepLink.modelManagement": "モデル管理",
|
||||
"xpack.ml.deepLink.nodesOverview": "ノード",
|
||||
"xpack.ml.deepLink.overview": "概要",
|
||||
"xpack.ml.deepLink.settings": "設定",
|
||||
"xpack.ml.deepLink.trainedModels": "学習済みモデル",
|
||||
|
@ -21752,8 +21751,6 @@
|
|||
"xpack.ml.mlEntitySelector.dfaOptionsLabel": "データフレーム分析",
|
||||
"xpack.ml.mlEntitySelector.fetchError": "MLエンティティを取得できませんでした",
|
||||
"xpack.ml.mlEntitySelector.trainedModelsLabel": "学習済みモデル",
|
||||
"xpack.ml.modelManagement.nodesOverview.docTitle": "ノード",
|
||||
"xpack.ml.modelManagement.nodesOverviewHeader": "ノード",
|
||||
"xpack.ml.modelManagement.trainedModels.docTitle": "学習済みモデル",
|
||||
"xpack.ml.modelManagement.trainedModelsHeader": "学習済みモデル",
|
||||
"xpack.ml.modelManagementLabel": "モデル管理",
|
||||
|
@ -21864,7 +21861,6 @@
|
|||
"xpack.ml.navMenu.logCategorizationLinkText": "ログパターン分析",
|
||||
"xpack.ml.navMenu.mlAppNameText": "機械学習",
|
||||
"xpack.ml.navMenu.modelManagementText": "モデル管理",
|
||||
"xpack.ml.navMenu.nodesOverviewText": "ノード",
|
||||
"xpack.ml.navMenu.notificationsTabLinkText": "通知",
|
||||
"xpack.ml.navMenu.overviewTabLinkText": "概要",
|
||||
"xpack.ml.navMenu.settingsTabLinkText": "設定",
|
||||
|
|
|
@ -21242,7 +21242,6 @@
|
|||
"xpack.ml.deepLink.filterListsSettings": "筛选列表",
|
||||
"xpack.ml.deepLink.indexDataVisualizer": "索引数据可视化工具",
|
||||
"xpack.ml.deepLink.modelManagement": "模型管理",
|
||||
"xpack.ml.deepLink.nodesOverview": "节点",
|
||||
"xpack.ml.deepLink.overview": "概览",
|
||||
"xpack.ml.deepLink.settings": "设置",
|
||||
"xpack.ml.deepLink.trainedModels": "已训练模型",
|
||||
|
@ -21782,8 +21781,6 @@
|
|||
"xpack.ml.mlEntitySelector.dfaOptionsLabel": "数据帧分析",
|
||||
"xpack.ml.mlEntitySelector.fetchError": "无法提取 ML 实体",
|
||||
"xpack.ml.mlEntitySelector.trainedModelsLabel": "已训练模型",
|
||||
"xpack.ml.modelManagement.nodesOverview.docTitle": "节点",
|
||||
"xpack.ml.modelManagement.nodesOverviewHeader": "节点",
|
||||
"xpack.ml.modelManagement.trainedModels.docTitle": "已训练模型",
|
||||
"xpack.ml.modelManagement.trainedModelsHeader": "已训练模型",
|
||||
"xpack.ml.modelManagementLabel": "模型管理",
|
||||
|
@ -21894,7 +21891,6 @@
|
|||
"xpack.ml.navMenu.logCategorizationLinkText": "日志模式分析",
|
||||
"xpack.ml.navMenu.mlAppNameText": "Machine Learning",
|
||||
"xpack.ml.navMenu.modelManagementText": "模型管理",
|
||||
"xpack.ml.navMenu.nodesOverviewText": "节点",
|
||||
"xpack.ml.navMenu.notificationsTabLinkText": "通知",
|
||||
"xpack.ml.navMenu.overviewTabLinkText": "概览",
|
||||
"xpack.ml.navMenu.settingsTabLinkText": "设置",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue