QTZPOC-20
QTZPOC-20: Implement structured logging with metadata for repository, worker, and OPC activities.
This commit is contained in:
@@ -52,7 +52,7 @@ MODEL_PICKLE_NAME = 'model.pkl'
|
||||
INVALID_FLAVOR_MESSAGE = "Invalid flavor. Use 'sklearn' or 'pyfunc' or 'joblib'."
|
||||
|
||||
|
||||
def force_memory_release(logger: Logger):
|
||||
def force_memory_release(logger: Logger, metadata: dict[str, Any] | None = None):
|
||||
"""Attempt to release memory from the Python process.
|
||||
|
||||
Executes a garbage collection cycle and calls `malloc_trim(0)` on glibc
|
||||
@@ -61,14 +61,17 @@ def force_memory_release(logger: Logger):
|
||||
|
||||
Args:
|
||||
logger (Logger): Logger for observability.
|
||||
metadata (dict[str, Any] | None): Metadata used for structured logging.
|
||||
"""
|
||||
gc.collect()
|
||||
|
||||
metadata = metadata or {}
|
||||
|
||||
try:
|
||||
ctypes.CDLL('libc.so.6').malloc_trim(0)
|
||||
logger.info('Memory released')
|
||||
logger.custom_info('Memory released', metadata)
|
||||
except Exception as e:
|
||||
logger.info(f'Memory release failed: {e}')
|
||||
logger.custom_info(f'Memory release failed: {e}', metadata)
|
||||
|
||||
|
||||
class MLFlowRepository(SientiaMonitoring):
|
||||
@@ -320,7 +323,7 @@ class MLFlowRepository(SientiaMonitoring):
|
||||
rmtree(full_path)
|
||||
makedirs(output_dir, exist_ok=True)
|
||||
|
||||
self.info(f'Downloading artifacts from {run_id} to {output_dir}')
|
||||
self.info(f'Downloading artifacts from {run_id} to {output_dir}', metadata)
|
||||
|
||||
core_labels = self.get_core_labels(metadata, operation_type='download_artifacts')
|
||||
|
||||
@@ -676,7 +679,7 @@ class MLFlowRepository(SientiaMonitoring):
|
||||
model_uri = None if flavor == 'joblib' else f'models:/{model_name}/production'
|
||||
|
||||
if model_uri is not None:
|
||||
self.info(f'Loading prediction model {model_name} from {model_uri}')
|
||||
self.info(f'Loading prediction model {model_name} from {model_uri}', metadata)
|
||||
|
||||
core_labels = self.get_core_labels(metadata, operation_type='load_predict_model')
|
||||
start_time = time.time()
|
||||
@@ -756,7 +759,7 @@ class MLFlowRepository(SientiaMonitoring):
|
||||
)
|
||||
|
||||
if model_uri is not None:
|
||||
self.info(f'Loading data model {model_name} from {model_uri}')
|
||||
self.info(f'Loading data model {model_name} from {model_uri}', metadata)
|
||||
|
||||
core_labels = self.get_core_labels(metadata, operation_type='load_transform_model')
|
||||
start_time = time.time()
|
||||
@@ -822,7 +825,8 @@ class MLFlowRepository(SientiaMonitoring):
|
||||
"""
|
||||
|
||||
self.info(
|
||||
f'Downloading {model_type} model {model_name} with flavor {flavor} and load_wrapper {load_wrapper}'
|
||||
f'Downloading {model_type} model {model_name} with flavor {flavor} and load_wrapper {load_wrapper}',
|
||||
metadata,
|
||||
)
|
||||
|
||||
if model_type not in ['predict', 'transform']:
|
||||
@@ -838,7 +842,10 @@ class MLFlowRepository(SientiaMonitoring):
|
||||
|
||||
if load_wrapper:
|
||||
core_labels = self.get_core_labels(metadata, operation_type='download_model_wrapper')
|
||||
self.info(f'Loading wrapper for {model_type} model {model_name} with flavor {flavor}')
|
||||
self.info(
|
||||
f'Loading wrapper for {model_type} model {model_name} with flavor {flavor}',
|
||||
metadata,
|
||||
)
|
||||
|
||||
try:
|
||||
artifact_path = await self.dowload_artifacts(
|
||||
@@ -846,7 +853,8 @@ class MLFlowRepository(SientiaMonitoring):
|
||||
)
|
||||
|
||||
self.info(
|
||||
f'Model with type {model_type} and name {model_name} is compressed, loading from {artifact_path}'
|
||||
f'Model with type {model_type} and name {model_name} is compressed, loading from {artifact_path}',
|
||||
metadata,
|
||||
)
|
||||
|
||||
raw_model = mlflow.pyfunc.load_model(artifact_path)
|
||||
@@ -959,33 +967,37 @@ class MLFlowRepository(SientiaMonitoring):
|
||||
return False
|
||||
return True
|
||||
|
||||
def handle_valid_model(self, model_name: str, cache: dict) -> dict:
|
||||
def handle_valid_model(self, model_name: str, cache: dict, metadata: dict[str, Any]) -> dict:
|
||||
"""
|
||||
Return the cached model configuration when retention is valid.
|
||||
|
||||
Args:
|
||||
model_name (str): Name of the model (for logging/consistency).
|
||||
cache (dict): Cached model data structure.
|
||||
metadata (dict[str, Any]): Metadata used for structured logging.
|
||||
|
||||
Returns:
|
||||
dict: Model configuration.
|
||||
"""
|
||||
self.debug(f'Model {model_name} is still valid, using cached version')
|
||||
self.debug(f'Model {model_name} is still valid, using cached version', metadata)
|
||||
|
||||
return cache['target']
|
||||
|
||||
def handle_outdated_model(self, model_name: str, model_key: str) -> None:
|
||||
def handle_outdated_model(
|
||||
self, model_name: str, model_key: str, metadata: dict[str, Any]
|
||||
) -> None:
|
||||
"""
|
||||
Clean up outdated cached model and its artifacts.
|
||||
|
||||
Args:
|
||||
model_name (str): Name of the model.
|
||||
model_key (str): Cache key for the model.
|
||||
metadata (dict[str, Any]): Metadata used for structured logging.
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
self.debug(f'Model {model_name} is outdated, downloading a new one')
|
||||
self.debug(f'Model {model_name} is outdated, downloading a new one', metadata)
|
||||
|
||||
del self.model_cache[model_key]['target']
|
||||
del self.model_cache[model_key]
|
||||
@@ -1031,13 +1043,18 @@ class MLFlowRepository(SientiaMonitoring):
|
||||
|
||||
# Check if config has changed or is outdated
|
||||
if self.check_cache_retention(cache, retention):
|
||||
return self.handle_valid_model(model_name=model_name, cache=cache)
|
||||
return self.handle_valid_model(
|
||||
model_name=model_name, cache=cache, metadata=metadata
|
||||
)
|
||||
else:
|
||||
# Model is outdated, delete old model files
|
||||
self.handle_outdated_model(model_name=model_name, model_key=model_key)
|
||||
self.handle_outdated_model(
|
||||
model_name=model_name, model_key=model_key, metadata=metadata
|
||||
)
|
||||
else:
|
||||
self.debug(
|
||||
f'Model {model_name} is not in {model_type} cache, downloading a new one'
|
||||
f'Model {model_name} is not in {model_type} cache, downloading a new one',
|
||||
metadata,
|
||||
)
|
||||
|
||||
# Donwload new model (without lock to avoid blocking other threads)
|
||||
@@ -1113,10 +1130,10 @@ class MLFlowRepository(SientiaMonitoring):
|
||||
prediction = model.predict(data)
|
||||
|
||||
if retention == 0:
|
||||
self.info(f'Deleting model {model_name}:{operation} from memory')
|
||||
self.info(f'Deleting model {model_name}:{operation} from memory', metadata)
|
||||
del model
|
||||
|
||||
force_memory_release(self.logger)
|
||||
force_memory_release(self.logger, metadata)
|
||||
|
||||
return prediction
|
||||
|
||||
@@ -1550,7 +1567,7 @@ class MLFlowRepository(SientiaMonitoring):
|
||||
del data_model['model']
|
||||
del data_model
|
||||
|
||||
force_memory_release(self.logger)
|
||||
force_memory_release(self.logger, metadata)
|
||||
|
||||
return {
|
||||
'run_id': run_id,
|
||||
|
||||
Reference in New Issue
Block a user