Arduino : créer une horloge synchronisée

Dans cet article, nous allons voir comment afficher sur un écran la date et l'heure courantes, avec la possibilité de se synchroniser sur un serveur de temps public (NTP - Network Time Protocol), comme le font tous les PC ou téléphones.

Matériel

Pour cet exemple, il faut vous munir :

  • d'un arduino (ici un DUE)
  • un shield Ethernet
  • un module RTC (Real Time Clock, ici un Gravitech)
  • un écran LCD

Voici un schéma du montage :

L'écran est branché, de manière classique, sur des broches numériques. Le module RTC quant à lui est intégré sur le bus I2C par les broches SDA1 et SCL1.

Logiciel

Nous allons utiliser les librairies :

  • Ethernet
  • Wire1 (pour le bus I2C)
  • LiquidCrystal
  • Time (pour faciliter la décomposition de la date à partir d'un timestamp)

Données importantes :

Selon la spécification du constructeur, l'adresse de communication du module RTC sur le bus I2C est 0x68. Le serveur NTP choisi est celui de l'Observatoire de Paris (ntp-p1.obspm.fr, IP: 145.238.203.14). Le dialogue avec un serveur NTP se fait par échange de paquets par le protocole UDP.

C'est parti!

1. Configuration d'une date par défaut sur le module RTC

Commençons par un premier échange avec ce module :


const int I2C_address = 0x68;
...
Wire1.begin();        // join i2c bus (address optional for master)
Wire1.beginTransmission(I2C_address);// Début de transaction I2C
Wire1.write(0); // Arrête l'oscillateur
Wire1.write(0); // sec
Wire1.write(0x11); // min
Wire1.write(0x20); // heure
Wire1.write(6); // jour de la semaine
Wire1.write(0x20); // jour
Wire1.write(9); // mois
Wire1.write(0x14); // annee
Wire1.write(0); // Redémarre l'oscillateur
Wire1.endTransmission(); // Fin de transaction I2C

Ici nous définissons la date "20/09/2014 20:11:00". Remarquez que les valeurs transmises sont au format hexadécimal.

2. Lecture de la date sur le module RTC

Interrogeons notre module pour récupérer la date :


void getTime(char* tempData) {
 byte i = -1;
 byte error;
 
 Wire1.beginTransmission(I2C_address); // Début de transaction I2C
 Wire1.write(0); // Demande les info à partir de l'adresse 0 (soit toutes les info)
 error = Wire1.endTransmission(); // Fin de transaction I2C
 
 if(error==0) {
  Wire1.requestFrom(I2C_address, 7); // Récupère les info (7 octets = 7 valeurs correspondant à l'heure et à la date courante)
 
  while(Wire1.available())        
  {
   tempData[++i] = Wire1.read(); // receive a byte as character
  }
 } else {
  // le composant RTC n'etait pas joignable, on relance le bus I2C pour la prochaine fois
  Wire1.begin();
 }
}

Cette méthode nous permet de récupérer, dans un tableau de 7 char (tempData), les données du module.

3. Affichage de la date sur l'écran


void displayTime()
{
 char tempchar[7] = {'\0'};
 // recuperation de la date
 getTime(tempchar);
 // affichage
 if(tempchar[0]!='\0') {
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print(tempchar[4],HEX);
  lcd.write("/");
  lcd.print(tempchar[5],HEX);
  lcd.write("/20");
  lcd.print(tempchar[6],HEX);
  lcd.setCursor(0, 1);
  lcd.print(tempchar[2],HEX);
  lcd.write(":");
  lcd.print(tempchar[1],HEX);
  lcd.write(":");
  lcd.print(tempchar[0],HEX);
 }
}

Avec cette méthode, on envoie à l'écran chaque caractère (toujours au format hexadecimal) retourné par l'appel au module RTC.

Il ne vous reste plus qu'à placer l'appel à cette méthode dans une boucle toutes les secondes pour voir défiler le temps. Essayez même de couper l'alimentation du module RTC : l'affichage de l'heure se fige, jusqu'à nouvelle alimentation du module. Elle sera alors mise à jour avec la date qui a continué à défiler grâce à la pile du module.

4. Interrogation du serveur de temps

Le shield Ethernet va nous permettre de contacter le serveur public et de récupérer les informations de date "de référence".


IPAddress timeServer(145, 238, 203, 14); // ntp-p1.obspm.fr (IP: 145.238.203.14)
const int NTP_PACKET_SIZE= 48; // NTP time stamp is in the first 48 bytes of the message
uint8_t packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets
/* Set this to the offset (in seconds) to your local time
   GMT - 2 */
const long timeZoneOffset = -7200L;  
// A UDP instance to let us send and receive packets over UDP
EthernetUDP udpClient;
EthernetClient client;

/**
* Démarrage des librairies
*/
Ethernet.begin(mac);
udpClient.begin(8888); // port UDP local


/**
* Envoi de la requete 
*/
// set all bytes in the buffer to 0
memset(packetBuffer, 0, NTP_PACKET_SIZE);
// Initialize values needed to form NTP request
// (see URL above for details on the packets)
packetBuffer[0] = 0b11100011;   // LI, Version, Mode
packetBuffer[1] = 0;     // Stratum, or type of clock
packetBuffer[2] = 6;     // Polling Interval
packetBuffer[3] = 0xEC;  // Peer Clock Precision
// 8 bytes of zero for Root Delay & Root Dispersion
packetBuffer[12]  = 49;
packetBuffer[13]  = 0x4E;
packetBuffer[14]  = 49;
packetBuffer[15]  = 52;

// all NTP fields have been given values, now
// you can send a packet requesting a timestamp:
udpClient.beginPacket(timeServer, 123); //NTP requests are to port 123
udpClient.write(packetBuffer,NTP_PACKET_SIZE);
udpClient.endPacket();


/**
* Parcours de la réponse du serveur
*/
unsigned long epoch = 0;
if ( udpClient.parsePacket() ) {
 // We've received a packet, read the data from it
 udpClient.read(packetBuffer,NTP_PACKET_SIZE);  // read the packet into the buffer

 //the timestamp starts at byte 40 of the received packet and is four bytes,
 // or two words, long. First, esxtract the two words:

 unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
 unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
 // combine the four bytes (two words) into a long integer
 // this is NTP time (seconds since Jan 1 1900):
 unsigned long secsSince1900 = highWord << 16 | lowWord;

 // now convert NTP time into everyday time:
 // Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
 const unsigned long seventyYears = 2208988800UL + timeZoneOffset;
 // subtract seventy years:
 epoch = secsSince1900 - seventyYears;
}


/**
* Configuration du module RTC avec la nouvelle date
*/
setTime(epoch); // on affecte le timestamp récupéré à la librairie Time
Wire1.beginTransmission(I2C_address);// Début de transaction I2C
Wire1.write(0); // Arrête l'oscillateur
String strval = String(second(), DEC);
Wire1.write(strtol(strval.c_str(),NULL, HEX)); // sec
strval = String(minute(), DEC);
Wire1.write(strtol(strval.c_str(),NULL, HEX)); // min
strval = String(hour(), DEC);
Wire1.write(strtol(strval.c_str(),NULL, HEX)); // heure
Wire1.write(weekday()-1); // jour de la semaine
strval = String(day(), DEC);
Wire1.write(strtol(strval.c_str(),NULL, HEX)); // jour
strval = String(month(), DEC);
Wire1.write(strtol(strval.c_str(),NULL, HEX)); // mois
strval = String(year(), DEC);
Wire1.write(strtol(strval.c_str(),NULL, HEX)); // annee
Wire1.write(0); // Redémarre l'oscillateur
Wire1.endTransmission(); // Fin de transaction I2C

La librairie Time nous permet de décomposer facilement les données de la date. Mais le serveur NTP nous a envoyé la date au format décimal "20/09/2014 20:11:00". Il faut donc convertir les données au format hexa avant de configurer le module RTC.

Ainsi, après l'appel à ce code (à découper en méthodes pour plus de clarté), l'affichage de la date sera automatiquement actualisé avec la date de référence.

Have fun!

Sources :


Fichier(s) joint(s) :



Utiliser les librairies Arduino dans Atmel Studio

Dans cet article je vais vous présenter comment démarrer un projet simple (exemple Blink) avec un Arduino Due, Atmel Studio 6.2 et les librairies du projet Arduino.

Pour commencer, vous pouvez suivre cet article très clair et très simple pour initier l'environnement : http://www.engblaze.com/tutorial-using-atmel-studio-6-with-arduino-projects/

Datant cependant de 2012, il mérite quelques mises à jour : il vous faudra télécharger la dernière version du software Arduino capable de gérer le Due. Les chemins vers les répertoires à inclure seront donc légèrement différents, puisque spécifiques à la plateforme (par ex ".\hardware\arduino\sam\variants\arduino_due_x"). Il faudra également ajouter dans le Linker un lien vers la librairie libsam_sam3x8e_gcc_rel située sous ".\hardware\arduino\sam\variants\arduino_due_x".

Il se peut que vous rencontriez des petites erreurs de compilation, que vous pourrez résoudre à la main en commentant/déplaçant du code.

Voici maintenant la nouvelle version de l'exemple Blink :


#include "sam.h"
#include "Arduino.h"


int led = 53;

/**
 * \brief SysTick_Handler.
 */
void SysTick_Handler(void)
{
 /* Increment counter necessary in delay(). */
 TimeTick_Increment();
}

/**
 * \brief Application entry point.
 *
 * \return Unused (ANSI-C compatibility).
 */
int main(void)
{
    /* Initialize the SAM system */
    SystemInit();
    SysTick_Config(SystemCoreClock / 1000); //1ms per interrupt
 
    pinMode(led, OUTPUT);

    while (1) 
    {
        digitalWrite(led, HIGH);   // turn the LED on (HIGH is the voltage level)
        delay(1000);               // wait for a second
        digitalWrite(led, LOW);    // turn the LED off by making the voltage LOW
        delay(1000);
    }
}

Pour faire fonctionner la méthode delay, il est nécessaire d'ajouter le code activant l'incrémentation du compteur systeme :


void SysTick_Handler(void)
{
 /* Increment counter necessary in delay(). */
 TimeTick_Increment();
}
...
SysTick_Config(SystemCoreClock / 1000); //1ms per interrupt

Ainsi, la méthode d'incrémentation TimeTick_Increment sera appelée par l'handler interne au processeur SysTick_Handler toutes les 1ms comme indiqué par SysTick_Config

Ne reste plus qu'à indiquer la plateforme cible à la compilation (processeur SAM3X8E) et lancer la programmation!

Edit : il est également possible d'appeler la méthode Arduino TimeTick_Configure(SystemCoreClock)


Fichier(s) joint(s) :



SQLServer, JDBC et procédures stockées

On ne le repètera jamais assez, migrer une base de données n'a rien d'anodin... Surtout lorsque l'application s'appuie sur des procédures stockées. En effet, chaque SGBD en propose son propre moteur et aucun driver JDBC ne se ressemble...


Le diable est dans les détails

Voici donc quelques exemples de "détails" qui vous éviteront de vous arracher les cheveux. La configuration utilisée ici est SQL Server Express 2014 et le driver JDBC Microsoft 4.0.

Un message peut en cacher un autre

Prenons l'exemple de la procédure stockées suivante :


CREATE PROCEDURE p_maproc(@param INTEGER)
AS
BEGIN

  UPDATE MATABLE SET COL1 = 'VALEUR'

  select 'data1' as RET1, 'data2' as RET2, 'data3' as RET3

END

Rien de bien compliqué, un UPDATE et un SELECT dont le but est de renvoyer un ResultSet avec 3 colonnes de données.

Appelons maintenant cette procédure avec un outil type Squirrel (configuré pour utiliser le driver JDBC) :

Tout se passe bien, le retour est bien constitué des 3 colonnes du SELECT.

Cependant, lorsque cette même procédure est appelée dans une application Java (via le même driver JDBC), l'erreur suivante est renvoyée :

com.microsoft.sqlserver.jdbc.SQLServerException: L'instruction n'a pas renvoyé le jeu de résultat.

En somme, le driver n'a pas pu remonter le ResultSet qu'il attendait...

Ceci est en fait du à la présence de l'instruction UPDATE. Son exécution génère l'émission d'un message appelé DONE_IN_PROC, qui sert à indiquer le nombre de lignes modifiées. Et c'est donc ce message qui est en priorité capté comme retour par le driver JDBC, ce qui l'empêche ensuite de récupérer le vrai ResultSet final.

Pour corriger ce problème, il existe l'instruction

SET NOCOUNT ON
qui permet de désactiver l'envoi des messages DONE_IN_PROC par la base. Notre procédure devient donc :


CREATE PROCEDURE p_maproc(@param INTEGER)
AS
BEGIN

  SET NOCOUNT ON
  UPDATE MATABLE SET COL1 = 'VALEUR'
  SET NOCOUNT OFF

  select 'data1' as RET1, 'data2' as RET2, 'data3' as RET3

END

Il est également possible de configurer directement le serveur de base de données pour ne pas avoir à écrire cette instruction dans toutes les procédures (voir les liens plus bas).

Du bon usage des types utilisateur

Les UDT (user defined type) fonctionnent comme des alias, permettant de créer des types de données personnalisés. Exemple :


CREATE TYPE [dbo].[T_DATEHEURE] FROM [datetime] NULL

Cette instruction va créer un type de données T_DATEHEURE basé sur le type primitif datetime, utilisable dans la base de données courante.

Prenons donc maintenant l'instruction suivante (toujours dans une procédure stockée) :


CREATE PROCEDURE p_maproc(@param INTEGER)
AS
BEGIN

  CREATE TABLE #TABLETEMP (ID int, DATE T_DATEHEURE);
 
  ...

END

Cette fois-ci, l'erreur suivante sera levée :

Msg 2715, Level 16, State 7, Line 1
Colonne, parametre, ou variable #2: Type de données T_DATEHEURE introuvable.

Ceci provient de la nécessité de déclarer également le type de données T_DATEHEURE dans la base de données tempdb, utilisée par SQLServer pour créer les tables temporaires. Ainsi, une fois l'ordre "CREATE TYPE" exécuté sur tempdb, tout fonctionnera normalement...

Hope this helps!

Sources :


Fichier(s) joint(s) :



Développeurs écolos!

Depuis quelques années, la notion de GreenIT envahi l'univers des systèmes d'informations, en particulier au niveau matériel : composants de moins en moins énergivores, utilisation d'énergies renouvelables pour le refroidissement des machines...

Mais il est également possible de faire un geste pour la planète en tant que développeur, cela s'appelle l'éco-conception logicielle.

L'idée de départ de ce concept peut être représentée par le paradoxe de Jevons :

(...) à mesure que les améliorations technologiques augmentent l'efficacité avec laquelle une ressource est employée, la consommation totale de cette ressource peut augmenter au lieu de diminuer.

Autrement connu en économie sous le nom d' "effet rebond" :

(...) augmentation de consommation liée à la réduction des limites à l’utilisation d’une technologie.

Prenons un exemple : l'apparition des écrans LCD. Au tout début, le passage des écrans à tube cathodique vers la technologie à cristaux liquides devait générer d’importantes économies d’énergie car, à diagonale identique, un écran LCD consomme de 2 à 3 fois moins d’énergie qu’un écran CRT. Or, l'augmentation exponentielle du nombre d'écrans LCD utilisés a finalement engendré une consommation globale plus importante. De plus, la fabrication d'un écran LCD entraine une production de gaz à effet de serre 2 fois plus importante que celle d'un CRT. Et les exemples sont innombrables.

Dans cette idée, de plus en plus d'organisations pointent du doigt la surconsommation des logiciels, n'hésitant pas à qualifier d' "obésiciels" (bloated software) ou de "gras numérique" le surplus de code embarqué par les applications modernes.

Même les plus hautes instances réfléchissent à ce sujet : en 2011, la WWF a publié un rapport intitulé "Guide pour un système d'information éco-responsable", dans lequel il pointe le constat suivant :

Si on prend l’exemple de Microsoft, chaque nouvelle version du couple Windows – Office nécessite 2 fois plus de ressources que la précédente.
En d’autres termes, la puissance nécessaire pour écrire un texte double tous les deux ans. Si bien qu’il faut 70 fois plus de mémoire vive sous Windows 7 – Office 2010 pour écrire le même texte que sous Windows 98 – Office 97 !
On imagine mal devoir utiliser une voiture 70 fois plus puissante qu’il y a 12 ans pour parcourir le même nombre de kilomètres, à la même vitesse.

Ainsi, de plus en plus de grandes entreprises se lancent dans la course au logiciel vert : Facebook a par exemple économisé des centaines de millions de dollars en investissant quelques centaines de "jours.homme" dans le développement d’un compilateur PHP vers C++ : "HHVM" a permis au réseau social de diviser par 2 le nombre de serveurs nécessaires à la génération des milliards de pages servies chaque mois. Facebook a ainsi évité la construction d’un nouveau data center et récupéré près de 50 % de la capacité de ses data centers existants ! Sans compter une facture électrique divisée par 2 et des émissions de gaz à effet de serre réduites d’autant. On peut encore trouver des exemples similaires chez IBM ou LinkedIn.

Bien sûr, il y a aussi des contre-exemples d'entreprises peu consciencieuses de ces considérations : Greenpeace à son tour dénonce, dans ce rapport de 2012, le manque de transparence de Twitter, affublée de "lanterne rouge" de l'industrie, ne fournissant aucune information sur son empreinte énergétique. De même, Amazon est accusée de continuer à agrandir son infrastructure dont l' "alimentation électrique ne dépend d'aucune énergie renouvelable, et repose uniquement sur le charbon, le gaz et le nucléaire".

Il est également intéressant de noter que ce rapport indique que dès 2005, la consommation électrique du "cloud computing" dépassait déjà celle de certains grands pays :

Mais venons-en à l'essentiel : peut-on développer des logiciels "verts"? Et comment?

Au vu des éléments précédents, la première réponse serait : avec des logiciels "simples"! En effet, en se contentant d'effectuer les calculs et traitements strictement nécessaires, un logiciel pourrait réduire son impact sur la consommation d'énergie. Mais la loi de Moore (qui veut que la puissance des PC double tous les ans) ajoutée au paradoxe de Jevons nous a permis de produire des logiciels de plus en plus complexes (frameworks, surcouches...), sans tenir compte de l'impact de l'exécution de ce code "superficiel".

Autre exemple "anti-écolo" : on souhaite mettre en place un algorithme, selon deux méthodes :

  • La première sera développée en 1 mois et produira un algorithme s'exécutant en 1 minute
  • La seconde sera développée en 2 mois et produira un algorithme s'exécutant en 30 secondes

Que choisiriez-vous? Sûrement le plus rapide à développer... Qui sera pourtant le plus gourmand en ressources et donc le moins "vert" ! Cet exemple prouve que la réflexion d'éco-conception peut venir contrarier l'élaboration classique de logiciels.

Tout ceci pousse donc directement à l'adoption de nouveau matériel toujours plus puissant et accélère le renouvellement des parcs informatiques : la durée de vie moyenne d'un PC a été divisée par 3 en quelques années (généralement 3 ans au lieu de 10), jouant ainsi le jeu de la société de consommation. [Effet papillon :)]

