Skip to content

Commit 9289744

Browse files
committed
monitor_pico.py: improve hog detection.
1 parent 057ae66 commit 9289744

File tree

4 files changed

+110
-27
lines changed

4 files changed

+110
-27
lines changed

v3/as_demos/monitor/README.md

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ This library provides a means of examining the behaviour of a running
44
`uasyncio` system. The device under test is linked to a Raspberry Pi Pico. The
55
latter displays the behaviour of the host by pin changes and/or optional print
66
statements. A logic analyser or scope provides an insight into the way an
7-
asynchronous application is working.
7+
asynchronous application is working, although valuable informtion can be
8+
gleaned without such tools.
89

910
Communication with the Pico may be by UART or SPI, and is uni-directional from
1011
system under test to Pico. If a UART is used only one GPIO pin is used; at last
11-
a use for the ESP8266 transmit-only UART(1). SPI requires three - mosi, sck and
12-
cs/.
12+
a use for the ESP8266 transmit-only UART(1). SPI requires three - `mosi`, `sck`
13+
and `cs/`.
1314

1415
Where an application runs multiple concurrent tasks it can be difficult to
1516
locate a task which is hogging CPU time. Long blocking periods can also result
@@ -25,11 +26,14 @@ trigger because another task is hogging the CPU. Lines 01 and 03 show the `foo`
2526
and `bar` tasks.
2627
![Image](./monitor.jpg)
2728

28-
### Breaking changes to support SPI
29+
### Status
2930

30-
The `set_uart` method is replaced by `set_device`. Pin mappings on the Pico
31-
have changed. Barring bug fixes or user suggestions I consider this project to
32-
be complete.
31+
30th Sep 2021 Pico code has improved hog detection.
32+
33+
27th Sep 2021 SPI support added. The `set_uart` method is replaced by
34+
`set_device`. Pin mappings on the Pico changed.
35+
36+
21st Sep 2021 Initial release.
3337

3438
## 1.1 Pre-requisites
3539

@@ -133,10 +137,8 @@ To aid in detecting the gaps in execution, the Pico code implements a timer.
133137
This is retriggered by activity on `ident=0`. If it times out, a brief high
134138
going pulse is produced on pin 28, along with the console message "Hog". The
135139
pulse can be used to trigger a scope or logic analyser. The duration of the
136-
timer may be adjusted - see [section 4](./README.md~4-the-pico-code).
137-
138-
Note that hog detection will be triggered if the host application terminates.
139-
The Pico cannot determine the reason why the `hog_detect` task has stopped.
140+
timer may be adjusted. Other modes of hog detection are also supported. See
141+
[section 4](./README.md~4-the-pico-code).
140142

141143
# 2. Monitoring synchronous code
142144

