blob: 421bed43957c71d0ce76ed678387a3db72141786 [file] [log] [blame]
Adam Lesinski282e1812014-01-23 18:17:42 -08001#!/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()