Estudando o motor VoIP Mediastreamer2. Parte 12

O conteúdo deste artigo foi retirado do meu canal Zen.

Estudando o motor VoIP Mediastreamer2. Parte 12

Na parte anterior artigo, eu prometi considerar a questão da avaliação da carga no ticker e as formas de lidar com a carga computacional excessiva no media streamer. Mas decidi que seria mais lógico abordar as questões de depuração de filtros personalizados, relacionadas ao fluxo de dados, e só depois discutir a otimização de desempenho.

Depuração de filtros personalizados

Depois que discutimos o mecanismo de movimentação de dados no media streamer no artigo anterior, seria lógico falar sobre os perigos que ele esconde. Uma das características do princípio de "fluxo de dados" é que a alocação de memória da heap ocorre nos filtros localizados no início do fluxo de dados, enquanto a liberação de memória, retornando para a heap, ocorre nos filtros localizados no final do caminho do fluxo. Além disso, a criação de novos dados e sua destruição pode ocorrer em pontos intermediários. Em geral, a liberação de memória não é realizada pelo mesmo filtro que criou o bloco de dados.

Do ponto de vista de um monitoramento transparente da memória, seria razoável que o filtro, ao receber um bloco de entrada, após o processamento, destruísse imediatamente esse bloco liberando a memória, enquanto na saída apresentava um novo bloco criado com os dados de saída. Nesse caso, um vazamento de memória no filtro poderia ser facilmente rastreado — se o analisador detectasse um vazamento no filtro, significaria que o filtro seguinte não está destruindo os blocos de entrada de forma adequada e a falha estaria nele. Mas, do ponto de vista da manutenção de um alto desempenho, essa abordagem para trabalhar com blocos de dados não é produtiva — ela leva a um grande número de operações de alocação/liberação de memória para blocos de dados sem qualquer saída útil.

Por esse motivo, os filtros do mediastreamer, para não retardar o processamento de dados, ao copiar mensagens utilizam funções que criam cópias leves (falamos sobre elas no artigo anterior). Essas funções apenas criam uma nova instância do cabeçalho da mensagem "anexando" a ele um bloco de dados da mensagem "antiga" que está sendo copiada. Como resultado, dois cabeçalhos ficam vinculados a um único bloco de dados e o contador de referências no bloco de dados é incrementado. No entanto, isso parecerá como duas mensagens. Pode haver mais mensagens com esse bloco de dados "compartilhado", por exemplo, o filtro MS_TEE gera imediatamente uma dúzia dessas cópias leves, distribuindo-as por suas saídas. Com o funcionamento correto de todos os filtros na cadeia, ao final do pipeline esse contador de referências deve atingir zero e será chamada a função de liberação de memória: ms_free(). Se não ocorrer a chamada, significa que esse pedaço de memória já não retornará ao heap, ou seja, "vazará". O preço pelo uso de cópias leves é a perda da capacidade de identificar facilmente (como seria no caso de cópias normais) em qual filtro a memória está vazando.

Como a responsabilidade pela detecção de vazamentos de memória nos filtros "nativos" recai sobre os desenvolvedores do mediastreamer, é provável que você não precise depurá-los. Mas com seu filtro artesanal — você é o responsável pelo seu próprio sucesso e sua precisão determinará o tempo que você passará em busca de vazamentos em seu código. Para reduzir seu tempo de perrengues com a depuração, devemos considerar técnicas para localizar vazamentos durante o desenvolvimento de filtros. Ademais, pode acontecer que o vazamento se manifeste apenas ao aplicar o filtro em um sistema real, onde a quantidade de "suspeitos" pode ser enorme e o tempo para depuração limitado.

Como se manifesta um vazamento de memória?

É lógico supor que na saída do programa top um percentual crescente de memória ocupado pela sua aplicação será exibido.

A manifestação externa será que, em algum momento, o sistema começará a responder lentamente ao movimento do mouse, a tela será redesenhada de forma lenta. Também poderá haver um aumento no log do sistema, ocupando espaço no disco rígido. Nesse momento, seu aplicativo começará a se comportar de maneira estranha, não respondendo a comandos, não conseguindo abrir arquivos, etc.

Para identificar a ocorrência de um vazamento, usaremos um analisador de memória (daqui em diante denominado analisador). Isso pode ser Valgrind (bom artigo sobre ele) ou um analisador embutido no compilador gcc MemorySanitizer ou algo similar. Se o analisador indicar que o vazamento ocorre em um dos filtros do gráfico, isso significa que é hora de aplicar um dos métodos descritos abaixo.

