Commits (2)
......@@ -35,18 +35,22 @@ def mean(data):
raise ValueError('mean requires at least one data point')
return sum(data)/n # in Python 2 use sum(data)/float(n)
def _ss(data):
def _ss(data, mu=None):
"""Return sum of square deviations of sequence data."""
c = mean(data)
if None == mu:
c = mean(data)
else:
c = mu
ss = sum((x-c)**2 for x in data)
return ss
def pstdev(data):
# fixme, need to handle mu=mean
def pstdev(data, mu=None):
"""Calculates the population standard deviation."""
n = len(data)
if n < 2:
raise ValueError('variance requires at least two data points')
ss = _ss(data)
ss = _ss(data, mu)
pvar = ss/n # the population variance
return pvar**0.5
......@@ -354,41 +358,69 @@ plot \
sys.stderr.write("ntpviz: WARNING: no loopstats to graph\n")
return ''
sitename = self.sitename
cnt = collections.Counter()
for line in self.loopstats:
# put into 100 nSec buckets
cnt[ round( float(line.split()[1]), 7)] += 1
# TODO normalize to 0 to 100
# grab and sort the values, no need for the timestamp, etc.
values = [float(line.split()[1]) for line in self.loopstats]
values.sort()
values_mean = mean( values ) * 1000000
ninetynine = self.percentile(2, 99, self.loopstats) * 1000000
seventyfive = self.percentile(2, 75, self.loopstats) * 1000000
twentyfive = self.percentile(2, 25, self.loopstats) * 1000000
one = self.percentile(2, 1, self.loopstats) * 1000000
mu = mean( values )
values_mean = mu * 1000000
values_mean_str = str( round( values_mean, 3 ) )
values_pstd = round( pstdev( values, mu=mu ) * 1000000, 3)
# plus/minus of one sigma range
m1sigma = values_mean - (values_pstd / 2)
p1sigma = values_mean + (values_pstd / 2)
ninetynine = round( self.percentiles(99, values) * 1000000, 2)
ninety = round( self.percentiles(90, values) * 1000000, 2)
five = round( self.percentiles( 5, values) * 1000000, 2)
one = round( self.percentiles( 1, values) * 1000000, 2)
cnt = collections.Counter()
for value in values:
# put into 100 nSec buckets
# for a +/- 50 microSec range that is 1,000 buckets to plot
cnt[ round( float(value), 7)] += 1
# skip the mean
#set label 3 "mean = %(values_mean_str)s μs" at graph 0.01,0.3 left front
plot_template = '''\
set terminal png size 900,600
set grid
set xtic rotate by -45 scale 0
set title "%(sitename)s: Local Clock Time Offset - Histogram"
set xtics format "@1.1f μs" nomirror
set label 1 gprintf("99@@ = @1.2f μs",%(ninetynine)s) at %(ninetynine)s, graph 0.91 left front offset 1,-1
set style arrow 1 nohead
set arrow from %(ninetynine)s,0 to %(ninetynine)s,graph 0.91 as 1
set label 2 gprintf(" 1@@ = @1.2f μs",%(one)s) at %(one)s, graph 0.91 right front offset -1,-1
set arrow from %(m1sigma)s,0 to %(m1sigma)s,graph 0.90 as 1
set style arrow 2 nohead
set arrow from %(one)s,0 to %(one)s,graph 0.91 as 2
set label 3 gprintf("25@@ = @1.2f μs",%(twentyfive)s) at %(twentyfive)s, graph 0.7 right front offset -1,-1
set arrow from %(p1sigma)s,0 to %(p1sigma)s,graph 0.90 as 2
set style arrow 3 nohead
set arrow from %(twentyfive)s,0 to %(twentyfive)s,graph 0.7 as 3
set label 4 gprintf("75@@ = @1.2f μs",%(seventyfive)s) at %(seventyfive)s, graph 0.7 left front offset 1,-1
set arrow from %(ninetynine)s,0 to %(ninetynine)s,graph 0.15 as 3
set style arrow 4 nohead
set arrow from %(seventyfive)s,0 to %(seventyfive)s,graph 0.7 as 4
set arrow from %(ninety)s,0 to %(ninety)s,graph 0.30 as 4
set style arrow 5 nohead
set arrow from %(five)s,0 to %(five)s,graph 0.30 as 5
set style arrow 6 nohead
set arrow from %(one)s,0 to %(one)s,graph 0.15 as 6
set key off
set lmargin 12
set rmargin 12
set label 1 gprintf("mean = %(values_mean)s μs",50) at graph 0.01,0.3 left front
set label 1 "1σ" at %(m1sigma)s, graph 0.96 left front offset -1,-1
set label 2 "1σ" at %(p1sigma)s, graph 0.96 left front offset -1,-1
set label 3 "99%%" at %(ninetynine)s, graph 0.20 left front offset -1,-1
set label 4 "90%%" at %(ninety)s, graph 0.35 left front offset -1,-1
set label 5 "1%%" at %(one)s, graph 0.20 left front offset -1,-1
set label 6 "5%%" at %(five)s, graph 0.35 left front offset -1,-1
set label 10 "1σ (68%%) = %(values_pstd)s μs" at graph 0.01,0.95 left front
set label 11 "99%% = %(ninetynine)s μs" at graph 0.01,0.90 left front
set label 12 "90%% = %(ninety)s μs" at graph 0.01,0.85 left front
set label 13 "5%% = %(five)s μs" at graph 0.01,0.80 left front
set label 14 "1%% = %(one)s μs" at graph 0.01,0.75 left front
plot \
"-" using ($1 * 1000000):2 title "histogram" with boxes
''' % locals()
......@@ -629,7 +661,7 @@ heating).</p>
""",
"local-offset-histogram": """\
<p>This shows the clock offsets of the local clock as a histogram. It
includes 1%, 25%, 75%, and 99% percentiles to show the performance of
includes 1%, 5%, 95%, and 99% percentiles to show the performance of
the system.</p>
""",
"local-stability": """\
......
......@@ -125,10 +125,16 @@ class NTPStats:
return m
def percentile(self, n, percentile, entries):
"Return given percentiles of a given row in a given set of entries."
"If you call this twice on the same data set you should use"
"percentiles() instead"
# Row is decremented so we match GNUPLOT's 1-origin indexing.
values = [float(line.split()[n-1]) for line in entries]
values.sort()
return values[int(len(values) * (percentile/100))]
def percentiles(self, percentile, values):
"Return given percentiles of a given row in a given set of entries."
"assuming values are already spilit and sorted"
return values[int(len(values) * (percentile/100))]
def peersplit(self):
"Return a dictionary mapping peerstats IPs to entry subsets."
peermap = {}
......