Débogage et profilage

Une règle d’or en programmation est que le code ne fait pas ce que vous attendez de lui, mais ce que vous lui dites de faire. Combler ce fossé peut parfois s’avérer difficile. Dans ce cours, nous allons aborder des techniques utiles pour corriger les bugs et améliorer du code inefficace : le débogage et le profilage.

Débogage

Déboguer avec Printf et les logs

“L’outil de débogage le plus efficace reste une réflexion approfondie, associée à des instructions ‘print’ judicieusement placées” - Brian Kernighan, Unix for Beginners.

Une première approche pour déboguer un programme consiste à ajouter des instructions “print” autour de l’endroit où vous avez détecté le problème, et de continuer à itérer jusqu’à ce que vous ayez extrait suffisamment d’informations pour comprendre ce qui est à l’origine du problème.

Une deuxième approche consiste à utiliser des logs dans votre programme, au lieu d’instructions “print” ad hoc. Utiliser des logs est préférable aux instructions “print” classiques pour plusieurs raisons :

Voici un exemple de code qui log des messages :

$ python logger.py
# Sortie brute comme avec uniquement des "print"
$ python logger.py log
# Sortie formatée avec des logs
$ python logger.py log ERROR
# Ne print que les niveaus ERROR ou supérieurs
$ python logger.py color
# Sortie formatée avec des couleurs

L’une de mes astuces préférées pour rendre les logs plus lisibles est de leur attribuer un code couleur. Vous avez probablement déjà réalisé que votre terminal utilise des couleurs pour rendre les choses plus lisibles. Mais comment le fait-il ? Des programmes comme ls ou grep utilisent des caractères ANSI (escape codes), qui sont des séquences spéciales de caractères indiquant à l’interpréteur de commandes de changer la couleur de la sortie. Par exemple, l’exécution de echo -e "\e[38;2;255;0;0mCeci est rouge\e[0m" affiche le message Ceci est rouge en rouge dans votre terminal, à condition qu’il prenne en charge true color. Si votre terminal ne le supporte pas (par exemple Terminal.app de macOS), vous pouvez utiliser les escape codes plus universellement supportés pour 16 choix de couleurs, par exemple echo -e "\e[31;1mCeci est rouge\e[0m".

Le script suivant montre comment afficher plusieurs couleurs RGB dans votre terminal (à condition qu’il supporte true color).

#!/usr/bin/env bash
for R in $(seq 0 20 255); do
    for G in $(seq 0 20 255); do
        for B in $(seq 0 20 255); do
            printf "\e[38;2;${R};${G};${B}m█\e[0m";
        done
    done
done

Logs d’applications tierces

Au fur et à mesure que vous construisez des systèmes logiciels plus importants, vous rencontrerez très probablement des dépendances qui s’exécutent en tant que programmes distincts. Les serveurs web, les bases de données ou les messages brokers sont des exemples courants de ce type de dépendances. Lorsque vous interagissez avec ces systèmes, il est souvent nécessaire de lire leurs logs, car les messages d’erreur côté client peuvent ne pas suffire.

Heureusement, la plupart des programmes écrivent leurs propres logs quelque part dans votre système. Dans les systèmes UNIX, il est courant que les programmes écrivent leurs logs dans /var/log. Par exemple, le serveur web NGINX écrit ses logs dans /var/log/nginx. Plus récemment, les systèmes ont commencé à utiliser les logs système, qui est de plus en plus souvent l’endroit où vont tous les messages de log. La plupart des systèmes Linux (mais pas tous) utilisent systemd, un daemon système qui contrôle de nombreux éléments de votre système, tels que quels services sont activés et en cours d’exécution. systemd écrit les logs dans /var/log/journal dans un format spécial et vous pouvez utiliser la commande journalctl pour afficher les messages. De même, sur macOS, il y a toujours /var/log/system.log, mais un nombre croissant d’outils utilisent les logs système, qui peut être affiché avec log show. Sur la plupart des systèmes UNIX, vous pouvez également utiliser la commande dmesg pour accéder aux logs du noyau.

