Zeroconf reports system properties (#59)

* Zeroconf.found_clients() now returns dicts of clients, not just hostnames

* Adjustments to distributed_job_manager.py

* Undo config change

* Report system metrics (cpu, os, etc) via zeroconf_server.py

* Zeroconf.found_clients() now returns dicts of clients, not just hostnames

* Adjustments to distributed_job_manager.py

* Undo config change

* Zeroconf.found_clients() now returns dicts of clients, not just hostnames

* Adjustments to distributed_job_manager.py

* Undo config change

* Adjustments to distributed_job_manager.py

* Undo config change

* Rename ZeroconfServer.found_clients() to found_hostnames()
This commit is contained in:
2023-11-04 20:46:27 -05:00
committed by GitHub
parent d3b84c6212
commit 06a613fcc4
9 changed files with 38 additions and 33 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 249 B

After

Width:  |  Height:  |  Size: 450 B

BIN
resources/icons/linux.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
resources/icons/macos.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
resources/icons/windows.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 806 B

View File

@@ -1,6 +1,7 @@
#!/usr/bin/env python3
import json
import logging
import multiprocessing
import os
import pathlib
import shutil
@@ -277,7 +278,7 @@ def snapshot():
@server.get('/api/_detected_clients')
def detected_clients():
# todo: dev/debug only. Should not ship this - probably.
return ZeroconfServer.found_clients()
return ZeroconfServer.found_hostnames()
# New version
@@ -548,6 +549,9 @@ def start_server():
logger.info(f"Starting Zordon Render Server - Hostname: '{server.config['HOSTNAME']}:'")
ZeroconfServer.configure("_zordon._tcp.local.", server.config['HOSTNAME'], server.config['PORT'])
ZeroconfServer.properties = {'system_cpu': current_system_cpu(), 'system_cpu_cores': multiprocessing.cpu_count(),
'system_os': current_system_os(),
'system_os_version': current_system_os_version()}
ZeroconfServer.start()
try:

View File

@@ -220,10 +220,10 @@ class DistributedJobManager:
time.sleep(5)
@classmethod
def split_into_subjobs(cls, worker, job_data, project_path):
def split_into_subjobs(cls, worker, job_data, project_path, system_os=None):
# Check availability
available_servers = cls.find_available_servers(worker.renderer)
available_servers = cls.find_available_servers(worker.renderer, system_os)
logger.debug(f"Splitting into subjobs - Available servers: {available_servers}")
subjob_servers = cls.distribute_server_work(worker.start_frame, worker.end_frame, available_servers)
local_hostname = socket.gethostname()
@@ -353,17 +353,20 @@ class DistributedJobManager:
return server_breakdown
@staticmethod
def find_available_servers(engine_name):
def find_available_servers(engine_name, system_os=None):
"""
Scan the Zeroconf network for currently available render servers supporting a specific engine.
:param engine_name: str, The engine type to search for
:param system_os: str, Restrict results to servers running a specific OS
:return: A list of dictionaries with each dict containing hostname and cpu_count of available servers
"""
available_servers = []
for hostname in ZeroconfServer.found_clients():
response = RenderServerProxy(hostname).is_engine_available(engine_name)
if response and response.get('available', False):
available_servers.append(response)
for hostname in ZeroconfServer.found_hostnames():
host_properties = ZeroconfServer.get_hostname_properties(hostname)
if not system_os or (system_os and system_os == host_properties.get('system_os')):
response = RenderServerProxy(hostname).is_engine_available(engine_name)
if response and response.get('available', False):
available_servers.append(response)
return available_servers

View File

@@ -179,7 +179,7 @@ class NewRenderJobForm(QWidget):
self.renderer_type.addItems(self.renderer_info.keys())
def update_server_list(self):
clients = ZeroconfServer.found_clients()
clients = ZeroconfServer.found_hostnames()
self.server_input.clear()
self.server_input.addItems(clients)

View File

@@ -214,25 +214,14 @@ class MainWindow(QMainWindow):
# Update the Server Info box when a server is changed
self.server_info_hostname.setText(self.current_hostname or "unknown")
if self.current_server_proxy.system_os:
self.server_info_os.setText(f"OS: {self.current_server_proxy.system_os} "
f"{self.current_server_proxy.system_os_version}")
self.server_info_cpu.setText(f"CPU: {self.current_server_proxy.system_cpu} - "
f"{self.current_server_proxy.system_cpu_count} cores")
server_info = ZeroconfServer.get_hostname_properties(self.current_hostname)
if server_info:
self.server_info_os.setText(f"OS: {server_info['system_os']} {server_info['system_os_version']}")
self.server_info_cpu.setText(f"CPU: {server_info['system_cpu']} - {server_info.get('system_cpu_cores')} cores")
else:
self.server_info_os.setText(f"OS: Loading...")
self.server_info_cpu.setText(f"CPU: Loading...")
self.server_info_os.setText(f"OS: Unknown")
self.server_info_cpu.setText(f"CPU: Unknown")
def update_server_info_worker():
server_details = self.current_server_proxy.get_status()
if server_details['hostname'] == self.current_hostname:
self.server_info_os.setText(f"OS: {server_details.get('system_os')} "
f"{server_details.get('system_os_version')}")
self.server_info_cpu.setText(f"CPU: {server_details.get('system_cpu')} - "
f"{server_details.get('cpu_count')} cores")
update_thread = threading.Thread(target=update_server_info_worker)
update_thread.start()
except AttributeError:
pass
@@ -379,7 +368,7 @@ class MainWindow(QMainWindow):
self.image_label.setPixmap(pixmap)
def update_servers(self):
found_servers = list(set(ZeroconfServer.found_clients() + self.added_hostnames))
found_servers = list(set(ZeroconfServer.found_hostnames() + self.added_hostnames))
# Always make sure local hostname is first
current_hostname = socket.gethostname()
if found_servers and found_servers[0] != current_hostname:
@@ -402,7 +391,8 @@ class MainWindow(QMainWindow):
current_server_list.append(self.server_list_view.item(i).text())
for hostname in found_servers:
if hostname not in current_server_list:
image_path = os.path.join(resources_dir(), 'icons', 'Monitor.png')
properties = ZeroconfServer.get_hostname_properties(hostname)
image_path = os.path.join(resources_dir(), 'icons', f"{properties.get('system_os', 'Monitor')}.png")
list_widget = QListWidgetItem(QIcon(image_path), hostname)
self.server_list_view.addItem(list_widget)

View File

@@ -1,6 +1,7 @@
import logging
import socket
import zeroconf
from zeroconf import Zeroconf, ServiceInfo, ServiceBrowser, ServiceStateChange
logger = logging.getLogger()
@@ -24,6 +25,8 @@ class ZeroconfServer:
@classmethod
def start(cls, listen_only=False):
if not cls.service_type:
raise RuntimeError("The 'configure' method must be run before starting the zeroconf server")
if not listen_only:
cls._register_service()
cls._browse_services()
@@ -49,8 +52,8 @@ class ZeroconfServer:
cls.service_info = info
cls.zeroconf.register_service(info)
logger.info(f"Registered zeroconf service: {cls.service_info.name}")
except socket.gaierror as e:
logger.error(f"Error starting zeroconf service: {e}")
except zeroconf.NonUniqueNameException as e:
logger.error(f"Error establishing zeroconf: {e}")
@classmethod
def _unregister_service(cls):
@@ -75,19 +78,24 @@ class ZeroconfServer:
cls.client_cache.pop(name)
@classmethod
def found_clients(cls):
def found_hostnames(cls):
fetched_hostnames = [x.split(f'.{cls.service_type}')[0] for x in cls.client_cache.keys()]
local_hostname = socket.gethostname()
# Define a sort key function
def sort_key(hostname):
# Return 0 if it's the local hostname so it comes first, else return 1
return 0 if hostname == local_hostname else 1
return False if hostname == local_hostname else True
# Sort the list with the local hostname first
sorted_hostnames = sorted(fetched_hostnames, key=sort_key)
return sorted_hostnames
@classmethod
def get_hostname_properties(cls, hostname):
new_key = hostname + '.' + cls.service_type
server_info = cls.client_cache.get(new_key).properties
decoded_server_info = {key.decode('utf-8'): value.decode('utf-8') for key, value in server_info.items()}
return decoded_server_info
# Example usage:
if __name__ == "__main__":