Du monitoring à la matière noire

J’ai participé au Breizhcamp cette année avec une présentation sur le monitoring.

Au passage merci aux organisateurs pour cette édition 2014. Les conférences sont toujours l’occasion de discussions passionnées sur les sujets les plus chaud du moment et puis c’est cool finalement de pouvoir assister à des conférences sans rien avoir à faire. Bravo aussi à l’initiative de faire un repas ouvert à tous le jeudi soir. On a ainsi pu partager les discussions passionnées des speaker diners avec d’autres passionnés.

Le but ce cette présentation est de revoir un peu nos idées reçues sur le monitoring. Même si le sujet n’est pas forcément très hype, Lean et Devops changent notre vision des mesures et les outils BigData viennent à notre secours.

La présentation se trouve .

Sébastien Brousse a twitté sur une partie de la présentation où je fais un détour par la matière noire. Je pense que le rapport avec le monitoring est peu évident sans les explications que j’ai donné de vive voix et le slide ne vous sera pas forcément d’une grande aide.

Pour ceux qui n’étaient là en attendant la vidéo voici les explications et même quelques infos supplémentaires.

Mais pourquoi la matière noire ?

Non, je n’ai pas changé de métier, la présentation parlait bien de monitoring.

Tout d’abord il y a peu de femmes dans l’informatique et donc dans les conférences, et c’était l’occasion d’avoir deux grandes dames avec nous, Margaret Burbidge et Vera Rubin, deux astrophysiciennes.

La première a imaginé le concept de matière noire, la seconde a poursuivi les travaux et mis au point une méthode pour prouver son existence.

Nous on est n’est pas des astrophysiciens, je vais rester à un niveau assez général. Pour les détails, sur la matière noire je vous renvoie à Wikipedia.

Des galaxies prises en excès de vitesse

Cette découverte a un rapport avec le monitoring parce que tout ça découle de problèmes de mesure.

Les astronomes avait constaté depuis longtemps que les galaxies spirales tournaient trop vite par rapport à la masse qu’elles sont supposées avoir d’après les mesures.

Plusieurs hypothèses ont été avancées pour expliquer cette bizarrerie en particulier l’imprécision des mesures.

Dans les années 1970, Margaret Burbidge a refait les mesures avec toute la précision des moyens de l’époque. Et là, ça ne collait toujours pas.

Plutôt que de remettre en cause les mesures, Margaret Burbidge a remis en cause le raisonnenment. Si la vitesse ne colle pas avec la masse, c’est que la masse n’est pas celle que l’on pense.

A cette époque on évalue la masse à partir des objets que l’on peut observer au téléscope, donc ceux qu’on peut voir parce qu’ils émettent de la lumière. Par conséquent, elle en a déduit qu’il y a dans les galaxies quelque chose qui a une masse mais que l’on ne voit pas parce que ça n’émet pas de lumière. C’est le concept de matière noire.

Vera Rubin qui travaillait avec Margaret Burbidge a apporté des éléments de preuve en mettant au point une méthode de calcul de la masse basée sur l’influence gravitationnelle de la galaxie sur son environnement, ce qu’on appelle la masse dynamique. A la différence de la masse lumineuse, la masse dynamique est compatible avec la vitesse de rotation des galaxie spirales. Et la masse dynamique basée directement sur l’effet de la masse n’a pas de raison d’être fausse, ce qui justifie qu’il y a un élément non visible ayant une masse.

Bon, d’accord mais moi je mesure des requêtes HTTP

Ok, nous on ne mesure pas des galaxies, mais même si on peut parfois regarder le serveur, enfin la boîte, finalement ce qu’on mesure est tout aussi peu visualisable.

Cette histoire montre que c’est important de mesurer de la bonne manière.

Si on mesure des effets indirects, on est dépendant d’un modèle. Ce modèle c’est l’idée qu’on se fait du fonctionnement du système et il peut être faux.

Dans un certain nombre de cas, cette mesure indirecte marche, par exemple la masse lumineuse convient pour les objets très lumineux comme les étoiles. Mais de temps en temps, ça ne marche pas.

Bien sûr, on n’a pas toujours la possibilité de mesurer l’effet direct, et de temps en temps on doit se baser sur une mesure indirecte parce que c’est plus abordable. Dans ce cas il faut rester vigilant, et savoir remettre en cause le modèle qu’on se fait du système.

Notre matière noire

Qu’est ce que c’est notre matière noire à nous les informaticiens ?

Ce sont les caches, les buffers, les load balancers, les heuristiques sur les files d’attente, les optimisations de JVM, tout un tas de mécanismes internes au système qui le rendent plus performant, mais qui font aussi que certaines de nos mesures ont un comportement erratique ou n’ont plus de sens dans certaines situations.

