OpenSees Cloud

OpenSees AMI

Runnin' Down a Leak

Original Post - 04 Jun 2023 - Michael H. Scott

Visit Structural Analysis Is Simple on Substack.


Issue #1214 by zAlexliu-8895 on OpenSees GitHub demonstrated a memory leak with creating patches for fiber sections. The script posted with the GitHub issue is reproduced below.

import openseespy.opensees as ops
 
Counter = 0
while Counter < 100000000:
    Counter += 1
 
    ops.wipe()
    ops.model('basic', '-ndm', 2, '-ndf', 3)
     
    ops.uniaxialMaterial("Concrete02", 1, -33, -0.0015, -20, -0.005, 0.1, 2.2, 1100)
 
    ops.section("Fiber", 1, "-GJ", 200e9)
    ops.patch("quad", 1, 40, 10, -200, -38, 200, -38, 200, 62, -200, 62)

Sure enough, if you run this simple script, you’ll see the available system memory go to zero, throwing your device into a tizzy.

Thank you zAlexLiu-8895 for making a minimal leaking example that clearly illustrates the issue!

In running this leak down, I asked myself three questions.

1. Does The Leak Happen in Tcl?

Since the script is short and simple, a Tcl version was easy to create.

set counter 0
while {$counter < 100000000} {
    incr counter
 
    wipe
    model basic -ndm 2 -ndf 3
 
    uniaxialMaterial Concrete02 1 -33 -0.0015 -20 -0.005 0.1 2.2 1100
 
    section Fiber 1 -GJ 200e9 {
        patch quad 1 40 10 -200 -38 200 -38 200 62 -200 62
    }
}

No, no memory leak with Tcl. So the issue must be in parsing the Python commands, not in the constructors and destructors of the C++ classes.

2. Is the Leak Due to the Material?

Back to the original Python script for a quick check to see if the issue is somehow related to parsing the Concrete02 command. After changing the material to Elastic, there’s still a memory leak. So, the problem is definitely in parsing the section and patch commands for OpenSeesPy.

3. What Does valgrind Have to Say?

Rather than stare at C++ code and try to match every new with a delete, I usually turn to valgrind, a Linux-based tool for detecting memory management issues. You can use valgrind with the OpenSeesPy pip package or with a locally compiled OpenSeesPy library.

OpenSeesPy pip Package

Run valgrind with the OpenSeesPy package to get basic information on a leak. First, decrease the counter limit from 100000000 down to like 100, otherwise the script will take a while to run through valgrind–and, more importantly, you don’t want to run out of memory while valgrinding.

import openseespy.opensees as ops
 
Counter = 0
while Counter < 100:
    Counter += 1
 
    ops.wipe()
    ops.model('basic', '-ndm', 2, '-ndf', 3)
     
    ops.uniaxialMaterial("Concrete02", 1, -33, -0.0015, -20, -0.005, 0.1, 2.2, 1100)
 
    ops.section("Fiber", 1, "-GJ", 200e9)
    ops.patch("quad", 1, 40, 10, -200, -38, 200, -38, 200, 62, -200, 62)

Next, run valgrind from the command line, passing flags to check for and show all leaks and to dump the results to a log file.

~/$ valgrind --leak-check=full --show-leak-kinds=all --log-file=leaks.txt python3 fiberSection.py

When you open leaks.txt, you’ll see there’s a lot going on. But search for the word “definitely” and you’ll find information on the memory leak.

Valgrind output using OpenSeesPy pip package

The log file says we’re definitely leaking memory in OPS_Patch(), from somewhere in the opensees.so library of the OpenSeesPy package installation. For this simple model, that’s probably enough information to track down the issue.

But if the leak is not so obvious and/or you want to get more detailed information out of valgrind, use a compiled version of the opensees.so library.

OpenSeesPy Library Compiled in Debug Mode

To take full advantage of valgrind, you need to recompile OpenSees in DEBUG mode. In DEBUG mode, lots of hooks, symbols, and assertions are added to the compiled OpenSees object files, making it easy for valgrind and other debugging tools to figure out what’s going on and to relay that information on to you.

Due to all the added stuff, OpenSees runs slowly in DEBUG mode. So, be sure to get out of DEBUG mode after you’ve finished debugging. More on that later.

First, in OpenSees/Makefile.def, set the DEBUG_MODE variable to DEBUG–or simply comment the NO_DEBUG line.

DEBUG_MODE = DEBUG
#DEBUG_MODE = NO_DEBUG

Next, wipe your current build of opensees.so and re-build everything in DEBUG mode.

~/OpenSees$ make wipe
~/OpenSees$ make python -j 4

The re-build will take a few minutes. When the job is done, change the import statement from the OpenSeesPy package to the opensees.so Python library that was just built.

import sys
sys.path.append('/home/mhscott/OpenSees/SRC/interpreter')
import opensees as ops
#import openseespy.opensees as ops
 
Counter = 0
while Counter < 100:
    Counter += 1
 
    ops.wipe()
    ops.model('basic', '-ndm', 2, '-ndf', 3)
     
    ops.uniaxialMaterial("Concrete02", 1, -33, -0.0015, -20, -0.005, 0.1, 2.2, 1100)
 
    ops.section("Fiber", 1, "-GJ", 200e9)
    ops.patch("quad", 1, 40, 10, -200, -38, 200, -38, 200, 62, -200, 62)

Now, run valgrind with the same options as before.

~/$ valgrind --leak-check=full --show-leak-kinds=all --log-file=leaks.txt python3 fiberSection.py

With this execution of valgrind, the leaks.txt file will show better information.

Valgrind output using local OpenSeesPy library

The valgrind log file now says the leak in OPS_Patch() emanates from line 535 of OpenSeesSectionCommands.cpp. The problem is we’re not deleting the Fiber object that was allocated on this line of code.

The fix was easy, and you can see details in PR #1215.

Now we can confirm that the leak is plugged.

Compile OpenSees with the updated code, still in DEBUG mode (no need to first make wipe this time), then run valgrind again. Open the log file and search for “definitely”–you’ll see there were no leaks.

Valgrind output with leak fixed

Yay! Leak plugged.

Now, so that you don’t keep running OpenSees in sluggish DEBUG mode, uncomment the NO_DEBUG line in Makefile.def.

DEBUG_MODE = DEBUG
DEBUG_MODE = NO_DEBUG

Then rebuild from zero again, this time removing all the debugging symbols from the object files.

~/OpenSees$ make wipe
~/OpenSees$ make python -j 4

Now you’re good to go.

Note that other types of leaks will require asking different questions. But whatever type of leak it is, using valgrind will be incredibly helpful. In fact, use valgrind every now and then on your IDA and Monte Carlo scripts–some leaks are likely to appear. Let me know what you find.



Before you run down the next leak, enjoy some Tom Petty.

Tom Petty, Runnin' Down a Dream