Pour écrire des logs dans les logs système, vous pouvez utiliser le programme shell logger. Voici un exemple d’utilisation de logger et de comment vérifier que le log a bien été écrit dans les logs système. De plus, la plupart des langages de programmation ont des “bindings” (sorte de lien qui permet à un language de programmation à communiquer avec un autre service) qui permettent d’écrire des données dans les logs système.

logger "Hello Logs"
# Sur macOS
log show --last 1m | grep Hello
# Sur Linux
journalctl --since "1m ago" | grep Hello

Comme nous l’avons vu dans le cours sur le traitement des données, les logs peuvent être assez verbeux et nécessitent un certain niveau de traitement et de filtrage pour en tirer les informations souhaitées. Si vous vous retrouvez à filtrer lourdement journalctl et log show, vous pouvez envisager d’utiliser leurs flags, qui peuvent effectuer un premier passage de filtrage de leur sortie. Il existe également des outils comme lnav, qui améliorent la présentation et la navigation dans les fichiers de log.

Débogueurs

Lorsque le débogage avec printf ne suffit pas, vous devrez utiliser un débogueur. Les débogueurs sont des programmes qui vous permettent d’interagir avec l’exécution d’un programme. Ils permettent :

De nombreux langages de programmation sont dotés d’une forme ou d’une autre de débogueur. En Python, il s’agit du débogueur Python pdb.

Voici une brève description de certaines des commandes que pdb prend en charge :

Voyons un exemple d’utilisation de pdb pour corriger le code python bogué suivant. (Voir la vidéo du cours).

def bubble_sort(arr):
    n = len(arr)
    for i in range(n):
        for j in range(n):
            if arr[j] > arr[j+1]:
                arr[j] = arr[j+1]
                arr[j+1] = arr[j]
    return arr

print(bubble_sort([4, 2, 1, 8, 7, 6]))

Notez que puisque Python est un langage interprété, nous pouvons utiliser le shell pdb pour exécuter des commandes et des instructions. ipdb est un pdb amélioré qui utilise le REPL IPython permettant la complétion de tabulation, la coloration syntaxique, de meilleures tracebacks et une meilleure introspection tout en conservant la même interface que le module pdb.

Pour de la programmation de plus bas niveau, vous voudrez probablement vous tourner vers gdb (et son amélioration plus intuitive pwndbg) et lldb. Ils sont optimisés pour le débogage en langage C mais vous permettront de sonder à peu près n’importe quel processus et d’obtenir son état actuel : registres, pile, compteur de programme, etc.

Outils spécialisés

Même si ce que vous essayez de déboguer est une boîte noire binaire, il existe des outils qui peuvent vous aider. Lorsque des programmes doivent effectuer des actions que seul le noyau peut réaliser, ils utilisent des appels système. Il existe des commandes qui vous permettent de tracer les appels système effectués par votre programme. Sous Linux, il existe strace et macOS et BSD possèdent dtrace. dtrace peut être difficile à utiliser parce qu’il utilise son propre langage D, mais il existe un wrapper appelé dtruss qui fournit une interface plus similaire à strace (plus de détails ici).

Voici quelques exemples d’utilisation de strace ou de dtruss pour afficher les traces des appels de système à stat pour une exécution de ls. Pour une plongée plus profonde dans strace, cet article et ce fanzine sont de bonnes lectures.

# Sur Linux
sudo strace -e lstat ls -l > /dev/null
# Sur macOS
sudo dtruss -t lstat64_extended ls -l > /dev/null

Dans certaines circonstances, il peut être nécessaire d’examiner les paquets réseau pour trouver le problème dans votre programme. Des outils comme tcpdump et Wireshark sont des analyseurs de paquets réseau qui vous permettent de lire le contenu des paquets réseau et de les filtrer en fonction de différents critères.

Pour le développement web, les outils de développement Chrome/Firefox sont très pratiques. Ils proposent un grand nombre d’outils, notamment :

