1 #!/usr/bin/env python2.6 2 # 3 # Copyright (C) 2011 The Android Open Source Project 4 # 5 # Licensed under the Apache License, Version 2.0 (the "License"); 6 # you may not use this file except in compliance with the License. 7 # You may obtain a copy of the License at 8 # 9 # http://www.apache.org/licenses/LICENSE-2.0 10 # 11 # Unless required by applicable law or agreed to in writing, software 12 # distributed under the License is distributed on an "AS IS" BASIS, 13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 # See the License for the specific language governing permissions and 15 # limitations under the License. 16 # 17 18 # 19 # Plots debug log output from VelocityTracker. 20 # Enable DEBUG_VELOCITY to print the output. 21 # 22 # This code supports side-by-side comparison of two algorithms. 23 # The old algorithm should be modified to emit debug log messages containing 24 # the word "OLD". 25 # 26 27 import numpy as np 28 import matplotlib.pyplot as plot 29 import subprocess 30 import re 31 import fcntl 32 import os 33 import errno 34 import bisect 35 from datetime import datetime, timedelta 36 37 # Parameters. 38 timespan = 15 # seconds total span shown 39 scrolljump = 5 # seconds jump when scrolling 40 timeticks = 1 # seconds between each time tick 41 42 # Non-blocking stream wrapper. 43 class NonBlockingStream: 44 def __init__(self, stream): 45 fcntl.fcntl(stream, fcntl.F_SETFL, os.O_NONBLOCK) 46 self.stream = stream 47 self.buffer = '' 48 self.pos = 0 49 50 def readline(self): 51 while True: 52 index = self.buffer.find('\n', self.pos) 53 if index != -1: 54 result = self.buffer[self.pos:index] 55 self.pos = index + 1 56 return result 57 58 self.buffer = self.buffer[self.pos:] 59 self.pos = 0 60 try: 61 chunk = os.read(self.stream.fileno(), 4096) 62 except OSError, e: 63 if e.errno == errno.EAGAIN: 64 return None 65 raise e 66 if len(chunk) == 0: 67 if len(self.buffer) == 0: 68 raise(EOFError) 69 else: 70 result = self.buffer 71 self.buffer = '' 72 self.pos = 0 73 return result 74 self.buffer += chunk 75 76 # Plotter 77 class Plotter: 78 def __init__(self, adbout): 79 self.adbout = adbout 80 81 self.fig = plot.figure(1) 82 self.fig.suptitle('Velocity Tracker', fontsize=12) 83 self.fig.set_dpi(96) 84 self.fig.set_size_inches(16, 12, forward=True) 85 86 self.velocity_x = self._make_timeseries() 87 self.velocity_y = self._make_timeseries() 88 self.velocity_magnitude = self._make_timeseries() 89 self.velocity_axes = self._add_timeseries_axes( 90 1, 'Velocity', 'px/s', [-5000, 5000], 91 yticks=range(-5000, 5000, 1000)) 92 self.velocity_line_x = self._add_timeseries_line( 93 self.velocity_axes, 'vx', 'red') 94 self.velocity_line_y = self._add_timeseries_line( 95 self.velocity_axes, 'vy', 'green') 96 self.velocity_line_magnitude = self._add_timeseries_line( 97 self.velocity_axes, 'magnitude', 'blue') 98 self._add_timeseries_legend(self.velocity_axes) 99 100 shared_axis = self.velocity_axes 101 102 self.old_velocity_x = self._make_timeseries() 103 self.old_velocity_y = self._make_timeseries() 104 self.old_velocity_magnitude = self._make_timeseries() 105 self.old_velocity_axes = self._add_timeseries_axes( 106 2, 'Old Algorithm Velocity', 'px/s', [-5000, 5000], 107 sharex=shared_axis, 108 yticks=range(-5000, 5000, 1000)) 109 self.old_velocity_line_x = self._add_timeseries_line( 110 self.old_velocity_axes, 'vx', 'red') 111 self.old_velocity_line_y = self._add_timeseries_line( 112 self.old_velocity_axes, 'vy', 'green') 113 self.old_velocity_line_magnitude = self._add_timeseries_line( 114 self.old_velocity_axes, 'magnitude', 'blue') 115 self._add_timeseries_legend(self.old_velocity_axes) 116 117 self.timer = self.fig.canvas.new_timer(interval=100) 118 self.timer.add_callback(lambda: self.update()) 119 self.timer.start() 120 121 self.timebase = None 122 self._reset_parse_state() 123 124 # Initialize a time series. 125 def _make_timeseries(self): 126 return [[], []] 127 128 # Add a subplot to the figure for a time series. 129 def _add_timeseries_axes(self, index, title, ylabel, ylim, yticks, sharex=None): 130 num_graphs = 2 131 height = 0.9 / num_graphs 132 top = 0.95 - height * index 133 axes = self.fig.add_axes([0.1, top, 0.8, height], 134 xscale='linear', 135 xlim=[0, timespan], 136 ylabel=ylabel, 137 yscale='linear', 138 ylim=ylim, 139 sharex=sharex) 140 axes.text(0.02, 0.02, title, transform=axes.transAxes, fontsize=10, fontweight='bold') 141 axes.set_xlabel('time (s)', fontsize=10, fontweight='bold') 142 axes.set_ylabel(ylabel, fontsize=10, fontweight='bold') 143 axes.set_xticks(range(0, timespan + 1, timeticks)) 144 axes.set_yticks(yticks) 145 axes.grid(True) 146 147 for label in axes.get_xticklabels(): 148 label.set_fontsize(9) 149 for label in axes.get_yticklabels(): 150 label.set_fontsize(9) 151 152 return axes 153 154 # Add a line to the axes for a time series. 155 def _add_timeseries_line(self, axes, label, color, linewidth=1): 156 return axes.plot([], label=label, color=color, linewidth=linewidth)[0] 157 158 # Add a legend to a time series. 159 def _add_timeseries_legend(self, axes): 160 axes.legend( 161 loc='upper left', 162 bbox_to_anchor=(1.01, 1), 163 borderpad=0.1, 164 borderaxespad=0.1, 165 prop={'size': 10}) 166 167 # Resets the parse state. 168 def _reset_parse_state(self): 169 self.parse_velocity_x = None 170 self.parse_velocity_y = None 171 self.parse_velocity_magnitude = None 172 self.parse_old_velocity_x = None 173 self.parse_old_velocity_y = None 174 self.parse_old_velocity_magnitude = None 175 176 # Update samples. 177 def update(self): 178 timeindex = 0 179 while True: 180 try: 181 line = self.adbout.readline() 182 except EOFError: 183 plot.close() 184 return 185 if line is None: 186 break 187 print line 188 189 try: 190 timestamp = self._parse_timestamp(line) 191 except ValueError, e: 192 continue 193 if self.timebase is None: 194 self.timebase = timestamp 195 delta = timestamp - self.timebase 196 timeindex = delta.seconds + delta.microseconds * 0.000001 197 198 if line.find(': position') != -1: 199 self.parse_velocity_x = self._get_following_number(line, 'vx=') 200 self.parse_velocity_y = self._get_following_number(line, 'vy=') 201 self.parse_velocity_magnitude = self._get_following_number(line, 'speed=') 202 self._append(self.velocity_x, timeindex, self.parse_velocity_x) 203 self._append(self.velocity_y, timeindex, self.parse_velocity_y) 204 self._append(self.velocity_magnitude, timeindex, self.parse_velocity_magnitude) 205 206 if line.find(': OLD') != -1: 207 self.parse_old_velocity_x = self._get_following_number(line, 'vx=') 208 self.parse_old_velocity_y = self._get_following_number(line, 'vy=') 209 self.parse_old_velocity_magnitude = self._get_following_number(line, 'speed=') 210 self._append(self.old_velocity_x, timeindex, self.parse_old_velocity_x) 211 self._append(self.old_velocity_y, timeindex, self.parse_old_velocity_y) 212 self._append(self.old_velocity_magnitude, timeindex, self.parse_old_velocity_magnitude) 213 214 # Scroll the plots. 215 if timeindex > timespan: 216 bottom = int(timeindex) - timespan + scrolljump 217 self.timebase += timedelta(seconds=bottom) 218 self._scroll(self.velocity_x, bottom) 219 self._scroll(self.velocity_y, bottom) 220 self._scroll(self.velocity_magnitude, bottom) 221 self._scroll(self.old_velocity_x, bottom) 222 self._scroll(self.old_velocity_y, bottom) 223 self._scroll(self.old_velocity_magnitude, bottom) 224 225 # Redraw the plots. 226 self.velocity_line_x.set_data(self.velocity_x) 227 self.velocity_line_y.set_data(self.velocity_y) 228 self.velocity_line_magnitude.set_data(self.velocity_magnitude) 229 self.old_velocity_line_x.set_data(self.old_velocity_x) 230 self.old_velocity_line_y.set_data(self.old_velocity_y) 231 self.old_velocity_line_magnitude.set_data(self.old_velocity_magnitude) 232 233 self.fig.canvas.draw_idle() 234 235 # Scroll a time series. 236 def _scroll(self, timeseries, bottom): 237 bottom_index = bisect.bisect_left(timeseries[0], bottom) 238 del timeseries[0][:bottom_index] 239 del timeseries[1][:bottom_index] 240 for i, timeindex in enumerate(timeseries[0]): 241 timeseries[0][i] = timeindex - bottom 242 243 # Extract a word following the specified prefix. 244 def _get_following_word(self, line, prefix): 245 prefix_index = line.find(prefix) 246 if prefix_index == -1: 247 return None 248 start_index = prefix_index + len(prefix) 249 delim_index = line.find(',', start_index) 250 if delim_index == -1: 251 return line[start_index:] 252 else: 253 return line[start_index:delim_index] 254 255 # Extract a number following the specified prefix. 256 def _get_following_number(self, line, prefix): 257 word = self._get_following_word(line, prefix) 258 if word is None: 259 return None 260 return float(word) 261 262 # Add a value to a time series. 263 def _append(self, timeseries, timeindex, number): 264 timeseries[0].append(timeindex) 265 timeseries[1].append(number) 266 267 # Parse the logcat timestamp. 268 # Timestamp has the form '01-21 20:42:42.930' 269 def _parse_timestamp(self, line): 270 return datetime.strptime(line[0:18], '%m-%d %H:%M:%S.%f') 271 272 # Notice 273 print "Velocity Tracker plotting tool" 274 print "-----------------------------------------\n" 275 print "Please enable debug logging and recompile the code." 276 277 # Start adb. 278 print "Starting adb logcat.\n" 279 280 adb = subprocess.Popen(['adb', 'logcat', '-s', '-v', 'time', 'Input:*', 'VelocityTracker:*'], 281 stdout=subprocess.PIPE) 282 adbout = NonBlockingStream(adb.stdout) 283 284 # Prepare plotter. 285 plotter = Plotter(adbout) 286 plotter.update() 287 288 # Main loop. 289 plot.show() 290