Nagios : l’auteur tente de museler sa communauté pour cause d’avoir été trop libre!!

Posted in Nagios by Nap on February 23, 2010

C’est un cri d’alarme de la communauté Nagios que je me permets de relayer ici. En effet, après une longue phase d’ignorance de cette communauté de la part de l’auteur de l’outil, nous sommes arrivés à un point qui semble sans retour : l’auteur tente de museler la communauté française pour avoir été trop ouverte!!

Revenons un peu sur les faits : lancé il y a plus de 10ans, Nagios est encore à l’heure actuelle l’outil de référence dans le monde de la supervision open source, et de sérieux concurrents comme Zabbix ou Zenoss commencent à lui faire de l’ombre. Dans le domaine de l’open source, et tout particulièrement celui de la supervision, la force des outils reposent sur leur communauté. Celle de Nagios a été fleurissante pendant des années. Cependant, depuis environs deux ans, l’absence de réponse de l’auteur principal aux propositions de cette communauté a légitimement irrité cette communauté, au point de voir fleurir un fork il y a environs un an, nommé Icinga.

Là où l’histoire se complexifie se passe dans la sphère économique des sociétés utilisant et supportant Nagios. Comme pour beaucoup de logiciels commençant à prendre de l’ampleur, après plus de 8 années dédiées au projet l’auteur a légitimement crée une société de support et d’intégration nommée “Nagios Enterprise” et qui a déposé le nom “Nagios”. Le fork “Icinga” est issu de membres de la communauté Nagios qui sont employés par “Netways”, société allemande qui fait elle aussi du support et de l’intégration de Nagios. Si la licence GPLv2 est respectée, l’opportunisme et le manque de fair-play de cette société a fortement irritée l’auteur de Nagios et le reste de la communauté qui est restée fidèle dans sa grande majorité au projet initial.

Cette histoire semble avoir malheureusement eu un effet collatéral : l’auteur de Nagios s’est retourné contre sa communauté et l’ouverture qui a permis ce fork en l’ignorant encore davantage si c’était encore possible, en ne répondant pas par exemple à des propositions d’amélioration substantielles de code. Dernier affront en date et non des moindres : l’auteur demande à ce que lui soit cédé l’enregistrement DNS nagios-fr.org afin qu’il puisse réunir une communauté plus “corporate” car celle-ci a parlée sur son site du projet open source, mais félon à ses yeux, Icinga. Posts qui de plus n’étaient pas particulièrement élogieux. Fork qui au final aura plus coupé l’auteur de sa communauté que simplement un projet en deux.

La coupe est pleine pour la communauté qui se veut aussi ouverte que les outils qu’elle défend. Dans logiciel Open Source, il y “free as in beer” mais aussi “free as in speech”. Olivier Jan, fondateur et leader de cette communauté francophone, a ainsi répondu [1] qu’il ne serait pas possible de se laisser dicter ce sur quoi la communauté pouvait parler. Pour ne pas risquer une hypothétique assignation en justice (pour protection de trademark), il a donc été décidé d’obtempérer à l’attaque en déplaçant le site de la communauté vers monitoring-fr et de donner nagios-fr.org à la société Nagios Enterprise une fois la migration finie.

C’est donc avec une lettre ouverte [2] au titre de “The nagios community wants to keep its open soul” que la communauté demande où ce projet se dirige, s’il sera toujours ouvert et respectueux des principes même des l’open source. Des questions se posent en effet sur la réelle volonté de laisser ce projet open source en gelant la partie GPL et en l’incluant dans une solution fermée (”Nagios XI” éditée et diffusée sous peu par Nagios Enterprise) qui sera vraisemblablement seule à évoluer dans les années à venir. La communauté demande encore comment elle est perçue, si elle a encore sa place et si elle est tenue de tenir sa langue dans sa bouche sous peine de poursuite judiciaire en cas de non respect de la sacro sainte parole corporate de Nagios Enterprise.

Cette communauté fortement active ces dernières année est supportrice de l’industrialisation et le support de Nagios par des sociétés de logiciels libres, mais à la condition de pouvoir garder son âme. Nous souhaitons tous que Nagios conserve son rang qui ne l’oublions pas lui a été apporté grâce au soutient de toute sa communauté et que le projet revienne à la “bonne époque” où les codeurs et personnes de bonne volontés avaient encore leur mots à dire face à la communication marketing.

