[Git][NTPsec/ntpsec][master] 28 commits: Added i8unit formatter.

Eric S. Raymond gitlab at mg.gitlab.com
Mon Apr 3 13:42:24 UTC 2017


Eric S. Raymond pushed to branch master at NTPsec / ntpsec


Commits:
8c8af96e by Ian Bruene at 2017-04-03T13:35:41+00:00
Added i8unit formatter.

i8unit is a drop in replacement for f8dot4/f8dot3, and displays both the sign
  of the number as well as the unit.

Note that i8unit is only an 80% solution: it almost always shows five digits,
regardless of need. It will show 50000us rather than 50ms

- - - - -
b363568f by Ian Bruene at 2017-04-03T13:35:44+00:00
Added optional units display to ntpq peers

ntpq option -u / --units will display peer times with units instead
of decimals.

In interactive mode the 'units' command will toggle the display setting.

- - - - -
9de6c11b by Ian Bruene at 2017-04-03T13:35:44+00:00
Updated ntpq documentation to reflect new -u option.

- - - - -
3ce0630a by Ian Bruene at 2017-04-03T13:35:46+00:00
Updated TODO to reflect changes to ntpq

- - - - -
51cb504f by Ian Bruene at 2017-04-03T13:35:47+00:00
Redid unit display, also pep8 + pyflaked.

Unit display now keeps everything in the range 1.000-999.9, with 5 units
worth of magnitude (15 orders of magnitude).

u8unit has been replaced by f8unit and a helper function scaleforunit.

f8unit can optionally strip surrounding whitespace for cases such as 'rv'
where a compact display is required.

pylib/util.py and ntqq have been checked with pep8 and pyflakes to clean
up after previous neglect.

- - - - -
2ca46615 by Ian Bruene at 2017-04-03T13:35:47+00:00
Fixed bug that caused units to change in the incorrect direction.

- - - - -
c2e41936 by Ian Bruene at 2017-04-03T13:35:48+00:00
Fixed f8unit formating.

f8unit now correctly formats zero values. "Shouldn't be possible" indeed...

f8unit will now print an integer if the passed value does not have any
decimal places. This takes care of the N.something nanoseconds problem.

f8unit no longer prints a sign on positive numbers.

- - - - -
030b583c by Ian Bruene at 2017-04-03T13:35:49+00:00
Added partial unit display for ntpq 'rv' command.

This does not display units for unit/s variables yet, and there may still be
a few other missing variables.

- - - - -
58a12c38 by Ian Bruene at 2017-04-03T13:35:50+00:00
Fixed some rendering and added variables to cook() unit display.

Fixed bug in f8unit which would lead to displaying .00000000000001 as 10ms.

Out of unit bounds numbers are now rendered in scientific notation. No good
options here, anything else is just about guaranteed to blow the formatting.

0.0 is now rendered without decimal places.

Added temporary renderer for data in ppm.

Added some missing variables, including ppms, to the cook() handler. This can't
be completed until the Great Unit Hunt is complete.

- - - - -
7e89a9be by Ian Bruene at 2017-04-03T13:35:51+00:00
ntpmon now has unit display.

ntp.util now has global lists of variables that use units (incomplete)

- - - - -
342c92b7 by Ian Bruene at 2017-04-03T13:35:52+00:00
Updated ntpq and ntpmon documentation to reflect new options.

- - - - -
497ab30b by Ian Bruene at 2017-04-03T13:35:53+00:00
Fixed various display bugs.

- - - - -
1bce0ac2 by Ian Bruene at 2017-04-03T13:35:54+00:00
Fixes for f8unit and docs.

- - - - -
c9dd0d5d by Ian Bruene at 2017-04-03T13:35:55+00:00
Rewrote f8unit to solve multiple issues.

f8unit now properly handles:
  zero values
  column adjustments due to negative signs
  different sets of units

- - - - -
e63f853c by Ian Bruene at 2017-04-03T13:35:55+00:00
f8unit is now unitformatter, and can format for variable length fields.

- - - - -
3088e230 by Ian Bruene at 2017-04-03T13:35:56+00:00
mreadlist now properly displays units.

- - - - -
7e4f3dd4 by Ian Bruene at 2017-04-03T13:35:57+00:00
Changed unit scaling to be less prone to floating point fun

- - - - -
69bc6947 by Ian Bruene at 2017-04-03T13:35:57+00:00
Fixed scaling. Again. Floats work as they should.

- - - - -
8395790d by Ian Bruene at 2017-04-03T13:35:58+00:00
Fixed float handling, again. Scaled floats now rounded to base unit.

99 buggy floats in the code.. fix one, pass the source around..

98 buggy floats in the code..

- - - - -
0e6719ff by Ian Bruene at 2017-04-03T13:35:59+00:00
ntpmon now works with units.

