Forcer le vidage du tampon de sortie dans le programme en cours d'exécution

20

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

python script.py > output.txt

Ce script fonctionne depuis un certain temps et je veux l'arrêter avec Ctrl+ Cmais ne perdre aucune de ses sorties. 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 sys.stdout.flush()(la solution précédemment suggérée pour forcer le vidage de sortie), donc invoquer Ctrl+ Cmaintenant me fera perdre toute ma sortie.

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, un processus en cours d'exécution) pour le forcer à vider son tampon de sortie. Je ne demande pas comment éditer et réexécuter le script pour le faire vider correctement - cette question concerne spécifiquement l'interaction avec un processus en cours (et, dans mon cas, ne pas perdre la sortie de mon exécution de code actuelle).

josliber
la source

Réponses:

18

Si l'on voulait vraiment ces données, je suggérerais d'attacher le débogueur gdb à l'interpréteur python, d'arrêter momentanément la tâche, d'appeler fsync(1)( stdout ), de la détacher (reprendre le processus) et d'aller parcourir le fichier de sortie.

Regardez /proc/$(pidof python)/fdpour voir les descripteurs de fichiers 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 modifier les répertoires de travail, modifier les paramètres à la volée ... beaucoup de choses. Hélas, vous ne pouvez appeler que des fonctions définies dans le programme en cours d'exécution, mais cela fsyncfonctionne bien.

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

Il y a aussi la commande peekfd(trouvée dans le psmiscpaquet 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)/fdvous montrera des descripteurs de fichiers valides à donner comme arguments à peekfd.

Si vous ne vous souvenez pas -ude python, vous pouvez toujours préfixer une commande avec stdbuf(in coreutils, déjà installé) pour définir stdin / stdout / stderr sur non tamponné, ligne tamponnée ou bloc tamponné comme vous le souhaitez:

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

Bien sûr, man pagessont vos amis, hé! peut-être qu'un alias pourrait également être utile ici.

alias python='python -u'

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

lornix
la source
5

Assurez-vous d'abord que vous disposez des symboles de débogage pour Python (ou au moins glibc). Sur Fedora 1, vous pouvez les installer avec:

dnf debuginfo-install python

Ensuite , fixez 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 videra stdout et désactivera également la mise en mémoire tampon. Le 2de l' setvbufappel est la valeur de _IONBFsur mon système. Vous aurez besoin de savoir ce que vous avez (vous grep _IONBF /usr/include/stdio.hdevriez faire l'affaire).

D'après ce que j'ai vu dans l'implémentation de PyFile_SetBufSizeet PyFile_WriteStringdans CPython 2.7, cela devrait fonctionner assez 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 programme, mais déplacés dans un fichier externe.

Cristian Ciupitu
la source
J'ai essayé python 2.7 et j'ai fini avec le même résultat. Je vais jeter un œil à la mise à jour de débogage que vous avez publiée.
DarkHeart
Pour ce que ça vaut, CPython 3.5 semble avoir une implémentation d'E / S ( fileobject.c) différente de 2.7 . Quelqu'un a besoin de creuser dans le iomodule.
Cristian Ciupitu
@DarkHeart, vous voudrez peut-être d'abord tester avec un programme simple comme celui-ci .
Cristian Ciupitu
4

Il n'y a pas de solution à votre problème immédiat. Si votre script a déjà démarré, 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é au démarrage du script, à l'ouverture des descripteurs de fichiers, à la création de canaux, etc.

À long terme, 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 synccommande; mais cela est généralement peu probable dans un cas comme celui-ci.

À l'avenir, vous pouvez utiliser l' -uoption * de Python pour exécuter le script. En général, de nombreuses commandes ont des options spécifiques aux commandes pour désactiver la mise en mémoire tampon stdin / stdout, et vous pouvez également avoir un certain succès générique avec la unbuffercommande du expectpackage.

Un Ctrl+ Centraînerait le vidage des tampons au niveau du système lorsque le programme est interrompu à moins que le tampon ne soit effectué par Python lui-même et qu'il n'ait pas implémenté la logique pour vider ses propres tampons avec Ctrl+ C. Une suspension, un crash ou un kill ne serait pas si gentil.

* Force stdin, stdout et stderr à être totalement sans tampon.

Jason C
la source
2

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

-u

Forcer stdin, stdout et stderr à être totalement sans tampon. Sur les systèmes où cela est important, mettez également stdin, stdout et stderr en mode binaire.

Notez qu'il existe un tampon interne dans file.readlines () et File Objects (pour la ligne dans sys.stdin) qui n'est pas influencé 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 ce paramètre est défini sur une chaîne non vide, cela équivaut à spécifier l'option -u.

harrymc
la source
1
Merci, mais ces deux options sonnent comme des options que je devrais spécifier lors de la première exécution de mon script python. Je me demande s'il existe un moyen d'obtenir un script en cours d'exécution pour vider sa sortie.
josliber
Je ne crois pas qu'il existe une telle solution, car les données sont probablement quelque part dans une mémoire tampon. Vous auriez besoin d'injecter une DLL dans python qui connaît suffisamment son exécutable pour savoir où se trouve le tampon et comment l'écrire. Je crois que la plupart des gens utiliseraient simplement l'une des 2 méthodes ci-dessus. L'ajout d'une variable d'environnement est assez facile, après tout.
harrymc
OK, bon de savoir qu'il n'y a peut-être pas de solution. Comme indiqué dans ma question, je sais comment vider les tampons en python (j'aurais utilisé sys.stdout.flush(), mais votre -uoption semble encore plus facile), mais j'avais juste oublié de le faire lors de l'appel de mon code. Ayant déjà exécuté mon code pendant plus d'une semaine, j'espérais qu'il y avait un moyen d'obtenir ma sortie sans avoir besoin de relancer le code pendant une autre semaine.
josliber
Une méthode farfelue, si vous savez à quoi ressemblent les données, consiste à effectuer un vidage complet de la mémoire du processus à l'aide de Process Explorer , puis à rechercher les chaînes dans le fichier. Cela ne mettra pas fin au processus, vous pouvez donc essayer d'autres méthodes.
harrymc
Je suis sous Linux - y a-t-il des équivalents Linux de ce logiciel?
josliber
2

Il semble que j'étais trop prudent à propos de la perte par sortie tamponnée après avoir exécuté Ctrl-C; selon cet article, je dois m'attendre à ce que le tampon soit vidé si mon programme a une sortie normale, ce qui serait le cas si je tapais Ctrl-C. D'un autre côté, je perdrais la sortie tamponnée si je tuais le script avec SIGKILL ou similaire.

josliber
la source
Il faudrait l'essayer pour le savoir. Ctrl-C provoquera le vidage des tampons d'E / S de bas niveau. Si Python fait sa propre mise en mémoire tampon, Ctrl-C ne les purgera que si Python est assez gentil pour implémenter la logique pour le faire. Espérons que Python ait décidé de ne pas réinventer une roue et s'appuie sur le niveau normal de mise en mémoire tampon du système. Je ne sais pas si c'est le cas. Mais soyez prévenu.
Jason C
Le système d'exploitation ne peut jamais vider ce qui se trouve dans l'espace mémoire du programme. Ce qui est vidé, ce sont les données de la mémoire système, ce qui signifie que les données ont déjà été écrites par le programme à l'aide d'appels système. En cas de sortie d'erreur, même ces tampons système sont supprimés. En bref, les données non encore écrites par Python ne peuvent pas être vidées et sont perdues dans tous les cas.
harrymc
0

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

jacek
la source