Seguimiento de un programa mientras se ejecuta

Hay dos formas de inyectar código para ver la ejecución de un programa: rastreo y perfilado. Son similares, pero están destinados a diferentes propósitos y, por lo tanto, tienen diferentes restricciones. La manera más fácil, pero menos eficiente, de monitorear un programa es a través de un gancho de rastreo, que se puede usar para escribir un depurador, monitorear la cobertura del código o lograr muchos otros propósitos.

El gancho de rastreo se modifica pasando una función de devolución de llamada a sys.settrace(). La devolución de llamada recibirá tres argumentos: el marco de la pila del código que se está ejecutando, una cadena que nombra el tipo de notificación y un valor de argumento específico del evento. the table below enumera los siete tipos de eventos para diferentes niveles de información que ocurren cuando se ejecuta un programa.

Ganchos de evento para settrace()
Evento Cuando ocurre Valor de argumento
call Antes de ejecutar una función None
line Antes de ejecutar una línea None
return Antes de que regrese una función El valor que se devuelve
exception Después de que se produce una excepción La tupla (exception, value, traceback)
c_call Antes de que una función C sea invocada El objecto de función C
c_return Después de que una función C regrese None
c_exception Después de que una función C arroje un error None

Seguimiento de llamadas de función

Se genera un evento call antes de cada llamada de función. El marco pasado a la devolución de llamada se puede utilizar para averiguar qué función se llama y desde dónde.

sys_settrace_call.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#!/usr/bin/env python3
# encoding: utf-8

import sys


def trace_calls(frame, event, arg):
    if event != 'call':
        return
    co = frame.f_code
    func_name = co.co_name
    if func_name == 'write':
        # Ignore write() calls from printing
        return
    func_line_no = frame.f_lineno
    func_filename = co.co_filename
    if not func_filename.endswith('sys_settrace_call.py'):
        # Ignore calls not in this module
        return
    caller = frame.f_back
    caller_line_no = caller.f_lineno
    caller_filename = caller.f_code.co_filename
    print('* Call to', func_name)
    print('*  on line {} of {}'.format(
        func_line_no, func_filename))
    print('*  from line {} of {}'.format(
        caller_line_no, caller_filename))
    return


def b():
    print('inside b()\n')


def a():
    print('inside a()\n')
    b()


sys.settrace(trace_calls)
a()

Este ejemplo ignora las llamadas a write(), como la usa print para escribir en sys.stdout.

$ python3 sys_settrace_call.py

* Call to a
*  on line 35 of sys_settrace_call.py
*  from line 41 of sys_settrace_call.py
inside a()

* Call to b
*  on line 31 of sys_settrace_call.py
*  from line 37 of sys_settrace_call.py
inside b()

Seguimiento de funciones internas

El enlace de rastreo puede devolver un nuevo enlace que se utilizará dentro del nuevo ámbito (la función de rastreo local). Es posible, por ejemplo, controlar el rastreo para ejecutar solo línea por línea dentro de ciertos módulos o funciones.

sys_settrace_line.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#!/usr/bin/env python3
# encoding: utf-8

import functools
import sys


def trace_lines(frame, event, arg):
    if event != 'line':
        return
    co = frame.f_code
    func_name = co.co_name
    line_no = frame.f_lineno
    print('*  {} line {}'.format(func_name, line_no))


def trace_calls(frame, event, arg, to_be_traced):
    if event != 'call':
        return
    co = frame.f_code
    func_name = co.co_name
    if func_name == 'write':
        # Ignore write() calls from printing
        return
    line_no = frame.f_lineno
    filename = co.co_filename
    if not filename.endswith('sys_settrace_line.py'):
        # Ignore calls not in this module
        return
    print('* Call to {} on line {} of {}'.format(
        func_name, line_no, filename))
    if func_name in to_be_traced:
        # Trace into this function
        return trace_lines
    return


def c(input):
    print('input =', input)
    print('Leaving c()')


def b(arg):
    val = arg * 5
    c(val)
    print('Leaving b()')


def a():
    b(2)
    print('Leaving a()')


tracer = functools.partial(trace_calls, to_be_traced=['b'])
sys.settrace(tracer)
a()

En este ejemplo, la lista de funciones se mantiene en la variable :py``to_be_traced``, por lo que cuando se ejecuta trace_calls() puede devolver trace_lines() para habilitar el seguimiento dentro de b().

