web-dev-qa-db-fra.com

Forcer le vidage du tampon de sortie dans le programme en cours

J'ai un script python de longue durée qui sort périodiquement des données vers une sortie standard que j'ai invoquées avec quelque chose comme:

python script.py > output.txt

Ce script est en cours d'exécution depuis un moment et je veux l'arrêter avec Ctrl+C mais ne perdez rien de sa sortie. Malheureusement, lorsque j'ai implémenté le script, j'ai oublié de vider le tampon après chaque ligne de sortie avec quelque chose comme la sys.stdout.flush() (la solution précédemment suggérée pour forcer le vidage de la sortie), Ctrl+C maintenant me fera perdre toute ma production.

Si vous vous demandez s'il existe un moyen d'interagir avec un script python en cours d'exécution (ou, plus généralement, avec un processus en cours d'exécution) pour le forcer à vider son tampon de sortie. Je ne vous demande pas comment éditer et ré-exécuter le script pour le vider correctement - cette question concerne spécifiquement l'interaction avec un processus en cours d'exécution (et, dans mon cas, ne perd pas la sortie de l'exécution de mon code actuel).

18
josliber

Il semble que j'étais trop prudent de perdre par la sortie en mémoire tampon après avoir exécuté Ctrl-C; selon cet article Je m'attendrais à ce que le tampon soit vidé si mon programme a une sortie normale, ce qui serait le cas si je frappais Ctrl-C. D'autre part, je perdrais la sortie en mémoire tampon si je tuais le script avec SIGKILL ou similaire.

2
josliber

SI on manquait vraiment de ces données, je suggèrerais de joindre le débogueur gdb à l'interpréteur python, en arrêtant momentanément la tâche, en appelant fsync(1) ( stdout ), détachez-vous (reprise du processus) et parcourez le fichier de sortie.

Regardez dans /proc/$(pidof python)/fd pour voir les descripteurs de fichier valides. $(pidof x) renvoie le PID du processus nommé 'x'.

# your python script is running merrily over there.... with some PID you've determined.
#
# load gdb
gdb
#
# attach to python interpreter (use the number returned by $(pidof python))
attach 1234
#
# force a sync within the program's world (1 = stdout, which is redirected in your example)
call fsync(1)
#
# the call SHOULD have returned 0x0, sync successful.   If you get 0xffffffff (-1), perhaps that wasn't stdout.  0=stdin, 1=stdout, 2=stderr
#
# remove our claws from poor python
detach
#
# we're done!
quit

J'ai utilisé cette méthode pour changer les paramètres de travail, les réglages de Tweak à la volée ... beaucoup de choses. Hélas, vous ne pouvez appeler que les fonctions définies dans le programme en cours, mais fsync fonctionne bien.

(La commande 'info functions' de gdb listera toutes les fonctions disponibles. Attention cependant. Vous utilisez LIVE sur un processus.)

Il existe également la commande peekfd (présente dans le paquet psmisc sur Debian Jessie et autres) qui vous permettra de voir ce qui se cache dans les tampons d’un processus. Encore une fois, /proc/$(pidof python)/fd vous montrera des descripteurs de fichier valides à donner comme arguments à peekfd.

Si vous ne vous souvenez pas de -u pour python, vous pouvez toujours préfixer une commande avec stdbuf (dans coreutils, déjà installé) pour définir stdin/stdout/stderr à mettre en mémoire tampon, en mémoire tampon de ligne ou en mémoire tampon de bloc comme vous le souhaitez:

stdbuf -i 0 -o 0 -e 0 python myscript.py > unbuffered.output

Bien sûr, man pages sont vos amis, hé! peut-être un alias pourrait être utile ici aussi.

alias python='python -u'

Maintenant, votre python utilise toujours -u pour tous vos efforts en ligne de commande!

16
lornix

Commencez par vous assurer que vous avez les symboles de débogage pour Python (ou au moins glibc). Sur Fedora1 vous pouvez les installer avec:

dnf debuginfo-install python

Ensuite, attachez gdb au script en cours d'exécution et exécutez les commandes suivantes:

[user@Host ~]$ pidof python2
9219
[user@Host ~]$ gdb python2 9219
GNU gdb (GDB) Fedora 7.7.1-13.fc20
...
0x00007fa934278780 in __read_nocancel () at ../sysdeps/unix/syscall-template.S:81
81  T_PSEUDO (SYSCALL_SYMBOL, SYSCALL_NAME, SYSCALL_NARGS)
(gdb) call fflush(stdout)
$1 = 0
(gdb) call setvbuf(stdout, 0, 2, 0)
$2 = 0
(gdb) quit
A debugging session is active.

    Inferior 1 [process 9219] will be detached.

Quit anyway? (y or n) y
Detaching from program: /usr/bin/python2, process 9219

Cela effacera stdout et désactivera également la mise en mémoire tampon. Le 2 de l'appel setvbuf est la valeur de _IONBF sur mon système. Vous aurez besoin de savoir ce qu'il y a sur vous (un grep _IONBF /usr/include/stdio.h devrait faire l'affaire).

Basé sur ce que j'ai vu dans l'implémentation de PyFile_SetBufSize et PyFile_WriteString dans CPython 2.7, cela devrait fonctionner plutôt bien, mais je ne peux donner aucune garantie.


1 Fedora inclut un type spécial de RPM appelé debuginfo rpms . Ces RPM créés automatiquement contiennent les informations de débogage des fichiers du programme, mais ont été déplacés vers un fichier externe.

5
Cristian Ciupitu

Il n'y a pas de solution à votre problème immédiat. Si votre script a déjà commencé, vous ne pouvez pas modifier le mode de mise en mémoire tampon après coup. Ce sont tous des tampons en mémoire et tout cela est configuré lorsque le script démarre, que les descripteurs de fichiers sont ouverts, que les canaux sont créés, etc.

De manière générale, si et seulement si une partie ou la totalité de la mise en mémoire tampon en question est effectuée au niveau IO en sortie, vous pouvez exécuter une commande sync; mais cela est généralement peu probable dans un cas comme celui-ci.

À l'avenir, vous pourrez utiliser l'option -u de Python.* exécuter le script. En général, de nombreuses commandes ont des options spécifiques pour désactiver la mise en mémoire tampon stdin/stdout et vous pouvez également connaître un certain succès générique avec la commande unbuffer du package expect.

UNE Ctrl+C provoquerait le vidage des tampons au niveau du système lorsque le programme serait interrompu sauf si le tamponnage est effectué par Python lui-même et il n'a pas implémenté la logique pour vider ses propres tampons avec Ctrl+C. Suspendre, planter ou tuer ne serait pas si gentil.

*Forcer stdin, stdout et stderr à ne pas bouger.

4
Jason C

Documentation Python 2.7.7, section "Configuration et utilisation de Python", sous-section 1. La ligne de commande et l’environnement , décrit cet argument Python:

- u

Forcer stdin, stdout et stderr à ne pas bouger. Sur les systèmes où cela compte, mettez également stdin, stdout et stderr en mode binaire.

Notez que la mise en mémoire tampon interne dans file.readlines () et les objets de fichier (pour la ligne dans sys.stdin) ne sont pas influencés par cette option. Pour contourner ce problème, vous souhaiterez utiliser file.readline () dans une boucle while: 1.

Et aussi cette variable d'environnement:

PYTHONUNBUFFERED

Si cette option est définie sur une chaîne non vide, cela équivaut à spécifier l'option -u.

2
harrymc

Je pense qu'une autre solution possible peut consister à forcer le processus kill avec le noyau vidé puis à analyser le contenu de la mémoire à titre posthume.

0
jacek