Analyse statique

Pour certains problèmes, il n’est pas nécessaire d’exécuter du code. Par exemple, en examinant attentivement un morceau de code, vous pouvez vous rendre compte que votre variable de boucle cache une variable déjà existante ou un nom de fonction, ou qu’un programme lit une variable avant de la définir. C’est là que les outils d’analyse statique entrent en jeu. Les programmes d’analyse statique prennent le code source en entrée et l’analysent à l’aide de règles prédéfinies pour en déterminer l’exactitude.

L’extrait Python suivant contient plusieurs erreurs. Tout d’abord, notre variable de boucle foo cache la définition précédente de la fonction foo. Nous avons également écrit baz au lieu de bar dans la dernière ligne, de sorte que le programme crashera après avoir effectué l’appel à sleep (qui prendra une minute).

import time

def foo():
    return 42

for foo in range(5):
    print(foo)
bar = 1
bar *= 0.2
time.sleep(60)
print(baz)

Les outils d’analyse statique peuvent identifier ce type de problèmes. Lorsque nous exécutons pyflakes sur le code, nous obtenons les erreurs liées aux deux bugs. mypy est un autre outil qui peut détecter les problèmes de vérification de type. Ici, mypy nous avertira que bar est initialement un int et qu’il est ensuite casté en float. Encore une fois, notez que tous ces problèmes ont été détectés sans avoir à exécuter le code.

$ pyflakes foobar.py
foobar.py:6: redefinition of unused 'foo' from line 3
foobar.py:11: undefined name 'baz'

$ mypy foobar.py
foobar.py:6: error: Incompatible types in assignment (expression has type "int", variable has type "Callable[[], Any]")
foobar.py:9: error: Incompatible types in assignment (expression has type "float", variable has type "int")
foobar.py:11: error: Name 'baz' is not defined
Found 3 errors in 1 file (checked 1 source file)

Dans le cours sur les outils du shell, nous avons abordé shellcheck, qui est un outil similaire pour les scripts shell.

La plupart des éditeurs et des IDE permettent d’afficher la sortie de ces outils dans l’éditeur lui-même, en mettant en évidence l’emplacement des avertissements et des erreurs. C’est ce que l’on appelle souvent le code linting et il peut également être utilisé pour afficher d’autres types de problèmes tels que les violations de style ou les constructions insécurisées.

Dans Vim, les plugins ale ou syntastic vous permettront de le faire. Pour Python, pylint et pep8 sont des exemples de linters stylistiques et bandit est un outil conçu pour trouver les problèmes de sécurité courants. Pour d’autres langages, des personnes ont rassemblé des listes complètes d’outils d’analyse statique utiles, comme Awesome Static Analysis (vous pouvez jeter un coup d’oeil à la section Writing) et pour les linters, il existe Awesome Linters.

Un outil complémentaire à l’analyse stylistique sont les formateurs de code, tel que black pour Python, gofmt pour Go, rustfmt pour Rust ou prettier pour JavaScript, HTML et CSS. Ces outils autoformatent votre code de manière à ce qu’il soit cohérent avec les modèles stylistiques courants pour le langage de programmation en question. Même si vous n’êtes pas disposé à donner le contrôle stylistique de votre code, la normalisation du format du code aidera les autres à lire votre code et vous permettra de mieux lire le code des autres (normalisé sur le plan stylistique).

Profilage

Même si votre code se comporte fonctionnellement comme vous l’attendez, ce n’est peut-être pas suffisant s’il utilise tout votre CPU ou votre mémoire lors de son exécution. Les cours d’algorithmique enseignent souvent la notation big O, mais pas la manière de trouver les points sensibles dans vos programmes. L’optimisation prématurée étant la racine de tous les maux, vous devriez vous familiariser avec les profileurs et les outils de monitoring. Ils vous aideront à comprendre quelles parties de votre programme prennent le plus de temps et/ou de ressources afin que vous puissiez vous concentrer sur l’optimisation de ces parties.

