#!/usr/bin/env python3 import os import sys import time import datetime import subprocess import json PTP_REMOTE_CONFIG_PATH = "/opt/ptp_conf/" GM_FILE = "ptp4l_grandmaster.conf" SV_FILE = "ptp4l_slave.conf" MA_FILE = "ptp4l_master.conf" BC_FILE = "ptp4l_boundary.conf" PTP4L_INSTANCE = "ptp4l" GRANDMASTER_LOG = "/tmp/ptp4l_grandmaster.log" SLAVE_LOG = "/tmp/ptp4l_slave.log" MASTER_LOG = "/tmp/ptp4l_master.log" BOUNDARY_LOG = "/tmp/ptp4l_boundary.log" PHC2SYS_LOG = "/tmp/phc2sys.log" RECEIVER_PATH = "/opt/timestamping/receiver_hwts_logger" BROADCASTER_PATH = "/home/apu/testbed_files/experiments/reference_broadcast/broadcaster" PTP_CONFIG_DIR = "ptp_config/eth" EXPERIMENT_NAME = "ptp_loop_refbc" def get_hostname(node_id): return f"apu{str(node_id).zfill(2)}" def load_ptp_config(filepath): with open(filepath, "r") as f: data = json.load(f) lines = [] for section, options in data.items(): lines.append(f"[{section}]") for key, value in options.items(): lines.append(f"{key} {value}") return "\n".join(lines) def write_ptp_config_to_node(hostname, role, conf_str): file_map = { "grandmaster": GM_FILE, "slave": SV_FILE, "master": MA_FILE, "boundary": BC_FILE } file_name = file_map.get(role) if not file_name: raise ValueError("Invalid role") cmd = [ "ssh", hostname, f"echo '{conf_str}' | sudo tee '{PTP_REMOTE_CONFIG_PATH}/{file_name}' > /dev/null" ] subprocess.run(cmd, check=True) def stop_all_processes(hostname): subprocess.run(["ssh", hostname, "sudo pkill -x ptp4l phc2sys receiver_hwts_logger || true"], check=False) def start_ptp_topology(nodes): first, last = min(nodes), max(nodes) for node_id in nodes: hostname = get_hostname(node_id) stop_all_processes(hostname) if node_id == first: config_slave = load_ptp_config(os.path.join(PTP_CONFIG_DIR, "ptp4l_bc_slave.json")) write_ptp_config_to_node(hostname, "slave", config_slave) cmd = ( f"{PTP4L_INSTANCE} -i eth1 -m -H -f {PTP_REMOTE_CONFIG_PATH}{SV_FILE} > /tmp/ptp4l_slave.log 2>&1 & " f"phc2sys -s eth1 -c CLOCK_REALTIME -O 0 -m > {PHC2SYS_LOG} 2>&1 &" ) elif node_id == last: config_master = load_ptp_config(os.path.join(PTP_CONFIG_DIR, "ptp4l_bc_master.json")) write_ptp_config_to_node(hostname, "master", config_master) cmd = f"{PTP4L_INSTANCE} -i eth2 -m -H -f {PTP_REMOTE_CONFIG_PATH}{MA_FILE} > /tmp/ptp4l_master.log 2>&1 &" else: config_slave = load_ptp_config(os.path.join(PTP_CONFIG_DIR, "ptp4l_bc_slave.json")) config_master = load_ptp_config(os.path.join(PTP_CONFIG_DIR, "ptp4l_bc_master.json")) write_ptp_config_to_node(hostname, "slave", config_slave) write_ptp_config_to_node(hostname, "master", config_master) cmd = ( f"{PTP4L_INSTANCE} -i eth1 -m -H -f {PTP_REMOTE_CONFIG_PATH}{SV_FILE} > /tmp/ptp4l_slave.log 2>&1 & " f"phc2sys -s eth1 -c CLOCK_REALTIME -O 0 -m > {PHC2SYS_LOG} 2>&1 & " f"{PTP4L_INSTANCE} -i eth2 -m -H -f {PTP_REMOTE_CONFIG_PATH}{MA_FILE} > /tmp/ptp4l_master.log 2>&1 &" ) subprocess.run(["ssh", hostname, cmd], check=True) def start_receivers(nodes): for node_id in nodes: hostname = get_hostname(node_id) cmd = f"sudo {RECEIVER_PATH} eth0 > /dev/null 2>&1 &" subprocess.run(["ssh", hostname, cmd], check=True) def wait_for_receivers(nodes, timeout=10): for node_id in nodes: hostname = get_hostname(node_id) for _ in range(timeout): result = subprocess.run([ "ssh", hostname, "pgrep -x receiver_hwts_logger > /dev/null" ]) if result.returncode == 0: break time.sleep(1) else: print(f"ERROR: receiver not running on {hostname}") sys.exit(1) def start_broadcaster(): cmd = f"sudo {BROADCASTER_PATH} eth0 1000 > /dev/null 2>&1 & echo $!" proc = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE, text=True) return int(proc.stdout.strip()) def stop_all_nodes(nodes): for node_id in nodes: hostname = get_hostname(node_id) stop_all_processes(hostname) def fetch_logs(nodes, experiment_dir): for node_id in nodes: hostname = get_hostname(node_id) node_dir = os.path.join(experiment_dir, hostname) os.makedirs(node_dir, exist_ok=True) # Fetch timestamp logs tslog_dir = os.path.join(node_dir, "tslogs") os.makedirs(tslog_dir, exist_ok=True) remote_cmd = "ls -td /tmp/tslog_* | head -n1" result = subprocess.run(["ssh", hostname, remote_cmd], capture_output=True, text=True) folder = result.stdout.strip() if folder: subprocess.run(["scp", f"{hostname}:{folder}/*.csv", tslog_dir]) else: print(f"[WARN] No tslog folder found on {hostname}") # Fetch PTP-related logs log_files = { "ptp4l_grandmaster": "/tmp/ptp4l_grandmaster.log", "ptp4l_slave": "/tmp/ptp4l_slave.log", "ptp4l_master": "/tmp/ptp4l_master.log", "phc2sys": "/tmp/phc2sys.log" } for label, path in log_files.items(): try: result = subprocess.run([ "ssh", hostname, f'bash -c "sync; sleep 1; cat \\\"{path}\\\""' ], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) content = result.stdout.replace('\x00', '') with open(os.path.join(node_dir, f"{label}.csv"), "w") as f: f.write(content) except subprocess.CalledProcessError as e: with open(os.path.join(node_dir, f"{label}_ERROR.txt"), "w") as f: f.write(e.stderr + "\n") def main(): if len(sys.argv) < 3: print("Usage: run_ptp_loop_refbc.py ") sys.exit(1) duration = int(sys.argv[1]) nodes = list(map(int, sys.argv[2:])) print("🔄 Starting experiment...") stop_all_nodes(nodes) start_ptp_topology(nodes) start_receivers(nodes) wait_for_receivers(nodes) broadcaster_pid = start_broadcaster() print(f"⏳ Running for {duration} seconds...") time.sleep(duration) print("🛑 Stopping broadcaster") subprocess.run(["sudo", "kill", str(broadcaster_pid)]) print("🛑 Stopping all node processes") stop_all_nodes(nodes) print("📥 Fetching logs...") timestamp = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S") experiment_dir = os.path.join("logs", EXPERIMENT_NAME, timestamp) os.makedirs(experiment_dir, exist_ok=True) fetch_logs(nodes, experiment_dir) print(f"✅ Done. Logs saved to {experiment_dir}") if __name__ == "__main__": main()