web-dev-qa-db-fra.com

Comment localiser une fuite de mémoire dans mon code Ruby?

Question

Je débogue une fuite de mémoire dans une tâche de râteau. Je veux voir une pile d'appels de:

  • Objets vivants
  • Quel objet ou ligne a initialement alloué ces objets

Est-ce possible avec Ruby-prof?

Sinon, quel outil dois-je utiliser?

Installer

Gemmes

Tâche de râteau

  • Importe un fichier CSV directement dans une base de données MySql à l'aide des objets DATA LOAD INFILE et Active Record.

Ce que j'ai essayé

J'ai essayé les modes

  • RubyProf :: ALLOCATIONS
  • RubyProf :: MÉMOIRE

Tout ce qu'il dit dans la documentation est:

RubyProf :: ALLOCATIONS Les rapports d'allocation d'objets montrent combien d'objets chaque méthode d'un programme alloue.

Les rapports d'utilisation de la mémoire RubyProf :: MEMORY indiquent la quantité de mémoire utilisée par chaque méthode d'un programme.

Cela implique que Ruby-prof ne fait que rapporter l'allocation totale des objets, pas seulement ceux qui sont vivants.

J'ai essayé Ruby-Mass et Bloat Check mais aucun ne semble pouvoir faire ce que je veux. Ruby-Mass plante également parce qu'il trouve des objets FactoryGirl en mémoire pour une raison quelconque ...

30
John Gallagher

Je n'ai pas trouvé Ruby-prof très utile pour localiser les fuites de mémoire, car vous avez besoin d'un interpréteur corrigé Ruby. Le suivi de l'allocation des objets est devenu plus facile dans Ruby = 2.1. C'est peut-être le meilleur choix pour l'explorer vous-même.

Je recommande le billet de blog Ruby 2.1: objspace.so par tmml qui est l'un des développeurs principaux Ruby. En gros, vous pouvez récupérer beaucoup d'informations tout en déboguant votre application :

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 */

Avec Ruby 2.1 vous pouvez même commencer à suivre l'allocation de nouveaux objets et collecter des métadonnées sur chaque nouvel objet:

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

Utilisez pry et pry-byebug et commencez à explorer le tas de mémoire où vous pensez qu'il va probablement croître, essayez respectivement différents segments dans votre code. Avant Ruby 2.1 J'ai toujours compté sur ObjectSpace.count_objects et calculé la différence du résultat, pour voir si un type d'objet se développe en particulier.

La récupération de place fonctionne correctement lorsque le nombre d'objets en croissance est retesté à une quantité beaucoup plus petite au cours des itérations plutôt que de continuer à croître. Le garbage collector devrait toujours fonctionner de toute façon, vous pouvez vous rassurer en consultant les statistiques du garbage collector.

D'après mon expérience, il s'agit soit d'une chaîne soit d'un symbole (T_STRING). Les symboles avant Ruby 2.2. n'étaient pas récupérés, alors assurez-vous que votre CSV ou des parties de celui-ci ne sont pas convertis en symboles en cours de route.

Si vous ne vous sentez pas à l'aise, essayez d'exécuter votre code sur la JVM avec JRuby. Au moins, le profilage de la mémoire est beaucoup mieux pris en charge avec des outils comme VisualVM.

35
Konrad Reiche

Considérez la gemme memory_profiler pour une utilisation avec Ruby 2.1.

4
Pistos

Pour gagner du temps, vous pouvez vérifier une liste de Ruby gemmes qui ont des fuites de mémoire en premier. https://github.com/ASoftCo/leaky-gems

3
Sergey Alekseev

Il y a un Ruby-mass gem, qui fournit une belle API sur ObjectSpace.

L'une des façons d'aborder le problème consiste à vérifier les références une fois que vous avez terminé avec votre objet.

object = ...
# more logic
puts Mass.references(object)

S'il existe au moins une référence, l'objet n'est pas récupéré et vous devez comprendre comment supprimer cette référence. Par exemple:

object.instance_variable_set("@example", nil)

# or

ObjectSpace.each_object(Your::Object::Class::Name).each do |obj|
  obj.instance_variable_set("@example", nil)
end
2
Anton K