mlrun 1.7.0rc39__py3-none-any.whl → 1.7.0rc42__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 +3 -0
- mlrun/common/db/sql_session.py +3 -2
- mlrun/common/helpers.py +0 -1
- mlrun/common/schemas/api_gateway.py +6 -6
- mlrun/common/schemas/common.py +4 -4
- mlrun/common/schemas/model_monitoring/model_endpoints.py +0 -1
- mlrun/config.py +1 -1
- mlrun/data_types/to_pandas.py +12 -12
- mlrun/datastore/alibaba_oss.py +1 -0
- mlrun/datastore/azure_blob.py +1 -6
- mlrun/datastore/base.py +12 -0
- mlrun/datastore/dbfs_store.py +1 -5
- mlrun/datastore/filestore.py +1 -3
- mlrun/datastore/google_cloud_storage.py +1 -9
- mlrun/datastore/redis.py +1 -0
- mlrun/datastore/s3.py +1 -0
- mlrun/datastore/storeytargets.py +147 -0
- mlrun/datastore/targets.py +67 -69
- mlrun/datastore/v3io.py +1 -0
- mlrun/errors.py +7 -4
- mlrun/feature_store/feature_vector.py +3 -1
- mlrun/feature_store/retrieval/job.py +3 -1
- mlrun/frameworks/sklearn/mlrun_interface.py +13 -3
- mlrun/model.py +1 -1
- mlrun/model_monitoring/api.py +1 -2
- mlrun/model_monitoring/applications/_application_steps.py +25 -43
- mlrun/model_monitoring/applications/context.py +206 -70
- mlrun/model_monitoring/controller.py +0 -1
- mlrun/model_monitoring/db/stores/__init__.py +3 -3
- mlrun/model_monitoring/db/stores/sqldb/sql_store.py +17 -8
- mlrun/model_monitoring/db/stores/v3io_kv/kv_store.py +14 -4
- mlrun/model_monitoring/db/tsdb/__init__.py +3 -3
- mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +18 -10
- mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +35 -23
- mlrun/model_monitoring/helpers.py +38 -1
- mlrun/model_monitoring/stream_processing.py +8 -26
- mlrun/package/packagers/default_packager.py +2 -2
- mlrun/projects/project.py +17 -16
- mlrun/runtimes/funcdoc.py +1 -1
- mlrun/runtimes/nuclio/api_gateway.py +9 -0
- mlrun/runtimes/nuclio/application/application.py +131 -55
- mlrun/runtimes/nuclio/function.py +4 -10
- mlrun/runtimes/nuclio/serving.py +2 -2
- mlrun/runtimes/sparkjob/spark3job.py +1 -1
- mlrun/runtimes/utils.py +16 -0
- mlrun/serving/routers.py +1 -1
- mlrun/serving/server.py +19 -5
- mlrun/serving/states.py +8 -0
- mlrun/serving/v2_serving.py +34 -26
- mlrun/utils/helpers.py +12 -2
- mlrun/utils/v3io_clients.py +2 -2
- mlrun/utils/version/version.json +2 -2
- {mlrun-1.7.0rc39.dist-info → mlrun-1.7.0rc42.dist-info}/METADATA +2 -2
- {mlrun-1.7.0rc39.dist-info → mlrun-1.7.0rc42.dist-info}/RECORD +58 -57
- {mlrun-1.7.0rc39.dist-info → mlrun-1.7.0rc42.dist-info}/WHEEL +1 -1
- {mlrun-1.7.0rc39.dist-info → mlrun-1.7.0rc42.dist-info}/LICENSE +0 -0
- {mlrun-1.7.0rc39.dist-info → mlrun-1.7.0rc42.dist-info}/entry_points.txt +0 -0
- {mlrun-1.7.0rc39.dist-info → mlrun-1.7.0rc42.dist-info}/top_level.txt +0 -0
|
@@ -11,19 +11,22 @@
|
|
|
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
15
|
import json
|
|
15
|
-
import
|
|
16
|
+
import socket
|
|
17
|
+
from typing import Any, Optional, cast
|
|
16
18
|
|
|
17
19
|
import numpy as np
|
|
18
20
|
import pandas as pd
|
|
19
21
|
|
|
20
|
-
import mlrun.common.
|
|
21
|
-
import mlrun.common.model_monitoring.helpers
|
|
22
|
+
import mlrun.common.constants as mlrun_constants
|
|
22
23
|
import mlrun.common.schemas.model_monitoring.constants as mm_constants
|
|
23
24
|
import mlrun.feature_store as fstore
|
|
24
|
-
|
|
25
|
+
import mlrun.features
|
|
26
|
+
import mlrun.serving
|
|
27
|
+
import mlrun.utils
|
|
28
|
+
from mlrun.artifacts import Artifact, DatasetArtifact, ModelArtifact, get_model
|
|
25
29
|
from mlrun.common.model_monitoring.helpers import FeatureStats, pad_features_hist
|
|
26
|
-
from mlrun.execution import MLClientCtx
|
|
27
30
|
from mlrun.model_monitoring.helpers import (
|
|
28
31
|
calculate_inputs_statistics,
|
|
29
32
|
get_endpoint_record,
|
|
@@ -31,13 +34,17 @@ from mlrun.model_monitoring.helpers import (
|
|
|
31
34
|
from mlrun.model_monitoring.model_endpoint import ModelEndpoint
|
|
32
35
|
|
|
33
36
|
|
|
34
|
-
class MonitoringApplicationContext
|
|
37
|
+
class MonitoringApplicationContext:
|
|
35
38
|
"""
|
|
36
39
|
The monitoring context holds all the relevant information for the monitoring application,
|
|
37
40
|
and also it can be used for logging artifacts and results.
|
|
38
41
|
The monitoring context has the following attributes:
|
|
39
42
|
|
|
40
|
-
:param application_name: (str)
|
|
43
|
+
:param application_name: (str) The model monitoring application name.
|
|
44
|
+
:param project_name: (str) The project name.
|
|
45
|
+
:param project: (MlrunProject) The project object.
|
|
46
|
+
:param logger: (mlrun.utils.Logger) MLRun logger.
|
|
47
|
+
:param nuclio_logger: (nuclio.request.Logger) Nuclio logger.
|
|
41
48
|
:param sample_df_stats: (FeatureStats) The new sample distribution dictionary.
|
|
42
49
|
:param feature_stats: (FeatureStats) The train sample distribution dictionary.
|
|
43
50
|
:param sample_df: (pd.DataFrame) The new sample DataFrame.
|
|
@@ -49,79 +56,89 @@ class MonitoringApplicationContext(MLClientCtx):
|
|
|
49
56
|
:param model_endpoint: (ModelEndpoint) The model endpoint object.
|
|
50
57
|
:param feature_names: (list[str]) List of models feature names.
|
|
51
58
|
:param label_names: (list[str]) List of models label names.
|
|
52
|
-
:param model: (tuple[str, ModelArtifact, dict]) The model file, model spec object,
|
|
53
|
-
|
|
59
|
+
:param model: (tuple[str, ModelArtifact, dict]) The model file, model spec object,
|
|
60
|
+
and a list of extra data items.
|
|
54
61
|
"""
|
|
55
62
|
|
|
56
|
-
def __init__(
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
self.endpoint_id: typing.Optional[str] = None
|
|
65
|
-
self.output_stream_uri: typing.Optional[str] = None
|
|
66
|
-
|
|
67
|
-
self._sample_df: typing.Optional[pd.DataFrame] = None
|
|
68
|
-
self._model_endpoint: typing.Optional[ModelEndpoint] = None
|
|
69
|
-
self._feature_stats: typing.Optional[FeatureStats] = None
|
|
70
|
-
self._sample_df_stats: typing.Optional[FeatureStats] = None
|
|
71
|
-
|
|
72
|
-
@classmethod
|
|
73
|
-
def from_dict(
|
|
74
|
-
cls,
|
|
75
|
-
attrs: dict,
|
|
76
|
-
context=None,
|
|
77
|
-
model_endpoint_dict=None,
|
|
78
|
-
**kwargs,
|
|
79
|
-
) -> "MonitoringApplicationContext":
|
|
63
|
+
def __init__(
|
|
64
|
+
self,
|
|
65
|
+
*,
|
|
66
|
+
graph_context: mlrun.serving.GraphContext,
|
|
67
|
+
application_name: str,
|
|
68
|
+
event: dict[str, Any],
|
|
69
|
+
model_endpoint_dict: dict[str, ModelEndpoint],
|
|
70
|
+
) -> None:
|
|
80
71
|
"""
|
|
81
|
-
|
|
72
|
+
Initialize a `MonitoringApplicationContext` object.
|
|
73
|
+
Note: this object should not be instantiated manually.
|
|
82
74
|
|
|
83
|
-
:param
|
|
84
|
-
:param
|
|
75
|
+
:param application_name: The application name.
|
|
76
|
+
:param event: The instance data dictionary.
|
|
85
77
|
:param model_endpoint_dict: Dictionary of model endpoints.
|
|
86
|
-
|
|
87
78
|
"""
|
|
79
|
+
self.application_name = application_name
|
|
88
80
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
81
|
+
self.project_name = graph_context.project
|
|
82
|
+
self.project = mlrun.load_project(url=self.project_name)
|
|
83
|
+
|
|
84
|
+
# MLRun Logger
|
|
85
|
+
self.logger = mlrun.utils.create_logger(
|
|
86
|
+
level=mlrun.mlconf.log_level,
|
|
87
|
+
formatter_kind=mlrun.mlconf.log_formatter,
|
|
88
|
+
name="monitoring-application",
|
|
89
|
+
)
|
|
90
|
+
# Nuclio logger - `nuclio.request.Logger`.
|
|
91
|
+
# Note: this logger does not accept keyword arguments.
|
|
92
|
+
self.nuclio_logger = graph_context.logger
|
|
99
93
|
|
|
100
|
-
|
|
101
|
-
|
|
94
|
+
# event data
|
|
95
|
+
self.start_infer_time = pd.Timestamp(
|
|
96
|
+
cast(str, event.get(mm_constants.ApplicationEvent.START_INFER_TIME))
|
|
102
97
|
)
|
|
103
|
-
|
|
104
|
-
|
|
98
|
+
self.end_infer_time = pd.Timestamp(
|
|
99
|
+
cast(str, event.get(mm_constants.ApplicationEvent.END_INFER_TIME))
|
|
105
100
|
)
|
|
106
|
-
|
|
107
|
-
|
|
101
|
+
self.latest_request = pd.Timestamp(
|
|
102
|
+
cast(str, event.get(mm_constants.ApplicationEvent.LAST_REQUEST))
|
|
108
103
|
)
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
attrs.get(mm_constants.ApplicationEvent.FEATURE_STATS, "{}")
|
|
104
|
+
self.endpoint_id = cast(
|
|
105
|
+
str, event.get(mm_constants.ApplicationEvent.ENDPOINT_ID)
|
|
112
106
|
)
|
|
113
|
-
|
|
114
|
-
|
|
107
|
+
self.output_stream_uri = cast(
|
|
108
|
+
str, event.get(mm_constants.ApplicationEvent.OUTPUT_STREAM_URI)
|
|
115
109
|
)
|
|
116
110
|
|
|
117
|
-
|
|
118
|
-
|
|
111
|
+
self._feature_stats: Optional[FeatureStats] = json.loads(
|
|
112
|
+
event.get(mm_constants.ApplicationEvent.FEATURE_STATS, "{}")
|
|
113
|
+
)
|
|
114
|
+
self._sample_df_stats: Optional[FeatureStats] = json.loads(
|
|
115
|
+
event.get(mm_constants.ApplicationEvent.CURRENT_STATS, "{}")
|
|
116
|
+
)
|
|
119
117
|
|
|
120
|
-
|
|
118
|
+
# Default labels for the artifacts
|
|
119
|
+
self._default_labels = self._get_default_labels()
|
|
120
|
+
|
|
121
|
+
# Persistent data - fetched when needed
|
|
122
|
+
self._sample_df: Optional[pd.DataFrame] = None
|
|
123
|
+
self._model_endpoint: Optional[ModelEndpoint] = model_endpoint_dict.get(
|
|
124
|
+
self.endpoint_id
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
def _get_default_labels(self) -> dict[str, str]:
|
|
128
|
+
return {
|
|
129
|
+
mlrun_constants.MLRunInternalLabels.runner_pod: socket.gethostname(),
|
|
130
|
+
mlrun_constants.MLRunInternalLabels.producer_type: "model-monitoring-app",
|
|
131
|
+
mlrun_constants.MLRunInternalLabels.app_name: self.application_name,
|
|
132
|
+
mlrun_constants.MLRunInternalLabels.endpoint_id: self.endpoint_id,
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
def _add_default_labels(self, labels: Optional[dict[str, str]]) -> dict[str, str]:
|
|
136
|
+
"""Add the default labels to logged artifacts labels"""
|
|
137
|
+
return (labels or {}) | self._default_labels
|
|
121
138
|
|
|
122
139
|
@property
|
|
123
140
|
def sample_df(self) -> pd.DataFrame:
|
|
124
|
-
if
|
|
141
|
+
if self._sample_df is None:
|
|
125
142
|
feature_set = fstore.get_feature_set(
|
|
126
143
|
self.model_endpoint.status.monitoring_feature_set_uri
|
|
127
144
|
)
|
|
@@ -144,15 +161,15 @@ class MonitoringApplicationContext(MLClientCtx):
|
|
|
144
161
|
|
|
145
162
|
@property
|
|
146
163
|
def model_endpoint(self) -> ModelEndpoint:
|
|
147
|
-
if not
|
|
164
|
+
if not self._model_endpoint:
|
|
148
165
|
self._model_endpoint = ModelEndpoint.from_flat_dict(
|
|
149
|
-
get_endpoint_record(self.
|
|
166
|
+
get_endpoint_record(self.project_name, self.endpoint_id)
|
|
150
167
|
)
|
|
151
168
|
return self._model_endpoint
|
|
152
169
|
|
|
153
170
|
@property
|
|
154
171
|
def feature_stats(self) -> FeatureStats:
|
|
155
|
-
if not
|
|
172
|
+
if not self._feature_stats:
|
|
156
173
|
self._feature_stats = json.loads(self.model_endpoint.status.feature_stats)
|
|
157
174
|
pad_features_hist(self._feature_stats)
|
|
158
175
|
return self._feature_stats
|
|
@@ -160,7 +177,7 @@ class MonitoringApplicationContext(MLClientCtx):
|
|
|
160
177
|
@property
|
|
161
178
|
def sample_df_stats(self) -> FeatureStats:
|
|
162
179
|
"""statistics of the sample dataframe"""
|
|
163
|
-
if not
|
|
180
|
+
if not self._sample_df_stats:
|
|
164
181
|
self._sample_df_stats = calculate_inputs_statistics(
|
|
165
182
|
self.feature_stats, self.sample_df
|
|
166
183
|
)
|
|
@@ -184,13 +201,11 @@ class MonitoringApplicationContext(MLClientCtx):
|
|
|
184
201
|
|
|
185
202
|
@property
|
|
186
203
|
def model(self) -> tuple[str, ModelArtifact, dict]:
|
|
187
|
-
"""
|
|
204
|
+
"""The model file, model spec object, and a list of extra data items"""
|
|
188
205
|
return get_model(self.model_endpoint.spec.model_uri)
|
|
189
206
|
|
|
190
207
|
@staticmethod
|
|
191
|
-
def dict_to_histogram(
|
|
192
|
-
histogram_dict: mlrun.common.model_monitoring.helpers.FeatureStats,
|
|
193
|
-
) -> pd.DataFrame:
|
|
208
|
+
def dict_to_histogram(histogram_dict: FeatureStats) -> pd.DataFrame:
|
|
194
209
|
"""
|
|
195
210
|
Convert histogram dictionary to pandas DataFrame with feature histograms as columns
|
|
196
211
|
|
|
@@ -210,3 +225,124 @@ class MonitoringApplicationContext(MLClientCtx):
|
|
|
210
225
|
histograms = pd.DataFrame(histograms)
|
|
211
226
|
|
|
212
227
|
return histograms
|
|
228
|
+
|
|
229
|
+
def log_artifact(
|
|
230
|
+
self,
|
|
231
|
+
item,
|
|
232
|
+
body=None,
|
|
233
|
+
tag: str = "",
|
|
234
|
+
local_path: str = "",
|
|
235
|
+
artifact_path: Optional[str] = None,
|
|
236
|
+
format: Optional[str] = None,
|
|
237
|
+
upload: Optional[bool] = None,
|
|
238
|
+
labels: Optional[dict[str, str]] = None,
|
|
239
|
+
target_path: Optional[str] = None,
|
|
240
|
+
**kwargs,
|
|
241
|
+
) -> Artifact:
|
|
242
|
+
"""
|
|
243
|
+
Log an artifact.
|
|
244
|
+
See :func:`~mlrun.projects.MlrunProject.log_artifact` for the documentation.
|
|
245
|
+
"""
|
|
246
|
+
labels = self._add_default_labels(labels)
|
|
247
|
+
return self.project.log_artifact(
|
|
248
|
+
item,
|
|
249
|
+
body=body,
|
|
250
|
+
tag=tag,
|
|
251
|
+
local_path=local_path,
|
|
252
|
+
artifact_path=artifact_path,
|
|
253
|
+
format=format,
|
|
254
|
+
upload=upload,
|
|
255
|
+
labels=labels,
|
|
256
|
+
target_path=target_path,
|
|
257
|
+
**kwargs,
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
def log_dataset(
|
|
261
|
+
self,
|
|
262
|
+
key,
|
|
263
|
+
df,
|
|
264
|
+
tag="",
|
|
265
|
+
local_path=None,
|
|
266
|
+
artifact_path=None,
|
|
267
|
+
upload=None,
|
|
268
|
+
labels=None,
|
|
269
|
+
format="",
|
|
270
|
+
preview=None,
|
|
271
|
+
stats=None,
|
|
272
|
+
target_path="",
|
|
273
|
+
extra_data=None,
|
|
274
|
+
label_column: Optional[str] = None,
|
|
275
|
+
**kwargs,
|
|
276
|
+
) -> DatasetArtifact:
|
|
277
|
+
"""
|
|
278
|
+
Log a dataset artifact.
|
|
279
|
+
See :func:`~mlrun.projects.MlrunProject.log_dataset` for the documentation.
|
|
280
|
+
"""
|
|
281
|
+
labels = self._add_default_labels(labels)
|
|
282
|
+
return self.project.log_dataset(
|
|
283
|
+
key,
|
|
284
|
+
df,
|
|
285
|
+
tag=tag,
|
|
286
|
+
local_path=local_path,
|
|
287
|
+
artifact_path=artifact_path,
|
|
288
|
+
upload=upload,
|
|
289
|
+
labels=labels,
|
|
290
|
+
format=format,
|
|
291
|
+
preview=preview,
|
|
292
|
+
stats=stats,
|
|
293
|
+
target_path=target_path,
|
|
294
|
+
extra_data=extra_data,
|
|
295
|
+
label_column=label_column,
|
|
296
|
+
**kwargs,
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
def log_model(
|
|
300
|
+
self,
|
|
301
|
+
key,
|
|
302
|
+
body=None,
|
|
303
|
+
framework="",
|
|
304
|
+
tag="",
|
|
305
|
+
model_dir=None,
|
|
306
|
+
model_file=None,
|
|
307
|
+
algorithm=None,
|
|
308
|
+
metrics=None,
|
|
309
|
+
parameters=None,
|
|
310
|
+
artifact_path=None,
|
|
311
|
+
upload=None,
|
|
312
|
+
labels=None,
|
|
313
|
+
inputs: Optional[list[mlrun.features.Feature]] = None,
|
|
314
|
+
outputs: Optional[list[mlrun.features.Feature]] = None,
|
|
315
|
+
feature_vector: Optional[str] = None,
|
|
316
|
+
feature_weights: Optional[list] = None,
|
|
317
|
+
training_set=None,
|
|
318
|
+
label_column=None,
|
|
319
|
+
extra_data=None,
|
|
320
|
+
**kwargs,
|
|
321
|
+
) -> ModelArtifact:
|
|
322
|
+
"""
|
|
323
|
+
Log a model artifact.
|
|
324
|
+
See :func:`~mlrun.projects.MlrunProject.log_model` for the documentation.
|
|
325
|
+
"""
|
|
326
|
+
labels = self._add_default_labels(labels)
|
|
327
|
+
return self.project.log_model(
|
|
328
|
+
key,
|
|
329
|
+
body=body,
|
|
330
|
+
framework=framework,
|
|
331
|
+
tag=tag,
|
|
332
|
+
model_dir=model_dir,
|
|
333
|
+
model_file=model_file,
|
|
334
|
+
algorithm=algorithm,
|
|
335
|
+
metrics=metrics,
|
|
336
|
+
parameters=parameters,
|
|
337
|
+
artifact_path=artifact_path,
|
|
338
|
+
upload=upload,
|
|
339
|
+
labels=labels,
|
|
340
|
+
inputs=inputs,
|
|
341
|
+
outputs=outputs,
|
|
342
|
+
feature_vector=feature_vector,
|
|
343
|
+
feature_weights=feature_weights,
|
|
344
|
+
training_set=training_set,
|
|
345
|
+
label_column=label_column,
|
|
346
|
+
extra_data=extra_data,
|
|
347
|
+
**kwargs,
|
|
348
|
+
)
|
|
@@ -596,7 +596,6 @@ class MonitoringApplicationController:
|
|
|
596
596
|
project=project,
|
|
597
597
|
function_name=mm_constants.MonitoringFunctionNames.WRITER,
|
|
598
598
|
),
|
|
599
|
-
mm_constants.ApplicationEvent.MLRUN_CONTEXT: {}, # TODO : for future use by ad-hoc batch infer
|
|
600
599
|
}
|
|
601
600
|
for app_name in applications_names:
|
|
602
601
|
data.update({mm_constants.ApplicationEvent.APPLICATION_NAME: app_name})
|
|
@@ -63,7 +63,7 @@ class ObjectStoreFactory(enum.Enum):
|
|
|
63
63
|
:param value: Provided enum (invalid) value.
|
|
64
64
|
"""
|
|
65
65
|
valid_values = list(cls.__members__.keys())
|
|
66
|
-
raise mlrun.errors.
|
|
66
|
+
raise mlrun.errors.MLRunInvalidMMStoreTypeError(
|
|
67
67
|
f"{value} is not a valid endpoint store, please choose a valid value: %{valid_values}."
|
|
68
68
|
)
|
|
69
69
|
|
|
@@ -101,7 +101,7 @@ def get_store_object(
|
|
|
101
101
|
|
|
102
102
|
:return: `StoreBase` object. Using this object, the user can apply different operations such as write, update, get
|
|
103
103
|
and delete a model endpoint record.
|
|
104
|
-
:raise: `
|
|
104
|
+
:raise: `MLRunInvalidMMStoreTypeError` if the user didn't provide store connection
|
|
105
105
|
or the provided store connection is invalid.
|
|
106
106
|
"""
|
|
107
107
|
|
|
@@ -123,7 +123,7 @@ def get_store_object(
|
|
|
123
123
|
mlrun.common.schemas.model_monitoring.ModelEndpointTarget.V3IO_NOSQL
|
|
124
124
|
)
|
|
125
125
|
else:
|
|
126
|
-
raise mlrun.errors.
|
|
126
|
+
raise mlrun.errors.MLRunInvalidMMStoreTypeError(
|
|
127
127
|
"You must provide a valid store connection by using "
|
|
128
128
|
"set_model_monitoring_credentials API."
|
|
129
129
|
)
|
|
@@ -20,7 +20,7 @@ import pandas as pd
|
|
|
20
20
|
import sqlalchemy
|
|
21
21
|
import sqlalchemy.exc
|
|
22
22
|
import sqlalchemy.orm
|
|
23
|
-
from sqlalchemy.engine import make_url
|
|
23
|
+
from sqlalchemy.engine import Engine, make_url
|
|
24
24
|
from sqlalchemy.sql.elements import BinaryExpression
|
|
25
25
|
|
|
26
26
|
import mlrun.common.model_monitoring.helpers
|
|
@@ -61,9 +61,15 @@ class SQLStoreBase(StoreBase):
|
|
|
61
61
|
)
|
|
62
62
|
|
|
63
63
|
self._sql_connection_string = kwargs.get("store_connection_string")
|
|
64
|
-
self._engine =
|
|
64
|
+
self._engine = None
|
|
65
65
|
self._init_tables()
|
|
66
66
|
|
|
67
|
+
@property
|
|
68
|
+
def engine(self) -> Engine:
|
|
69
|
+
if not self._engine:
|
|
70
|
+
self._engine = get_engine(dsn=self._sql_connection_string)
|
|
71
|
+
return self._engine
|
|
72
|
+
|
|
67
73
|
def create_tables(self):
|
|
68
74
|
self._create_tables_if_not_exist()
|
|
69
75
|
|
|
@@ -116,7 +122,7 @@ class SQLStoreBase(StoreBase):
|
|
|
116
122
|
:param table_name: Target table name.
|
|
117
123
|
:param event: Event dictionary that will be written into the DB.
|
|
118
124
|
"""
|
|
119
|
-
with self.
|
|
125
|
+
with self.engine.connect() as connection:
|
|
120
126
|
# Convert the result into a pandas Dataframe and write it into the database
|
|
121
127
|
event_df = pd.DataFrame([event])
|
|
122
128
|
event_df.to_sql(table_name, con=connection, index=False, if_exists="append")
|
|
@@ -177,7 +183,7 @@ class SQLStoreBase(StoreBase):
|
|
|
177
183
|
param table: SQLAlchemy declarative table.
|
|
178
184
|
:param criteria: A list of binary expressions that filter the query.
|
|
179
185
|
"""
|
|
180
|
-
if not self.
|
|
186
|
+
if not self.engine.has_table(table.__tablename__):
|
|
181
187
|
logger.debug(
|
|
182
188
|
f"Table {table.__tablename__} does not exist in the database. Skipping deletion."
|
|
183
189
|
)
|
|
@@ -524,9 +530,9 @@ class SQLStoreBase(StoreBase):
|
|
|
524
530
|
for table in self._tables:
|
|
525
531
|
# Create table if not exist. The `metadata` contains the `ModelEndpointsTable`
|
|
526
532
|
db_name = make_url(self._sql_connection_string).database
|
|
527
|
-
if not self.
|
|
533
|
+
if not self.engine.has_table(table):
|
|
528
534
|
logger.info(f"Creating table {table} on {db_name} db.")
|
|
529
|
-
self._tables[table].metadata.create_all(bind=self.
|
|
535
|
+
self._tables[table].metadata.create_all(bind=self.engine)
|
|
530
536
|
else:
|
|
531
537
|
logger.info(f"Table {table} already exists on {db_name} db.")
|
|
532
538
|
|
|
@@ -574,8 +580,11 @@ class SQLStoreBase(StoreBase):
|
|
|
574
580
|
"""
|
|
575
581
|
Delete all the model monitoring resources of the project in the SQL tables.
|
|
576
582
|
"""
|
|
583
|
+
logger.debug(
|
|
584
|
+
"Deleting model monitoring endpoints resources from the SQL tables",
|
|
585
|
+
project=self.project,
|
|
586
|
+
)
|
|
577
587
|
endpoints = self.list_model_endpoints()
|
|
578
|
-
logger.debug("Deleting model monitoring resources", project=self.project)
|
|
579
588
|
|
|
580
589
|
for endpoint_dict in endpoints:
|
|
581
590
|
endpoint_id = endpoint_dict[mm_schemas.EventFieldType.UID]
|
|
@@ -612,7 +621,7 @@ class SQLStoreBase(StoreBase):
|
|
|
612
621
|
|
|
613
622
|
# Note: the block below does not use self._get, as we need here all the
|
|
614
623
|
# results, not only `one_or_none`.
|
|
615
|
-
with sqlalchemy.orm.Session(self.
|
|
624
|
+
with sqlalchemy.orm.Session(self.engine) as session:
|
|
616
625
|
metric_rows = (
|
|
617
626
|
session.query(table) # pyright: ignore[reportOptionalCall]
|
|
618
627
|
.filter(table.endpoint_id == endpoint_id)
|
|
@@ -20,6 +20,7 @@ from http import HTTPStatus
|
|
|
20
20
|
import v3io.dataplane
|
|
21
21
|
import v3io.dataplane.output
|
|
22
22
|
import v3io.dataplane.response
|
|
23
|
+
from v3io.dataplane import Client as V3IOClient
|
|
23
24
|
|
|
24
25
|
import mlrun.common.model_monitoring.helpers
|
|
25
26
|
import mlrun.common.schemas.model_monitoring as mm_schemas
|
|
@@ -100,13 +101,18 @@ class KVStoreBase(StoreBase):
|
|
|
100
101
|
project: str,
|
|
101
102
|
) -> None:
|
|
102
103
|
super().__init__(project=project)
|
|
103
|
-
|
|
104
|
-
self.client = mlrun.utils.v3io_clients.get_v3io_client(
|
|
105
|
-
endpoint=mlrun.mlconf.v3io_api,
|
|
106
|
-
)
|
|
104
|
+
self._client = None
|
|
107
105
|
# Get the KV table path and container
|
|
108
106
|
self.path, self.container = self._get_path_and_container()
|
|
109
107
|
|
|
108
|
+
@property
|
|
109
|
+
def client(self) -> V3IOClient:
|
|
110
|
+
if not self._client:
|
|
111
|
+
self._client = mlrun.utils.v3io_clients.get_v3io_client(
|
|
112
|
+
endpoint=mlrun.mlconf.v3io_api,
|
|
113
|
+
)
|
|
114
|
+
return self._client
|
|
115
|
+
|
|
110
116
|
def write_model_endpoint(self, endpoint: dict[str, typing.Any]):
|
|
111
117
|
"""
|
|
112
118
|
Create a new endpoint record in the KV table.
|
|
@@ -285,6 +291,10 @@ class KVStoreBase(StoreBase):
|
|
|
285
291
|
"""
|
|
286
292
|
Delete all model endpoints resources in V3IO KV.
|
|
287
293
|
"""
|
|
294
|
+
logger.debug(
|
|
295
|
+
"Deleting model monitoring endpoints resources in V3IO KV",
|
|
296
|
+
project=self.project,
|
|
297
|
+
)
|
|
288
298
|
|
|
289
299
|
endpoints = self.list_model_endpoints()
|
|
290
300
|
|
|
@@ -57,7 +57,7 @@ class ObjectTSDBFactory(enum.Enum):
|
|
|
57
57
|
:param value: Provided enum (invalid) value.
|
|
58
58
|
"""
|
|
59
59
|
valid_values = list(cls.__members__.keys())
|
|
60
|
-
raise mlrun.errors.
|
|
60
|
+
raise mlrun.errors.MLRunInvalidMMStoreTypeError(
|
|
61
61
|
f"{value} is not a valid tsdb, please choose a valid value: %{valid_values}."
|
|
62
62
|
)
|
|
63
63
|
|
|
@@ -76,7 +76,7 @@ def get_tsdb_connector(
|
|
|
76
76
|
|
|
77
77
|
:return: `TSDBConnector` object. The main goal of this object is to handle different operations on the
|
|
78
78
|
TSDB connector such as updating drift metrics or write application record result.
|
|
79
|
-
:raise: `
|
|
79
|
+
:raise: `MLRunInvalidMMStoreTypeError` if the user didn't provide TSDB connection
|
|
80
80
|
or the provided TSDB connection is invalid.
|
|
81
81
|
"""
|
|
82
82
|
|
|
@@ -93,7 +93,7 @@ def get_tsdb_connector(
|
|
|
93
93
|
elif tsdb_connection_string and tsdb_connection_string == "v3io":
|
|
94
94
|
tsdb_connector_type = mlrun.common.schemas.model_monitoring.TSDBTarget.V3IO_TSDB
|
|
95
95
|
else:
|
|
96
|
-
raise mlrun.errors.
|
|
96
|
+
raise mlrun.errors.MLRunInvalidMMStoreTypeError(
|
|
97
97
|
"You must provide a valid tsdb store connection by using "
|
|
98
98
|
"set_model_monitoring_credentials API."
|
|
99
99
|
)
|
|
@@ -47,10 +47,17 @@ class TDEngineConnector(TSDBConnector):
|
|
|
47
47
|
)
|
|
48
48
|
self._tdengine_connection_string = kwargs.get("connection_string")
|
|
49
49
|
self.database = database
|
|
50
|
-
|
|
50
|
+
|
|
51
|
+
self._connection = None
|
|
51
52
|
self._init_super_tables()
|
|
52
53
|
|
|
53
|
-
|
|
54
|
+
@property
|
|
55
|
+
def connection(self) -> taosws.Connection:
|
|
56
|
+
if not self._connection:
|
|
57
|
+
self._connection = self._create_connection()
|
|
58
|
+
return self._connection
|
|
59
|
+
|
|
60
|
+
def _create_connection(self) -> taosws.Connection:
|
|
54
61
|
"""Establish a connection to the TSDB server."""
|
|
55
62
|
conn = taosws.connect(self._tdengine_connection_string)
|
|
56
63
|
try:
|
|
@@ -61,7 +68,7 @@ class TDEngineConnector(TSDBConnector):
|
|
|
61
68
|
try:
|
|
62
69
|
conn.execute(f"USE {self.database}")
|
|
63
70
|
except taosws.QueryError as e:
|
|
64
|
-
raise mlrun.errors.
|
|
71
|
+
raise mlrun.errors.MLRunTSDBConnectionFailureError(
|
|
65
72
|
f"Failed to use TDEngine database {self.database}, {mlrun.errors.err_to_str(e)}"
|
|
66
73
|
)
|
|
67
74
|
return conn
|
|
@@ -84,7 +91,7 @@ class TDEngineConnector(TSDBConnector):
|
|
|
84
91
|
"""Create TDEngine supertables."""
|
|
85
92
|
for table in self.tables:
|
|
86
93
|
create_table_query = self.tables[table]._create_super_table_query()
|
|
87
|
-
self.
|
|
94
|
+
self.connection.execute(create_table_query)
|
|
88
95
|
|
|
89
96
|
def write_application_event(
|
|
90
97
|
self,
|
|
@@ -128,10 +135,10 @@ class TDEngineConnector(TSDBConnector):
|
|
|
128
135
|
create_table_query = table._create_subtable_query(
|
|
129
136
|
subtable=table_name, values=event
|
|
130
137
|
)
|
|
131
|
-
self.
|
|
138
|
+
self.connection.execute(create_table_query)
|
|
132
139
|
|
|
133
140
|
insert_statement = table._insert_subtable_query(
|
|
134
|
-
self.
|
|
141
|
+
self.connection,
|
|
135
142
|
subtable=table_name,
|
|
136
143
|
values=event,
|
|
137
144
|
)
|
|
@@ -176,7 +183,8 @@ class TDEngineConnector(TSDBConnector):
|
|
|
176
183
|
mm_schemas.EventFieldType.PROJECT,
|
|
177
184
|
mm_schemas.EventFieldType.ENDPOINT_ID,
|
|
178
185
|
],
|
|
179
|
-
max_events=
|
|
186
|
+
max_events=1000,
|
|
187
|
+
flush_after_seconds=30,
|
|
180
188
|
)
|
|
181
189
|
|
|
182
190
|
apply_process_before_tsdb()
|
|
@@ -196,12 +204,12 @@ class TDEngineConnector(TSDBConnector):
|
|
|
196
204
|
get_subtable_names_query = self.tables[table]._get_subtables_query(
|
|
197
205
|
values={mm_schemas.EventFieldType.PROJECT: self.project}
|
|
198
206
|
)
|
|
199
|
-
subtables = self.
|
|
207
|
+
subtables = self.connection.query(get_subtable_names_query)
|
|
200
208
|
for subtable in subtables:
|
|
201
209
|
drop_query = self.tables[table]._drop_subtable_query(
|
|
202
210
|
subtable=subtable[0]
|
|
203
211
|
)
|
|
204
|
-
self.
|
|
212
|
+
self.connection.execute(drop_query)
|
|
205
213
|
logger.info(
|
|
206
214
|
f"Deleted all project resources in the TSDB connector for project {self.project}"
|
|
207
215
|
)
|
|
@@ -273,7 +281,7 @@ class TDEngineConnector(TSDBConnector):
|
|
|
273
281
|
database=self.database,
|
|
274
282
|
)
|
|
275
283
|
try:
|
|
276
|
-
query_result = self.
|
|
284
|
+
query_result = self.connection.query(full_query)
|
|
277
285
|
except taosws.QueryError as e:
|
|
278
286
|
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
279
287
|
f"Failed to query table {table} in database {self.database}, {str(e)}"
|