Exploring the Mediastreamer2 VoIP engine. Part 12

The material of the article is taken from my zen channel.

Exploring the Mediastreamer2 VoIP engine. Part 12

In the past article, I promised to consider the issue of ticker load estimation and ways to deal with excessive computing load in the media streamer. But I decided that it would be more logical to cover the issues of debugging craft filters related to data movement and only then consider performance optimization issues.

Debugging craft filters

After we examined the mechanism of data movement in a media streamer in the previous article, it would be logical to talk about the dangers hidden in it. One of the features of the "data flow" principle is that the allocation of memory from the heap occurs in the filters located at the sources of the data flow, and the filters located at the end of the flow path already deallocate the memory with return to the heap. In addition, the creation of new data and their destruction can occur somewhere in the intermediate points. In general, the release of memory is performed by a filter other than the one that created the data block.

From the point of view of transparent monitoring of memory, it would be reasonable for the filter, when receiving an input block, to immediately destroy it after processing, freeing memory, and put a newly created block with output data on the output. In this case, the memory leak in the filter could be easily traced β€” if the analyzer detected a leak in the filter, then the filter following it does not properly destroy incoming blocks and there is an error in it. But from the point of view of maintaining high performance, this approach to working with data blocks is not productive - it leads to a large number of operations to allocate / free memory for data blocks without any useful exhaust.

For this reason, media streamer filters, in order not to slow down data processing, when copying messages use functions that create light copies (we talked about them in a previous article). These functions only create a new instance of the message header by "attaching" the data block from the copied "old" message to it. As a result, two headers are attached to one data block and the reference counter in the data block is incremented. But it will look like two messages. There can be more messages with such a "shared" data block, for example, the MS_TEE filter generates ten such light copies at once, distributing them among its outputs. If all filters in the chain work correctly, by the end of the pipeline this reference count should reach zero and the memory deallocation function will be called: ms_free(). If the call does not occur, then this piece of memory will no longer be returned to the heap, i.e. he "leaks". The cost of using light copies is the loss of the ability to easily determine (as it would be in the case of using regular copies) in which graph filter the memory is leaking.

Since the responsibility for finding memory leaks in the "native" filters lies with the developers of the media streamer, then most likely you will not have to debug them. But with your crafting filter, you yourself are the grasshopper of your own happiness, and the time you spend searching for leaks in your code will depend on your accuracy. To cut down on your debugging time, we need to look at leak localization techniques when designing filters. In addition, it may happen that the leak will manifest itself only when applying the filter in a real system, where the number of "suspects" can be huge, and the time for debugging is limited.

How does a memory leak manifest itself?

It is logical to assume that in the output of the program top will show an increasing percentage of memory occupied by your application.

The external manifestation will consist in the fact that at some point the system will react slowly to the movement of the mouse, slowly redrawing the screen. It is also possible that the system log will grow, eating up space on the hard drive. In this case, your application will begin to behave strangely, not responding to commands, cannot open the file, etc.

To identify the fact of a leak, we will use a memory analyzer (hereinafter referred to as the analyzer). It could be valgrind (good article about it) or built into the compiler gcc MemorySanitizer or something else. If the analyzer shows that the leak occurs in one of the graph filters, then it means that it is time to apply one of the methods described below.

Three Pines Method

As mentioned above, in case of a memory leak, the analyzer will point to the filter that requested memory allocation from the heap. But it will not point to the filter that "forgot" to return it, which, in fact, is to blame. Thus, the analyzer can only confirm our fears, but not point to their root.

To find out the location of the "bad" filter in the graph, you can go by reducing the graph to the minimum number of nodes at which the analyzer still detects a leak and locate the problematic filter in the remaining three pines.

But it may happen that by reducing the number of filters in the column you will disrupt the normal course of interaction between filters and other elements of your system and the leak will no longer appear. In this case, you will have to work with a full-size graph and use the approach described below.

Sliding insulator method

For simplicity of presentation, we will use a graph that consists of a single chain of filters. She is shown in the picture.

Exploring the Mediastreamer2 VoIP engine. Part 12

An ordinary graph, in which, along with ready-made media streamer filters, four craft filters F1…F4 are used, four different types that you made a long time ago and have no doubt about their correctness. However, suppose that several of them have a memory leak. When running our analyzer supervision program, we learn from its report that a certain filter requested a certain amount of memory and did not return it to the heap N times. It is easy to guess that there will be a reference to the internal filter functions of the MS_VOID_SOURCE type. His task is to take memory from the heap. Other filters should return it there. Those. we'll find the leak.

