8
8
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9
9
# See the License for the specific language governing permissions and
10
10
# limitations under the License.
11
- import argparse
12
11
import atexit
13
- import logging
14
12
import queue
15
- import subprocess
16
13
import threading
17
14
import time
18
15
from enum import Enum
21
18
from pathlib import Path
22
19
from typing import Callable , List , Optional , Tuple
23
20
21
+ import psutil
24
22
import matplotlib
25
23
import matplotlib .pyplot as plt
26
- import psutil
27
- from tabulate import tabulate
24
+ import logging as log
28
25
29
- logger = logging .getLogger ("memory_monitor" )
30
26
31
27
# CUSTOM FIX TO AVOID ISSUE: RuntimeError: main thread is not in main loop
32
28
matplotlib .use ('Agg' )
@@ -50,7 +46,7 @@ class MemoryUnit(Enum):
50
46
@lru_cache
51
47
def system_memory_warning ():
52
48
# Log once
53
- logger .warning (
49
+ log .warning (
54
50
"Please note that MemoryType.SYSTEM in general is affected by other processes that change RAM availability."
55
51
)
56
52
@@ -265,7 +261,7 @@ def _monitor_memory(self):
265
261
class memory_monitor_context :
266
262
def __init__ (
267
263
self ,
268
- interval : Optional [float ] = 0.1 ,
264
+ interval : Optional [float ] = 0.01 ,
269
265
memory_unit : Optional [MemoryUnit ] = MemoryUnit .MiB ,
270
266
return_max_value : Optional [bool ] = True ,
271
267
save_dir : Optional [Path ] = None ,
@@ -296,7 +292,7 @@ def __init__(
296
292
self .return_max_value = return_max_value
297
293
self .save_dir = save_dir
298
294
299
- self .memory_data = {}
295
+ self .memory_data = {'full_mem' : {}, 'from_zero' : {} }
300
296
301
297
def __enter__ (self ):
302
298
for mm in self .memory_monitors .values ():
@@ -312,18 +308,96 @@ def __exit__(self, exc_type, exc_val, exc_tb):
312
308
mm .stop ()
313
309
for fz in [False , True ]:
314
310
time_values , memory_values = mm .get_data (memory_from_zero = fz )
315
- if fz :
316
- self .memory_data [mt ] = max (memory_values ) if self .return_max_value else (time_values , memory_values )
311
+
312
+ mm_measure_type = 'from_zero' if fz else 'full_mem'
313
+ self .memory_data [mm_measure_type ][mt ] = max (memory_values ) if self .return_max_value else (time_values , memory_values )
317
314
318
315
if self .save_dir :
319
316
mm .save_memory_logs (
320
317
time_values ,
321
318
memory_values ,
322
319
save_dir = self .save_dir ,
323
- filename_suffix = "_from-zero " if fz else "" ,
320
+ filename_suffix = "_mem_increase " if fz else "" ,
324
321
)
325
322
326
323
324
+ class MemMonitorWrapper ():
325
+ def __init__ (self ):
326
+ self .save_dir = None
327
+
328
+ self .interval = 0.01
329
+ self .memory_unit = MemoryUnit .MiB
330
+
331
+ self .memory_types = [MemoryType .RSS , MemoryType .SYSTEM ]
332
+
333
+ self .memory_monitors = {}
334
+ self .memory_data = {'full_mem' : {}, 'from_zero' : {}}
335
+
336
+ def create_monitors (self ):
337
+ for memory_type in self .memory_types :
338
+ self .memory_monitors [memory_type ] = MemoryMonitor (
339
+ interval = self .interval , memory_type = memory_type , memory_unit = self .memory_unit
340
+ )
341
+
342
+ def set_dir (self , dir ):
343
+ if not Path (dir ).exists ():
344
+ log .warning (f"Path to dir for memory consamption data is not exists { dir } , run without it." )
345
+ else :
346
+ self .save_dir = Path (dir )
347
+
348
+ def start (self , delay = None ):
349
+ self .memory_data = {'full_mem' : {}, 'from_zero' : {}}
350
+ for mm in self .memory_monitors .values ():
351
+ mm .start ()
352
+
353
+ # compilation could be very fast, apply delay
354
+ if delay :
355
+ time .sleep (delay )
356
+ else :
357
+ time .sleep (self .interval * 3 )
358
+
359
+ def stop_and_collect_data (self , dir_name = 'mem_monitor_log' ):
360
+ self .stop ()
361
+
362
+ for mt , mm in self .memory_monitors .items ():
363
+ if not mm ._memory_values_queue or len (mm ._memory_values_queue .queue ) == 0 :
364
+ continue
365
+
366
+ for from_zero in [False , True ]:
367
+ time_values , memory_values = mm .get_data (memory_from_zero = from_zero )
368
+
369
+ mm_measure_type = 'from_zero' if from_zero else 'full_mem'
370
+ self .memory_data [mm_measure_type ][mt ] = max (memory_values )
371
+
372
+ if self .save_dir :
373
+ mm .save_memory_logs (
374
+ time_values ,
375
+ memory_values ,
376
+ save_dir = self .save_dir / dir_name ,
377
+ filename_suffix = "_mem_increase" if from_zero else "" ,
378
+ )
379
+
380
+ def stop (self ):
381
+ # Stop addition of new values as soon as possible
382
+ for mm in self .memory_monitors .values ():
383
+ mm ._monitoring_thread_should_stop = True
384
+
385
+ for mm in self .memory_monitors .values ():
386
+ mm .stop ()
387
+
388
+ def get_data (self ):
389
+ return (self .memory_data ['full_mem' ].get (MemoryType .RSS , - 1 ), self .memory_data ['from_zero' ].get (MemoryType .RSS , - 1 ),
390
+ self .memory_data ['full_mem' ].get (MemoryType .SYSTEM , - 1 ), self .memory_data ['from_zero' ].get (MemoryType .SYSTEM , - 1 ))
391
+
392
+ def log_data (self , comment ):
393
+ max_rss_mem , max_rss_increase , max_sys_mem , max_sys_increase = self .get_data ()
394
+ msg = (f"Max rss memory cost { comment } : { max_rss_mem :.2f} { self .memory_unit .value } , "
395
+ f"rss memory increase { comment } : { max_rss_increase :.2f} { self .memory_unit .value } , "
396
+ f"max system memory cost { comment } : { max_sys_mem :.2f} { self .memory_unit .value } , "
397
+ f"system memory increase { comment } : { max_sys_increase :.2f} { self .memory_unit .value } " )
398
+ log .info (msg )
399
+
400
+
327
401
def _cast_bytes_to (bytes , memory_unit , round_to_int = False ):
328
402
memory_unit_divisors = {
329
403
MemoryUnit .B : 1 ,
@@ -343,44 +417,3 @@ def _subtract_first_element(data):
343
417
data [i ] = data [i ] - data [0 ]
344
418
data [0 ] = 0
345
419
return data
346
-
347
-
348
- if __name__ == "__main__" :
349
- parser = argparse .ArgumentParser (
350
- description = "Memory Monitor Tool. Monitors memory for an executable and saves logs at specified location." ,
351
- epilog = "Examples:\n "
352
- " python memory_monitor.py --log-dir ./allocation_logs python allocate.py\n "
353
- " python memory_monitor.py optimum-cli export openvino ..." ,
354
- formatter_class = argparse .RawTextHelpFormatter ,
355
- )
356
- parser .add_argument (
357
- "--log-dir" , type = str , default = "memory_logs" , help = "A directory to save logs at. './memory_logs' by default."
358
- )
359
- parser .add_argument ("executable" , type = str , nargs = "+" , help = "Target executable to monitor memory for." )
360
- args = parser .parse_args ()
361
-
362
- memory_monitors = [
363
- MemoryMonitor (memory_type = mt , include_child_processes = True ).start ()
364
- for mt in (MemoryType .RSS , MemoryType .SYSTEM )
365
- ]
366
-
367
- with subprocess .Popen (" " .join (args .executable ), shell = True ) as p :
368
- p .wait ()
369
-
370
- # Stop addition of new values as soon as possible
371
- for mm in memory_monitors :
372
- mm ._monitoring_thread_should_stop = True
373
-
374
- summary_data = []
375
- for mm in memory_monitors :
376
- mm .stop ()
377
- for fz in (True , False ):
378
- time_values , memory_values = mm .get_data (memory_from_zero = fz )
379
- # Most probably the last value is recorded once the child process has already died
380
- time_values , memory_values = time_values [:- 1 ], memory_values [:- 1 ]
381
- mm .save_memory_logs (
382
- time_values , memory_values , save_dir = Path (args .log_dir ), filename_suffix = "_from-zero" if fz else ""
383
- )
384
- summary_data .append ([mm .memory_type .value , fz , f"{ int (max (memory_values ))} { mm .memory_unit .value } " ])
385
- print ("\n Memory summary:" )
386
- print (tabulate (summary_data , headers = ["Memory type" , "From zero" , "Peak value" ]))
0 commit comments