tangled

Spaghetti code is a pejorative word used by readers that cannot figure out the code structure. The mental image of who does what is just too hard to get. We will see here how you can use Call graphs to deal with the refactoring of entangled spaghetti. (Si, questi sono bucatini…)

Getting started

The call graph is a log of the different routines calling each other. It traces what the reader would look if he were to perform an exhaustive read in the chronological order.

tpical

A call graph generated for a simple computer program in Python.

In python there are several utilities for this task:

All are installable from the PyPI index. To use them you will also need a non-python resource : Graphviz. Read the download section to find your way out.

At CERFACS most of the user will get it on OSX using

brew install graphviz

(Beware, brew can mess up your Python installation by upgrading it). This tutorial will use pycallgraph3, which we install this way:

pip install pycallgraph3

Generating your first graph

We will create a small script to investigate a target main function, for the example run_cli().

What are the inputs needed to create a call graph?

We will use function call_graph_filtered() as a wrapper for pycallgraph3. In the arguments we find the target function cli_run - Note that there is no () since we pass the function, not its result-. The argument custom_include is narrowing the callgraph to a limited set of packages. It is highly recommended to limit the depth of the packages you want to trace. Indeed, without any limitation, python callgraph can become extremely large.

#!/usr/bin/env python
(...)
from oms.plan40.run_plan40 import cli_run

call_graph_filtered(
    cli_run,
    custom_include=["oms.*", "pyavbp.*"],# "kokiy.*", "ms_thermo.*", "arnica.*"],
)
Creating the wrapper call_graph_filtered()

The wrapper function call_graph_filtered() is defined as the following.

from pycallgraph3 import PyCallGraph
from pycallgraph3 import Config
from pycallgraph3 import GlobbingFilter
from pycallgraph3.output import GraphvizOutput

def call_graph_filtered(
        function_, 
        output_png="call_graph_png",
        custom_include=None

    ):
    """A call graph generator filtered"""
    config = Config()
    config.trace_filter = GlobbingFilter(
        include=custom_include)
    graphviz = GraphvizOutput(output_file=output_png)

    with PyCallGraph(output=graphviz, config=config):
            function_()

output

The output here is rather wide due to the structure of this specific cli_run, but this is a real-life example. The call graph shows clearly oms, the package of the function, heavily relying on a second package pyavbp. You can get deeper graphs by including more packages, or investigate only one portion of the graph.

Where are the spaghetti?

The spaghetti can be visible from the call graph. The following picture focuses on an earlier version of the code. You can spot 6-8 methods are calling each other, and it is hard to figure out who called who -which is a code smell to me- . This is where we had to refactor things a lot.

spaghettihere

So cool that I want to print it! But how?

Among the many ways to print a large image, this is a low tech version:

  1. Open the image in an Excel spreadsheet
  2. Zoom out to see the printing zones lines
  3. paste your image into Excel
  4. resize the image to fit on the printing grid
  5. preview then print (BW, single page)

Like this post? Share on: TwitterFacebookEmail


Antoine Dauptain is a research scientist focused on computer science and engineering topics for HPC.

Keep Reading


Published

Category

Pitch

Tags

Stay in Touch