| name | fabric-precision-timing |
| description | Set up PTP time synchronization and OWL one-way latency measurements on FABRIC |
| allowed-tools | ["Read","Grep","Glob","Write","Edit","Bash"] |
Instructions
When invoked, generate code for precision timing experiments on FABRIC. Two main capabilities:
- PTP (Precision Time Protocol) — IEEE 1588 sub-microsecond time synchronization via Ansible playbooks
- OWL (One-Way Latency) — Docker-based latency measurement using hardware timestamps
Help the user set up PTP-enabled slices and run OWL measurements between nodes.
API Reference
Finding PTP-Capable Sites
ptp_sites = fablib.get_random_sites(
count=2,
filter_function=lambda x: x['ptp_capable'] is True and x['state'] == 'Active'
)
OWL Library
from mflib import owl
owl.nodes_ip_addrs(slice) -> dict
owl.check_owl_prerequisites(slice)
owl.start_owl(slice, src_node, dst_node, img_name, probe_freq=1,
no_ptp=False, duration=120, delete_previous_output=True)
owl.start_owl_all(slice, img_name, probe_freq=1, duration=600, delete_previous=True)
owl.start_owl_sender(slice, src_node, dst_node, img_name, duration=120)
owl.start_owl_capturer(slice, dst_node, img_name, duration=120)
owl.check_owl_all(slice)
owl.stop_owl_all(slice)
owl.stop_owl_sender(slice, src_node, dst_node)
owl.stop_owl_capturer(slice, dst_node)
owl.download_output(node, local_output_dir)
Patterns
PTP-Enabled Slice Creation
from fabrictestbed_extensions.fablib.fablib import FablibManager
fablib = FablibManager()
[site1, site2] = fablib.get_random_sites(
count=2,
filter_function=lambda x: x['ptp_capable'] is True and x['state'] == 'Active',
)
slice = fablib.new_slice(name="ptp-experiment")
node1 = slice.add_node(name="Node1", site=site1, image="docker_rocky_8")
node1.add_fabnet()
node2 = slice.add_node(name="Node2", site=site2, image="docker_rocky_8")
node2.add_fabnet()
slice.submit()
Install PTP on Nodes
nodes = slice.get_nodes()
pre_requisites = (
"`sudo dnf -y install epel-release; sudo dnf -y install ansible git`"
)
repo_branch = "main"
destination = f"/tmp/ptp-{repo_branch}"
clone_instructions = (
f"cd /tmp/ && rm -rf {destination} && "
f"git clone --branch {repo_branch} "
f"https://github.com/fabric-testbed/ptp.git {destination}"
)
ansible_instructions = (
f"cd {destination}/ansible && "
f"ansible-playbook --connection=local "
f"--inventory 127.0.0.1, --limit 127.0.0.1 "
f"playbook_fabric_experiment_ptp.yml"
)
execute_threads = {}
for node in nodes:
execute_threads[node] = node.execute_thread(
f"{pre_requisites} && {clone_instructions} && {ansible_instructions}",
output_file=f"/tmp/{node.get_name()}_ptpinstall.log",
)
for node, thread in execute_threads.items():
print(f"Waiting for PTP install on {node.get_name()}")
stdout, stderr = thread.result()
print(f"PTP installed on {node.get_name()}")
PTP with Interface Restrictions
import json
NODE_RESTRICTIONS = {
"Node1": {"AVOID_IFACES": ["enp6s0"], "SYNC_SYSTEM_CLOCK": False},
"Node2": {"AVOID_IFACES": ["enp6s0", "enp7s0"]},
}
for node in nodes:
node_name = node.get_name()
extra_params = ""
if node_name in NODE_RESTRICTIONS:
params_file = f"/tmp/{node_name}-parameters.json"
with open(params_file, "w") as f:
json.dump(NODE_RESTRICTIONS[node_name], f)
node.upload_file(params_file, f"{destination}/ansible/parameters.json")
extra_params = " --extra-vars @parameters.json"
node.execute(
f"{pre_requisites} && {clone_instructions} && "
f"cd {destination}/ansible && "
f"ansible-playbook --connection=local "
f"--inventory 127.0.0.1, --limit 127.0.0.1 "
f"playbook_fabric_experiment_ptp.yml{extra_params}"
)
Start Docker on Nodes
for node in nodes:
node.execute("sudo systemctl start docker")
node.execute("sudo systemctl enable docker")
node.execute("sudo usermod -aG docker rocky")
Run OWL Measurements (Specific Link)
from mflib import owl
owl.check_owl_prerequisites(slice)
image_name = "fabrictestbed/owl:0.2.0"
for node in nodes:
node.execute(f"sudo docker pull {image_name}")
owl.start_owl(
slice,
src_node=slice.get_node(name="Node1"),
dst_node=slice.get_node(name="Node2"),
img_name=image_name,
probe_freq=10,
no_ptp=False,
duration=600,
delete_previous_output=True,
)
owl.check_owl_all(slice)
Run OWL Measurements (All Links)
owl.start_owl_all(
slice,
img_name=image_name,
probe_freq=10,
duration=600,
delete_previous=True,
)
owl.check_owl_all(slice)
owl_output_dir = "/home/rocky/owl-output"
for node in nodes:
print(node.get_name())
node.execute(f"ls -lh {owl_output_dir}")
Stop and Download Results
owl.stop_owl_all(slice)
local_output_dir = "./owl-results/"
for node in nodes:
print(f"Downloading from {node.get_name()}")
owl.download_output(node, local_output_dir)
Important Notes
- PTP-capable sites only — use
ptp_capable filter function
- Use
docker_rocky_8 image — required for Docker and PTP compatibility
- OWL output — PCAP files saved in
/home/rocky/owl-output/ as {dest_ip}.pcap
- Hardware timestamps — OWL uses
phc2sys for PTP-synced timestamps
- Probe frequency — 1–10 probes per second recommended
- Each packet contains: sent timestamp (UDP payload) and received timestamp (tcpdump)
- Latency = received_timestamp - sent_timestamp
- NAT64 may be needed for IPv6 nodes to reach GitHub