import numeral from '@elastic/numeral' ; import { IScopedClusterClient } from 'kibana/server' ; import { CombinedJob } from '../../../common/types/anomaly_detection_jobs' ; import { validateJobObject } from './validate_job_object' ; import { calculateModelMemoryLimitProvider } from '../calculate_model_memory_limit' ; import { ALLOWED_DATA_UNITS } from '../../../common/constants/validation' ; import { MlInfoResponse } from '../../../common/types/ml_server_info' ; import type { MlClient } from '../../lib/ml_client' ; const MODEL_MEMORY_LIMIT_MINIMUM_BYTES = 1048576 ; export async function validateModelMemoryLimit ( client , mlClient , job , duration ? ) { validateJobObject ( job ) ; const mml = job ?. analysis_limits ?. model_memory_limit ?. toUpperCase ( ) ?? null ; const messages = [ ] ; if ( mml !== null ) { const mmlSplit = mml . match ( /\d+(\w+)/ ) ; const unit = mmlSplit && mmlSplit . length === 2 ? mmlSplit [ 1 ] : null ; if ( unit === null || ! ALLOWED_DATA_UNITS . includes ( unit ) ) { messages . push ( { id : 'mml_value_invalid' , mml , } ) ; return messages ; } } const runCalcModelMemoryTest = duration && duration ?. start !== undefined && duration ?. end !== undefined ; const { body } = await mlClient . info < MlInfoResponse > ( ) ; const maxModelMemoryLimit = body . limits . max_model_memory_limit ?. toUpperCase ( ) ; const effectiveMaxModelMemoryLimit = body . limits . effective_max_model_memory_limit ?. toUpperCase ( ) ; if ( runCalcModelMemoryTest ) { const { modelMemoryLimit } = await calculateModelMemoryLimitProvider ( client , mlClient ) ( job . analysis_config , job . datafeed_config . indices . join ( ',' ) , job . datafeed_config . query , job . data_description . time_field ! , duration ! . start as number , duration ! . end as number , true , job . datafeed_config ) ; const mmlEstimateBytes = numeral ( modelMemoryLimit ) . value ( ) ; let runEstimateGreaterThenMml = true ; if ( typeof maxModelMemoryLimit !== 'undefined' ) { const maxMmlBytes = numeral ( maxModelMemoryLimit ) . value ( ) ; if ( mmlEstimateBytes > maxMmlBytes ) { runEstimateGreaterThenMml = false ; messages . push ( { id : 'estimated_mml_greater_than_max_mml' , maxModelMemoryLimit , modelMemoryLimit , } ) ; } } if ( runEstimateGreaterThenMml && mml !== null ) { const mmlBytes = numeral ( mml ) . value ( ) ; if ( mmlBytes < MODEL_MEMORY_LIMIT_MINIMUM_BYTES ) { messages . push ( { id : 'mml_value_invalid' , mml , } ) ; } else if ( mmlEstimateBytes / 2 > mmlBytes ) { messages . push ( { id : 'half_estimated_mml_greater_than_mml' , maxModelMemoryLimit , mml , } ) ; } else if ( mmlEstimateBytes > mmlBytes ) { messages . push ( { id : 'estimated_mml_greater_than_mml' , maxModelMemoryLimit , mml , } ) ; } } } if ( mml !== null ) { let maxMmlExceeded = false ; const mmlBytes = numeral ( mml ) . value ( ) ; if ( maxModelMemoryLimit !== undefined ) { const maxMmlBytes = numeral ( maxModelMemoryLimit ) . value ( ) ; if ( mmlBytes > maxMmlBytes ) { maxMmlExceeded = true ; messages . push ( { id : 'mml_greater_than_max_mml' , maxModelMemoryLimit , mml , } ) ; } } if ( effectiveMaxModelMemoryLimit !== undefined && maxMmlExceeded === false ) { const effectiveMaxMmlBytes = numeral ( effectiveMaxModelMemoryLimit ) . value ( ) ; if ( mmlBytes > effectiveMaxMmlBytes ) { messages . push ( { id : 'mml_greater_than_effective_max_mml' , maxModelMemoryLimit , mml , effectiveMaxModelMemoryLimit , } ) ; } } } if ( messages . length === 0 && runCalcModelMemoryTest === true ) { messages . push ( { id : 'success_mml' } ) ; } return messages ; }