#!/usr/bin/env python # # Collect and plot latency-profiling data from a running gpsd. # Requires gnuplot. # import sys, os, time, getopt, gps, tempfile, time, socket class rawplot: "Print basic data, ignoring timezone skew." name = "raw" def header(self, fp): fp.write("#GPS time Received Transmitted Read\n") fp.write("#---------------- --------- ----------- ----------\n") def formatter(self, session, fp): fp.write("%.6f %2.6f %2.6f %2.6f # %s\n" \ % (session.rs232, (session.recv_time - session.gps_time) % 3600, (session.emit_time - session.gps_time) % 3600, (time.time() - session.gps_time) % 3600, session.tag)) return True def plot(self, file, title, session): fmt = ''' set autoscale set key below set key title "Raw latency data, %dN%d, %s" plot \ "%s" using 0:4 title "TCP/IP latency" with impulses, \ "%s" using 0:3 title "Decode time" with impulses, \ "%s" using 0:2 title "Other latency" with impulses, \ "%s" using 0:1 title "RS232 time" with impulses ''' return fmt % (session.baudrate, session.stopbits, title, file, file, file, file) class stripplot: "Discard base time, throw out latencies >1 sec." name = "strip" def header(self, fp): fp.write("#RS-232 Received Transmitted Read\n") fp.write("#--------- --------- ----------- ----------\n") def formatter(self, session, fp): # Throw out total line latencies above 1 second, these are # serial-buffering artifacts at the start of the session line_latency = (session.recv_time - session.gps_time) % 3600 if line_latency > 1: return False fp.write("%2.6f %2.6f %2.6f %2.6f # %s\n" \ % (session.rs232, line_latency, (session.emit_time - session.gps_time) % 3600, (time.time() - session.gps_time) % 3600, session.tag)) return True def plot(self, file, title, session): fmt = ''' set autoscale set key below set key title "Filtered latency data, %dN%d, %s" plot \ "%s" using 0:4 title "TCP/IP latency" with impulses, \ "%s" using 0:3 title "Decode time" with impulses, \ "%s" using 0:2 title "Other latency" with impulses, \ "%s" using 0:1 title "RS232 time" with impulses ''' return fmt % (session.baudrate, session.stopbits, title, file, file, file, file) class splitplot: "Discard base time, throw out latencies >1 sec." name = "split" sentences = ("GPGGA", "GPRMC", "GPGLL") def __init__(self): self.found = {} def header(self, fp): fp.write("#") for s in splitplot.sentences: fp.write(s + " \t") fp.write("Received Transmitted Read\n") fp.write("#") for s in splitplot.sentences: fp.write("---------\t") fp.write("--------- ----------- ----------\n") def formatter(self, session, fp): # Throw out total line latencies above 1 second, these are # serial-buffering artifacts at the start of the session line_latency = (session.recv_time - session.gps_time) % 3600 if line_latency > 1: return False for s in splitplot.sentences: if s == session.tag: fp.write("%2.6f\t"% session.rs232) self.found[s] = True else: fp.write("- \t") fp.write("%2.6f\t%2.6f\t%2.6f\t# %s\n" \ % (line_latency, (session.emit_time - session.gps_time) % 3600, (time.time() - session.gps_time) % 3600, session.tag)) return True def plot(self, file, title, session): fixed = ''' set autoscale set key below set key title "Filtered latency data, %dN%d, %s" plot \\ "%s" using 0:%d title "TCP/IP latency" with impulses, \\ "%s" using 0:%d title "Decode time" with impulses, \\ "%s" using 0:%d title "Other latency" with impulses, \\ ''' sc = len(splitplot.sentences) fmt = fixed % (session.baudrate, session.stopbits, title, file, sc+3, file, sc+2, file, sc+1) for i in range(sc): if splitplot.sentences[i] in self.found: fmt += ' "%s" using 0:%d title "%s" with impulses, \\\n' % \ (file, i+1, splitplot.sentences[i]) return fmt[:-4] + "\n" formatters = (rawplot, stripplot, splitplot) if __name__ == '__main__': (options, arguments) = getopt.getopt(sys.argv[1:], "f:hn:o:rt:T:") formatter = splitplot raw = False file = None terminal = None title = time.ctime() await = 100 for (switch, val) in options: if (switch == '-f'): for formatter in formatters: if formatter.name == val: break else: sys.stderr.write("gpsprof: no such formatter.\n") sys.exit(1) elif (switch == '-n'): await = int(val) elif (switch == '-o'): file = val elif (switch == '-r'): raw = True elif (switch == '-t'): title = val elif (switch == '-T'): terminal = val elif (switch == '-h'): sys.stderr.write("usage: gpsprof [-h] [-r] [-n samplecount] " + "[-f {" + "|".join(map(lambda x: x.name, formatters)) + "}] [-t title] [-o file]\n") sys.exit(0) plotter = formatter() if file: out = open(file, "w") elif raw: out = sys.stdout else: out = tempfile.NamedTemporaryFile() try: session = gps.gps() except socket.error: sys.stderr.write("gpsprof: gpsd unreachable.\n") sys.exit(0) try: session.query("wZ+") plotter.header(out) while await > 0: session.poll() if session.utc == "?": # Timestamp no good, skip it. continue session.gps_time = gps.isotime(session.utc) session.rs232 = (session.length * (8.0+session.stopbits))/session.baudrate if plotter.formatter(session, out): await -= 1 finally: session.query("wZ-") out.flush() if not raw: command = plotter.plot(out.name, title, session) if terminal: command = "set terminal " + terminal + "\n" + command pfp = os.popen("gnuplot -persist", "w") pfp.write(command) pfp.close() del session out.close()