Forçar liberação do buffer de saída no programa em execução

19

Eu tenho um script python de longa execução que periodicamente gera dados para a saída padrão que invoquei com algo como:

python script.py > output.txt

Este script está em execução há um tempo e eu quero pará-lo com Ctrl+, Cmas não perder nada de sua saída. Infelizmente, quando implementei o script, esqueci de liberar o buffer após cada linha de saída com algo parecido com o sys.stdout.flush()(a solução sugerida anteriormente para forçar a liberação da saída), portanto, chamar o Ctrl+ Cagora fará com que eu perca toda a minha saída.

Se estiver imaginando se há alguma maneira de interagir com um script python em execução (ou, mais geralmente, um processo em execução) para forçá-lo a liberar seu buffer de saída. Não estou perguntando como editar e executar novamente o script para que ele seja liberado corretamente - esta questão é especificamente sobre a interação com um processo em execução (e, no meu caso, não perder a saída da minha execução atual do código).

josliber
fonte

Respostas:

17

Se alguém realmente quisesse esses dados, sugiro anexar o depurador gdb ao interpretador python, interrompendo momentaneamente a tarefa, chamando fsync(1)( stdout ), desanexando-o (retomando o processo) e examinando o arquivo de saída.

Procure /proc/$(pidof python)/fdver descritores de arquivo válidos. $(pidof x)retorna o PID do processo chamado ' 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

Eu usei esse método para alterar as dir diretas, ajustar as configurações em tempo real ... muitas coisas. Infelizmente, você só pode chamar funções definidas no programa em execução, mas fsyncfunciona bem.

(O comando gdb ' info functions' listará todas as funções disponíveis. Tenha cuidado, porém. Você está operando o LIVE em um processo.)

Há também o comando peekfd(encontrado no psmiscpacote Debian Jessie e outros) que permitirá que você veja o que está oculto nos buffers de um processo. Novamente, /proc/$(pidof python)/fdmostrará descritores de arquivo válidos para fornecer como argumentos ao peekfd.

Se você não se lembra -udo python, sempre pode prefixar um comando com stdbuf(in coreutils, já instalado) para definir stdin / stdout / stderr como sem buffer, com buffer de linha ou com buffer de bloco, conforme desejado:

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

Claro, man pagessão seus amigos, ei! talvez um alias também possa ser útil aqui.

alias python='python -u'

Agora seu python sempre usa -upara todos os seus esforços na linha de comando!

lornix
fonte
5

Primeiro, verifique se você possui os símbolos de depuração para Python (ou pelo menos glibc). No Fedora 1 você pode instalá-los com:

dnf debuginfo-install python

Em seguida, anexe o gdb ao script em execução e execute os seguintes 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

Isso liberará o stdout e também desativará o buffer. O valor 2da setvbufchamada é o valor de _IONBFno meu sistema. Você precisará descobrir o que está no seu (a grep _IONBF /usr/include/stdio.hdeve fazer o truque).

Com base no que vi na implementação PyFile_SetBufSizee PyFile_WriteStringno CPython 2.7, ele deve funcionar muito bem, mas não posso garantir.


1 O Fedora inclui um tipo especial de RPMs chamado debuginfo rpms . Esses RPMs criados automaticamente contêm as informações de depuração dos arquivos de programa, mas foram movidos para um arquivo externo.

Cristian Ciupitu
fonte
Eu tentei o python 2.7 e acabei com o mesmo resultado. Vou dar uma olhada na atualização de depuração que você postou.
DarkHeart 2/16/16
Pelo que vale, o CPython 3.5 parece ter uma implementação diferente de E / S ( fileobject.c) que 2.7 . Alguém precisa se aprofundar no iomódulo.
Cristian Ciupitu
@ DarkHeart, você pode querer testar primeiro com um programa simples como este .
Cristian Ciupitu
4

Não há solução para o seu problema imediato. Se o seu script já foi iniciado, você não poderá alterar o modo de buffer após o fato. Esses são todos os buffers da memória e tudo isso é configurado quando o script é iniciado, os identificadores de arquivo são abertos, os pipes são criados etc.

