성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] VT sequences to "CONOUT$" vs. STD_O...
[정성태] NetCoreDbg is a managed code debugg...
[정성태] Evaluating tail call elimination in...
[정성태] What’s new in System.Text.Json in ....
[정성태] What's new in .NET 9: Cryptography ...
[정성태] 아... 제시해 주신 "https://akrzemi1.wordp...
[정성태] 다시 질문을 정리할 필요가 있을 것 같습니다. 제가 본문에...
[이승준] 완전히 잘못 짚었습니다. 댓글 지우고 싶네요. 검색을 해보...
[정성태] 우선 답글 감사합니다. ^^ 그런데, 사실 저 예제는 (g...
[이승준] 수정이 안되어서... byteArray는 BYTE* 타입입니다...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>파이썬 pystack 소개 - 메모리 덤프로부터 콜 스택 열거</h1> <p> 닷넷의 경험에 비춰 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 리눅스 환경의 .NET Core 3/5+ 메모리 덤프를 분석하는 방법 - 두 번째 이야기 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/13135'>https://www.sysnet.pe.kr/2/0/13135</a> </pre> <br /> 사후 분석을 위한 자료로써 메모리 덤프가 상당히 중요한 역할을 합니다. 사실, 현업에서 실 서버 운영자들은 서비스에 장애가 발생했을 때 보통은 서버 재시작을 하는데요, 그렇게 되면 나중에 개발자들이 문제를 찾기가 어렵습니다. 따라서, 해당 현상에 대한 재발 가능성은 여전한 상태로 남습니다.<br /> <br /> 바로 그런 상황에서, 협업이 잘 된 서버 운영자라면 문제가 된 프로세스의 메모리 덤프를 뜬 다음 서버 재시작을 할 수 있습니다. 그런 경우라면 개발자들이 덤프 분석을 통해 어떤 문제점이 있었는지 추적할 수 있고 운이 좋다면 원인을 찾아 버그 수정을 할 수 있을 것입니다.<br /> <br /> 그동안 Microsoft 환경에서만 있다 보니, <a target='tab' href='https://www.sysnet.pe.kr/2/0/991'>메모리 덤프</a>는 그다지 어려운 작업이 아니었는데요, 이게 ^^; 리눅스로 와서는 문제가 좀 달라집니다.<br /> <br /> 예전에도 한 번 실습을 했듯이,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > linux - lldb를 이용한 .NET Core 응용 프로그램의 메모리 덤프 분석 방법 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12083'>https://www.sysnet.pe.kr/2/0/12083</a> </pre> <br /> 리눅스 역시 메모리 덤프 기능을 제공하지만, 기본적인 core 덤프는 크기가 너무 커서 부담스럽습니다. 닷넷 프로세스의 경우 <a target='tab' href='https://www.sysnet.pe.kr/2/0/12078#core_dump'>일반적인 예제 코드 상황에서도 22GB가 넘는 크기의 덤프 파일이 생성</a>되는데요, 그런 탓에 마이크로소프트는 닷넷 프로세스에 최적화된 dotnet-dump 전용 도구를 제공해 덤프 용량을 (<a target='tab' href='https://www.sysnet.pe.kr/2/0/13136#dotnet_dump_size'>59GB가 2GB로</a>) 확 줄여줍니다.<br /> <br /> 아쉽게도 파이썬은 프레임워크 차원에서 제공하는 전용 덤프 도구가 없습니다. 게다가 (닷넷의 sos와 같은) 전용 분석 도구도 없습니다. 이에 대해 찾아보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > How do I dump an entire Python process for later debugging inspection? ; <a target='tab' href='https://stackoverflow.com/questions/141802/how-do-i-dump-an-entire-python-process-for-later-debugging-inspection'>https://stackoverflow.com/questions/141802/how-do-i-dump-an-entire-python-process-for-later-debugging-inspection</a> </pre> <br /> pystack 도구를 소개하는데,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > bloomberg/pystack ; <a target='tab' href='https://github.com/bloomberg/pystack'>https://github.com/bloomberg/pystack</a> </pre> <br /> 이것 역시 리눅스의 기본 (gdb의) gcore 덤프를 분석하는 것이라고 하니... 문제는 여전할 것 같습니다. 그래도, 혹시나 파이썬의 메모리 할당이 조직적이어서 크기가 좀 작지 않을까요? ^^<br /> <br /> python:3.8-slim-buster 이미지의 Django 4.2.3 환경에서 간단한 예제 사이트를 만들어 올린 것을 gcore로 덤프 파일을 생성해 봤습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > # <span style='color: blue; font-weight: bold'>apt install gdb</span> # <span style='color: blue; font-weight: bold'>ps -aux</span> USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 140 1.0 0.1 1652836 178556 ? Sl Jul18 27:00 uwsgi ...[생략]... ...[생략]... # <span style='color: blue; font-weight: bold'>gcore 140</span> [New LWP 162] [New LWP 163] [New LWP 167] [New LWP 168] [New LWP 169] [New LWP 170] [New LWP 171] [New LWP 172] [New LWP 173] [New LWP 174] [New LWP 175] [New LWP 180] [New LWP 181] [New LWP 182] [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". __lll_lock_wait () at ../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:103 103 ../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S: No such file or directory. warning: target file /proc/140/cmdline contained unexpected null characters Saved corefile core.140 [Inferior 1 (process 140) detached] # <span style='color: blue; font-weight: bold'>ls -l core.140</span> -rw-r--r-- 1 root root 1380938936 Jul 20 01:17 core.140 </pre> <br /> 1,316MB면 1GB 약간 넘는 용량입니다. 오호~~~ 이 정도면 (비록 간단한 예제 사이트였지만) 현실적인 메모리 덤프 크기입니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 자, 그럼 분석을 해볼까요? 상황을 간단하게 만들기 위해 덤프를 뜬 환경에 매뉴얼에 따라 pystack도 설치한 후,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > $ <span style='color: blue; font-weight: bold'>git clone https://github.com/bloomberg/pystack.git</span> $ <span style='color: blue; font-weight: bold'>cd pystack</span> $ <span style='color: blue; font-weight: bold'>python3 -m pip install virtualenv</span> $ <span style='color: blue; font-weight: bold'>python3 -m venv ../pystack-env/</span> $ <span style='color: blue; font-weight: bold'>source ../pystack-env/bin/activate</span> (pystack-env) ~/pystack$ <span style='color: blue; font-weight: bold'>python3 -m pip install --upgrade pip</span> (pystack-env) ~/pystack$ <span style='color: blue; font-weight: bold'>python3 -m pip install -e .</span> (pystack-env) ~/pystack$ <span style='color: blue; font-weight: bold'>python3 -m pip install -r requirements-test.txt -r requirements-extra.txt</span> </pre> <br /> 위에서 떠 두었던 core.140 덤프 파일을 지정하면 자동으로 다음과 같은 내용을 출력합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > # <span style='color: blue; font-weight: bold'>pystack core /core.140</span> Using executable found in the core file: /usr/local/bin/uwsgi Core file information: state: t zombie: True niceness: 0 pid: 140 ppid: 1 sid: 1 uid: 0 gid: 0 pgrp: 1 executable: uwsgi arguments: uwsgi The process died due receiving signal SIGSTOP Traceback for thread 182 (uwsgi) [] (most recent call last): (Python) File "/usr/local/lib/python3.8/threading.py", line 890, in _bootstrap self._bootstrap_inner() (Python) File "/usr/local/lib/python3.8/threading.py", line 932, in _bootstrap_inner self.run() (Python) File "/usr/local/lib/python3.8/threading.py", line 870, in run self._target(*self._args, **self._kwargs) (Python) File "/usr/local/lib/python3.8/site-packages/pymongo/periodic_executor.py", line 140, in _run time.sleep(self._min_interval) Traceback for thread 181 (uwsgi) [] (most recent call last): (Python) File "/usr/local/lib/python3.8/threading.py", line 890, in _bootstrap self._bootstrap_inner() (Python) File "/usr/local/lib/python3.8/threading.py", line 932, in _bootstrap_inner self.run() (Python) File "/usr/local/lib/python3.8/threading.py", line 870, in run self._target(*self._args, **self._kwargs) (Python) File "/usr/local/lib/python3.8/site-packages/pymongo/periodic_executor.py", line 140, in _run time.sleep(self._min_interval) Traceback for thread 180 (uwsgi) [] (most recent call last): (Python) File "/usr/local/lib/python3.8/threading.py", line 890, in _bootstrap self._bootstrap_inner() (Python) File "/usr/local/lib/python3.8/threading.py", line 932, in _bootstrap_inner self.run() (Python) File "/usr/local/lib/python3.8/threading.py", line 870, in run self._target(*self._args, **self._kwargs) (Python) File "/usr/local/lib/python3.8/site-packages/pymongo/periodic_executor.py", line 125, in _run if not self._target(): (Python) File "/usr/local/lib/python3.8/site-packages/pymongo/monitor.py", line 53, in target monitor._run() # type:ignore[attr-defined] (Python) File "/usr/local/lib/python3.8/site-packages/pymongo/monitor.py", line 173, in _run self._server_description = self._check_server() (Python) File "/usr/local/lib/python3.8/site-packages/pymongo/monitor.py", line 214, in _check_server return self._check_once() (Python) File "/usr/local/lib/python3.8/site-packages/pymongo/monitor.py", line 250, in _check_once response, round_trip_time = self._check_with_socket(sock_info) (Python) File "/usr/local/lib/python3.8/site-packages/pymongo/monitor.py", line 271, in _check_with_socket response = Hello(conn._next_reply(), awaitable=True) (Python) File "/usr/local/lib/python3.8/site-packages/pymongo/pool.py", line 811, in _next_reply reply = self.receive_message(None) (Python) File "/usr/local/lib/python3.8/site-packages/pymongo/pool.py", line 931, in receive_message return receive_message(self, request_id, self.max_message_size) (Python) File "/usr/local/lib/python3.8/site-packages/pymongo/network.py", line 217, in receive_message _receive_data_on_socket(sock_info, 16, deadline) (Python) File "/usr/local/lib/python3.8/site-packages/pymongo/network.py", line 293, in _receive_data_on_socket wait_for_read(sock_info, deadline) (Python) File "/usr/local/lib/python3.8/site-packages/pymongo/network.py", line 274, in wait_for_read readable = sock_info.socket_checker.select(sock, read=True, timeout=timeout) (Python) File "/usr/local/lib/python3.8/site-packages/pymongo/socket_checker.py", line 65, in select res = self._poller.poll(timeout_) The frame stack for thread 175 is empty The frame stack for thread 173 is empty The frame stack for thread 174 is empty The frame stack for thread 172 is empty The frame stack for thread 171 is empty The frame stack for thread 170 is empty The frame stack for thread 169 is empty The frame stack for thread 168 is empty The frame stack for thread 167 is empty The frame stack for thread 140 is empty </pre> <br /> 가만 보면 "pstree -p" 명령어로 출력된 스레드의 목록과 일치하는군요. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > # <span style='color: blue; font-weight: bold'>pstree -p</span> ...[생략]... `-uwsgi(140)-+-{uwsgi}(167) |-{uwsgi}(168) |-{uwsgi}(169) |-{uwsgi}(170) |-{uwsgi}(171) |-{uwsgi}(172) |-{uwsgi}(173) |-{uwsgi}(174) |-{uwsgi}(175) |-{uwsgi}(180) |-{uwsgi}(181) `-{uwsgi}(182) </pre> <br /> 그런데, pystack의 분석은 이게 끝입니다. 닷넷의 sos가 다양한 명령어를 지원하는 것과는 달리 pystack은 오로지 스택 트레이스만 보여주고 있습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 사실, 위의 예제 환경은 너무 비현실적인 환경이긴 합니다. 파이썬의 특성상 <a target='tab' href='https://www.sysnet.pe.kr/2/0/13390'>--threads 보다는 --processes 옵션을 통해 서비스를 할 것</a>이고, 그렇다면 문제가 발생한 프로세스의 정확한 PID를 판단해 core 덤프를 남겨야 한다는 불편함이 있습니다. 보통, 문제가 발생했다고 하면 서비스 중인 프로세스가 대개 1개인 경우는 거의 없을 것입니다. 적게는 몇 수십 개에서 많게는 몇 천 개까지 설정했을 수 있는데, 과연 그런 문제의 상황에서 정확한 덤프를 뜰 수 있는 침착한 서버 운영자가 얼마나 될 것이냐... 라는 것도 문제가 될 수 있습니다.<br /> <br /> 아마도 현업에서 pystack의 효용성은 거의 없다고 봐도 무방하지 않을까요? ^^;<br /> <br /> <hr style='width: 50%' /><br /> <br /> 아래와 같은 오류가 발생한다면?<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > (pystack-env) ~/pystack$ <span style='color: blue; font-weight: bold'>python3 -m pip install -e .</span> ...[생략]... In file included from src/pystack/_pystack.cpp:1150: src/pystack/_pystack/elf_common.h:13:10: fatal error: elfutils/libdwelf.h: No such file or directory 13 | #include <elfutils/libdwelf.h> | ^~~~~~~~~~~~~~~~~~~~~ compilation terminated. ...[생략]... !! cmd_obj.run() error: command '/usr/bin/x86_64-linux-gnu-gcc' failed with exit code 1 [end of output] note: This error originates from a subprocess, and is likely not a problem with pip. ERROR: Failed building editable for pystack Failed to build pystack ERROR: Could not build wheels for pystack, which is required to install pyproject.toml-based projects </pre> <br /> README.md를 잘 안 읽어서 그런 것입니다. ^^ 다음과 같이 빌드를 위해 필요한 구성 요소를 설치해야 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > # apt-get install libdw-dev libelf-dev </pre> <br /> <hr style='width: 50%' /><br /> <br /> 무심코 실행한 명령어가 이런 오류를 낼 수도 있습니다. ^^;<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > # <span style='color: blue; font-weight: bold'>python3 ../pystack-env/bin/activate</span> File "../pystack-env/bin/activate", line 4 deactivate () { ^} </pre> <br /> 잘 보시면, activate 스크립트는 python 파일이 아니므로 python 런타임으로 실행할 대상이 아닙니다. 다음의 명령어를 실수로 입력했을 것입니다. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > $ <span style='color: blue; font-weight: bold'>source ../pystack-env/bin/activate</span> </pre> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1779
(왼쪽의 숫자를 입력해야 합니다.)