mlrun 1.10.0rc16__py3-none-any.whl → 1.10.0rc17__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/common/constants.py CHANGED
@@ -84,6 +84,8 @@ class MLRunInternalLabels:
84
84
  original_workflow_id = "original-workflow-id"
85
85
  workflow_id = "workflow-id"
86
86
  retrying = "retrying"
87
+ rerun_counter = "rerun-counter"
88
+ rerun_index = "rerun-index"
87
89
 
88
90
  owner = "owner"
89
91
  v3io_user = "v3io_user"
@@ -133,6 +133,7 @@ from .k8s import NodeSelectorOperator, Resources, ResourceSpec
133
133
  from .memory_reports import MostCommonObjectTypesReport, ObjectTypeReport
134
134
  from .model_monitoring import (
135
135
  DriftStatus,
136
+ EndpointMode,
136
137
  EndpointType,
137
138
  EndpointUID,
138
139
  EventFieldType,
@@ -16,6 +16,7 @@ from .constants import (
16
16
  INTERSECT_DICT_KEYS,
17
17
  ApplicationEvent,
18
18
  DriftStatus,
19
+ EndpointMode,
19
20
  EndpointType,
20
21
  EndpointUID,
21
22
  EventFieldType,
@@ -205,6 +205,11 @@ class ControllerEvent(MonitoringStrEnum):
205
205
  FIRST_REQUEST = "first_request"
206
206
  FEATURE_SET_URI = "feature_set_uri"
207
207
  ENDPOINT_TYPE = "endpoint_type"
208
+
209
+ # first_timestamp and last_timestamp are used to batch completed events
210
+ FIRST_TIMESTAMP = "first_timestamp"
211
+ LAST_TIMESTAMP = "last_timestamp"
212
+
208
213
  ENDPOINT_POLICY = "endpoint_policy"
209
214
  # Note: currently under endpoint policy we will have a dictionary including the keys: "application_names"
210
215
  # "base_period", and "updated_endpoint" stand for when the MEP was updated
@@ -219,6 +224,7 @@ class ControllerEventEndpointPolicy(MonitoringStrEnum):
219
224
  class ControllerEventKind(MonitoringStrEnum):
220
225
  NOP_EVENT = "nop_event"
221
226
  REGULAR_EVENT = "regular_event"
227
+ BATCH_COMPLETE = "batch_complete"
222
228
 
223
229
 
224
230
  class MetricData(MonitoringStrEnum):
@@ -320,6 +326,19 @@ class EndpointType(IntEnum):
320
326
  def top_level_list(cls):
321
327
  return [cls.NODE_EP, cls.ROUTER, cls.BATCH_EP]
322
328
 
329
+ @classmethod
330
+ def real_time_list(cls):
331
+ return [cls.NODE_EP, cls.ROUTER, cls.LEAF_EP]
332
+
333
+ @classmethod
334
+ def batch_list(cls):
335
+ return [cls.BATCH_EP]
336
+
337
+
338
+ class EndpointMode(StrEnum):
339
+ REAL_TIME = "real_time"
340
+ BATCH = "batch"
341
+
323
342
 
324
343
  class MonitoringFunctionNames(MonitoringStrEnum):
325
344
  STREAM = "model-monitoring-stream"
@@ -47,3 +47,6 @@ class MonitoringData(StrEnum):
47
47
  class ModelsData(enum.Enum):
48
48
  MODEL_CLASS = 0
49
49
  MODEL_PARAMETERS = 1
50
+
51
+
52
+ MAX_BATCH_JOB_DURATION = "1w"
@@ -53,6 +53,7 @@ class RerunWorkflowRequest(pydantic.v1.BaseModel):
53
53
  workflow_runner_node_selector: typing.Optional[dict[str, str]] = None
54
54
  original_workflow_runner_uid: typing.Optional[str] = None
55
55
  original_workflow_name: typing.Optional[str] = None
56
+ rerun_index: typing.Optional[int] = None
56
57
 
57
58
 
58
59
  class WorkflowResponse(pydantic.v1.BaseModel):
mlrun/config.py CHANGED
@@ -406,11 +406,7 @@ default_config = {
406
406
  #
407
407
  # if set to "nil" or "none", nothing would be set
408
408
  "modes": (
409
- "STRICT_TRANS_TABLES"
410
- ",NO_ZERO_IN_DATE"
411
- ",NO_ZERO_DATE"
412
- ",ERROR_FOR_DIVISION_BY_ZERO"
413
- ",NO_ENGINE_SUBSTITUTION",
409
+ "STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION"
414
410
  )
415
411
  },
416
412
  },
mlrun/db/base.py CHANGED
@@ -741,6 +741,7 @@ class RunDBInterface(ABC):
741
741
  tsdb_metrics: bool = False,
742
742
  metric_list: Optional[list[str]] = None,
743
743
  top_level: bool = False,
744
+ mode: Optional[mlrun.common.schemas.EndpointMode] = None,
744
745
  uids: Optional[list[str]] = None,
745
746
  latest_only: bool = False,
746
747
  ) -> mlrun.common.schemas.ModelEndpointList:
mlrun/db/httpdb.py CHANGED
@@ -3813,6 +3813,7 @@ class HTTPRunDB(RunDBInterface):
3813
3813
  tsdb_metrics: bool = False,
3814
3814
  metric_list: Optional[list[str]] = None,
3815
3815
  top_level: bool = False,
3816
+ mode: mm_constants.EndpointMode = None,
3816
3817
  uids: Optional[list[str]] = None,
3817
3818
  latest_only: bool = False,
3818
3819
  ) -> mlrun.common.schemas.ModelEndpointList:
@@ -3833,6 +3834,8 @@ class HTTPRunDB(RunDBInterface):
3833
3834
  If tsdb_metrics=False, this parameter will be ignored and no tsdb metrics
3834
3835
  will be included.
3835
3836
  :param top_level: Whether to return only top level model endpoints.
3837
+ :param mode: Specifies the mode of the model endpoint. Can be "real-time", "batch", or both if set
3838
+ to None.
3836
3839
  :param uids: A list of unique ids to filter by.
3837
3840
  :param latest_only: Whether to return only the latest model endpoint version.
3838
3841
  :return: A list of model endpoints.
@@ -3856,6 +3859,7 @@ class HTTPRunDB(RunDBInterface):
3856
3859
  "tsdb-metrics": tsdb_metrics,
3857
3860
  "metric": metric_list,
3858
3861
  "top-level": top_level,
3862
+ "mode": mode,
3859
3863
  "uid": uids,
3860
3864
  "latest-only": latest_only,
3861
3865
  },
@@ -12,13 +12,11 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- import collections
16
15
  import concurrent.futures
17
16
  import datetime
18
17
  import json
19
18
  import os
20
19
  import traceback
21
- from collections import OrderedDict
22
20
  from collections.abc import Iterator
23
21
  from contextlib import AbstractContextManager
24
22
  from types import TracebackType
@@ -29,20 +27,17 @@ import pandas as pd
29
27
 
30
28
  import mlrun
31
29
  import mlrun.common.schemas.model_monitoring.constants as mm_constants
32
- import mlrun.feature_store as fstore
33
30
  import mlrun.model_monitoring
34
31
  import mlrun.model_monitoring.db._schedules as schedules
35
32
  import mlrun.model_monitoring.helpers
36
33
  import mlrun.platforms.iguazio
37
- from mlrun.common.schemas import EndpointType
38
34
  from mlrun.common.schemas.model_monitoring.constants import (
39
35
  ControllerEvent,
40
36
  ControllerEventEndpointPolicy,
41
- ControllerEventKind,
42
37
  )
43
38
  from mlrun.errors import err_to_str
44
39
  from mlrun.model_monitoring.helpers import batch_dict2timedelta
45
- from mlrun.utils import datetime_now, logger
40
+ from mlrun.utils import logger
46
41
 
47
42
  _SECONDS_IN_DAY = int(datetime.timedelta(days=1).total_seconds())
48
43
  _SECONDS_IN_MINUTE = 60
@@ -62,6 +57,7 @@ class _BatchWindow:
62
57
  timedelta_seconds: int,
63
58
  last_updated: int,
64
59
  first_request: int,
60
+ endpoint_mode: mm_constants.EndpointMode = mm_constants.EndpointMode.REAL_TIME,
65
61
  ) -> None:
