Принудительная очистка буфера вывода в работающей программе

20

У меня есть долго работающий скрипт Python, который периодически выводит данные в стандартный вывод, который я вызывал с чем-то вроде:

python script.py > output.txt

Этот скрипт был запущен некоторое время, и я хочу остановить его с помощью Ctrl+, Cно не потерять его вывод. К сожалению, когда я реализовал сценарий, я забыл очищать буфер после каждой строки вывода чем-то вроде sys.stdout.flush()( ранее предложенное решение для принудительной очистки вывода), поэтому вызов Ctrl+ Cпрямо сейчас заставит меня потерять весь вывод.

Если вам интересно, есть ли какой-нибудь способ взаимодействия с работающим скриптом Python (или, в более общем случае, с запущенным процессом), чтобы заставить его очистить выходной буфер. Я не спрашиваю, как отредактировать и повторно запустить скрипт, чтобы заставить его корректно сбрасываться - этот вопрос конкретно о взаимодействии с запущенным процессом (и, в моем случае, не о потере вывода из моего текущего выполнения кода).

josliber
источник

Ответы:

18

Если кто-то действительно хочет эти данные, я бы предложил подключить отладчик gdb к интерпретатору python, на мгновение остановить задачу, вызвать fsync(1)( stdout ), отсоединиться от нее (возобновить процесс) и перейти к просмотру выходного файла.

Посмотрите, /proc/$(pidof python)/fdчтобы увидеть действительные файловые дескрипторы. $(pidof x)возвращает PID процесса с именем ' 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

Я использовал этот метод, чтобы изменить рабочий каталог, настроить параметры на лету ... многие вещи. Увы, вы можете вызывать только те функции, которые определены в работающей программе, но fsyncработает хорошо.

(Команда GDB ' info functions' выведет список всех доступных функций. Будьте осторожны. Вы работаете с LIVE в процессе.)

Также есть команда peekfd(находится в psmiscпакете на Debian Jessie и других), которая позволит вам увидеть, что скрывается в буферах процесса. Опять же, /proc/$(pidof python)/fdпокажет вам действительные файловые дескрипторы, чтобы предоставить в качестве аргументов peekfd.

Если вы не помните -uPython, вы всегда можете добавить к команде префикс stdbuf(in coreutils, уже установленный), чтобы установить для stdin / stdout / stderr значение unbuffered, буферизацию строки или буферизацию блока по желанию:

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

Конечно, man pagesтвои друзья, эй! возможно, псевдоним может быть полезен и здесь.

alias python='python -u'

Теперь ваш Python всегда использует -uдля всех ваших усилий командной строки!

lornix
источник
5

Сначала убедитесь, что у вас есть символы отладки для Python (или хотя бы glibc). На Fedora 1 вы можете установить их с помощью:

dnf debuginfo-install python

Затем присоедините gdb к запущенному сценарию и выполните следующие команды:

