OpenSees Cloud

OpenSees AMI

A Better Way to Find a Memory Leak in OpenSees

Original Post - 27 Dec 2023 - Michael H. Scott

Visit Structural Analysis Is Simple on Substack.


In a previous post, I explained how to find a memory leak in OpenSees. The basic idea was to put the analysis inside a loop, run the loop a million times, and monitor your operating system for increasing memory usage. A perfectly fine leak hunting approach–as long as you are willing to monitor your operating system in real time.

Recently, I found a slightly better approach that will allow you to make plots of memory usage and not run the analysis a million times–probably a few thousand instead.

Some light Googling led me to Stack Exchange (of course), where this post described how to use the psutil Python package to get memory usage statistics. Although I only tried the package with Ubuntu 20.04, the package should also work on Windows and MacOS.

Below is the leaky analysis from the aforementioned previous post.

First, use the os package to get the process ID of your OpenSeesPy script and pass the ID when creating a psutil Process object. You can obtain the memory used by this process by calling the memory_info() method–the 'rss' value (index 0) of the returned tuple gives the physical memory used by the process.

Put the wipe command at the end of the analysis loop, then get the memory information. Plot the memory usage and see if the usage creeps up with the repeated analyses.

import openseespy.opensees as ops
import matplotlib.pyplot as plt
import os
import psutil

pid = os.getpid()
thisprocess = psutil.Process(pid)

Nruns = 2500
mem = [0]*Nruns
MB = 1024**2 # Megabytes

for i in range(Nruns):
    ops.wipe()
    ops.model('basic','-ndm',3,'-ndf',6)

    ops.node(1,0,0,0)
    ops.node(2,1,0,0)
    ops.node(3,1,1,0)
    ops.node(4,0,1,0)

    ops.fix(1,1,1,1,0,0,0)
    ops.fix(2,1,1,1,0,0,0)
    ops.fix(3,1,1,1,0,0,0)
    ops.fix(4,1,1,1,0,0,0)

    # No memory leak
    #ops.nDMaterial('ElasticIsotropic',1,20,0.1)
    # Causes memory leak
    ops.nDMaterial('ElasticOrthotropic',1,20,20,20,0.1,0.1,0.1,10,10,10)

    ops.section('PlateFiber',1,1,0.1)

    ops.element('ShellMITC4',1,1,2,3,4,1)

    ops.timeSeries('Constant',1)
    ops.pattern('Plain',1,1)
    ops.load(1,0,0,0,20,0,0)

    ops.system('UmfPack')
    ops.numberer('RCM')
    ops.constraints('Plain')
    ops.integrator('LoadControl',0)
    ops.algorithm('Newton')

    ops.analysis('Static')
    ops.analyze(1)
    
    ops.wipe()
    if i == 0: # First run
        mem0 = thisprocess.memory_info()[0]
    mem[i] = (thisprocess.memory_info()[0] - mem0)/MB

plt.plot(mem,'-k')

The memory leak for the PlateFiber material wrapper was plugged over a year ago via web edit, so I downgraded OpenSeesPy to version 3.3.0.1 in order to reproduce the leak. Running the above scripts gives the following plots when using the ElasticIsotropic material (no wrapper, no memory leak) and the ElasticOrthotropic material (with wrapper, memory leak).

Memory used over repeated analyses

Note that it takes many analysis runs (nearly 1000 in this case) for the memory leak to overcome the initial memory allocated to this process by the operating system. As the analysis runs continue, chunks of memory are allocated periodically, creating a stepped plot of memory usage as the leak gobbles up the memory chunks.

The plot of memory usage will look different depending on the magnitude of the memory leak, the size of the model, the length of each analysis run, and the operating system.

The nice thing is you can run the script and walk away. When you come back, a plot is waiting for you!