mlrun 1.7.0rc37__py3-none-any.whl → 1.7.0rc39__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.

Files changed (52) hide show
  1. mlrun/alerts/alert.py +34 -30
  2. mlrun/common/schemas/alert.py +3 -0
  3. mlrun/common/schemas/model_monitoring/constants.py +4 -0
  4. mlrun/common/schemas/notification.py +4 -3
  5. mlrun/datastore/alibaba_oss.py +2 -2
  6. mlrun/datastore/azure_blob.py +124 -31
  7. mlrun/datastore/base.py +1 -1
  8. mlrun/datastore/dbfs_store.py +2 -2
  9. mlrun/datastore/google_cloud_storage.py +83 -20
  10. mlrun/datastore/s3.py +2 -2
  11. mlrun/datastore/sources.py +54 -0
  12. mlrun/datastore/targets.py +9 -53
  13. mlrun/db/httpdb.py +6 -1
  14. mlrun/errors.py +8 -0
  15. mlrun/execution.py +7 -0
  16. mlrun/feature_store/api.py +5 -0
  17. mlrun/feature_store/common.py +6 -11
  18. mlrun/feature_store/retrieval/job.py +1 -0
  19. mlrun/model.py +29 -3
  20. mlrun/model_monitoring/api.py +9 -0
  21. mlrun/model_monitoring/applications/_application_steps.py +36 -0
  22. mlrun/model_monitoring/applications/histogram_data_drift.py +15 -13
  23. mlrun/model_monitoring/controller.py +15 -11
  24. mlrun/model_monitoring/db/stores/v3io_kv/kv_store.py +14 -11
  25. mlrun/model_monitoring/db/tsdb/base.py +121 -1
  26. mlrun/model_monitoring/db/tsdb/tdengine/schemas.py +85 -47
  27. mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +100 -12
  28. mlrun/model_monitoring/db/tsdb/v3io/stream_graph_steps.py +23 -1
  29. mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +214 -36
  30. mlrun/model_monitoring/helpers.py +16 -17
  31. mlrun/model_monitoring/stream_processing.py +68 -27
  32. mlrun/projects/operations.py +1 -1
  33. mlrun/projects/pipelines.py +19 -30
  34. mlrun/projects/project.py +76 -52
  35. mlrun/run.py +8 -6
  36. mlrun/runtimes/__init__.py +19 -8
  37. mlrun/runtimes/nuclio/api_gateway.py +9 -0
  38. mlrun/runtimes/nuclio/application/application.py +64 -9
  39. mlrun/runtimes/nuclio/function.py +1 -1
  40. mlrun/runtimes/pod.py +2 -2
  41. mlrun/runtimes/remotesparkjob.py +2 -5
  42. mlrun/runtimes/sparkjob/spark3job.py +7 -9
  43. mlrun/serving/v2_serving.py +1 -0
  44. mlrun/track/trackers/mlflow_tracker.py +5 -0
  45. mlrun/utils/helpers.py +21 -0
  46. mlrun/utils/version/version.json +2 -2
  47. {mlrun-1.7.0rc37.dist-info → mlrun-1.7.0rc39.dist-info}/METADATA +14 -11
  48. {mlrun-1.7.0rc37.dist-info → mlrun-1.7.0rc39.dist-info}/RECORD +52 -52
  49. {mlrun-1.7.0rc37.dist-info → mlrun-1.7.0rc39.dist-info}/WHEEL +1 -1
  50. {mlrun-1.7.0rc37.dist-info → mlrun-1.7.0rc39.dist-info}/LICENSE +0 -0
  51. {mlrun-1.7.0rc37.dist-info → mlrun-1.7.0rc39.dist-info}/entry_points.txt +0 -0
  52. {mlrun-1.7.0rc37.dist-info → mlrun-1.7.0rc39.dist-info}/top_level.txt +0 -0
@@ -17,6 +17,8 @@ from dataclasses import dataclass
17
17
  from io import StringIO
18
18
  from typing import Optional, Union
19
19
 
20
+ import taosws
21
+
20
22
  import mlrun.common.schemas.model_monitoring as mm_schemas
