Skip to content

Commit d22283c

Browse files
authored
Merge pull request adafruit#3163 from tannewt/memmonitor
Add `memorymonitor` for memory allocation debugging
2 parents 02b71e0 + 049921f commit d22283c

19 files changed

+957
-6
lines changed

locale/circuitpython.pot

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,15 @@ msgstr ""
7878
msgid "%q list must be a list"
7979
msgstr ""
8080

81+
#: shared-bindings/memorymonitor/AllocationAlarm.c
82+
msgid "%q must be >= 0"
83+
msgstr ""
84+
8185
#: shared-bindings/_bleio/CharacteristicBuffer.c
8286
#: shared-bindings/_bleio/PacketBuffer.c shared-bindings/displayio/Group.c
83-
#: shared-bindings/displayio/Shape.c shared-bindings/vectorio/Circle.c
84-
#: shared-bindings/vectorio/Rectangle.c
87+
#: shared-bindings/displayio/Shape.c
88+
#: shared-bindings/memorymonitor/AllocationAlarm.c
89+
#: shared-bindings/vectorio/Circle.c shared-bindings/vectorio/Rectangle.c
8590
msgid "%q must be >= 1"
8691
msgstr ""
8792

@@ -319,6 +324,11 @@ msgstr ""
319324
msgid "Already advertising."
320325
msgstr ""
321326

327+
#: shared-module/memorymonitor/AllocationAlarm.c
328+
#: shared-module/memorymonitor/AllocationSize.c
329+
msgid "Already running"
330+
msgstr ""
331+
322332
#: ports/cxd56/common-hal/analogio/AnalogIn.c
323333
msgid "AnalogIn not supported on given pin"
324334
msgstr ""
@@ -354,6 +364,11 @@ msgstr ""
354364
msgid "At most %d %q may be specified (not %d)"
355365
msgstr ""
356366

367+
#: shared-module/memorymonitor/AllocationAlarm.c
368+
#, c-format
369+
msgid "Attempt to allocate %d blocks"
370+
msgstr ""
371+
357372
#: supervisor/shared/safe_mode.c
358373
msgid "Attempted heap allocation when MicroPython VM not running."
359374
msgstr ""
@@ -473,7 +488,9 @@ msgstr ""
473488
msgid "Can't set CCCD on local Characteristic"
474489
msgstr ""
475490

476-
#: shared-bindings/displayio/Bitmap.c shared-bindings/pulseio/PulseIn.c
491+
#: shared-bindings/displayio/Bitmap.c
492+
#: shared-bindings/memorymonitor/AllocationSize.c
493+
#: shared-bindings/pulseio/PulseIn.c
477494
msgid "Cannot delete values"
478495
msgstr ""
479496

@@ -1353,6 +1370,7 @@ msgstr ""
13531370
msgid "Random number generation error"
13541371
msgstr ""
13551372

1373+
#: shared-bindings/memorymonitor/AllocationSize.c
13561374
#: shared-bindings/pulseio/PulseIn.c
13571375
msgid "Read-only"
13581376
msgstr ""
@@ -1437,7 +1455,9 @@ msgid "Slice and value different lengths."
14371455
msgstr ""
14381456

14391457
#: shared-bindings/displayio/Bitmap.c shared-bindings/displayio/Group.c
1440-
#: shared-bindings/displayio/TileGrid.c shared-bindings/pulseio/PulseIn.c
1458+
#: shared-bindings/displayio/TileGrid.c
1459+
#: shared-bindings/memorymonitor/AllocationSize.c
1460+
#: shared-bindings/pulseio/PulseIn.c
14411461
msgid "Slices not supported"
14421462
msgstr ""
14431463

main.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@
6464
#include "shared-module/displayio/__init__.h"
6565
#endif
6666