ntpmon has been updated to use the new unit handling code.

ntpmon responds to -u and --units.

- - - - -
646286da by Ian Bruene at 2017-04-03T13:35:59+00:00
Updated ntpmon docs.

- - - - -
1f67ac26 by Ian Bruene at 2017-04-03T13:36:00+00:00
Added test for rescaleunit. Simplified rescaleunit. Spelling fix.

Spelling fix: parts-per-thousand (ppt) is now parts-per-kilo (ppk) so as
not to conflict with parts-per-trillion (ppt).

- - - - -
84abcfad by Ian Bruene at 2017-04-03T13:36:01+00:00
Added test for scaleforunit. Tweaked scaleforunit.

- - - - -
3356f16c by Ian Bruene at 2017-04-03T13:36:02+00:00
Added test for oomsbetweenunits.

- - - - -
dc6425bc by Ian Bruene at 2017-04-03T13:36:02+00:00
Deleted outdated comment in filtcooker.

- - - - -
c0131961 by Ian Bruene at 2017-04-03T13:36:03+00:00
Added test for filtcooker.

- - - - -
d7d96898 by Ian Bruene at 2017-04-03T13:36:03+00:00
Added test for formatdigitsplit.

- - - - -
28efcdac by Ian Bruene at 2017-04-03T13:36:04+00:00
Added test for unitformatter.

- - - - -


8 changed files:

- devel/TODO
- + devel/units.txt
- docs/includes/ntpmon-body.txt
- docs/includes/ntpq-body.txt
- ntpclients/ntpmon
- ntpclients/ntpq
- pylib/util.py
- tests/pylib/test_util.py


Changes:

=====================================
devel/TODO
=====================================
--- a/devel/TODO
+++ b/devel/TODO
@@ -2,7 +2,7 @@
 
 == Checklist for a quality 1.0 release ==
 
-* Units as an option in ntpq/ntpmon.
+* Units as an option in ntpmon.
 
 * Package metadata for Debian, Ubuntu, Raspbian, Red Hat, Gentoo, and SuSe.
 
@@ -35,45 +35,6 @@ of the current startup logic gets discussed.
 
 == User interface ==
 
-[quote, Gary Miller]
-__________
-'ntpq -p' is user hostile compared to 'chronyc sources'.  chronyc adds
-units to the display, so you do not have to keep referring to the
-manual, and it makes it easy to deal with jitter and delay that
-varies by orders of magnitude.
-
-So, instead of this:
-
-     remote           refid      st t when poll reach   delay   offset  jitter
-==============================================================================
--india.colorado. .NIST.           1 u   33   64  377   79.023  -11.071  10.247
- 140.142.16.34   .INIT.          16 u    -  256    0    0.000    0.000   0.000
-+clepsydra.labs. .GPS.            1 u   28   64  377   26.473   15.307   4.261
-+catbert.rellim. .GPS1.           1 u   31   64  377    0.200   22.061   6.440
--SHM(0)          .GPS.            0 l   45   64  377    0.000  151.500  15.858
-*SHM(1)          .GPS1.           0 l   44   64  377    0.000   20.870   7.680
-+131.107.13.100  .ACTS.           1 u   25   64  337   14.746   18.469   6.634
-
-I see this:
-
-dagwood ~ # chronyc sources
-210 Number of sources = 11
-MS Name/IP address         Stratum Poll Reach LastRx Last sample
-===============================================================================
-#- SHM0                          0   4   377    19   +119ms[ +119ms] +/-  501ms
-#- SHM1                          0   4   377    21   +276us[ +276us] +/-   13us
-#* SOC2                          0   4   377    22   +260us[ +291us] +/-   24us
-#? SOC3                          0   4     0  247d  +7275ms[  +99us] +/-  230ms
-^x catbert.rellim.com            1   8   377     0  -1566us[-1566us] +/- 1564us
-^- spidey.rellim.com             1   6   377    54    +18ms[  +18ms] +/-   27ms
-^- kong.rellim.com               2  10   377   177   -722us[ -735us] +/- 2004us
-^- ha2.smatwebdesign.com         2  10   177   492  -2890us[-2905us] +/-  103ms
-^- soft-sea-01.servers.octos     2  10   377   775  -5897us[-5843us] +/-   83ms
-^- gw-kila-wl0.ipv6.avioc.or     2  10   377   17m   -519us[ -640us] +/-   64ms
-^- 66.241.101.63                 2  10   377   449  +7966us[+7973us] +/-   52ms
-
-Neither is ideal, easy pickings for someone to code on.
-
 === Future plans ===
 
 * ntpd is remarkably bad at standalone Stratum 1 - requires multiple chimers