Espérons que cette lettre ne restera pas lettre morte, car ceci finirait de creuser le gouffre qui sépare l’auteur de Nagios et sa communauté qui est la vraie force motrice du projet et ferait le nid d’un fork qui diviserait encore la communauté, et qui rajouterai encore des noms tabous sur les sites traitant de Nagios.

Toutes les personnes souhaitant apporter leur soutient à notre démarche peux le faire en votant sur http://ideas.nagios.org/a/dtd/22035-3955 (pas besoin d’avoir un compte) qui est le site des idées de Nagios.

[1] : http://www.nagios-fr.org/2010/02/accuse-nagios-fr-org-levez-vous/
[2] : https://sourceforge.net/mailarchive/forum.php?thread_name=20100223103544.9upjoc1mbogwwc4o%40intra.expertise-online.net&forum_name=nagios-devel

EDIT: l’auteur a répondu. Voir pour sa réponse.

Partie à trois : Python, __slots__ et metaclass

Posted in Programmation by Nap on February 15, 2010 3 Comments

Les langages dynamiques sont pratiques pour se frotter facilement à de nouveaux paradigmes de programmations. Aucune technique n’étant parfaite, l’aspect dynamique se paye.

Le prix à payer pour les langages dynamiques

Bien souvent on pense au coût CPU pour ces langages, mais cette ressource n’est pas la seule à prendre cher. Là où un accès à une structure est en 0(1) en C ou C++, il peut être plus élevé dans des langages où les propriétés des objets ne sont pas identiques entre les instances. Il en est de même pour la RAM : les objets pouvant avoir de nouvelles propriétés à chaud, leur accès se fait en vérifiant le dictionnaire __dict__ des objets. Le dictionnaire est fort simple:

class Test:
   def __init__(self, x, y):
      self.x = x
      self.y = y
point = Test(1, 2)
print ‘Initialement’, point.__dict__
point.z = 3
print ‘Apres’, point.__dict__

Donne :