Como um tiro no escuro, se e somente se parte ou todo o buffer em questão está sendo feito no nível de E / S na saída, você pode executar um synccomando; mas isso geralmente é improvável em um caso como esse.

No futuro, você pode usar a -uopção * do Python para executar o script. Em geral, muitos comandos têm opções específicas de comando para desativar o buffer stdin / stdout, e você também pode ter algum sucesso genérico com o unbuffercomando do expectpacote.

O A Ctrl+ Cfaria com que os buffers no nível do sistema fossem liberados quando o programa for interrompido , a menos que o buffer seja feito pelo próprio Python e ele não tenha implementado a lógica para liberar seus próprios buffers com o Ctrl+ C. Suspender, travar ou matar não seria tão gentil.

* Force stdin, stdout e stderr a serem totalmente inalterados.

Jason C
fonte
2

Documentação do Python 2.7.7, seção "Configuração e uso do Python", subseção 1. Linha de comando e ambiente , descreve este argumento do Python:

-você

Força stdin, stdout e stderr a serem totalmente inalterados. Nos sistemas onde importa, coloque também stdin, stdout e stderr no modo binário.

Observe que há buffer interno em file.readlines () e objetos de arquivo (para linha em sys.stdin) que não é influenciado por esta opção. Para contornar isso, convém usar file.readline () dentro de um tempo 1: loop.

E também esta variável de ambiente:

PYTHONUNBUFFERED

Se isso estiver definido como uma sequência não vazia, será equivalente a especificar a opção -u.

harrymc
fonte
1
Obrigado - mas esses dois parecem opções que eu precisaria especificar quando executei meu script python pela primeira vez. Gostaria de saber se existe uma maneira de obter um script em execução para despejar sua saída.
josliber
Não acredito que exista essa solução, porque os dados provavelmente estão em um buffer de memória em algum lugar. Você precisaria injetar uma dll no python que conheça seu executável o suficiente para saber onde está o buffer e como escrevê-lo. Acredito que a maioria das pessoas usaria apenas um dos 2 métodos acima. Adicionar uma variável de ambiente é bastante fácil, afinal.
21814 harrymc
OK, é bom saber que pode não haver uma solução. Conforme declarado na minha pergunta, eu sei como liberar buffers em python (eu teria usado sys.stdout.flush(), mas sua -uopção parece ainda mais fácil), mas acabei de esquecer de fazê-lo ao invocar meu código. Já tendo executado meu código por mais de uma semana, esperava que houvesse uma maneira de obter minha saída sem precisar executar novamente o código por mais uma semana.
josliber
Um método rebuscado, se você souber como são os dados, é fazer um despejo de memória completo do processo usando o Process Explorer e , em seguida, procurar as seqüências de caracteres no arquivo. Isso não encerrará o processo, portanto você ainda pode tentar outros métodos.
harrymc
Estou no linux - existem equivalentes no linux desse software?
josliber
2

Parece que eu estava sendo muito cauteloso em perder a saída em buffer depois de executar o Ctrl-C; de acordo com este post , devo esperar que o buffer seja liberado se meu programa tiver uma saída normal, o que seria o caso se eu pressionar Ctrl-C. Por outro lado, eu perderia a saída em buffer se matasse o script com SIGKILL ou similar.

josliber
fonte
Você teria que tentar descobrir. Ctrl-C fará com que os buffers de E / S de baixo nível sejam liberados. Se o Python fizer seu próprio buffer, o Ctrl-C somente os liberará se o Python tiver a gentileza de implementar a lógica para fazê-lo. Felizmente, o Python decidiu não reinventar uma roda e confia no nível normal de buffer do sistema. Não faço ideia se é esse o caso. Mas esteja avisado.
Jason C
O sistema operacional nunca pode liberar o que está no espaço de memória do programa. O que é liberado são dados na memória do sistema, ou seja, dados já gravados pelo programa usando chamadas do sistema. No caso de uma saída de erro, mesmo esses buffers do sistema são descartados. Em resumo, os dados ainda não gravados pelo Python não podem ser liberados e são perdidos em todos os casos.
precisa saber é
0

Penso que outra solução possível pode ser forçar a morte do processo com o núcleo despejado e depois analisar o conteúdo da memória postumamente.

jacek
fonte