=====================================
devel/units.txt
=====================================
--- /dev/null
+++ b/devel/units.txt
@@ -0,0 +1,50 @@
+This document is a list of the important time variables and what units they represent in various areas throughout the program.
+
+System Variables
+
+Name                  | internal  | mode 6
+=================================================
+leap                  | seconds   | seconds (* presumed seconds)
+precision             | UNKNOWN   | UNKNOWN
+rootdelay             | seconds   | milliseconds
+rootdisp              | seconds   | milliseconds
+offset                | seconds   | milliseconds
+frequency (aka drift) | seconds/s | microseconds/s (aka ppm)
+sys_jitter            | seconds   | milliseconds
+clk_jitter            | seconds   | milliseconds
+clk_wander            | seconds/s | microseconds/s (probably ppm)
+leapsmearinterval     | seconds   | seconds
+leapsmearoffset       | seconds   | milliseconds
+mintc (aka CR_RATE)   | UNKNOWN   | UNKNOWN
+authdelay             | seconds   | milliseconds
+koffset               | seconds   | milliseconds
+kmaxerr               | seconds   | milliseconds
+kesterr               | seconds   | milliseconds
+kprecis               | seconds   | milliseconds
+kppsjitter            | seconds   | milliseconds
+fuzz                  | seconds   | milliseconds
+clk_wander_threshold  | seconds   | microseconds (probably ppm)
+tick                  | seconds   | milliseconds
+
+
+Peer Variables
+
+Name       | internal | mode 6
+=====================================
+in         | seconds  | milliseconds
+out        | seconds  | milliseconds
+rootdelay  | seconds  | milliseconds
+rootdisp   | seconds  | milliseconds
+bias       | seconds  | milliseconds
+delay      | seconds  | milliseconds
+offset     | seconds  | milliseconds
+jitter     | seconds  | milliseconds
+dispersion | seconds  | milliseconds
+
+
+Clock Variables
+
+Name       | internal | mode 6
+====================================
+fudgetime1 | seconds  | milliseconds
+fudgetime2 | seconds  | milliseconds


=====================================
docs/includes/ntpmon-body.txt
=====================================
--- a/docs/includes/ntpmon-body.txt
+++ b/docs/includes/ntpmon-body.txt
@@ -65,6 +65,8 @@ q:: Cleanly terminate the program.
 
 s:: Toggle display of only reachable hosts (default is all hosts).
 
+u:: Toggle display of units for time values. (default is off)
+
 w:: Toggle wide mode.
 
 x:: Cleanly terminate the program.
@@ -79,6 +81,8 @@ x:: Cleanly terminate the program.
 
 == Options ==
 
+-u:: Show units
+
 -V:: Display version and exit.
 
 == Known Bugs ==


=====================================
docs/includes/ntpq-body.txt
=====================================
--- a/docs/includes/ntpq-body.txt
+++ b/docs/includes/ntpq-body.txt
@@ -4,7 +4,7 @@
 
 == Synopsis ==
 
-+ntpq+ [+-46dhinpwW+] [+-c+ 'command'] ['host'] [...]
++ntpq+ [+-46dhinpwWu+] [+-c+ 'command'] ['host'] [...]
 
 == Description ==
 
@@ -88,6 +88,8 @@ attempt to read interactive format commands from the standard input.
 +-W+, +--width+::
   Force the terminal width.  Only relevant for composition of the
   peers display.
++-u+, +--units+::
+  Display timing information with units.
 
 == Internal Commands ==
 
@@ -190,6 +192,9 @@ following.
   once after a timeout, the total waiting time for a timeout will be
   twice the timeout value set.
 
++units+::
+  Toggle whether times in the peers display are shown with units.
+
 +version+::
   Print the version of the +ntpq+ program.
 


=====================================
ntpclients/ntpmon
=====================================
--- a/ntpclients/ntpmon
+++ b/ntpclients/ntpmon
@@ -13,6 +13,7 @@ Any keystroke causes a poll and update. Keystroke commands:
 'p': Change peer display to default mode, showing refid.
 'q': Cleanly terminate the program.
 's': Toggle display of only reachable hosts (default is all hosts).
+'u': Toggle display of units.
 'w': Toggle wide mode.
 'x': Cleanly terminate the program.
 ' ': Rotate through a/n/o/p display modes.
@@ -70,7 +71,7 @@ def statline(_peerlist, _mrulist, nyquist):
     return leader + spacer + trailer
 
 
-def peer_detail(variables):
+def peer_detail(variables, showunits=False):
     "Show the things a peer summary doesn't, cooked slightly differently"
     # All of an rv display except refid, reach, delay, offset, jitter.
     # One of the goals here is to emit field values at fixed positions
@@ -84,14 +85,31 @@ def peer_detail(variables):
             vcopy[fld] = "***missing***"
         else:
             vcopy[fld] = ntp.util.rfc3339(ntp.ntpc.lfptofloat(vcopy[fld]))