21
23
  import mlrun.common.types
22
24
 
@@ -28,6 +30,9 @@ class _TDEngineColumnType:
28
30
  self.data_type = data_type
29
31
  self.length = length
30
32
 
33
+ def values_to_column(self, values):
34
+ raise NotImplementedError()
35
+
31
36
  def __str__(self):
32
37
  if self.length is not None:
33
38
  return f"{self.data_type}({self.length})"
@@ -44,6 +49,26 @@ class _TDEngineColumn(mlrun.common.types.StrEnum):
44
49
  BINARY_10000 = _TDEngineColumnType("BINARY", 10000)
45
50
 
46
51
 
52
+ def values_to_column(values, column_type):
53
+ if column_type == _TDEngineColumn.TIMESTAMP:
54
+ timestamps = [round(timestamp.timestamp() * 1000) for timestamp in values]
55
+ return taosws.millis_timestamps_to_column(timestamps)
56
+ if column_type == _TDEngineColumn.FLOAT:
57
+ return taosws.floats_to_column(values)
58
+ if column_type == _TDEngineColumn.INT:
59
+ return taosws.ints_to_column(values)
60
+ if column_type == _TDEngineColumn.BINARY_40:
61
+ return taosws.binary_to_column(values)
62
+ if column_type == _TDEngineColumn.BINARY_64:
63
+ return taosws.binary_to_column(values)
64
+ if column_type == _TDEngineColumn.BINARY_10000:
65
+ return taosws.binary_to_column(values)
66
+
67
+ raise mlrun.errors.MLRunInvalidArgumentError(
68
+ f"unsupported column type '{column_type}'"
69
+ )
70
+
71
+
47
72
  @dataclass
48
73
  class TDEngineSchema:
49
74
  """
@@ -55,13 +80,14 @@ class TDEngineSchema:
55
80
  def __init__(
56
81
  self,
57
82
  super_table: str,
58
- columns: dict[str, str],
83
+ columns: dict[str, _TDEngineColumn],
59
84
  tags: dict[str, str],
85
+ database: Optional[str] = None,
60
86
  ):
61
87
  self.super_table = super_table
62
88
  self.columns = columns
63
89
  self.tags = tags
64
- self.database = _MODEL_MONITORING_DATABASE
90
+ self.database = database or _MODEL_MONITORING_DATABASE
65
91
 
66
92
  def _create_super_table_query(self) -> str:
67
93
  columns = ", ".join(f"{col} {val}" for col, val in self.columns.items())
@@ -83,11 +109,23 @@ class TDEngineSchema:
83
109
 
84
110
  def _insert_subtable_query(
85
111
  self,
112
+ connection: taosws.Connection,
86
113
  subtable: str,
87
114
  values: dict[str, Union[str, int, float, datetime.datetime]],
88
- ) -> str:
89
- values = ", ".join(f"'{values[val]}'" for val in self.columns)
90
- return f"INSERT INTO {self.database}.{subtable} VALUES ({values});"
115
+ ) -> taosws.TaosStmt:
116
+ stmt = connection.statement()
117
+ question_marks = ", ".join("?" * len(self.columns))
118
+ stmt.prepare(f"INSERT INTO ? VALUES ({question_marks});")
119
+ stmt.set_tbname_tags(subtable, [])
120
+
121
+ bind_params = []
122
+
123
+ for col_name, col_type in self.columns.items():
124
+ val = values[col_name]
125
+ bind_params.append(values_to_column([val], col_type))
126
+
127
+ stmt.bind_param(bind_params)
128
+ return stmt
91
129
 
92
130
  def _delete_subtable_query(
93
131
  self,
@@ -188,53 +226,53 @@ class TDEngineSchema:
188
226
 
189
227
  @dataclass
190
228
  class AppResultTable(TDEngineSchema):
191
- super_table = mm_schemas.TDEngineSuperTables.APP_RESULTS
192
- columns = {
193
- mm_schemas.WriterEvent.END_INFER_TIME: _TDEngineColumn.TIMESTAMP,
194
- mm_schemas.WriterEvent.START_INFER_TIME: _TDEngineColumn.TIMESTAMP,
195
- mm_schemas.ResultData.RESULT_VALUE: _TDEngineColumn.FLOAT,
196
- mm_schemas.ResultData.RESULT_STATUS: _TDEngineColumn.INT,
197
- mm_schemas.ResultData.CURRENT_STATS: _TDEngineColumn.BINARY_10000,
198
- }
199
-
200
- tags = {
201
- mm_schemas.EventFieldType.PROJECT: _TDEngineColumn.BINARY_64,
202
- mm_schemas.WriterEvent.ENDPOINT_ID: _TDEngineColumn.BINARY_64,
203
- mm_schemas.WriterEvent.APPLICATION_NAME: _TDEngineColumn.BINARY_64,
204
- mm_schemas.ResultData.RESULT_NAME: _TDEngineColumn.BINARY_64,
205
- mm_schemas.ResultData.RESULT_KIND: _TDEngineColumn.INT,
206
- }
207
- database = _MODEL_MONITORING_DATABASE
229
+ def __init__(self, database: Optional[str] = None):
230
+ super_table = mm_schemas.TDEngineSuperTables.APP_RESULTS
231
+ columns = {
232
+ mm_schemas.WriterEvent.END_INFER_TIME: _TDEngineColumn.TIMESTAMP,
233
+ mm_schemas.WriterEvent.START_INFER_TIME: _TDEngineColumn.TIMESTAMP,
234
+ mm_schemas.ResultData.RESULT_VALUE: _TDEngineColumn.FLOAT,
235
+ mm_schemas.ResultData.RESULT_STATUS: _TDEngineColumn.INT,
236
+ }
237
+ tags = {
238
+ mm_schemas.EventFieldType.PROJECT: _TDEngineColumn.BINARY_64,
239
+ mm_schemas.WriterEvent.ENDPOINT_ID: _TDEngineColumn.BINARY_64,
240
+ mm_schemas.WriterEvent.APPLICATION_NAME: _TDEngineColumn.BINARY_64,
241
+ mm_schemas.ResultData.RESULT_NAME: _TDEngineColumn.BINARY_64,
242
+ mm_schemas.ResultData.RESULT_KIND: _TDEngineColumn.INT,
243
+ }
244
+ super().__init__(super_table, columns, tags, database)
208
245
 
209
246
 
210
247
  @dataclass
211
248
  class Metrics(TDEngineSchema):
212
- super_table = mm_schemas.TDEngineSuperTables.METRICS
213
- columns = {
214
- mm_schemas.WriterEvent.END_INFER_TIME: _TDEngineColumn.TIMESTAMP,
215
- mm_schemas.WriterEvent.START_INFER_TIME: _TDEngineColumn.TIMESTAMP,
216
- mm_schemas.MetricData.METRIC_VALUE: _TDEngineColumn.FLOAT,
217
- }
218
-
219
- tags = {
220
- mm_schemas.EventFieldType.PROJECT: _TDEngineColumn.BINARY_64,
221
- mm_schemas.WriterEvent.ENDPOINT_ID: _TDEngineColumn.BINARY_64,
222
- mm_schemas.WriterEvent.APPLICATION_NAME: _TDEngineColumn.BINARY_64,
223
- mm_schemas.MetricData.METRIC_NAME: _TDEngineColumn.BINARY_64,
224
- }
225
- database = _MODEL_MONITORING_DATABASE
249
+ def __init__(self, database: Optional[str] = None):
250
+ super_table = mm_schemas.TDEngineSuperTables.METRICS
251
+ columns = {
252
+ mm_schemas.WriterEvent.END_INFER_TIME: _TDEngineColumn.TIMESTAMP,
253
+ mm_schemas.WriterEvent.START_INFER_TIME: _TDEngineColumn.TIMESTAMP,
254
+ mm_schemas.MetricData.METRIC_VALUE: _TDEngineColumn.FLOAT,
255
+ }
256
+ tags = {
257
+ mm_schemas.EventFieldType.PROJECT: _TDEngineColumn.BINARY_64,
258
+ mm_schemas.WriterEvent.ENDPOINT_ID: _TDEngineColumn.BINARY_64,
259
+ mm_schemas.WriterEvent.APPLICATION_NAME: _TDEngineColumn.BINARY_64,
260
+ mm_schemas.MetricData.METRIC_NAME: _TDEngineColumn.BINARY_64,
261
+ }
262
+ super().__init__(super_table, columns, tags, database)
226
263
 
227
264
 
228
265
  @dataclass
229
266
  class Predictions(TDEngineSchema):
230
- super_table = mm_schemas.TDEngineSuperTables.PREDICTIONS
231
- columns = {
232
- mm_schemas.EventFieldType.TIME: _TDEngineColumn.TIMESTAMP,
233
- mm_schemas.EventFieldType.LATENCY: _TDEngineColumn.FLOAT,
234
- mm_schemas.EventKeyMetrics.CUSTOM_METRICS: _TDEngineColumn.BINARY_10000,
235
- }
236
- tags = {
237
- mm_schemas.EventFieldType.PROJECT: _TDEngineColumn.BINARY_64,
238
- mm_schemas.WriterEvent.ENDPOINT_ID: _TDEngineColumn.BINARY_64,
239
- }
240
- database = _MODEL_MONITORING_DATABASE
267
+ def __init__(self, database: Optional[str] = None):
268
+ super_table = mm_schemas.TDEngineSuperTables.PREDICTIONS
269
+ columns = {
270
+ mm_schemas.EventFieldType.TIME: _TDEngineColumn.TIMESTAMP,
271
+ mm_schemas.EventFieldType.LATENCY: _TDEngineColumn.FLOAT,
272
+ mm_schemas.EventKeyMetrics.CUSTOM_METRICS: _TDEngineColumn.BINARY_10000,
273
+ }
274
+ tags = {
275
+ mm_schemas.EventFieldType.PROJECT: _TDEngineColumn.BINARY_64,
276
+ mm_schemas.WriterEvent.ENDPOINT_ID: _TDEngineColumn.BINARY_64,
277
+ }
278
+ super().__init__(super_table, columns, tags, database)
@@ -14,6 +14,7 @@
14
14
 
15
15
  import typing
16
16
  from datetime import datetime
17
+ from typing import Union
17
18
 
18
19
  import pandas as pd
19
20
  import taosws
@@ -57,15 +58,26 @@ class TDEngineConnector(TSDBConnector):
57
58
  except taosws.QueryError:
58
59
  # Database already exists
59
60
  pass
60
- conn.execute(f"USE {self.database}")
61
+ try:
62
+ conn.execute(f"USE {self.database}")
63
+ except taosws.QueryError as e:
64
+ raise mlrun.errors.MLRunTSDBConnectionFailure(
65
+ f"Failed to use TDEngine database {self.database}, {mlrun.errors.err_to_str(e)}"
66
+ )
61
67
  return conn
62
68
 
63
69
  def _init_super_tables(self):
64
70
  """Initialize the super tables for the TSDB."""
65
71
  self.tables = {
66
- mm_schemas.TDEngineSuperTables.APP_RESULTS: tdengine_schemas.AppResultTable(),
67
- mm_schemas.TDEngineSuperTables.METRICS: tdengine_schemas.Metrics(),
68
- mm_schemas.TDEngineSuperTables.PREDICTIONS: tdengine_schemas.Predictions(),
72
+ mm_schemas.TDEngineSuperTables.APP_RESULTS: tdengine_schemas.AppResultTable(
73
+ self.database
74
+ ),
75
+ mm_schemas.TDEngineSuperTables.METRICS: tdengine_schemas.Metrics(
76
+ self.database
77
+ ),
78
+ mm_schemas.TDEngineSuperTables.PREDICTIONS: tdengine_schemas.Predictions(
79
+ self.database
80
+ ),
69
81
  }
70
82
 
71
83
  def create_tables(self):
@@ -96,6 +108,7 @@ class TDEngineConnector(TSDBConnector):
96
108
  table_name = (
97
109
  f"{table_name}_" f"{event[mm_schemas.ResultData.RESULT_NAME]}"
98
110
  ).replace("-", "_")
111
+ event.pop(mm_schemas.ResultData.CURRENT_STATS, None)
99
112
 
100
113
  else:
101
114
  # Write a new metric
@@ -104,14 +117,30 @@ class TDEngineConnector(TSDBConnector):
104
117
  f"{table_name}_" f"{event[mm_schemas.MetricData.METRIC_NAME]}"
105
118
  ).replace("-", "_")
106
119
 
120
+ # Convert the datetime strings to datetime objects
121
+ event[mm_schemas.WriterEvent.END_INFER_TIME] = self._convert_to_datetime(
122
+ val=event[mm_schemas.WriterEvent.END_INFER_TIME]
123
+ )
124
+ event[mm_schemas.WriterEvent.START_INFER_TIME] = self._convert_to_datetime(
125
+ val=event[mm_schemas.WriterEvent.START_INFER_TIME]
126
+ )
127
+
107
128
  create_table_query = table._create_subtable_query(
108
129
  subtable=table_name, values=event
109
130
  )
110
131
  self._connection.execute(create_table_query)
111
- insert_table_query = table._insert_subtable_query(
112
- subtable=table_name, values=event
132
+
133
+ insert_statement = table._insert_subtable_query(
134
+ self._connection,
135
+ subtable=table_name,
136
+ values=event,
113
137
  )
114
- self._connection.execute(insert_table_query)
138
+ insert_statement.add_batch()
139
+ insert_statement.execute()
140
+
141
+ @staticmethod
142
+ def _convert_to_datetime(val: typing.Union[str, datetime]) -> datetime:
143
+ return datetime.fromisoformat(val) if isinstance(val, str) else val
115
144
 
116
145
  def apply_monitoring_stream_steps(self, graph):
117
146
  """
