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.md184
1 files changed, 184 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..e4eece4
--- /dev/null
+++ b/_posts/2017-04-21-profiling-python-web-applications-with-visual-tools.md
@@ -0,0 +1,184 @@
1---
2layout: post
3title: "Profiling Python web applications with visual tools"
4description: "Missing link when debugging and profiling python web applications"
5---
6
7I 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.
8
9Before we begin there are some requirements. We will need to:
10
11- implement [cProfile](https://docs.python.org/2/library/profile.html#module-cProfile) into our web app,
12- convert output to [callgrind](http://valgrind.org/docs/manual/cl-manual.html) format with [pyprof2calltree](https://pypi.python.org/pypi/pyprof2calltree/),
13- visualize data with [KCachegrind](http://kcachegrind.sourceforge.net/html/Home.html) or [Profiling Viewer](http://www.profilingviewer.com/).
14
15
16If you are using MacOS you should check out [Profiling Viewer](http://www.profilingviewer.com/) or [MacCallGrind](http://www.maccallgrind.com/).
17
18![KCachegrind](/files/kcachegrind.png)
19
20We will be dividing this post into two main categories:
21
22- writing simple web-service,
23- visualize profile of this web-service.
24
25## Simple web-service
26
27Let'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.
28
29```bash
30# let's install virtualenv globally
31$ sudo pip install virtualenv
32
33# let's also install pyprof2calltree globally
34$ sudo pip install pyprof2calltree
35
36# now we create project
37$ mkdir demo-project
38$ cd demo-project/
39
40# now let's create folder where we will store profiles
41$ mkdir prof
42
43# now we create empty virtualenv in venv/ folder
44$ virtualenv --no-site-packages venv
45
46# we now need to activate virtualenv
47$ source venv/bin/activate
48
49# you can check if virtualenv was correctly initialized by
50# checking where your python interpreter is located
51# if command bellow points to your created directory and not some
52# system dir like /usr/bin/python then everything is fine
53$ which python
54
55# we can check now if all is good ➜ if ok couple of
56# lines will be displayed
57$ pip freeze
58# appdirs==1.4.3
59# packaging==16.8
60# pyparsing==2.2.0
61# six==1.10.0
62
63# now we are ready to install bottlepy ➜ web micro-framework
64$ pip install bottle
65
66# you can deactivate virtualenv but you will then go
67# under system domain ➜ for now don't deactivate
68$ deactivate
69```
70
71We are now ready to write simple web service. Let's create file app.py and paste code bellow in this newly created file.
72
73```python
74# -*- coding: utf-8 -*-
75
76import bottle
77import random
78import cProfile
79
80app = bottle.Bottle()
81
82# this function is a decorator and encapsulates function
83# and performs profiling and then saves it to subfolder
84# prof/function-name.prof
85# in our example only awesome_random_number function will
86# be profiled because it has do_cprofile defined
87def do_cprofile(func):
88 def profiled_func(*args, **kwargs):
89 profile = cProfile.Profile()
90 try:
91 profile.enable()
92 result = func(*args, **kwargs)
93 profile.disable()
94 return result
95 finally:
96 profile.dump_stats("prof/" + str(func.__name__) + ".prof")
97 return profiled_func
98
99
100# we use profiling over specific function with including
101# @do_cprofile above function declaration
102@app.route("/")
103@do_cprofile
104def awesome_random_number():
105 awesome_random_number = random.randint(0, 100)
106 return "awesome random number is " + str(awesome_random_number)
107
108@app.route("/test")
109def test():
110 return "dummy test"
111
112if __name__ == '__main__':
113 bottle.run(
114 app = app,
115 host = "0.0.0.0",
116 port = 4000
117 )
118
119# run with 'python app.py'
120# open browser 'http://0.0.0.0:4000'
121```
122
123When browser hits awesome\_random\_number() function profile is created in prof/ subfolder.
124
125## Visualize profile
126
127Now let's create callgrind format from this cProfile output.
128
129```bash
130$ cd prof/
131$ pyprof2calltree -i awesome_random_number.prof
132# this creates 'awesome_random_number.prof.log' file in the same folder
133```
134
135This 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.
136
137![Profilling Viewer](/files/profiling-viewer.png)
138
139> 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.
140
141This 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.
142
143### Update 2017-04-22
144
145Reddit 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.
146
147<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>
148
149```bash
150# let's install it globally as well
151$ sudo pip install snakeviz
152
153# now let's visualize
154$ cd prof/
155$ snakeviz awesome_random_number.prof
156# this automatically opens browser window and
157# shows visualized profile
158```
159
160![SnakeViz](/files/snakeviz.png)
161
162Reddit user [ccharles](https://www.reddit.com/user/ccharles) suggested a better way for installing pip software by targeting user level instead of using sudo.
163
164<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>
165
166```bash
167# now we need to add this path to our $PATH variable
168# we do this my adding this line at the end of your
169# ~/.bashrc file
170PATH=$PATH:$HOME/.local/bin/
171
172# in order to use this new configuration you can close
173# and reopen terminal or reload .bashrc file
174$ source ~/.bashrc
175
176# now let's test if new directory is present in $PATH
177$ echo $PATH
178
179# now we can install on user level by adding --user
180# without use of sudo
181$ pip install snakeviz --user
182```
183
184Or as suggested by [mvt](https://www.reddit.com/user/mvt) you can use [pipsi](https://github.com/mitsuhiko/pipsi).