Skip to content

Commit bf983b8

Browse files
committed
Merge pull request #39 from Overdrivr/transport-monitoring
Health monitoring
2 parents 94577f8 + 7ea4891 commit bf983b8

6 files changed

Lines changed: 236 additions & 5 deletions

File tree

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,4 @@ install:
3434

3535
# command to run tests
3636
script:
37-
- py.test --cov
37+
- py.test -v --cov

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,15 @@ Plots <topic> in a graph window.
9393
Usage: plot <topic>
9494
```
9595

96+
### stats
97+
```bash
98+
Displays different metrics about the active transport (ex : serial port).
99+
This allows you to know if for instance corrupted frames are received, what fraction
100+
of the maximum baudrate is being used, etc.
101+
102+
Usage: stats
103+
```
104+
96105
### disconnect
97106
```bash
98107
Disconnects from any open connection.

appveyor.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,4 @@ install:
4343
- "cd %APPVEYOR_BUILD_FOLDER%"
4444
- "SET PYTHONPATH=%PYTHONPATH%;%APPVEYOR_BUILD_FOLDER%"
4545
test_script:
46-
- "%MINICONDA%\\Scripts\\py.test --cov"
46+
- "%MINICONDA%\\Scripts\\py.test -v --cov"

pytelemetrycli/cli.py

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ def __init__(self, transport=None, stdout=None):
5757
else:
5858
self.transport = transport
5959
self.telemetry = Pytelemetry(self.transport)
60+
6061
self.topics = Topics()
6162
self.plots = []
6263
self.plotsLock = Lock()
@@ -65,6 +66,7 @@ def __init__(self, transport=None, stdout=None):
6566
self.plots,
6667
self.plotsLock,
6768
self.topics)
69+
6870
self.telemetry.subscribe(None,self.topics.process)
6971

