Профилирование памяти в Python
Если вам нужно отладить ошибку в коде, это может оказаться сложной или увлекательной задачей. Выяснить, почему ваша программа/скрипт/инструмент потребляет такой объем памяти, может быть сложно. При анализе нескольких сетевых захватов я столкнулся с несколькими вариантами, работающими по-разному. Я хотел выяснить несколько различий между функциями. Здесь я делюсь несколькими тестами, которые я сделал, и рассказываю о том, как легко выполнить профилирование памяти в Python.
Написание инструмента для разбора сетевых захватов на Python с помощью Scapy Я наткнулся на несколько альтернатив. Имея дело с проблемой производительности для больших файлов, я решил сравнить несколько альтернатив. Это связано с тем, как работают rdpcap()
и PcapReader()
.
Некоторые различия были отмечены в предыдущем посте. В поисках разных альтернатив я нашел довольно крутую ветку в StackOverflow с несколькими вариантами, которые можно попробовать. Я выбрал Memory Profiler, в нем есть ключ, который мне нужен.
- Легко интегрируется
- Легко запустить
- Сюжеты
Учебник в рамках проекта GitHub довольно хорош, и функциональность была довольно удобной для того, что мне нужно было сделать. Для меня это было так же просто, как добавить декоратор @profile
в функции скриптов (здесь и здесь).
Код выглядит так:
Memory Profiler можно запустить как модуль Python или с помощью команды mprof
, и он сгенерирует файл со всеми временными метками. Это довольно удобно и просто в исполнении.
Сначала нам нужно выполнить mprof run <script>
, чтобы сгенерировать файл со всеми временными метками, а затем мы можем построить это с помощью mprof plot
.
Запустить этот инструмент с файлом Pcap, который я скачал из Анализ трафика вредоносного ПО, было так просто:
mprof run -o sample_run_rdpcap.dat pcap_parsing_rdpcap.py --pcap pcaps/2021-06-04-part-02-after-reboot-Qakbot-with-Cobalt-Strike-and-spambot-activity.pcap --output urls_testing_rdpcap.txt mprof: Sampling memory every 0.1s running new process running as a Python program... Filename: pcap_parsing_rdpcap.py Line # Mem usage Increment Occurences Line Contents ============================================================ 21 100.0 MiB 100.0 MiB 1 @profile 22 def parse_pcap(pcap_path, urls_file): 23 441.6 MiB 341.6 MiB 1 pcap_flow = rdpcap(pcap_path) 24 442.7 MiB 1.2 MiB 1 sessions = pcap_flow.sessions() 25 442.7 MiB 0.0 MiB 1 urls_output = open(urls_file, "wb") 26 442.7 MiB 0.0 MiB 1775 for session in sessions: 27 442.7 MiB 0.0 MiB 49194 for packet in sessions[session]: 28 442.7 MiB 0.0 MiB 47420 try: 29 442.7 MiB 0.0 MiB 47420 if packet[TCP].dport == 80: 30 payload = bytes(packet[TCP].payload) 31 url = get_url_from_payload(payload) 32 urls_output.write(url.encode()) 33 442.7 MiB 0.0 MiB 1300 except Exception as e: 34 442.7 MiB 0.0 MiB 1300 pass 35 442.7 MiB 0.0 MiB 1 urls_output.close()
Файл sample_run_rdpcap.dat
содержит временные метки и данные о потреблении памяти, измеряемые каждую секунду. Теперь мы можем построить это и сохранить в изображение:
Теперь у нас есть хорошее представление о том, сколько памяти может занять rdpcap
при чтении файла. В данном конкретном случае файл pcap имел размер 28 МБ. После загрузки в память он потреблял около 450 МБ, а скрипт выполнялся более 200 секунд.
С PcapReader()
мы получаем другие результаты, с меньшим потреблением памяти и значительно меньшим временем выполнения скрипта:
Наконец, мы можем сравнить оба выполнения и отобразить их на одном графике, что является хорошим способом сравнить поведение сценариев и попытаться решить, какой из них более эффективен для той цели, которую я преследовал:
Различия между сценариями говорят сами за себя. Например, rdpcap
загружает и анализирует весь файл в памяти, что дает больше информации и возможностей во время чтения сетевого захвата. Тем не менее, pcapreader
читает файл последовательно, что быстрее, но менее гибко.
На графике для rdpcap
видно, что потребление памяти увеличивается, а затем выходит на плато. Как только весь файл будет прочитан, именно тогда он будет обрабатывать пакеты.
Из любопытства я запустил сценарии для нескольких файлов pcap и попытался посмотреть, как их можно идентифицировать. График выглядит следующим образом:
Все образцы, проанализированные с помощью rdpcap
, имеют разное потребление памяти и имеют наклон в начале, когда он загружает файл. PcapReader
пиковое потребление памяти остается постоянным и составляет около 100 МБ.
В чем здесь смысл?
Тестируйте, тестируйте и тестируйте. Иногда полезно протестировать, исследовать и выяснить, как лучше всего написать фрагмент кода для инструмента или цели. Я никогда раньше не пробовал профилирование памятив Python, и это только для поверхностного изучения.
Тем не менее, иметь возможность протестировать, сравнить и решить, какой подход лучше всего подходит для решения проблемы, это ключевой момент при попытке понять, как что-то работает. Я еще не закончил со скриптом/инструментом, который я создаю, и у меня определенно есть еще несколько вопросов о профилировании в python.
Этот простой способ протестировать вещи может быть полезен кому-то другому. Есть еще несколько трюков, которые могут быть реализованы и полезны для многих других случаев использования Python.