Skip to content
Commits on Source (2)
......@@ -130,7 +130,6 @@ NTP_2BIT = 0x9 # leap bits
class Ntpq(cmd.Cmd):
"ntpq command interpreter"
def __init__(self, session):
cmd.Cmd.__init__(self)
self.session = session
......@@ -150,25 +149,6 @@ class Ntpq(cmd.Cmd):
self.debug = 0
self.pktversion = NTP_OLDVERSION + 1
self.uservars = collections.OrderedDict()
# By default, the peer spreadsheet layout is designed so lines just
# fit in 80 characters. This tells us how much extra horizontal space
# we have available on a wider terminal emulator
self.horizontal_slack = termsize()[1] - 80
# Peer spreadsheet column widths
self.namewidth = 15 + self.horizontal_slack
self.refidwidth = 15
# Compute peer spreadsheet headers
self.__remote = " remote ".ljust(self.namewidth)
self.__common = "st t when poll reach delay offset "
self.__opeerheader = self.__remote + \
" local ".ljust(self.refidwidth) + \
self.__common + " disp\n"
self.__peerheader = self.__remote + \
" refid ".ljust(self.refidwidth) + \
self.__common + "jitter\n"
self.__apeerheader = self.__remote + \
" refid assid ".ljust(self.refidwidth) + \
self.__common + "jitter\n"
def emptyline(self):
"Called when an empty line is entered in response to the prompt."
......@@ -188,15 +168,6 @@ usage: help [ command ]
# Unexposed helper tables and functions begin here
@staticmethod
def high_truncate(hostname, maxlen):
"Truncate on the left using leading _ to indicate 'more'."
# Used for local IPv6 addresses, best distinguished by low bits
if len(hostname) <= maxlen:
return hostname
else:
return '-' + hostname[-maxlen+1:]
def __dogetassoc(self):
try:
self.peers = self.session.readstat()
......@@ -295,179 +266,23 @@ usage: help [ command ]
condition, last_event, event_count)
self.say(display + "\n")
@staticmethod
def prettyinterval(diff):
"Print an interval in natural time units."
if diff <= 0:
return "-"
if diff <= 2048:
return str(diff)
diff = (diff + 29) / 60
if diff <= 300:
return "%dm" % diff
diff = (diff + 29) / 60
if diff <= 96:
return "%dh" % diff
diff = (diff + 11) / 24
return "%dd" % diff
def __doprintpeers(self, variables, header, associd):
hmode = 0
srchost = None
srcport = 0
srcaddr = None
dstadr_refid = ""
ppoll = 0
hpoll = 0
reach = 0
ptype = '?'
have_jitter = False
clock_name = ''
now = time.time()
for (name, value) in variables.items():
if name in ("srcadr", "peeradr"):
srcaddr = value
elif name == "srchost":
srchost = value
elif name == "dstadr":
# The C code tried to get a fallback pytpe from this in case
# the hmode field was not included
if "local" in header:
dstadr_refid = value
elif name == "hmode":
hmode = value
elif name == "refid":
# The C code for this looked crazily overelaborate. Best
# guess is that it was designed to deal with formats that
# no longer occur in this field.
if "refid" in header:
dstadr_refid = value
elif name == "hpoll":
hpoll = value
if hpoll < 0:
hpoll = NTP_MINPOLL
elif name == "ppoll":
ppoll = value
if ppoll < 0:
ppoll = NTP_MINPOLL
elif name == "reach":
# Shipped as hex, displayed in octal
reach = value
elif name == "delay":
estdelay = value
elif name == "offset":
estoffset = value
elif name == "jitter":
if "jitter" in header:
estjitter = value
have_jitter = True
elif name == "rootdisp" or name == "dispersion":
estdisp = value
elif name == "rec":
rec = value # l_fp timestamp
elif name == "srcport" or name == "peerport":
srcport = value
elif name == "reftime":
reftime = value # l_fp timestamp
if hmode == MODE_BCLIENT:
# broadcastclient or multicastclient
ptype = 'b'
elif hmode == MODE_BROADCAST:
# broadcast or multicast server
if srcaddr.startswith("224."): # IANA multicast address prefix
ptype = 'M'
else:
ptype = 'B'
elif hmode == MODE_CLIENT:
if srchost and '(' in srchost:
ptype = 'l' # local refclock
elif dstadr_refid == "POOL":
ptype = 'p' # pool
elif srcaddr.startswith("224."):
ptype = 'a' # manycastclient
else:
ptype = 'u' # unicast
elif hmode == MODE_ACTIVE:
ptype = 's' # symmetric active
elif hmode == MODE_PASSIVE:
ptype = 'S' # symmetric passive
#
# Got everything, format the line
#
poll_sec = 1 << min(ppoll, hpoll)
if self.pktversion > NTP_OLDVERSION:
c = " x.-+#*o"[CTL_PEER_STATVAL(self.session.rstatus) & 0x7]
else:
c = " .+*"[CTL_PEER_STATVAL(self.session.rstatus) & 0x3]
if len(self.chosts) > 1:
maxhostlen = max([len(host) for (host, _af) in self.chosts])
self.say(Ntpq.high_truncate(self.session.hostname, maxhostlen)+ " ")
# Source host or clockname
if srchost != None:
clock_name = srchost
else:
clock_name = canonicalize_dns(srcaddr)
if interpreter.wideremote and len(clock_name) > self.namewidth:
self.say("%c%s\n" % (c, clock_name))
sys.stdout(" " * (self.namewidth + 2))
else:
self.say("%c%-*.*s " % \
(c, self.namewidth, self.namewidth, clock_name[:self.namewidth]))
# Destination address, assoc ID or refid.
assocwidth = 7 if "assid" in header else 0
if "." not in dstadr_refid:
dstadr_refid = "." + dstadr_refid + "."
if assocwidth and len(dstadr_refid) >= self.refidwidth - assocwidth:
visible = "..."
else:
visible = dstadr_refid
self.say(visible)
if "assid" in header:
self.say(" " * (self.refidwidth - len(visible) - assocwidth + 1))
self.say("%-6d" % (associd))
else:
self.say(" " * (self.refidwidth - len(visible)))
# The rest of the story
last_sync = variables.get("rec") or variables.get("reftime")
jd = estjitter if have_jitter else estdisp
jd = " -" if jd >= 999 else ("%7.3f" % jd)
self.say(
" %2ld %c %4.4s %4.4s %3lo %7.3f %8.3f %s\n" % \
(variables.get("stratum", 0),
ptype,
Ntpq.prettyinterval(now if last_sync is None else int(now - lfptofloat(last_sync))),
Ntpq.prettyinterval(poll_sec),
reach, estdelay, estoffset,
jd))
return True
def __dogetpeers(self, header, associd):
try:
variables = self.session.readvar(associd)
except Mode6Exception as e:
print(e.message)
return False
if not variables:
if len(self.chosts) > 1:
self.warn("server=%s ", self.session.hostname)
self.warn("***No information returned for association %d\n" \
% associd)
return False;
return self.__doprintpeers(variables, header, associd);
def __dopeers(self, showall, header):
def __dopeers(self, showall, mode):
if not self.__dogetassoc():
return
report = PeerSummary(mode,
self.pktversion,
self.showhostnames,
self.wideremote)
maxhostlen = 0
if len(self.chosts) > 1:
maxhostlen = max([len(host) for (host, _af) in self.chosts])
self.say("%-*.*s " % \
(maxhostlen, maxhostlen+1, "server"))
self.say(header)
self.say(("=" * (maxhostlen + 78 + self.horizontal_slack)) + "\n")
self.say(report.header() + "\n")
if len(self.chosts) > 1:
maxhostlen = max([len(host) for (host, _af) in self.chosts])
self.say("=" * (maxhostlen + 1))
self.say(("=" * report.width()) + "\n")
for peer in self.peers:
if not showall and \
not (CTL_PEER_STATVAL(peer.status)
......@@ -475,8 +290,21 @@ usage: help [ command ]
if self.debug:
self.warn(stderr, "eliding [%d]\n" % peer.associd)
continue
if not self.__dogetpeers(header, peer.associd):
try:
variables = self.session.readvar(peer.associd)
except Mode6Exception as e:
print(e.message)
return
if not variables:
if len(self.chosts) > 1:
self.warn("server=%s ", self.session.hostname)
self.warn("***No information returned for association %d\n" \
% associd)
continue
if len(self.chosts) > 1:
self.say(PeerSummary.high_truncate(self.session.hostname, maxhostlen)+ " ")
self.say(report.summary(self.session.rstatus,
variables, peer.associd))
def __assoc_valid(self, line, required=False):
"Process a numeric associd or index."
......@@ -1220,7 +1048,7 @@ usage: pstats assocID
def do_peers(self, line):
"obtain and print a list of the server's peers [IP version]"
self.__dopeers(showall=False, header=self.__peerheader)
self.__dopeers(showall=False, mode="peers")
def help_peers(self):
self.say("""\
......@@ -1230,7 +1058,7 @@ usage: peers
def do_apeers(self, line):
"obtain and print a list of the server's peers and their assocIDs [IP version]"
self.__dopeers(showall=False, header=self.__apeerheader)
self.__dopeers(showall=False, mode="apeers")
def help_apeers(self):
self.say("""\
......@@ -1240,7 +1068,7 @@ usage: apeers
def do_lpeers(self, line):
"obtain and print a list of all peers and clients [IP version]"
self.__dopeers(showall=True, header=self.__peerheader)
self.__dopeers(showall=True, mode="peers")
def help_lpeers(self):
self.say("""\
......@@ -1250,7 +1078,7 @@ usage: lpeers
def do_opeers(self, line):
"print peer list the old way, with dstadr shown rather than refid [IP version]"
self.__dopeers(showall=False, header=self.__opeerheader)
self.__dopeers(showall=False, mode="opeers")
def help_opeers(self):
self.say("""\
......
......@@ -5,6 +5,10 @@ from __future__ import print_function
import socket
import sys
import subprocess
import time
import ntp.ntpc
from ntp.packet import *
def canonicalize_dns(hostname):
portsuffix = ""
......@@ -42,4 +46,202 @@ def termsize():
else:
return (24, 80)
class PeerSummary:
"Reusable report generator for peer statistics"
def __init__(self, displaymode, pktversion, showhostnames, wideremote):
self.displaymode = displaymode # peers/apeers.opeers
self.pktversion = pktversion # interpretation of flash bits
self.showhostnames = showhostnames # If false, display numeric IPs
self.wideremote = wideremote # show wide remote names?
# By default, the peer spreadsheet layout is designed so lines just
# fit in 80 characters. This tells us how much extra horizontal space
# we have available on a wider terminal emulator
self.horizontal_slack = termsize()[1] - 80
# Peer spreadsheet column widths
self.namewidth = 15 + self.horizontal_slack
self.refidwidth = 15
# Compute peer spreadsheet headers
self.__remote = " remote ".ljust(self.namewidth)
self.__common = "st t when poll reach delay offset "
self.__header = None
@staticmethod
def prettyinterval(diff):
"Print an interval in natural time units."
if diff <= 0:
return "-"
if diff <= 2048:
return str(diff)
diff = (diff + 29) / 60
if diff <= 300:
return "%dm" % diff
diff = (diff + 29) / 60
if diff <= 96:
return "%dh" % diff
diff = (diff + 11) / 24
return "%dd" % diff
@staticmethod
def high_truncate(hostname, maxlen):
"Truncate on the left using leading _ to indicate 'more'."
# Used for local IPv6 addresses, best distinguished by low bits
if len(hostname) <= maxlen:
return hostname
else:
return '-' + hostname[-maxlen+1:]
def header(self):
"Column headers for peer display"
if self.displaymode == "apeers":
self.__header = self.__remote + \
" refid assid ".ljust(self.refidwidth) + \
self.__common + "jitter"
elif self.displaymode == "opeers":
self.__header = self.__remote + \
" local ".ljust(self.refidwidth) + \
self.__common + " disp"
else:
self.__header = self.__remote + \
" refid ".ljust(self.refidwidth) + \
self.__common + "jitter"
return self.__header
def width(self):
"Width of display"
return 78 + self.horizontal_slack
def summary(self, rstatus, variables, associd):
"Peer status summary line."
hmode = 0
srchost = None
srcport = 0
srcadr = None
dstadr_refid = ""
ppoll = 0
hpoll = 0
reach = 0
ptype = '?'
have_jitter = False
clock_name = ''
now = time.time()
for (name, value) in variables.items():
if name in ("srcadr", "peeradr"):
srcadr = value
elif name == "srchost":
srchost = value
elif name == "dstadr":
# The C code tried to get a fallback pytpe from this in case
# the hmode field was not included
if "local" in self.__header:
dstadr_refid = value
elif name == "hmode":
hmode = value
elif name == "refid":
# The C code for this looked crazily overelaborate. Best
# guess is that it was designed to deal with formats that
# no longer occur in this field.
if "refid" in self.__header:
dstadr_refid = value
elif name == "hpoll":
hpoll = value
if hpoll < 0:
hpoll = NTP_MINPOLL
elif name == "ppoll":
ppoll = value
if ppoll < 0:
ppoll = NTP_MINPOLL
elif name == "reach":
# Shipped as hex, displayed in octal
reach = value
elif name == "delay":
estdelay = value
elif name == "offset":
estoffset = value
elif name == "jitter":
if "jitter" in self.__header:
estjitter = value
have_jitter = True
elif name == "rootdisp" or name == "dispersion":
estdisp = value
elif name == "rec":
rec = value # l_fp timestamp
elif name == "srcport" or name == "peerport":
srcport = value
elif name == "reftime":
reftime = value # l_fp timestamp
if hmode == MODE_BCLIENT:
# broadcastclient or multicastclient
ptype = 'b'
elif hmode == MODE_BROADCAST:
# broadcast or multicast server
if srcadr.startswith("224."): # IANA multicast address prefix
ptype = 'M'
else:
ptype = 'B'
elif hmode == MODE_CLIENT:
if srchost and '(' in srchost:
ptype = 'l' # local refclock
elif dstadr_refid == "POOL":
ptype = 'p' # pool
elif srcadr.startswith("224."):
ptype = 'a' # manycastclient
else:
ptype = 'u' # unicast
elif hmode == MODE_ACTIVE:
ptype = 's' # symmetric active
elif hmode == MODE_PASSIVE:
ptype = 'S' # symmetric passive
#
# Got everything, format the line
#
line = ""
poll_sec = 1 << min(ppoll, hpoll)
if self.pktversion > NTP_OLDVERSION:
c = " x.-+#*o"[CTL_PEER_STATVAL(rstatus) & 0x7]
else:
c = " .+*"[CTL_PEER_STATVAL(rstatus) & 0x3]
# Source host or clockname
if srchost != None:
clock_name = srchost
elif self.showhostnames:
clock_name = canonicalize_dns(srcadr)
else:
clock_name = srcadr
if self.wideremote and len(clock_name) > self.namewidth:
line += ("%c%s\n" % (c, clock_name))
line + (" " * (self.namewidth + 2))
else:
line += ("%c%-*.*s " % \
(c, self.namewidth, self.namewidth, clock_name[:self.namewidth]))
# Destination address, assoc ID or refid.
assocwidth = 7 if self.displaymode == "apeers" else 0
if "." not in dstadr_refid and ":" not in dstadr_refid:
dstadr_refid = "." + dstadr_refid + "."
if assocwidth and len(dstadr_refid) >= self.refidwidth - assocwidth:
visible = "..."
else:
visible = dstadr_refid
line += self.high_truncate(visible, self.refidwidth)
if self.displaymode == "apeers":
line += (" " * (self.refidwidth - len(visible) - assocwidth + 1))
line += ("%-6d" % (associd))
else:
line += (" " * (self.refidwidth - len(visible)))
# The rest of the story
last_sync = variables.get("rec") or variables.get("reftime")
jd = estjitter if have_jitter else estdisp
jd = " -" if jd >= 999 else ("%7.3f" % jd)
line += (
" %2ld %c %4.4s %4.4s %3lo %7.3f %8.3f %s\n" % \
(variables.get("stratum", 0),
ptype,
PeerSummary.prettyinterval(now if last_sync is None else int(now - ntp.ntpc.lfptofloat(last_sync))),
PeerSummary.prettyinterval(poll_sec),
reach, estdelay, estoffset,
jd))
return line
# end