--format
매개변수를 사용하여 파일 형식을 변경하여 속도 범위 프로필 또는 원시 데이터를 생성할 수 있습니다. 샘플링 속도 변경, GIL을 보유하는 스레드만 포함하도록 필터링, 네이티브 C 확장 프로파일링, 스레드 ID 표시, 하위 프로세스 프로파일링 등을 포함한 기타 옵션에 대한 자세한 내용은 py-spy record --help
참조하세요.
Top은 Unix top 명령과 유사하게 Python 프로그램에서 어떤 기능이 가장 많은 시간을 소비하는지 실시간 보기로 보여줍니다. 다음을 사용하여 py-spy 실행:
py-spy top --pid 12345
# OR
py-spy top -- python myprogram.py
Python 프로그램의 실시간 업데이트 상위 수준 보기가 표시됩니다.
py-spy는 dump
명령을 사용하여 각 Python 스레드에 대한 현재 호출 스택을 표시할 수도 있습니다.
py-spy dump --pid 12345
그러면 각 스레드의 호출 스택과 기타 기본 프로세스 정보가 콘솔에 출력됩니다.
이는 Python 프로그램이 중단된 위치를 파악하기 위해 단일 호출 스택만 필요한 경우에 유용합니다. 이 명령에는 --locals
플래그를 설정하여 각 스택 프레임과 관련된 지역 변수를 인쇄하는 기능도 있습니다.
이 프로젝트의 목표는 프로그램이 프로덕션 트래픽을 제공하는 경우에도 실행 중인 Python 프로그램을 프로파일링하고 디버그할 수 있도록 하는 것입니다.
다른 많은 Python 프로파일링 프로젝트가 있지만 거의 모두 어떤 방식으로든 프로파일링된 프로그램을 수정해야 합니다. 일반적으로 프로파일링 코드는 대상 Python 프로세스 내부에서 실행되므로 속도가 느려지고 프로그램 작동 방식이 변경됩니다. 이는 일반적으로 성능에 눈에 띄는 영향을 미치기 때문에 프로덕션 서비스에서 문제를 디버깅하기 위해 이러한 프로파일러를 사용하는 것이 일반적으로 안전하지 않다는 것을 의미합니다.
py-spy는 Linux의 process_vm_readv 시스템 호출, OSX의 vm_read 호출 또는 Windows의 ReadProcessMemory 호출을 사용하여 Python 프로그램의 메모리를 직접 읽는 방식으로 작동합니다.
Python 프로그램의 호출 스택을 파악하는 것은 전역 PyInterpreterState 변수를 보고 인터프리터에서 실행 중인 모든 Python 스레드를 가져온 다음 각 스레드의 각 PyFrameObject를 반복하여 호출 스택을 얻는 방식으로 수행됩니다. Python ABI는 버전마다 변경되므로 Rust의 bingen을 사용하여 관심 있는 각 Python 인터프리터 클래스에 대해 서로 다른 Rust 구조를 생성하고 이렇게 생성된 구조체를 사용하여 Python 프로그램의 메모리 레이아웃을 파악합니다.
Python 인터프리터의 메모리 주소를 얻는 것은 주소 공간 레이아웃 무작위화로 인해 약간 까다로울 수 있습니다. 대상 Python 인터프리터가 기호와 함께 제공되는 경우 Python 버전에 따라 interp_head
또는 _PyRuntime
변수를 역참조하여 인터프리터의 메모리 주소를 알아내는 것이 매우 쉽습니다. 그러나 많은 Python 버전은 제거된 바이너리와 함께 제공되거나 Windows에서 해당 PDB 기호 파일 없이 제공됩니다. 이러한 경우 우리는 BSS 섹션을 통해 유효한 PyInterpreterState를 가리키는 것처럼 보이는 주소를 검색하고 해당 주소의 레이아웃이 우리가 기대하는 것과 같은지 확인합니다.
예! py-spy는 x86_64 Linux 및 Windows에서 C/C++ 또는 Cython과 같은 언어로 작성된 기본 Python 확장의 프로파일링을 지원합니다. 명령줄에 --native
전달하여 이 모드를 활성화할 수 있습니다. 최상의 결과를 얻으려면 기호를 사용하여 Python 확장을 컴파일해야 합니다. Cython 프로그램에 대해 주목할 만한 또 다른 점은 py-spy가 원본 .pyx 파일의 줄 번호를 반환하기 위해 생성된 C 또는 C++ 파일이 필요하다는 것입니다. 자세한 내용은 블로그 게시물을 읽어보세요.
--subprocesses
플래그를 레코드 또는 상위 뷰에 전달하면 py-spy는 대상 프로그램의 하위 프로세스인 모든 Python 프로세스의 출력도 포함합니다. 이는 다중 처리 또는 Gunicorn 작업자 풀을 사용하는 애플리케이션을 프로파일링하는 데 유용합니다. py-spy는 생성되는 새 프로세스를 모니터링하고 자동으로 프로세스에 연결하여 출력에 샘플을 포함합니다. 레코드 보기에는 호출 스택에 있는 각 프로그램의 PID 및 cmdline이 포함되며, 하위 프로세스는 상위 프로세스의 하위 프로세스로 표시됩니다.
py-spy는 다른 Python 프로세스에서 메모리를 읽어 작동하며, 이는 OS 및 시스템 설정에 따라 보안상의 이유로 허용되지 않을 수 있습니다. 대부분의 경우 루트 사용자(sudo 또는 유사한 사용자)로 실행하면 이러한 보안 제한 사항을 피할 수 있습니다. OSX에서는 항상 루트로 실행해야 하지만 Linux에서는 py-spy 실행 방법과 시스템 보안 설정에 따라 달라집니다.
Linux에서 기본 구성은 하위 프로세스가 아닌 프로세스에 연결할 때 루트 권한을 요구하는 것입니다. py-spy의 경우 이는 py-spy를 사용하여 프로세스( py-spy record -- python myprogram.py
)를 생성함으로써 루트 액세스 없이 프로파일링할 수 있지만 PID를 지정하여 기존 프로세스에 연결하려면 일반적으로 루트( sudo py-spy record --pid 123456
가 필요함을 의미합니다. sudo py-spy record --pid 123456
). ptrace_scope sysctl 변수를 설정하여 Linux에서 이 제한을 제거할 수 있습니다.
py-spy는 코드를 적극적으로 실행 중인 스레드의 스택 추적만 포함하고 휴면 상태이거나 유휴 상태인 스레드는 제외하려고 시도합니다. 가능하면 py-spy는 Linux에서 /proc/PID/stat
읽고, OSX에서 mach thread_basic_info 호출을 사용하고, 현재 SysCall이 유휴 상태인지 확인하여 OS에서 이 스레드 활동 정보를 가져오려고 시도합니다. Windows에서.
이 접근 방식에는 몇 가지 제한 사항이 있지만 이로 인해 유휴 스레드가 여전히 활성 상태로 표시될 수 있습니다. 우선, 프로그램을 일시 중지하기 전에 이 스레드 활동 정보를 얻어야 합니다. 왜냐하면 일시 중지된 프로그램에서 이 정보를 얻으면 항상 유휴 상태임을 반환하게 되기 때문입니다. 이는 스레드 활동을 얻은 다음 스택 추적을 얻을 때 스레드가 다른 상태에 있는 잠재적인 경쟁 조건이 있음을 의미합니다. Linux의 FreeBSD 및 i686/ARM 프로세서에 대해서는 OS에서 스레드 활동을 쿼리하는 기능도 아직 구현되지 않았습니다. Windows에서는 IO에서 차단된 호출도 예를 들어 stdin에서 입력을 읽을 때 유휴 상태로 표시되지 않습니다. 마지막으로 일부 Linux 호출에서는 우리가 사용하고 있는 ptrace 연결로 인해 유휴 스레드가 일시적으로 깨어나 procfs에서 읽을 때 거짓 긍정이 발생할 수 있습니다. 이러한 이유로 우리는 Python에서 알려진 특정 호출을 유휴 상태로 표시하는 경험적 폴백도 제공합니다.
py-spy가 유휴 상태로 간주하는 프레임을 포함하는 --idle
플래그를 설정하여 이 기능을 비활성화할 수 있습니다.
Python 3.6 및 이전 버전의 _PyThreadState_Current
기호가 가리키는 threadid 값을 확인하고 Python 3.7 이상의 _PyRuntime
구조체에서 해당 값을 찾아 GIL 활동을 얻습니다. 이러한 기호는 Python 배포판에 포함되지 않을 수 있으며, 이로 인해 GIL을 유지하는 스레드를 확인하는 데 실패하게 됩니다. 현재 GIL 사용량도 top
뷰에 %GIL로 표시됩니다.
--gil
플래그를 전달하면 전역 해석기 잠금을 유지하는 스레드에 대한 추적만 포함됩니다. 어떤 경우에는 이것이 Python 프로그램이 시간을 어떻게 소비하는지에 대한 더 정확한 보기일 수 있지만, 이것이 여전히 활성 상태인 동안 GIL을 릴리스하는 확장의 활동을 놓칠 수 있다는 점을 알아야 합니다.
OSX에는 루트 사용자도 /usr/bin에 있는 바이너리에서 메모리를 읽지 못하도록 방지하는 시스템 무결성 보호라는 기능이 있습니다. 불행하게도 여기에는 OSX와 함께 제공되는 Python 인터프리터가 포함됩니다.
이 문제를 처리하는 방법에는 몇 가지가 있습니다.
Docker 컨테이너 내에서 py-spy를 실행하면 일반적으로 루트로 실행하는 경우에도 권한 거부 오류가 발생합니다.
이 오류는 우리가 사용하는 process_vm_readv 시스템 호출을 제한하는 docker로 인해 발생합니다. 이는 Docker 컨테이너를 시작할 때 --cap-add SYS_PTRACE
설정하여 재정의할 수 있습니다.
또는 docker-compose yaml 파일을 편집할 수 있습니다.
your_service:
cap_add:
- SYS_PTRACE
이 설정을 적용하려면 Docker 컨테이너를 다시 시작해야 합니다.
호스트 OS에서 py-spy를 사용하여 Docker 컨테이너 내에서 실행 중인 프로세스를 프로파일링할 수도 있습니다.
py-spy가 프로세스 메모리를 읽으려면 SYS_PTRACE
필요합니다. Kubernetes는 기본적으로 해당 기능을 삭제하여 오류가 발생합니다.
Permission Denied: Try running again with elevated permissions by going 'sudo env "PATH=$PATH" !!'
이를 처리하는 권장 방법은 사양을 편집하고 해당 기능을 추가하는 것입니다. 배포의 경우 이를 Deployment.spec.template.spec.containers
에 추가하면 됩니다.
securityContext:
capabilities:
add:
- SYS_PTRACE
이에 대한 자세한 내용은 여기를 참조하세요. https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-capability-for-a-container 이렇게 하면 기존 포드가 제거되고 다시 생성됩니다. .
알파인 파이썬은 manylinux
휠을 선택 해제합니다: pypa/pip#3969(코멘트). 다음 단계로 이동하여 pip를 사용하여 Alpine에 py-spy를 설치하도록 이 동작을 재정의할 수 있습니다.
echo 'manylinux1_compatible = True' > /usr/local/lib/python3.7/site-packages/_manylinux.py
또는 GitHub 릴리스 페이지에서 musl 바이너리를 다운로드할 수 있습니다.
--nonblocking
옵션을 설정하면 py-spy는 프로파일링 중인 대상 Python을 일시 중지하지 않습니다. py-spy를 사용한 프로세스 샘플링이 성능에 미치는 영향은 일반적으로 매우 낮지만, 이 옵션을 설정하면 실행 중인 Python 프로그램이 중단되는 것을 완전히 방지할 수 있습니다.
이 옵션을 설정하면 py-spy는 실행 중인 Python 프로세스에서 인터프리터 상태를 대신 읽습니다. 메모리를 읽는 데 사용하는 호출은 원자적이지 않고 스택 추적을 얻기 위해 여러 호출을 실행해야 하기 때문에 샘플링할 때 가끔 오류가 발생한다는 의미입니다. 이는 샘플링 시 오류율이 증가하거나 출력에 부분 스택 프레임이 포함되는 것으로 나타날 수 있습니다.
아직은 아닙니다 =).
py-spy에서 보고 싶은 기능이 있는 경우 해당 문제를 추천하거나 누락된 기능을 설명하는 새 문제를 만드세요.
py-spy는 CLICOLOR 사양을 따르므로 환경에서 CLICOLOR_FORCE=1
설정하면 호출기에 파이프될 때에도 py-spy가 컬러 출력을 인쇄합니다.
py-spy는 Julia Evans의 뛰어난 rbspy 작업에서 많은 영감을 받았습니다. 특히, Flamegraph 및 Speedscope 파일을 생성하는 코드는 rbspy에서 직접 가져왔으며, 이 프로젝트에서는 rbspy에서 파생된 read-process-memory 및 proc-maps 크레이트를 사용합니다.
py-spy는 MIT 라이선스에 따라 출시됩니다. 전체 텍스트는 LICENSE 파일을 참조하세요.