scripted/profilerDump.py

scripted/profilerDump.py
1 import maya.OpenMaya as om
2 import json
3 import csv
4 
5 __all__ = [ 'profilerToJSON', 'profilerToCSV', 'profilerFormatJSON']
6 
7 #
8 # The following is sample code which uses the OpenMaya API to show how
9 # data from Maya's profiler can be output to file on disk in a custom text format.
10 #
11 # Note that references to Maya's profiler output format are valid at the time of writing.
12 #
13 # The format's chosen to illustrate this are JSON and CSV. For each sample output is provided.
14 
15 # Sample 1: Profiler to JSON output
16 # ---------------------------------
17 #
18 def profilerToJSON(fileName, useIndex, durationMin):
19  """
20  fileName : name of file to write to disk
21  useIndex : write events using index lookup to category and name lists
22  durationMin : only write out events which have at least this minimum time duration
23 
24  Description:
25  Sample code to extract profiler information and write to file in JSON format
26 
27  Example usage:
28  > profilerToJSON('profiler_indexed.json', True, 0.0) # Index without a duration clamp
29  > profilerToJSON('profiler_nonIndexed.json', False, 10.0) # Non-Indexed with duration clamp
30  """
31  # Below is some sample output from after running the JSON export. Note that here we try to mimic
32  # the data and ordering of Maya's internal profiler save by storing category and event name lists
33  # and specifying lookups for each event. e.g. the first event stores a name index (nameIdx) of 59
34  # which would reference element 59 in "eventNames".
35  #
36  # Note that the total available events and the number of events actually written out is included.
37  #
38  # {
39  # "version": 1,
40  # "eventCount": 11276,
41  # "cpuCount": 8,
42  # "categories": ["Main", "Change", "Dirty Propagation", "Evaluation", "VP1 Evaluation", "Poly", "Mudbox", "VP2 Evaluation", "Qt", "Render Setup", "Anim UI Refresh"],
43  # "eventNames": ["QtDynamicPropertyChange", "PreRefreshTopLevel", "QtCreate", "Vp2ShaderDoDG", "QtShortcutOverride", "Vp2SceneRender", "Vp2ShaderItemCompileShader", "QtShowToParent", "QtMove", "Vp2AssignShaderInstance", "opaqueUIList", "Vp2ParallelEvaluationTask", "QtResize", "colorPass", "pConeShape1", "QtWindowDeactivate", "QtZeroTimerEvent", "QtTimer", "QtNonClientAreaMouseButtonPress", "QtMouseButtonRelease", "QtChildRemoved", "QtApplicationDeactivate", "Vp2ShaderWrite", "QtChildAdded", "QtWindowActivate", "Vp2PrepareToUpdate", "QtMetaCall", "QtActivationChange", "QtUpdateLater", "QtFocusIn", "QtShow", "QtUserEvent", "QtWindowStateChange", "QtModifiedChange", "Vp2UpdateGeometryBuffer", "setDirty", "Vp2AcquireOffScreenTarget", "QtKeyRelease", "uiGeometry", "Vp2UpdateUI", "QtUpdateRequest", "QtToolTipChange", "QtApplicationPaletteChange", "QtDragMove", "preUIGeometry", "Vp2WaitForEvaluation", "QtKeyPress", "QtNonClientAreaMouseButtonRelease", "QtLeave", "opaqueGeometry", "initialShadingGroup", "QtDrop", "Vp2BuildRenderLists", "Vp2UpdateGeometry", "Compute", "Vp2ClearRenderLists", "Vp2UpdateScene", "postUIGeometry", "QtMouseButtonPress", "QtIdleTimer", "QtDragEnter", "QtDestroy", "QtPaint", "Vp2BuildShadowMaps", "Vp2UpdateDagObject", "QtEnter", "pPyramidShape1", "QtMouseMove", "Vp2TranslateGeometry", "QtWindowTitleChange", "QtZOrderChange", "ScheduleRefreshAllViews", "QtDeferredDelete", "QtPaletteChange", "QtNonClientAreaMouseMove", "shadedBeautyGraphSemantic", "QtApplicationActivate", "QtCursorChange", "Vp2WaitForTranslation", "sendAttributeChangedMsg", "QtLayoutRequest", "QtStatusTip", "QtDragLeave", "QtFocusOut", "Vp2Draw3dBeautyPass", "Vp2HUD", "Vp2ConstructFragmentGraph", "QtHide"],
44  # "events": [{
45  # "time": 25610841341,
46  # "nameIdx": 59,
47  # "desc": "",
48  # "catIdx": 8,
49  # "duration": 39014,
50  # "tDuration": 39724089,
51  # "tId": 6972,
52  # "cpuId": 4,
53  # "colorId": 6
54  # }, {
55  # "time": 25620057925,
56  # "nameIdx": 79,
57  # "desc": "",
58  # "catIdx": 1,
59  # "duration": 228,
60  # "tDuration": 212774,
61  # "tId": 6972,
62  # "cpuId": 0,
63  # "colorId": 12
64  # }
65  # .... more events ...
66  # ]
67  # "eventsWritten": 364
68  # }
69  #
70  # Here is the same data without using indexing.
71  # {
72  # "cpuCount": 8,
73  # "eventCount": 11276,
74  # "events": [
75  # {
76  # "category": "Qt",
77  # "colorId": 6,
78  # "cpuId": 4,
79  # "desc": "",
80  # "duration": 39014,
81  # "name": "QtIdleTimer",
82  # "tDuration": 39724089,
83  # "tId": 6972,
84  # "time": 25610841341
85  # },
86  # {
87  # "category": "Change",
88  # "colorId": 12,
89  # "cpuId": 0,
90  # "desc": "",
91  # "duration": 228,
92  # "name": "sendAttributeChangedMsg",
93  # "tDuration": 212774,
94  # "tId": 6972,
95  # "time": 25620057925
96  # }
97  # .... more events ...
98  # ]
99  # "eventsWritten": 364
100  #
101  stripped = lambda s: "".join(i for i in s if 31 < ord(i) < 127)
102 
103  eventCount = om.MProfiler.getEventCount()
104  if eventCount == 0:
105  return
106 
107  file = open(fileName, "w")
108  if not file:
109  return
110 
111 
112  file.write("{\n")
113 
114  # Output version
115  file.write("\t\"version\": 1,\n")
116  # Output event count
117  file.write("\t\"eventCount\": " + str(eventCount) + ",\n")
118  # Output number of CPUS. Missing from Python API
119  file.write("\t\"cpuCount\": " + str(om.MProfiler.getNumberOfCPUs()) + ",\n")
120 
121  # Output event categories if using indexing lookup
122  categories = []
123  om.MProfiler.getAllCategories(categories)
124  asciiString = json.dumps(categories, True, True)
125  if useIndex:
126  file.write("\t\"categories\": " + asciiString + ",\n")
127 
128  # Output event name list if using indexing
129  nameDict = {}
130  for i in range(0, eventCount, 1):
131  eventName = om.MProfiler.getEventName(i)
132  eventName = eventName.decode('ascii', 'replace')
133  eventName = stripped(eventName)
134  if eventName not in nameDict:
135  nameDict[eventName] = len(nameDict)
136  if useIndex:
137  nameString = json.dumps(nameDict.keys(), True, True)
138  file.write('\t\"eventNames\" : ' + nameString + ",\n")
139 
140  # Write out each event:
141  # Event time, Event Name / Event Index, Description , Category / Category index, Duration, Thread Duration, Thread id, Cpu id, Color id
142  file.write('\t\"events\": [\n')
143  dumped = False
144  eventsWritten = 0
145  for i in range(0, eventCount):
146 
147  duration = om.MProfiler.getEventDuration(i)
148  if duration > durationMin:
149  eventsWritten = eventsWritten + 1
150 
151  eventTime = om.MProfiler.getEventTime(i)
152  eventName = om.MProfiler.getEventName(i)
153  eventName = eventName.decode('ascii', 'replace')
154  eventName = stripped(eventName)
155  if useIndex:
156  eventNameIndex = nameDict.keys().index(eventName)
157 
158  description = ''
159  if om.MProfiler.getDescription(i):
160  description = om.MProfiler.getDescription(i)
161 
162  eventCategory = om.MProfiler.getEventCategory(i)
163  eventCategoryName = om.MProfiler.getCategoryName(eventCategory)
164  if useIndex:
165  eventCatagoryIndex = categories.index(eventCategoryName)
166 
167  threadDuration = om.MProfiler.getThreadDuration(i)
168 
169  threadId = om.MProfiler.getThreadId(i)
170 
171  cpuId = om.MProfiler.getCPUId(i)
172 
173  colorId = om.MProfiler.getColor(i)
174 
175  # Instead of using json library, the code just writes on the fly
176  if dumped:
177  file.write('\t,{ ')
178  else:
179  file.write('\t{ ')
180  dumped = True
181  file.write('\"time\" : ' + str(eventTime) + ', ')
182  if useIndex:
183  file.write('\"nameIdx\" : ' + str(eventNameIndex) + ', ')
184  else:
185  file.write('\"name\" : \"' + eventName + '\", ')
186  file.write('\"desc\" : \"' + str(description) + '\", ')
187  if useIndex:
188  file.write('\"catIdx\" : ' + str(eventCatagoryIndex) + ', ')
189  else:
190  file.write('\"category\" : \"' + eventCategoryName + '\", ')
191  file.write('\"duration\" : ' + str(duration) + ', ')
192  file.write('\"tDuration\" : ' + str(threadDuration) + ', ')
193  file.write('\"tId\" : ' + str(threadId) + ', ')
194  file.write('\"cpuId\" : ' + str(cpuId) + ', ')
195  file.write('\"colorId\" : ' + str(colorId) + '')
196  file.write('\t}\n')
197 
198  file.write("\t],\n")
199  file.write("\t\"eventsWritten\": " + str(eventsWritten) + "\n")
200  file.write("}\n")
201  file.close()
202 
203 def profilerFormatJSON(fileName, fileName2):
204  """
205  fileName : name of file to read
206  fileName2 : name of file to write to
207 
208  Description:
209  Simple utility code to read a JSON file sort and format it before
210  writing to a secondary file.
211 
212  Example:
213  > profilerFormatJSON('profilerIn.json', 'profilerFormatted.json')
214 
215  """
216  file = open(fileName, "r")
217  if not file:
218  return
219 
220  result = json.load(file)
221  file.close()
222 
223  dump = json.dumps(result, sort_keys=True, indent=4)
224 
225  file2 = open(fileName2, "w")
226  if not file2:
227  return
228 
229  file2.write(dump)
230  file2.close()
231 
232 
233 
234 
235 # Sample 1: Profiler to CSV output
236 # ---------------------------------
237 #
238 def profilerToCSV(fileName, durationMin):
239  """
240  fileName : name of file to write to disk
241  useIndex : write events using index lookup to category and name lists
242  durationMin : only write out events which have at least this minimum time duration
243 
244  Description:
245  Sample to output profiler event information only to CSV format.
246  Example:
247  > profilerToCSV('profiler.csv', 0.0)
248  """
249  #
250  # Sample output:
251  #
252  # Event Time","Event Name","Description","Event Category","Duration","Thread Duration","Thread Id","CPU Id","Color Id"
253  # 25610841341,"QtIdleTimer","","Qt",39014,39724089,6972,4,6
254  # 25620057925,"sendAttributeChangedMsg","","Change",228,212774,6972,0,12
255  # 25620058186,"setDirty","","Dirty Propagation",8,7806,6972,0,1
256  # 25620058276,"sendAttributeChangedMsg","","Change",11,10633,6972,0,12
257  # 25620058310,"sendAttributeChangedMsg","","Change",8,7732,6972,0,12
258  # 25620058332,"sendAttributeChangedMsg","","Change",7,6844,6972,0,12
259  # ... <more events> ...
260  #
261  stripped = lambda s: "".join(i for i in s if 31 < ord(i) < 127)
262 
263  eventCount = om.MProfiler.getEventCount()
264  if eventCount == 0:
265  return
266 
267  file = open(fileName, "w")
268  if not file:
269  return
270 
271  csvWriter = csv.writer(file, quoting=csv.QUOTE_NONNUMERIC)
272 
273  # Write out each event:
274  # Event time, Event Name / Event Index, Description , Category / Category index, Duration, Thread Duration, Thread id, Cpu id, Color id
275 
276  head = ( 'Event Time', 'Event Name', 'Description', 'Event Category', 'Duration', 'Thread Duration', 'Thread Id', 'CPU Id', 'Color Id' )
277  csvWriter.writerow(head)
278 
279  for i in range(0, eventCount):
280 
281  duration = om.MProfiler.getEventDuration(i)
282  if duration > durationMin:
283 
284  eventTime = om.MProfiler.getEventTime(i)
285  eventName = om.MProfiler.getEventName(i)
286  eventName = eventName.decode('ascii', 'replace')
287  eventName = stripped(eventName)
288 
289  description = ''
290  if om.MProfiler.getDescription(i):
291  description = om.MProfiler.getDescription(i)
292 
293  eventCategory = om.MProfiler.getEventCategory(i)
294  eventCategoryName = om.MProfiler.getCategoryName(eventCategory)
295 
296  threadDuration = om.MProfiler.getThreadDuration(i)
297 
298  threadId = om.MProfiler.getThreadId(i)
299 
300  cpuId = om.MProfiler.getCPUId(i)
301 
302  colorId = om.MProfiler.getColor(i)
303 
304  row = ( eventTime, eventName, description, eventCategoryName, duration, threadDuration, threadId, cpuId, colorId )
305 
306  csvWriter.writerow(row)
307 
308  file.close()
309 
310 
311 # Nothing run on initialize for now
312 def initializePlugin(obj):
313  obj
314 
315 def uninitializePlugin(obj):
316  obj