commit
						1fe6c3b880
					
				| @ -0,0 +1,650 @@ | |||||||
|  | --[[ | ||||||
|  | 
 | ||||||
|  | == Introduction == | ||||||
|  | 
 | ||||||
|  |   Note that this requires os.clock(), debug.sethook(), | ||||||
|  |   and debug.getinfo() or your equivalent replacements to | ||||||
|  |   be available if this is an embedded application. | ||||||
|  | 
 | ||||||
|  |   Example usage: | ||||||
|  | 
 | ||||||
|  |     profiler = newProfiler() | ||||||
|  |     profiler:start() | ||||||
|  | 
 | ||||||
|  |     < call some functions that take time > | ||||||
|  | 
 | ||||||
|  |     profiler:stop() | ||||||
|  | 
 | ||||||
|  |     local outfile = io.open( "profile.txt", "w+" ) | ||||||
|  |     profiler:report( outfile ) | ||||||
|  |     outfile:close() | ||||||
|  | 
 | ||||||
|  | == Optionally choosing profiling method == | ||||||
|  | 
 | ||||||
|  | The rest of this comment can be ignored if you merely want a good profiler. | ||||||
|  | 
 | ||||||
|  |   newProfiler(method, sampledelay): | ||||||
|  | 
 | ||||||
|  | If method is omitted or "time", will profile based on real performance. | ||||||
|  | optionally, frequency can be provided to control the number of opcodes | ||||||
|  | per profiling tick. By default this is 100000, which (on my system) provides | ||||||
|  | one tick approximately every 2ms and reduces system performance by about 10%. | ||||||
|  | This can be reduced to increase accuracy at the cost of performance, or | ||||||
|  | increased for the opposite effect. | ||||||
|  | 
 | ||||||
|  | If method is "call", will profile based on function calls. Frequency is | ||||||
|  | ignored. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | "time" may bias profiling somewhat towards large areas with "simple opcodes", | ||||||
|  | as the profiling function (which introduces a certain amount of unavoidable | ||||||
|  | overhead) will be called more often. This can be minimized by using a larger | ||||||
|  | sample delay - the default should leave any error largely overshadowed by | ||||||
|  | statistical noise. With a delay of 1000 I was able to achieve inaccuray of | ||||||
|  | approximately 25%. Increasing the delay to 100000 left inaccuracy below my | ||||||
|  | testing error. | ||||||
|  | 
 | ||||||
|  | "call" may bias profiling heavily towards areas with many function calls. | ||||||
|  | Testing found a degenerate case giving a figure inaccurate by approximately | ||||||
|  | 20,000%.  (Yes, a multiple of 200.) This is, however, more directly comparable | ||||||
|  | to common profilers (such as gprof) and also gives accurate function call | ||||||
|  | counts, which cannot be retrieved from "time". | ||||||
|  | 
 | ||||||
|  | I strongly recommend "time" mode, and it is now the default. | ||||||
|  | 
 | ||||||
|  | == History == | ||||||
|  | 
 | ||||||
|  | 2008-09-16 - Time-based profiling and conversion to Lua 5.1 | ||||||
|  |   by Ben Wilhelm ( zorba-pepperfish@pavlovian.net ). | ||||||
|  |   Added the ability to optionally choose profiling methods, along with a new | ||||||
|  |   profiling method. | ||||||
|  | 
 | ||||||
|  | Converted to Lua 5, a few improvements, and | ||||||
|  | additional documentation by Tom Spilman ( tom@sickheadgames.com ) | ||||||
|  | 
 | ||||||
|  | Additional corrections and tidying by original author | ||||||
|  | Daniel Silverstone ( dsilvers@pepperfish.net ) | ||||||
|  | 
 | ||||||
|  | == Status == | ||||||
|  | 
 | ||||||
|  | Daniel Silverstone is no longer using this code, and judging by how long it's | ||||||
|  | been waiting for Lua 5.1 support, I don't think Tom Spilman is either. I'm | ||||||
|  | perfectly willing to take on maintenance, so if you have problems or | ||||||
|  | questions, go ahead and email me :) | ||||||
|  | -- Ben Wilhelm ( zorba-pepperfish@pavlovian.net ) ' | ||||||
|  | 
 | ||||||
|  | == Copyright == | ||||||
|  | 
 | ||||||
|  | Lua profiler - Copyright Pepperfish 2002,2003,2004 | ||||||
|  | 
 | ||||||
|  | Permission is hereby granted, free of charge, to any person obtaining a copy | ||||||
|  | of this software and associated documentation files (the "Software"), to | ||||||
|  | deal in the Software without restriction, including without limitation the | ||||||
|  | rights to use, copy, modify, merge, publish, distribute, and/or sell copies | ||||||
|  | of the Software, and to permit persons to whom the Software is furnished to | ||||||
|  | do so, subject to the following conditions: | ||||||
|  | 
 | ||||||
|  | The above copyright notice and this permission notice shall be included in | ||||||
|  | all copies or substantial portions of the Software. | ||||||
|  | 
 | ||||||
|  | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||||
|  | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||||
|  | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||||
|  | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||||
|  | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | ||||||
|  | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS | ||||||
|  | IN THE SOFTWARE. | ||||||
|  | 
 | ||||||
|  | --]] | ||||||
|  | 
 | ||||||
