Sam & Max » process http://sametmax.com Du code, du cul Sat, 07 Nov 2015 10:56:13 +0000 en-US hourly 1 http://wordpress.org/?v=4.1 Remplacer les threads avec le module multiprocessing en Python 12 http://sametmax.com/remplacer-les-threads-avec-le-module-multiprocessing-en-python/ http://sametmax.com/remplacer-les-threads-avec-le-module-multiprocessing-en-python/#comments Tue, 31 Jul 2012 12:24:03 +0000 http://sametmax.com/?p=1430 Liferea. Liferea a pendant bien longtemps freezé l'intégralité de l'UI pendant la mise à jour de la liste d'articles (ben oui le temps de charger une page Web, la main loop attend). On peut éviter cela en utilisant des threads ou, dans notre, cas, de multiples processus.]]> Les threads Python sont limités par le Global Interpreter Lock, et si ils permettent de s’affranchir des problèmes de concurrence d’accès IO, ils sont inefficaces pour profiter de nos merveilleux processeurs multi-coeurs. Les coroutines, une alternative élégante aux threads, ont la même limitation.

Heureusement Python vient avec le module multiprocessing, qui permet justement de créer plusieurs processus séparés, et les orchestrer pour qu’ils travaillent ensemble, et ainsi saturer la consommation de ressource de nos serveurs modernes si chers et si puissants.

Prenons un employé de banque que nous appellerons A, et un épagneul Breton, que nous appellerons Catherine.

Euh non…

Prenons plutôt une application qui poll des flux RSS comme Liferea. Liferea a pendant bien longtemps freezé l’intégralité de l’UI pendant la mise à jour de la liste d’articles (ben oui le temps de charger une page Web, la main loop attend). On peut éviter cela en utilisant des threads ou, dans notre, cas, de multiples processus.

Bon, il y a peu de chance que Lifera soit CPU bound, donc c’est vrai que dans ce cas les threads feraient aussi bien, mais c’est pour l’exemple, bande de tatillons.

Pour notre cas de figure, nous avons besoin:

  • d’un process qui demande aux autres de vérifier les derniers flux RSS (pour simuler une interaction utilisateur);
  • d’un process qui va faire la vérification des flux RSS sans bloquer les autres process;
  • de feedparser, une lib Python qui parse les flux RSS;
  • d’un process qui va lancer tout ça, récupérer le résultat et l’afficher.

Pour feedparser avec pip:

    pip install feedparser

Ça c’est fait.

Pour le reste, on se fait un petit fichier rssmania.py:

# -*- coding: utf-8 -*-
 
import time
from time import mktime
from datetime import datetime
from multiprocessing import Process, Queue, TimeoutError
 
import feedparser
 
# cette fonction va être utilisée comme worker
# elle va lancer un process qui tourne en boucle et vérifie de manière
# régulière si il y a des flux à mettre à jour
def mettre_a_jour_les_flux(queue_flux_a_mettre_a_jour, queue_de_mises_a_jour_des_flux):
 
    last_update = {}
 
    while True: # une bonne boucle infinie pour la main loop
        try:
            # on vérifie si il y a un message dans la queue pendant 0.1 secondes
            # si oui, on parse le flux (sinon, ça raise une TimeoutError)
            flux = queue_flux_a_mettre_a_jour.get(0.1)
 
            feed = feedparser.parse(flux)
            nouveaux_articles = []
            # pour chaque article, on vérifie si la date de parution est
            # antérieur au dernier check, et si oui, on le déclare
            # "nouvel article"
            for article in feed.entries:
                try:
                    dt = datetime.fromtimestamp(mktime(article.updated_parsed))
                    if dt > last_update[flux]:
                        nouveaux_articles.append(article.link)
                except KeyError:
                    nouveaux_articles.append(article.link)
 
            # on balance tous les nouveaux articles dans la queue
            if nouveaux_articles:
                queue_de_mises_a_jour_des_flux.put((feed.feed.title, nouveaux_articles))
 
            last_update[flux] = datetime.now()
 
        # en cas de time out on repart sur un tour de boucle
        # si l'utilisateur fait CTRL+C sur le worker principal, il sera
        # broadcasté ici, donc on le catch et on exit proprement
        except TimeoutError:
            pass
        except KeyboardInterrupt:
            sys.exit(0)
 
 
