Des nouvelles du projet LDAP Tool Box, la boîte à outils pour les annuaires LDAP

Le projet LDAP Tool Box rassemble différents outils pour aider les administrateurs LDAP, que ce soit des paquets OpenLDAP, des scripts de supervision ou encore des applications Web. L’objectif est de simplifier la vie des administrateurs LDAP, car comme l’indique la description du projet :

Même les administrateurs LDAP ont besoin d’aide

Tous les outils proposés sont sous licence libre et disponibles sur GitHub.

Supervision

Des greffons de supervision sont fournis pour Nagios (ou tout autre logiciel compatible) et Cacti. Ils permettent par exemple de vérifier le statut de la réplication OpenLDAP, de collecter les statistiques sur les opérations LDAP ou encore surveiller le taux de remplissage d’une base MDB.

16c97f6538278bee29e8b9de1aac3516-media-593x381

Des configurations sont également disponibles pour la pile logicielle ELK (ElasticSearch, Logstash, Kibana) afin de tracer en temps réel l’utilisation des annuaires OpenLDAP.

openldap-elk-ldap-operations

Paquets OpenLDAP

OpenLDAP est le serveur LDAP le plus répandu, très proche du standard et offrant d’excellentes performances. Il est présent dans la plupart des distributions GNU/Linux mais celles-ci font souvent des choix de compilations non avalisées par les développeurs du projet, l’exemple le plus représentatif étant la bibliothèque SSL/TLS.

Le projet LDAP Tool Box fournit des paquets pour les environnements Debian/Ubuntu et Red Hat/CentOS. Ils sont compilés avec OpenSSL et embarquent les overlays officiels ainsi que certains overlays contribués comme « lastbind » et « smbk5pwd ».

Ils offrent également des modules spécifiques de gestion de la qualité des mots de passe comme check_password et ppm. Un script de gestion du service est installé par défaut, permettant les sauvegardes/restaurations des données et de la configuration.

Interface de gestion du mot de passe

L’application Self Service Password permet de fournir aux utilisateurs une interface simple pour changer son mot de passe, ou le réinitialiser quand celui-ci est perdu (ou expiré). Cette réinitialisation peut se faire :

  • Par un lien envoyé par mail
  • Par un code envoyé par SMS
  • Par la réponse à une question

Cette interface est compatible avec différents annuaires LDAP dont Active Directory et Samba 4.

ssp_1_0_change_password

La version 1.0 de ce logiciel est sortie en octobre 2016 (voir l’article sur LinuxFR).

Interface de consultation des données

L’application White Pages est une interface de consultation des données d’un annuaire LDAP, avec les fonctionnalités suivantes :

  • Recherche rapide : une invite de recherche dans le menu permet de retrouver directement des entrées à partir du nom, du prénom, du mail ou de l’identifiant
  • Recherche avancée : un formulaire liste les différents attributs sur lesquels une recherche peut être faite
  • Trombinoscope : toutes les entrées de l’annuaire sont affichées avec leur photo

Ces fonctions peuvent être activées/désactivées et les attributs cherchés ou affichés sont configurables. L’interface peut donc être utilisée avec n’importe quel annuaire LDAP.

wp_0_1_full_display

La version 0.1 de ce logiciel est sortie en novembre 2016 (voir l’article sur LinuxFR).

LDAPCon 2015: la gestion d’identité LDAP de Windows à Linux en passant par le « cloud »

Pour la première fois, cette année, Savoir-faire Linux sera présent à titre de commanditaire à la conférence LDAPCon 2015, qui se tiendra du 11 au 13 novembre à l’Université d’Édimbourg, en Écosse. Il s’agit de la cinquième conférence internationale sur LDAP, et plus largement sur les services d’annuaires et la gestion des identités :

LDAPCon2015

Aujourd’hui, LDAP c’est:

  • Un protocole de troisième génération révisé en 2006.
  • Un écosystème ouvert et interopérable de serveurs commerciaux (Novell, Apple, Microsoft, IBM, Oracle, etc.) et open source (Apache Directory Server, OpenLDAP, 389 DS, OpenDJ, etc.).
  • De nombreux logiciels libres et projets open source dérivés (Apache Directory Studio, Fusion Directory, LDAP Toolbox, LemonLDAP::NG, LDAP Synchronization Connector, phpLDAPadmin, etc.)
  • Un standard d’authentification supporté par un nombre astronomique de logiciels libres ou propriétaires, des clients lourds comme des applications web.

« LDAP est la clé d’intéropérabilité de l’identification »

Pour Jonathan Le Lous, « la gestion d’identité a toujours été la clé, le nœud central de la communication au niveau des systèmes d’information et cela reste un enjeu critique chez tous les clients avec lesquels nous travaillons aujourd’hui. » Des entreprises technologiques aux banques en passant par les compagnies de transports ou n’importe quel autre secteur d’activités, en effet, les infrastructures TI sont de plus en plus et massivement tournées vers les environnements Linux et infonuagique. « La question essentielle, c’est : comment moi, qui gère mes utilisateurs dans des environnement Windows, puis-je leur permettre de communiquer ouvertement et de façon sécuritaire dans les environnements Linux, Mac OS et infonuagique? LDAP joue un rôle essentiel pour faire communiquer tous ces environnements. »

Clement OudotClément Oudot, spécialiste de la sécurité et des infrastructure et contributeur du projet LemonLDAP::NG, sera le délégué de Savoir-faire Linux à LDAPCon 2015. Il suivra pour nous le programme de la conférence et animera lui-même une présentation du protocole OpenID Connect.

« Cet événement est très important pour la communauté LDAP, qui se retrouve tous les deux ans pour présenter les nouveautés de ses logiciels et faire vivre ce standard, explique-t-il. Les sujets sont assez variés, de l’introduction à des nouveaux protocoles à des retours d’expériences sur des architectures critiques. J’ai pour ma part hâte de suivre la conférence d’Howard Chu qui parlera des dernières avancées d’OpenLDAP. »

​​​​​Administrateurs de systèmes Red Hat: obtenez votre certification!

red_hat_partnerLe Centre de formation de Savoir-faire Linux, partenaire certifié Red Hat, organise en septembre prochain une session de formation RHCSA (Administrateur système certifié Red Hat) qui donnera lieu à une certification.

Cette formation s’adresse aux administrateurs de systèmes qui ont de 1 à 3 ans d’expérience à temps plein en tant qu’administrateur Linux.

Pour plus détails concernant les prix et conditions de cette formation, contactez-moi par courriel (formation(a)savoirfairelinux.com) ou par téléphone (514-276-5468 poste 348). Merci!

Photo de la salle de formation Aaron Swartz.

Analyseur logique : visualiser la latence entre deux signaux numériques en temps réel avec sigrok et matplotlib

Salae Logic AnalyzerDans mon billet précédent, je décrivais comment manipuler avec Python les données extraites d’un analyseur logique Saleae afin de visualiser la latence entre deux signaux numériques et en déduire la gigue temporelle de l’un d’entre eux. En conclusion, j’émettais l’hypothèse que le SDK de Saleae pourrait nous permettre de récupérer des informations sur les signaux en temps réel et non plus par l’intermédiaire d’un fichier CSV exporté. Mais au final, c’est un logiciel libre plus générique qui nous a fourni la meilleure solution.

La solution de Saleae et son SDK

Saleae propose aux développeurs deux solutions :

  • une API (en bêta à l’heure actuelle) qui permet uniquement de contrôler leur logiciel de visualisation depuis une socket.
  • un SDK qui permet d’étendre leur logiciel et écrire son propre analyseur de protocole.

Nous aurions pu a priori nous intéresser au SDK et écrire un analyseur qui exporte à la volée la latence entre deux signaux définis, mais cette solution est dépendante du logiciel de Saleae et de son évolution, ce qui nous garantit une très faible pérennité. De plus, le logiciel de visualisation de Saleae n’a pas pour vocation d’être utilisé avec un autre analyseur logique que ceux qu’ils produisent. Nous cherchions donc une solution plus générique et permanente.

Sigrok: une suite logicielle open-source pour les analyseurs logiques

C’est lors d’une discussion entre collègues qu’Emeric Vigier m’a mit sur la piste d’un logiciel nommé Sigrok. Ayant pour objectif de fournir une solution libre et générique pour les analyseurs logiques, Sigrok est une suite logicielle permettant d’extraire les données collectées par divers types d’analyseurs et de les afficher, voire de les traiter, à l’aide de décodeurs de protocole.

Cette suite se compose de plusieurs sous-projets :

  • libsigrok : une librairie écrite en C qui standardise l’accès aux pilotes des différents analyseurs.
  • libsigrokdecode : une librairie écrite en C qui fournit une API pour le décodage de protocole. Les décodeurs sont écrits en Python (>= 3).
  • Sigrok-cli : une interface en ligne de commande pour manipuler Sigrok.
  • PulseView : une interface graphique en Qt pour manipuler Sigrok.
  • Fx2lafw : Sigrok fournit également une implémentation open source du firmware des puces Cypress FX2 qui est la puce utilisé entre autre par Saleae dans toutes les déclinaisons de ses analyseurs logiques hormis le Logic Pro 16. Ce firmware permet de programmer le FPGA pour qu’il fonctionne comme un simple analyseur logique matériel.

Sigrok : exemple d’utilisation

Un exemple de cette suite mise bout à bout sera plus parlant qu’un long discours. Nous allons donc utiliser PulseView pour capturer et visualiser les signaux d’un analyseur logique.

À l’ouverture PulseView dans sa version 0.2.0 ressemble à ceci :
[cliquez sur les images pour les agrandir]

pulseview01

Nous allons commencer une capture en laissant le périphérique de démonstration générer des signaux aléatoires :

pulseview02

Nous allons ensuite ajouter un décodeur assez simple qui calcule le rapport cyclique d’un signal numérique :

pulseview03

Chaque décodeur dispose de ses options. Dans notre cas nous allons simplement définir le canal sur lequel nous voulons appliquer le décodeur :

pulseview04

Le calcul du rapport cyclique est appliqué sur le canal et reporté directement dans l’interface de PulseView :

pulseview05

Sigrok : écriture d’un décodeur pour le calcul de la latence entre deux signaux numériques

Ce qui suit est basé sur la libsigrok et la libsigrokdecode dans leurs version 0.3.0.
Chaque décodeur de protocole est un module Python qui possède son propre sous-répertoire dans le répertoire de décodeurs libsigrokdecode.

Un décodeur se compose de deux fichiers :

  • __init__.py : Ce fichier est requis pour l’initialisation du décodeur et contient une simple description du protocole.
  • pd.py : Ce fichier contient des métadonnées sur le décodeur, et son code, la plupart du temps implémenté dans la méthode decode()

Nous allons nous intéresser au fichier pd.py contenant le code de notre décodeur. L’API de la libsigrokdecode nous fournit toutes les informations sur les signaux capturés.

Rappelons nous que pour calculer la latence entre deux signaux numériques, notre logique est de considérer l’un des signaux comme une horloge de référence, et l’autre comme une résultante de ce premier. Les deux étant liés, à chaque transition d’état du signal d’horloge, le signal résultant changera aussi d’état avec une latence plus ou moins variable. Nous parlerons de la fluctuation de cette latence plus loin.

Les options du décodeur

La première chose à faire est donc de permettre à notre décodeur de définir quel canal correspond au signal d’horloge et quel canal correspond au signal résultant. La classe du décodeur permet justement de définir quelques attributs précisant ses options, notamment celle permettant à l’utilisateur de choisir les canaux :

class Decoder(srd.Decoder):
   # ...
   channels = (
      {'id': 'clk', 'name': 'Clock', 'desc': 'Clock reference channel'},
      {'id': 'sig', 'name': 'Resulting signal', 'desc': 'Resulting signal controlled by the clock'},
   )
   ...

La méthode decode()

La seconde étape va être d’implémenter le contenu de la méthode decode(). Cette méthode est appelé par la libsigrokdecode chaque fois qu’un nouveau bloc de données à traiter est disponible.

Chaque bloc est en fait un échantillon dépendant de la fréquence d’échantillonnage de nos signaux. Par exemple pour une valeur d’échantillonnage de 100 Hz, nous obtiendrons 100 blocs de données par seconde. Ces blocs de données contiennent le numéro de l’échantillon actuel, ainsi que l’état des différents signaux à cet instant.

À partir de ces informations, il est ainsi très facile d’implémenter une machine à état qui va noter à quel échantillon à eu lieu la transition du signal d’horloge et celle du signal résultant. Le nombre d’échantillons écoulés entre les deux, multiplié par la valeur de la fréquence d’échantillonnage, va nous donner la latence exprimée en secondes.

Dans sa version simplifiée, notre machine à état ressemble à cela :

