mlrun 1.7.0rc14__py3-none-any.whl → 1.7.0rc21__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 +10 -1
- mlrun/__main__.py +23 -111
- mlrun/alerts/__init__.py +15 -0
- mlrun/alerts/alert.py +144 -0
- mlrun/api/schemas/__init__.py +4 -3
- mlrun/artifacts/__init__.py +8 -3
- mlrun/artifacts/base.py +36 -253
- mlrun/artifacts/dataset.py +9 -190
- mlrun/artifacts/manager.py +46 -42
- mlrun/artifacts/model.py +9 -141
- mlrun/artifacts/plots.py +14 -375
- mlrun/common/constants.py +65 -3
- mlrun/common/formatters/__init__.py +19 -0
- mlrun/{runtimes/mpijob/v1alpha1.py → common/formatters/artifact.py} +6 -14
- mlrun/common/formatters/base.py +113 -0
- mlrun/common/formatters/function.py +46 -0
- mlrun/common/formatters/pipeline.py +53 -0
- mlrun/common/formatters/project.py +51 -0
- mlrun/{runtimes → common/runtimes}/constants.py +32 -4
- mlrun/common/schemas/__init__.py +10 -5
- mlrun/common/schemas/alert.py +92 -11
- mlrun/common/schemas/api_gateway.py +56 -0
- mlrun/common/schemas/artifact.py +15 -5
- mlrun/common/schemas/auth.py +2 -0
- mlrun/common/schemas/client_spec.py +1 -0
- mlrun/common/schemas/frontend_spec.py +1 -0
- mlrun/common/schemas/function.py +4 -0
- mlrun/common/schemas/model_monitoring/__init__.py +15 -3
- mlrun/common/schemas/model_monitoring/constants.py +58 -7
- mlrun/common/schemas/model_monitoring/grafana.py +9 -5
- mlrun/common/schemas/model_monitoring/model_endpoints.py +86 -2
- mlrun/common/schemas/pipeline.py +0 -9
- mlrun/common/schemas/project.py +5 -11
- mlrun/common/types.py +1 -0
- mlrun/config.py +27 -9
- mlrun/data_types/to_pandas.py +9 -9
- mlrun/datastore/base.py +41 -9
- mlrun/datastore/datastore.py +6 -2
- mlrun/datastore/datastore_profile.py +56 -4
- mlrun/datastore/inmem.py +2 -2
- mlrun/datastore/redis.py +2 -2
- mlrun/datastore/s3.py +5 -0
- mlrun/datastore/sources.py +147 -7
- mlrun/datastore/store_resources.py +7 -7
- mlrun/datastore/targets.py +110 -42
- mlrun/datastore/utils.py +42 -0
- mlrun/db/base.py +54 -10
- mlrun/db/httpdb.py +282 -79
- mlrun/db/nopdb.py +52 -10
- mlrun/errors.py +11 -0
- mlrun/execution.py +24 -9
- mlrun/feature_store/__init__.py +0 -2
- mlrun/feature_store/api.py +12 -47
- mlrun/feature_store/feature_set.py +9 -0
- mlrun/feature_store/feature_vector.py +8 -0
- mlrun/feature_store/ingestion.py +7 -6
- mlrun/feature_store/retrieval/base.py +9 -4
- mlrun/feature_store/retrieval/conversion.py +9 -9
- mlrun/feature_store/retrieval/dask_merger.py +2 -0
- mlrun/feature_store/retrieval/job.py +9 -3
- mlrun/feature_store/retrieval/local_merger.py +2 -0
- mlrun/feature_store/retrieval/spark_merger.py +16 -0
- mlrun/frameworks/_dl_common/loggers/tensorboard_logger.py +7 -12
- mlrun/frameworks/parallel_coordinates.py +2 -1
- mlrun/frameworks/tf_keras/__init__.py +4 -1
- mlrun/k8s_utils.py +10 -11
- mlrun/launcher/base.py +4 -3
- mlrun/launcher/client.py +5 -3
- mlrun/launcher/local.py +8 -2
- mlrun/launcher/remote.py +8 -2
- mlrun/lists.py +6 -2
- mlrun/model.py +45 -21
- mlrun/model_monitoring/__init__.py +1 -1
- mlrun/model_monitoring/api.py +41 -18
- mlrun/model_monitoring/application.py +5 -305
- mlrun/model_monitoring/applications/__init__.py +11 -0
- mlrun/model_monitoring/applications/_application_steps.py +157 -0
- mlrun/model_monitoring/applications/base.py +280 -0
- mlrun/model_monitoring/applications/context.py +214 -0
- mlrun/model_monitoring/applications/evidently_base.py +211 -0
- mlrun/model_monitoring/applications/histogram_data_drift.py +132 -91
- mlrun/model_monitoring/applications/results.py +99 -0
- mlrun/model_monitoring/controller.py +3 -1
- mlrun/model_monitoring/db/__init__.py +2 -0
- mlrun/model_monitoring/db/stores/__init__.py +0 -2
- mlrun/model_monitoring/db/stores/base/store.py +22 -37
- mlrun/model_monitoring/db/stores/sqldb/models/__init__.py +43 -21
- mlrun/model_monitoring/db/stores/sqldb/models/base.py +39 -8
- mlrun/model_monitoring/db/stores/sqldb/models/mysql.py +27 -7
- mlrun/model_monitoring/db/stores/sqldb/models/sqlite.py +5 -0
- mlrun/model_monitoring/db/stores/sqldb/sql_store.py +246 -224
- mlrun/model_monitoring/db/stores/v3io_kv/kv_store.py +232 -216
- mlrun/model_monitoring/db/tsdb/__init__.py +100 -0
- mlrun/model_monitoring/db/tsdb/base.py +329 -0
- mlrun/model_monitoring/db/tsdb/helpers.py +30 -0
- mlrun/model_monitoring/db/tsdb/tdengine/__init__.py +15 -0
- mlrun/model_monitoring/db/tsdb/tdengine/schemas.py +240 -0
- mlrun/model_monitoring/db/tsdb/tdengine/stream_graph_steps.py +45 -0
- mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +397 -0
- mlrun/model_monitoring/db/tsdb/v3io/__init__.py +15 -0
- mlrun/model_monitoring/db/tsdb/v3io/stream_graph_steps.py +117 -0
- mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +636 -0
- mlrun/model_monitoring/evidently_application.py +6 -118
- mlrun/model_monitoring/helpers.py +46 -1
- mlrun/model_monitoring/model_endpoint.py +3 -2
- mlrun/model_monitoring/stream_processing.py +57 -216
- mlrun/model_monitoring/writer.py +134 -124
- mlrun/package/utils/_formatter.py +2 -2
- mlrun/platforms/__init__.py +10 -9
- mlrun/platforms/iguazio.py +21 -202
- mlrun/projects/operations.py +19 -12
- mlrun/projects/pipelines.py +79 -102
- mlrun/projects/project.py +265 -103
- mlrun/render.py +15 -14
- mlrun/run.py +16 -46
- mlrun/runtimes/__init__.py +6 -3
- mlrun/runtimes/base.py +8 -7
- mlrun/runtimes/databricks_job/databricks_wrapper.py +1 -1
- mlrun/runtimes/funcdoc.py +0 -28
- mlrun/runtimes/kubejob.py +2 -1
- mlrun/runtimes/local.py +5 -2
- mlrun/runtimes/mpijob/__init__.py +0 -20
- mlrun/runtimes/mpijob/v1.py +1 -1
- mlrun/runtimes/nuclio/api_gateway.py +194 -84
- mlrun/runtimes/nuclio/application/application.py +170 -8
- mlrun/runtimes/nuclio/function.py +39 -49
- mlrun/runtimes/pod.py +16 -36
- mlrun/runtimes/remotesparkjob.py +9 -3
- mlrun/runtimes/sparkjob/spark3job.py +1 -1
- mlrun/runtimes/utils.py +6 -45
- mlrun/serving/server.py +2 -1
- mlrun/serving/v2_serving.py +5 -1
- mlrun/track/tracker.py +2 -1
- mlrun/utils/async_http.py +25 -5
- mlrun/utils/helpers.py +107 -75
- mlrun/utils/logger.py +39 -7
- mlrun/utils/notifications/notification/__init__.py +14 -9
- mlrun/utils/notifications/notification/base.py +1 -1
- mlrun/utils/notifications/notification/slack.py +34 -7
- mlrun/utils/notifications/notification/webhook.py +1 -1
- mlrun/utils/notifications/notification_pusher.py +147 -16
- mlrun/utils/regex.py +9 -0
- mlrun/utils/v3io_clients.py +0 -1
- mlrun/utils/version/version.json +2 -2
- {mlrun-1.7.0rc14.dist-info → mlrun-1.7.0rc21.dist-info}/METADATA +14 -6
- {mlrun-1.7.0rc14.dist-info → mlrun-1.7.0rc21.dist-info}/RECORD +150 -130
- mlrun/kfpops.py +0 -865
- mlrun/platforms/other.py +0 -305
- {mlrun-1.7.0rc14.dist-info → mlrun-1.7.0rc21.dist-info}/LICENSE +0 -0
- {mlrun-1.7.0rc14.dist-info → mlrun-1.7.0rc21.dist-info}/WHEEL +0 -0
- {mlrun-1.7.0rc14.dist-info → mlrun-1.7.0rc21.dist-info}/entry_points.txt +0 -0
- {mlrun-1.7.0rc14.dist-info → mlrun-1.7.0rc21.dist-info}/top_level.txt +0 -0
|
@@ -19,14 +19,17 @@ import uuid
|
|
|
19
19
|
|
|
20
20
|
import pandas as pd
|
|
21
21
|
import sqlalchemy
|
|
22
|
+
import sqlalchemy.exc
|
|
23
|
+
import sqlalchemy.orm
|
|
24
|
+
from sqlalchemy.sql.elements import BinaryExpression
|
|
22
25
|
|
|
23
26
|
import mlrun.common.model_monitoring.helpers
|
|
24
|
-
import mlrun.common.schemas.model_monitoring
|
|
27
|
+
import mlrun.common.schemas.model_monitoring as mm_schemas
|
|
25
28
|
import mlrun.model_monitoring.db
|
|
26
29
|
import mlrun.model_monitoring.db.stores.sqldb.models
|
|
27
30
|
import mlrun.model_monitoring.helpers
|
|
28
31
|
from mlrun.common.db.sql_session import create_session, get_engine
|
|
29
|
-
from mlrun.utils import logger
|
|
32
|
+
from mlrun.utils import datetime_now, logger
|
|
30
33
|
|
|
31
34
|
|
|
32
35
|
class SQLStoreBase(mlrun.model_monitoring.db.StoreBase):
|
|
@@ -36,7 +39,6 @@ class SQLStoreBase(mlrun.model_monitoring.db.StoreBase):
|
|
|
36
39
|
data, the user needs to provide a valid connection string for the database.
|
|
37
40
|
"""
|
|
38
41
|
|
|
39
|
-
_engine = None
|
|
40
42
|
_tables = {}
|
|
41
43
|
|
|
42
44
|
def __init__(
|
|
@@ -64,6 +66,7 @@ class SQLStoreBase(mlrun.model_monitoring.db.StoreBase):
|
|
|
64
66
|
def _init_tables(self):
|
|
65
67
|
self._init_model_endpoints_table()
|
|
66
68
|
self._init_application_results_table()
|
|
69
|
+
self._init_application_metrics_table()
|
|
67
70
|
self._init_monitoring_schedules_table()
|
|
68
71
|
|
|
69
72
|
def _init_model_endpoints_table(self):
|
|
@@ -72,83 +75,91 @@ class SQLStoreBase(mlrun.model_monitoring.db.StoreBase):
|
|
|
72
75
|
connection_string=self._sql_connection_string
|
|
73
76
|
)
|
|
74
77
|
)
|
|
75
|
-
self._tables[
|
|
76
|
-
|
|
77
|
-
|
|
78
|
+
self._tables[mm_schemas.EventFieldType.MODEL_ENDPOINTS] = (
|
|
79
|
+
self.ModelEndpointsTable
|
|
80
|
+
)
|
|
78
81
|
|
|
79
82
|
def _init_application_results_table(self):
|
|
80
|
-
self.
|
|
83
|
+
self.application_results_table = (
|
|
81
84
|
mlrun.model_monitoring.db.stores.sqldb.models._get_application_result_table(
|
|
82
85
|
connection_string=self._sql_connection_string
|
|
83
86
|
)
|
|
84
87
|
)
|
|
85
|
-
self._tables[
|
|
86
|
-
|
|
87
|
-
|
|
88
|
+
self._tables[mm_schemas.FileTargetKind.APP_RESULTS] = (
|
|
89
|
+
self.application_results_table
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
def _init_application_metrics_table(self) -> None:
|
|
93
|
+
self.application_metrics_table = mlrun.model_monitoring.db.stores.sqldb.models._get_application_metrics_table(
|
|
94
|
+
connection_string=self._sql_connection_string
|
|
95
|
+
)
|
|
96
|
+
self._tables[mm_schemas.FileTargetKind.APP_METRICS] = (
|
|
97
|
+
self.application_metrics_table
|
|
98
|
+
)
|
|
88
99
|
|
|
89
100
|
def _init_monitoring_schedules_table(self):
|
|
90
101
|
self.MonitoringSchedulesTable = mlrun.model_monitoring.db.stores.sqldb.models._get_monitoring_schedules_table(
|
|
91
102
|
connection_string=self._sql_connection_string
|
|
92
103
|
)
|
|
93
|
-
self._tables[
|
|
94
|
-
|
|
95
|
-
|
|
104
|
+
self._tables[mm_schemas.FileTargetKind.MONITORING_SCHEDULES] = (
|
|
105
|
+
self.MonitoringSchedulesTable
|
|
106
|
+
)
|
|
96
107
|
|
|
97
|
-
def _write(self,
|
|
108
|
+
def _write(self, table_name: str, event: dict[str, typing.Any]) -> None:
|
|
98
109
|
"""
|
|
99
110
|
Create a new record in the SQL table.
|
|
100
111
|
|
|
101
|
-
:param
|
|
102
|
-
:param event:
|
|
112
|
+
:param table_name: Target table name.
|
|
113
|
+
:param event: Event dictionary that will be written into the DB.
|
|
103
114
|
"""
|
|
104
|
-
|
|
105
115
|
with self._engine.connect() as connection:
|
|
106
116
|
# Convert the result into a pandas Dataframe and write it into the database
|
|
107
117
|
event_df = pd.DataFrame([event])
|
|
108
|
-
|
|
109
|
-
event_df.to_sql(table, con=connection, index=False, if_exists="append")
|
|
118
|
+
event_df.to_sql(table_name, con=connection, index=False, if_exists="append")
|
|
110
119
|
|
|
111
120
|
def _update(
|
|
112
121
|
self,
|
|
113
122
|
attributes: dict[str, typing.Any],
|
|
114
123
|
table: sqlalchemy.orm.decl_api.DeclarativeMeta,
|
|
115
|
-
|
|
116
|
-
):
|
|
124
|
+
criteria: list[BinaryExpression],
|
|
125
|
+
) -> None:
|
|
117
126
|
"""
|
|
118
127
|
Update a record in the SQL table.
|
|
119
128
|
|
|
120
129
|
:param attributes: Dictionary of attributes that will be used for update the record. Note that the keys
|
|
121
130
|
of the attributes dictionary should exist in the SQL table.
|
|
122
131
|
:param table: SQLAlchemy declarative table.
|
|
123
|
-
|
|
132
|
+
:param criteria: A list of binary expressions that filter the query.
|
|
124
133
|
"""
|
|
125
|
-
filter_query_ = []
|
|
126
|
-
for _filter in filtered_values:
|
|
127
|
-
filter_query_.append(f"{_filter} = '{filtered_values[_filter]}'")
|
|
128
|
-
|
|
129
134
|
with create_session(dsn=self._sql_connection_string) as session:
|
|
130
135
|
# Generate and commit the update session query
|
|
131
|
-
session.query(
|
|
132
|
-
|
|
133
|
-
)
|
|
136
|
+
session.query(
|
|
137
|
+
table # pyright: ignore[reportOptionalCall]
|
|
138
|
+
).filter(*criteria).update(attributes, synchronize_session=False)
|
|
134
139
|
session.commit()
|
|
135
140
|
|
|
136
|
-
def _get(
|
|
141
|
+
def _get(
|
|
142
|
+
self,
|
|
143
|
+
table: sqlalchemy.orm.decl_api.DeclarativeMeta,
|
|
144
|
+
criteria: list[BinaryExpression],
|
|
145
|
+
):
|
|
137
146
|
"""
|
|
138
147
|
Get a record from the SQL table.
|
|
139
148
|
|
|
140
|
-
param table:
|
|
149
|
+
param table: SQLAlchemy declarative table.
|
|
150
|
+
:param criteria: A list of binary expressions that filter the query.
|
|
141
151
|
"""
|
|
142
|
-
|
|
143
|
-
filter_query_ = []
|
|
144
|
-
for _filter in filtered_values:
|
|
145
|
-
filter_query_.append(f"{_filter} = '{filtered_values[_filter]}'")
|
|
146
152
|
with create_session(dsn=self._sql_connection_string) as session:
|
|
147
153
|
try:
|
|
154
|
+
logger.debug(
|
|
155
|
+
"Querying the DB",
|
|
156
|
+
table=table.__name__,
|
|
157
|
+
criteria=[str(criterion) for criterion in criteria],
|
|
158
|
+
)
|
|
148
159
|
# Generate the get query
|
|
149
160
|
return (
|
|
150
|
-
session.query(table)
|
|
151
|
-
.filter(
|
|
161
|
+
session.query(table) # pyright: ignore[reportOptionalCall]
|
|
162
|
+
.filter(*criteria)
|
|
152
163
|
.one_or_none()
|
|
153
164
|
)
|
|
154
165
|
except sqlalchemy.exc.ProgrammingError:
|
|
@@ -157,21 +168,21 @@ class SQLStoreBase(mlrun.model_monitoring.db.StoreBase):
|
|
|
157
168
|
return
|
|
158
169
|
|
|
159
170
|
def _delete(
|
|
160
|
-
self,
|
|
161
|
-
|
|
171
|
+
self,
|
|
172
|
+
table: sqlalchemy.orm.decl_api.DeclarativeMeta,
|
|
173
|
+
criteria: list[BinaryExpression],
|
|
174
|
+
) -> None:
|
|
162
175
|
"""
|
|
163
176
|
Delete records from the SQL table.
|
|
164
177
|
|
|
165
|
-
param table:
|
|
178
|
+
param table: SQLAlchemy declarative table.
|
|
179
|
+
:param criteria: A list of binary expressions that filter the query.
|
|
166
180
|
"""
|
|
167
|
-
filter_query_ = []
|
|
168
|
-
for _filter in filtered_values:
|
|
169
|
-
filter_query_.append(f"{_filter} = '{filtered_values[_filter]}'")
|
|
170
181
|
with create_session(dsn=self._sql_connection_string) as session:
|
|
171
182
|
# Generate and commit the delete query
|
|
172
|
-
session.query(
|
|
173
|
-
|
|
174
|
-
)
|
|
183
|
+
session.query(
|
|
184
|
+
table # pyright: ignore[reportOptionalCall]
|
|
185
|
+
).filter(*criteria).delete(synchronize_session=False)
|
|
175
186
|
session.commit()
|
|
176
187
|
|
|
177
188
|
def write_model_endpoint(self, endpoint: dict[str, typing.Any]):
|
|
@@ -183,15 +194,12 @@ class SQLStoreBase(mlrun.model_monitoring.db.StoreBase):
|
|
|
183
194
|
"""
|
|
184
195
|
|
|
185
196
|
# Adjust timestamps fields
|
|
186
|
-
endpoint[
|
|
187
|
-
|
|
188
|
-
)
|
|
189
|
-
mlrun.common.schemas.model_monitoring.EventFieldType.LAST_REQUEST
|
|
190
|
-
] = mlrun.utils.datetime_now()
|
|
197
|
+
endpoint[mm_schemas.EventFieldType.FIRST_REQUEST] = (endpoint)[
|
|
198
|
+
mm_schemas.EventFieldType.LAST_REQUEST
|
|
199
|
+
] = datetime_now()
|
|
191
200
|
|
|
192
201
|
self._write(
|
|
193
|
-
|
|
194
|
-
event=endpoint,
|
|
202
|
+
table_name=mm_schemas.EventFieldType.MODEL_ENDPOINTS, event=endpoint
|
|
195
203
|
)
|
|
196
204
|
|
|
197
205
|
def update_model_endpoint(
|
|
@@ -207,31 +215,26 @@ class SQLStoreBase(mlrun.model_monitoring.db.StoreBase):
|
|
|
207
215
|
"""
|
|
208
216
|
self._init_model_endpoints_table()
|
|
209
217
|
|
|
210
|
-
attributes.pop(
|
|
211
|
-
mlrun.common.schemas.model_monitoring.EventFieldType.ENDPOINT_ID, None
|
|
212
|
-
)
|
|
213
|
-
|
|
214
|
-
filter_endpoint = {
|
|
215
|
-
mlrun.common.schemas.model_monitoring.EventFieldType.UID: endpoint_id
|
|
216
|
-
}
|
|
218
|
+
attributes.pop(mm_schemas.EventFieldType.ENDPOINT_ID, None)
|
|
217
219
|
|
|
218
220
|
self._update(
|
|
219
|
-
attributes=attributes,
|
|
221
|
+
attributes=attributes,
|
|
222
|
+
table=self.ModelEndpointsTable,
|
|
223
|
+
criteria=[self.ModelEndpointsTable.uid == endpoint_id],
|
|
220
224
|
)
|
|
221
225
|
|
|
222
|
-
def delete_model_endpoint(self, endpoint_id: str):
|
|
226
|
+
def delete_model_endpoint(self, endpoint_id: str) -> None:
|
|
223
227
|
"""
|
|
224
228
|
Deletes the SQL record of a given model endpoint id.
|
|
225
229
|
|
|
226
230
|
:param endpoint_id: The unique id of the model endpoint.
|
|
227
231
|
"""
|
|
228
232
|
self._init_model_endpoints_table()
|
|
229
|
-
|
|
230
|
-
filter_endpoint = {
|
|
231
|
-
mlrun.common.schemas.model_monitoring.EventFieldType.UID: endpoint_id
|
|
232
|
-
}
|
|
233
233
|
# Delete the model endpoint record using sqlalchemy ORM
|
|
234
|
-
self._delete(
|
|
234
|
+
self._delete(
|
|
235
|
+
table=self.ModelEndpointsTable,
|
|
236
|
+
criteria=[self.ModelEndpointsTable.uid == endpoint_id],
|
|
237
|
+
)
|
|
235
238
|
|
|
236
239
|
def get_model_endpoint(
|
|
237
240
|
self,
|
|
@@ -248,11 +251,11 @@ class SQLStoreBase(mlrun.model_monitoring.db.StoreBase):
|
|
|
248
251
|
"""
|
|
249
252
|
self._init_model_endpoints_table()
|
|
250
253
|
|
|
251
|
-
# Get the model endpoint record
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
254
|
+
# Get the model endpoint record
|
|
255
|
+
endpoint_record = self._get(
|
|
256
|
+
table=self.ModelEndpointsTable,
|
|
257
|
+
criteria=[self.ModelEndpointsTable.uid == endpoint_id],
|
|
258
|
+
)
|
|
256
259
|
|
|
257
260
|
if not endpoint_record:
|
|
258
261
|
raise mlrun.errors.MLRunNotFoundError(f"Endpoint {endpoint_id} not found")
|
|
@@ -288,7 +291,7 @@ class SQLStoreBase(mlrun.model_monitoring.db.StoreBase):
|
|
|
288
291
|
endpoint_list = []
|
|
289
292
|
|
|
290
293
|
model_endpoints_table = (
|
|
291
|
-
self.ModelEndpointsTable.__table__ # pyright: ignore[
|
|
294
|
+
self.ModelEndpointsTable.__table__ # pyright: ignore[reportAttributeAccessIssue]
|
|
292
295
|
)
|
|
293
296
|
|
|
294
297
|
# Get the model endpoints records using sqlalchemy ORM
|
|
@@ -303,36 +306,32 @@ class SQLStoreBase(mlrun.model_monitoring.db.StoreBase):
|
|
|
303
306
|
query = self._filter_values(
|
|
304
307
|
query=query,
|
|
305
308
|
model_endpoints_table=model_endpoints_table,
|
|
306
|
-
key_filter=
|
|
309
|
+
key_filter=mm_schemas.EventFieldType.MODEL,
|
|
307
310
|
filtered_values=[model],
|
|
308
311
|
)
|
|
309
312
|
if function:
|
|
310
313
|
query = self._filter_values(
|
|
311
314
|
query=query,
|
|
312
315
|
model_endpoints_table=model_endpoints_table,
|
|
313
|
-
key_filter=
|
|
316
|
+
key_filter=mm_schemas.EventFieldType.FUNCTION,
|
|
314
317
|
filtered_values=[function],
|
|
315
318
|
)
|
|
316
319
|
if uids:
|
|
317
320
|
query = self._filter_values(
|
|
318
321
|
query=query,
|
|
319
322
|
model_endpoints_table=model_endpoints_table,
|
|
320
|
-
key_filter=
|
|
323
|
+
key_filter=mm_schemas.EventFieldType.UID,
|
|
321
324
|
filtered_values=uids,
|
|
322
325
|
combined=False,
|
|
323
326
|
)
|
|
324
327
|
if top_level:
|
|
325
|
-
node_ep = str(
|
|
326
|
-
|
|
327
|
-
)
|
|
328
|
-
router_ep = str(
|
|
329
|
-
mlrun.common.schemas.model_monitoring.EndpointType.ROUTER.value
|
|
330
|
-
)
|
|
328
|
+
node_ep = str(mm_schemas.EndpointType.NODE_EP.value)
|
|
329
|
+
router_ep = str(mm_schemas.EndpointType.ROUTER.value)
|
|
331
330
|
endpoint_types = [node_ep, router_ep]
|
|
332
331
|
query = self._filter_values(
|
|
333
332
|
query=query,
|
|
334
333
|
model_endpoints_table=model_endpoints_table,
|
|
335
|
-
key_filter=
|
|
334
|
+
key_filter=mm_schemas.EventFieldType.ENDPOINT_TYPE,
|
|
336
335
|
filtered_values=endpoint_types,
|
|
337
336
|
combined=False,
|
|
338
337
|
)
|
|
@@ -350,68 +349,89 @@ class SQLStoreBase(mlrun.model_monitoring.db.StoreBase):
|
|
|
350
349
|
|
|
351
350
|
return endpoint_list
|
|
352
351
|
|
|
353
|
-
def
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
352
|
+
def write_application_event(
|
|
353
|
+
self,
|
|
354
|
+
event: dict[str, typing.Any],
|
|
355
|
+
kind: mm_schemas.WriterEventKind = mm_schemas.WriterEventKind.RESULT,
|
|
356
|
+
) -> None:
|
|
357
|
+
"""
|
|
358
|
+
Write a new application event in the target table.
|
|
359
|
+
|
|
360
|
+
:param event: An event dictionary that represents the application result or metric,
|
|
361
|
+
should be corresponded to the schema defined in the
|
|
362
|
+
:py:class:`~mm_constants.constants.WriterEvent` object.
|
|
363
|
+
:param kind: The type of the event, can be either "result" or "metric".
|
|
364
|
+
"""
|
|
365
|
+
|
|
366
|
+
if kind == mm_schemas.WriterEventKind.METRIC:
|
|
367
|
+
self._init_application_metrics_table()
|
|
368
|
+
table = self.application_metrics_table
|
|
369
|
+
table_name = mm_schemas.FileTargetKind.APP_METRICS
|
|
370
|
+
elif kind == mm_schemas.WriterEventKind.RESULT:
|
|
371
|
+
self._init_application_results_table()
|
|
372
|
+
table = self.application_results_table
|
|
373
|
+
table_name = mm_schemas.FileTargetKind.APP_RESULTS
|
|
374
|
+
else:
|
|
375
|
+
raise ValueError(f"Invalid {kind = }")
|
|
362
376
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
event
|
|
366
|
-
)
|
|
367
|
-
}
|
|
377
|
+
application_result_uid = self._generate_application_result_uid(event, kind=kind)
|
|
378
|
+
criteria = [table.uid == application_result_uid]
|
|
368
379
|
|
|
369
|
-
application_record = self._get(
|
|
370
|
-
table=self.ApplicationResultsTable, **application_filter_dict
|
|
371
|
-
)
|
|
380
|
+
application_record = self._get(table=table, criteria=criteria)
|
|
372
381
|
if application_record:
|
|
373
382
|
self._convert_to_datetime(
|
|
374
|
-
event=event,
|
|
375
|
-
key=mlrun.common.schemas.model_monitoring.WriterEvent.START_INFER_TIME,
|
|
383
|
+
event=event, key=mm_schemas.WriterEvent.START_INFER_TIME
|
|
376
384
|
)
|
|
377
385
|
self._convert_to_datetime(
|
|
378
|
-
event=event,
|
|
379
|
-
key=mlrun.common.schemas.model_monitoring.WriterEvent.END_INFER_TIME,
|
|
386
|
+
event=event, key=mm_schemas.WriterEvent.END_INFER_TIME
|
|
380
387
|
)
|
|
381
388
|
# Update an existing application result
|
|
382
|
-
self._update(
|
|
383
|
-
attributes=event,
|
|
384
|
-
table=self.ApplicationResultsTable,
|
|
385
|
-
**application_filter_dict,
|
|
386
|
-
)
|
|
389
|
+
self._update(attributes=event, table=table, criteria=criteria)
|
|
387
390
|
else:
|
|
388
391
|
# Write a new application result
|
|
389
|
-
event[
|
|
390
|
-
|
|
391
|
-
mlrun.common.schemas.model_monitoring.EventFieldType.UID
|
|
392
|
-
]
|
|
393
|
-
)
|
|
394
|
-
|
|
395
|
-
self._write(
|
|
396
|
-
table=mlrun.common.schemas.model_monitoring.FileTargetKind.APP_RESULTS,
|
|
397
|
-
event=event,
|
|
398
|
-
)
|
|
392
|
+
event[mm_schemas.EventFieldType.UID] = application_result_uid
|
|
393
|
+
self._write(table_name=table_name, event=event)
|
|
399
394
|
|
|
400
395
|
@staticmethod
|
|
401
|
-
def _convert_to_datetime(event: dict[str, typing.Any], key: str):
|
|
396
|
+
def _convert_to_datetime(event: dict[str, typing.Any], key: str) -> None:
|
|
402
397
|
if isinstance(event[key], str):
|
|
403
398
|
event[key] = datetime.datetime.fromisoformat(event[key])
|
|
399
|
+
event[key] = event[key].astimezone(tz=datetime.timezone.utc)
|
|
404
400
|
|
|
405
401
|
@staticmethod
|
|
406
|
-
def _generate_application_result_uid(
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
402
|
+
def _generate_application_result_uid(
|
|
403
|
+
event: dict[str, typing.Any],
|
|
404
|
+
kind: mm_schemas.WriterEventKind = mm_schemas.WriterEventKind.RESULT,
|
|
405
|
+
) -> str:
|
|
406
|
+
if kind == mm_schemas.WriterEventKind.RESULT:
|
|
407
|
+
name = event[mm_schemas.ResultData.RESULT_NAME]
|
|
408
|
+
else:
|
|
409
|
+
name = event[mm_schemas.MetricData.METRIC_NAME]
|
|
410
|
+
return "_".join(
|
|
411
|
+
[
|
|
412
|
+
event[mm_schemas.WriterEvent.ENDPOINT_ID],
|
|
413
|
+
event[mm_schemas.WriterEvent.APPLICATION_NAME],
|
|
414
|
+
name,
|
|
415
|
+
]
|
|
413
416
|
)
|
|
414
417
|
|
|
418
|
+
@staticmethod
|
|
419
|
+
def _get_filter_criteria(
|
|
420
|
+
*,
|
|
421
|
+
table: sqlalchemy.orm.decl_api.DeclarativeMeta,
|
|
422
|
+
endpoint_id: str,
|
|
423
|
+
application_name: typing.Optional[str] = None,
|
|
424
|
+
) -> list[BinaryExpression]:
|
|
425
|
+
"""
|
|
426
|
+
Return the filter criteria for the given endpoint_id and application_name.
|
|
427
|
+
Note: the table object must include the relevant columns:
|
|
428
|
+
`endpoint_id` and `application_name`.
|
|
429
|
+
"""
|
|
430
|
+
criteria = [table.endpoint_id == endpoint_id]
|
|
431
|
+
if application_name is not None:
|
|
432
|
+
criteria.append(table.application_name == application_name)
|
|
433
|
+
return criteria
|
|
434
|
+
|
|
415
435
|
def get_last_analyzed(self, endpoint_id: str, application_name: str) -> int:
|
|
416
436
|
"""
|
|
417
437
|
Get the last analyzed time for the provided model endpoint and application.
|
|
@@ -421,14 +441,15 @@ class SQLStoreBase(mlrun.model_monitoring.db.StoreBase):
|
|
|
421
441
|
|
|
422
442
|
:return: Timestamp as a Unix time.
|
|
423
443
|
:raise: MLRunNotFoundError if last analyzed value is not found.
|
|
424
|
-
|
|
425
444
|
"""
|
|
426
445
|
self._init_monitoring_schedules_table()
|
|
427
|
-
application_filter_dict = self.filter_endpoint_and_application_name(
|
|
428
|
-
endpoint_id=endpoint_id, application_name=application_name
|
|
429
|
-
)
|
|
430
446
|
monitoring_schedule_record = self._get(
|
|
431
|
-
table=self.MonitoringSchedulesTable,
|
|
447
|
+
table=self.MonitoringSchedulesTable,
|
|
448
|
+
criteria=self._get_filter_criteria(
|
|
449
|
+
table=self.MonitoringSchedulesTable,
|
|
450
|
+
endpoint_id=endpoint_id,
|
|
451
|
+
application_name=application_name,
|
|
452
|
+
),
|
|
432
453
|
)
|
|
433
454
|
if not monitoring_schedule_record:
|
|
434
455
|
raise mlrun.errors.MLRunNotFoundError(
|
|
@@ -450,53 +471,67 @@ class SQLStoreBase(mlrun.model_monitoring.db.StoreBase):
|
|
|
450
471
|
"""
|
|
451
472
|
self._init_monitoring_schedules_table()
|
|
452
473
|
|
|
453
|
-
|
|
454
|
-
|
|
474
|
+
criteria = self._get_filter_criteria(
|
|
475
|
+
table=self.MonitoringSchedulesTable,
|
|
476
|
+
endpoint_id=endpoint_id,
|
|
477
|
+
application_name=application_name,
|
|
455
478
|
)
|
|
456
479
|
monitoring_schedule_record = self._get(
|
|
457
|
-
table=self.MonitoringSchedulesTable,
|
|
480
|
+
table=self.MonitoringSchedulesTable, criteria=criteria
|
|
458
481
|
)
|
|
459
482
|
if not monitoring_schedule_record:
|
|
460
|
-
# Add a new record with
|
|
483
|
+
# Add a new record with last analyzed value
|
|
461
484
|
self._write(
|
|
462
|
-
|
|
485
|
+
table_name=mm_schemas.FileTargetKind.MONITORING_SCHEDULES,
|
|
463
486
|
event={
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
487
|
+
mm_schemas.SchedulingKeys.UID: uuid.uuid4().hex,
|
|
488
|
+
mm_schemas.SchedulingKeys.APPLICATION_NAME: application_name,
|
|
489
|
+
mm_schemas.SchedulingKeys.ENDPOINT_ID: endpoint_id,
|
|
490
|
+
mm_schemas.SchedulingKeys.LAST_ANALYZED: last_analyzed,
|
|
468
491
|
},
|
|
469
492
|
)
|
|
470
493
|
|
|
471
494
|
self._update(
|
|
472
|
-
attributes={
|
|
473
|
-
mlrun.common.schemas.model_monitoring.SchedulingKeys.LAST_ANALYZED: last_analyzed
|
|
474
|
-
},
|
|
495
|
+
attributes={mm_schemas.SchedulingKeys.LAST_ANALYZED: last_analyzed},
|
|
475
496
|
table=self.MonitoringSchedulesTable,
|
|
476
|
-
|
|
497
|
+
criteria=criteria,
|
|
477
498
|
)
|
|
478
499
|
|
|
479
|
-
def _delete_last_analyzed(
|
|
500
|
+
def _delete_last_analyzed(
|
|
501
|
+
self, endpoint_id: str, application_name: typing.Optional[str] = None
|
|
502
|
+
) -> None:
|
|
480
503
|
self._init_monitoring_schedules_table()
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
endpoint_id=endpoint_id,
|
|
504
|
+
criteria = self._get_filter_criteria(
|
|
505
|
+
table=self.MonitoringSchedulesTable,
|
|
506
|
+
endpoint_id=endpoint_id,
|
|
507
|
+
application_name=application_name,
|
|
484
508
|
)
|
|
485
|
-
|
|
486
509
|
# Delete the model endpoint record using sqlalchemy ORM
|
|
487
|
-
self._delete(table=self.MonitoringSchedulesTable,
|
|
510
|
+
self._delete(table=self.MonitoringSchedulesTable, criteria=criteria)
|
|
488
511
|
|
|
489
512
|
def _delete_application_result(
|
|
490
|
-
self, endpoint_id: str
|
|
491
|
-
):
|
|
513
|
+
self, endpoint_id: str, application_name: typing.Optional[str] = None
|
|
514
|
+
) -> None:
|
|
492
515
|
self._init_application_results_table()
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
endpoint_id=endpoint_id,
|
|
516
|
+
criteria = self._get_filter_criteria(
|
|
517
|
+
table=self.application_results_table,
|
|
518
|
+
endpoint_id=endpoint_id,
|
|
519
|
+
application_name=application_name,
|
|
496
520
|
)
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
521
|
+
# Delete the relevant records from the results table
|
|
522
|
+
self._delete(table=self.application_results_table, criteria=criteria)
|
|
523
|
+
|
|
524
|
+
def _delete_application_metrics(
|
|
525
|
+
self, endpoint_id: str, application_name: typing.Optional[str] = None
|
|
526
|
+
) -> None:
|
|
527
|
+
self._init_application_metrics_table()
|
|
528
|
+
criteria = self._get_filter_criteria(
|
|
529
|
+
table=self.application_metrics_table,
|
|
530
|
+
endpoint_id=endpoint_id,
|
|
531
|
+
application_name=application_name,
|
|
532
|
+
)
|
|
533
|
+
# Delete the relevant records from the metrics table
|
|
534
|
+
self._delete(table=self.application_metrics_table, criteria=criteria)
|
|
500
535
|
|
|
501
536
|
def _create_tables_if_not_exist(self):
|
|
502
537
|
self._init_tables()
|
|
@@ -504,9 +539,7 @@ class SQLStoreBase(mlrun.model_monitoring.db.StoreBase):
|
|
|
504
539
|
for table in self._tables:
|
|
505
540
|
# Create table if not exist. The `metadata` contains the `ModelEndpointsTable`
|
|
506
541
|
if not self._engine.has_table(table):
|
|
507
|
-
self._tables[table].metadata.create_all(
|
|
508
|
-
bind=self._engine
|
|
509
|
-
)
|
|
542
|
+
self._tables[table].metadata.create_all(bind=self._engine)
|
|
510
543
|
|
|
511
544
|
@staticmethod
|
|
512
545
|
def _filter_values(
|
|
@@ -567,9 +600,7 @@ class SQLStoreBase(mlrun.model_monitoring.db.StoreBase):
|
|
|
567
600
|
|
|
568
601
|
# Convert endpoint labels into dictionary
|
|
569
602
|
endpoint_labels = json.loads(
|
|
570
|
-
endpoint_dict.get(
|
|
571
|
-
mlrun.common.schemas.model_monitoring.EventFieldType.LABELS
|
|
572
|
-
)
|
|
603
|
+
endpoint_dict.get(mm_schemas.EventFieldType.LABELS)
|
|
573
604
|
)
|
|
574
605
|
|
|
575
606
|
for label in labels:
|
|
@@ -585,78 +616,69 @@ class SQLStoreBase(mlrun.model_monitoring.db.StoreBase):
|
|
|
585
616
|
|
|
586
617
|
return True
|
|
587
618
|
|
|
588
|
-
|
|
589
|
-
def filter_endpoint_and_application_name(
|
|
590
|
-
endpoint_id: str, application_name: str
|
|
591
|
-
) -> dict[str, str]:
|
|
592
|
-
"""Generate a dictionary filter for endpoint id and application name"""
|
|
593
|
-
if not endpoint_id and not application_name:
|
|
594
|
-
raise mlrun.errors.MLRunBadRequestError(
|
|
595
|
-
"Please provide a valid endpoint_id and/or application_name"
|
|
596
|
-
)
|
|
597
|
-
application_filter_dict = {}
|
|
598
|
-
if endpoint_id:
|
|
599
|
-
application_filter_dict[
|
|
600
|
-
mlrun.common.schemas.model_monitoring.SchedulingKeys.ENDPOINT_ID
|
|
601
|
-
] = endpoint_id
|
|
602
|
-
if application_name:
|
|
603
|
-
application_filter_dict[
|
|
604
|
-
mlrun.common.schemas.model_monitoring.SchedulingKeys.APPLICATION_NAME
|
|
605
|
-
] = application_name
|
|
606
|
-
return application_filter_dict
|
|
607
|
-
|
|
608
|
-
def delete_model_endpoints_resources(self, endpoints: list[dict[str, typing.Any]]):
|
|
619
|
+
def delete_model_endpoints_resources(self) -> None:
|
|
609
620
|
"""
|
|
610
|
-
Delete all model
|
|
611
|
-
|
|
612
|
-
:param endpoints: A list of model endpoints flattened dictionaries.
|
|
621
|
+
Delete all the model monitoring resources of the project in the SQL tables.
|
|
613
622
|
"""
|
|
623
|
+
endpoints = self.list_model_endpoints()
|
|
624
|
+
logger.debug("Deleting model monitoring resources", project=self.project)
|
|
614
625
|
|
|
615
626
|
for endpoint_dict in endpoints:
|
|
616
|
-
endpoint_id = endpoint_dict[
|
|
617
|
-
mlrun.common.schemas.model_monitoring.EventFieldType.UID
|
|
618
|
-
]
|
|
627
|
+
endpoint_id = endpoint_dict[mm_schemas.EventFieldType.UID]
|
|
619
628
|
|
|
620
629
|
# Delete last analyzed records
|
|
621
630
|
self._delete_last_analyzed(endpoint_id=endpoint_id)
|
|
622
631
|
|
|
623
|
-
# Delete application results records
|
|
632
|
+
# Delete application results and metrics records
|
|
624
633
|
self._delete_application_result(endpoint_id=endpoint_id)
|
|
634
|
+
self._delete_application_metrics(endpoint_id=endpoint_id)
|
|
625
635
|
|
|
626
636
|
# Delete model endpoint record
|
|
627
637
|
self.delete_model_endpoint(endpoint_id=endpoint_id)
|
|
628
638
|
|
|
629
|
-
def
|
|
630
|
-
self,
|
|
631
|
-
|
|
632
|
-
metrics: list[str],
|
|
633
|
-
start: str = "now-1h",
|
|
634
|
-
end: str = "now",
|
|
635
|
-
access_key: str = None,
|
|
636
|
-
) -> dict[str, list[tuple[str, float]]]:
|
|
639
|
+
def get_model_endpoint_metrics(
|
|
640
|
+
self, endpoint_id: str, type: mm_schemas.ModelEndpointMonitoringMetricType
|
|
641
|
+
) -> list[mm_schemas.ModelEndpointMonitoringMetric]:
|
|
637
642
|
"""
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
earliest time.
|
|
647
|
-
:param end: The end time of the metrics. Can be represented by a string containing an RFC 3339
|
|
648
|
-
time, a Unix timestamp in milliseconds, a relative time (`'now'` or
|
|
649
|
-
`'now-[0-9]+[mhd]'`, where `m` = minutes, `h` = hours, and `'d'` = days), or 0 for the
|
|
650
|
-
earliest time.
|
|
651
|
-
:param access_key: V3IO access key that will be used for generating Frames client object. If not
|
|
652
|
-
provided, the access key will be retrieved from the environment variables.
|
|
653
|
-
|
|
654
|
-
:return: A dictionary of metrics in which the key is a metric name and the value is a list of tuples that
|
|
655
|
-
includes timestamps and the values.
|
|
656
|
-
"""
|
|
657
|
-
# # TODO : Implement this method once Perometheus is supported
|
|
658
|
-
logger.warning(
|
|
659
|
-
"Real time metrics service using Prometheus will be implemented in 1.4.0"
|
|
643
|
+
Fetch the model endpoint metrics or results (according to `type`) for the
|
|
644
|
+
requested endpoint.
|
|
645
|
+
"""
|
|
646
|
+
logger.debug(
|
|
647
|
+
"Fetching metrics for model endpoint",
|
|
648
|
+
project=self.project,
|
|
649
|
+
endpoint_id=endpoint_id,
|
|
650
|
+
type=type,
|
|
660
651
|
)
|
|
652
|
+
if type == mm_schemas.ModelEndpointMonitoringMetricType.METRIC:
|
|
653
|
+
self._init_application_metrics_table()
|
|
654
|
+
table = self.application_metrics_table
|
|
655
|
+
name_col = mm_schemas.MetricData.METRIC_NAME
|
|
656
|
+
else:
|
|
657
|
+
self._init_application_results_table()
|
|
658
|
+
table = self.application_results_table
|
|
659
|
+
name_col = mm_schemas.ResultData.RESULT_NAME
|
|
660
|
+
|
|
661
|
+
# Note: the block below does not use self._get, as we need here all the
|
|
662
|
+
# results, not only `one_or_none`.
|
|
663
|
+
with sqlalchemy.orm.Session(self._engine) as session:
|
|
664
|
+
metric_rows = (
|
|
665
|
+
session.query(table) # pyright: ignore[reportOptionalCall]
|
|
666
|
+
.filter(table.endpoint_id == endpoint_id)
|
|
667
|
+
.all()
|
|
668
|
+
)
|
|
661
669
|
|
|
662
|
-
return
|
|
670
|
+
return [
|
|
671
|
+
mm_schemas.ModelEndpointMonitoringMetric(
|
|
672
|
+
project=self.project,
|
|
673
|
+
app=metric_row.application_name,
|
|
674
|
+
type=type,
|
|
675
|
+
name=getattr(metric_row, name_col),
|
|
676
|
+
full_name=mlrun.model_monitoring.helpers._compose_full_name(
|
|
677
|
+
project=self.project,
|
|
678
|
+
app=metric_row.application_name,
|
|
679
|
+
type=type,
|
|
680
|
+
name=getattr(metric_row, name_col),
|
|
681
|
+
),
|
|
682
|
+
)
|
|
683
|
+
for metric_row in metric_rows
|
|
684
|
+
]
|