Pour résumer, même s'il peut paraître quelques fois compliqué de conserver des considérations écologiques dans le cadre du développement d'un logiciel, ces réflexions peuvent permettre de produire du code simple, efficace et finalement bon pour la planète.

Sources :


Fichier(s) joint(s) :



Comprendre les objets connectés avec Arduino

J'ai récemment eu l'occasion de publier un article sur les objets connectés dans le magazine Programmez!

Comprendre les objets connectés avec Arduino

Alors n'hésitez pas à acheter ce numéro, cela fera une lecture intéressante sur la plage!


Fichier(s) joint(s) :



Arduino et après?

Ce titre est un peu raccoleur, surtout que je vais continuer à parler d'Arduino... Le sujet exact ici est plutôt "AVR et après?".

En effet, l'ampleur ou les besoins de certains projets nécessitent d'aborder une solution plus complète que les plateformes Arduino classiques, utilisant des microcontrôleurs AVR 8 bits.

Voici donc comment prendre en main l'Arduino Due, et surtout son microprocesseur ARM 32 bits (ATSAM3X8E), utilisé par exemple dans les smartphones.

Plus de mémoire!

Le point le plus marquant et intéressant de la plateforme est sans doute la quantité de mémoire disponible : 512Ko de Flash (pour le programme) et 96Ko de RAM (pour l'execution et les données).

De plus la vitesse d'exécution de 84Mhz peut apporter un sacré coup de pouce aux projets qui le nécessitent (temps réel...). Enfin, l'utilisation de données sur 32 bits offre une puissance de calcul importante.

Pour une présentation détaillée, je vous laisse lire l'article suivant : [Test] Arduino Due & IDE arduino 1.5.x

Appréhender le nouveau coeur

Un processeur ARM est bien plus complexe que les traditionnels AVR : bus de données, contrôleur avancé d'interruptions, optimisation du code exécuté, timers (watchdog)... Je ne vais pas m'attarder ici sur ce sujet complexe, je vous laisse vous lancer par vous-même en lisant par exemple cette présetation, l'excellent ouvrage "Processeurs arm - architecture et langage d'assemblage" de Jacques Jorda ou le livre "The Definitive Guide to the ARM Cortex-M3, Second Edition" de Joseph Yiu.

Outils de développement

Qui dit nouvelle architecture, dit nouveaux outils... Pour commencer, le compilateur : place à la toolchain ARM-GCC. Maintenue par les développeurs de AVR-GCC, elle permet de faire du bare-metal (oh-yeah!), comprenez du code indépendant de la plateforme. Pour la culture, voici une exlication du nom du compilateur "arm-none-eabi" :

Basically, "arm-none-eabi" means:

- 'arm' we are building for the ARM architecture (ARM, Thumb, Thumb-2, etc).
- 'none' means we are not building for a specific operating system. or 'bare-metal' (...)
- 'eabi' says we're going to use the ARM standard EABI, which describes how binary files (libraries, etc) are stored (the actual file formats), and calling conventions, register usage, etc. This makes libraries from different parties compatible with each other and your code. ABI's and EABI's standardize our compiled code.. (...)

source : http://kunen.org/uC/gnu_tool.html

Qu'en est-il du code lui-même? Là où les plateformes AVR supportent très bien le C++, les processeurs ARM préfèrent nettement le C car ils sont bien plus optimisés pour gérer ce langage. Qui plus est, la plupart des améliorations apportées par le C++ (orienté objet...) ne sont que très peu utilisées dans cet environnement et ne justifient donc pas (pour le moment) son utilisation.

Des librairies et frameworks permettent également de faciliter la prise en main du code : CMSIS, une couche d'abstration de moyen niveau fournie par ARM (donc "vendor-independant"), ou bien Atmel Software Framework, cette fois-ci proposé par le fabricant Atmel dans son environnement de développement, qui apporte notamment une tonne d'exemples et de bonnes pratiques.

Débugger un ARM

La nouvelle gestion de la mémoire du processeur ainsi que les nouvelles possibilités d'observation du code exécuté requièrent forcément de nouveaux outils pour débugger votre programme. Adieu donc notre bel AVR-Dragon vu précédemment. Même s'il est heureusement toujours possible d'utiliser une interface JTAG, il vous faudra investir dans par exemple ce J-Link de Segger :

Pourquoi celui-ci en particulier? En effet, il existe également le SAM-ICE de Atmel. Mais je pense que lorsque vous aurez jeté un oeil aux prix pratiqués, la version Education du JLink fera pencher la balance! Surtout qu'au final les fonctionnalités sont quasi-identiques, l'un n'étant qu'une déclinaison de l'autre...

Pour la commande, je me permets de vous conseiller le site Mouser, très pro et très rapide pour la livraison. Dernier point important, même si l'Arduino Due dispose bien d'une interface JTAG 2x5 broches, elle est de taille réduite! Et les debuggers quant à eux utilisent des interfaces 2x10 broches. Il faudra vous procurer l'adaptateur correspondant : 10-PIN TO 20-PIN JTAG ADAPTER.

Vous voilà bien armés pour démarrer... La suite au prochain épisode! Have fun!

Sources :


Fichier(s) joint(s) :



Logstash et MongoDB en 10 minutes

Lorsqu'il s'agit de mettre en place des outils de gestion de logs applicatifs, Logstash est souvent mis en avant.

Il est souvent proposé en combinaison avec d'autres utilitaires comme ElasticSearch et Kibana, qui, bien que remarquables, posent deux soucis :

  1. l'effet "usine à gaz" : en effet, 3 outils à prendre en main d'un coup et à déployer, cela peut paraître intimidant.
  2. "Quels sont mes besoins ?" : ces outils sont essentiellement destinés à la création/analyse de statistiques extraites des informations loggées.

Dans l'exemple que j'ai choisi ici, la structure des fichiers de logs ne permet pas ce genre d'utilisation : en effet, une ligne de log en elle-même ne comporte que peu d'information (entrée dans un service, appel d'une procédure stockée...). Mais c'est plutôt en ensemble de lignes qui permet de reconstituer un scénario dans l'application : ce que l'utilisateur a fait, par où est passé le code, etc. En somme une analyse contextuelle (verticale) des logs plutôt que du contenu (horizontale).

Nous allons donc voir comment il est tout de même possible d'utiliser Logstash et MongoDB pour créer une application facilitant l'analyse de fichiers de logs volumineux.

Tout d'abord, pourquoi ces outils?

Logstash : il est prévu pour traiter des volumes de données importants, de manière efficace, même pour des fichiers au contenu spécifique grâce à son moteur d'expressions régulières Grok. Sa configuration est également très simple car il embarque des composants tout prêts, comme l'accès aux bases MongoDB.

MongoDB : NoSQL pour une mise en place rapide (schemaless orientée document), sans configuration, gérant également des grands volumes de données (au format JSON, utile pour les applis web).

C'est parti, mise en place!

Logstash

Il se présente sous la forme d'un Jar autonome exécutable et ne nécessite qu'un fichier de configuration. En voici un exemple, l'explication par la suite :


input {
  file {
    type => "serveur"
    path => "C:/log/serveur_*.log"
  }

  file {
    type => "client"
    path => "C:/log/client_*.log"
  }
}

filter {
  grok {
    match => [ "message", "%{DATESTAMP:date} %{LOGLEVEL:level} ?(<context>[a-Z0-9_]*) %{GREEDYDATA:message}" ]
  }
}

output {
  mongodb {
    collection => "collec-%{type}"
    database   => "local"
    uri        => "mongodb://localhost"
  }
}

En détails :

  • Les entrées (input) de type "fichier" :
    • type : une étiquette qui permet de différencier nos sources de fichiers (logs serveur d'un côté et client de l'autre)
    • path : le chemin d'accès, utilisant des wildcards
  • filter : la traitement a effectuer sur les entrées, ici un découpage via une expression régulière Grok
    • %{DATESTAMP:date} : on crée un champ nommé "date" constitué d'un timestamp, quel que soit le format (trop fort!)
    • %{LOGLEVEL:method} : un champ nommé "level" contenant les constantes classique de niveau de log : INFO, ERROR...
    • ?(<context>[a-Z0-9_]*) : un champ nommé "context" basé sur une expression régulière personnalisée
    • %{GREEDYDATA:message} : un champ nommé "message" contenat... tout et n'importe quoi!
  • Les sorties (output) de type "mongodb", avec les infos de connexion basiques. Petite astuce : il est possible de rendre dynamiques les éléments de configuration pour chaque ligne de log traitée.

Il est possible de se faciliter l'écriture de l'expression Grok avec Grok Debugger. Comme vous le voyez, très efficace!

Ces quelques lignes placées dans un fichier "ma-config.conf", Logstash se lance simplement avec :

java -jar logstash-1.3.2-flatjar.jar agent -f ma-config.conf

MongoDB

Là, rien de plus simple (la doc ici) :

  1. Lancer "mongod.exe" pour démarrer la base
  2. Optionnel : lancer "mongo.exe" pour ouvrir la console d'administration
  3. Dans notre exemple, pas besoin de créer une base de données, "local" est déjà créée par défaut.

Et voilà! Vous pouvez d'ores-et-déjà déposer des fichiers dans les répertoires indiqués, Logstash va lancer nos traitements pour fournir les documents correspondant dans notre base...

Comme je vous sens coupé dans votre élan, allons plus loin.

API Java

Bien évidemment, MongoDB vient avec une API Java très simple d'utilisation. Quelques lignes de code déployées par exemple sur un serveur Glassfish exposant des webservices REST via Jersey et vous avez une application web pour consulter et requêter dans vos fichiers de logs!

Petits exemples, avec JQuery datatables et un tableau de bord avec gridster :

Plus loin...

Ils vous est également possible de pousser un peu plus loin ce système en utilisant cette extension de log4j, log4mongo, afin d'écrire vos logs en temps réel dans la base MongoDB depuis votre application!


Fichier(s) joint(s) :



Introduction au NoSQL avec Apache CouchDB

Je vais vous montrer comment prendre en main facilement une base de données NoSQL, grâce à un exemple simple.

Nous allons créer une petite application utilisant Ektorp, une API de mapping par annotations et CouchApp, un utilitaire pour déployer des applications HTML5.

Création des données

Pour commencer, il suffit de créer un simple POJO représentant un objet métier. Comme illustré dans la documentation Ektorp ici, il n'est même pas nécessaire de mapper les attributs à enregistrer. C'est même le contraire, Ektorp part du principe que tous les attributs seront sauvegardés sauf ceux explicitement annotés comme ignorés! Un bon gain de temps...

Ensuite, quelques lignes suffisent pour implémenter les fonctionnalités primaires de CRUD :

Here's how a SofaRepository implemented with the generic repository looks like

public class SofaRepository extends CouchDbRepositorySupport {

    public SofaRepository(CouchDbConnector db) {
        super(Sofa.class, db);
    }

}

This repository will have the following methods "out of the box":

SofaRepository repo = new SofaRepository(db);

repo.add(Sofa s);
repo.contains("doc_id");
Sofa sofa = repo.get("doc_id");
repo.update(Sofa s);
repo.remove(Sofa s);
List repo.getAll();

A ce stade, nous avons déjà de quoi mettre en place une moulinette pour insérer des données. Une fois ceci fait, il est possible de consulter le contenu de la base avec Futon, une interface web fournie par CouchDB, à l'adresse http://localhost:5984/_utils/ :

Initier l'application web

CouchDB propose une interface REST très pratique. Ce qui signifie que n'importe quel outil effectuant des requêtes HTTP peut interroger directement la base, y compris un navigateur et... Ajax!

Seulement, comme certains yeux affûtés l'auront remarqué, CouchDB démarre par défaut sur le port 5984, ce qui rend impossible les accès directs via Ajax, par raison de sécurité. Si vous n'avez pas la main sur le serveur hébergeant la base afin de configurer le mécanisme HTTP "CORS", comme indiqué sur cette page, il vous faudra passer par CouchApp afin de déployer votre application sur le serveur interne de CouchDB (comme c'est le cas de Futon).

Un tutoriel très concis explique comment générer et déployer une application basique :

En réalité, tous le fichiers composant cette application sont des documents au sens NoSQL que CouchDB gère comme tels. Tous les fichiers "statiques" (images, css, js...) sont des "_attachments".

Lister les documents

Imaginons que nos documents possèdent un attribut "date", par lequel nous voulons les filtrer, entre une date de début et une de fin.

Nous allons commencer par créer une vue, qui va nous retourner la liste des documents identifiés par une clé issue de notre champ "date".

Ceci se fait par l'intermédiaire des fonctions view, fonctions Javascript pures appelées pour chaque document, pouvant servir de filtre (le cas échéant). Créons par exemple un fichier "by_date.js" dans le répertoire "/views/by_date/" de notre application. Voici donc un exemple de ce que nous cherchons :


function(doc) {
        if(doc.date) {
                emit(doc.date,doc);
        }
} 

Le résultat de notre fonction est accessible à l'adresse (objets JSON) : GET /[db]/_design/[appName]/_view/by_date/

{
  "total_rows": 2,
  "offset": 0,
  "rows": [
    {
      "key": "2014/01/15 15:52:20",
      "value": {...}
    },

    {
      "key": "2014/01/30 18:04:11",
      "value": {...}
    }

  ]
}

Utiliser JQuery

Maintenant nous pouvons compléter le fichier "index.html" (situé dans "_attachments") afin d'ajouter des fonctionnalités. Mais il faut savoir que CouchDB dispose d'un plugin JQuery pour faciliter les interactions. Par exemple, pour interroger notre vue :


$.couch.db("[db]").view("[appName]/by_date", {
    success: function(data) {
        console.log(data);
    },
    error: function(status) {
        console.log(status);
    }
});

Enfin, pour ajouter simplement des filtres sur les dates, il suffit d'intégrer les mots clés : "[appName]/by_date?startkey=[date1]&endkey=[date2]"

Ainsi, avec REST et HTML5/Javascript, vous voilà équipé pour jouer avec CouchDB! :) Enjoy!

Sources :


Fichier(s) joint(s) :