[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

Это очистит стандартный вывод, а также отключит буферизацию. Значение 2от setvbufвызова является значением в _IONBFмоей системе. Вам нужно будет выяснить, что у вас ( grep _IONBF /usr/include/stdio.hнужно сделать свое дело).

Исходя из того, что я видел в реализации PyFile_SetBufSizeи PyFile_WriteStringв CPython 2.7, он должен работать довольно хорошо, но я не могу дать никаких гарантий.


1 Fedora включает в себя специальный тип RPM, называемый debuginfo rpms . Эти автоматически созданные RPM содержат информацию об отладке из программных файлов, но перемещены во внешний файл.

Кристиан Чиупиту
источник
Я попробовал Python 2.7 и в итоге получил тот же результат. Я посмотрю на выложенное вами обновление отладки.
DarkHeart
Что бы ни стоило, CPython 3.5, кажется, имеет другую реализацию I / O ( fileobject.c), чем 2.7 . Кто-то должен покопаться в ioмодуле.
Кристиан Чупиту
@ DarkHeart, вы можете сначала протестировать с помощью простой программы, подобной этой .
Кристиан Чиупиту
4

Там нет решения вашей непосредственной проблемы. Если ваш скрипт уже запущен, вы не можете изменить режим буферизации по факту. Это все буферы в памяти, и все это устанавливается при запуске скрипта, при открытии файловых дескрипторов, создании каналов и т. Д.

В общем, если и только если некоторая или вся рассматриваемая буферизация выполняется на уровне ввода-вывода на выходе, вы можете выполнить syncкоманду; но это обычно маловероятно в таком случае.

В будущем вы можете использовать -uопцию Python * для запуска скрипта. В общем, многие команды имеют специфичные для команды опции для отключения буферизации stdin / stdout, и вы также можете добиться общего успеха с unbufferкомандой из expectпакета.

« CtrlCПриведет к сбросу буферов системного уровня при прерывании программы, если только буферизация не выполняется самим Python и в нем не реализована логика сброса собственных буферов с « CtrlC. Приостановка, сбой или убийство не будут такими добрыми.

* Принудительно отключить буферизацию stdin, stdout и stderr.

Джейсон С
источник
2

Python 2.7.7 Документация, раздел «Настройка и использование Python», подраздел 1. Командная строка и среда , описывает этот аргумент Python:

-u

Заставьте stdin, stdout и stderr быть полностью небуферизованными. В системах, где это важно, также установите stdin, stdout и stderr в двоичном режиме.

Обратите внимание, что в file.readlines () и File Objects (для строки в sys.stdin) есть внутренняя буферизация, на которую не влияет эта опция. Чтобы обойти это, вы захотите использовать file.readline () внутри цикла while 1 :.

А также эта переменная среды:

PYTHONUNBUFFERED

Если для этого параметра задана непустая строка, это эквивалентно указанию опции -u.

harrymc
источник
1
Спасибо - но оба они звучат как опции, которые мне нужно будет указать при первом запуске скрипта Python. Мне интересно, есть ли способ получить работающий скрипт для вывода его вывода.
josliber
Я не верю, что есть такое решение, потому что данные, вероятно, где-то в буфере памяти. Вам нужно будет внедрить dll в python, который достаточно хорошо знает свой исполняемый файл, чтобы знать, где находится буфер и как его записать. Я полагаю, что большинство людей просто используют один из двух вышеуказанных методов. В конце концов, добавить переменную среды довольно просто.
harrymc
Хорошо, приятно знать, что не может быть решения. Как указано в моем вопросе, я знаю, как очищать буферы в python (я бы использовал sys.stdout.flush(), но ваш -uвариант кажется еще проще), но просто забыл сделать это при вызове моего кода. После того, как мой код был запущен более недели, я надеялся, что есть способ получить результат без необходимости повторного запуска кода в течение еще одной недели.
josliber
Надуманным методом, если вы знаете, как выглядят данные, является получение полного дампа памяти процесса с помощью Process Explorer , а затем поиск строк в файле. Это не прекратит процесс, поэтому вы можете попробовать другие методы.
harrymc
Я нахожусь на Linux - есть ли Linux-эквиваленты этого программного обеспечения?
josliber
2

Похоже, я был слишком осторожен с потерей буферизованного вывода после запуска Ctrl-C; в соответствии с этим постом я должен ожидать, что буфер будет очищен, если моя программа имеет нормальный выход, что было бы, если бы я нажал Ctrl-C. С другой стороны, я бы потерял буферизованный вывод, если бы убил скрипт с помощью SIGKILL или подобного.

josliber
источник
Вы должны попробовать это, чтобы узнать. Ctrl-C приведет к сбросу низкоуровневых буферов ввода-вывода. Если Python выполняет свою собственную буферизацию, Ctrl-C будет сбрасывать их, только если Python достаточно любезен, чтобы реализовать логику для этого. Надеемся, что Python решил не изобретать велосипед и полагается на нормальный уровень буферизации системы. Я понятия не имею, так ли это. Но будьте осторожны.
Джейсон С
ОС никогда не сможет сбросить то, что находится в памяти программы. Сбрасываются данные в системной памяти, то есть данные, уже записанные программой с помощью системных вызовов. В случае выхода из ошибки даже эти системные буферы отбрасываются. Короче говоря, данные, еще не записанные Python, не могут быть сброшены и потеряны во всех случаях.
harrymc
0

Я думаю, что другое возможное решение может заключаться в том, чтобы принудительно завершить процесс с выгруженным ядром, а затем посмертно анализировать содержимое памяти.

Яцек
источник