# worker très basique qui demande la mise à jour de tous les flux
# c'est bourrin, mais c'est pour l'exemple on vous dit !
def demander_la_mise_a_jour_des_flux(queue_de_flux_a_mettre_a_jour, flux_rss):
    """
        Demande la mise à jour des flux toutes les 5 minutes
    """
 
    # pareil, petite boucle infinie, temporisation et gestion du CTRL + C
    # en gros on ne fait que remplir la queue toutes les 5 minutes
    # avec des urls
    while True:
 
        try:
            for flux in flux_rss:
                queue_de_flux_a_mettre_a_jour.put(flux)
 
            time.sleep(300)
 
        except KeyboardInterrupt:
            sys.exit(0)
 
 
# très important ce if, sinon sous windows le module sera importé plusieurs
# fois et lancera ce bloc plusieurs fois
if __name__ == '__main__':
 
    # les flux à mettre à jour, RAS
    flux_rss = (
        'http://sametmax.com/feed/',
        "http://sebsauvage.net/links/index.php?do=rss",
        "http://charlesleifer.com/blog/rss/",
        "http://xkcd.com/rss.xml"
    )
 
    # les queues. Ces objets sont comme des listes partageables entre
    # les workers, sur lesquelles on pourrait faire uniquement insert(0, elem)
    # (ici put(elem)) et pop() (ici get()). Des FIFO thread safe quoi.
    queue_de_flux_a_mettre_a_jour = Queue()
    queue_de_mises_a_jour_des_flux = Queue()
 
    # ici on créé nos workers: on dit quelle fonction lancer avec quels
    # arguments. Nos arguments ici sont essentiellement les queues,
    # puisque c'est ce qui va nous permettre de partager les infos
    # entre les process (qui sont sinon isolés les uns des autres)
    worker_qui_met_a_jour_les_flux = Process(target=mettre_a_jour_les_flux,
                                             args=(queue_de_flux_a_mettre_a_jour,
                                                   queue_de_mises_a_jour_des_flux))
 
    worker_qui_demande_la_mise_a_jour = Process(target=demander_la_mise_a_jour_des_flux,
                                                args=(queue_de_flux_a_mettre_a_jour,
                                                      flux_rss))
 
    # On démarre les workers, et à partir de là, 2 processus sont créés
    # et lançant chacun une fonction, les boucles infinies tournent joyeusement
    # et une personne est agressée toutes les 7 secondes à New York aussi,
    # mais on s'en fout dans notre cas présent.
    # Bien faire gaffe que les fonctions soient capables de tourner à vide :-)
    worker_qui_met_a_jour_les_flux.start()
    worker_qui_demande_la_mise_a_jour.start()
 
    # et voici notre worker principal, qui pop les nouveaux flux tout
    # frais, et les affiche à l'écran
    try:
        while True:
            try:
                feed, articles = queue_de_mises_a_jour_des_flux.get(0.2)
                print "Voici les derniers articles de %s :" % feed
                for article in articles:
                    print "- %s" % article
            except TimeoutError:
                pass
 
    except KeyboardInterrupt:
        pass
    finally:
        # si la boucle while s'arrête d'une manière ou d'une autre
        # on attend que les autres processus s'arrêtent avant de quitter
        # En vrai on mettrait beaucoup plus de code que ça, une file
        # de controle, peut être un handler de SIGTERM, etc
        # là on va à l'essentiel
        worker_qui_met_a_jour_les_flux.join()
        worker_qui_demande_la_mise_a_jour.join()
 
    print "Fin des haricots"

On lance le bouzin:

    python rssmania.py

Python se charge automatiquement de créer 2 subprocess, un qui lance la fonction mettre_a_jour_les_flux() et un pour demander_la_mise_a_jour_des_flux() puis il va faire tourner notre bouclinette principale avec amour.

Normalement, au premier lancement ça donne un truc comme ça:

Voici les derniers articles de Sam & Max: Python, Django, Git et du cul :
- http://sametmax.com/rassurez-vous-vous-netes-pas-bizarres/
- http://sametmax.com/fonctions-anonymes-en-python-ou-lambda/
- http://sametmax.com/deterer-le-cadavre-dun-troll-non-php-nest-pas-simple/
- http://sametmax.com/concurrence-sans-threads-en-python/
- http://sametmax.com/humour-reflexion-et-cul-la-formule-ne-date-pas-dhier/
- http://sametmax.com/state-machine-en-python-en-labsence-dalgos-recursifs-beneficiant-de-tail-call-optimisation/
- http://sametmax.com/appel-a-contributeurs-impertinents/
- http://sametmax.com/synchroniser-les-freeplugs-les-adaptateurs-reseaux-cpl-de-free/
- http://sametmax.com/incendie-en-espagne-un-megot-peut-se-tranformer-en-arme-mortelle/
- http://sametmax.com/jadore-les-context-managers-python/
Voici les derniers articles de Liens en vrac de sebsauvage :
- http://imgur.com/37R4c
- http://www.clubic.com/navigateur-internet-mobile/opera-mini/actualite-503834-opera-mini-depasse-200-utilisateurs.html
- http://www.lesnumeriques.com/jeux-video/impire-p14041/impire-creez-vos-donjons-comme-a-bonne-epoque-annees-bullfrog-n25461.html
- http://sebsauvage.net/links/index.php?Jr5VKg
- http://sebsauvage.net/links/index.php?PQUdwA
- http://imgur.com/A4xkr

Et 5 minutes plus tard (dans le cas improbable où un article a été publié entre temps), ça affiche les nouveaux articles.

Si vous appuyez sur CTRL + C, SIGINT va être envoyé à tous les workers, et ils vont tous s’arrêter gentiment. Normalement. En théorie. Souvent ça marche. Sur ma machine.

]]>
http://sametmax.com/remplacer-les-threads-avec-le-module-multiprocessing-en-python/feed/ 12
Diminuer la charge cpu d’un process avec renice 2 http://sametmax.com/diminuer-la-charge-cpu-dun-process-avec-renice/ http://sametmax.com/diminuer-la-charge-cpu-dun-process-avec-renice/#comments Tue, 29 May 2012 08:44:10 +0000 http://sametmax.com/?p=780 Il arrive que l’on ai a executer certains scripts sur un serveur de prod deja pas mal encombré, je prends comme exemple mon cas ou j’ai du reencoder des vidéos, le load average est monté à 6, les perfs en sont du coup devenues execrables.
Sous linux on a heureusement plein d’outils sympas, renice en fait partie.

La commande renice permet d’assigner une priorite a un process en cours qui va de -20 (tres elevee) à 20 (tres bas), 0 etant la valeur par défaut. La commande est “renice priorite id_process”, dans mon cas:

Note: On peut connaitre l’id du process avec un top ou htop

PID USER        PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND          
14378 max 	35  15  129m  12m 4632 R 99.4  0.2   0:03.22 ffmpeg       
22959 max       20  15  101m  79m  924 S  1.7  1.3   8:51.85 python encode.py                
22958 nginx     20   0  105m  83m  924 S  1.0  1.4   8:51.44 nginx

Ici ffmpeg à un nice (NI) de 15 qu’il a herité de mon script Python encode.py a qui j’ai fais un renice de 15 avec la commande “renice 15 22959″. Les process heritent automatiquement du nice des parents qui les ont lancés.

]]>
http://sametmax.com/diminuer-la-charge-cpu-dun-process-avec-renice/feed/ 2
Checker ses process et les relancer en cas de plantage 6 http://sametmax.com/checker-ses-process-et-les-relancer-en-cas-de-plantage/ http://sametmax.com/checker-ses-process-et-les-relancer-en-cas-de-plantage/#comments Tue, 22 May 2012 16:55:40 +0000 http://sametmax.com/?p=672 J’ai quelques scripts qui se mettent à planter des fois, je sais que c’est parceque je les ai codé comme un gros porc mais ça m’emmerde de les refaires parceque après tout ils font ce que je leur dit de faire, c’est juste qu’ils plantent des fois.
Ceci dit j’ai aussi des applications qui ne sont pas mienne qui plantent (privoxy, tor, des fois même lighttpd) et là on est bien content que quelqu’un puisse le relancer pendant qu’on est en train de se cuiter au bar du coin.

Voici un petit script tout simple en python que je lance avec un crontab à intervalles réguliers.

Exemple pour nginx, on va rechercher le process à partir du mot clef “nginx”:

ps aux | grep nginx
root     29231  0.0  0.0  22144   948 ?        Ss   13:05   0:00 nginx: master process /usr/local/sbin/nginx -c /usr/local/nginx/conf/nginx.conf

Dans le script ça donne:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# vim: ai ts=4 sts=4 et sw=4
 
"""
    check if script is running else relaunch
"""
 
