Как отследить утечку памяти в моем коде Ruby?

Вопрос

Я отлаживаю утечку памяти в грабли. Я хочу увидеть стек вызовов:

  • Живые объекты
  • Какой объект или линия изначально выделяли эти объекты

Это возможно с ruby-prof?

Если нет, какой инструмент я должен использовать?

Настроить

Драгоценные камни

Рейк-задание

  • Импортирует файл 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.

Рассмотрим гем memory_profiler для использования с Ruby 2.1.

Чтобы сэкономить время, вы можете проверить список драгоценных камней 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
Другие вопросы по тегам