diff --git a/modules/src/.gitkeep b/modules/src/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/modules/src/count_events/count_events.py b/modules/src/count_events/count_events.py new file mode 100644 index 000000000..c2d6444e4 --- /dev/null +++ b/modules/src/count_events/count_events.py @@ -0,0 +1,39 @@ +# Copyright 2025 Iguazio +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from mlrun.model_monitoring.applications import ( + ModelMonitoringApplicationBase, ModelMonitoringApplicationMetric, +) +import mlrun.model_monitoring.applications.context as mm_context + + +class CountApp(ModelMonitoringApplicationBase): + def do_tracking( + self, monitoring_context: mm_context.MonitoringApplicationContext + ) -> ModelMonitoringApplicationMetric: + sample_df = monitoring_context.sample_df + monitoring_context.logger.debug("Sample data-frame", sample_df=sample_df) + count = len(sample_df) + monitoring_context.logger.info( + "Counted events for model endpoint window", + model_endpoint_name=monitoring_context.model_endpoint.metadata.name, + count=count, + start=monitoring_context.start_infer_time, + end=monitoring_context.end_infer_time, + ) + return ModelMonitoringApplicationMetric( + name="count", + value=count, + ) \ No newline at end of file diff --git a/modules/src/count_events/item.yaml b/modules/src/count_events/item.yaml new file mode 100644 index 000000000..a11d45fc7 --- /dev/null +++ b/modules/src/count_events/item.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +categories: +- model-serving +description: Count events in each time window +example: +generationDate: 2025-09-16:12-25 +hidden: false +labels: + author: iguazio +mlrunVersion: 1.10.0-rc27 +name: count_events +spec: + filename: count_events.py + image: mlrun/mlrun + kind: monitoring_application + requirements: +version: 1.0.0 \ No newline at end of file diff --git a/modules/src/count_events/requirements.txt b/modules/src/count_events/requirements.txt new file mode 100644 index 000000000..89741402a --- /dev/null +++ b/modules/src/count_events/requirements.txt @@ -0,0 +1,3 @@ +mlrun==1.10.0-rc27 +pandas==2.1.4 +pytest~=8.2 diff --git a/modules/src/count_events/test_count_events.py b/modules/src/count_events/test_count_events.py new file mode 100644 index 000000000..66a94c932 --- /dev/null +++ b/modules/src/count_events/test_count_events.py @@ -0,0 +1,75 @@ +# Copyright 2025 Iguazio +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + +from mlrun.model_monitoring.applications import ModelMonitoringApplicationMetric +import mlrun.model_monitoring.applications.context as mm_context + +from count_events import CountApp + +from unittest.mock import Mock +from datetime import datetime +import pandas as pd +import pytest + +class TestCountApp: + """Test suite for CountApp class.""" + + def setup_method(self): + """Set up test fixtures before each test method.""" + self.count_app = CountApp() + @staticmethod + def _create_mock_monitoring_context(sample_df, model_endpoint_name="test-model"): + """Helper method to create a mock monitoring context.""" + mock_context = Mock(spec=mm_context.MonitoringApplicationContext) + + # Mock the sample dataframe + mock_context.sample_df = sample_df + + # Mock the logger + mock_logger = Mock() + mock_context.logger = mock_logger + + # Mock the model endpoint + mock_model_endpoint = Mock() + mock_model_endpoint.metadata.name = model_endpoint_name + mock_context.model_endpoint = mock_model_endpoint + + # Mock time attributes + mock_context.start_infer_time = datetime(2025, 1, 1, 10, 0, 0) + mock_context.end_infer_time = datetime(2025, 1, 1, 11, 0, 0) + + return mock_context + + + @pytest.mark.parametrize("df_size", [0, 1, 10, 100, 1000]) + def test_do_tracking_with_various_dataframe_sizes(self, df_size): + """Test do_tracking with various dataframe sizes using parametrized test.""" + # Arrange + if df_size == 0: + test_df = pd.DataFrame() + else: + test_df = pd.DataFrame({"col1": range(df_size)}) + + mock_context = self._create_mock_monitoring_context(test_df) + + # Act + result = self.count_app.do_tracking(mock_context) + + # Assert + assert isinstance(result, ModelMonitoringApplicationMetric) + assert result.value == df_size + assert result.name == "count" +