amd-node-scraper 0.0.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.
- amd_node_scraper-0.0.1.dist-info/LICENSE +21 -0
- amd_node_scraper-0.0.1.dist-info/METADATA +424 -0
- amd_node_scraper-0.0.1.dist-info/RECORD +197 -0
- amd_node_scraper-0.0.1.dist-info/WHEEL +5 -0
- amd_node_scraper-0.0.1.dist-info/entry_points.txt +2 -0
- amd_node_scraper-0.0.1.dist-info/top_level.txt +1 -0
- nodescraper/__init__.py +32 -0
- nodescraper/base/__init__.py +34 -0
- nodescraper/base/inbandcollectortask.py +118 -0
- nodescraper/base/inbanddataplugin.py +39 -0
- nodescraper/base/regexanalyzer.py +120 -0
- nodescraper/cli/__init__.py +29 -0
- nodescraper/cli/cli.py +511 -0
- nodescraper/cli/constants.py +27 -0
- nodescraper/cli/dynamicparserbuilder.py +171 -0
- nodescraper/cli/helper.py +517 -0
- nodescraper/cli/inputargtypes.py +129 -0
- nodescraper/configbuilder.py +123 -0
- nodescraper/configregistry.py +66 -0
- nodescraper/configs/node_status.json +19 -0
- nodescraper/connection/__init__.py +25 -0
- nodescraper/connection/inband/__init__.py +46 -0
- nodescraper/connection/inband/inband.py +171 -0
- nodescraper/connection/inband/inbandlocal.py +93 -0
- nodescraper/connection/inband/inbandmanager.py +151 -0
- nodescraper/connection/inband/inbandremote.py +173 -0
- nodescraper/connection/inband/sshparams.py +43 -0
- nodescraper/constants.py +26 -0
- nodescraper/enums/__init__.py +40 -0
- nodescraper/enums/eventcategory.py +89 -0
- nodescraper/enums/eventpriority.py +42 -0
- nodescraper/enums/executionstatus.py +44 -0
- nodescraper/enums/osfamily.py +34 -0
- nodescraper/enums/systeminteraction.py +41 -0
- nodescraper/enums/systemlocation.py +33 -0
- nodescraper/generictypes.py +36 -0
- nodescraper/interfaces/__init__.py +44 -0
- nodescraper/interfaces/connectionmanager.py +143 -0
- nodescraper/interfaces/dataanalyzertask.py +138 -0
- nodescraper/interfaces/datacollectortask.py +185 -0
- nodescraper/interfaces/dataplugin.py +356 -0
- nodescraper/interfaces/plugin.py +127 -0
- nodescraper/interfaces/resultcollator.py +56 -0
- nodescraper/interfaces/task.py +164 -0
- nodescraper/interfaces/taskresulthook.py +39 -0
- nodescraper/models/__init__.py +48 -0
- nodescraper/models/analyzerargs.py +93 -0
- nodescraper/models/collectorargs.py +30 -0
- nodescraper/models/connectionconfig.py +34 -0
- nodescraper/models/datamodel.py +171 -0
- nodescraper/models/datapluginresult.py +39 -0
- nodescraper/models/event.py +158 -0
- nodescraper/models/pluginconfig.py +38 -0
- nodescraper/models/pluginresult.py +39 -0
- nodescraper/models/systeminfo.py +44 -0
- nodescraper/models/taskresult.py +185 -0
- nodescraper/models/timerangeargs.py +38 -0
- nodescraper/pluginexecutor.py +274 -0
- nodescraper/pluginregistry.py +152 -0
- nodescraper/plugins/__init__.py +25 -0
- nodescraper/plugins/inband/__init__.py +25 -0
- nodescraper/plugins/inband/amdsmi/__init__.py +28 -0
- nodescraper/plugins/inband/amdsmi/amdsmi_analyzer.py +821 -0
- nodescraper/plugins/inband/amdsmi/amdsmi_collector.py +1313 -0
- nodescraper/plugins/inband/amdsmi/amdsmi_plugin.py +43 -0
- nodescraper/plugins/inband/amdsmi/amdsmidata.py +1002 -0
- nodescraper/plugins/inband/amdsmi/analyzer_args.py +50 -0
- nodescraper/plugins/inband/amdsmi/cper.py +65 -0
- nodescraper/plugins/inband/bios/__init__.py +29 -0
- nodescraper/plugins/inband/bios/analyzer_args.py +64 -0
- nodescraper/plugins/inband/bios/bios_analyzer.py +93 -0
- nodescraper/plugins/inband/bios/bios_collector.py +93 -0
- nodescraper/plugins/inband/bios/bios_plugin.py +43 -0
- nodescraper/plugins/inband/bios/biosdata.py +30 -0
- nodescraper/plugins/inband/cmdline/__init__.py +25 -0
- nodescraper/plugins/inband/cmdline/analyzer_args.py +80 -0
- nodescraper/plugins/inband/cmdline/cmdline_analyzer.py +113 -0
- nodescraper/plugins/inband/cmdline/cmdline_collector.py +77 -0
- nodescraper/plugins/inband/cmdline/cmdline_plugin.py +43 -0
- nodescraper/plugins/inband/cmdline/cmdlinedata.py +30 -0
- nodescraper/plugins/inband/device_enumeration/__init__.py +29 -0
- nodescraper/plugins/inband/device_enumeration/analyzer_args.py +73 -0
- nodescraper/plugins/inband/device_enumeration/device_enumeration_analyzer.py +81 -0
- nodescraper/plugins/inband/device_enumeration/device_enumeration_collector.py +176 -0
- nodescraper/plugins/inband/device_enumeration/device_enumeration_plugin.py +45 -0
- nodescraper/plugins/inband/device_enumeration/deviceenumdata.py +36 -0
- nodescraper/plugins/inband/dimm/__init__.py +25 -0
- nodescraper/plugins/inband/dimm/collector_args.py +31 -0
- nodescraper/plugins/inband/dimm/dimm_collector.py +151 -0
- nodescraper/plugins/inband/dimm/dimm_plugin.py +40 -0
- nodescraper/plugins/inband/dimm/dimmdata.py +30 -0
- nodescraper/plugins/inband/dkms/__init__.py +25 -0
- nodescraper/plugins/inband/dkms/analyzer_args.py +85 -0
- nodescraper/plugins/inband/dkms/dkms_analyzer.py +106 -0
- nodescraper/plugins/inband/dkms/dkms_collector.py +76 -0
- nodescraper/plugins/inband/dkms/dkms_plugin.py +43 -0
- nodescraper/plugins/inband/dkms/dkmsdata.py +33 -0
- nodescraper/plugins/inband/dmesg/__init__.py +28 -0
- nodescraper/plugins/inband/dmesg/analyzer_args.py +33 -0
- nodescraper/plugins/inband/dmesg/collector_args.py +39 -0
- nodescraper/plugins/inband/dmesg/dmesg_analyzer.py +503 -0
- nodescraper/plugins/inband/dmesg/dmesg_collector.py +164 -0
- nodescraper/plugins/inband/dmesg/dmesg_plugin.py +44 -0
- nodescraper/plugins/inband/dmesg/dmesgdata.py +116 -0
- nodescraper/plugins/inband/fabrics/__init__.py +28 -0
- nodescraper/plugins/inband/fabrics/fabrics_collector.py +726 -0
- nodescraper/plugins/inband/fabrics/fabrics_plugin.py +37 -0
- nodescraper/plugins/inband/fabrics/fabricsdata.py +140 -0
- nodescraper/plugins/inband/journal/__init__.py +28 -0
- nodescraper/plugins/inband/journal/collector_args.py +33 -0
- nodescraper/plugins/inband/journal/journal_collector.py +107 -0
- nodescraper/plugins/inband/journal/journal_plugin.py +40 -0
- nodescraper/plugins/inband/journal/journaldata.py +44 -0
- nodescraper/plugins/inband/kernel/__init__.py +25 -0
- nodescraper/plugins/inband/kernel/analyzer_args.py +64 -0
- nodescraper/plugins/inband/kernel/kernel_analyzer.py +91 -0
- nodescraper/plugins/inband/kernel/kernel_collector.py +129 -0
- nodescraper/plugins/inband/kernel/kernel_plugin.py +43 -0
- nodescraper/plugins/inband/kernel/kerneldata.py +32 -0
- nodescraper/plugins/inband/kernel_module/__init__.py +25 -0
- nodescraper/plugins/inband/kernel_module/analyzer_args.py +59 -0
- nodescraper/plugins/inband/kernel_module/kernel_module_analyzer.py +211 -0
- nodescraper/plugins/inband/kernel_module/kernel_module_collector.py +264 -0
- nodescraper/plugins/inband/kernel_module/kernel_module_data.py +60 -0
- nodescraper/plugins/inband/kernel_module/kernel_module_plugin.py +43 -0
- nodescraper/plugins/inband/memory/__init__.py +25 -0
- nodescraper/plugins/inband/memory/analyzer_args.py +45 -0
- nodescraper/plugins/inband/memory/memory_analyzer.py +98 -0
- nodescraper/plugins/inband/memory/memory_collector.py +330 -0
- nodescraper/plugins/inband/memory/memory_plugin.py +43 -0
- nodescraper/plugins/inband/memory/memorydata.py +90 -0
- nodescraper/plugins/inband/network/__init__.py +28 -0
- nodescraper/plugins/inband/network/network_collector.py +1828 -0
- nodescraper/plugins/inband/network/network_plugin.py +37 -0
- nodescraper/plugins/inband/network/networkdata.py +319 -0
- nodescraper/plugins/inband/nvme/__init__.py +28 -0
- nodescraper/plugins/inband/nvme/nvme_collector.py +167 -0
- nodescraper/plugins/inband/nvme/nvme_plugin.py +37 -0
- nodescraper/plugins/inband/nvme/nvmedata.py +45 -0
- nodescraper/plugins/inband/os/__init__.py +25 -0
- nodescraper/plugins/inband/os/analyzer_args.py +64 -0
- nodescraper/plugins/inband/os/os_analyzer.py +73 -0
- nodescraper/plugins/inband/os/os_collector.py +131 -0
- nodescraper/plugins/inband/os/os_plugin.py +43 -0
- nodescraper/plugins/inband/os/osdata.py +31 -0
- nodescraper/plugins/inband/package/__init__.py +25 -0
- nodescraper/plugins/inband/package/analyzer_args.py +48 -0
- nodescraper/plugins/inband/package/package_analyzer.py +253 -0
- nodescraper/plugins/inband/package/package_collector.py +273 -0
- nodescraper/plugins/inband/package/package_plugin.py +43 -0
- nodescraper/plugins/inband/package/packagedata.py +41 -0
- nodescraper/plugins/inband/pcie/__init__.py +29 -0
- nodescraper/plugins/inband/pcie/analyzer_args.py +63 -0
- nodescraper/plugins/inband/pcie/pcie_analyzer.py +1081 -0
- nodescraper/plugins/inband/pcie/pcie_collector.py +690 -0
- nodescraper/plugins/inband/pcie/pcie_data.py +2017 -0
- nodescraper/plugins/inband/pcie/pcie_plugin.py +43 -0
- nodescraper/plugins/inband/process/__init__.py +25 -0
- nodescraper/plugins/inband/process/analyzer_args.py +45 -0
- nodescraper/plugins/inband/process/collector_args.py +31 -0
- nodescraper/plugins/inband/process/process_analyzer.py +91 -0
- nodescraper/plugins/inband/process/process_collector.py +115 -0
- nodescraper/plugins/inband/process/process_plugin.py +46 -0
- nodescraper/plugins/inband/process/processdata.py +34 -0
- nodescraper/plugins/inband/rocm/__init__.py +25 -0
- nodescraper/plugins/inband/rocm/analyzer_args.py +66 -0
- nodescraper/plugins/inband/rocm/rocm_analyzer.py +100 -0
- nodescraper/plugins/inband/rocm/rocm_collector.py +205 -0
- nodescraper/plugins/inband/rocm/rocm_plugin.py +43 -0
- nodescraper/plugins/inband/rocm/rocmdata.py +62 -0
- nodescraper/plugins/inband/storage/__init__.py +25 -0
- nodescraper/plugins/inband/storage/analyzer_args.py +38 -0
- nodescraper/plugins/inband/storage/collector_args.py +31 -0
- nodescraper/plugins/inband/storage/storage_analyzer.py +152 -0
- nodescraper/plugins/inband/storage/storage_collector.py +110 -0
- nodescraper/plugins/inband/storage/storage_plugin.py +44 -0
- nodescraper/plugins/inband/storage/storagedata.py +70 -0
- nodescraper/plugins/inband/sysctl/__init__.py +29 -0
- nodescraper/plugins/inband/sysctl/analyzer_args.py +67 -0
- nodescraper/plugins/inband/sysctl/sysctl_analyzer.py +81 -0
- nodescraper/plugins/inband/sysctl/sysctl_collector.py +101 -0
- nodescraper/plugins/inband/sysctl/sysctl_plugin.py +43 -0
- nodescraper/plugins/inband/sysctl/sysctldata.py +42 -0
- nodescraper/plugins/inband/syslog/__init__.py +28 -0
- nodescraper/plugins/inband/syslog/syslog_collector.py +121 -0
- nodescraper/plugins/inband/syslog/syslog_plugin.py +37 -0
- nodescraper/plugins/inband/syslog/syslogdata.py +46 -0
- nodescraper/plugins/inband/uptime/__init__.py +25 -0
- nodescraper/plugins/inband/uptime/uptime_collector.py +88 -0
- nodescraper/plugins/inband/uptime/uptime_plugin.py +37 -0
- nodescraper/plugins/inband/uptime/uptimedata.py +31 -0
- nodescraper/resultcollators/__init__.py +25 -0
- nodescraper/resultcollators/tablesummary.py +159 -0
- nodescraper/taskresulthooks/__init__.py +28 -0
- nodescraper/taskresulthooks/filesystemloghook.py +88 -0
- nodescraper/typeutils.py +171 -0
- nodescraper/utils.py +412 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
###############################################################################
|
|
2
|
+
#
|
|
3
|
+
# MIT License
|
|
4
|
+
#
|
|
5
|
+
# Copyright (c) 2025 Advanced Micro Devices, Inc.
|
|
6
|
+
#
|
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
# in the Software without restriction, including without limitation the rights
|
|
10
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
# furnished to do so, subject to the following conditions:
|
|
13
|
+
#
|
|
14
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
15
|
+
# copies or substantial portions of the Software.
|
|
16
|
+
#
|
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
|
+
# SOFTWARE.
|
|
24
|
+
#
|
|
25
|
+
###############################################################################
|
|
26
|
+
import abc
|
|
27
|
+
import copy
|
|
28
|
+
import datetime
|
|
29
|
+
import logging
|
|
30
|
+
from typing import Any, Optional, Union
|
|
31
|
+
|
|
32
|
+
from nodescraper.constants import DEFAULT_LOGGER
|
|
33
|
+
from nodescraper.enums import EventCategory, EventPriority
|
|
34
|
+
from nodescraper.models import Event, SystemInfo, TaskResult
|
|
35
|
+
|
|
36
|
+
from .taskresulthook import TaskResultHook
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class SystemCompatibilityError(Exception):
|
|
40
|
+
"""Exception raised when system has invalid attributes for the task"""
|
|
41
|
+
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class Task(abc.ABC):
|
|
46
|
+
"""Parent class for all tasks"""
|
|
47
|
+
|
|
48
|
+
TASK_TYPE: str
|
|
49
|
+
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
system_info: SystemInfo,
|
|
53
|
+
logger: Optional[logging.Logger] = None,
|
|
54
|
+
max_event_priority_level: Union[EventPriority, str] = EventPriority.CRITICAL,
|
|
55
|
+
parent: Optional[str] = None,
|
|
56
|
+
task_result_hooks: Optional[list[TaskResultHook]] = None,
|
|
57
|
+
**kwargs: dict[str, Any],
|
|
58
|
+
):
|
|
59
|
+
if logger is None:
|
|
60
|
+
logger = logging.getLogger(DEFAULT_LOGGER)
|
|
61
|
+
self.system_info = system_info
|
|
62
|
+
self.logger = logger
|
|
63
|
+
self.max_event_priority_level = max_event_priority_level
|
|
64
|
+
self.parent = parent
|
|
65
|
+
if not task_result_hooks:
|
|
66
|
+
task_result_hooks = []
|
|
67
|
+
self.task_result_hooks = task_result_hooks
|
|
68
|
+
self.result: TaskResult = self._init_result()
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def max_event_priority_level(self) -> EventPriority:
|
|
72
|
+
"""maximum priority level for events generated by task
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
EventPriority: max priority level enum
|
|
76
|
+
"""
|
|
77
|
+
return self._max_event_priority_level
|
|
78
|
+
|
|
79
|
+
@max_event_priority_level.setter
|
|
80
|
+
def max_event_priority_level(self, input_value: Union[str, EventPriority]):
|
|
81
|
+
if isinstance(input_value, str):
|
|
82
|
+
value: EventPriority = getattr(EventPriority, input_value)
|
|
83
|
+
elif isinstance(input_value, int):
|
|
84
|
+
value = EventPriority(input_value)
|
|
85
|
+
elif isinstance(input_value, EventPriority):
|
|
86
|
+
value: EventPriority = input_value # type:ignore
|
|
87
|
+
else:
|
|
88
|
+
raise ValueError(f"Invalid type for max_event_priority_level: {type(input_value)}")
|
|
89
|
+
|
|
90
|
+
self._max_event_priority_level = value
|
|
91
|
+
|
|
92
|
+
def __init_subclass__(cls, **kwargs) -> None:
|
|
93
|
+
super().__init_subclass__(**kwargs)
|
|
94
|
+
if cls.TASK_TYPE is None:
|
|
95
|
+
raise TypeError(f"No value provided for TASK_TYPE in task class {cls.__name__}")
|
|
96
|
+
|
|
97
|
+
def _build_event(
|
|
98
|
+
self,
|
|
99
|
+
category: Union[EventCategory, str],
|
|
100
|
+
description: str,
|
|
101
|
+
priority: EventPriority,
|
|
102
|
+
data: Optional[dict] = None,
|
|
103
|
+
timestamp: Optional[datetime.datetime] = None,
|
|
104
|
+
) -> Event:
|
|
105
|
+
|
|
106
|
+
if data is None:
|
|
107
|
+
data = {"task_name": self.__class__.__name__, "task_type": self.TASK_TYPE}
|
|
108
|
+
|
|
109
|
+
else:
|
|
110
|
+
# Copy to avoid mutating the caller's dict
|
|
111
|
+
data = copy.copy(data)
|
|
112
|
+
data["task_name"] = self.__class__.__name__
|
|
113
|
+
data["task_type"] = self.TASK_TYPE
|
|
114
|
+
|
|
115
|
+
if self.parent:
|
|
116
|
+
data["parent"] = self.parent
|
|
117
|
+
|
|
118
|
+
if self.system_info.metadata:
|
|
119
|
+
data["system_metadata"] = copy.copy(self.system_info.metadata)
|
|
120
|
+
|
|
121
|
+
if priority > self.max_event_priority_level:
|
|
122
|
+
priority = self.max_event_priority_level
|
|
123
|
+
|
|
124
|
+
event = Event(
|
|
125
|
+
category=category,
|
|
126
|
+
description=description,
|
|
127
|
+
priority=priority,
|
|
128
|
+
data=data,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
if timestamp:
|
|
132
|
+
event.timestamp = timestamp
|
|
133
|
+
|
|
134
|
+
return event
|
|
135
|
+
|
|
136
|
+
def _log_event(
|
|
137
|
+
self,
|
|
138
|
+
category: Union[EventCategory, str],
|
|
139
|
+
description: str,
|
|
140
|
+
priority: EventPriority,
|
|
141
|
+
data: Optional[dict] = None,
|
|
142
|
+
timestamp: Optional[datetime.datetime] = None,
|
|
143
|
+
console_log: bool = False,
|
|
144
|
+
):
|
|
145
|
+
event = self._build_event(
|
|
146
|
+
category=category,
|
|
147
|
+
description=description,
|
|
148
|
+
priority=priority,
|
|
149
|
+
data=data,
|
|
150
|
+
timestamp=timestamp,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
if console_log:
|
|
154
|
+
self.logger.log(getattr(logging, priority.name, logging.INFO), description)
|
|
155
|
+
|
|
156
|
+
self.result.events.append(event)
|
|
157
|
+
|
|
158
|
+
def _init_result(self):
|
|
159
|
+
result = TaskResult(task=self.__class__.__name__, parent=self.parent)
|
|
160
|
+
return result
|
|
161
|
+
|
|
162
|
+
def _run_hooks(self, result, **kwargs):
|
|
163
|
+
for hook in self.task_result_hooks:
|
|
164
|
+
hook.process_result(result, **kwargs)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
###############################################################################
|
|
2
|
+
#
|
|
3
|
+
# MIT License
|
|
4
|
+
#
|
|
5
|
+
# Copyright (c) 2025 Advanced Micro Devices, Inc.
|
|
6
|
+
#
|
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
# in the Software without restriction, including without limitation the rights
|
|
10
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
# furnished to do so, subject to the following conditions:
|
|
13
|
+
#
|
|
14
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
15
|
+
# copies or substantial portions of the Software.
|
|
16
|
+
#
|
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
|
+
# SOFTWARE.
|
|
24
|
+
#
|
|
25
|
+
###############################################################################
|
|
26
|
+
import abc
|
|
27
|
+
|
|
28
|
+
from nodescraper.models import TaskResult
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class TaskResultHook(abc.ABC):
|
|
32
|
+
|
|
33
|
+
@abc.abstractmethod
|
|
34
|
+
def process_result(self, task_result: TaskResult, **kwargs):
|
|
35
|
+
"""post process task result
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
task_result (TaskResult): input task result
|
|
39
|
+
"""
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
###############################################################################
|
|
2
|
+
#
|
|
3
|
+
# MIT License
|
|
4
|
+
#
|
|
5
|
+
# Copyright (c) 2025 Advanced Micro Devices, Inc.
|
|
6
|
+
#
|
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
# in the Software without restriction, including without limitation the rights
|
|
10
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
# furnished to do so, subject to the following conditions:
|
|
13
|
+
#
|
|
14
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
15
|
+
# copies or substantial portions of the Software.
|
|
16
|
+
#
|
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
|
+
# SOFTWARE.
|
|
24
|
+
#
|
|
25
|
+
###############################################################################
|
|
26
|
+
from .analyzerargs import AnalyzerArgs
|
|
27
|
+
from .collectorargs import CollectorArgs
|
|
28
|
+
from .datamodel import DataModel
|
|
29
|
+
from .datapluginresult import DataPluginResult
|
|
30
|
+
from .event import Event
|
|
31
|
+
from .pluginconfig import PluginConfig
|
|
32
|
+
from .pluginresult import PluginResult
|
|
33
|
+
from .systeminfo import SystemInfo
|
|
34
|
+
from .taskresult import TaskResult
|
|
35
|
+
from .timerangeargs import TimeRangeAnalysisArgs
|
|
36
|
+
|
|
37
|
+
__all__ = [
|
|
38
|
+
"AnalyzerArgs",
|
|
39
|
+
"CollectorArgs",
|
|
40
|
+
"DataModel",
|
|
41
|
+
"TaskResult",
|
|
42
|
+
"Event",
|
|
43
|
+
"SystemInfo",
|
|
44
|
+
"PluginResult",
|
|
45
|
+
"DataPluginResult",
|
|
46
|
+
"PluginConfig",
|
|
47
|
+
"TimeRangeAnalysisArgs",
|
|
48
|
+
]
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
###############################################################################
|
|
2
|
+
#
|
|
3
|
+
# MIT License
|
|
4
|
+
#
|
|
5
|
+
# Copyright (c) 2025 Advanced Micro Devices, Inc.
|
|
6
|
+
#
|
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
# in the Software without restriction, including without limitation the rights
|
|
10
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
# furnished to do so, subject to the following conditions:
|
|
13
|
+
#
|
|
14
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
15
|
+
# copies or substantial portions of the Software.
|
|
16
|
+
#
|
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
|
+
# SOFTWARE.
|
|
24
|
+
#
|
|
25
|
+
###############################################################################
|
|
26
|
+
from typing import Any
|
|
27
|
+
|
|
28
|
+
from pydantic import BaseModel, model_validator
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class AnalyzerArgs(BaseModel):
|
|
32
|
+
"""Base class for all analyzer arguments.
|
|
33
|
+
|
|
34
|
+
This class provides automatic string stripping for all string values
|
|
35
|
+
in analyzer args. All analyzer args classes should inherit from this
|
|
36
|
+
directly.
|
|
37
|
+
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
model_config = {"extra": "forbid", "exclude_none": True}
|
|
41
|
+
|
|
42
|
+
@model_validator(mode="before")
|
|
43
|
+
@classmethod
|
|
44
|
+
def strip_string_values(cls, data: Any) -> Any:
|
|
45
|
+
"""Strip whitespace from all string values in analyzer args.
|
|
46
|
+
|
|
47
|
+
This validator recursively processes:
|
|
48
|
+
- String values: strips whitespace
|
|
49
|
+
- Lists: strips strings in lists
|
|
50
|
+
- Dicts: strips string values in dicts
|
|
51
|
+
- Other types: left unchanged
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
data: The input data to validate
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
The data with all string values stripped
|
|
58
|
+
"""
|
|
59
|
+
if isinstance(data, dict):
|
|
60
|
+
return {k: cls._strip_value(v) for k, v in data.items()}
|
|
61
|
+
return data
|
|
62
|
+
|
|
63
|
+
@classmethod
|
|
64
|
+
def _strip_value(cls, value: Any) -> Any:
|
|
65
|
+
"""Recursively strip string values.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
value: The value to process
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
The processed value
|
|
72
|
+
"""
|
|
73
|
+
if isinstance(value, str):
|
|
74
|
+
return value.strip()
|
|
75
|
+
elif isinstance(value, list):
|
|
76
|
+
return [cls._strip_value(item) for item in value]
|
|
77
|
+
elif isinstance(value, dict):
|
|
78
|
+
return {k: cls._strip_value(v) for k, v in value.items()}
|
|
79
|
+
return value
|
|
80
|
+
|
|
81
|
+
@classmethod
|
|
82
|
+
def build_from_model(cls, datamodel):
|
|
83
|
+
"""Build analyzer args instance from data model object
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
datamodel (TDataModel): data model to use for creating analyzer args
|
|
87
|
+
|
|
88
|
+
Raises:
|
|
89
|
+
NotImplementedError: Not implemented error
|
|
90
|
+
"""
|
|
91
|
+
raise NotImplementedError(
|
|
92
|
+
"Setting analyzer args from datamodel is not implemented for class: %s", cls.__name__
|
|
93
|
+
)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
###############################################################################
|
|
2
|
+
#
|
|
3
|
+
# MIT License
|
|
4
|
+
#
|
|
5
|
+
# Copyright (c) 2025 Advanced Micro Devices, Inc.
|
|
6
|
+
#
|
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
# in the Software without restriction, including without limitation the rights
|
|
10
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
# furnished to do so, subject to the following conditions:
|
|
13
|
+
#
|
|
14
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
15
|
+
# copies or substantial portions of the Software.
|
|
16
|
+
#
|
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
|
+
# SOFTWARE.
|
|
24
|
+
#
|
|
25
|
+
###############################################################################
|
|
26
|
+
from pydantic import BaseModel
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class CollectorArgs(BaseModel):
|
|
30
|
+
model_config = {"extra": "forbid", "exclude_none": True}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
###############################################################################
|
|
2
|
+
#
|
|
3
|
+
# MIT License
|
|
4
|
+
#
|
|
5
|
+
# Copyright (c) 2025 Advanced Micro Devices, Inc.
|
|
6
|
+
#
|
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
# in the Software without restriction, including without limitation the rights
|
|
10
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
# furnished to do so, subject to the following conditions:
|
|
13
|
+
#
|
|
14
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
15
|
+
# copies or substantial portions of the Software.
|
|
16
|
+
#
|
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
|
+
# SOFTWARE.
|
|
24
|
+
#
|
|
25
|
+
###############################################################################
|
|
26
|
+
from pydantic import BaseModel, Field
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ConnectionConfig(BaseModel):
|
|
30
|
+
"""
|
|
31
|
+
Model that defines the structure for connection configurations.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
connection: dict[str, dict] = Field(default_factory=dict)
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
###############################################################################
|
|
2
|
+
#
|
|
3
|
+
# MIT License
|
|
4
|
+
#
|
|
5
|
+
# Copyright (c) 2025 Advanced Micro Devices, Inc.
|
|
6
|
+
#
|
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
# in the Software without restriction, including without limitation the rights
|
|
10
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
# furnished to do so, subject to the following conditions:
|
|
13
|
+
#
|
|
14
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
15
|
+
# copies or substantial portions of the Software.
|
|
16
|
+
#
|
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
|
+
# SOFTWARE.
|
|
24
|
+
#
|
|
25
|
+
###############################################################################
|
|
26
|
+
import io
|
|
27
|
+
import json
|
|
28
|
+
import os
|
|
29
|
+
import tarfile
|
|
30
|
+
from typing import TypeVar, Union
|
|
31
|
+
|
|
32
|
+
from pydantic import BaseModel, Field, field_validator
|
|
33
|
+
|
|
34
|
+
from nodescraper.utils import get_unique_filename
|
|
35
|
+
|
|
36
|
+
TDataModel = TypeVar("TDataModel", bound="DataModel")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class FileModel(BaseModel):
|
|
40
|
+
file_contents: bytes = Field(exclude=True)
|
|
41
|
+
file_name: str
|
|
42
|
+
|
|
43
|
+
@field_validator("file_contents", mode="before")
|
|
44
|
+
@classmethod
|
|
45
|
+
def file_contents_conformer(cls, value: Union[io.BytesIO, str, bytes]) -> bytes:
|
|
46
|
+
if isinstance(value, io.BytesIO):
|
|
47
|
+
return value.getvalue()
|
|
48
|
+
if isinstance(value, str):
|
|
49
|
+
return value.encode("utf-8")
|
|
50
|
+
return value
|
|
51
|
+
|
|
52
|
+
def log_model(self, log_path: str) -> None:
|
|
53
|
+
"""Log data model to a file
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
log_path (str): log path
|
|
57
|
+
"""
|
|
58
|
+
log_name = os.path.join(log_path, self.file_name)
|
|
59
|
+
with open(log_name, "wb") as log_file:
|
|
60
|
+
log_file.write(self.file_contents)
|
|
61
|
+
|
|
62
|
+
def file_contents_str(self) -> None:
|
|
63
|
+
return self.file_contents.decode("utf-8")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class DataModel(BaseModel):
|
|
67
|
+
"""Base class for data model, used to define structure of data collected from the system"""
|
|
68
|
+
|
|
69
|
+
def log_model(self, log_path: str):
|
|
70
|
+
"""Log data model to a file
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
log_path (str): log path
|
|
74
|
+
"""
|
|
75
|
+
log_name = os.path.join(
|
|
76
|
+
log_path,
|
|
77
|
+
get_unique_filename(log_path, f"{self.__class__.__name__.lower()}.json"),
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
exlude_fields = set()
|
|
81
|
+
for key in self.model_fields:
|
|
82
|
+
data = getattr(self, key)
|
|
83
|
+
if isinstance(data, FileModel):
|
|
84
|
+
data.log_model(log_path)
|
|
85
|
+
exlude_fields.add(key)
|
|
86
|
+
|
|
87
|
+
with open(log_name, "w", encoding="utf-8") as log_file:
|
|
88
|
+
log_file.write(self.model_dump_json(indent=2, exclude=exlude_fields))
|
|
89
|
+
|
|
90
|
+
def merge_data(self, input_data: "DataModel") -> None:
|
|
91
|
+
"""Merge data into current data"""
|
|
92
|
+
pass
|
|
93
|
+
|
|
94
|
+
@classmethod
|
|
95
|
+
def import_model(cls: type[TDataModel], model_input: Union[dict, str]) -> TDataModel:
|
|
96
|
+
"""import a data model
|
|
97
|
+
if the input is a string attempt to read data from file using the string as a file name
|
|
98
|
+
if input is a dict, pass key value pairs directly to init function
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
cls (type[DataModel]): Data model class
|
|
103
|
+
model_input (Union[dict, str]): model data input
|
|
104
|
+
|
|
105
|
+
Raises:
|
|
106
|
+
ValueError: if model_input has an invalid type
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
DataModel: instance of the data model
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
if isinstance(model_input, dict):
|
|
113
|
+
return cls(**model_input)
|
|
114
|
+
|
|
115
|
+
if isinstance(model_input, str):
|
|
116
|
+
# Build from tarfile if supported
|
|
117
|
+
if tarfile.is_tarfile(model_input):
|
|
118
|
+
return cls.build_from_tar(model_input)
|
|
119
|
+
# Build from folder if supported
|
|
120
|
+
if os.path.isdir(model_input):
|
|
121
|
+
return cls.build_from_folder(model_input)
|
|
122
|
+
|
|
123
|
+
# Build from json file
|
|
124
|
+
with open(model_input, "r", encoding="utf-8") as input_file:
|
|
125
|
+
data = json.load(input_file)
|
|
126
|
+
|
|
127
|
+
return cls(**data)
|
|
128
|
+
|
|
129
|
+
raise ValueError("Invalid input for model data")
|
|
130
|
+
|
|
131
|
+
@classmethod
|
|
132
|
+
def build_from_tar(cls: type[TDataModel], tar_path: str) -> TDataModel:
|
|
133
|
+
"""Placeholder for building data model from tarfile.
|
|
134
|
+
|
|
135
|
+
Intended for use with models that contains multiple FileModel attributes, and when collected they
|
|
136
|
+
are in a tarfile. This is left blank if the model requires this then this function should be implemented.
|
|
137
|
+
|
|
138
|
+
Parameters
|
|
139
|
+
----------
|
|
140
|
+
cls : type[DataModelGeneric@build_from_tar]
|
|
141
|
+
A DataModel class
|
|
142
|
+
tar_path : str
|
|
143
|
+
A path to a folder containing the data in format .tar.xz
|
|
144
|
+
|
|
145
|
+
Returns
|
|
146
|
+
-------
|
|
147
|
+
DataModelGeneric@build_from_tar
|
|
148
|
+
A datamodel object of type cls
|
|
149
|
+
"""
|
|
150
|
+
raise NotImplementedError("Model does not support construction from tar")
|
|
151
|
+
|
|
152
|
+
@classmethod
|
|
153
|
+
def build_from_folder(cls: type[TDataModel], folder_path: str) -> TDataModel:
|
|
154
|
+
"""Placeholder for building data model from folder.
|
|
155
|
+
|
|
156
|
+
Intended for use with models that contains multiple FileModel attributes. This is left blank
|
|
157
|
+
if that model requires this then this function should be implemented.
|
|
158
|
+
|
|
159
|
+
Parameters
|
|
160
|
+
----------
|
|
161
|
+
cls : type[DataModelGeneric@build_from_folder]
|
|
162
|
+
A DataModel class
|
|
163
|
+
folder_path : str
|
|
164
|
+
A path to a folder containing the data in format .tar.xz
|
|
165
|
+
|
|
166
|
+
Returns
|
|
167
|
+
-------
|
|
168
|
+
DataModelGeneric@build_from_folder
|
|
169
|
+
A datamodel object of type cls
|
|
170
|
+
"""
|
|
171
|
+
raise NotImplementedError("Model does not support construction from folder")
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
###############################################################################
|
|
2
|
+
#
|
|
3
|
+
# MIT License
|
|
4
|
+
#
|
|
5
|
+
# Copyright (c) 2025 Advanced Micro Devices, Inc.
|
|
6
|
+
#
|
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
# in the Software without restriction, including without limitation the rights
|
|
10
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
# furnished to do so, subject to the following conditions:
|
|
13
|
+
#
|
|
14
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
15
|
+
# copies or substantial portions of the Software.
|
|
16
|
+
#
|
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
|
+
# SOFTWARE.
|
|
24
|
+
#
|
|
25
|
+
###############################################################################
|
|
26
|
+
from typing import Optional
|
|
27
|
+
|
|
28
|
+
from pydantic import BaseModel
|
|
29
|
+
|
|
30
|
+
from .datamodel import DataModel
|
|
31
|
+
from .taskresult import TaskResult
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class DataPluginResult(BaseModel):
|
|
35
|
+
"""Object for result of data plugin"""
|
|
36
|
+
|
|
37
|
+
system_data: Optional[DataModel] = None
|
|
38
|
+
collection_result: Optional[TaskResult] = None
|
|
39
|
+
analysis_result: Optional[TaskResult] = None
|