Yappi Documentation

repository·master·Indexed 23 days ago

https://github.com/sumerc/yappi

A high-performance tracing profiler for Python designed for multithreading, asyncio, and gevent. Yappi provides accurate CPU and wall-clock time measurements for complex asynchronous and concurrent applications, offering detailed function, thread, and greenlet statistics via YFuncStat, YThreadStat, and YGreenletStats.

Tokens
7.2K
Snippets
21
Records
31
Agent score
83%

What's inside Yappi

  1. How Yappi handles coroutine profiling

    master

    Unlike deterministic profilers (like cProfile) or statistical profilers (like pyinstrument), Yappi (since v1.2) correctly handles coroutine context switches.

    In standard profilers, when a coroutine yields (e.g., during an await), the profiler treats it as a function exit. This leads to two major inaccuracies:

    1. Incorrect Call Counts: Every yield increments the call count as if the function exited.
    2. Incorrect Wall Time: The time spent while the coroutine is in a yield state is not accumulated into the cumtime (cumulative time).

    Yappi differentiates between a yield and a real function exit. When using clock_type='wall', Yappi accumulates the time spent during await states and maintains an accurate call count for the coroutine.

  2. Use `set_tag_callback` for request-based profiling

    master

    The set_tag_callback(callback) function (available since v1.2) allows you to associate every profiled function with a specific tag. This is useful for aggregating statistics for specific request/response cycles in ASGI or WSGI applications.

    The callback should return an integer. By returning a unique ID (like a request ID) for each request, you can later retrieve stats for that specific request using get_func_stats(tag=request_id).

    import threading
    import yappi
    
    _req_counter = 0
    tlocal = threading.local()
    
    def _worker_tag_cbk():
        global _req_counter
        if not hasattr(tlocal, '_request_id'):
            _req_counter += 1
            tlocal._request_id = _req_counter
        return tlocal._request_id
    
    yappi.set_tag_callback(_worker_tag_cbk)
    yappi.start()
    
    # ... application code ...
    
    yappi.stop()
    
    # Retrieve stats for a specific request tag
    for i in range(_req_counter):
        req_stats = yappi.get_func_stats(filter={'tag': i})
        req_stats.print_all()
  3. Profile asyncio applications

    master

    Yappi supports coroutine profiling, allowing you to see correct wall-time and call counts for coroutines, including time spent in context switches. Use yappi.set_clock_type("WALL") and wrap your asyncio.run() calls within a with yappi.run(): block.

    import asyncio
    import yappi
    
    async def foo():
        await asyncio.sleep(1.0)
        await baz()
        await asyncio.sleep(0.5)
    
    async def bar():
        await asyncio.sleep(2.0)
    
    async def baz():
        await asyncio.sleep(1.0)
    
    yappi.set_clock_type("WALL")
    with yappi.run():
        asyncio.run(foo())
        asyncio.run(bar())
    yappi.get_func_stats().print_all()
  4. Profile multithreaded applications and retrieve per-thread stats

    master

    Yappi is aware of multiple threads. To profile a multithreaded application, start the profiler, run your threads, and then stop it. You can retrieve specific statistics for each thread by using the ctx_id parameter in yappi.get_func_stats(), passing the id obtained from yappi.get_thread_stats().

    import yappi
    import time
    import threading
    
    _NTHREAD = 3
    
    
    def _work(n):
        time.sleep(n * 0.1)
    
    yappi.start()
    
    threads = []
    # generate _NTHREAD threads
    for i in range(_NTHREAD):
        t = threading.Thread(target=_work, args=(i + 1, ))
        t.start()
        threads.append(t)
    # wait all threads to finish
    for t in threads:
        t.join()
    
    yappi.stop()
    
    # retrieve thread stats by their thread id (given by yappi)
    threads = yappi.get_thread_stats()
    for thread in threads:
        print(
            "Function stats for (%s) (%d)" % (thread.name, thread.id)
        )  # it is the Thread.__class__.__name__
        yappi.get_func_stats(ctx_id=thread.id).print_all()
  5. Profile gevent (greenlet) applications

    master

    To profile greenlet-based applications, set the context backend to greenlet using yappi.set_context_backend("greenlet"). It is recommended to use yappi.start(builtins=True) to ensure comprehensive coverage.

    import yappi
    from greenlet import greenlet
    import time
    
    class GreenletA(greenlet):
        def run(self):
            time.sleep(1)
    
    yappi.set_context_backend("greenlet")
    yappi.set_clock_type("wall")
    
    yappi.start(builtins=True)
    a = GreenletA()
    a.switch()
    yappi.stop()
    
    yappi.get_func_stats().print_all()
  6. Profile gevent applications with monkey patching

    master

    When using gevent.monkey.patch_all(), threading.Thread is used to spawn greenlets. To ensure Yappi correctly captures the names of these greenlets, you must provide a custom name callback to yappi.set_context_name_callback(). This callback should check if the current greenlet is the gevent Hub; if not, it should fall back to Yappi's internal thread name callback.

    from gevent import monkey
    monkey.patch_all()
    
    import yappi
    import threading
    import gevent
    import time
    
    # ... application logic ...
    
    # Step 1: Configure the profiler to work with greenlets
    yappi.set_context_backend("greenlet")
    yappi.set_clock_type("cpu")
    
    # Step 2: Configure the system to capture thread names correctly
    def _ctx_name_callback():
        curr_gl = gevent.getcurrent()
        if curr_gl is gevent.get_hub():
            return curr_gl.__class__.__name__
        # yappi._ctx_name_callback returns the name of the thread class
        return yappi._ctx_name_callback()
    
    yappi.set_context_name_callback(_ctx_name_callback)
    
    # Step 3: Run the profiler and stop it
    yappi.start()
    
    a = ThreadA()
    b = ThreadB()
    
    a.start()
    b.start()
    a.join()
    b.join()
    
    yappi.stop()
    
    # Step 4: View results
    print("## Function stats:")
    yappi.get_func_stats().print_all()
    
    print("\n## Greenlet stats:")
    yappi.get_greenlet_stats().print_all()
  7. Profile simple greenlet applications

    master

    To profile applications using the greenlet library, you must configure Yappi's context backend to greenlet and set the clock type (e.g., cpu). After starting the profiler, you can retrieve and print function statistics using yappi.get_func_stats() and greenlet-specific statistics using yappi.get_greenlet_stats().

    import yappi
    from greenlet import greenlet
    import time
    
    # ... application logic ...
    
    # Step 1: Configure the profiler to work with greenlets
    yappi.set_context_backend("greenlet")
    yappi.set_clock_type("cpu")
    
    # Step 2: Run the profiler and stop it
    yappi.start()
    
    a = GreenletA()
    b = GreenletB()
    
    a.switch()
    b.switch()
    
    yappi.stop()
    
    # Step 3: View results
    print("## Function stats:")
    yappi.get_func_stats().print_all()
    
    print("\n## Greenlet stats:")
    yappi.get_greenlet_stats().print_all()
  8. Profile gevent applications

    master

    Yappi supports profiling gevent applications by setting the context backend to greenlet. This allows you to capture statistics for greenlets managed by the gevent event loop. Note that functions running in a gevent ThreadPool cannot be tracked by Yappi.

    import yappi
    from gevent import Greenlet
    import time
    
    # ... application logic ...
    
    # Step 1: Configure the profiler to work with greenlets
    yappi.set_context_backend("greenlet")
    yappi.set_clock_type("cpu")
    
    # Step 2: Run the profiler and stop it
    yappi.start()
    
    a = GreenletA()
    b = GreenletB()
    
    a.start()
    b.start()
    a.get()
    b.get()
    
    yappi.stop()
    
    # Step 3: View results
    print("## Function stats:")
    yappi.get_func_stats().print_all()
    
    print("\n## Greenlet stats:")
    yappi.get_greenlet_stats().print_all()
  9. Basic profiling workflow with Yappi

    master

    To profile a Python application with Yappi, follow these three steps:

    1. Call yappi.start() to begin profiling.
    2. Execute the code you wish to profile.
    3. Retrieve and print statistics using yappi.get_func_stats().print_all() for function-level data and yappi.get_thread_stats().print_all() for thread-level data.

    Note that the output will indicate the Clock type (e.g., cpu) and the Ordered by criteria used for the report.

    import yappi
    
    
    def a():
        for i in range(10000000): pass
    
    yappi.start()
    
    a()
    
    yappi.get_func_stats().print_all()
    yappi.get_thread_stats().print_all()
  10. Compare CPU vs Wall Clock timing

    master

    The following example demonstrates the difference between cpu and wall clock types when profiling a function that calls time.sleep().

    When using the default cpu clock, time.sleep() will show negligible timing because the CPU is not actively processing instructions for that thread. When using wall clock, the full duration of the sleep is captured in ttot (total time).

    import time
    import yappi
    
    def my_func():
        time.sleep(4.0)
    
    # Example 1: CPU Clock (Default)
    # Results will show near-zero time for the sleep call
    yappi.set_clock_type("cpu")
    yappi.start()
    my_func()
    yappi.get_func_stats().print_all()
    
    # Example 2: Wall Clock
    # Results will show ~4.0 seconds for the sleep call
    yappi.set_clock_type("wall")
    yappi.start()
    my_func()
    yappi.get_func_stats().print_all()
  11. Basic profiling usage with Yappi

    master

    To perform a simple profile, use yappi.set_clock_type() to choose between cpu or wall time, then call yappi.start() and yappi.stop(). You can retrieve function statistics using yappi.get_func_stats() and thread statistics using yappi.get_thread_stats().

    Note: yappi.set_clock_type("cpu") measures actual CPU time, while yappi.set_clock_type("wall") measures elapsed real time.

    import yappi
    
    def a():
        for _ in range(10000000):  # do something CPU heavy
            pass
    
    yappi.set_clock_type("cpu") # Use set_clock_type("wall") for wall time
    yappi.start()
    a()
    
    yappi.get_func_stats().print_all()
    yappi.get_thread_stats().print_all()