Aller au contenu principal

En guise d’introduction

Voici une série de notes et astuces. Rien d’extra ordinairement nouveau ici, mais c’est pratique de publier en vrac des notes prises au fil des jours pour pouvoir ensuite les retrouver facilement.

Maison renversée. Cliquer pour voir l’image en taille plus grande

Awk pour les fichiers csv

Exemple pour compter le nombre de valeurs uniques du quatrième champs d’un fichier csv, dont le séparateur est un ;:

cat Customer.csv | awk -F ';' '{print $4}'| sort -u | wc -l

Awk pour docker

Pour afficher la liste des container en filtrant sur la commande exécutée:

docker ps -a | awk '$3 ~ /docker-php-entry/ '

Git

Lister les fichiers modifiés entre deux commits, ou deux branches (ici master et develop).

git diff-tree --no-commit-id --name-only  -r master..develop

Ansible

J’ai eu du mal à trouver comment exécuter en local_action une commande du module shell. La solution: utiliser _raw_params comme ci-dessous:

- name: Build frontend
  local_action:
    module: shell
    _raw_params: grunt build
    args:
      chdir: "{{ build_root_path }}/src/views/"

Hardware

Quand votre station d’accueil dell buggue, la solution est le reset de tous les périphériques usb:

find /sys/bus/usb/devices/*/authorized -exec sh -c 'echo 0 > ${0}; echo 1 > ${0}' {} \;

(la vrai solution reste de remplacer la station d’accueil par un hub usb)

Mysql

Quelques commandes en vrac, suffisamment explicites pour ne pas avoir à les commenter, à garder sous le coude:

SHOW FULL PROCESSLIST;
SET GLOBAL general_log = 1;
SHOW TABLE LIKE 'prefix%';

Composer

Après la résolution d’un conflit dans git, il peut arriver que vous ayez manuellement modifier le contenu du fichier composer.lock. Il est alors nécessaire de mettre à jour les champs qui contiennent les champs de contrôle:

composer update nothing

Visualiser correctement les journaux contenant des \n

tail -f oxideshop.log | sed 's/\\n/\
/g'

Configuration

Je ne suis pas un fanatique de la configuration à outrance des logiciels utilisables dans un terminal. J’essaie généralement de me contenter de la configuration par défaut, afin de ne pas être pris au dépourvu ensuite lorsque je dois utiliser ces mêmes logiciels sur un serveur distant, où ces personnalisations seront absentes.

Dernièrement j’ai fait une exception pour tmux. Dans 90% des cas, lorsque je découpe une fenêtre, je souhaite rester dans le même répertoire duquel j’ai lancé la découpe. En revanche, lorsque j’ouvre une nouvelle fenêtre, je souhaite retourner dans mon HOME, ce qui est le comportement par défault. C’est très pratique sur mon ordinateur personnel, et si je n’ai pas cette fonctionnalité sur un serveur, je peux m’en passer aisément. J’ai donc créé un fichier ~/.tmux.conf avec la configuration suivante:

bind '"' split-window -c "#{pane_current_path}"
bind % split-window -h -c "#{pane_current_path}"

Je n’utilise emacs que sur mon ordinateur personnel. Pour me me connecter à une base de données mariadb (ou mysql) en utilisant un autre port que le port standard, ce qui arrive systématiquement en utilisant des base de données hébergé dans des containers, j’ai dû ajouter la ligne suivante:

(setq sql-mysql-login-params (append sql-mysql-login-params '(port)))(with-eval-after-load "sql"
  (setq sql-mysql-login-params (append sql-mysql-login-params '(port)))
  )

Flame Graph pour Php

Nous avons vu précédemment comment générer des graphes de flammes pour analyser son système GNU/Linux. Dans cet article nous allons voir comment générer ce type de graphe pour une application php.

Grâce à l’extension php-spx ce n’est pas simplement une image svg qui va nous fournir des indications sur les fonctions les plus gourmandes de notre programme php, mais c’est une application web dédiée, beaucoup plus riche en terme d’information qui sera à notre disposition.

Comment installer php-spx

Je ne vais détailler ce qui est déjà écrit dans la documentation du projet. Je vais simplement expliquer les modifications que j’effectue sur mon container docker php-fpm, lancé par docker-compose, qui contient aussi un serveur web et une base de donnée (ces autres containers ne seront pas décrit ici).

Par défaut mon Dockerfile copie déjà des fichiers de configuration ou bien des fichiers à exécuter dans le container. J’ajoute donc deux fichiers supplémentaires pour l’extension php-spx:

diff --git a/docker/php/Dockerfile b/docker/php/Dockerfile
 
 COPY ./xdebug.sh /tmp/xdebug.sh
+COPY ./spx.sh /tmp/spx.sh
+COPY ./spx.ini /tmp/spx.ini
 
 USER ${CONTAINER_UID}:${CONTAINER_GID}
 COPY --chown=${CONTAINER_UID}:${CONTAINER_GID} ./install.sh /tmp/install.sh
 

install.sh est le fichier qui est exécuté par le container avec CMD ["/bin/bash","/tmp/install.sh"], et bien évidement la dernière ligne de ce fichier est php-fpm

diff --git a/docker/php/install.sh b/docker/php/install.sh
 
     sudo bash /tmp/xdebug.sh
+    sudo bash /tmp/spx.sh
 
     if [ -f "source/index.php.installing.tmp" ]
     then

Voici maintenant le contenu des deux nouveaux fichiers. Tout d’abord la configuration de l’extension:

diff --git a/docker/php/spx.ini b/docker/php/spx.ini
new file mode 100644
+extension=spx.so
+spx.http_enabled=1
+spx.http_key="dev"
+spx.http_ip_whitelist="127.0.0.1,{EXTERNAL_IP}"

Et ensuite le fichier qui va installer l’extension dans le container:

diff --git a/docker/php/spx.sh b/docker/php/spx.sh
new file mode 100644
+cd /tmp
+apt-get update && apt-get install zlib1g-dev
+git clone https://github.com/NoiseByNorthwest/php-spx.git
+cd php-spx
+phpize
+./configure
+make
+make install
+cp /tmp/spx.ini /usr/local/etc/php/conf.d/spx.ini
+EXTERNAL_IP=$(/sbin/ip route|awk '/default/ { print $3 }')
+sed -i "s/{EXTERNAL_IP}/${EXTERNAL_IP}/" /usr/local/etc/php/conf.d/spx.ini

Et comment ça marche

Maintenant, en allant sur l’url de l’application et en y ajoutant le suffixe, /?SPX_UI_URI=/ vous devriez accéder à l’interface de gestion de php-spx.

Checkbox qui permet d’activer l’enregistrement d’une session. Cliquer pour voir l’image en taille plus grande

Figure 1 : Checkbox qui permet d’activer l’enregistrement d’une session

Nous allons activer l’enregistrement des évènements en cliquant sur la checkbox. Ensuite nous pouvons aller visiter une ou plusieurs pages de notre application avant de revenir à cette page.

Nous avons maintenant dans le bas de la page la liste des urls que nous avons visitées et en cliquant sur un des liens, nous pouvons enfin analyser l’exécution du code de notre application.

Écran principal de l’extension php-spx  lors d’une analyse de session. Cliquer pour voir l’image en taille plus grande

Figure 2 : Écran principal de l’extension php-spx lors d’une analyse de session

La ligne de temps, en haut de la page, montre chaque fonction appelée. La vue globale est au dessus et la vue détaillée juste en dessous. Plus la fonction prend du temps à s’exécuter, plus elle apparaît large. Lorsque la la barre représentant une fonction est cliquée (depuis la vue détaillée ou globale), s’affiche le détail de la fonction:

  • son nom
  • la profondeur
  • le temps d’exécution

Vue sur la ligne de temps et la zone détaillée. Cliquer pour voir l’image en taille plus grande

Figure 3 : Vue sur la ligne de temps et la zone détaillée

En bas à droite s’affiche un résumé du graphe de flammes: toutes les stacks (chaînes de fonctions qui s’appellent) identiques sont regroupés ensemble. Pour information, cet affichage qui regroupe les stacks identiques est le type d’affichage par défaut présenté précédemment.

Le graphe de flammes d’une application php. Cliquer pour voir l’image en taille plus grande

Figure 4 : Le graphe de flammes d’une application php

Le tableau à gauche montre toutes les fonctions. Il est possible de trier le tableau par chaque colonne. Les informations les plus importantes sont les colonnes Inc et Exc:

  • Inc: est le temps passé au total dans la fonction
  • Exc: est le temps passé dans la fonction elle même, soit le temps total, moins le temps passé dans les autres fonctions appelées
  • exemple: index.php a une valeur inc de 100%, mais généralement de 0% pour exc, puisqu’il ne se passe rien dans ce fichier même.

Tableau des fonctions. Cliquer pour voir l’image en taille plus grande

Figure 5 : Tableau des fonctions

La vue globale est extrêmement pratique, parce qu’elle permet de zoomer à sur une période de temps très très précise. Toutes les informations de la page se mettent à jour pour correspondre à ce qui se passe pendant l’intervalle de temps zoomé: vue détaillée, tableau et graphe de flammes.

Et en ligne de commande

Et bien c’est simple comme:

SPX_ENABLED=1 SPX_FP_LIVE=1 vendor/bin/oxid cache:clear

Affichage du profile d’une commande php lancée dans un terminal. Cliquer pour voir l’image en taille plus grande

Figure 6 : Affichage du profile d’une commande php lancée dans un terminal

Pour finir

Php-spx est une extension incroyable et très pratique, pour analyser la performance d’une page php, mais aussi pour apprendre le fonctionnement des frameworks php en examinant en live ce qu’ils font.

La seule limite est qu’elle ne fournit pas les arguments des fonctions, ce qui peut-être une limite parfois, comme par exemple pour trouver les requêtes SQL les plus lentes.

Présentation succincte de bpftrace

Je suis en train d’apprendre à utiliser bpftrace. Bpftrace est une interface à eBPF, sous la forme d’un nouveau langage inspiré de la syntaxe d’awk, qui permet d’observer un système GNU/Linux.

Cliquer pour accéder au site officiel de bpftrace

Figure 1 : Liste des sondes (probes) accessible avec bpftrace

Comme awk, il est possible de créer des commandes d’une seule ligne (des unilignes), à lancer depuis un terminal, ou bien de créer des scripts en utilisant un shebang spécifique: #!/usr/bin/env bpftrace

Surveiller bash

Le premier exemple du projet est un programme qui affiche la liste des commande lancées par tous les shells interactifs bash.

Mais ce script est incomplet lorsqu’on le compare au besoin réel d’un hébergeur. Il n’affiche que le pid du script et la valeur de retour: printf("%-6d %s\n", pid, str(retval));.

On peut afficher très facilement l’id et le nom de l’utilisateur: printf("%-6d %s %5d (%s) - ", pid, str(retval), uid, username);.

On peut aussi vouloir afficher certaines variables d’environnement. Si on peut accéder à toutes les variables d’environnement, en utilisant le fichier /proc/pid/environ, malheureusement bpftrace ne proprose pas d’outil complexe pour manipuler les chaînes de caractères et ne supporte les boucles que depuis la version 5.3 du noyau. Dès lors je n’ai pas cherché à aller plus loin pour extraire les valeurs dont on a besoin.

Au final on obtient l’uniligne suivant:

bpftrace -e 'uretprobe:/bin/bash:readline { time("%H:%M:%S  ");
    printf("%-6d %s %5d (%s) -", pid, str(retval), uid, username);
    cat("/proc/%d/environ", pid) ; printf("\n")}'

Il est possible de transformer les lignes produites par cet outil, afin d’extraire les variables d’environnement que l’on veut sauvegarder.

Comment mieux faire

La sonde utilisée ici est uretprobe:/bin/bash:readline: on récupère la valeur retour de la fonction readline appelée par bash. Un utilisateur qui veut être discret pourra utiliser un autre shell, comme dash, pour lancer d’autres commandes. Il faudrait donc créer une sonde différente pour chaque type de shell, ou bien trouver une autre sonde commune à tous les shells.

Quoi qu’il en soit, bpftrace est un outil surpuissant pour surveiller ce qui se passe sur un serveur. La liste des exemples fournit une base solide pour développer ses propres sondes de surveillance.

De quoi parle-t-on?

Les graphes de flammes (traduction plus qu’approximative de flam graphs) sont une représentation graphiques des appels de fonctions d’un serveur ou d’un programme, qui permet de visualiser:

  • l’empilement des appels de fonctions (quelle fonction appelle qui);
  • la durée (ou une autre quantité comme la mémoire) occupée par chaque fonction.

Comme une image vaut mieux qu’un long discours voici un exemple:

Cliquer pour voir l’original en SVG image

Figure 1 : Un serveur qui ne fait presque rien

Ceci est une image statique. Mais l’image originale permet de zoomer à volonté.

Premières flammes

Pour créer un tel graphe, trois étapes théoriques sont nécessaires:

  1. collecter les données avec l’outil perf (apt install linux-perf sous Debian pour l’obtenir);
  2. mettre en forme les données;
  3. générer l’image.

J’ai écrit en théorie ci-dessus, parce qu’en pratique l’étape deux et trois sont généralement effectuées en même temps.

La génération des données s’effectue donc avec la commande perf. Par exemple:

perf record -F 99 -a -g -- sleep 20

Cette commande collecte pendant 20 secondes tous les appels de fonctions à la fréquence 99. Elle génère un fichier perf.data dans le répertoire où elle a été lancée.

La première transformation que l’on va apporter à ce fichier s’effectue aussi avec la commande perf1:

perf script --header -i perf.data > out.perf

Cela génère un fichier texte, auquel vous pouvez jeter un œil. C’est lisible, mais pas très pratique. À la limite, pour lire le contenu des données collectée, la commande report est plus adaptée: perf report -n --stdio.

Pour la suite, vous allez avoir besoin de FlameGraph. Il suffit de récupérer le dépôt, et d’appeler les commandes suivantes depuis le répertoire où est téléchargé le dépôt:

./stackcollapse-perf.pl ../out.perf > out.folded
./flamegraph.pl --title "Server On Fire" out.folded > image.svg

Et voilà, vous avez généré votre premier graphe de flammes!

On peut résumer les dernières commandes en une seule ligne:

perf script --header -i ../path-to/perf.data | ./stackcollapse-perf.pl | ./flamegraph.pl --title "Title" > en-flamme.svg

Plus de précisions

Parfois il y a beaucoup de unknown qui apparaissent comme nom de fonction. Cela signifie que perf n’arrive pas à retrouver la fonction appelée. Généralement en remplaçant -g par --call-graph dwarf cela résout le problème. J’ai aussi rencontré une situation où la solution a été d’effacer le contenu de mon répertoire ~/.debug/ qui contenait des fichiers obsolètes.

Jusqu’à présent, nous avons obtenu une image globale de notre système. Mais il est aussi possible de surveiller un ou plusieurs processus, grâce à l’argument -p:

record -F 99 -a --call-graph dwarf -p 469325,521774 -- sleep 10

Cliquer pour voir l’original en SVG image

Figure 2 : Graphe de flamme d’un processus Emacs

De même les arguments -t et -u filtrent le premier sur les thread id, et le second sur les processus appartenant à un utilisateur donné.

Il est aussi possible d’observer un container.

perf record -F 99 -a --call-graph dwarf -e instructions --cgroup=docker/424d518a79c2bf2bed363a32ef0ceee3e9ec47e34020eaff41bf0b09fba84251 -- sleep 10

Quelques explications s’imposent:

  1. il faut fournir le nom complet du cgroup du container, préfixé par docker/. Le nom complet s’obtient avec docker inspect --format={{.Id}} nom-du-container
  2. il faut spécifier un event. Perf, qui sert à beaucoup d’autres choses que produire des graphes de flammes, permet d’observer uniquement certains évènement. L’évènement insstructions est suffisant pour notre besoin.

Références

Cet article n’est qu’une légère introduction.

Quelques références, toutes en anglais, pour aller plus loin:

1

L’argument -i, qui indique le fichier à traiter, est inutile dans le cas présent, puisque perf.data est le fichier par défaut. Je ne l’ai mis ici uniquement pour vous évitez de le rechercher dans la documentation (perf help script).

Origine

Après avoir lu un article sur comment monitorer des certificats avec Perl, je n’ai pas pu m’empêcher de penser: «mais perl n’est pas nécessaire!». En effet, openssl est un véritable couteau suisse pour manipuler les certificats.

Voici donc un petit script, qui prend en argument une liste de domaines et de ports, et qui retourne le nombre de jours avant leurs expirations.

Ligne de commande

Le script

#!/bin/bash

get_date () {
    local host=$1
    local port=$2
    text_date=$(echo - | openssl s_client -ign_eof -showcerts -servername $host -connect $host:$port 2>/dev/null | openssl x509 -inform pem -noout --enddate|cut -f 2 -d =)
    echo $text_date
}

get_nb_days () {
    local date=$1
    now=$(date +"%s")
    expire=$(date --date="${date}" --utc +"%s")
    seconds_expire=$(($expire-$now))
    days_expire=$((${seconds_expire}/$((60*60*24))))
    echo $days_expire
}

main () {
    for var in "$@"
    do
        text_date=$(get_date ${var/:/ })
        nb_days=$(get_nb_days "$text_date")
        echo $var $nb_days
    done
}

main "$@"

Explications et limites

Ce script utilise essentiellement:

  • la commande s_client d’OpenSSL;
  • la commande date, version GNU (j’ai lu quelque part que l’argument +"%s" ne marchait pas avec la version fournie par MacOS)

Le programme boucle sur les arguments passés, qui doivent être de la forme: host:port. En sortie il affiche chaque host:port suivi du nombre de jours avant l’expiration du certificat.

Les deux fonctions get_date et get_nb_days peuvent être re-utilisées pour d’autres scripts, par exemple pour n’afficher que les certificats qui expirent dans 15 jours ou dans 7 jours.