Open-AutoTools 0.0.3rc5__py3-none-any.whl → 0.0.4rc1__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.
- autotools/autocaps/commands.py +3 -7
- autotools/autocaps/core.py +5 -4
- autotools/autoip/commands.py +6 -11
- autotools/autoip/core.py +151 -200
- autotools/autolower/commands.py +3 -7
- autotools/autolower/core.py +4 -3
- autotools/autopassword/commands.py +27 -33
- autotools/autopassword/core.py +32 -73
- autotools/autotest/__init__.py +2 -0
- autotools/autotest/commands.py +205 -0
- autotools/cli.py +123 -62
- autotools/utils/commands.py +13 -0
- autotools/utils/loading.py +14 -6
- autotools/utils/performance.py +392 -0
- autotools/utils/updates.py +30 -22
- autotools/utils/version.py +69 -63
- open_autotools-0.0.4rc1.dist-info/METADATA +103 -0
- open_autotools-0.0.4rc1.dist-info/RECORD +28 -0
- {Open_AutoTools-0.0.3rc5.dist-info → open_autotools-0.0.4rc1.dist-info}/WHEEL +1 -1
- {Open_AutoTools-0.0.3rc5.dist-info → open_autotools-0.0.4rc1.dist-info}/entry_points.txt +0 -3
- Open_AutoTools-0.0.3rc5.dist-info/METADATA +0 -317
- Open_AutoTools-0.0.3rc5.dist-info/RECORD +0 -44
- autotools/autocaps/tests/__init__.py +0 -1
- autotools/autocaps/tests/test_autocaps_core.py +0 -45
- autotools/autocaps/tests/test_autocaps_integration.py +0 -46
- autotools/autodownload/__init__.py +0 -0
- autotools/autodownload/commands.py +0 -38
- autotools/autodownload/core.py +0 -433
- autotools/autoip/tests/__init__.py +0 -1
- autotools/autoip/tests/test_autoip_core.py +0 -72
- autotools/autoip/tests/test_autoip_integration.py +0 -92
- autotools/autolower/tests/__init__.py +0 -1
- autotools/autolower/tests/test_autolower_core.py +0 -45
- autotools/autolower/tests/test_autolower_integration.py +0 -46
- autotools/autospell/__init__.py +0 -3
- autotools/autospell/commands.py +0 -123
- autotools/autospell/core.py +0 -222
- autotools/autotranslate/__init__.py +0 -3
- autotools/autotranslate/commands.py +0 -42
- autotools/autotranslate/core.py +0 -52
- autotools/test/__init__.py +0 -3
- autotools/test/commands.py +0 -118
- {Open_AutoTools-0.0.3rc5.dist-info → open_autotools-0.0.4rc1.dist-info/licenses}/LICENSE +0 -0
- {Open_AutoTools-0.0.3rc5.dist-info → open_autotools-0.0.4rc1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import gc
|
|
3
|
+
import sys
|
|
4
|
+
import time
|
|
5
|
+
import click
|
|
6
|
+
import resource
|
|
7
|
+
import tracemalloc
|
|
8
|
+
from contextlib import contextmanager
|
|
9
|
+
from typing import Dict, List, Tuple, Optional
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
import psutil
|
|
13
|
+
PSUTIL_AVAILABLE = True
|
|
14
|
+
except ImportError:
|
|
15
|
+
PSUTIL_AVAILABLE = False
|
|
16
|
+
|
|
17
|
+
# GLOBAL FLAG TO ENABLE/DISABLE PERFORMANCE METRICS
|
|
18
|
+
ENABLE_PERFORMANCE_METRICS = False
|
|
19
|
+
if os.getenv('AUTOTOOLS_DISABLE_PERF', '').lower() in ('1', 'true', 'yes'): ENABLE_PERFORMANCE_METRICS = False
|
|
20
|
+
|
|
21
|
+
# FLAG TO ENABLE/DISABLE TRACEMALLOC (CAN BE SLOW IN PRODUCTION)
|
|
22
|
+
# ENABLE BY DEFAULT IN TEST ENVIRONMENTS (DETECTED BY PRESENCE OF PYTEST OR TEST IN SYS.ARGV)
|
|
23
|
+
_ENV_TRACEMALLOC = os.getenv('AUTOTOOLS_ENABLE_TRACEMALLOC', '').lower() in ('1', 'true', 'yes')
|
|
24
|
+
_IS_TEST_ENV = 'pytest' in sys.modules or any('test' in arg.lower() or 'pytest' in arg.lower() for arg in sys.argv)
|
|
25
|
+
ENABLE_TRACEMALLOC = _ENV_TRACEMALLOC or _IS_TEST_ENV
|
|
26
|
+
|
|
27
|
+
# PERFORMANCE METRICS COLLECTOR
|
|
28
|
+
class PerformanceMetrics:
|
|
29
|
+
def __init__(self):
|
|
30
|
+
self.reset()
|
|
31
|
+
self.steps: List[Tuple[str, float]] = []
|
|
32
|
+
self._step_start: Optional[float] = None
|
|
33
|
+
self._current_step: Optional[str] = None
|
|
34
|
+
|
|
35
|
+
def reset(self):
|
|
36
|
+
# TIMING METRICS
|
|
37
|
+
self.startup_start = None
|
|
38
|
+
self.startup_end = None
|
|
39
|
+
self.command_start = None
|
|
40
|
+
self.command_end = None
|
|
41
|
+
self.process_start = None
|
|
42
|
+
self.process_end = None
|
|
43
|
+
|
|
44
|
+
# CPU METRICS
|
|
45
|
+
self.cpu_user_start = None
|
|
46
|
+
self.cpu_sys_start = None
|
|
47
|
+
self.cpu_user_end = None
|
|
48
|
+
self.cpu_sys_end = None
|
|
49
|
+
|
|
50
|
+
# MEMORY METRICS
|
|
51
|
+
self.rss_start = None
|
|
52
|
+
self.rss_peak = None
|
|
53
|
+
|
|
54
|
+
# ALLOCATION TRACKING
|
|
55
|
+
self.tracemalloc_started = False
|
|
56
|
+
self.alloc_start = None
|
|
57
|
+
self.alloc_end = None
|
|
58
|
+
|
|
59
|
+
# GARBAGE COLLECTION METRICS
|
|
60
|
+
self.gc_start_stats = None
|
|
61
|
+
self.gc_end_stats = None
|
|
62
|
+
|
|
63
|
+
# FILESYSTEM I/O METRICS
|
|
64
|
+
self.fs_read_start = None
|
|
65
|
+
self.fs_write_start = None
|
|
66
|
+
self.fs_read_end = None
|
|
67
|
+
self.fs_write_end = None
|
|
68
|
+
self.fs_ops_start = None
|
|
69
|
+
self.fs_ops_end = None
|
|
70
|
+
|
|
71
|
+
# STEP TRACKING
|
|
72
|
+
self.steps = []
|
|
73
|
+
self._step_start = None
|
|
74
|
+
self._current_step = None
|
|
75
|
+
|
|
76
|
+
# STARTS PROCESS-LEVEL METRICS TRACKING
|
|
77
|
+
def start_process(self):
|
|
78
|
+
self.process_start = time.perf_counter()
|
|
79
|
+
self._record_cpu_start()
|
|
80
|
+
self._record_rss_start()
|
|
81
|
+
self._record_fs_start()
|
|
82
|
+
|
|
83
|
+
# STARTS STARTUP PHASE TRACKING
|
|
84
|
+
def start_startup(self):
|
|
85
|
+
self.startup_start = time.perf_counter()
|
|
86
|
+
if tracemalloc.is_tracing() and not self.tracemalloc_started:
|
|
87
|
+
self.tracemalloc_started = True
|
|
88
|
+
elif ENABLE_TRACEMALLOC and not self.tracemalloc_started:
|
|
89
|
+
tracemalloc.start(1)
|
|
90
|
+
self.tracemalloc_started = True
|
|
91
|
+
|
|
92
|
+
if self.tracemalloc_started and tracemalloc.is_tracing():
|
|
93
|
+
self.alloc_start = tracemalloc.take_snapshot()
|
|
94
|
+
self.gc_start_stats = self._get_gc_stats()
|
|
95
|
+
|
|
96
|
+
# ENDS STARTUP PHASE TRACKING
|
|
97
|
+
def end_startup(self):
|
|
98
|
+
self.startup_end = time.perf_counter()
|
|
99
|
+
|
|
100
|
+
# STARTS COMMAND EXECUTION TRACKING
|
|
101
|
+
def start_command(self):
|
|
102
|
+
self.command_start = time.perf_counter()
|
|
103
|
+
|
|
104
|
+
# ENDS COMMAND EXECUTION TRACKING
|
|
105
|
+
def end_command(self):
|
|
106
|
+
self.command_end = time.perf_counter()
|
|
107
|
+
|
|
108
|
+
# ENDS PROCESS-LEVEL METRICS TRACKING
|
|
109
|
+
def end_process(self):
|
|
110
|
+
self.process_end = time.perf_counter()
|
|
111
|
+
self._record_cpu_end()
|
|
112
|
+
self._record_rss_end()
|
|
113
|
+
self._record_fs_end()
|
|
114
|
+
if self.tracemalloc_started and tracemalloc.is_tracing():
|
|
115
|
+
self.alloc_end = tracemalloc.take_snapshot()
|
|
116
|
+
tracemalloc.stop()
|
|
117
|
+
self.gc_end_stats = self._get_gc_stats()
|
|
118
|
+
|
|
119
|
+
# STARTS TRACKING A NAMED STEP
|
|
120
|
+
def step_start(self, name: str):
|
|
121
|
+
if self._current_step:
|
|
122
|
+
self.step_end()
|
|
123
|
+
self._current_step = name
|
|
124
|
+
self._step_start = time.perf_counter()
|
|
125
|
+
|
|
126
|
+
# ENDS TRACKING THE CURRENT STEP
|
|
127
|
+
def step_end(self):
|
|
128
|
+
if self._current_step and self._step_start:
|
|
129
|
+
duration = time.perf_counter() - self._step_start
|
|
130
|
+
self.steps.append((self._current_step, duration))
|
|
131
|
+
self._current_step = None
|
|
132
|
+
self._step_start = None
|
|
133
|
+
|
|
134
|
+
# RECORDS CPU USAGE AT START
|
|
135
|
+
def _record_cpu_start(self):
|
|
136
|
+
usage = resource.getrusage(resource.RUSAGE_SELF)
|
|
137
|
+
self.cpu_user_start = usage.ru_utime
|
|
138
|
+
self.cpu_sys_start = usage.ru_stime
|
|
139
|
+
|
|
140
|
+
# RECORDS CPU USAGE AT END
|
|
141
|
+
def _record_cpu_end(self):
|
|
142
|
+
usage = resource.getrusage(resource.RUSAGE_SELF)
|
|
143
|
+
self.cpu_user_end = usage.ru_utime
|
|
144
|
+
self.cpu_sys_end = usage.ru_stime
|
|
145
|
+
|
|
146
|
+
# RECORDS MEMORY USAGE AT START
|
|
147
|
+
def _record_rss_start(self):
|
|
148
|
+
if PSUTIL_AVAILABLE:
|
|
149
|
+
process = psutil.Process()
|
|
150
|
+
self.rss_start = process.memory_info().rss / (1024 * 1024) # MB
|
|
151
|
+
else:
|
|
152
|
+
usage = resource.getrusage(resource.RUSAGE_SELF)
|
|
153
|
+
self.rss_start = usage.ru_maxrss / 1024 # MB (LINUX) OR KB (MACOS)
|
|
154
|
+
if sys.platform == 'darwin':
|
|
155
|
+
self.rss_start = self.rss_start / 1024 # CONVERT KB TO MB ON MACOS
|
|
156
|
+
|
|
157
|
+
# RECORDS MEMORY USAGE AT END
|
|
158
|
+
def _record_rss_end(self):
|
|
159
|
+
if PSUTIL_AVAILABLE:
|
|
160
|
+
process = psutil.Process()
|
|
161
|
+
mem_info = process.memory_info()
|
|
162
|
+
self.rss_peak = mem_info.rss / (1024 * 1024) # MB
|
|
163
|
+
|
|
164
|
+
try:
|
|
165
|
+
if hasattr(process, 'memory_info_ex'):
|
|
166
|
+
mem_ext = process.memory_info_ex()
|
|
167
|
+
if hasattr(mem_ext, 'peak_wss'):
|
|
168
|
+
self.rss_peak = max(self.rss_peak, mem_ext.peak_wss / (1024 * 1024))
|
|
169
|
+
except Exception:
|
|
170
|
+
pass
|
|
171
|
+
else:
|
|
172
|
+
usage = resource.getrusage(resource.RUSAGE_SELF)
|
|
173
|
+
rss_current = usage.ru_maxrss / 1024
|
|
174
|
+
if sys.platform == 'darwin':
|
|
175
|
+
rss_current = rss_current / 1024
|
|
176
|
+
self.rss_peak = max(self.rss_start, rss_current) if self.rss_start else rss_current
|
|
177
|
+
|
|
178
|
+
# RECORDS FILESYSTEM I/O AT START
|
|
179
|
+
def _record_fs_start(self):
|
|
180
|
+
if PSUTIL_AVAILABLE:
|
|
181
|
+
try:
|
|
182
|
+
process = psutil.Process()
|
|
183
|
+
io_counters = process.io_counters()
|
|
184
|
+
self.fs_read_start = io_counters.read_bytes
|
|
185
|
+
self.fs_write_start = io_counters.write_bytes
|
|
186
|
+
self.fs_ops_start = getattr(io_counters, 'read_count', 0) + getattr(io_counters, 'write_count', 0)
|
|
187
|
+
except (AttributeError, psutil.AccessDenied):
|
|
188
|
+
self.fs_read_start = 0
|
|
189
|
+
self.fs_write_start = 0
|
|
190
|
+
self.fs_ops_start = 0
|
|
191
|
+
else:
|
|
192
|
+
self.fs_read_start = 0
|
|
193
|
+
self.fs_write_start = 0
|
|
194
|
+
self.fs_ops_start = 0
|
|
195
|
+
|
|
196
|
+
# RECORDS FILESYSTEM I/O AT END
|
|
197
|
+
def _record_fs_end(self):
|
|
198
|
+
if PSUTIL_AVAILABLE:
|
|
199
|
+
try:
|
|
200
|
+
process = psutil.Process()
|
|
201
|
+
io_counters = process.io_counters()
|
|
202
|
+
self.fs_read_end = io_counters.read_bytes
|
|
203
|
+
self.fs_write_end = io_counters.write_bytes
|
|
204
|
+
self.fs_ops_end = getattr(io_counters, 'read_count', 0) + getattr(io_counters, 'write_count', 0)
|
|
205
|
+
except (AttributeError, psutil.AccessDenied):
|
|
206
|
+
self.fs_read_end = self.fs_read_start
|
|
207
|
+
self.fs_write_end = self.fs_write_start
|
|
208
|
+
self.fs_ops_end = self.fs_ops_start
|
|
209
|
+
else:
|
|
210
|
+
self.fs_read_end = self.fs_read_start
|
|
211
|
+
self.fs_write_end = self.fs_write_start
|
|
212
|
+
self.fs_ops_end = self.fs_ops_start
|
|
213
|
+
|
|
214
|
+
# GETS CURRENT GARBAGE COLLECTION STATISTICS
|
|
215
|
+
def _get_gc_stats(self) -> List[Dict]:
|
|
216
|
+
return gc.get_stats()
|
|
217
|
+
|
|
218
|
+
# CALCULATES DURATION METRICS IN MILLISECONDS
|
|
219
|
+
def _calculate_durations(self) -> Tuple[float, float, float]:
|
|
220
|
+
total_duration_ms = (self.process_end - self.process_start) * 1000 if self.process_end and self.process_start else 0
|
|
221
|
+
startup_duration_ms = (self.startup_end - self.startup_start) * 1000 if self.startup_end and self.startup_start else 0
|
|
222
|
+
command_duration_ms = (self.command_end - self.command_start) * 1000 if self.command_end and self.command_start else 0
|
|
223
|
+
return total_duration_ms, startup_duration_ms, command_duration_ms
|
|
224
|
+
|
|
225
|
+
# CALCULATES CPU TIME METRICS IN MILLISECONDS
|
|
226
|
+
def _calculate_cpu_time(self) -> Tuple[float, float, float]:
|
|
227
|
+
cpu_user_ms = (self.cpu_user_end - self.cpu_user_start) * 1000 if self.cpu_user_end and self.cpu_user_start else 0
|
|
228
|
+
cpu_sys_ms = (self.cpu_sys_end - self.cpu_sys_start) * 1000 if self.cpu_sys_end and self.cpu_sys_start else 0
|
|
229
|
+
cpu_time_total_ms = cpu_user_ms + cpu_sys_ms
|
|
230
|
+
return cpu_time_total_ms, cpu_user_ms, cpu_sys_ms
|
|
231
|
+
|
|
232
|
+
# CALCULATES TOTAL MEMORY ALLOCATIONS IN MB
|
|
233
|
+
def _calculate_allocations(self) -> float:
|
|
234
|
+
alloc_mb_total = 0
|
|
235
|
+
if self.alloc_start and self.alloc_end:
|
|
236
|
+
diff = self.alloc_end.compare_to(self.alloc_start, 'lineno')
|
|
237
|
+
alloc_bytes = sum(stat.size_diff for stat in diff if stat.size_diff > 0)
|
|
238
|
+
alloc_mb_total = alloc_bytes / (1024 * 1024)
|
|
239
|
+
return alloc_mb_total
|
|
240
|
+
|
|
241
|
+
# CALCULATES GARBAGE COLLECTION METRICS
|
|
242
|
+
def _calculate_gc_stats(self) -> Tuple[float, int]:
|
|
243
|
+
gc_pause_total_ms = 0
|
|
244
|
+
gc_collections_count = 0
|
|
245
|
+
if self.gc_start_stats and self.gc_end_stats:
|
|
246
|
+
end_stats = gc.get_stats()
|
|
247
|
+
start_stats = self.gc_start_stats
|
|
248
|
+
|
|
249
|
+
if len(start_stats) == len(end_stats):
|
|
250
|
+
for start_stat, end_stat in zip(start_stats, end_stats):
|
|
251
|
+
start_collections = start_stat.get('collections', 0)
|
|
252
|
+
end_collections = end_stat.get('collections', 0)
|
|
253
|
+
gc_collections_count += max(0, end_collections - start_collections)
|
|
254
|
+
|
|
255
|
+
start_time = start_stat.get('total_time', 0)
|
|
256
|
+
end_time = end_stat.get('total_time', 0)
|
|
257
|
+
gc_pause_total_ms += max(0, (end_time - start_time) * 1000) # CONVERT TO MS
|
|
258
|
+
|
|
259
|
+
return gc_pause_total_ms, gc_collections_count
|
|
260
|
+
|
|
261
|
+
# CALCULATES FILESYSTEM I/O METRICS
|
|
262
|
+
def _calculate_fs_io(self) -> Tuple[int, int, int]:
|
|
263
|
+
fs_bytes_read_total = self.fs_read_end - self.fs_read_start if self.fs_read_end and self.fs_read_start else 0
|
|
264
|
+
fs_bytes_written_total = self.fs_write_end - self.fs_write_start if self.fs_write_end and self.fs_write_start else 0
|
|
265
|
+
fs_ops_count = self.fs_ops_end - self.fs_ops_start if self.fs_ops_end and self.fs_ops_start else 0
|
|
266
|
+
return fs_bytes_read_total, fs_bytes_written_total, fs_ops_count
|
|
267
|
+
|
|
268
|
+
# CALCULATES AND RETURNS ALL PERFORMANCE METRICS AS A DICTIONARY
|
|
269
|
+
def get_metrics(self) -> Dict:
|
|
270
|
+
if self._current_step: self.step_end()
|
|
271
|
+
|
|
272
|
+
# CALCULATE ALL METRICS
|
|
273
|
+
total_duration_ms, startup_duration_ms, command_duration_ms = self._calculate_durations()
|
|
274
|
+
cpu_time_total_ms, cpu_user_ms, cpu_sys_ms = self._calculate_cpu_time()
|
|
275
|
+
alloc_mb_total = self._calculate_allocations()
|
|
276
|
+
gc_pause_total_ms, gc_collections_count = self._calculate_gc_stats()
|
|
277
|
+
fs_bytes_read_total, fs_bytes_written_total, fs_ops_count = self._calculate_fs_io()
|
|
278
|
+
|
|
279
|
+
# MEMORY
|
|
280
|
+
rss_mb_peak = self.rss_peak if self.rss_peak else 0
|
|
281
|
+
|
|
282
|
+
# TOP SLOWEST STEPS
|
|
283
|
+
top_slowest_steps = sorted(self.steps, key=lambda x: x[1], reverse=True)[:5]
|
|
284
|
+
top_slowest_steps_formatted = [
|
|
285
|
+
{'step': name, 'duration_ms': duration * 1000}
|
|
286
|
+
for name, duration in top_slowest_steps
|
|
287
|
+
]
|
|
288
|
+
|
|
289
|
+
return {
|
|
290
|
+
'total_duration_ms': round(total_duration_ms, 2),
|
|
291
|
+
'startup_duration_ms': round(startup_duration_ms, 2),
|
|
292
|
+
'command_duration_ms': round(command_duration_ms, 2),
|
|
293
|
+
'top_slowest_steps': top_slowest_steps_formatted,
|
|
294
|
+
'cpu_time_total_ms': round(cpu_time_total_ms, 2),
|
|
295
|
+
'cpu_user_ms': round(cpu_user_ms, 2),
|
|
296
|
+
'cpu_sys_ms': round(cpu_sys_ms, 2),
|
|
297
|
+
'rss_mb_peak': round(rss_mb_peak, 2),
|
|
298
|
+
'alloc_mb_total': round(alloc_mb_total, 2),
|
|
299
|
+
'gc_pause_total_ms': round(gc_pause_total_ms, 2),
|
|
300
|
+
'gc_collections_count': gc_collections_count,
|
|
301
|
+
'fs_bytes_read_total': fs_bytes_read_total,
|
|
302
|
+
'fs_bytes_written_total': fs_bytes_written_total,
|
|
303
|
+
'fs_ops_count': fs_ops_count
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
# GLOBAL METRICS INSTANCE
|
|
307
|
+
_metrics = PerformanceMetrics()
|
|
308
|
+
|
|
309
|
+
# CONTEXT MANAGER FOR TRACKING NAMED STEPS
|
|
310
|
+
@contextmanager
|
|
311
|
+
def track_step(name: str):
|
|
312
|
+
_metrics.step_start(name)
|
|
313
|
+
try:
|
|
314
|
+
yield
|
|
315
|
+
finally:
|
|
316
|
+
_metrics.step_end()
|
|
317
|
+
|
|
318
|
+
# CHECKS IF PERFORMANCE METRICS SHOULD BE ENABLED
|
|
319
|
+
def should_enable_metrics(ctx) -> bool:
|
|
320
|
+
current = ctx
|
|
321
|
+
while current:
|
|
322
|
+
if current.params.get('perf', False): return True
|
|
323
|
+
current = getattr(current, 'parent', None)
|
|
324
|
+
|
|
325
|
+
if not ENABLE_PERFORMANCE_METRICS: return False
|
|
326
|
+
|
|
327
|
+
module = sys.modules.get('autotools')
|
|
328
|
+
module_file = getattr(module, '__file__', '') or ''
|
|
329
|
+
is_dev = module and 'site-packages' not in module_file.lower()
|
|
330
|
+
|
|
331
|
+
return is_dev
|
|
332
|
+
|
|
333
|
+
# DISPLAYS PERFORMANCE METRICS IN A FORMATTED WAY
|
|
334
|
+
def display_metrics(metrics: Dict):
|
|
335
|
+
click.echo(click.style("\n" + "="*60, fg='cyan'))
|
|
336
|
+
click.echo(click.style("PERFORMANCE METRICS", fg='cyan', bold=True))
|
|
337
|
+
click.echo(click.style("="*60, fg='cyan'))
|
|
338
|
+
|
|
339
|
+
# DURATION METRICS
|
|
340
|
+
click.echo(click.style("\nDURATION METRICS:", fg='yellow', bold=True))
|
|
341
|
+
click.echo(f" Total Duration: {metrics['total_duration_ms']:.2f} ms")
|
|
342
|
+
click.echo(f" Startup Duration: {metrics['startup_duration_ms']:.2f} ms")
|
|
343
|
+
click.echo(f" Command Duration: {metrics['command_duration_ms']:.2f} ms")
|
|
344
|
+
|
|
345
|
+
# CPU METRICS
|
|
346
|
+
click.echo(click.style("\nCPU METRICS:", fg='yellow', bold=True))
|
|
347
|
+
click.echo(f" CPU Time Total: {metrics['cpu_time_total_ms']:.2f} ms")
|
|
348
|
+
click.echo(f" CPU User Time: {metrics['cpu_user_ms']:.2f} ms")
|
|
349
|
+
click.echo(f" CPU System Time: {metrics['cpu_sys_ms']:.2f} ms")
|
|
350
|
+
cpu_ratio = (metrics['cpu_time_total_ms'] / metrics['total_duration_ms'] * 100) if metrics['total_duration_ms'] > 0 else 0
|
|
351
|
+
click.echo(f" CPU Usage Ratio: {cpu_ratio:.1f}%")
|
|
352
|
+
|
|
353
|
+
# MEMORY METRICS
|
|
354
|
+
click.echo(click.style("\nMEMORY METRICS:", fg='yellow', bold=True))
|
|
355
|
+
click.echo(f" RSS Peak: {metrics['rss_mb_peak']:.2f} MB")
|
|
356
|
+
click.echo(f" Allocations Total: {metrics['alloc_mb_total']:.2f} MB")
|
|
357
|
+
|
|
358
|
+
# GC METRICS
|
|
359
|
+
if metrics['gc_collections_count'] > 0 or metrics['gc_pause_total_ms'] > 0:
|
|
360
|
+
click.echo(click.style("\nGARBAGE COLLECTION METRICS:", fg='yellow', bold=True))
|
|
361
|
+
click.echo(f" GC Pause Total: {metrics['gc_pause_total_ms']:.2f} ms")
|
|
362
|
+
click.echo(f" GC Collections: {metrics['gc_collections_count']}")
|
|
363
|
+
|
|
364
|
+
# FS I/O METRICS
|
|
365
|
+
if metrics['fs_bytes_read_total'] > 0 or metrics['fs_bytes_written_total'] > 0:
|
|
366
|
+
click.echo(click.style("\nFILESYSTEM I/O METRICS:", fg='yellow', bold=True))
|
|
367
|
+
click.echo(f" Bytes Read: {metrics['fs_bytes_read_total']:,} bytes ({metrics['fs_bytes_read_total'] / 1024 / 1024:.2f} MB)")
|
|
368
|
+
click.echo(f" Bytes Written: {metrics['fs_bytes_written_total']:,} bytes ({metrics['fs_bytes_written_total'] / 1024 / 1024:.2f} MB)")
|
|
369
|
+
click.echo(f" FS Operations: {metrics['fs_ops_count']:,}")
|
|
370
|
+
|
|
371
|
+
# TOP SLOWEST STEPS
|
|
372
|
+
if metrics['top_slowest_steps']:
|
|
373
|
+
click.echo(click.style("\nTOP SLOWEST STEPS:", fg='yellow', bold=True))
|
|
374
|
+
for i, step in enumerate(metrics['top_slowest_steps'], 1): click.echo(f" {i}. {step['step']}: {step['duration_ms']:.2f} ms")
|
|
375
|
+
|
|
376
|
+
click.echo(click.style("\n" + "="*60 + "\n", fg='cyan'))
|
|
377
|
+
|
|
378
|
+
# INITIALIZES METRICS TRACKING
|
|
379
|
+
def init_metrics():
|
|
380
|
+
_metrics.reset()
|
|
381
|
+
_metrics.start_process()
|
|
382
|
+
_metrics.start_startup()
|
|
383
|
+
|
|
384
|
+
# FINALIZES AND DISPLAYS METRICS IF ENABLED
|
|
385
|
+
def finalize_metrics(ctx):
|
|
386
|
+
if should_enable_metrics(ctx):
|
|
387
|
+
_metrics.end_process()
|
|
388
|
+
metrics = _metrics.get_metrics()
|
|
389
|
+
display_metrics(metrics)
|
|
390
|
+
|
|
391
|
+
# GETS THE GLOBAL METRICS INSTANCE
|
|
392
|
+
def get_metrics() -> PerformanceMetrics: return _metrics
|
autotools/utils/updates.py
CHANGED
|
@@ -1,30 +1,38 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
1
3
|
import click
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
+
import urllib.request
|
|
5
|
+
import urllib.error
|
|
6
|
+
from importlib.metadata import version as get_version, distribution, PackageNotFoundError
|
|
4
7
|
from packaging.version import parse as parse_version
|
|
5
|
-
import pkg_resources
|
|
6
8
|
|
|
9
|
+
# CHECKS PYPI FOR AVAILABLE UPDATES TO THE PACKAGE
|
|
7
10
|
def check_for_updates():
|
|
8
|
-
|
|
11
|
+
# SKIP UPDATE CHECK IN TEST ENVIRONMENT
|
|
12
|
+
if os.getenv('PYTEST_CURRENT_TEST') or os.getenv('CI'): return None
|
|
13
|
+
|
|
9
14
|
try:
|
|
10
|
-
|
|
11
|
-
|
|
15
|
+
try:
|
|
16
|
+
dist = distribution("Open-AutoTools")
|
|
17
|
+
current_version = parse_version(dist.version)
|
|
18
|
+
except PackageNotFoundError:
|
|
19
|
+
return None
|
|
12
20
|
|
|
13
21
|
pypi_url = "https://pypi.org/pypi/Open-AutoTools/json"
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
except
|
|
28
|
-
|
|
29
|
-
|
|
22
|
+
req = urllib.request.Request(pypi_url)
|
|
23
|
+
with urllib.request.urlopen(req, timeout=5) as response:
|
|
24
|
+
if response.status == 200:
|
|
25
|
+
data = json.loads(response.read().decode())
|
|
26
|
+
latest_version = data["info"]["version"]
|
|
27
|
+
latest_parsed = parse_version(latest_version)
|
|
28
|
+
|
|
29
|
+
if latest_parsed > current_version:
|
|
30
|
+
update_cmd = "pip install --upgrade Open-AutoTools"
|
|
31
|
+
return (
|
|
32
|
+
click.style(f"\nUpdate available: v{latest_version}", fg='red', bold=True) + "\n" +
|
|
33
|
+
click.style(f"Run '{update_cmd}' to update", fg='red')
|
|
34
|
+
)
|
|
35
|
+
except urllib.error.URLError:
|
|
36
|
+
pass
|
|
37
|
+
|
|
30
38
|
return None
|
autotools/utils/version.py
CHANGED
|
@@ -1,74 +1,80 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import json
|
|
1
4
|
import click
|
|
2
|
-
|
|
3
|
-
import
|
|
4
|
-
import requests
|
|
5
|
-
from packaging.version import parse as parse_version
|
|
5
|
+
import urllib.request
|
|
6
|
+
import urllib.error
|
|
6
7
|
from datetime import datetime
|
|
8
|
+
from packaging.version import parse as parse_version
|
|
9
|
+
from importlib.metadata import version as get_version, PackageNotFoundError
|
|
10
|
+
|
|
11
|
+
# FORMATS AND DISPLAYS RELEASE DATE
|
|
12
|
+
def _format_release_date(upload_time, pkg_version):
|
|
13
|
+
date_formats = ["%Y-%m-%dT%H:%M:%S", "%Y-%m-%dT%H:%M:%S.%fZ", "%Y-%m-%d %H:%M:%S"]
|
|
14
|
+
for date_format in date_formats:
|
|
15
|
+
try:
|
|
16
|
+
published_date = datetime.strptime(upload_time, date_format)
|
|
17
|
+
formatted_date = published_date.strftime("%d %B %Y at %H:%M:%S")
|
|
18
|
+
prefix = "Pre-Released" if "rc" in pkg_version.lower() else "Released"
|
|
19
|
+
click.echo(f"{prefix}: {formatted_date}")
|
|
20
|
+
return True
|
|
21
|
+
except ValueError:
|
|
22
|
+
continue
|
|
23
|
+
|
|
24
|
+
return False
|
|
25
|
+
|
|
26
|
+
# DISPLAYS RELEASE INFORMATION FROM PYPI DATA
|
|
27
|
+
def _display_release_info(data, pkg_version):
|
|
28
|
+
releases = data["releases"]
|
|
29
|
+
if pkg_version in releases and releases[pkg_version]:
|
|
30
|
+
try:
|
|
31
|
+
upload_time = releases[pkg_version][0]["upload_time"]
|
|
32
|
+
_format_release_date(upload_time, pkg_version)
|
|
33
|
+
except Exception:
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
# CHECKS AND DISPLAYS UPDATE INFORMATION
|
|
37
|
+
def _check_for_updates(current_version, latest_version):
|
|
38
|
+
latest_parsed = parse_version(latest_version)
|
|
39
|
+
if latest_parsed > current_version:
|
|
40
|
+
update_cmd = "pip install --upgrade Open-AutoTools"
|
|
41
|
+
click.echo(click.style(f"\nUpdate available: v{latest_version}", fg='red', bold=True))
|
|
42
|
+
click.echo(click.style(f"Run '{update_cmd}' to update", fg='red'))
|
|
43
|
+
|
|
44
|
+
# FETCHES AND PROCESSES PYPI VERSION INFORMATION
|
|
45
|
+
def _fetch_pypi_version_info(pkg_version):
|
|
46
|
+
pypi_url = "https://pypi.org/pypi/Open-AutoTools/json"
|
|
47
|
+
try:
|
|
48
|
+
req = urllib.request.Request(pypi_url)
|
|
49
|
+
with urllib.request.urlopen(req, timeout=5) as response:
|
|
50
|
+
if response.status != 200:
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
data = json.loads(response.read().decode())
|
|
54
|
+
latest_version = data["info"]["version"]
|
|
55
|
+
current_version = parse_version(pkg_version)
|
|
56
|
+
|
|
57
|
+
_display_release_info(data, pkg_version)
|
|
58
|
+
_check_for_updates(current_version, latest_version)
|
|
59
|
+
except urllib.error.URLError:
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
# PRINTS VERSION INFORMATION AND CHECKS FOR UPDATES
|
|
63
|
+
def print_version(ctx, value):
|
|
64
|
+
if not value or ctx.resilient_parsing: return
|
|
7
65
|
|
|
8
|
-
# VERSION CALLBACK
|
|
9
|
-
def print_version(ctx, param, value):
|
|
10
|
-
"""PRINT VERSION AND CHECK FOR UPDATES"""
|
|
11
|
-
if not value or ctx.resilient_parsing:
|
|
12
|
-
return
|
|
13
|
-
|
|
14
66
|
try:
|
|
15
|
-
# GET CURRENT VERSION
|
|
16
67
|
pkg_version = get_version('Open-AutoTools')
|
|
17
68
|
click.echo(f"Open-AutoTools version {pkg_version}")
|
|
18
69
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
70
|
+
module = sys.modules.get('autotools')
|
|
71
|
+
module_file = getattr(module, '__file__', '') or ''
|
|
72
|
+
if module and 'site-packages' not in module_file.lower():
|
|
73
|
+
click.echo(click.style("Development mode: enabled", fg='yellow', bold=True))
|
|
22
74
|
|
|
23
|
-
|
|
24
|
-
pypi_url = "https://pypi.org/pypi/Open-AutoTools/json"
|
|
25
|
-
response = requests.get(pypi_url)
|
|
26
|
-
|
|
27
|
-
# CHECK IF RESPONSE IS SUCCESSFUL
|
|
28
|
-
if response.status_code == 200:
|
|
29
|
-
data = response.json()
|
|
30
|
-
latest_version = data["info"]["version"]
|
|
31
|
-
releases = data["releases"]
|
|
32
|
-
|
|
33
|
-
# GET RELEASE DATE
|
|
34
|
-
if pkg_version in releases and releases[pkg_version]:
|
|
35
|
-
try:
|
|
36
|
-
upload_time = releases[pkg_version][0]["upload_time"]
|
|
37
|
-
for date_format in [
|
|
38
|
-
"%Y-%m-%dT%H:%M:%S",
|
|
39
|
-
"%Y-%m-%dT%H:%M:%S.%fZ",
|
|
40
|
-
"%Y-%m-%d %H:%M:%S"
|
|
41
|
-
]:
|
|
42
|
-
# TRY TO PARSE DATE
|
|
43
|
-
try:
|
|
44
|
-
published_date = datetime.strptime(upload_time, date_format)
|
|
45
|
-
formatted_date = published_date.strftime("%d %B %Y at %H:%M:%S")
|
|
46
|
-
# CHECK IF VERSION IS RC
|
|
47
|
-
if "rc" in pkg_version.lower():
|
|
48
|
-
click.echo(f"Pre-Released: {formatted_date}")
|
|
49
|
-
else:
|
|
50
|
-
click.echo(f"Released: {formatted_date}")
|
|
51
|
-
break
|
|
52
|
-
except ValueError:
|
|
53
|
-
continue
|
|
54
|
-
except Exception:
|
|
55
|
-
pass # SKIP DATE IF PARSING FAILS
|
|
56
|
-
|
|
57
|
-
# CHECK FOR UPDATES
|
|
58
|
-
latest_parsed = parse_version(latest_version)
|
|
59
|
-
|
|
60
|
-
# COMPARE VERSIONS AND PRINT UPDATE MESSAGE IF NEEDED
|
|
61
|
-
if latest_parsed > current_version:
|
|
62
|
-
update_cmd = "pip install --upgrade Open-AutoTools"
|
|
63
|
-
click.echo(click.style(f"\nUpdate available: v{latest_version}", fg='red', bold=True))
|
|
64
|
-
click.echo(click.style(f"Run '{update_cmd}' to update", fg='red'))
|
|
75
|
+
_fetch_pypi_version_info(pkg_version)
|
|
65
76
|
|
|
66
|
-
|
|
67
|
-
except
|
|
68
|
-
click.echo("Package distribution not found")
|
|
69
|
-
except PackageNotFoundError:
|
|
70
|
-
click.echo("Open-AutoTools version information not available")
|
|
71
|
-
except Exception as e:
|
|
72
|
-
click.echo(f"Error checking updates: {str(e)}")
|
|
77
|
+
except PackageNotFoundError: click.echo("Open-AutoTools version information not available")
|
|
78
|
+
except Exception as e: click.echo(f"Error checking updates: {str(e)}")
|
|
73
79
|
|
|
74
80
|
ctx.exit()
|