Au final, mesurer correctement les performances d’un système ou d’une application informatiques est une activité qui réserve toujours des surprises. On ne peut pas se contenter de poser des sondes au petit bonheur la chance sans comprendre ce qu’on fait. Il faut comprendre le système et comment il fonctionne pour le mesurer correctement.

On a un peu plus de chances, en cas de doute on peut souvent se reporter à la documentation (quoique à la réflexion pas toujours) ou demander au développeur (enfin des fois … ).

Mais par contre, un cache c’est beaucoup moins beau sur les photos qu’une galaxie spirale.

Les images proviennent toutes de la galerie d’images de la Nasa.

Publicités

Devoxx4Kids au Breizhcamp

Devoxx4Kids était au Breizhcamp pour une session le 13 Juin et le support au BreizhKids le 15 Juin.

Tout d’abord si Devoxx4Kids ne vous dit rien, c’est un projet open source qui regroupe les initiatives pour donner aux enfants le goût de la programmation, de la robotique et de l’ingénierie en général. Vous pourrez trouver des liens vers la communauté en langue française à partir de cette page Devoxx4Kids.

Dans la session, Audrey Neveu et moi avons partagé nos pratiques avec celles de bretons tout aussi passionnés que nous.

Voici un résumé des discussions. N’hésitez pas à faire des retours sur vos expériences avec ces jeux.

L’article est en deux partie, les plus petits et la programmation visuelle dans celui ci et l’apprentissage des langages sera dans un seond article.

Les tout petits

Avant la maîtrise de la lecture même les outils de programmation visuelle sont difficile à utiliser. Quelques pistes sur des jeux plutôt orientés vers la l’enchaînement logique d’opérations.


Robozzle : c’est un jeu en ligne dont le but est de sortir le petit robot du labyrinthe en écrivant un programme fait de mouvements de déplacement (aller tout droit, tourner à gauche, etc). Ce programme est ensuite joué et il marche ou pas. L’interface est en anglais mais le jeu est essentiellement graphique.

DrTechniko est un jeu à base de cartes où l’enfant doit déplacer un robot dans un parcours en ordonnant les cartes représentant divers mouvements. En général le robot est joué par un adulte.

Je sais lire !

OK, montre moi ça. On va pouvoir passer à des jeux basés sur la programmation visuelle.

Le plus connu est Scratch qui existe maintenant en 2 versions, la version 1 à installer et une toute nouvelle version 2 en ligne et en Flash qui demande soit un accès Internet soit d’installer un serveur local et un OS acceptant un player Flash :-/.

Scratch c’est aussi un réseau social permettant de partager ses projets.

C’est un outil de programmation visuelle basé sur des éléments colorés de divers types que l’on peut combiner pour déplacer un objet dans la scène, raconter des histoires, ou faire réagir l’objet à des actions sur le clavier.

L’accès est possible à partir de 6 ans, accompagné pour arriver à lire facilement les instructions et organiser son raisonnement, et autonome à partir de 8 ans. Le logiciel est en français si votre OS est en français.

Scratch a des extensions pour faire de la programmation visuelle d’autres types d’appareils comme par exemple Ardublock pour les Arduino. Il tourne aussi sur Raspberry Pi.

Je vous laisse découvrir l’excellent Mycophone à base d’Arduino dont l’auteur était parmi nous.

Alice est un jeu équivalent à Scratch mais pour faire des animations 3D. Il est écrit en Java.

L’accès est quand même plus compliqué et il n’est pas adapté aux moins de 10 ans. L’interface est moins intuitive, les déplacements des personnages et de la caméra en 3D sont plus compliqués et donc les fonctions sont moins évidentes. Le logiciel a un version française dans les options.

Mon robot défend ma chambre

Bien sûr nous n’avons pas oublié le Lego Mindstorm. Je l’ai mis un peu à part car contrairement aux logiciels précédents celui-ci n’est pas gratuit.

Il faut un robot et un ordinateur pour faire tourner le logiciel avec lequel on conçoit le programme du robot. Il faut aussi pas mal de temps pour assembler le robot, même le plus simple, le robot qui tire des billes lorsqu’il détecte un intrus.
C’est un jeu plus mobile et plus tactile que les précédents qui aborde la programmation par le déplacement du robot et la gestion des capteurs. C’est adapté au 10 ans et plus.

Dans le prochain article, les plus grands avec l’apprentissage des langages de programmation.

Tests de performance au YaJUG

J’ai uploadé les slides de la présentation sur les tests de performance que j’ai faite au YaJUG fin Octobre.

