Введение
В прошлой статье, я описал как можно профилировать программы на MacOS, но этого бывает недостаточно и нужно понять есть ли в программе или сервисе утечка памяти. Поэтому в этой статье я тезисно хочу показать какие способы есть, чтобы определить их на платформе MacOS. Я нашел 3 способа:
- утилита Leaks в Instruments
- консольная утилита
leaks
- Dtarce скрипты
Leaks в Instruments
Это простой визуальный способ через GUI, у которого я сразу наткнулся на то, что не понял как подключаться к уже запущенному процессу (это надо не частно, но бывает).
Для начала использования запускается Instruments.app и выбирается пунк Leaks:

Выбираем приложение, которое нас интересует и запускаем процесс записи:

В результате увидим сколько памяти выделяется/освобождается приложением:

Leaks
Как я сказал, у способа выше я столкнулся с некоторыми ограничениями, поэтоиму пошел искать дальше и нашел утилиту leaks, которая встречается и в официальной документации. В отличии от предыдущего способа, с ее помощью можно подключаться к уже запущенному процессу.
Чтобы можно было посмотреть стек трейс утечек, необходимо запустить отлаживаемый процесс с переменной окружения MallocStackLogging=true
.
Затем выбрать pid процесса и подключить к нему leaks
:
leaks -quiet -groupByType 17120
После остановки будет выдан отчет следующего вида:
leaks Report Version: 4.0, multi-line stacks
Process 17120: 18637 nodes malloced for 87445 KB
Process 17120: 2 leaks for 1048576 total leaked bytes.
STACK OF 2 INSTANCES OF 'ROOT LEAK: malloc in signal_stack_init':
6 libsystem_pthread.dylib 0x7ff8045b1ae3 thread_start + 15
5 libsystem_pthread.dylib 0x7ff8045b618b _pthread_start + 99
4 app 0x109840156 etp_proc + 134 etp.c:333
3 app 0x10945d652 fn_on_start + 114 coio_task.c:116
2 app 0x109446ee6 task_create + 1046 fiber.c:1840
1 app 0x10944764e signal_stack_init + 46 fiber.c:231
0 libsystem_malloc.dylib 0x7ff8043fd7ea _malloc_zone_malloc_instrumented_or_legacy + 297
====
2 (1.00M) ROOT LEAK: malloc in signal_stack_init
STACK OF 2 INSTANCES OF 'ROOT LEAK: malloc in signal_stack_init':
6 libsystem_pthread.dylib 0x7ff8045b1ae3 thread_start + 15
5 libsystem_pthread.dylib 0x7ff8045b618b _pthread_start + 99
4 app 0x109840156 etp_proc + 134 etp.c:333
3 app 0x10945d652 fn_on_start + 114 coio_task.c:116
2 app 0x109446ee6 task_create + 1046 fiber.c:1840
1 app 0x10944764e signal_stack_init + 46 fiber.c:231
0 libsystem_malloc.dylib 0x7ff8043fd7ea _malloc_zone_malloc_instrumented_or_legacy + 297
====
1 (512K) ROOT LEAK: malloc in signal_stack_init
В отчете виден стек трейс вызовов, количество утечек и их размер.
DTrace
Этот способ описан у Брендана Грегга в его блоге. Кромет того я частично затрагивал dtrace
в статье о профилировании. В кратце суть метода проста, с помощью dtarce посмотреть все вызовы функций malloc, realloc, calloc, free с помощью скрипта
#!/usr/sbin/dtrace -s
/*
* Thanks to :
* # http://www.brendangregg.com/Solaris/memoryflamegraphs.html
* # http://ewaldertl.blogspot.com/2010/09/debugging-memory-leaks-with-dtrace-and.html
*
* Dtrace script that logs all
* malloc, calloc, realloc and free calls and their call stacks
*
* The output of the script is further processed as described in
* https://github.com/ppissias/DTLeakAnalyzer
*
* Adapt the aggsize, aggsize and bufsize parameters accordingly if needed.
* Author Petros Pissias
*/
#pragma D option quiet
#pragma D option aggrate=100us
#pragma D option bufpolicy=fill
#pragma D option bufsize=100m
#!/usr/sbin/dtrace -s
pid$1::malloc:entry
{
self->trace = 1;
self->size = arg0;
}
pid$1::malloc:return
/self->trace == 1/
{
/* log the memory allocation */
printf("<__%i;%Y;%d;malloc;0x%x;%d;\n", i++, walltimestamp, tid, arg1, self->size);
ustack(50);
printf("__>\n\n");
self->trace = 0;
self->size = 0;
}
pid$1::realloc:entry
{
self->trace = 1;
self->size = arg1;
self->oldptr = arg0;
}
pid$1::realloc:return
/self->trace == 1/
{
/* log the memory re-allocation. Log the old memory address and the new memory address */
printf("<__%i;%Y;%d;realloc;0x%x;0x%x;%d;\n", i++, walltimestamp, tid, self->oldptr, arg1, self->size);
ustack(50);
printf("__>\n\n");
self->trace = 0;
self->size = 0;
self->oldptr = 0;
}
pid$1::calloc:entry
{
self->trace = 1;
self->size = arg1;
self->nelements = arg0;
}
pid$1::calloc:return
/self->trace == 1/
{
/* log the memory allocation with the total size*/
printf("<__%i;%Y;%d;calloc;0x%x;%d;\n", i++, walltimestamp, tid, arg1, self->size*self->nelements);
ustack(50);
printf("__>\n\n");
self->trace = 0;
self->size = 0;
self->nelements = 0;
}
pid$1::free:entry
{
printf("<__%i;%Y;%d;free;0x%x;\n", i++, walltimestamp, tid, arg0);
ustack(50);
printf("__>\n\n");
}
END
{
printf("== FINISHED ==\n\n");
}
И далее обработать его результаты, например с помощью perl скрипта или java утилиты для определения наличия утечек.
Заключение
В статье я тезисно показал ряд способов поиска утечек, для более углубленного понимания каждый инструмент, конечно же, нужно смотреть отдельно и выбирать тот который больше подходит Вам.