66
62
  """
67
63
  Initialize a batch window object that handles the batch interval time range
@@ -74,6 +70,7 @@ class _BatchWindow:
74
70
  self._stop = last_updated
75
71
  self._step = timedelta_seconds
76
72
  self._db = schedules_file
73
+ self._endpoint_mode = endpoint_mode
77
74
  self._start = self._get_last_analyzed()
78
75
 
79
76
  def _get_saved_last_analyzed(self) -> Optional[int]:
@@ -85,10 +82,20 @@ class _BatchWindow:
85
82
  )
86
83
 
87
84
  def _get_initial_last_analyzed(self) -> int:
85
+ if self._endpoint_mode == mm_constants.EndpointMode.BATCH:
86
+ logger.info(
87
+ "No last analyzed time was found for this endpoint and application, as this is "
88
+ "probably the first time this application is running. Initializing last analyzed "
89
+ "to the start of the batch time",
90
+ application=self._application,
91
+ start_batch_time=self._first_request,
92
+ )
93
+ return self._first_request
88
94
  logger.info(
89
95
  "No last analyzed time was found for this endpoint and application, as this is "
90
96
  "probably the first time this application is running. Initializing last analyzed "
91
- "to the latest between first request time or last update time minus one day",
97
+ "to the latest between first request the latest between first request time or last "
98
+ "update time minus one day",
92
99
  application=self._application,
93
100
  first_request=self._first_request,
94
101
  last_updated=self._stop,
@@ -103,6 +110,9 @@ class _BatchWindow:
103
110
  def _get_last_analyzed(self) -> int:
104
111
  saved_last_analyzed = self._get_saved_last_analyzed()
105
112
  if saved_last_analyzed is not None:
113
+ if self._endpoint_mode == mm_constants.EndpointMode.BATCH:
114
+ # Use the maximum between the saved last analyzed and the start of the batch
115
+ return max(saved_last_analyzed, self._first_request)
106
116
  return saved_last_analyzed
107
117
  else:
108
118
  last_analyzed = self._get_initial_last_analyzed()
@@ -113,6 +123,7 @@ class _BatchWindow:
113
123
  def get_intervals(self) -> Iterator[_Interval]:
114
124
  """Generate the batch interval time ranges."""
115
125
  entered = False
126
+ last_analyzed = None
116
127
  # Iterate timestamp from start until timestamp <= stop - step
117
128
  # so that the last interval will end at (timestamp + step) <= stop.
118
129
  # Add 1 to stop - step to get <= and not <.
@@ -134,6 +145,40 @@ class _BatchWindow:
134
145
  last_analyzed=last_analyzed,
135
146
  )
136
147
 
148
+ if self._endpoint_mode == mm_constants.EndpointMode.BATCH:
149
+ # If the endpoint is a batch endpoint, we need to update the last analyzed time
150
+ # to the end of the batch time.
151
+ if last_analyzed:
152
+ if last_analyzed < self._stop:
153
+ # If the last analyzed time is earlier than the stop time,
154
+ # yield the final partial interval from last_analyzed to stop
155
+ yield _Interval(
156
+ datetime.datetime.fromtimestamp(
157
+ last_analyzed, tz=datetime.timezone.utc
158
+ ),
159
+ datetime.datetime.fromtimestamp(
160
+ self._stop, tz=datetime.timezone.utc
161
+ ),
162
+ )
163
+ else:
164
+ # The time span between the start and end of the batch is shorter than the step,
165
+ # so we need to yield a partial interval covering that range.
166
+ yield _Interval(
167
+ datetime.datetime.fromtimestamp(
168
+ self._start, tz=datetime.timezone.utc
169
+ ),
170
+ datetime.datetime.fromtimestamp(
171
+ self._stop, tz=datetime.timezone.utc
172
+ ),
173
+ )
174
+
175
+ self._update_last_analyzed(last_analyzed=self._stop)
176
+ logger.debug(
177
+ "Updated the last analyzed time for this endpoint and application to the end of the batch time",
178
+ application=self._application,
179
+ last_analyzed=self._stop,
180
+ )
181
+
137
182
  if not entered:
138
183
  logger.debug(
139
184
  "All the data is set, but no complete intervals were found. "
@@ -183,28 +228,25 @@ class _BatchWindowGenerator(AbstractContextManager):
183
228
 
184
229
  @classmethod
185
230
  def _get_last_updated_time(
186
- cls, last_request: datetime.datetime, not_batch_endpoint: bool
231
+ cls,
232
+ last_request: datetime.datetime,
233
+ endpoint_mode: mm_constants.EndpointMode,
187
234
  ) -> int:
188
235
  """
189
236
  Get the last updated time of a model endpoint.
190
237
  """
191
- last_updated = int(
192
- last_request.timestamp()
193
- - cast(
194
- float,
195
- mlrun.mlconf.model_endpoint_monitoring.parquet_batching_timeout_secs,
196
- )
197
- )
198
- if not not_batch_endpoint:
199
- # If the endpoint does not have a stream, `last_updated` should be
200
- # the minimum between the current time and the last updated time.
201
- # This compensates for the bumping mechanism - see
202
- # `update_model_endpoint_last_request`.
203
- last_updated = min(int(datetime_now().timestamp()), last_updated)
204
- logger.debug(
205
- "The endpoint does not have a stream", last_updated=last_updated
238
+
239
+ if endpoint_mode == mm_constants.EndpointMode.REAL_TIME:
240
+ last_updated = int(
241
+ last_request.timestamp()
242
+ - cast(
243
+ float,
244
+ mlrun.mlconf.model_endpoint_monitoring.parquet_batching_timeout_secs,
245
+ )
206
246
  )
207
- return last_updated
247
+
248
+ return last_updated
249
+ return int(last_request.timestamp())
208
250
 
209
251
  def get_intervals(
210
252
  self,
@@ -212,19 +254,21 @@ class _BatchWindowGenerator(AbstractContextManager):
212
254
  application: str,
213
255
  first_request: datetime.datetime,
214
256
  last_request: datetime.datetime,
215
- not_batch_endpoint: bool,
257
+ endpoint_mode: mm_constants.EndpointMode,
216
258
  ) -> Iterator[_Interval]:
217
259
  """
218
260
  Get the batch window for a specific endpoint and application.
219
261
  `first_request` and `last_request` are the timestamps of the first request and last
220
262
  request to the endpoint, respectively. They are guaranteed to be nonempty at this point.
221
263
  """
264
+
222
265
  self.batch_window = _BatchWindow(
223
266
  schedules_file=self._schedules_file,
224
267
  application=application,
225
268
  timedelta_seconds=self._timedelta,
226
- last_updated=self._get_last_updated_time(last_request, not_batch_endpoint),
269
+ last_updated=self._get_last_updated_time(last_request, endpoint_mode),
227
270
  first_request=int(first_request.timestamp()),
271
+ endpoint_mode=endpoint_mode,
228
272
  )
229
273
  yield from self.batch_window.get_intervals()
230
274
 
@@ -247,8 +291,6 @@ class MonitoringApplicationController:
247
291
  Note that the MonitoringApplicationController object requires access keys along with valid project configurations.
248
292
  """
249
293
 
250
- _MAX_FEATURE_SET_PER_WORKER = 1000
251
-
252
294
  def __init__(self) -> None:
253
295
  """Initialize Monitoring Application Controller"""
254
296
  self.project = cast(str, mlrun.mlconf.active_project)
@@ -282,9 +324,6 @@ class MonitoringApplicationController:
282
324
  mlrun.platforms.iguazio.KafkaOutputStream,
283
325
  ],
284
326
  ] = {}
285
- self.feature_sets: OrderedDict[str, mlrun.feature_store.FeatureSet] = (
286
- collections.OrderedDict()
287
- )
288
327
  self.tsdb_connector = mlrun.model_monitoring.get_tsdb_connector(
289
328
  project=self.project
290
329
  )
@@ -421,7 +460,6 @@ class MonitoringApplicationController:
421
460
  last_request=endpoint.status.last_request,
422
461
  first_request=endpoint.status.first_request,
423
462
  endpoint_type=endpoint.metadata.endpoint_type,
424
- feature_set_uri=endpoint.spec.monitoring_feature_set_uri,
425
463
  )
426
464
  return False
427
465
 
@@ -477,24 +515,67 @@ class MonitoringApplicationController:
477
515
  try:
478
516
  project_name = event[ControllerEvent.PROJECT]
479
517
  endpoint_id = event[ControllerEvent.ENDPOINT_ID]
