Обнаруживаем утечки памяти в MacOS

Введение

В прошлой статье, я описал как можно профилировать программы на MacOS, но этого бывает недостаточно и нужно понять есть ли в программе или сервисе утечка памяти. Поэтому в этой статье я тезисно хочу показать какие способы есть, чтобы определить их на платформе MacOS. Я нашел 3 способа:

  • утилита Leaks в Instruments
  • консольная утилита leaks
  • Dtarce скрипты

Leaks в Instruments

Это простой визуальный способ через GUI, у которого я сразу наткнулся на то, что не понял как подключаться к уже запущенному процессу (это надо не частно, но бывает).

Для начала использования запускается Instruments.app и выбирается пунк Leaks:

главное окно Instruments

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

Instruments выбор процесса

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

Instruments результат

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 утилиты для определения наличия утечек.

Заключение

В статье я тезисно показал ряд способов поиска утечек, для более углубленного понимания каждый инструмент, конечно же, нужно смотреть отдельно и выбирать тот который больше подходит Вам.

Полезные ссылки

 
comments powered by Disqus