In order to determine at which section of the pipeline inactivity occurred that led to a memory leak, it is proposed to introduce an additional filter that simply shifts messages from the input to the output, but at the same time creates a non-light, normal "heavy" copy of the input message, then completely deleting the message that arrived at the output. entrance. We will call such a filter an insulator. We believe that since the filter is simple, leakage in it is excluded. And one more positive property - if we add it to any place in our graph, then this will not affect the operation of the circuit in any way. We will depict the insulator filter as a circle with a double contour.

Enable the isolator right after the voidsourse filter:
Exploring the Mediastreamer2 VoIP engine. Part 12

We run the program with the analyzer again, and we see that this time, the analyzer will put the blame on the isolator. After all, it is he who now creates blocks of data, which are then lost by an unknown negligent filter (or filters). The next step is to shift the insulator along the chain to the right, by one filter, and start the analysis again. So, step by step, moving the isolator to the right, we get a situation when the number of "leaked" memory blocks in the next report of the analyzer decreases. This means that at this step the insulator ended up in the chain immediately after the problematic filter. If there was only one "bad" filter, then the leak will disappear altogether. Thus, we localized the problematic filter (or one of several). Having "fixed" the filter, we can continue to move the isolator to the right along the chain until the memory leaks are completely eliminated.

Implementing an isolator filter

The isolator implementation looks just like a normal filter. Header file:

/* Π€Π°ΠΉΠ» iso_filter.h  ОписаниС ΠΈΠ·ΠΎΠ»ΠΈΡ€ΡƒΡŽΡ‰Π΅Π³ΠΎ Ρ„ΠΈΠ»ΡŒΡ‚Ρ€Π°. */

#ifndef iso_filter_h
#define iso_filter_h

/* Π—Π°Π΄Π°Π΅ΠΌ ΠΈΠ΄Π΅Π½Ρ‚ΠΈΡ„ΠΈΠΊΠ°Ρ‚ΠΎΡ€ Ρ„ΠΈΠ»ΡŒΡ‚Ρ€Π°. */
#include <mediastreamer2/msfilter.h>

#define MY_ISO_FILTER_ID 1024

extern MSFilterDesc iso_filter_desc;

#endif

The filter itself:

/* Π€Π°ΠΉΠ» iso_filter.c  ОписаниС ΠΈΠ·ΠΎΠ»ΠΈΡ€ΡƒΡŽΡ‰Π΅Π³ΠΎ Ρ„ΠΈΠ»ΡŒΡ‚Ρ€Π°. */

#include "iso_filter.h"

    static void
iso_init (MSFilter * f)
{
}
    static void
iso_uninit (MSFilter * f)
{
}

    static void
iso_process (MSFilter * f)
{
    mblk_t *im;

    while ((im = ms_queue_get (f->inputs[0])) != NULL)
    {
        ms_queue_put (f->outputs[0], copymsg (im));
        freemsg (im);
    }
}

static MSFilterMethod iso_methods[] = {
    {0, NULL}
};

MSFilterDesc iso_filter_desc = {
    MY_ISO_FILTER_ID,
    "iso_filter",
    "A filter that reads from input and copy to its output.",
    MS_FILTER_OTHER,
    NULL,
    1,
    1,
    iso_init,
    NULL,
    iso_process,
    NULL,
    iso_uninit,
    iso_methods
};

MS_FILTER_DESC_EXPORT (iso_desc)

Method of replacing memory management functions

For more subtle research, the media streamer provides the ability to replace the memory access functions with your own, which, in addition to the main work, will fix "Who, where and why." Three functions are being replaced. This is done in the following way:

OrtpMemoryFunctions reserv;
OrtpMemoryFunctions my;

reserv.malloc_fun = ortp_malloc;
reserv.realloc_fun = ortp_realloc;
reserv.free_fun = ortp_free;

my.malloc_fun = &my_malloc;
my.realloc_fun = &my_realloc;
my.free_fun = &my_free;

ortp_set_memory_functions(&my);

This feature comes to the rescue in cases when the analyzer slows down the filters so much that the operation of the system in which our circuit is built is disrupted. In such a situation, you have to abandon the analyzer and use the substitution of memory functions.

We have considered an algorithm of actions for a simple graph that does not contain branches. But this approach can be applied to other cases, of course with complication, but the idea remains the same.

In the next article, we will look at the issue of ticker load estimation and how to deal with excessive computing load in the media streamer.

Source: habr.com

Add a comment