mlrun 1.4.0rc25__py3-none-any.whl → 1.5.0rc2__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 +2 -35
- mlrun/__main__.py +3 -41
- mlrun/api/api/api.py +6 -0
- mlrun/api/api/endpoints/feature_store.py +0 -4
- mlrun/api/api/endpoints/files.py +14 -2
- mlrun/api/api/endpoints/frontend_spec.py +2 -1
- mlrun/api/api/endpoints/functions.py +95 -59
- mlrun/api/api/endpoints/grafana_proxy.py +9 -9
- mlrun/api/api/endpoints/logs.py +17 -3
- mlrun/api/api/endpoints/model_endpoints.py +3 -2
- mlrun/api/api/endpoints/pipelines.py +1 -5
- mlrun/api/api/endpoints/projects.py +88 -0
- mlrun/api/api/endpoints/runs.py +48 -6
- mlrun/api/api/endpoints/submit.py +2 -1
- mlrun/api/api/endpoints/workflows.py +355 -0
- mlrun/api/api/utils.py +3 -4
- mlrun/api/crud/__init__.py +1 -0
- mlrun/api/crud/client_spec.py +6 -2
- mlrun/api/crud/feature_store.py +5 -0
- mlrun/api/crud/model_monitoring/__init__.py +1 -0
- mlrun/api/crud/model_monitoring/deployment.py +497 -0
- mlrun/api/crud/model_monitoring/grafana.py +96 -42
- mlrun/api/crud/model_monitoring/helpers.py +159 -0
- mlrun/api/crud/model_monitoring/model_endpoints.py +202 -476
- mlrun/api/crud/notifications.py +9 -4
- mlrun/api/crud/pipelines.py +6 -11
- mlrun/api/crud/projects.py +2 -2
- mlrun/api/crud/runtime_resources.py +4 -3
- mlrun/api/crud/runtimes/nuclio/helpers.py +5 -1
- mlrun/api/crud/secrets.py +21 -0
- mlrun/api/crud/workflows.py +352 -0
- mlrun/api/db/base.py +16 -1
- mlrun/api/db/init_db.py +2 -4
- mlrun/api/db/session.py +1 -1
- mlrun/api/db/sqldb/db.py +129 -31
- mlrun/api/db/sqldb/models/models_mysql.py +15 -1
- mlrun/api/db/sqldb/models/models_sqlite.py +16 -2
- mlrun/api/launcher.py +38 -6
- mlrun/api/main.py +3 -2
- mlrun/api/rundb/__init__.py +13 -0
- mlrun/{db → api/rundb}/sqldb.py +36 -84
- mlrun/api/runtime_handlers/__init__.py +56 -0
- mlrun/api/runtime_handlers/base.py +1247 -0
- mlrun/api/runtime_handlers/daskjob.py +209 -0
- mlrun/api/runtime_handlers/kubejob.py +37 -0
- mlrun/api/runtime_handlers/mpijob.py +147 -0
- mlrun/api/runtime_handlers/remotesparkjob.py +29 -0
- mlrun/api/runtime_handlers/sparkjob.py +148 -0
- mlrun/api/schemas/__init__.py +17 -6
- mlrun/api/utils/builder.py +1 -4
- mlrun/api/utils/clients/chief.py +14 -0
- mlrun/api/utils/clients/iguazio.py +33 -33
- mlrun/api/utils/clients/nuclio.py +2 -2
- mlrun/api/utils/periodic.py +9 -2
- mlrun/api/utils/projects/follower.py +14 -7
- mlrun/api/utils/projects/leader.py +2 -1
- mlrun/api/utils/projects/remotes/nop_follower.py +2 -2
- mlrun/api/utils/projects/remotes/nop_leader.py +2 -2
- mlrun/api/utils/runtimes/__init__.py +14 -0
- mlrun/api/utils/runtimes/nuclio.py +43 -0
- mlrun/api/utils/scheduler.py +98 -15
- mlrun/api/utils/singletons/db.py +5 -1
- mlrun/api/utils/singletons/project_member.py +4 -1
- mlrun/api/utils/singletons/scheduler.py +1 -1
- mlrun/artifacts/base.py +6 -6
- mlrun/artifacts/dataset.py +4 -4
- mlrun/artifacts/manager.py +2 -3
- mlrun/artifacts/model.py +2 -2
- mlrun/artifacts/plots.py +8 -8
- mlrun/common/db/__init__.py +14 -0
- mlrun/common/helpers.py +37 -0
- mlrun/{mlutils → common/model_monitoring}/__init__.py +3 -2
- mlrun/common/model_monitoring/helpers.py +69 -0
- mlrun/common/schemas/__init__.py +13 -1
- mlrun/common/schemas/auth.py +4 -1
- mlrun/common/schemas/client_spec.py +1 -1
- mlrun/common/schemas/function.py +17 -0
- mlrun/common/schemas/model_monitoring/__init__.py +48 -0
- mlrun/common/{model_monitoring.py → schemas/model_monitoring/constants.py} +11 -23
- mlrun/common/schemas/model_monitoring/grafana.py +55 -0
- mlrun/common/schemas/{model_endpoints.py → model_monitoring/model_endpoints.py} +32 -65
- mlrun/common/schemas/notification.py +1 -0
- mlrun/common/schemas/object.py +4 -0
- mlrun/common/schemas/project.py +1 -0
- mlrun/common/schemas/regex.py +1 -1
- mlrun/common/schemas/runs.py +1 -8
- mlrun/common/schemas/schedule.py +1 -8
- mlrun/common/schemas/workflow.py +54 -0
- mlrun/config.py +45 -42
- mlrun/datastore/__init__.py +21 -0
- mlrun/datastore/base.py +1 -1
- mlrun/datastore/datastore.py +9 -0
- mlrun/datastore/dbfs_store.py +168 -0
- mlrun/datastore/helpers.py +18 -0
- mlrun/datastore/sources.py +1 -0
- mlrun/datastore/store_resources.py +2 -5
- mlrun/datastore/v3io.py +1 -2
- mlrun/db/__init__.py +4 -68
- mlrun/db/base.py +12 -0
- mlrun/db/factory.py +65 -0
- mlrun/db/httpdb.py +175 -20
- mlrun/db/nopdb.py +4 -2
- mlrun/execution.py +4 -2
- mlrun/feature_store/__init__.py +1 -0
- mlrun/feature_store/api.py +1 -2
- mlrun/feature_store/common.py +2 -1
- mlrun/feature_store/feature_set.py +1 -11
- mlrun/feature_store/feature_vector.py +340 -2
- mlrun/feature_store/ingestion.py +5 -10
- mlrun/feature_store/retrieval/base.py +118 -104
- mlrun/feature_store/retrieval/dask_merger.py +17 -10
- mlrun/feature_store/retrieval/job.py +4 -1
- mlrun/feature_store/retrieval/local_merger.py +18 -18
- mlrun/feature_store/retrieval/spark_merger.py +21 -14
- mlrun/feature_store/retrieval/storey_merger.py +22 -16
- mlrun/kfpops.py +3 -9
- mlrun/launcher/base.py +57 -53
- mlrun/launcher/client.py +5 -4
- mlrun/launcher/factory.py +24 -13
- mlrun/launcher/local.py +6 -6
- mlrun/launcher/remote.py +4 -4
- mlrun/lists.py +0 -11
- mlrun/model.py +11 -17
- mlrun/model_monitoring/__init__.py +2 -22
- mlrun/model_monitoring/features_drift_table.py +1 -1
- mlrun/model_monitoring/helpers.py +22 -210
- mlrun/model_monitoring/model_endpoint.py +1 -1
- mlrun/model_monitoring/model_monitoring_batch.py +127 -50
- mlrun/model_monitoring/prometheus.py +219 -0
- mlrun/model_monitoring/stores/__init__.py +16 -11
- mlrun/model_monitoring/stores/kv_model_endpoint_store.py +95 -23
- mlrun/model_monitoring/stores/models/mysql.py +47 -29
- mlrun/model_monitoring/stores/models/sqlite.py +47 -29
- mlrun/model_monitoring/stores/sql_model_endpoint_store.py +31 -19
- mlrun/model_monitoring/{stream_processing_fs.py → stream_processing.py} +206 -64
- mlrun/model_monitoring/tracking_policy.py +104 -0
- mlrun/package/packager.py +6 -8
- mlrun/package/packagers/default_packager.py +121 -10
- mlrun/package/packagers/numpy_packagers.py +1 -1
- mlrun/platforms/__init__.py +0 -2
- mlrun/platforms/iguazio.py +0 -56
- mlrun/projects/pipelines.py +53 -159
- mlrun/projects/project.py +10 -37
- mlrun/render.py +1 -1
- mlrun/run.py +8 -124
- mlrun/runtimes/__init__.py +6 -42
- mlrun/runtimes/base.py +29 -1249
- mlrun/runtimes/daskjob.py +2 -198
- mlrun/runtimes/funcdoc.py +0 -9
- mlrun/runtimes/function.py +25 -29
- mlrun/runtimes/kubejob.py +5 -29
- mlrun/runtimes/local.py +1 -1
- mlrun/runtimes/mpijob/__init__.py +2 -2
- mlrun/runtimes/mpijob/abstract.py +10 -1
- mlrun/runtimes/mpijob/v1.py +0 -76
- mlrun/runtimes/mpijob/v1alpha1.py +1 -74
- mlrun/runtimes/nuclio.py +3 -2
- mlrun/runtimes/pod.py +28 -18
- mlrun/runtimes/remotesparkjob.py +1 -15
- mlrun/runtimes/serving.py +14 -6
- mlrun/runtimes/sparkjob/__init__.py +0 -1
- mlrun/runtimes/sparkjob/abstract.py +4 -131
- mlrun/runtimes/utils.py +0 -26
- mlrun/serving/routers.py +7 -7
- mlrun/serving/server.py +11 -8
- mlrun/serving/states.py +7 -1
- mlrun/serving/v2_serving.py +6 -6
- mlrun/utils/helpers.py +23 -42
- mlrun/utils/notifications/notification/__init__.py +4 -0
- mlrun/utils/notifications/notification/webhook.py +61 -0
- mlrun/utils/notifications/notification_pusher.py +5 -25
- mlrun/utils/regex.py +7 -2
- mlrun/utils/version/version.json +2 -2
- {mlrun-1.4.0rc25.dist-info → mlrun-1.5.0rc2.dist-info}/METADATA +26 -25
- {mlrun-1.4.0rc25.dist-info → mlrun-1.5.0rc2.dist-info}/RECORD +180 -158
- {mlrun-1.4.0rc25.dist-info → mlrun-1.5.0rc2.dist-info}/WHEEL +1 -1
- mlrun/mlutils/data.py +0 -160
- mlrun/mlutils/models.py +0 -78
- mlrun/mlutils/plots.py +0 -902
- mlrun/utils/model_monitoring.py +0 -249
- /mlrun/{api/db/sqldb/session.py → common/db/sql_session.py} +0 -0
- {mlrun-1.4.0rc25.dist-info → mlrun-1.5.0rc2.dist-info}/LICENSE +0 -0
- {mlrun-1.4.0rc25.dist-info → mlrun-1.5.0rc2.dist-info}/entry_points.txt +0 -0
- {mlrun-1.4.0rc25.dist-info → mlrun-1.5.0rc2.dist-info}/top_level.txt +0 -0
|
@@ -12,29 +12,22 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
#
|
|
15
|
-
|
|
15
|
+
|
|
16
16
|
import os
|
|
17
17
|
import typing
|
|
18
18
|
import warnings
|
|
19
19
|
|
|
20
20
|
import sqlalchemy.orm
|
|
21
21
|
|
|
22
|
-
import mlrun.api.api.endpoints.functions
|
|
23
22
|
import mlrun.api.api.utils
|
|
24
|
-
import mlrun.api.crud.
|
|
25
|
-
import mlrun.api.
|
|
23
|
+
import mlrun.api.crud.model_monitoring.deployment
|
|
24
|
+
import mlrun.api.crud.model_monitoring.helpers
|
|
25
|
+
import mlrun.api.crud.secrets
|
|
26
|
+
import mlrun.api.rundb.sqldb
|
|
26
27
|
import mlrun.artifacts
|
|
27
|
-
import mlrun.common.
|
|
28
|
-
import mlrun.common.schemas
|
|
29
|
-
import mlrun.common.schemas.model_endpoints
|
|
30
|
-
import mlrun.config
|
|
31
|
-
import mlrun.datastore.store_resources
|
|
32
|
-
import mlrun.errors
|
|
28
|
+
import mlrun.common.helpers
|
|
29
|
+
import mlrun.common.schemas.model_monitoring
|
|
33
30
|
import mlrun.feature_store
|
|
34
|
-
import mlrun.model_monitoring.helpers
|
|
35
|
-
import mlrun.utils.helpers
|
|
36
|
-
import mlrun.utils.model_monitoring
|
|
37
|
-
import mlrun.utils.v3io_clients
|
|
38
31
|
from mlrun.model_monitoring.stores import get_model_endpoint_store
|
|
39
32
|
from mlrun.utils import logger
|
|
40
33
|
|
|
@@ -115,7 +108,8 @@ class ModelEndpoints:
|
|
|
115
108
|
# Get labels from model object if not found in model endpoint object
|
|
116
109
|
if not model_endpoint.spec.label_names and model_obj.spec.outputs:
|
|
117
110
|
model_label_names = [
|
|
118
|
-
|
|
111
|
+
mlrun.api.crud.model_monitoring.helpers.clean_feature_name(f.name)
|
|
112
|
+
for f in model_obj.spec.outputs
|
|
119
113
|
]
|
|
120
114
|
model_endpoint.spec.label_names = model_label_names
|
|
121
115
|
|
|
@@ -126,7 +120,7 @@ class ModelEndpoints:
|
|
|
126
120
|
# Create monitoring feature set if monitoring found in model endpoint object
|
|
127
121
|
if (
|
|
128
122
|
model_endpoint.spec.monitoring_mode
|
|
129
|
-
== mlrun.common.model_monitoring.ModelMonitoringMode.enabled.value
|
|
123
|
+
== mlrun.common.schemas.model_monitoring.ModelMonitoringMode.enabled.value
|
|
130
124
|
):
|
|
131
125
|
monitoring_feature_set = self.create_monitoring_feature_set(
|
|
132
126
|
model_endpoint, model_obj, db_session, run_db
|
|
@@ -143,7 +137,7 @@ class ModelEndpoints:
|
|
|
143
137
|
logger.info("Feature stats found, cleaning feature names")
|
|
144
138
|
if model_endpoint.spec.feature_names:
|
|
145
139
|
# Validate that the length of feature_stats is equal to the length of feature_names and label_names
|
|
146
|
-
self._validate_length_features_and_labels(model_endpoint)
|
|
140
|
+
self._validate_length_features_and_labels(model_endpoint=model_endpoint)
|
|
147
141
|
|
|
148
142
|
# Clean feature names in both feature_stats and feature_names
|
|
149
143
|
(
|
|
@@ -163,6 +157,9 @@ class ModelEndpoints:
|
|
|
163
157
|
# Write the new model endpoint
|
|
164
158
|
model_endpoint_store = get_model_endpoint_store(
|
|
165
159
|
project=model_endpoint.metadata.project,
|
|
160
|
+
secret_provider=mlrun.api.crud.secrets.get_project_secret_provider(
|
|
161
|
+
project=model_endpoint.metadata.project
|
|
162
|
+
),
|
|
166
163
|
)
|
|
167
164
|
model_endpoint_store.write_model_endpoint(endpoint=model_endpoint.flat_dict())
|
|
168
165
|
|
|
@@ -170,12 +167,51 @@ class ModelEndpoints:
|
|
|
170
167
|
|
|
171
168
|
return model_endpoint
|
|
172
169
|
|
|
173
|
-
def
|
|
170
|
+
def patch_model_endpoint(
|
|
174
171
|
self,
|
|
172
|
+
project: str,
|
|
173
|
+
endpoint_id: str,
|
|
174
|
+
attributes: dict,
|
|
175
|
+
) -> mlrun.common.schemas.ModelEndpoint:
|
|
176
|
+
"""
|
|
177
|
+
Update a model endpoint record with a given attributes.
|
|
178
|
+
|
|
179
|
+
:param project: The name of the project.
|
|
180
|
+
:param endpoint_id: The unique id of the model endpoint.
|
|
181
|
+
:param attributes: Dictionary of attributes that will be used for update the model endpoint. Note that the keys
|
|
182
|
+
of the attributes dictionary should exist in the DB table. More details about the model
|
|
183
|
+
endpoint available attributes can be found under
|
|
184
|
+
:py:class:`~mlrun.common.schemas.ModelEndpoint`.
|
|
185
|
+
|
|
186
|
+
:return: A patched `ModelEndpoint` object.
|
|
187
|
+
"""
|
|
188
|
+
|
|
189
|
+
# Generate a model endpoint store object and apply the update process
|
|
190
|
+
model_endpoint_store = get_model_endpoint_store(
|
|
191
|
+
project=project,
|
|
192
|
+
secret_provider=mlrun.api.crud.secrets.get_project_secret_provider(
|
|
193
|
+
project=project
|
|
194
|
+
),
|
|
195
|
+
)
|
|
196
|
+
model_endpoint_store.update_model_endpoint(
|
|
197
|
+
endpoint_id=endpoint_id, attributes=attributes
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
logger.info("Model endpoint table updated", endpoint_id=endpoint_id)
|
|
201
|
+
|
|
202
|
+
# Get the patched model endpoint record
|
|
203
|
+
model_endpoint_record = model_endpoint_store.get_model_endpoint(
|
|
204
|
+
endpoint_id=endpoint_id,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
return self._convert_into_model_endpoint_object(endpoint=model_endpoint_record)
|
|
208
|
+
|
|
209
|
+
@staticmethod
|
|
210
|
+
def create_monitoring_feature_set(
|
|
175
211
|
model_endpoint: mlrun.common.schemas.ModelEndpoint,
|
|
176
212
|
model_obj: mlrun.artifacts.ModelArtifact,
|
|
177
213
|
db_session: sqlalchemy.orm.Session,
|
|
178
|
-
run_db: mlrun.
|
|
214
|
+
run_db: mlrun.api.rundb.sqldb.SQLRunDB,
|
|
179
215
|
):
|
|
180
216
|
"""
|
|
181
217
|
Create monitoring feature set with the relevant parquet target.
|
|
@@ -190,7 +226,12 @@ class ModelEndpoints:
|
|
|
190
226
|
"""
|
|
191
227
|
|
|
192
228
|
# Define a new feature set
|
|
193
|
-
|
|
229
|
+
(
|
|
230
|
+
_,
|
|
231
|
+
serving_function_name,
|
|
232
|
+
_,
|
|
233
|
+
_,
|
|
234
|
+
) = mlrun.common.helpers.parse_versioned_object_uri(
|
|
194
235
|
model_endpoint.spec.function_uri
|
|
195
236
|
)
|
|
196
237
|
|
|
@@ -198,15 +239,15 @@ class ModelEndpoints:
|
|
|
198
239
|
|
|
199
240
|
feature_set = mlrun.feature_store.FeatureSet(
|
|
200
241
|
f"monitoring-{serving_function_name}-{model_name}",
|
|
201
|
-
entities=[
|
|
202
|
-
timestamp_key=
|
|
242
|
+
entities=[mlrun.common.schemas.model_monitoring.EventFieldType.ENDPOINT_ID],
|
|
243
|
+
timestamp_key=mlrun.common.schemas.model_monitoring.EventFieldType.TIMESTAMP,
|
|
203
244
|
description=f"Monitoring feature set for endpoint: {model_endpoint.spec.model}",
|
|
204
245
|
)
|
|
205
246
|
feature_set.metadata.project = model_endpoint.metadata.project
|
|
206
247
|
|
|
207
248
|
feature_set.metadata.labels = {
|
|
208
|
-
|
|
209
|
-
|
|
249
|
+
mlrun.common.schemas.model_monitoring.EventFieldType.ENDPOINT_ID: model_endpoint.metadata.uid,
|
|
250
|
+
mlrun.common.schemas.model_monitoring.EventFieldType.MODEL_CLASS: model_endpoint.spec.model_class,
|
|
210
251
|
}
|
|
211
252
|
|
|
212
253
|
# Add features to the feature set according to the model object
|
|
@@ -239,14 +280,14 @@ class ModelEndpoints:
|
|
|
239
280
|
|
|
240
281
|
# Define parquet target for this feature set
|
|
241
282
|
parquet_path = (
|
|
242
|
-
|
|
283
|
+
mlrun.api.crud.model_monitoring.helpers.get_monitoring_parquet_path(
|
|
243
284
|
db_session=db_session, project=model_endpoint.metadata.project
|
|
244
285
|
)
|
|
245
286
|
+ f"/key={model_endpoint.metadata.uid}"
|
|
246
287
|
)
|
|
247
288
|
|
|
248
289
|
parquet_target = mlrun.datastore.targets.ParquetTarget(
|
|
249
|
-
|
|
290
|
+
mlrun.common.schemas.model_monitoring.FileTargetKind.PARQUET, parquet_path
|
|
250
291
|
)
|
|
251
292
|
driver = mlrun.datastore.targets.get_target_driver(parquet_target, feature_set)
|
|
252
293
|
|
|
@@ -257,7 +298,6 @@ class ModelEndpoints:
|
|
|
257
298
|
driver.update_resource_status("created")
|
|
258
299
|
|
|
259
300
|
# Save the new feature set
|
|
260
|
-
feature_set._override_run_db(db_session)
|
|
261
301
|
feature_set.save()
|
|
262
302
|
logger.info(
|
|
263
303
|
"Monitoring feature set created",
|
|
@@ -267,125 +307,6 @@ class ModelEndpoints:
|
|
|
267
307
|
|
|
268
308
|
return feature_set
|
|
269
309
|
|
|
270
|
-
@staticmethod
|
|
271
|
-
def _get_monitoring_parquet_path(
|
|
272
|
-
db_session: sqlalchemy.orm.Session, project: str
|
|
273
|
-
) -> str:
|
|
274
|
-
"""Getting model monitoring parquet target for the current project. The parquet target path is based on the
|
|
275
|
-
project artifact path. If project artifact path is not defined, the parquet target path will be based on MLRun
|
|
276
|
-
artifact path.
|
|
277
|
-
|
|
278
|
-
:param db_session: A session that manages the current dialog with the database. Will be used in this function
|
|
279
|
-
to get the project record from DB.
|
|
280
|
-
:param project: Project name.
|
|
281
|
-
|
|
282
|
-
:return: Monitoring parquet target path.
|
|
283
|
-
"""
|
|
284
|
-
|
|
285
|
-
# Get the artifact path from the project record that was stored in the DB
|
|
286
|
-
project_obj = mlrun.api.crud.projects.Projects().get_project(
|
|
287
|
-
session=db_session, name=project
|
|
288
|
-
)
|
|
289
|
-
artifact_path = project_obj.spec.artifact_path
|
|
290
|
-
# Generate monitoring parquet path value
|
|
291
|
-
parquet_path = mlrun.mlconf.get_model_monitoring_file_target_path(
|
|
292
|
-
project=project,
|
|
293
|
-
kind=model_monitoring_constants.FileTargetKind.PARQUET,
|
|
294
|
-
target="offline",
|
|
295
|
-
artifact_path=artifact_path,
|
|
296
|
-
)
|
|
297
|
-
return parquet_path
|
|
298
|
-
|
|
299
|
-
@staticmethod
|
|
300
|
-
def _validate_length_features_and_labels(model_endpoint):
|
|
301
|
-
"""
|
|
302
|
-
Validate that the length of feature_stats is equal to the length of `feature_names` and `label_names`
|
|
303
|
-
|
|
304
|
-
:param model_endpoint: An object representing the model endpoint.
|
|
305
|
-
"""
|
|
306
|
-
|
|
307
|
-
# Getting the length of label names, feature_names and feature_stats
|
|
308
|
-
len_of_label_names = (
|
|
309
|
-
0
|
|
310
|
-
if not model_endpoint.spec.label_names
|
|
311
|
-
else len(model_endpoint.spec.label_names)
|
|
312
|
-
)
|
|
313
|
-
len_of_feature_names = len(model_endpoint.spec.feature_names)
|
|
314
|
-
len_of_feature_stats = len(model_endpoint.status.feature_stats)
|
|
315
|
-
|
|
316
|
-
if len_of_feature_stats != len_of_feature_names + len_of_label_names:
|
|
317
|
-
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
318
|
-
f"The length of model endpoint feature_stats is not equal to the "
|
|
319
|
-
f"length of model endpoint feature names and labels "
|
|
320
|
-
f"feature_stats({len_of_feature_stats}), "
|
|
321
|
-
f"feature_names({len_of_feature_names}),"
|
|
322
|
-
f"label_names({len_of_label_names}"
|
|
323
|
-
)
|
|
324
|
-
|
|
325
|
-
def _adjust_feature_names_and_stats(
|
|
326
|
-
self, model_endpoint
|
|
327
|
-
) -> typing.Tuple[typing.Dict, typing.List]:
|
|
328
|
-
"""
|
|
329
|
-
Create a clean matching version of feature names for both `feature_stats` and `feature_names`. Please note that
|
|
330
|
-
label names exist only in `feature_stats` and `label_names`.
|
|
331
|
-
|
|
332
|
-
:param model_endpoint: An object representing the model endpoint.
|
|
333
|
-
:return: A tuple of:
|
|
334
|
-
[0] = Dictionary of feature stats with cleaned names
|
|
335
|
-
[1] = List of cleaned feature names
|
|
336
|
-
"""
|
|
337
|
-
clean_feature_stats = {}
|
|
338
|
-
clean_feature_names = []
|
|
339
|
-
for i, (feature, stats) in enumerate(
|
|
340
|
-
model_endpoint.status.feature_stats.items()
|
|
341
|
-
):
|
|
342
|
-
clean_name = self._clean_feature_name(feature)
|
|
343
|
-
clean_feature_stats[clean_name] = stats
|
|
344
|
-
# Exclude the label columns from the feature names
|
|
345
|
-
if (
|
|
346
|
-
model_endpoint.spec.label_names
|
|
347
|
-
and clean_name in model_endpoint.spec.label_names
|
|
348
|
-
):
|
|
349
|
-
continue
|
|
350
|
-
clean_feature_names.append(clean_name)
|
|
351
|
-
return clean_feature_stats, clean_feature_names
|
|
352
|
-
|
|
353
|
-
def patch_model_endpoint(
|
|
354
|
-
self,
|
|
355
|
-
project: str,
|
|
356
|
-
endpoint_id: str,
|
|
357
|
-
attributes: dict,
|
|
358
|
-
) -> mlrun.common.schemas.ModelEndpoint:
|
|
359
|
-
"""
|
|
360
|
-
Update a model endpoint record with a given attributes.
|
|
361
|
-
|
|
362
|
-
:param project: The name of the project.
|
|
363
|
-
:param endpoint_id: The unique id of the model endpoint.
|
|
364
|
-
:param attributes: Dictionary of attributes that will be used for update the model endpoint. Note that the keys
|
|
365
|
-
of the attributes dictionary should exist in the DB table. More details about the model
|
|
366
|
-
endpoint available attributes can be found under
|
|
367
|
-
:py:class:`~mlrun.common.schemas.ModelEndpoint`.
|
|
368
|
-
|
|
369
|
-
:return: A patched `ModelEndpoint` object.
|
|
370
|
-
"""
|
|
371
|
-
|
|
372
|
-
# Generate a model endpoint store object and apply the update process
|
|
373
|
-
model_endpoint_store = get_model_endpoint_store(
|
|
374
|
-
project=project,
|
|
375
|
-
)
|
|
376
|
-
model_endpoint_store.update_model_endpoint(
|
|
377
|
-
endpoint_id=endpoint_id, attributes=attributes
|
|
378
|
-
)
|
|
379
|
-
|
|
380
|
-
logger.info("Model endpoint table updated", endpoint_id=endpoint_id)
|
|
381
|
-
|
|
382
|
-
# Get the patched model endpoint record
|
|
383
|
-
model_endpoint_record = model_endpoint_store.get_model_endpoint(
|
|
384
|
-
endpoint_id=endpoint_id,
|
|
385
|
-
)
|
|
386
|
-
|
|
387
|
-
return self._convert_into_model_endpoint_object(endpoint=model_endpoint_record)
|
|
388
|
-
|
|
389
310
|
@staticmethod
|
|
390
311
|
def delete_model_endpoint(
|
|
391
312
|
project: str,
|
|
@@ -399,6 +320,9 @@ class ModelEndpoints:
|
|
|
399
320
|
"""
|
|
400
321
|
model_endpoint_store = get_model_endpoint_store(
|
|
401
322
|
project=project,
|
|
323
|
+
secret_provider=mlrun.api.crud.secrets.get_project_secret_provider(
|
|
324
|
+
project=project
|
|
325
|
+
),
|
|
402
326
|
)
|
|
403
327
|
|
|
404
328
|
model_endpoint_store.delete_model_endpoint(endpoint_id=endpoint_id)
|
|
@@ -447,7 +371,11 @@ class ModelEndpoints:
|
|
|
447
371
|
|
|
448
372
|
# Generate a model endpoint store object and get the model endpoint record as a dictionary
|
|
449
373
|
model_endpoint_store = get_model_endpoint_store(
|
|
450
|
-
project=project,
|
|
374
|
+
project=project,
|
|
375
|
+
access_key=auth_info.data_session,
|
|
376
|
+
secret_provider=mlrun.api.crud.secrets.get_project_secret_provider(
|
|
377
|
+
project=project
|
|
378
|
+
),
|
|
451
379
|
)
|
|
452
380
|
|
|
453
381
|
model_endpoint_record = model_endpoint_store.get_model_endpoint(
|
|
@@ -536,13 +464,15 @@ class ModelEndpoints:
|
|
|
536
464
|
)
|
|
537
465
|
|
|
538
466
|
# Initialize an empty model endpoints list
|
|
539
|
-
endpoint_list = mlrun.common.schemas.
|
|
540
|
-
endpoints=[]
|
|
541
|
-
)
|
|
467
|
+
endpoint_list = mlrun.common.schemas.ModelEndpointList(endpoints=[])
|
|
542
468
|
|
|
543
469
|
# Generate a model endpoint store object and get a list of model endpoint dictionaries
|
|
544
470
|
endpoint_store = get_model_endpoint_store(
|
|
545
|
-
access_key=auth_info.data_session,
|
|
471
|
+
access_key=auth_info.data_session,
|
|
472
|
+
project=project,
|
|
473
|
+
secret_provider=mlrun.api.crud.secrets.get_project_secret_provider(
|
|
474
|
+
project=project
|
|
475
|
+
),
|
|
546
476
|
)
|
|
547
477
|
|
|
548
478
|
endpoint_dictionary_list = endpoint_store.list_model_endpoints(
|
|
@@ -554,7 +484,6 @@ class ModelEndpoints:
|
|
|
554
484
|
)
|
|
555
485
|
|
|
556
486
|
for endpoint_dict in endpoint_dictionary_list:
|
|
557
|
-
|
|
558
487
|
# Convert to `ModelEndpoint` object
|
|
559
488
|
endpoint_obj = self._convert_into_model_endpoint_object(
|
|
560
489
|
endpoint=endpoint_dict
|
|
@@ -575,9 +504,116 @@ class ModelEndpoints:
|
|
|
575
504
|
|
|
576
505
|
return endpoint_list
|
|
577
506
|
|
|
507
|
+
def verify_project_has_no_model_endpoints(self, project_name: str):
|
|
508
|
+
"""Verify that there no model endpoint records in the DB by trying to list all of the project model endpoints.
|
|
509
|
+
This method is usually being used during the process of deleting a project.
|
|
510
|
+
|
|
511
|
+
:param project_name: project name.
|
|
512
|
+
"""
|
|
513
|
+
auth_info = mlrun.common.schemas.AuthInfo(
|
|
514
|
+
data_session=os.getenv("V3IO_ACCESS_KEY")
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
if not mlrun.mlconf.igz_version or not mlrun.mlconf.v3io_api:
|
|
518
|
+
return
|
|
519
|
+
|
|
520
|
+
endpoints = self.list_model_endpoints(auth_info, project_name)
|
|
521
|
+
if endpoints.endpoints:
|
|
522
|
+
raise mlrun.errors.MLRunPreconditionFailedError(
|
|
523
|
+
f"Project {project_name} can not be deleted since related resources found: model endpoints"
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
@staticmethod
|
|
527
|
+
def delete_model_endpoints_resources(project_name: str):
|
|
528
|
+
"""
|
|
529
|
+
Delete all model endpoints resources.
|
|
530
|
+
|
|
531
|
+
:param project_name: The name of the project.
|
|
532
|
+
"""
|
|
533
|
+
auth_info = mlrun.common.schemas.AuthInfo(
|
|
534
|
+
data_session=os.getenv("V3IO_ACCESS_KEY")
|
|
535
|
+
)
|
|
536
|
+
|
|
537
|
+
# We would ideally base on config.v3io_api but can't for backwards compatibility reasons,
|
|
538
|
+
# we're using the igz version heuristic
|
|
539
|
+
if not mlrun.mlconf.igz_version or not mlrun.mlconf.v3io_api:
|
|
540
|
+
return
|
|
541
|
+
|
|
542
|
+
# Generate a model endpoint store object and get a list of model endpoint dictionaries
|
|
543
|
+
endpoint_store = get_model_endpoint_store(
|
|
544
|
+
access_key=auth_info.data_session,
|
|
545
|
+
project=project_name,
|
|
546
|
+
secret_provider=mlrun.api.crud.secrets.get_project_secret_provider(
|
|
547
|
+
project=project_name
|
|
548
|
+
),
|
|
549
|
+
)
|
|
550
|
+
endpoints = endpoint_store.list_model_endpoints()
|
|
551
|
+
|
|
552
|
+
# Delete model endpoints resources from databases using the model endpoint store object
|
|
553
|
+
endpoint_store.delete_model_endpoints_resources(endpoints)
|
|
554
|
+
|
|
555
|
+
@staticmethod
|
|
556
|
+
def _validate_length_features_and_labels(
|
|
557
|
+
model_endpoint: mlrun.common.schemas.ModelEndpoint,
|
|
558
|
+
):
|
|
559
|
+
"""
|
|
560
|
+
Validate that the length of feature_stats is equal to the length of `feature_names` and `label_names`
|
|
561
|
+
|
|
562
|
+
:param model_endpoint: An object representing the model endpoint.
|
|
563
|
+
"""
|
|
564
|
+
|
|
565
|
+
# Getting the length of label names, feature_names and feature_stats
|
|
566
|
+
len_of_label_names = (
|
|
567
|
+
0
|
|
568
|
+
if not model_endpoint.spec.label_names
|
|
569
|
+
else len(model_endpoint.spec.label_names)
|
|
570
|
+
)
|
|
571
|
+
len_of_feature_names = len(model_endpoint.spec.feature_names)
|
|
572
|
+
len_of_feature_stats = len(model_endpoint.status.feature_stats)
|
|
573
|
+
|
|
574
|
+
if len_of_feature_stats != len_of_feature_names + len_of_label_names:
|
|
575
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
576
|
+
f"The length of model endpoint feature_stats is not equal to the "
|
|
577
|
+
f"length of model endpoint feature names and labels "
|
|
578
|
+
f"feature_stats({len_of_feature_stats}), "
|
|
579
|
+
f"feature_names({len_of_feature_names}),"
|
|
580
|
+
f"label_names({len_of_label_names}"
|
|
581
|
+
)
|
|
582
|
+
|
|
583
|
+
@staticmethod
|
|
584
|
+
def _adjust_feature_names_and_stats(
|
|
585
|
+
model_endpoint,
|
|
586
|
+
) -> typing.Tuple[typing.Dict, typing.List]:
|
|
587
|
+
"""
|
|
588
|
+
Create a clean matching version of feature names for both `feature_stats` and `feature_names`. Please note that
|
|
589
|
+
label names exist only in `feature_stats` and `label_names`.
|
|
590
|
+
|
|
591
|
+
:param model_endpoint: An object representing the model endpoint.
|
|
592
|
+
:return: A tuple of:
|
|
593
|
+
[0] = Dictionary of feature stats with cleaned names
|
|
594
|
+
[1] = List of cleaned feature names
|
|
595
|
+
"""
|
|
596
|
+
clean_feature_stats = {}
|
|
597
|
+
clean_feature_names = []
|
|
598
|
+
for i, (feature, stats) in enumerate(
|
|
599
|
+
model_endpoint.status.feature_stats.items()
|
|
600
|
+
):
|
|
601
|
+
clean_name = mlrun.api.crud.model_monitoring.helpers.clean_feature_name(
|
|
602
|
+
feature
|
|
603
|
+
)
|
|
604
|
+
clean_feature_stats[clean_name] = stats
|
|
605
|
+
# Exclude the label columns from the feature names
|
|
606
|
+
if (
|
|
607
|
+
model_endpoint.spec.label_names
|
|
608
|
+
and clean_name in model_endpoint.spec.label_names
|
|
609
|
+
):
|
|
610
|
+
continue
|
|
611
|
+
clean_feature_names.append(clean_name)
|
|
612
|
+
return clean_feature_stats, clean_feature_names
|
|
613
|
+
|
|
578
614
|
@staticmethod
|
|
579
615
|
def _add_real_time_metrics(
|
|
580
|
-
model_endpoint_store: mlrun.model_monitoring.
|
|
616
|
+
model_endpoint_store: mlrun.model_monitoring.ModelEndpointStore,
|
|
581
617
|
model_endpoint_object: mlrun.common.schemas.ModelEndpoint,
|
|
582
618
|
metrics: typing.List[str] = None,
|
|
583
619
|
start: str = "now-1h",
|
|
@@ -616,22 +652,23 @@ class ModelEndpoints:
|
|
|
616
652
|
)
|
|
617
653
|
if endpoint_metrics:
|
|
618
654
|
model_endpoint_object.status.metrics[
|
|
619
|
-
|
|
655
|
+
mlrun.common.schemas.model_monitoring.EventKeyMetrics.REAL_TIME
|
|
620
656
|
] = endpoint_metrics
|
|
621
657
|
return model_endpoint_object
|
|
622
658
|
|
|
659
|
+
@staticmethod
|
|
623
660
|
def _convert_into_model_endpoint_object(
|
|
624
|
-
|
|
661
|
+
endpoint: typing.Dict[str, typing.Any], feature_analysis: bool = False
|
|
625
662
|
) -> mlrun.common.schemas.ModelEndpoint:
|
|
626
663
|
"""
|
|
627
664
|
Create a `ModelEndpoint` object according to a provided model endpoint dictionary.
|
|
628
665
|
|
|
629
|
-
:param endpoint:
|
|
666
|
+
:param endpoint: Dictionary that represents a DB record of a model endpoint which need to be converted
|
|
630
667
|
into a valid `ModelEndpoint` object.
|
|
631
668
|
:param feature_analysis: When True, the base feature statistics and current feature statistics will be added to
|
|
632
669
|
the output of the resulting object.
|
|
633
670
|
|
|
634
|
-
:return: A
|
|
671
|
+
:return: A `~mlrun.common.schemas.ModelEndpoint` object.
|
|
635
672
|
"""
|
|
636
673
|
|
|
637
674
|
# Convert into `ModelEndpoint` object
|
|
@@ -639,332 +676,21 @@ class ModelEndpoints:
|
|
|
639
676
|
|
|
640
677
|
# If feature analysis was applied, add feature stats and current stats to the model endpoint result
|
|
641
678
|
if feature_analysis and endpoint_obj.spec.feature_names:
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
679
|
+
endpoint_features = (
|
|
680
|
+
mlrun.api.crud.model_monitoring.deployment.get_endpoint_features(
|
|
681
|
+
feature_names=endpoint_obj.spec.feature_names,
|
|
682
|
+
feature_stats=endpoint_obj.status.feature_stats,
|
|
683
|
+
current_stats=endpoint_obj.status.current_stats,
|
|
684
|
+
)
|
|
647
685
|
)
|
|
648
686
|
if endpoint_features:
|
|
649
687
|
endpoint_obj.status.features = endpoint_features
|
|
650
688
|
# Add the latest drift measures results (calculated by the model monitoring batch)
|
|
651
|
-
drift_measures =
|
|
689
|
+
drift_measures = mlrun.api.crud.model_monitoring.helpers.json_loads_if_not_none(
|
|
652
690
|
endpoint.get(
|
|
653
|
-
|
|
691
|
+
mlrun.common.schemas.model_monitoring.EventFieldType.DRIFT_MEASURES
|
|
654
692
|
)
|
|
655
693
|
)
|
|
656
694
|
endpoint_obj.status.drift_measures = drift_measures
|
|
657
695
|
|
|
658
696
|
return endpoint_obj
|
|
659
|
-
|
|
660
|
-
@staticmethod
|
|
661
|
-
def get_endpoint_features(
|
|
662
|
-
feature_names: typing.List[str],
|
|
663
|
-
feature_stats: dict = None,
|
|
664
|
-
current_stats: dict = None,
|
|
665
|
-
) -> typing.List[mlrun.common.schemas.Features]:
|
|
666
|
-
"""
|
|
667
|
-
Getting a new list of features that exist in feature_names along with their expected (feature_stats) and
|
|
668
|
-
actual (current_stats) stats. The expected stats were calculated during the creation of the model endpoint,
|
|
669
|
-
usually based on the data from the Model Artifact. The actual stats are based on the results from the latest
|
|
670
|
-
model monitoring batch job.
|
|
671
|
-
|
|
672
|
-
param feature_names: List of feature names.
|
|
673
|
-
param feature_stats: Dictionary of feature stats that were stored during the creation of the model endpoint
|
|
674
|
-
object.
|
|
675
|
-
param current_stats: Dictionary of the latest stats that were stored during the last run of the model monitoring
|
|
676
|
-
batch job.
|
|
677
|
-
|
|
678
|
-
return: List of feature objects. Each feature has a name, weight, expected values, and actual values. More info
|
|
679
|
-
can be found under `mlrun.common.schemas.Features`.
|
|
680
|
-
"""
|
|
681
|
-
|
|
682
|
-
# Initialize feature and current stats dictionaries
|
|
683
|
-
safe_feature_stats = feature_stats or {}
|
|
684
|
-
safe_current_stats = current_stats or {}
|
|
685
|
-
|
|
686
|
-
# Create feature object and add it to a general features list
|
|
687
|
-
features = []
|
|
688
|
-
for name in feature_names:
|
|
689
|
-
if feature_stats is not None and name not in feature_stats:
|
|
690
|
-
logger.warn("Feature missing from 'feature_stats'", name=name)
|
|
691
|
-
if current_stats is not None and name not in current_stats:
|
|
692
|
-
logger.warn("Feature missing from 'current_stats'", name=name)
|
|
693
|
-
f = mlrun.common.schemas.Features.new(
|
|
694
|
-
name, safe_feature_stats.get(name), safe_current_stats.get(name)
|
|
695
|
-
)
|
|
696
|
-
features.append(f)
|
|
697
|
-
return features
|
|
698
|
-
|
|
699
|
-
@staticmethod
|
|
700
|
-
def _json_loads_if_not_none(field: typing.Any) -> typing.Any:
|
|
701
|
-
return (
|
|
702
|
-
json.loads(field)
|
|
703
|
-
if field and field != "null" and field is not None
|
|
704
|
-
else None
|
|
705
|
-
)
|
|
706
|
-
|
|
707
|
-
def deploy_monitoring_functions(
|
|
708
|
-
self,
|
|
709
|
-
project: str,
|
|
710
|
-
model_monitoring_access_key: str,
|
|
711
|
-
db_session: sqlalchemy.orm.Session,
|
|
712
|
-
auth_info: mlrun.common.schemas.AuthInfo,
|
|
713
|
-
tracking_policy: mlrun.utils.model_monitoring.TrackingPolicy,
|
|
714
|
-
):
|
|
715
|
-
"""
|
|
716
|
-
Invoking monitoring deploying functions.
|
|
717
|
-
|
|
718
|
-
:param project: The name of the project.
|
|
719
|
-
:param model_monitoring_access_key: Access key to apply the model monitoring process.
|
|
720
|
-
:param db_session: A session that manages the current dialog with the database.
|
|
721
|
-
:param auth_info: The auth info of the request.
|
|
722
|
-
:param tracking_policy: Model monitoring configurations.
|
|
723
|
-
"""
|
|
724
|
-
self.deploy_model_monitoring_stream_processing(
|
|
725
|
-
project=project,
|
|
726
|
-
model_monitoring_access_key=model_monitoring_access_key,
|
|
727
|
-
db_session=db_session,
|
|
728
|
-
auth_info=auth_info,
|
|
729
|
-
tracking_policy=tracking_policy,
|
|
730
|
-
)
|
|
731
|
-
self.deploy_model_monitoring_batch_processing(
|
|
732
|
-
project=project,
|
|
733
|
-
model_monitoring_access_key=model_monitoring_access_key,
|
|
734
|
-
db_session=db_session,
|
|
735
|
-
auth_info=auth_info,
|
|
736
|
-
tracking_policy=tracking_policy,
|
|
737
|
-
)
|
|
738
|
-
|
|
739
|
-
def verify_project_has_no_model_endpoints(self, project_name: str):
|
|
740
|
-
auth_info = mlrun.common.schemas.AuthInfo(
|
|
741
|
-
data_session=os.getenv("V3IO_ACCESS_KEY")
|
|
742
|
-
)
|
|
743
|
-
|
|
744
|
-
if not mlrun.mlconf.igz_version or not mlrun.mlconf.v3io_api:
|
|
745
|
-
return
|
|
746
|
-
|
|
747
|
-
endpoints = self.list_model_endpoints(auth_info, project_name)
|
|
748
|
-
if endpoints.endpoints:
|
|
749
|
-
raise mlrun.errors.MLRunPreconditionFailedError(
|
|
750
|
-
f"Project {project_name} can not be deleted since related resources found: model endpoints"
|
|
751
|
-
)
|
|
752
|
-
|
|
753
|
-
@staticmethod
|
|
754
|
-
def delete_model_endpoints_resources(project_name: str):
|
|
755
|
-
"""
|
|
756
|
-
Delete all model endpoints resources.
|
|
757
|
-
|
|
758
|
-
:param project_name: The name of the project.
|
|
759
|
-
"""
|
|
760
|
-
auth_info = mlrun.common.schemas.AuthInfo(
|
|
761
|
-
data_session=os.getenv("V3IO_ACCESS_KEY")
|
|
762
|
-
)
|
|
763
|
-
|
|
764
|
-
# We would ideally base on config.v3io_api but can't for backwards compatibility reasons,
|
|
765
|
-
# we're using the igz version heuristic
|
|
766
|
-
if not mlrun.mlconf.igz_version or not mlrun.mlconf.v3io_api:
|
|
767
|
-
return
|
|
768
|
-
|
|
769
|
-
# Generate a model endpoint store object and get a list of model endpoint dictionaries
|
|
770
|
-
endpoint_store = get_model_endpoint_store(
|
|
771
|
-
access_key=auth_info.data_session, project=project_name
|
|
772
|
-
)
|
|
773
|
-
endpoints = endpoint_store.list_model_endpoints()
|
|
774
|
-
|
|
775
|
-
# Delete model endpoints resources from databases using the model endpoint store object
|
|
776
|
-
endpoint_store.delete_model_endpoints_resources(endpoints)
|
|
777
|
-
|
|
778
|
-
def deploy_model_monitoring_stream_processing(
|
|
779
|
-
self,
|
|
780
|
-
project: str,
|
|
781
|
-
model_monitoring_access_key: str,
|
|
782
|
-
db_session: sqlalchemy.orm.Session,
|
|
783
|
-
auth_info: mlrun.common.schemas.AuthInfo,
|
|
784
|
-
tracking_policy: mlrun.utils.model_monitoring.TrackingPolicy,
|
|
785
|
-
):
|
|
786
|
-
"""
|
|
787
|
-
Deploying model monitoring stream real time nuclio function. The goal of this real time function is
|
|
788
|
-
to monitor the log of the data stream. It is triggered when a new log entry is detected.
|
|
789
|
-
It processes the new events into statistics that are then written to statistics databases.
|
|
790
|
-
|
|
791
|
-
:param project: The name of the project.
|
|
792
|
-
:param model_monitoring_access_key: Access key to apply the model monitoring process.
|
|
793
|
-
:param db_session: A session that manages the current dialog with the database.
|
|
794
|
-
:param auth_info: The auth info of the request.
|
|
795
|
-
:param tracking_policy: Model monitoring configurations.
|
|
796
|
-
"""
|
|
797
|
-
|
|
798
|
-
logger.info(
|
|
799
|
-
"Checking if model monitoring stream is already deployed",
|
|
800
|
-
project=project,
|
|
801
|
-
)
|
|
802
|
-
try:
|
|
803
|
-
# validate that the model monitoring stream has not yet been deployed
|
|
804
|
-
mlrun.api.crud.runtimes.nuclio.function.get_nuclio_deploy_status(
|
|
805
|
-
name="model-monitoring-stream",
|
|
806
|
-
project=project,
|
|
807
|
-
tag="",
|
|
808
|
-
auth_info=auth_info,
|
|
809
|
-
)
|
|
810
|
-
logger.info(
|
|
811
|
-
"Detected model monitoring stream processing function already deployed",
|
|
812
|
-
project=project,
|
|
813
|
-
)
|
|
814
|
-
return
|
|
815
|
-
except mlrun.errors.MLRunNotFoundError:
|
|
816
|
-
logger.info(
|
|
817
|
-
"Deploying model monitoring stream processing function", project=project
|
|
818
|
-
)
|
|
819
|
-
|
|
820
|
-
# Get parquet target value for model monitoring stream function
|
|
821
|
-
parquet_target = self._get_monitoring_parquet_path(
|
|
822
|
-
db_session=db_session, project=project
|
|
823
|
-
)
|
|
824
|
-
|
|
825
|
-
fn = mlrun.model_monitoring.helpers.initial_model_monitoring_stream_processing_function(
|
|
826
|
-
project=project,
|
|
827
|
-
model_monitoring_access_key=model_monitoring_access_key,
|
|
828
|
-
tracking_policy=tracking_policy,
|
|
829
|
-
auth_info=auth_info,
|
|
830
|
-
parquet_target=parquet_target,
|
|
831
|
-
)
|
|
832
|
-
|
|
833
|
-
mlrun.api.api.endpoints.functions._build_function(
|
|
834
|
-
db_session=db_session, auth_info=auth_info, function=fn
|
|
835
|
-
)
|
|
836
|
-
|
|
837
|
-
def deploy_model_monitoring_batch_processing(
|
|
838
|
-
self,
|
|
839
|
-
project: str,
|
|
840
|
-
model_monitoring_access_key: str,
|
|
841
|
-
db_session: sqlalchemy.orm.Session,
|
|
842
|
-
auth_info: mlrun.common.schemas.AuthInfo,
|
|
843
|
-
tracking_policy: mlrun.utils.model_monitoring.TrackingPolicy,
|
|
844
|
-
):
|
|
845
|
-
"""
|
|
846
|
-
Deploying model monitoring batch job. The goal of this job is to identify drift in the data
|
|
847
|
-
based on the latest batch of events. By default, this job is executed on the hour every hour.
|
|
848
|
-
Note that if the monitoring batch job was already deployed then you will have to delete the
|
|
849
|
-
old monitoring batch job before deploying a new one.
|
|
850
|
-
|
|
851
|
-
:param project: The name of the project.
|
|
852
|
-
:param model_monitoring_access_key: Access key to apply the model monitoring process.
|
|
853
|
-
:param db_session: A session that manages the current dialog with the database.
|
|
854
|
-
:param auth_info: The auth info of the request.
|
|
855
|
-
:param tracking_policy: Model monitoring configurations.
|
|
856
|
-
"""
|
|
857
|
-
|
|
858
|
-
logger.info(
|
|
859
|
-
"Checking if model monitoring batch processing function is already deployed",
|
|
860
|
-
project=project,
|
|
861
|
-
)
|
|
862
|
-
|
|
863
|
-
# Try to list functions that named model monitoring batch
|
|
864
|
-
# to make sure that this job has not yet been deployed
|
|
865
|
-
function_list = mlrun.api.utils.singletons.db.get_db().list_functions(
|
|
866
|
-
session=db_session, name="model-monitoring-batch", project=project
|
|
867
|
-
)
|
|
868
|
-
|
|
869
|
-
if function_list:
|
|
870
|
-
logger.info(
|
|
871
|
-
"Detected model monitoring batch processing function already deployed",
|
|
872
|
-
project=project,
|
|
873
|
-
)
|
|
874
|
-
return
|
|
875
|
-
|
|
876
|
-
# Create a monitoring batch job function object
|
|
877
|
-
fn = mlrun.model_monitoring.helpers.get_model_monitoring_batch_function(
|
|
878
|
-
project=project,
|
|
879
|
-
model_monitoring_access_key=model_monitoring_access_key,
|
|
880
|
-
db_session=db_session,
|
|
881
|
-
auth_info=auth_info,
|
|
882
|
-
tracking_policy=tracking_policy,
|
|
883
|
-
)
|
|
884
|
-
|
|
885
|
-
# Get the function uri
|
|
886
|
-
function_uri = fn.save(versioned=True)
|
|
887
|
-
function_uri = function_uri.replace("db://", "")
|
|
888
|
-
|
|
889
|
-
task = mlrun.new_task(name="model-monitoring-batch", project=project)
|
|
890
|
-
task.spec.function = function_uri
|
|
891
|
-
|
|
892
|
-
# Apply batching interval params
|
|
893
|
-
interval_list = [
|
|
894
|
-
tracking_policy.default_batch_intervals.minute,
|
|
895
|
-
tracking_policy.default_batch_intervals.hour,
|
|
896
|
-
tracking_policy.default_batch_intervals.day,
|
|
897
|
-
]
|
|
898
|
-
minutes, hours, days = self._get_batching_interval_param(interval_list)
|
|
899
|
-
batch_dict = {"minutes": minutes, "hours": hours, "days": days}
|
|
900
|
-
|
|
901
|
-
task.spec.parameters[
|
|
902
|
-
model_monitoring_constants.EventFieldType.BATCH_INTERVALS_DICT
|
|
903
|
-
] = batch_dict
|
|
904
|
-
|
|
905
|
-
data = {
|
|
906
|
-
"task": task.to_dict(),
|
|
907
|
-
"schedule": self._convert_to_cron_string(
|
|
908
|
-
tracking_policy.default_batch_intervals
|
|
909
|
-
),
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
logger.info(
|
|
913
|
-
"Deploying model monitoring batch processing function", project=project
|
|
914
|
-
)
|
|
915
|
-
|
|
916
|
-
# Add job schedule policy (every hour by default)
|
|
917
|
-
mlrun.api.api.utils.submit_run_sync(
|
|
918
|
-
db_session=db_session, auth_info=auth_info, data=data
|
|
919
|
-
)
|
|
920
|
-
|
|
921
|
-
@staticmethod
|
|
922
|
-
def _clean_feature_name(feature_name):
|
|
923
|
-
return feature_name.replace(" ", "_").replace("(", "").replace(")", "")
|
|
924
|
-
|
|
925
|
-
@staticmethod
|
|
926
|
-
def get_access_key(auth_info: mlrun.common.schemas.AuthInfo):
|
|
927
|
-
"""
|
|
928
|
-
Getting access key from the current data session. This method is usually used to verify that the session
|
|
929
|
-
is valid and contains an access key.
|
|
930
|
-
|
|
931
|
-
param auth_info: The auth info of the request.
|
|
932
|
-
|
|
933
|
-
:return: Access key as a string.
|
|
934
|
-
"""
|
|
935
|
-
access_key = auth_info.data_session
|
|
936
|
-
if not access_key:
|
|
937
|
-
raise mlrun.errors.MLRunBadRequestError("Data session is missing")
|
|
938
|
-
return access_key
|
|
939
|
-
|
|
940
|
-
@staticmethod
|
|
941
|
-
def _get_batching_interval_param(intervals_list: typing.List):
|
|
942
|
-
"""Converting each value in the intervals list into a float number. None
|
|
943
|
-
Values will be converted into 0.0.
|
|
944
|
-
|
|
945
|
-
param intervals_list: A list of values based on the ScheduleCronTrigger expression. Note that at the moment
|
|
946
|
-
it supports minutes, hours, and days. e.g. [0, '*/1', None] represents on the hour
|
|
947
|
-
every hour.
|
|
948
|
-
|
|
949
|
-
:return: A tuple of:
|
|
950
|
-
[0] = minutes interval as a float
|
|
951
|
-
[1] = hours interval as a float
|
|
952
|
-
[2] = days interval as a float
|
|
953
|
-
"""
|
|
954
|
-
return tuple(
|
|
955
|
-
[
|
|
956
|
-
0.0
|
|
957
|
-
if isinstance(interval, (float, int)) or interval is None
|
|
958
|
-
else float(f"0{interval.partition('/')[-1]}")
|
|
959
|
-
for interval in intervals_list
|
|
960
|
-
]
|
|
961
|
-
)
|
|
962
|
-
|
|
963
|
-
@staticmethod
|
|
964
|
-
def _convert_to_cron_string(
|
|
965
|
-
cron_trigger: mlrun.common.schemas.schedule.ScheduleCronTrigger,
|
|
966
|
-
):
|
|
967
|
-
"""Converting the batch interval `ScheduleCronTrigger` into a cron trigger expression"""
|
|
968
|
-
return "{} {} {} * *".format(
|
|
969
|
-
cron_trigger.minute, cron_trigger.hour, cron_trigger.day
|
|
970
|
-
).replace("None", "*")
|