custom-profiler 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- custom_profiler-0.1.0/PKG-INFO +11 -0
- custom_profiler-0.1.0/README.md +186 -0
- custom_profiler-0.1.0/custom_profiler/__init__.py +11 -0
- custom_profiler-0.1.0/custom_profiler/collecteur.py +140 -0
- custom_profiler-0.1.0/custom_profiler/custum_logger.py +63 -0
- custom_profiler-0.1.0/custom_profiler/custum_profiler.py +95 -0
- custom_profiler-0.1.0/custom_profiler/human_readable_time.py +49 -0
- custom_profiler-0.1.0/custom_profiler/line_by_line.py +75 -0
- custom_profiler-0.1.0/custom_profiler.egg-info/PKG-INFO +11 -0
- custom_profiler-0.1.0/custom_profiler.egg-info/SOURCES.txt +16 -0
- custom_profiler-0.1.0/custom_profiler.egg-info/dependency_links.txt +1 -0
- custom_profiler-0.1.0/custom_profiler.egg-info/requires.txt +1 -0
- custom_profiler-0.1.0/custom_profiler.egg-info/top_level.txt +1 -0
- custom_profiler-0.1.0/setup.cfg +4 -0
- custom_profiler-0.1.0/setup.py +16 -0
- custom_profiler-0.1.0/test/test_log_in_csl.py +5 -0
- custom_profiler-0.1.0/test/test_log_in_file.py +5 -0
- custom_profiler-0.1.0/test/test_logger.py +42 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# ⚡ custom_profiler ⚡
|
|
2
|
+
|
|
3
|
+
**custom_profiler** is a simple, interactive and lightweight (the only dependency is psutil) way of profiling the memory and execution time of your python code.
|
|
4
|
+
|
|
5
|
+
<p align="center"><img src="/gif/demoProf.gif?raw=true"/></p>
|
|
6
|
+
|
|
7
|
+
## Installation :
|
|
8
|
+
|
|
9
|
+
For user :
|
|
10
|
+
```bash
|
|
11
|
+
pip install git+https://github.com/KarGeekrie/customProfiler.git
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
For devellopeur :
|
|
15
|
+
```bash
|
|
16
|
+
git clone https://github.com/KarGeekrie/customProfiler.git
|
|
17
|
+
pip install -e customProfiler
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Profil function :
|
|
21
|
+
|
|
22
|
+
For profil python function, just add *@profiler* :
|
|
23
|
+
```python
|
|
24
|
+
import time
|
|
25
|
+
from custom_profiler import profiler
|
|
26
|
+
|
|
27
|
+
@profiler
|
|
28
|
+
def my_func():
|
|
29
|
+
a = [1] * (10 ** 6)
|
|
30
|
+
b = [2] * (2 * 10 ** 7)
|
|
31
|
+
time.sleep(1)
|
|
32
|
+
del b
|
|
33
|
+
time.sleep(5)
|
|
34
|
+
return a
|
|
35
|
+
|
|
36
|
+
a = my_func()
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Your log :
|
|
40
|
+
```bash
|
|
41
|
+
⚡ my_func took : 4.12s consumes : Δ 7.8M / peak 160.3M
|
|
42
|
+
|
|
43
|
+
⚡⚡⚡⚡⚡⚡ customProfiler log : global timer 4.15s / max memory use 172.2M ⚡⚡⚡⚡⚡
|
|
44
|
+
⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡
|
|
45
|
+
⚡ fct name | Nb call | mean time / global | mem Δ / peak ⚡
|
|
46
|
+
⚡ =============================================================================================== ⚡
|
|
47
|
+
⚡ my_func | 1 | 4.12s / 4.12s | 7.8M / 160.3M ⚡
|
|
48
|
+
⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Profil row code (with context managers) :
|
|
53
|
+
|
|
54
|
+
Profil row code with minimal impact :
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
import time
|
|
58
|
+
from custom_profiler import magic_profiler
|
|
59
|
+
|
|
60
|
+
with magic_profiler("my_code_to_prof") :
|
|
61
|
+
d = [1] * (10 ** 6)
|
|
62
|
+
e = [2] * (2 * 10 ** 7)
|
|
63
|
+
time.sleep(3)
|
|
64
|
+
del e
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Your log :
|
|
68
|
+
```bash
|
|
69
|
+
⚡ my_code_to_prof took : 3.12s consumes : Δ 7.8M / peak 160.3M
|
|
70
|
+
|
|
71
|
+
⚡⚡⚡⚡⚡⚡ customProfiler log : global timer 3.16s / max memory use 172.0M ⚡⚡⚡⚡⚡
|
|
72
|
+
⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡
|
|
73
|
+
⚡ fct name | Nb call | mean time / global | mem Δ / peak ⚡
|
|
74
|
+
⚡ =============================================================================================== ⚡
|
|
75
|
+
⚡ my_code_to_prof | 1 | 3.12s / 3.12s | 7.8M / 160.3M ⚡
|
|
76
|
+
⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Profil line by line :
|
|
81
|
+
|
|
82
|
+
For profil python function line by line, just add *@profiler_lbl* (follow memory peak is Not Avail in this case) :
|
|
83
|
+
```python
|
|
84
|
+
import time
|
|
85
|
+
from custom_profiler import profiler_lbl
|
|
86
|
+
|
|
87
|
+
@profiler_lbl
|
|
88
|
+
def my_func():
|
|
89
|
+
a = [1] * (10 ** 6)
|
|
90
|
+
b = [2] * (2 * 10 ** 7)
|
|
91
|
+
time.sleep(1)
|
|
92
|
+
del b
|
|
93
|
+
time.sleep(5)
|
|
94
|
+
return a
|
|
95
|
+
|
|
96
|
+
a = my_func()
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Your log :
|
|
100
|
+
```bash
|
|
101
|
+
⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡ line per line : my_func from test_custromProfiler.py
|
|
102
|
+
⚡ l 7 a = [1] * (10 ** 6) took : 3.10ms consumes : 7.5M
|
|
103
|
+
⚡ l 8 b = [2] * (2 * 10 ** 7) took : 39.74ms consumes : 152.5M
|
|
104
|
+
⚡ l 9 time.sleep(2) took : 2.00s consumes : 0.0B
|
|
105
|
+
⚡ l 10 del b took : 52.63ms consumes : -152.3M
|
|
106
|
+
⚡ l 11 time.sleep(2) took : 2.01s consumes : 0.0B
|
|
107
|
+
⚡ l 12 return a took : 1.07ms consumes : 0.0B
|
|
108
|
+
⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡ line per line : end
|
|
109
|
+
⚡ my_func took : 4.14s consumes : 8.0M
|
|
110
|
+
|
|
111
|
+
⚡⚡⚡⚡⚡⚡ customProfiler log : global timer 4.16s / max memory use 172.3M ⚡⚡⚡⚡⚡
|
|
112
|
+
⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡
|
|
113
|
+
⚡ fct name | Nb call | mean time / global | mem Δ / peak ⚡
|
|
114
|
+
⚡ =============================================================================================== ⚡
|
|
115
|
+
⚡ my_func l 7 | 1 | 3.10ms / 3.10ms | 7.5M / N.A ⚡
|
|
116
|
+
⚡ my_func l 8 | 1 | 39.74ms / 39.74ms | 152.5M / N.A ⚡
|
|
117
|
+
⚡ my_func l 9 | 1 | 2.00s / 2.00s | 0.0B / N.A ⚡
|
|
118
|
+
⚡ my_func l 10 | 1 | 52.63ms / 52.63ms | -152.3M / N.A ⚡
|
|
119
|
+
⚡ my_func l 11 | 1 | 2.01s / 2.01s | 0.0B / N.A ⚡
|
|
120
|
+
⚡ my_func l 12 | 1 | 1.07ms / 1.07ms | 0.0B / N.A ⚡
|
|
121
|
+
⚡ my_func | 1 | 4.14s / 4.14s | 8.0M / N.A ⚡
|
|
122
|
+
⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Options and logger :
|
|
127
|
+
|
|
128
|
+
The profiler uses thread to monitor memory evolution and offert interactive report (follow time and memory). Thread options are :
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
class INTERACTIVITY_OPT_ENUM :
|
|
132
|
+
ENABLE = "ENABLE" # thread (for memory peak follow) and interactive print
|
|
133
|
+
MF_NO_INTERAC = "MF_NO_INTERAC" # memory peak follow (with thread), no interacrtif print
|
|
134
|
+
DISABLE = "DISABLE" # no thread, no memory peak follow, no interacrtif print
|
|
135
|
+
AUTO = "AUTO" # if console is redirect in file (sys.stdout.isatty() == false) AUTO is equivalente to MF_NO_INTERAC else is equivalente to ENABLE
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
The other options allow you to activate a logger :
|
|
139
|
+
* *useLogger* : put log in a logger, default : *False*
|
|
140
|
+
* *loggername* : name of logger, if useLogger set at True, default : " ⚡"
|
|
141
|
+
* *addCustumLvl* : add new logging level call *PROFILER* at level *profilerlvl*. Log is put in *INFO" is addCustumLvl is *False*
|
|
142
|
+
* *profilerlvl* : logging level, default : *25*
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
import time
|
|
146
|
+
import logging
|
|
147
|
+
|
|
148
|
+
from custom_profiler import profiler, profiler_lbl, magic_profiler, profiler_collecteur, INTERACTIVITY_OPT_ENUM
|
|
149
|
+
|
|
150
|
+
loggername = " ⚡" # logger name
|
|
151
|
+
addCustumLvl = False # add "PROFILER" level in logger
|
|
152
|
+
|
|
153
|
+
pc = profiler_collecteur()
|
|
154
|
+
pc.options(interractivity = INTERACTIVITY_OPT_ENUM.ENABLE # ENABLE / MF_NO_INTERAC / DISABLE / AUTO
|
|
155
|
+
, useLogger = True
|
|
156
|
+
, loggername = loggername
|
|
157
|
+
, addCustumLvl = addCustumLvl
|
|
158
|
+
, profilerlvl = 25)
|
|
159
|
+
|
|
160
|
+
logInConsol = True
|
|
161
|
+
|
|
162
|
+
logger = logging.getLogger(loggername)
|
|
163
|
+
if logInConsol: #Log in consol
|
|
164
|
+
logging.basicConfig()
|
|
165
|
+
else : #Log in file
|
|
166
|
+
logging.basicConfig(filename='custom_profiler.log', filemode='w')
|
|
167
|
+
|
|
168
|
+
#[... run your code to profil...]
|
|
169
|
+
@profiler
|
|
170
|
+
def my_func():
|
|
171
|
+
a = [1] * (10 ** 6)
|
|
172
|
+
b = [2] * (2 * 10 ** 7)
|
|
173
|
+
time.sleep(1)
|
|
174
|
+
del b
|
|
175
|
+
time.sleep(5)
|
|
176
|
+
return a
|
|
177
|
+
|
|
178
|
+
a = my_func()
|
|
179
|
+
|
|
180
|
+
#if you want log summary in your logger :
|
|
181
|
+
if addCustumLvl :
|
|
182
|
+
logger.profiler(pc.__str__())
|
|
183
|
+
else :
|
|
184
|
+
logger.info(pc.__str__())
|
|
185
|
+
```
|
|
186
|
+
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
|
|
2
|
+
from . import custum_profiler
|
|
3
|
+
from . import collecteur
|
|
4
|
+
|
|
5
|
+
from functools import partial
|
|
6
|
+
|
|
7
|
+
profiler = partial(custum_profiler.profiler, linePerline=False)
|
|
8
|
+
profiler_lbl = partial(custum_profiler.profiler, linePerline=True)
|
|
9
|
+
magic_profiler = custum_profiler.magic_profiler
|
|
10
|
+
profiler_collecteur = collecteur.profiler_collecteur
|
|
11
|
+
INTERACTIVITY_OPT_ENUM = collecteur.INTERACTIVITY_OPT_ENUM
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import sys
|
|
3
|
+
if sys.platform == 'linux':
|
|
4
|
+
import resource
|
|
5
|
+
import logging
|
|
6
|
+
from collections import OrderedDict
|
|
7
|
+
|
|
8
|
+
import psutil
|
|
9
|
+
from psutil._common import bytes2human
|
|
10
|
+
process = psutil.Process()
|
|
11
|
+
|
|
12
|
+
from custom_profiler.custum_logger import add_logging_level
|
|
13
|
+
from custom_profiler.human_readable_time import human_time_duration as htd
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class INTERACTIVITY_OPT_ENUM :
|
|
17
|
+
ENABLE = "ENABLE" # thread (for memory peak follow) and interactive print
|
|
18
|
+
MF_NO_INTERAC = "MF_NO_INTERAC" # memory peak follow (with thread), no interacrtif print
|
|
19
|
+
DISABLE = "DISABLE" # no thread, no memory peak follow, no interacrtif print
|
|
20
|
+
AUTO = "AUTO" # if console is redirect in file (sys.stdout.isatty() == false) AUTO is equivalente to MF_NO_INTERAC else is equivalente to ENABLE
|
|
21
|
+
|
|
22
|
+
def get_ENUM_list(ENUM):
|
|
23
|
+
return [key for key in ENUM.__dict__ if key not in ["__main__", "__module__", "__doc__", '__dict__', '__weakref__']]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class profiler_collecteur(object):
|
|
27
|
+
_instance = None
|
|
28
|
+
|
|
29
|
+
def __new__(self):
|
|
30
|
+
if self._instance is None:
|
|
31
|
+
self._instance = super(profiler_collecteur, self).__new__(self)
|
|
32
|
+
self.profData = OrderedDict()
|
|
33
|
+
self.profThread = OrderedDict()
|
|
34
|
+
self.interractivity = INTERACTIVITY_OPT_ENUM.ENABLE
|
|
35
|
+
self.logger = None
|
|
36
|
+
self.start_time = time.perf_counter()
|
|
37
|
+
return self._instance
|
|
38
|
+
|
|
39
|
+
def options(self, interractivity = INTERACTIVITY_OPT_ENUM.ENABLE
|
|
40
|
+
, useLogger=False
|
|
41
|
+
, loggername = " ⚡"
|
|
42
|
+
, addCustumLvl= True
|
|
43
|
+
, profilerlvl = 25):
|
|
44
|
+
|
|
45
|
+
assert interractivity in get_ENUM_list(INTERACTIVITY_OPT_ENUM), f'interractivity {interractivity} must be in INTERACTIVITY_OPT_ENUM : {getEnumList(INTERACTIVITY_OPT_ENUM)}'
|
|
46
|
+
|
|
47
|
+
if interractivity == INTERACTIVITY_OPT_ENUM.AUTO :
|
|
48
|
+
if sys.stdout.isatty():
|
|
49
|
+
self.interractivity = INTERACTIVITY_OPT_ENUM.ENABLE
|
|
50
|
+
else :
|
|
51
|
+
self.interractivity = INTERACTIVITY_OPT_ENUM.MF_NO_INTERAC
|
|
52
|
+
|
|
53
|
+
if useLogger:
|
|
54
|
+
if self.interractivity == INTERACTIVITY_OPT_ENUM.ENABLE :
|
|
55
|
+
self.interractivity = INTERACTIVITY_OPT_ENUM.MF_NO_INTERAC
|
|
56
|
+
if addCustumLvl :
|
|
57
|
+
add_logging_level('PROFILER', profilerlvl)
|
|
58
|
+
logging.getLogger().setLevel("PROFILER")
|
|
59
|
+
self.logger = logging.getLogger(loggername).profiler
|
|
60
|
+
else :
|
|
61
|
+
logging.getLogger().setLevel("INFO")
|
|
62
|
+
self.logger = logging.getLogger(loggername).info
|
|
63
|
+
else :
|
|
64
|
+
self.logger = None
|
|
65
|
+
|
|
66
|
+
def save(self, fname, deltaTime, deltaMem, long_fname=None):
|
|
67
|
+
if fname in self.profData.keys():
|
|
68
|
+
self.profData[fname]["dt"] += deltaTime
|
|
69
|
+
self.profData[fname]["dm"] += deltaMem
|
|
70
|
+
self.profData[fname]["dm_list"].append(deltaMem)
|
|
71
|
+
self.profData[fname]["nbCall"] += 1
|
|
72
|
+
else :
|
|
73
|
+
self.profData[fname] = {"dt": deltaTime ,"dm": deltaMem, "dm_list": [deltaMem], "nbCall": 1}
|
|
74
|
+
|
|
75
|
+
t_str = htd(deltaTime)
|
|
76
|
+
value = f"{t_str}"
|
|
77
|
+
strmen = bytes2human(deltaMem)
|
|
78
|
+
if long_fname == None :
|
|
79
|
+
long_fname = fname
|
|
80
|
+
self.print_line(long_fname, value, strmen)
|
|
81
|
+
|
|
82
|
+
def thread_view(self, fname, deltaMem):
|
|
83
|
+
if fname in self.profThread.keys():
|
|
84
|
+
if deltaMem > self.profThread[fname]:
|
|
85
|
+
self.profThread[fname] = deltaMem
|
|
86
|
+
else :
|
|
87
|
+
self.profThread[fname] = deltaMem
|
|
88
|
+
|
|
89
|
+
def get_global_info(self):
|
|
90
|
+
run_time = htd(time.perf_counter() - self.start_time)
|
|
91
|
+
if sys.platform == 'win32':
|
|
92
|
+
mem_peack = bytes2human(process.memory_info().peak_wset)
|
|
93
|
+
else :
|
|
94
|
+
mem_peack = bytes2human(resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 0.0009765625) # in bytes
|
|
95
|
+
return run_time, mem_peack
|
|
96
|
+
|
|
97
|
+
def _print(self, toprint, end='\n'):
|
|
98
|
+
if self.logger:
|
|
99
|
+
self.logger(toprint)
|
|
100
|
+
else:
|
|
101
|
+
print(toprint, end=end)
|
|
102
|
+
|
|
103
|
+
def print_line(self, fname, delta_time, delta_mem, end='\n', color=""):
|
|
104
|
+
delta_mem = " Δ " + f"{delta_mem:>7}"
|
|
105
|
+
if fname in self.profThread.keys():
|
|
106
|
+
mmax = 0.
|
|
107
|
+
if fname in self.profData.keys():
|
|
108
|
+
mmax = max(self.profData[fname]["dm_list"])
|
|
109
|
+
if mmax < self.profThread[fname] :
|
|
110
|
+
mmax = self.profThread[fname]
|
|
111
|
+
delta_mem += " / peak " + f"{bytes2human(mmax):>7}"
|
|
112
|
+
toprint = f"{color} ⚡ {fname: ^21} took : {delta_time:<10} consumes : {delta_mem} \033[0m"
|
|
113
|
+
self._print(toprint, end)
|
|
114
|
+
|
|
115
|
+
def __str__(self):
|
|
116
|
+
run_time, mem_peack = self.get_global_info()
|
|
117
|
+
str = ("\n " + "⚡" * 6 + f" customProfiler log : global timer {run_time} / max memory use {mem_peack:^10}"+ "⚡" * 6)
|
|
118
|
+
str += "\n " + "⚡" * 50
|
|
119
|
+
str += "\n ⚡ {:^31} | {:8} | {:<29} | {:^17} ⚡".format("fct name"
|
|
120
|
+
, "Nb call"
|
|
121
|
+
, " time : mean / global"
|
|
122
|
+
, "mem : mean / max")
|
|
123
|
+
str += "\n ⚡ "+ "="*95 + "⚡"
|
|
124
|
+
for key, val in self.profData.items():
|
|
125
|
+
t_str = htd(val["dt"])
|
|
126
|
+
t_p_call_str = htd(val["dt"]/val['nbCall'])
|
|
127
|
+
str += f"\n ⚡ {key: ^31.31} | {val['nbCall']:^8} "
|
|
128
|
+
str += f"| {t_p_call_str} / {t_str} "
|
|
129
|
+
strmen = bytes2human(self.profData[key]["dm"]/val['nbCall'])
|
|
130
|
+
mmax = max(self.profData[key]["dm_list"])
|
|
131
|
+
if key in self.profThread.keys():
|
|
132
|
+
if mmax < self.profThread[key] :
|
|
133
|
+
mmax = self.profThread[key]
|
|
134
|
+
strmaxmem = bytes2human(mmax)
|
|
135
|
+
str += f"| {strmen:>7} / {strmaxmem:>7} ⚡"
|
|
136
|
+
str += "\n " + "⚡" * 50
|
|
137
|
+
return str
|
|
138
|
+
|
|
139
|
+
def __del__(self):
|
|
140
|
+
print(self)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
#https://stackoverflow.com/questions/2183233/how-to-add-a-custom-loglevel-to-pythons-logging-facility/35804945#35804945
|
|
4
|
+
def add_logging_level(levelName, levelNum, methodName=None):
|
|
5
|
+
"""
|
|
6
|
+
Comprehensively adds a new logging level to the `logging` module and the
|
|
7
|
+
currently configured logging class.
|
|
8
|
+
|
|
9
|
+
`levelName` becomes an attribute of the `logging` module with the value
|
|
10
|
+
`levelNum`. `methodName` becomes a convenience method for both `logging`
|
|
11
|
+
itself and the class returned by `logging.getLoggerClass()` (usually just
|
|
12
|
+
`logging.Logger`). If `methodName` is not specified, `levelName.lower()` is
|
|
13
|
+
used.
|
|
14
|
+
|
|
15
|
+
To avoid accidental clobberings of existing attributes, this method will
|
|
16
|
+
raise an `AttributeError` if the level name is already an attribute of the
|
|
17
|
+
`logging` module or if the method name is already present
|
|
18
|
+
|
|
19
|
+
Example
|
|
20
|
+
-------
|
|
21
|
+
>>> addLoggingLevel('TRACE', logging.DEBUG - 5)
|
|
22
|
+
>>> logging.getLogger(__name__).setLevel("TRACE")
|
|
23
|
+
>>> logging.getLogger(__name__).trace('that worked')
|
|
24
|
+
>>> logging.trace('so did this')
|
|
25
|
+
>>> logging.TRACE
|
|
26
|
+
5
|
|
27
|
+
|
|
28
|
+
"""
|
|
29
|
+
if not methodName:
|
|
30
|
+
methodName = levelName.lower()
|
|
31
|
+
|
|
32
|
+
if hasattr(logging, levelName):
|
|
33
|
+
raise AttributeError('{} already defined in logging module'.format(levelName))
|
|
34
|
+
if hasattr(logging, methodName):
|
|
35
|
+
raise AttributeError('{} already defined in logging module'.format(methodName))
|
|
36
|
+
if hasattr(logging.getLoggerClass(), methodName):
|
|
37
|
+
raise AttributeError('{} already defined in logger class'.format(methodName))
|
|
38
|
+
|
|
39
|
+
# This method was inspired by the answers to Stack Overflow post
|
|
40
|
+
# http://stackoverflow.com/q/2183233/2988730, especially
|
|
41
|
+
# http://stackoverflow.com/a/13638084/2988730
|
|
42
|
+
def logForLevel(self, message, *args, **kwargs):
|
|
43
|
+
if self.isEnabledFor(levelNum):
|
|
44
|
+
self._log(levelNum, message, args, **kwargs)
|
|
45
|
+
def logToRoot(message, *args, **kwargs):
|
|
46
|
+
logging.log(levelNum, message, *args, **kwargs)
|
|
47
|
+
|
|
48
|
+
logging.addLevelName(levelNum, levelName)
|
|
49
|
+
setattr(logging, levelName, levelNum)
|
|
50
|
+
setattr(logging.getLoggerClass(), methodName, logForLevel)
|
|
51
|
+
setattr(logging, methodName, logToRoot)
|
|
52
|
+
|
|
53
|
+
if __name__ == "__main__":
|
|
54
|
+
|
|
55
|
+
add_logging_level('PROFILER', 25)
|
|
56
|
+
logging.getLogger().setLevel("PROFILER")
|
|
57
|
+
logger = logging.getLogger("⚡")
|
|
58
|
+
|
|
59
|
+
ch = logging.StreamHandler()
|
|
60
|
+
logger.addHandler(ch)
|
|
61
|
+
|
|
62
|
+
logger.profiler("test print")
|
|
63
|
+
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import time
|
|
3
|
+
|
|
4
|
+
from functools import wraps
|
|
5
|
+
|
|
6
|
+
import threading
|
|
7
|
+
from threading import Thread
|
|
8
|
+
from threading import Event
|
|
9
|
+
|
|
10
|
+
import psutil
|
|
11
|
+
from psutil._common import bytes2human
|
|
12
|
+
process = psutil.Process()
|
|
13
|
+
|
|
14
|
+
from custom_profiler.line_by_line import trace_calls
|
|
15
|
+
from custom_profiler.collecteur import profiler_collecteur, INTERACTIVITY_OPT_ENUM
|
|
16
|
+
from custom_profiler.human_readable_time import human_time_duration as htd
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
profC = profiler_collecteur()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def task(event, fname, start_time, start_mem):
|
|
23
|
+
i = 0
|
|
24
|
+
while True :
|
|
25
|
+
time.sleep(0.01)
|
|
26
|
+
if i % 100 == True :
|
|
27
|
+
t_str = htd(time.perf_counter() - start_time)
|
|
28
|
+
dm = process.memory_info().rss - start_mem
|
|
29
|
+
profC.thread_view(fname, dm) #sauvegarde delta mem max
|
|
30
|
+
strmen = bytes2human(dm)
|
|
31
|
+
if profC.interractivity == INTERACTIVITY_OPT_ENUM.ENABLE :
|
|
32
|
+
if threading.active_count() < 3:
|
|
33
|
+
profC.print_line(fname, t_str, strmen, end="\r", color="\033[93m")
|
|
34
|
+
|
|
35
|
+
i += 1
|
|
36
|
+
if event.is_set():
|
|
37
|
+
break
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class thread_mananger:
|
|
41
|
+
def __init__(self, fname, start_time, start_mem):
|
|
42
|
+
self.event = Event()
|
|
43
|
+
self.t = Thread(target=task, args=(self.event, fname, start_time, start_mem))
|
|
44
|
+
self.t.daemon = True
|
|
45
|
+
self.t.start()
|
|
46
|
+
|
|
47
|
+
def end(self):
|
|
48
|
+
self.event.set()
|
|
49
|
+
self.t.join()
|
|
50
|
+
|
|
51
|
+
#https://stackoverflow.com/questions/5929107/decorators-with-parameters
|
|
52
|
+
def profiler(func, linePerline):
|
|
53
|
+
@wraps(func)
|
|
54
|
+
def wrapper(*args, **kwargs):
|
|
55
|
+
if profC.interractivity != INTERACTIVITY_OPT_ENUM.DISABLE and linePerline == False:
|
|
56
|
+
tm = thread_mananger(func.__name__, time.perf_counter(), process.memory_info().rss)
|
|
57
|
+
|
|
58
|
+
start_mem = process.memory_info().rss
|
|
59
|
+
start_time = time.perf_counter()
|
|
60
|
+
|
|
61
|
+
if linePerline :
|
|
62
|
+
sys.settrace(trace_calls)
|
|
63
|
+
result = func(*args, **kwargs)
|
|
64
|
+
if linePerline :
|
|
65
|
+
sys.settrace(None)
|
|
66
|
+
|
|
67
|
+
end_time = time.perf_counter()
|
|
68
|
+
end_mem = process.memory_info().rss
|
|
69
|
+
|
|
70
|
+
if profC.interractivity != INTERACTIVITY_OPT_ENUM.DISABLE and linePerline == False :
|
|
71
|
+
tm.end()
|
|
72
|
+
|
|
73
|
+
profC.save(func.__name__, end_time - start_time, end_mem - start_mem)
|
|
74
|
+
|
|
75
|
+
return result
|
|
76
|
+
return wrapper
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class magic_profiler():
|
|
80
|
+
|
|
81
|
+
def __init__(self, func_name):
|
|
82
|
+
self.func_name = func_name
|
|
83
|
+
|
|
84
|
+
def __enter__(self):
|
|
85
|
+
if profC.interractivity != INTERACTIVITY_OPT_ENUM.DISABLE :
|
|
86
|
+
self.tm = thread_mananger(self.func_name, time.perf_counter(), process.memory_info().rss)
|
|
87
|
+
self.start_mem = process.memory_info().rss
|
|
88
|
+
self.start_time = time.perf_counter()
|
|
89
|
+
|
|
90
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
91
|
+
end_time = time.perf_counter()
|
|
92
|
+
end_mem = process.memory_info().rss
|
|
93
|
+
if profC.interractivity != INTERACTIVITY_OPT_ENUM.DISABLE :
|
|
94
|
+
self.tm.end()
|
|
95
|
+
profC.save(self.func_name, end_time - self.start_time, end_mem - self.start_mem)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from decimal import Decimal
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
TIME_UTS_DURATION_UNITS = (
|
|
6
|
+
('d', 60*60*24),
|
|
7
|
+
('h', 60*60),
|
|
8
|
+
('min', 60),
|
|
9
|
+
('s', 1),
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
TIME_LTS_DURATION_UNITS = (
|
|
14
|
+
('s ', 1),
|
|
15
|
+
('ms', 1e-3),
|
|
16
|
+
('µs', 1e-6),
|
|
17
|
+
('ns', 1e-9),
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def human_time_duration(seconds):
|
|
22
|
+
assert type(seconds) in [float, int], f'seconds ({type(seconds)}) must be a float or int'
|
|
23
|
+
if seconds == 0:
|
|
24
|
+
return 0
|
|
25
|
+
if seconds <= 60 :
|
|
26
|
+
for unit, div in TIME_LTS_DURATION_UNITS:
|
|
27
|
+
if seconds >= div :
|
|
28
|
+
return f"{seconds/div:11.2f}{unit}"
|
|
29
|
+
return f"{Decimal(seconds):11.2E}s "
|
|
30
|
+
else :
|
|
31
|
+
if seconds < 3600 :
|
|
32
|
+
return f"{seconds//60:6.0f}min{seconds%60:2.0f}s"
|
|
33
|
+
else :
|
|
34
|
+
h = seconds // 3600
|
|
35
|
+
m = (seconds - h * 3600) // 60
|
|
36
|
+
s = seconds - (h * 3600) - (m * 60)
|
|
37
|
+
return f"{h:3}h{m:2}min{s:2}s"
|
|
38
|
+
|
|
39
|
+
if __name__ == "__main__":
|
|
40
|
+
|
|
41
|
+
for i in range(0, 11):
|
|
42
|
+
print(f"{10**-i:8}", human_time_duration(10**-i) \
|
|
43
|
+
, human_time_duration(2*10**-i)
|
|
44
|
+
, human_time_duration(10**-i - 10**(-i-3))
|
|
45
|
+
, human_time_duration(1/3*10**-i))
|
|
46
|
+
|
|
47
|
+
for i in [1, 2, 59, 60, 61, 144, 3599, 3600, 3601, 3660, 3661, 3600*120]:
|
|
48
|
+
print(f"{i:8}",human_time_duration(i))
|
|
49
|
+
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import time
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
import psutil
|
|
6
|
+
from psutil._common import bytes2human
|
|
7
|
+
process = psutil.Process()
|
|
8
|
+
|
|
9
|
+
from custom_profiler.collecteur import profiler_collecteur
|
|
10
|
+
from custom_profiler.human_readable_time import human_time_duration as htd
|
|
11
|
+
|
|
12
|
+
profC = profiler_collecteur()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def trace_lines(frame, event, arg):
|
|
16
|
+
|
|
17
|
+
code_source, lineStart = inspect.getsourcelines(frame.f_code)
|
|
18
|
+
co_name = frame.f_code.co_name
|
|
19
|
+
|
|
20
|
+
if event != "line":
|
|
21
|
+
fname = f"l {frame.f_lineno:<3} {profC.__src[-1]:40}"
|
|
22
|
+
fnameSave = f"{co_name} l {frame.f_lineno:<3}"
|
|
23
|
+
t = time.perf_counter() - profC.__tic
|
|
24
|
+
men = process.memory_info().rss - profC.__tic_mem
|
|
25
|
+
profC.save(fnameSave, t, men, fname)
|
|
26
|
+
profC._print( " " + "⚡"*20 + f" line per line : end")
|
|
27
|
+
return
|
|
28
|
+
|
|
29
|
+
profC.__src.append(code_source[frame.f_lineno - lineStart].split('\n')[0])
|
|
30
|
+
|
|
31
|
+
if len(profC.__src) == 1 :
|
|
32
|
+
head = code_source[1].split('\n')[0]
|
|
33
|
+
profC._print( " " + "⚡"*20 + f" line per line : {head.split()[1].split('(')[0]} from {frame.f_code.co_filename}")
|
|
34
|
+
else :
|
|
35
|
+
fname = f"l {frame.f_lineno-1:<3} {profC.__src[-2]:40}"
|
|
36
|
+
fnameSave = f"{co_name} l {frame.f_lineno-1:<3}"
|
|
37
|
+
t = time.perf_counter() - profC.__tic
|
|
38
|
+
men = process.memory_info().rss - profC.__tic_mem
|
|
39
|
+
profC.save(fnameSave, t, men, fname)
|
|
40
|
+
|
|
41
|
+
profC.__tic = time.perf_counter()
|
|
42
|
+
profC.__tic_mem = process.memory_info().rss
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def trace_calls(frame, event, arg):
|
|
46
|
+
profC.__tic = 0.
|
|
47
|
+
profC.__tic_mem = 0.
|
|
48
|
+
profC.__src = []
|
|
49
|
+
|
|
50
|
+
if event != "call":
|
|
51
|
+
return
|
|
52
|
+
return trace_lines
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def lpl(fonction):
|
|
56
|
+
def wrapper(*args, **kwargs):
|
|
57
|
+
sys.settrace(trace_calls)
|
|
58
|
+
resultat = fonction(*args, **kwargs)
|
|
59
|
+
sys.settrace(None)
|
|
60
|
+
return resultat
|
|
61
|
+
return wrapper
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
if __name__ == "__main__":
|
|
65
|
+
|
|
66
|
+
@lpl
|
|
67
|
+
def my_func():
|
|
68
|
+
a = [1] * (10 ** 6)
|
|
69
|
+
b = [2] * (2 * 10 ** 7)
|
|
70
|
+
time.sleep(1)
|
|
71
|
+
del b
|
|
72
|
+
# time.sleep(5)
|
|
73
|
+
return a
|
|
74
|
+
|
|
75
|
+
my_func()
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
setup.py
|
|
3
|
+
custom_profiler/__init__.py
|
|
4
|
+
custom_profiler/collecteur.py
|
|
5
|
+
custom_profiler/custum_logger.py
|
|
6
|
+
custom_profiler/custum_profiler.py
|
|
7
|
+
custom_profiler/human_readable_time.py
|
|
8
|
+
custom_profiler/line_by_line.py
|
|
9
|
+
custom_profiler.egg-info/PKG-INFO
|
|
10
|
+
custom_profiler.egg-info/SOURCES.txt
|
|
11
|
+
custom_profiler.egg-info/dependency_links.txt
|
|
12
|
+
custom_profiler.egg-info/requires.txt
|
|
13
|
+
custom_profiler.egg-info/top_level.txt
|
|
14
|
+
test/test_log_in_csl.py
|
|
15
|
+
test/test_log_in_file.py
|
|
16
|
+
test/test_logger.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
psutil
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
custom_profiler
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
from setuptools import find_packages, setup
|
|
4
|
+
|
|
5
|
+
setup(
|
|
6
|
+
name='custom_profiler',
|
|
7
|
+
version='0.1.0',
|
|
8
|
+
description='time and memory profiler',
|
|
9
|
+
long_description='time and memory profiler',
|
|
10
|
+
author='Karim Ammar',
|
|
11
|
+
maintainer="Karim Ammar",
|
|
12
|
+
author_email='karim.ammar@cea.fr',
|
|
13
|
+
packages=find_packages(),
|
|
14
|
+
keywords="profiler",
|
|
15
|
+
install_requires=["psutil"]
|
|
16
|
+
)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
from custom_profiler import profiler, profiler_lbl, magic_profiler, profiler_collecteur, INTERACTIVITY_OPT_ENUM
|
|
5
|
+
|
|
6
|
+
def test_options(filename='custom_profiler.log', # None = logger in csl ; False = no logger
|
|
7
|
+
interractivity = INTERACTIVITY_OPT_ENUM.ENABLE, # ENABLE / MF_NO_INTERAC / DISABLE / AUTO
|
|
8
|
+
loggername = ' ⚡',
|
|
9
|
+
addCustumLvl = False):
|
|
10
|
+
|
|
11
|
+
useLogger = True if filename != False else False
|
|
12
|
+
|
|
13
|
+
pc = profiler_collecteur()
|
|
14
|
+
pc.options(interractivity = interractivity # ENABLE / MF_NO_INTERAC / DISABLE / AUTO
|
|
15
|
+
, useLogger = useLogger
|
|
16
|
+
, loggername = loggername
|
|
17
|
+
, addCustumLvl = addCustumLvl
|
|
18
|
+
, profilerlvl = 25)
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(loggername)
|
|
21
|
+
|
|
22
|
+
if filename:
|
|
23
|
+
logging.basicConfig(filename=filename, filemode='w')
|
|
24
|
+
else :
|
|
25
|
+
logging.basicConfig()
|
|
26
|
+
|
|
27
|
+
@profiler
|
|
28
|
+
def my_func():
|
|
29
|
+
a = [1] * (10 ** 6)
|
|
30
|
+
b = [2] * (2 * 10 ** 7)
|
|
31
|
+
time.sleep(2)
|
|
32
|
+
del b
|
|
33
|
+
time.sleep(2)
|
|
34
|
+
return a
|
|
35
|
+
|
|
36
|
+
a = my_func()
|
|
37
|
+
if useLogger and filename!=None :
|
|
38
|
+
if addCustumLvl :
|
|
39
|
+
logger.profiler(pc.__str__())
|
|
40
|
+
else :
|
|
41
|
+
logger.info(pc.__str__())
|
|
42
|
+
|