67+
#if CIRCUITPY_MEMORYMONITOR
68+
#include "shared-module/memorymonitor/__init__.h"
69+
#endif
70+
6771
#if CIRCUITPY_NETWORK
6872
#include "shared-module/network/__init__.h"
6973
#endif
@@ -206,6 +210,9 @@ void cleanup_after_vm(supervisor_allocation* heap) {
206210
#if CIRCUITPY_DISPLAYIO
207211
reset_displays();
208212
#endif
213+
#if CIRCUITPY_MEMORYMONITOR
214+
memorymonitor_reset();
215+
#endif
209216
filesystem_flush();
210217
stop_mp();
211218
free_memory(heap);

py/circuitpy_defns.mk

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,9 @@ endif
174174
ifeq ($(CIRCUITPY__EVE),1)
175175
SRC_PATTERNS += _eve/%
176176
endif
177+
ifeq ($(CIRCUITPY_MEMORYMONITOR),1)
178+
SRC_PATTERNS += memorymonitor/%
179+
endif
177180
ifeq ($(CIRCUITPY_MICROCONTROLLER),1)
178181
SRC_PATTERNS += microcontroller/%
179182
endif
@@ -398,6 +401,9 @@ SRC_SHARED_MODULE_ALL = \
398401
gamepad/__init__.c \
399402
gamepadshift/GamePadShift.c \
400403
gamepadshift/__init__.c \
404+
memorymonitor/__init__.c \
405+
memorymonitor/AllocationAlarm.c \
406+
memorymonitor/AllocationSize.c \
401407
network/__init__.c \
402408
os/__init__.c \
403409
random/__init__.c \

py/circuitpy_mpconfig.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,16 @@ extern const struct _mp_obj_module_t _eve_module;
430430
#define _EVE_MODULE
431431
#endif
432432

433+
#if CIRCUITPY_MEMORYMONITOR
434+
extern const struct _mp_obj_module_t memorymonitor_module;
435+
#define MEMORYMONITOR_MODULE { MP_OBJ_NEW_QSTR(MP_QSTR_memorymonitor), (mp_obj_t)&memorymonitor_module },
436+
#define MEMORYMONITOR_ROOT_POINTERS mp_obj_t active_allocationsizes; \
437+
mp_obj_t active_allocationalarms;
438+
#else
439+
#define MEMORYMONITOR_MODULE
440+
#define MEMORYMONITOR_ROOT_POINTERS
441+
#endif
442+
433443
#if CIRCUITPY_MICROCONTROLLER
434444
extern const struct _mp_obj_module_t microcontroller_module;
435445
#define MICROCONTROLLER_MODULE { MP_OBJ_NEW_QSTR(MP_QSTR_microcontroller), (mp_obj_t)&microcontroller_module },
@@ -709,6 +719,7 @@ extern const struct _mp_obj_module_t watchdog_module;
709719
JSON_MODULE \
710720
MATH_MODULE \
711721
_EVE_MODULE \
722+
MEMORYMONITOR_MODULE \
712723
MICROCONTROLLER_MODULE \
713724
NEOPIXEL_WRITE_MODULE \
714725
NETWORK_MODULE \
@@ -766,6 +777,7 @@ extern const struct _mp_obj_module_t watchdog_module;
766777
mp_obj_t terminal_tilegrid_tiles; \
767778
BOARD_UART_ROOT_POINTER \
768779
FLASH_ROOT_POINTERS \
780+
MEMORYMONITOR_ROOT_POINTERS \
769781
NETWORK_ROOT_POINTERS \
770782

771783
void supervisor_run_background_tasks_if_tick(void);

py/circuitpy_mpconfig.mk

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,9 @@ CFLAGS += -DCIRCUITPY_MATH=$(CIRCUITPY_MATH)
118118
CIRCUITPY__EVE ?= 0
119119
CFLAGS += -DCIRCUITPY__EVE=$(CIRCUITPY__EVE)
120120

121+
CIRCUITPY_MEMORYMONITOR ?= 0
122+
CFLAGS += -DCIRCUITPY_MEMORYMONITOR=$(CIRCUITPY_MEMORYMONITOR)
123+
121124
CIRCUITPY_MICROCONTROLLER ?= 1
122125
CFLAGS += -DCIRCUITPY_MICROCONTROLLER=$(CIRCUITPY_MICROCONTROLLER)
123126

py/gc.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@
3333

3434
#include "supervisor/shared/safe_mode.h"
3535

36+
#if CIRCUITPY_MEMORYMONITOR
37+
#include "shared-module/memorymonitor/__init__.h"
38+
#endif
39+
3640
#if MICROPY_ENABLE_GC
3741

3842
#if MICROPY_DEBUG_VERBOSE // print debugging info
@@ -653,6 +657,10 @@ void *gc_alloc(size_t n_bytes, bool has_finaliser, bool long_lived) {
653657
gc_dump_alloc_table();
654658
#endif
655659

660+
#if CIRCUITPY_MEMORYMONITOR
661+
memorymonitor_track_allocation(end_block - start_block + 1);
662+
#endif
663+
656664
return ret_ptr;
657665
}
658666

@@ -906,6 +914,10 @@ void *gc_realloc(void *ptr_in, size_t n_bytes, bool allow_move) {
906914
gc_log_change(block, new_blocks);
907915
#endif
908916

917+
#if CIRCUITPY_MEMORYMONITOR
918+
memorymonitor_track_allocation(new_blocks);
919+
#endif
920+
909921
return ptr_in;
910922
}
911923

@@ -935,6 +947,10 @@ void *gc_realloc(void *ptr_in, size_t n_bytes, bool allow_move) {
935947
gc_log_change(block, new_blocks);
936948
#endif
937949

950+
#if CIRCUITPY_MEMORYMONITOR
951+
memorymonitor_track_allocation(new_blocks);
952+
#endif
953+
938954
return ptr_in;
939955
}
940956

shared-bindings/_bleio/__init__.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,8 @@
5858
//| This object is the sole instance of `_bleio.Adapter`."""
5959
//|
6060

61-
6261
//| class BluetoothError(Exception):
63-
//| """Catch all exception for Bluetooth related errors."""
62+
//| """Catchall exception for Bluetooth related errors."""
6463
//| ...
6564
MP_DEFINE_BLEIO_EXCEPTION(BluetoothError, Exception)
6665

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
/*
2+
* This file is part of the Micro Python project, http://micropython.org/
3+
*
4+
* The MIT License (MIT)
5+
*
6+
* Copyright (c) 2020 Scott Shawcroft for Adafruit Industries
7+
*
8+
* Permission is hereby granted, free of charge, to any person obtaining a copy
9+
* of this software and associated documentation files (the "Software"), to deal
10+
* in the Software without restriction, including without limitation the rights
11+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
* copies of the Software, and to permit persons to whom the Software is
13+
* furnished to do so, subject to the following conditions:
14+
*
15+
* The above copyright notice and this permission notice shall be included in
16+
* all copies or substantial portions of the Software.
17+
*
18+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+
* THE SOFTWARE.
25+
*/
26+
27+
#include <stdint.h>
28+
29+
#include "py/objproperty.h"
30+
#include "py/runtime.h"
31+
#include "py/runtime0.h"
32+
#include "shared-bindings/memorymonitor/AllocationAlarm.h"
33+
#include "shared-bindings/util.h"
34+
#include "supervisor/shared/translate.h"
35+
36+
//| class AllocationAlarm:
37+
//|
38+
//| def __init__(self, *, minimum_block_count=1):
39+
//| """Throw an exception when an allocation of ``minimum_block_count`` or more blocks
40+
//| occurs while active.
41+
//|
42+
//| Track allocations::
43+
//|
44+
//| import memorymonitor
45+
//|
46+
//| aa = memorymonitor.AllocationAlarm(minimum_block_count=2)
47+
//| x = 2
48+
//| # Should not allocate any blocks.
49+
//| with aa:
50+
//| x = 5
51+
//|
52+
//| # Should throw an exception when allocating storage for the 20 bytes.
53+
//| with aa:
54+
//| x = bytearray(20)
55+
//|
56+
//| """
57+
//| ...
58+
//|
59+
STATIC mp_obj_t memorymonitor_allocationalarm_make_new(const mp_obj_type_t *type, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
60+
enum { ARG_minimum_block_count };
61+
static const mp_arg_t allowed_args[] = {
62+
{ MP_QSTR_minimum_block_count, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 1} },
63+
};
64+
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
65+
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
66+
mp_int_t minimum_block_count = args[ARG_minimum_block_count].u_int;
67+
if (minimum_block_count < 1) {
68+
mp_raise_ValueError_varg(translate("%q must be >= 1"), MP_QSTR_minimum_block_count);
69+
}
70+
71+
memorymonitor_allocationalarm_obj_t *self = m_new_obj(memorymonitor_allocationalarm_obj_t);
72+
self->base.type = &memorymonitor_allocationalarm_type;
73+
74+
common_hal_memorymonitor_allocationalarm_construct(self, minimum_block_count);
75+
76+
return MP_OBJ_FROM_PTR(self);
77+
}
78+
79+
//| def ignore(self, count) -> AllocationAlarm:
80+
//| """Sets the number of applicable allocations to ignore before raising the exception.
81+
//| Automatically set back to zero at context exit.
82+
//|
83+
//| Use it within a ``with`` block::
84+
//|
85+
//| # Will not alarm because the bytearray allocation will be ignored.
86+
//| with aa.ignore(2):
87+
//| x = bytearray(20)
88+
//| """
89+
//| ...
90+
//|
91+
STATIC mp_obj_t memorymonitor_allocationalarm_obj_ignore(mp_obj_t self_in, mp_obj_t count_obj) {
92+
mp_int_t count = mp_obj_get_int(count_obj);
93+
if (count < 0) {
94+
mp_raise_ValueError_varg(translate("%q must be >= 0"), MP_QSTR_count);
95+
}
96+
common_hal_memorymonitor_allocationalarm_set_ignore(self_in, count);
97+
return self_in;
98+
}
99+
MP_DEFINE_CONST_FUN_OBJ_2(memorymonitor_allocationalarm_ignore_obj, memorymonitor_allocationalarm_obj_ignore);
100+
101+
//| def __enter__(self) -> memorymonitor.AllocationAlarm:
102+
//| """Enables the alarm."""
103+
//| ...
104+
//|
105+
STATIC mp_obj_t memorymonitor_allocationalarm_obj___enter__(mp_obj_t self_in) {
106+
common_hal_memorymonitor_allocationalarm_resume(self_in);
107+
return self_in;
108+
}
109+
MP_DEFINE_CONST_FUN_OBJ_1(memorymonitor_allocationalarm___enter___obj, memorymonitor_allocationalarm_obj___enter__);
110+
111+
//| def __exit__(self) -> None:
112+
//| """Automatically disables the allocation alarm when exiting a context. See
113+
//| :ref:`lifetime-and-contextmanagers` for more info."""
114+
//| ...
115+
//|
116+
STATIC mp_obj_t memorymonitor_allocationalarm_obj___exit__(size_t n_args, const mp_obj_t *args) {
117+
(void)n_args;
118+
common_hal_memorymonitor_allocationalarm_set_ignore(args[0], 0);
119+
common_hal_memorymonitor_allocationalarm_pause(args[0]);
120+
return mp_const_none;
121+
}
122+
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(memorymonitor_allocationalarm___exit___obj, 4, 4, memorymonitor_allocationalarm_obj___exit__);
123+
124+
STATIC const mp_rom_map_elem_t memorymonitor_allocationalarm_locals_dict_table[] = {
125+
// Methods
126+
{ MP_ROM_QSTR(MP_QSTR_ignore), MP_ROM_PTR(&memorymonitor_allocationalarm_ignore_obj) },
127+
{ MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&memorymonitor_allocationalarm___enter___obj) },
128+
{ MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&memorymonitor_allocationalarm___exit___obj) },
129+
};
130+
STATIC MP_DEFINE_CONST_DICT(memorymonitor_allocationalarm_locals_dict, memorymonitor_allocationalarm_locals_dict_table);
131+
132+
const mp_obj_type_t memorymonitor_allocationalarm_type = {
133+
{ &mp_type_type },
134+
.name = MP_QSTR_AllocationAlarm,
135+
.make_new = memorymonitor_allocationalarm_make_new,
136+
.locals_dict = (mp_obj_dict_t*)&memorymonitor_allocationalarm_locals_dict,
137+
};
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* This file is part of the Micro Python project, http://micropython.org/
3+
*
4+
* The MIT License (MIT)
5+
*
6+
* Copyright (c) 2020 Scott Shawcroft for Adafruit Industries
7+
*
8+
* Permission is hereby granted, free of charge, to any person obtaining a copy
9+
* of this software and associated documentation files (the "Software"), to deal
10+
* in the Software without restriction, including without limitation the rights
11+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
* copies of the Software, and to permit persons to whom the Software is
13+
* furnished to do so, subject to the following conditions:
14+
*
15+
* The above copyright notice and this permission notice shall be included in
16+
* all copies or substantial portions of the Software.
17+
*
18+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+
* THE SOFTWARE.
25+
*/
26+
27+
#ifndef MICROPY_INCLUDED_SHARED_BINDINGS_MEMORYMONITOR_ALLOCATIONALARM_H
28+
#define MICROPY_INCLUDED_SHARED_BINDINGS_MEMORYMONITOR_ALLOCATIONALARM_H
29+
30+
#include "shared-module/memorymonitor/AllocationAlarm.h"
31+
32+
extern const mp_obj_type_t memorymonitor_allocationalarm_type;
33+
34+
void common_hal_memorymonitor_allocationalarm_construct(memorymonitor_allocationalarm_obj_t* self, size_t minimum_block_count);
35+
void common_hal_memorymonitor_allocationalarm_pause(memorymonitor_allocationalarm_obj_t* self);
36+
void common_hal_memorymonitor_allocationalarm_resume(memorymonitor_allocationalarm_obj_t* self);
37+
void common_hal_memorymonitor_allocationalarm_set_ignore(memorymonitor_allocationalarm_obj_t* self, mp_int_t count);
38+
39+
#endif // MICROPY_INCLUDED_SHARED_BINDINGS_MEMORYMONITOR_ALLOCATIONALARM_H

0 commit comments

Comments
 (0)