|  | local _ENV = mkmodule('profiler') | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | -- | ||||||
|  | -- All profiler related stuff is stored in the top level table '_profiler' | ||||||
|  | -- | ||||||
|  | local _profiler = {} | ||||||
|  | local DEFAULT_FILTERED_FUNC = 1 | ||||||
|  | local DEFAULT_MISSING_FUNC = 3 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | -- | ||||||
|  | -- newProfiler() creates a new profiler object for managing | ||||||
|  | -- the profiler and storing state.  Note that only one profiler | ||||||
|  | -- object can be executing at one time. | ||||||
|  | -- | ||||||
|  | function newProfiler(variant, sampledelay) | ||||||
|  |   if _profiler.running then | ||||||
|  |     print("Profiler already running.") | ||||||
|  |     return | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   variant = variant or "time" | ||||||
|  | 
 | ||||||
|  |   if variant ~= "time" and variant ~= "call" then | ||||||
|  |     print("Profiler method must be 'time' or 'call'.") | ||||||
|  |     return | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   local newprof = {} | ||||||
|  |   for k,v in pairs(_profiler) do | ||||||
|  |     newprof[k] = v | ||||||
|  |   end | ||||||
|  |   newprof.variant = variant | ||||||
|  |   newprof.sampledelay = sampledelay or 10000 | ||||||
|  |   return newprof | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | -- | ||||||
|  | -- This function stops the profiler.  It will do nothing | ||||||
|  | -- if a profiler is not running, and nothing if it isn't | ||||||
|  | -- the currently running profiler. | ||||||
|  | -- | ||||||
|  | function _profiler.stop(self) | ||||||
|  |   if _profiler.running ~= self then | ||||||
|  |     return | ||||||
|  |   end | ||||||
|  |   -- Stop the profiler. | ||||||
|  |   debug.sethook() | ||||||
|  |   _profiler.running = nil | ||||||
|  | end | ||||||
|  | local function get_stats(rawstats, prevented_functions, func, depth) | ||||||
|  |     if prevented_functions[func] then | ||||||
|  |       return nil | ||||||
|  |     end | ||||||
|  |     local stats = rawstats[func] | ||||||
|  |     if not stats then | ||||||
|  |       depth = depth + 1 | ||||||
|  |       ci = debug.getinfo(depth,"fnS") | ||||||
|  |       if ci.what == "C" and ci.name == "__index" then | ||||||
|  |         local value1 | ||||||
|  |         _, value1 = debug.getlocal(depth, 1) | ||||||
|  |         value1 = tostring(value1) | ||||||
|  |         value1 = value1:match("^<([^:]*):") or value1 | ||||||
|  |         ci.short_src = value1 | ||||||
|  |       end | ||||||
|  |       stats = { | ||||||
|  |         func = ci.func, | ||||||
|  |         anon = ci.name == nil, | ||||||
|  |         count = 0, | ||||||
|  |         time = 0, | ||||||
|  |         profile_time = 0, | ||||||
|  |         anon_child_time = 0, | ||||||
|  |         name_child_time = 0, | ||||||
|  |         children = {}, | ||||||
|  |         children_time = {}, | ||||||
|  |         currentline = {}, | ||||||
|  |         func_info = ci, | ||||||
|  |       } | ||||||
|  |       rawstats[func] = stats | ||||||
|  |     end | ||||||
|  |     return stats | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | -- | ||||||
|  | -- Simple wrapper to handle the hook.  You should not | ||||||
|  | -- be calling this directly. Duplicated to reduce overhead. | ||||||
|  | -- | ||||||
|  | local function _profiler_hook_wrapper_by_call(action) | ||||||
|  |   local entry = os.clock() | ||||||
|  |   local self = _profiler.running | ||||||
|  |   local depth, stack = self.depth, self.stack | ||||||
|  |   if action == "call" then | ||||||
|  |     local parent = stack[depth] | ||||||
|  |     if parent and parent.should_not_profile > 0 then | ||||||
|  |       parent.should_not_profile = parent.should_not_profile + 1 | ||||||
|  |       parent.profile_time = parent.profile_time + (os.clock() - entry) | ||||||
|  |       return | ||||||
|  |     end | ||||||
|  |     local ci = debug.getinfo(2,"f") | ||||||
|  |     local func = ci.func | ||||||
|  |     local rawstats = self.rawstats | ||||||
|  |     local stats = get_stats(rawstats, self.prevented_functions, func, 2) | ||||||
|  |     depth = depth + 1 | ||||||
|  |     self.depth = depth | ||||||
|  |     local this = stack[depth] | ||||||
|  |     if not this then | ||||||
|  |       this = { | ||||||
|  |         should_not_profile = stats and 0 or 1, | ||||||
|  |         stats = stats, | ||||||
|  |         clock_start = entry, | ||||||
|  |         profile_time = 0, | ||||||
|  |       } | ||||||
|  |       stack[depth] = this | ||||||
|  |     else | ||||||
|  |       this.should_not_profile = stats and 0 or 1 | ||||||
|  |       this.stats = stats | ||||||
|  |       this.clock_start = entry | ||||||
|  |     end | ||||||
|  |     this.profile_time = (os.clock() - entry) | ||||||
|  |   else | ||||||
|  |     if depth == 0 then | ||||||
|  |       return | ||||||
|  |     end | ||||||
|  |     local this = stack[depth] | ||||||
|  |     if this.should_not_profile > 0 then | ||||||
|  |       this.should_not_profile = this.should_not_profile - 1 | ||||||
|  |       if this.should_not_profile == 0 then | ||||||
|  |         self.depth = depth - 1 | ||||||
|  |         local parent = stack[self.depth] | ||||||
|  |         if parent then | ||||||
|  |           local time = entry - this.start_time - this.profile_time | ||||||
|  |           parent.stats.anon_child_time = parent.stats.anon_child_time + time | ||||||
|  |           parent.profile_time = parent.profile_time + this.profile_time + (os.clock() - entry) | ||||||
|  |         end | ||||||
|  |       else | ||||||
|  |         this.profile_time = this.profile_time + (os.clock() - entry) | ||||||
|  |       end | ||||||
|  |       return | ||||||
|  |     end | ||||||
|  |     local time = entry - this.clock_start - this.profile_time | ||||||
|  |     depth = depth - 1 | ||||||
|  |     self.depth = depth | ||||||
|  |     local parent = stack[depth] | ||||||
|  | 
 | ||||||