Les présentations, la mienne, celle de Stéphane Landelle sur Gatling, et celle d’Antonio Gomes Rodrigues sur JMeter ont été filmées par le YaJUG et seront disponibles sur Parleys dans l’espace du YaJUG http://www.yajug.org/confluence/display/Public/Past+Events+2012.

A cette occasion je me suis rendue compte qu’on connait des petits trucs que les autres n’utilisent pas forcément.

Mon truc c’est d’utiliser les profils Firefox pour éviter la reconfiguration du proxy.

JMeter, comme la plupart des outils de test de charge, utilise un proxy HTTP pour enregister une séquence d’actions  que vous jouez dans votre navigateur et générer un squelette de scénario de test. Pour que cela fonctionne, il faut configurer un HTTP Proxy Server dans JMeter, puis aller dans la configuration du navigateur pour indiquer  le port du proxy et enregistrer le scénario. Si vous ne l’avez jamais fait, la procédure complète se trouve là http://jmeter.apache.org/usermanual/jmeter_proxy_step_by_step.pdf

Première chose, le HTTP Proxy Server n’est pas toujours sur un port pratique. Il est par défaut sur le port 8080, ce qui est ennuyeux si vous avez aussi un Tomcat sur la même machine. Le navigateur trouvera bien quelque chose sur le port 8080, mais ça n’est pas votre proxy.

Une fois le port changé et les options de filtrage configurées, je sauve cet élément en utilisant « Save Selection As … ». Ceci permet de le réimporter plus tard en utilisant la fonction « Merge » dans le menu. Donc voilà, mon port est 4567.

 

Ensuite, il faut créer un profil dans Firefox qui passera toujours par Proxy sur le port 4567.

Il faut activer le Profile Manager car il n’est pas actif par défaut. La procédure varie selon les OS. Pour MacOSX, la procédure se trouve ici, pour Windows il faut ajouter -ProfieManager à la fin de  la ligne de commande du raccourci.

Au prochain démarrage, Firefox vous proposera de choisir un des profils existants et vous pourrez créer un profil JMeter.

Il y a un autre intérêt à passer par un profil spécifique. Vous laisserez ce profil vierge de tout add-on et vous éviterez ainsi de devoir configurer des filtres au niveau du proxy pour ignorer les requêtes émises en continu par ces extensions.

La prochaine fois, il n’y a plus rien à faire. Vous importez l’élément HTTP Proxy Server sauvegardé, vous le démarrez, vous lancez Firefox avec le profil JMeter et tout marche.

L’Open World Forum 2012

logo open world forum 2012L’Open World Forum 2012 s’est tenu le week end dernier à Paris.

Certains blocs de sessions étaient pris en charge par l’OSDC, le FUDCon (Fedora), le PAUG, Ruby.rb et divers groupes FOSS. De la diversité mais un planning assez opaque et quand à trouver la bonne salle parmi 9 salles sur 3 étages plus un auditorium …

Même si on retrouve toujours un peu les mêmes, la communauté Java était peu représentée et c’était l’occasion de croiser d’autres communautés de langages ou de pratiques.

Les gros événements étaient les présentations Google TV et le PAUG Conf Day. Beaucoup de monde et j’étais en retard pour d’autres contraintes donc au final j’ai beaucoup picoré dans les sessions de l’OSDC qui avait fourni un planning détaillé et j’ai gardé deux des sessions pour faire ce post. Les sessions ont été filmées par le PAUG et l’OSDC et seront disponibles bientôt.

L’Open World Forum c’était aussi la présence d’artistes et de sessions d’initiation à l’informatique pour les enfants. J’en parlerai dans un autre post sur le site de Duchess France.

Mais revenons en aux sessions techniques.

Choisir entre la corde et le poison

NoSql, cloud et montée en chargePierre Couzyosdc

Row of network servers in data centerPierre Couzy travaille chez Microsoft. Il est venu partager son expérience et les leçons qu’il a apprises sur le jeu Atlantis Fantasy. C’est un jeu Flash dont le back end est hébergé sur le cloud Azure qui utilise MongoDB en plus d’une base SqlServer classique.

Je n’aurais jamais cru écrire deux des mots de la phrase précédente sur ce site, mais en définitive les leçons qu’il a partagé sont valables sur beaucoup d’environnements Cloud.

Les jeux en ligne Facebook ont un cycle de vie en loi de Poisson avec un démarrage assez brutal lors de la propagation sur les réseaux sociaux, un plateau plus ou moins court selon l’attrait du jeu et un déclin en longue traîne au fur et à mesure que les joueurs se lassent. Les périodes critiques économiquement sont la croissance très rapide avant le plateau et le début du déclin. Dans les deux cas, la capacité doit s’adapter rapidement pour satisfaire les utilisateurs ou pour éviter des coûts disproportionnés. Le cloud est alors bien adapté pour gérer les phases de croissance et de maturité et ces jeux migrent souvent sur un hébergement classique moins couteux pour gérer le déclin. L’autre souci à gérer est qu’un jeu en ligne peut difficilement tolérer des arrêts pour maintenance et a très peu d’inactivité. Il marque seulement un creux dans la nuit. D’un autre coté des arrêts très brefs sont peu perçus par les joueurs car le client Flash les masque.