-    vcopy['filtdelay'] = vcopy['filtdelay'].replace(' ', '\t')
-    vcopy['filtoffset'] = vcopy['filtoffset'].replace(' ', '\t')
-    vcopy['filtdisp'] = vcopy['filtdisp'].replace(' ', '\t')
+    if showunits:
+        for name in ntp.util.MS_VARS:
+            if name in vcopy:
+                vcopy[name] = ntp.util.unitformatter(vcopy[name],
+                                                     ntp.util.UNITS_SEC,
+                                                     ntp.util.UNIT_MS,
+                                                     strip=True)
+        for name in ntp.util.PPM_VARS:
+            if name in vcopy:
+                vcopy[name] = ntp.util.unitformatter(vcopy[name],
+                                                     ntp.util.UNIT_PPM,
+                                                     ntp.util.UNITS_PPX,
+                                                     strip=True)
+        vcopy['filtdelay'] = ntp.util.filtcooker(vcopy['filtdelay'])
+        vcopy['filtoffset'] = ntp.util.filtcooker(vcopy['filtoffset'])
+        vcopy['filtdisp'] = ntp.util.filtcooker(vcopy['filtdisp'])
+    else:
+        vcopy['filtdelay'] = vcopy['filtdelay'].replace(' ', '\t')
+        vcopy['filtoffset'] = vcopy['filtoffset'].replace(' ', '\t')
+        vcopy['filtdisp'] = vcopy['filtdisp'].replace(' ', '\t')
     peerfmt = """\
 srcadr=%(srcadr)s, srcport=%(srcport)d, dstadr=%(dstadr)s, dstport=%(dstport)s
 leap=%(leap)s\treftime=%(reftime)s\trootdelay=%(rootdelay)s
 stratum=%(stratum)2d\trec=%(rec)s\trootdisp=%(rootdisp)s
-precision=%(precision)3d\txmt=%(xmt)s\tdispersion=%(dispersion)6.6f
+precision=%(precision)3d\txmt=%(xmt)s\tdispersion=%(dispersion)s
 unreach=%(unreach)d, hmode=%(hmode)d, pmode=%(pmode)d, hpoll=%(hpoll)d, ppoll=%(ppoll)d, headway=%(headway)s, flash=%(flash)s, keyid=%(keyid)s
 filtdelay  = %(filtdelay)s
 filtoffset = %(filtoffset)s
@@ -137,7 +155,7 @@ USAGE: ntpmon [-V] [host]
 if __name__ == '__main__':
     try:
         (options, arguments) = getopt.getopt(sys.argv[1:],
-                                             "V", ["version"])
+                                             "Vu", ["version", "units"])
     except getopt.GetoptError as e:
         sys.stderr.write("%s\n" % e)
         sys.stderr.write(usage)
@@ -148,12 +166,15 @@ if __name__ == '__main__':
     wideremote = False
     showall = True
     showpeers = True
+    showunits = False
     nyquist = 1
 
     for (switch, val) in options:
         if switch in ("-V", "--version"):
             print("ntpmon %s" % ntp.util.stdversion())
             raise SystemExit(0)
+        elif switch in ("-u", "--units"):
+            showunits = True
 
     poll_interval = 1
     helpmode = selectmode = detailmode = False
@@ -162,6 +183,7 @@ if __name__ == '__main__':
                                        pktversion=ntp.magic.NTP_VERSION,
                                        showhostnames=showhostnames,
                                        wideremote=wideremote,
+                                       showunits=showunits,
                                        termwidth=80,
                                        debug=0)
     mru_report = ntp.util.MRUSummary(showhostnames)
@@ -252,7 +274,7 @@ if __name__ == '__main__':
                                                     peers[selected].status)
                             stdscr.addstr("assoc=%d: %s\n"
                                           % (peers[selected].associd, sw))
-                            stdscr.addstr(peer_detail(retained))
+                            stdscr.addstr(peer_detail(retained, showunits))
                             try:
                                 clockvars = session.readvar(
                                     peers[selected].associd,
@@ -295,6 +317,9 @@ if __name__ == '__main__':
                         peer_report.displaymode = 'peers'
                     elif key == 's':
                         showall = not showall
+                    elif key == 'u':
+                        showunits = not showunits
+                        peer_report.showunits = showunits
                     elif key == 'w':
                         peer_report.wideremote = not peer_report.wideremote
                     elif key == " ":


=====================================
ntpclients/ntpq
=====================================
--- a/ntpclients/ntpq
+++ b/ntpclients/ntpq
@@ -153,6 +153,7 @@ class Ntpq(cmd.Cmd):
         self.rawmode = False            # Flag which indicates raw mode output.
         self.directmode = False         # Flag for direct MRU output.
         self.showhostnames = True       # If false, display numeric IPs
+        self.showunits = False          # If False, show old style float
         self.auth_delay = 20            # delay time (default 20msec)
         self.wideremote = False         # show wide remote names?
         self.ccmds = []                 # Queued commands
@@ -293,6 +294,7 @@ usage: help [ command ]
                                       self.pktversion,
                                       self.showhostnames,
                                       self.wideremote,
+                                      self.showunits,
                                       termwidth=termwidth,
                                       debug=interpreter.debug)
         try:
@@ -404,7 +406,7 @@ usage: help [ command ]
                 self.say("status=%04x %s,\n"
                          % (self.session.rstatus,
                             ntp.ntpc.statustoa(dtype, self.session.rstatus)))
-            text = ntp.util.cook(variables)
+            text = ntp.util.cook(variables, self.showunits)
         text = text.replace("'", '"')
         self.say(text)
 
@@ -436,6 +438,10 @@ usage: help [ command ]
 
     # Unexposed helper tables and functions end here
 
+    def do_units(self, _unused):
+        "toggle unit display"
+        self.showunits = not self.showunits
+
     def do_EOF(self, _unused):
         "exit ntpq"
         self.say("\n")
@@ -1569,17 +1575,18 @@ USAGE: ntpq [-46dphinOV] [-c str] [-D lvl] [host ...]
                          on a separate line
    -W no  width          force output width to this value instead of
                          querying the terminal size
+   -u no  units          Display time with units.
 '''
 
 if __name__ == '__main__':
     try:
         (options, arguments) = getopt.getopt(sys.argv[1:],
-                                             "46c:dD:hinpVwW:",
+                                             "46c:dD:hinpVwW:u",
                                              ["ipv4", "ipv6", "command=",
                                               "debug", "set-debug-level=",
                                               "help", "interactive", "numeric",
                                               "peers", "version",
-                                              "wide", "width="])
+                                              "wide", "width=", "units"])
     except getopt.GetoptError as e:
         sys.stderr.write("%s\n" % e)
         sys.stderr.write(usage)
@@ -1630,6 +1637,8 @@ if __name__ == '__main__':
                                  % val)
                 sys.stderr.write(usage)
                 raise SystemExit(1)
+        elif switch in ("-u", "--units"):
+            interpreter.showunits = True
 
     if interpreter.interactive and len(interpreter.ccmds) > 0:
         interpreter.warn("%s: invalid option combination.\n" % progname)


=====================================
pylib/util.py
=====================================
--- a/pylib/util.py
+++ b/pylib/util.py
@@ -10,6 +10,7 @@ import os
 import re
 import shutil
 import collections
+import math
 
 import ntp.ntpc
 import ntp.version
@@ -30,6 +31,31 @@ OLD_CTL_PST_SEL_SYNCCAND = 2
 OLD_CTL_PST_SEL_SYSPEER = 3
 
 
+timefuzz = 1e-9  # Time variables have max precision of 1ns. Use for x==0.0.
+
+# Units for formatting
+UNIT_NS = 0
+UNIT_US = 1
+UNIT_MS = 2
+UNIT_S = 3
+UNIT_KS = 4
+UNITS_SEC = ["ns", "us", "ms", "s", "ks"]
+UNIT_PPT = 0
+UNIT_PPB = 1
+UNIT_PPM = 2
+UNIT_PPK = 3
+UNITS_PPX = ["ppt", "ppb", "ppm", "ppk"]
+
+
+# Variables that have units
+MS_VARS = ("rootdelay", "rootdisp", "offset", "sys_jitter", "clk_jitter",
+           "leapsmearoffset", "authdelay", "koffset", "kmaxerr", "kesterr",
+           "kprecis", "kppsjitter", "fuzz", "clk_wander_threshold", "tick",
+           "in", "out", "bias", "delay", "jitter", "dispersion",
+           "fudgetime1", "fudgetime2")
+PPM_VARS = ("frequency", "clk_wander", "clk_wander_threshold")
+
+
 def stdversion():
     return "%s-%s+%s %s" % (ntp.version.VCS_BASENAME,
                             ntp.version.VERSION,
@@ -64,6 +90,135 @@ def portsplit(hostname):
     return (hostname, portsuffix)
 
 
+def filtcooker(data):
+    "Cooks the string of space seperated numbers with units"
+    parts = data.split()
+    floatyparts = []
+    oomcount = {}
+    # Find out what the 'natural' unit of each value is
+    for part in parts:
+        part = float(part)
+        floatyparts.append(part)
+        value, oom = scaleforunit(part)
+        oomcount[oom] = oomcount.get(oom, 0) + 1
+    # Find the most common unit
+    mostcommon = None
+    highestcount = 0
+    for key in oomcount.keys():
+        count = oomcount[key]
+        if count > highestcount:
+            mostcommon = key
+            highestcount = count
+    newunit = mostcommon + UNIT_MS
+    # Shift all values to the new unit
+    cooked = []
+    for part in floatyparts:
+        part = rescaleunit(part, mostcommon)
+        fmt = formatdigitsplit(part, 7)
+        temp = fmt % part
+        cooked.append(temp)
+    rendered = " ".join(cooked) + " " + UNITS_SEC[newunit]
+    return rendered
+
+
+def rescaleunit(f, units):
+    "Rescale a number by enough orders of magnitude for N units"
+    multiplier = 10 ** (units * 3)
+    f *= multiplier
+    return f
+
+
+def scaleforunit(f):
+    "Scales a number by units to keep it in the range 0.000-999.9"
+    if -timefuzz < f < timefuzz:  # if sufficiently close to zero do nothing
+        return (f, 0)
+    unitsmoved = 0
+    af = abs(f)
+    if af < 1.0:
+        oom = math.floor(math.log10(af))
+    else:
+        oom = math.log10(af)  # Orders Of Magnitude
+    oom -= oom % 3  # We only want to move in groups of 3 ooms
+    multiplier = 10 ** -oom  # Reciprocol because floating * more accurate
+    unitsmoved = oom // 3
+    f *= multiplier
+    f = round(f, 13)  # Min round to catch something like 191.20000000000002
+    return (f, int(unitsmoved))
+
+
+def formatdigitsplit(f, fieldsize):
+    "Create a format string for a float without adding fake precision."
+    af = abs(f)
+    if f.is_integer() or (af < timefuzz):
+        return "%" + str(fieldsize) + "d"
+    if af >= 100.0:
+        maxdigits = fieldsize - 4  # xxx.
+    elif af >= 10.0:
+        maxdigits = fieldsize - 3  # xx.
+    elif af >= 1.0:
+        maxdigits = fieldsize - 2  # x.
+    else:  # Yes, this can happen with filts
+        maxdigits = fieldsize - 2  # 0.
+    if f < 0.0:
+        maxdigits -= 1  # need to fit a negative symbol
+    sig = len(str(f).split(".")[1])  # count digits after the decimal point
+    subdigits = min(sig, maxdigits)  # use min so we don't add fake data
+    formatter = "%" + str(fieldsize) + "." + str(subdigits) + "f"
+    return formatter
+
+
+def oomsbetweenunits(a, b):
+    return abs((a - b) * 3)
+
+
+def unitformatter(f, unitgroup, startingunit, baseunit=None,
+                  strip=False, width=8):
+    "Formatting for unit associated values in N characters."
+    if width is not None:  # For padding to n characters
+        padder = (lambda x: (" " * (width - len(x))) + x)
+    else:
+        strip = True
+    if baseunit is None:
+        baseunit = 0  # Assume that the lowest unit is equal to LSB
+    if -timefuzz < f < timefuzz:  # Zero, don't show decimals
+        unit = unitgroup[baseunit]  # all the way to the lsb
+        rendered = "0" + unit
+        if not strip:
+            rendered = padder(rendered)
+        return rendered
+    oldf = f  # keep this in case we don't fit in the units
+    f, unitsmoved = scaleforunit(f)
+    unitget = startingunit + unitsmoved
+    rounddigits = oomsbetweenunits(unitget, baseunit)
+    f = round(f, rounddigits)
+    if (0 <= unitget < len(unitgroup)):
+        unit = unitgroup[unitget]
+        if width is None:  # Don't care about size, just display everything
+            if unitget == baseunit:  # Don't want fake decimals
+                formatter = "%d"
+                rendered = (formatter % f) + unit
+            else:
+                rendered = repr(f) + unit
+            if strip:
+                rendered = rendered.strip()
+            return rendered
+        else:  # Do care about size, crop value so it will fit
+            displaysize = width - len(unit)
+            if unitget == baseunit:  # Don't want fake decimals
+                formatter = "%" + str(displaysize) + "d"
+            else:
+                formatter = formatdigitsplit(f, displaysize)
+            rendered = (formatter % f) + unit
+            if strip:
+                rendered = rendered.strip()
+            return rendered
+    else:  # Out of units so revert to the original. Ugly but there are very
+        rendered = repr(oldf) + unitgroup[startingunit]  # few options here
+        if not strip:
+            rendered = padder(rendered)
+        return rendered
+
+
 def f8dot4(f):
     "Scaled floating point formatting to fit in 8 characters"
     if f >= 0:
@@ -240,12 +395,18 @@ class PeerStatusWord:
                 % self.__dict__)
 
 
-def cook(variables):
+def cook(variables, showunits=False):
     "Cooked-mode variable display."
     width = ntp.util.termsize().width - 2
     text = ""
+    specials = ("filtdelay", "filtoffset", "filtdisp", "filterror")
+    longestspecial = len(max(specials, key=len))
     for (name, value) in variables.items():
-        item = "%s=" % name
+        if name in specials:  # need special formatting for column alignment
+            formatter = "%" + str(longestspecial) + "s ="
+            item = formatter % name
+        else:
+            item = "%s=" % name
         if name in ("reftime", "clock", "org", "rec", "xmt"):
             item += ntp.ntpc.prettydate(value)
         elif name in ("srcadr", "peeradr", "dstadr", "refid"):
@@ -259,8 +420,11 @@ def cook(variables):
             item += ("00", "01", "10", "11")[value]
         elif name == "reach":
             item += "%03lo" % value
-        elif name in("filtdelay", "filtoffset", "filtdisp", "filterror"):
-            item += "\t".join(value.split())
+        elif name in specials:
+            if showunits:
+                item += filtcooker(value)
+            else:
+                item += "\t".join(value.split())
         elif name == "flash":
             item += "%02x" % value
             if value == 0:
@@ -286,6 +450,21 @@ def cook(variables):
                     if (1 << i) & value:
                         item += tstflagnames[i] + " "
                 item = item[:-1]
+        elif name in MS_VARS:
+            #  Note that this is *not* complete, there are definitely
+            #   missing variables here.
+            #  Completion cannot occur until all units are tracked down.
+            if showunits:
+                item += unitformatter(value, UNITS_SEC, UNIT_MS, UNIT_NS,
+                                      True, width=None)
+            else:
+                item += repr(value)
+        elif name in PPM_VARS:
+            if showunits:
+                item += unitformatter(value, UNITS_PPX, UNIT_PPM,
+                                      strip=True, width=None)
+            else:
+                item += repr(value)
         else:
             item += repr(value)
         item += ", "
@@ -306,10 +485,11 @@ class PeerSummary:
     "Reusable report generator for peer statistics"
 
     def __init__(self, displaymode, pktversion, showhostnames,
-                 wideremote, termwidth=None, debug=0):
+                 wideremote, showunits=False, termwidth=None, debug=0):
         self.displaymode = displaymode          # peers/apeers/opeers
         self.pktversion = pktversion            # interpretation of flash bits
         self.showhostnames = showhostnames      # If false, display numeric IPs
+        self.showunits = showunits              # If False show old style float
         self.wideremote = wideremote            # show wide remote names?
         self.debug = debug
         self.termwidth = termwidth
@@ -530,15 +710,29 @@ class PeerSummary:
                     else int(now - ntp.ntpc.lfptofloat(last_sync))),
                    PeerSummary.prettyinterval(poll_sec), reach))
             if saw6:
-                line += (
-                    " %s %s %s" %
-                    (f8dot4(estdelay), f8dot4(estoffset), f8dot4(jd)))
+                if self.showunits:
+                    line += (
+                        " %s %s %s" %
+                        (unitformatter(estdelay, UNITS_SEC, UNIT_MS),
+                         unitformatter(estoffset, UNITS_SEC, UNIT_MS),
+                         unitformatter(jd, UNITS_SEC, UNIT_MS)))
+                else:
+                    line += (
+                        " %s %s %s" %
+                        (f8dot4(estdelay), f8dot4(estoffset), f8dot4(jd)))
             else:
                 # old servers only have 3 digits of fraction
                 # don't print a fake 4th digit
-                line += (
-                    " %s %s %s" %
-                    (f8dot3(estdelay), f8dot3(estoffset), f8dot3(jd)))
+                if self.showunits:
+                    line += (
+                        " %s %s %s" %
+                        (unitformatter(estdelay, UNITS_SEC, UNIT_MS),
+                         unitformatter(estoffset, UNITS_SEC, UNIT_MS),
+                         unitformatter(jd, UNITS_SEC, UNIT_MS)))
+                else:
+                    line += (
+                        " %s %s %s" %
+                        (f8dot3(estdelay), f8dot3(estoffset), f8dot3(jd)))
             line += "\n"
             return line
         except TypeError:


=====================================
tests/pylib/test_util.py
=====================================
--- a/tests/pylib/test_util.py
+++ b/tests/pylib/test_util.py
@@ -21,5 +21,96 @@ class TestPylibUtilMethods(unittest.TestCase):
             "[0000:1111:2222:3333:4444:5555:6666:7777]:123"),
             ("0000:1111:2222:3333:4444:5555:6666:7777", ":123"))
 
+    def test_rescaleunit(self):
+        self.assertEqual(ntp.util.rescaleunit(1.0, 0),
+                         1.0)
+        self.assertEqual(ntp.util.rescaleunit(10.0, 1),
+                         10000.0)
+        self.assertEqual(ntp.util.rescaleunit(0.1, 1),
+                         100.0)
+        self.assertEqual(ntp.util.rescaleunit(10.0, -1),
+                         0.01)
+        self.assertEqual(ntp.util.rescaleunit(0.1, -1),
+                         0.0001)
+        self.assertEqual(ntp.util.rescaleunit(-100.0, 1),
+                         -100000.0)
+        self.assertEqual(ntp.util.rescaleunit(-.001, 1),
+                         -1.0)
+        self.assertEqual(ntp.util.rescaleunit(-100.0, -1),
+                         -0.1)
+        self.assertEqual(ntp.util.rescaleunit(-.001, -1),
+                         -0.000001)
+
+    def test_scaleforunit(self):
+        self.assertEqual(ntp.util.scaleforunit(1.0),
+                         (1.0, 0))
+        self.assertEqual(ntp.util.scaleforunit(999.2342),
+                         (999.2342, 0))
+        self.assertEqual(ntp.util.scaleforunit(5042.7),
+                         (5.0427, 1))
+        self.assertEqual(ntp.util.scaleforunit(0.1912),
+                         (191.2, -1))
+        self.assertEqual(ntp.util.scaleforunit(0.00000042),
+                         (420.0, -3))
+        self.assertEqual(ntp.util.scaleforunit(-1.0),
+                         (-1.0, 0))
+        self.assertEqual(ntp.util.scaleforunit(-999.2342),
+                         (-999.2342, 0))
+        self.assertEqual(ntp.util.scaleforunit(-5042.7),
+                         (-5.0427, 1))
+        self.assertEqual(ntp.util.scaleforunit(-0.1912),
+                         (-191.2, -1))
+        self.assertEqual(ntp.util.scaleforunit(-0.00000042),
+                         (-420.0, -3))
+
+    def test_oomsbetweenunits(self):
+        self.assertEqual(ntp.util.oomsbetweenunits(3, 2),
+                         3)
+        self.assertEqual(ntp.util.oomsbetweenunits(2, 3),
+                         3)
+
+    def test_filtcooker(self):
+        self.assertEqual(ntp.util.filtcooker(
+            "1.02 34.5 0.67835 -23.0 9 6.7 1.0 .1"),
+                         "   1.02    34.5 0.67835     -23       9     6.7       1     0.1 ms")
+
+    def test_formatdigitsplit(self):
+        self.assertEqual(ntp.util.formatdigitsplit(10.0, 5),
+                         "%5d")
+        self.assertEqual(ntp.util.formatdigitsplit(100.52, 5),
+                         "%5.1f")
+        self.assertEqual(ntp.util.formatdigitsplit(10.123456789, 8),
+                         "%8.5f")
+        self.assertEqual(ntp.util.formatdigitsplit(1.123456789, 8),
+                         "%8.6f")
+        self.assertEqual(ntp.util.formatdigitsplit(0.123456789, 8),
+                         "%8.6f")
+        self.assertEqual(ntp.util.formatdigitsplit(1.234, 10),
+                         "%10.3f")
+        self.assertEqual(ntp.util.formatdigitsplit(-1.23456789, 6),
+                         "%6.3f")
+
+    def test_unitformatter(self):
+        f = ntp.util.unitformatter
+        usec = ntp.util.UNITS_SEC
+        self.assertEqual(f(0.0000000005, usec, ntp.util.UNIT_MS),
+                         "     0ns")  # Checking timefuzz
+        self.assertEqual(f(0.0000000005, usec, ntp.util.UNIT_MS, strip=True),
+                         "0ns")  # Checking timefuzz, strip
+        self.assertEqual(f(12.45, usec, ntp.util.UNIT_MS),
+                         " 12.45ms")  # Checking normal
+        self.assertEqual(f(12.45, usec, ntp.util.UNIT_MS, strip=True),
+                         "12.45ms")  # Checking normal, strip
+        self.assertEqual(f(123456789.1234, usec, ntp.util.UNIT_MS, width=None),
+                         "123.4567891234ks")  # Checking normal, no width
+        self.assertEqual(f(0.000005, usec, ntp.util.UNIT_MS),
+                         "     5ns")  # Unit == base
+        self.assertEqual(f(0.000005, usec, ntp.util.UNIT_MS, strip=True),
+                         "5ns")  # Unit == base, strip
+        self.assertEqual(f(0.000005, usec, ntp.util.UNIT_MS, width=None),
+                         "5ns")  # Unit == base, no width
+        self.assertEqual(f(10000.1, usec, ntp.util.UNIT_KS),
+                         "10000.1ks")  # Value outside of unit ranges
+
 if __name__ == '__main__':
     unittest.main()



View it on GitLab: https://gitlab.com/NTPsec/ntpsec/compare/a85e57f0df90b42efe2c72db534cf98b21e44819...28efcdac29bac021544ee0a7772751849c19d296
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.ntpsec.org/pipermail/vc/attachments/20170403/6ab2ebef/attachment.html>


More information about the vc mailing list