|  |     local this_stats,parent_stats = this.stats,parent and parent.stats or nil | ||||||
|  |     local func = this_stats.func | ||||||
|  | 
 | ||||||
|  |     this_stats.count = this_stats.count + 1 | ||||||
|  |     this_stats.time = this_stats.time + time | ||||||
|  |     this_stats.profile_time = this_stats.profile_time + this.profile_time | ||||||
|  |     if not parent then return end | ||||||
|  |     if  this_stats.anon then | ||||||
|  |       parent_stats.anon_child_time = parent_stats.anon_child_time + time | ||||||
|  |     else | ||||||
|  |       parent_stats.name_child_time = parent_stats.name_child_time + time | ||||||
|  |     end | ||||||
|  |     local ch = parent_stats.children[func] | ||||||
|  |     if not ch then | ||||||
|  |       parent_stats.children[func] = 1 | ||||||
|  |       parent_stats.children_time[func] = time | ||||||
|  |     else | ||||||
|  |       parent_stats.children[func] = ch + 1 | ||||||
|  |       parent_stats.children_time[func] = parent_stats.children_time[func] + time | ||||||
|  |     end | ||||||
|  |     parent.profile_time = parent.profile_time + this.profile_time + (os.clock() - entry) | ||||||
|  |   end | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | local function _profiler_hook_wrapper_by_time() | ||||||
|  |   local self = _profiler.running | ||||||
|  |   local timetaken = os.clock() - self.lastclock | ||||||
|  |   local rawstats = self.rawstats | ||||||
|  |   local prevented = self.prevented_functions | ||||||
|  |   local depth = 2 | ||||||
|  |   local caller = debug.getinfo(depth,'fl') | ||||||
|  |   local child | ||||||
|  |   if not caller then | ||||||
|  |     return | ||||||
|  |   end | ||||||
|  |   local cf = caller.func | ||||||
|  |   if cf then | ||||||
|  |     child = get_stats(rawstats, prevented, cf, depth) | ||||||
|  |     if child then | ||||||
|  |       child.count = child.count + 1 | ||||||
|  |       child.time = child.time + timetaken | ||||||
|  |       local line = caller.currentline | ||||||
|  |       child.currentline[line] = (child.currentline[line] or 0) + 1 | ||||||
|  |     else | ||||||
|  |       cf = DEFAULT_FILTERED_FUNC | ||||||
|  |     end | ||||||
|  |   else | ||||||
|  |     cf = DEFAULT_MISSING_FUNC | ||||||
|  |   end | ||||||
|  |   depth = 3 | ||||||
|  |   local caller = debug.getinfo(depth,'f') | ||||||
|  |   while caller do | ||||||
|  |     if caller.func then | ||||||
|  |       local this = get_stats(rawstats, prevented, caller.func, depth) | ||||||
|  |       if this then | ||||||
|  |         this.time = this.time + timetaken | ||||||
|  |         if not child or child.anon then | ||||||
|  |           this.anon_child_time = this.anon_child_time + timetaken | ||||||
|  |         else | ||||||
|  |           this.name_child_time = this.name_child_time + timetaken | ||||||
|  |         end | ||||||
|  |         local ch = this.children[cf] | ||||||
|  |         if ch then | ||||||
|  |           this.children[cf] = ch + 1 | ||||||
|  |           this.children_time[cf] = this.children_time[cf] + timetaken | ||||||
|  |         else | ||||||
|  |           this.children[cf] = 1 | ||||||
|  |           this.children_time[cf] = timetaken | ||||||
|  |         end | ||||||
|  |         cf = caller.func | ||||||
|  |       else | ||||||
|  |         cf = DEFAULT_FILTERED_FUNC | ||||||
|  |       end | ||||||
|  |       child = this | ||||||
|  |     else | ||||||
|  |       cf = DEFAULT_MISSING_FUNC | ||||||
|  |       child = nil | ||||||
|  |     end | ||||||
|  |     depth = depth + 1 | ||||||
|  |     caller = debug.getinfo(depth, 'f') | ||||||
|  |   end | ||||||
|  |   self.lastclock = os.clock() | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | -- | ||||||
|  | -- This function starts the profiler.  It will do nothing | ||||||
|  | -- if this (or any other) profiler is already running. | ||||||
|  | -- | ||||||
|  | function _profiler.start(self) | ||||||
|  |   if _profiler.running then | ||||||
|  |     return | ||||||
|  |   end | ||||||
|  |   -- Start the profiler. This begins by setting up internal profiler state | ||||||
|  |   _profiler.running = self | ||||||
|  |   assert(_profiler.running) | ||||||
|  |   self.rawstats = {} | ||||||
|  |   self.stack = {} | ||||||
|  |   self.depth = 0 | ||||||
|  |   if self.variant == "time" then | ||||||
|  |     self.lastclock = os.clock() | ||||||
|  |     debug.sethook( _profiler_hook_wrapper_by_time, "", self.sampledelay ) | ||||||
|  |   elseif self.variant == "call" then | ||||||
|  |     debug.sethook( _profiler_hook_wrapper_by_call, "cr" ) | ||||||
|  |   else | ||||||
|  |     error("Profiler method must be 'time' or 'call'.") | ||||||
|  |   end | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | -- | ||||||
|  | -- This writes a profile report to the output file object.  If | ||||||
|  | -- sort_by_total_time is nil or false the output is sorted by | ||||||
|  | -- the function time minus the time in it's children. | ||||||
|  | -- | ||||||
|  | function _profiler.report( self, outfile, sort_by_total_time ) | ||||||
|  | 
 | ||||||
