<div dir="ltr"><font face="monospace">Hi, </font><div><font face="monospace"><br></font></div><div><span style="color:rgb(0,0,0);font-family:monospace;line-height:21px">The PEP-418 is about performance counters, but there is no mention of performance management unit (PMU) counters, such as cache misses and instruction counts.</span></div><div><span style="color:rgb(0,0,0);font-family:monospace;line-height:21px"><br></span></div><div><span style="color:rgb(0,0,0);font-family:monospace;line-height:21px">The Linux perf tool aims at recording these samples at the system level. </span><span style="font-family:monospace">I ran linux perf on CPython for profiling. The resulting callstack is inside libpython.so, mostly recursive calls to </span><span style="font-family:monospace;color:rgb(0,0,0);line-height:21px">PyEval_EvalFrameEx(), because the tool works at the ELF level. Here is an example with a dummy program (linux-tools on Ubuntu 14.04): </span></div><div><font face="monospace"><span style="color:rgb(0,0,0);line-height:21px"><br></span></font></div><div><font color="#000000" face="monospace"><span style="line-height:21px">$ perf record python crunch.py</span></font></div><div><font color="#000000" face="monospace"><span style="line-height:21px">$ perf report --stdio</span></font></div><div><font face="monospace"><div><font color="#000000"><span style="line-height:21px"># Overhead  Command       Shared Object                            Symbol</span></font></div><div><font color="#000000"><span style="line-height:21px"># ........  .......  ..................  ................................</span></font></div><div><font color="#000000"><span style="line-height:21px">#</span></font></div><div><font color="#000000"><span style="line-height:21px">    32.37%   python  python2.7           [.] PyEval_EvalFrameEx          </span></font></div><div><font color="#000000"><span style="line-height:21px">    13.70%   python  <a href="http://libm-2.19.so" target="_blank">libm-2.19.so</a>        [.] __sin_avx                   </span></font></div><div><font color="#000000"><span style="line-height:21px">     5.25%   python  python2.7           [.] binary_op1.5010             </span></font></div><div><font color="#000000"><span style="line-height:21px">     4.82%   python  python2.7           [.] PyObject_GetAttr            </span></font></div><div style="color:rgb(0,0,0);line-height:21px"><br></div><div style="color:rgb(0,0,0);line-height:21px">While this may be insightful for the interpreter developers, it it not so for the average Python developer. The report should display Python code instead. <span style="line-height:normal;color:rgb(34,34,34)">It seems obvious, still I haven't found the feature for that.</span></div></font><font face="monospace"><div style="color:rgb(0,0,0);line-height:21px"><br></div><div style="color:rgb(0,0,0);line-height:21px">When a performance counter reaches a given value, a sample is recorded. The most basic sample only records a timestamps, thread ID and the program counter (%rip). In addition, all executable memory maps of libraries are recorded. For the callstack, frame pointers are traversed, but most of the time, they are optimized on x86, so there is a fall back to unwind, which requires saving register values and a chunk of the stack. The memory space of the process is reconstructed offline.</div><div style="color:rgb(0,0,0);line-height:21px"><br></div><div style="color:rgb(0,0,0);line-height:21px">CPython seems to allocates code and frames on mmap() pages. If the data is outside about 1k from the top of stack, it is not available offline in the trace. We need some way to reconstitute this memory space of the interpreter to resolve the symbols, probably by  dumping the data on disk.</div><div style="color:rgb(0,0,0);line-height:21px"><br></div><div style="color:rgb(0,0,0);line-height:21px">In Java, there is a small HotSpot agent that spits out the symbols of JIT code:</div><div style="color:rgb(0,0,0);line-height:21px"><br></div><div><font color="#000000"><span style="line-height:21px"><a href="https://github.com/jrudolph/perf-map-agent" target="_blank">https://github.com/jrudolph/perf-map-agent</a></span></font><br></div><div><font color="#000000"><span style="line-height:21px"><br></span></font></div><div><font color="#000000"><span style="line-height:21px">The problem is that CPython does not JIT code, and executed code is the ELF library itself. The executed frames are parameters of functions of the interpreter. I don't think the same approach can be used (maybe this can be applied to PyPy?). </span></font></div><div><font color="#000000"><span style="line-height:21px"><br></span></font></div><div>I looked at how Python frames are handled in GDB (file cpython/Tools/gdb/libpython.py). A python frame is detected in Frame(gdbframe).is_evalframeex() by a C call to PyEval_EvalFrameEx(). However, the traceback accesses PyFrameObject on the heap (at least for f->f_back = 0xa57460), which is possible in GDB when the program is paused and the whole memory space is available, but is not recorded for offline use in perf. Here is an example of callstack from GDB:</div><div><br></div><div><div>#0  PyEval_EvalFrameEx (f=Frame 0x7ffff7f1b060, for file crunch.py, line 7, in bar (num=466829), </div><div>    throwflag=0) at ../Python/ceval.c:1039</div><div>#1  0x0000000000527877 in fast_function (func=<function at remote 0x7ffff6ec45a0>, </div><div>    pp_stack=0x7fffffffd280, n=1, na=1, nk=0) at ../Python/ceval.c:4106</div><div>#2  0x0000000000527582 in call_function (pp_stack=0x7fffffffd280, oparg=1) at ../Python/ceval.c:4041</div></div><div><br></div><div><br></div><div>We could add a kernel module that "knows" how to make samples of CPython, but it means python structures becomes sort of ABI, and kernel devs won't allow a python interpreter in kernel mode ;-).<br></div><div><br></div><div>What we really want is f_code data and related objects:</div><div><div><br></div><div>(gdb) print (void *)(f->f_code)</div><div>$8 = (void *) 0x7ffff7e370f0</div></div><div><br></div><div>Maybe we could save these pages every time some code is loaded from the interpreter? (the memory range is about 1.7MB, but )</div></font><font face="monospace"><div><br></div><div>Anyway, I think we must change CPython to support tools such as perf. Any thoughts? </div><div><br></div><div>Cheers,</div><div><br></div><div>Francis</div><div><br></div></font></div></div>