-
Notifications
You must be signed in to change notification settings - Fork 479
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
python function tracing support #436
Comments
tested on an Ubuntu 16.04 box:
It seems filter is needed for replay.. |
Looks great! It works fine in my environment but it shows too many library calls.
So I need to use
|
Yes, that's why I added |
Just an idea, Well, it might be unpleasant having a dependency on an external module, |
I don't know about python profiling internals. But if there's a better way for uftrace, why not? :) |
I tested First, when uftrace detected system features:
... prefix: /usr/local
... libelf: [ on ] - more flexible ELF data handling
... libdw: [ on ] - DWARF debug info support
... libpython2.7: [ on ] - python scripting support
... libncursesw: [ on ] - TUI support
... cxa_demangle: [ on ] - full demangler support with libstdc++
......
GEN version.h
CC uftrace.o
LINK uftrace
/home/claudia/uftrace/cmds/info.o: In function `fill_exe_build_id':
/home/claudia/uftrace/cmds/info.c:98: undefined reference to `elf_getshdrstrndx'
/home/claudia/uftrace/cmds/info.c:98: undefined reference to `elf_nextscn'
/home/claudia/uftrace/cmds/info.c:98: undefined reference to `elf_nextscn'
/home/claudia/uftrace/cmds/info.c:98: undefined reference to `gelf_getshdr'
/home/claudia/uftrace/cmds/info.c:105: undefined reference to `elf_strptr'
/home/claudia/uftrace/cmds/info.c:117: undefined reference to `elf_getdata'
/home/claudia/uftrace/cmds/info.c:117: undefined reference to `gelf_getnote'
/home/claudia/uftrace/cmds/tui.o: In function `tui_window_move_down':
/home/claudia/uftrace/cmds/tui.c:1785: undefined reference to `LINES'
/home/claudia/uftrace/cmds/tui.c:1785: undefined reference to `LINES'
/home/claudia/uftrace/cmds/tui.o: In function `win_header_report':
/home/claudia/uftrace/cmds/tui.c:1362: undefined reference to `printw'
/home/claudia/uftrace/cmds/tui.c:1363: undefined reference to `COLS'
/home/claudia/uftrace/cmds/tui.o: In function `win_display_info':
/home/claudia/uftrace/cmds/tui.c:1557: undefined reference to `COLS'
/home/claudia/uftrace/cmds/tui.o: In function `win_header_info':
/home/claudia/uftrace/cmds/tui.c:1514: undefined reference to `COLS'
/home/claudia/uftrace/cmds/tui.o: In function `win_header_session':
/home/claudia/uftrace/cmds/tui.c:1621: undefined reference to `COLS'
....
collect2: error: ld returned 1 exit status
make: *** [/home/claudia/uftrace/uftrace] 오류 1 Second, when I ran test.py from uftrace, the error occurred because of elf format issue. $ uftrace --python ../test.py
WARN: Segmentation fault
uftrace: /home/claudia/uftrace/cmds/record.c:387:fill_file_header
ERROR: cannot open info file: No such file or directory
child terminated by signal: 11: Segmentation fault I think that the reason is [vdso] section in
So I suggest that ELF parsing logic in this PR needs to be updated. |
(+ It keeps search ex) # trying /home/manjaro/git/uf2/misc/uftrace.so
# trying /home/manjaro/git/uf2/misc/uftracemodule.so
# trying /home/manjaro/git/uf2/misc/uftrace.py
# trying trace_python.so
# trying trace_pythonmodule.so
# trying trace_python.py
# trying trace_python.pyc
# trying /home/manjaro/git/uf2/misc/trace_python.so
dlopen("/home/manjaro/git/uf2/misc/trace_python.so", 2);
import trace_python # dynamically loaded from /home/manjaro/git/uf2/misc/trace_python.so which means, When removing the compiled directory, it won't work at all. |
(+ Due to python's module import logic, Maybe the trace result of import should be hidden with options.) Example code from sqlalchemy import create_engine
engine = create_engine('sqlite:///:memory:', echo=True) This example imports sqlalchemy ORM and provisions SQLite memory DB. $ uftrace --python test.py
# DURATION TID FUNCTION
[ 31701] | <module>() {
[ 31701] | <module>() {
[ 31701] | <module>() {
[ 31701] | <module>() {
[ 31701] | <module>() {
[ 31701] | <module>() {
25.046 us [ 31701] | <module>();
764.568 us [ 31701] | <module>();
21.142 us [ 31701] | OrderedDict();
9.359 us [ 31701] | Counter();
2.261 ms [ 31701] | } /* <module> */
[ 31701] | <module>() {
[ 31701] | <module>() {
[ 31701] | <module>() {
356.349 us [ 31701] | <module>();
2.810 us [ 31701] | GeneratorContextManager();
[ 31701] | contextmanager() {
4.267 us [ 31701] | wraps();
18.103 us [ 31701] | update_wrapper();
33.232 us [ 31701] | } /* contextmanager */
2.664 us [ 31701] | closing();
563.707 us [ 31701] | } /* <module> */
[ 31701] | <module>() {
[ 31701] | filterwarnings() {
[ 31701] | compile() {
[ 31701] | _compile() {
3.774 us [ 31701] | isstring();
[ 31701] | compile() {
2.633 us [ 31701] | isstring();
[ 31701] | parse() {
[ 31701] | __init__() {
7.184 us [ 31701] | __next();
12.869 us [ 31701] | } /* __init__ */
3.479 us [ 31701] | __init__();
[ 31701] | _parse_sub() {
[ 31701] | _parse() {
3.547 us [ 31701] | __init__();
[ 31701] | get() {
5.965 us [ 31701] | __next();
10.091 us [ 31701] | } /* get */
2.023 us [ 31701] | append();
[ 31701] | get() {
4.505 us [ 31701] | __next();
7.912 us [ 31701] | } /* get */
1.372 us [ 31701] | append();
[ 31701] | get() {
4.787 us [ 31701] | __next();
7.777 us [ 31701] | } /* get */
1.154 us [ 31701] | append(); $ uftrace -D 2 --python test.py
# DURATION TID FUNCTION
[ 4991] | <module>() {
179.633 ms [ 4991] | <module>();
12.627 ms [ 4991] | create_engine();
192.395 ms [ 4991] | } /* <module> */
[ 4991] | _exitfunc() {
14.307 us [ 4991] | __stop();
13.025 us [ 4991] | _pickSomeNonDaemonThread();
0.967 us [ 4991] | _note();
1.976 us [ 4991] | __delete();
37.923 us [ 4991] | } /* _exitfunc */
[ 4991] | _run_exitfuncs() {
47.101 us [ 4991] | shutdown();
53.613 us [ 4991] | } /* _run_exitfuncs */
5.729 us [ 4991] | _remove();
1.544 us [ 4991] | _remove(); Most of the trace results are module import logic, and I think import logic looks aren't that necessary by default with tracing. (Ps. tracing should provide much more info than |
Pushed review/python-tracing-v2 Changelog:
|
Thanks for the update. I just tested it with a python version of
Here is the test program, #!/usr/bin/env python
import os
def c():
return os.getpid() % 100000
def b():
return c() + 1
def a():
return b() - 1
def main():
ret = 0
pid = os.fork()
if pid:
os.wait()
ret += a()
return ret
if __name__=='__main__':
main() |
Regarding on One disadvantage of cProfile that does not exist in uftrace is, cProfile prints a flat list of functions, and it's hard to see the relationship between functions. For example, if one code path calls (EDITED: Removed unrelated content) |
Some comments on review/python-tracing-v2. Mostly about reducing overhead:
|
@quark-zju thanks for detailed comments, I'll take a look at them to reduce overheads. Note that libpython is not a required dependency of uftrace (and I don't want to make it is) and we load it with |
@honggyukim yes it doesn't consider multi-process fully yet - but I guess it's mostly for works during replay. |
Thanks! Making libpython optional was a wise decision. It seems the next missing thing would be exception handling. It can probably be done by following |
Probably, but is there a way for python interpreter to let us know about the exception info? |
Actually, I was wrong. According to https://github.com/python/cpython/blob/62be74290aca26d16f3f55ece7ff6dad14e60e8d/Modules/_lsprof.c#L454, PyTrace_RETURN will be generated, so there is no need to specially handling exceptions. But |
I was trying to test it for OE bitbake script, but it doesn't accept additional arguments for python script. Here is a test script.
But the result is not same as original execution.
The result doesn't show |
Pushed review/python-tracing-v3 Changelog:
It was easy thanks to recent script file support but I think we need to consider supporting python3 as well.
|
Thanks for the update! I just updated to make both python2 and python3 work based on review/python-tracing-v3. I pushed it into honggyukim/review/python3-tracing-v1. Here are test scripts. $ cat p-abc.py
#!/usr/bin/env python
import os
def c():
return os.getpid()
def b():
return c()
def a():
return b()
print(a()) The below script is same as above, but just uses python3. $ cat p-abc3.py
#!/usr/bin/env python3
import os
def c():
return os.getpid()
def b():
return c()
def a():
return b()
print(a()) It works both python2 and python3 scripts as follows.
Please take a look. Thanks! |
@chbae I also tested it for poky bitbake script, which is written in python3 and the tracing result looks fine although it needs to fix some symbol names.
|
It'd be really great if we can also add source location info for python functions. |
It seems that we can also provide Python C API functions as built-in functions that can be processed by |
Thanks. I don't see the problem
It looks good but can we add an option not to defer sys.setprofile() just like we have I still see difference when running
I still need to give a full path with v11 as follows.
|
In addition, it would be really great if we can provide source location info for python code as well. Then we can jump to the actual python source code in |
Yep, I think it's doable. I'll leave it as the next step. |
I'm not sure about it. Anyway what it shows is the implementation of our loader script |
Ok, I found a problem dealing with script files. It should check PATH env to get the full pathname when accessing the file. |
Pushed review/python-tracing-v12 Changes:
Now it can run
|
It looks great. Thanks.
Then it's okay to skip it. One more thing is that we added |
One more thing is if #1605 is merged before this, then we should use |
Good idea. Will move.
Yep, I plan to merge it before this work. I'll take care of the change. |
It works fine with
If I run
But most of the graph sessions are empty except for the first session. I'm just wondering if it correctly follows the underline python scripts that run each pre-commit hooks. |
Another test with
|
Probably not. It needs to add |
It would be useful if there is an environment variable to add a module by default but I don't have an idea how to handle it. I see another bugs when running
It didn't see the problem when running 1 or 2 tests but increasing the number of tests makes this problem. |
And the following test shows
|
The runtest can be tricky since it runs uftrace under uftrace. The options are passed by env so it can be affected by outer uftrace. |
OK. Then please ignore the result of runtest.py. |
Pushed review/python-tracing-v13 Changes:
|
Thanks for the update. It looks good but it would be better if we can run some python feature tests. It looks cpython internally has some tests and one of the test shows some failures when recording with uftrace.
And apply the following change to make it running by uftrace. diff --git a/Lib/test/test_array.py b/Lib/test/test_array.py
index 5b2c107a60..eb080d1e06 100755
--- a/Lib/test/test_array.py
+++ b/Lib/test/test_array.py
@@ -1,3 +1,4 @@
+#!/usr/bin/env python
"""Test the arraymodule.
Roger E. Masse
""" Then the test works fine showing
I tried to run it with uftrace and it shows some failures at the end of execution as follows.
|
I see a segfault this time in cpython test. But this test only works fine when I checkout cpython repo to the same version of my running python diff --git a/Lib/test/test_uuid.py b/Lib/test/test_uuid.py
index d6a8333427..11ee1e9c6d 100755
--- a/Lib/test/test_uuid.py
+++ b/Lib/test/test_uuid.py
@@ -1,3 +1,4 @@
+#!/usr/bin/env python
import unittest
from test import support
from test.support import import_helper The test runs fine.
But it gets crashed as follows when running with uftrace record.
|
Some reference count errors are expected as it increases refcnt of code object for the symbol table. I guess it won't be a problem in practice unless the python code is extremely sensitive to the refcnt behavior. |
Let's talk about them in separate issues. |
This is a RFC for tracing python function using python's
sys.settrace()
hook. I implemented C extension module to be called from python and it calls__cyg_profile_func_enter/exit()
like a normal C program. But python functions don't have fixed address so I just used a sequence counter to give it the address. uftrace will treat any functions that have address under 4K as python functions.As it's just a PoC, I don't expect it can run serious programs and it'd contain many rough edges. Also filters don't work on the python functions. The
--python
option was added tentatively to work around issues on the python script as an executable. Maybe it'd be nice if it could detect and do the right things automatically.I only tested it on my Archlinux machine but it doesn't have PLT in the python interpreter. So it only shows python functions but I expect it can also show (library) functions in the interpreter too.
I pushed the code to review/python-tracing-v1 branch. Have fun :)
Here's my test program (test.py):
You can run it like below:
The text was updated successfully, but these errors were encountered: