onboarding/specs.py

283 lines
9.3 KiB
Python
Raw Permalink Normal View History

2024-10-04 16:50:23 +00:00
# Library to load specs of machine without the need for any 3rd party libs
import subprocess
import xml.etree.ElementTree as ET
import requests
import shutil
import time
import json
import os
def drop_caches():
try:
with open('/proc/sys/vm/drop_caches', 'w') as f:
f.write('3\n')
except Exception as e:
pass
def write_test(file_path, block_size, num_blocks):
data = os.urandom(block_size)
total_bytes = block_size * num_blocks
start_time = time.time()
with open(file_path, 'wb') as f:
for _ in range(num_blocks):
f.write(data)
f.flush()
os.fsync(f.fileno())
elapsed_time = time.time() - start_time
write_speed = total_bytes / elapsed_time / (1024 * 1024)
return write_speed, elapsed_time
def read_test(file_path, block_size, num_blocks):
total_bytes = block_size * num_blocks
# Drop caches to avoid OS-level caching effects
drop_caches()
start_time = time.time()
with open(file_path, 'rb') as f:
for _ in range(num_blocks):
data = f.read(block_size)
if not data:
break
elapsed_time = time.time() - start_time
read_speed = total_bytes / elapsed_time / (1024 * 1024)
return read_speed, elapsed_time
def disk_benchmark():
total, used, free = shutil.disk_usage("/")
free_gb = free/1024/1024/1024
if free_gb<1:
return 0,0
block_size = 1024*1024
num_blocks = 250 if free_gb < 3 else 1500
file_path="/tmp/output"
print("Running disk benchmark...")
print(f"Block Size: {block_size} bytes, Number of Blocks: {num_blocks}")
# Run write test
write_speed, write_time = write_test(file_path, block_size, num_blocks)
print(f"Write Speed: {write_speed:.2f} MB/s, Time: {write_time:.2f} seconds")
# Run read test
read_speed, read_time = read_test(file_path, block_size, num_blocks)
print(f"Read Speed: {read_speed:.2f} MB/s, Time: {read_time:.2f} seconds")
# Cleanup
os.remove(file_path)
return float(round(write_speed,2)), float(round(read_speed,2))
def get_root_disk_model():
try:
# Get the root filesystem mount point
root_mount = '/'
# Find the device associated with the root mount
cmd_find_device = ['df', root_mount]
output = subprocess.check_output(cmd_find_device).decode()
lines = output.splitlines()
device_line = lines[1] # Second line contains the device info
device_path = device_line.split()[0]
# Strip partition number to get the main disk device
if device_path.startswith('/dev/'):
device_path = device_path.rstrip('0123456789')
# Get disk model information using lsblk command
cmd_lsblk = ['lsblk', '-no', 'MODEL', device_path]
model = subprocess.check_output(cmd_lsblk).decode().strip()
# Check if the disk is virtual
virtual_identifiers = ['virtual', 'qemu', 'vmware', 'hyper-v', 'vbox']
if any(virtual_id.lower() in model.lower() for virtual_id in virtual_identifiers):
return "Virtual"
return model
except Exception:
return ""
def get_free_space_gb():
try:
statvfs = os.statvfs('/')
free_space_gb = (statvfs.f_frsize * statvfs.f_bfree) / (1024 ** 3)
return round(free_space_gb, 2)
except Exception:
print("Can't measure the available disk space")
os._exit(1)
return ""
def all_items_same(lst):
if len(lst) <= 1:
return True
return all(item == lst[0] for item in lst)
def get_motherboard_name():
filepath = "/sys/devices/virtual/dmi/id/board_name"
if os.path.exists(filepath):
try:
with open(filepath, 'r') as file:
return file.read().strip().replace('\n','')[:32]
except Exception as e:
print(f"Error reading Motherboard name: {e}")
return "Unknown"
else:
return "Unknown"
def get_cpu_info():
lscpu_out = subprocess.check_output(['lscpu']).decode('utf-8')
threads_per_code=1
total_threads = os.cpu_count()
model_name = "Unknown CPU"
for line in lscpu_out.split('\n'):
try:
key, value = line.split(':', 1)
value=value.strip(' ')
if "model name" in key.lower():
model_name=value
elif "Thread(s) per core" == key and int(value):
threads_per_code=int(value)
except Exception as e:
pass
total_cores = int(total_threads/threads_per_code)
return total_threads, total_cores, model_name
def get_ram_size():
try:
with open('/proc/meminfo', 'r') as f:
lines = f.readlines()
for line in lines:
if line.startswith('MemTotal'):
total_memory_kb = int(line.split()[1])
total_memory_gb = total_memory_kb / (1024) / 1000
return round(total_memory_gb, 4)
except Exception as e:
return 0
def get_gpu_info_from_xml():
result = subprocess.run(['nvidia-smi', '-x', '-q'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if result.returncode != 0:
print(f"Error running nvidia-smi: {result.stderr}")
os._exit(1)
xml_output = result.stdout
gpu_names = []
gpu_vram_sizes = []
try:
root = ET.fromstring(xml_output)
for gpu in root.findall('.//gpu'):
product_name = gpu.find('product_name')
fb_memory_usage = gpu.find('.//fb_memory_usage')
total_memory = fb_memory_usage.find('total') if fb_memory_usage is not None else None
if product_name is not None and total_memory is not None:
memory_size_mb = total_memory.text.split()[0]
memory_size_gb = int(round(float(memory_size_mb) / 1024, 0))
gpu_names.append(product_name.text)
gpu_vram_sizes.append(memory_size_gb)
except ET.ParseError as e:
print(f"Error parsing XML: {e}")
return gpu_names, gpu_vram_sizes
def get_country():
# Define the directory and file path
directory_path = "/opt/clore-hosting"
file_path = os.path.join(directory_path, "CC")
# Check if the file exists; if so, read from the file
if os.path.exists(file_path):
try:
with open(file_path, "r") as file:
country = file.read().strip()
if country: # Ensure the file is not empty
return country
except IOError as e:
print(f"Error reading the country from file: {e}")
apis = [
"https://ipinfo.io/country"
]
headers = {
'User-Agent': 'CloreAutoOnboard/1.0'
}
for api in apis:
try:
response = requests.get(api, headers=headers, timeout=5)
response.raise_for_status() # Raise an exception for HTTP errors
country = response.text.strip() # Remove any extra whitespace/newlines
if country: # Check if the response is not empty
save_data_to_file(country, directory_path, file_path)
return country
except (requests.RequestException, ValueError) as e:
print(f"Error with {api}: {e}")
continue
return "Unable to determine country"
def save_data_to_file(data, directory_path, file_path):
if not os.path.exists(directory_path):
os.makedirs(directory_path)
try:
with open(file_path, "w") as file:
file.write(data)
except IOError as e:
print(f"Error saving the data to file: {e}")
def get(benchmark_disk=False, mock=False):
if mock:
return {'mb': 'X99-6PLUS', 'cpu': 'Intel(R) Xeon(R) CPU E5-2698 v3 @ 2.30GHz', 'cpus': '32/64', 'ram': 128.8009, 'gpu': '1x NVIDIA GeForce RTX 4090', 'gpuram': 24, 'disk': 'MSATA SSD 1TB 878.78GB', 'disk_speed': 0, 'net': {'up': 0, 'down': 0, 'cc': 'UA'}}
threads, cores, model = get_cpu_info()
gpu_names, gpu_vram_sizes = get_gpu_info_from_xml()
disk_speeds = None
if len(gpu_names)==0:
print("No nvidia GPUs found")
os._exit(1)
if benchmark_disk:
benchmark_file = "/opt/clore-hosting/disk_benchmark.json"
if os.path.exists(benchmark_file):
try:
with open(benchmark_file, "r") as file:
benchmark_result = file.read().strip()
benchmark_result = json.loads(benchmark_result)
disk_speeds = benchmark_result
except Exception as load_fail:
pass
if not disk_speeds:
disk_speeds = disk_benchmark()
save_data_to_file(json.dumps(disk_speeds), "/opt/clore-hosting", "/opt/clore-hosting/disk_benchmark.json")
else:
disk_speeds = [0,0]
specs = {
"mb": get_motherboard_name(),
"cpu": model,
"cpus": f"{cores}/{threads}",
"ram": get_ram_size(),
"gpu": f"{len(gpu_names)}x "+(gpu_names[0] if all_items_same(gpu_names) else "Mixed GPUs"),
"gpuram": min(gpu_vram_sizes),
"disk": get_root_disk_model()[:32]+f' {get_free_space_gb()}GB',
"disk_speed": disk_speeds[1],
"net":{
"up": 0,
"down": 0,
"cc": get_country()
}
}
return specs