480
- endpoint_name = event[ControllerEvent.ENDPOINT_NAME]
481
- applications_names = event[ControllerEvent.ENDPOINT_POLICY][
482
- ControllerEventEndpointPolicy.MONITORING_APPLICATIONS
483
- ]
484
518
 
485
- not_batch_endpoint = (
486
- event[ControllerEvent.ENDPOINT_TYPE] != EndpointType.BATCH_EP
487
- )
519
+ if (
520
+ event[ControllerEvent.KIND]
521
+ == mm_constants.ControllerEventKind.BATCH_COMPLETE
522
+ ):
523
+ monitoring_functions = (
524
+ self.project_obj.list_model_monitoring_functions()
525
+ )
526
+ if monitoring_functions:
527
+ applications_names = list(
528
+ {app.metadata.name for app in monitoring_functions}
529
+ )
530
+ last_stream_timestamp = datetime.datetime.fromisoformat(
531
+ event[ControllerEvent.LAST_TIMESTAMP]
532
+ )
533
+ first_request = datetime.datetime.fromisoformat(
534
+ event[ControllerEvent.FIRST_TIMESTAMP]
535
+ )
536
+ endpoint_mode = mm_constants.EndpointMode.BATCH
537
+ model_endpoint = self.project_obj.list_model_endpoints(
538
+ uids=[endpoint_id],
539
+ latest_only=True,
540
+ ).endpoints
541
+
542
+ if not model_endpoint:
543
+ logger.error(
544
+ "Batch model endpoint not found",
545
+ endpoint_id=endpoint_id,
546
+ project=project_name,
547
+ )
548
+ return
549
+
550
+ endpoint_name = model_endpoint[0].metadata.name
551
+ endpoint_updated = model_endpoint[0].metadata.updated.isoformat()
552
+
553
+ else:
554
+ logger.info("No monitoring functions found", project=self.project)
555
+ return
556
+
557
+ else:
558
+ endpoint_name = event[ControllerEvent.ENDPOINT_NAME]
559
+ applications_names = event[ControllerEvent.ENDPOINT_POLICY][
560
+ ControllerEventEndpointPolicy.MONITORING_APPLICATIONS
561
+ ]
562
+ last_stream_timestamp = datetime.datetime.fromisoformat(
563
+ event[ControllerEvent.TIMESTAMP]
564
+ )
565
+ first_request = datetime.datetime.fromisoformat(
566
+ event[ControllerEvent.FIRST_REQUEST]
567
+ )
568
+
569
+ endpoint_updated = event[ControllerEvent.ENDPOINT_POLICY][
570
+ ControllerEventEndpointPolicy.ENDPOINT_UPDATED
571
+ ]
572
+
573
+ endpoint_mode = mm_constants.EndpointMode.REAL_TIME
488
574
 
489
575
  logger.info(
490
- "Starting analyzing for", timestamp=event[ControllerEvent.TIMESTAMP]
491
- )
492
- last_stream_timestamp = datetime.datetime.fromisoformat(
493
- event[ControllerEvent.TIMESTAMP]
494
- )
495
- first_request = datetime.datetime.fromisoformat(
496
- event[ControllerEvent.FIRST_REQUEST]
576
+ "Starting to analyze", timestamp=last_stream_timestamp.isoformat()
497
577
  )
578
+
498
579
  with _BatchWindowGenerator(
499
580
  project=project_name,
500
581
  endpoint_id=endpoint_id,
@@ -506,42 +587,20 @@ class MonitoringApplicationController:
506
587
  end_infer_time,
507
588
  ) in batch_window_generator.get_intervals(
508
589
  application=application,
509
- not_batch_endpoint=not_batch_endpoint,
510
590
  first_request=first_request,
511
591
  last_request=last_stream_timestamp,
592
+ endpoint_mode=endpoint_mode,
512
593
  ):
513
594
  data_in_window = False
514
- if not_batch_endpoint:
515
- # Serving endpoint - get the relevant window data from the TSDB
516
- prediction_metric = self.tsdb_connector.read_predictions(
517
- start=start_infer_time,
518
- end=end_infer_time,
519
- endpoint_id=endpoint_id,
520
- )
521
- if prediction_metric.data:
522
- data_in_window = True
523
- else:
524
- if endpoint_id not in self.feature_sets:
525
- self.feature_sets[endpoint_id] = fstore.get_feature_set(
526
- event[ControllerEvent.FEATURE_SET_URI]
527
- )
528
- self.feature_sets.move_to_end(endpoint_id, last=False)
529
- if (
530
- len(self.feature_sets)
531
- > self._MAX_FEATURE_SET_PER_WORKER
532
- ):
533
- self.feature_sets.popitem(last=True)
534
- m_fs = self.feature_sets.get(endpoint_id)
535
-
536
- # Batch endpoint - get the relevant window data from the parquet target
537
- df = m_fs.to_dataframe(
538
- start_time=start_infer_time,
539
- end_time=end_infer_time,
540
- time_column=mm_constants.EventFieldType.TIMESTAMP,
541
- storage_options=self.storage_options,
542
- )
543
- if len(df) > 0:
544
- data_in_window = True
595
+ # Serving endpoint - get the relevant window data from the TSDB
596
+ prediction_metric = self.tsdb_connector.read_predictions(
597
+ start=start_infer_time,
598
+ end=end_infer_time,
599
+ endpoint_id=endpoint_id,
600
+ )
601
+ if prediction_metric.data:
602
+ data_in_window = True
603
+
545
604
  if not data_in_window:
546
605
  logger.info(
547
606
  "No data found for the given interval",
@@ -564,49 +623,47 @@ class MonitoringApplicationController:
564
623
  project=project_name,
565
624
  applications_names=[application],
566
625
  model_monitoring_access_key=self.model_monitoring_access_key,
567
- endpoint_updated=event[ControllerEvent.ENDPOINT_POLICY][
568
- ControllerEventEndpointPolicy.ENDPOINT_UPDATED
569
- ],
626
+ endpoint_updated=endpoint_updated,
570
627
  )
571
- base_period = event[ControllerEvent.ENDPOINT_POLICY][
572
- ControllerEventEndpointPolicy.BASE_PERIOD
573
- ]
574
- current_time = mlrun.utils.datetime_now()
628
+
575
629
  if (
576
- self._should_send_nop_event(
630
+ event[ControllerEvent.KIND]
631
+ == mm_constants.ControllerEventKind.REGULAR_EVENT
632
+ ):
633
+ base_period = event[ControllerEvent.ENDPOINT_POLICY][
634
+ ControllerEventEndpointPolicy.BASE_PERIOD
635
+ ]
636
+ current_time = mlrun.utils.datetime_now()
637
+ if self._should_send_nop_event(
577
638
  base_period,
578
639
  batch_window_generator.get_min_last_analyzed(),
579
640
  current_time,
580
- )
581
- and event[ControllerEvent.KIND] != ControllerEventKind.NOP_EVENT
582
- ):
583
- event = {
584
- ControllerEvent.KIND: mm_constants.ControllerEventKind.NOP_EVENT,
585
- ControllerEvent.PROJECT: project_name,
586
- ControllerEvent.ENDPOINT_ID: endpoint_id,
587
- ControllerEvent.ENDPOINT_NAME: endpoint_name,
588
- ControllerEvent.TIMESTAMP: current_time.isoformat(
589
- timespec="microseconds"
590
- ),
591
- ControllerEvent.ENDPOINT_POLICY: event[
592
- ControllerEvent.ENDPOINT_POLICY
593
- ],
594
- ControllerEvent.ENDPOINT_TYPE: event[
595
- ControllerEvent.ENDPOINT_TYPE
596
- ],
597
- ControllerEvent.FEATURE_SET_URI: event[
598
- ControllerEvent.FEATURE_SET_URI
599
- ],
600
- ControllerEvent.FIRST_REQUEST: event[
601
- ControllerEvent.FIRST_REQUEST
602
- ],
603
- }
604
- self._push_to_main_stream(
605
- event=event,
606
- endpoint_id=endpoint_id,
607
- )
641
+ ):
642
+ event = {
643
+ ControllerEvent.KIND: mm_constants.ControllerEventKind.NOP_EVENT,
644
+ ControllerEvent.PROJECT: project_name,
645
+ ControllerEvent.ENDPOINT_ID: endpoint_id,
646
+ ControllerEvent.ENDPOINT_NAME: endpoint_name,
647
+ ControllerEvent.TIMESTAMP: current_time.isoformat(
648
+ timespec="microseconds"
649
+ ),
650
+ ControllerEvent.ENDPOINT_POLICY: event[
651
+ ControllerEvent.ENDPOINT_POLICY
652
+ ],
653
+ ControllerEvent.ENDPOINT_TYPE: event[
654
+ ControllerEvent.ENDPOINT_TYPE
655
+ ],
656
+ ControllerEvent.FIRST_REQUEST: event[
657
+ ControllerEvent.FIRST_REQUEST
658
+ ],
659
+ }
660
+ self._push_to_main_stream(
661
+ event=event,
662
+ endpoint_id=endpoint_id,
663
+ )
608
664
  logger.info(
609
- "Finish analyze for", timestamp=event[ControllerEvent.TIMESTAMP]
665
+ "Finish analyze for",
666
+ timestamp=last_stream_timestamp,
610
667
  )
