diff --git a/docs/detectors/random_detector.md b/docs/detectors/random_detector.md index 5bf112d1..a2d41ce1 100644 --- a/docs/detectors/random_detector.md +++ b/docs/detectors/random_detector.md @@ -1,38 +1,62 @@ # Random Detector -The Random Detector produces randomized alerts for incoming parsed logs. It is useful for testing pipelines, alert routing, and downstream consumers without needing a real detection model. +The Random Detector inspects incoming ParserSchema instances and, according to its configuration, emits alerts with synthetic content. It can be configured to sample specific log variables, set thresholds or control alert frequency. Use it for integration testing, load testing, or as a simple example of a detector implementation. + +## In/out + +Input and output schemas in the pipeline | | Schema | Description | |------------|------------------------|--------------------| | **Input** | [ParserSchema](../schemas.md) | Structured log | | **Output** | [DetectorSchema](../schemas.md) | Generated alerts | -## Description +## Configuration arguments + +Arguments used in the initalization of the component. + + +| Field | Type | Default Value| Description| +|-------|------|-----|---| +|method_type|string|random_detector|Indicates what type of method is.| +|auto_config|boolean|True|Runs the configuration step before the training process.| +|start_id|integer|10|Number use to start the unique ID generator.| +|data_use_training|integer, null|None|Data use for training, if None, training is not done.| +|data_use_configure|integer, null|None|Data use for configuration, if None, configuration is not done.| +|use_config_data_as_training|boolean|True|Combine the configure data in the training process if True.| +|parser|string|PARSER|Name of the parser used.| +|events|object|{}|Events configuration dict keyed by event_id.| +|global_instances|object|{}|Configuration for a specific instance within an event.| + -The detector inspects incoming ParserSchema instances and, according to its configuration, emits alerts with synthetic content. It can be configured to sample specific log variables, set thresholds or control alert frequency. Use it for integration testing, load testing, or as a simple example of a detector implementation. +## Examples -## Configuration example +Examples to use the component in the DetectMate environment. +## Service usage + +To use it in [DetectMateService](https://github.com/ait-detectmate/DetectMateService), you can use the example bellow. + + ```yaml - RandomDetector: +detectors: + : method_type: random_detector - auto_config: False - params: {} - events: - 1: - test: - params: {} - variables: - - pos: 0 - name: var1 - params: - threshold: 0. - header_variables: - - pos: level - params: {} + auto_config: true + params: + start_id: 10 + data_use_training: null + data_use_configure: null + use_config_data_as_training: true + parser: PARSER + global_instances: {} + events: {} ``` + + +## Library usage -## Example usage +To use it as a python script, you can follow the example bellow. ```python --8<-- "docs/examples/detectors/random_detector.py:example" diff --git a/docs/examples/config/update.py b/docs/examples/config/update.py new file mode 100644 index 00000000..96a2687a --- /dev/null +++ b/docs/examples/config/update.py @@ -0,0 +1,58 @@ +from detectmatelibrary.detectors.random_detector import RandomDetectorConfig + +from detectmatelibrary.common.core import CoreConfig + +import yaml + + +# %% Methods +def append_docs(docs: str, start_cmd: str, end_cmd: str, add: str) -> None: + start_idx = docs.index(start_cmd) + end_idx = docs.index(end_cmd) + if start_idx > end_idx: + raise Exception(f"'{start_cmd}' should be before '{end_cmd}'") + + return docs[:start_idx + 1] + [add] + docs[end_idx:] + + +def get_arguments(config: CoreConfig) -> str: + arguments = "| Field | Type | Default Value| Description|\n|-------|------|-----|---|\n" + for arg in config.get_docs(): + arguments += f"|{arg['Name']}|{arg["Type"]}|{arg["Default value"]}|{arg["Description"]}|\n" + return arguments + + +def config_yaml(config: CoreConfig) -> str: + pretty_yaml = yaml.dump( + config.to_dict(""), indent=4, default_flow_style=False, sort_keys=False + ) + return "```yaml\n" + pretty_yaml + "```\n" + + +def update_docs(config: CoreConfig, doc_path: str) -> None: + try: + with open(doc_path, "r") as f: + docs = f.readlines() + + docs = append_docs( + docs=docs, + start_cmd="\n", + end_cmd="\n", + add=get_arguments(config) + ) + + docs = append_docs( + docs=docs, + start_cmd="\n", + end_cmd="\n", + add=config_yaml(config) + ) + + with open(doc_path, "w") as f: + f.writelines(docs) + except Exception as e: + raise Exception(f"While updating {doc_path} -> {str(e)}") + + +# %% Documentation update +update_docs(RandomDetectorConfig(), doc_path="docs/detectors/random_detector.md") diff --git a/src/detectmatelibrary/common/_config/__init__.py b/src/detectmatelibrary/common/_config/__init__.py index 12b86205..6162d237 100644 --- a/src/detectmatelibrary/common/_config/__init__.py +++ b/src/detectmatelibrary/common/_config/__init__.py @@ -3,7 +3,7 @@ __all__ = ["ConfigMethods", "generate_detector_config", "EventsConfig", "BasicConfig"] -from pydantic import BaseModel, ConfigDict +from pydantic import BaseModel, ConfigDict, Field from typing_extensions import Self from typing import Any, Dict @@ -23,10 +23,35 @@ class BasicConfig(BaseModel): model_config = ConfigDict(extra="forbid") - method_type: str = "default_method_type" - component_type: str = "default_type" - - auto_config: bool = False + method_type: str = Field( + default="default_method_type", + description="Indicates what type of method is." + ) + component_type: str = Field( + default="default_type", + description="Component type that the class inherent from." + ) + + auto_config: bool = Field( + default=False, + description="Runs the configuration step before the training process." + ) + + def get_docs(self) -> list[dict[str, str]]: + docs = [] + for field_na, field_info in self.model_json_schema().get("properties", {}).items(): + desc = field_info.get("description", "No description provided.") + if "<$IGNORE$>" in desc: + continue + type_ = field_info.get("type", "unknown") + if 'anyOf' in field_info: + types = [item['type'] for item in field_info['anyOf'] if 'type' in item] + type_ = ", ".join(types) + + docs.append({ + "Name": field_na, "Type": type_, "Default value": getattr(self, field_na), "Description": desc + }) + return docs def get_config(self) -> Dict[str, Any]: """Return the configuration as a dictionary.""" diff --git a/src/detectmatelibrary/common/core.py b/src/detectmatelibrary/common/core.py index 75a3c645..4752ed38 100644 --- a/src/detectmatelibrary/common/core.py +++ b/src/detectmatelibrary/common/core.py @@ -15,6 +15,7 @@ from typing import Any +from pydantic import Field from detectmatelibrary.utils.persistency.component_interfaces import PersistencyOp @@ -47,10 +48,22 @@ def __iter__(self) -> "TrainBuffer": # Core component ################################################ class CoreConfig(BasicConfig): - start_id: int = 10 - data_use_training: int | None = None - data_use_configure: int | None = None - use_config_data_as_training: bool = True + start_id: int = Field( + default=10, + description="Number use to start the unique ID generator." + ) + data_use_training: int | None = Field( + default=None, + description="Data use for training, if None, training is not done." + ) + data_use_configure: int | None = Field( + default=None, + description="Data use for configuration, if None, configuration is not done.", + ) + use_config_data_as_training: bool = Field( + default=True, + description="Combine the configure data in the training process if True." + ) class CoreComponent(Component, FedOperations): diff --git a/src/detectmatelibrary/common/detector.py b/src/detectmatelibrary/common/detector.py index 0efc2c99..ba3fe862 100644 --- a/src/detectmatelibrary/common/detector.py +++ b/src/detectmatelibrary/common/detector.py @@ -10,6 +10,7 @@ from typing_extensions import override from typing import Dict, List, Optional, Any, cast +from pydantic import Field from detectmatelibrary.utils.persistency.component_interfaces import PersistConfig from detectmatelibrary.utils.time_format_handler import TimeFormatHandler @@ -36,14 +37,25 @@ def _extract_logIDs( class CoreDetectorConfig(CoreConfig): - component_type: str = "detectors" - method_type: str = "core_detector" - parser: str = "" - - auto_config: bool = True - events: EventsConfig | dict[str, Any] = {} - global_instances: Dict[str, _EventInstance] = {} - persist: PersistConfig | None = None + component_type: str = Field(default="detectors", description="<$IGNORE$>") + method_type: str = Field(default="core_detector", description="<$IGNORE$>") + parser: str = Field( + default="PARSER", description="Name of the parser used." + ) + + auto_config: bool = Field( + default=True, + description="Runs the configuration step before the training process." + ) + events: EventsConfig | dict[str, Any] = Field( + default={}, description=EventsConfig.__doc__, + ) + global_instances: Dict[str, _EventInstance] = Field( + default={}, description=_EventInstance.__doc__ + ) + persist: PersistConfig | None = Field( + default=None, description="<$IGNORE$>" + ) class CoreDetector(CoreComponent): diff --git a/src/detectmatelibrary/detectors/random_detector.py b/src/detectmatelibrary/detectors/random_detector.py index 6e2f2aa8..9f4fdaa0 100644 --- a/src/detectmatelibrary/detectors/random_detector.py +++ b/src/detectmatelibrary/detectors/random_detector.py @@ -1,4 +1,4 @@ -from detectmatelibrary.common._config._formats import EventsConfig, Variable +from detectmatelibrary.common._config._formats import Variable from detectmatelibrary.common.detector import CoreDetector, CoreDetectorConfig @@ -7,14 +7,17 @@ import detectmatelibrary.schemas as schemas from typing_extensions import override -from typing import List, Any +from typing import List + +from pydantic import Field import numpy as np class RandomDetectorConfig(CoreDetectorConfig): - method_type: str = "random_detector" - - events: EventsConfig | dict[str, Any] = {} + method_type: str = Field( + default="random_detector", + description="Indicates what type of method is." + ) class RandomDetector(CoreDetector): diff --git a/tests/test_common/test_config.py b/tests/test_common/test_config.py index d83c5960..09cbfc86 100644 --- a/tests/test_common/test_config.py +++ b/tests/test_common/test_config.py @@ -8,7 +8,7 @@ ) from detectmatelibrary.common._config._formats import EventsConfig, _EventConfig from detectmatelibrary.common._config import BasicConfig -from pydantic import ValidationError +from pydantic import ValidationError, Field from tests.test_data import TEST_CONFIG import pytest import yaml @@ -22,6 +22,57 @@ def load_test_config() -> dict: config_test = load_test_config() +class DummyConfigDoc(BasicConfig): + hello: str | None = Field(default="Hello", description="a way to salute people") + dont_show: str = Field(default="a", description="<$IGNORE$> dont show stuff") + auto_config: bool = Field( + default=True, + description="Runs the configuration step before the training process." + ) + + +class TestConfigDocs: + def test_get_configs(self): + docs = BasicConfig().get_docs() + + assert len(docs) == 3 + assert { + 'Name': 'method_type', + 'Type': 'string', + 'Default value': 'default_method_type', + 'Description': 'Indicates what type of method is.' + } in docs + assert { + 'Name': 'component_type', + 'Type': 'string', + 'Default value': 'default_type', + 'Description': 'Component type that the class inherent from.' + } in docs + assert { + 'Name': 'auto_config', + 'Type': 'boolean', + 'Default value': False, + 'Description': 'Runs the configuration step before the training process.' + } in docs + + def test_inherent_class_docs(self): + docs = DummyConfigDoc().get_docs() + + assert len(docs) == 4 + assert { + 'Name': 'auto_config', + 'Type': 'boolean', + 'Default value': True, + 'Description': 'Runs the configuration step before the training process.' + } in docs + assert { + 'Name': 'hello', + 'Type': 'string, null', + 'Default value': 'Hello', + 'Description': 'a way to salute people' + } in docs + + class TestConfigMethods: def test_get_method(self): config = ConfigMethods.get_method(