├── .gitignore ├── README.md ├── aprofiler.py └── examples ├── generate-frame-graph.png └── generate-frame-graph.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | 21 | # Installer logs 22 | pip-log.txt 23 | 24 | # Unit test / coverage reports 25 | .coverage 26 | .tox 27 | nosetests.xml 28 | 29 | # Translations 30 | *.mo 31 | 32 | # Mr Developer 33 | .mr.developer.cfg 34 | .project 35 | .pydevproject 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Aprofiler 2 | ========= 3 | 4 | Unlike the others python profilers, this one is dedicated to profile your 5 | application logic. You can emit(push/pop) events, or marks with information. 6 | When you stop, the profiler will generate a json with all the events. 7 | You can then generate HTML graphics out of it. 8 | 9 | Let's say you got something like:: 10 | 11 | def my_app_update(): 12 | if something_happened: 13 | foo += 1 14 | do_stuff() 15 | do_more_stuff() 16 | 17 | You want to be able to see how much time is spent in both `do_stuff` and 18 | `do_more_stuff`, and maybe within then. So you can change your application code 19 | to include a profiler:: 20 | 21 | from aprofiler import profiler 22 | profiler.start() 23 | 24 | def my_app_update(): 25 | profiler.push('mainloop') 26 | 27 | if something_happen: 28 | foo += 1 29 | profiler.mark('something happened, foo is now', foo) 30 | 31 | profiler.push('do_stuff') 32 | do_stuff() 33 | profiler.pop('do_stuff') 34 | 35 | profiler.push('do_more_stuff') 36 | do_more_stuff() 37 | profiler.pop('do_more_stuff') 38 | 39 | profiler.pop('mainloop') 40 | 41 | # at the end 42 | profiler.stop() 43 | 44 | When the profiler stop, it will generate a .json with all the events generated. 45 | 46 | Now you can create your own converter to generate cool graphics. Have a look at 47 | example to see how we used in the Kivy project: 48 | 49 | [![Frame profiling](https://github.com/kivy/aprofiler/raw/master/examples/generate-frame-graph.png)](#framegraph) 50 | 51 | -------------------------------------------------------------------------------- /aprofiler.py: -------------------------------------------------------------------------------- 1 | ''' 2 | A profiler 3 | ========== 4 | 5 | Unlike the others python profilers, this one is dedicated to profile your 6 | application logic. You can emit(push/pop) events, or marks with information. 7 | When you stop, the profiler will generate a json with all the events. 8 | You can then generate HTML graphics out of it. 9 | 10 | ''' 11 | 12 | __all__ = ('Profiler', 'profiler') 13 | 14 | from os import getpid 15 | from json import dump 16 | from time import time 17 | 18 | class Aprofiler(object): 19 | '''Class for doing a profiling. 20 | ''' 21 | def __init__(self, name): 22 | object.__init__(self) 23 | self.name = 'mainloop' 24 | self.enabled = False 25 | self.events = [] 26 | self.marks = [] 27 | self.last_start_index = 0 28 | 29 | def push(self, event, t=None): 30 | '''Add a "start" event to the events list 31 | ''' 32 | if not self.enabled: 33 | return 34 | if t is None: 35 | t = time() 36 | if event == 'mainloop': 37 | self.last_start_index = len(self.events) 38 | self.events.append((t, 'start-' + event)) 39 | return t 40 | 41 | def pop(self, event, t=None): 42 | '''Add an "end" event to the events list 43 | ''' 44 | if not self.enabled: 45 | return 46 | if t is None: 47 | t = time() 48 | if event == 'mainloop': 49 | if self.marks and self.last_start_index: 50 | i = self.last_start_index 51 | self.events = self.events[:i] + self.marks + self.events[i:] 52 | self.marks = [] 53 | self.last_start_index = 0 54 | self.events.append((t, 'stop-' + event)) 55 | return t 56 | 57 | def mark(self, name, message): 58 | '''Add a "mark" to the events list 59 | If the mark is generated during a mainloop, it will be added before the 60 | start of the main loop, when the main loop is end event is emitted. 61 | ''' 62 | t = time() 63 | self.marks.append((t, 'mark-{}'.format(name), message)) 64 | 65 | def start(self): 66 | '''Start the profiler 67 | ''' 68 | self.enabled = True 69 | 70 | def stop(self): 71 | '''Stop the profiler, and dump everything on the json 72 | ''' 73 | self.enabled = False 74 | fn = 'profiler-{}.json'.format(getpid()) 75 | with open(fn, 'w') as fd: 76 | dump(self.events, fd) 77 | 78 | 79 | #: Create a default profiler 80 | profiler = Aprofiler() 81 | 82 | -------------------------------------------------------------------------------- /examples/generate-frame-graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kivy/aprofiler/4edbd155031ecf9113ce1d91c9cdde8ea65a0c3e/examples/generate-frame-graph.png -------------------------------------------------------------------------------- /examples/generate-frame-graph.py: -------------------------------------------------------------------------------- 1 | from json import load 2 | from sys import argv 3 | from cgi import escape 4 | 5 | HTML_HEADER = ''' 6 | 7 | 8 | 98 | 99 | ''' 100 | 101 | HTML_FOOTER = ''' 102 |
103 | 104 | 105 | ''' 106 | 107 | WIDTH = 700 108 | 109 | def generate(fn): 110 | with open(fn) as fd: 111 | events = load(fd) 112 | 113 | # extract event per frame 114 | html = [] 115 | tags = {} 116 | start_mainloop = 0 117 | for infos in events: 118 | time, event = infos[:2] 119 | 120 | # extract a main event 121 | if event == 'start-mainloop': 122 | start_mainloop = time 123 | index_mainloop = len(html) 124 | html.append('') 125 | elif event == 'end-mainloop': 126 | html[index_mainloop] = '
' 127 | html.append('
') 128 | 129 | cmd, event = event.split('-', 1) 130 | 131 | # analyse commands 132 | if cmd == 'mark': 133 | html.append('
{}: {}
'.format( 134 | event, escape(str(infos[2])))) 135 | elif cmd == 'start': 136 | index = len(html) 137 | html.append('') 138 | tags[event] = (time, index) 139 | elif cmd == 'end': 140 | start_time, index = tags[event] 141 | width = int((time - start_time) * WIDTH / (1 / 60.)) 142 | offset = int((start_time - start_mainloop) * WIDTH / (1 / 60.)) 143 | duration = (time - start_time) 144 | text = ( 145 | '
' 146 | '
{0}
' 147 | '
{3:.2f}
' 148 | '
' 149 | '
' 150 | '{0}
' 151 | '
' 152 | '
' 153 | ).format(event, width, offset, duration * 1000) 154 | 155 | if duration > 1. / 60.: 156 | text = '
' + text + '
' 157 | 158 | html[index] = text 159 | 160 | with open('output.html', 'w') as fd: 161 | fd.write(HTML_HEADER) 162 | fd.write(''.join(html)) 163 | fd.write(HTML_FOOTER) 164 | 165 | 166 | generate(argv[1]) 167 | --------------------------------------------------------------------------------