611
668
 
612
669
  except Exception:
@@ -674,7 +731,9 @@ class MonitoringApplicationController:
674
731
  """
675
732
  logger.info("Starting monitoring controller chief")
676
733
  applications_names = []
677
- endpoints = self.project_obj.list_model_endpoints(tsdb_metrics=False).endpoints
734
+ endpoints = self.project_obj.list_model_endpoints(
735
+ tsdb_metrics=False, mode=mm_constants.EndpointMode.REAL_TIME
736
+ ).endpoints
678
737
  last_request_dict = self.tsdb_connector.get_last_request(
679
738
  endpoint_ids=[mep.metadata.uid for mep in endpoints]
680
739
  )
@@ -783,7 +842,6 @@ class MonitoringApplicationController:
783
842
  sep=" ", timespec="microseconds"
784
843
  ),
785
844
  endpoint_type=endpoint.metadata.endpoint_type,
786
- feature_set_uri=endpoint.spec.monitoring_feature_set_uri,
787
845
  endpoint_policy=json.dumps(policy),
788
846
  )
789
847
  policy[ControllerEventEndpointPolicy.ENDPOINT_UPDATED] = (
@@ -801,7 +859,6 @@ class MonitoringApplicationController:
801
859
  sep=" ", timespec="microseconds"
802
860
  ),
803
861
  endpoint_type=endpoint.metadata.endpoint_type.value,
804
- feature_set_uri=endpoint.spec.monitoring_feature_set_uri,
805
862
  endpoint_policy=policy,
806
863
  )
807
864
 
@@ -814,7 +871,6 @@ class MonitoringApplicationController:
814
871
  timestamp: str,
815
872
  first_request: str,
816
873
  endpoint_type: int,
817
- feature_set_uri: str,
818
874
  endpoint_policy: dict[str, Any],
819
875
  ) -> None:
820
876
  """
@@ -827,7 +883,6 @@ class MonitoringApplicationController:
827
883
  :param endpoint_id: endpoint id string
828
884
  :param endpoint_name: the endpoint name string
829
885
  :param endpoint_type: Enum of the endpoint type
