source: trunk/luaui/widgets/dbg_widgetprofiler.lua @ 5877

Revision 3512, 11.8 KB checked in by Bluestone, 5 years ago (diff)

show widget profiler at top of widget list

Line 
1--------------------------------------------------------------------------------
2--------------------------------------------------------------------------------
3
4function widget:GetInfo()
5  return {
6    name      = "Widget Profiler",
7    desc      = "",
8    author    = "jK",
9    version   = "2.0",
10    date      = "2007,2008,2009",
11    license   = "GNU GPL, v2 or later",
12    layer     = math.huge,
13    handler   = true,
14    enabled   = false  --  loaded by default?
15  }
16end
17
18--------------------------------------------------------------------------------
19--------------------------------------------------------------------------------
20
21-- make a table of the names of user widgets
22local userWidgets = {}
23function widget:Initialize()
24    for name,wData in pairs(widgetHandler.knownWidgets) do
25        userWidgets[name] = (not wData.fromZip)
26    end
27end
28
29-- special widgets (things with a right to high usage that the user should not touch!)
30local specialWidgets = {
31    ["Lups"] = true,
32    ["API Chili"] = true,
33    ["Red_UI_Framework"] = true,
34    ["Red_Drawing"] = true,
35}
36
37--------------------------------------------------------------------------------
38--------------------------------------------------------------------------------
39
40local callinTimes       = {}
41
42local oldUpdateWidgetCallIn
43local oldInsertWidget
44
45local listOfHooks = {}
46setmetatable(listOfHooks, { __mode = 'k' })
47
48local inHook = false
49
50--------------------------------------------------------------------------------
51--------------------------------------------------------------------------------
52
53local SCRIPT_DIR = Script.GetName() .. '/'
54
55local function IsHook(func)
56  return listOfHooks[func]
57end
58
59local function Hook(g,name)
60  local spGetTimer = Spring.GetTimer
61  local spDiffTimers = Spring.DiffTimers
62  local widgetName = g.whInfo.name
63
64  local realFunc = g[name]
65  g["_old" .. name] = realFunc
66
67  if (widgetName=="WidgetProfiler") then
68    return realFunc
69  end
70  local widgetCallinTime = callinTimes[widgetName] or {}
71  callinTimes[widgetName] = widgetCallinTime
72  widgetCallinTime[name] = widgetCallinTime[name] or {0,0}
73  local timeStats = widgetCallinTime[name]
74
75  local t
76
77  local helper_func = function(...)
78    local dt = spDiffTimers(spGetTimer(),t)
79    timeStats[1] = timeStats[1] + dt
80    timeStats[2] = timeStats[2] + dt
81    inHook = nil
82    return ...
83  end
84
85  local hook_func = function(...)
86    if (inHook) then
87      return realFunc(...)
88    end
89
90    inHook = true
91    t = spGetTimer()
92    return helper_func(realFunc(...))
93  end
94
95  listOfHooks[hook_func] = true
96
97  return hook_func
98end
99
100local function ArrayInsert(t, f, g)
101  if (f) then
102    local layer = g.whInfo.layer
103    local index = 1
104    for i,v in ipairs(t) do
105      if (v == g) then
106        return -- already in the table
107      end
108      if (layer >= v.whInfo.layer) then
109        index = i + 1
110      end
111    end
112    table.insert(t, index, g)
113  end
114end
115
116
117local function ArrayRemove(t, g)
118  for k,v in ipairs(t) do
119    if (v == g) then
120      table.remove(t, k)
121      -- break
122    end
123  end
124end
125
126
127local function StartHook()
128  Spring.Echo("start profiling")
129
130  local wh = widgetHandler
131
132  local CallInsList = {}
133  for name,e in pairs(wh) do
134    local i = name:find("List")
135    if (i)and(type(e)=="table") then
136      CallInsList[#CallInsList+1] = name:sub(1,i-1)
137    end
138  end
139
140  --// hook all existing callins
141  for _,callin in ipairs(CallInsList) do
142    local callinGadgets = wh[callin .. "List"]
143    for _,w in ipairs(callinGadgets or {}) do
144      w[callin] = Hook(w,callin)
145    end
146  end
147
148  Spring.Echo("hooked all callins: OK")
149
150  --// hook the UpdateCallin function
151
152  oldUpdateWidgetCallIn =  wh.UpdateWidgetCallIn
153  wh.UpdateWidgetCallIn = function(self,name,w)
154    local listName = name .. 'List'
155    local ciList = self[listName]
156    if (ciList) then
157      local func = w[name]
158      if (type(func) == 'function') then
159        if (not IsHook(func)) then
160          w[name] = Hook(w,name)
161        end
162        ArrayInsert(ciList, func, w)
163      else
164        ArrayRemove(ciList, w)
165      end
166      self:UpdateCallIn(name)
167    else
168      print('UpdateWidgetCallIn: bad name: ' .. name)
169    end
170  end
171
172  oldInsertWidget =  wh.InsertWidget
173  widgetHandler.InsertWidget = function(self,widget)
174    if (widget == nil) then
175      return
176    end
177
178    oldInsertWidget(self,widget)
179
180    for _,callin in ipairs(CallInsList) do
181      local func = widget[callin]
182      if (type(func) == 'function') then
183        widget[callin] = Hook(widget,callin)
184      end
185    end
186  end
187
188  Spring.Echo("hooked UpdateCallin: OK")
189end
190
191
192local function StopHook()
193  Spring.Echo("stop profiling")
194
195  local wh = widgetHandler
196
197  local CallInsList = {}
198  for name,e in pairs(wh) do
199    local i = name:find("List")
200    if (i)and(type(e)=="table") then
201      CallInsList[#CallInsList+1] = name:sub(1,i-1)
202    end
203  end
204
205  --// unhook all existing callins
206  for _,callin in ipairs(CallInsList) do
207    local callinGadgets = wh[callin .. "List"]
208    for _,w in ipairs(callinGadgets or {}) do
209      if (w["_old" .. callin]) then
210        w[callin] = w["_old" .. callin]
211      end
212    end
213  end
214
215  Spring.Echo("unhooked all callins: OK")
216
217  --// unhook the UpdateCallin function
218  wh.UpdateWidgetCallIn = oldUpdateWidgetCallIn
219  wh.InsertWidget = oldInsertWidget
220
221  Spring.Echo("unhooked UpdateCallin: OK")
222end
223
224--------------------------------------------------------------------------------
225--------------------------------------------------------------------------------
226
227  local startTimer
228
229  function widget:Update()
230    widgetHandler:RemoveWidgetCallIn("Update", self)
231    StartHook()
232    startTimer = Spring.GetTimer()
233  end
234
235  function widget:Shutdown()
236    StopHook()
237  end
238
239local tick = 0.2
240local averageTime = 2
241local loadAverages = {}
242
243local function CalcLoad(old_load, new_load, t)
244  return old_load*math.exp(-tick/t) + new_load*(1 - math.exp(-tick/t))
245end
246
247local maximum = 0
248local avg = 0
249local totalLoads = {}
250local allOverTime = 0
251local allOverTimeSec = 0
252
253local sortedList = {}
254local function SortFunc(a,b)
255    return a.plainname < b.plainname
256end
257
258local maxLines = 50
259local deltaTime
260local redStrength = {}
261
262function DrawWidgetList(list,name,x,y,j)
263    if j>=maxLines-5 then x = x - 350; j = 0; end
264    j = j + 1
265    gl.Text("\255\160\255\160"..name.." WIDGETS", x+150, y-1-(12)*j, 10)
266    j = j + 2
267   
268    for i=1,#list do
269      if j>=maxLines then x = x - 350; j = 0; end
270      local v = list[i]
271      local name = v.plainname
272      local wname = v.fullname
273      local tLoad = v.tLoad
274      local colour = v.colourString
275      gl.Text(wname, x+150, y+1-(12)*j, 10)
276      gl.Text(colour .. ('%.3f%%'):format(tLoad), x+105, y+1-(12)*j, 10)         
277
278          j = j + 1
279    end
280   
281    gl.Text("\255\255\064\064total load ("..string.lower(name)..")", x+150, y+1-(12)*j, 10)
282    gl.Text("\255\255\064\064"..('%.3f%%'):format(list.allOverTime), x+105, y+1-(12)*j, 10)       
283    j = j + 1
284   
285    return x,j
286end
287
288function ColourString(R,G,B)
289        R255 = math.floor(R*255) 
290    G255 = math.floor(G*255)
291    B255 = math.floor(B*255)
292    if (R255%10 == 0) then R255 = R255+1 end
293    if (G255%10 == 0) then G255 = G255+1 end
294    if (B255%10 == 0) then B255 = B255+1 end
295        return "\255"..string.char(R255)..string.char(G255)..string.char(B255)
296end
297
298local minTime = 0.005 -- above this value, we fade in how heavy we mark a widget
299local maxTime = 0.02 -- above this value, we mark a widget as heavy
300local minFPS = 30 -- above this value, we fade out how red we mark heavy widgets
301local maxFPS = 60 -- above this value, we don't mark any widgets red
302
303function CheckLoad(v) --tLoad is %
304    local tTime = v.tTime
305    local name = v.plainname
306    local FPS = Spring.GetFPS()
307   
308    if tTime>maxTime then tTime = maxTime end
309    if tTime<minTime then tTime = minTime end
310    if FPS>maxFPS then FPS = maxFPS end
311    if FPS<minFPS then FPS = minFPS end
312   
313    local new_r = ((tTime-minTime)/(maxTime-minTime)) * ((maxFPS-FPS)/(maxFPS-minFPS))
314    local u = math.exp(-deltaTime/5) --magic colour changing rate
315    redStrength[name] = u*redStrength[name] + (1-u)*new_r
316    local r,g,b = 1, 1-redStrength[name]*((255-64)/255), 1-redStrength[name]*((255-64)/255)
317    return ColourString(r,g,b)
318end
319
320  function widget:DrawScreen()
321    if not (next(callinTimes)) then
322      return --// nothing to do
323    end
324
325    deltaTime = Spring.DiffTimers(Spring.GetTimer(),startTimer)
326
327    -- sort & count timing
328    if (deltaTime>=tick) then
329      startTimer = Spring.GetTimer()
330          sortedList = {}
331         
332      totalLoads = {}
333      maximum = 0
334      avg = 0
335      allOverTime = 0
336      local n = 1
337      for wname,callins in pairs(callinTimes) do
338        local total = 0
339        local cmax  = 0
340        local cmaxname = ""
341        for cname,timeStats in pairs(callins) do
342          total = total + timeStats[1]
343          if (timeStats[2]>cmax) then
344            cmax = timeStats[2]
345            cmaxname = cname
346          end
347          timeStats[1] = 0
348        end
349
350        local load = 100*total/deltaTime
351        local load_avg = CalcLoad(loadAverages[wname] or load, load, averageTime)
352                loadAverages[wname] = load_avg
353
354        allOverTimeSec = allOverTimeSec + total
355
356        local tLoad = loadAverages[wname]
357        sortedList[n] = {plainname=wname, fullname=wname..'('..cmaxname..')', tLoad=tLoad, tTime=total/deltaTime}
358        allOverTime = allOverTime + tLoad
359        avg = avg + tLoad
360        if (maximum<tLoad) then maximum=tLoad end
361        n = n + 1
362      end
363      avg = avg/n
364         
365      table.sort(sortedList,SortFunc)
366    end
367
368    if (not sortedList[1]) then
369      return --// nothing to do
370    end
371
372    -- add to category and set colour
373    local userList = {}
374    local gameList = {}
375    local specialList = {}
376    userList.allOverTime = 0
377    gameList.allOverTime = 0
378    specialList.allOverTime = 0
379    for i=1,#sortedList do
380        redStrength[sortedList[i].plainname] = redStrength[sortedList[i].plainname] or 0
381        if userWidgets[sortedList[i].plainname] then
382            sortedList[i].colourString = CheckLoad(sortedList[i])
383            userList[#userList+1] = sortedList[i]
384            userList.allOverTime = userList.allOverTime + sortedList[i].tLoad
385        elseif specialWidgets[sortedList[i].plainname] then
386            sortedList[i].colourString = "\255\255\255\255"
387            specialList[#specialList+1] = sortedList[i]
388            specialList.allOverTime = specialList.allOverTime + sortedList[i].tLoad 
389        else
390            sortedList[i].colourString = CheckLoad(sortedList[i])
391            gameList[#gameList+1] = sortedList[i]
392            gameList.allOverTime = gameList.allOverTime + sortedList[i].tLoad
393        end
394    end
395   
396    -- draw
397    local vsx, vsy = gl.GetViewSizes()
398    local x,y = vsx-350, vsy-150
399
400    gl.Color(1,1,1,1)
401    gl.BeginText()
402        local j = -1 --line number
403   
404    x,j = DrawWidgetList(gameList,"GAME",x,y,j)
405    x,j = DrawWidgetList(specialList,"API & SPECIAL",x,y,j)
406    x,j = DrawWidgetList(userList,"USER",x,y,j)
407   
408    if j>=maxLines-5 then x = x - 350; j = 0; end
409    j = j + 1
410    gl.Text("\255\180\255\180TOTAL", x+150, y-1-(12)*j, 10)
411    j = j + 1
412
413        j = j + 1
414    gl.Text("\255\255\064\064total load", x+150, y-1-(12)*j, 10)
415    gl.Text("\255\255\064\064"..('%.1f%%'):format(allOverTime), x+105, y-1-(12)*j, 10)
416    j = j + 1
417    gl.Text("\255\255\064\064total time", x+150, y-1-(12)*j, 10)
418    gl.Text("\255\255\064\064"..('%.3fs'):format(allOverTimeSec), x+105, y-1-(12)*j, 10)
419    gl.EndText()
420 
421  end
422 
423 
424
425--------------------------------------------------------------------------------
426--------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.