communication and internet network server roomLe premier constat est que sur un cloud toute opération disque devient une opération réseau car le stockage disque est fourni sous forme de service réseau de type EBS chez Amazon ou Azure Drives. Or la performance des bases de données dépend pour beaucoup des performances du stockage. Des défauts qui passent inaperçus sur des disques rapides locaux ressortent ici et sont difficiles à diagnostiquer car on raisonne toujours en volume d’écriture en mesurant la vitesse de transfert en bytes per second. Or c’est la latence du réseau qui bride le système pas le débit binaire du disque. C’est donc le nombre d’opérations, les IOPS qu’il faut surveiller. Elles plafonnent en général autour de 1000.

VenomLa deuxième recommandation est de diviser pour mieux régner. Les données sensibles comme les transactions financières sont restées sur un serveur SQL classique car il est impossible de garantir leur intégrité sur MongoDB. MongoDB est utilisé là où la souplesse du modèle est utile. Pour les données stockées dans MongoDB, Pierre Couzy recommande de partager les données entre plusieurs bases en fonction de leur cycle de vie pour pouvoir faire des optimisations plus agressives. Dans le cas du jeu, les données sont classées en trois catégories, l’état du jeu relativement stable, les données sociales assez volatiles, et des verrous logiques dont la durée de vie est très courte. Une des raisons de ce découpage est qu’il n’est pas utile de stocker physiquement des données à durée de vie très courte, l’autre est que les ralentissements sur les verrous doivent être absolument évités car ils ralentissent tout le reste du jeu.

Un troisième constat est que le multi-instance n’est pas la panacée. Lorsque l’instance MongoDB atteint sa limite de capacité, la première idée est de répartir la charge sur plusieurs instances. Mais une instance unique est plus efficace qu’une des instances du cluster. La réplication avec chacune des  autres instances mobilise une partie des ressources, à peu près autant que les écritures primaires dans ce cas. Elle entraîne aussi des risques d’incohérence qui demandent des mécanismes de contrôles eux-aussi coûteux. La réplication sur les 3 instances pendant que le jeu est à pleine charge prend des jours et peut pénaliser les temps de réponse du jeu. Les journaux de réplication dépassaient leur taille limite ce qui créait d’autres problèmes. A contrario, une instance unique peut se limiter au strict nécessaire sans Oplog et sans journalisation le temps de gérer l’installation en multi-instance. Risqué mais ça tient la charge. Mais attention, ces optimisations très agressives rendent le passage du cap vers le muti-instance encore plus difficile.

Infinity clock. Vector illustrationLe sharding nécessite aussi un peu de réflexion. Par défaut, la shard key est l’id généré par MongoDB. Cet id est monotone et les données nouvelles partaient sur la même partition. La bonne clé dépend du cycle de vie des données. Si ces données sont répliquées par la suite le problème n’est que transitoire. La solution retenue a été de transformer l’id et déplacer des caractères de la fin vers le début pour forcer la clé à changer rapidement d’ordre de grandeur.

funambuleLe réglage a été un processus incrémental. Viser un objectif pas trop haut, trouver la bonne configuration, puis augmenter la charge. Les tests de performance se sont avérés quasiment impossibles car ils ne reproduisaient pas la réalité correctement. Sur du cloud il n’est pas nécessaire de prévoir la capacité totale du système, elle peut être augmentée avec un peu d’anticipation. Pour cela, les logs, le monitoring et des représentations visuelles de l’usage des ressources sont indispensables.

Pierre Couzy recommande aussi de choisir la taille de machine en fonction du facteur limitant que l’on préfère subir. Les petites configurations sont limitées par la mémoire, les moyennes par la CPU, les grandes par le réseau. Il préfère la limitation par la CPU car elle est simple à monitorer et qu’elle cause moins d’effets de bord que la mémoire et le réseau. Toutefois, même dans ce cas la limite de capacité créera des timeouts et des effets de cascade car les clients recommencent faute de réponse rapide. La durée des timeouts tend à mettre le système en résonance ce qui est identifiable par des courbes en zigzag.

Nous voilà parés pour le backend. Et côté client quoi de neuf ?

Les nettoyeurs de code Android

Pierre-Yves Ricau et Alexandre Thomas  – Android Annotations 

