aboutsummaryrefslogtreecommitdiff
path: root/posts/2017-04-21-profiling-python-web-applications-with-visual-tools.md
diff options
context:
space:
mode:
Diffstat (limited to 'posts/2017-04-21-profiling-python-web-applications-with-visual-tools.md')
-rw-r--r--posts/2017-04-21-profiling-python-web-applications-with-visual-tools.md187
1 files changed, 187 insertions, 0 deletions
diff --git a/posts/2017-04-21-profiling-python-web-applications-with-visual-tools.md b/posts/2017-04-21-profiling-python-web-applications-with-visual-tools.md
new file mode 100644
index 0000000..af2c65a
--- /dev/null
+++ b/posts/2017-04-21-profiling-python-web-applications-with-visual-tools.md
@@ -0,0 +1,187 @@
1---
2Title: Profiling Python web applications with visual tools
3Description: Missing link when debugging and profiling python web application
4Slug: profiling-python-web-applications-with-visual-tools
5Listing: true
6Created: 2017, April 21
7Tags: []
8---
9
10I have been profiling my software with KCachegrind for a long time now and I was missing this option when I am developing API's or other web services. I always knew that this is possible but never really took the time and dive into it.
11
12Before we begin there are some requirements. We will need to:
13
14- implement [cProfile](https://docs.python.org/2/library/profile.html#module-cProfile) into our web app,
15- convert output to [callgrind](http://valgrind.org/docs/manual/cl-manual.html) format with [pyprof2calltree](https://pypi.python.org/pypi/pyprof2calltree/),
16- visualize data with [KCachegrind](http://kcachegrind.sourceforge.net/html/Home.html) or [Profiling Viewer](http://www.profilingviewer.com/).
17
18
19If you are using MacOS you should check out [Profiling Viewer](http://www.profilingviewer.com/) or [MacCallGrind](http://www.maccallgrind.com/).
20
21![KCachegrind](/assets/python-profiling/kcachegrind.png)
22
23We will be dividing this post into two main categories:
24
25- writing simple web-service,
26- visualize profile of this web-service.
27
28## Simple web-service
29
30Let's use virtualenv so we won't pollute our base system. If you don't have virtualenv installed on your system you can install it with pip command.
31
32```bash
33# let's install virtualenv globally
34$ sudo pip install virtualenv
35
36# let's also install pyprof2calltree globally
37$ sudo pip install pyprof2calltree
38
39# now we create project
40$ mkdir demo-project
41$ cd demo-project/
42
43# now let's create folder where we will store profiles
44$ mkdir prof
45
46# now we create empty virtualenv in venv/ folder
47$ virtualenv --no-site-packages venv
48
49# we now need to activate virtualenv
50$ source venv/bin/activate
51
52# you can check if virtualenv was correctly initialized by
53# checking where your python interpreter is located
54# if command bellow points to your created directory and not some
55# system dir like /usr/bin/python then everything is fine
56$ which python
57
58# we can check now if all is good ➜ if ok couple of
59# lines will be displayed
60$ pip freeze
61# appdirs==1.4.3
62# packaging==16.8
63# pyparsing==2.2.0
64# six==1.10.0
65
66# now we are ready to install bottlepy ➜ web micro-framework
67$ pip install bottle
68
69# you can deactivate virtualenv but you will then go
70# under system domain ➜ for now don't deactivate
71$ deactivate
72```
73
74We are now ready to write simple web service. Let's create file app.py and paste code bellow in this newly created file.
75
76```python
77# -*- coding: utf-8 -*-
78
79import bottle
80import random
81import cProfile
82
83app = bottle.Bottle()
84
85# this function is a decorator and encapsulates function
86# and performs profiling and then saves it to subfolder
87# prof/function-name.prof
88# in our example only awesome_random_number function will
89# be profiled because it has do_cprofile defined
90def do_cprofile(func):
91 def profiled_func(*args, **kwargs):
92 profile = cProfile.Profile()
93 try:
94 profile.enable()
95 result = func(*args, **kwargs)
96 profile.disable()
97 return result
98 finally:
99 profile.dump_stats("prof/" + str(func.__name__) + ".prof")
100 return profiled_func
101
102
103# we use profiling over specific function with including
104# @do_cprofile above function declaration
105@app.route("/")
106@do_cprofile
107def awesome_random_number():
108 awesome_random_number = random.randint(0, 100)
109 return "awesome random number is " + str(awesome_random_number)
110
111@app.route("/test")
112def test():
113 return "dummy test"
114
115if __name__ == '__main__':
116 bottle.run(
117 app = app,
118 host = "0.0.0.0",
119 port = 4000
120 )
121
122# run with 'python app.py'
123# open browser 'http://0.0.0.0:4000'
124```
125
126When browser hits awesome\_random\_number() function profile is created in prof/ subfolder.
127
128## Visualize profile
129
130Now let's create callgrind format from this cProfile output.
131
132```bash
133$ cd prof/
134$ pyprof2calltree -i awesome_random_number.prof
135# this creates 'awesome_random_number.prof.log' file in the same folder
136```
137
138This file can be opened with visualizing tools listed above. In this case we will be using Profilling Viewer under MacOS. You can open image in new tab. As you can see from this example there is hierarchy of execution order of your code.
139
140![Profilling Viewer](/assets/python-profiling/profiling-viewer.png)
141
142> Make sure you convert output of the cProfile output every time you want to refresh and take a look at your possible optimizations because cProfile updates .prof file every time browser hits the function.
143
144This is just a simple example but when you are developing real-life applications this can be very illuminating, especially to see which parts of your code are bottlenecks and need to be optimized.
145
146## Update 2017-04-22
147
148Reddit user [mvt](https://www.reddit.com/user/mvt) also recommended this awesome web based profile visualizer [SnakeViz](https://jiffyclub.github.io/snakeviz/) that directly takes output from [cProfile](https://docs.python.org/2/library/profile.html#module-cProfile) module.
149
150<div class="reddit-embed" data-embed-media="www.redditmedia.com" data-embed-parent="false" data-embed-live="false" data-embed-uuid="583880c1-002e-41ed-a373-020a0ef2cff9" data-embed-created="2017-04-22T19:46:54.810Z"><a href="https://www.reddit.com/r/Python/comments/66v373/profiling_python_web_applications_with_visual/dgljhsb/">Comment</a> from discussion <a href="https://www.reddit.com/r/Python/comments/66v373/profiling_python_web_applications_with_visual/">Profiling Python web applications with visual tools</a>.</div><script async src="https://www.redditstatic.com/comment-embed.js"></script>
151
152```bash
153# let's install it globally as well
154$ sudo pip install snakeviz
155
156# now let's visualize
157$ cd prof/
158$ snakeviz awesome_random_number.prof
159# this automatically opens browser window and
160# shows visualized profile
161```
162
163![SnakeViz](/assets/python-profiling/snakeviz.png)
164
165Reddit user [ccharles](https://www.reddit.com/user/ccharles) suggested a better way for installing pip software by targeting user level instead of using sudo.
166
167<div class="reddit-embed" data-embed-media="www.redditmedia.com" data-embed-parent="false" data-embed-live="false" data-embed-uuid="f4f0459e-684d-441e-bebe-eb49b2f0a31d" data-embed-created="2017-04-22T19:46:10.874Z"><a href="https://www.reddit.com/r/Python/comments/66v373/profiling_python_web_applications_with_visual/dglpzkx/">Comment</a> from discussion <a href="https://www.reddit.com/r/Python/comments/66v373/profiling_python_web_applications_with_visual/">Profiling Python web applications with visual tools</a>.</div><script async src="https://www.redditstatic.com/comment-embed.js"></script>
168
169```bash
170# now we need to add this path to our $PATH variable
171# we do this my adding this line at the end of your
172# ~/.bashrc file
173PATH=$PATH:$HOME/.local/bin/
174
175# in order to use this new configuration you can close
176# and reopen terminal or reload .bashrc file
177$ source ~/.bashrc
178
179# now let's test if new directory is present in $PATH
180$ echo $PATH
181
182# now we can install on user level by adding --user
183# without use of sudo
184$ pip install snakeviz --user
185```
186
187Or as suggested by [mvt](https://www.reddit.com/user/mvt) you can use [pipsi](https://github.com/mitsuhiko/pipsi).