From e48123f8d0d3a3b7c76c5b4f1a565c4595527084 Mon Sep 17 00:00:00 2001 From: Roger Yan Date: Wed, 19 Jun 2013 12:04:49 +0900 Subject: [PATCH 1/6] make tk ui runnable under debian wheezy --- namebench.py | 19 +++++----- namebench/client/geoip.py | 33 +---------------- namebench/ui/base_ui.py | 2 +- namebench/ui/tk.py | 75 +++++++++++++++++++-------------------- 4 files changed, 50 insertions(+), 79 deletions(-) diff --git a/namebench.py b/namebench.py index 4e5b5ed..5e1aed7 100755 --- a/namebench.py +++ b/namebench.py @@ -49,17 +49,20 @@ use_tk = True if use_tk: + # Workaround for unicode path errors. + # See http://code.google.com/p/namebench/issues/detail?id=41 + if hasattr(sys, 'winver') and hasattr(sys, 'frozen'): + os.environ['TCL_LIBRARY'] = os.path.join(os.path.dirname(sys.executable), 'tcl', 'tcl8.5') + os.environ['TK_LIBRARY'] = os.path.join(os.path.dirname(sys.executable), 'tcl', 'tk8.5') try: - # Workaround for unicode path errors. - # See http://code.google.com/p/namebench/issues/detail?id=41 - if hasattr(sys, 'winver') and hasattr(sys, 'frozen'): - os.environ['TCL_LIBRARY'] = os.path.join(os.path.dirname(sys.executable), 'tcl', 'tcl8.5') - os.environ['TK_LIBRARY'] = os.path.join(os.path.dirname(sys.executable), 'tcl', 'tk8.5') import tkinter except ImportError: - if len(sys.argv) == 1: - print("- The python-tk (tkinter) library is missing, using the command-line interface.\n") - use_tk = False + try: + import _tkinter + except ImportError: + if len(sys.argv) == 1: + print("- The python-tk (tkinter) library is missing, using the command-line interface.\n") + use_tk = False if use_tk: print('Starting graphical interface for namebench (use -x to force command-line usage)') diff --git a/namebench/client/geoip.py b/namebench/client/geoip.py index d593c02..318fec7 100644 --- a/namebench/client/geoip.py +++ b/namebench/client/geoip.py @@ -26,35 +26,6 @@ # third_party dependencies import httplib2 -def GetFromGoogleLocAPI(): - """Use the Google Loc JSON API from Google Gears. - - Returns: - A dictionary containing geolocation information - - NOTE: This is in violation of the Gears Terms of Service. See: - http://code.google.com/p/gears/wiki/GeolocationAPI - """ - h = httplib2.Http(tempfile.gettempdir(), timeout=10) - url = 'http://www.google.com/loc/json' - post_data = {'request_address': 'true', 'version': '1.1.0', 'source': 'namebench'} - unused_resp, content = h.request(url, 'POST', json.dumps(post_data)) - try: - data = json.loads(content)['location'] - return { - 'region_name': data['address'].get('region'), - 'country_name': data['address'].get('country'), - 'country_code': data['address'].get('country_code'), - 'city': data['address'].get('city'), - 'latitude': data['latitude'], - 'longitude': data['longitude'], - 'source': 'gloc' - } - except: - print(('* Failed to use GoogleLocAPI: %s (content: %s)' % (util.GetLastExceptionString(), content))) - return {} - - def GetFromMaxmindJSAPI(): h = httplib2.Http(tempfile.gettempdir(), timeout=10) unused_resp, content = h.request('http://j.maxmind.com/app/geoip.js', 'GET') @@ -70,9 +41,7 @@ def GetFromMaxmindJSAPI(): def GetGeoData(): """Get geodata from any means necessary. Sanitize as necessary.""" try: - json_data = GetFromGoogleLocAPI() - if not json_data: - json_data = GetFromMaxmindJSAPI() + json_data = GetFromMaxmindJSAPI() # Make our data less accurate. We don't need any more than that. json_data['latitude'] = '%.3f' % float(json_data['latitude']) diff --git a/namebench/ui/base_ui.py b/namebench/ui/base_ui.py index c376bcd..dfdcfa5 100644 --- a/namebench/ui/base_ui.py +++ b/namebench/ui/base_ui.py @@ -93,7 +93,7 @@ def GetExternalNetworkData(self): domain = None client_ip = providers.GetExternalIp() if client_ip: -# self.UpdateStatus("Detected external IP as %s" % client_ip) + self.UpdateStatus("Detected external IP as %s" % client_ip) local_ns = providers.SystemResolver() hostname = local_ns.GetReverseIp(client_ip) if hostname != client_ip: diff --git a/namebench/ui/tk.py b/namebench/ui/tk.py index 4c16d7d..f270600 100755 --- a/namebench/ui/tk.py +++ b/namebench/ui/tk.py @@ -18,21 +18,33 @@ import datetime import os -import queue +try: + import queue +except ImportError: + import Queue as queue import sys import threading -import tkinter.font +try: + import tkinter.font +except ImportError: + import tkFont # Wildcard imports are evil. -from tkinter import * -import tkinter.messagebox +try: + from tkinter import * +except ImportError: + from Tkinter import * +try: + import tkinter.messagebox +except ImportError: + import tkMessageBox import traceback -from . import addr_util +from ..client import addr_util from . import base_ui -from . import conn_quality -from . import nameserver_list -from . import sys_nameservers -from . import util +from ..client import conn_quality +from ..client import nameserver_list +from ..client import sys_nameservers +from ..client import util THREAD_UNSAFE_TK = 0 LOG_FILE_PATH = util.GenerateOutputFilename('log') @@ -93,17 +105,13 @@ def __init__(self, message, error=False, count=False, total=False, class WorkerThread(threading.Thread, base_ui.BaseUI): """Handle benchmarking and preparation in a separate UI thread.""" - def __init__(self, supplied_ns, global_ns, regional_ns, options, data_source=None, master=None, - backup_notifier=None): + def __init__(self, options, data_source=None, master=None, backup_notifier=None): threading.Thread.__init__(self) self.SetupDataStructures() self.status_callback = self.msg self.data_src = data_source self.backup_notifier = backup_notifier self.include_internal = False - self.supplied_ns = supplied_ns - self.global_ns = global_ns - self.regional_ns = regional_ns self.master = master self.options = options self.resource_dir = os.path.dirname(os.path.dirname(__file__)) @@ -139,17 +147,12 @@ def run(self): class NameBenchGui(object): """The main GUI.""" - def __init__(self, options, supplied_ns, global_ns, regional_ns, version=None): + def __init__(self, options): self.options = options - self.supplied_ns = supplied_ns - self.global_ns = global_ns - self.regional_ns = regional_ns - self.version = version def Execute(self): self.root = Tk() - app = MainWindow(self.root, self.options, self.supplied_ns, self.global_ns, - self.regional_ns, self.version) + app = MainWindow(self.root, self.options) app.DrawWindow() self.root.bind('<>', app.MessageHandler) self.root.mainloop() @@ -158,16 +161,12 @@ def Execute(self): class MainWindow(Frame, base_ui.BaseUI): """The main Tk GUI class.""" - def __init__(self, master, options, supplied_ns, global_ns, regional_ns, version=None): + def __init__(self, master, options): """TODO(tstromberg): Remove duplication from NameBenchGui class.""" Frame.__init__(self) self.SetupDataStructures() self.master = master self.options = options - self.supplied_ns = supplied_ns - self.global_ns = global_ns - self.regional_ns = regional_ns - self.version = version try: self.log_file = open(LOG_FILE_PATH, 'w') except: @@ -221,7 +220,7 @@ def DrawWindow(self): else: seperator_width = 585 - bold_font = tkinter.font.Font(font=status['font']) + bold_font = tkFont.Font(font=status['font']) bold_font['weight'] = 'bold' ns_label = Label(inner_frame, text='Nameservers') @@ -232,7 +231,7 @@ def DrawWindow(self): textvariable=self.nameserver_form, width=80) nameservers.grid(row=1, columnspan=2, sticky=W, padx=4, pady=2) - self.nameserver_form.set(', '.join(nameserver_list.InternalNameServers())) + #self.nameserver_form.set(', '.join(nameserver_list.InternalNameServers())) global_button = Checkbutton(inner_frame, text='Include global DNS providers (Google Public DNS, OpenDNS, UltraDNS, etc.)', @@ -280,7 +279,7 @@ def DrawWindow(self): source_titles = self.data_src.ListSourceTitles() left_dropdown_width = max([len(x) for x in source_titles]) - 3 - location_choices = [self.country, '(Other)'] + location_choices = [self.geodata.get('country_name', None), '(Other)'] location = OptionMenu(inner_frame, self.location, *location_choices) location.configure(width=left_dropdown_width) location.grid(row=11, column=0, sticky=W) @@ -315,7 +314,7 @@ def DrawWindow(self): self.button.grid(row=15, sticky=E, column=1, pady=4, padx=1) self.UpdateRunState(running=True) self.UpdateRunState(running=False) - self.UpdateStatus('namebench %s is ready!' % self.version) + self.UpdateStatus('namebench %s is ready!' % self.options.version) def MessageHandler(self, unused_event): """Pinged when there is a new message in our queue to handle.""" @@ -331,7 +330,7 @@ def MessageHandler(self, unused_event): def ErrorPopup(self, title, message): print(('Showing popup: %s' % title)) - tkinter.messagebox.showerror(str(title), str(message), master=self.master) + tkMessageBox.showerror(str(title), str(message), master=self.master) def UpdateRunState(self, running=True): """Update the run state of the window, using nasty threading hacks.""" @@ -362,19 +361,19 @@ def StartJob(self): """Events that get called when the Start button is pressed.""" self.ProcessForm() - thread = WorkerThread(self.supplied_ns, self.global_ns, self.regional_ns, self.options, - data_source=self.data_src, + thread = WorkerThread(self.options, data_source=self.data_src, master=self.master, backup_notifier=self.MessageHandler) thread.start() def ProcessForm(self): """Read form and populate instance variables.""" - self.supplied_ns = addr_util.ExtractIPTuplesFromString(self.nameserver_form.get()) - if not self.use_global.get(): - self.global_ns = [] - if not self.use_regional.get(): - self.regional_ns = [] + self.options.servers = addr_util.ExtractIPsFromString(self.nameserver_form.get()) + if self.use_global.get(): + self.options.tags.add('global') + if self.use_regional.get(): + self.options.tags.add('regional') + self.options.tags.add('specified') if 'Slow' in self.health_performance.get(): self.options.health_thread_count = 10 From 33bb12260ed7ac075f5315a8294befc2526376a4 Mon Sep 17 00:00:00 2001 From: Roger Yan Date: Mon, 24 Jun 2013 23:30:39 +0900 Subject: [PATCH 2/6] make check health work --- config/sanity_checks.cfg | 12 ++++++------ namebench/client/nameserver_list.py | 1 + namebench/ui/tk.py | 2 ++ 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/config/sanity_checks.cfg b/config/sanity_checks.cfg index ffda586..f44ff48 100644 --- a/config/sanity_checks.cfg +++ b/config/sanity_checks.cfg @@ -6,19 +6,19 @@ version=4 [primary] A a.root-servers.net.=198.41.0.4 -A google.com.=74.125.,66.102.9.,66.102.11.,66.102.13.,66.102.7.,66.102.9.,209.85.1,209.85.22,209.85.231,64.233.16,64.233.17,64.233.18,66.249.8,66.249.9,72.14.2,216.239.3,216.239.4,216.239.5,216.239.6,150.101.98,210.8.185,173.194.33 -A www.facebook.com.=69.63.17,69.63.18,69.63.190,69.63.191,66.220.14,66.220.15,www.facebook.com.edgesuite.net.,69.171.2 -A www.google.com.=www.l.google.com.,www-tmmdi.l.google.com +A google.com.=74.125.,66.102.9.,66.102.11.,66.102.13.,66.102.7.,66.102.9.,209.85.1,209.85.22,209.85.231,64.233.16,64.233.17,64.233.18,66.249.8,66.249.9,72.14.2,216.239.3,216.239.4,216.239.5,216.239.6,150.101.98,210.8.185,173.194., # 216.239.32.0/19 64.233.160.0/19 66.249.80.0/20 72.14.192.0/18 209.85.128.0/17 66.102.0.0/20 74.125.0.0/16 64.18.0.0/20 207.126.144.0/20 173.194.0.0/16 # dig _netblocks.google.com txt +A www.google.com.=www.l.google.com.,74.125.,66.102.9.,66.102.11.,66.102.13.,66.102.7.,66.102.9.,209.85.1,209.85.22,209.85.231,64.233.16,64.233.17,64.233.18,66.249.8,66.249.9,72.14.2,216.239.3,216.239.4,216.239.5,216.239.6,150.101.98,210.8.185,173.194., A www.google-analytics.com.=www-google-analytics.l.google.com. -A static.ak.fbcdn.net.=static.ak.facebook.com.edgesuite.net.,a749.g.akamai.net. A safebrowsing.clients.google.com.=clients.l.google.com. +A www.facebook.com.=star.c10r.facebook.com. +A static.ak.fbcdn.net.=static.ak.facebook.com.edgesuite.net.,a749.g.akamai.net.,a749.dsw4.akamai.net A liveupdate.symantecliveupdate.com.=liveupdate.symantec.d4p.net. A windowsupdate.microsoft.com.=windowsupdate.microsoft.nsatc.net. -A twitter.com.=168.143.162.,128.121.146.,128.121.243.,168.143.171.,168.143.161.,128.242.24,199.59.148 +A twitter.com.=199.59.148.,199.59.149.,199.59.150.,199.59.151. [secondary] # The rest are run later -A www.paypal.com.=66.211.169.,64.4.241. +A www.paypal.com.=www.paypal.com.akadns.net. [censorship] diff --git a/namebench/client/nameserver_list.py b/namebench/client/nameserver_list.py index ca631be..2cbe1f1 100644 --- a/namebench/client/nameserver_list.py +++ b/namebench/client/nameserver_list.py @@ -408,6 +408,7 @@ def CheckHealth(self, sanity_checks=None, max_servers=11, prefer_asn=None): self.HideSlowSupplementalServers(max_servers) self.RunFinalHealthCheckThreads(sanity_checks['secondary']) + #self.RunCensorshipCheckThreads(sanity_checks['censorship']) self.RunNodeIdThreads() self.HideBrokenIPV6Servers() diff --git a/namebench/ui/tk.py b/namebench/ui/tk.py index f270600..26d89da 100755 --- a/namebench/ui/tk.py +++ b/namebench/ui/tk.py @@ -125,6 +125,8 @@ def run(self): try: self.PrepareTestRecords() self.PrepareNameServers() + if not self.options.skip_health_checks: + self.CheckNameServerHealth() self.PrepareBenchmark() self.RunAndOpenReports() except nameserver_list.OutgoingUdpInterception: From de7abaa11638c69c9812e489a565cad57692e445 Mon Sep 17 00:00:00 2001 From: Roger Yan Date: Tue, 25 Jun 2013 00:40:59 +0900 Subject: [PATCH 3/6] New feature: report CDN speed/ping-time result with health check --- config/sanity_checks.cfg | 9 +++++ namebench/client/health_checks.py | 44 ++++++++++++++++++++ namebench/client/nameserver_list.py | 8 ++++ third_party/shell_ping.py | 62 +++++++++++++++++++++++++++++ 4 files changed, 123 insertions(+) create mode 100755 third_party/shell_ping.py diff --git a/config/sanity_checks.cfg b/config/sanity_checks.cfg index f44ff48..dfcea9e 100644 --- a/config/sanity_checks.cfg +++ b/config/sanity_checks.cfg @@ -63,3 +63,12 @@ A liberalthai.wordpress.com.=lb.wordpress.com. A uddthailand.com.=74.52.22 A www.armtoday.info.=87.242.11 A www.aawsat.com.=83.244.20 + +[CDN] +A download.microsoft.com.= +A dl.google.com.= +A dl-ssl.google.com.= +A appldnld.apple.com.= +A www.facebook.com.= +A d36cz9buwru1tt.cloudfront.net.= +A cachefly.cachefly.net.= diff --git a/namebench/client/health_checks.py b/namebench/client/health_checks.py index 1a279da..e265ce9 100644 --- a/namebench/client/health_checks.py +++ b/namebench/client/health_checks.py @@ -23,6 +23,7 @@ from . import util from dns import rcode +import shell_ping WILDCARD_DOMAINS = ('live.com.', 'blogspot.com.', 'wordpress.com.') LIKELY_HIJACKS = ['www.google.com.', 'windowsupdate.microsoft.com.', 'www.paypal.com.'] @@ -107,6 +108,38 @@ def TestAnswers(self, record_type, record, expected, critical=False, timeout=Non return (is_broken, error_msg, duration) + def TestCDNAnswers(self, record_type, record, timeout=None): + """Test to see that an answer returns correct IP's. + + Args: + record_type: text record type for NS query (A, CNAME, etc) + record: string to query for + timeout: timeout for query in seconds (int) + + Returns: + (is_broken, error_msg, duration) + """ + is_broken = False + error_msg = '' + duration = -1 + host = '' + if not timeout: + timeout = self.health_timeout + (response, duration, error_msg) = self.TimedRequest(record_type, record, timeout) + if response and not rcode.to_text(response.rcode()) in FATAL_RCODES and response.answer: + for answer in response.answer: + for rdata in answer: + if rdata.rdtype == 1: + host = str(rdata.address) + duration = shell_ping.ping(host, times=5)[2] + break + if duration == -1: + is_broken = True + error_msg = '%s is failed to resolve' % record.rstrip('.') + else: + error_msg = '%dms ping time to CDN host %s(%s)' % (duration, record.rstrip('.'), host) + return (is_broken, error_msg, duration) + def TestBindVersion(self): """Test for BIND version. This acts as a pretty decent ping.""" _, duration = self.GetVersion() @@ -225,6 +258,17 @@ def CheckCensorship(self, tests): if warning: self.AddWarning(warning, penalty=False) + def CheckCDN(self, tests): + """Check the quality of CDN result from a nameserver.""" + for (check, expected) in tests: + (req_type, req_name) = check.split(' ') + is_broken, warning = self.TestCDNAnswers(req_type.upper(), req_name, timeout=CENSORSHIP_TIMEOUT)[0:2] + if is_broken: + is_broken, warning = self.TestCDNAnswers(req_type.upper(), req_name, timeout=CENSORSHIP_TIMEOUT)[0:2] + if warning: + self.AddWarning(warning, penalty=False) + return self.is_disabled + def CheckHealth(self, sanity_checks=None, fast_check=False, final_check=False, port_check=False): """Qualify a nameserver to see if it is any good.""" diff --git a/namebench/client/nameserver_list.py b/namebench/client/nameserver_list.py index 2cbe1f1..53afbd9 100644 --- a/namebench/client/nameserver_list.py +++ b/namebench/client/nameserver_list.py @@ -126,6 +126,8 @@ def run(self): self.results.put(ns.CheckHealth(sanity_checks=self.checks, port_check=True)) elif self.action_type == 'censorship': self.results.put(ns.CheckCensorship(self.checks)) + elif self.action_type == 'cdn': + self.results.put(ns.CheckCDN(self.checks)) elif self.action_type == 'store_wildcards': self.results.put(ns.StoreWildcardCache()) elif self.action_type == 'node_id': @@ -409,6 +411,7 @@ def CheckHealth(self, sanity_checks=None, max_servers=11, prefer_asn=None): self.RunFinalHealthCheckThreads(sanity_checks['secondary']) #self.RunCensorshipCheckThreads(sanity_checks['censorship']) + self.RunCDNCheckThreads(sanity_checks['CDN']) self.RunNodeIdThreads() self.HideBrokenIPV6Servers() @@ -650,6 +653,11 @@ def RunCensorshipCheckThreads(self, checks): status_msg = 'Running censorship checks on %s servers' % len(self.enabled_servers) return self._LaunchQueryThreads('censorship', status_msg, list(self.enabled_servers), checks=checks) + def RunCDNCheckThreads(self, checks): + """Check ping time of CDN hostname.""" + status_msg = 'Running CDN checks on %s servers' % len(self.enabled_servers) + return self._LaunchQueryThreads('cdn', status_msg, list(self.enabled_servers), checks=checks) + def RunPortBehaviorThreads(self): """Get port behavior data.""" status_msg = 'Running port behavior checks on %s servers' % len(self.enabled_servers) diff --git a/third_party/shell_ping.py b/third_party/shell_ping.py new file mode 100755 index 0000000..b034468 --- /dev/null +++ b/third_party/shell_ping.py @@ -0,0 +1,62 @@ +import re +import os +import subprocess + +def ping(target, times=4): + """ ping target in IP/hostname format + + e.g. + '192.168.1.1' or 'www.google.com' + + return target, ip, time, lost + target: IP/hostname + ip: IP address, default is 0.0.0.0 + time_min: min ping time(ms), default is -1 + time_avg: avg ping time(ms), default is -1 + time_max: max ping time(ms), default is -1 + lost: packet loss(%), default is 100 + + ('www.google.com', '127.0.0.1', 5, 0) + """ + + if os.name == 'nt': # win32 + cmd = 'ping ' + target + else: # unix/linux + cmd = 'LC_CTYPE=C ping -c %d %s' % (times, target) + + # execute ping command and get stdin thru pipe + pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True).communicate()[0] + if not pipe: + if os.name == 'nt': # win32 + cmd = 'ping ' + target + else: # unix/linux + cmd = 'LC_CTYPE=C ping6 -c %d %s' % (times, target) + pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True).communicate()[0] + if not pipe: + return target, '0.0.0.0', -1, -1, -1, 100 + + # replace CR/LF + text = pipe.replace('\r\n', '\n').replace('\r', '\n') + + # match IP adddres in format: [192.168.1.1] (192.168.1.1) + ip = re.findall(r'(?<=\(|\[)\d+\.\d+\.\d+\.\d+(?=\)|\])', text) + ip = ip[0] if ip else '0.0.0.0' + + # avg ping time + if os.name == 'nt': + # TODO + time = re.findall(r'\d+(?=ms$)', text) + else: + time = re.findall(r'(?=\d+\.\d+/)(\d+\.\d+)+', text) + if time: + time_min = float(time[0]) + time_avg = float(time[1]) + time_max = float(time[2]) + else: + time_min = time_avg = time_max = -1 + + # packet loss rate + lost = re.findall(r'\d+(?=%)', text) + lost = int(round(float(lost[0]))) if lost else 100 + + return target, ip, time_min, time_avg, time_max, lost From 966208ef631a65f3263b011a6d5e2a1606c2e9c7 Mon Sep 17 00:00:00 2001 From: Roger Yan Date: Sun, 30 Jun 2013 18:56:14 +0900 Subject: [PATCH 4/6] New feature: pass CDN speed/ping-time result to html report --- namebench/client/health_checks.py | 19 ++++++++++++++++--- namebench/client/nameserver.py | 3 +++ namebench/client/reporter.py | 7 +++++-- templates/html.tmpl | 6 ++++++ third_party/shell_ping.py | 13 ++++++------- 5 files changed, 36 insertions(+), 12 deletions(-) diff --git a/namebench/client/health_checks.py b/namebench/client/health_checks.py index e265ce9..592d7ce 100644 --- a/namebench/client/health_checks.py +++ b/namebench/client/health_checks.py @@ -131,7 +131,8 @@ def TestCDNAnswers(self, record_type, record, timeout=None): for rdata in answer: if rdata.rdtype == 1: host = str(rdata.address) - duration = shell_ping.ping(host, times=5)[2] + # ip, time_min, time_avg, time_max, lost + duration = shell_ping.ping(host, times=5)[1] # the min ping time among the results break if duration == -1: is_broken = True @@ -262,11 +263,23 @@ def CheckCDN(self, tests): """Check the quality of CDN result from a nameserver.""" for (check, expected) in tests: (req_type, req_name) = check.split(' ') - is_broken, warning = self.TestCDNAnswers(req_type.upper(), req_name, timeout=CENSORSHIP_TIMEOUT)[0:2] + is_broken, warning, duration = self.TestCDNAnswers(req_type.upper(), req_name, timeout=CENSORSHIP_TIMEOUT) if is_broken: - is_broken, warning = self.TestCDNAnswers(req_type.upper(), req_name, timeout=CENSORSHIP_TIMEOUT)[0:2] + is_broken, warning, duration = self.TestCDNAnswers(req_type.upper(), req_name, timeout=CENSORSHIP_TIMEOUT) if warning: self.AddWarning(warning, penalty=False) + if is_broken: + self.is_disabled = True + self.cdn_ping_avg = 0 + self.cdn_ping_min = 65535 + self.cdn_ping_max = 0 + self.AddWarning('Failed to ping CDN host %s, so removed from final comparison list.' % (req_name), penalty=False) + break + else: + self.cdn_ping_avg += duration + self.cdn_ping_min = min(self.cdn_ping_min, duration) + self.cdn_ping_max = max(self.cdn_ping_max, duration) + self.cdn_ping_avg /= float(len(tests)) return self.is_disabled def CheckHealth(self, sanity_checks=None, fast_check=False, final_check=False, port_check=False): diff --git a/namebench/client/nameserver.py b/namebench/client/nameserver.py index 9b08ecc..57931f0 100755 --- a/namebench/client/nameserver.py +++ b/namebench/client/nameserver.py @@ -169,6 +169,9 @@ def ResetTestStatus(self): self.share_check_count = 0 self.cache_checks = [] self.is_slower_replica = False + self.cdn_ping_max = 0 + self.cdn_ping_min = 65535 + self.cdn_ping_avg = 0 self.ResetErrorCounts() def ResetErrorCounts(self): diff --git a/namebench/client/reporter.py b/namebench/client/reporter.py index 9c9b525..2109913 100644 --- a/namebench/client/reporter.py +++ b/namebench/client/reporter.py @@ -316,7 +316,7 @@ def _GenerateNameServerSummary(self): } # Fill the scores in. - for (ns, unused_avg, run_averages, fastest, slowest, unused_failures, nx_count, unused_total) in sorted_averages: + for (ns, overall_average, run_averages, fastest, slowest, unused_failures, nx_count, unused_total) in sorted_averages: placed_at += 1 durations = [] @@ -325,7 +325,10 @@ def _GenerateNameServerSummary(self): nsdata[ns].update({ 'position': placed_at, - 'overall_average': util.CalculateListAverage(run_averages), + 'overall_average': overall_average, + 'cdn_result_min': ns.cdn_ping_min, + 'cdn_result_avg': ns.cdn_ping_avg, + 'cdn_result_max': ns.cdn_ping_max, 'averages': run_averages, 'duration_min': float(fastest), 'duration_max': slowest, diff --git a/templates/html.tmpl b/templates/html.tmpl index 7c143bf..44f5ada 100644 --- a/templates/html.tmpl +++ b/templates/html.tmpl @@ -47,6 +47,9 @@ Descr. Hostname Avg (ms) + CDN_min(ms) + CDN_avg(ms) + CDN_max(ms) Diff Min Max @@ -63,6 +66,9 @@ {{ row.name }} {{ row.hostname }}{% if row.node_ids %}{% for node in row.node_ids %}{{ node }} {% endfor %}{% endif %} {% if row.overall_average %}{{ "%0.2f"|format(row.overall_average) }}{% else %}{{ "%0.2f"|format(row.check_average) }}{% endif %} + {% if row.cdn_result_min %}{{ "%0.2f"|format(row.cdn_result_min) }}{% else %}{{ "%0.2f"|format(-1) }}{% endif %} + {% if row.cdn_result_avg %}{{ "%0.2f"|format(row.cdn_result_avg) }}{% else %}{{ "%0.2f"|format(-1) }}{% endif %} + {% if row.cdn_result_max %}{{ "%0.2f"|format(row.cdn_result_max) }}{% else %}{{ "%0.2f"|format(-1) }}{% endif %} {% if row.diff %}{{ "%0.1f"|format(row.diff) }}%{% endif %} {% if row.duration_min %}{{ "%0.1f"|format(row.duration_min) }}{% endif %} {% if row.duration_max %}{{ "%0.1f"|format(row.duration_max) }}{% endif %} diff --git a/third_party/shell_ping.py b/third_party/shell_ping.py index b034468..c5d0974 100755 --- a/third_party/shell_ping.py +++ b/third_party/shell_ping.py @@ -8,9 +8,8 @@ def ping(target, times=4): e.g. '192.168.1.1' or 'www.google.com' - return target, ip, time, lost - target: IP/hostname - ip: IP address, default is 0.0.0.0 + return ip, time_min, time_avg, time_max, lost + ip: IP address of target, default is 0.0.0.0 time_min: min ping time(ms), default is -1 time_avg: avg ping time(ms), default is -1 time_max: max ping time(ms), default is -1 @@ -22,7 +21,7 @@ def ping(target, times=4): if os.name == 'nt': # win32 cmd = 'ping ' + target else: # unix/linux - cmd = 'LC_CTYPE=C ping -c %d %s' % (times, target) + cmd = 'ping -c%d -W2000 %s' % (times, target) # execute ping command and get stdin thru pipe pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True).communicate()[0] @@ -30,10 +29,10 @@ def ping(target, times=4): if os.name == 'nt': # win32 cmd = 'ping ' + target else: # unix/linux - cmd = 'LC_CTYPE=C ping6 -c %d %s' % (times, target) + cmd = 'ping6 -c%d -W2000 %s' % (times, target) pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True).communicate()[0] if not pipe: - return target, '0.0.0.0', -1, -1, -1, 100 + return '0.0.0.0', -1, -1, -1, 100 # replace CR/LF text = pipe.replace('\r\n', '\n').replace('\r', '\n') @@ -59,4 +58,4 @@ def ping(target, times=4): lost = re.findall(r'\d+(?=%)', text) lost = int(round(float(lost[0]))) if lost else 100 - return target, ip, time_min, time_avg, time_max, lost + return ip, time_min, time_avg, time_max, lost From 0096f003a84b0989a5edd6b766b767f849485947 Mon Sep 17 00:00:00 2001 From: Roger Yan Date: Mon, 1 Jul 2013 15:45:58 +0900 Subject: [PATCH 5/6] fix import order and a typo for shell_ping library --- third_party/shell_ping.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/third_party/shell_ping.py b/third_party/shell_ping.py index c5d0974..fd4c2af 100755 --- a/third_party/shell_ping.py +++ b/third_party/shell_ping.py @@ -1,5 +1,5 @@ -import re import os +import re import subprocess def ping(target, times=4): @@ -37,7 +37,7 @@ def ping(target, times=4): # replace CR/LF text = pipe.replace('\r\n', '\n').replace('\r', '\n') - # match IP adddres in format: [192.168.1.1] (192.168.1.1) + # match IP address in format: [192.168.1.1] (192.168.1.1) ip = re.findall(r'(?<=\(|\[)\d+\.\d+\.\d+\.\d+(?=\)|\])', text) ip = ip[0] if ip else '0.0.0.0' From 5ea5e23c3472ba82a9384f27733b7feab5e32d5a Mon Sep 17 00:00:00 2001 From: Roger Yan Date: Fri, 5 Jul 2013 14:56:08 +0900 Subject: [PATCH 6/6] fix for win32 (under python2.7.5) --- namebench/client/better_webbrowser.py | 5 ++++- third_party/shell_ping.py | 29 +++++++++++++++++---------- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/namebench/client/better_webbrowser.py b/namebench/client/better_webbrowser.py index 8b37750..d4e3f00 100644 --- a/namebench/client/better_webbrowser.py +++ b/namebench/client/better_webbrowser.py @@ -99,7 +99,10 @@ def open(url): # # If we are running on Windows, register the WindowsHttpDefault class. if sys.platform[:3] == 'win': - import winreg + try: + import winreg + except ImportError: + import _winreg # We don't want to load this class by default, because Python 2.4 doesn't have BaseBrowser. diff --git a/third_party/shell_ping.py b/third_party/shell_ping.py index fd4c2af..b3b038e 100755 --- a/third_party/shell_ping.py +++ b/third_party/shell_ping.py @@ -19,7 +19,7 @@ def ping(target, times=4): """ if os.name == 'nt': # win32 - cmd = 'ping ' + target + cmd = 'ping -w 2000 ' + target else: # unix/linux cmd = 'ping -c%d -W2000 %s' % (times, target) @@ -27,7 +27,7 @@ def ping(target, times=4): pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True).communicate()[0] if not pipe: if os.name == 'nt': # win32 - cmd = 'ping ' + target + cmd = 'ping -w 2000 ' + target else: # unix/linux cmd = 'ping6 -c%d -W2000 %s' % (times, target) pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True).communicate()[0] @@ -39,20 +39,27 @@ def ping(target, times=4): # match IP address in format: [192.168.1.1] (192.168.1.1) ip = re.findall(r'(?<=\(|\[)\d+\.\d+\.\d+\.\d+(?=\)|\])', text) - ip = ip[0] if ip else '0.0.0.0' + if ip: + ip = ip[0] + else: + ip = re.findall(r'\d+\.\d+\.\d+\.\d+', text) + ip = ip[0] if ip else '0.0.0.0' # avg ping time if os.name == 'nt': - # TODO - time = re.findall(r'\d+(?=ms$)', text) + time = re.findall(r'(\d+(?=ms))+', text) + if time: + time_avg = float(time[len(time) - 1]) + time_max = float(time[len(time) - 2]) + time_min = float(time[len(time) - 3]) else: time = re.findall(r'(?=\d+\.\d+/)(\d+\.\d+)+', text) - if time: - time_min = float(time[0]) - time_avg = float(time[1]) - time_max = float(time[2]) - else: - time_min = time_avg = time_max = -1 + if time: + time_min = float(time[0]) + time_avg = float(time[1]) + time_max = float(time[2]) + if not time: + time_min = time_avg = time_max = -1 # packet loss rate lost = re.findall(r'\d+(?=%)', text)