def decode(self, ss, es, data):

    self.oldpin, (clk, sig) = pins, pins

    # State machine:
    # For each sample we can move 2 steps forward in the state machine.
    while True:
        # Clock state has the lead.
        if self.state == 'CLK':
            if self.clk_start == self.samplenum:
                # Clock transition already treated.
                # We have done everything we can with this sample.
                break
            else:
                if self.clk_edge(self.oldclk, clk) is True:
                    # Clock edge found.
                    # We note the sample and move to the next state.
                    self.clk_start = self.samplenum
                    self.state = 'SIG'

        if self.state == 'SIG':
            if self.sig_start == self.samplenum:
                # Signal transition already treated.
                # We have done everything we can with this sample.
                break
            else:
                if self.sig_edge(self.oldsig, sig) is True:
                    # Signal edge found.
                    # We note the sample, calculate the latency
                    # and move to the next state.
                    self.sig_start = self.samplenum
                    self.state = 'CLK'
                    # Calculate and report the latency.
                    self.putx((self.sig_start - self.clk_start) / self.samplerate)

    # Save current CLK/SIG values for the next round.
    self.oldclk, self.oldsig = clk, sig

Les sorties de notre décodeur

La libsigrokdecode propose différentes façons de remonter les informations de notre décodeur. Une méthode register() permet d’enregistrer les sorties que notre décodeur génère. Chaque sortie a un type défini; par exemple, le type OUTPUT_ANN est utilisé pour définir une sortie de type annotation qui sera représentée dans PulseView par les boîtes graphiques que nous avons vues précédemment avec le décodeur de rapport cyclique.

Pour notre décodeur, nous voulons principalement deux types de sorties:

  • une sortie de type annotation (OUTPUT_ANN) pour visualiser la latence mise en forme dans PulseView,
  • et une sortie de type binaire (OUTPUT_BINARY) pour sortir les valeurs de latence brute sans mise en forme afin d’être analysé.

La version finale de notre décodeur inclut également une sortie de type métadonnée (OUTPUT_META) rapportant des statistiques sur les signaux manqués, mais ce n’est pas important ici.

Nos deux sorties donc sont définies dans notre décodeur comme suit :

self.out_ann = self.register(srd.OUTPUT_ANN)
self.out_bin = self.register(srd.OUTPUT_BINARY)

Pour reporter des informations sur ces sorties nous utilisons la méthode put() qui a pour prototype :

put(debut_echantillon, fin_echantillon, type_de_sortie, donnees)

type_de_sortie est une de nos deux méthodes enregistrées au préalable (out_ann, out_bin).

Pour reporter la latence entre deux signaux en utilisant le type annotation nous pourrons par exemple écrire :

put(clk_start, sig_start, out_ann, [0, [ma_latence]])

Les résultats obtenus avec notre décodeur de latence

Voici un exemple des résultats obtenus avec notre décodeur dans PulseView :
pulseview06

Et les résultats obtenus avec sigrok-cli dans sa version 0.5.0 et la sortie binaire pour avoir les valeurs de latence brute :
sigrok-cli

Visualisation des données en temps réel pour en déduire la gigue temporelle

Comme nous venons de le voir, sigrok-cli nous permet de sortir les valeurs de latence brute sur la console en temps réel. Nous allons maintenant nous intéresser aux variations de cette latence. En effet, rien ne nous garantit que la latence entre ces deux signaux sera constante.

Considérant que le signal d’horloge est périodique et sans fluctuation, en théorie, le signal résultant devrait l’être aussi et la latence entre ces deux signaux devraient être constante. Si ce n’est pas le cas, c’est qu’il y a une fluctuation de cette latence que nous avions décrite dans le précédent article comme la gigue temporelle d’un signal, et c’est ce que nous voulons visualiser maintenant.

Nous pouvons donc imaginer récupérer ces valeurs à la volée et les mettre en forme dans un graphique. Pour cela, nous allons encore une fois écrire un script en Python.

Je me suis intéressé à la librairie matplotlib et son module d’animation, qui propose une méthode FuncAnimation() permettant de définir une fonction à appeler pour mettre à jour le graphique à chaque fois que de nouvelles données sont disponibles. Cette fonction prend en paramètre la figure sur laquelle nous travaillons, la fonction d’animation à appeler et l’ensemble de données à traiter.

anim = animation.FuncAnimation(fig, animate, frames=data_gen)

