Skip to content

Instantly share code, notes, and snippets.

@marek22k
Created April 9, 2022 14:08
Show Gist options
  • Save marek22k/2559a1daa726b02413c200f74b767b25 to your computer and use it in GitHub Desktop.
Save marek22k/2559a1daa726b02413c200f74b767b25 to your computer and use it in GitHub Desktop.
This script pings all Yggdrasil nodes and then displays a recommendation of nodes.
# Copyright (c) 2022 Marek Kuethe
# License: WTFPL
# This script pings all Yggdrasil nodes and then displays a recommendation of
# nodes. The script is assumed to be in the public-peers folder. If not, a
# different folder can be given as an argument. Nodes that cannot be reached via
# TLS or TCP are omitted. Each node is pinged 20 times over TCP. There is a 0.2
# second pause after each ping.
# Require gem net-ping
require "net/ping"
$pinger = Net::Ping::TCP
def yggdrasil_node_list dir = "./"
list = {}
max_len_region = 0
max_len_node = 0
Dir["#{dir}*/*.md"].each { |file|
if File.file?(file)
cnt = File.read file
path = file.split("/")
item = "#{path[-2]}/#{path[-1].split(".")[0]}"
max_len_region = item.length if item.length > max_len_region
list[item] = cnt.scan(/\`(tls|tcp|socks):\/\/([a-z0-9\.\-\:\[\]]+):([0-9]+)\`/).to_a
list[item].each { |node|
len_node = "#{node[1]}:#{node[2]}".length
max_len_node = len_node if len_node > max_len_node
}
# regex by https://github.com/zhoreeq/peer_checker.py/blob/master/md_to_json.py
end
}
return [list, max_len_node, max_len_region]
end
def ping_node node_info, count = 20, timeout = 5, pause = 0.2
sum = 0.0
fail = 0
ping = $pinger.new node_info[1], node_info[2], timeout
count.times {
res = ping.ping
if res
sum += res
else
fail += 1
end
sleep pause
}
sum /= count
return [sum, fail]
end
def collect_ping_times list, count = 20
ping_res = {}
threads = []
# ping over socks is not supported
protocols = ["tcp", "tls"]
puts "Collecting ping times..."
list[0].each_pair { |region, nodes|
# open a new thread for every country / subregion
threads << Thread.new(region, nodes) { |region, nodes|
puts "Pinging nodes from region #{region}"
nodes.each { |node_info|
if protocols.include? node_info[0]
puts "Ping node #{node_info[1]} (#{node_info[0]})"
res = ping_node node_info, count
puts "count=#{count} avg=#{res[0].round 3} fail=#{res[1]}"
res << region
ping_res[node_info] = res
end
}
}
}
threads.each.with_index { |th, index|
puts "Waiting for thread #{index + 1}..."
th.join
}
return ping_res
end
def display_ping_times list, ping_res
node_with_fail = {}
node_without_fail = {}
name_len = list[1] + 2
region_len = list[2] + 2
# split hash into nodes with and without ping fails
ping_res.each_pair { |node, res|
if res[1] == 0
node_without_fail[node] = res
else
node_with_fail[node] = res
end
}
# sort by ping time
node_with_fail = node_with_fail.sort_by { |node, res|
res[0]
}
node_without_fail = node_without_fail.sort_by { |node, res|
res[0]
}
puts "=" * 43
puts
puts "Nodes with ping fails:"
node_with_fail.each { |node_res|
node_name = "#{node_res[0][1]}:#{node_res[0][2]}"
name_padding = " " * (name_len - node_name.length)
region = "region=#{node_res[1][2]}"
region_padding = " " * (region_len - node_res[1][2].length)
data = "fail=#{node_res[1][1]}\tavg=#{node_res[1][0].round 3}"
puts "#{node_name}#{name_padding} (#{node_res[0][0]}) #{region}#{region_padding}#{data}"
}
puts
puts "-" * 43
puts
puts "Nodes without ping fails:"
node_without_fail.each { |node_res|
node_name = "#{node_res[0][1]}:#{node_res[0][2]}"
name_padding = " " * (name_len - node_name.length)
region = "region=#{node_res[1][2]}"
region_padding = " " * (region_len - node_res[1][2].length)
data = "avg=#{node_res[1][0].round 3}"
puts "#{node_name}#{name_padding} (#{node_res[0][0]}) #{region}#{region_padding}#{data}"
}
puts
puts "-" * 43
puts
puts "I would recommend you to add the following three nodes:"
puts " Peers: ["
node_without_fail.first(3).each { |node, res|
puts " #{node[0]}://#{node[1]}:#{node[2]} \# #{res[2]}"
}
puts " ]"
puts
puts "If you want more choice, here are the twenty nodes with the lowest ping time:"
node_without_fail.first(20).each { |node, res|
puts " #{node[0]}://#{node[1]}:#{node[2]} \# #{res[2]}"
}
end
def test_all dir = "./"
list = yggdrasil_node_list dir
display_ping_times list, collect_ping_times(list)
end
dir = "./"
if ARGV[0] && File.directory?(ARGV[0])
dir = ARGV[0]
end
test_all dir
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment