Forzar la descarga del búfer de salida en el programa en ejecución


20

Tengo un script de Python de larga ejecución que genera periódicamente datos en la salida estándar que he invocado con algo como:

python script.py > output.txt

Este script se ha estado ejecutando durante un tiempo y quiero detenerlo con Ctrl+ Cpero no perder nada de su salida. Desafortunadamente, cuando implementé el script, olvidé vaciar el búfer después de cada línea de salida con algo como sys.stdout.flush()(la solución sugerida anteriormente para forzar el vaciado de salida), por lo que invocar Ctrl+ Cahora me hará perder toda mi salida.

Si se pregunta si hay alguna forma de interactuar con un script de Python en ejecución (o, más generalmente, un proceso en ejecución) para forzarlo a vaciar su búfer de salida. No estoy preguntando cómo editar y volver a ejecutar el script para que se ejecute correctamente: esta pregunta se trata específicamente de interactuar con un proceso en ejecución (y, en mi caso, no perder el resultado de la ejecución de mi código actual).

Respuestas:


18

SI uno realmente quisiera esos datos, sugeriría adjuntar el depurador gdb al intérprete de python, detener momentáneamente la tarea, llamar fsync(1)( stdout ), desconectarla (reanudar el proceso) y examinar el archivo de salida.

Mire /proc/$(pidof python)/fdpara ver descriptores de archivo válidos. $(pidof x)devuelve el PID del proceso llamado ' 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

He usado este método para cambiar los directorios de trabajo, ajustar la configuración sobre la marcha ... muchas cosas. Por desgracia, solo puede llamar a funciones definidas en el programa en ejecución, fsyncaunque funciona bien.

(El comando gdb ' info functions' enumerará todas las funciones disponibles. Sin embargo, tenga cuidado. Está operando EN VIVO en un proceso).

También está el comando peekfd(que se encuentra en el psmiscpaquete de Debian Jessie y otros) que le permitirá ver lo que se esconde en las memorias intermedias de un proceso. Nuevamente, /proc/$(pidof python)/fdle mostraremos descriptores de archivo válidos para dar como argumentos a peekfd.

Si no recuerda -upara python, siempre puede prefijar un comando con stdbuf(in coreutils, ya instalado) para establecer stdin / stdout / stderr en no protegido, bloqueado en línea o bloqueado como lo desee:

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

Por supuesto, man pagesson tus amigos, ¡oye! Tal vez un alias podría ser útil aquí también.

alias python='python -u'

¡Ahora tu python siempre usa -upara todos tus esfuerzos de línea de comando!


5

Primero asegúrese de tener los símbolos de depuración para Python (o al menos glibc). En Fedora 1 puede instalarlos con:

dnf debuginfo-install python

Luego adjunte gdb al script en ejecución y ejecute los siguientes comandos:

[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

Esto vaciará stdout y también deshabilitará el almacenamiento en búfer. El 2de la setvbufllamada es el valor de _IONBFen mi sistema. Tendrá que averiguar qué hay en el suyo ( grep _IONBF /usr/include/stdio.hdebería hacer el truco).

Basado en lo que he visto en la implementación PyFile_SetBufSizey PyFile_WriteStringen CPython 2.7, debería funcionar bastante bien, pero no puedo hacer ninguna garantía.


1 Fedora incluye un tipo especial de RPM llamados debuginfo rpms . Estos RPM creados automáticamente contienen la información de depuración de los archivos del programa, pero se trasladan a un archivo externo.


Probé Python 2.7 y terminé con el mismo resultado. Echaré un vistazo a la actualización de depuración que publicaste.
DarkHeart

Por lo que vale, CPython 3.5 parece tener una implementación diferente de E / S ( fileobject.c) que 2.7 . Alguien necesita cavar en el iomódulo.
Cristian Ciupitu

@DarkHeart, es posible que desee probar primero con un programa simple como este .
Cristian Ciupitu

4

No hay solución a su problema inmediato. Si su script ya comenzó, no puede cambiar el modo de almacenamiento en búfer después del hecho. Todos estos son buffers en memoria y todo eso se configura cuando se inicia el script, se abren los identificadores de archivo, se crean tuberías, etc.

Como una posibilidad remota, si y solo si una parte o la totalidad del almacenamiento intermedio en cuestión se realiza en el nivel IO en la salida, puede hacer un synccomando; pero esto es generalmente poco probable en un caso como este.

En el futuro, puede usar la -uopción * de Python para ejecutar el script. En general, muchos comandos tienen opciones específicas de comando para deshabilitar el almacenamiento en búfer stdin / stdout, y también puede tener cierto éxito genérico con el unbuffercomando del expectpaquete.

A Ctrl+ Ccausaría que los búferes a nivel del sistema se vacíen cuando el programa se interrumpa a menos que Python realice el almacenamiento en búfer y no haya implementado la lógica para vaciar sus propios búferes con Ctrl+ C. Una suspensión, choque o muerte no sería tan amable.

* Forzar que stdin, stdout y stderr estén totalmente libres de búfer.


2

Documentación de Python 2.7.7, sección "Configuración y uso de Python", subsección 1. La línea de comandos y el entorno , describe este argumento de Python:

-u

Obliga a stdin, stdout y stderr a estar totalmente libres de búfer. En los sistemas donde importa, también ponga stdin, stdout y stderr en modo binario.

Tenga en cuenta que existe un almacenamiento intermedio interno en file.readlines () y File Objects (para la línea en sys.stdin) que no está influenciada por esta opción. Para evitar esto, querrá usar file.readline () dentro de un tiempo 1: bucle.

Y también esta variable de entorno:

PYTHONUNBUFFERED

Si se establece en una cadena no vacía, es equivalente a especificar la opción -u.


1
Gracias, pero ambas parecen opciones que necesitaría especificar la primera vez que ejecuté mi script de Python. Me pregunto si hay una manera de obtener un script en ejecución para volcar su salida.
josliber

No creo que haya tal solución, porque los datos probablemente estén en un búfer de memoria en alguna parte. Debería inyectar un dll en python que conozca su ejecutable lo suficientemente bien como para saber dónde está el búfer y cómo escribirlo. Creo que la mayoría de la gente simplemente usaría uno de los 2 métodos anteriores. Agregar una variable de entorno es bastante fácil, después de todo.
harrymc

Bien, es bueno saber que puede que no haya una solución. Como se indicó en mi pregunta, sé cómo vaciar los buffers en python (lo hubiera usado sys.stdout.flush(), pero su -uopción parece aún más fácil), pero me había olvidado de hacerlo al invocar mi código. Después de haber ejecutado mi código durante más de una semana, esperaba que hubiera una manera de obtener mi salida sin necesidad de volver a ejecutar el código durante otra semana.
josliber

Un método descabellado, si sabe cómo se ven los datos, es tomar un volcado de memoria completo del proceso usando Process Explorer y luego buscar las cadenas en el archivo. Esto no terminará el proceso, por lo que aún puede probar otros métodos.
harrymc

Estoy en Linux, ¿hay equivalentes Linux de ese software?
josliber

2

Parece que estaba siendo demasiado cauteloso acerca de perder por la salida almacenada después de ejecutar Ctrl-C; De acuerdo con esta publicación , debería esperar que el búfer se vacíe si mi programa tiene una salida normal, que sería el caso si presiono Ctrl-C. Por otro lado, perdería la salida almacenada en búfer si matara el script con SIGKILL o similar.


Tendrías que probarlo para descubrirlo. Ctrl-C hará que se vacíen las memorias intermedias de E / S de bajo nivel. Si Python hace su propio almacenamiento en búfer, Ctrl-C solo los eliminará si Python es lo suficientemente amable como para implementar la lógica para hacerlo. Con suerte, Python decidió no reinventar una rueda y se basa en el nivel normal de almacenamiento en búfer del sistema. No tengo idea si ese es el caso. Pero ten cuidado.
Jason C

El sistema operativo nunca puede eliminar lo que hay en el espacio de memoria del programa. Lo que se descarga son datos en la memoria del sistema, es decir, datos ya escritos por el programa mediante llamadas al sistema. En caso de una salida de error, incluso estos búferes del sistema se descartan. En resumen, los datos aún no escritos por Python no se pueden vaciar y se pierden en todos los casos.
harrymc

0

Creo que otra posible solución puede ser forzar el proceso de matar con el núcleo descargado y luego analizar póstumamente el contenido de memoria.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.