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 +2 -0
- mlrun/common/schemas/__init__.py +1 -0
- mlrun/common/schemas/model_monitoring/__init__.py +1 -0
- mlrun/common/schemas/model_monitoring/constants.py +19 -0
- mlrun/common/schemas/serving.py +3 -0
- mlrun/common/schemas/workflow.py +1 -0
- mlrun/config.py +1 -5
- mlrun/db/base.py +1 -0
- mlrun/db/httpdb.py +4 -0
- mlrun/model_monitoring/controller.py +175 -121
- mlrun/model_monitoring/stream_processing.py +29 -2
- mlrun/projects/project.py +7 -2
- mlrun/serving/server.py +98 -11
- mlrun/serving/states.py +7 -18
- mlrun/serving/system_steps.py +20 -10
- mlrun/utils/version/version.json +2 -2
- {mlrun-1.10.0rc16.dist-info → mlrun-1.10.0rc17.dist-info}/METADATA +1 -1
- {mlrun-1.10.0rc16.dist-info → mlrun-1.10.0rc17.dist-info}/RECORD +22 -22
- {mlrun-1.10.0rc16.dist-info → mlrun-1.10.0rc17.dist-info}/WHEEL +0 -0
- {mlrun-1.10.0rc16.dist-info → mlrun-1.10.0rc17.dist-info}/entry_points.txt +0 -0
- {mlrun-1.10.0rc16.dist-info → mlrun-1.10.0rc17.dist-info}/licenses/LICENSE +0 -0
- {mlrun-1.10.0rc16.dist-info → mlrun-1.10.0rc17.dist-info}/top_level.txt +0 -0
mlrun/common/constants.py
CHANGED
mlrun/common/schemas/__init__.py
CHANGED
|
@@ -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,
|
|
@@ -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"
|
mlrun/common/schemas/serving.py
CHANGED
mlrun/common/schemas/workflow.py
CHANGED
|
@@ -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
|
|
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
|
|
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,
|
|
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
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
486
|
-
event[ControllerEvent.
|
|
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
|
|
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
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
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=
|
|
568
|
-
ControllerEventEndpointPolicy.ENDPOINT_UPDATED
|
|
569
|
-
],
|
|
626
|
+
endpoint_updated=endpoint_updated,
|
|
570
627
|
)
|
|
571
|
-
|
|
572
|
-
ControllerEventEndpointPolicy.BASE_PERIOD
|
|
573
|
-
]
|
|
574
|
-
current_time = mlrun.utils.datetime_now()
|
|
628
|
+
|
|
575
629
|
if (
|
|
576
|
-
|
|
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
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
ControllerEvent.
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
ControllerEvent.
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
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",
|
|
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(
|
|
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)
|
|
3920
|
-
9)
|
|
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
|
-
|
|
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
|
-
|
|
643
|
-
|
|
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
|
-
|
|
702
|
+
tasks.append(asyncio.create_task(run(batch)))
|
|
657
703
|
batch = []
|
|
658
704
|
else:
|
|
659
|
-
|
|
705
|
+
tasks.append(asyncio.create_task(run(data)))
|
|
660
706
|
|
|
661
707
|
if batch:
|
|
662
|
-
|
|
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,
|
|
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
|
mlrun/serving/system_steps.py
CHANGED
|
@@ -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[
|
|
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:
|
|
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:
|
|
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),
|
mlrun/utils/version/version.json
CHANGED
|
@@ -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=
|
|
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=
|
|
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=
|
|
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
|
|
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=
|
|
77
|
-
mlrun/common/schemas/model_monitoring/__init__.py,sha256=
|
|
78
|
-
mlrun/common/schemas/model_monitoring/constants.py,sha256=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
317
|
-
mlrun/serving/system_steps.py,sha256=
|
|
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=
|
|
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.
|
|
353
|
-
mlrun-1.10.
|
|
354
|
-
mlrun-1.10.
|
|
355
|
-
mlrun-1.10.
|
|
356
|
-
mlrun-1.10.
|
|
357
|
-
mlrun-1.10.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|