logo AndroidAnnotationsPierre-Yves et Alexandre nous ont présenté Android Annotations, qui comme son nom l’indique permet de remplacer de la plomberie un peu systématique dans la programmation Android par des annotations.

Cette session est essentiellement basée sur une démo de code donc je ne rentrerai pas dans le détail, il faudra aller la voir 😉

Une partie des annotations remplace les déclaration et les initialisations des extras, des vues, des ressources via de l’injection de dépendance. D’autres annotations permettent de remplacer les AsyncTasks en déclarant simplement par une annotation si la méthode tourne sur l’UI Thread ou en background. Enfin les annotations qui ont éliminées le plus de code sont celles qui remplacent les classes anonymes des event handlers. A l’arrivée un code plus court, plus lisible et qui marche toujours (si si on a vérifié).

Le surpoids est assez faible car le framework sépare la librairie embarquée sur le device de la librairie qui gère la génération des classes supplémentaires pour le code généré. Au final, le framework rajoute une classe pour chaque classe annotée et un peu de code.

logo AndroidKickStartrAlexandre Thomas nous a aussi rapidement présenté Android KickstartR qui regroupe tout un tas de librairies couramment utilisées dans un même bundle et permet d’initier rapidement un projet Android.

Présentation Incanter à l’Open World Forum2012

L’Open World Forum commence aujourd’hui et se tiendra jusqu’à demain. C’est une conférence Open Source gratuite qui s’adresse à tous.

Vous pouvez trouver des informations plus détaillées sur le track Code sur le site de l’OSDC.

Je ferai une présentation sur l’analyse de données avec Incanter samedi après midi. Les slides et le repository de code sont accessibles

Quickie sur Incanter à Devoxx France

Et voilà, ce premier Devoxx France est terminé. Un superbe évènement, des speakers passionnants, des participants tous plus motivés les uns que les autres, et une équipe d’organisation soudée pour donner le meilleur d’elle même durant ces 3 jours. Du Java, mais pas que ça et surtout beaucoup de rencontres, de discussions enflammées et de plans pour refaire le monde en mieux. On attend avec l’impatience l’édition Devoxx World à Anvers fin 2012 et le prochain Devoxx France en 2013. Bon là on est sur les rotules, il faut qu’on récupère.

Merci aussi à l’équipe des Cast Codeurs qui a permis à 4 Duchess d’intervenir pendant l’enregistrement Live à la fin de Devoxx France.  On s’est bien amusées et on attend la sortie avec impatience pour le réécouter.

J’ai uploadé les slides de mon quickie sur Incanter  http://www.slideshare.net/claude.falguiere/incanter-devoxx

Le code des examples est sur Github https://github.com/cfalguiere/Demo-Incanter-Devoxx-France

Le temps du quickie est passé très vite. Je prépare une version atelier de 1h pour présenter Incanter et Clojure et pratiquer un peu Clojure en l’appliquant à l’analyse de données.

Alohomora Incanter-4

Incanter logo

Quand on ne sait plus trop où trouver des formules magiques, il reste Harry Potter, Alohomora l’incantation qui ouvre les portes, les portes de la voie des statistiques.

Allez au boulot, on a du rangement à faire.

Cet article fait partie d’une suite qui commence avec cet article épisode 1.

Recharger les données

First things first, charger nos librairies et nos données et on reprend où on en était dans l’épisode 3.

