Как отследить утечку памяти в моем коде Ruby?
Вопрос
Я отлаживаю утечку памяти в грабли. Я хочу увидеть стек вызовов:
- Живые объекты
- Какой объект или линия изначально выделяли эти объекты
Это возможно с ruby-prof?
Если нет, какой инструмент я должен использовать?
Настроить
Драгоценные камни
- рельсы 3.2.16
- event_bus 1.0.0 ( https://github.com/kevinrutherford/event_bus)
- activerecord-fast-import ( https://github.com/jsuchal/activerecord-fast-import)
Рейк-задание
- Импортирует файл CSV непосредственно в базу данных MySql, используя объекты DATA LOAD INFILE и Active Record.
Что я пробовал
Я пробовал режимы
- RubyProf:: РАСПРЕДЕЛЕНИЯ
- RubyProf:: ПАМЯТЬ
Все, что говорится в документации:
RubyProf::ALLOCATIONS Отчеты о распределении объектов показывают, сколько объектов выделяет каждый метод в программе.
RubyProf::MEMORY Отчеты об использовании памяти показывают, сколько памяти использует каждый метод в программе.
Это означает, что ruby-prof просто сообщает об общем распределении объектов, а не только тех, которые живут.
Я пробовал Ruby-Mass и Bloat Check, но ни один из них не может делать то, что я хочу. Ruby-Mass также дает сбой, потому что он находит объекты FactoryGirl в памяти по какой-то причине...
4 ответа
Я не нашел ruby-prof очень полезным, когда дело доходит до обнаружения утечек памяти, потому что вам нужен исправленный интерпретатор Ruby. Отслеживание выделения объектов стало проще в Ruby 2.1. Может быть, это лучший выбор, чтобы изучить это самостоятельно.
Я рекомендую пост в блоге Ruby 2.1: objspace.so от tmml, который является одним из разработчиков ядра Ruby. В основном вы можете получить много информации при отладке вашего приложения:
ObjectSpace.each_object{ |o| ... }
ObjectSpace.count_objects #=> {:TOTAL=>55298, :FREE=>10289, :T_OBJECT=>3371, ...}
require 'objspace'
ObjectSpace.memsize_of(o) #=> 0 /* additional bytes allocated by object */
ObjectSpace.count_tdata_objects #=> {Encoding=>100, Time=>87, RubyVM::Env=>17, ...}
ObjectSpace.count_nodes #=> {:NODE_SCOPE=>2, :NODE_BLOCK=>688, :NODE_IF=>9, ...}
ObjectSpace.reachable_objects_from(o) #=> [referenced, objects, ...]
ObjectSpace.reachable_objects_from_root #=> {"symbols"=>..., "global_tbl"=>...} /* in 2.1 */
С Ruby 2.1 вы можете даже начать отслеживать размещение новых объектов и собирать метаданные о каждом новом объекте:
require 'objspace'
ObjectSpace.trace_object_allocations_start
class MyApp
def perform
"foobar"
end
end
o = MyApp.new.perform
ObjectSpace.allocation_sourcefile(o) #=> "example.rb"
ObjectSpace.allocation_sourceline(o) #=> 6
ObjectSpace.allocation_generation(o) #=> 1
ObjectSpace.allocation_class_path(o) #=> "MyApp"
ObjectSpace.allocation_method_id(o) #=> :perform
Используйте pry и pry-debugger и начните исследовать кучу памяти там, где вы думаете, что она, вероятно, будет расти, соответственно попробуйте разные сегменты в вашем коде. До Ruby 2.1 я всегда полагался на ObjectSpace.count_objects
и вычислил разницу в результате, чтобы увидеть, растет ли в частности один тип объекта.
Сборка мусора работает должным образом, когда количество растущих объектов повторно проверяется в гораздо меньшем количестве во время итераций, а не продолжает расти. В любом случае сборщик мусора должен работать все время, вы можете убедиться в этом, просмотрев статистику сборщика мусора.
По моему опыту это либо строка, либо символ (T_STRING
). Символы до ruby 2.2.0 не собирались мусором, поэтому убедитесь, что ваш CSV или его части не конвертируются в символы в пути.
Если вам неудобно, попробуйте запустить код на JVM с JRuby. По крайней мере, профилирование памяти намного лучше поддерживается такими инструментами, как VisualVM.
Чтобы сэкономить время, вы можете проверить список драгоценных камней Ruby, которые имеют утечки памяти в первую очередь. https://github.com/ASoftCo/leaky-gems
Существует гем рубиновой массы, который обеспечивает хороший API поверх ObjectSpace.
Один из способов решения этой проблемы - проверка ссылок после того, как вы покончили с вашим объектом.
object = ...
# more logic
puts Mass.references(object)
Если есть хотя бы одна ссылка, объект не является сборщиком мусора, и вам нужно выяснить, как удалить эту ссылку. Например:
object.instance_variable_set("@example", nil)
# or
ObjectSpace.each_object(Your::Object::Class::Name).each do |obj|
obj.instance_variable_set("@example", nil)
end