Método das Três Pinheiros

Como já mencionado, em caso de vazamento de memória, o analisador apontará para o filtro que solicitou a alocação de memória na pilha. Mas não indicará o filtro que "esqueceu" de devolver a memória, que é, na verdade, o culpado. Dessa forma, o analisador pode apenas confirmar nossas suspeitas, mas não indicar sua raiz.

Para determinar a localização do filtro "problemático" no gráfico, pode-se simplificar o gráfico para o número mínimo de nós, onde o analisador ainda detecta o vazamento e, com os três nós restantes, localizar o filtro problemático.

Mas pode acontecer que, ao reduzir o número de filtros no gráfico, você interrompa o fluxo normal de interação dos filtros com outros elementos do seu sistema e o vazamento deixe de se manifestar. Nesse caso, será necessário trabalhar com o gráfico completo e usar a abordagem descrita abaixo.

Método do Isolador Deslizante

Para simplificar a explicação, utilizaremos um gráfico que consiste em uma única cadeia de filtros. Ele é representado na figura.

Estudando o motor VoIP Mediastreamer2. Parte 12

Um gráfico comum, no qual, junto com os filtros prontos do media streamer, são aplicados quatro filtros artesanais F1…F4, de quatro tipos diferentes, que você fez há muito tempo e cuja correção você não duvida. No entanto, vamos supor que alguns deles tenham um vazamento de memória. Ao executar nosso programa de supervisão do analisador, podemos descobrir em seu relatório que um certo filtro solicitou uma quantidade de memória e não a devolveu para o heap um número N de vezes. É fácil adivinhar que haverá uma referência às funções internas do filtro do tipo MS_VOID_SOURCE. A tarefa dele é pegar a memória do heap. Outros filtros devem devolvê-la. Ou seja, vamos descobrir o fato do vazamento.

Para determinar em qual parte da cadeia ocorreu a inatividade que levou ao vazamento de memória, propõe-se introduzir um filtro adicional que simplesmente transfere as mensagens da entrada para a saída, mas cria uma cópia "pesada" da mensagem de entrada antes de apagar completamente a mensagem que chegou à entrada. Vamos chamar esse filtro de isolador. Consideramos que, uma vez que o filtro é simples, o vazamento nele está excluído. E mais uma característica positiva - se o adicionarmos em qualquer lugar do nosso gráfico, isso não afetará o funcionamento do esquema. Representaremos o filtro-isolador como um círculo com um contorno duplo.

Ligamos o isolador logo após o filtro voidsource:
Estudando o motor VoIP Mediastreamer2. Parte 12

Executamos novamente o programa com o analisador e vemos que, desta vez, o analisador atribui a culpa ao isolador. Afinal, é ele quem agora cria blocos de dados que depois se perdem por um filtro (ou filtros) descuidado(s) desconhecido(s). O próximo passo é mover o isolador pela cadeia para a direita, um filtro, e reiniciar a análise. Assim, passo a passo, movendo o isolador para a direita, obteremos uma situação em que no próximo relatório do analisador a quantidade de blocos de memória "vazados" diminui. Isso significa que, neste passo, o isolador se posicionou na cadeia logo após o filtro problemático. Se o "filtro ruim" fosse o único, o vazamento desapareceria completamente. Assim, localizamos o filtro problemático (ou um dos vários). "Consertando" o filtro, podemos continuar movendo o isolador para a direita na cadeia até derrotar completamente os vazamentos de memória.

Implementação do filtro-isolador

A implementação do isolador parece um filtro comum. Cabeçalho:

/* Файл 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

O filtro:

/* Файл 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)

Método de substituição das funções de gerenciamento de memória

Para investigações mais detalhadas, o mediastreamer oferece a possibilidade de substituir funções de acesso à memória pelas suas próprias, que, além da função principal, registrarão "Quem, para onde e por quê". Três funções são substituídas. Isso é feito da seguinte maneira:

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);

Essa possibilidade é útil em casos em que o analisador desacelera o funcionamento dos filtros ao ponto de prejudicar o sistema em que nossa configuração está integrada. Nessa situação, é necessário abrir mão do analisador e utilizar a substituição das funções de gerenciamento de memória.

Consideramos o algoritmo de ações para um grafo simples, sem ramificações. Mas essa abordagem também pode ser aplicada a outros casos, claro que com complexidade, mas a ideia permanecerá a mesma.

No próximo artigo, discutiremos a questão da avaliação da carga sobre o ticker e as maneiras de combater a carga computacional excessiva no mediastreamer.

Fonte: habr.com

Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS 🔥 Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS | ProHoster