@@ -156,6 +185,9 @@ class TDEngineConnector(TSDBConnector):
156
185
  after="ProcessBeforeTDEngine",
157
186
  )
158
187
 
188
+ def handle_model_error(self, graph, **kwargs) -> None:
189
+ pass
190
+
159
191
  def delete_tsdb_resources(self):
160
192
  """
161
193
  Delete all project resources in the TSDB connector, such as model endpoints data and drift results.
@@ -246,11 +278,9 @@ class TDEngineConnector(TSDBConnector):
246
278
  raise mlrun.errors.MLRunInvalidArgumentError(
247
279
  f"Failed to query table {table} in database {self.database}, {str(e)}"
248
280
  )
249
- columns = []
250
- for column in query_result.fields:
251
- columns.append(column.name())
252
281
 
253
- return pd.DataFrame(query_result, columns=columns)
282
+ df_columns = [field.name() for field in query_result.fields]
283
+ return pd.DataFrame(query_result, columns=df_columns)
254
284
 
255
285
  def read_metrics_data(
256
286
  self,
@@ -274,13 +304,22 @@ class TDEngineConnector(TSDBConnector):
274
304
  ],
275
305
  ],
276
306
  ]:
307
+ timestamp_column = mm_schemas.WriterEvent.END_INFER_TIME
308
+ columns = [timestamp_column, mm_schemas.WriterEvent.APPLICATION_NAME]
277
309
  if type == "metrics":
278
310
  table = mm_schemas.TDEngineSuperTables.METRICS
279
311
  name = mm_schemas.MetricData.METRIC_NAME
312
+ columns += [name, mm_schemas.MetricData.METRIC_VALUE]
280
313
  df_handler = self.df_to_metrics_values
281
314
  elif type == "results":
282
315
  table = mm_schemas.TDEngineSuperTables.APP_RESULTS
283
316
  name = mm_schemas.ResultData.RESULT_NAME
317
+ columns += [
318
+ name,
319
+ mm_schemas.ResultData.RESULT_VALUE,
320
+ mm_schemas.ResultData.RESULT_STATUS,
321
+ mm_schemas.ResultData.RESULT_KIND,
322
+ ]
284
323
  df_handler = self.df_to_results_values
285
324
  else:
286
325
  raise mlrun.errors.MLRunInvalidArgumentError(
@@ -300,7 +339,8 @@ class TDEngineConnector(TSDBConnector):
300
339
  start=start,
301
340
  end=end,
302
341
  filter_query=filter_query,
303
- timestamp_column=mm_schemas.WriterEvent.END_INFER_TIME,
342
+ timestamp_column=timestamp_column,
343
+ columns=columns,
304
344
  )
305
345
 
306
346
  df[mm_schemas.WriterEvent.END_INFER_TIME] = pd.to_datetime(
@@ -377,6 +417,54 @@ class TDEngineConnector(TSDBConnector):
377
417
  ), # pyright: ignore[reportArgumentType]
378
418
  )
379
419
 
420
+ def get_last_request(
421
+ self,
422
+ endpoint_ids: Union[str, list[str]],
423
+ start: Union[datetime, str] = "0",
424
+ end: Union[datetime, str] = "now",
425
+ ) -> pd.DataFrame:
426
+ pass
427
+
428
+ def get_drift_status(
429
+ self,
430
+ endpoint_ids: Union[str, list[str]],
431
+ start: Union[datetime, str] = "now-24h",
432
+ end: Union[datetime, str] = "now",
433
+ ) -> pd.DataFrame:
434
+ pass
435
+
436
+ def get_metrics_metadata(
437
+ self,
438
+ endpoint_id: str,
439
+ start: Union[datetime, str] = "0",
440
+ end: Union[datetime, str] = "now",
441
+ ) -> pd.DataFrame:
442
+ pass
443
+
444
+ def get_results_metadata(
445
+ self,
446
+ endpoint_id: str,
447
+ start: Union[datetime, str] = "0",
448
+ end: Union[datetime, str] = "now",
449
+ ) -> pd.DataFrame:
450
+ pass
451
+
452
+ def get_error_count(
453
+ self,
454
+ endpoint_ids: Union[str, list[str]],
455
+ start: Union[datetime, str] = "0",
456
+ end: Union[datetime, str] = "now",
457
+ ) -> pd.DataFrame:
458
+ pass
459
+
460
+ def get_avg_latency(
461
+ self,
462
+ endpoint_ids: Union[str, list[str]],
463
+ start: Union[datetime, str] = "0",
464
+ end: Union[datetime, str] = "now",
465
+ ) -> pd.DataFrame:
466
+ pass
467
+
380
468
  # Note: this function serves as a reference for checking the TSDB for the existence of a metric.
381
469
  #
382
470
  # def read_prediction_metric_for_endpoint_if_exists(
@@ -11,7 +11,7 @@
11
11
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
-
14
+ from datetime import datetime
15
15
  from typing import Any
16
16
 
17
17
  import mlrun.feature_store.steps
@@ -20,6 +20,7 @@ from mlrun.common.schemas.model_monitoring import (
20
20
  EventKeyMetrics,
21
21
  EventLiveStats,
22
22
  )
23
+ from mlrun.utils import logger
23
24
 
24
25
 
25
26
  def _normalize_dict_for_v3io_frames(event: dict[str, Any]) -> dict[str, Any]:
@@ -134,3 +135,24 @@ class FilterAndUnpackKeys(mlrun.feature_store.steps.MapClass):
134
135
  else:
135
136
  unpacked[key] = new_event[key]
136
137
  return unpacked if unpacked else None
138
+
139
+
140
+ class ErrorExtractor(mlrun.feature_store.steps.MapClass):
141
+ def __init__(self, **kwargs):
142
+ """
143
+ Prepare the event for insertion into the errors TSDB table.
144
+ """
145
+ super().__init__(**kwargs)
146
+
147
+ def do(self, event):
148
+ error = event.get("error")
149
+ timestamp = datetime.fromisoformat(event.get("when"))
150
+ endpoint_id = event[EventFieldType.ENDPOINT_ID]
151
+ event = {
152
+ EventFieldType.MODEL_ERROR: str(error),
153
+ EventFieldType.ENDPOINT_ID: endpoint_id,
154
+ EventFieldType.TIMESTAMP: timestamp,
155
+ EventFieldType.ERROR_COUNT: 1.0,
156
+ }
157
+ logger.info("Write error to errors TSDB table", event=event)
158
+ return event