830
- :param feature_set_uri: the feature set uri string
831
886
  """
832
887
  event = {
833
888
  ControllerEvent.KIND.value: kind,
@@ -837,7 +892,6 @@ class MonitoringApplicationController:
837
892
  ControllerEvent.TIMESTAMP.value: timestamp,
838
893
  ControllerEvent.FIRST_REQUEST.value: first_request,
839
894
  ControllerEvent.ENDPOINT_TYPE.value: endpoint_type,
840
- ControllerEvent.FEATURE_SET_URI.value: feature_set_uri,
841
895
  ControllerEvent.ENDPOINT_POLICY.value: endpoint_policy,
842
896
  }
843
897
  logger.info(
@@ -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
+ import asyncio
15
15
  import datetime
16
16
  import typing
17
17
 
@@ -134,6 +134,9 @@ class EventStreamProcessor:
134
134
  the default parquet path is under mlrun.mlconf.model_endpoint_monitoring.user_space. Note that if you are
135
135
  using CE, the parquet target path is based on the defined MLRun artifact path.
136
136
 
137
+ In a separate branch, "batch complete" events are forwarded to the controller stream with an intentional delay,
138
+ to allow for data to first be written to parquet.
139
+
137
140
  :param fn: A serving function.
138
141
  :param tsdb_connector: Time series database connector.
139
142
  :param controller_stream_uri: The controller stream URI. Runs on server api pod so needed to be provided as
@@ -145,6 +148,20 @@ class EventStreamProcessor:
145
148
  fn.set_topology(mlrun.serving.states.StepKinds.flow, engine="async"),
146
149
  )
147
150
 
151
+ # forward back complete events to controller
152
+ graph.add_step(
153
+ "storey.Filter",
154
+ "FilterBatchComplete",
155
+ _fn="(event.get('kind') == 'batch_complete')",
156
+ )
157
+
158
+ graph.add_step(
159
+ "Delay",
160
+ name="BatchDelay",
161
+ after="FilterBatchComplete",
162
+ delay=self.parquet_batching_timeout_secs + 5, # add margin
163
+ )
164
+
148
165
  # split the graph between event with error vs valid event
149
166
  graph.add_step(
150
167
  "storey.Filter",
@@ -261,7 +278,7 @@ class EventStreamProcessor:
261
278
  "controller_stream",
262
279
  path=stream_uri,
263
280
  sharding_func=ControllerEvent.ENDPOINT_ID,
264
- after="ForwardNOP",
281
+ after=["ForwardNOP", "BatchDelay"],
265
282
  # Force using the pipeline key instead of the one in the profile in case of v3io profile.
266
283
  # In case of Kafka, this parameter will be ignored.
267
284
  alternative_v3io_access_key="V3IO_ACCESS_KEY",
@@ -309,6 +326,16 @@ class ProcessBeforeParquet(mlrun.feature_store.steps.MapClass):
309
326
  return event
310
327
 
311
328
 
329
+ class Delay(mlrun.feature_store.steps.MapClass):
330
+ def __init__(self, delay: int, **kwargs):
331
+ super().__init__(**kwargs)
332
+ self._delay = delay
333
+
334
+ async def do(self, event):
335
+ await asyncio.sleep(self._delay)
336
+ return event
337
+
338
+
312
339
  class ProcessEndpointEvent(mlrun.feature_store.steps.MapClass):
313
340
  def __init__(
314
341
  self,
mlrun/projects/project.py CHANGED
@@ -3901,6 +3901,7 @@ class MlrunProject(ModelObj):
3901
3901
  start: Optional[datetime.datetime] = None,
3902
3902
  end: Optional[datetime.datetime] = None,
3903
3903
  top_level: bool = False,
3904
+ mode: Optional[mlrun.common.schemas.EndpointMode] = None,
3904
3905
  uids: Optional[list[str]] = None,
3905
3906
  latest_only: bool = False,
3906
3907
  tsdb_metrics: bool = False,
@@ -3916,8 +3917,9 @@ class MlrunProject(ModelObj):
3916
3917
  5) function_tag
3917
3918
  6) labels
3918
3919
  7) top level
3919
- 8) uids
3920
- 9) start and end time, corresponding to the `created` field.
3920
+ 8) mode
3921
+ 9) uids
3922
+ 10) start and end time, corresponding to the `created` field.
3921
3923
  By default, when no filters are applied, all available endpoints for the given project will be listed.
3922
3924
 
3923
3925
  In addition, this functions provides a facade for listing endpoint related metrics. This facade is time-based
@@ -3937,6 +3939,8 @@ class MlrunProject(ModelObj):
3937
3939
  :param start: The start time to filter by.Corresponding to the `created` field.
3938
3940
  :param end: The end time to filter by. Corresponding to the `created` field.
3939
3941
  :param top_level: If true will return only routers and endpoint that are NOT children of any router.
3942
+ :param mode: Specifies the mode of the model endpoint. Can be "real-time", "batch", or both if set
3943
+ to None.
3940
3944
  :param uids: If passed will return a list `ModelEndpoint` object with uid in uids.
3941
3945
  :param tsdb_metrics: When True, the time series metrics will be added to the output
3942
3946
  of the resulting.
@@ -3958,6 +3962,7 @@ class MlrunProject(ModelObj):
3958
3962
  start=start,
3959
3963
  end=end,
3960
3964
  top_level=top_level,
3965
+ mode=mode,
3961
3966
  uids=uids,
3962
3967
  latest_only=latest_only,
3963
3968
  tsdb_metrics=tsdb_metrics,
mlrun/serving/server.py CHANGED
@@ -22,8 +22,10 @@ import os
22
22
  import socket
23
23
  import traceback
24
24
  import uuid
25
+ from datetime import datetime, timezone
25
26
  from typing import Any, Optional, Union
26
27
 
28
+ import pandas as pd
27
29
  import storey
28
30
  from nuclio import Context as NuclioContext
29
31
  from nuclio.request import Logger as NuclioLogger
@@ -40,6 +42,7 @@ from mlrun.secrets import SecretsStore
40
42
 
41
43
  from ..common.helpers import parse_versioned_object_uri
42
44
  from ..common.schemas.model_monitoring.constants import FileTargetKind
45
+ from ..common.schemas.serving import MAX_BATCH_JOB_DURATION
43
46
  from ..datastore import DataItem, get_stream_pusher
44
47
  from ..datastore.store_resources import ResourceCache
45
48
  from ..errors import MLRunInvalidArgumentError
@@ -561,6 +564,7 @@ def v2_serving_init(context, namespace=None):
561
564
  async def async_execute_graph(
562
565
  context: MLClientCtx,
563
566
  data: DataItem,
567
+ timestamp_column: Optional[str],
564
568
  batching: bool,
565
569
  batch_size: Optional[int],
566
570
  read_as_lists: bool,
@@ -605,10 +609,43 @@ async def async_execute_graph(
605
609
  f"(status='{task_state}')"
606
610
  )
607
611
 
612
+ df = data.as_df()
613
+
614
+ if df.empty:
615
+ context.logger.warn("Job terminated due to empty inputs (0 rows)")
616
+ return []
617
+
618
+ track_models = spec.get("track_models")
619
+
620
+ if track_models and timestamp_column:
621
+ context.logger.info(f"Sorting dataframe by {timestamp_column}")
622
+ df[timestamp_column] = pd.to_datetime( # in case it's a string
623
+ df[timestamp_column]
624
+ )
625
+ df.sort_values(by=timestamp_column, inplace=True)
626
+ if len(df) > 1:
627
+ start_time = df[timestamp_column].iloc[0]
628
+ end_time = df[timestamp_column].iloc[-1]
629
+ time_range = end_time - start_time
630
+ start_time = start_time.isoformat()
631
+ end_time = end_time.isoformat()
632
+ # TODO: tie this to the controller's base period
633
+ if time_range > pd.Timedelta(MAX_BATCH_JOB_DURATION):
634
+ raise mlrun.errors.MLRunRuntimeError(
635
+ f"Dataframe time range is too long: {time_range}. "
636
+ "Please disable tracking or reduce the input dataset's time range below the defined limit "
637
+ f"of {MAX_BATCH_JOB_DURATION}."
638
+ )
639
+ else:
640
+ start_time = end_time = df["timestamp"].iloc[0].isoformat()
641
+ else:
642
+ # end time will be set from clock time when the batch completes
643
+ start_time = datetime.now(tz=timezone.utc).isoformat()
644
+
608
645
  server.graph = add_system_steps_to_graph(
609
646
  server.project,
610
647
  copy.deepcopy(server.graph),
611
- spec.get("track_models"),
648
+ track_models,
612
649
  context,
613
650
  spec,
614
651
  pause_until_background_task_completion=False, # we've already awaited it
@@ -633,19 +670,28 @@ async def async_execute_graph(
633
670
  if server.verbose:
634
671
  context.logger.info(server.to_yaml())
635
672
 
636
- df = data.as_df()
637
-
638
- responses = []
639
-
640
673
  async def run(body):
641
674
  event = storey.Event(id=index, body=body)
642
- response = await server.run(event, context)
643
- responses.append(response)
675
+ if timestamp_column:
676
+ if batching:
677
+ # we use the first row in the batch to determine the timestamp for the whole batch
678
+ body = body[0]
679
+ if not isinstance(body, dict):
680
+ raise mlrun.errors.MLRunRuntimeError(
681
+ f"When timestamp_column=True, event body must be a dict – got {type(body).__name__} instead"
682
+ )
683
+ if timestamp_column not in body:
684
+ raise mlrun.errors.MLRunRuntimeError(
685
+ f"Event body '{body}' did not contain timestamp column '{timestamp_column}'"
686
+ )
687
+ event._original_timestamp = body[timestamp_column]
688
+ return await server.run(event, context)
644
689
 
645
690
  if batching and not batch_size:
646
691
  batch_size = len(df)
647
692
 
648
693
  batch = []
694
+ tasks = []
649
695
  for index, row in df.iterrows():
650
696
  data = row.to_list() if read_as_lists else row.to_dict()
651
697
  if nest_under_inputs:
@@ -653,24 +699,56 @@ async def async_execute_graph(
653
699
  if batching:
654
700
  batch.append(data)
655
701
  if len(batch) == batch_size:
656
- await run(batch)
702
+ tasks.append(asyncio.create_task(run(batch)))
657
703
  batch = []
658
704
  else:
659
- await run(data)
705
+ tasks.append(asyncio.create_task(run(data)))
660
706
 
661
707
  if batch:
662
- await run(batch)
708
+ tasks.append(asyncio.create_task(run(batch)))
709
+
710
+ responses = await asyncio.gather(*tasks)
663
711
 
664
712
  termination_result = server.wait_for_completion()
665
713
  if asyncio.iscoroutine(termination_result):
666
714
  await termination_result
667
715
 
716
+ model_endpoint_uids = spec.get("model_endpoint_uids", [])
717
+
718
+ # needed for output_stream to be created
719
+ server = GraphServer.from_dict(spec)
720
+ server.init_states(None, namespace)
721
+
722
+ batch_completion_time = datetime.now(tz=timezone.utc).isoformat()
723
+
724
+ if not timestamp_column:
725
+ end_time = batch_completion_time
726
+
727
+ mm_stream_record = dict(
728
+ kind="batch_complete",
729
+ project=context.project,
730
+ first_timestamp=start_time,
731
+ last_timestamp=end_time,
732
+ batch_completion_time=batch_completion_time,
733
+ )
734
+ output_stream = server.context.stream.output_stream
735
+ for mep_uid in spec.get("model_endpoint_uids", []):
736
+ mm_stream_record["endpoint_id"] = mep_uid
737
+ output_stream.push(mm_stream_record, partition_key=mep_uid)
738
+
739
+ context.logger.info(
740
+ f"Job completed processing {len(df)} rows",
741
+ timestamp_column=timestamp_column,
742
+ model_endpoint_uids=model_endpoint_uids,
743
+ )
744
+
668
745
  return responses
669
746
 
670
747
 
671
748
  def execute_graph(
672
749
  context: MLClientCtx,
673
750
  data: DataItem,
751
+ timestamp_column: Optional[str] = None,
674
752
  batching: bool = False,
675
753
  batch_size: Optional[int] = None,
676
754
  read_as_lists: bool = False,
@@ -681,6 +759,9 @@ def execute_graph(
681
759
 
682
760
  :param context: The job's execution client context.
683
761
  :param data: The input data to the job, to be pushed into the graph row by row, or in batches.
762
+ :param timestamp_column: The name of the column that will be used as the timestamp for model monitoring purposes.
763
+ when timestamp_column is used in conjunction with batching, the first timestamp will be used for the entire
764
+ batch.
684
765
  :param batching: Whether to push one or more batches into the graph rather than row by row.
685
766
  :param batch_size: The number of rows to push per batch. If not set, and batching=True, the entire dataset will
686
767
  be pushed into the graph in one batch.
@@ -691,7 +772,13 @@ def execute_graph(
691
772
  """
692
773
  return asyncio.run(
693
774
  async_execute_graph(
694
- context, data, batching, batch_size, read_as_lists, nest_under_inputs
775
+ context,
776
+ data,
777
+ timestamp_column,
778
+ batching,
779
+ batch_size,
780
+ read_as_lists,
781
+ nest_under_inputs,
695
782
  )
696
783
  )
697
784
 
mlrun/serving/states.py CHANGED
@@ -1754,9 +1754,10 @@ class ModelRunnerStep(MonitoredStep):
1754
1754
  except (
1755
1755
  mlrun.errors.MLRunNotFoundError,
1756
1756
  mlrun.errors.MLRunInvalidArgumentError,
1757
- ):
1757
+ ) as ex:
1758
1758
  logger.warning(
1759
- f"Model endpoint not found, using default output schema for model {name}"
1759
+ f"Model endpoint not found, using default output schema for model {name}",
1760
+ error=f"{type(ex).__name__}: {ex}",
1760
1761
  )