Timing

Comme dans le cas du débogage, dans de nombreux scénarios, il peut suffire d’imprimer le temps que votre code a mis entre deux points. Voici un exemple en Python utilisant le module time.

import time, random
n = random.randint(1, 10) * 100

# Get current time
start = time.time()

# Do some work
print("Sleeping for {} ms".format(n))
time.sleep(n/1000)

# Compute time between start and now
print(time.time() - start)

# Output
# Sleeping for 500 ms
# 0.5713930130004883

Cependant, le temps calculé par le programme peut être trompeur car votre ordinateur peut exécuter d’autres processus en même temps ou attendre que des événements se produisent. Il est courant que les outils fassent une distinction entre le temps réel (Real), le temps utilisateur (User) et le temps système (Sys). En général, User + Sys vous indique combien de temps votre processus a réellement passé dans le CPU (explication plus détaillée ici).

Par exemple, essayez d’exécuter une commande qui effectue une requête HTTP et rajouter time devant. Si vous disposez d’une connexion lente, vous obtiendrez peut-être un résultat comme celui présenté ci-dessous. Ici, la requête a mis plus de 2 secondes à se terminer, mais le processus n’a pris que 15 ms de temps utilisateur sur le CPU et 12 ms de temps CPU sur le noyau.

$ time curl https://missing.csail.mit.edu &> /dev/null
real    0m2.561s
user    0m0.015s
sys     0m0.012s

Profileurs

CPU

La plupart du temps, lorsque l’on parle de profileurs, il s’agit en fait de profileurs de CPU, qui sont les plus courants. Il existe deux types principaux de profileurs de CPU : les profileurs de trace et les profileurs d’échantillonnage. Les profileurs de trace enregistrent chaque appel de fonction effectué par votre programme, tandis que les profileurs d’échantillonnage sondent votre programme périodiquement (généralement toutes les millisecondes) et enregistrent la pile (stack) du programme. Ils utilisent ces enregistrements pour présenter des statistiques globales sur ce que votre programme a passé le plus de temps à faire. Voici un bon article d’introduction si vous voulez plus de détails sur ce sujet.

La plupart des langages de programmation disposent d’une sorte de profileur en ligne de commande que vous pouvez utiliser pour analyser votre code. Ils s’intègrent souvent à des IDE complets, mais pour ce cours, nous allons nous concentrer sur les outils en ligne de commande.

En Python, nous pouvons utiliser le module cProfile pour profiler le temps par appel de fonction. Voici un exemple simple qui implémente un grep rudimentaire en Python :

#!/usr/bin/env python

import sys, re

def grep(pattern, file):
    with open(file, 'r') as f:
        print(file)
        for i, line in enumerate(f.readlines()):
            pattern = re.compile(pattern)
            match = pattern.search(line)
            if match is not None:
                print("{}: {}".format(i, line), end="")

if __name__ == '__main__':
    times = int(sys.argv[1])
    pattern = sys.argv[2]
    for i in range(times):
        for file in sys.argv[3:]:
            grep(pattern, file)

Nous pouvons profiler ce code en utilisant la commande suivante. En analysant la sortie, nous pouvons voir que l’IO prend la plupart du temps et que la compilation de la regex prend également beaucoup de temps. Puisque la regex ne doit être compilée qu’une seule fois, nous pouvons l’exclure du for.

$ python -m cProfile -s tottime grep.py 1000 '^(import|\s*def)[^,]*$' *.py

[omitted program output]

 ncalls  tottime  percall  cumtime  percall filename:lineno(function)
     8000    0.266    0.000    0.292    0.000 {built-in method io.open}
     8000    0.153    0.000    0.894    0.000 grep.py:5(grep)
    17000    0.101    0.000    0.101    0.000 {built-in method builtins.print}
     8000    0.100    0.000    0.129    0.000 {method 'readlines' of '_io._IOBase' objects}
    93000    0.097    0.000    0.111    0.000 re.py:286(_compile)
    93000    0.069    0.000    0.069    0.000 {method 'search' of '_sre.SRE_Pattern' objects}
    93000    0.030    0.000    0.141    0.000 re.py:231(compile)
    17000    0.019    0.000    0.029    0.000 codecs.py:318(decode)
        1    0.017    0.017    0.911    0.911 grep.py:3(<module>)