import sys
import os
from subprocess import Popen, call, PIPE
 
 
SUCCESS_EXIT_CODE=0
ERR_PS_SCRIPT_RUNNING=3
ERR_PID_SCRIPT_RUNNING=4
 
def check_ps_cmd(script_name):
    try:
        p1 = Popen(["ps", "aux"], stdout=PIPE)
        p2 = Popen(["grep", script_name], stdin=p1.stdout, stdout=PIPE)
        p3 = Popen(["grep", "-v", "grep"], stdin=p2.stdout, stdout=PIPE)
        output = p3.communicate()[0]
        return output
    except Exception, e:
        print >>sys.stderr, "Execution failed:", e
        return None
 
 
# process to check
if not check_ps_cmd('nginx'):
    print "Nginx dead, relaunch..."
    retcode = call("service nginx restart", shell=True)
 
# add here new processes to check...

On peut imaginer mettre un sendmail pour se voir notifier lors de la mort d’un process et ainsi briller en soirées mondaines lorsque votre téléphone vous bippe sous le nom de Jarvis vous avertissant de la mort prématurée de lighttpd suite à votre toute dernière super config que vous avez pris soins de mettre en prod un vendredi soir sans vous soucier de la tester et ce juste avant de partir du bureau dont la clef est dans la poche du boss qui vient de se tirer en long weekend avec sa secrétaire…

]]>
http://sametmax.com/checker-ses-process-et-les-relancer-en-cas-de-plantage/feed/ 6
Vérifier la RAM que consomme un process sous Linux avec pmap 1 http://sametmax.com/verifier-la-ram-que-consome-un-process-sous-linux-avec-pmap/ http://sametmax.com/verifier-la-ram-que-consome-un-process-sous-linux-avec-pmap/#comments Thu, 10 May 2012 00:30:09 +0000 http://sametmax.com/?p=583 Des fois on a son serveur qui swap à mort, tout ralentie, votre site vaut plus rien, vous êtes désespéré, c’est la faute à tout le monde sauf vous, etc… Sauf que vous avez fait un script qui check rien du tout, qui lance des process externes qui buguent 2 fois sur 3, votre script tout pourri a alors bouffé toute la RAM, ne laissant rien aux autres process, le système swap à mort, c’est le drame…

Avec pmap vous pouvez connaitre la mémoire utilisée par un process, très pratique pour dénicher qui consomme et remonter à la source.

Un petit ps aux pour connaitre l’id de votre process à espionner ou à défaut un htop:

ps aux | grep redis
redis    23777  0.1  6.8  72480 70708 ?        Ss   01:15   0:03 /usr/sbin/redis-server /etc/redis.conf

Ensuite on passe l’ID à pmap:

pmap 23777
23777:   /usr/sbin/redis-server /etc/redis.conf
0089a000    108K r-x--  /lib/ld-2.5.so
...
bf9d5000     84K rw---    [ stack ]
 total    72476K

Redis occupe 72Mo de RAM. Dans mon cas c’est normal, celà correspond à mes valeurs de config.

]]>
http://sametmax.com/verifier-la-ram-que-consome-un-process-sous-linux-avec-pmap/feed/ 1
htop – un visualiseur interactif de process 6 http://sametmax.com/htop-un-visualiseur-interactif-de-process/ http://sametmax.com/htop-un-visualiseur-interactif-de-process/#comments Wed, 18 Apr 2012 08:36:26 +0000 http://sametmax.com/?p=422 Htop est une sorte de top super vitaminé, très pratique pour visualiser en temps réél les process qui bouffent le plus de RAM ou de CPU, permet aussi de savoir où en est l’état de sa RAM et bien d’autres choses.

Pour l’install vous pouvez essayer un “yum install htop” ou aller sur le site et compiler la version proposée: http://htop.sourceforge.net/

Si vous voulez le compiler:
Téléchargez la source ici : http://sourceforge.net/projects/htop/

tar xvf htop-1.0.1.tar.gz
cd htop-1.0.1
./configure
make && make install

Quelques options interressantes:

  • Trier par charge CPU: SHIFT + P
  • Killer un process: Sélectionner le process à tuer avec ESPACE, ensuite CMD + F9 et sélectionner le mode SIGKILL à gauche
  • Afficher l’arboresence des process: F5
  • Trier par RAM utilisée: SHIFT + M
  • Afficher l’aide: H
  • Rechercher un process: CMD + F3
]]>
http://sametmax.com/htop-un-visualiseur-interactif-de-process/feed/ 6