bear-utils 0.7.11__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.
- bear_utils/__init__.py +13 -0
- bear_utils/ai/__init__.py +30 -0
- bear_utils/ai/ai_helpers/__init__.py +130 -0
- bear_utils/ai/ai_helpers/_common.py +19 -0
- bear_utils/ai/ai_helpers/_config.py +24 -0
- bear_utils/ai/ai_helpers/_parsers.py +188 -0
- bear_utils/ai/ai_helpers/_types.py +20 -0
- bear_utils/cache/__init__.py +119 -0
- bear_utils/cli/__init__.py +4 -0
- bear_utils/cli/commands.py +59 -0
- bear_utils/cli/prompt_helpers.py +166 -0
- bear_utils/cli/shell/__init__.py +0 -0
- bear_utils/cli/shell/_base_command.py +74 -0
- bear_utils/cli/shell/_base_shell.py +390 -0
- bear_utils/cli/shell/_common.py +19 -0
- bear_utils/config/__init__.py +11 -0
- bear_utils/config/config_manager.py +92 -0
- bear_utils/config/dir_manager.py +64 -0
- bear_utils/config/settings_manager.py +232 -0
- bear_utils/constants/__init__.py +16 -0
- bear_utils/constants/_exceptions.py +3 -0
- bear_utils/constants/_lazy_typing.py +15 -0
- bear_utils/constants/date_related.py +36 -0
- bear_utils/constants/time_related.py +22 -0
- bear_utils/database/__init__.py +6 -0
- bear_utils/database/_db_manager.py +104 -0
- bear_utils/events/__init__.py +16 -0
- bear_utils/events/events_class.py +52 -0
- bear_utils/events/events_module.py +65 -0
- bear_utils/extras/__init__.py +17 -0
- bear_utils/extras/_async_helpers.py +15 -0
- bear_utils/extras/_tools.py +178 -0
- bear_utils/extras/platform_utils.py +53 -0
- bear_utils/extras/wrappers/__init__.py +0 -0
- bear_utils/extras/wrappers/add_methods.py +98 -0
- bear_utils/files/__init__.py +4 -0
- bear_utils/files/file_handlers/__init__.py +3 -0
- bear_utils/files/file_handlers/_base_file_handler.py +93 -0
- bear_utils/files/file_handlers/file_handler_factory.py +278 -0
- bear_utils/files/file_handlers/json_file_handler.py +44 -0
- bear_utils/files/file_handlers/log_file_handler.py +33 -0
- bear_utils/files/file_handlers/txt_file_handler.py +34 -0
- bear_utils/files/file_handlers/yaml_file_handler.py +57 -0
- bear_utils/files/ignore_parser.py +298 -0
- bear_utils/graphics/__init__.py +4 -0
- bear_utils/graphics/bear_gradient.py +140 -0
- bear_utils/graphics/image_helpers.py +39 -0
- bear_utils/gui/__init__.py +3 -0
- bear_utils/gui/gui_tools/__init__.py +5 -0
- bear_utils/gui/gui_tools/_settings.py +37 -0
- bear_utils/gui/gui_tools/_types.py +12 -0
- bear_utils/gui/gui_tools/qt_app.py +145 -0
- bear_utils/gui/gui_tools/qt_color_picker.py +119 -0
- bear_utils/gui/gui_tools/qt_file_handler.py +138 -0
- bear_utils/gui/gui_tools/qt_input_dialog.py +306 -0
- bear_utils/logging/__init__.py +25 -0
- bear_utils/logging/logger_manager/__init__.py +0 -0
- bear_utils/logging/logger_manager/_common.py +47 -0
- bear_utils/logging/logger_manager/_console_junk.py +131 -0
- bear_utils/logging/logger_manager/_styles.py +91 -0
- bear_utils/logging/logger_manager/loggers/__init__.py +0 -0
- bear_utils/logging/logger_manager/loggers/_base_logger.py +238 -0
- bear_utils/logging/logger_manager/loggers/_base_logger.pyi +50 -0
- bear_utils/logging/logger_manager/loggers/_buffer_logger.py +55 -0
- bear_utils/logging/logger_manager/loggers/_console_logger.py +249 -0
- bear_utils/logging/logger_manager/loggers/_console_logger.pyi +64 -0
- bear_utils/logging/logger_manager/loggers/_file_logger.py +141 -0
- bear_utils/logging/logger_manager/loggers/_level_sin.py +58 -0
- bear_utils/logging/logger_manager/loggers/_logger.py +18 -0
- bear_utils/logging/logger_manager/loggers/_sub_logger.py +110 -0
- bear_utils/logging/logger_manager/loggers/_sub_logger.pyi +38 -0
- bear_utils/logging/loggers.py +76 -0
- bear_utils/monitoring/__init__.py +10 -0
- bear_utils/monitoring/host_monitor.py +350 -0
- bear_utils/time/__init__.py +16 -0
- bear_utils/time/_helpers.py +91 -0
- bear_utils/time/_time_class.py +316 -0
- bear_utils/time/_timer.py +80 -0
- bear_utils/time/_tools.py +17 -0
- bear_utils/time/time_manager.py +218 -0
- bear_utils-0.7.11.dist-info/METADATA +260 -0
- bear_utils-0.7.11.dist-info/RECORD +83 -0
- bear_utils-0.7.11.dist-info/WHEEL +4 -0
@@ -0,0 +1,76 @@
|
|
1
|
+
from ..constants.date_related import DATE_TIME_FORMAT
|
2
|
+
from .logger_manager._common import FIVE_MEGABYTES, SIMPLE_FORMAT, VERBOSE_CONSOLE_FORMAT, VERBOSE_FORMAT, ExecValues
|
3
|
+
from .logger_manager._console_junk import ConsoleBuffering, ConsoleFormatter, ConsoleHandler
|
4
|
+
from .logger_manager._styles import DEFAULT_THEME, LOGGER_METHODS, VERBOSE, LoggerExtraInfo, get_method
|
5
|
+
from .logger_manager.loggers._base_logger import BaseLogger
|
6
|
+
from .logger_manager.loggers._buffer_logger import BufferLogger
|
7
|
+
from .logger_manager.loggers._console_logger import ConsoleLogger
|
8
|
+
from .logger_manager.loggers._file_logger import FileLogger
|
9
|
+
from .logger_manager.loggers._sub_logger import SubConsoleLogger
|
10
|
+
|
11
|
+
AllLoggers = BaseLogger | ConsoleLogger | SubConsoleLogger
|
12
|
+
Loggers = BaseLogger | ConsoleLogger
|
13
|
+
|
14
|
+
|
15
|
+
def get_logger(
|
16
|
+
console: bool = True,
|
17
|
+
file: bool = False,
|
18
|
+
queue_handler: bool = False,
|
19
|
+
buffering: bool = False,
|
20
|
+
**kwargs,
|
21
|
+
) -> BaseLogger | ConsoleLogger | BufferError | FileLogger:
|
22
|
+
"""Get a logger instance based on the specified parameters.
|
23
|
+
|
24
|
+
Args:
|
25
|
+
name (str): The name of the logger.
|
26
|
+
level (int): The logging level.
|
27
|
+
console (bool): Whether to enable console logging.
|
28
|
+
file (bool): Whether to enable file logging.
|
29
|
+
queue_handler (bool): Whether to use a queue handler.
|
30
|
+
buffering (bool): Whether to enable buffering.
|
31
|
+
style_disabled (bool): Whether to disable styling.
|
32
|
+
logger_mode (bool): Whether the logger is in logger mode.
|
33
|
+
**kwargs: Additional keyword arguments for customization.
|
34
|
+
Returns:
|
35
|
+
BaseLogger | ConsoleLogger | BufferLogger| FileLogger: An instance of the appropriate logger.
|
36
|
+
"""
|
37
|
+
if (not console and not file) and buffering:
|
38
|
+
return BufferLogger(queue_handler=queue_handler, **kwargs)
|
39
|
+
elif (console and file) or (console and buffering):
|
40
|
+
return ConsoleLogger(queue_handler=queue_handler, buffering=buffering, **kwargs)
|
41
|
+
elif not console and not buffering and file:
|
42
|
+
return FileLogger(queue_handler=queue_handler, **kwargs)
|
43
|
+
else:
|
44
|
+
return BaseLogger(**kwargs)
|
45
|
+
|
46
|
+
|
47
|
+
def get_console(namespace: str) -> tuple[BaseLogger, SubConsoleLogger]:
|
48
|
+
"""
|
49
|
+
Get a console logger and a sub-logger for a specific namespace.
|
50
|
+
|
51
|
+
Args:
|
52
|
+
namespace (str): The namespace for the sub-logger.
|
53
|
+
|
54
|
+
Returns:
|
55
|
+
tuple[BaseLogger, SubConsoleLogger]: A tuple containing the base logger and the sub-logger.
|
56
|
+
"""
|
57
|
+
base_logger = BaseLogger.get_instance(init=True)
|
58
|
+
sub_logger = SubConsoleLogger(logger=base_logger, namespace=namespace)
|
59
|
+
return base_logger, sub_logger
|
60
|
+
|
61
|
+
|
62
|
+
def get_sub_logger(logger: BaseLogger | ConsoleLogger, namespace: str) -> SubConsoleLogger[BaseLogger | ConsoleLogger]:
|
63
|
+
"""
|
64
|
+
Get a sub-logger for a specific namespace.
|
65
|
+
|
66
|
+
Args:
|
67
|
+
logger (BaseLogger): The parent logger.
|
68
|
+
namespace (str): The namespace for the sub-logger.
|
69
|
+
|
70
|
+
Returns:
|
71
|
+
SubConsoleLogger: A sub-logger instance.
|
72
|
+
"""
|
73
|
+
if not isinstance(logger, (BaseLogger, ConsoleLogger)):
|
74
|
+
raise TypeError("Expected logger to be an instance of BaseLogger or ConsoleLogger")
|
75
|
+
|
76
|
+
return SubConsoleLogger(logger=logger, namespace=namespace)
|
@@ -0,0 +1,350 @@
|
|
1
|
+
import asyncio
|
2
|
+
import subprocess
|
3
|
+
from asyncio import Task
|
4
|
+
from collections import deque
|
5
|
+
from collections.abc import Awaitable, Callable
|
6
|
+
from dataclasses import dataclass
|
7
|
+
from enum import StrEnum
|
8
|
+
from typing import Literal, Self, TypedDict, cast, overload
|
9
|
+
|
10
|
+
from ..logging.loggers import BaseLogger, SubConsoleLogger, get_console
|
11
|
+
|
12
|
+
ROLLING_AVERAGE_TIME = 300
|
13
|
+
|
14
|
+
_base_logger, console = get_console("HostMonitor")
|
15
|
+
|
16
|
+
|
17
|
+
class TaskChoice(StrEnum):
|
18
|
+
"""Enum for task choices."""
|
19
|
+
|
20
|
+
CPU = "cpu"
|
21
|
+
MEM = "mem"
|
22
|
+
DISK = "disk"
|
23
|
+
GPU = "gpu"
|
24
|
+
|
25
|
+
|
26
|
+
CPU = TaskChoice.CPU
|
27
|
+
MEM = TaskChoice.MEM
|
28
|
+
DISK = TaskChoice.DISK
|
29
|
+
GPU = TaskChoice.GPU
|
30
|
+
|
31
|
+
CPU_MEM: list[TaskChoice] = [CPU, MEM]
|
32
|
+
CPU_MEM_GPU: list[TaskChoice] = [CPU, MEM, GPU]
|
33
|
+
ALL_TASKS: list[TaskChoice] = [CPU, MEM, DISK, GPU]
|
34
|
+
|
35
|
+
|
36
|
+
def has_nvidia_gpu() -> bool:
|
37
|
+
"""Check if the system has an NVIDIA GPU."""
|
38
|
+
try:
|
39
|
+
result = subprocess.run(
|
40
|
+
args=["nvidia-smi", "--query-gpu=name", "--format=csv,noheader"],
|
41
|
+
capture_output=True,
|
42
|
+
text=True,
|
43
|
+
)
|
44
|
+
return result.returncode == 0
|
45
|
+
except Exception as e:
|
46
|
+
console.error(f"Error checking for NVIDIA GPU: {e}", exc_info=True)
|
47
|
+
return False
|
48
|
+
|
49
|
+
|
50
|
+
@dataclass(slots=True)
|
51
|
+
class GPUSamples:
|
52
|
+
"""GPU samples for monitoring."""
|
53
|
+
|
54
|
+
gpu_usage: float = 0.0
|
55
|
+
gpu_mem_usage: float = 0.0
|
56
|
+
gpu_mem_total: float = 0.0
|
57
|
+
gpu_mem_free: float = 0.0
|
58
|
+
|
59
|
+
def __post_init__(self) -> None:
|
60
|
+
if self.gpu_mem_total == 0:
|
61
|
+
self.gpu_mem_total = self.gpu_mem_usage + self.gpu_mem_free
|
62
|
+
|
63
|
+
@classmethod
|
64
|
+
async def get_gpu_samples(cls) -> Self:
|
65
|
+
"""Get GPU samples using nvidia-smi."""
|
66
|
+
try:
|
67
|
+
result = subprocess.run(
|
68
|
+
args=[
|
69
|
+
"nvidia-smi",
|
70
|
+
"--query-gpu=utilization.gpu,memory.used,memory.total,memory.free",
|
71
|
+
"--format=csv,noheader",
|
72
|
+
],
|
73
|
+
capture_output=True,
|
74
|
+
text=True,
|
75
|
+
)
|
76
|
+
if result.returncode != 0:
|
77
|
+
raise RuntimeError("nvidia-smi command failed")
|
78
|
+
gpu_usage, gpu_mem_used, gpu_mem_total, gpu_mem_free = map(float, result.stdout.split(","))
|
79
|
+
return cls(
|
80
|
+
gpu_usage=gpu_usage,
|
81
|
+
gpu_mem_usage=gpu_mem_used,
|
82
|
+
gpu_mem_total=gpu_mem_total,
|
83
|
+
gpu_mem_free=gpu_mem_free,
|
84
|
+
)
|
85
|
+
except Exception as e:
|
86
|
+
console.error(f"Error getting GPU samples: {e}", exc_info=True)
|
87
|
+
return cls()
|
88
|
+
|
89
|
+
|
90
|
+
@dataclass(slots=True)
|
91
|
+
class HostMonitorResult:
|
92
|
+
# CPU
|
93
|
+
cpu_usage_avg: float = 0.0
|
94
|
+
cpu_max: float = 0.0
|
95
|
+
cpu_min: float = 0.0
|
96
|
+
|
97
|
+
mem_usage_avg: float = 0.0
|
98
|
+
mem_max: float = 0.0
|
99
|
+
mem_min: float = 0.0
|
100
|
+
|
101
|
+
disk_usage_avg: float = 0.0
|
102
|
+
|
103
|
+
gpu_usage_avg: float = 0.0
|
104
|
+
gpu_usage_max: float = 0.0
|
105
|
+
gpu_usage_min: float = 0.0
|
106
|
+
|
107
|
+
gpu_mem_usage_avg: float = 0.0
|
108
|
+
gpu_mem_usage_max: float = 0.0
|
109
|
+
gpu_mem_usage_min: float = 0.0
|
110
|
+
|
111
|
+
@property
|
112
|
+
def empty(self) -> bool:
|
113
|
+
"""Check if the result is empty."""
|
114
|
+
return not self.has_cpu_data and not self.has_mem_data and not self.has_disk_data and not self.has_gpu_data
|
115
|
+
|
116
|
+
@property
|
117
|
+
def has_cpu_data(self) -> bool:
|
118
|
+
"""Check if CPU data is available."""
|
119
|
+
return self.cpu_usage_avg != 0.0 or self.cpu_max != 0.0 or self.cpu_min != 0.0
|
120
|
+
|
121
|
+
@property
|
122
|
+
def has_mem_data(self) -> bool:
|
123
|
+
"""Check if memory data is available."""
|
124
|
+
return self.mem_usage_avg != 0.0 or self.mem_max != 0.0 or self.mem_min != 0.0
|
125
|
+
|
126
|
+
@property
|
127
|
+
def has_disk_data(self) -> bool:
|
128
|
+
"""Check if disk data is available."""
|
129
|
+
return self.disk_usage_avg != 0.0
|
130
|
+
|
131
|
+
@property
|
132
|
+
def has_gpu_data(self) -> bool:
|
133
|
+
"""Check if GPU data is available."""
|
134
|
+
return (
|
135
|
+
self.gpu_usage_avg != 0.0
|
136
|
+
or self.gpu_usage_max != 0.0
|
137
|
+
or self.gpu_usage_min != 0.0
|
138
|
+
or self.gpu_mem_usage_avg != 0.0
|
139
|
+
)
|
140
|
+
|
141
|
+
|
142
|
+
class SampleStore(TypedDict):
|
143
|
+
samples: deque[float | GPUSamples]
|
144
|
+
getter: Callable[[], Awaitable[float | GPUSamples]]
|
145
|
+
|
146
|
+
|
147
|
+
class HostMonitor:
|
148
|
+
def __init__(self, sample_interval: float = 1, tasks: list[TaskChoice] = [CPU, MEM]):
|
149
|
+
self.console = console
|
150
|
+
self.disk_path = "/"
|
151
|
+
self.sample_stores: dict[TaskChoice, SampleStore] = {}
|
152
|
+
self.tasks: list[TaskChoice] = tasks
|
153
|
+
|
154
|
+
if self.is_task_enabled(GPU):
|
155
|
+
if not has_nvidia_gpu():
|
156
|
+
self.tasks.remove(TaskChoice.GPU)
|
157
|
+
self.console.warning("No NVIDIA GPU detected, removing GPU task from monitoring.")
|
158
|
+
|
159
|
+
self.sample_stores = {
|
160
|
+
task: {
|
161
|
+
"samples": deque(maxlen=int(ROLLING_AVERAGE_TIME // sample_interval)),
|
162
|
+
"getter": getattr(self, f"get_{task.value}"),
|
163
|
+
}
|
164
|
+
for task in tasks
|
165
|
+
}
|
166
|
+
|
167
|
+
self.sampling_task: Task | None = None
|
168
|
+
self.is_monitoring: bool = False
|
169
|
+
self.sample_interval = sample_interval
|
170
|
+
self.last_result = HostMonitorResult()
|
171
|
+
|
172
|
+
self.console.verbose("HostMonitor initialized")
|
173
|
+
|
174
|
+
def is_task_enabled(self, task: TaskChoice) -> bool:
|
175
|
+
"""Check if a specific task is enabled."""
|
176
|
+
return task in self.tasks
|
177
|
+
|
178
|
+
async def start(self) -> None:
|
179
|
+
self.is_monitoring = True
|
180
|
+
self.sampling_task = asyncio.create_task(self._collect_samples())
|
181
|
+
|
182
|
+
async def stop(self) -> None:
|
183
|
+
self.is_monitoring = False
|
184
|
+
if self.sampling_task:
|
185
|
+
self.sampling_task.cancel()
|
186
|
+
try:
|
187
|
+
await self.sampling_task
|
188
|
+
except asyncio.CancelledError:
|
189
|
+
pass
|
190
|
+
self.sampling_task = None
|
191
|
+
|
192
|
+
async def clear(self) -> None:
|
193
|
+
for store in self.sample_stores.values():
|
194
|
+
store["samples"].clear()
|
195
|
+
|
196
|
+
@overload
|
197
|
+
async def get(self, task: Literal[TaskChoice.CPU, TaskChoice.MEM, TaskChoice.DISK]) -> float: ...
|
198
|
+
|
199
|
+
@overload
|
200
|
+
async def get(self, task: Literal[TaskChoice.GPU]) -> GPUSamples: ...
|
201
|
+
|
202
|
+
async def get(self, task: TaskChoice) -> float | GPUSamples:
|
203
|
+
"""Manually get a sample for the specified task."""
|
204
|
+
getter_func = self.sample_stores[task]["getter"]
|
205
|
+
if getter_func is None:
|
206
|
+
self.console.error(f"Getter method for task {task} is None.")
|
207
|
+
return 0.0
|
208
|
+
result: float | GPUSamples | None = await getter_func()
|
209
|
+
if result is not None:
|
210
|
+
if isinstance(result, GPUSamples):
|
211
|
+
return result
|
212
|
+
return float(result)
|
213
|
+
|
214
|
+
async def _collect_samples(self) -> None:
|
215
|
+
await self.clear()
|
216
|
+
while self.is_monitoring:
|
217
|
+
await self._record_data()
|
218
|
+
await asyncio.sleep(self.sample_interval)
|
219
|
+
|
220
|
+
async def _record_data(self) -> None:
|
221
|
+
for task in self.tasks:
|
222
|
+
getter_func = self.sample_stores[task]["getter"]
|
223
|
+
if getter_func is None:
|
224
|
+
self.console.error(f"Getter method for task {task} is None.")
|
225
|
+
continue
|
226
|
+
result: float | GPUSamples | None = await getter_func()
|
227
|
+
if result is not None:
|
228
|
+
self.sample_stores[task]["samples"].append(result)
|
229
|
+
|
230
|
+
@overload
|
231
|
+
async def _get_samples(self, task: Literal[TaskChoice.CPU, TaskChoice.MEM, TaskChoice.DISK]) -> list[float]: ...
|
232
|
+
|
233
|
+
@overload
|
234
|
+
async def _get_samples(self, task: Literal[TaskChoice.GPU]) -> list[GPUSamples]: ...
|
235
|
+
|
236
|
+
async def _get_samples(self, task: TaskChoice) -> list[float] | list[GPUSamples]:
|
237
|
+
"""Get collected samples for the specified task."""
|
238
|
+
if not self.is_monitoring or not self.sample_stores.get(task):
|
239
|
+
return [0.0]
|
240
|
+
try:
|
241
|
+
sample = list(self.sample_stores[task]["samples"])
|
242
|
+
return cast(list[GPUSamples], sample) if task == GPU else cast(list[float], sample)
|
243
|
+
except Exception as e:
|
244
|
+
self.console.error(f"Error getting {task} samples: {e}", exc_info=True)
|
245
|
+
return [0.0]
|
246
|
+
|
247
|
+
async def get_sample(self, task: TaskChoice) -> float:
|
248
|
+
"""Get a single sample for the specified task."""
|
249
|
+
if not self.is_monitoring or not self.sample_stores.get(task):
|
250
|
+
return 0.0
|
251
|
+
try:
|
252
|
+
result: list[float] | list[GPUSamples] = await self._get_samples(task)
|
253
|
+
if not result:
|
254
|
+
return 0.0
|
255
|
+
if task == GPU and isinstance(result[0], GPUSamples):
|
256
|
+
first_result: GPUSamples = result[0]
|
257
|
+
return first_result.gpu_usage if isinstance(first_result, GPUSamples) else 0.0
|
258
|
+
else:
|
259
|
+
return result[0] if isinstance(result[0], float) else 0.0
|
260
|
+
except Exception as e:
|
261
|
+
self.console.error(f"Error getting single {task} sample: {e}", exc_info=True)
|
262
|
+
return 0.0
|
263
|
+
|
264
|
+
@property
|
265
|
+
def is_running(self) -> bool:
|
266
|
+
"""Check if the monitor is running."""
|
267
|
+
return self.is_monitoring and self.sampling_task is not None and bool(self.sample_stores)
|
268
|
+
|
269
|
+
async def get_current_samples(self) -> HostMonitorResult:
|
270
|
+
result = HostMonitorResult()
|
271
|
+
if not self.is_running:
|
272
|
+
return result
|
273
|
+
try:
|
274
|
+
if self.is_task_enabled(CPU):
|
275
|
+
cpu_samples: list[float] = await self._get_samples(TaskChoice.CPU)
|
276
|
+
if cpu_samples:
|
277
|
+
result.cpu_usage_avg = round(sum(cpu_samples) / len(cpu_samples), 2)
|
278
|
+
result.cpu_max = max(cpu_samples)
|
279
|
+
result.cpu_min = min(cpu_samples)
|
280
|
+
if self.is_task_enabled(MEM):
|
281
|
+
mem_samples: list[float] = await self._get_samples(TaskChoice.MEM)
|
282
|
+
if mem_samples:
|
283
|
+
result.mem_usage_avg = round(sum(mem_samples) / len(mem_samples), 2)
|
284
|
+
result.mem_max = max(mem_samples)
|
285
|
+
result.mem_min = min(mem_samples)
|
286
|
+
if self.is_task_enabled(DISK):
|
287
|
+
disk_samples: list[float] = await self._get_samples(TaskChoice.DISK)
|
288
|
+
if disk_samples:
|
289
|
+
result.disk_usage_avg = round(sum(disk_samples) / len(disk_samples), 2)
|
290
|
+
if self.is_task_enabled(GPU):
|
291
|
+
gpu_samples: list[GPUSamples] = await self._get_samples(TaskChoice.GPU)
|
292
|
+
if gpu_samples:
|
293
|
+
gpu_usage: list[float] = [sample.gpu_usage for sample in gpu_samples]
|
294
|
+
result.gpu_usage_avg = round(sum(gpu_usage) / len(gpu_usage), 2)
|
295
|
+
result.gpu_usage_max = max(gpu_usage)
|
296
|
+
result.gpu_usage_min = min(gpu_usage)
|
297
|
+
self.last_result: HostMonitorResult = result
|
298
|
+
except Exception as e:
|
299
|
+
self.console.error(f"Error getting current samples: {e}", exc_info=True)
|
300
|
+
return result
|
301
|
+
|
302
|
+
async def get_avg_cpu_temp(self) -> float:
|
303
|
+
if not self.is_monitoring or not self.sample_stores.get(CPU):
|
304
|
+
return 0.0
|
305
|
+
try:
|
306
|
+
current_cpu_samples: list[float] = await self._get_samples(TaskChoice.CPU)
|
307
|
+
if current_cpu_samples:
|
308
|
+
average_cpu = round(sum(current_cpu_samples) / len(current_cpu_samples), 2)
|
309
|
+
return average_cpu
|
310
|
+
except Exception as e:
|
311
|
+
print(f"Error getting CPU temperature: {e}")
|
312
|
+
return 0.0
|
313
|
+
|
314
|
+
async def get_avg_mem_usage(self) -> float:
|
315
|
+
if not self.is_monitoring or not self.sample_stores.get(MEM):
|
316
|
+
return 0.0
|
317
|
+
try:
|
318
|
+
current_mem_samples: list[float] = await self._get_samples(TaskChoice.MEM)
|
319
|
+
if current_mem_samples:
|
320
|
+
average_mem: float = round(sum(current_mem_samples) / len(current_mem_samples), 2)
|
321
|
+
return average_mem
|
322
|
+
except Exception as e:
|
323
|
+
print(f"Error getting memory usage: {e}")
|
324
|
+
return 0.0
|
325
|
+
|
326
|
+
async def get_disk_usage(self) -> float:
|
327
|
+
if not self.is_monitoring or not self.sample_stores.get(DISK):
|
328
|
+
return 0.0
|
329
|
+
try:
|
330
|
+
current_disk_samples: list[float] = await self._get_samples(TaskChoice.DISK)
|
331
|
+
if current_disk_samples:
|
332
|
+
average_disk: float = round(sum(current_disk_samples) / len(current_disk_samples), 2)
|
333
|
+
return average_disk
|
334
|
+
except Exception as e:
|
335
|
+
print(f"Error getting disk usage: {e}")
|
336
|
+
return 0.0
|
337
|
+
|
338
|
+
async def get_avg_gpu_usage(self) -> float:
|
339
|
+
if not self.is_monitoring or not self.sample_stores.get(GPU):
|
340
|
+
return 0.0
|
341
|
+
try:
|
342
|
+
current_gpu_samples: list[GPUSamples] = await self._get_samples(TaskChoice.GPU)
|
343
|
+
if current_gpu_samples:
|
344
|
+
average_gpu: float = round(
|
345
|
+
sum(sample.gpu_usage for sample in current_gpu_samples) / len(current_gpu_samples), 2
|
346
|
+
)
|
347
|
+
return average_gpu
|
348
|
+
except Exception as e:
|
349
|
+
print(f"Error getting GPU usage: {e}")
|
350
|
+
return 0.0
|
@@ -0,0 +1,16 @@
|
|
1
|
+
from ..constants.date_related import DATE_FORMAT, DATE_TIME_FORMAT
|
2
|
+
from ._time_class import EpochTimestamp
|
3
|
+
from ._timer import TimerData, create_timer, timer
|
4
|
+
from ._tools import add_ord_suffix
|
5
|
+
from .time_manager import TimeTools
|
6
|
+
|
7
|
+
__all__ = [
|
8
|
+
"EpochTimestamp",
|
9
|
+
"TimerData",
|
10
|
+
"create_timer",
|
11
|
+
"timer",
|
12
|
+
"TimeTools",
|
13
|
+
"add_ord_suffix",
|
14
|
+
"DATE_FORMAT",
|
15
|
+
"DATE_TIME_FORMAT",
|
16
|
+
]
|
@@ -0,0 +1,91 @@
|
|
1
|
+
import re
|
2
|
+
from datetime import timedelta
|
3
|
+
|
4
|
+
from ..constants.time_related import SECONDS_IN_DAY, SECONDS_IN_HOUR, SECONDS_IN_MINUTE, SECONDS_IN_MONTH
|
5
|
+
|
6
|
+
|
7
|
+
def convert_to_seconds(time_str: str) -> int:
|
8
|
+
"""Convert a time string to seconds.
|
9
|
+
|
10
|
+
Examples
|
11
|
+
--------
|
12
|
+
>>> convert_to_seconds("1M 30m")
|
13
|
+
2610000
|
14
|
+
|
15
|
+
Notes
|
16
|
+
-----
|
17
|
+
* ``M`` or ``mo`` denotes **months**.
|
18
|
+
* ``m`` denotes **minutes**.
|
19
|
+
"""
|
20
|
+
|
21
|
+
time_parts: list[tuple[str, str]] = re.findall(r"(\d+)\s*(M|mo|[dhms])", time_str)
|
22
|
+
total_seconds = 0
|
23
|
+
for value, unit in time_parts:
|
24
|
+
if not value.isdigit():
|
25
|
+
raise ValueError(f"Invalid time value: {value}")
|
26
|
+
value = int(value)
|
27
|
+
|
28
|
+
if unit == "M" or unit.lower() == "mo":
|
29
|
+
total_seconds += value * SECONDS_IN_MONTH
|
30
|
+
elif unit == "d":
|
31
|
+
total_seconds += value * SECONDS_IN_DAY
|
32
|
+
elif unit == "h":
|
33
|
+
total_seconds += value * SECONDS_IN_HOUR
|
34
|
+
elif unit == "m":
|
35
|
+
total_seconds += value * SECONDS_IN_MINUTE
|
36
|
+
elif unit == "s":
|
37
|
+
total_seconds += value
|
38
|
+
else:
|
39
|
+
raise ValueError(f"Invalid time unit: {unit}")
|
40
|
+
return total_seconds
|
41
|
+
|
42
|
+
|
43
|
+
def timedelta_to_seconds(td: timedelta) -> int:
|
44
|
+
"""Convert a timedelta object to seconds."""
|
45
|
+
if not isinstance(td, timedelta):
|
46
|
+
raise ValueError("Input must be a timedelta object")
|
47
|
+
return int(td.total_seconds())
|
48
|
+
|
49
|
+
|
50
|
+
def convert_to_milliseconds(time_str: str) -> int:
|
51
|
+
return convert_to_seconds(time_str) * 1000
|
52
|
+
|
53
|
+
|
54
|
+
def milliseconds_to_time(milliseconds: int) -> str:
|
55
|
+
"""Convert milliseconds to a human-readable time string."""
|
56
|
+
if milliseconds < 0:
|
57
|
+
raise ValueError("Milliseconds cannot be negative")
|
58
|
+
seconds = milliseconds // 1000
|
59
|
+
return seconds_to_time(seconds)
|
60
|
+
|
61
|
+
|
62
|
+
def seconds_to_timedelta(seconds: int) -> timedelta:
|
63
|
+
"""Convert seconds to a timedelta object."""
|
64
|
+
if seconds < 0:
|
65
|
+
raise ValueError("Seconds cannot be negative")
|
66
|
+
return timedelta(seconds=seconds)
|
67
|
+
|
68
|
+
|
69
|
+
def seconds_to_time(seconds: int) -> str:
|
70
|
+
"""Convert seconds to a human-readable time string.
|
71
|
+
|
72
|
+
Months are represented with ``M`` while minutes use ``m``.
|
73
|
+
"""
|
74
|
+
if seconds < 0:
|
75
|
+
raise ValueError("Seconds cannot be negative")
|
76
|
+
months, reminder = divmod(seconds, SECONDS_IN_MONTH)
|
77
|
+
days, reminder = divmod(reminder, SECONDS_IN_DAY)
|
78
|
+
hours, reminder = divmod(reminder, SECONDS_IN_HOUR)
|
79
|
+
minutes, seconds = divmod(reminder, SECONDS_IN_MINUTE)
|
80
|
+
time_parts = []
|
81
|
+
if months > 0:
|
82
|
+
time_parts.append(f"{months}M")
|
83
|
+
if days > 0:
|
84
|
+
time_parts.append(f"{days}d")
|
85
|
+
if hours > 0:
|
86
|
+
time_parts.append(f"{hours}h")
|
87
|
+
if minutes > 0:
|
88
|
+
time_parts.append(f"{minutes}m")
|
89
|
+
if seconds > 0:
|
90
|
+
time_parts.append(f"{seconds}s")
|
91
|
+
return " ".join(time_parts)
|