Wymuś opróżnienie bufora wyjściowego w uruchomionym programie

20

Mam długo działający skrypt Pythona, który okresowo wysyła dane do standardowego wyjścia, które wywołałem za pomocą czegoś takiego:

python script.py > output.txt

Ten skrypt działa już od jakiegoś czasu i chcę go zatrzymać za pomocą Ctrl+, Cale nie stracę żadnego z jego wyników. Niestety, kiedy zaimplementowałem skrypt, zapomniałem opróżnić bufor po każdej linii danych wyjściowych czymś takim sys.stdout.flush()( poprzednio sugerowane rozwiązanie wymuszające opróżnianie danych wyjściowych), więc wywołanie Ctrl+ w Ctej chwili spowoduje, że stracę całą moją wydajność.

Jeśli zastanawiasz się, czy jest jakiś sposób interakcji z działającym skryptem Pythona (lub, bardziej ogólnie, działającym procesem), aby zmusić go do opróżnienia bufora wyjściowego. Nie pytam, jak edytować i ponownie uruchamiać skrypt, aby poprawnie się opróżnił - to pytanie dotyczy konkretnie interakcji z uruchomionym procesem (i, w moim przypadku, utraty danych wyjściowych z mojego bieżącego wykonania kodu).

josliber
źródło

Odpowiedzi:

18

JEŚLI ktoś naprawdę chciałby tych danych, sugerowałbym podłączenie debugera gdb do interpretera Pythona, chwilowe zatrzymanie zadania, wywołanie fsync(1)(standardowe wyjście ), oderwanie się od niego (wznowienie procesu) i przejrzenie pliku wyjściowego.

Zajrzyj, /proc/$(pidof python)/fdaby zobaczyć prawidłowe deskryptory plików. $(pidof x)zwraca PID procesu o nazwie „ 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

Użyłem tej metody, aby zmienić działający reż, zmienić ustawienia w locie ... wiele rzeczy. Niestety, możesz wywoływać tylko funkcje zdefiniowane w uruchomionym programie, ale fsyncdziała dobrze.

(polecenie gdb ' info functions' wyświetli listę wszystkich dostępnych funkcji. Bądź jednak ostrożny. Pracujesz na żywo na procesie).

Istnieje również polecenie peekfd(znalezione w psmiscpakiecie na Debian Jessie i innych), które pozwoli ci zobaczyć, co ukrywa się w buforach procesu. Ponownie /proc/$(pidof python)/fdpokaże ci prawidłowe deskryptory plików , które możesz podać jako argumenty dla peekfd.

Jeśli nie pamiętasz -uPythona, zawsze możesz poprzedzić komendę stdbuf(w coreutils, już zainstalowane), aby ustawić stdin / stdout / stderr na niebuforowane, buforowane w linii lub buforowane w zależności od potrzeb:

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

Oczywiście, man pagessą twoi przyjaciele, hej! być może przydałby się tu także alias.

alias python='python -u'

Teraz twój python zawsze używa -udo wszystkich twoich działań w linii poleceń!

lornix
źródło
5

Najpierw upewnij się, że masz symbole debugowania dla Pythona (lub przynajmniej glibc). W Fedorze 1 możesz je zainstalować za pomocą:

dnf debuginfo-install python

Następnie dołącz gdb do działającego skryptu i uruchom następujące polecenia:

[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

Spowoduje to opróżnienie standardowego wejścia, a także wyłączenie buforowania. 2Z setvbufrozmowy jest wartością _IONBFw moim systemie. Musisz dowiedzieć się, co jest na twoim ( grep _IONBF /usr/include/stdio.hpowinno wystarczyć ).

Na podstawie tego, co widziałem w implementacji PyFile_SetBufSizei PyFile_WriteStringw CPython 2.7, powinno działać całkiem dobrze, ale nie mogę dać żadnych gwarancji.


1 Fedora zawiera specjalny typ RPM o nazwie debuginfo rpms . Te automatycznie utworzone RPM zawierają informacje debugowania z plików programu, ale zostały przeniesione do pliku zewnętrznego.

Cristian Ciupitu
źródło
Próbowałem Pythona 2.7 i uzyskałem ten sam wynik. Rzucę okiem na opublikowaną aktualizację debugowania.
DarkHeart,
Pod względem wartości CPython 3.5 wydaje się mieć inną implementację I / O ( fileobject.c) niż 2.7 . Ktoś musi zagłębić się w iomoduł.
Cristian Ciupitu,
@DarkHeart, możesz najpierw przetestować za pomocą prostego programu takiego jak ten .
Cristian Ciupitu,
4

Nie ma rozwiązania twojego bezpośredniego problemu. Jeśli skrypt już się rozpoczął, nie można zmienić trybu buforowania po fakcie. Są to wszystkie bufory pamięci i wszystko to jest konfigurowane po uruchomieniu skryptu, otwieraniu uchwytów plików, tworzeniu potoków itp.

Długie podejście, jeśli i tylko jeśli część lub całość buforowania jest wykonywana na poziomie IO na wyjściu, możesz wykonać syncpolecenie; ale w takich przypadkach jest to na ogół mało prawdopodobne.

W przyszłości możesz użyć -uopcji Pythona * do uruchomienia skryptu. Zasadniczo wiele poleceń ma opcje specyficzne dla polecenia, aby wyłączyć buforowanie stdin / stdout, a także może być ogólny sukces z unbufferpoleceniem z expectpakietu.

A Ctrl+ Cspowodowałoby opróżnienie buforów na poziomie systemu, gdy program zostanie przerwany, chyba że buforowanie jest wykonywane przez sam Python i nie zaimplementował logiki opróżniania własnych buforów za pomocą Ctrl+ C. Zawieszenie, awaria lub zabicie nie byłyby tak miłe.

* Zmusza stdin, stdout i stderr do całkowitego niebuforowania.

Jason C.
źródło
2

Dokumentacja Pythona 2.7.7, sekcja „Konfiguracja i użytkowanie Pythona”, podsekcja 1. Wiersz poleceń i środowisko , opisuje ten argument Pythona:

-u

Zmuś stdin, stdout i stderr do całkowitego niebuforowania. W systemach, w których ma to znaczenie, ustaw także stdin, stdout i stderr w trybie binarnym.

Zauważ, że w plikach.readlines () i obiektach plików (dla linii w sys.stdin) istnieje wewnętrzne buforowanie, na które ta opcja nie ma wpływu. Aby obejść ten problem, należy użyć file.readline () wewnątrz pętli while 1:.

A także ta zmienna środowiskowa:

PYTHONUNBUFFERED

Jeśli ustawiono niepusty ciąg znaków, jest to równoważne z określeniem opcji -u.

harrymc
źródło
1
Dzięki - ale oba wyglądają jak opcje, które musiałbym określić przy pierwszym uruchomieniu skryptu Python. Zastanawiam się, czy istnieje sposób na uruchomienie działającego skryptu, który zrzuci dane wyjściowe.
josliber
Nie sądzę, aby istnieje takie rozwiązanie, ponieważ dane są prawdopodobnie gdzieś w buforze pamięci. Będziesz musiał wstrzyknąć bibliotekę DLL do Pythona, który zna jego plik wykonywalny wystarczająco dobrze, aby wiedzieć, gdzie jest bufor i jak go wypisać. Wierzę, że większość ludzi użyłaby jednej z powyższych 2 metod. W końcu dodanie zmiennej środowiskowej jest raczej łatwe.
harrymc
OK, dobrze wiedzieć, że może nie być rozwiązania. Jak stwierdzono w moim pytaniu, wiem, jak opróżniać bufory w pythonie (użyłbym sys.stdout.flush(), ale twoja -uopcja wydaje się jeszcze łatwiejsza), ale właśnie zapomniałem to zrobić, wywołując mój kod. Mając już uruchomiony kod przez ponad tydzień, miałem nadzieję, że istnieje sposób na uzyskanie moich danych wyjściowych bez konieczności ponownego uruchamiania kodu przez kolejny tydzień.
josliber
Dalekosiężną metodą, jeśli wiesz, jak wyglądają dane, jest wykonanie pełnego zrzutu pamięci procesu za pomocą Eksploratora procesów , a następnie wyszukiwanie ciągów w pliku. To nie zakończy procesu, więc nadal możesz wypróbować inne metody.
harrymc
Jestem na Linuksie - czy istnieją odpowiedniki tego oprogramowania dla Linuksa?
josliber
2

Wygląda na to, że byłem zbyt ostrożny w przegrywaniu przez buforowane wyjście po uruchomieniu Ctrl-C; zgodnie z tym postem powinienem oczekiwać, że bufor zostanie opróżniony, jeśli mój program ma normalne wyjście, co miałoby miejsce, gdybym nacisnął Ctrl-C. Z drugiej strony straciłbym buforowane wyjście, gdybym zabił skrypt za pomocą SIGKILL lub podobnego.

josliber
źródło
Musisz spróbować, żeby się dowiedzieć. Ctrl-C spowoduje opróżnienie buforów IO niskiego poziomu. Jeśli Python wykonuje własne buforowanie, wówczas Ctrl-C opróżni je tylko wtedy, gdy Python jest na tyle uprzejmy, aby zaimplementować logikę. Mam nadzieję, że Python postanowił nie wymyślać koła na nowo i opiera się na normalnym poziomie buforowania w systemie. Nie mam pojęcia, czy tak jest. Ale ostrzegam.
Jason C,
System operacyjny nigdy nie może wyczyścić zawartości pamięci programu. Spłukiwane są dane w pamięci systemowej, co oznacza dane już zapisane przez program przy użyciu wywołań systemowych. W przypadku błędu wyjścia, nawet te bufory systemowe są odrzucane. Krótko mówiąc, dane, które nie zostały jeszcze zapisane przez Python, nie mogą zostać usunięte i we wszystkich przypadkach są tracone.
harrymc
0

Myślę, że innym możliwym rozwiązaniem może być wymuszenie zabicia procesu z zrzutem rdzenia, a następnie analiza zawartości pamięci pośmiertnie.

jacek
źródło