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