mlrun 1.7.2rc3__py3-none-any.whl → 1.8.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of mlrun might be problematic. Click here for more details.
- mlrun/__init__.py +26 -22
- mlrun/__main__.py +15 -16
- mlrun/alerts/alert.py +150 -15
- mlrun/api/schemas/__init__.py +1 -9
- mlrun/artifacts/__init__.py +2 -3
- mlrun/artifacts/base.py +62 -19
- mlrun/artifacts/dataset.py +17 -17
- mlrun/artifacts/document.py +454 -0
- mlrun/artifacts/manager.py +28 -18
- mlrun/artifacts/model.py +91 -59
- mlrun/artifacts/plots.py +2 -2
- mlrun/common/constants.py +8 -0
- mlrun/common/formatters/__init__.py +1 -0
- mlrun/common/formatters/artifact.py +1 -1
- mlrun/common/formatters/feature_set.py +2 -0
- mlrun/common/formatters/function.py +1 -0
- mlrun/{model_monitoring/db/stores/v3io_kv/__init__.py → common/formatters/model_endpoint.py} +17 -0
- mlrun/common/formatters/pipeline.py +1 -2
- mlrun/common/formatters/project.py +9 -0
- mlrun/common/model_monitoring/__init__.py +0 -5
- mlrun/common/model_monitoring/helpers.py +12 -62
- mlrun/common/runtimes/constants.py +25 -4
- mlrun/common/schemas/__init__.py +9 -5
- mlrun/common/schemas/alert.py +114 -19
- mlrun/common/schemas/api_gateway.py +3 -3
- mlrun/common/schemas/artifact.py +22 -9
- mlrun/common/schemas/auth.py +8 -4
- mlrun/common/schemas/background_task.py +7 -7
- mlrun/common/schemas/client_spec.py +4 -4
- mlrun/common/schemas/clusterization_spec.py +2 -2
- mlrun/common/schemas/common.py +53 -3
- mlrun/common/schemas/constants.py +15 -0
- mlrun/common/schemas/datastore_profile.py +1 -1
- mlrun/common/schemas/feature_store.py +9 -9
- mlrun/common/schemas/frontend_spec.py +4 -4
- mlrun/common/schemas/function.py +10 -10
- mlrun/common/schemas/hub.py +1 -1
- mlrun/common/schemas/k8s.py +3 -3
- mlrun/common/schemas/memory_reports.py +3 -3
- mlrun/common/schemas/model_monitoring/__init__.py +4 -8
- mlrun/common/schemas/model_monitoring/constants.py +127 -46
- mlrun/common/schemas/model_monitoring/grafana.py +18 -12
- mlrun/common/schemas/model_monitoring/model_endpoints.py +154 -160
- mlrun/common/schemas/notification.py +24 -3
- mlrun/common/schemas/object.py +1 -1
- mlrun/common/schemas/pagination.py +4 -4
- mlrun/common/schemas/partition.py +142 -0
- mlrun/common/schemas/pipeline.py +3 -3
- mlrun/common/schemas/project.py +26 -18
- mlrun/common/schemas/runs.py +3 -3
- mlrun/common/schemas/runtime_resource.py +5 -5
- mlrun/common/schemas/schedule.py +1 -1
- mlrun/common/schemas/secret.py +1 -1
- mlrun/{model_monitoring/db/stores/sqldb/__init__.py → common/schemas/serving.py} +10 -1
- mlrun/common/schemas/tag.py +3 -3
- mlrun/common/schemas/workflow.py +6 -5
- mlrun/common/types.py +1 -0
- mlrun/config.py +157 -89
- mlrun/data_types/__init__.py +5 -3
- mlrun/data_types/infer.py +13 -3
- mlrun/data_types/spark.py +2 -1
- mlrun/datastore/__init__.py +59 -18
- mlrun/datastore/alibaba_oss.py +4 -1
- mlrun/datastore/azure_blob.py +4 -1
- mlrun/datastore/base.py +19 -24
- mlrun/datastore/datastore.py +10 -4
- mlrun/datastore/datastore_profile.py +178 -45
- mlrun/datastore/dbfs_store.py +4 -1
- mlrun/datastore/filestore.py +4 -1
- mlrun/datastore/google_cloud_storage.py +4 -1
- mlrun/datastore/hdfs.py +4 -1
- mlrun/datastore/inmem.py +4 -1
- mlrun/datastore/redis.py +4 -1
- mlrun/datastore/s3.py +14 -3
- mlrun/datastore/sources.py +89 -92
- mlrun/datastore/store_resources.py +7 -4
- mlrun/datastore/storeytargets.py +51 -16
- mlrun/datastore/targets.py +38 -31
- mlrun/datastore/utils.py +87 -4
- mlrun/datastore/v3io.py +4 -1
- mlrun/datastore/vectorstore.py +291 -0
- mlrun/datastore/wasbfs/fs.py +13 -12
- mlrun/db/base.py +286 -100
- mlrun/db/httpdb.py +1562 -490
- mlrun/db/nopdb.py +250 -83
- mlrun/errors.py +6 -2
- mlrun/execution.py +194 -50
- mlrun/feature_store/__init__.py +2 -10
- mlrun/feature_store/api.py +20 -458
- mlrun/feature_store/common.py +9 -9
- mlrun/feature_store/feature_set.py +20 -18
- mlrun/feature_store/feature_vector.py +105 -479
- mlrun/feature_store/feature_vector_utils.py +466 -0
- mlrun/feature_store/retrieval/base.py +15 -11
- mlrun/feature_store/retrieval/job.py +2 -1
- mlrun/feature_store/retrieval/storey_merger.py +1 -1
- mlrun/feature_store/steps.py +3 -3
- mlrun/features.py +30 -13
- mlrun/frameworks/__init__.py +1 -2
- mlrun/frameworks/_common/__init__.py +1 -2
- mlrun/frameworks/_common/artifacts_library.py +2 -2
- mlrun/frameworks/_common/mlrun_interface.py +10 -6
- mlrun/frameworks/_common/model_handler.py +31 -31
- mlrun/frameworks/_common/producer.py +3 -1
- mlrun/frameworks/_dl_common/__init__.py +1 -2
- mlrun/frameworks/_dl_common/loggers/__init__.py +1 -2
- mlrun/frameworks/_dl_common/loggers/mlrun_logger.py +4 -4
- mlrun/frameworks/_dl_common/loggers/tensorboard_logger.py +3 -3
- mlrun/frameworks/_ml_common/__init__.py +1 -2
- mlrun/frameworks/_ml_common/loggers/__init__.py +1 -2
- mlrun/frameworks/_ml_common/model_handler.py +21 -21
- mlrun/frameworks/_ml_common/plans/__init__.py +1 -2
- mlrun/frameworks/_ml_common/plans/confusion_matrix_plan.py +3 -1
- mlrun/frameworks/_ml_common/plans/dataset_plan.py +3 -3
- mlrun/frameworks/_ml_common/plans/roc_curve_plan.py +4 -4
- mlrun/frameworks/auto_mlrun/__init__.py +1 -2
- mlrun/frameworks/auto_mlrun/auto_mlrun.py +22 -15
- mlrun/frameworks/huggingface/__init__.py +1 -2
- mlrun/frameworks/huggingface/model_server.py +9 -9
- mlrun/frameworks/lgbm/__init__.py +47 -44
- mlrun/frameworks/lgbm/callbacks/__init__.py +1 -2
- mlrun/frameworks/lgbm/callbacks/logging_callback.py +4 -2
- mlrun/frameworks/lgbm/callbacks/mlrun_logging_callback.py +4 -2
- mlrun/frameworks/lgbm/mlrun_interfaces/__init__.py +1 -2
- mlrun/frameworks/lgbm/mlrun_interfaces/mlrun_interface.py +5 -5
- mlrun/frameworks/lgbm/model_handler.py +15 -11
- mlrun/frameworks/lgbm/model_server.py +11 -7
- mlrun/frameworks/lgbm/utils.py +2 -2
- mlrun/frameworks/onnx/__init__.py +1 -2
- mlrun/frameworks/onnx/dataset.py +3 -3
- mlrun/frameworks/onnx/mlrun_interface.py +2 -2
- mlrun/frameworks/onnx/model_handler.py +7 -5
- mlrun/frameworks/onnx/model_server.py +8 -6
- mlrun/frameworks/parallel_coordinates.py +11 -11
- mlrun/frameworks/pytorch/__init__.py +22 -23
- mlrun/frameworks/pytorch/callbacks/__init__.py +1 -2
- mlrun/frameworks/pytorch/callbacks/callback.py +2 -1
- mlrun/frameworks/pytorch/callbacks/logging_callback.py +15 -8
- mlrun/frameworks/pytorch/callbacks/mlrun_logging_callback.py +19 -12
- mlrun/frameworks/pytorch/callbacks/tensorboard_logging_callback.py +22 -15
- mlrun/frameworks/pytorch/callbacks_handler.py +36 -30
- mlrun/frameworks/pytorch/mlrun_interface.py +17 -17
- mlrun/frameworks/pytorch/model_handler.py +21 -17
- mlrun/frameworks/pytorch/model_server.py +13 -9
- mlrun/frameworks/sklearn/__init__.py +19 -18
- mlrun/frameworks/sklearn/estimator.py +2 -2
- mlrun/frameworks/sklearn/metric.py +3 -3
- mlrun/frameworks/sklearn/metrics_library.py +8 -6
- mlrun/frameworks/sklearn/mlrun_interface.py +3 -2
- mlrun/frameworks/sklearn/model_handler.py +4 -3
- mlrun/frameworks/tf_keras/__init__.py +11 -12
- mlrun/frameworks/tf_keras/callbacks/__init__.py +1 -2
- mlrun/frameworks/tf_keras/callbacks/logging_callback.py +17 -14
- mlrun/frameworks/tf_keras/callbacks/mlrun_logging_callback.py +15 -12
- mlrun/frameworks/tf_keras/callbacks/tensorboard_logging_callback.py +21 -18
- mlrun/frameworks/tf_keras/model_handler.py +17 -13
- mlrun/frameworks/tf_keras/model_server.py +12 -8
- mlrun/frameworks/xgboost/__init__.py +19 -18
- mlrun/frameworks/xgboost/model_handler.py +13 -9
- mlrun/k8s_utils.py +2 -5
- mlrun/launcher/base.py +3 -4
- mlrun/launcher/client.py +2 -2
- mlrun/launcher/local.py +6 -2
- mlrun/launcher/remote.py +1 -1
- mlrun/lists.py +8 -4
- mlrun/model.py +132 -46
- mlrun/model_monitoring/__init__.py +3 -5
- mlrun/model_monitoring/api.py +113 -98
- mlrun/model_monitoring/applications/__init__.py +0 -5
- mlrun/model_monitoring/applications/_application_steps.py +81 -50
- mlrun/model_monitoring/applications/base.py +467 -14
- mlrun/model_monitoring/applications/context.py +212 -134
- mlrun/model_monitoring/{db/stores/base → applications/evidently}/__init__.py +6 -2
- mlrun/model_monitoring/applications/evidently/base.py +146 -0
- mlrun/model_monitoring/applications/histogram_data_drift.py +89 -56
- mlrun/model_monitoring/applications/results.py +67 -15
- mlrun/model_monitoring/controller.py +701 -315
- mlrun/model_monitoring/db/__init__.py +0 -2
- mlrun/model_monitoring/db/_schedules.py +242 -0
- mlrun/model_monitoring/db/_stats.py +189 -0
- mlrun/model_monitoring/db/tsdb/__init__.py +33 -22
- mlrun/model_monitoring/db/tsdb/base.py +243 -49
- mlrun/model_monitoring/db/tsdb/tdengine/schemas.py +76 -36
- mlrun/model_monitoring/db/tsdb/tdengine/stream_graph_steps.py +33 -0
- mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connection.py +213 -0
- mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +534 -88
- mlrun/model_monitoring/db/tsdb/v3io/stream_graph_steps.py +1 -0
- mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +436 -106
- mlrun/model_monitoring/helpers.py +356 -114
- mlrun/model_monitoring/stream_processing.py +190 -345
- mlrun/model_monitoring/tracking_policy.py +11 -4
- mlrun/model_monitoring/writer.py +49 -90
- mlrun/package/__init__.py +3 -6
- mlrun/package/context_handler.py +2 -2
- mlrun/package/packager.py +12 -9
- mlrun/package/packagers/__init__.py +0 -2
- mlrun/package/packagers/default_packager.py +14 -11
- mlrun/package/packagers/numpy_packagers.py +16 -7
- mlrun/package/packagers/pandas_packagers.py +18 -18
- mlrun/package/packagers/python_standard_library_packagers.py +25 -11
- mlrun/package/packagers_manager.py +35 -32
- mlrun/package/utils/__init__.py +0 -3
- mlrun/package/utils/_pickler.py +6 -6
- mlrun/platforms/__init__.py +47 -16
- mlrun/platforms/iguazio.py +4 -1
- mlrun/projects/operations.py +30 -30
- mlrun/projects/pipelines.py +116 -47
- mlrun/projects/project.py +1292 -329
- mlrun/render.py +5 -9
- mlrun/run.py +57 -14
- mlrun/runtimes/__init__.py +1 -3
- mlrun/runtimes/base.py +30 -22
- mlrun/runtimes/daskjob.py +9 -9
- mlrun/runtimes/databricks_job/databricks_runtime.py +6 -5
- mlrun/runtimes/function_reference.py +5 -2
- mlrun/runtimes/generators.py +3 -2
- mlrun/runtimes/kubejob.py +6 -7
- mlrun/runtimes/mounts.py +574 -0
- mlrun/runtimes/mpijob/__init__.py +0 -2
- mlrun/runtimes/mpijob/abstract.py +7 -6
- mlrun/runtimes/nuclio/api_gateway.py +7 -7
- mlrun/runtimes/nuclio/application/application.py +11 -13
- mlrun/runtimes/nuclio/application/reverse_proxy.go +66 -64
- mlrun/runtimes/nuclio/function.py +127 -70
- mlrun/runtimes/nuclio/serving.py +105 -37
- mlrun/runtimes/pod.py +159 -54
- mlrun/runtimes/remotesparkjob.py +3 -2
- mlrun/runtimes/sparkjob/__init__.py +0 -2
- mlrun/runtimes/sparkjob/spark3job.py +22 -12
- mlrun/runtimes/utils.py +7 -6
- mlrun/secrets.py +2 -2
- mlrun/serving/__init__.py +8 -0
- mlrun/serving/merger.py +7 -5
- mlrun/serving/remote.py +35 -22
- mlrun/serving/routers.py +186 -240
- mlrun/serving/server.py +41 -10
- mlrun/serving/states.py +432 -118
- mlrun/serving/utils.py +13 -2
- mlrun/serving/v1_serving.py +3 -2
- mlrun/serving/v2_serving.py +161 -203
- mlrun/track/__init__.py +1 -1
- mlrun/track/tracker.py +2 -2
- mlrun/track/trackers/mlflow_tracker.py +6 -5
- mlrun/utils/async_http.py +35 -22
- mlrun/utils/clones.py +7 -4
- mlrun/utils/helpers.py +511 -58
- mlrun/utils/logger.py +119 -13
- mlrun/utils/notifications/notification/__init__.py +22 -19
- mlrun/utils/notifications/notification/base.py +39 -15
- mlrun/utils/notifications/notification/console.py +6 -6
- mlrun/utils/notifications/notification/git.py +11 -11
- mlrun/utils/notifications/notification/ipython.py +10 -9
- mlrun/utils/notifications/notification/mail.py +176 -0
- mlrun/utils/notifications/notification/slack.py +16 -8
- mlrun/utils/notifications/notification/webhook.py +24 -8
- mlrun/utils/notifications/notification_pusher.py +191 -200
- mlrun/utils/regex.py +12 -2
- mlrun/utils/version/version.json +2 -2
- {mlrun-1.7.2rc3.dist-info → mlrun-1.8.0.dist-info}/METADATA +81 -54
- mlrun-1.8.0.dist-info/RECORD +351 -0
- {mlrun-1.7.2rc3.dist-info → mlrun-1.8.0.dist-info}/WHEEL +1 -1
- mlrun/model_monitoring/applications/evidently_base.py +0 -137
- mlrun/model_monitoring/db/stores/__init__.py +0 -136
- mlrun/model_monitoring/db/stores/base/store.py +0 -213
- mlrun/model_monitoring/db/stores/sqldb/models/__init__.py +0 -71
- mlrun/model_monitoring/db/stores/sqldb/models/base.py +0 -190
- mlrun/model_monitoring/db/stores/sqldb/models/mysql.py +0 -103
- mlrun/model_monitoring/db/stores/sqldb/models/sqlite.py +0 -40
- mlrun/model_monitoring/db/stores/sqldb/sql_store.py +0 -659
- mlrun/model_monitoring/db/stores/v3io_kv/kv_store.py +0 -726
- mlrun/model_monitoring/model_endpoint.py +0 -118
- mlrun-1.7.2rc3.dist-info/RECORD +0 -351
- {mlrun-1.7.2rc3.dist-info → mlrun-1.8.0.dist-info}/entry_points.txt +0 -0
- {mlrun-1.7.2rc3.dist-info → mlrun-1.8.0.dist-info/licenses}/LICENSE +0 -0
- {mlrun-1.7.2rc3.dist-info → mlrun-1.8.0.dist-info}/top_level.txt +0 -0
|
@@ -12,7 +12,5 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
-
from .stores import ObjectStoreFactory, get_store_object
|
|
16
|
-
from .stores.base import StoreBase
|
|
17
15
|
from .tsdb import get_tsdb_connector
|
|
18
16
|
from .tsdb.base import TSDBConnector
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
# Copyright 2024 Iguazio
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
from abc import ABC, abstractmethod
|
|
17
|
+
from contextlib import AbstractContextManager
|
|
18
|
+
from types import TracebackType
|
|
19
|
+
from typing import Final, Optional
|
|
20
|
+
|
|
21
|
+
import botocore.exceptions
|
|
22
|
+
|
|
23
|
+
import mlrun.common.schemas as schemas
|
|
24
|
+
import mlrun.errors
|
|
25
|
+
import mlrun.model_monitoring.helpers
|
|
26
|
+
from mlrun.utils import logger
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ModelMonitoringSchedulesFileBase(AbstractContextManager, ABC):
|
|
30
|
+
DEFAULT_SCHEDULES: Final = {}
|
|
31
|
+
INITIAL_CONTENT = json.dumps(DEFAULT_SCHEDULES)
|
|
32
|
+
ENCODING = "utf-8"
|
|
33
|
+
|
|
34
|
+
def __init__(self):
|
|
35
|
+
self._item = self.get_data_item_object()
|
|
36
|
+
if self._item:
|
|
37
|
+
self._path = self._item.url
|
|
38
|
+
self._fs = self._item.store.filesystem
|
|
39
|
+
# `self._schedules` is an in-memory copy of the DB for all the applications for
|
|
40
|
+
# the same model endpoint.
|
|
41
|
+
self._schedules = self.DEFAULT_SCHEDULES.copy()
|
|
42
|
+
# Does `self._schedules` hold the content of `self._item`?
|
|
43
|
+
self._open_schedules = False
|
|
44
|
+
|
|
45
|
+
@abstractmethod
|
|
46
|
+
def get_data_item_object(self) -> mlrun.DataItem:
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
def create(self) -> None:
|
|
50
|
+
"""Create a schedules file with initial content - an empty dictionary"""
|
|
51
|
+
logger.debug("Creating model monitoring schedules file", path=self._item.url)
|
|
52
|
+
self._item.put(self.INITIAL_CONTENT)
|
|
53
|
+
|
|
54
|
+
def delete(self) -> None:
|
|
55
|
+
"""Delete schedules file if it exists"""
|
|
56
|
+
if (
|
|
57
|
+
self._fs is None # In-memory store
|
|
58
|
+
or self._fs.exists(self._path)
|
|
59
|
+
):
|
|
60
|
+
logger.debug(
|
|
61
|
+
"Deleting model monitoring schedules file", path=self._item.url
|
|
62
|
+
)
|
|
63
|
+
self._item.delete()
|
|
64
|
+
else:
|
|
65
|
+
logger.debug(
|
|
66
|
+
"Model monitoring schedules file does not exist, nothing to delete",
|
|
67
|
+
path=self._item.url,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
def _open(self) -> None:
|
|
71
|
+
try:
|
|
72
|
+
content = self._item.get()
|
|
73
|
+
except (
|
|
74
|
+
mlrun.errors.MLRunNotFoundError,
|
|
75
|
+
# Different errors are raised for S3 or local storage, see ML-8042
|
|
76
|
+
botocore.exceptions.ClientError,
|
|
77
|
+
FileNotFoundError,
|
|
78
|
+
) as err:
|
|
79
|
+
if (
|
|
80
|
+
isinstance(err, botocore.exceptions.ClientError)
|
|
81
|
+
# Add a log only to "NoSuchKey" errors codes - equivalent to `FileNotFoundError`
|
|
82
|
+
and err.response["Error"]["Code"] != "NoSuchKey"
|
|
83
|
+
):
|
|
84
|
+
raise
|
|
85
|
+
|
|
86
|
+
logger.exception(
|
|
87
|
+
"The schedules file was not found. It should have been created "
|
|
88
|
+
"as a part of the model endpoint's creation",
|
|
89
|
+
path=self._path,
|
|
90
|
+
)
|
|
91
|
+
raise
|
|
92
|
+
|
|
93
|
+
if isinstance(content, bytes):
|
|
94
|
+
content = content.decode(encoding=self.ENCODING)
|
|
95
|
+
self._schedules = json.loads(content)
|
|
96
|
+
self._open_schedules = True
|
|
97
|
+
|
|
98
|
+
def _close(self) -> None:
|
|
99
|
+
self._item.put(json.dumps(self._schedules))
|
|
100
|
+
self._schedules = self.DEFAULT_SCHEDULES
|
|
101
|
+
self._open_schedules = False
|
|
102
|
+
|
|
103
|
+
def __enter__(self) -> "ModelMonitoringSchedulesFileBase":
|
|
104
|
+
self._open()
|
|
105
|
+
return super().__enter__()
|
|
106
|
+
|
|
107
|
+
def __exit__(
|
|
108
|
+
self,
|
|
109
|
+
exc_type: Optional[type[BaseException]],
|
|
110
|
+
exc_value: Optional[BaseException],
|
|
111
|
+
traceback: Optional[TracebackType],
|
|
112
|
+
) -> Optional[bool]:
|
|
113
|
+
self._close()
|
|
114
|
+
|
|
115
|
+
def _check_open_schedules(self) -> None:
|
|
116
|
+
if not self._open_schedules:
|
|
117
|
+
raise mlrun.errors.MLRunValueError(
|
|
118
|
+
"Open the schedules file as a context manager first"
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class ModelMonitoringSchedulesFileEndpoint(ModelMonitoringSchedulesFileBase):
|
|
123
|
+
def __init__(self, project: str, endpoint_id: str) -> None:
|
|
124
|
+
"""
|
|
125
|
+
Initialize applications monitoring schedules file object.
|
|
126
|
+
The JSON file stores a dictionary of registered application name as key and Unix timestamp as value.
|
|
127
|
+
When working with the schedules data, use this class as a context manager to read and write the data.
|
|
128
|
+
|
|
129
|
+
:param project: The project name.
|
|
130
|
+
:param endpoint_id: The endpoint ID.
|
|
131
|
+
"""
|
|
132
|
+
# `self._item` is the persistent version of the monitoring schedules.
|
|
133
|
+
self._project = project
|
|
134
|
+
self._endpoint_id = endpoint_id
|
|
135
|
+
super().__init__()
|
|
136
|
+
|
|
137
|
+
def get_data_item_object(self) -> mlrun.DataItem:
|
|
138
|
+
return mlrun.model_monitoring.helpers.get_monitoring_schedules_endpoint_data(
|
|
139
|
+
project=self._project, endpoint_id=self._endpoint_id
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
@classmethod
|
|
143
|
+
def from_model_endpoint(
|
|
144
|
+
cls, model_endpoint: schemas.ModelEndpoint
|
|
145
|
+
) -> "ModelMonitoringSchedulesFileEndpoint":
|
|
146
|
+
return cls(
|
|
147
|
+
project=model_endpoint.metadata.project,
|
|
148
|
+
endpoint_id=model_endpoint.metadata.uid,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
def get_application_time(self, application: str) -> Optional[int]:
|
|
152
|
+
self._check_open_schedules()
|
|
153
|
+
return self._schedules.get(application)
|
|
154
|
+
|
|
155
|
+
def update_application_time(self, application: str, timestamp: int) -> None:
|
|
156
|
+
self._check_open_schedules()
|
|
157
|
+
self._schedules[application] = timestamp
|
|
158
|
+
|
|
159
|
+
def get_application_list(self) -> set[str]:
|
|
160
|
+
self._check_open_schedules()
|
|
161
|
+
return set(self._schedules.keys())
|
|
162
|
+
|
|
163
|
+
def get_min_timestamp(self) -> Optional[int]:
|
|
164
|
+
self._check_open_schedules()
|
|
165
|
+
return min(self._schedules.values(), default=None)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class ModelMonitoringSchedulesFileChief(ModelMonitoringSchedulesFileBase):
|
|
169
|
+
def __init__(self, project: str) -> None:
|
|
170
|
+
"""
|
|
171
|
+
Initialize applications monitoring schedules chief file object.
|
|
172
|
+
The JSON file stores a dictionary of registered model endpoints uid as key and point to a dictionary of
|
|
173
|
+
"last_request" and "last_analyzed" mapped to two Unix timestamps as values.
|
|
174
|
+
When working with the schedules data, use this class as a context manager to read and write the data.
|
|
175
|
+
|
|
176
|
+
:param project: The project name.
|
|
177
|
+
"""
|
|
178
|
+
# `self._item` is the persistent version of the monitoring schedules.
|
|
179
|
+
self._project = project
|
|
180
|
+
super().__init__()
|
|
181
|
+
|
|
182
|
+
def get_data_item_object(self) -> mlrun.DataItem:
|
|
183
|
+
return mlrun.model_monitoring.helpers.get_monitoring_schedules_chief_data(
|
|
184
|
+
project=self._project
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
def get_endpoint_last_request(self, endpoint_uid: str) -> Optional[int]:
|
|
188
|
+
self._check_open_schedules()
|
|
189
|
+
if endpoint_uid in self._schedules:
|
|
190
|
+
return self._schedules[endpoint_uid].get(
|
|
191
|
+
schemas.model_monitoring.constants.ScheduleChiefFields.LAST_REQUEST
|
|
192
|
+
)
|
|
193
|
+
else:
|
|
194
|
+
return None
|
|
195
|
+
|
|
196
|
+
def update_endpoint_timestamps(
|
|
197
|
+
self, endpoint_uid: str, last_request: int, last_analyzed: int
|
|
198
|
+
) -> None:
|
|
199
|
+
self._check_open_schedules()
|
|
200
|
+
self._schedules[endpoint_uid] = {
|
|
201
|
+
schemas.model_monitoring.constants.ScheduleChiefFields.LAST_REQUEST: last_request,
|
|
202
|
+
schemas.model_monitoring.constants.ScheduleChiefFields.LAST_ANALYZED: last_analyzed,
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
def get_endpoint_last_analyzed(self, endpoint_uid: str) -> Optional[int]:
|
|
206
|
+
self._check_open_schedules()
|
|
207
|
+
if endpoint_uid in self._schedules:
|
|
208
|
+
return self._schedules[endpoint_uid].get(
|
|
209
|
+
schemas.model_monitoring.constants.ScheduleChiefFields.LAST_ANALYZED
|
|
210
|
+
)
|
|
211
|
+
else:
|
|
212
|
+
return None
|
|
213
|
+
|
|
214
|
+
def get_endpoint_list(self) -> set[str]:
|
|
215
|
+
self._check_open_schedules()
|
|
216
|
+
return set(self._schedules.keys())
|
|
217
|
+
|
|
218
|
+
def get_or_create(self) -> None:
|
|
219
|
+
try:
|
|
220
|
+
self._open()
|
|
221
|
+
except (
|
|
222
|
+
mlrun.errors.MLRunNotFoundError,
|
|
223
|
+
# Different errors are raised for S3 or local storage, see ML-8042
|
|
224
|
+
botocore.exceptions.ClientError,
|
|
225
|
+
FileNotFoundError,
|
|
226
|
+
):
|
|
227
|
+
self.create()
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def delete_model_monitoring_schedules_folder(project: str) -> None:
|
|
231
|
+
"""Delete the model monitoring schedules folder of the project"""
|
|
232
|
+
folder = mlrun.model_monitoring.helpers._get_monitoring_schedules_folder_path(
|
|
233
|
+
project
|
|
234
|
+
)
|
|
235
|
+
fs = mlrun.datastore.store_manager.object(folder).store.filesystem
|
|
236
|
+
if fs and fs.exists(folder):
|
|
237
|
+
logger.debug("Deleting model monitoring schedules folder", folder=folder)
|
|
238
|
+
fs.rm(folder, recursive=True)
|
|
239
|
+
elif fs is None: # In-memory store
|
|
240
|
+
raise mlrun.errors.MLRunValueError(
|
|
241
|
+
"Cannot delete a folder without a file-system"
|
|
242
|
+
)
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# Copyright 2024 Iguazio
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
import abc
|
|
15
|
+
import json
|
|
16
|
+
from abc import abstractmethod
|
|
17
|
+
from datetime import datetime, timezone
|
|
18
|
+
from typing import cast
|
|
19
|
+
|
|
20
|
+
import botocore.exceptions
|
|
21
|
+
import fsspec
|
|
22
|
+
|
|
23
|
+
import mlrun.datastore.base
|
|
24
|
+
from mlrun.common.schemas.model_monitoring.constants import StatsKind
|
|
25
|
+
from mlrun.model_monitoring.helpers import (
|
|
26
|
+
get_monitoring_current_stats_data,
|
|
27
|
+
get_monitoring_drift_measures_data,
|
|
28
|
+
get_monitoring_stats_directory_path,
|
|
29
|
+
)
|
|
30
|
+
from mlrun.utils import logger
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ModelMonitoringStatsFile(abc.ABC):
|
|
34
|
+
"""
|
|
35
|
+
Abstract class
|
|
36
|
+
Initialize applications monitoring stats file object.
|
|
37
|
+
The JSON file stores a dictionary of registered application name as key and Unix timestamp as value.
|
|
38
|
+
When working with the schedules data, use this class as a context manager to read and write the data.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __init__(self, item: mlrun.datastore.base.DataItem, file_type: str):
|
|
42
|
+
self._path = item.url
|
|
43
|
+
self._item = item
|
|
44
|
+
self._file_type = file_type
|
|
45
|
+
self._fs = cast(fsspec.AbstractFileSystem, self._item.store.filesystem)
|
|
46
|
+
|
|
47
|
+
def create(self) -> None:
|
|
48
|
+
"""Create a json file with initial content - an empty dictionary"""
|
|
49
|
+
logger.debug(
|
|
50
|
+
f"Creating model monitoring {self._file_type} file", path=self._item.url
|
|
51
|
+
)
|
|
52
|
+
self._item.put(
|
|
53
|
+
json.dumps(
|
|
54
|
+
{
|
|
55
|
+
"data": dict(),
|
|
56
|
+
"timestamp": mlrun.utils.datetime_now().isoformat(
|
|
57
|
+
sep=" ", timespec="microseconds"
|
|
58
|
+
),
|
|
59
|
+
}
|
|
60
|
+
)
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
def delete(self) -> None:
|
|
64
|
+
"""Delete json file if it exists"""
|
|
65
|
+
if self._fs.exists(self._path):
|
|
66
|
+
logger.debug(
|
|
67
|
+
f"Deleting model monitoring {self._file_type} file", path=self._item.url
|
|
68
|
+
)
|
|
69
|
+
self._item.delete()
|
|
70
|
+
else:
|
|
71
|
+
logger.debug(
|
|
72
|
+
f"Model monitoring {self._file_type} file does not exist, nothing to delete",
|
|
73
|
+
path=self._item.url,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
def read(self) -> tuple[dict, datetime]:
|
|
77
|
+
"""
|
|
78
|
+
Read the stats data and timestamp saved in file
|
|
79
|
+
:return: tuple[dict, str] dictionary with stats data and timestamp saved in file
|
|
80
|
+
"""
|
|
81
|
+
try:
|
|
82
|
+
content = json.loads(self._item.get().decode())
|
|
83
|
+
timestamp = content.get("timestamp")
|
|
84
|
+
if timestamp is not None:
|
|
85
|
+
timestamp = datetime.fromisoformat(timestamp).astimezone(
|
|
86
|
+
tz=timezone.utc
|
|
87
|
+
)
|
|
88
|
+
return content.get("data"), timestamp
|
|
89
|
+
except (
|
|
90
|
+
mlrun.errors.MLRunNotFoundError,
|
|
91
|
+
# Different errors are raised for S3 or local storage, see ML-8042
|
|
92
|
+
botocore.exceptions.ClientError,
|
|
93
|
+
FileNotFoundError,
|
|
94
|
+
) as err:
|
|
95
|
+
if (
|
|
96
|
+
isinstance(err, botocore.exceptions.ClientError)
|
|
97
|
+
# Add a log only to "NoSuchKey" errors codes - equivalent to `FileNotFoundError`
|
|
98
|
+
and err.response["Error"]["Code"] != "NoSuchKey"
|
|
99
|
+
):
|
|
100
|
+
raise
|
|
101
|
+
|
|
102
|
+
logger.exception(
|
|
103
|
+
"The Stats file was not found. It should have been created "
|
|
104
|
+
"as a part of the model endpoint's creation",
|
|
105
|
+
path=self._path,
|
|
106
|
+
error=err,
|
|
107
|
+
)
|
|
108
|
+
raise
|
|
109
|
+
|
|
110
|
+
def write(self, stats: dict, timestamp: datetime) -> None:
|
|
111
|
+
"""
|
|
112
|
+
Write stats data to file overwrite the existing file
|
|
113
|
+
:param stats: dictionary with the stats data
|
|
114
|
+
:param timestamp: datetime object with the timestamp of last entry point for the stats calculation
|
|
115
|
+
"""
|
|
116
|
+
content = {
|
|
117
|
+
"data": stats,
|
|
118
|
+
"timestamp": timestamp.isoformat(sep=" ", timespec="microseconds"),
|
|
119
|
+
}
|
|
120
|
+
self._item.put(json.dumps(content))
|
|
121
|
+
|
|
122
|
+
@classmethod
|
|
123
|
+
@abstractmethod
|
|
124
|
+
def from_model_endpoint(
|
|
125
|
+
cls, model_endpoint: mlrun.common.schemas.ModelEndpoint
|
|
126
|
+
) -> "ModelMonitoringStatsFile":
|
|
127
|
+
"""
|
|
128
|
+
Return ModelMonitoringStatsFile child object using ModelEndpoint metadata
|
|
129
|
+
:param model_endpoint: The current model endpoint to get a stats object for
|
|
130
|
+
:return: ModelMonitoringStatsFile child object instance
|
|
131
|
+
"""
|
|
132
|
+
pass
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class ModelMonitoringCurrentStatsFile(ModelMonitoringStatsFile):
|
|
136
|
+
def __init__(self, project: str, endpoint_id: str) -> None:
|
|
137
|
+
"""
|
|
138
|
+
Initialize File object specific for current stats.
|
|
139
|
+
:param project: (str) Project name
|
|
140
|
+
:param endpoint_id: (str) Endpoint name
|
|
141
|
+
"""
|
|
142
|
+
super().__init__(
|
|
143
|
+
get_monitoring_current_stats_data(project, endpoint_id),
|
|
144
|
+
StatsKind.CURRENT_STATS.value,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
@classmethod
|
|
148
|
+
def from_model_endpoint(
|
|
149
|
+
cls, model_endpoint: mlrun.common.schemas.ModelEndpoint
|
|
150
|
+
) -> "ModelMonitoringCurrentStatsFile":
|
|
151
|
+
return cls(
|
|
152
|
+
project=model_endpoint.metadata.project,
|
|
153
|
+
endpoint_id=model_endpoint.metadata.uid,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class ModelMonitoringDriftMeasuresFile(ModelMonitoringStatsFile):
|
|
158
|
+
def __init__(self, project: str, endpoint_id: str) -> None:
|
|
159
|
+
"""
|
|
160
|
+
Initialize File object specific for drift measures.
|
|
161
|
+
:param project: (str) Project name
|
|
162
|
+
:param endpoint_id: (str) Endpoint name
|
|
163
|
+
"""
|
|
164
|
+
super().__init__(
|
|
165
|
+
get_monitoring_drift_measures_data(project, endpoint_id),
|
|
166
|
+
StatsKind.DRIFT_MEASURES.value,
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
@classmethod
|
|
170
|
+
def from_model_endpoint(
|
|
171
|
+
cls, model_endpoint: mlrun.common.schemas.ModelEndpoint
|
|
172
|
+
) -> "ModelMonitoringDriftMeasuresFile":
|
|
173
|
+
return cls(
|
|
174
|
+
project=model_endpoint.metadata.project,
|
|
175
|
+
endpoint_id=model_endpoint.metadata.uid,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def delete_model_monitoring_stats_folder(project: str) -> None:
|
|
180
|
+
"""Delete the model monitoring schedules folder of the project"""
|
|
181
|
+
folder = get_monitoring_stats_directory_path(project)
|
|
182
|
+
fs = mlrun.datastore.store_manager.object(folder).store.filesystem
|
|
183
|
+
if fs and fs.exists(folder):
|
|
184
|
+
logger.debug("Deleting model monitoring stats folder", folder=folder)
|
|
185
|
+
fs.rm(folder, recursive=True)
|
|
186
|
+
elif fs is None: # In-memory store
|
|
187
|
+
raise mlrun.errors.MLRunValueError(
|
|
188
|
+
"Cannot delete a folder without a file-system"
|
|
189
|
+
)
|
|
@@ -16,7 +16,10 @@ import enum
|
|
|
16
16
|
import typing
|
|
17
17
|
|
|
18
18
|
import mlrun.common.schemas.secret
|
|
19
|
+
import mlrun.datastore.datastore_profile
|
|
19
20
|
import mlrun.errors
|
|
21
|
+
import mlrun.model_monitoring.helpers
|
|
22
|
+
from mlrun.datastore.datastore_profile import DatastoreProfile
|
|
20
23
|
|
|
21
24
|
from .base import TSDBConnector
|
|
22
25
|
|
|
@@ -27,10 +30,13 @@ class ObjectTSDBFactory(enum.Enum):
|
|
|
27
30
|
v3io_tsdb = "v3io-tsdb"
|
|
28
31
|
tdengine = "tdengine"
|
|
29
32
|
|
|
30
|
-
def to_tsdb_connector(
|
|
33
|
+
def to_tsdb_connector(
|
|
34
|
+
self, project: str, profile: DatastoreProfile, **kwargs
|
|
35
|
+
) -> TSDBConnector:
|
|
31
36
|
"""
|
|
32
37
|
Return a TSDBConnector object based on the provided enum value.
|
|
33
38
|
:param project: The name of the project.
|
|
39
|
+
:param profile: Datastore profile containing DSN and credentials for TSDB connection
|
|
34
40
|
:return: `TSDBConnector` object.
|
|
35
41
|
"""
|
|
36
42
|
|
|
@@ -49,7 +55,7 @@ class ObjectTSDBFactory(enum.Enum):
|
|
|
49
55
|
|
|
50
56
|
from .tdengine.tdengine_connector import TDEngineConnector
|
|
51
57
|
|
|
52
|
-
return TDEngineConnector(project=project, **kwargs)
|
|
58
|
+
return TDEngineConnector(project=project, profile=profile, **kwargs)
|
|
53
59
|
|
|
54
60
|
@classmethod
|
|
55
61
|
def _missing_(cls, value: typing.Any):
|
|
@@ -65,41 +71,46 @@ class ObjectTSDBFactory(enum.Enum):
|
|
|
65
71
|
def get_tsdb_connector(
|
|
66
72
|
project: str,
|
|
67
73
|
secret_provider: typing.Optional[typing.Callable[[str], str]] = None,
|
|
68
|
-
|
|
69
|
-
**kwargs,
|
|
74
|
+
profile: typing.Optional[mlrun.datastore.datastore_profile.DatastoreProfile] = None,
|
|
70
75
|
) -> TSDBConnector:
|
|
71
76
|
"""
|
|
72
77
|
Get TSDB connector object.
|
|
73
78
|
:param project: The name of the project.
|
|
74
79
|
:param secret_provider: An optional secret provider to get the connection string secret.
|
|
75
|
-
:param
|
|
80
|
+
:param profile: An optional profile to initialize the TSDB connector from.
|
|
76
81
|
|
|
77
|
-
:return:
|
|
82
|
+
:return: ``TSDBConnector`` object. The main goal of this object is to handle different operations on the
|
|
78
83
|
TSDB connector such as updating drift metrics or write application record result.
|
|
79
|
-
:raise:
|
|
80
|
-
|
|
84
|
+
:raise: ``MLRunNotFoundError`` if the user didn't set the TSDB datastore profile and didn't provide it through
|
|
85
|
+
the ``profile`` parameter.
|
|
86
|
+
:raise: ``MLRunInvalidMMStoreTypeError`` if the TSDB datastore profile is of an invalid type.
|
|
81
87
|
"""
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
tsdb_connection_string
|
|
85
|
-
or mlrun.model_monitoring.helpers.get_tsdb_connection_string(
|
|
86
|
-
secret_provider=secret_provider
|
|
87
|
-
)
|
|
88
|
+
profile = profile or mlrun.model_monitoring.helpers._get_tsdb_profile(
|
|
89
|
+
project=project, secret_provider=secret_provider
|
|
88
90
|
)
|
|
89
|
-
|
|
90
|
-
if
|
|
91
|
-
tsdb_connector_type = mlrun.common.schemas.model_monitoring.TSDBTarget.TDEngine
|
|
92
|
-
kwargs["connection_string"] = tsdb_connection_string
|
|
93
|
-
elif tsdb_connection_string and tsdb_connection_string == "v3io":
|
|
91
|
+
kwargs = {}
|
|
92
|
+
if isinstance(profile, mlrun.datastore.datastore_profile.DatastoreProfileV3io):
|
|
94
93
|
tsdb_connector_type = mlrun.common.schemas.model_monitoring.TSDBTarget.V3IO_TSDB
|
|
94
|
+
elif isinstance(
|
|
95
|
+
profile, mlrun.datastore.datastore_profile.DatastoreProfileTDEngine
|
|
96
|
+
):
|
|
97
|
+
tsdb_connector_type = mlrun.common.schemas.model_monitoring.TSDBTarget.TDEngine
|
|
95
98
|
else:
|
|
99
|
+
extra_message = (
|
|
100
|
+
""
|
|
101
|
+
if profile
|
|
102
|
+
else " by using `project.set_model_monitoring_credentials` API"
|
|
103
|
+
)
|
|
96
104
|
raise mlrun.errors.MLRunInvalidMMStoreTypeError(
|
|
97
|
-
"You must provide a valid
|
|
98
|
-
"
|
|
105
|
+
"You must provide a valid TSDB datastore profile"
|
|
106
|
+
f"{extra_message}. "
|
|
107
|
+
f"Found an unexpected profile of class: {type(profile)}"
|
|
99
108
|
)
|
|
100
109
|
|
|
101
110
|
# Get connector type value from ObjectTSDBFactory enum class
|
|
102
111
|
tsdb_connector_factory = ObjectTSDBFactory(tsdb_connector_type)
|
|
103
112
|
|
|
104
113
|
# Convert into TSDB connector object
|
|
105
|
-
return tsdb_connector_factory.to_tsdb_connector(
|
|
114
|
+
return tsdb_connector_factory.to_tsdb_connector(
|
|
115
|
+
project=project, profile=profile, **kwargs
|
|
116
|
+
)
|