summaryrefslogtreecommitdiff
path: root/gpsprof
diff options
context:
space:
mode:
authorGreg Troxel <gdt@ir.bbn.com>2009-12-02 15:47:06 +0000
committerGreg Troxel <gdt@ir.bbn.com>2009-12-02 15:47:06 +0000
commit96d59fdac2b9eee55171ade78a729331a9f92197 (patch)
treea62c813a44d9e3e3ba218750adba8b017bbd77a1 /gpsprof
parent36d56a924abac976402b0ef0b72703009ab649b6 (diff)
downloadgpsd-96d59fdac2b9eee55171ade78a729331a9f92197.tar.gz
Substitute $PYTHON instead of assuming 'python' is python.
Now 'make check' runs on NetbSD, and $(prefix)/bin/gpsfake works.
Diffstat (limited to 'gpsprof')
-rwxr-xr-xgpsprof481
1 files changed, 0 insertions, 481 deletions
diff --git a/gpsprof b/gpsprof
deleted file mode 100755
index 8410f19e..00000000
--- a/gpsprof
+++ /dev/null
@@ -1,481 +0,0 @@
-#!/usr/bin/env python
-#
-# Collect and plot latency-profiling data from a running gpsd.
-# Requires gnuplot.
-#
-import sys, os, time, getopt, tempfile, time, socket, math, copy
-import gps
-
-class Baton:
- "Ship progress indication to stderr."
- def __init__(self, prompt, endmsg=None):
- self.stream = sys.stderr
- self.stream.write(prompt + "...")
- if os.isatty(self.stream.fileno()):
- self.stream.write(" \010")
- self.stream.flush()
- self.count = 0
- self.endmsg = endmsg
- self.time = time.time()
- return
-
- def twirl(self, ch=None):
- if self.stream is None:
- return
- if ch:
- self.stream.write(ch)
- elif os.isatty(self.stream.fileno()):
- self.stream.write("-/|\\"[self.count % 4])
- self.stream.write("\010")
- self.count = self.count + 1
- self.stream.flush()
- return
-
- def end(self, msg=None):
- if msg == None:
- msg = self.endmsg
- if self.stream:
- self.stream.write("...(%2.2f sec) %s.\n" % (time.time() - self.time, msg))
- return
-
-class spaceplot:
- "Total times without instrumentation."
- name = "space"
- def __init__(self):
- self.fixes = []
- def d(self, a, b):
- return math.sqrt((a[0] - b[0])**2 + (a[1] - b[1])**2)
- def gather(self, session):
- # Include altitude, not used here, for 3D plot experiments.
- # Watch out for the NaN value from gps.py.
- self.fixes.append((session.fix.latitude, session.fix.longitude, session.fix.altitude))
- return True
- def header(self, session):
- res = "# Position uncertainty, %s, %s, %ds cycle\n" % \
- (title, session.gps_id, session.cycle)
- return res
- def data(self, session):
- res = ""
- for i in range(len(self.recentered)):
- (lat, lon) = self.recentered[i][:2]
- (raw1, raw2, alt) = self.fixes[i]
- res += "%f\t%f\t%f\t%f\t%f\n" % (lat, lon, raw1, raw2, alt)
- return res
- def plot(self, title, session):
- if len(self.fixes) == 0:
- sys.stderr.write("No fixes collected, can't estimate accuracy.")
- sys.exit(1)
- # centroid is just arithmetic avg of lat,lon
- self.centroid = (sum(map(lambda x:x[0], self.fixes))/len(self.fixes), sum(map(lambda x:x[1], self.fixes))/len(self.fixes))
- # Sort fixes by distance from centroid
- self.fixes.sort(lambda x, y: cmp(self.d(self.centroid, x), self.d(self.centroid, y)))
- # Convert fixes to offsets from centroid in meters
- self.recentered = map(lambda fix: gps.MeterOffset(self.centroid, fix[:2]), self.fixes)
- # Compute CEP(50%)
- cep_meters = gps.EarthDistance(self.centroid[:2], self.fixes[len(self.fixes)/2][:2])
- alt_sum = 0
- alt_num = 0
- lon_max = -9999
- for i in range(len(self.recentered)):
- (lat, lon) = self.recentered[i][:2]
- (raw1, raw2, alt) = self.fixes[i]
- if not gps.isnan(alt):
- alt_sum += alt
- alt_num += 1
- if lon > lon_max :
- lon_max = lon
- if alt_num == 0:
- alt_avg = gps.NaN
- else:
- alt_avg = alt_sum / alt_num
- if self.centroid[0] < 0:
- latstring = "%fS" % -self.centroid[0]
- elif self.centroid[0] == 0:
- latstring = "0"
- else:
- latstring = "%fN" % self.centroid[0]
- if self.centroid[1] < 0:
- lonstring = "%fW" % -self.centroid[1]
- elif self.centroid[1] == 0:
- lonstring = "0"
- else:
- lonstring = "%fE" % self.centroid[1]
- fmt = "set autoscale\n"
- fmt += 'set key below\n'
- fmt += 'set key title "%s"\n' % time.asctime()
- fmt += 'set size ratio -1\n'
- fmt += 'set style line 2 pt 1\n'
- fmt += 'set style line 3 pt 2\n'
- fmt += 'set xlabel "Meters east from %s"\n' % lonstring
- fmt += 'set ylabel "Meters north from %s"\n' % latstring
- fmt += 'set border 15\n'
- if not gps.isnan(alt_avg):
- fmt += 'set y2label "Meters Altitude from %f"\n' % alt_avg
- fmt += 'set ytics nomirror\n'
- fmt += 'set y2tics\n'
- fmt += 'cep=%f\n' % self.d((0,0), self.recentered[len(self.fixes)/2])
- fmt += 'set parametric\n'
- fmt += 'set trange [0:2*pi]\n'
- fmt += 'cx(t, r) = sin(t)*r\n'
- fmt += 'cy(t, r) = cos(t)*r\n'
- fmt += 'chlen = cep/20\n'
- fmt += "set arrow from -chlen,0 to chlen,0 nohead\n"
- fmt += "set arrow from 0,-chlen to 0,chlen nohead\n"
- fmt += 'plot cx(t, cep),cy(t, cep) title "CEP (50%%) = %f meters", ' % (cep_meters)
- fmt += ' "-" using 1:2 with points ls 3 title "%d GPS fixes" ' % (len(self.fixes))
- if not gps.isnan(alt_avg):
- fmt += ', "-" using ( %f ):($5 < 100000 ? $5 - %f : 1/0) axes x1y2 with points ls 2 title " %d Altitude fixes, Average = %f" \n' % (lon_max +1, alt_avg, alt_num, alt_avg)
- else:
- fmt += "\n"
- fmt += self.header(session)
- fmt += self.data(session)
- if not gps.isnan(alt_avg):
- fmt += "e\n" + self.data(session)
- return fmt
-
-class uninstrumented:
- "Total times without instrumentation."
- name = "uninstrumented"
- def __init__(self):
- self.stats = []
- def gather(self, session):
- if session.fix.time:
- seconds = time.time() - session.fix.time
- self.stats.append(seconds)
- return True
- else:
- return False
- def header(self, session):
- return "# Uninstrumented total latency, %s, %s, %dN%d, cycle %ds\n" % \
- (title,
- session.gps_id, session.baudrate,
- session.stopbits, session.cycle)
- def data(self, session):
- res = ""
- for seconds in self.stats:
- res += "%2.6lf\n" % seconds
- return res
- def plot(self, title, session):
- fmt = '''
-set autoscale
-set key below
-set key title "Uninstrumented total latency, %s, %s, %dN%d, cycle %ds"
-plot "-" using 0:1 title "Total time" with impulses
-'''
- res = fmt % (title,
- session.gps_id, session.baudrate,
- session.stopbits, session.cycle)
- res += self.header(session)
- return res + self.data(session)
-
-class rawplot:
- "All measurement, no deductions."
- name = "raw"
- def __init__(self):
- self.stats = []
- def gather(self, session):
- self.stats.append(copy.copy(session.timings))
- return True
- def header(self, session):
- res = "# Raw latency data, %s, %s, %dN%d, cycle %ds\n" % \
- (title,
- session.gps_id, session.baudrate,
- session.stopbits, session.cycle)
- res += "# tag len xmit "
- for hn in ("T1", "D1", "E2", "T2", "D2"):
- res += "%-13s" % hn
- res += "\n#------- ----- --------------------"
- for i in range(0, 5):
- res += " " + ("-" * 11)
- return res + "\n"
- def data(self, session):
- res = ""
- for timings in self.stats:
- res += "% 8s %4d %2.9f %2.9f %2.9f %2.9f %2.9f %2.9f\n" \
- % (timings.tag,
- timings.len,
- timings.xmit,
- timings.recv - timings.xmit,
- timings.decode - timings.recv,
- timings.emit - timings.decode,
- timings.c_recv - timings.emit,
- timings.c_decode - timings.c_recv)
- return res
- def plot(self, file, session):
- fmt = '''
-set autoscale
-set key below
-set key title "Raw latency data, %s, %s, %dN%d, cycle %ds"
-plot \
- "-" using 0:8 title "D2 = Client decode time" with impulses, \
- "-" using 0:7 title "T2 = TCP/IP latency" with impulses, \
- "-" using 0:6 title "E2 = Daemon encode time" with impulses, \
- "-" using 0:5 title "D1 = Daemon decode time" with impulses, \
- "-" using 0:4 title "T1 = RS232 time" with impulses
-'''
- res = fmt % (title,
- session.gps_id, session.baudrate,
- session.stopbits, session.cycle)
- res += self.header(session)
- for dummy in range(0, 5):
- res += self.data(session) + "e\n"
- return res
-
-class splitplot:
- "Discard base time, use color to indicate different tags."
- name = "split"
- sentences = []
- def __init__(self):
- self.stats = []
- def gather(self, session):
- self.stats.append(copy.copy(session.timings))
- if session.timings.tag not in self.sentences:
- self.sentences.append(session.timings.tag)
- return True
- def header(self, session):
- res = "# Split latency data, %s, %s, %dN%d, cycle %ds\n#" % \
- (title,
- session.gps_id, session.baudrate,
- session.stopbits, session.cycle)
- for s in splitplot.sentences:
- res += "%8s\t" % s
- for hn in ("T1", "D1", "E2", "T2", "D2", "length"):
- res += "%8s\t" % hn
- res += "tag\n# "
- for s in tuple(splitplot.sentences) + ("T1", "D1", "E2", "T2", "D2", "length"):
- res += "---------\t"
- return res + "--------\n"
- def data(self, session):
- res = ""
- for timings in self.stats:
- for s in splitplot.sentences:
- if s == timings.tag:
- res += "%2.6f\t" % timings.xmit
- else:
- res += "- \t"
- res += "%2.6f\t%2.6f\t%2.6f\t%2.6f\t%2.6f\t%8d\t# %s\n" \
- % (timings.recv - timings.xmit,
- timings.decode - timings.recv,
- timings.emit - timings.decode,
- timings.c_recv - timings.emit,
- timings.c_decode - timings.c_recv,
- timings.len,
- timings.tag)
- return res
- def plot(self, title, session):
- fixed = '''
-set autoscale
-set key below
-set key title "Filtered latency data, %s, %s, %dN%d, cycle %ds"
-plot \
- "-" using 0:%d title "D2 = Client decode time" with impulses, \
- "-" using 0:%d title "T2 = TCP/IP latency" with impulses, \
- "-" using 0:%d title "E2 = Daemon encode time" with impulses, \
- "-" using 0:%d title "D1 = Daemon decode time" with impulses, \
- "-" using 0:%d title "T1 = RS3232 time" with impulses, \
-'''
- sc = len(splitplot.sentences)
- fmt = fixed % (title,
- session.gps_id, session.baudrate,
- session.stopbits, session.cycle,
- sc+5,
- sc+4,
- sc+3,
- sc+2,
- sc+1)
- for i in range(sc):
- fmt += ' "-" using 0:%d title "%s" with impulses,' % \
- (i+1, self.sentences[i])
- res = fmt[:-1] + "\n"
- res += self.header(session)
- for dummy in range(sc+5):
- res += self.data(session) + "e\n"
- return res
-
-class cycle:
- "Send-cycle analysis."
- name = "cycle"
- def __init__(self):
- self.stats = []
- def gather(self, session):
- self.stats.append(copy.copy(session.timings))
- return True
- def plot(self, title, session):
- msg = ""
- def roundoff(n):
- # Round a time to hundredths of a second
- return round(n*100) / 100.0
- intervals = {}
- last_seen = {}
- for timing in self.stats:
- # Throw out everything but the leader in each GSV group
- if timing.tag[-3:] == "GSV" and last_command[-3:] == "GSV":
- continue
- last_command = timing.tag
- # Record timings
- received = timing.d_received()
- if not timing.tag in intervals:
- intervals[timing.tag] = []
- if timing.tag in last_seen:
- intervals[timing.tag].append(roundoff(received - last_seen[timing.tag]))
- last_seen[timing.tag] = received
-
- # Step three: get command frequencies and the basic send cycle time
- frequencies = {}
- for (key, interval_list) in intervals.items():
- frequencies[key] = {}
- for interval in interval_list:
- frequencies[key][interval] = frequencies[key].get(interval, 0) + 1
- # filter out noise
- for key in frequencies:
- distribution = frequencies[key]
- for interval in distribution.keys():
- if distribution[interval] < 2:
- del distribution[interval]
- cycles = {}
- for key in frequencies:
- distribution = frequencies[key]
- if len(frequencies[key].values()) == 1:
- # The value is uniqe after filtering
- cycles[key] = distribution.keys()[0]
- else:
- # Compute the mode
- maxfreq = 0
- for (interval, frequency) in distribution.items():
- if distribution[interval] > maxfreq:
- cycles[key] = interval
- maxfreq = distribution[interval]
- msg += "Cycle report %s, %s, %dN%d, cycle %ds" % \
- (title,
- session.gps_id, session.baudrate,
- session.stopbits, session.cycle)
- msg += "The sentence set emitted by this GPS is: %s\n" % " ".join(intervals.keys())
- for key in cycles:
- if len(frequencies[key].values()) == 1:
- if cycles[key] == 1:
- msg += "%s: is emitted once a second.\n" % key
- else:
- msg += "%s: is emitted once every %d seconds.\n" % (key, cycles[key])
- else:
- if cycles[key] == 1:
- msg += "%s: is probably emitted once a second.\n" % key
- else:
- msg += "%s: is probably emitted once every %d seconds.\n" % (key, cycles[key])
- sendcycle = min(*cycles.values())
- if sendcycle == 1:
- msg += "Send cycle is once per second.\n"
- else:
- msg += "Send cycle is once per %d seconds.\n" % sendcycle
- return msg
-
-formatters = (spaceplot, uninstrumented, rawplot, splitplot, cycle)
-
-def plotframe(await, fname, speed, threshold, title):
- "Return a string containing a GNUplot script "
- if fname:
- for formatter in formatters:
- if formatter.name == fname:
- plotter = formatter()
- break
- else:
- sys.stderr.write("gpsprof: no such formatter.\n")
- sys.exit(1)
- try:
- session = gps.gps(verbose=verbose)
- except socket.error:
- sys.stderr.write("gpsprof: gpsd unreachable.\n")
- sys.exit(1)
- # Initialize
- session.poll()
- if session.version == None:
- print >>sys.stderr, "gpsprof: requires gpsd to speak new protocol."
- sys.exit(1)
- session.send("?DEVICES;")
- while session.poll() != -1:
- if session.data["class"] == "DEVICES":
- break
- if len(session.data.devices) != 1:
- print >>sys.stderr, "gpsprof: exactly one device must be attached."
- sys.exit(1)
- device = session.data.devices[0]
- path = device["path"]
- session.baudrate = device["bps"]
- session.parity = device["bps"]
- session.stopbits = device["stopbits"]
- session.cycle = device["cycle"]
- session.gps_id = device["driver"]
- # Set parameters
- if speed:
- session.send('?DEVICE={"path":"%s","bps:":%d}' % (path, speed))
- session.poll()
- if session.baudrate != speed:
- sys.stderr.write("gpsprof: baud rate change failed.\n")
- options = ""
- if formatter not in (spaceplot, uninstrumented):
- options = ',"timing":true'
- try:
- #session.set_raw_hook(lambda x: sys.stderr.write(`x`+"\n"))
- session.send('?WATCH={"enable":true%s}' % options)
- baton = Baton("gpsprof: looking for fix", "done")
- countdown = await
- basetime = time.time()
- while countdown > 0:
- if session.poll() == -1:
- sys.stderr.write("gpsprof: gpsd has vanished.\n")
- sys.exit(1)
- baton.twirl()
- if session.data["class"] == "WATCH":
- if "timing" in options and not session.data.get("timing"):
- sys.stderr.write("gpsprof: timing is not enabled.\n")
- sys.exit(1)
- # We can get some funky artifacts at start of session
- # apparently due to RS232 buffering effects. Ignore
- # them.
- if threshold and time.time()-basetime < session.cycle * threshold:
- continue
- if session.fix.mode <= gps.MODE_NO_FIX:
- continue
- if countdown == await:
- sys.stderr.write("first fix in %.2fsec, gathering %d samples..." % (time.time()-basetime,await))
- if plotter.gather(session):
- countdown -= 1
- baton.end()
- finally:
- session.send('?WATCH={"enable":false,"timing":false}')
- command = plotter.plot(title, session)
- del session
- return command
-
-if __name__ == '__main__':
- try:
- (options, arguments) = getopt.getopt(sys.argv[1:], "f:hm:n:s:t:v")
- formatter = "space"
- raw = False
- speed = 0
- title = time.ctime()
- threshold = 0
- await = 100
- verbose = False
- for (switch, val) in options:
- if (switch == '-f'):
- formatter = val
- elif (switch == '-m'):
- threshold = int(val)
- elif (switch == '-n'):
- await = int(val)
- elif (switch == '-s'):
- speed = int(val)
- elif (switch == '-t'):
- title = val
- elif (switch == '-v'):
- verbose = True
- elif (switch == '-h'):
- sys.stderr.write(\
- "usage: gpsprof [-h] [-m threshold] [-n samplecount] \n"
- + "\t[-f {" + "|".join(map(lambda x: x.name, formatters)) + "}] [-s speed] [-t title]\n")
- sys.exit(0)
- sys.stdout.write(plotframe(await,formatter,speed,threshold,title))
- except KeyboardInterrupt:
- pass
-
-