Initialement {’y': 2, ‘x’: 1}
Apres {’y': 2, ‘x’: 1, ‘z’: 3}

C’est sympa, c’est dynamique. Mais ceci a un coût en Mémoire : ici nous avons un seul objet, mais si nous avons 1000 points, chacun aura son propre __dict__ indépendant, et surtout les chaînes ‘x’, ‘y’ et ‘z’ seront dupliquées dans chaque instance. Imaginons que nous avons 1000000 de points à conserver, la consommation de RAM va être de 176mo sur notre exemple (Python 2.6.4). Si nous prenons des classes avec des noms de propriétés plus grandes que ‘x’, on peut atteindre des sommets en terme de consommation de RAM pour finalement pas grand chose.

Le module guppy (disponible sur pypi de mémoire) peut être très pratique pour observer qui consomme de la RAM dans notre application. Son utilisation est fort simple :

from guppy import hpy
hp=hpy()
print hp.heap()

Sa sortie est (relativement) éloquente :

Partition of a set of 2024657 objects. Total size = 173885852 bytes.
Index  Count   %     Size   % Cumulative  % Kind (class / dict of class)
0 999999  49 135999864  78 135999864  78 dict of __main__.Test
1 999999  49 31999968  18 167999832  97 __main__.Test
2    127   0  4073248   2 172073080  99 list
3  10686   1   744928   0 172818008  99 str
4   5540   0   203368   0 173021376 100 tuple
5    347   0   115160   0 173136536 100 dict (no owner)
6   1539   0   104652   0 173241188 100 types.CodeType
7     64   0   100480   0 173341668 100 dict of module
8    175   0    94840   0 173436508 100 dict of type
9    194   0    86040   0 173522548 100 type

78% de la consommation mémoire est due aux __dict__ de nos points, les valeurs de ces instances consommant quant à elles 18%.

__slots__ : c’est les soldes pour Python

Lorsque l’on sait à l’avance quelles vont être les possibilités des noms de propriétés de nos instances, il peut être pratique de recourir à l’utilisation des __slots__. C’est un tuple dans la classe où les noms des propriétés vont être mises en commun pour toutes les instances de la classe. Attention, son utilisation est fort simple, mais elle limite certaines possibilités de Python par la suite, comme certains problèmes avec tout ce qui touche la sérialisation d’objet par exemple.

Si vous souhaitez l’utiliser, c’est fort simple, il suffit de rajouter le tuple à la classe si elle hérite d’object :

class Test(object):
   __slots__ = (’x', ‘y’, ‘z’)
   def __init__(self, x, y):
      self.x = x
      self.y = y

Si simple? Non en fait. le __slots__ va remplacer __dict__ qui va tout simplement disparaitre! Notre code va lamentablement échouer avec:

Initialement
Traceback (most recent call last):
File “test_slot.py”, line 10, in <module>
print ‘Initialement’, point.__dict__
AttributeError: ‘Test’ object has no attribute ‘__dict__’

Pour contourner cela, il suffit de rajouter __dict__ au slots:

class Test(object):
   __slots__ = (’__dict__’, ‘x’, ‘y’, ‘z’)
   def __init__(self, x, y):
      self.x = x
      self.y = y

On relance, la consommation passe à 47Mo. (Les gains sont encore plus importants avec des chaînes de plus d’un caractère :) ). Pour Shinken par exemple, avec 100000 services, j’étais à plus de 2Go de RAM consommée, avec les slots, je suis tombé à moins 50Mo environs…

Metaclass : une classe pour en modifier d’autres

En Python, on a déjà vu que les classes sont des objets comme les autres. Qui dit objet dit instanciation. Lors de cette instanciation, il peut être pratique de changer des choses à la volée. C’est justement le rôle des metaclass. C’est une classe qui va contrôler la création d’une autre. Elles peuvent être utilisées pour par exemple tracer automatiquement tous les appels de méthode d’une classe. Pour un tel exemple, voir sur http://www.afpy.org/Members/kerflyn/metaclass qui présente très bien cela.

On mixe le tout

Vous allez me dire: bon c’est bien les metaclass, mais c’est quoi le rapport avec les __slots__? Et bien c’est pratique lorsque l’on a beaucoup de propriétés dans une classe, comme par exemple Service ou Host de Shinken. Jusqu’à maintenant, lorsque je rajoutait une nouvelle propriété à ces classes, je rajoutais une ligne dans le tableau properties ou running_properties, mais je devais penser à rajouter ce même paramètre dans le tuple  __slots__ de la classe. Autant dire qu’une fois sur deux, j’oubliais. De plus, ça fait un gros pâté en début de classe, et je n’aime pas ça.

Je suis tombé sur http://code.activestate.com/recipes/435880/ qui présente comment générer automatiquement le tuple __slots__ pour ses classes en regardant tout simplement les variables fournies à __init__ (il semble créer d’ailleurs une liste qui doit être changée en tuple par l’interpréteur). Bon pour les Host et Service, il n’y a qu’un seul paramètre, un tableau de construction. Mais ça m’a donné l’idée d’adapter ce code pour qu’il utilise les tableaux properties et running_properties de mes classes qui contiennent toutes les propriétés de mes objets.

Edit : Merci à Bertrand Mathieu pour la simplification du code par set.

Ceci donne au final la classe AutoSlots suivante :

class AutoSlots(type):
   def __new__(cls, name, bases, dct):
      slots = dct.get(’__slots__’, set())
      #Now get properties from properties and running_properties
      if ‘properties’ in dct:
         slots.update((p for p in dct['properties']))
      if ‘running_properties’ in dct:
         lots.update((p for p in dct['running_properties']))
      dct['__slots__'] = tuple(slots)
      return type.__new__(cls, name, bases, dct)

Qui est appelée avec :

class Service(SchedulingItem):
   #AutoSlots create the __slots__ with properties and
   #running_properties names
   __metaclass__ = AutoSlots

[..]

Maintenant les __slots__ sont construits à la volée, et il n’y a plus de risque d’oublier des paramètres et mes classes Host/Service se re-concentrent un peu sur ce qu’elles doivent faire, et non sur une astuce pour contourner une consommation excessive de RAM par Python.