pysinfo 1.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.
- pysinfo/__init__.py +795 -0
- pysinfo/__main__.py +8 -0
- pysinfo/setup.py +41 -0
- pysinfo-1.1.0.dist-info/METADATA +131 -0
- pysinfo-1.1.0.dist-info/RECORD +9 -0
- pysinfo-1.1.0.dist-info/WHEEL +5 -0
- pysinfo-1.1.0.dist-info/entry_points.txt +2 -0
- pysinfo-1.1.0.dist-info/licenses/LICENSE +674 -0
- pysinfo-1.1.0.dist-info/top_level.txt +1 -0
pysinfo/__init__.py
ADDED
|
@@ -0,0 +1,795 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PySInfo: A python command line tool that displays information about the current system, including hardware and critical software.
|
|
3
|
+
version 1.1
|
|
4
|
+
"""
|
|
5
|
+
__version__ = "1.1.1"
|
|
6
|
+
|
|
7
|
+
import platform
|
|
8
|
+
import psutil
|
|
9
|
+
import os
|
|
10
|
+
import socket
|
|
11
|
+
import datetime
|
|
12
|
+
import distro
|
|
13
|
+
import shutil
|
|
14
|
+
import GPUtil
|
|
15
|
+
import colorama
|
|
16
|
+
import sys
|
|
17
|
+
import subprocess
|
|
18
|
+
import re
|
|
19
|
+
from colorama import Fore, Style, Back
|
|
20
|
+
|
|
21
|
+
def get_system_info():
|
|
22
|
+
info = {}
|
|
23
|
+
|
|
24
|
+
# OS Information
|
|
25
|
+
if platform.system() == "Windows":
|
|
26
|
+
info["os"] = f"{platform.system()} {platform.version()}"
|
|
27
|
+
elif platform.system() == "Darwin":
|
|
28
|
+
mac_ver = platform.mac_ver()[0]
|
|
29
|
+
info["os"] = f"macOS {mac_ver}"
|
|
30
|
+
else:
|
|
31
|
+
info["os"] = f"{distro.name()} {distro.version()}"
|
|
32
|
+
|
|
33
|
+
info["hostname"] = socket.gethostname()
|
|
34
|
+
info["kernel"] = platform.release()
|
|
35
|
+
info["uptime"] = get_uptime()
|
|
36
|
+
|
|
37
|
+
# Hardware Information - Enhanced CPU details
|
|
38
|
+
cpu_info = get_detailed_cpu_info()
|
|
39
|
+
info["cpu_model"] = cpu_info.get("model", platform.processor())
|
|
40
|
+
info["cpu_arch"] = cpu_info.get("architecture", platform.machine())
|
|
41
|
+
info["cpu_cores"] = f"{psutil.cpu_count(logical=False)} (Physical), {psutil.cpu_count(logical=True)} (Logical)"
|
|
42
|
+
info["cpu_freq"] = cpu_info.get("frequency", "Unknown")
|
|
43
|
+
info["cpu_cache"] = cpu_info.get("cache", "Unknown")
|
|
44
|
+
info["cpu_process"] = cpu_info.get("process", "Unknown")
|
|
45
|
+
info["cpu_usage"] = f"{psutil.cpu_percent()}%"
|
|
46
|
+
|
|
47
|
+
# Enhanced Memory Information
|
|
48
|
+
memory_info = get_detailed_memory_info()
|
|
49
|
+
mem = psutil.virtual_memory()
|
|
50
|
+
info["memory"] = f"{bytes_to_readable(mem.used)} / {bytes_to_readable(mem.total)} ({mem.percent}%)"
|
|
51
|
+
info["memory_slots"] = memory_info.get("slots", "Unknown")
|
|
52
|
+
info["memory_speed"] = memory_info.get("speed", "Unknown")
|
|
53
|
+
|
|
54
|
+
# Python Information
|
|
55
|
+
info["python_version"] = f"{platform.python_version()} ({platform.python_implementation()})"
|
|
56
|
+
info["python_path"] = sys.executable
|
|
57
|
+
|
|
58
|
+
# Try to get GPU info and CUDA support
|
|
59
|
+
try:
|
|
60
|
+
gpus = GPUtil.getGPUs()
|
|
61
|
+
if gpus:
|
|
62
|
+
gpu_info = []
|
|
63
|
+
for i, gpu in enumerate(gpus):
|
|
64
|
+
gpu_info.append(f"{gpu.name} ({bytes_to_readable(gpu.memoryTotal * 1024 * 1024)})")
|
|
65
|
+
info["gpu"] = " | ".join(gpu_info)
|
|
66
|
+
|
|
67
|
+
# Check CUDA support
|
|
68
|
+
cuda_version = check_cuda_version()
|
|
69
|
+
info["cuda"] = cuda_version if cuda_version else "Not detected"
|
|
70
|
+
else:
|
|
71
|
+
info["gpu"] = "No dedicated GPU detected"
|
|
72
|
+
info["cuda"] = "Not available"
|
|
73
|
+
except Exception as e:
|
|
74
|
+
if platform.system() == "Darwin":
|
|
75
|
+
result = subprocess.run(
|
|
76
|
+
["system_profiler", "SPDisplaysDataType"],
|
|
77
|
+
capture_output=True,
|
|
78
|
+
text=True
|
|
79
|
+
)
|
|
80
|
+
matches = re.findall(r"Chipset Model:\s+(.*)", result.stdout)
|
|
81
|
+
info["gpu"] = " | ".join(matches) if matches else "Apple GPU"
|
|
82
|
+
info["cuda"] = "Not supported on macOS"
|
|
83
|
+
else:
|
|
84
|
+
info["gpu"] = "GPU information unavailable"
|
|
85
|
+
info["cuda"] = "Not detected"
|
|
86
|
+
|
|
87
|
+
# Check OpenCL support
|
|
88
|
+
info["opencl"] = check_opencl_version()
|
|
89
|
+
|
|
90
|
+
# Check Vulkan support
|
|
91
|
+
info["vulkan"] = check_vulkan_version()
|
|
92
|
+
|
|
93
|
+
# Disk Information
|
|
94
|
+
disk = psutil.disk_usage('/')
|
|
95
|
+
info["disk"] = f"{bytes_to_readable(disk.used)} / {bytes_to_readable(disk.total)} ({disk.percent}%)"
|
|
96
|
+
|
|
97
|
+
# Network Information
|
|
98
|
+
info["ip_address"] = socket.gethostbyname(socket.gethostname())
|
|
99
|
+
|
|
100
|
+
# Shell Information
|
|
101
|
+
if platform.system() == "Windows":
|
|
102
|
+
info["shell"] = os.environ.get("COMSPEC", "Unknown")
|
|
103
|
+
else:
|
|
104
|
+
info["shell"] = os.environ.get("SHELL", "Unknown")
|
|
105
|
+
|
|
106
|
+
# Terminal Information
|
|
107
|
+
terminal_size = shutil.get_terminal_size()
|
|
108
|
+
info["terminal"] = f"{terminal_size.columns}x{terminal_size.lines}"
|
|
109
|
+
|
|
110
|
+
return info
|
|
111
|
+
|
|
112
|
+
def get_detailed_cpu_info():
|
|
113
|
+
"""Get more detailed CPU information."""
|
|
114
|
+
cpu_info = {
|
|
115
|
+
"model": platform.processor(),
|
|
116
|
+
"architecture": platform.machine(),
|
|
117
|
+
"frequency": psutil.cpu_freq().current if psutil.cpu_freq() else "Unknown",
|
|
118
|
+
"cores": f"{psutil.cpu_count(logical=False)} (Physical), {psutil.cpu_count(logical=True)} (Logical)",
|
|
119
|
+
"usage": f"{psutil.cpu_percent()}%"
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
try:
|
|
123
|
+
if platform.system() == "Windows":
|
|
124
|
+
# Try multiple methods to get CPU model name
|
|
125
|
+
model = None
|
|
126
|
+
|
|
127
|
+
# Method 1: Try using WMI
|
|
128
|
+
try:
|
|
129
|
+
result = subprocess.run(["wmic", "cpu", "get", "name"], capture_output=True, text=True)
|
|
130
|
+
if result.returncode == 0:
|
|
131
|
+
lines = result.stdout.strip().split('\n')
|
|
132
|
+
if len(lines) >= 2:
|
|
133
|
+
model = lines[1].strip()
|
|
134
|
+
except:
|
|
135
|
+
pass
|
|
136
|
+
|
|
137
|
+
# Method 2: Try using PowerShell if WMI fails
|
|
138
|
+
if not model or model == platform.processor():
|
|
139
|
+
try:
|
|
140
|
+
result = subprocess.run(["powershell", "-Command", "(Get-CimInstance -Class Win32_Processor).Name"],
|
|
141
|
+
capture_output=True, text=True)
|
|
142
|
+
if result.returncode == 0 and result.stdout.strip():
|
|
143
|
+
model = result.stdout.strip()
|
|
144
|
+
except:
|
|
145
|
+
pass
|
|
146
|
+
|
|
147
|
+
# Method 3: Registry as fallback
|
|
148
|
+
if not model or model == platform.processor():
|
|
149
|
+
try:
|
|
150
|
+
result = subprocess.run(["reg", "query", "HKEY_LOCAL_MACHINE\\HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0",
|
|
151
|
+
"/v", "ProcessorNameString"], capture_output=True, text=True)
|
|
152
|
+
if result.returncode == 0:
|
|
153
|
+
for line in result.stdout.split('\n'):
|
|
154
|
+
if "ProcessorNameString" in line:
|
|
155
|
+
model = line.split('REG_SZ')[1].strip()
|
|
156
|
+
except:
|
|
157
|
+
pass
|
|
158
|
+
|
|
159
|
+
if model and model != platform.processor():
|
|
160
|
+
cpu_info["model"] = model
|
|
161
|
+
|
|
162
|
+
# Rest of the function remains the same...
|
|
163
|
+
# Get CPU frequency
|
|
164
|
+
|
|
165
|
+
# Get CPU frequency
|
|
166
|
+
result = subprocess.run(["wmic", "cpu", "get", "MaxClockSpeed"], capture_output=True, text=True)
|
|
167
|
+
if result.returncode == 0:
|
|
168
|
+
lines = result.stdout.strip().split('\n')
|
|
169
|
+
if len(lines) >= 2:
|
|
170
|
+
freq_mhz = lines[1].strip()
|
|
171
|
+
if freq_mhz.isdigit():
|
|
172
|
+
cpu_info["frequency"] = f"{int(freq_mhz)/1000:.2f} GHz"
|
|
173
|
+
|
|
174
|
+
# Try to get cache information
|
|
175
|
+
result = subprocess.run(["wmic", "cpu", "get", "L2CacheSize,L3CacheSize"], capture_output=True, text=True)
|
|
176
|
+
if result.returncode == 0:
|
|
177
|
+
lines = result.stdout.strip().split('\n')
|
|
178
|
+
if len(lines) >= 2:
|
|
179
|
+
cache_parts = lines[1].strip().split()
|
|
180
|
+
if len(cache_parts) >= 2:
|
|
181
|
+
l2_cache = f"{int(cache_parts[0])/1024:.1f} MB" if cache_parts[0].isdigit() else "Unknown"
|
|
182
|
+
l3_cache = f"{int(cache_parts[1])/1024:.1f} MB" if cache_parts[1].isdigit() else "Unknown"
|
|
183
|
+
cpu_info["cache"] = f"L2: {l2_cache}, L3: {l3_cache}"
|
|
184
|
+
|
|
185
|
+
# Try to get CPU process information from model name
|
|
186
|
+
model = cpu_info["model"].lower()
|
|
187
|
+
if any(x in model for x in ["ryzen", "epyc"]):
|
|
188
|
+
if "3000" in model or "4000" in model or "5000" in model:
|
|
189
|
+
cpu_info["process"] = "7nm"
|
|
190
|
+
elif "6000" in model or "7000" in model:
|
|
191
|
+
cpu_info["process"] = "5nm"
|
|
192
|
+
elif "7945" in model:
|
|
193
|
+
cpu_info["process"] = "4nm"
|
|
194
|
+
elif any(x in model for x in ["intel", "core", "xeon"]):
|
|
195
|
+
if "10th" in model or "10" in model:
|
|
196
|
+
cpu_info["process"] = "14nm"
|
|
197
|
+
elif "11th" in model or "11" in model:
|
|
198
|
+
cpu_info["process"] = "10nm"
|
|
199
|
+
elif "12th" in model or "12" in model:
|
|
200
|
+
cpu_info["process"] = "Intel 7 (10nm)"
|
|
201
|
+
elif "13th" in model or "13" in model or "14th" in model or "14" in model:
|
|
202
|
+
cpu_info["process"] = "Intel 7 (10nm)"
|
|
203
|
+
elif platform.system() == "Darwin":
|
|
204
|
+
# CPU model
|
|
205
|
+
result = subprocess.run(
|
|
206
|
+
["sysctl", "-n", "machdep.cpu.brand_string"],
|
|
207
|
+
capture_output=True,
|
|
208
|
+
text=True
|
|
209
|
+
)
|
|
210
|
+
if result.returncode == 0 and result.stdout.strip():
|
|
211
|
+
cpu_info["model"] = result.stdout.strip()
|
|
212
|
+
else:
|
|
213
|
+
# Apple Silicon fallback
|
|
214
|
+
result = subprocess.run(
|
|
215
|
+
["sysctl", "-n", "hw.model"],
|
|
216
|
+
capture_output=True,
|
|
217
|
+
text=True
|
|
218
|
+
)
|
|
219
|
+
cpu_info["model"] = result.stdout.strip()
|
|
220
|
+
|
|
221
|
+
# CPU frequency
|
|
222
|
+
result = subprocess.run(
|
|
223
|
+
["sysctl", "-n", "hw.cpufrequency"],
|
|
224
|
+
capture_output=True,
|
|
225
|
+
text=True
|
|
226
|
+
)
|
|
227
|
+
if result.returncode == 0 and result.stdout.strip().isdigit():
|
|
228
|
+
cpu_info["frequency"] = f"{int(result.stdout.strip()) / 1e9:.2f} GHz"
|
|
229
|
+
|
|
230
|
+
# Cache (best effort)
|
|
231
|
+
l2 = subprocess.run(["sysctl", "-n", "hw.l2cachesize"], capture_output=True, text=True)
|
|
232
|
+
l3 = subprocess.run(["sysctl", "-n", "hw.l3cachesize"], capture_output=True, text=True)
|
|
233
|
+
if l2.stdout.strip().isdigit() or l3.stdout.strip().isdigit():
|
|
234
|
+
cpu_info["cache"] = f"L2: {int(l2.stdout)/1024/1024:.1f} MB, L3: {int(l3.stdout)/1024/1024:.1f} MB"
|
|
235
|
+
|
|
236
|
+
# Apple Silicon process size
|
|
237
|
+
if platform.machine() == "arm64":
|
|
238
|
+
cpu_info["process"] = "5nm / 3nm (Apple Silicon)"
|
|
239
|
+
else:
|
|
240
|
+
# For Linux systems
|
|
241
|
+
if os.path.exists("/proc/cpuinfo"):
|
|
242
|
+
with open("/proc/cpuinfo", "r") as f:
|
|
243
|
+
cpuinfo = f.read()
|
|
244
|
+
|
|
245
|
+
# Extract model name
|
|
246
|
+
model_match = re.search(r"model name\s+:\s+(.*)", cpuinfo)
|
|
247
|
+
if model_match:
|
|
248
|
+
cpu_info["model"] = model_match.group(1)
|
|
249
|
+
|
|
250
|
+
# Extract CPU frequency
|
|
251
|
+
freq_match = re.search(r"cpu MHz\s+:\s+(.*)", cpuinfo)
|
|
252
|
+
if freq_match:
|
|
253
|
+
freq_mhz = float(freq_match.group(1))
|
|
254
|
+
cpu_info["frequency"] = f"{freq_mhz/1000:.2f} GHz"
|
|
255
|
+
|
|
256
|
+
# Extract cache info
|
|
257
|
+
cache_match = re.search(r"cache size\s+:\s+(.*)", cpuinfo)
|
|
258
|
+
if cache_match:
|
|
259
|
+
cpu_info["cache"] = cache_match.group(1)
|
|
260
|
+
except Exception as e:
|
|
261
|
+
pass
|
|
262
|
+
|
|
263
|
+
return cpu_info
|
|
264
|
+
def get_detailed_memory_info():
|
|
265
|
+
"""Get detailed memory information including slots, type, speed, and timings."""
|
|
266
|
+
memory_info = {
|
|
267
|
+
"slots": "Unknown",
|
|
268
|
+
"type": "Unknown",
|
|
269
|
+
"speed": "Unknown",
|
|
270
|
+
"timings": "Unknown"
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
try:
|
|
274
|
+
if platform.system() == "Windows":
|
|
275
|
+
# Try multiple methods to get accurate memory information
|
|
276
|
+
|
|
277
|
+
# Method 1: Try using PowerShell (more reliable than WMIC)
|
|
278
|
+
try:
|
|
279
|
+
# Get memory details using Get-CimInstance
|
|
280
|
+
ps_cmd = "Get-CimInstance -ClassName Win32_PhysicalMemory | Select-Object Capacity, Speed, ConfiguredClockSpeed, DeviceLocator, SMBIOSMemoryType, PartNumber | ConvertTo-Json"
|
|
281
|
+
result = subprocess.run(["powershell", "-Command", ps_cmd],
|
|
282
|
+
capture_output=True, text=True, timeout=5)
|
|
283
|
+
|
|
284
|
+
if result.returncode == 0 and result.stdout.strip():
|
|
285
|
+
# Try to parse PowerShell output as JSON
|
|
286
|
+
import json
|
|
287
|
+
try:
|
|
288
|
+
mem_data = json.loads(result.stdout)
|
|
289
|
+
# Convert to list if single object is returned
|
|
290
|
+
if isinstance(mem_data, dict):
|
|
291
|
+
mem_data = [mem_data]
|
|
292
|
+
|
|
293
|
+
# Process memory data
|
|
294
|
+
if mem_data:
|
|
295
|
+
# Parse memory slots info
|
|
296
|
+
slots = []
|
|
297
|
+
speeds = []
|
|
298
|
+
configured_speeds = []
|
|
299
|
+
mem_types = []
|
|
300
|
+
part_numbers = []
|
|
301
|
+
|
|
302
|
+
for module in mem_data:
|
|
303
|
+
# Get capacity
|
|
304
|
+
if "Capacity" in module and module["Capacity"]:
|
|
305
|
+
capacity_gb = int(module["Capacity"]) / (1024**3)
|
|
306
|
+
slots.append(f"{capacity_gb:.0f} GB")
|
|
307
|
+
|
|
308
|
+
# Get memory speed
|
|
309
|
+
if "Speed" in module and module["Speed"]:
|
|
310
|
+
speeds.append(f"{module['Speed']} MHz")
|
|
311
|
+
|
|
312
|
+
# Get configured clock speed (often more accurate)
|
|
313
|
+
if "ConfiguredClockSpeed" in module and module["ConfiguredClockSpeed"]:
|
|
314
|
+
configured_speeds.append(f"{module['ConfiguredClockSpeed']} MHz")
|
|
315
|
+
|
|
316
|
+
# Get memory type
|
|
317
|
+
if "SMBIOSMemoryType" in module and module["SMBIOSMemoryType"]:
|
|
318
|
+
mem_type_code = str(module["SMBIOSMemoryType"])
|
|
319
|
+
mem_type_map = {
|
|
320
|
+
"0": "Unknown", "1": "Other", "2": "DRAM",
|
|
321
|
+
"3": "Synchronous DRAM", "4": "Cache DRAM",
|
|
322
|
+
"5": "EDO", "6": "EDRAM", "7": "VRAM",
|
|
323
|
+
"8": "SRAM", "9": "RAM", "10": "ROM",
|
|
324
|
+
"11": "Flash", "12": "EEPROM", "13": "FEPROM",
|
|
325
|
+
"14": "EPROM", "15": "CDRAM", "16": "3DRAM",
|
|
326
|
+
"17": "SDRAM", "18": "SGRAM", "19": "RDRAM",
|
|
327
|
+
"20": "DDR", "21": "DDR2", "22": "DDR2 FB-DIMM",
|
|
328
|
+
"24": "DDR3", "26": "DDR4", "27": "DDR5",
|
|
329
|
+
"28": "LPDDR", "29": "LPDDR2", "30": "LPDDR3",
|
|
330
|
+
"31": "LPDDR4", "32": "LPDDR5"
|
|
331
|
+
}
|
|
332
|
+
mem_types.append(mem_type_map.get(mem_type_code, "Unknown"))
|
|
333
|
+
|
|
334
|
+
# Get part number (might contain timing info)
|
|
335
|
+
if "PartNumber" in module and module["PartNumber"]:
|
|
336
|
+
part_num = module["PartNumber"].strip()
|
|
337
|
+
if part_num:
|
|
338
|
+
part_numbers.append(part_num)
|
|
339
|
+
|
|
340
|
+
# Set memory info based on collected data
|
|
341
|
+
if slots:
|
|
342
|
+
memory_info["slots"] = f"{len(slots)} ({', '.join(slots)})"
|
|
343
|
+
|
|
344
|
+
# Prefer configured speeds if available
|
|
345
|
+
if configured_speeds and len(set(configured_speeds)) == 1:
|
|
346
|
+
memory_info["speed"] = configured_speeds[0]
|
|
347
|
+
elif configured_speeds:
|
|
348
|
+
memory_info["speed"] = ", ".join(configured_speeds)
|
|
349
|
+
elif speeds and len(set(speeds)) == 1:
|
|
350
|
+
memory_info["speed"] = speeds[0]
|
|
351
|
+
elif speeds:
|
|
352
|
+
memory_info["speed"] = ", ".join(speeds)
|
|
353
|
+
|
|
354
|
+
# Set memory type
|
|
355
|
+
if mem_types and len(set(mem_types)) == 1 and mem_types[0] != "Unknown":
|
|
356
|
+
memory_info["type"] = mem_types[0]
|
|
357
|
+
elif mem_types:
|
|
358
|
+
memory_info["type"] = ", ".join(set(mem_types))
|
|
359
|
+
|
|
360
|
+
# Try to extract timing info from part numbers
|
|
361
|
+
if part_numbers:
|
|
362
|
+
for part in part_numbers:
|
|
363
|
+
# Look for common timing patterns in part numbers
|
|
364
|
+
timing_match = re.search(r"[^0-9](\d{1,2})-(\d{1,2})-(\d{1,2})(?:-(\d{1,2}))?", part)
|
|
365
|
+
if timing_match:
|
|
366
|
+
if timing_match.group(4):
|
|
367
|
+
memory_info["timings"] = f"CL{timing_match.group(1)}-{timing_match.group(2)}-{timing_match.group(3)}-{timing_match.group(4)}"
|
|
368
|
+
else:
|
|
369
|
+
memory_info["timings"] = f"CL{timing_match.group(1)}-{timing_match.group(2)}-{timing_match.group(3)}"
|
|
370
|
+
break
|
|
371
|
+
|
|
372
|
+
# Second pattern attempt
|
|
373
|
+
cl_match = re.search(r"CL(\d{1,2})", part, re.IGNORECASE)
|
|
374
|
+
if cl_match:
|
|
375
|
+
memory_info["timings"] = f"CL{cl_match.group(1)} (partial)"
|
|
376
|
+
break
|
|
377
|
+
except json.JSONDecodeError:
|
|
378
|
+
pass
|
|
379
|
+
except (subprocess.SubprocessError, subprocess.TimeoutExpired):
|
|
380
|
+
pass
|
|
381
|
+
|
|
382
|
+
# Method 2: Fall back to WMIC if PowerShell approach failed
|
|
383
|
+
if memory_info["slots"] == "Unknown":
|
|
384
|
+
# Use the existing WMIC implementation
|
|
385
|
+
result = subprocess.run(["wmic", "memorychip", "get", "Capacity,Speed,DeviceLocator,MemoryType"],
|
|
386
|
+
capture_output=True, text=True)
|
|
387
|
+
if result.returncode == 0:
|
|
388
|
+
lines = result.stdout.strip().split('\n')
|
|
389
|
+
if len(lines) >= 2:
|
|
390
|
+
# Parse memory slots info
|
|
391
|
+
slots = []
|
|
392
|
+
speeds = []
|
|
393
|
+
mem_types = []
|
|
394
|
+
|
|
395
|
+
for line in lines[1:]:
|
|
396
|
+
if line.strip():
|
|
397
|
+
parts = line.strip().split()
|
|
398
|
+
if len(parts) >= 3:
|
|
399
|
+
try:
|
|
400
|
+
capacity = int(parts[0])
|
|
401
|
+
slots.append(f"{capacity / (1024**3):.0f} GB")
|
|
402
|
+
|
|
403
|
+
if parts[-2].isdigit():
|
|
404
|
+
speeds.append(f"{parts[-2]} MHz")
|
|
405
|
+
|
|
406
|
+
# Convert memory type code to human-readable
|
|
407
|
+
mem_type_code = parts[-1] if parts[-1].isdigit() else "0"
|
|
408
|
+
mem_type = "Unknown"
|
|
409
|
+
mem_type_map = {
|
|
410
|
+
"0": "Unknown", "1": "Other", "2": "DRAM",
|
|
411
|
+
"3": "Synchronous DRAM", "4": "Cache DRAM",
|
|
412
|
+
"5": "EDO", "6": "EDRAM", "7": "VRAM",
|
|
413
|
+
"8": "SRAM", "9": "RAM", "10": "ROM",
|
|
414
|
+
"11": "Flash", "12": "EEPROM", "13": "FEPROM",
|
|
415
|
+
"14": "EPROM", "15": "CDRAM", "16": "3DRAM",
|
|
416
|
+
"17": "SDRAM", "18": "SGRAM", "19": "RDRAM",
|
|
417
|
+
"20": "DDR", "21": "DDR2", "22": "DDR2 FB-DIMM",
|
|
418
|
+
"24": "DDR3", "26": "DDR4", "27": "DDR5",
|
|
419
|
+
"28": "LPDDR", "29": "LPDDR2", "30": "LPDDR3",
|
|
420
|
+
"31": "LPDDR4", "32": "LPDDR5"
|
|
421
|
+
}
|
|
422
|
+
mem_types.append(mem_type_map.get(mem_type_code, "Unknown"))
|
|
423
|
+
except:
|
|
424
|
+
pass
|
|
425
|
+
|
|
426
|
+
if slots:
|
|
427
|
+
memory_info["slots"] = f"{len(slots)} ({', '.join(slots)})"
|
|
428
|
+
if speeds and len(set(speeds)) == 1:
|
|
429
|
+
memory_info["speed"] = speeds[0]
|
|
430
|
+
elif speeds:
|
|
431
|
+
memory_info["speed"] = ', '.join(speeds)
|
|
432
|
+
if mem_types and len(set(mem_types)) == 1 and mem_types[0] != "Unknown":
|
|
433
|
+
memory_info["type"] = mem_types[0]
|
|
434
|
+
|
|
435
|
+
# Method 3: Use Windows Management Instrumentation Command-line (WMIC)
|
|
436
|
+
# to get SPD data if timing info is still unknown
|
|
437
|
+
if memory_info["timings"] == "Unknown":
|
|
438
|
+
try:
|
|
439
|
+
# Try to get SPD info using WMI
|
|
440
|
+
ps_cmd = """
|
|
441
|
+
$SPDInfoClass = Get-WmiObject -Class MSMemory_MemoryDevice
|
|
442
|
+
$SPDInfoClass | Select-Object DataWidth, TotalWidth, Speed | ConvertTo-Json
|
|
443
|
+
"""
|
|
444
|
+
result = subprocess.run(["powershell", "-Command", ps_cmd],
|
|
445
|
+
capture_output=True, text=True, timeout=5)
|
|
446
|
+
|
|
447
|
+
if result.returncode == 0 and result.stdout.strip():
|
|
448
|
+
# Try to determine memory timings from detailed memory specs
|
|
449
|
+
if memory_info["type"] != "Unknown" and memory_info["speed"] != "Unknown":
|
|
450
|
+
# Get the speed in MHz
|
|
451
|
+
speed_match = re.search(r"(\d+)\s*MHz", memory_info["speed"])
|
|
452
|
+
if speed_match:
|
|
453
|
+
speed_mhz = int(speed_match.group(1))
|
|
454
|
+
|
|
455
|
+
# Determine memory timings based on type and speed
|
|
456
|
+
if "DDR4" in memory_info["type"]:
|
|
457
|
+
if speed_mhz >= 3600:
|
|
458
|
+
memory_info["timings"] = "CL16-18-18-38 (typical)"
|
|
459
|
+
elif speed_mhz >= 3200:
|
|
460
|
+
memory_info["timings"] = "CL16-18-18-36 (typical)"
|
|
461
|
+
elif speed_mhz >= 2666:
|
|
462
|
+
memory_info["timings"] = "CL16-18-18-35 (typical)"
|
|
463
|
+
else:
|
|
464
|
+
memory_info["timings"] = "CL16-16-16-32 (typical)"
|
|
465
|
+
elif "DDR5" in memory_info["type"]:
|
|
466
|
+
if speed_mhz >= 6000:
|
|
467
|
+
memory_info["timings"] = "CL40-40-40-76 (typical)"
|
|
468
|
+
elif speed_mhz >= 5600:
|
|
469
|
+
memory_info["timings"] = "CL36-36-36-76 (typical)"
|
|
470
|
+
elif speed_mhz >= 4800:
|
|
471
|
+
memory_info["timings"] = "CL34-34-34-68 (typical)"
|
|
472
|
+
else:
|
|
473
|
+
memory_info["timings"] = "CL30-30-30-60 (typical)"
|
|
474
|
+
elif "DDR3" in memory_info["type"]:
|
|
475
|
+
if speed_mhz >= 2133:
|
|
476
|
+
memory_info["timings"] = "CL15-15-15-35 (typical)"
|
|
477
|
+
elif speed_mhz >= 1866:
|
|
478
|
+
memory_info["timings"] = "CL13-13-13-32 (typical)"
|
|
479
|
+
elif speed_mhz >= 1600:
|
|
480
|
+
memory_info["timings"] = "CL11-11-11-28 (typical)"
|
|
481
|
+
else:
|
|
482
|
+
memory_info["timings"] = "CL9-9-9-24 (typical)"
|
|
483
|
+
except (subprocess.SubprocessError, subprocess.TimeoutExpired):
|
|
484
|
+
pass
|
|
485
|
+
|
|
486
|
+
# Method 4: Get memory details from CPU-Z report if available (Windows only)
|
|
487
|
+
if memory_info["type"] == "Unknown" or memory_info["timings"] == "Unknown":
|
|
488
|
+
try:
|
|
489
|
+
# Check if CPU-Z is installed (common location)
|
|
490
|
+
cpuz_paths = [
|
|
491
|
+
os.path.join(os.environ.get('ProgramFiles', ''), 'CPUID\\CPU-Z\\cpuz.exe'),
|
|
492
|
+
os.path.join(os.environ.get('ProgramFiles(x86)', ''), 'CPUID\\CPU-Z\\cpuz.exe')
|
|
493
|
+
]
|
|
494
|
+
|
|
495
|
+
cpuz_installed = any(os.path.exists(path) for path in cpuz_paths)
|
|
496
|
+
|
|
497
|
+
if cpuz_installed:
|
|
498
|
+
# CPU-Z is installed, we could potentially run it in command line mode
|
|
499
|
+
# This is a placeholder - CPU-Z CLI options are limited
|
|
500
|
+
pass
|
|
501
|
+
except:
|
|
502
|
+
pass
|
|
503
|
+
elif platform.system() == "Darwin":
|
|
504
|
+
mem = psutil.virtual_memory()
|
|
505
|
+
memory_info["slots"] = "Unified Memory"
|
|
506
|
+
memory_info["type"] = "LPDDR (Apple Silicon)" if platform.machine() == "arm64" else "DDR"
|
|
507
|
+
memory_info["speed"] = "Unified (SoC)"
|
|
508
|
+
memory_info["timings"] = "N/A"
|
|
509
|
+
else:
|
|
510
|
+
# The Linux section remains the same
|
|
511
|
+
# For Linux systems
|
|
512
|
+
if os.path.exists("/proc/meminfo"):
|
|
513
|
+
# Try using dmidecode if available
|
|
514
|
+
try:
|
|
515
|
+
# This requires sudo permissions - may not work without them
|
|
516
|
+
result = subprocess.run(["sudo", "dmidecode", "--type", "memory"], capture_output=True, text=True)
|
|
517
|
+
if result.returncode == 0:
|
|
518
|
+
output = result.stdout
|
|
519
|
+
|
|
520
|
+
# Count memory slots
|
|
521
|
+
slot_count = output.count("Memory Device")
|
|
522
|
+
if slot_count > 0:
|
|
523
|
+
memory_info["slots"] = str(slot_count)
|
|
524
|
+
|
|
525
|
+
# Look for type
|
|
526
|
+
type_match = re.search(r"Type: (DDR\d+)", output)
|
|
527
|
+
if type_match:
|
|
528
|
+
memory_info["type"] = type_match.group(1)
|
|
529
|
+
|
|
530
|
+
# Look for speed
|
|
531
|
+
speed_match = re.search(r"Speed: (\d+ MHz)", output)
|
|
532
|
+
if speed_match:
|
|
533
|
+
memory_info["speed"] = speed_match.group(1)
|
|
534
|
+
except:
|
|
535
|
+
pass
|
|
536
|
+
except Exception as e:
|
|
537
|
+
pass
|
|
538
|
+
|
|
539
|
+
return memory_info
|
|
540
|
+
def check_cuda_version():
|
|
541
|
+
try:
|
|
542
|
+
if platform.system() == "Windows":
|
|
543
|
+
# Try to get CUDA version from nvcc
|
|
544
|
+
result = subprocess.run(["nvcc", "--version"], capture_output=True, text=True)
|
|
545
|
+
if result.returncode == 0:
|
|
546
|
+
for line in result.stdout.split('\n'):
|
|
547
|
+
if "release" in line.lower() and "V" in line:
|
|
548
|
+
return line.split("V")[1].split(" ")[0]
|
|
549
|
+
else:
|
|
550
|
+
# For Linux/macOS
|
|
551
|
+
cuda_path = "/usr/local/cuda/bin/nvcc"
|
|
552
|
+
if os.path.exists(cuda_path):
|
|
553
|
+
result = subprocess.run([cuda_path, "--version"], capture_output=True, text=True)
|
|
554
|
+
if result.returncode == 0:
|
|
555
|
+
for line in result.stdout.split('\n'):
|
|
556
|
+
if "release" in line.lower() and "V" in line:
|
|
557
|
+
return line.split("V")[1].split(" ")[0]
|
|
558
|
+
|
|
559
|
+
# Alternative check with nvidia-smi for CUDA driver version
|
|
560
|
+
result = subprocess.run(["nvidia-smi"], capture_output=True, text=True)
|
|
561
|
+
if result.returncode == 0:
|
|
562
|
+
for line in result.stdout.split('\n'):
|
|
563
|
+
if "CUDA Version:" in line:
|
|
564
|
+
return line.split("CUDA Version:")[1].strip()
|
|
565
|
+
|
|
566
|
+
return "Not detected"
|
|
567
|
+
except:
|
|
568
|
+
return "Not detected"
|
|
569
|
+
|
|
570
|
+
def check_opencl_version():
|
|
571
|
+
try:
|
|
572
|
+
# Try to detect OpenCL with a simple command
|
|
573
|
+
if platform.system() == "Windows":
|
|
574
|
+
result = subprocess.run(["clinfo", "--version"], capture_output=True, text=True)
|
|
575
|
+
if result.returncode == 0:
|
|
576
|
+
for line in result.stdout.split('\n'):
|
|
577
|
+
if "OpenCL" in line:
|
|
578
|
+
return line.strip()
|
|
579
|
+
elif platform.system() == "Darwin":
|
|
580
|
+
return "OpenCL supported (Apple framework)"
|
|
581
|
+
else:
|
|
582
|
+
# For Linux
|
|
583
|
+
if os.path.exists("/usr/bin/clinfo"):
|
|
584
|
+
result = subprocess.run(["clinfo", "--version"], capture_output=True, text=True)
|
|
585
|
+
if result.returncode == 0:
|
|
586
|
+
for line in result.stdout.split('\n'):
|
|
587
|
+
if "OpenCL" in line:
|
|
588
|
+
return line.strip()
|
|
589
|
+
|
|
590
|
+
return "Not detected"
|
|
591
|
+
except:
|
|
592
|
+
return "Not detected"
|
|
593
|
+
|
|
594
|
+
def check_vulkan_version():
|
|
595
|
+
try:
|
|
596
|
+
# Try to detect Vulkan with vulkaninfo
|
|
597
|
+
if platform.system() == "Windows":
|
|
598
|
+
result = subprocess.run(["vulkaninfo", "--summary"], capture_output=True, text=True)
|
|
599
|
+
elif platform.system() == "Darwin":
|
|
600
|
+
return "Not supported (use MoltenVK)"
|
|
601
|
+
else:
|
|
602
|
+
result = subprocess.run(["vulkaninfo"], capture_output=True, text=True)
|
|
603
|
+
|
|
604
|
+
if result.returncode == 0:
|
|
605
|
+
for line in result.stdout.split('\n'):
|
|
606
|
+
if "Vulkan Instance Version:" in line:
|
|
607
|
+
return line.split("Vulkan Instance Version:")[1].strip()
|
|
608
|
+
if "Vulkan API Version:" in line:
|
|
609
|
+
return line.split("Vulkan API Version:")[1].strip()
|
|
610
|
+
|
|
611
|
+
return "Not detected"
|
|
612
|
+
except:
|
|
613
|
+
return "Not detected"
|
|
614
|
+
|
|
615
|
+
def get_uptime():
|
|
616
|
+
if platform.system() == "Windows":
|
|
617
|
+
boot_time = datetime.datetime.fromtimestamp(psutil.boot_time())
|
|
618
|
+
elif platform.system() == "Darwin":
|
|
619
|
+
result = subprocess.run(
|
|
620
|
+
["sysctl", "-n", "kern.boottime"],
|
|
621
|
+
capture_output=True,
|
|
622
|
+
text=True
|
|
623
|
+
)
|
|
624
|
+
match = re.search(r"sec = (\d+)", result.stdout)
|
|
625
|
+
boot_time = datetime.datetime.fromtimestamp(int(match.group(1))) if match else datetime.datetime.now()
|
|
626
|
+
else:
|
|
627
|
+
with open('/proc/uptime', 'r') as f:
|
|
628
|
+
uptime_seconds = float(f.readline().split()[0])
|
|
629
|
+
boot_time = datetime.datetime.now() - datetime.timedelta(seconds=uptime_seconds)
|
|
630
|
+
|
|
631
|
+
uptime = datetime.datetime.now() - boot_time
|
|
632
|
+
days, remainder = divmod(uptime.total_seconds(), 86400)
|
|
633
|
+
hours, remainder = divmod(remainder, 3600)
|
|
634
|
+
minutes, _ = divmod(remainder, 60)
|
|
635
|
+
return f"{int(days)} days, {int(hours)} hours, {int(minutes)} minutes"
|
|
636
|
+
|
|
637
|
+
def bytes_to_readable(bytes):
|
|
638
|
+
for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
|
|
639
|
+
if bytes < 1024:
|
|
640
|
+
return f"{bytes:.2f} {unit}"
|
|
641
|
+
bytes /= 1024
|
|
642
|
+
return f"{bytes:.2f} PB"
|
|
643
|
+
|
|
644
|
+
def get_ascii_logo(system):
|
|
645
|
+
logos = {
|
|
646
|
+
"Windows": [
|
|
647
|
+
" ..,",
|
|
648
|
+
" ....,,:;+ccllll",
|
|
649
|
+
" ...,,+:; cllllllllllllllllll",
|
|
650
|
+
",cclllllllllll lllllllllllllllllll",
|
|
651
|
+
"llllllllllllll lllllllllllllllllll",
|
|
652
|
+
"llllllllllllll lllllllllllllllllll",
|
|
653
|
+
"llllllllllllll lllllllllllllllllll",
|
|
654
|
+
"llllllllllllll lllllllllllllllllll",
|
|
655
|
+
"llllllllllllll lllllllllllllllllll",
|
|
656
|
+
" ",
|
|
657
|
+
"llllllllllllll lllllllllllllllllll",
|
|
658
|
+
"llllllllllllll lllllllllllllllllll",
|
|
659
|
+
"llllllllllllll lllllllllllllllllll",
|
|
660
|
+
"llllllllllllll lllllllllllllllllll",
|
|
661
|
+
"llllllllllllll lllllllllllllllllll",
|
|
662
|
+
"`'ccllllllllll lllllllllllllllllll",
|
|
663
|
+
" `' \\*:: :ccllllllllllllllll",
|
|
664
|
+
" ````''*::cll",
|
|
665
|
+
" ``",
|
|
666
|
+
],
|
|
667
|
+
"Linux": [
|
|
668
|
+
" _nnnn_ ",
|
|
669
|
+
" dGGGGMMb ",
|
|
670
|
+
" @p~qp~~qMb ",
|
|
671
|
+
" M|@||@) M| ",
|
|
672
|
+
" @,----.JM| ",
|
|
673
|
+
" JS^\\__/ qKL ",
|
|
674
|
+
" dZP qKRb ",
|
|
675
|
+
" dZP qKKb ",
|
|
676
|
+
" fZP SMMb ",
|
|
677
|
+
" HZM MMMM ",
|
|
678
|
+
" FqM MMMM ",
|
|
679
|
+
" __| \". |\\dS\"qML ",
|
|
680
|
+
" | `. | `' \\Zq ",
|
|
681
|
+
"_) \\.___.,| .' ",
|
|
682
|
+
"\\____ )MMMMMP| .' ",
|
|
683
|
+
" `-' `--' ",
|
|
684
|
+
],
|
|
685
|
+
"Darwin": [
|
|
686
|
+
" ##### ",
|
|
687
|
+
" ###### ",
|
|
688
|
+
" ###### ",
|
|
689
|
+
" ####### ",
|
|
690
|
+
" ####### ",
|
|
691
|
+
" ####### ",
|
|
692
|
+
" ####### ",
|
|
693
|
+
" ####### ##### ",
|
|
694
|
+
" ####### ####### ",
|
|
695
|
+
" ################## ",
|
|
696
|
+
" ################# ",
|
|
697
|
+
" ################ ",
|
|
698
|
+
" ############### ",
|
|
699
|
+
" ############## ",
|
|
700
|
+
" ############# ",
|
|
701
|
+
" ############ ",
|
|
702
|
+
" ########### ",
|
|
703
|
+
],
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
default_logo = [
|
|
707
|
+
" _____ _____ __ ",
|
|
708
|
+
" | __ \\ / ____| / _| ",
|
|
709
|
+
" | |__) | | (___ ___ | |_ ___ ",
|
|
710
|
+
" | ___/ | | \\___ \\ / _ \\| _/ _ \\ ",
|
|
711
|
+
" | | | |_| |___) | (_) | || (_) | ",
|
|
712
|
+
" |_| \\__, |____/ \\___/|_| \\___/ ",
|
|
713
|
+
" __/ | ",
|
|
714
|
+
" |___/ ",
|
|
715
|
+
]
|
|
716
|
+
|
|
717
|
+
return logos.get(system, default_logo)
|
|
718
|
+
|
|
719
|
+
def print_system_info():
|
|
720
|
+
colorama.init()
|
|
721
|
+
system_info = get_system_info()
|
|
722
|
+
ascii_logo = get_ascii_logo(platform.system())
|
|
723
|
+
|
|
724
|
+
# ANSI Color codes for different OS
|
|
725
|
+
color = Fore.CYAN # Default color
|
|
726
|
+
if platform.system() == "Windows":
|
|
727
|
+
color = Fore.BLUE
|
|
728
|
+
elif platform.system() == "Linux":
|
|
729
|
+
color = Fore.GREEN
|
|
730
|
+
elif platform.system() == "Darwin":
|
|
731
|
+
color = Fore.WHITE
|
|
732
|
+
|
|
733
|
+
# Header
|
|
734
|
+
print(f"\n{Style.BRIGHT}{Back.BLACK}{color}╒════════════════════════════════════════════════════════════════╕{Style.RESET_ALL}")
|
|
735
|
+
print(f"{Style.BRIGHT}{Back.BLACK}{color}│ PySinfo: A Python Tool to Get System Infomation │{Style.RESET_ALL}")
|
|
736
|
+
print(f"{Style.BRIGHT}{Back.BLACK}{color}╘════════════════════════════════════════════════════════════════╛{Style.RESET_ALL}\n")
|
|
737
|
+
|
|
738
|
+
# Prepare data display
|
|
739
|
+
data_lines = [
|
|
740
|
+
f"{color}OS:{Style.RESET_ALL} {system_info['os']}",
|
|
741
|
+
f"{color}Host:{Style.RESET_ALL} {system_info['hostname']}",
|
|
742
|
+
f"{color}Kernel:{Style.RESET_ALL} {system_info['kernel']}",
|
|
743
|
+
f"{color}Uptime:{Style.RESET_ALL} {system_info['uptime']}",
|
|
744
|
+
f"{color}Shell:{Style.RESET_ALL} {os.path.basename(system_info['shell'])}",
|
|
745
|
+
f"{color}Terminal:{Style.RESET_ALL} {system_info['terminal']}",
|
|
746
|
+
"",
|
|
747
|
+
f"{Style.BRIGHT}{color}Hardware:{Style.RESET_ALL}",
|
|
748
|
+
f"{color}CPU Model:{Style.RESET_ALL} {system_info['cpu_model']}",
|
|
749
|
+
f"{color}CPU Architecture:{Style.RESET_ALL} {system_info['cpu_arch']}",
|
|
750
|
+
f"{color}CPU Current Freq:{Style.RESET_ALL} {system_info['cpu_freq']}",
|
|
751
|
+
f"{color}CPU Cores:{Style.RESET_ALL} {system_info['cpu_cores']}",
|
|
752
|
+
f"{color}CPU Usage:{Style.RESET_ALL} {system_info['cpu_usage']}",
|
|
753
|
+
f"{color}GPU:{Style.RESET_ALL} {system_info['gpu']}",
|
|
754
|
+
f"{color}Memory:{Style.RESET_ALL} {system_info['memory']}",
|
|
755
|
+
f"{color}Memory Slots:{Style.RESET_ALL} {system_info['memory_slots']}",
|
|
756
|
+
f"{color}Memory Speed:{Style.RESET_ALL} {system_info['memory_speed']}",
|
|
757
|
+
f"{color}Disk:{Style.RESET_ALL} {system_info['disk']}",
|
|
758
|
+
"",
|
|
759
|
+
f"{Style.BRIGHT}{color}Network:{Style.RESET_ALL}",
|
|
760
|
+
f"{color}IP Address:{Style.RESET_ALL} {system_info['ip_address']}",
|
|
761
|
+
"",
|
|
762
|
+
f"{Style.BRIGHT}{color}Development Environment:{Style.RESET_ALL}",
|
|
763
|
+
f"{color}Python:{Style.RESET_ALL} {system_info['python_version']}",
|
|
764
|
+
f"{color}Python Path:{Style.RESET_ALL} {system_info['python_path']}",
|
|
765
|
+
"",
|
|
766
|
+
f"{Style.BRIGHT}{color}Graphics API Support:{Style.RESET_ALL}",
|
|
767
|
+
f"{color}CUDA:{Style.RESET_ALL} {system_info['cuda']}",
|
|
768
|
+
f"{color}OpenCL:{Style.RESET_ALL} {system_info['opencl']}",
|
|
769
|
+
f"{color}Vulkan:{Style.RESET_ALL} {system_info['vulkan']}",
|
|
770
|
+
]
|
|
771
|
+
|
|
772
|
+
# Calculate spacing
|
|
773
|
+
logo_width = max(len(line) for line in ascii_logo)
|
|
774
|
+
spacing = 4
|
|
775
|
+
|
|
776
|
+
# Print logo and info side by side
|
|
777
|
+
logo_length = len(ascii_logo)
|
|
778
|
+
for i in range(min(logo_length, len(data_lines))):
|
|
779
|
+
logo_line = ascii_logo[i]
|
|
780
|
+
info_line = data_lines[i]
|
|
781
|
+
print(f"{color}{logo_line}{' ' * spacing}{Style.RESET_ALL}{info_line}")
|
|
782
|
+
|
|
783
|
+
# Print remaining info
|
|
784
|
+
for i in range(logo_length, len(data_lines)):
|
|
785
|
+
print(f"{' ' * (logo_width + spacing)}{data_lines[i]}")
|
|
786
|
+
|
|
787
|
+
# Print color blocks
|
|
788
|
+
print("\n" + "".join(f"{color}██████{Style.RESET_ALL}" for color in [Fore.RED, Fore.GREEN, Fore.YELLOW, Fore.BLUE, Fore.MAGENTA, Fore.CYAN, Fore.WHITE]))
|
|
789
|
+
|
|
790
|
+
# Footer
|
|
791
|
+
current_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
792
|
+
print(f"\n{Style.DIM}Report generated on: {current_time}{Style.RESET_ALL}")
|
|
793
|
+
|
|
794
|
+
if __name__ == "__main__":
|
|
795
|
+
print_system_info()
|