Puede cambiar el formato del archivo para generar perfiles de velocímetro o datos sin procesar con el parámetro --format
. Consulte py-spy record --help
para obtener información sobre otras opciones, incluido cambiar la frecuencia de muestreo, filtrar para incluir solo subprocesos que contienen el GIL, generar perfiles de extensiones C nativas, mostrar identificadores de subprocesos, crear perfiles de subprocesos y más.
Top muestra una vista en vivo de qué funciones toman más tiempo en su programa Python, similar al comando top de Unix. Ejecutando py-spy con:
py-spy top --pid 12345
# OR
py-spy top -- python myprogram.py
Aparecerá una vista de alto nivel de actualización en vivo de su programa Python:
py-spy también puede mostrar la pila de llamadas actual para cada subproceso de Python con el comando dump
:
py-spy dump --pid 12345
Esto descargará las pilas de llamadas para cada hilo y alguna otra información básica del proceso en la consola:
Esto es útil para el caso en el que solo necesita una única pila de llamadas para determinar dónde está colgado su programa Python. Este comando también tiene la capacidad de imprimir las variables locales asociadas con cada marco de pila configurando el indicador --locals
.
Este proyecto tiene como objetivo permitirle perfilar y depurar cualquier programa Python en ejecución, incluso si el programa atiende tráfico de producción.
Si bien existen muchos otros proyectos de creación de perfiles de Python, casi todos requieren modificar el programa perfilado de alguna manera. Por lo general, el código de creación de perfiles se ejecuta dentro del proceso Python de destino, lo que ralentizará y cambiará el funcionamiento del programa. Esto significa que, en general, no es seguro utilizar estos generadores de perfiles para depurar problemas en los servicios de producción, ya que normalmente tendrán un impacto notable en el rendimiento.
py-spy funciona leyendo directamente la memoria del programa Python utilizando la llamada al sistema Process_vm_readv en Linux, la llamada vm_read en OSX o la llamada ReadProcessMemory en Windows.
Para determinar la pila de llamadas del programa Python, observe la variable global PyInterpreterState para ejecutar todos los subprocesos de Python en el intérprete y luego itere sobre cada PyFrameObject en cada subproceso para obtener la pila de llamadas. Dado que la ABI de Python cambia entre versiones, usamos el bindgen de Rust para generar diferentes estructuras de Rust para cada clase de intérprete de Python que nos interesa y usamos estas estructuras generadas para determinar el diseño de la memoria en el programa Python.
Obtener la dirección de memoria del intérprete de Python puede ser un poco complicado debido a la aleatorización del diseño del espacio de direcciones. Si el intérprete de Python de destino viene con símbolos, es bastante fácil determinar la dirección de memoria del intérprete eliminando la referencia a las variables interp_head
o _PyRuntime
según la versión de Python. Sin embargo, muchas versiones de Python se envían con binarios eliminados o sin los archivos de símbolos PDB correspondientes en Windows. En estos casos, escaneamos la sección BSS en busca de direcciones que parezcan apuntar a un PyInterpreterState válido y verificamos si el diseño de esa dirección es el que esperamos.
¡Sí! py-spy admite la creación de perfiles de extensiones nativas de Python escritas en lenguajes como C/C++ o Cython, en Linux x86_64 y Windows. Puede habilitar este modo pasando --native
en la línea de comando. Para obtener mejores resultados, debes compilar tu extensión de Python con símbolos. También vale la pena señalar que para los programas Cython, py-spy necesita el archivo C o C++ generado para devolver los números de línea del archivo .pyx original. Lea la publicación del blog para obtener más información.
Al pasar el indicador --subprocesses
al registro o a la vista superior, py-spy también incluirá la salida de cualquier proceso de Python que sea un proceso secundario del programa de destino. Esto es útil para crear perfiles de aplicaciones que utilizan multiprocesamiento o grupos de trabajadores gunicorn. py-spy monitoreará la creación de nuevos procesos, los adjuntará automáticamente e incluirá muestras de ellos en la salida. La vista de registro incluirá el PID y la línea cmd de cada programa en la pila de llamadas, y los subprocesos aparecerán como hijos de sus procesos principales.
py-spy funciona leyendo la memoria de un proceso de Python diferente, y es posible que esto no esté permitido por razones de seguridad dependiendo de su sistema operativo y la configuración del sistema. En muchos casos, ejecutar como usuario root (con sudo o similar) evita estas restricciones de seguridad. OSX siempre requiere ejecutarse como root, pero en Linux depende de cómo inicie py-spy y de la configuración de seguridad del sistema.
En Linux, la configuración predeterminada es requerir permisos de root al conectarse a un proceso que no es secundario. Para py-spy, esto significa que puede crear un perfil sin acceso de root al hacer que py-spy cree el proceso ( py-spy record -- python myprogram.py
), pero adjuntarlo a un proceso existente especificando un PID generalmente requerirá root ( sudo py-spy record --pid 123456
). Puede eliminar esta restricción en Linux configurando la variable sysctl ptrace_scope.
py-spy intenta incluir únicamente seguimientos de pila de subprocesos que ejecutan código activamente y excluir subprocesos que están inactivos o inactivos. Cuando es posible, py-spy intenta obtener esta información de actividad de subprocesos del sistema operativo: leyendo /proc/PID/stat
en Linux, usando la llamada mach thread_basic_info en OSX y mirando si se sabe que el SysCall actual está inactivo. en Windows.
Sin embargo, existen algunas limitaciones con este enfoque que pueden hacer que los subprocesos inactivos sigan marcados como activos. En primer lugar, tenemos que obtener la información de la actividad del hilo antes de pausar el programa, porque obtenerla de un programa pausado hará que siempre responda que está inactivo. Esto significa que existe una posible condición de carrera, en la que obtenemos la actividad del subproceso y luego el subproceso está en un estado diferente cuando obtenemos el seguimiento de la pila. La consulta del sistema operativo para la actividad de subprocesos tampoco está implementada todavía para los procesadores FreeBSD e i686/ARM en Linux. En Windows, las llamadas que están bloqueadas en IO tampoco se marcarán como inactivas todavía, por ejemplo, al leer la entrada de la entrada estándar. Finalmente, en algunas llamadas de Linux, el ptrace adjunto que estamos usando puede hacer que los subprocesos inactivos se activen momentáneamente, provocando falsos positivos al leer desde procfs. Por estas razones, también tenemos un recurso heurístico que marca ciertas llamadas conocidas en Python como inactivas.
Puede desactivar esta funcionalidad configurando el indicador --idle
, que incluirá marcos que py-spy considera inactivos.
Obtenemos actividad GIL observando el valor de threadid señalado por el símbolo _PyThreadState_Current
para Python 3.6 y versiones anteriores y averiguando el equivalente de la estructura _PyRuntime
en Python 3.7 y versiones posteriores. Es posible que estos símbolos no estén incluidos en su distribución de Python, lo que provocará que falle la resolución de qué subproceso retiene el GIL. El uso actual de GIL también se muestra en la vista top
como %GIL.
Pasar el indicador --gil
solo incluirá rastros de subprocesos que mantienen el bloqueo global del intérprete. En algunos casos, esta podría ser una visión más precisa de cómo emplea su tiempo su programa Python, aunque debe tener en cuenta que esto perderá actividad en las extensiones que liberan el GIL mientras aún están activas.
OSX tiene una característica llamada Protección de integridad del sistema que evita que incluso el usuario root lea la memoria de cualquier binario ubicado en /usr/bin. Desafortunadamente, esto incluye el intérprete de Python que viene con OSX.
Hay un par de maneras diferentes de lidiar con esto:
Al ejecutar py-spy dentro de un contenedor acoplable, generalmente también aparecerá un error de permisos denegados incluso cuando se ejecuta como root.
Este error se debe a que Docker restringe la llamada al sistema Process_vm_readv que estamos usando. Esto se puede anular configurando --cap-add SYS_PTRACE
al iniciar el contenedor acoplable.
Alternativamente, puede editar el archivo yaml de docker-compose
your_service:
cap_add:
- SYS_PTRACE
Tenga en cuenta que deberá reiniciar el contenedor de la ventana acoplable para que esta configuración surta efecto.
También puede utilizar py-spy desde el sistema operativo host para perfilar un proceso en ejecución que se ejecuta dentro del contenedor acoplable.
py-spy necesita SYS_PTRACE
para poder leer la memoria del proceso. Kubernetes elimina esa capacidad de forma predeterminada, lo que genera el error
Permission Denied: Try running again with elevated permissions by going 'sudo env "PATH=$PATH" !!'
La forma recomendada de solucionar esto es editar la especificación y agregar esa capacidad. Para una implementación, esto se hace agregando esto a Deployment.spec.template.spec.containers
securityContext:
capabilities:
add:
- SYS_PTRACE
Más detalles sobre esto aquí: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-capabilities-for-a-container Tenga en cuenta que esto eliminará los pods existentes y los creará nuevamente .
Alpine Python opta por no participar en manylinux
wheels: pypa/pip#3969 (comentario). Puede anular este comportamiento para usar pip para instalar py-spy en Alpine de la siguiente manera:
echo 'manylinux1_compatible = True' > /usr/local/lib/python3.7/site-packages/_manylinux.py
Alternativamente, puede descargar un binario musl desde la página de lanzamientos de GitHub.
Al configurar la opción --nonblocking
, py-spy no pausará el Python de destino desde el que estás generando el perfil. Si bien el impacto en el rendimiento del muestreo de un proceso con py-spy suele ser extremadamente bajo, configurar esta opción evitará por completo la interrupción de su programa Python en ejecución.
Con esta opción configurada, py-spy leerá el estado del intérprete del proceso de Python mientras se ejecuta. Dado que las llamadas que utilizamos para leer la memoria no son atómicas y tenemos que realizar varias llamadas para obtener un seguimiento de la pila, esto significa que ocasionalmente obtenemos errores al realizar el muestreo. Esto puede manifestarse como una mayor tasa de error durante el muestreo o como la inclusión de marcos de pila parciales en la salida.
Todavía no =).
Si hay características que le gustaría ver en py-spy, seleccione el problema apropiado o cree uno nuevo que describa qué funcionalidad falta.
py-spy sigue la especificación CLICOLOR, por lo que al configurar CLICOLOR_FORCE=1
en su entorno, py-spy imprimirá resultados en color incluso cuando se conecte a un buscapersonas.
py-spy está fuertemente inspirado en el excelente trabajo de Julia Evans en rbspy. En particular, el código para generar archivos flamegraph y speedscope se toma directamente de rbspy, y este proyecto utiliza las cajas read-process-memory y proc-maps que se derivaron de rbspy.
py-spy se publica bajo la licencia MIT; consulte el archivo de LICENCIA para obtener el texto completo.