CapsAdmin

Untitled

Oct 18th, 2013
223
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 7.79 KB | None | 0 0
  1. --[[
  2.  
  3. basic instrumental and statistical profiler which only provides the raw data
  4. the statistical profiler is wip and is for luajit 2.1 alpha only
  5. garbage details may not be accurate (i think garbage can be collected in between??)
  6.  
  7. times are probably in microsecond so * 100 and get rid of around 5 decimals
  8.  
  9. BASIC USE
  10.     profiler.Start()
  11.     -- enjoy the lag
  12.  
  13.     local tbl = profiler.GetBenchmark()
  14.     -- parse the table how you want it
  15.    
  16.     profiler.Stop()
  17.    
  18. for every function called, tbl[i] looks like this:
  19. {
  20.     ["total_time"] = 33.787754476143,
  21.     ["debug_info"] = {
  22.         ["linedefined"] = 131,
  23.         ["isvararg"] = false,
  24.         ["namewhat"] = "",
  25.         ["lastlinedefined"] = 156,
  26.         ["nups"] = 4,
  27.         ["what"] = "Lua",
  28.         ["nparams"] = 0,
  29.     },
  30.     ["path"] = "X:/dropbox/goluwa/lua/platforms/glw/init.lua",
  31.     ["times_called"] = 292,
  32.     ["average_time"] = 0.115711487932,
  33.     ["sample_duration"] = 34.010924339816,
  34.     ["line"] = 131,
  35.     ["name"] = "window.Update()",
  36. }
  37.  
  38. ]]
  39.  
  40. profiler = {}
  41.  
  42. profiler.type = "instrumental"
  43. profiler.default_zone = "no zone"
  44. profiler.enabled = true
  45.  
  46. local clock = SysTime
  47.  
  48. local function fix_path(path)
  49.     return path:gsub("\\", "/"):gsub("(/+)", "/"):gsub("^%s*(.-)%s*$", "%1" )
  50. end
  51.  
  52. local function getparams(func)
  53.     local params = {}
  54.    
  55.     for i = 1, math.huge do
  56.         local key = debug.getlocal(func, i)
  57.         if key then
  58.             table.insert(params, key)
  59.         else
  60.             break
  61.         end
  62.     end
  63.  
  64.     return params
  65. end
  66.  
  67. profiler.data = profiler.data or {}
  68.  
  69. local active = false
  70. local zone = profiler.default_zone
  71. local data = profiler.data
  72. local read_file
  73.  
  74.  
  75. if jit.version_num >= 20100 then
  76.     profiler.jitpf = require("jit.profile")
  77.     profiler.default_mode = "l"
  78.     profiler.dump_depth = 10
  79.     profiler.dump_format = "pl\n"
  80. end
  81.  
  82. -- call this with glfw.GetTime or something after glfw is loaded
  83. function profiler.SetClockFunction(func)
  84.     time = func
  85.     profiler.Restart()
  86. end
  87.  
  88. -- call this with glfw.GetTime or something after glfw is loaded
  89. function profiler.SetReadFileFunction(func)
  90.     read_file = func
  91. end
  92.  
  93. do
  94.     local function statistical_callback(thread, samples, vmstate)
  95.         if not active or not profiler.enabled then
  96.             profiler.Stop()
  97.         return end
  98.        
  99.         profiler.jitpf.dumpstack(thread, profiler.dump_format, profiler.dump_depth):gsub("(.-):(%d+)", function(path, line)
  100.            
  101.             data[zone] = data[zone] or {}
  102.             data[zone][path] = data[zone][path] or {}
  103.             data[zone][path][line] = data[zone][path][line] or {total_time = 0, samples = 0}
  104.            
  105.             data[zone][path][line].samples = data[zone][path][line].samples + samples
  106.             data[zone][path][line].start_time = data[zone][path][line].start_time or clock()
  107.            
  108.         end)
  109.     end
  110.    
  111.     local function instrumental_callback(type)
  112.         if not active or not profiler.enabled then
  113.             profiler.Stop()
  114.         return end
  115.    
  116.         local info = debug.getinfo(2)
  117.        
  118.         if info.linedefined <= 0 then return end
  119.        
  120.         local path = info.source
  121.         local line = info.linedefined
  122.                
  123.         data[zone] = data[zone] or {}
  124.         data[zone][path] = data[zone][path] or {}
  125.         data[zone][path][line] = data[zone][path][line] or {total_time = 0, samples = 0, total_garbage = 0, func = info.func, func_name = info.name}
  126.        
  127.         data[zone][path][line].samples = data[zone][path][line].samples + 1
  128.         data[zone][path][line].start_time = data[zone][path][line].start_time or clock()
  129.        
  130.         if type == "call" then
  131.             data[zone][path][line].call_time = clock()
  132.             data[zone][path][line].call_garbage = collectgarbage("count")
  133.         elseif type == "return" and data[zone][path][line].call_time then
  134.             data[zone][path][line].total_time = data[zone][path][line].total_time + (clock() - data[zone][path][line].call_time)
  135.             data[zone][path][line].total_garbage = data[zone][path][line].total_garbage + (collectgarbage("count") - data[zone][path][line].call_garbage)
  136.         end
  137.     end
  138.  
  139.     function profiler.Start(zone)
  140.         if not profiler.enabled then return end
  141.                
  142.         if not zone then
  143.             local info = debug.getinfo(2)
  144.             if info then
  145.                 zone = info.name
  146.             end
  147.         end
  148.        
  149.         zone = zone or profiler.default_zone
  150.        
  151.         if profiler.type == "statistical" then
  152.             profiler.jitpf.start(profiler.default_mode, statistical_callback)
  153.         elseif profiler.type == "instrumental" then
  154.             debug.sethook(instrumental_callback, "cr")
  155.         end
  156.        
  157.         active = true
  158.     end
  159. end
  160.  
  161. function profiler.Stop()
  162.     if not profiler.enabled then return end
  163.    
  164.     if profiler.type == "statistical" then
  165.         profiler.jitpf.stop()
  166.     elseif profiler.type == "instrumental" then
  167.         debug.sethook()
  168.     end
  169.    
  170.     active = false
  171. end
  172.  
  173. function profiler.Restart()
  174.     profiler.data = {}
  175.     data = profiler.data
  176. end
  177.  
  178. function profiler.GetZone()
  179.     return zone
  180. end
  181.  
  182. function profiler.GetBenchmark()
  183.     local out = {}
  184.    
  185.     for zone, file_data in pairs(data) do
  186.         for path, lines in pairs(file_data) do
  187.             for line, data in pairs(lines) do
  188.                
  189.                 line =  tonumber(line)
  190.                
  191.                 local path = fix_path(path:gsub("%[.-%]", ""):gsub("@", ""))
  192.                 local name
  193.                 local debug_info
  194.                
  195.                 if data.func then                  
  196.                     debug_info = debug.getinfo(data.func)
  197.                    
  198.                     -- remove some useless fields
  199.                     debug_info.source = nil
  200.                     debug_info.short_src = nil
  201.                     debug_info.currentline = nil
  202.                     debug_info.func = nil
  203.                 end
  204.            
  205.                 if read_file then
  206.                     local content = file.Read(path, "GAME")
  207.                    
  208.                     if content then
  209.                         name = content:Split("\n")[line]
  210.                         if name then
  211.                             name = name:gsub("function ", "")
  212.                             name = name:Trim()     
  213.                             name = name:gsub("\t+", "    ")
  214.                         else
  215.                             name = "unknown(line not found)"
  216.                         end
  217.                     else                       
  218.                         name = tostring(data.func) .. " (" .. path .. ")"
  219.                     end
  220.                 elseif data.func then      
  221.                     name = ("%s(%s)"):format(data.func_name, table.concat(getparams(data.func), ", "))
  222.                 end
  223.                
  224.                 local temp = {
  225.                     zone = zone ~= "no zone" and zone or nil,
  226.                     path = path,
  227.                    
  228.                     line = line,
  229.                     name = name,
  230.                     debug_info = debug_info,
  231.                 }
  232.                
  233.                 if data.total_time then
  234.                     temp.average_time = data.total_time / data.samples
  235.                     temp.total_time = data.total_time
  236.                 end
  237.                
  238.                 if data.total_garbage and data.total_garbage > 0 then
  239.                     temp.average_garbage = math.floor(data.total_garbage / data.samples)
  240.                     temp.total_garbage = data.total_garbage
  241.                 end
  242.                                
  243.                 temp.sample_duration = clock() - data.start_time
  244.                 temp.times_called = data.samples
  245.                
  246.                 table.insert(out, temp)
  247.             end
  248.         end
  249.     end
  250.  
  251.     return out
  252. end
  253.  
  254.  
  255. concommand.Add("profile", function(ply, _, args)
  256.     local time = args[1]
  257.     profiler.SetClockFunction(SysTime)
  258.     profiler.SetReadFileFunction(file.Read)
  259.  
  260.     time = tonumber(time)
  261.    
  262.     profiler.Start()
  263.    
  264.     MsgN("starting profiler for ", time, " seconds")
  265.    
  266.     if time > 2 then
  267.         timer.Create("profile_status", 1, time-1, function()
  268.             MsgN("profiling...")
  269.         end)
  270.     end
  271.    
  272.     timer.Simple(time, function()
  273.         profiler.Stop()
  274.        
  275.         local benchmark = profiler.GetBenchmark()
  276.         local top = {}
  277.        
  278.         for k,v in pairs(benchmark) do
  279.             if v.times_called > 50 and v.average_time > 0 then
  280.                 table.insert(top, v)
  281.             end
  282.         end
  283.        
  284.         table.sort(top, function(a, b)
  285.             return a.average_time > b.average_time
  286.         end)
  287.        
  288.         local max = 0
  289.         local max2 = 0
  290.         for k, v in pairs(top) do
  291.             if #v.name > max then
  292.                 max = #v.name
  293.             end
  294.            
  295.             v.average_time = tostring(v.average_time * 100)
  296.            
  297.             if #v.average_time > max2 then
  298.                 max2 = #v.average_time
  299.             end
  300.         end
  301.        
  302.         local frame = vgui.Create("DFrame")
  303.         frame:SetSize(500, 500)
  304.         frame:Center()
  305.         frame:SetSizable(true)
  306.  
  307.         local list = vgui.Create("DListView", frame)
  308.         list:Dock(FILL)
  309.         list:SetMultiSelect(false)
  310.         list:AddColumn("name")
  311.         list:AddColumn("average ms")
  312.         list:AddColumn("call count")
  313.         list:AddColumn("total time")
  314.            
  315.         for k,v in pairs(top) do
  316.             list:AddLine(v.name, v.average_time, v.times_called, v.total_time)
  317.         end
  318.        
  319.         frame:MakePopup()
  320.        
  321.     end)   
  322. end)
Advertisement
Add Comment
Please, Sign In to add comment