7072
self.types_lookup = {'--s' : 'string',
@@ -115,6 +117,10 @@ def do_serial(self, arg):
115117
logger.info(s)
116118
self.topics.clear()
117119
logger.info("Cleared all topics for new session.")
120+
self.transport.resetStats()
121+
self.runner.resetStats()
122+
self.telemetry.resetStats()
123+
logger.info("Cleared all stats for new session.")
118124

119125
@docopt_cmd
120126
def do_print(self, arg):
@@ -146,7 +152,7 @@ def do_print(self, arg):
146152

147153
if s is not None:
148154
for i in s:
149-
self.stdout.write("%i\n" % i)
155+
self.stdout.write("{0}\n".format(i))
150156
else:
151157
logger.error("Could not retrieve {0} sample(s) under topic '{1}'.\n".format(amount,topic))
152158

@@ -274,13 +280,35 @@ def do_disconnect(self, arg):
274280
self.runner.disconnect()
275281
self.stdout.write("Disconnected.\n")
276282
logger.info("Disconnected.")
283+
284+
measures = self.transport.stats()
285+
286+
for key,item in measures.items():
287+
logger.info("Raw IO : %s : %s" % (key,item))
288+
289+
measures = self.runner.stats()
290+
291+
for key,item in measures.items():
292+
logger.info("IO speeds : %s : %s" % (key,item))
293+
294+
measures = self.telemetry.stats()
295+
296+
for key,item in measures['framing'].items():
297+
logger.info("Framing : %s : %s" % (key,item))
298+
299+
for key,item in measures['protocol'].items():
300+
logger.info("Protocol : %s : %s" % (key,item))
301+
302+
logger.info("Logged session statistics.")
303+
304+
277305
except:
278306
logger.warn("Already disconnected. Continuing happily.")
279307

280308
@docopt_cmd
281309
def do_info(self, arg):
282310
"""
283-
Disconnects from any open connection.
311+
Prints out cli.py full path, module version.
284312
285313
Usage: info
286314
"""
@@ -290,13 +318,45 @@ def do_info(self, arg):
290318
except:
291319
self.stdout.write("- version : not found.\n")
292320

321+
@docopt_cmd
322+
def do_stats(self, arg):
323+
"""
324+
Displays different metrics about the active transport (ex : serial port).
325+
This allows you to know if for instance corrupted frames are received, what fraction
326+
of the maximum baudrate is being used, etc.
327+
328+
Usage: stats
329+
"""
330+
measures = self.transport.stats()
331+
332+
self.stdout.write("Raw IO:\n")
333+
for key,item in measures.items():
334+
self.stdout.write("\t%s : %s\n" % (key,item))
335+
336+
measures = self.runner.stats()
337+
338+
self.stdout.write("IO speeds:\n")
339+
for key,item in measures.items():
340+
self.stdout.write("\t%s : %s\n" % (key,item))
341+
342+
measures = self.telemetry.stats()
343+
344+
self.stdout.write("Framing:\n")
345+
for key,item in measures['framing'].items():
346+
self.stdout.write("\t%s : %s\n" % (key,item))
347+
348+
self.stdout.write("Protocol:\n")
349+
for key,item in measures['protocol'].items():
350+
self.stdout.write("\t%s : %s\n" % (key,item))
351+
293352
def do_quit(self, arg):
294353
"""
295354
Exits the terminal application.
296355
297356
Usage: quit
298357
"""
299358
self.runner.terminate()
359+
self.do_disconnect("")
300360
self.stdout.write("Good Bye!\n")
301361
logger.info("Application quit.")
302362
exit()

pytelemetrycli/runner.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,13 @@ def __init__(self, transport, telemetry, plots, plotsLock, topics):
1818
self.connected = threading.Event()
1919
self.connected.clear()
2020

21+
self.resetStats()
22+
2123
def connect(self,port,bauds):
2224
options = dict()
2325
options['port'] = port
2426
options['baudrate'] = bauds
27+
self.baudrate = bauds
2528
self.transport.connect(options)
2629
self.connected.set()
2730
self.thread = threading.Thread(target=self.run)
@@ -40,6 +43,21 @@ def terminate(self):
4043
except:
4144
pass # Already disconnected
4245

46+
def stats(self):
47+
return {
48+
"baudspeed" : self.baudspeed,
49+
"baudspeed_avg" : self.baudspeed_avg,
50+
"baudratio" : self.baudspeed / self.baudrate,
51+
"baudratio_avg" : self.baudspeed_avg / self.baudrate
52+
}
53+
54+
def resetStats(self):
55+
self.baudrate = 1.0
56+
self.baudspeed = 0.0
57+
self.lasttime = time.time()
58+
self.lastamount = 0.0
59+
self.baudspeed_avg = 0.0
60+
4361
def update(self):
4462
# Update protocol decoding
4563
self.telemetryWrapper.update()
@@ -48,6 +66,26 @@ def update(self):
4866
# being modified from the main thread
4967
self.plotsLock.acquire()
5068

69+
# Update baudspeed value
70+
current = time.time()
71+
difft = current - self.lasttime
72+
73+
if difft > 0.05 :
74+
self.lasttime = current
75+
76+
current = self.transport.stats()['rx_bytes']
77+
diff = current - self.lastamount
78+
self.lastamount = current
79+
80+
self.baudspeed = diff / difft
81+
82+
# Compute rolling average baud speed on about 1 second window
83+
n = 20
84+
self.baudspeed_avg = (self.baudspeed + n * self.baudspeed_avg) / (n + 1)
85+
# Need a dedicated flag with ls to display program parameters
86+
#self.topics.process("baudspeed",self.baudspeed)
87+
#self.topics.process("baudspeed_avg",self.baudspeed_avg)
88+
5189
# Poll each poll pipe to see if user closed them
5290
plotToDelete = None
5391
for p, i in zip(self.plots,range(len(self.plots))):

pytelemetrycli/test/test_cli.py

Lines changed: 125 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ class TransportMock:
1111
def __init__(self):
1212
self.q = queue.Queue()
1313
self.canConnect = False
14+
self.counter = 0
1415
def authorizeConnect(self,value):
1516
self.canConnect = value
1617
def connect(self,options):
@@ -20,6 +21,7 @@ def disconnect(self):
2021
pass
2122
def read(self, maxbytes=1):
2223
amount = maxbytes if self.q.qsize() > maxbytes else self.q.qsize()
24+
self.counter += amount
2325
data = []
2426
for i in range(amount):
2527
data.append(self.q.get())
@@ -31,6 +33,12 @@ def write(self, data):
3133
self.q.put(c)
3234
def writeable(self):
3335
return True
36+
def resetStats(self):
37+
self.counter = 0
38+
def stats(self):
39+
return {
40+
"rx_bytes": self.counter
41+
}
3442

3543
# To be done
3644
class SuperplotMock:
@@ -122,6 +130,12 @@ def test_print(fixturefortests):
122130

123131
clear(outstream)
124132

133+
tlm.onecmd("pub --s hello world")
134+
tlm.runner.update()
135+
assert outstream.getvalue() == "Published on topic 'hello' : world [string]\n"
136+
137+
clear(outstream)
138+
125139
tlm.onecmd("pub --i32 foo 4")
126140
tlm.runner.update()
127141
assert outstream.getvalue() == "Published on topic 'foo' : 4 [int32]\n"
@@ -164,6 +178,12 @@ def test_print(fixturefortests):
164178

165179
clear(outstream)
166180

181+
tlm.onecmd("print hello")
182+
tlm.runner.update()
183+
assert outstream.getvalue() == "world\n"
184+
185+
clear(outstream)
186+
167187
tlm.onecmd("print foo -a 2.3")
168188
tlm.runner.update()
169189
assert outstream.getvalue() == "Could not cast --amount = '2.3' to integer. Using 1.\n4\n"
@@ -228,7 +248,7 @@ def test_disconnect_quit(fixturefortests):
228248
clear(outstream)
229249

230250
pytest.raises(SystemExit, tlm.onecmd, "quit")
231-
assert outstream.getvalue() == "Good Bye!\n"
251+
assert outstream.getvalue() == "Disconnected.\nGood Bye!\n"
232252

233253
clear(outstream)
234254

@@ -311,3 +331,107 @@ def test_topics_are_cleared_after_reconnect(fixturefortests):
311331
assert outstream.getvalue() == ""
312332

313333
clear(outstream)
334+
335+
336+
def test_stats(fixturefortests):
337+
tr, outstream, tlm = fixturefortests
338+
clear(outstream)
339+
tlm.topics.clear() # Clear all topics
340+
341+
tr.resetStats()
342+
tlm.runner.resetStats()
343+
tlm.telemetry.resetStats()
344+
tlm.onecmd("stats")
345+
346+
assert "Raw IO:\n" in outstream.getvalue()
347+
assert "\trx_bytes : 0\n" in outstream.getvalue()
348+
assert "IO speeds:\n" in outstream.getvalue()
349+
assert "\tbaudspeed : 0.0\n" in outstream.getvalue()
350+
assert "\tbaudratio : 0.0\n" in outstream.getvalue()
351+
assert "\tbaudratio_avg : 0.0\n" in outstream.getvalue()
352+
assert "\tbaudspeed_avg : 0.0\n" in outstream.getvalue()
353+
assert "Framing:\n" in outstream.getvalue()
354+
assert "\ttx_encoded_frames : 0\n" in outstream.getvalue()
355+
assert "\trx_uncomplete_frames : 0\n" in outstream.getvalue()
356+
assert "\ttx_processed_bytes : 0\n" in outstream.getvalue()
357+
assert "\trx_complete_frames : 0\n" in outstream.getvalue()
358+
assert "\ttx_escaped_bytes : 0\n" in outstream.getvalue()
359+
assert "\trx_discarded_bytes : 0\n" in outstream.getvalue()
360+
assert "\trx_processed_bytes : 0\n" in outstream.getvalue()
361+
assert "\trx_escaped_bytes : 0\n" in outstream.getvalue()
362+
assert "Protocol:\n" in outstream.getvalue()
363+
assert "\ttx_encoded_frames : 0\n" in outstream.getvalue()
364+
assert "\trx_corrupted_header : 0\n" in outstream.getvalue()
365+
assert "\trx_decoded_frames : 0\n" in outstream.getvalue()
366+
assert "\trx_corrupted_payload : 0\n" in outstream.getvalue()
367+
assert "\trx_corrupted_crc : 0\n" in outstream.getvalue()
368+
assert "\trx_corrupted_eol : 0\n" in outstream.getvalue()
369+
assert "\trx_corrupted_topic : 0\n" in outstream.getvalue()
370+
371+
tlm.onecmd("pub --i32 foo 2")
372+
373+
clear(outstream)
374+
375+
tlm.runner.update()
376+
377+
tlm.onecmd("stats")
378+
379+
speeds = tlm.runner.stats()
380+
381+
assert "Raw IO:\n" in outstream.getvalue()
382+
assert "\trx_bytes : 14\n" in outstream.getvalue()
383+
assert "IO speeds:\n" in outstream.getvalue()
384+
assert "\tbaudspeed : {0}\n".format(speeds['baudspeed']) in outstream.getvalue()
385+
assert "\tbaudratio : {0}\n".format(speeds['baudratio']) in outstream.getvalue()
386+
assert "\tbaudratio_avg : {0}\n".format(speeds['baudratio_avg']) in outstream.getvalue()
387+
assert "\tbaudspeed_avg : {0}\n".format(speeds['baudspeed_avg']) in outstream.getvalue()
388+
assert "Framing:\n" in outstream.getvalue()
389+
assert "\ttx_encoded_frames : 1\n" in outstream.getvalue()
390+
assert "\trx_uncomplete_frames : 0\n" in outstream.getvalue()
391+
assert "\ttx_processed_bytes : 12\n" in outstream.getvalue()
392+
assert "\trx_complete_frames : 1\n" in outstream.getvalue()
393+
assert "\ttx_escaped_bytes : 0\n" in outstream.getvalue()
394+
assert "\trx_discarded_bytes : 0\n" in outstream.getvalue()
395+
assert "\trx_processed_bytes : 14\n" in outstream.getvalue()
396+
assert "\trx_escaped_bytes : 0\n" in outstream.getvalue()
397+
assert "Protocol:\n" in outstream.getvalue()
398+
assert "\ttx_encoded_frames : 1\n" in outstream.getvalue()
399+
assert "\trx_corrupted_header : 0\n" in outstream.getvalue()
400+
assert "\trx_decoded_frames : 1\n" in outstream.getvalue()
401+
assert "\trx_corrupted_payload : 0\n" in outstream.getvalue()
402+
assert "\trx_corrupted_crc : 0\n" in outstream.getvalue()
403+
assert "\trx_corrupted_eol : 0\n" in outstream.getvalue()
404+
assert "\trx_corrupted_topic : 0\n" in outstream.getvalue()
405+
406+
# Check stats are cleaned after restart
407+
tr.authorizeConnect(True)
408+
tlm.onecmd("serial com123")
409+
410+
clear(outstream)
411+
412+
tlm.onecmd("stats")
413+
414+
assert "Raw IO:\n" in outstream.getvalue()
415+
assert "\trx_bytes : 0\n" in outstream.getvalue()
416+
assert "IO speeds:\n" in outstream.getvalue()
417+
assert "\tbaudspeed : 0.0\n" in outstream.getvalue()
418+
assert "\tbaudratio : 0.0\n" in outstream.getvalue()
419+
assert "\tbaudratio_avg : 0.0\n" in outstream.getvalue()
420+
assert "\tbaudspeed_avg : 0.0\n" in outstream.getvalue()
421+
assert "Framing:\n" in outstream.getvalue()
422+
assert "\ttx_encoded_frames : 0\n" in outstream.getvalue()
423+
assert "\trx_uncomplete_frames : 0\n" in outstream.getvalue()
424+
assert "\ttx_processed_bytes : 0\n" in outstream.getvalue()
425+
assert "\trx_complete_frames : 0\n" in outstream.getvalue()
426+
assert "\ttx_escaped_bytes : 0\n" in outstream.getvalue()
427+
assert "\trx_discarded_bytes : 0\n" in outstream.getvalue()
428+
assert "\trx_processed_bytes : 0\n" in outstream.getvalue()
429+
assert "\trx_escaped_bytes : 0\n" in outstream.getvalue()
430+
assert "Protocol:\n" in outstream.getvalue()
431+
assert "\ttx_encoded_frames : 0\n" in outstream.getvalue()
432+
assert "\trx_corrupted_header : 0\n" in outstream.getvalue()
433+
assert "\trx_decoded_frames : 0\n" in outstream.getvalue()
434+
assert "\trx_corrupted_payload : 0\n" in outstream.getvalue()
435+
assert "\trx_corrupted_crc : 0\n" in outstream.getvalue()
436+
assert "\trx_corrupted_eol : 0\n" in outstream.getvalue()
437+
assert "\trx_corrupted_topic : 0\n" in outstream.getvalue()

0 commit comments

Comments
 (0)