1761
1762
  return output_schema
1762
1763
 
@@ -1768,22 +1769,6 @@ class ModelRunnerStep(MonitoredStep):
1768
1769
  )
1769
1770
  if isinstance(monitoring_data, dict):
1770
1771
  for model in monitoring_data:
1771
- monitoring_data[model][schemas.MonitoringData.OUTPUTS] = (
1772
- monitoring_data.get(model, {}).get(schemas.MonitoringData.OUTPUTS)
1773
- or self._get_model_endpoint_output_schema(
1774
- name=model,
1775
- project=self.context.project if self.context else None,
1776
- uid=monitoring_data.get(model, {}).get(
1777
- mlrun.common.schemas.MonitoringData.MODEL_ENDPOINT_UID
1778
- ),
1779
- )
1780
- )
1781
- # Prevent calling _get_model_output_schema for same model more than once
1782
- self.class_args[
1783
- mlrun.common.schemas.ModelRunnerStepData.MONITORING_DATA
1784
- ][model][schemas.MonitoringData.OUTPUTS] = monitoring_data[model][
1785
- schemas.MonitoringData.OUTPUTS
1786
- ]
1787
1772
  monitoring_data[model][schemas.MonitoringData.INPUT_PATH] = split_path(
1788
1773
  monitoring_data[model][schemas.MonitoringData.INPUT_PATH]
1789
1774
  )
@@ -1791,6 +1776,10 @@ class ModelRunnerStep(MonitoredStep):
1791
1776
  monitoring_data[model][schemas.MonitoringData.RESULT_PATH]
1792
1777
  )
1793
1778
  return monitoring_data
1779
+ else:
1780
+ raise mlrun.errors.MLRunInvalidArgumentError(
1781
+ "Monitoring data must be a dictionary."
1782
+ )
1794
1783
 
1795
1784
  def init_object(self, context, namespace, mode="sync", reset=False, **extra_kwargs):
1796
1785
  self.context = context
@@ -104,7 +104,7 @@ class MonitoringPreProcessor(storey.MapClass):
104
104
  @staticmethod
