Memory pressure

When faced with the risk of running low on memory, you know that some of your programs’ variables are cache. They can be discarded, dropped on the floor, to be recomputed later.

Problem is, that they need a trigger to do it. Memory pressure management from Satella solves that problem.

satella.instrumentation.memory.get_size(obj, seen=None) → int

Recursively finds the total size of an object (object + it’s components).

Parameters:obj – object to measure
Returns:size in bytes of the object and all of it’s subcomponents
Raises:RuntimeError – when ran on PyPy

Defining severity levels

To define a severity level, use the following classes:

class satella.instrumentation.memory.GlobalAbsoluteValue(value: int)

If free memory globally falls below this many bytes, given severity level starts

class satella.instrumentation.memory.GlobalRelativeValue(value: int)

If percentage of global free memory falls below this much percents, given severity level starts

class satella.instrumentation.memory.LocalAbsoluteValue(value: int)

If free memory falls below this many bytes from what the program can maximally consume this severity level starts

class satella.instrumentation.memory.LocalRelativeValue(value: int)

If percentage of memory available to this process in regards to what the program can maximally consume falls below this level, given severity level starts

Here you can either provide a callable or override the can_fire method

class satella.instrumentation.memory.CustomCondition(callable_: Callable[[], bool])

A custom condition. Condition that is true if attached callable/0 returns True.

Parameters:callable – callable to call upon asking whether this condition is valid. This should be relatively cheap to compute.
can_fire(local_memory_data, local_maximum_consume: Optional[int]) → bool

Has this severity level been reached?

You can combine them with following operators:

class satella.instrumentation.memory.All(*conditions)

This is true if all arguments are True

class satella.instrumentation.memory.Any(*conditions)

This is true if one of the arguments is True

class satella.instrumentation.memory.Not(condition: satella.instrumentation.memory.conditions.BaseCondition)

True only if provided condition is false

Then, you make a list out of them. This list, with indices counted from 1, signals what condition needs to be true for the program to enter given severity level.

Handlers

It is impossible to go from severity level 1 to say 3 without hitting 2. 2 will be hit by the way, the manager will call any handlers that are in the way. Note that severity levels are concurrent - for example, level 1 coexists with level 2, and if level 2 is in effect, that means that level 1 is still in effect. You can register your handlers here:

class satella.instrumentation.memory.MemoryPressureManager(maximum_available: Optional[int] = None, severity_levels: List[satella.instrumentation.memory.conditions.BaseCondition] = None, check_interval: Union[str, int] = 10, log_transitions: bool = True)

Manager of the memory pressure.

The program is in some severity state. The baseline state is 0, meaning everything’s OK.

Please note that it is sufficient to instantiate this class for the thread to run.

Eg.

>>> mt = MemoryPressureManager(maximum_available=4*GB, severity_levels=[GlobalRelativeValue(20),
>>>                            GlobalRelativeValue(10)])
>>> @mt.register_on_severity(1)
>>> def trigger_a():
>>>     print('80% consumption of memory exceeded')
>>> @mt.register_on_severity(2)
>>> def trigger_b():
>>>     print('90% consumption of memory exceeded')

As well, this object is a singleton.

Parameters:
  • maximum_available – maximum amount of memory that this program can use
  • severity_levels – this defines the levels of severity. A level is reached when program’s consumption is other this many percent of it’s maximum_available amount of memory.
  • check_interval – amount of seconds of pause between consecutive checks, or a time string
  • log_transitions – whether to log to logger when a transition takes place
Variables:

severity_level – current severity level (int) 0 means memory is OK, 1 and more means memory is progressively more limited

calculate_severity_level() → int

This returns a severity level. 0 is the baseline severity level.

loop() → None

Override me!

static register_on_entered_severity(severity: int)

Register this handler to fire on entered a particular severity level.

This means that situation has gotten worse.

Use like this:

>>> MemoryPressureManager.register_on_entered_severity(1)
>>> def entered_severity_one():
>>>     print('Entered memory severity level 1')
Parameters:severity – severity level to react to
static register_on_left_severity(severity: int)

Register a handler to be called when given severity level is left. This means that we have advanced to a lower severity level.

>>> MemoryPressureManager.register_on_left_severity(1)
>>> def entered_severity_one():
>>>     print('Memory comsumption no longer 1')
Parameters:severity – severity level to leave
static register_on_memory_normal(fun: Callable) → satella.coding.concurrent.callablegroup.CancellableCallback

Register this handler to fire when memory state falls back to 0.

This will be fired once, once memory state falls back to normal.

Parameters:fun – callable to register
Returns:a CancellableCallback under this callback is registered
static register_on_remaining_in_severity(severity: int, call_no_more_often_than: int = 0)

Register this handler to fire on remaining in a particular severity level. Use like this:

>>> MemoryPressureManager.register_on_remaining_in_severity(0, 30)
>>> def entered_severity_one():
>>>     print('Memory comsumption OK. I am called no more often than each 30 seconds')
Parameters:
  • severity – severity level
  • call_no_more_often_than – call no more often than this amount of seconds
resume()

Resume the operation of this thread

stop()

Stop this thread from operating

install_force_gc_collect

If you want, you can install a GC handler that will force a complete GC collection upon entering given severity level.

satella.instrumentation.memory.install_force_gc_collect(severity_level: int = 1) → None

Install a default first severity level handler that forces a GC collection

Parameters:severity_level – severity level on which to call

Dumping memory information

satella.instrumentation.memory.dump_memory_on(output: TextIO = <_io.TextIOWrapper name='<stderr>' mode='w' encoding='UTF-8'>)

Dump statistics about current Python memory usage to target stream.

Each Python object will be printed, along with a breakdown of most types and their total usage.

Make sure you have enough memory to generate a breakdown. You can preallocate something at the start for example.

Warning

This will return size of 0 on PyPy

Parameters:output – output, default is stderr
satella.instrumentation.memory.install_dump_memory_on(signal_number, output: TextIO = <_io.TextIOWrapper name='<stderr>' mode='w' encoding='UTF-8'>)

Instruct Python to dump all frames onto output, along with their local variables upon receiving given signal

Parameters:
  • signal_number – number of the signal
  • output – output