mlrun 1.10.0rc18__py3-none-any.whl → 1.11.0rc16__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/__init__.py +24 -3
- mlrun/__main__.py +0 -4
- mlrun/artifacts/dataset.py +2 -2
- mlrun/artifacts/document.py +6 -1
- mlrun/artifacts/llm_prompt.py +21 -15
- mlrun/artifacts/model.py +3 -3
- mlrun/artifacts/plots.py +1 -1
- mlrun/{model_monitoring/db/tsdb/tdengine → auth}/__init__.py +2 -3
- mlrun/auth/nuclio.py +89 -0
- mlrun/auth/providers.py +429 -0
- mlrun/auth/utils.py +415 -0
- mlrun/common/constants.py +14 -0
- mlrun/common/model_monitoring/helpers.py +123 -0
- mlrun/common/runtimes/constants.py +28 -0
- mlrun/common/schemas/__init__.py +14 -3
- mlrun/common/schemas/alert.py +2 -2
- mlrun/common/schemas/api_gateway.py +3 -0
- mlrun/common/schemas/auth.py +12 -10
- mlrun/common/schemas/client_spec.py +4 -0
- mlrun/common/schemas/constants.py +25 -0
- mlrun/common/schemas/frontend_spec.py +1 -8
- mlrun/common/schemas/function.py +34 -0
- mlrun/common/schemas/hub.py +33 -20
- mlrun/common/schemas/model_monitoring/__init__.py +2 -1
- mlrun/common/schemas/model_monitoring/constants.py +12 -15
- mlrun/common/schemas/model_monitoring/functions.py +13 -4
- mlrun/common/schemas/model_monitoring/model_endpoints.py +11 -0
- mlrun/common/schemas/pipeline.py +1 -1
- mlrun/common/schemas/secret.py +17 -2
- mlrun/common/secrets.py +95 -1
- mlrun/common/types.py +10 -10
- mlrun/config.py +69 -19
- mlrun/data_types/infer.py +2 -2
- mlrun/datastore/__init__.py +12 -5
- mlrun/datastore/azure_blob.py +162 -47
- mlrun/datastore/base.py +274 -10
- mlrun/datastore/datastore.py +7 -2
- mlrun/datastore/datastore_profile.py +84 -22
- mlrun/datastore/model_provider/huggingface_provider.py +225 -41
- mlrun/datastore/model_provider/mock_model_provider.py +87 -0
- mlrun/datastore/model_provider/model_provider.py +206 -74
- mlrun/datastore/model_provider/openai_provider.py +226 -66
- mlrun/datastore/s3.py +39 -18
- mlrun/datastore/sources.py +1 -1
- mlrun/datastore/store_resources.py +4 -4
- mlrun/datastore/storeytargets.py +17 -12
- mlrun/datastore/targets.py +1 -1
- mlrun/datastore/utils.py +25 -6
- mlrun/datastore/v3io.py +1 -1
- mlrun/db/base.py +63 -32
- mlrun/db/httpdb.py +373 -153
- mlrun/db/nopdb.py +54 -21
- mlrun/errors.py +4 -2
- mlrun/execution.py +66 -25
- mlrun/feature_store/api.py +1 -1
- mlrun/feature_store/common.py +1 -1
- mlrun/feature_store/feature_vector_utils.py +1 -1
- mlrun/feature_store/steps.py +8 -6
- mlrun/frameworks/_common/utils.py +3 -3
- mlrun/frameworks/_dl_common/loggers/logger.py +1 -1
- mlrun/frameworks/_dl_common/loggers/tensorboard_logger.py +2 -1
- mlrun/frameworks/_ml_common/loggers/mlrun_logger.py +1 -1
- mlrun/frameworks/_ml_common/utils.py +2 -1
- mlrun/frameworks/auto_mlrun/auto_mlrun.py +4 -3
- mlrun/frameworks/lgbm/mlrun_interfaces/mlrun_interface.py +2 -1
- mlrun/frameworks/onnx/dataset.py +2 -1
- mlrun/frameworks/onnx/mlrun_interface.py +2 -1
- mlrun/frameworks/pytorch/callbacks/logging_callback.py +5 -4
- mlrun/frameworks/pytorch/callbacks/mlrun_logging_callback.py +2 -1
- mlrun/frameworks/pytorch/callbacks/tensorboard_logging_callback.py +2 -1
- mlrun/frameworks/pytorch/utils.py +2 -1
- mlrun/frameworks/sklearn/metric.py +2 -1
- mlrun/frameworks/tf_keras/callbacks/logging_callback.py +5 -4
- mlrun/frameworks/tf_keras/callbacks/mlrun_logging_callback.py +2 -1
- mlrun/frameworks/tf_keras/callbacks/tensorboard_logging_callback.py +2 -1
- mlrun/hub/__init__.py +52 -0
- mlrun/hub/base.py +142 -0
- mlrun/hub/module.py +172 -0
- mlrun/hub/step.py +113 -0
- mlrun/k8s_utils.py +105 -16
- mlrun/launcher/base.py +15 -7
- mlrun/launcher/local.py +4 -1
- mlrun/model.py +14 -4
- mlrun/model_monitoring/__init__.py +0 -1
- mlrun/model_monitoring/api.py +65 -28
- mlrun/model_monitoring/applications/__init__.py +1 -1
- mlrun/model_monitoring/applications/base.py +299 -128
- mlrun/model_monitoring/applications/context.py +2 -4
- mlrun/model_monitoring/controller.py +132 -58
- mlrun/model_monitoring/db/_schedules.py +38 -29
- mlrun/model_monitoring/db/_stats.py +6 -16
- mlrun/model_monitoring/db/tsdb/__init__.py +9 -7
- mlrun/model_monitoring/db/tsdb/base.py +29 -9
- mlrun/model_monitoring/db/tsdb/preaggregate.py +234 -0
- mlrun/model_monitoring/db/tsdb/stream_graph_steps.py +63 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/queries/timescaledb_metrics_queries.py +414 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/queries/timescaledb_predictions_queries.py +376 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/queries/timescaledb_results_queries.py +590 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_connection.py +434 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_connector.py +541 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_operations.py +808 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_schema.py +502 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_stream.py +163 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_stream_graph_steps.py +60 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/utils/timescaledb_dataframe_processor.py +141 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/utils/timescaledb_query_builder.py +585 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/writer_graph_steps.py +73 -0
- mlrun/model_monitoring/db/tsdb/v3io/stream_graph_steps.py +20 -9
- mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +235 -51
- mlrun/model_monitoring/features_drift_table.py +2 -1
- mlrun/model_monitoring/helpers.py +30 -6
- mlrun/model_monitoring/stream_processing.py +34 -28
- mlrun/model_monitoring/writer.py +224 -4
- mlrun/package/__init__.py +2 -1
- mlrun/platforms/__init__.py +0 -43
- mlrun/platforms/iguazio.py +8 -4
- mlrun/projects/operations.py +17 -11
- mlrun/projects/pipelines.py +2 -2
- mlrun/projects/project.py +187 -123
- mlrun/run.py +95 -21
- mlrun/runtimes/__init__.py +2 -186
- mlrun/runtimes/base.py +103 -25
- mlrun/runtimes/constants.py +225 -0
- mlrun/runtimes/daskjob.py +5 -2
- mlrun/runtimes/databricks_job/databricks_runtime.py +2 -1
- mlrun/runtimes/local.py +5 -2
- mlrun/runtimes/mounts.py +20 -2
- mlrun/runtimes/nuclio/__init__.py +12 -7
- mlrun/runtimes/nuclio/api_gateway.py +36 -6
- mlrun/runtimes/nuclio/application/application.py +339 -40
- mlrun/runtimes/nuclio/function.py +222 -72
- mlrun/runtimes/nuclio/serving.py +132 -42
- mlrun/runtimes/pod.py +213 -21
- mlrun/runtimes/utils.py +49 -9
- mlrun/secrets.py +99 -14
- mlrun/serving/__init__.py +2 -0
- mlrun/serving/remote.py +84 -11
- mlrun/serving/routers.py +26 -44
- mlrun/serving/server.py +138 -51
- mlrun/serving/serving_wrapper.py +6 -2
- mlrun/serving/states.py +997 -283
- mlrun/serving/steps.py +62 -0
- mlrun/serving/system_steps.py +149 -95
- mlrun/serving/v2_serving.py +9 -10
- mlrun/track/trackers/mlflow_tracker.py +29 -31
- mlrun/utils/helpers.py +292 -94
- mlrun/utils/http.py +9 -2
- mlrun/utils/notifications/notification/base.py +18 -0
- mlrun/utils/notifications/notification/git.py +3 -5
- mlrun/utils/notifications/notification/mail.py +39 -16
- mlrun/utils/notifications/notification/slack.py +2 -4
- mlrun/utils/notifications/notification/webhook.py +2 -5
- mlrun/utils/notifications/notification_pusher.py +3 -3
- mlrun/utils/version/version.json +2 -2
- mlrun/utils/version/version.py +3 -4
- {mlrun-1.10.0rc18.dist-info → mlrun-1.11.0rc16.dist-info}/METADATA +63 -74
- {mlrun-1.10.0rc18.dist-info → mlrun-1.11.0rc16.dist-info}/RECORD +161 -143
- mlrun/api/schemas/__init__.py +0 -259
- mlrun/db/auth_utils.py +0 -152
- mlrun/model_monitoring/db/tsdb/tdengine/schemas.py +0 -344
- mlrun/model_monitoring/db/tsdb/tdengine/stream_graph_steps.py +0 -75
- mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connection.py +0 -281
- mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +0 -1266
- {mlrun-1.10.0rc18.dist-info → mlrun-1.11.0rc16.dist-info}/WHEEL +0 -0
- {mlrun-1.10.0rc18.dist-info → mlrun-1.11.0rc16.dist-info}/entry_points.txt +0 -0
- {mlrun-1.10.0rc18.dist-info → mlrun-1.11.0rc16.dist-info}/licenses/LICENSE +0 -0
- {mlrun-1.10.0rc18.dist-info → mlrun-1.11.0rc16.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
# Copyright 2025 Iguazio
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from datetime import datetime, timedelta
|
|
16
|
+
from typing import Optional, Union
|
|
17
|
+
|
|
18
|
+
import pandas as pd
|
|
19
|
+
import v3io_frames.client
|
|
20
|
+
|
|
21
|
+
import mlrun
|
|
22
|
+
import mlrun.common.schemas.model_monitoring as mm_schemas
|
|
23
|
+
import mlrun.errors
|
|
24
|
+
import mlrun.model_monitoring.db.tsdb.timescaledb.timescaledb_schema as timescaledb_schema
|
|
25
|
+
import mlrun.utils
|
|
26
|
+
from mlrun.common.schemas.model_monitoring.model_endpoints import _MetricPoint
|
|
27
|
+
from mlrun.model_monitoring.db.tsdb.timescaledb.utils.timescaledb_dataframe_processor import (
|
|
28
|
+
TimescaleDBDataFrameProcessor,
|
|
29
|
+
)
|
|
30
|
+
from mlrun.model_monitoring.db.tsdb.timescaledb.utils.timescaledb_query_builder import (
|
|
31
|
+
TimescaleDBQueryBuilder,
|
|
32
|
+
)
|
|
33
|
+
from mlrun.model_monitoring.helpers import get_invocations_fqn
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class TimescaleDBPredictionsQueries:
|
|
37
|
+
"""
|
|
38
|
+
Query class containing predictions-related query methods for TimescaleDB.
|
|
39
|
+
|
|
40
|
+
Can be used as a mixin or standalone instance with proper initialization.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
project: Optional[str] = None,
|
|
46
|
+
connection=None,
|
|
47
|
+
pre_aggregate_manager=None,
|
|
48
|
+
tables: Optional[dict] = None,
|
|
49
|
+
):
|
|
50
|
+
"""
|
|
51
|
+
Initialize TimescaleDB predictions query handler.
|
|
52
|
+
|
|
53
|
+
:param project: Project name
|
|
54
|
+
:param connection: TimescaleDB connection instance
|
|
55
|
+
:param pre_aggregate_manager: PreAggregateManager instance
|
|
56
|
+
:param tables: Dictionary of table schemas
|
|
57
|
+
"""
|
|
58
|
+
self.project = project
|
|
59
|
+
self._connection = connection
|
|
60
|
+
self._pre_aggregate_manager = pre_aggregate_manager
|
|
61
|
+
self.tables = tables
|
|
62
|
+
|
|
63
|
+
def read_predictions_impl(
|
|
64
|
+
self,
|
|
65
|
+
*,
|
|
66
|
+
endpoint_id: Optional[str] = None,
|
|
67
|
+
start: datetime,
|
|
68
|
+
end: datetime,
|
|
69
|
+
columns: Optional[list[str]] = None,
|
|
70
|
+
aggregation_window: Optional[str] = None,
|
|
71
|
+
agg_funcs: Optional[list[str]] = None,
|
|
72
|
+
limit: Optional[int] = None,
|
|
73
|
+
use_pre_aggregates: bool = True,
|
|
74
|
+
timestamp_column: Optional[str] = None,
|
|
75
|
+
) -> pd.DataFrame:
|
|
76
|
+
"""Read predictions data from TimescaleDB (predictions table) - returns DataFrame.
|
|
77
|
+
|
|
78
|
+
:param endpoint_id: Endpoint ID to filter by, or None to get all endpoints
|
|
79
|
+
:param start: Start time
|
|
80
|
+
:param end: End time
|
|
81
|
+
:param columns: Optional list of specific columns to return
|
|
82
|
+
:param aggregation_window: Optional aggregation window (e.g., "1h", "1d")
|
|
83
|
+
:param agg_funcs: Optional list of aggregation functions (e.g., ["avg", "max"])
|
|
84
|
+
:param limit: Optional limit on number of results
|
|
85
|
+
:param use_pre_aggregates: Whether to use pre-aggregates if available
|
|
86
|
+
:param timestamp_column: Optional timestamp column to use for time filtering
|
|
87
|
+
:return: DataFrame with predictions data
|
|
88
|
+
"""
|
|
89
|
+
if (agg_funcs and not aggregation_window) or (
|
|
90
|
+
aggregation_window and not agg_funcs
|
|
91
|
+
):
|
|
92
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
93
|
+
"both or neither of `aggregation_window` and `agg_funcs` must be provided"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# Align times if aggregation window is provided
|
|
97
|
+
start, end = self._pre_aggregate_manager.align_time_range(
|
|
98
|
+
start, end, aggregation_window
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# Check if we can use pre-aggregates
|
|
102
|
+
can_use_pre_aggregates = (
|
|
103
|
+
use_pre_aggregates
|
|
104
|
+
and self._pre_aggregate_manager.can_use_pre_aggregates(
|
|
105
|
+
interval=aggregation_window, agg_funcs=agg_funcs
|
|
106
|
+
)
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
table_schema = self.tables[mm_schemas.TimescaleDBTables.PREDICTIONS]
|
|
110
|
+
filter_query = TimescaleDBQueryBuilder.build_endpoint_filter(endpoint_id)
|
|
111
|
+
|
|
112
|
+
query = table_schema._get_records_query(
|
|
113
|
+
start=start,
|
|
114
|
+
end=end,
|
|
115
|
+
columns_to_filter=columns,
|
|
116
|
+
filter_query=filter_query,
|
|
117
|
+
interval=aggregation_window if can_use_pre_aggregates else None,
|
|
118
|
+
agg_funcs=agg_funcs if can_use_pre_aggregates else None,
|
|
119
|
+
limit=limit,
|
|
120
|
+
use_pre_aggregates=can_use_pre_aggregates,
|
|
121
|
+
timestamp_column=timestamp_column,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
result = self._connection.run(query=query)
|
|
125
|
+
df = TimescaleDBDataFrameProcessor.from_query_result(result)
|
|
126
|
+
|
|
127
|
+
if not df.empty:
|
|
128
|
+
# Set up time index based on whether we used aggregation
|
|
129
|
+
if aggregation_window and can_use_pre_aggregates:
|
|
130
|
+
time_col = timescaledb_schema.TIME_BUCKET_COLUMN
|
|
131
|
+
else:
|
|
132
|
+
time_col = table_schema.time_column
|
|
133
|
+
|
|
134
|
+
if time_col in df.columns:
|
|
135
|
+
df[time_col] = pd.to_datetime(df[time_col])
|
|
136
|
+
df.set_index(time_col, inplace=True)
|
|
137
|
+
|
|
138
|
+
return df
|
|
139
|
+
|
|
140
|
+
def read_predictions(
|
|
141
|
+
self,
|
|
142
|
+
*,
|
|
143
|
+
endpoint_id: str,
|
|
144
|
+
start: datetime,
|
|
145
|
+
end: datetime,
|
|
146
|
+
aggregation_window: Optional[str] = None,
|
|
147
|
+
agg_funcs: Optional[list[str]] = None,
|
|
148
|
+
limit: Optional[int] = None,
|
|
149
|
+
use_pre_aggregates: bool = True,
|
|
150
|
+
) -> Union[
|
|
151
|
+
mm_schemas.ModelEndpointMonitoringMetricValues,
|
|
152
|
+
mm_schemas.ModelEndpointMonitoringMetricNoData,
|
|
153
|
+
]:
|
|
154
|
+
"""Read predictions with optional pre-aggregate optimization."""
|
|
155
|
+
|
|
156
|
+
table_schema = self.tables[mm_schemas.TimescaleDBTables.PREDICTIONS]
|
|
157
|
+
columns = [
|
|
158
|
+
table_schema.time_column,
|
|
159
|
+
mm_schemas.EventFieldType.ESTIMATED_PREDICTION_COUNT,
|
|
160
|
+
]
|
|
161
|
+
|
|
162
|
+
# Get raw DataFrame from read_predictions_impl
|
|
163
|
+
df = self.read_predictions_impl(
|
|
164
|
+
endpoint_id=endpoint_id,
|
|
165
|
+
start=start,
|
|
166
|
+
end=end,
|
|
167
|
+
columns=columns,
|
|
168
|
+
aggregation_window=aggregation_window,
|
|
169
|
+
agg_funcs=agg_funcs,
|
|
170
|
+
limit=limit,
|
|
171
|
+
use_pre_aggregates=use_pre_aggregates,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
# Convert to domain objects
|
|
175
|
+
full_name = get_invocations_fqn(self.project)
|
|
176
|
+
|
|
177
|
+
if df.empty:
|
|
178
|
+
return TimescaleDBDataFrameProcessor.handle_empty_dataframe(full_name)
|
|
179
|
+
|
|
180
|
+
# Determine value column name based on whether aggregation was used
|
|
181
|
+
can_use_pre_aggregates = (
|
|
182
|
+
use_pre_aggregates
|
|
183
|
+
and aggregation_window
|
|
184
|
+
and agg_funcs
|
|
185
|
+
and self._pre_aggregate_manager.can_use_pre_aggregates(
|
|
186
|
+
interval=aggregation_window, agg_funcs=agg_funcs
|
|
187
|
+
)
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
if agg_funcs and can_use_pre_aggregates:
|
|
191
|
+
value_col = (
|
|
192
|
+
f"{agg_funcs[0]}_{mm_schemas.EventFieldType.ESTIMATED_PREDICTION_COUNT}"
|
|
193
|
+
)
|
|
194
|
+
else:
|
|
195
|
+
value_col = mm_schemas.EventFieldType.ESTIMATED_PREDICTION_COUNT
|
|
196
|
+
|
|
197
|
+
return mm_schemas.ModelEndpointMonitoringMetricValues(
|
|
198
|
+
full_name=full_name,
|
|
199
|
+
values=[
|
|
200
|
+
_MetricPoint(timestamp=timestamp, value=value)
|
|
201
|
+
for timestamp, value in zip(df.index, df[value_col])
|
|
202
|
+
],
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
def get_last_request(
|
|
206
|
+
self,
|
|
207
|
+
endpoint_ids: Union[str, list[str]],
|
|
208
|
+
start: Optional[datetime] = None,
|
|
209
|
+
end: Optional[datetime] = None,
|
|
210
|
+
interval: Optional[str] = None,
|
|
211
|
+
) -> pd.DataFrame:
|
|
212
|
+
"""Get last request timestamp with optional pre-aggregate optimization."""
|
|
213
|
+
|
|
214
|
+
# Prepare time range and interval (no auto-determination since interval may be None)
|
|
215
|
+
start, end, interval = TimescaleDBQueryBuilder.prepare_time_range_and_interval(
|
|
216
|
+
self._pre_aggregate_manager,
|
|
217
|
+
start,
|
|
218
|
+
end,
|
|
219
|
+
interval,
|
|
220
|
+
auto_determine_interval=False,
|
|
221
|
+
)
|
|
222
|
+
use_pre_aggregates = self._pre_aggregate_manager.can_use_pre_aggregates(
|
|
223
|
+
interval=interval
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
table_schema = self.tables[mm_schemas.TimescaleDBTables.PREDICTIONS]
|
|
227
|
+
filter_query = TimescaleDBQueryBuilder.build_endpoint_filter(endpoint_ids)
|
|
228
|
+
|
|
229
|
+
if use_pre_aggregates:
|
|
230
|
+
# Calculate latest (MAX) timestamp and corresponding latency per endpoint
|
|
231
|
+
# Use subquery to get time-bucketed data, then MAX over those results
|
|
232
|
+
subquery = table_schema._get_records_query(
|
|
233
|
+
start=start,
|
|
234
|
+
end=end,
|
|
235
|
+
columns_to_filter=[
|
|
236
|
+
timescaledb_schema.TIME_BUCKET_COLUMN,
|
|
237
|
+
f"max_{table_schema.time_column}",
|
|
238
|
+
f"max_{mm_schemas.EventFieldType.LATENCY}",
|
|
239
|
+
mm_schemas.WriterEvent.ENDPOINT_ID,
|
|
240
|
+
],
|
|
241
|
+
filter_query=filter_query,
|
|
242
|
+
agg_funcs=["max"],
|
|
243
|
+
interval=interval,
|
|
244
|
+
use_pre_aggregates=True,
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
# Use helper to build endpoint aggregation query
|
|
248
|
+
query = TimescaleDBQueryBuilder.build_endpoint_aggregation_query(
|
|
249
|
+
subquery=subquery,
|
|
250
|
+
aggregation_columns={
|
|
251
|
+
mm_schemas.EventFieldType.LAST_REQUEST: f"MAX(max_{table_schema.time_column})",
|
|
252
|
+
"last_latency": f"MAX(max_{mm_schemas.EventFieldType.LATENCY})",
|
|
253
|
+
},
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
result = self._connection.run(query=query)
|
|
257
|
+
df = TimescaleDBDataFrameProcessor.from_query_result(result)
|
|
258
|
+
else:
|
|
259
|
+
# Use PostgreSQL DISTINCT ON for raw data - most efficient approach
|
|
260
|
+
query = f"""
|
|
261
|
+
SELECT DISTINCT ON ({mm_schemas.WriterEvent.ENDPOINT_ID})
|
|
262
|
+
{mm_schemas.WriterEvent.ENDPOINT_ID} AS endpoint_id,
|
|
263
|
+
{table_schema.time_column} AS {mm_schemas.EventFieldType.LAST_REQUEST},
|
|
264
|
+
{mm_schemas.EventFieldType.LATENCY} AS last_latency
|
|
265
|
+
FROM {table_schema.full_name()}
|
|
266
|
+
WHERE {filter_query}
|
|
267
|
+
AND {table_schema.time_column} >= '{start}'
|
|
268
|
+
AND {table_schema.time_column} <= '{end}'
|
|
269
|
+
ORDER BY {mm_schemas.WriterEvent.ENDPOINT_ID}, {table_schema.time_column} DESC;
|
|
270
|
+
"""
|
|
271
|
+
|
|
272
|
+
result = self._connection.run(query=query)
|
|
273
|
+
df = TimescaleDBDataFrameProcessor.from_query_result(result)
|
|
274
|
+
|
|
275
|
+
# Convert timestamp to proper format (common for both paths)
|
|
276
|
+
if not df.empty and mm_schemas.EventFieldType.LAST_REQUEST in df.columns:
|
|
277
|
+
df[mm_schemas.EventFieldType.LAST_REQUEST] = pd.to_datetime(
|
|
278
|
+
df[mm_schemas.EventFieldType.LAST_REQUEST], errors="coerce", utc=True
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
return df
|
|
282
|
+
|
|
283
|
+
def get_avg_latency(
|
|
284
|
+
self,
|
|
285
|
+
endpoint_ids: Union[str, list[str]],
|
|
286
|
+
start: Optional[datetime] = None,
|
|
287
|
+
end: Optional[datetime] = None,
|
|
288
|
+
get_raw: bool = False,
|
|
289
|
+
) -> Union[pd.DataFrame, list[v3io_frames.client.RawFrame]]:
|
|
290
|
+
"""Get average latency with automatic pre-aggregate optimization, returning single value per endpoint."""
|
|
291
|
+
|
|
292
|
+
# Convert single endpoint to list for consistent handling
|
|
293
|
+
if isinstance(endpoint_ids, str):
|
|
294
|
+
endpoint_ids = [endpoint_ids]
|
|
295
|
+
|
|
296
|
+
# Set default start time and get end time
|
|
297
|
+
start = start or (mlrun.utils.datetime_now() - timedelta(hours=24))
|
|
298
|
+
# Prepare time range with auto-determined interval
|
|
299
|
+
start, end, interval = TimescaleDBQueryBuilder.prepare_time_range_and_interval(
|
|
300
|
+
self._pre_aggregate_manager, start, end
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
table_schema = self.tables[mm_schemas.TimescaleDBTables.PREDICTIONS]
|
|
304
|
+
filter_query = TimescaleDBQueryBuilder.build_endpoint_filter(endpoint_ids)
|
|
305
|
+
|
|
306
|
+
def build_pre_agg_query():
|
|
307
|
+
# Calculate overall average in SQL across all time buckets
|
|
308
|
+
# Use subquery to get time-bucketed data, then AVG over those results
|
|
309
|
+
subquery = table_schema._get_records_query(
|
|
310
|
+
start=start,
|
|
311
|
+
end=end,
|
|
312
|
+
columns_to_filter=[
|
|
313
|
+
timescaledb_schema.TIME_BUCKET_COLUMN,
|
|
314
|
+
mm_schemas.ModelEndpointSchema.AVG_LATENCY,
|
|
315
|
+
mm_schemas.WriterEvent.ENDPOINT_ID,
|
|
316
|
+
],
|
|
317
|
+
filter_query=filter_query,
|
|
318
|
+
agg_funcs=["avg"],
|
|
319
|
+
interval=interval,
|
|
320
|
+
use_pre_aggregates=True,
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
# Use helper to build endpoint aggregation query
|
|
324
|
+
return TimescaleDBQueryBuilder.build_endpoint_aggregation_query(
|
|
325
|
+
subquery=subquery,
|
|
326
|
+
aggregation_columns={
|
|
327
|
+
mm_schemas.ModelEndpointSchema.AVG_LATENCY: f"AVG({mm_schemas.ModelEndpointSchema.AVG_LATENCY})"
|
|
328
|
+
},
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
def build_raw_query():
|
|
332
|
+
# Single aggregated value across entire time range
|
|
333
|
+
columns = [
|
|
334
|
+
f"{mm_schemas.WriterEvent.ENDPOINT_ID} AS {mm_schemas.WriterEvent.ENDPOINT_ID}",
|
|
335
|
+
f"AVG({mm_schemas.EventFieldType.LATENCY}) AS {mm_schemas.ModelEndpointSchema.AVG_LATENCY}",
|
|
336
|
+
]
|
|
337
|
+
group_by_columns = [mm_schemas.WriterEvent.ENDPOINT_ID]
|
|
338
|
+
|
|
339
|
+
# Add additional filter to exclude invalid latency values
|
|
340
|
+
latency_col = mm_schemas.EventFieldType.LATENCY
|
|
341
|
+
latency_filter = f"{latency_col} IS NOT NULL AND {latency_col} > 0"
|
|
342
|
+
enhanced_filter_query = (
|
|
343
|
+
f"{filter_query} AND {latency_filter}"
|
|
344
|
+
if filter_query
|
|
345
|
+
else latency_filter
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
return table_schema._get_records_query(
|
|
349
|
+
start=start,
|
|
350
|
+
end=end,
|
|
351
|
+
columns_to_filter=columns,
|
|
352
|
+
filter_query=enhanced_filter_query,
|
|
353
|
+
group_by=group_by_columns,
|
|
354
|
+
order_by=mm_schemas.WriterEvent.ENDPOINT_ID,
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
# Column mapping rules for results (both pre-agg and raw return same structure now)
|
|
358
|
+
column_mapping_rules = {
|
|
359
|
+
mm_schemas.ModelEndpointSchema.AVG_LATENCY: [
|
|
360
|
+
mm_schemas.ModelEndpointSchema.AVG_LATENCY,
|
|
361
|
+
"average_latency",
|
|
362
|
+
mm_schemas.EventFieldType.LATENCY,
|
|
363
|
+
],
|
|
364
|
+
mm_schemas.WriterEvent.ENDPOINT_ID: [mm_schemas.WriterEvent.ENDPOINT_ID],
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
# Both queries now return single value per endpoint, no post-processing needed
|
|
368
|
+
return self._connection.execute_with_fallback(
|
|
369
|
+
self._pre_aggregate_manager,
|
|
370
|
+
build_pre_agg_query,
|
|
371
|
+
build_raw_query,
|
|
372
|
+
interval=interval,
|
|
373
|
+
agg_funcs=["avg"],
|
|
374
|
+
column_mapping_rules=column_mapping_rules,
|
|
375
|
+
debug_name="avg_latency",
|
|
376
|
+
)
|