Skip to main content

Inventory the long-lived Python objects in a process.

Project description

memory_inventory is a Python module that collects and outputs statistics on global objects. It can help you determine why a Python program consumes an excessive amount of memory when it's idle. This is different than memory profilers like memray that track memory allocations as they happen.

memory_inventory is written entirely in typed Python and is compatible with versions 3.12 and above of CPython.

How it works

memory_inventory starts from the sys.modules dictionary, adds its keys and values to a queue, and accesses the native attributes of objects in the queue in order to find more objects, until there are no more to find or a pre-defined limit is reached. This should find all global objects in memory, except those created by native code and not exposed to Python code.

Obviously this isn't the best way to take stock of Python objects in memory. It isn't entirely accurate, it isn't fast, and it uses a significant amount of memory, but the idea was to explore what could be done in pure Python without using the ctypes module to access raw memory words.

You should take a look at the source code of memory_inventory.

Installation

memory_inventory is on PyPI, so:

python -m pip install memory_inventory

Usage

CLI

memory_inventory has a simple command line interface that can be used to load a specific module and subsequently examine the global objects in memory:

python -m memory_inventory $module_name

memory_inventory always excludes its own objects from the analysis, so asking it to analyse itself doesn't work.

Running memory_inventory without specifying a module name launches an analysis of the entire Python standard library:

python -m memory_inventory

That last command should output something close to the following text when run with CPython 3.13.3 on Linux:

Starting import of (almost) all the standard library modules
596 modules have been imported, increasing the total to 715.
14 modules failed to load: _overlapped, _pyrepl.windows_console, _scproxy, _winapi, _wmi, asyncio.windows_events, asyncio.windows_utils, encodings.mbcs, encodings.oem, msvcrt, multiprocessing.popen_spawn_win32, nt, winreg, winsound
Elapsed time: 0.222s (84.6% in userland). Resident memory: 60872 kB now, 61692 kB at peak. Active threads: 1.
The garbage collector is tracking 51452 objects and has previously deleted 478 others in 48 runs. It has found 0 uncollectable objects.
Types by intrinsic memory usage:
| type                       | cumulated memory usage | instances found | average memory usage per instance |
| ----------top 15---------- | ---------------------- | --------------- | --------------------------------- |
| str                        |            7_572_197 B |          82_021 |                              92 B |
| code                       |            6_784_072 B |          16_198 |                             418 B |
| bytes                      |            6_235_865 B |          34_906 |                             178 B |
| type                       |            4_009_648 B |           2_810 |                           1_426 B |
| tuple                      |            3_797_600 B |          51_369 |                              73 B |
| dict                       |            3_420_272 B |          21_467 |                             159 B |
| function                   |            2_485_600 B |          15_535 |                             160 B |
| set                        |              199_616 B |             232 |                             860 B |
| abc.ABCMeta                |              197_968 B |             143 |                           1_384 B |
| list                       |              176_312 B |           1_736 |                             101 B |
| int                        |              162_228 B |           5_702 |                              28 B |
| getset_descriptor          |              140_800 B |           2_200 |                              64 B |
| frozenset                  |              123_384 B |             269 |                             458 B |
| method_descriptor          |              105_840 B |           1_470 |                              72 B |
| builtin_function_or_method |               97_920 B |           1_360 |                              72 B |
| ..........total........... | ...................... | ............... | ................................. |
|                        311 |           36_401_131 B |         247_125 |                             147 B |
Uninstantiated types (excluding BaseException and its subclasses): 2178
Duplicate objects by type:
| type      | savable memory | duplicate instances |
| --------- | -------------- | ------------------- |
| tuple     |      845_032 B |              15_180 |
| bytes     |      585_127 B |               6_821 |
| str       |      449_390 B |               7_304 |
| int       |       75_880 B |               2_679 |
| frozenset |       32_552 B |                 127 |
| float     |        3_096 B |                 129 |
| complex   |            0 B |                   0 |
| ..total.. | .............. | ................... |
|         7 |    1_991_077 B |              32_240 |
Instantiated classes lacking slots:
| class                                       | savable memory | __dict__ memory usage | memory reduction | missing slots | instances found |
| -------------------top 15------------------ | -------------- | --------------------- | ---------------- | ------------- | --------------- |
| _frozen_importlib.ModuleSpec                |       51_336 B |             102_672 B |            50.0% |             9 |             713 |
| _frozen_importlib_external.SourceFileLoader |       43_128 B |              52_712 B |            81.8% |             2 |             599 |
| classmethod                                 |       38_088 B |              49_128 B |            77.5% |             5 |             276 |
| re._constants._NamedIntConstant             |       22_496 B |              23_104 B |            97.4% |             1 |              76 |
| http.HTTPStatus                             |       15_872 B |              18_848 B |            84.2% |             6 |              62 |
| staticmethod                                |       13_392 B |              21_712 B |            61.7% |             5 |             208 |
| tkinter.EventType                           |       10_064 B |              11_248 B |            89.5% |             4 |              37 |
| ssl._TLSAlertType                           |        9_248 B |              10_336 B |            89.5% |             4 |              34 |
| signal.Signals                              |        8_976 B |              10_032 B |            89.5% |             4 |              33 |
| socket.AddressFamily                        |        8_704 B |               9_728 B |            89.5% |             4 |              32 |
| functools._lru_cache_wrapper                |        7_488 B |               9_792 B |            76.5% |             8 |              36 |
| ssl.AlertDescription                        |        7_344 B |               8_208 B |            89.5% |             4 |              27 |
| ssl._TLSMessageType                         |        5_984 B |               6_688 B |            89.5% |             4 |              22 |
| ast._Precedence                             |        4_896 B |               5_472 B |            89.5% |             4 |              18 |
| inspect.BufferFlags                         |        4_624 B |               5_168 B |            89.5% |             4 |              17 |
| ...................total................... | .............. | ..................... | ................ | ............. | ............... |
|                                         148 |      372_928 B |             490_816 B |            76.0% |           829 |           2_848 |