user=> (use '(incanter core io datasets stats))
nil
user=> (def data (read-dataset
"/Users/cfalguiere/Workspaces/Diapason/report-data/20111217-ETALON_WSW_TPS2011.csv" :header true))

#'user/data

Il est grandement temps de faire une fonction qui charge les résultats. On peut être sorcier et fainéant.

user=> (defn read-results []
  (read-dataset "/Users/cfalguiere/Documents/2012-01/RD-Clojure/Workspace/incanter/resources/sample-results.csv" :header true))

#'user/read-results

N’oubliez pas les parenthèses, sinon au lieu du prince charmant vous auriez une jolie mangouste, jolie mais bon ça n’est pas ce que vous vouliez. En l’occurrence, vous ne voulez pas que data représente la fonction mais son résultat.

user=>(def data read-results)
#'user/data
user=> data
#<user$read_results user$read_results@6bd46c20>

Donc voilà, le tour exécuté correctement et quelques donnnées présentes.

user=>(def data (read-results))
#'user/data

Pour le moment j’expérimente interactivement, donc peu importe l’IDE. Lançons un éditeur de texte pour copier ce code et chargeons le dans le REPL.

(use '(incanter core io datasets stats))

(def result-file-name "/Users/cfalguiere/Documents/2012-01/RD-Clojure/Workspace/incanter/resources/sample-results.csv")

(defn read-results []
  (read-dataset result-file-name :header true))

(def data (read-results))

J’aimerais bien indiquer le répertoire courant pour éviter tout ces chemins mais à la différence de R on ne peut pas changer le répertoire courant, parce que la JVM ne permet pas de le faire. Mais au fait quel serait mon répertoire courant ? Une rapide recherche sur Google indique ça :

user=> (. (java.io.File. ".") getCanonicalPath)
"/Applications"

Un peu ésotérique. C’est plus clair avec un exemple plus évident comme la méthode toUpperCase de la classe String. Le . applique la méthode à l’objet.

user=>(. "aaa" toUpperCase)
"AAA"

Il ne reste plus qu’à charger le programme et data est disponibles pour d’autres tours.

user=>(load-file "/Users/cfalguiere/Documents/2012-01/RD-Clojure/workspace/incanter/tuto1.clj")
#'user/data
user=> (nrow data)
209

Agrégeons, agrégeons, ils en restera toujours quelque chose

En général, j’aime bien avoir une vue d’ensemble de mes données : temps max, temps moyen, taux d’erreur …

Qu’est ce que j’ai là dedans ?

Le dataset est une map qui contient un vecteur de labels et une liste de lignes de données. La fonction col-names retourne les labels des colonnes (c’est équivalent (:column-names data))

user=> (col-names data)
[:lb :t :lt :ts :s :rc :rm :by :na]
user=> (:column-names data)
[:lb :t :lt :ts :s :rc :rm :by :na]

Les labels JMeter ne sont pas des plus parlants pour des raisons de compacité du résultat.

Pour chaque relevé nous disposons des informations suivantes :

  • :t le temps de réponse,
  • :lt le ‘latency time’ c’est à dire le temps qui s’est écoulé jusqu’à la réception du premier octet de la réponse,
  • :by le nombre d’octets de la réponse,
  • :na : le nombre d’utilisateurs simulés au moment de cette requête,
  • :rc le code retour de la requête HTTP, habituellement 200 ou 302,
  • :s le résultat des assertions sur les contenus reçus (true ou false),
  • :rm : un message,
  • :lb le label soit le nom donné au sample dans le script JMeter soit une url,
  • :ts  le timestamp du relevé.

Les 4 premières lignes sont des séries numériques. Les autres sont des catégories (l’équivalent des factors pour R). Elles vont servir à filtrer (pour les statuts) ou à regrouper les données.

Le module stats fourni les statistiques habituelles. Pour faire quelques expériementations, on va générer une série de nombres dont la moyenne est connue. sample-normal construit une liste de 1000 nombres distribués selon une loi normale centrée sur 10 avec un écart-type de 1. Sans surprise, la moyenne est autour de 10 et l’écart type est autour de 1.

user=> (def mysample (sample-normal 1000 :mean 10 :std 1))
(10.099828740537856 9.903470850168006 9.52228847634955 9.003518490739022 9.171579274811812 ...
user=> (mean mysample)
9.941436842660105
user=> (sd mysample)
1.0007997747452984

Et min et max ?

présentation des 4 quartiles

Un magicien ne voit pas les choses aussi simplement. Le min est la valeur qui est inférieure à tous les autres relevés. Et symétriquement le max est la valeur qui est supérieure à tous les autres relevés.

Mais les magiciens aiment bien découper les données en tranches. Donc ils veulent aussi savoir quelle est la valeur qui sépare les relevés en deux groupes, inférieurs et supérieurs à une valeur qui s’appelle la médiane.

Pour améliorer le spectacle, les données sont découpées en quatre tranches par pas de 25% comme le montre le schéma de droite, ce sont des quartiles. On voit sur le schéma que la surface rouge qui représente les 25% les plus bas se termine vers 9, la surface bleu qui représente les 25% suivants se termine vers 10 etc.

Les valeurs de 25% sont arbitraires. La forme générale de cet agrégat est le quantile. La fonction quantile retourne le min, la valeur maximale pour 25% des relevés, 50% (la médiane) et 75%, et le max (le quantile 100%).

user=> (quantile mysample)
(6.078738192126153 9.284660278736457 9.9269193454763 10.609659921191085 12.899443382831647)

Le monde étant ce qu’il est les sorciers qui font du test de charge ne s’intéressent pas aux quantiles des magiciens. En fait la seule chose qui les intéressent, c’est l’impact des 5% ou 10% de temps les plus longs. Donc ils calculent la valeurs maximale pour 90% ou 95% des relevés.

user=> (quantile mysample :probs 0.95)
11.591767882673627
user=> (quantile mysample :probs [0.9 0.95])
(11.278966306153395 11.591767882673627)

Qu’est ce que ça donne sur nos données de test ?

user=> (mean ($ :t data))
1429.579831932773
user=> (sd ($ :t data))
2237.25502018713

On peut faire toutes ces opérations sur la même série de données en utilisant la fonction associée au dataset with-data. Elle  factorise le dataset et permet de définir l’expression à utiliser, ici construire un tableau contenant les différentes métriques. $data représente la série à l’intérieur du with-data.

user=> (with-data ($ :t data)
  [(mean $data)(sd $data)])

[1429.579831932773 2237.25502018713]

Si on rajoute le quantile ça nous donne ce qui suit. Un bon début, mais on a un tableau des différents indicateurs plus une liste avec les quantiles.

user=> (with-data ($ :t data)
  [(count $data)(mean $data)(sd $data)(quantile $data :probs[0 0.5 0.9 0.95 1])])

[119 1429.579831932773 2237.25502018713 (10.0 708.0 3393.4 4130.999999999982 13007.0)]

Mmm … un coup de baguette magique flatten et les données se retrouvent bien alignées dans le tableau.

user=> (with-data ($ :t data)
  (flatten [(count $data)(mean $data)(sd $data)(quantile $data :probs[0 0.5 0.9 0.95 1])]) )

(119 1429.579831932773 2237.25502018713 10.0 708.0 3393.4 4130.999999999982 13007.0)

Une variable pour stocker le résultat et on sauve

user=> (def stats
  (with-data ($ :t data)
    (flatten [(count $data)(mean $data)(sd $data)(quantile $data :probs[0 0.5 0.9 0.95 1])]) ) )

#'user/stats
user=> stats
(119 1429.579831932773 2237.25502018713 10.0 708.0 3393.4 4130.999999999982 13007.0)

Quoique, pas tout de suite apparemment.

user=> (save 'stats "stats.csv")
java.lang.IllegalArgumentException: No method in multimethod 'save' for dispatch value: class clojure.lang.Symbol (NO_SOURCE_FILE:0)

En quête d’une multiméthode …

Que peut bien être une multiméthode ?

Vous trouverez une réponse complète dans cet article sur le polymorphisme en Clojure. En quelques mots, c’est le mécanisme qui permet à une fonction de se comporter différement en fonction des paremètres qu’on lui passe (des nombres ou des chaînes par exemple).

Vous avez probablement déjà noté que les fonctions en clojure sont souvent définies pour plusieurs nombres d’arguments (l’arité de la fonction si vous voulez briller la nuit au prochain ParisJUG). Clojure permet qu’une fonction soit surchargée (overloaded en anglais) et elle peut ainsi avoir une définition différente selon le nombre d’arguments.

Ci-après un exemple très simple de surcharge qui crée une map rectangle avec 1 ou 2 paramètres. La forme a un seul paramètre utilise la forme a 2 paramètres pour construire un carré.

user=> (defn make-rectangle
  ([width] (make-rectangle width width))
  ([width height] {:width width :height height}))

#'user/make-rectangle
user=> (make-rectangle 20)
{:width 20, :height 20}
user=> (make-rectangle 10 20)
{:width 10, :height 20}

Autre exemple plus compliqué, la multiplication implémentée de manière récursive en utilisant 4 formes :

(def mult
  (fn this
    ([] 1)
    ([x] x)
    ([x y] (* x y))
    ([x y & more]
      (apply this (this x y) more))))

La fonction est définie pour 0, 1, 2 arguments et un nombre quelconque d’arguments. Le & indique que ce qui se trouve à droite contient une liste d’arguments, et c’est par ce moyen qu’on définie une fonction variadique (pour quand vous ne brillerez plus assez avec arité).

Décryptons un peu cette multiplication. Tout d’abord, cet exemple n’utilise pas la macro defn, on a donc deux les étapes, décrire la fonction (fn) et la nommer (def). La multiplication utilise 4 variantes. Dans le cas général (4) je prend les 2 paramètres les plus à gauche, je leur applique « moi-même » (this x y) et j’applique ensuite « moi-même » en utilisant ce résultat et tous les paramètres qui restent à droite (more). Si je n’ai que 2 paramètres (3), je les multiple. Si je n’en ai plus qu’un (2), je le renvoie. Si je n’en ai plus (1) je renvoie 1.

Et pourquoi apply ? apply est utilisé dans les contextes où le nombre d’élements n’est pas connu à la compilation et donc typiquement lorsque l’on déconstruit une séquence. Par exemple

user=> (+ 1 2 3 4 5)
15
user=> (+ [1 2 3 4 5])
java.lang.ClassCastException (NO_SOURCE_FILE:0)
user=> (apply + [1 2 3 4 5])
15

L’addition du contenu de la séquence n’est pas possible directement. L’utilisation d’apply permet de déconstruire le tableau pour en lister les éléments et d’effectuer l’opération. Dans le cas de la fonction mult, apply permet de traiter more qui est une séquence.

Et ma multiméthode ?

La multiméthode est un mécanisme plus puissant qui permet de gérer le polymorphisme. La syntaxe est un peu différente et basée sur les verbes defmulti et defmethod.

L’exemple suivant montre une multiméthode « rencontre » entre différentes espèces. Si le paramètre 1 est un lapin et le paramètre 2 un lion, le comportement de « rencontre » est équivalent à la fonction « s’enfuit ». Dans le cas inverse, elle est équivalente à « mange »

(defmulti encounter (fn [x y] [(:Species x) (:Species y)]))
(defmethod encounter [:Bunny :Lion] [b l] :run-away)
(defmethod encounter [:Lion :Bunny] [l b] :eat)

L’exemple complet se trouve dans cet article sur le polymorphisme en Clojure.

Les multimethodes peuvent dispatcher en fonction des types des paramètres mais également sur des valeurs des arguments, leur nombre ou des méta-données selon la syntaxe.

Bref,  « No method in multimethod 'save' for dispatch value: class clojure.lang.Symbol (NO_SOURCE_FILE:0)" signifie seulement « je n’ai pas d’implémentation de save pour le type que tu m’envoie ». Oui, je sais …, mais les magiciens ne vont pas dévoiler tous leurs tours si facilement.

Enfin sauvé

stats est une liste et  save est une fonction Incanter qui fonctionnera que sur une matrice ou un dataset.
Qu’à celà ne tienne, on va construire un dataset avec ces données. Il faut lui indiquer les noms de colonnes et les valeurs.

user=> stats
(119 1429.579831932773 2237.25502018713 10.0 708.0 3393.4
4130.999999999982 13007.0)
user=> (def statsds
 (dataset ["count", "mean", "sd", "min", "median", "q90", "q95", "max"] stats) )

#'user/statsds
user=> statsds
#:incanter.core.Dataset{:column-names ["count" "mean" "sd" "min" "median" "q90" "q95" "max"],
:rows ({"count" 119} {"count" 1429.579831932773} {"count" 2237.25502018713} {"count" 10.0} {"count" 708.0} {"count" 3393.4} {"count" 4130.99999
9999982} {"count" 13007.0})}
user=> (view statsds)

Mmm, est ce bien ce que l’on veut ? Non, en fait le dataset n’est pas dans le bon sens. Il devrait avoir 1 ligne avec une colonne par indicateur.

Si vous allez sur la fonction dataset vous pouvez voir le source sous github. La fonction dataset attend une séquence de séquences ou une séquence de maps. Avec une simple liste, dataset a considéré que l’on voulait une liste de plusieurs lignes de 1 colonne.

La façon la plus simple de résoudre le problème est de transformer la liste en dataset en la transposant pour qu’elle constitue 1 ligne de plusieurs colonnes. Ensuite on lui ajoute des noms de colonnes.

user=> (def statsds (col-names
  (to-dataset stats :transpose true) ["count", "mean", "sd", "min", "median", "q90", "q95", "max"]) )

#'user/statsds
user=> statsds
#:incanter.core.Dataset{:column-names ["count" "mean" "sd" "min" "median" "q90" "q95" "max"],
:rows ({"max" 13007.0, "q95" 4130.999999999982, "q90" 3393.4, "median" 708.0, "min" 10.0, "sd" 2237.25502018713, "mean" 1429.579831932773, "count" 119})}
user=> (view statsds)
Le dataset est bien dans le bon sens et on peut le sauver
user=> (save statsds "/Users/cfalguiere/Documents/stats.csv")
nil

Il manque juste une dernière info utile le taux d’erreurs et le top n.
Pour le taux d »erreurs, on verra plus tard (surtout qu’il n’y en a pas dans le jeu de données) mais pour le top 5 des pires temps voici l’incantation trier-les-lignes-renverser-l-ordre-et-prendre-les-5-premiers

user=> (take 5 (reverse (sort-by :t (:rows data))))
({:na 1, :by 172777, :rm "OK", :rc 200, :s "true", :ts 1.324109405557E12, :lt 1398, :t 13007, :lb "/CategoryDisplay"}
{:na 1, :by 33235, :rm "OK", :rc 200, :s "true", :ts 1.324109282891E12, :lt 8967, :t 8981, :lb "/--product--.html"} ...

Et voilà d’autres tours de sorciers inscrits dans le grimoire. Dans l’épisode 5 on regroupera ces données pour avoir des résultats plus détaillés, par label par exemple et on fera des graphes de ces données.