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 27import numpy as np 28import matplotlib.pyplot as plot 29import subprocess 30import re 31import fcntl 32import os 33import errno 34import bisect 35from datetime import datetime, timedelta 36 37# Parameters. 38timespan = 15 # seconds total span shown 39scrolljump = 5 # seconds jump when scrolling 40timeticks = 1 # seconds between each time tick 41 42# Non-blocking stream wrapper. 43class 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 77class 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 273print "Velocity Tracker plotting tool" 274print "-----------------------------------------\n" 275print "Please enable debug logging and recompile the code." 276 277# Start adb. 278print "Starting adb logcat.\n" 279 280adb = subprocess.Popen(['adb', 'logcat', '-s', '-v', 'time', 'Input:*', 'VelocityTracker:*'], 281 stdout=subprocess.PIPE) 282adbout = NonBlockingStream(adb.stdout) 283 284# Prepare plotter. 285plotter = Plotter(adbout) 286plotter.update() 287 288# Main loop. 289plot.show() 290