|  |   outfile:write | ||||||
|  |     [[Lua Profile output created by profiler.lua. Copyright Pepperfish 2002+ | ||||||
|  | 
 | ||||||
|  | ]] | ||||||
|  | 
 | ||||||
|  |   -- This is pretty awful. | ||||||
|  |   local terms = {} | ||||||
|  |   if self.variant == "time" then | ||||||
|  |     terms.capitalized = "Sample" | ||||||
|  |     terms.single = "sample" | ||||||
|  |     terms.pastverb = "sampled" | ||||||
|  |   elseif self.variant == "call" then | ||||||
|  |     terms.capitalized = "Call" | ||||||
|  |     terms.single = "call" | ||||||
|  |     terms.pastverb = "called" | ||||||
|  |   else | ||||||
|  |     error("Profiler method must be 'time' or 'call'.") | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   local total_time = 0 | ||||||
|  |   local ordering = {} | ||||||
|  |   for func,record in pairs(self.rawstats) do | ||||||
|  |     table.insert(ordering, func) | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   if sort_by_total_time then | ||||||
|  |     table.sort( ordering, | ||||||
|  |       function(a,b) return self.rawstats[a].time > self.rawstats[b].time end | ||||||
|  |     ) | ||||||
|  |   else | ||||||
|  |     table.sort( ordering, | ||||||
|  |       function(a,b) | ||||||
|  |         local arec = self.rawstats[a] | ||||||
|  |         local brec = self.rawstats[b] | ||||||
|  |         local atime = arec.time - (arec.anon_child_time + arec.name_child_time) | ||||||
|  |         local btime = brec.time - (brec.anon_child_time + brec.name_child_time) | ||||||
|  |         return atime > btime | ||||||
|  |       end | ||||||
|  |     ) | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   for i=1,#ordering do | ||||||
|  |     local func = ordering[i] | ||||||
|  |     local record = self.rawstats[func] | ||||||
|  |     local thisfuncname = " " .. self:_pretty_name(func) .. " " | ||||||
|  |     if string.len( thisfuncname ) < 42 then | ||||||
|  |       thisfuncname = | ||||||
|  |         string.rep( "-", math.floor((42 - string.len(thisfuncname))/2) ) .. thisfuncname | ||||||
|  |       thisfuncname = | ||||||
|  |         thisfuncname .. string.rep( "-", 42 - string.len(thisfuncname) ) | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     local child_count = 0 | ||||||
|  |     for _,v in pairs(record.children) do | ||||||
|  |       child_count = child_count + v | ||||||
|  |     end | ||||||
|  |     total_time = total_time + ( record.time - ( record.anon_child_time + | ||||||
|  |       record.name_child_time ) ) | ||||||
|  |     outfile:write( string.rep( "-", 19 ) .. thisfuncname .. | ||||||
|  |       string.rep( "-", 19 ) .. "\n" ) | ||||||
|  |     outfile:write( terms.capitalized.." count:             " .. | ||||||
|  |       string.format( "%5d", record.count ) .. "\n" ) | ||||||
|  |     outfile:write( terms.capitalized.." count in children: " .. | ||||||
|  |       string.format( "%5d", child_count ) .. "\n" ) | ||||||
|  |     outfile:write( "Time spend total:         " .. | ||||||
|  |       string.format( "%4.3f", record.time ) .. "s\n" ) | ||||||
|  |     outfile:write( "Time spent in children:   " .. | ||||||
|  |       string.format("%4.3f",record.anon_child_time+record.name_child_time) .. | ||||||
|  |       "s\n" ) | ||||||
|  |     outfile:write( "Time spent in profiler:   " .. | ||||||
|  |       string.format("%4.3f",record.profile_time) .. | ||||||
|  |       "s\n" ) | ||||||
|  |     local timeinself = | ||||||
|  |       record.time - (record.anon_child_time + record.name_child_time) | ||||||
|  |     outfile:write( "Time spent in self:       " .. | ||||||
|  |       string.format("%4.3f", timeinself) .. "s\n" ) | ||||||
|  |     outfile:write( "Time spent per " .. terms.single .. ":    " .. | ||||||
|  |                   string.format("%4.6f", record.time/(record.count+(self.variant == "time" and child_count or 0))) .. | ||||||
|  |       "s/" .. terms.single .. "\n" ) | ||||||
|  |     outfile:write( "Time spent in self per "..terms.single..": " .. | ||||||
|  |       string.format( "%4.6f", record.count > 0 and timeinself/record.count or 0.0 ) .. "s/" .. | ||||||
|  |       terms.single.."\n" ) | ||||||
|  | 
 | ||||||
