blob: 66c50569a67c4bf65b90a1bc7338fe1497c86071 [file] [log] [blame]
Austin Schuhd1b28992014-10-26 20:55:06 -07001#!/usr/bin/python3
Philipp Schrader7861dce2015-02-23 00:27:59 +00002import matplotlib
3from matplotlib import pylab
4
5class Dataset(object):
6 def __init__(self):
7 self.time = []
8 self.data = []
9
10 def Add(self, time, data):
11 self.time.append(time)
12 self.data.append(data)
13
14
15class Plotter(object):
16 def __init__(self):
17 self.signal = dict()
18
19 def Add(self, binary, struct_instance_name, *data_search_path):
20 """
21 Specifies a specific piece of data to plot
22
23 Args:
24 binary: str, The name of the executable that generated the log.
25 struct_instance_name: str, The name of the struct instance whose data
26 contents should be plotted.
27 data_search_path: [str], The path into the struct of the exact piece of
28 data to plot.
29
30 Returns:
31 None
32 """
33 self.signal[(binary, struct_instance_name, data_search_path)] = Dataset()
34
35 def HandleLine(self, line):
36 """
37 Parses a line from a log file and adds the data to the plot data.
38
39 Args:
40 line: str, The line from the log file to parse
41
42 Returns:
43 None
44 """
45 pline = ParseLine(line)
46 for key in self.signal:
47 value = self.signal[key]
48 binary = key[0]
49 struct_instance_name = key[1]
50 data_search_path = key[2]
51
52 # Make sure that we're looking at the right binary structure instance.
53 if binary == pline.name:
54 if pline.msg.startswith(struct_instance_name + ': '):
55 # Parse the structure and traverse it as specified in
56 # `data_search_path`. This lets the user access very deeply nested
57 # structures.
58 _, _, data = pline.ParseStruct()
59 for path in data_search_path:
60 data = data[path]
61
62 value.Add(pline.time, data)
63
64 def Plot(self):
65 """
66 Plots all the data after it's parsed.
67
68 This should only be called after `HandleFile` has been called so that there
69 is actual data to plot.
70 """
71 for key in self.signal:
72 value = self.signal[key]
73 pylab.plot(value.time, value.data, label=key[0] + ' ' + '.'.join(key[2]))
74 pylab.legend()
75 pylab.show()
76
77 def PlotFile(self, f):
78 """
79 Parses and plots all the data.
80
81 Args:
82 f: str, The filename of the log whose data to parse and plot.
83
84 Returns:
85 None
86 """
87 self.HandleFile(f)
88 self.Plot()
89
90 def HandleFile(self, f):
91 """
92 Parses the specified log file.
93
94 Args:
95 f: str, The filename of the log whose data to parse.
96
97 Returns:
98 None
99 """
100 with open(f, 'r') as fd:
101 for line in fd:
102 self.HandleLine(line)
103
Austin Schuhd1b28992014-10-26 20:55:06 -0700104
105class LogEntry:
106 """This class provides a way to parse log entries."""
107
108 def __init__(self, line):
109 """Creates a LogEntry from a line."""
110 name_index = line.find('(')
111 self.name = line[0:name_index]
112
113 pid_index = line.find(')', name_index + 1)
114 self.pid = int(line[name_index + 1:pid_index])
115
116 msg_index_index = line.find(')', pid_index + 1)
117 self.msg_index = int(line[pid_index + 2:msg_index_index])
118
119 level_index = line.find(' ', msg_index_index + 3)
120 self.level = line[msg_index_index + 3:level_index]
121
122 time_index_start = line.find(' at ', level_index) + 4
123 time_index_end = line.find('s:', level_index)
124 self.time = float(line[time_index_start:time_index_end])
125
126 filename_end = line.find(':', time_index_end + 3)
127 self.filename = line[time_index_end + 3:filename_end]
128
129 linenumber_end = line.find(':', filename_end + 2)
130 self.linenumber = int(line[filename_end + 2:linenumber_end])
131
132 self.msg = line[linenumber_end+2:]
133
134 def __str__(self):
135 """Formats the data cleanly."""
136 return '%s(%d)(%d): %s at %fs: %s: %d: %s' % (self.name, self.pid, self.msg_index, self.level, self.time, self.filename, self.linenumber, self.msg)
137
138 def __JsonizeTokenArray(self, sub_array, tokens, token_index):
139 """Parses an array from the provided tokens.
140
141 Args:
142 sub_array: list, The list to stick the elements in.
143 tokens: list of strings, The list with all the tokens in it.
144 token_index: int, Where to start in the token list.
145
146 Returns:
147 int, The last token used.
148 """
149 # Make sure the data starts with a '['
150 if tokens[token_index] != '[':
151 print(tokens)
152 print('Expected [ at beginning, found', tokens[token_index + 1])
153 return None
154
155 # Eat the '['
156 token_index += 1
157
158 # Loop through the tokens.
159 while token_index < len(tokens):
160 if tokens[token_index + 1] == ',':
161 # Next item is a comma, so we should just add the element.
162 sub_array.append(tokens[token_index])
163 token_index += 2
164 elif tokens[token_index + 1] == ']':
165 # Next item is a ']', so we should just add the element and finish.
166 sub_array.append(tokens[token_index])
167 token_index += 1
168 return token_index
169 else:
170 # Otherwise, it must be a sub-message.
171 sub_json = dict()
172 token_index = self.JsonizeTokens(sub_json, tokens, token_index + 1)
173 sub_array.append(sub_json)
174 if tokens[token_index] == ',':
175 # Handle there either being another data element.
176 token_index += 1
177 elif tokens[token_index] == ']':
178 # Handle the end of the array.
179 return token_index
180 else:
181 print('Unexpected ', tokens[token_index])
182 return None
183
184 print('Unexpected end')
185 return None
186
187 def JsonizeTokens(self, json, tokens, token_index):
188 """Creates a json-like dictionary from the provided tokens.
189
190 Args:
191 json: dict, The dict to stick the elements in.
192 tokens: list of strings, The list with all the tokens in it.
193 token_index: int, Where to start in the token list.
194
195 Returns:
196 int, The last token used.
197 """
198 # Check that the message starts with a {
199 if tokens[token_index] != '{':
200 print(tokens)
201 print('Expected { at beginning, found', tokens[token_index])
202 return None
203
204 # Eat the {
205 token_index += 1
206
207 # States and state variable for parsing elements.
208 STATE_INIT = 'init'
209 STATE_HAS_NAME = 'name'
210 STATE_HAS_COLON = 'colon'
211 STATE_EXPECTING_SUBMSG = 'submsg'
212 STATE_EXPECTING_COMMA = 'comma'
213 parser_state = STATE_INIT
214
215 while token_index < len(tokens):
216 if tokens[token_index] == '}':
217 # Finish if there is a }
218 return token_index + 1
219 elif tokens[token_index] == '{':
220 if parser_state != STATE_EXPECTING_SUBMSG:
221 print(tokens)
222 print(parser_state)
223 print('Bad input, was not expecting {')
224 return None
225 # Found a submessage, parse it.
226 sub_json = dict()
227 token_index = self.JsonizeTokens(sub_json, tokens, token_index)
228 json[token_name] = sub_json
229 parser_state = STATE_EXPECTING_COMMA
230 else:
231 if parser_state == STATE_INIT:
232 # This token is the name.
233 token_name = tokens[token_index]
234 parser_state = STATE_HAS_NAME
235 elif parser_state == STATE_HAS_NAME:
236 if tokens[token_index] != ':':
237 print(tokens)
238 print(parser_state)
239 print('Bad input, found', tokens[token_index], 'expected :')
240 return None
241 # After a name, comes a :
242 parser_state = STATE_HAS_COLON
243 elif parser_state == STATE_HAS_COLON:
244 # After the colon, figure out what is next.
245 if tokens[token_index] == '[':
246 # Found a sub-array!
247 sub_array = []
248 token_index = self.__JsonizeTokenArray(sub_array, tokens, token_index)
249 json[token_name] = sub_array
250 parser_state = STATE_EXPECTING_COMMA
251 elif tokens[token_index + 1] == '{':
252 # Found a sub-message, trigger parsing it.
253 parser_state = STATE_EXPECTING_SUBMSG
254 else:
255 # This is just an element, move on.
256 json[token_name] = tokens[token_index]
257 parser_state = STATE_EXPECTING_COMMA
258 elif parser_state == STATE_EXPECTING_COMMA:
259 # Complain if there isn't a comma here.
260 if tokens[token_index] != ',':
261 print(tokens)
262 print(parser_state)
263 print('Bad input, found', tokens[token_index], 'expected ,')
264 return None
265 parser_state = STATE_INIT
266 else:
267 print('Bad parser state')
268 return None
269 token_index += 1
270
271 print('Unexpected end')
272 return None
273
274 def ParseStruct(self):
275 """Parses the message as a structure.
276
277 Returns:
278 struct_name, struct_type, json dict.
279 """
280 struct_name_index = self.msg.find(':')
281 struct_name = self.msg[0:struct_name_index]
282
283 struct_body = self.msg[struct_name_index+2:]
284 tokens = []
285 this_token = ''
286 # For the various deliminators, append what we have found so far to the
287 # list and the token.
288 for char in struct_body:
289 if char == '{':
290 if this_token:
291 tokens.append(this_token)
292 this_token = ''
293 tokens.append('{')
294 elif char == '}':
295 if this_token:
296 tokens.append(this_token)
297 this_token = ''
298 tokens.append('}')
299 elif char == '[':
300 if this_token:
301 tokens.append(this_token)
302 this_token = ''
303 tokens.append('[')
304 elif char == ']':
305 if this_token:
306 tokens.append(this_token)
307 this_token = ''
308 tokens.append(']')
309 elif char == ':':
310 if this_token:
311 tokens.append(this_token)
312 this_token = ''
313 tokens.append(':')
314 elif char == ',':
315 if this_token:
316 tokens.append(this_token)
317 this_token = ''
318 tokens.append(',')
319 elif char == ' ':
320 if this_token:
321 tokens.append(this_token)
322 this_token = ''
323 else:
324 this_token += char
325 if this_token:
326 tokens.append(this_token)
327
328 struct_type = tokens[0]
329 json = dict()
330 # Now that we have tokens, parse them.
331 self.JsonizeTokens(json, tokens, 1)
332
333 return (struct_name, struct_type, json)
334
335
336def ParseLine(line):
337 return LogEntry(line)
338
339if __name__ == '__main__':
340 print('motor_writer(2240)(07421): DEBUG at 0000000819.99620s: ../../frc971/output/motor_writer.cc: 105: sending: .aos.controls.OutputCheck{pwm_value:221, pulse_length:2.233333}')
341 line = ParseLine('motor_writer(2240)(07421): DEBUG at 0000000819.99620s: ../../frc971/output/motor_writer.cc: 105: sending: .aos.controls.OutputCheck{pwm_value:221, pulse_length:2.233333}')
342 if '.aos.controls.OutputCheck' in line.msg:
343 print(line)
344 print(line.ParseStruct())
345
346 line = ParseLine('claw(2263)(19404): DEBUG at 0000000820.00000s: ../../aos/common/controls/control_loop-tmpl.h: 104: position: .frc971.control_loops.ClawGroup.Position{top:.frc971.control_loops.HalfClawPosition{position:1.672153, front:.frc971.HallEffectStruct{current:f, posedge_count:0, negedge_count:52}, calibration:.frc971.HallEffectStruct{current:f, posedge_count:6, negedge_count:13}, back:.frc971.HallEffectStruct{current:f, posedge_count:0, negedge_count:62}, posedge_value:0.642681, negedge_value:0.922207}, bottom:.frc971.control_loops.HalfClawPosition{position:1.353539, front:.frc971.HallEffectStruct{current:f, posedge_count:2, negedge_count:150}, calibration:.frc971.HallEffectStruct{current:f, posedge_count:8, negedge_count:18}, back:.frc971.HallEffectStruct{current:f, posedge_count:0, negedge_count:6}, posedge_value:0.434514, negedge_value:0.759491}}')
347 print(line.ParseStruct())
348
349 line = ParseLine('joystick_proxy(2255)(39560): DEBUG at 0000000820.00730s: ../../aos/prime/input/joystick_input.cc: 61: sending: .aos.RobotState{joysticks:[.aos.Joystick{buttons:0, axis:[0.000000, 1.000000, 1.000000, 0.000000]}, .aos.Joystick{buttons:0, axis:[-0.401575, 1.000000, -1.007874, 0.000000]}, .aos.Joystick{buttons:0, axis:[0.007874, 0.000000, 1.000000, -1.007874]}, .aos.Joystick{buttons:0, axis:[0.000000, 0.000000, 0.000000, 0.000000]}], test_mode:f, fms_attached:f, enabled:T, autonomous:f, team_id:971, fake:f}')
350 print(line.ParseStruct())