$ python3 sys_settrace_line.py

* Call to a on line 49 of sys_settrace_line.py
* Call to b on line 43 of sys_settrace_line.py
*  b line 44
*  b line 45
* Call to c on line 38 of sys_settrace_line.py
input = 10
Leaving c()
*  b line 46
Leaving b()
Leaving a()

Seguimiento de la pila

Otra forma útil de usar los ganchos es mantenerse al día con las funciones que se están llamando y cuáles son sus valores de retorno. Para monitorear los valores de retorno, observa el evento return.

sys_settrace_return.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#!/usr/bin/env python3
# encoding: utf-8

import sys


def trace_calls_and_returns(frame, event, arg):
    co = frame.f_code
    func_name = co.co_name
    if func_name == 'write':
        # Ignore write() calls from printing
        return
    line_no = frame.f_lineno
    filename = co.co_filename
    if not filename.endswith('sys_settrace_return.py'):
        # Ignore calls not in this module
        return
    if event == 'call':
        print('* Call to {} on line {} of {}'.format(
            func_name, line_no, filename))
        return trace_calls_and_returns
    elif event == 'return':
        print('* {} => {}'.format(func_name, arg))
    return


def b():
    print('inside b()')
    return 'response_from_b '


def a():
    print('inside a()')
    val = b()
    return val * 2


sys.settrace(trace_calls_and_returns)
a()

La función de rastreo local se usa para ver eventos de retorno, por lo que trace_calls_and_returns() necesita devolver una referencia a sí misma cuando se llama a una función, por lo que el valor de retorno puede ser monitoreado.

$ python3 sys_settrace_return.py

* Call to a on line 32 of sys_settrace_return.py
inside a()
* Call to b on line 27 of sys_settrace_return.py
inside b()
* b => response_from_b
* a => response_from_b response_from_b

Propagación de excepciones

Las excepciones se pueden monitorear buscando el evento exception en una función de rastreo local. Cuando se produce una excepción, se llama al enlace de rastreo con una tupla que contiene el tipo de excepción, el objeto de excepción y un objeto de rastreo.

sys_settrace_exception.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#!/usr/bin/env python3
# encoding: utf-8

import sys


def trace_exceptions(frame, event, arg):
    if event != 'exception':
        return
    co = frame.f_code
    func_name = co.co_name
    line_no = frame.f_lineno
    exc_type, exc_value, exc_traceback = arg
    print(('* Tracing exception:\n'
           '* {} "{}"\n'
           '* on line {} of {}\n').format(
               exc_type.__name__, exc_value, line_no,
               func_name))


def trace_calls(frame, event, arg):
    if event != 'call':
        return
    co = frame.f_code
    func_name = co.co_name
    if func_name in TRACE_INTO:
        return trace_exceptions


def c():
    raise RuntimeError('generating exception in c()')


def b():
    c()
    print('Leaving b()')


def a():
    b()
    print('Leaving a()')


TRACE_INTO = ['a', 'b', 'c']

sys.settrace(trace_calls)
try:
    a()
except Exception as e:
    print('Exception handler:', e)

Ten cuidado de limitar dónde se aplica la función local porque algunas de las partes internas de los mensajes de error de formato generan e ignoran sus propias excepciones. El gancho de rastreo ve cada excepción, ya sea que la persona que llama la capture o la ignore o no.

$ python3 sys_settrace_exception.py

* Tracing exception:
* RuntimeError "generating exception in c()"
* on line 31 of c

* Tracing exception:
* RuntimeError "generating exception in c()"
* on line 35 of b

* Tracing exception:
* RuntimeError "generating exception in c()"
* on line 40 of a

Exception handler: generating exception in c()

Ver también

  • profile – La documentación del módulo profile muestra cómo usar un perfilador listo para usar.
  • trace – El módulo trace implementa varias funciones de análisis de código.
  • Types and Members – Las descripciones de los objetos de marco, de código y sus atributos.
  • Tracing python code – Otro tutorial de settrace().
  • Wicked hack: Python bytecode tracing – Los experimentos de Ned Batchelder con el rastreo con más granularidad que el nivel de línea de origen.
  • smiley – Rastreado de aplicaciones Python