#!/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 Baton: "Ship progress indication to stderr." def __init__(self, prompt, endmsg=None): self.stream = sys.stderr self.stream.write(prompt + "... \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) else: 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 uninstrumented: "Total times wihout instrumentation, ignoring timezone skew." name = "uninstrumented" def header(self, fp): fp.write("#Read\n") fp.write("----------\n") def formatter(self, session, fp): fp.write("%2.6f\n" % ((time.time() - session.gps_time) % 3600,)) return True def plot(self, file, title, session): fmt = ''' set autoscale set key below set key title "Uninstrumented total latency, %s, %dN%d, cycle %ds" plot "%s" using 0:1 title "Total time" with impulses ''' return fmt % (title, session.baudrate, session.stopbits, session.cycle, file) class rawplot: "Print basic data, ignoring timezone skew." name = "raw" def header(self, fp): fp.write("# ") for hn in ("T1", "E1", "D1", "W", "E2", "T2", "D2"): fp.write("%8s\t" % hn) fp.write("tag\n# ") for i in range(0, 7): fp.write("--------\t") fp.write("--------\n") def formatter(self, session, fp): fp.write("%2.6f %2.6f %2.6f %2.6f %2.6f %2.6f %2.6f # %s\n" \ % (session.rs232, session.d_recv_time - session.rs232, session.d_recv_time + session.d_decode_time, session.d_recv_time + session.poll_time, session.d_recv_time + session.emit_time, session.d_recv_time + session.c_recv_time, session.d_recv_time + session.c_decode_time, session.tag)) return True def plot(self, file, title, session): fmt = ''' set autoscale set key below set key title "Raw latency data, %s, %dN%d, cycle %ds" plot \ "%s" using 0:7 title "D2 = Client decode time" with impulses, \ "%s" using 0:6 title "T2 = TCP/IP latency" with impulses, \ "%s" using 0:5 title "E2 = Daemon encode time" with impulses, \ "%s" using 0:4 title "W = Poll wait time" with impulses, \ "%s" using 0:3 title "D1 = Daemon decode time" with impulses, \ "%s" using 0:2 title "E1 = GPS encode time" with impulses, \ "%s" using 0:1 title "T1 = RS232 time" with impulses ''' return fmt % (title, session.baudrate, session.stopbits, session.cycle, file, file, file, file, file, file, file) class splitplot: "Discard base time, use color to indicate different tags." name = "split" sentences = ("GPGGA", "GPRMC", "GPGLL") def __init__(self): self.found = {} def header(self, fp): fp.write("#") for s in splitplot.sentences: fp.write("%7s\t" % s) for hn in ("E1", "D1", "W", "E2", "T2", "D2"): fp.write("%7s\t" % hn) fp.write("tag\n# ") for s in splitplot.sentences + ("E1", "D1", "W", "E2", "T2", "D2"): fp.write("---------\t") fp.write("--------\n") def formatter(self, session, fp): 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 %2.6f %2.6f %2.6f %2.6f %2.6f # %s\n" \ % (session.d_recv_time - session.rs232, session.d_recv_time + session.d_decode_time, session.d_recv_time + session.poll_time, session.d_recv_time + session.emit_time, session.d_recv_time + session.c_recv_time, session.d_recv_time + session.c_decode_time, session.tag)) return True def plot(self, file, title, session): fixed = ''' set autoscale set key below set key title "Filtered latency data, %s, %dN%d, cycle %ds" plot \\ "%s" using 0:%d title "D2 = Client decode time" with impulses, \ "%s" using 0:%d title "T2 = TCP/IP latency" with impulses, \ "%s" using 0:%d title "E2 = Daemon encode time" with impulses, \ "%s" using 0:%d title "W = Poll wait time" with impulses, \ "%s" using 0:%d title "D1 = Daemon decode time" with impulses, \ "%s" using 0:%d title "E1 = GPS encode time" with impulses, \ ''' sc = len(splitplot.sentences) fmt = fixed % (title, session.baudrate, session.stopbits, session.cycle, file, sc+6, file, sc+5, file, sc+4, 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, splitplot, uninstrumented) if __name__ == '__main__': (options, arguments) = getopt.getopt(sys.argv[1:], "f:hn:o:rs:t:T:") formatter = splitplot raw = False file = None speed = 0 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 == '-s'): speed = int(val) 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)) + "}] [-s speed] [-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: if speed: session.query("b=%d" % speed) if session.baudrate != speed: sys.stderr.write("gpsprof: baud rate change failed.\n") session.query("w+bc") if formatter != uninstrumented: session.query("z+") #session.set_raw_hook(lambda x: sys.stdout.write(x)) plotter.header(out) baton = Baton("gpsprof: gathering samples") while await > 0: session.poll() baton.twirl() # If timestamp is no good, skip it. if not session.isotime or session.isotime == "?": continue session.rs232 = (session.length * (8.0+session.stopbits))/session.baudrate if plotter.formatter(session, out): await -= 1 baton.end() finally: session.query("w-z-") 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()