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.
@@ -0,0 +1,11 @@
1
+ Metadata-Version: 2.1
2
+ Name: custom_profiler
3
+ Version: 0.1.0
4
+ Summary: time and memory profiler
5
+ Author: Karim Ammar
6
+ Author-email: karim.ammar@cea.fr
7
+ Maintainer: Karim Ammar
8
+ Keywords: profiler
9
+ Requires-Dist: psutil
10
+
11
+ time and memory profiler
@@ -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,11 @@
1
+ Metadata-Version: 2.1
2
+ Name: custom_profiler
3
+ Version: 0.1.0
4
+ Summary: time and memory profiler
5
+ Author: Karim Ammar
6
+ Author-email: karim.ammar@cea.fr
7
+ Maintainer: Karim Ammar
8
+ Keywords: profiler
9
+ Requires-Dist: psutil
10
+
11
+ time and memory profiler
@@ -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
+ custom_profiler
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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,5 @@
1
+ import test_logger
2
+
3
+ test_logger.test_options(filename=None, # None = logger in csl ; False = no logger
4
+ loggername = ' ⚡',
5
+ addCustumLvl = True)
@@ -0,0 +1,5 @@
1
+ import test_logger
2
+
3
+ test_logger.test_options(filename='custom_profiler.log', # None = logger in csl ; False = no logger
4
+ loggername = ' ⚡',
5
+ addCustumLvl = False)
@@ -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
+