105
105
  def transpose_by_key(
106
106
  data: dict, schema: Optional[Union[str, list[str]]] = None
107
- ) -> Union[list[float], list[list[float]]]:
107
+ ) -> Union[list[Any], list[list[Any]]]:
108
108
  """
109
109
  Transpose values from a dictionary by keys.
110
110
 
@@ -146,7 +146,11 @@ class MonitoringPreProcessor(storey.MapClass):
146
146
  else:
147
147
  keys = schema
148
148
 
149
- values = [data[key] for key in keys]
149
+ values = [data[key] for key in keys if key in data]
150
+ if len(values) != len(keys):
151
+ raise mlrun.MLRunInvalidArgumentError(
152
+ f"Schema keys {keys} do not match the data keys {list(data.keys())}."
153
+ )
150
154
 
151
155
  # Detect if all are scalars ie: int,float,str
152
156
  all_scalars = all(not isinstance(v, (list, tuple, np.ndarray)) for v in values)
@@ -158,9 +162,9 @@ class MonitoringPreProcessor(storey.MapClass):
158
162
  )
159
163
 
160
164
  if all_scalars:
161
- transposed = np.array([values])
165
+ transposed = np.array([values], dtype=object)
162
166
  elif all_lists and len(keys) > 1:
163
- arrays = [np.array(v) for v in values]
167
+ arrays = [np.array(v, dtype=object) for v in values]
164
168
  mat = np.stack(arrays, axis=0)
165
169
  transposed = mat.T
166
170
  else:
@@ -192,6 +196,12 @@ class MonitoringPreProcessor(storey.MapClass):
192
196
  request, resp = self.reconstruct_request_resp_fields(
193
197
  event, model, monitoring_data[model]
194
198
  )
199
+ if hasattr(event, "_original_timestamp"):
200
+ when = event._original_timestamp
201
+ else:
202
+ when = event._metadata.get(model, {}).get(
203
+ mm_schemas.StreamProcessingEvent.WHEN
204
+ )
195
205
  monitoring_event_list.append(
196
206
  {
197
207
  mm_schemas.StreamProcessingEvent.MODEL: model,
@@ -201,9 +211,7 @@ class MonitoringPreProcessor(storey.MapClass):
201
211
  mm_schemas.StreamProcessingEvent.MICROSEC: event._metadata.get(
202
212
  model, {}
203
213
  ).get(mm_schemas.StreamProcessingEvent.MICROSEC),
204
- mm_schemas.StreamProcessingEvent.WHEN: event._metadata.get(
205
- model, {}
206
- ).get(mm_schemas.StreamProcessingEvent.WHEN),
214
+ mm_schemas.StreamProcessingEvent.WHEN: when,
207
215
  mm_schemas.StreamProcessingEvent.ENDPOINT_ID: monitoring_data[
208
216
  model
209
217
  ].get(
@@ -236,6 +244,10 @@ class MonitoringPreProcessor(storey.MapClass):
236
244
  request, resp = self.reconstruct_request_resp_fields(
237
245
  event, model, monitoring_data[model]
238
246
  )
247
+ if hasattr(event, "_original_timestamp"):
248
+ when = event._original_timestamp
249
+ else:
250
+ when = event._metadata.get(mm_schemas.StreamProcessingEvent.WHEN)
239
251
  monitoring_event_list.append(
240
252
  {
241
253
  mm_schemas.StreamProcessingEvent.MODEL: model,
@@ -245,9 +257,7 @@ class MonitoringPreProcessor(storey.MapClass):
245
257
  mm_schemas.StreamProcessingEvent.MICROSEC: event._metadata.get(
246
258
  mm_schemas.StreamProcessingEvent.MICROSEC
247
259
  ),
248
- mm_schemas.StreamProcessingEvent.WHEN: event._metadata.get(
249
- mm_schemas.StreamProcessingEvent.WHEN
250
- ),
260
+ mm_schemas.StreamProcessingEvent.WHEN: when,
251
261
  mm_schemas.StreamProcessingEvent.ENDPOINT_ID: monitoring_data[
252
262
  model
253
263
  ].get(mlrun.common.schemas.MonitoringData.MODEL_ENDPOINT_UID),
@@ -1,4 +1,4 @@
1
1
  {
2
- "git_commit": "78045e1e85e7c81eee93682240c4ebe7b22fa67c",
3
- "version": "1.10.0-rc16"
2
+ "git_commit": "2e72ac795fbd5a2a1422956417d742f8dd9bcdb5",
3
+ "version": "1.10.0-rc17"
4
4
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mlrun
3
- Version: 1.10.0rc16
3
+ Version: 1.10.0rc17
4
4
  Summary: Tracking and config of machine learning runs
5
5
  Home-page: https://github.com/mlrun/mlrun
6
6
  Author: Yaron Haviv
@@ -1,6 +1,6 @@
1
1
  mlrun/__init__.py,sha256=Y_AFhZV1hEx4vfiO-cyjup0aLGcp6R0SeL75GqLFQrc,7514
2
2
  mlrun/__main__.py,sha256=wQNaxW7QsqFBtWffnPkw-497fnpsrQzUnscBQQAP_UM,48364
3
- mlrun/config.py,sha256=_-WgQPZeSbvd51ndG9qFAaV6TXdDaEgZa9W9B0Imvq8,72399
3
+ mlrun/config.py,sha256=c3F899B20Xe_wpO7cg9G5jEM7iMxfK92LaNxPKnQTyg,72306
4
4
  mlrun/errors.py,sha256=bAk0t_qmCxQSPNK0TugOAfA5R6f0G6OYvEvXUWSJ_5U,9062
5
5
  mlrun/execution.py,sha256=dJ4PFwg5AlDHbCL2Q9dVDjWA_i64UTq2qBiF8kTU9tw,56922
6
6
  mlrun/features.py,sha256=jMEXo6NB36A6iaxNEJWzdtYwUmglYD90OIKTIEeWhE8,15841
@@ -23,7 +23,7 @@ mlrun/artifacts/manager.py,sha256=_cDNCS7wwmFIsucJ2uOgHxZQECmIGb8Wye64b6oLgKU,16
23
23
  mlrun/artifacts/model.py,sha256=8EVaD70SOkTohQIWqkDk0MEwskdofxs3wJTgspa2sho,25615
24
24
  mlrun/artifacts/plots.py,sha256=wmaxVXiAPSCyn3M7pIlcBu9pP3O8lrq0Ewx6iHRDF9s,4238
25
25
  mlrun/common/__init__.py,sha256=kXGBqhLN0rlAx0kTXhozGzFsIdSqW0uTSKMmsLgq_is,569
26
- mlrun/common/constants.py,sha256=bkWWccWZJHB08mkSrrnWuCFXZWdNKf4Q9SwuLowQrVM,4059
26
+ mlrun/common/constants.py,sha256=BDxV8kAAf8D2W-gsa7IP8HJe5da-sPwVUfiodQ1O7kI,4127
27
27
  mlrun/common/helpers.py,sha256=DIdqs_eN3gO5bZ8iFobIvx8cEiOxYxhFIyut6-O69T0,1385
28
28
  mlrun/common/secrets.py,sha256=8g9xtIw-9DGcwiZRT62a5ozSQM-aYo8yK5Ghey9WM0g,5179
29
29
  mlrun/common/types.py,sha256=1gxThbmC0Vd0U1ffIkEwz4T4S7JOgHt70rvw8TCO21c,1073
@@ -41,7 +41,7 @@ mlrun/common/formatters/run.py,sha256=LlqhhVY4dAp5y17k_sWBtHaJogdNdtJWF0iO9sX-bU
41
41
  mlrun/common/model_monitoring/__init__.py,sha256=kXGBqhLN0rlAx0kTXhozGzFsIdSqW0uTSKMmsLgq_is,569
42
42
  mlrun/common/model_monitoring/helpers.py,sha256=AkuHz4u318MEP4ebxmNWlNXh6HiNLrI5oF7QvJiJkYc,2707
43
43
  mlrun/common/runtimes/constants.py,sha256=CGMHE2gdsNHXNsa-u3eL0o8sQmDs6PN5FLpMlCDClns,12218
44
- mlrun/common/schemas/__init__.py,sha256=Mx_N6UtivKP-FQeIvVu6n0unkT7w461lahyNJbaOL_I,5468
44
+ mlrun/common/schemas/__init__.py,sha256=HbnF4nd6jIGbej9Qj6WYjajIr44f3vYNpgWyeLk7N6I,5486
45
45
  mlrun/common/schemas/alert.py,sha256=u6INAHBhQIfm-mMsGqDJo1_JDN6gOuWZa-8fOU-aOUE,10182
46
46
  mlrun/common/schemas/api_gateway.py,sha256=bgC3vXbyb1SVwsSZkLXtEoQLCe_QDKpIhAVX3X_HWW4,7126
47
47
  mlrun/common/schemas/artifact.py,sha256=JojMRRa4n0Rge2olGOpUyp348hkTGsMEnvUBRSoo4oE,4310
@@ -71,11 +71,11 @@ mlrun/common/schemas/runs.py,sha256=yKY29ByTS4SruWQyPpDNFGulMrcT9Ms-3lnwBUDp3us,
71
71
  mlrun/common/schemas/runtime_resource.py,sha256=TybJmCHJXmm1z3s5J1dd89TeFE6lG5t7vjcrf1R9YfE,1568
72
72
  mlrun/common/schemas/schedule.py,sha256=L7z9Lp06-xmFmdp0q5PypCU_DCl6zZIyQTVoJa01gfM,4291
73
73
  mlrun/common/schemas/secret.py,sha256=Td2UAeWHSAdA4nIP3rQv_PIVKVqcBnCnK6xjr528tS8,1486
74
- mlrun/common/schemas/serving.py,sha256=-3U45YLtmVWMZrx4R8kaPgFGoJ4JmD7RE3nydpYNTz8,1359
74
+ mlrun/common/schemas/serving.py,sha256=4ek9JZDagkdeXyfkX6P6xp4deUNSf_kqXUaXcKSuv-g,1391
75
75
  mlrun/common/schemas/tag.py,sha256=1wqEiAujsElojWb3qmuyfcaLFjXSNAAQdafkDx7fkn0,891
76
- mlrun/common/schemas/workflow.py,sha256=GS6RNp66v7iV2flBMXZs4R5mQr15KJx6PMXFlAjyOmc,2496
77
- mlrun/common/schemas/model_monitoring/__init__.py,sha256=lQkWiDZagEmZd7pNE_-ySVJEzTjEzH-JS6OKZPmJiVk,1907
78
- mlrun/common/schemas/model_monitoring/constants.py,sha256=yjTaSGiRs0zYIE20QSuJuMNnS5iuJpnV1wBiq7leVpg,13238
76
+ mlrun/common/schemas/workflow.py,sha256=Y-FHJnxs5c86yetuOAPdEJPkne__tLPCxjSXSb4lrjo,2541
77
+ mlrun/common/schemas/model_monitoring/__init__.py,sha256=FqFiFIDcylquQdY0XTBamB5kMzMrMFEpVYM_ecsVfLg,1925
78
+ mlrun/common/schemas/model_monitoring/constants.py,sha256=spujh8a2GOb7pm8LksAQwndxPWSsjeyRub3ZVICdgNI,13685
79
79
  mlrun/common/schemas/model_monitoring/functions.py,sha256=GpfSGp05D87wEKemECD3USL368pvnAM2WfS-nef5qOg,2210
80
80
  mlrun/common/schemas/model_monitoring/grafana.py,sha256=THQlLfPBevBksta8p5OaIsBaJtsNSXexLvHrDxOaVns,2095
81
81
  mlrun/common/schemas/model_monitoring/model_endpoints.py,sha256=aCrVqgoJsUEwvDJ84YFabDSy78CHcVBV3b0RdWj4JUw,13250
@@ -115,9 +115,9 @@ mlrun/datastore/wasbfs/__init__.py,sha256=s5Ul-0kAhYqFjKDR2X0O2vDGDbLQQduElb32Ev
115
115
  mlrun/datastore/wasbfs/fs.py,sha256=ge8NK__5vTcFT-krI155_8RDUywQw4SIRX6BWATXy9Q,6299
116
116
  mlrun/db/__init__.py,sha256=WqJ4x8lqJ7ZoKbhEyFqkYADd9P6E3citckx9e9ZLcIU,1163
117
117
  mlrun/db/auth_utils.py,sha256=hpg8D2r82oN0BWabuWN04BTNZ7jYMAF242YSUpK7LFM,5211
118
- mlrun/db/base.py,sha256=afej-ZtJiBBv0MtZx2B1MH1ZRXno4s94aL_Qs5u-WCA,32044
118
+ mlrun/db/base.py,sha256=QUncUfRYep_2Bsui7y-DduAEm8qdgIlWIYmrns-S7HA,32110
119
119
  mlrun/db/factory.py,sha256=yP2vVmveUE7LYTCHbS6lQIxP9rW--zdISWuPd_I3d_4,2111
120
- mlrun/db/httpdb.py,sha256=MiXRygol2qMIMVLDOB89pouR8oICDaR510rVpZtPc4w,239160
120
+ mlrun/db/httpdb.py,sha256=oSzc4zigDgu9S2Vs7Lkz_NY1Oc78OGyHN-xeV19nVtk,239397
121
121
  mlrun/db/nopdb.py,sha256=kRWKEEI9LUskI3mp2ofTdAWVLov-99-nSMdaqhi3XT8,28194
122
122
  mlrun/feature_store/__init__.py,sha256=SlI845bWt6xX34SXunHHqhmFAR9-5v2ak8N-qpcAPGo,1328
123
123
  mlrun/feature_store/api.py,sha256=qKj5Tk6prTab6XWatWhBuPRVp0eJEctoxRMN2wz48vA,32168
@@ -226,10 +226,10 @@ mlrun/launcher/local.py,sha256=3gv-IQYoIChSmRaZ0vLUh0Tu26oLMCx9GbBYh4fWygQ,12161
226
226
  mlrun/launcher/remote.py,sha256=zFXE52Cq_7EkC8lfNKT0ceIbye0CfFiundF7O1YU4Xw,7810
227
227
  mlrun/model_monitoring/__init__.py,sha256=qDQnncjya9XPTlfvGyfWsZWiXc-glGZrrNja-5QmCZk,782
228
228
  mlrun/model_monitoring/api.py,sha256=lAsUp-gzqw8D1cpHVGA2_nPMYn5R4jdxk9UaGOiQ8fE,25945
229
- mlrun/model_monitoring/controller.py,sha256=CQxK9Sq5k8XonvVBQnSimakpTwMMAyqT5mOaG534MaM,37660
229
+ mlrun/model_monitoring/controller.py,sha256=FVckATzREAzldj68D1KxcnKSdilgKUDRdqhRUf9XpWU,39592
230
230
  mlrun/model_monitoring/features_drift_table.py,sha256=c6GpKtpOJbuT1u5uMWDL_S-6N4YPOmlktWMqPme3KFY,25308
231
231
  mlrun/model_monitoring/helpers.py,sha256=0xhIYKzhaBrgyjLiA_ekCZsXzi3GBXpLyG40Bhj-PTY,23596
232
- mlrun/model_monitoring/stream_processing.py,sha256=Gu3TQzYoNjbreZYI73-F49QpYrod9RZOyGSgininBsA,33373
232
+ mlrun/model_monitoring/stream_processing.py,sha256=Mzn9Pelcblw8UzOFLGKb9oXOX0tkP2aoPcFbjtfHhcA,34247
233
233
  mlrun/model_monitoring/writer.py,sha256=rGRFzSOkqZWvD3Y6sVk2H1Gepfnkzkp9ce00PsApTLo,8288
234
234
  mlrun/model_monitoring/applications/__init__.py,sha256=MaH_n4GiqqQvSkntM5yQ7_FCANtM_IfgK-IJTdo4G_E,757
235
235
  mlrun/model_monitoring/applications/_application_steps.py,sha256=t9LDIqQUGE10cyjyhlg0QqN1yVx0apD1HpERYLJfm8U,7409
@@ -277,7 +277,7 @@ mlrun/platforms/iguazio.py,sha256=6VBTq8eQ3mzT96tzjYhAtcMQ2VjF4x8LpIPW5DAcX2Q,13
277
277
  mlrun/projects/__init__.py,sha256=hdCOA6_fp8X4qGGGT7Bj7sPbkM1PayWuaVZL0DkpuZw,1240
278
278
  mlrun/projects/operations.py,sha256=Rc__P5ucNAY2G-lHc2LrnZs15PUbNFt8-NqNNT2Bjpk,20623
279
279
  mlrun/projects/pipelines.py,sha256=nGDzBABEOqoe9sWbax4SfF8CVLgrvK0NLWBadzEthVE,52219
280
- mlrun/projects/project.py,sha256=B2KCd5bAGMeHOGhEdVr5NKtTOigL6O-A-Bbs2FLvokY,253884
280
+ mlrun/projects/project.py,sha256=a75Sj1lYzWNggTXIKxerSwy52YqNciGvrT2k-ddRmkQ,254149
281
281
  mlrun/runtimes/__init__.py,sha256=8cqrYKy1a0_87XG7V_p96untQ4t8RocadM4LVEEN1JM,9029
282
282
  mlrun/runtimes/base.py,sha256=FVEooeQMpwxIK2iW1R0FNbC5P1sZ_efKtJcsdNSYNmc,38266
283
283
  mlrun/runtimes/daskjob.py,sha256=kR5sDQtXtXY_VGn5Y3mapjEEB5P6Lj30pSrPe1DqsAg,20077
@@ -311,10 +311,10 @@ mlrun/serving/__init__.py,sha256=nriJAcVn5aatwU03T7SsE6ngJEGTxr3wIGt4WuvCCzY,139
311
311
  mlrun/serving/merger.py,sha256=pfOQoozUyObCTpqXAMk94PmhZefn4bBrKufO3MKnkAc,6193
312
312
  mlrun/serving/remote.py,sha256=Igha2FipK3-6rV_PZ1K464kTbiTu8rhc6SMm-HiEJ6o,18817
313
313
  mlrun/serving/routers.py,sha256=SmBOlHn7rT2gWTa-W8f16UB0UthgIFc4D1cPOZAA9ss,54003
314
- mlrun/serving/server.py,sha256=aFhPlIauww1e2OleZdrQxRiqMmEsoHEdTSujnAZeNe4,35202
314
+ mlrun/serving/server.py,sha256=_P_SR4_7YKqruVzzDHgSPHWlNLGPG5-ksSUwuGhnmjg,38851
315
315
  mlrun/serving/serving_wrapper.py,sha256=UL9hhWCfMPcTJO_XrkvNaFvck1U1E7oS8trTZyak0cA,835
316
- mlrun/serving/states.py,sha256=ZKhlPRlyvbRaGqNueR74IP3q7n8_k-4lYzq0QhPp88Q,124767
317
- mlrun/serving/system_steps.py,sha256=G-vFdFXGDMZECasT831GxAayitCORNfpxPPRgK5a86w,17960
316
+ mlrun/serving/states.py,sha256=nrwQcWC0q6A6xFSvTWvaDmtSVgv8Fva9TxCuBHGwZHs,124062
317
+ mlrun/serving/system_steps.py,sha256=tCxkJ54peOzRTMaqvHQCbcwx0ITqZkSpGXbtpRUEfzU,18463
318
318
  mlrun/serving/utils.py,sha256=Zbfqm8TKNcTE8zRBezVBzpvR2WKeKeIRN7otNIaiYEc,4170
319
319
  mlrun/serving/v1_serving.py,sha256=c6J_MtpE-Tqu00-6r4eJOCO6rUasHDal9W2eBIcrl50,11853
320
320
  mlrun/serving/v2_serving.py,sha256=257LVOvWxV0KjeY0-Kxro6YgKmPu2QzNne2IORlXi5E,25434
@@ -347,11 +347,11 @@ mlrun/utils/notifications/notification/mail.py,sha256=ZyJ3eqd8simxffQmXzqd3bgbAq
347
347
  mlrun/utils/notifications/notification/slack.py,sha256=kfhogR5keR7Zjh0VCjJNK3NR5_yXT7Cv-x9GdOUW4Z8,7294
348
348
  mlrun/utils/notifications/notification/webhook.py,sha256=zxh8CAlbPnTazsk6r05X5TKwqUZVOH5KBU2fJbzQlG4,5330
349
349
  mlrun/utils/version/__init__.py,sha256=YnzE6tlf24uOQ8y7Z7l96QLAI6-QEii7-77g8ynmzy0,613
350
- mlrun/utils/version/version.json,sha256=yzjgOoSOXuxZFECc2IfKerszx07aKIlhF7wENYuFF_E,90
350
+ mlrun/utils/version/version.json,sha256=V37lX2OyNEzNPMQsoXG-KqHjDgxsQv-dFqRr2AHbFCk,90
351
351
  mlrun/utils/version/version.py,sha256=M2hVhRrgkN3SxacZHs3ZqaOsqAA7B6a22ne324IQ1HE,1877
352
- mlrun-1.10.0rc16.dist-info/licenses/LICENSE,sha256=zTiv1CxWNkOk1q8eJS1G_8oD4gWpWLwWxj_Agcsi8Os,11337
353
- mlrun-1.10.0rc16.dist-info/METADATA,sha256=U1uo_EFf7k8D102Lq0z_EQ1K2AOCjwU5iNQuLVvCVjM,26195
354
- mlrun-1.10.0rc16.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
355
- mlrun-1.10.0rc16.dist-info/entry_points.txt,sha256=1Owd16eAclD5pfRCoJpYC2ZJSyGNTtUr0nCELMioMmU,46
356
- mlrun-1.10.0rc16.dist-info/top_level.txt,sha256=NObLzw3maSF9wVrgSeYBv-fgnHkAJ1kEkh12DLdd5KM,6
357
- mlrun-1.10.0rc16.dist-info/RECORD,,
352
+ mlrun-1.10.0rc17.dist-info/licenses/LICENSE,sha256=zTiv1CxWNkOk1q8eJS1G_8oD4gWpWLwWxj_Agcsi8Os,11337
353
+ mlrun-1.10.0rc17.dist-info/METADATA,sha256=D6HN7BHzSm7-exb9kSE5N2h4m_dPOSsQc-PoM_5avj4,26195
354
+ mlrun-1.10.0rc17.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
355
+ mlrun-1.10.0rc17.dist-info/entry_points.txt,sha256=1Owd16eAclD5pfRCoJpYC2ZJSyGNTtUr0nCELMioMmU,46
356
+ mlrun-1.10.0rc17.dist-info/top_level.txt,sha256=NObLzw3maSF9wVrgSeYBv-fgnHkAJ1kEkh12DLdd5KM,6
357
+ mlrun-1.10.0rc17.dist-info/RECORD,,