detectmatelibrary 0.3.1__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.
- detectmatelibrary/__init__.py +24 -0
- detectmatelibrary/common/__init__.py +0 -0
- detectmatelibrary/common/_config/__init__.py +119 -0
- detectmatelibrary/common/_config/_compile.py +212 -0
- detectmatelibrary/common/_config/_formats.py +147 -0
- detectmatelibrary/common/_core_op/_fit_logic.py +153 -0
- detectmatelibrary/common/_core_op/_schema_pipeline.py +28 -0
- detectmatelibrary/common/core.py +171 -0
- detectmatelibrary/common/detector.py +224 -0
- detectmatelibrary/common/parser.py +84 -0
- detectmatelibrary/common/persist.py +32 -0
- detectmatelibrary/constants.py +7 -0
- detectmatelibrary/detectors/__init__.py +19 -0
- detectmatelibrary/detectors/bigram_frequency_detector.py +285 -0
- detectmatelibrary/detectors/charset_detector.py +158 -0
- detectmatelibrary/detectors/new_event_detector.py +96 -0
- detectmatelibrary/detectors/new_value_combo_detector.py +237 -0
- detectmatelibrary/detectors/new_value_detector.py +150 -0
- detectmatelibrary/detectors/random_detector.py +65 -0
- detectmatelibrary/detectors/rule_detector.py +104 -0
- detectmatelibrary/detectors/value_range_detector.py +184 -0
- detectmatelibrary/helper/from_to.py +305 -0
- detectmatelibrary/metadata.py +12 -0
- detectmatelibrary/parsers/__init__.py +0 -0
- detectmatelibrary/parsers/drain.py +0 -0
- detectmatelibrary/parsers/json_parser.py +92 -0
- detectmatelibrary/parsers/logbatcher/__init__.py +29 -0
- detectmatelibrary/parsers/logbatcher/engine/__init__.py +5 -0
- detectmatelibrary/parsers/logbatcher/engine/additional_cluster.py +185 -0
- detectmatelibrary/parsers/logbatcher/engine/cluster.py +175 -0
- detectmatelibrary/parsers/logbatcher/engine/matching.py +110 -0
- detectmatelibrary/parsers/logbatcher/engine/parser.py +120 -0
- detectmatelibrary/parsers/logbatcher/engine/parsing_base.py +220 -0
- detectmatelibrary/parsers/logbatcher/engine/parsing_cache.py +416 -0
- detectmatelibrary/parsers/logbatcher/engine/postprocess.py +195 -0
- detectmatelibrary/parsers/logbatcher/engine/sample.py +140 -0
- detectmatelibrary/parsers/logbatcher/engine/util.py +169 -0
- detectmatelibrary/parsers/logbatcher/engine/vars.py +41 -0
- detectmatelibrary/parsers/logbatcher/parser.py +66 -0
- detectmatelibrary/parsers/template_matcher/__init__.py +6 -0
- detectmatelibrary/parsers/template_matcher/_matcher_op.py +241 -0
- detectmatelibrary/parsers/template_matcher/_parser.py +155 -0
- detectmatelibrary/parsers/tree_matcher.py +43 -0
- detectmatelibrary/schemas/__init__.py +23 -0
- detectmatelibrary/schemas/_classes.py +169 -0
- detectmatelibrary/schemas/_op.py +110 -0
- detectmatelibrary/schemas/schemas_pb2.py +45 -0
- detectmatelibrary/utils/aux.py +15 -0
- detectmatelibrary/utils/data_buffer.py +108 -0
- detectmatelibrary/utils/data_normalizer.py +31 -0
- detectmatelibrary/utils/id_generator.py +7 -0
- detectmatelibrary/utils/key_extractor.py +103 -0
- detectmatelibrary/utils/log_format_utils.py +56 -0
- detectmatelibrary/utils/persistency/__init__.py +17 -0
- detectmatelibrary/utils/persistency/event_data_structures/base.py +45 -0
- detectmatelibrary/utils/persistency/event_data_structures/dataframes/__init__.py +4 -0
- detectmatelibrary/utils/persistency/event_data_structures/dataframes/chunked_event_dataframe.py +119 -0
- detectmatelibrary/utils/persistency/event_data_structures/dataframes/event_dataframe.py +54 -0
- detectmatelibrary/utils/persistency/event_data_structures/trackers/__init__.py +32 -0
- detectmatelibrary/utils/persistency/event_data_structures/trackers/base/__init__.py +10 -0
- detectmatelibrary/utils/persistency/event_data_structures/trackers/base/event_tracker.py +118 -0
- detectmatelibrary/utils/persistency/event_data_structures/trackers/base/multi_tracker.py +36 -0
- detectmatelibrary/utils/persistency/event_data_structures/trackers/base/single_tracker.py +40 -0
- detectmatelibrary/utils/persistency/event_data_structures/trackers/stability/__init__.py +9 -0
- detectmatelibrary/utils/persistency/event_data_structures/trackers/stability/stability_classifier.py +90 -0
- detectmatelibrary/utils/persistency/event_data_structures/trackers/stability/stability_tracker.py +157 -0
- detectmatelibrary/utils/persistency/event_persistency.py +137 -0
- detectmatelibrary/utils/persistency/persistency_saver.py +215 -0
- detectmatelibrary/utils/persistency/rle_list.py +63 -0
- detectmatelibrary/utils/preview_helpers.py +19 -0
- detectmatelibrary/utils/time_format_handler.py +125 -0
- detectmatelibrary-0.3.1.dist-info/METADATA +145 -0
- detectmatelibrary-0.3.1.dist-info/RECORD +145 -0
- detectmatelibrary-0.3.1.dist-info/WHEEL +5 -0
- detectmatelibrary-0.3.1.dist-info/entry_points.txt +2 -0
- detectmatelibrary-0.3.1.dist-info/licenses/LICENSE.md +287 -0
- detectmatelibrary-0.3.1.dist-info/top_level.txt +3 -0
- detectmatelibrary_tests/__init__.py +0 -0
- detectmatelibrary_tests/test_common/__init__.py +0 -0
- detectmatelibrary_tests/test_common/test_config.py +192 -0
- detectmatelibrary_tests/test_common/test_config_roundtrip.py +379 -0
- detectmatelibrary_tests/test_common/test_core.py +524 -0
- detectmatelibrary_tests/test_common/test_core_detector.py +196 -0
- detectmatelibrary_tests/test_common/test_core_parser.py +165 -0
- detectmatelibrary_tests/test_common/test_extract_timestamp.py +52 -0
- detectmatelibrary_tests/test_common/test_fit_logic.py +62 -0
- detectmatelibrary_tests/test_common/test_persist_config.py +145 -0
- detectmatelibrary_tests/test_detectors/__init__.py +0 -0
- detectmatelibrary_tests/test_detectors/dummy_detector.py +43 -0
- detectmatelibrary_tests/test_detectors/test_bigram_frequency_detector.py +596 -0
- detectmatelibrary_tests/test_detectors/test_charset_detector.py +422 -0
- detectmatelibrary_tests/test_detectors/test_mismatch_warnings.py +218 -0
- detectmatelibrary_tests/test_detectors/test_new_event_detector.py +250 -0
- detectmatelibrary_tests/test_detectors/test_new_value_combo_detector.py +578 -0
- detectmatelibrary_tests/test_detectors/test_new_value_detector.py +312 -0
- detectmatelibrary_tests/test_detectors/test_persist_integration.py +132 -0
- detectmatelibrary_tests/test_detectors/test_random_detector.py +72 -0
- detectmatelibrary_tests/test_detectors/test_rule_detector.py +179 -0
- detectmatelibrary_tests/test_detectors/test_value_range_detector.py +427 -0
- detectmatelibrary_tests/test_folder/__init__.py +0 -0
- detectmatelibrary_tests/test_helper/__init__.py +0 -0
- detectmatelibrary_tests/test_helper/test_from_to.py +497 -0
- detectmatelibrary_tests/test_others/__init__.py +0 -0
- detectmatelibrary_tests/test_others/test_logging.py +84 -0
- detectmatelibrary_tests/test_parsers/__init__.py +0 -0
- detectmatelibrary_tests/test_parsers/dummy_parser.py +33 -0
- detectmatelibrary_tests/test_parsers/test_json_parser.py +211 -0
- detectmatelibrary_tests/test_parsers/test_logbatcher_parser.py +82 -0
- detectmatelibrary_tests/test_parsers/test_template_matcher.py +241 -0
- detectmatelibrary_tests/test_parsers/test_tree_macther.py +47 -0
- detectmatelibrary_tests/test_persistency/__init__.py +0 -0
- detectmatelibrary_tests/test_persistency/test_stability_tracker.py +132 -0
- detectmatelibrary_tests/test_pipelines/__init__.py +0 -0
- detectmatelibrary_tests/test_pipelines/test_bad_players.py +70 -0
- detectmatelibrary_tests/test_pipelines/test_basic_pipelines.py +117 -0
- detectmatelibrary_tests/test_pipelines/test_configuration_engine.py +88 -0
- detectmatelibrary_tests/test_pipelines/test_dummy_methods.py +76 -0
- detectmatelibrary_tests/test_schemas/__init__.py +0 -0
- detectmatelibrary_tests/test_schemas/test_default_values.py +61 -0
- detectmatelibrary_tests/test_schemas/test_ops.py +170 -0
- detectmatelibrary_tests/test_schemas/test_schema_class.py +165 -0
- detectmatelibrary_tests/test_utils/__init__.py +0 -0
- detectmatelibrary_tests/test_utils/test_aux.py +25 -0
- detectmatelibrary_tests/test_utils/test_data_buffer.py +83 -0
- detectmatelibrary_tests/test_utils/test_data_normalizer.py +9 -0
- detectmatelibrary_tests/test_utils/test_events_seen.py +66 -0
- detectmatelibrary_tests/test_utils/test_log_format_utils.py +45 -0
- detectmatelibrary_tests/test_utils/test_persistency.py +486 -0
- detectmatelibrary_tests/test_utils/test_persistency_dump_load.py +190 -0
- detectmatelibrary_tests/test_utils/test_persistency_saver.py +278 -0
- detectmatelibrary_tests/test_utils/test_stability_tracking.py +554 -0
- detectmatelibrary_tests/test_workspace/__init__.py +0 -0
- detectmatelibrary_tests/test_workspace/test_create_workspace.py +206 -0
- detectmatelibrary_tests/test_workspace/test_utils.py +21 -0
- detectmatelibrary_tools/__init__.py +0 -0
- detectmatelibrary_tools/logging.py +51 -0
- detectmatelibrary_tools/workspace/__init__.py +0 -0
- detectmatelibrary_tools/workspace/create_workspace.py +191 -0
- detectmatelibrary_tools/workspace/templates/CustomDetector.py +66 -0
- detectmatelibrary_tools/workspace/templates/CustomParser.py +55 -0
- detectmatelibrary_tools/workspace/templates/__init__.py +0 -0
- detectmatelibrary_tools/workspace/templates/test_templates/__init__.py +0 -0
- detectmatelibrary_tools/workspace/templates/test_templates/test_CustomDetector.py +37 -0
- detectmatelibrary_tools/workspace/templates/test_templates/test_CustomParser.py +32 -0
- detectmatelibrary_tools/workspace/utils.py +155 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from . import common
|
|
2
|
+
from . import detectors
|
|
3
|
+
from . import parsers
|
|
4
|
+
from . import schemas
|
|
5
|
+
from . import utils
|
|
6
|
+
from .metadata import (__authors__, __contact__, __copyright__, __date__, __deprecated__, __website__,
|
|
7
|
+
__license__, __status__, __version__)
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"common",
|
|
11
|
+
"detectors",
|
|
12
|
+
"parsers",
|
|
13
|
+
"schemas",
|
|
14
|
+
"utils",
|
|
15
|
+
"__authors__",
|
|
16
|
+
"__contact__",
|
|
17
|
+
"__copyright__",
|
|
18
|
+
"__date__",
|
|
19
|
+
"__deprecated__",
|
|
20
|
+
"__website__",
|
|
21
|
+
"__license__",
|
|
22
|
+
"__status__",
|
|
23
|
+
"__version__"
|
|
24
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
from ._compile import ConfigMethods, generate_detector_config
|
|
2
|
+
from ._formats import EventsConfig
|
|
3
|
+
|
|
4
|
+
__all__ = ["ConfigMethods", "generate_detector_config", "EventsConfig", "BasicConfig"]
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, ConfigDict
|
|
7
|
+
|
|
8
|
+
from typing_extensions import Self
|
|
9
|
+
from typing import Any, Dict
|
|
10
|
+
from copy import deepcopy
|
|
11
|
+
|
|
12
|
+
from numpy.random import choice
|
|
13
|
+
import string
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def random_id(length: int = 10) -> str:
|
|
17
|
+
characters = [s for s in string.ascii_letters + string.digits]
|
|
18
|
+
return "".join(str(choice(characters)) for _ in range(length))
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class BasicConfig(BaseModel):
|
|
22
|
+
"""Base configuration class with helper methods."""
|
|
23
|
+
|
|
24
|
+
model_config = ConfigDict(extra="forbid")
|
|
25
|
+
|
|
26
|
+
method_type: str = "default_method_type"
|
|
27
|
+
component_type: str = "default_type"
|
|
28
|
+
|
|
29
|
+
auto_config: bool = False
|
|
30
|
+
|
|
31
|
+
def get_config(self) -> Dict[str, Any]:
|
|
32
|
+
"""Return the configuration as a dictionary."""
|
|
33
|
+
return self.model_dump()
|
|
34
|
+
|
|
35
|
+
def update_config(self, new_config: Dict[str, Any]) -> None:
|
|
36
|
+
"""Update the configuration with new values."""
|
|
37
|
+
for key, value in new_config.items():
|
|
38
|
+
setattr(self, key, value)
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
def from_dict(cls, data: Dict[str, Any], method_id: str) -> Self:
|
|
42
|
+
aux = cls()
|
|
43
|
+
config_ = ConfigMethods.get_method(
|
|
44
|
+
deepcopy(data), component_type=aux.component_type, method_id=method_id
|
|
45
|
+
)
|
|
46
|
+
ConfigMethods.check_type(config_, method_type=aux.method_type)
|
|
47
|
+
|
|
48
|
+
return cls(**ConfigMethods.process(config_))
|
|
49
|
+
|
|
50
|
+
def to_dict(self, method_id: str = random_id()) -> Dict[str, Any]:
|
|
51
|
+
"""Convert the config back to YAML-compatible dictionary format.
|
|
52
|
+
|
|
53
|
+
This is the inverse of from_dict() and ensures yaml -> pydantic -> yaml preservation.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
method_id: The method identifier to use in the output structure
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
Dictionary with structure: {component_type: {method_id: config_data}}
|
|
60
|
+
"""
|
|
61
|
+
# Build the config in the format expected by from_dict
|
|
62
|
+
result: Dict[str, Any] = {
|
|
63
|
+
"method_type": self.method_type,
|
|
64
|
+
"auto_config": self.auto_config,
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
# Collect all non-meta fields for params
|
|
68
|
+
params = {}
|
|
69
|
+
events_data = None
|
|
70
|
+
instances_data = None
|
|
71
|
+
persist_data: dict[str, Any] | None = None
|
|
72
|
+
|
|
73
|
+
for field_name, field_value in self:
|
|
74
|
+
# Skip meta fields
|
|
75
|
+
if field_name in ("component_type", "method_type", "auto_config"):
|
|
76
|
+
continue
|
|
77
|
+
|
|
78
|
+
# Handle EventsConfig specially
|
|
79
|
+
if field_name == "events":
|
|
80
|
+
if field_value is not None:
|
|
81
|
+
if isinstance(field_value, EventsConfig):
|
|
82
|
+
events_data = field_value.to_dict()
|
|
83
|
+
else:
|
|
84
|
+
events_data = field_value
|
|
85
|
+
# Handle global instances specially (top-level, not in params)
|
|
86
|
+
# Serialized as "global" in YAML (Python field is "global_instances")
|
|
87
|
+
elif field_name == "global_instances" and field_value:
|
|
88
|
+
instances_data = {
|
|
89
|
+
name: inst.to_dict()
|
|
90
|
+
for name, inst in field_value.items()
|
|
91
|
+
}
|
|
92
|
+
elif field_name == "persist":
|
|
93
|
+
if field_value is not None:
|
|
94
|
+
persist_data = field_value.model_dump()
|
|
95
|
+
else:
|
|
96
|
+
# All other fields go into params
|
|
97
|
+
params[field_name] = field_value
|
|
98
|
+
|
|
99
|
+
# Add params if there are any
|
|
100
|
+
if params:
|
|
101
|
+
result["params"] = params
|
|
102
|
+
|
|
103
|
+
# Add global instances if they exist (serialized as "global" in YAML)
|
|
104
|
+
if instances_data is not None:
|
|
105
|
+
result["global"] = instances_data
|
|
106
|
+
|
|
107
|
+
# Add events if they exist
|
|
108
|
+
if events_data is not None:
|
|
109
|
+
result["events"] = events_data
|
|
110
|
+
|
|
111
|
+
if persist_data is not None:
|
|
112
|
+
result["persist"] = persist_data
|
|
113
|
+
|
|
114
|
+
# Wrap in the component_type and method_id structure
|
|
115
|
+
return {
|
|
116
|
+
self.component_type: {
|
|
117
|
+
method_id: result
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
from detectmatelibrary.common._config._formats import EventsConfig, _EventInstance
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, List, Sequence, Tuple, Union
|
|
4
|
+
import warnings
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _classify_variables(
|
|
9
|
+
var_names: Sequence[str], var_pattern: re.Pattern[str]
|
|
10
|
+
) -> Dict[str, Any]:
|
|
11
|
+
"""Classify variable names into positional and header variables.
|
|
12
|
+
|
|
13
|
+
Returns an instance config dict with 'params', 'variables', and
|
|
14
|
+
'header_variables' keys.
|
|
15
|
+
"""
|
|
16
|
+
positional_variables = []
|
|
17
|
+
header_variables = []
|
|
18
|
+
|
|
19
|
+
for var_name in var_names:
|
|
20
|
+
match = var_pattern.match(var_name)
|
|
21
|
+
if match:
|
|
22
|
+
position = int(match.group(1))
|
|
23
|
+
positional_variables.append({
|
|
24
|
+
"pos": position,
|
|
25
|
+
"name": var_name,
|
|
26
|
+
"params": {}
|
|
27
|
+
})
|
|
28
|
+
else:
|
|
29
|
+
header_variables.append({
|
|
30
|
+
"pos": var_name,
|
|
31
|
+
"params": {}
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
instance_config: Dict[str, Any] = {"params": {}}
|
|
35
|
+
if positional_variables:
|
|
36
|
+
instance_config["variables"] = positional_variables
|
|
37
|
+
if header_variables:
|
|
38
|
+
instance_config["header_variables"] = header_variables
|
|
39
|
+
|
|
40
|
+
return instance_config
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class MethodNotFoundError(Exception):
|
|
44
|
+
def __init__(self, method_id: str, component_type: str) -> None:
|
|
45
|
+
super().__init__(
|
|
46
|
+
f"Method '{method_id}' of type '{component_type}' not found in configuration."
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class TypeNotFoundError(Exception):
|
|
51
|
+
def __init__(self, type_name: str) -> None:
|
|
52
|
+
super().__init__(f"Type '{type_name}' not found in configuration.")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class MethodTypeNotMatch(Exception):
|
|
56
|
+
def __init__(self, expected_type: str, actual_type: str) -> None:
|
|
57
|
+
super().__init__(
|
|
58
|
+
f"Type mismatch: expected '{expected_type}', got '{actual_type}'."
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class MissingParamsWarning(UserWarning):
|
|
63
|
+
def __init__(self) -> None:
|
|
64
|
+
super().__init__(
|
|
65
|
+
"'auto_config = False' and no 'params', 'events', 'global', or 'persist' provided. "
|
|
66
|
+
"Is that intended?"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class AutoConfigWarning(UserWarning):
|
|
71
|
+
def __init__(self) -> None:
|
|
72
|
+
super().__init__("'auto_config = True' will overwrite 'events' and 'params'.")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class ConfigMethods:
|
|
76
|
+
@staticmethod
|
|
77
|
+
def get_method(
|
|
78
|
+
config: Dict[str, Dict[str, Dict[str, Any]]], method_id: str, component_type: str
|
|
79
|
+
) -> Dict[str, Any]:
|
|
80
|
+
|
|
81
|
+
if component_type not in config:
|
|
82
|
+
raise TypeNotFoundError(component_type)
|
|
83
|
+
|
|
84
|
+
args = config[component_type]
|
|
85
|
+
if method_id not in args:
|
|
86
|
+
raise MethodNotFoundError(method_id, component_type)
|
|
87
|
+
|
|
88
|
+
return args[method_id]
|
|
89
|
+
|
|
90
|
+
@staticmethod
|
|
91
|
+
def check_type(config: Dict[str, Any], method_type: str) -> None:
|
|
92
|
+
if config["method_type"] != method_type:
|
|
93
|
+
raise MethodTypeNotMatch(expected_type=method_type, actual_type=config["method_type"])
|
|
94
|
+
|
|
95
|
+
@staticmethod
|
|
96
|
+
def process(config: Dict[str, Any]) -> Dict[str, Any]:
|
|
97
|
+
has_params = "params" in config
|
|
98
|
+
has_events = "events" in config
|
|
99
|
+
has_instances = "global" in config
|
|
100
|
+
has_persist = "persist" in config
|
|
101
|
+
|
|
102
|
+
no_data = not has_params and not has_events and not has_instances and not has_persist
|
|
103
|
+
if no_data and not config.get("auto_config", False):
|
|
104
|
+
warnings.warn(MissingParamsWarning())
|
|
105
|
+
|
|
106
|
+
if has_params:
|
|
107
|
+
if config.get("auto_config", False):
|
|
108
|
+
warnings.warn(AutoConfigWarning())
|
|
109
|
+
|
|
110
|
+
config.update(config["params"])
|
|
111
|
+
config.pop("params")
|
|
112
|
+
|
|
113
|
+
# Handle "events" key at top level
|
|
114
|
+
if has_events:
|
|
115
|
+
config["events"] = EventsConfig._init(config["events"])
|
|
116
|
+
|
|
117
|
+
# Handle "global" key: event-ID-independent global instances
|
|
118
|
+
# Renamed to "global_instances" to avoid collision with Python keyword
|
|
119
|
+
if has_instances:
|
|
120
|
+
config["global_instances"] = {
|
|
121
|
+
name: _EventInstance._init(**data)
|
|
122
|
+
for name, data in config.pop("global").items()
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return config
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def generate_detector_config(
|
|
129
|
+
variable_selection: Dict[int | str, List[Union[str, Tuple[str, ...]]]],
|
|
130
|
+
detector_name: str,
|
|
131
|
+
method_type: str,
|
|
132
|
+
**additional_params: Any
|
|
133
|
+
) -> Dict[str, Any]:
|
|
134
|
+
"""Generate a detector configuration dictionary from variable selections.
|
|
135
|
+
|
|
136
|
+
Transforms a variable selection mapping into the nested configuration
|
|
137
|
+
structure required by detector configs. Supports both individual variable
|
|
138
|
+
names (strings) and tuples of variable names. Each tuple produces a
|
|
139
|
+
separate detector instance in the config.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
variable_selection: Maps event_id to list of variable names or tuples
|
|
143
|
+
of variable names. Strings are grouped into a single instance.
|
|
144
|
+
Each tuple becomes its own instance. Variable names matching
|
|
145
|
+
'var_\\d+' are positional template variables; others are header
|
|
146
|
+
variables.
|
|
147
|
+
detector_name: Name of the detector, used as the base instance_id.
|
|
148
|
+
method_type: Type of detection method (e.g., "new_value_detector").
|
|
149
|
+
**additional_params: Additional parameters for the detector's params
|
|
150
|
+
dict (e.g., max_combo_size=3).
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
Dictionary with structure compatible with detector config classes.
|
|
154
|
+
|
|
155
|
+
Examples:
|
|
156
|
+
Single variable names (one instance per event)::
|
|
157
|
+
|
|
158
|
+
config = generate_detector_config(
|
|
159
|
+
variable_selection={1: ["var_0", "var_1", "level"]},
|
|
160
|
+
detector_name="MyDetector",
|
|
161
|
+
method_type="new_value_detector",
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
Tuples of variable names (one instance per tuple)::
|
|
165
|
+
|
|
166
|
+
config = generate_detector_config(
|
|
167
|
+
variable_selection={1: [("username", "src_ip"), ("var_0", "var_1")]},
|
|
168
|
+
detector_name="MyDetector",
|
|
169
|
+
method_type="new_value_combo_detector",
|
|
170
|
+
max_combo_size=2,
|
|
171
|
+
)
|
|
172
|
+
"""
|
|
173
|
+
var_pattern = re.compile(r"^var_(\d+)$")
|
|
174
|
+
|
|
175
|
+
events_config: Dict[int | str, Dict[str, Any]] = {}
|
|
176
|
+
|
|
177
|
+
for event_id, variable_names in variable_selection.items():
|
|
178
|
+
instances: Dict[str, Any] = {}
|
|
179
|
+
|
|
180
|
+
# Separate plain strings from tuples
|
|
181
|
+
single_vars: List[str] = []
|
|
182
|
+
tuple_vars: List[Tuple[str, ...]] = []
|
|
183
|
+
|
|
184
|
+
for entry in variable_names:
|
|
185
|
+
if isinstance(entry, tuple):
|
|
186
|
+
tuple_vars.append(entry)
|
|
187
|
+
else:
|
|
188
|
+
single_vars.append(entry)
|
|
189
|
+
|
|
190
|
+
# Plain strings -> single instance keyed by detector_name
|
|
191
|
+
if single_vars:
|
|
192
|
+
instances[detector_name] = _classify_variables(single_vars, var_pattern)
|
|
193
|
+
|
|
194
|
+
# Each tuple -> its own instance, keyed by joined variable names
|
|
195
|
+
for combo in tuple_vars:
|
|
196
|
+
instance_id = f"{detector_name}_{'_'.join(combo)}"
|
|
197
|
+
instances[instance_id] = _classify_variables(combo, var_pattern)
|
|
198
|
+
|
|
199
|
+
events_config[event_id] = instances
|
|
200
|
+
|
|
201
|
+
config_dict = {
|
|
202
|
+
"detectors": {
|
|
203
|
+
detector_name: {
|
|
204
|
+
"method_type": method_type,
|
|
205
|
+
"auto_config": False,
|
|
206
|
+
"params": additional_params,
|
|
207
|
+
"events": events_config
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return config_dict
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
from pydantic import BaseModel
|
|
2
|
+
|
|
3
|
+
from typing_extensions import Self
|
|
4
|
+
from typing import Dict, Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# Sub-formats ********************************************************+
|
|
8
|
+
class Variable(BaseModel):
|
|
9
|
+
pos: str | int
|
|
10
|
+
name: str = ""
|
|
11
|
+
params: Dict[str, Any] = {}
|
|
12
|
+
|
|
13
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
14
|
+
"""Convert Variable to YAML-compatible dictionary."""
|
|
15
|
+
result: Dict[str, Any] = {
|
|
16
|
+
"pos": self.pos,
|
|
17
|
+
}
|
|
18
|
+
if self.name:
|
|
19
|
+
result["name"] = self.name
|
|
20
|
+
if self.params:
|
|
21
|
+
result["params"] = self.params
|
|
22
|
+
return result
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Header(BaseModel):
|
|
26
|
+
pos: str
|
|
27
|
+
params: Dict[str, Any] = {}
|
|
28
|
+
|
|
29
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
30
|
+
"""Convert Header to YAML-compatible dictionary."""
|
|
31
|
+
result: Dict[str, Any] = {
|
|
32
|
+
"pos": self.pos,
|
|
33
|
+
}
|
|
34
|
+
if self.params:
|
|
35
|
+
result["params"] = self.params
|
|
36
|
+
return result
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class _EventInstance(BaseModel):
|
|
40
|
+
"""Configuration for a specific instance within an event."""
|
|
41
|
+
params: Dict[str, Any] = {}
|
|
42
|
+
variables: Dict[str | int, Variable] = {}
|
|
43
|
+
header_variables: Dict[str, Header] = {}
|
|
44
|
+
|
|
45
|
+
@classmethod
|
|
46
|
+
def _init(cls, **kwargs: dict[str, Any]) -> "_EventInstance":
|
|
47
|
+
for var, cl in zip(["variables", "header_variables"], [Variable, Header]):
|
|
48
|
+
if var in kwargs:
|
|
49
|
+
new_dict = {}
|
|
50
|
+
for v in kwargs[var]:
|
|
51
|
+
aux = cl(**v) # type: ignore
|
|
52
|
+
new_dict[aux.pos] = aux
|
|
53
|
+
kwargs[var] = new_dict
|
|
54
|
+
return cls(**kwargs) # type: ignore
|
|
55
|
+
|
|
56
|
+
def get_all(self) -> Dict[Any, Header | Variable]:
|
|
57
|
+
return {**self.variables, **self.header_variables}
|
|
58
|
+
|
|
59
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
60
|
+
"""Convert _EventInstance to YAML-compatible dictionary."""
|
|
61
|
+
result: Dict[str, Any] = {}
|
|
62
|
+
# Always include params even if empty (to match YAML structure)
|
|
63
|
+
result["params"] = self.params
|
|
64
|
+
if self.variables:
|
|
65
|
+
result["variables"] = [var.to_dict() for var in self.variables.values()]
|
|
66
|
+
if self.header_variables:
|
|
67
|
+
result["header_variables"] = [header.to_dict() for header in self.header_variables.values()]
|
|
68
|
+
return result
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class _EventConfig(BaseModel):
|
|
72
|
+
"""Configuration for all instances of a specific event."""
|
|
73
|
+
instances: Dict[str, _EventInstance]
|
|
74
|
+
|
|
75
|
+
@classmethod
|
|
76
|
+
def _init(cls, instances_dict: Dict[str, Dict[str, Any]]) -> "_EventConfig":
|
|
77
|
+
instances = {}
|
|
78
|
+
for instance_id, instance_data in instances_dict.items():
|
|
79
|
+
instances[instance_id] = _EventInstance._init(**instance_data)
|
|
80
|
+
return cls(instances=instances)
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def variables(self) -> Dict[str | int, Variable]:
|
|
84
|
+
"""Pass-through to first instance for compatibility."""
|
|
85
|
+
if self.instances:
|
|
86
|
+
return next(iter(self.instances.values())).variables
|
|
87
|
+
return {}
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def header_variables(self) -> Dict[str, Header]:
|
|
91
|
+
"""Pass-through to first instance for compatibility."""
|
|
92
|
+
if self.instances:
|
|
93
|
+
return next(iter(self.instances.values())).header_variables
|
|
94
|
+
return {}
|
|
95
|
+
|
|
96
|
+
def get_all(self) -> Dict[Any, Header | Variable]:
|
|
97
|
+
"""Pass-through to first instance for compatibility."""
|
|
98
|
+
if self.instances:
|
|
99
|
+
return next(iter(self.instances.values())).get_all()
|
|
100
|
+
return {}
|
|
101
|
+
|
|
102
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
103
|
+
"""Convert _EventConfig to YAML-compatible dictionary."""
|
|
104
|
+
return {
|
|
105
|
+
instance_id: instance.to_dict()
|
|
106
|
+
for instance_id, instance in self.instances.items()
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
# Main-formats ********************************************************+
|
|
111
|
+
class EventsConfig(BaseModel):
|
|
112
|
+
"""Events configuration dict keyed by event_id."""
|
|
113
|
+
events: Dict[Any, _EventConfig]
|
|
114
|
+
|
|
115
|
+
@classmethod
|
|
116
|
+
def _init(cls, events_dict: Dict[Any, Dict[str, Any]]) -> Self:
|
|
117
|
+
new_dict = {}
|
|
118
|
+
for event_id, instances in events_dict.items():
|
|
119
|
+
new_dict[event_id] = _EventConfig._init(instances)
|
|
120
|
+
return cls(events=new_dict)
|
|
121
|
+
|
|
122
|
+
def __getitem__(self, idx: str | int) -> _EventConfig | None:
|
|
123
|
+
if idx not in self.events:
|
|
124
|
+
return None
|
|
125
|
+
return self.events[idx]
|
|
126
|
+
|
|
127
|
+
def __contains__(self, idx: str | int) -> bool:
|
|
128
|
+
return idx in self.events
|
|
129
|
+
|
|
130
|
+
def to_dict(self) -> Dict[Any, Any]:
|
|
131
|
+
"""Convert EventsConfig to YAML-compatible dictionary.
|
|
132
|
+
|
|
133
|
+
This unwraps the internal 'events' dict structure to match YAML
|
|
134
|
+
format.
|
|
135
|
+
"""
|
|
136
|
+
result = {}
|
|
137
|
+
for event_id, event_config in self.events.items():
|
|
138
|
+
# Skip events with no instances
|
|
139
|
+
if not event_config.instances:
|
|
140
|
+
continue
|
|
141
|
+
# Convert string keys back to int if they were originally int
|
|
142
|
+
try:
|
|
143
|
+
key = int(event_id)
|
|
144
|
+
except (ValueError, TypeError):
|
|
145
|
+
key = event_id
|
|
146
|
+
result[key] = event_config.to_dict()
|
|
147
|
+
return result
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
|
|
2
|
+
from typing import Literal, get_args
|
|
3
|
+
from enum import Enum
|
|
4
|
+
import warnings
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TrainState(Enum):
|
|
8
|
+
DEFAULT = 0
|
|
9
|
+
STOP_TRAINING = 1
|
|
10
|
+
KEEP_TRAINING = 2
|
|
11
|
+
|
|
12
|
+
def describe(self) -> str:
|
|
13
|
+
descriptions = [
|
|
14
|
+
"Follow default training behavior.",
|
|
15
|
+
"Force stop training.",
|
|
16
|
+
"Keep training regardless of default behavior."
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
return descriptions[self.value]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ConfigState(Enum):
|
|
23
|
+
DEFAULT = 0
|
|
24
|
+
STOP_CONFIGURE = 1
|
|
25
|
+
KEEP_CONFIGURE = 2
|
|
26
|
+
|
|
27
|
+
def describe(self) -> str:
|
|
28
|
+
descriptions = [
|
|
29
|
+
"Follow default configuration behavior.",
|
|
30
|
+
"Force stop configuration.",
|
|
31
|
+
"Keep configuring regardless of default behavior."
|
|
32
|
+
]
|
|
33
|
+
return descriptions[self.value]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
StatesL = Literal["keep_training", "stop_training", "keep_configuring", "stop_configuring"]
|
|
37
|
+
|
|
38
|
+
def update_state(
|
|
39
|
+
state: StatesL, train_state: TrainState, config_state: ConfigState
|
|
40
|
+
) -> tuple[TrainState, ConfigState]:
|
|
41
|
+
if state == "keep_training":
|
|
42
|
+
train_state = TrainState.KEEP_TRAINING
|
|
43
|
+
elif state == "stop_training":
|
|
44
|
+
train_state = TrainState.STOP_TRAINING
|
|
45
|
+
elif state == "keep_configuring":
|
|
46
|
+
config_state = ConfigState.KEEP_CONFIGURE
|
|
47
|
+
elif state == "stop_configuring":
|
|
48
|
+
config_state = ConfigState.STOP_CONFIGURE
|
|
49
|
+
else:
|
|
50
|
+
warnings.warn(f"State {state} unknown, use: {get_args(StatesL)}")
|
|
51
|
+
|
|
52
|
+
return train_state, config_state
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def do_training(
|
|
56
|
+
data_use_training: int | None, index: int, train_state: TrainState
|
|
57
|
+
) -> bool:
|
|
58
|
+
if train_state == TrainState.STOP_TRAINING:
|
|
59
|
+
return False
|
|
60
|
+
elif train_state == TrainState.KEEP_TRAINING:
|
|
61
|
+
return True
|
|
62
|
+
|
|
63
|
+
return data_use_training is not None and data_use_training > index
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def do_configure(
|
|
67
|
+
data_use_configure: int | None, index: int, configure_state: ConfigState
|
|
68
|
+
) -> bool:
|
|
69
|
+
if configure_state == ConfigState.STOP_CONFIGURE:
|
|
70
|
+
return False
|
|
71
|
+
elif configure_state == ConfigState.KEEP_CONFIGURE:
|
|
72
|
+
return True
|
|
73
|
+
|
|
74
|
+
return data_use_configure is not None and data_use_configure > index
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class FitLogicState(Enum):
|
|
78
|
+
DO_CONFIG = 0
|
|
79
|
+
DO_TRAIN = 1
|
|
80
|
+
NOTHING = 2
|
|
81
|
+
|
|
82
|
+
def describe(self) -> str:
|
|
83
|
+
descriptions = [
|
|
84
|
+
"Configuring",
|
|
85
|
+
"Training.",
|
|
86
|
+
"Default"
|
|
87
|
+
]
|
|
88
|
+
return descriptions[self.value]
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class FitLogic:
|
|
92
|
+
def __init__(
|
|
93
|
+
self, data_use_configure: int | None, data_use_training: int | None
|
|
94
|
+
) -> None:
|
|
95
|
+
|
|
96
|
+
self.train_state = TrainState.DEFAULT
|
|
97
|
+
self.configure_state = ConfigState.DEFAULT
|
|
98
|
+
self.last_state = FitLogicState.NOTHING
|
|
99
|
+
|
|
100
|
+
self.data_used_train, self.data_used_configure = 0, 0
|
|
101
|
+
self._configuration_done, self.config_finished = False, False
|
|
102
|
+
self._training_done, self.training_finished = False, False
|
|
103
|
+
|
|
104
|
+
self.data_use_configure = data_use_configure
|
|
105
|
+
self.data_use_training = data_use_training
|
|
106
|
+
|
|
107
|
+
def get_last_state(self) -> str:
|
|
108
|
+
return self.last_state.describe()
|
|
109
|
+
|
|
110
|
+
def update_state(self, state: StatesL) -> None:
|
|
111
|
+
self.train_state, self.configure_state = update_state(
|
|
112
|
+
state=state, train_state=self.train_state, config_state=self.configure_state
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
def finish_config(self) -> bool:
|
|
116
|
+
if self._configuration_done and not self.config_finished:
|
|
117
|
+
self.config_finished = True
|
|
118
|
+
return True
|
|
119
|
+
return False
|
|
120
|
+
|
|
121
|
+
def finish_training(self) -> bool:
|
|
122
|
+
if self._training_done and not self.training_finished:
|
|
123
|
+
self.training_finished = True
|
|
124
|
+
return True
|
|
125
|
+
return False
|
|
126
|
+
|
|
127
|
+
def __check_state(self) -> FitLogicState:
|
|
128
|
+
if do_configure(
|
|
129
|
+
data_use_configure=self.data_use_configure,
|
|
130
|
+
index=self.data_used_configure,
|
|
131
|
+
configure_state=self.configure_state
|
|
132
|
+
):
|
|
133
|
+
self.data_used_configure += 1
|
|
134
|
+
return FitLogicState.DO_CONFIG
|
|
135
|
+
else:
|
|
136
|
+
if self.data_used_configure > 0 and not self._configuration_done:
|
|
137
|
+
self._configuration_done = True
|
|
138
|
+
|
|
139
|
+
if do_training(
|
|
140
|
+
data_use_training=self.data_use_training,
|
|
141
|
+
index=self.data_used_train,
|
|
142
|
+
train_state=self.train_state
|
|
143
|
+
):
|
|
144
|
+
self.data_used_train += 1
|
|
145
|
+
return FitLogicState.DO_TRAIN
|
|
146
|
+
elif self.data_used_train > 0 and not self._training_done:
|
|
147
|
+
self._training_done = True
|
|
148
|
+
|
|
149
|
+
return FitLogicState.NOTHING
|
|
150
|
+
|
|
151
|
+
def run(self) -> FitLogicState:
|
|
152
|
+
self.last_state = self.__check_state()
|
|
153
|
+
return self.last_state
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from detectmatelibrary.schemas import BaseSchema
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
from typing import Tuple
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class SchemaPipeline:
|
|
8
|
+
@staticmethod
|
|
9
|
+
def preprocess(
|
|
10
|
+
input_: BaseSchema, data: BaseSchema | bytes
|
|
11
|
+
) -> Tuple[bool, BaseSchema]:
|
|
12
|
+
|
|
13
|
+
is_byte = False
|
|
14
|
+
if isinstance(data, bytes):
|
|
15
|
+
is_byte = True
|
|
16
|
+
input_.deserialize(data)
|
|
17
|
+
data = input_.copy()
|
|
18
|
+
else:
|
|
19
|
+
data = data.copy()
|
|
20
|
+
|
|
21
|
+
return is_byte, data
|
|
22
|
+
|
|
23
|
+
@staticmethod
|
|
24
|
+
def postprocess(
|
|
25
|
+
data: BaseSchema, is_byte: bool
|
|
26
|
+
) -> BaseSchema | bytes:
|
|
27
|
+
|
|
28
|
+
return data if not is_byte else data.serialize()
|