|  |     -- Report on each child in the form | ||||||
|  |     -- Child  <funcname> called n times and took a.bs | ||||||
|  |     local added_blank = 0 | ||||||
|  |     for k,v in pairs(record.children) do | ||||||
|  |       if self.prevented_functions[k] == nil or | ||||||
|  |          self.prevented_functions[k] == 0 | ||||||
|  |       then | ||||||
|  |         if added_blank == 0 then | ||||||
|  |           outfile:write( "\n" ) -- extra separation line | ||||||
|  |           added_blank = 1 | ||||||
|  |         end | ||||||
|  |         local pretty_name | ||||||
|  |         if k == DEFAULT_FILTERED_FUNC then | ||||||
|  |           pretty_name = "(Filtered function)" | ||||||
|  |         elseif k == DEFAULT_MISSING_FUNC then | ||||||
|  |           pretty_name = "(Function pointer missing)" | ||||||
|  |         else | ||||||
|  |           pretty_name = self:_pretty_name(k) | ||||||
|  |         end | ||||||
|  |         outfile:write( "Child " .. pretty_name .. | ||||||
|  |           string.rep( " ", 41-string.len(pretty_name) ) .. " " .. | ||||||
|  |           terms.pastverb.." " .. string.format("%6d", v) ) | ||||||
|  |         outfile:write( " times. Took " .. | ||||||
|  |           string.format("%4.3f", record.children_time[k] ) .. "s\n" ) | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     local lines = {} | ||||||
|  |     for line,v in pairs(record.currentline) do | ||||||
|  |       if line >= 0 then | ||||||
|  |         lines[#lines+1] = line | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  |     table.sort(lines) | ||||||
|  |     for i=1,#lines do | ||||||
|  |       local line = lines[i] | ||||||
|  |       local v = record.currentline[line] | ||||||
|  |       -- @todo How about reading the source code from the file? | ||||||
|  |       outfile:write( ("%6d %s in line %d\n"):format(v, terms.pastverb, line)) | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     outfile:write( "\n" ) -- extra separation line | ||||||
|  |     outfile:flush() | ||||||
|  |   end | ||||||
|  |   outfile:write( "\n\n" ) | ||||||
|  |   outfile:write( "Total time spent in profiled functions: " .. | ||||||
|  |                  string.format("%5.3g",total_time) .. "s\n" ) | ||||||
|  |   outfile:write( [[ | ||||||
|  | 
 | ||||||
|  | END | ||||||
|  | ]] ) | ||||||
|  |   outfile:flush() | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | -- | ||||||
|  | -- This writes the profile to the output file object as | ||||||
|  | -- loadable Lua source. | ||||||
|  | -- | ||||||
|  | function _profiler.lua_report(self,outfile) | ||||||
|  |   -- Purpose: Write out the entire raw state in a cross-referenceable form. | ||||||
|  |   local ordering = {} | ||||||
|  |   local functonum = {} | ||||||
|  |   for func,record in pairs(self.rawstats) do | ||||||
|  |     table.insert(ordering, func) | ||||||
|  |     functonum[func] = #ordering | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   outfile:write( | ||||||
|  |     "-- Profile generated by profiler.lua Copyright Pepperfish 2002+\n\n" ) | ||||||
|  |   outfile:write( "-- Function names\nfuncnames = {}\n" ) | ||||||
|  |   for i=1,#ordering do | ||||||
|  |     local thisfunc = ordering[i] | ||||||
|  |     outfile:write( "funcnames[" .. i .. "] = " .. | ||||||
|  |       string.format("%q", self:_pretty_name(thisfunc)) .. "\n" ) | ||||||
|  |   end | ||||||
|  |   outfile:write( "\n" ) | ||||||
|  |   outfile:write( "-- Function times\nfunctimes = {}\n" ) | ||||||
|  |   for i=1,#ordering do | ||||||
|  |     local thisfunc = ordering[i] | ||||||
|  |     local record = self.rawstats[thisfunc] | ||||||
|  |     outfile:write( "functimes[" .. i .. "] = { " ) | ||||||
|  |     outfile:write( "tot=" .. record.time .. ", " ) | ||||||
|  |     outfile:write( "achild=" .. record.anon_child_time .. ", " ) | ||||||
|  |     outfile:write( "nchild=" .. record.name_child_time .. ", " ) | ||||||
|  |     outfile:write( "count=" .. record.count .. " }\n" ) | ||||||
|  |   end | ||||||
|  |   outfile:write( "\n" ) | ||||||
|  |   outfile:write( "-- Child links\nchildren = {}\n" ) | ||||||
|  |   for i=1,#ordering do | ||||||
|  |     local thisfunc = ordering[i] | ||||||
|  |     local record = self.rawstats[thisfunc] | ||||||
|  |     outfile:write( "children[" .. i .. "] = { " ) | ||||||
|  |     for k,v in pairs(record.children) do | ||||||
|  |       if functonum[k] then -- non-recorded functions will be ignored now | ||||||
|  |         outfile:write( functonum[k] .. ", " ) | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  |     outfile:write( "}\n" ) | ||||||
|  |   end | ||||||
|  |   outfile:write( "\n" ) | ||||||
|  |   outfile:write( "-- Child call counts\nchildcounts = {}\n" ) | ||||||
|  |   for i=1,#ordering do | ||||||
|  |     local thisfunc = ordering[i] | ||||||
|  |     local record = self.rawstats[thisfunc] | ||||||
|  |     outfile:write( "childcounts[" .. i .. "] = { " ) | ||||||
|  |     for k,v in pairs(record.children) do | ||||||
|  |       if functonum[k] then -- non-recorded functions will be ignored now | ||||||
|  |         outfile:write( v .. ", " ) | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  |     outfile:write( "}\n" ) | ||||||
|  |   end | ||||||
|  |   outfile:write( "\n" ) | ||||||
|  |   outfile:write( "-- Child call time\nchildtimes = {}\n" ) | ||||||
|  |   for i=1,#ordering do | ||||||
|  |     local thisfunc = ordering[i] | ||||||
|  |     local record = self.rawstats[thisfunc]; | ||||||
|  |     outfile:write( "childtimes[" .. i .. "] = { " ) | ||||||
|  |     for k,v in pairs(record.children) do | ||||||
|  |       if functonum[k] then -- non-recorded functions will be ignored now | ||||||
|  |         outfile:write( record.children_time[k] .. ", " ) | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  |     outfile:write( "}\n" ) | ||||||
|  |   end | ||||||
|  |   outfile:write( "\n\n-- That is all.\n\n" ) | ||||||
|  |   outfile:flush() | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | -- Internal function to calculate a pretty name for the profile output | ||||||
|  | function _profiler._pretty_name(self,func) | ||||||
|  | 
 | ||||||