[omitted lines]

Une mise en garde concernant le profileur cProfile de Python (et de nombreux profileurs d’ailleurs) est qu’ils affichent le temps par appel de fonction. Cela peut vite devenir non intuitif, surtout si vous utilisez des bibliothèques tierces dans votre code puisque les appels de fonctions internes sont également pris en compte. Une manière plus intuitive d’afficher les informations de profilage est d’inclure le temps pris par ligne de code, ce que font les profileurs de ligne (line profilers).

Par exemple, le morceau de code Python suivant effectue une requête vers le site web du cours et analyse la réponse pour obtenir toutes les URL de la page :

#!/usr/bin/env python
import requests
from bs4 import BeautifulSoup

# This is a decorator that tells line_profiler
# that we want to analyze this function
@profile
def get_urls():
    response = requests.get('https://missing.csail.mit.edu')
    s = BeautifulSoup(response.content, 'lxml')
    urls = []
    for url in s.find_all('a'):
        urls.append(url['href'])

if __name__ == '__main__':
    get_urls()

Si nous avions utilisé le profileur cProfile de Python, nous obtiendrions plus de 2500 lignes de sortie, et même en les triant, il serait difficile de comprendre où le programme a passé le plus de temps. Une exécution rapide avec line_profiler montre le temps pris par ligne :

$ kernprof -l -v a.py
Wrote profile results to urls.py.lprof
Timer unit: 1e-06 s

Total time: 0.636188 s
File: a.py
Function: get_urls at line 5

Line #  Hits         Time  Per Hit   % Time  Line Contents
==============================================================
 5                                           @profile
 6                                           def get_urls():
 7         1     613909.0 613909.0     96.5      response = requests.get('https://missing.csail.mit.edu')
 8         1      21559.0  21559.0      3.4      s = BeautifulSoup(response.content, 'lxml')
 9         1          2.0      2.0      0.0      urls = []
10        25        685.0     27.4      0.1      for url in s.find_all('a'):
11        24         33.0      1.4      0.0          urls.append(url['href'])

Mémoire

Dans des langages comme le C ou le C++, les fuites de mémoire peuvent faire en sorte que votre programme ne libère jamais la mémoire dont il n’a plus besoin. Pour vous aider dans le processus de débogage de la mémoire, vous pouvez utiliser des outils comme Valgrind qui vous aideront à identifier les fuites de mémoire.

Dans les langages avec garbage collector comme Python, il est également utile d’utiliser un profileur de mémoire car tant que vous avez des pointeurs sur des objets en mémoire, ils ne seront pas supprimés par le garbage collector. Voici un exemple de programme et sa sortie associée lorsqu’il est exécuté avec memory-profiler (notez le décorateur comme dans line-profiler).

@profile
def my_func():
    a = [1] * (10 ** 6)
    b = [2] * (2 * 10 ** 7)
    del b
    return a

if __name__ == '__main__':
    my_func()
$ python -m memory_profiler example.py
Line #    Mem usage  Increment   Line Contents
==============================================
     3                           @profile
     4      5.97 MB    0.00 MB   def my_func():
     5     13.61 MB    7.64 MB       a = [1] * (10 ** 6)
     6    166.20 MB  152.59 MB       b = [2] * (2 * 10 ** 7)
     7     13.61 MB -152.59 MB       del b
     8     13.61 MB    0.00 MB       return a

Profilage d’événements

Comme dans le cas de strace pour le débogage, vous pouriez vouloir ignorer les spécificités du code que vous exécutez et le traiter comme une boîte noire lors du profilage. La commande perf fait abstraction des différences entre les CPU et ne rapporte pas le temps ou la mémoire, mais plutôt les événements système liés à vos programmes. Par exemple, perf peut facilement signaler une mauvaise utilisation de la localité du cache, un grand nombre de “page faults” ou des “livelocks”. Voici un aperçu de la commande :