@@ -239,6 +241,38 @@ reported if blocking is for more than 60ms, issue
239241
from monitor_pico import run
240242
run(60, (4, 7))
241243
```
244+
Hog reporting is as follows. If ident 0 is inactive for more than the specified
245+
time, "Timeout" is issued. If ident 0 occurs after this, "Hog Nms" is issued
246+
where N is the duration of the outage. If the outage is longer than the prior
247+
maximum, "Max hog Nms" is also issued.
248+
249+
This means that if the application under test terminates, throws an exception
250+
or crashes, "Timeout" will be issued.
251+
252+
## 4.1 Advanced hog detection
253+
254+
The detection of rare instances of high latency is a key requirement and other
255+
modes are available. There are two aims: providing information to users lacking
256+
test equipment and enhancing the ability to detect infrequent cases. Modes
257+
affect the timing of the trigger pulse and the frequency of reports.
258+
259+
Modes are invoked by passing a 2-tuple as the `period` arg.
260+
* `period[0]` The period (ms): outages shorter than this time will be ignored.
261+
* `period[1]` is the mode: constants `SOON`, `LATE` and `MAX` are exported.
262+
263+
The mode has the following effect on the trigger pulse:
264+
* `SOON` Default behaviour: pulse occurs early at time `period[0]` ms after
265+
the last trigger.
266+
* `LATE` Pulse occurs when the outage ends.
267+
* `MAX` Pulse occurs when the outage ends and its duration exceeds the prior
268+
maximum.
269+
270+
The mode also affects reporting. The effect of mode is as follows:
271+
* `SOON` Default behaviour as described in section 4.
272+
* `LATE` As above, but no "Timeout" message: reporting occurs at the end of an
273+
outage only.
274+
* `MAX` Report at end of outage but only when prior maximum exceeded. This
275+
ensures worst-case is not missed.
242276

243277
# 5. Performance and design notes
244278

v3/as_demos/monitor/monitor_pico.py

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
import rp2
1313
from machine import UART, Pin, Timer, freq
14+
from time import ticks_ms, ticks_diff
1415

1516
freq(250_000_000)
1617

@@ -54,27 +55,43 @@ def read(self):
5455
# GPIO 0,1,2 are for interface so pins are 3..22, 26..27
5556
PIN_NOS = list(range(3, 23)) + list(range(26, 28))
5657

58+
# Index is incoming ID
59+
# contents [Pin, instance_count, verbose]
60+
pins = []
61+
for pin_no in PIN_NOS:
62+
pins.append([Pin(pin_no, Pin.OUT), 0, False])
63+
64+
# ****** Timing *****
65+
5766
pin_t = Pin(28, Pin.OUT)
5867
def _cb(_):
5968
pin_t(1)
60-
print('Hog')
69+
print("Timeout.")
6170
pin_t(0)
6271

6372
tim = Timer()
64-
t_ms = 100
65-
# Index is incoming ID
66-
# contents [Pin, instance_count, verbose]
67-
pins = []
68-
for pin_no in PIN_NOS:
69-
pins.append([Pin(pin_no, Pin.OUT), 0, False])
7073

7174
# ****** Monitor ******
75+
76+
SOON = const(0)
77+
LATE = const(1)
78+
MAX = const(2)
79+
# Modes. Pulses and reports only occur if an outage exceeds the threshold.
80+
# SOON: pulse early when timer times out. Report at outage end.
81+
# LATE: pulse when outage ends. Report at outage end.
82+
# MAX: pulse when outage exceeds prior maximum. Report only in that instance.
83+
7284
# native reduced latency to 10μs but killed the hog detector: timer never timed out.
7385
# Also locked up Pico so ctrl-c did not interrupt.
7486
#@micropython.native
7587
def run(period=100, verbose=(), device="uart", vb=True):
76-
global t_ms
77-
t_ms = period
88+
if isinstance(period, int):
89+
t_ms = period
90+
mode = SOON
91+
else:
92+
t_ms, mode = period
93+
if mode not in (SOON, LATE, MAX):
94+
raise ValueError('Invalid mode.')
7895
for x in verbose:
7996
pins[x][2] = True
8097
# A device must support a blocking read.
@@ -92,15 +109,36 @@ def read():
92109
raise ValueError("Unsupported device:", device)
93110

94111
vb and print('Awaiting communication')
112+
h_max = 0 # Max hog duration (ms)
113+
h_start = 0 # Absolute hog start time
95114
while True:
96115
if x := read(): # Get an initial 0 on UART
97116
if x == 0x7a: # Init: program under test has restarted
98117
vb and print('Got communication.')
118+
h_max = 0 # Restart timing
119+
h_start = 0
99120
for pin in pins:
100-
pin[1] = 0
121+
pin[1] = 0 # Clear instance counters
101122
continue
102-
if x == 0x40: # Retrigger hog detector.
103-
tim.init(period=t_ms, mode=Timer.ONE_SHOT, callback=_cb)
123+
if x == 0x40: # hog_detect task has started.
124+
t = ticks_ms() # Arrival time
125+
if mode == SOON: # Pulse on absence of activity
126+
tim.init(period=t_ms, mode=Timer.ONE_SHOT, callback=_cb)
127+
if h_start: # There was a prior trigger
128+
dt = ticks_diff(t, h_start)
129+
if dt > t_ms: # Delay exceeds threshold
130+
if mode != MAX:
131+
print(f"Hog {dt}ms")
132+
if mode == LATE:
133+
pin_t(1)
134+
pin_t(0)
135+
if dt > h_max:
136+
h_max = dt
137+
print(f"Max hog {dt}ms")
138+
if mode == MAX:
139+
pin_t(1)
140+
pin_t(0)
141+
h_start = t
104142
p = pins[x & 0x1f] # Key: 0x40 (ord('@')) is pin ID 0
105143
if x & 0x20: # Going down
106144
p[1] -= 1

v3/as_demos/monitor/monitor_test.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# monitor_test.py
22

3+
# Copyright (c) 2021 Peter Hinch
4+
# Released under the MIT License (MIT) - see LICENSE file
5+
36
import uasyncio as asyncio
47
from monitor import monitor, monitor_init, mon_func, mon_call, set_device
58

v3/as_demos/monitor/quick_test.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# quick_test.py
22

3+
# Copyright (c) 2021 Peter Hinch
4+
# Released under the MIT License (MIT) - see LICENSE file
5+
36
import uasyncio as asyncio
47
import time
58
from machine import Pin, UART, SPI
@@ -17,8 +20,9 @@ async def foo(t, pin):
1720

1821
@monitor(2)
1922
async def hog():
20-
await asyncio.sleep(5)
21-
time.sleep_ms(500)
23+
while True:
24+
await asyncio.sleep(5)
25+
time.sleep_ms(500)
2226

2327
@monitor(3)
2428
async def bar(t):
@@ -27,12 +31,16 @@ async def bar(t):
2731

2832
async def main():
2933
monitor_init()
30-
test_pin = Pin('X6', Pin.OUT)
34+
# test_pin = Pin('X6', Pin.OUT)
35+
test_pin = lambda _ : None # If you don't want to measure latency
3136
asyncio.create_task(hog_detect())
3237
asyncio.create_task(hog()) # Will hog for 500ms after 5 secs
3338
while True:
3439
asyncio.create_task(foo(100, test_pin))
3540
await bar(150)
3641
await asyncio.sleep_ms(50)
3742

38-
asyncio.run(main())
43+
try:
44+
asyncio.run(main())
45+
finally:
46+
asyncio.new_event_loop()

0 commit comments

Comments
 (0)