DataViews/PyProfileDialog.py

changeset 0
de9c2efb9d02
child 12
1d8dd9706f46
equal deleted inserted replaced
-1:000000000000 0:de9c2efb9d02
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2003 - 2009 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a dialog to display profile data.
8 """
9
10 import sys
11 import os
12 import pickle
13
14 from PyQt4.QtCore import *
15 from PyQt4.QtGui import *
16
17 from Ui_PyProfileDialog import Ui_PyProfileDialog
18 import Utilities
19
20 from eric4config import getConfig
21
22 class ProfileTreeWidgetItem(QTreeWidgetItem):
23 """
24 Class implementing a custom QTreeWidgetItem to allow sorting on numeric values.
25 """
26 def __getNC(self, itm):
27 """
28 Private method to get the value to compare on for the first column.
29
30 @param itm item to operate on (ProfileTreeWidgetItem)
31 """
32 s = itm.text(0)
33 return int(s.split('/')[0])
34
35 def __lt__(self, other):
36 """
37 Public method to check, if the item is less than the other one.
38
39 @param other reference to item to compare against (ProfileTreeWidgetItem)
40 @return true, if this item is less than other (boolean)
41 """
42 column = self.treeWidget().sortColumn()
43 if column == 0:
44 return self.__getNC(self) < self.__getNC(other)
45 if column == 6:
46 return int(self.text(column)) < int(other.text(column))
47 return self.text(column) < other.text(column)
48
49 class PyProfileDialog(QDialog, Ui_PyProfileDialog):
50 """
51 Class implementing a dialog to display the results of a syntax check run.
52 """
53 def __init__(self, parent = None):
54 """
55 Constructor
56
57 @param parent parent widget (QWidget)
58 """
59 QDialog.__init__(self, parent)
60 self.setupUi(self)
61
62 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
63 self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
64
65 self.cancelled = False
66 self.exclude = True
67 self.ericpath = getConfig('ericDir')
68 self.pyLibPath = Utilities.getPythonLibPath()
69
70 self.summaryList.headerItem().setText(self.summaryList.columnCount(), "")
71 self.resultList.headerItem().setText(self.resultList.columnCount(), "")
72 self.resultList.header().setSortIndicator(0, Qt.DescendingOrder)
73
74 self.__menu = QMenu(self)
75 self.filterItm = self.__menu.addAction(self.trUtf8('Exclude Python Library'),
76 self.__filter)
77 self.__menu.addSeparator()
78 self.__menu.addAction(self.trUtf8('Erase Profiling Info'),
79 self.__eraseProfile)
80 self.__menu.addAction(self.trUtf8('Erase Timing Info'), self.__eraseTiming)
81 self.__menu.addSeparator()
82 self.__menu.addAction(self.trUtf8('Erase All Infos'), self.__eraseAll)
83 self.resultList.setContextMenuPolicy(Qt.CustomContextMenu)
84 self.connect(self.resultList,
85 SIGNAL('customContextMenuRequested(const QPoint &)'),
86 self.__showContextMenu)
87 self.summaryList.setContextMenuPolicy(Qt.CustomContextMenu)
88 self.connect(self.summaryList,
89 SIGNAL('customContextMenuRequested(const QPoint &)'),
90 self.__showContextMenu)
91
92 def __createResultItem(self, calls, totalTime, totalTimePerCall, cumulativeTime,
93 cumulativeTimePerCall, file, line, functionName):
94 """
95 Private method to create an entry in the result list.
96
97 @param calls number of calls (integer)
98 @param totalTime total time (double)
99 @param totalTimePerCall total time per call (double)
100 @param cumulativeTime cumulative time (double)
101 @param cumulativeTimePerCall cumulative time per call (double)
102 @param file filename of file (string)
103 @param line linenumber (integer)
104 @param functionName function name (string)
105 """
106 itm = ProfileTreeWidgetItem(self.resultList, [
107 calls,
108 "% 8.3f" % totalTime,
109 totalTimePerCall,
110 "% 8.3f" % cumulativeTime,
111 cumulativeTimePerCall,
112 file,
113 str(line),
114 functionName
115 ])
116 for col in [0, 1, 2, 3, 4, 6]:
117 itm.setTextAlignment(col, Qt.AlignRight)
118
119 def __createSummaryItem(self, label, contents):
120 """
121 Private method to create an entry in the summary list.
122
123 @param label text of the first column (string)
124 @param contents text of the second column (string)
125 """
126 itm = QTreeWidgetItem(self.summaryList, [label, contents])
127 itm.setTextAlignment(1, Qt.AlignRight)
128
129 def __resortResultList(self):
130 """
131 Private method to resort the tree.
132 """
133 self.resultList.sortItems(self.resultList.sortColumn(),
134 self.resultList.header().sortIndicatorOrder())
135
136 def __populateLists(self, exclude = False):
137 """
138 Private method used to populate the listviews.
139
140 @param exclude flag indicating whether files residing in the
141 Python library should be excluded
142 """
143 self.resultList.clear()
144 self.summaryList.clear()
145
146 self.checkProgress.setMaximum(len(self.stats))
147 QApplication.processEvents()
148
149 progress = 0
150 total_calls = 0
151 prim_calls = 0
152 total_tt = 0
153
154 try:
155 # disable updates of the list for speed
156 self.resultList.setUpdatesEnabled(False)
157 self.resultList.setSortingEnabled(False)
158
159 # now go through all the files
160 for func, (cc, nc, tt, ct, callers) in self.stats.items():
161 if self.cancelled:
162 return
163
164 if not (self.ericpath and func[0].startswith(self.ericpath)) and \
165 not (exclude and func[0].startswith(self.pyLibPath)):
166 if self.file is None or func[0].startswith(self.file) or \
167 func[0].startswith(self.pyLibPath):
168 # calculate the totals
169 total_calls += nc
170 prim_calls += cc
171 total_tt += tt
172
173 if nc != cc:
174 c = "%d/%d" % (nc, cc)
175 else:
176 c = str(nc)
177 if nc == 0:
178 tpc = "% 8.3f" % 0.0
179 else:
180 tpc = "% 8.3f" % (tt/nc,)
181 if cc == 0:
182 cpc = "% 8.3f" % 0.0
183 else:
184 cpc = "% 8.3f" % (ct/cc,)
185 self.__createResultItem(c, tt, tpc, ct, cpc, func[0],
186 func[1], func[2])
187
188 progress += 1
189 self.checkProgress.setValue(progress)
190 QApplication.processEvents()
191 finally:
192 # reenable updates of the list
193 self.resultList.setSortingEnabled(True)
194 self.resultList.setUpdatesEnabled(True)
195 self.__resortResultList()
196
197 # now do the summary stuff
198 self.__createSummaryItem(self.trUtf8("function calls"), str(total_calls))
199 if total_calls != prim_calls:
200 self.__createSummaryItem(self.trUtf8("primitive calls"), str(prim_calls))
201 self.__createSummaryItem(self.trUtf8("CPU seconds"), "%.3f" % total_tt)
202
203 def start(self, pfn, fn=None):
204 """
205 Public slot to start the calculation of the profile data.
206
207 @param pfn basename of the profiling file (string)
208 @param fn file to display the profiling data for (string)
209 """
210 self.basename = os.path.splitext(pfn)[0]
211
212 fname = "%s.profile" % self.basename
213 if not os.path.exists(fname):
214 QMessageBox.warning(None,
215 self.trUtf8("Profile Results"),
216 self.trUtf8("""<p>There is no profiling data"""
217 """ available for <b>{0}</b>.</p>""")
218 .format(pfn))
219 self.close()
220 return
221 try:
222 f = open(fname, 'rb')
223 self.stats = pickle.load(f)
224 f.close()
225 except (EnvironmentError, pickle.PickleError):
226 QMessageBox.critical(None,
227 self.trUtf8("Loading Profiling Data"),
228 self.trUtf8("""<p>The profiling data could not be"""
229 """ read from file <b>{0}</b>.</p>""")
230 .format(fname))
231 self.close()
232 return
233
234 self.file = fn
235 self.__populateLists()
236 self.__finish()
237
238 def __finish(self):
239 """
240 Private slot called when the action finished or the user pressed the button.
241 """
242 self.cancelled = True
243 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True)
244 self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False)
245 self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
246 QApplication.processEvents()
247 self.resultList.header().resizeSections(QHeaderView.ResizeToContents)
248 self.resultList.header().setStretchLastSection(True)
249 self.summaryList.header().resizeSections(QHeaderView.ResizeToContents)
250 self.summaryList.header().setStretchLastSection(True)
251
252 def __unfinish(self):
253 """
254 Private slot called to revert the effects of the __finish slot.
255 """
256 self.cancelled = False
257 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
258 self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(True)
259 self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
260
261 def on_buttonBox_clicked(self, button):
262 """
263 Private slot called by a button of the button box clicked.
264
265 @param button button that was clicked (QAbstractButton)
266 """
267 if button == self.buttonBox.button(QDialogButtonBox.Close):
268 self.close()
269 elif button == self.buttonBox.button(QDialogButtonBox.Cancel):
270 self.__finish()
271
272 def __showContextMenu(self, coord):
273 """
274 Private slot to show the context menu of the listview.
275
276 @param coord the position of the mouse pointer (QPoint)
277 """
278 self.__menu.popup(self.mapToGlobal(coord))
279
280 def __eraseProfile(self):
281 """
282 Private slot to handle the Erase Profile context menu action.
283 """
284 fname = "%s.profile" % self.basename
285 if os.path.exists(fname):
286 os.remove(fname)
287
288 def __eraseTiming(self):
289 """
290 Private slot to handle the Erase Timing context menu action.
291 """
292 fname = "%s.timings" % self.basename
293 if os.path.exists(fname):
294 os.remove(fname)
295
296 def __eraseAll(self):
297 """
298 Private slot to handle the Erase All context menu action.
299 """
300 self.__eraseProfile()
301 self.__eraseTiming()
302
303 def __filter(self):
304 """
305 Private slot to handle the Exclude/Include Python Library context menu action.
306 """
307 self.__unfinish()
308 if self.exclude:
309 self.exclude = False
310 self.filterItm.setText(self.trUtf8('Include Python Library'))
311 self.__populateLists(True)
312 else:
313 self.exclude = True
314 self.filterItm.setText(self.trUtf8('Exclude Python Library'))
315 self.__populateLists(False)
316 self.__finish()

eric ide

mercurial