Visualisation

La sortie du profileur pour des programmes de taille réele contiendra de grandes quantités d’informations en raison de la complexité inhérente aux projets logiciels. Les êtres humains sont des créatures visuelles et ne sont pas très doués pour lire de grandes quantités de chiffres et leur donner un sens. Il existe donc de nombreux outils permettant d’afficher les résultats d’un profileur de manière plus facile à analyser.

Une façon courante d’afficher les informations de profilage du CPU pour les profileurs d’échantillonnage est d’utiliser un Flame Graph, qui affichera une hiérarchie d’appels de fonctions sur l’axe Y et le temps pris proportionnellement sur l’axe X. Ces graphiques sont également interactifs, vous permettant de zoomer sur les données et d’afficher les résultats. Ils sont également interactifs, vous permettant de zoomer sur des parties spécifiques du programme et d’obtenir leurs stack traces (essayez de cliquer sur l’image ci-dessous).

FlameGraph

Les graphes d’appels (call graphs) ou graphes de flux de contrôle (control flow graphs) affichent les relations entre les sous-programmes d’un programme en incluant les fonctions en tant que noeuds et les appels de fonctions entre eux en tant qu’arêtes dirigées. Associés à des informations de profilage telles que le nombre d’appels et le temps d’exécution, les graphes d’appels peuvent s’avérer très utiles pour interpréter le flux d’un programme. En Python, vous pouvez utiliser la librarie pycallgraph pour les générer.

Call Graph

Monitoring des ressources

Parfois, la première étape de l’analyse des performances de votre programme consiste à comprendre quelle est sa consommation réelle de ressources. Les programmes s’exécutent souvent lentement lorsqu’ils sont limités en ressources, par exemple s’ils n’ont pas assez de mémoire ou si leur connexion réseau est lente. Il existe une myriade d’outils en ligne de commande permettant de sonder et d’afficher les différentes ressources du système, telles que l’utilisation du processeur, de la mémoire, du réseau, du disque, etc.

Si vous souhaitez tester ces outils, vous pouvez également imposer des charges artificielles sur la machine à l’aide de la commande stress.

Outils spécialisés

Parfois, une analyse comparative “black box” suffit pour déterminer quel logiciel utiliser. Des outils comme hyperfine vous permettent d’évaluer rapidement les programmes en ligne de commande. Par exemple, dans le cours sur les outils de l’interpréteur de commandes et les scripts, nous avons recommandé fd plutôt que find. Nous pouvons utiliser hyperfine pour les comparer dans des tâches que nous exécutons souvent. Par exemple, dans l’exemple ci-dessous, fd était 20 fois plus rapide que find sur ma machine.

$ hyperfine --warmup 3 'fd -e jpg' 'find . -iname "*.jpg"'
Benchmark #1: fd -e jpg
  Time (mean ± σ):      51.4 ms ±   2.9 ms    [User: 121.0 ms, System: 160.5 ms]
  Range (min … max):    44.2 ms …  60.1 ms    56 runs

Benchmark #2: find . -iname "*.jpg"
  Time (mean ± σ):      1.126 s ±  0.101 s    [User: 141.1 ms, System: 956.1 ms]
  Range (min … max):    0.975 s …  1.287 s    10 runs

Summary
  'fd -e jpg' ran
   21.89 ± 2.33 times faster than 'find . -iname "*.jpg"'

Comme c’était le cas pour le débogage, les navigateurs sont également dotés d’un ensemble d’outils fantastiques pour profiler le chargement des pages web, ce qui vous permet de déterminer où le plus de temps est passé (chargement, rendu, scripts, etc). Plus d’informations pour Firefox et Chrome.

Exercices