Elapsed time: 8.455s (99.1% in userland). Resident memory: 182232 kB now, 184072 kB at peak. Active threads: 1.
The full results have been saved in /tmp/memory_inventory_python_3.13.3_stdlib/

From Python

memory_inventory can be imported as a Python module, for example to do something that the CLI doesn't support:

import memory_inventory
memory_inventory.main(attributes_to_skip={id(SomeClass.some_attribute)})

History

I started writing the memory_inventory module in 2022 as part of my work on Liberapay. I shelved it because I had too many other things to do, and pretty much forgot about it. When I checked it out and tried to run it again in April 2025, this time with Python 3.12, it triggered a segmentation fault. This pushed me to finish and publish the module.

Funding

memory_inventory wouldn't exist without the more than 1500 donors who support the development of Liberapay. If you're one of them, I thank you. If you aren't, please consider chipping in.

Copyright

Copyright Charly Coste, 2025, licensed under the EUPL.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

memory_inventory-1.1.tar.gz (25.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

memory_inventory-1.1-py3-none-any.whl (18.5 kB view details)

Uploaded Python 3

File details

Details for the file memory_inventory-1.1.tar.gz.

File metadata

  • Download URL: memory_inventory-1.1.tar.gz
  • Upload date:
  • Size: 25.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.3

File hashes

Hashes for memory_inventory-1.1.tar.gz
Algorithm Hash digest
SHA256 88025e66b1e10d20845f485bfb6b120258b15efeb622f7e929f6f862b6d45a3d
MD5 d4398a8fe0c80a9b9d8f4bf0ce49e7b2
BLAKE2b-256 2f896df7ad2445ef2878974e6e55f3f1a0ceb7e2fd2869cb31d5817fece5d726

See more details on using hashes here.

File details

Details for the file memory_inventory-1.1-py3-none-any.whl.

File metadata

  • Download URL: memory_inventory-1.1-py3-none-any.whl
  • Upload date:
  • Size: 18.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.3

File hashes

Hashes for memory_inventory-1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 b9d58c9fb6b1d385adf240b0fa70e1509bc79866d1b7027f9b41f501aab48c0a
MD5 7ed74c4816d96eabf1bc3c1ae8661035
BLAKE2b-256 33249643cdfe41f684d54b7b3498e8384aeb6b213f8a2435a46a523f1531c226

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page