|  |   -- Only the data collected during the actual | ||||||
|  |   -- run seems to be correct.... why? | ||||||
|  |   local info = self.rawstats[ func ].func_info | ||||||
|  |   -- local info = debug.getinfo( func ) | ||||||
|  | 
 | ||||||
|  |   local name = "" | ||||||
|  |   if info.what == "Lua" then | ||||||
|  |     name = "L:" | ||||||
|  |   end | ||||||
|  |   if info.what == "C" then | ||||||
|  |     name = "C:" | ||||||
|  |   end | ||||||
|  |   if info.what == "main" then | ||||||
|  |     name = " :" | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   if info.namewhat ~= nil then | ||||||
|  |     name = name .. info.namewhat .. ":" | ||||||
|  |   end | ||||||
|  |   if info.name == nil then | ||||||
|  |     name = name .. "<"..tostring(func) .. ">" | ||||||
|  |   else | ||||||
|  |     name = name .. info.name | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   if info.short_src then | ||||||
|  |     name = name .. "@" .. info.short_src | ||||||
|  |   else | ||||||
|  |     if info.what == "C" then | ||||||
|  |       name = name .. "@?" | ||||||
|  |     else | ||||||
|  |       name = name .. "@<string>" | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  |   name = name .. ":" | ||||||
|  |   if info.what == "C" then | ||||||
|  |     name = name .. "?" | ||||||
|  |   else | ||||||
|  |     name = name .. info.linedefined | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   return name | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | -- | ||||||
|  | -- This allows you to specify functions which you do | ||||||
|  | -- not want profiled. | ||||||
|  | -- | ||||||
|  | -- BUG: 2 will probably act exactly like 1 in "time" mode. | ||||||
|  | -- If anyone cares, let me (zorba) know and it can be fixed. | ||||||
|  | -- | ||||||
|  | function _profiler.prevent(self, func, enable) | ||||||
|  |   if enable then | ||||||
|  |     self.prevented_functions[func] = true | ||||||
|  |   else | ||||||
|  |     self.prevented_functions[func] = nil | ||||||
|  |   end | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | _profiler.prevented_functions = { | ||||||
|  |   [_profiler.start] = true, | ||||||
|  |   [_profiler.stop] = true, | ||||||
|  |   [_profiler_hook_wrapper_by_time] = true, | ||||||
|  |   [_profiler_hook_wrapper_by_call] = true, | ||||||
|  |   [_profiler.prevent] = true, | ||||||
|  |   [_profiler.report] = true, | ||||||
|  |   [_profiler.lua_report] = true, | ||||||
|  |   [_profiler._pretty_name] = true | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | return _ENV | ||||||
		Loading…
	
		Reference in New Issue