Débogage

  1. Utilisez journalctl sous Linux ou log show sous macOS pour obtenir les accès et commandes du super utilisateur au cours des dernières 24H. S’il n’y en a pas, vous pouvez exécuter des commandes inoffensives telles que sudo ls et vérifier à nouveau.

  2. Faites ce tutoriel pratique sur pdb pour vous familiariser avec les commandes. Pour un tutoriel plus approfondi, lisez ceci.

  3. Installez shellcheck et essayez de vérifier le script suivant. Qu’est-ce qui ne va pas dans le code ? Corrigez-le. Installez un plugin linter dans votre éditeur afin d’obtenir des avertissements automatiquement.
    #!/bin/sh
    ## Example: a typical script with several problems
    for f in $(ls *.m3u)
    do
      grep -qi hq.*mp3 $f \
        && echo -e 'Playlist $f contains a HQ file in mp3 format'
    done
    
  4. (Avancé) Lisez ceci sur le débogage réversible et faites fonctionner un exemple simple en utilisant rr ou RevPDB.

Profilage

  1. Voici quelques implémentations d’algorithmes de tri. Utilisez cProfile et line_profiler pour comparer la durée d’exécution du tri par insertion et du tri sélectif. Quel est le bottleneck de chaque algorithme ? Utilisez ensuite memory_profiler pour vérifier la consommation de mémoire, pourquoi le tri par insertion est-il meilleur ? Vérifiez maintenant la version en place de quicksort. Défi : Utilisez perf pour regarder le nombre de cycles et les cache hits et cache misses de chaque algorithme.

  2. Voici un code Python (sans doute alambiqué) pour calculer les nombres de Fibonacci à l’aide d’une fonction pour chaque nombre.

    #!/usr/bin/env python
    def fib0(): return 0
    
    def fib1(): return 1
    
    s = """def fib{}(): return fib{}() + fib{}()"""
    
    if __name__ == '__main__':
    
        for n in range(2, 10):
            exec(s.format(n, n-1, n-2))
        # from functools import lru_cache
        # for n in range(10):
        #     exec("fib{} = lru_cache(1)(fib{})".format(n, n))
        print(eval("fib9()"))
    

    Mettez le code dans un fichier et rendez-le exécutable. Installez les prérequis : pycallgraph et graphviz (si vous pouvez exécuter dot, vous avez déjà GraphViz). Exécutez le code tel quel avec pycallgraph graphviz -- ./fib.py et regardez le fichier pycallgraph.png. Combien de fois fib0 est-il appelé ? Nous pouvons faire mieux que cela en mémorisant les fonctions. Décommentez les lignes commentées et régénérez les images. Combien de fois appelons-nous chaque fonction fibN maintenant ?

  3. Un problème courant est qu’un port que vous souhaitez écouter est déjà occupé par un autre processus. Apprenons à découvrir le pid de ce processus. Exécutez d’abord python -m http.server 4444 pour démarrer un serveur web minimal écoutant sur le port 4444. Dans un autre terminal, exécutez lsof | grep LISTEN pour afficher tous les processus et ports en écoute. Trouvez le pid de ce processus et mettez-y fin en exécutant kill <PID>.

  4. Limiter les ressources d’un processus peut être un autre outil pratique dans votre boîte à outils. Essayez d’exécuter stress -c 3 et regardez la consommation du processeur avec htop. Maintenant, exécutez taskset --cpu-list 0,2 stress -c 3 et regardez la consommation à nouveau. Est-ce que stress utilise trois CPU ? Pourquoi ? Lisez man taskset. Défi : réaliser la même chose en utilisant cgroups. Essayez de limiter la consommation de mémoire de stress -m.

  5. (Avancé) La commande curl ipinfo.io effectue une requête HTTP et récupère des informations sur votre IP publique. Ouvrez Wireshark et essayez de sniffer les paquets de requête et de réponse que curl a envoyés et reçus. (Conseil : utilisez le filtre http pour ne regarder que les paquets HTTP).


Modifier cette page.

Sous licence CC BY-NC-SA.