From 67484d2fbb98826c29a1231fbf2ccb75edd87bd0 Mon Sep 17 00:00:00 2001 From: lcapocchi Date: Mon, 9 Mar 2020 09:52:16 -0400 Subject: [PATCH] Factoring of Profiling function --- Decorators.py | 34 +++++++++++++----------- Menu.py | 38 ++++++++++++++++++-------- SimulationGUI.py | 7 +---- SimulationNoGUI.py | 2 +- Utilities.py | 22 ++++++++++++++++ devsimpy.py | 66 ++++++++++++---------------------------------- 6 files changed, 86 insertions(+), 83 deletions(-) diff --git a/Decorators.py b/Decorators.py index fedae654..908fc4cb 100644 --- a/Decorators.py +++ b/Decorators.py @@ -5,12 +5,13 @@ import os import sys import time +from datetime import datetime import threading from tempfile import gettempdir import time import heapq import pickle -import io +import cProfile, pstats, io if builtins.__dict__['GUI_FLAG']: import wx @@ -39,29 +40,30 @@ def __call__(self, *args): self.memoized[args] = self.function(*args) return self.memoized[args] -hotshotProfilers = {} def hotshotit(func): def wrapper(*args, **kw): sim_thread = args[0] prof = sim_thread.prof ### if profiling check-box is checked in the simulationDialog if prof: + + ### name of .prof file label = sim_thread.model.getBlockModel().label + now = datetime.now() # current date and time + date_time = now.strftime('%m-%d-%Y_%H-%M-%S') + prof_name = os.path.join(gettempdir(),"%s_%s_%s%s"%(func.__name__, label, date_time ,'.prof')) + + ### profiling section with cProfile + pr = cProfile.Profile() + pr.enable() + r = func(*args, **kw) + pr.disable() + #Sort the statistics by the cumulative time spent in the function + sortby = 'cumulative' + ps = pstats.Stats(pr).sort_stats(sortby) + ps.dump_stats(prof_name) + #print(s.getvalue()) - try: - import hotshot - #import cProfile as hotshot - except ImportError: - sys.stderr.write(_("Please install hotshot module.")) - return - - global hotshotProfilers - prof_name = os.path.join(gettempdir(),"%s_%s%s"%(func.__name__, label, '.prof')) - profiler = hotshotProfilers.get(prof_name) - if profiler is None: - profiler = hotshot.Profile(prof_name) - hotshotProfilers[prof_name] = profiler - r = profiler.runcall(func, *args, **kw) else: r = func(*args, **kw) return r diff --git a/Menu.py b/Menu.py index 19419231..dc12f55d 100644 --- a/Menu.py +++ b/Menu.py @@ -24,6 +24,7 @@ import os import sys import platform +import profile from tempfile import gettempdir @@ -251,8 +252,10 @@ def __init__(self, parent): parent.Bind(wx.EVT_MENU, parent.OnProfiling, id=id) self.AppendSeparator() + AppendItem(wx.MenuItem(self, ID_DELETE_PROFILES, _("Delete all"))) self.Enable(ID_DELETE_PROFILES, self.GetMenuItemCount() > 2) + parent.Bind(wx.EVT_MENU, parent.OnDeleteProfiles, id = ID_DELETE_PROFILES) class RecentFileMenu(wx.Menu): @@ -449,10 +452,8 @@ def __init__(self, parent): AppendMenu(self, wx.NewIdRef(), _('Languages'), languagesSubmenu) ### Before Phoenix transition - ishotshot = 'hotshot' in list(sys.modules.keys()) - if ishotshot: - AppendMenu(self, ID_PROFILE, _('Profile'), ProfileFileMenu(parent)) - + AppendMenu(self, ID_PROFILE, _('Profile'), ProfileFileMenu(parent)) + parent = parent.GetParent() AppendItem(pref_item) @@ -463,7 +464,7 @@ def __init__(self, parent): parent.Bind(wx.EVT_MENU, parent.OnFrench, id=ID_FRENCH_LANGUAGE) parent.Bind(wx.EVT_MENU, parent.OnEnglish, id=ID_ENGLISH_LANGUAGE) parent.Bind(wx.EVT_MENU, parent.OnAdvancedSettings, id=ID_PREFERENCES) - + class HelpMenu(wx.Menu): """ """ @@ -509,7 +510,7 @@ def __init__(self, parent): self.Append(SettingsMenu(self), _("&Options")) self.Append(HelpMenu(self), _("&Help")) - self.Bind(wx.EVT_MENU_HIGHLIGHT_ALL, self.OnMenuHighlight) + self.Bind(wx.EVT_MENU_HIGHLIGHT, self.OnMenuHighlight) ### useless until Phoenix transition def OnOpenMenu(self, event): @@ -520,6 +521,8 @@ def OnOpenMenu(self, event): menu = event.GetMenu() + posm = self.FindMenu(menu.GetTitle()) + ### if the opened menu is the File menu if isinstance(menu, FileMenu): @@ -533,9 +536,15 @@ def OnOpenMenu(self, event): else: if platform.system() == 'Windows': ### After Pnoenix Transition - self.Replace(0, FileMenu(self), _("&File")) + self.Replace(posm, FileMenu(self), _("&File")) + else: + label = _("Recent files") + ID = menu.FindItem(label) + item, pos = menu.FindChildItem(ID) + menu.Remove(ID) + menu.Insert(pos, ID, label, RecentFileMenu(self)) - elif isinstance(menu, SettingsMenu) and 'hotshot' in list(sys.modules.keys()): + elif isinstance(menu, SettingsMenu): if wx.VERSION_STRING < '4.0': ### Before Pnoenix Transition @@ -545,10 +554,17 @@ def OnOpenMenu(self, event): ### we insert the profile files menu menu.InsertMenu(1, ID_PROFILE, _('Profile'), ProfileFileMenu(self)) else: + ### After Pnoenix Transition if platform.system() == 'Windows': - ### After Pnoenix Transition - self.Replace(4, SettingsMenu(self), _("&Options")) - + self.Replace(posm, SettingsMenu(self), _("&Options")) + else: + label = _('Profile') + ID = menu.FindItem(label) + item, pos = menu.FindChildItem(ID) + menu.Remove(ID) + menu.Insert(pos, ID, label, ProfileFileMenu(self)) + + #def OnCloseMenu(self, event): #""" Close menu has been detected #""" diff --git a/SimulationGUI.py b/SimulationGUI.py index 470468bd..d07f6941 100644 --- a/SimulationGUI.py +++ b/SimulationGUI.py @@ -178,11 +178,6 @@ def MakePaneContent(self, pane): cb5 = wx.CheckBox(pane, wx.NewIdRef(), name='real_time') if DEFAULT_DEVS_DIRNAME == 'PyDEVS': - if not 'hotshot' in list(sys.modules.keys()): - text3.Enable(False) - cb1.Enable(False) - self.parent.prof = False - self.cb2.SetValue(builtins.__dict__['NTL']) self.cb3.Enable(False) cb4.Enable(False) @@ -327,7 +322,7 @@ def __init__(self, parent, id, title, master): ### PyPDEVS threaded real time simulation self.real_time_flag = builtins.__dict__['REAL_TIME'] - ### profiling simulation with hotshot + ### profiling simulation self.prof = False ### No time limit simulation (defined in the builtin dictionary from .devsimpy file) diff --git a/SimulationNoGUI.py b/SimulationNoGUI.py index dc9d97c5..5204b224 100644 --- a/SimulationNoGUI.py +++ b/SimulationNoGUI.py @@ -196,7 +196,7 @@ def __init__(self, master, time): self.dynamic_structure_flag = builtins.__dict__['DYNAMIC_STRUCTURE'] self.real_time_flag = builtins.__dict__['REAL_TIME'] - ### profiling simulation with hotshot + ### profiling simulation self.prof = False self.verbose = False diff --git a/Utilities.py b/Utilities.py index 0bea34a0..d5f08f9d 100644 --- a/Utilities.py +++ b/Utilities.py @@ -59,6 +59,9 @@ sys.stdout.write("Unknown operating system.\n") sys.exit() +import pip +import importlib + #------------------------------------------------------------------------------- def PrintException(): exc_type, exc_obj, tb = sys.exc_info() @@ -123,6 +126,25 @@ def PyBuzyInfo(msg, time): del busy +def install_and_import(package): + try: + importlib.import_module(package) + installed = True + except: + dial = wx.MessageDialog(None, _('Do you want to install the %s using pip?'%package), _('Install Package'), wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION) + + if dial.ShowModal() == wx.ID_YES: + pip.main(['install', package]) + installed = True + dial.Destroy() + else: + installed = False + dial.Destroy() + finally: + if installed : globals()[package] = importlib.import_module(package) + + return installed + def getObjectFromString(scriptlet): """ """ diff --git a/devsimpy.py b/devsimpy.py index ddeb8b6c..9c4be3a2 100644 --- a/devsimpy.py +++ b/devsimpy.py @@ -51,6 +51,7 @@ import shutil import builtins import glob +import pstats from configparser import ConfigParser from tempfile import gettempdir @@ -184,7 +185,7 @@ from PreferencesGUI import PreferencesGUI from pluginmanager import load_plugins, enable_plugin from which import which -from Utilities import GetUserConfigDir +from Utilities import GetUserConfigDir, install_and_import from Decorators import redirectStdout, BuzyCursorNotification from DetachedFrame import DetachedFrame from LibraryTree import LibraryTree @@ -1888,54 +1889,29 @@ def OnAdvancedSettings(self, event): ### @BuzyCursorNotification def OnProfiling(self, event): - """ Simulation profiling for fn file + """ Simulation profiling for fn file. """ ### find the prof file name menu_item = self.GetMenuBar().FindItemById(event.GetId()) - fn = menu_item.GetLabel() + fn = menu_item.GetItemLabelText() prof_file_path = os.path.join(gettempdir(), fn) ### list of item in single choice dialogue - choices = [_('Embedded in DEVSimPy')] - - ### editor of profiling software - try: - kcachegrind = which('kcachegrind') - choices.append('kcachegrind') - except Exception: - kcachegrind = False - try: - kprof = which('kprof') - choices.append('kprof') - except Exception: - kprof = False - try: - converter = which('hotshot2calltree') - except Exception: - converter = False - - choices.append(_('Other...')) + choices = ['snakeviz','gprof2dot',_('Embedded in DEVSimPy')] dlg = wx.SingleChoiceDialog(self, _('What profiling software are you using?'), _('Single Choice'), choices) if dlg.ShowModal() == wx.ID_OK: response = dlg.GetStringSelection() - if response == 'kcachegrind': + if response == 'snakeviz': dlg.Destroy() - - if converter: - ### cache grind file name that will be generated - cachegrind_fn = os.path.join(gettempdir(), "%s%s"%(fn[:-len('.prof')],'.cachegrind')) - ### transform profile file for cachegrind - os.system("%s %s %s %s"%(converter,"-o", cachegrind_fn, prof_file_path)) - - self.LoadCachegrindFile(cachegrind_fn) - else: - wx.MessageBox(_("Hotshot converter (hotshot2calltree) not found"), _('Error'), wx.OK|wx.ICON_ERROR) - - elif response == 'kprof': + r = install_and_import(response) + if r: os.system(" ".join([response,prof_file_path,"&"])) + elif response == 'gprof2dot': dlg.Destroy() - self.LoadProfFileFromKProf(prof_file_path) + r = install_and_import(response) + png_file_path = prof_file_path.replace('.prof', '.png') + os.system(" ".join([response,'-f pstats',prof_file_path,"|", "dot", "-Tpng", "-o", png_file_path, "&&", "eog", png_file_path])) elif response == _('Embedded in DEVSimPy'): dlg.Destroy() output = self.LoadProfFile(prof_file_path) @@ -1945,24 +1921,16 @@ def OnProfiling(self, event): else: dlg.Destroy() - @staticmethod - def LoadCachegrindFile(cachegrind_fn): - ### lauch kcachegrid - os.system(" ".join(['kcachegrind',cachegrind_fn,"&"])) - - @staticmethod - def LoadProfFileFromKProf(prof_file_path): - ### lauch kprof - os.system(" ".join(['kprof',prof_file_path,"&"])) - @staticmethod @redirectStdout def LoadProfFile(prof_file_path): ### lauch embedded prof editor - stats = hotshot.stats.load(prof_file_path) + stats = pstats.Stats(prof_file_path) + # Clean up filenames for the report stats.strip_dirs() - stats.sort_stats('time', 'calls') - stats.print_stats(100) + # Sort the statistics by the cumulative time spent in the function + stats.sort_stats('cumulative') + stats.print_stats() ### def OnDeleteProfiles(self, event):