mlrun 1.7.0rc43__py3-none-any.whl → 1.7.0rc56__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/__main__.py +4 -2
- mlrun/artifacts/manager.py +3 -1
- mlrun/common/formatters/__init__.py +1 -0
- mlrun/{model_monitoring/application.py → common/formatters/feature_set.py} +20 -6
- mlrun/common/formatters/run.py +3 -0
- mlrun/common/schemas/__init__.py +1 -0
- mlrun/common/schemas/alert.py +11 -11
- mlrun/common/schemas/auth.py +5 -0
- mlrun/common/schemas/client_spec.py +0 -1
- mlrun/common/schemas/model_monitoring/__init__.py +2 -1
- mlrun/common/schemas/model_monitoring/constants.py +23 -9
- mlrun/common/schemas/model_monitoring/model_endpoints.py +24 -47
- mlrun/common/schemas/notification.py +12 -2
- mlrun/common/schemas/workflow.py +10 -2
- mlrun/config.py +28 -21
- mlrun/data_types/data_types.py +6 -1
- mlrun/datastore/base.py +4 -4
- mlrun/datastore/s3.py +12 -9
- mlrun/datastore/storeytargets.py +9 -6
- mlrun/db/base.py +3 -0
- mlrun/db/httpdb.py +28 -16
- mlrun/db/nopdb.py +24 -4
- mlrun/errors.py +7 -1
- mlrun/execution.py +40 -7
- mlrun/feature_store/api.py +1 -0
- mlrun/feature_store/retrieval/spark_merger.py +7 -7
- mlrun/frameworks/_common/plan.py +3 -3
- mlrun/frameworks/_ml_common/plan.py +1 -1
- mlrun/frameworks/parallel_coordinates.py +2 -3
- mlrun/launcher/client.py +6 -6
- mlrun/model.py +29 -0
- mlrun/model_monitoring/api.py +1 -12
- mlrun/model_monitoring/applications/__init__.py +1 -2
- mlrun/model_monitoring/applications/_application_steps.py +5 -1
- mlrun/model_monitoring/applications/base.py +2 -182
- mlrun/model_monitoring/applications/context.py +2 -9
- mlrun/model_monitoring/applications/evidently_base.py +0 -74
- mlrun/model_monitoring/applications/histogram_data_drift.py +2 -2
- mlrun/model_monitoring/applications/results.py +4 -4
- mlrun/model_monitoring/controller.py +46 -209
- mlrun/model_monitoring/db/stores/base/store.py +1 -0
- mlrun/model_monitoring/db/stores/sqldb/sql_store.py +15 -1
- mlrun/model_monitoring/db/stores/v3io_kv/kv_store.py +12 -0
- mlrun/model_monitoring/db/tsdb/tdengine/schemas.py +17 -16
- mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +49 -39
- mlrun/model_monitoring/helpers.py +13 -15
- mlrun/model_monitoring/writer.py +3 -1
- mlrun/projects/operations.py +11 -8
- mlrun/projects/pipelines.py +35 -16
- mlrun/projects/project.py +52 -24
- mlrun/render.py +3 -3
- mlrun/runtimes/daskjob.py +1 -1
- mlrun/runtimes/kubejob.py +6 -6
- mlrun/runtimes/nuclio/api_gateway.py +12 -0
- mlrun/runtimes/nuclio/application/application.py +3 -3
- mlrun/runtimes/nuclio/function.py +41 -0
- mlrun/runtimes/nuclio/serving.py +2 -2
- mlrun/runtimes/pod.py +19 -13
- mlrun/serving/server.py +2 -0
- mlrun/utils/helpers.py +62 -16
- mlrun/utils/version/version.json +2 -2
- {mlrun-1.7.0rc43.dist-info → mlrun-1.7.0rc56.dist-info}/METADATA +126 -44
- {mlrun-1.7.0rc43.dist-info → mlrun-1.7.0rc56.dist-info}/RECORD +67 -68
- {mlrun-1.7.0rc43.dist-info → mlrun-1.7.0rc56.dist-info}/WHEEL +1 -1
- mlrun/model_monitoring/evidently_application.py +0 -20
- {mlrun-1.7.0rc43.dist-info → mlrun-1.7.0rc56.dist-info}/LICENSE +0 -0
- {mlrun-1.7.0rc43.dist-info → mlrun-1.7.0rc56.dist-info}/entry_points.txt +0 -0
- {mlrun-1.7.0rc43.dist-info → mlrun-1.7.0rc56.dist-info}/top_level.txt +0 -0
|
@@ -15,28 +15,22 @@
|
|
|
15
15
|
import concurrent.futures
|
|
16
16
|
import datetime
|
|
17
17
|
import json
|
|
18
|
-
import multiprocessing
|
|
19
18
|
import os
|
|
20
19
|
import re
|
|
21
20
|
from collections.abc import Iterator
|
|
22
|
-
from typing import
|
|
21
|
+
from typing import NamedTuple, Optional, Union, cast
|
|
23
22
|
|
|
24
23
|
import nuclio
|
|
25
24
|
|
|
26
25
|
import mlrun
|
|
27
26
|
import mlrun.common.schemas.model_monitoring.constants as mm_constants
|
|
28
27
|
import mlrun.data_types.infer
|
|
29
|
-
import mlrun.feature_store as fstore
|
|
30
28
|
import mlrun.model_monitoring.db.stores
|
|
31
|
-
from mlrun.common.model_monitoring.helpers import FeatureStats, pad_features_hist
|
|
32
29
|
from mlrun.datastore import get_stream_pusher
|
|
33
|
-
from mlrun.datastore.targets import ParquetTarget
|
|
34
30
|
from mlrun.errors import err_to_str
|
|
35
31
|
from mlrun.model_monitoring.helpers import (
|
|
36
32
|
_BatchDict,
|
|
37
33
|
batch_dict2timedelta,
|
|
38
|
-
calculate_inputs_statistics,
|
|
39
|
-
get_monitoring_parquet_path,
|
|
40
34
|
get_stream_path,
|
|
41
35
|
)
|
|
42
36
|
from mlrun.utils import datetime_now, logger
|
|
@@ -219,7 +213,7 @@ class _BatchWindowGenerator:
|
|
|
219
213
|
# If the endpoint does not have a stream, `last_updated` should be
|
|
220
214
|
# the minimum between the current time and the last updated time.
|
|
221
215
|
# This compensates for the bumping mechanism - see
|
|
222
|
-
# `
|
|
216
|
+
# `update_model_endpoint_last_request`.
|
|
223
217
|
last_updated = min(int(datetime_now().timestamp()), last_updated)
|
|
224
218
|
logger.debug(
|
|
225
219
|
"The endpoint does not have a stream", last_updated=last_updated
|
|
@@ -292,15 +286,9 @@ class MonitoringApplicationController:
|
|
|
292
286
|
)
|
|
293
287
|
|
|
294
288
|
self.model_monitoring_access_key = self._get_model_monitoring_access_key()
|
|
295
|
-
self.
|
|
296
|
-
self.
|
|
297
|
-
kind=mm_constants.FileTargetKind.APPS_PARQUET,
|
|
289
|
+
self.tsdb_connector = mlrun.model_monitoring.get_tsdb_connector(
|
|
290
|
+
project=self.project
|
|
298
291
|
)
|
|
299
|
-
self.storage_options = None
|
|
300
|
-
if not mlrun.mlconf.is_ce_mode():
|
|
301
|
-
self._initialize_v3io_configurations()
|
|
302
|
-
elif self.parquet_directory.startswith("s3://"):
|
|
303
|
-
self.storage_options = mlrun.mlconf.get_s3_storage_options()
|
|
304
292
|
|
|
305
293
|
@staticmethod
|
|
306
294
|
def _get_model_monitoring_access_key() -> Optional[str]:
|
|
@@ -310,12 +298,6 @@ class MonitoringApplicationController:
|
|
|
310
298
|
access_key = mlrun.mlconf.get_v3io_access_key()
|
|
311
299
|
return access_key
|
|
312
300
|
|
|
313
|
-
def _initialize_v3io_configurations(self) -> None:
|
|
314
|
-
self.storage_options = dict(
|
|
315
|
-
v3io_access_key=self.model_monitoring_access_key,
|
|
316
|
-
v3io_api=mlrun.mlconf.v3io_api,
|
|
317
|
-
)
|
|
318
|
-
|
|
319
301
|
def run(self) -> None:
|
|
320
302
|
"""
|
|
321
303
|
Main method for run all the relevant monitoring applications on each endpoint.
|
|
@@ -367,11 +349,8 @@ class MonitoringApplicationController:
|
|
|
367
349
|
)
|
|
368
350
|
return
|
|
369
351
|
# Initialize a process pool that will be used to run each endpoint applications on a dedicated process
|
|
370
|
-
with concurrent.futures.
|
|
352
|
+
with concurrent.futures.ThreadPoolExecutor(
|
|
371
353
|
max_workers=min(len(endpoints), 10),
|
|
372
|
-
# On Linux, the default is "fork" (this is set to change in Python 3.14), which inherits the current heap
|
|
373
|
-
# and resources (such as sockets), which is not what we want (ML-7160)
|
|
374
|
-
mp_context=multiprocessing.get_context("spawn"),
|
|
375
354
|
) as pool:
|
|
376
355
|
for endpoint in endpoints:
|
|
377
356
|
if (
|
|
@@ -395,13 +374,10 @@ class MonitoringApplicationController:
|
|
|
395
374
|
applications_names=applications_names,
|
|
396
375
|
batch_window_generator=self._batch_window_generator,
|
|
397
376
|
project=self.project,
|
|
398
|
-
parquet_directory=self.parquet_directory,
|
|
399
|
-
storage_options=self.storage_options,
|
|
400
377
|
model_monitoring_access_key=self.model_monitoring_access_key,
|
|
378
|
+
tsdb_connector=self.tsdb_connector,
|
|
401
379
|
)
|
|
402
380
|
|
|
403
|
-
self._delete_old_parquet(endpoints=endpoints)
|
|
404
|
-
|
|
405
381
|
@classmethod
|
|
406
382
|
def model_endpoint_process(
|
|
407
383
|
cls,
|
|
@@ -409,9 +385,8 @@ class MonitoringApplicationController:
|
|
|
409
385
|
applications_names: list[str],
|
|
410
386
|
batch_window_generator: _BatchWindowGenerator,
|
|
411
387
|
project: str,
|
|
412
|
-
parquet_directory: str,
|
|
413
|
-
storage_options: dict,
|
|
414
388
|
model_monitoring_access_key: str,
|
|
389
|
+
tsdb_connector: mlrun.model_monitoring.db.tsdb.TSDBConnector,
|
|
415
390
|
) -> None:
|
|
416
391
|
"""
|
|
417
392
|
Process a model endpoint and trigger the monitoring applications. This function running on different process
|
|
@@ -422,16 +397,13 @@ class MonitoringApplicationController:
|
|
|
422
397
|
:param applications_names: (list[str]) List of application names to push results to.
|
|
423
398
|
:param batch_window_generator: (_BatchWindowGenerator) An object that generates _BatchWindow objects.
|
|
424
399
|
:param project: (str) Project name.
|
|
425
|
-
:param parquet_directory: (str) Directory to store application parquet files
|
|
426
|
-
:param storage_options: (dict) Storage options for writing ParquetTarget.
|
|
427
400
|
:param model_monitoring_access_key: (str) Access key to apply the model monitoring process.
|
|
401
|
+
:param tsdb_connector: (mlrun.model_monitoring.db.tsdb.TSDBConnector) TSDB connector
|
|
428
402
|
"""
|
|
429
403
|
endpoint_id = endpoint[mm_constants.EventFieldType.UID]
|
|
404
|
+
# if false the endpoint represent batch infer step.
|
|
405
|
+
has_stream = endpoint[mm_constants.EventFieldType.STREAM_PATH] != ""
|
|
430
406
|
try:
|
|
431
|
-
m_fs = fstore.get_feature_set(
|
|
432
|
-
endpoint[mm_constants.EventFieldType.FEATURE_SET_URI]
|
|
433
|
-
)
|
|
434
|
-
|
|
435
407
|
for application in applications_names:
|
|
436
408
|
batch_window = batch_window_generator.get_batch_window(
|
|
437
409
|
project=project,
|
|
@@ -439,158 +411,70 @@ class MonitoringApplicationController:
|
|
|
439
411
|
application=application,
|
|
440
412
|
first_request=endpoint[mm_constants.EventFieldType.FIRST_REQUEST],
|
|
441
413
|
last_request=endpoint[mm_constants.EventFieldType.LAST_REQUEST],
|
|
442
|
-
has_stream=
|
|
414
|
+
has_stream=has_stream,
|
|
443
415
|
)
|
|
444
416
|
|
|
445
417
|
for start_infer_time, end_infer_time in batch_window.get_intervals():
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
418
|
+
prediction_metric = tsdb_connector.read_predictions(
|
|
419
|
+
endpoint_id=endpoint_id,
|
|
420
|
+
start=start_infer_time,
|
|
421
|
+
end=end_infer_time,
|
|
422
|
+
)
|
|
423
|
+
if not prediction_metric.data and has_stream:
|
|
424
|
+
logger.info(
|
|
425
|
+
"No data found for the given interval",
|
|
426
|
+
start=start_infer_time,
|
|
427
|
+
end=end_infer_time,
|
|
428
|
+
endpoint_id=endpoint_id,
|
|
429
|
+
)
|
|
430
|
+
else:
|
|
431
|
+
logger.info(
|
|
432
|
+
"Data found for the given interval",
|
|
433
|
+
start=start_infer_time,
|
|
434
|
+
end=end_infer_time,
|
|
451
435
|
endpoint_id=endpoint_id,
|
|
436
|
+
)
|
|
437
|
+
cls._push_to_applications(
|
|
452
438
|
start_infer_time=start_infer_time,
|
|
453
439
|
end_infer_time=end_infer_time,
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
df = offline_response.to_dataframe()
|
|
460
|
-
parquet_target_path = offline_response.vector.get_target_path()
|
|
461
|
-
|
|
462
|
-
if len(df) == 0:
|
|
463
|
-
logger.info(
|
|
464
|
-
"During this time window, the endpoint has not received any data",
|
|
465
|
-
endpoint=endpoint[mm_constants.EventFieldType.UID],
|
|
466
|
-
start_time=start_infer_time,
|
|
467
|
-
end_time=end_infer_time,
|
|
468
|
-
)
|
|
469
|
-
continue
|
|
470
|
-
|
|
471
|
-
except FileNotFoundError:
|
|
472
|
-
logger.warn(
|
|
473
|
-
"No parquets were written yet",
|
|
474
|
-
endpoint=endpoint[mm_constants.EventFieldType.UID],
|
|
440
|
+
endpoint_id=endpoint_id,
|
|
441
|
+
project=project,
|
|
442
|
+
applications_names=[application],
|
|
443
|
+
model_monitoring_access_key=model_monitoring_access_key,
|
|
475
444
|
)
|
|
476
|
-
continue
|
|
477
|
-
|
|
478
|
-
# Get the timestamp of the latest request:
|
|
479
|
-
latest_request = df[mm_constants.EventFieldType.TIMESTAMP].iloc[-1]
|
|
480
|
-
|
|
481
|
-
# Get the feature stats from the model endpoint for reference data
|
|
482
|
-
feature_stats = json.loads(
|
|
483
|
-
endpoint[mm_constants.EventFieldType.FEATURE_STATS]
|
|
484
|
-
)
|
|
485
|
-
|
|
486
|
-
# Pad the original feature stats to accommodate current
|
|
487
|
-
# data out of the original range (unless already padded)
|
|
488
|
-
pad_features_hist(FeatureStats(feature_stats))
|
|
489
|
-
|
|
490
|
-
# Get the current stats:
|
|
491
|
-
current_stats = calculate_inputs_statistics(
|
|
492
|
-
sample_set_statistics=feature_stats, inputs=df
|
|
493
|
-
)
|
|
494
|
-
# end - TODO : delete in 1.9.0 (V1 app deprecation)
|
|
495
|
-
cls._push_to_applications(
|
|
496
|
-
current_stats=current_stats,
|
|
497
|
-
feature_stats=feature_stats,
|
|
498
|
-
start_infer_time=start_infer_time,
|
|
499
|
-
end_infer_time=end_infer_time,
|
|
500
|
-
endpoint_id=endpoint_id,
|
|
501
|
-
latest_request=latest_request,
|
|
502
|
-
project=project,
|
|
503
|
-
applications_names=[application],
|
|
504
|
-
model_monitoring_access_key=model_monitoring_access_key,
|
|
505
|
-
parquet_target_path=parquet_target_path,
|
|
506
|
-
)
|
|
507
445
|
except Exception:
|
|
508
446
|
logger.exception(
|
|
509
447
|
"Encountered an exception",
|
|
510
448
|
endpoint_id=endpoint[mm_constants.EventFieldType.UID],
|
|
511
449
|
)
|
|
512
450
|
|
|
513
|
-
def _delete_old_parquet(self, endpoints: list[dict[str, Any]], days: int = 1):
|
|
514
|
-
"""
|
|
515
|
-
Delete application parquets older than the argument days.
|
|
516
|
-
|
|
517
|
-
:param endpoints: A list of dictionaries of model endpoints records.
|
|
518
|
-
"""
|
|
519
|
-
if self.parquet_directory.startswith("v3io:///"):
|
|
520
|
-
# create fs with access to the user side (under projects)
|
|
521
|
-
store, _, _ = mlrun.store_manager.get_or_create_store(
|
|
522
|
-
self.parquet_directory,
|
|
523
|
-
{"V3IO_ACCESS_KEY": self.model_monitoring_access_key},
|
|
524
|
-
)
|
|
525
|
-
fs = store.filesystem
|
|
526
|
-
|
|
527
|
-
# calculate time threshold (keep only files from the last 24 hours)
|
|
528
|
-
time_to_keep = (
|
|
529
|
-
datetime.datetime.now(tz=datetime.timezone.utc)
|
|
530
|
-
- datetime.timedelta(days=days)
|
|
531
|
-
).timestamp()
|
|
532
|
-
|
|
533
|
-
for endpoint in endpoints:
|
|
534
|
-
try:
|
|
535
|
-
apps_parquet_directories = fs.listdir(
|
|
536
|
-
path=f"{self.parquet_directory}"
|
|
537
|
-
f"/key={endpoint[mm_constants.EventFieldType.UID]}"
|
|
538
|
-
)
|
|
539
|
-
for directory in apps_parquet_directories:
|
|
540
|
-
if directory["mtime"] < time_to_keep:
|
|
541
|
-
# Delete files
|
|
542
|
-
fs.rm(path=directory["name"], recursive=True)
|
|
543
|
-
# Delete directory
|
|
544
|
-
fs.rmdir(path=directory["name"])
|
|
545
|
-
except FileNotFoundError:
|
|
546
|
-
logger.info(
|
|
547
|
-
"Application parquet directory is empty, "
|
|
548
|
-
"probably parquets have not yet been created for this app",
|
|
549
|
-
endpoint=endpoint[mm_constants.EventFieldType.UID],
|
|
550
|
-
path=f"{self.parquet_directory}"
|
|
551
|
-
f"/key={endpoint[mm_constants.EventFieldType.UID]}",
|
|
552
|
-
)
|
|
553
|
-
|
|
554
451
|
@staticmethod
|
|
555
452
|
def _push_to_applications(
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
project,
|
|
563
|
-
applications_names,
|
|
564
|
-
model_monitoring_access_key,
|
|
565
|
-
parquet_target_path,
|
|
453
|
+
start_infer_time: datetime.datetime,
|
|
454
|
+
end_infer_time: datetime.datetime,
|
|
455
|
+
endpoint_id: str,
|
|
456
|
+
project: str,
|
|
457
|
+
applications_names: list[str],
|
|
458
|
+
model_monitoring_access_key: str,
|
|
566
459
|
):
|
|
567
460
|
"""
|
|
568
461
|
Pushes data to multiple stream applications.
|
|
569
462
|
|
|
570
|
-
:param
|
|
571
|
-
:param
|
|
572
|
-
:param
|
|
573
|
-
:param
|
|
574
|
-
:param
|
|
575
|
-
:param
|
|
576
|
-
:param project: mlrun Project name.
|
|
577
|
-
:param applications_names: List of application names to which data will be pushed.
|
|
463
|
+
:param start_infer_time: The beginning of the infer interval window.
|
|
464
|
+
:param end_infer_time: The end of the infer interval window.
|
|
465
|
+
:param endpoint_id: Identifier for the model endpoint.
|
|
466
|
+
:param project: mlrun Project name.
|
|
467
|
+
:param applications_names: List of application names to which data will be pushed.
|
|
468
|
+
:param model_monitoring_access_key: Access key to apply the model monitoring process.
|
|
578
469
|
|
|
579
470
|
"""
|
|
580
|
-
|
|
581
471
|
data = {
|
|
582
|
-
mm_constants.ApplicationEvent.CURRENT_STATS: json.dumps(current_stats),
|
|
583
|
-
mm_constants.ApplicationEvent.FEATURE_STATS: json.dumps(feature_stats),
|
|
584
|
-
mm_constants.ApplicationEvent.SAMPLE_PARQUET_PATH: parquet_target_path,
|
|
585
472
|
mm_constants.ApplicationEvent.START_INFER_TIME: start_infer_time.isoformat(
|
|
586
473
|
sep=" ", timespec="microseconds"
|
|
587
474
|
),
|
|
588
475
|
mm_constants.ApplicationEvent.END_INFER_TIME: end_infer_time.isoformat(
|
|
589
476
|
sep=" ", timespec="microseconds"
|
|
590
477
|
),
|
|
591
|
-
mm_constants.ApplicationEvent.LAST_REQUEST: latest_request.isoformat(
|
|
592
|
-
sep=" ", timespec="microseconds"
|
|
593
|
-
),
|
|
594
478
|
mm_constants.ApplicationEvent.ENDPOINT_ID: endpoint_id,
|
|
595
479
|
mm_constants.ApplicationEvent.OUTPUT_STREAM_URI: get_stream_path(
|
|
596
480
|
project=project,
|
|
@@ -608,53 +492,6 @@ class MonitoringApplicationController:
|
|
|
608
492
|
[data]
|
|
609
493
|
)
|
|
610
494
|
|
|
611
|
-
@staticmethod
|
|
612
|
-
def _get_sample_df(
|
|
613
|
-
feature_set: mlrun.common.schemas.FeatureSet,
|
|
614
|
-
endpoint_id: str,
|
|
615
|
-
start_infer_time: datetime.datetime,
|
|
616
|
-
end_infer_time: datetime.datetime,
|
|
617
|
-
parquet_directory: str,
|
|
618
|
-
storage_options: dict,
|
|
619
|
-
application_name: str,
|
|
620
|
-
) -> mlrun.feature_store.OfflineVectorResponse:
|
|
621
|
-
"""
|
|
622
|
-
Retrieves a sample DataFrame of the current input according to the provided infer interval window.
|
|
623
|
-
|
|
624
|
-
:param feature_set: The main feature set.
|
|
625
|
-
:param endpoint_id: Identifier for the model endpoint.
|
|
626
|
-
:param start_infer_time: The beginning of the infer interval window.
|
|
627
|
-
:param end_infer_time: The end of the infer interval window.
|
|
628
|
-
:param parquet_directory: Directory where Parquet files are stored.
|
|
629
|
-
:param storage_options: Storage options for accessing the data.
|
|
630
|
-
:param application_name: Current application name.
|
|
631
|
-
|
|
632
|
-
:return: OfflineVectorResponse that can be used for generating a sample DataFrame for the specified endpoint.
|
|
633
|
-
|
|
634
|
-
"""
|
|
635
|
-
features = [f"{feature_set.metadata.name}.*"]
|
|
636
|
-
vector = fstore.FeatureVector(
|
|
637
|
-
name=f"{endpoint_id}_vector",
|
|
638
|
-
features=features,
|
|
639
|
-
with_indexes=True,
|
|
640
|
-
)
|
|
641
|
-
vector.metadata.tag = application_name
|
|
642
|
-
vector.feature_set_objects = {feature_set.metadata.name: feature_set}
|
|
643
|
-
|
|
644
|
-
# get offline features based on application start and end time.
|
|
645
|
-
# store the result parquet by partitioning by controller end processing time
|
|
646
|
-
offline_response = vector.get_offline_features(
|
|
647
|
-
start_time=start_infer_time,
|
|
648
|
-
end_time=end_infer_time,
|
|
649
|
-
timestamp_for_filtering=mm_constants.EventFieldType.TIMESTAMP,
|
|
650
|
-
target=ParquetTarget(
|
|
651
|
-
path=parquet_directory
|
|
652
|
-
+ f"/key={endpoint_id}/{int(start_infer_time.timestamp())}/{application_name}.parquet",
|
|
653
|
-
storage_options=storage_options,
|
|
654
|
-
),
|
|
655
|
-
)
|
|
656
|
-
return offline_response
|
|
657
|
-
|
|
658
495
|
|
|
659
496
|
def handler(context: nuclio.Context, event: nuclio.Event) -> None:
|
|
660
497
|
"""
|
|
@@ -588,7 +588,11 @@ class SQLStoreBase(StoreBase):
|
|
|
588
588
|
|
|
589
589
|
for endpoint_dict in endpoints:
|
|
590
590
|
endpoint_id = endpoint_dict[mm_schemas.EventFieldType.UID]
|
|
591
|
-
|
|
591
|
+
logger.debug(
|
|
592
|
+
"Deleting model endpoint resources from the SQL tables",
|
|
593
|
+
endpoint_id=endpoint_id,
|
|
594
|
+
project=self.project,
|
|
595
|
+
)
|
|
592
596
|
# Delete last analyzed records
|
|
593
597
|
self._delete_last_analyzed(endpoint_id=endpoint_id)
|
|
594
598
|
|
|
@@ -598,6 +602,16 @@ class SQLStoreBase(StoreBase):
|
|
|
598
602
|
|
|
599
603
|
# Delete model endpoint record
|
|
600
604
|
self.delete_model_endpoint(endpoint_id=endpoint_id)
|
|
605
|
+
logger.debug(
|
|
606
|
+
"Successfully deleted model endpoint resources",
|
|
607
|
+
endpoint_id=endpoint_id,
|
|
608
|
+
project=self.project,
|
|
609
|
+
)
|
|
610
|
+
|
|
611
|
+
logger.debug(
|
|
612
|
+
"Successfully deleted model monitoring endpoints resources from the SQL tables",
|
|
613
|
+
project=self.project,
|
|
614
|
+
)
|
|
601
615
|
|
|
602
616
|
def get_model_endpoint_metrics(
|
|
603
617
|
self, endpoint_id: str, type: mm_schemas.ModelEndpointMonitoringMetricType
|
|
@@ -305,10 +305,22 @@ class KVStoreBase(StoreBase):
|
|
|
305
305
|
endpoint_id = endpoint_dict[mm_schemas.EventFieldType.ENDPOINT_ID]
|
|
306
306
|
else:
|
|
307
307
|
endpoint_id = endpoint_dict[mm_schemas.EventFieldType.UID]
|
|
308
|
+
|
|
309
|
+
logger.debug(
|
|
310
|
+
"Deleting model endpoint resources from the V3IO KV table",
|
|
311
|
+
endpoint_id=endpoint_id,
|
|
312
|
+
project=self.project,
|
|
313
|
+
)
|
|
314
|
+
|
|
308
315
|
self.delete_model_endpoint(
|
|
309
316
|
endpoint_id,
|
|
310
317
|
)
|
|
311
318
|
|
|
319
|
+
logger.debug(
|
|
320
|
+
"Successfully deleted model monitoring endpoints from the V3IO KV table",
|
|
321
|
+
project=self.project,
|
|
322
|
+
)
|
|
323
|
+
|
|
312
324
|
# Delete remain records in the KV
|
|
313
325
|
all_records = self.client.kv.new_cursor(
|
|
314
326
|
container=self.container,
|
|
@@ -94,38 +94,39 @@ class TDEngineSchema:
|
|
|
94
94
|
tags = ", ".join(f"{col} {val}" for col, val in self.tags.items())
|
|
95
95
|
return f"CREATE STABLE if NOT EXISTS {self.database}.{self.super_table} ({columns}) TAGS ({tags});"
|
|
96
96
|
|
|
97
|
-
def
|
|
97
|
+
def _create_subtable_sql(
|
|
98
98
|
self,
|
|
99
99
|
subtable: str,
|
|
100
100
|
values: dict[str, Union[str, int, float, datetime.datetime]],
|
|
101
101
|
) -> str:
|
|
102
102
|
try:
|
|
103
|
-
|
|
103
|
+
tags = ", ".join(f"'{values[val]}'" for val in self.tags)
|
|
104
104
|
except KeyError:
|
|
105
105
|
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
106
106
|
f"values must contain all tags: {self.tags.keys()}"
|
|
107
107
|
)
|
|
108
|
-
return f"CREATE TABLE if NOT EXISTS {self.database}.{subtable} USING {self.super_table} TAGS ({
|
|
108
|
+
return f"CREATE TABLE if NOT EXISTS {self.database}.{subtable} USING {self.super_table} TAGS ({tags});"
|
|
109
109
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
110
|
+
@staticmethod
|
|
111
|
+
def _insert_subtable_stmt(
|
|
112
|
+
statement: taosws.TaosStmt,
|
|
113
|
+
columns: dict[str, _TDEngineColumn],
|
|
113
114
|
subtable: str,
|
|
114
115
|
values: dict[str, Union[str, int, float, datetime.datetime]],
|
|
115
116
|
) -> taosws.TaosStmt:
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
stmt.set_tbname_tags(subtable, [])
|
|
117
|
+
question_marks = ", ".join("?" * len(columns))
|
|
118
|
+
statement.prepare(f"INSERT INTO ? VALUES ({question_marks});")
|
|
119
|
+
statement.set_tbname(subtable)
|
|
120
120
|
|
|
121
121
|
bind_params = []
|
|
122
122
|
|
|
123
|
-
for col_name, col_type in
|
|
123
|
+
for col_name, col_type in columns.items():
|
|
124
124
|
val = values[col_name]
|
|
125
125
|
bind_params.append(values_to_column([val], col_type))
|
|
126
126
|
|
|
127
|
-
|
|
128
|
-
|
|
127
|
+
statement.bind_param(bind_params)
|
|
128
|
+
statement.add_batch()
|
|
129
|
+
return statement
|
|
129
130
|
|
|
130
131
|
def _delete_subtable_query(
|
|
131
132
|
self,
|
|
@@ -163,8 +164,8 @@ class TDEngineSchema:
|
|
|
163
164
|
@staticmethod
|
|
164
165
|
def _get_records_query(
|
|
165
166
|
table: str,
|
|
166
|
-
start: datetime,
|
|
167
|
-
end: datetime,
|
|
167
|
+
start: datetime.datetime,
|
|
168
|
+
end: datetime.datetime,
|
|
168
169
|
columns_to_filter: list[str] = None,
|
|
169
170
|
filter_query: Optional[str] = None,
|
|
170
171
|
interval: Optional[str] = None,
|
|
@@ -211,7 +212,7 @@ class TDEngineSchema:
|
|
|
211
212
|
if filter_query:
|
|
212
213
|
query.write(f"{filter_query} AND ")
|
|
213
214
|
if start:
|
|
214
|
-
query.write(f"{timestamp_column} >= '{start}'
|
|
215
|
+
query.write(f"{timestamp_column} >= '{start}' AND ")
|
|
215
216
|
if end:
|
|
216
217
|
query.write(f"{timestamp_column} <= '{end}'")
|
|
217
218
|
if interval:
|