Ces données peuvent être de la forme d’un générateur Python qui va très bien de paire avec la lecture d’un flux de données (merci à Guillaume Roguez pour m’avoir présenté ce type d’objet).

Ainsi à chaque fois qu’une nouvelle latence sera écrite dans le flux, notre générateur récupérera une nouvelle donnée et la fonction d’animation sera appelée.

Voici à quoi ressemble le code du générateur :

# The data generator take its
# input from file or stdin
def data_gen():
    while True:
        line = fd.readline().strip()
        if line:
            yield line

Notre fonction d’animation va, si besoin est, mettre à jour l’abscisse pour visualiser toutes les latences, et ajouter la nouvelle valeur fournie par le générateur.

# The update graph function
def animate(data):
    global x_max, x_min, x, y

    try:
        # we must recalculate the abscissa range
        x_new = float(data)
        if x_max is None or x_new > x_max:
            x_max = x_new
        if x_min is None or x_new < x_min:
            x_min = x_new
        ax.set_xlim(x_min, x_max)

        # add the new plot coordinate
        x.append(x_new)
        y.append(0)
        line.set_data(x, y)

        return line,

    except KeyboardInterrupt:
        print("leaving...")

Finalement, il nous reste plus qu’à éxécuter sigrok-cli avec notre décodeur de latence et à récupérer ces valeurs dans notre script de visualisation.

sigrok-cli -d fx2lafw --config samplerate=24MHz --samples 2M -C 1,2 -P jitter:clk=1:sig=2 -B jitter | rt-draw.py

Ce qui nous donne le résultat final suivant et qui nous permet de visualiser la gigue temporelle du signal résultant :

rt_draw

Travailler les données d’un analyseur logique Saleae en Python

Salae Logic AnalyzerRécemment, j’ai été amené à devoir prendre des mesures de temps très précises pour répondre à une problématique de contrainte temps-réel. Sur un SoC particulier avec un système Linux dessus, la question était de savoir quelle était la fluctuation de la latence entre le déclenchement d’un événement matériel et la réponse d’une tâche logicielle associée à cet événement.

La gigue ou fluctuation du signal

Qu’est ce que la gigue ou jitter en anglais ? Prenons, par exemple, un événement avec une fréquence parfaite — disons l’heure à laquelle sonne mon réveil — et un autre événement moins parfait qui lui est associé, comme le moment où j’ouvre les yeux. Chaque jour, l’heure de mon réveil reste la même. En revanche, le moment où j’ouvre les yeux pour éteindre mon réveil varie… au désespoir de mon patron. :/

Cette fluctuation est la gigue. Dans ce cas, il s’agit d’une gigue temporelle, comme dans ma problématique.

Mesurer la gigue avec un oscilloscope

Typiquement, mesurer la latence entre deux événements matériels se fait très bien avec un oscilloscope à deux canaux. Il suffit de savoir où sonder, sur notre circuit, ces événements. Après quelques réglages, on peut visualiser la latence et, notamment, avoir une persistance sur chaque prise de mesure qui va nous donner une enveloppe de cette fluctuation.

Ma première démarche à donc été d’associer à ma tâche logicielle un autre événement matériel pour m’aider à prendre ces mesures. Mon événement matériel initial était une interruption et je savais où la sonder sur le SoC. À celui-ci, je réponds par un autre événement matériel qui consiste à faire commuter une GPIO que je sais également où sonder sur le SoC.

Ainsi à chaque fois que l’interruption se déclenche, la GPIO change d’état.

Ce qui nous donnera par exemple sur un oscilloscope quelque chose comme ça :

jitter_simulated

Avec en jaune l’interruption qui est réguliére, et en vert la fluctuation des temps de réponse de la GPIO.

Il ne me restait plus qu’à prendre mes mesures avec un oscilloscope. Mais là c’est le drame… je ne disposais pas d’oscilloscope suffisamment récent pour avoir une enveloppe consistante de mes signaux; faute de mémoire, je pouvais conserver tout au plus les 32 dernières mesures et je ne pouvais donc pas voir ma fluctuation sur de longues périodes.

Le seul autre équipement à ma disposition pour prendre des mesures était un analyseur logique Saleae Logic. Bien que très pratique, ce dernier ne me permettait pas de visualiser ma fluctuation du signal.

Comment visualiser la gigue avec l’analyseur logique de Saleae ?

Avec cet analyseur, ce que je peux obtenir comme résultat ressemble à cela:

logic_output

En zoomant on voit bien la latence entre le déclenchement de l’interruption (en haut) et la commutation de ma GPIO (en bas):

logic_output_zoom

Ce qui m’intéresse, c’est mesurer étant le temps entre le drapeau 1 et le drapeau 2 pour chaque interruption et le visualiser de façon facilement compréhensible, comme ceci:

logic_output_zoom_flag

Mais je ne peux pas faire ça à la main pour chaque interruption et recommencer à chaque prise de mesure.

C’est en discutant de mon problème, qu’un collègue et ami (Emeric Vigier, pour ne pas le nommer), me pointa à juste titre sur la fonction d’exportation de ce logiciel. Et, effectivement, l’ensemble des informations qui me sont nécessaires sont à ma disposition, le dernier problème étant juste qu’elles ne sont pas organisées comme je le désire.

Visualisation des données : du CSV au Graphique

Donc, comme précisé précédemment, l’analyseur logique de Saleae me permet d’exporter mes données au format CSV sous la forme:

Time[s], Interrupt Trigger, Gpio 0
0, 0, 1
0.00258295833333333, 1, 1
0.00258408333333333, 0, 1
0.002633, 0, 0
0.00758641666666667, 1, 0
0.00758758333333333, 0, 0
0.007629625, 0, 1
0.0125899166666667, 1, 1
...

La première valeur me donnant le temps en seconde, la deuxième l’état de mon interruption (0 = bas, 1 = haut), et la troisième l’état de ma GPIO. Donc, l’algorithme me permettant de récupérer ma latence sera:

Pour chaque ligne,

  1. Si mon interruption passe de 0 à 1, Alors je note son temps dans A
  2. Si j’ai A et que ma GPIO change d’état, Alors je note son temps dans B
  3. Si j’ai A et B, Alors je soustrais A à B et je garde le résultat dans le tableau C

Le tableau C contiendra ainsi l’ensemble des temps de latence. En quelques lignes de Python, cela donne le code suivant :

STEP_INTERRUPT  = 0
STEP_GPIO       = 1

with open(file_csv, 'r') as csvfile:
    cvsreader = csv.reader(csvfile, delimiter=',')

    # the first entry give us the initial state
    init_state      = next(cvsreader)
    interrupt_state = init_state[interrupt]
    gpio_state      = init_state[gpio]
    step            = STEP_INTERRUPT

    for row in cvsreader:

        # The first step is to find an interrupt edge
        if step == STEP_INTERRUPT:
            if (interrupt_state == 0 and int(row[interrupt]) == 1):
                interrupt_value = float(row[0])
                step = STEP_GPIO

        # The second step is to find a gpio commutation
        elif step == STEP_GPIO:
            if (gpio_state != int(row[gpio])):
                gpio_value = float(row[0])
               
                # here we know we have an interrupt edge value
                # and the gpio commutation time
                # we can take the delay mesure between the two
                delay = gpio_value - interrupt_value

                # store the delay value in millisecond.
                dic.append((delay * 1000, 0.5))

                # we can now reinit the step
                step = STEP_INTERRUPT

        interrupt_state = int(row[interrupt])
        gpio_state      = int(row[gpio])

À ce stade, je dispose de l’ensemble de mes latences, maintenant j’aimerais bien pouvoir les mettre en forme avec quelque chose de plus parlant — et ce n’est pas comme si l’on manquait de librairies pour réaliser des graphiques dans Python. Donc, prenons en une au hasard, et dessinons!

J’ai choisi la librairie pygal, qui permet de dessiner des points dans un espace à 2 dimensions. Il m’a suffit de rajouter ces quatre lignes de Python à mon script:

xy_chart = pygal.XY(stroke=False, fill=False, show_y_labels=False, legend_at_bottom=True, show_dots=True, dots_size=0.8, print_values=False)
xy_chart.title = 'Jiffies'
xy_chart.add('values in milli-second', sorted(dic))
xy_chart.render_to_png('jitter.png')

Et voilà le résultat tant désiré car nettement plus parlant:

jitter-with-pygal

On peut ainsi voir quelque chose qui se rapproche de notre première capture d’écran (l’exemple d’un oscilloscope) tout en économisant quelques milliers de dollars… cette fois, au grand bonheur de mon patron. 😉

Le script complet est disponible ici : github.com/sbourdelin/logic-jitter