| 1 | --[[
|
|---|
| 2 |
|
|---|
| 3 | == Introduction ==
|
|---|
| 4 |
|
|---|
| 5 | Note that this requires os.clock(), debug.sethook(),
|
|---|
| 6 | and debug.getinfo() or your equivalent replacements to
|
|---|
| 7 | be available if this is an embedded application.
|
|---|
| 8 |
|
|---|
| 9 | Example usage:
|
|---|
| 10 |
|
|---|
| 11 | profiler = newProfiler()
|
|---|
| 12 | profiler:start()
|
|---|
| 13 |
|
|---|
| 14 | < call some functions that take time >
|
|---|
| 15 |
|
|---|
| 16 | profiler:stop()
|
|---|
| 17 |
|
|---|
| 18 | local outfile = io.open( "profile.txt", "w+" )
|
|---|
| 19 | profiler:report( outfile )
|
|---|
| 20 | outfile:close()
|
|---|
| 21 |
|
|---|
| 22 | == Optionally choosing profiling method ==
|
|---|
| 23 |
|
|---|
| 24 | The rest of this comment can be ignored if you merely want a good profiler.
|
|---|
| 25 |
|
|---|
| 26 | newProfiler(method, sampledelay):
|
|---|
| 27 |
|
|---|
| 28 | If method is omitted or "time", will profile based on real performance.
|
|---|
| 29 | optionally, frequency can be provided to control the number of opcodes
|
|---|
| 30 | per profiling tick. By default this is 100000, which (on my system) provides
|
|---|
| 31 | one tick approximately every 2ms and reduces system performance by about 10%.
|
|---|
| 32 | This can be reduced to increase accuracy at the cost of performance, or
|
|---|
| 33 | increased for the opposite effect.
|
|---|
| 34 |
|
|---|
| 35 | If method is "call", will profile based on function calls. Frequency is
|
|---|
| 36 | ignored.
|
|---|
| 37 |
|
|---|
| 38 |
|
|---|
| 39 | "time" may bias profiling somewhat towards large areas with "simple opcodes",
|
|---|
| 40 | as the profiling function (which introduces a certain amount of unavoidable
|
|---|
| 41 | overhead) will be called more often. This can be minimized by using a larger
|
|---|
| 42 | sample delay - the default should leave any error largely overshadowed by
|
|---|
| 43 | statistical noise. With a delay of 1000 I was able to achieve inaccuray of
|
|---|
| 44 | approximately 25%. Increasing the delay to 100000 left inaccuracy below my
|
|---|
| 45 | testing error.
|
|---|
| 46 |
|
|---|
| 47 | "call" may bias profiling heavily towards areas with many function calls.
|
|---|
| 48 | Testing found a degenerate case giving a figure inaccurate by approximately
|
|---|
| 49 | 20,000%. (Yes, a multiple of 200.) This is, however, more directly comparable
|
|---|
| 50 | to common profilers (such as gprof) and also gives accurate function call
|
|---|
| 51 | counts, which cannot be retrieved from "time".
|
|---|
| 52 |
|
|---|
| 53 | I strongly recommend "time" mode, and it is now the default.
|
|---|
| 54 |
|
|---|
| 55 | == History ==
|
|---|
| 56 |
|
|---|
| 57 | 2008-09-16 - Time-based profiling and conversion to Lua 5.1
|
|---|
| 58 | by Ben Wilhelm ( zorba-pepperfish@pavlovian.net ).
|
|---|
| 59 | Added the ability to optionally choose profiling methods, along with a new
|
|---|
| 60 | profiling method.
|
|---|
| 61 |
|
|---|
| 62 | Converted to Lua 5, a few improvements, and
|
|---|
| 63 | additional documentation by Tom Spilman ( tom@sickheadgames.com )
|
|---|
| 64 |
|
|---|
| 65 | Additional corrections and tidying by original author
|
|---|
| 66 | Daniel Silverstone ( dsilvers@pepperfish.net )
|
|---|
| 67 |
|
|---|
| 68 | == Status ==
|
|---|
| 69 |
|
|---|
| 70 | Daniel Silverstone is no longer using this code, and judging by how long it's
|
|---|
| 71 | been waiting for Lua 5.1 support, I don't think Tom Spilman is either. I'm
|
|---|
| 72 | perfectly willing to take on maintenance, so if you have problems or
|
|---|
| 73 | questions, go ahead and email me :)
|
|---|
| 74 | -- Ben Wilhelm ( zorba-pepperfish@pavlovian.net ) '
|
|---|
| 75 |
|
|---|
| 76 | == Copyright ==
|
|---|
| 77 |
|
|---|
| 78 | Lua profiler - Copyright Pepperfish 2002,2003,2004
|
|---|
| 79 |
|
|---|
| 80 | Permission is hereby granted, free of charge, to any person obtaining a copy
|
|---|
| 81 | of this software and associated documentation files (the "Software"), to
|
|---|
| 82 | deal in the Software without restriction, including without limitation the
|
|---|
| 83 | rights to use, copy, modify, merge, publish, distribute, and/or sell copies
|
|---|
| 84 | of the Software, and to permit persons to whom the Software is furnished to
|
|---|
| 85 | do so, subject to the following conditions:
|
|---|
| 86 |
|
|---|
| 87 | The above copyright notice and this permission notice shall be included in
|
|---|
| 88 | all copies or substantial portions of the Software.
|
|---|
| 89 |
|
|---|
| 90 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|---|
| 91 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|---|
| 92 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|---|
| 93 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|---|
| 94 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|---|
| 95 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|---|
| 96 | IN THE SOFTWARE.
|
|---|
| 97 |
|
|---|
| 98 | --]]
|
|---|
| 99 |
|
|---|
| 100 |
|
|---|
| 101 | --
|
|---|
| 102 | -- All profiler related stuff is stored in the top level table '_profiler'
|
|---|
| 103 | --
|
|---|
| 104 | _profiler = {}
|
|---|
| 105 |
|
|---|
| 106 |
|
|---|
| 107 | --
|
|---|
| 108 | -- newProfiler() creates a new profiler object for managing
|
|---|
| 109 | -- the profiler and storing state. Note that only one profiler
|
|---|
| 110 | -- object can be executing at one time.
|
|---|
| 111 | --
|
|---|
| 112 | function newProfiler(variant, sampledelay)
|
|---|
| 113 | if _profiler.running then
|
|---|
| 114 | print("Profiler already running.")
|
|---|
| 115 | return
|
|---|
| 116 | end
|
|---|
| 117 |
|
|---|
| 118 | variant = variant or "time"
|
|---|
| 119 |
|
|---|
| 120 | if variant ~= "time" and variant ~= "call" then
|
|---|
| 121 | print("Profiler method must be 'time' or 'call'.")
|
|---|
| 122 | return
|
|---|
| 123 | end
|
|---|
| 124 |
|
|---|
| 125 | local newprof = {}
|
|---|
| 126 | for k,v in pairs(_profiler) do
|
|---|
| 127 | newprof[k] = v
|
|---|
| 128 | end
|
|---|
| 129 | newprof.variant = variant
|
|---|
| 130 | newprof.sampledelay = sampledelay or 100000
|
|---|
| 131 | return newprof
|
|---|
| 132 | end
|
|---|
| 133 |
|
|---|
| 134 |
|
|---|
| 135 | --
|
|---|
| 136 | -- This function starts the profiler. It will do nothing
|
|---|
| 137 | -- if this (or any other) profiler is already running.
|
|---|
| 138 | --
|
|---|
| 139 | function _profiler.start(self)
|
|---|
| 140 | if _profiler.running then
|
|---|
| 141 | return
|
|---|
| 142 | end
|
|---|
| 143 | -- Start the profiler. This begins by setting up internal profiler state
|
|---|
| 144 | _profiler.running = self
|
|---|
| 145 | self.rawstats = {}
|
|---|
| 146 | self.callstack = {}
|
|---|
| 147 | if self.variant == "time" then
|
|---|
| 148 | self.lastclock = os.clock()
|
|---|
| 149 | debug.sethook( _profiler_hook_wrapper_by_time, "", self.sampledelay )
|
|---|
| 150 | elseif self.variant == "call" then
|
|---|
| 151 | debug.sethook( _profiler_hook_wrapper_by_call, "cr" )
|
|---|
| 152 | else
|
|---|
| 153 | print("Profiler method must be 'time' or 'call'.")
|
|---|
| 154 | sys.exit(1)
|
|---|
| 155 | end
|
|---|
| 156 | end
|
|---|
| 157 |
|
|---|
| 158 |
|
|---|
| 159 | --
|
|---|
| 160 | -- This function stops the profiler. It will do nothing
|
|---|
| 161 | -- if a profiler is not running, and nothing if it isn't
|
|---|
| 162 | -- the currently running profiler.
|
|---|
| 163 | --
|
|---|
| 164 | function _profiler.stop(self)
|
|---|
| 165 | if _profiler.running ~= self then
|
|---|
| 166 | return
|
|---|
| 167 | end
|
|---|
| 168 | -- Stop the profiler.
|
|---|
| 169 | debug.sethook( nil )
|
|---|
| 170 | _profiler.running = nil
|
|---|
| 171 | end
|
|---|
| 172 |
|
|---|
| 173 |
|
|---|
| 174 | --
|
|---|
| 175 | -- Simple wrapper to handle the hook. You should not
|
|---|
| 176 | -- be calling this directly. Duplicated to reduce overhead.
|
|---|
| 177 | --
|
|---|
| 178 | function _profiler_hook_wrapper_by_call(action)
|
|---|
| 179 | if _profiler.running == nil then
|
|---|
| 180 | debug.sethook( nil )
|
|---|
| 181 | end
|
|---|
| 182 | _profiler.running:_internal_profile_by_call(action)
|
|---|
| 183 | end
|
|---|
| 184 | function _profiler_hook_wrapper_by_time(action)
|
|---|
| 185 | if _profiler.running == nil then
|
|---|
| 186 | debug.sethook( nil )
|
|---|
| 187 | end
|
|---|
| 188 | _profiler.running:_internal_profile_by_time(action)
|
|---|
| 189 | end
|
|---|
| 190 |
|
|---|
| 191 |
|
|---|
| 192 | --
|
|---|
| 193 | -- This is the main by-function-call function of the profiler and should not
|
|---|
| 194 | -- be called except by the hook wrapper
|
|---|
| 195 | --
|
|---|
| 196 | function _profiler._internal_profile_by_call(self,action)
|
|---|
| 197 | -- Since we can obtain the 'function' for the item we've had call us, we
|
|---|
| 198 | -- can use that...
|
|---|
| 199 | local caller_info = debug.getinfo( 3 )
|
|---|
| 200 | if caller_info == nil then
|
|---|
| 201 | print "No caller_info"
|
|---|
| 202 | return
|
|---|
| 203 | end
|
|---|
| 204 |
|
|---|
| 205 | --SHG_LOG("[_profiler._internal_profile] "..(caller_info.name or "<nil>"))
|
|---|
| 206 |
|
|---|
| 207 | -- Retrieve the most recent activation record...
|
|---|
| 208 | local latest_ar = nil
|
|---|
| 209 | local callstackLen = #self.callstack
|
|---|
| 210 | if callstackLen > 0 then
|
|---|
| 211 | latest_ar = self.callstack[callstackLen]
|
|---|
| 212 | end
|
|---|
| 213 |
|
|---|
| 214 | -- Are we allowed to profile this function?
|
|---|
| 215 | local should_not_profile = 0
|
|---|
| 216 | for k,v in pairs(self.prevented_functions) do
|
|---|
| 217 | if k == caller_info.func then
|
|---|
| 218 | should_not_profile = v
|
|---|
| 219 | end
|
|---|
| 220 | end
|
|---|
| 221 | -- Also check the top activation record...
|
|---|
| 222 | if latest_ar then
|
|---|
| 223 | if latest_ar.should_not_profile == 2 then
|
|---|
| 224 | should_not_profile = 2
|
|---|
| 225 | end
|
|---|
| 226 | end
|
|---|
| 227 |
|
|---|
| 228 | -- Now then, are we in 'call' or 'return' ?
|
|---|
| 229 | -- print("Profile:", caller_info.name, "SNP:", should_not_profile,
|
|---|
| 230 | -- "Action:", action )
|
|---|
| 231 | if action == "call" then
|
|---|
| 232 | -- Making a call...
|
|---|
| 233 | local this_ar = {}
|
|---|
| 234 | this_ar.should_not_profile = should_not_profile
|
|---|
| 235 | this_ar.parent_ar = latest_ar
|
|---|
| 236 | this_ar.anon_child = 0
|
|---|
| 237 | this_ar.name_child = 0
|
|---|
| 238 | this_ar.children = {}
|
|---|
| 239 | this_ar.children_time = {}
|
|---|
| 240 | this_ar.clock_start = os.clock()
|
|---|
| 241 | -- Last thing to do on a call is to insert this onto the ar stack...
|
|---|
| 242 | table.insert( self.callstack, this_ar )
|
|---|
| 243 | else
|
|---|
| 244 | local this_ar = latest_ar
|
|---|
| 245 | if this_ar == nil then
|
|---|
| 246 | return -- No point in doing anything if no upper activation record
|
|---|
| 247 | end
|
|---|
| 248 |
|
|---|
| 249 | -- Right, calculate the time in this function...
|
|---|
| 250 | this_ar.clock_end = os.clock()
|
|---|
| 251 | this_ar.this_time = this_ar.clock_end - this_ar.clock_start
|
|---|
| 252 |
|
|---|
| 253 | -- Now, if we have a parent, update its call info...
|
|---|
| 254 | if this_ar.parent_ar then
|
|---|
| 255 | this_ar.parent_ar.children[caller_info.func] =
|
|---|
| 256 | (this_ar.parent_ar.children[caller_info.func] or 0) + 1
|
|---|
| 257 | this_ar.parent_ar.children_time[caller_info.func] =
|
|---|
| 258 | (this_ar.parent_ar.children_time[caller_info.func] or 0 ) +
|
|---|
| 259 | this_ar.this_time
|
|---|
| 260 | if caller_info.name == nil then
|
|---|
| 261 | this_ar.parent_ar.anon_child =
|
|---|
| 262 | this_ar.parent_ar.anon_child + this_ar.this_time
|
|---|
| 263 | else
|
|---|
| 264 | this_ar.parent_ar.name_child =
|
|---|
| 265 | this_ar.parent_ar.name_child + this_ar.this_time
|
|---|
| 266 | end
|
|---|
| 267 | end
|
|---|
| 268 | -- Now if we're meant to record information about ourselves, do so...
|
|---|
| 269 | if this_ar.should_not_profile == 0 then
|
|---|
| 270 | local inforec = self:_get_func_rec(caller_info.func,1)
|
|---|
| 271 | inforec.count = inforec.count + 1
|
|---|
| 272 | inforec.time = inforec.time + this_ar.this_time
|
|---|
| 273 | inforec.anon_child_time = inforec.anon_child_time + this_ar.anon_child
|
|---|
| 274 | inforec.name_child_time = inforec.name_child_time + this_ar.name_child
|
|---|
| 275 | inforec.func_info = caller_info
|
|---|
| 276 | for k,v in pairs(this_ar.children) do
|
|---|
| 277 | inforec.children[k] = (inforec.children[k] or 0) + v
|
|---|
| 278 | inforec.children_time[k] =
|
|---|
| 279 | (inforec.children_time[k] or 0) + this_ar.children_time[k]
|
|---|
| 280 | end
|
|---|
| 281 | end
|
|---|
| 282 |
|
|---|
| 283 | -- Last thing to do on return is to drop the last activation record...
|
|---|
| 284 | local callstackLen = #self.callstack
|
|---|
| 285 | table.remove( self.callstack, callstackLen )
|
|---|
| 286 | end
|
|---|
| 287 | end
|
|---|
| 288 |
|
|---|
| 289 |
|
|---|
| 290 | --
|
|---|
| 291 | -- This is the main by-time internal function of the profiler and should not
|
|---|
| 292 | -- be called except by the hook wrapper
|
|---|
| 293 | --
|
|---|
| 294 | function _profiler._internal_profile_by_time(self,action)
|
|---|
| 295 | -- we do this first so we add the minimum amount of extra time to this call
|
|---|
| 296 | local timetaken = os.clock() - self.lastclock
|
|---|
| 297 |
|
|---|
| 298 | local depth = 3
|
|---|
| 299 | local at_top = true
|
|---|
| 300 | local last_caller
|
|---|
| 301 | local caller = debug.getinfo(depth)
|
|---|
| 302 | while caller do
|
|---|
| 303 | if not caller.func then caller.func = "(tail call)" end
|
|---|
| 304 | if self.prevented_functions[caller.func] == nil then
|
|---|
| 305 | local info = self:_get_func_rec(caller.func, 1, caller)
|
|---|
| 306 | info.count = info.count + 1
|
|---|
| 307 | info.time = info.time + timetaken
|
|---|
| 308 | if last_caller then
|
|---|
| 309 | -- we're not the head, so update the "children" times also
|
|---|
| 310 | if last_caller.name then
|
|---|
| 311 | info.name_child_time = info.name_child_time + timetaken
|
|---|
| 312 | else
|
|---|
| 313 | info.anon_child_time = info.anon_child_time + timetaken
|
|---|
| 314 | end
|
|---|
| 315 | info.children[last_caller.func] =
|
|---|
| 316 | (info.children[last_caller.func] or 0) + 1
|
|---|
| 317 | info.children_time[last_caller.func] =
|
|---|
| 318 | (info.children_time[last_caller.func] or 0) + timetaken
|
|---|
| 319 | end
|
|---|
| 320 | end
|
|---|
| 321 | depth = depth + 1
|
|---|
| 322 | last_caller = caller
|
|---|
| 323 | caller = debug.getinfo(depth)
|
|---|
| 324 | end
|
|---|
| 325 |
|
|---|
| 326 | self.lastclock = os.clock()
|
|---|
| 327 | end
|
|---|
| 328 |
|
|---|
| 329 |
|
|---|
| 330 | --
|
|---|
| 331 | -- This returns a (possibly empty) function record for
|
|---|
| 332 | -- the specified function. It is for internal profiler use.
|
|---|
| 333 | --
|
|---|
| 334 | function _profiler._get_func_rec(self,func,force,info)
|
|---|
| 335 | -- Find the function ref for 'func' (if force and not present, create one)
|
|---|
| 336 | local ret = self.rawstats[func]
|
|---|
| 337 | if ret == nil and force ~= 1 then
|
|---|
| 338 | return nil
|
|---|
| 339 | end
|
|---|
| 340 | if ret == nil then
|
|---|
| 341 | -- Build a new function statistics table
|
|---|
| 342 | ret = {}
|
|---|
| 343 | ret.func = func
|
|---|
| 344 | ret.count = 0
|
|---|
| 345 | ret.time = 0
|
|---|
| 346 | ret.anon_child_time = 0
|
|---|
| 347 | ret.name_child_time = 0
|
|---|
| 348 | ret.children = {}
|
|---|
| 349 | ret.children_time = {}
|
|---|
| 350 | ret.func_info = info
|
|---|
| 351 | self.rawstats[func] = ret
|
|---|
| 352 | end
|
|---|
| 353 | return ret
|
|---|
| 354 | end
|
|---|
| 355 |
|
|---|
| 356 |
|
|---|
| 357 | --
|
|---|
| 358 | -- This writes a profile report to the output file object. If
|
|---|
| 359 | -- sort_by_total_time is nil or false the output is sorted by
|
|---|
| 360 | -- the function time minus the time in it's children.
|
|---|
| 361 | --
|
|---|
| 362 | function _profiler.report( self, outfile, sort_by_total_time )
|
|---|
| 363 |
|
|---|
| 364 | outfile:write
|
|---|
| 365 | [[Lua Profile output created by profiler.lua. Copyright Pepperfish 2002+
|
|---|
| 366 |
|
|---|
| 367 | ]]
|
|---|
| 368 |
|
|---|
| 369 | -- This is pretty awful.
|
|---|
| 370 | local terms = {}
|
|---|
| 371 | if self.variant == "time" then
|
|---|
| 372 | terms.capitalized = "Sample"
|
|---|
| 373 | terms.single = "sample"
|
|---|
| 374 | terms.pastverb = "sampled"
|
|---|
| 375 | elseif self.variant == "call" then
|
|---|
| 376 | terms.capitalized = "Call"
|
|---|
| 377 | terms.single = "call"
|
|---|
| 378 | terms.pastverb = "called"
|
|---|
| 379 | else
|
|---|
| 380 | assert(false)
|
|---|
| 381 | end
|
|---|
| 382 |
|
|---|
| 383 | local total_time = 0
|
|---|
| 384 | local ordering = {}
|
|---|
| 385 | for func,record in pairs(self.rawstats) do
|
|---|
| 386 | table.insert(ordering, func)
|
|---|
| 387 | end
|
|---|
| 388 |
|
|---|
| 389 | if sort_by_total_time then
|
|---|
| 390 | table.sort( ordering,
|
|---|
| 391 | function(a,b) return self.rawstats[a].time > self.rawstats[b].time end
|
|---|
| 392 | )
|
|---|
| 393 | else
|
|---|
| 394 | table.sort( ordering,
|
|---|
| 395 | function(a,b)
|
|---|
| 396 | local arec = self.rawstats[a]
|
|---|
| 397 | local brec = self.rawstats[b]
|
|---|
| 398 | local atime = arec.time - (arec.anon_child_time + arec.name_child_time)
|
|---|
| 399 | local btime = brec.time - (brec.anon_child_time + brec.name_child_time)
|
|---|
| 400 | return atime > btime
|
|---|
| 401 | end
|
|---|
| 402 | )
|
|---|
| 403 | end
|
|---|
| 404 |
|
|---|
| 405 | local len = #ordering
|
|---|
| 406 | for i=1,len do
|
|---|
| 407 | local func = ordering[i]
|
|---|
| 408 | local record = self.rawstats[func]
|
|---|
| 409 | local thisfuncname = " " .. self:_pretty_name(func) .. " "
|
|---|
| 410 | if string.len( thisfuncname ) < 42 then
|
|---|
| 411 | local nameLen = math.ceil((42 - string.len(thisfuncname))/2)
|
|---|
| 412 | thisfuncname =
|
|---|
| 413 | string.rep( "-", nameLen) .. thisfuncname
|
|---|
| 414 | thisfuncname =
|
|---|
| 415 | thisfuncname .. string.rep( "-", 42 - string.len(thisfuncname) )
|
|---|
| 416 | end
|
|---|
| 417 |
|
|---|
| 418 | total_time = total_time + ( record.time - ( record.anon_child_time +
|
|---|
| 419 | record.name_child_time ) )
|
|---|
| 420 | outfile:write( string.rep( "-", 19 ) .. thisfuncname ..
|
|---|
| 421 | string.rep( "-", 19 ) .. "\n" )
|
|---|
| 422 | outfile:write( terms.capitalized.." count: " ..
|
|---|
| 423 | string.format( "%4d", record.count ) .. "\n" )
|
|---|
| 424 | outfile:write( "Time spend total: " ..
|
|---|
| 425 | string.format( "%4.3f", record.time ) .. "s\n" )
|
|---|
| 426 | outfile:write( "Time spent in children: " ..
|
|---|
| 427 | string.format("%4.3f",record.anon_child_time+record.name_child_time) ..
|
|---|
| 428 | "s\n" )
|
|---|
| 429 | local timeinself =
|
|---|
| 430 | record.time - (record.anon_child_time + record.name_child_time)
|
|---|
| 431 | outfile:write( "Time spent in self: " ..
|
|---|
| 432 | string.format("%4.3f", timeinself) .. "s\n" )
|
|---|
| 433 | outfile:write( "Time spent per " .. terms.single .. ": " ..
|
|---|
| 434 | string.format("%4.5f", record.time/record.count) ..
|
|---|
| 435 | "s/" .. terms.single .. "\n" )
|
|---|
| 436 | outfile:write( "Time spent in self per "..terms.single..": " ..
|
|---|
| 437 | string.format( "%4.5f", timeinself/record.count ) .. "s/" ..
|
|---|
| 438 | terms.single.."\n" )
|
|---|
| 439 |
|
|---|
| 440 | -- Report on each child in the form
|
|---|
| 441 | -- Child <funcname> called n times and took a.bs
|
|---|
| 442 | local added_blank = 0
|
|---|
| 443 | for k,v in pairs(record.children) do
|
|---|
| 444 | if self.prevented_functions[k] == nil or
|
|---|
| 445 | self.prevented_functions[k] == 0
|
|---|
| 446 | then
|
|---|
| 447 | if added_blank == 0 then
|
|---|
| 448 | outfile:write( "\n" ) -- extra separation line
|
|---|
| 449 | added_blank = 1
|
|---|
| 450 | end
|
|---|
| 451 | outfile:write( "Child " .. self:_pretty_name(k) ..
|
|---|
| 452 | string.rep( " ", 41-string.len(self:_pretty_name(k)) ) .. " " ..
|
|---|
| 453 | terms.pastverb.." " .. string.format("%6d", v) )
|
|---|
| 454 | outfile:write( " times. Took " ..
|
|---|
| 455 | string.format("%4.2f", record.children_time[k] ) .. "s\n" )
|
|---|
| 456 | end
|
|---|
| 457 | end
|
|---|
| 458 |
|
|---|
| 459 | outfile:write( "\n" ) -- extra separation line
|
|---|
| 460 | outfile:flush()
|
|---|
| 461 | end
|
|---|
| 462 | outfile:write( "\n\n" )
|
|---|
| 463 | outfile:write( "Total time spent in profiled functions: " ..
|
|---|
| 464 | string.format("%5.3g",total_time) .. "s\n" )
|
|---|
| 465 | outfile:write( [[
|
|---|
| 466 |
|
|---|
| 467 | END
|
|---|
| 468 | ]] )
|
|---|
| 469 | outfile:flush()
|
|---|
| 470 | end
|
|---|
| 471 |
|
|---|
| 472 |
|
|---|
| 473 | --
|
|---|
| 474 | -- This writes the profile to the output file object as
|
|---|
| 475 | -- loadable Lua source.
|
|---|
| 476 | --
|
|---|
| 477 | function _profiler.lua_report(self,outfile)
|
|---|
| 478 | -- Purpose: Write out the entire raw state in a cross-referenceable form.
|
|---|
| 479 | local ordering = {}
|
|---|
| 480 | local functonum = {}
|
|---|
| 481 | for func,record in pairs(self.rawstats) do
|
|---|
| 482 | table.insert(ordering, func)
|
|---|
| 483 | local len = #ordering
|
|---|
| 484 | functonum[func] = len
|
|---|
| 485 | end
|
|---|
| 486 |
|
|---|
| 487 | outfile:write(
|
|---|
| 488 | "-- Profile generated by profiler.lua Copyright Pepperfish 2002+\n\n" )
|
|---|
| 489 | outfile:write( "-- Function names\nfuncnames = {}\n" )
|
|---|
| 490 | local len = #ordering
|
|---|
| 491 | for i=1,len do
|
|---|
| 492 | local thisfunc = ordering[i]
|
|---|
| 493 | outfile:write( "funcnames[" .. i .. "] = " ..
|
|---|
| 494 | string.format("%q", self:_pretty_name(thisfunc)) .. "\n" )
|
|---|
| 495 | end
|
|---|
| 496 | outfile:write( "\n" )
|
|---|
| 497 | outfile:write( "-- Function times\nfunctimes = {}\n" )
|
|---|
| 498 |
|
|---|
| 499 | local len = #ordering
|
|---|
| 500 | for i=1,len do
|
|---|
| 501 | local thisfunc = ordering[i]
|
|---|
| 502 | local record = self.rawstats[thisfunc]
|
|---|
| 503 | outfile:write( "functimes[" .. i .. "] = { " )
|
|---|
| 504 | outfile:write( "tot=" .. record.time .. ", " )
|
|---|
| 505 | outfile:write( "achild=" .. record.anon_child_time .. ", " )
|
|---|
| 506 | outfile:write( "nchild=" .. record.name_child_time .. ", " )
|
|---|
| 507 | outfile:write( "count=" .. record.count .. " }\n" )
|
|---|
| 508 | end
|
|---|
| 509 | outfile:write( "\n" )
|
|---|
| 510 | outfile:write( "-- Child links\nchildren = {}\n" )
|
|---|
| 511 |
|
|---|
| 512 | local len = #ordering
|
|---|
| 513 | for i=1,len do
|
|---|
| 514 | local thisfunc = ordering[i]
|
|---|
| 515 | local record = self.rawstats[thisfunc]
|
|---|
| 516 | outfile:write( "children[" .. i .. "] = { " )
|
|---|
| 517 | for k,v in pairs(record.children) do
|
|---|
| 518 | if functonum[k] then -- non-recorded functions will be ignored now
|
|---|
| 519 | outfile:write( functonum[k] .. ", " )
|
|---|
| 520 | end
|
|---|
| 521 | end
|
|---|
| 522 | outfile:write( "}\n" )
|
|---|
| 523 | end
|
|---|
| 524 | outfile:write( "\n" )
|
|---|
| 525 | outfile:write( "-- Child call counts\nchildcounts = {}\n" )
|
|---|
| 526 |
|
|---|
| 527 | local len = #ordering
|
|---|
| 528 | for i=1,len do
|
|---|
| 529 | local thisfunc = ordering[i]
|
|---|
| 530 | local record = self.rawstats[thisfunc]
|
|---|
| 531 | outfile:write( "children[" .. i .. "] = { " )
|
|---|
| 532 | for k,v in record.children do
|
|---|
| 533 | if functonum[k] then -- non-recorded functions will be ignored now
|
|---|
| 534 | outfile:write( v .. ", " )
|
|---|
| 535 | end
|
|---|
| 536 | end
|
|---|
| 537 | outfile:write( "}\n" )
|
|---|
| 538 | end
|
|---|
| 539 | outfile:write( "\n" )
|
|---|
| 540 | outfile:write( "-- Child call time\nchildtimes = {}\n" )
|
|---|
| 541 | local len = #ordering
|
|---|
| 542 | for i=1,len do
|
|---|
| 543 | local thisfunc = ordering[i]
|
|---|
| 544 | local record = self.rawstats[thisfunc];
|
|---|
| 545 | outfile:write( "children[" .. i .. "] = { " )
|
|---|
| 546 | for k,v in pairs(record.children) do
|
|---|
| 547 | if functonum[k] then -- non-recorded functions will be ignored now
|
|---|
| 548 | outfile:write( record.children_time[k] .. ", " )
|
|---|
| 549 | end
|
|---|
| 550 | end
|
|---|
| 551 | outfile:write( "}\n" )
|
|---|
| 552 | end
|
|---|
| 553 | outfile:write( "\n\n-- That is all.\n\n" )
|
|---|
| 554 | outfile:flush()
|
|---|
| 555 | end
|
|---|
| 556 |
|
|---|
| 557 | -- Internal function to calculate a pretty name for the profile output
|
|---|
| 558 | function _profiler._pretty_name(self,func)
|
|---|
| 559 |
|
|---|
| 560 | -- Only the data collected during the actual
|
|---|
| 561 | -- run seems to be correct.... why?
|
|---|
| 562 | local info = self.rawstats[ func ].func_info
|
|---|
| 563 | -- local info = debug.getinfo( func )
|
|---|
| 564 |
|
|---|
| 565 | local name = ""
|
|---|
| 566 | if info.what == "Lua" then
|
|---|
| 567 | name = "L:"
|
|---|
| 568 | end
|
|---|
| 569 | if info.what == "C" then
|
|---|
| 570 | name = "C:"
|
|---|
| 571 | end
|
|---|
| 572 | if info.what == "main" then
|
|---|
| 573 | name = " :"
|
|---|
| 574 | end
|
|---|
| 575 |
|
|---|
| 576 | if info.name == nil then
|
|---|
| 577 | name = name .. "<"..tostring(func) .. ">"
|
|---|
| 578 | else
|
|---|
| 579 | name = name .. info.name
|
|---|
| 580 | end
|
|---|
| 581 |
|
|---|
| 582 | if info.source then
|
|---|
| 583 | name = name .. "@" .. info.source
|
|---|
| 584 | else
|
|---|
| 585 | if info.what == "C" then
|
|---|
| 586 | name = name .. "@?"
|
|---|
| 587 | else
|
|---|
| 588 | name = name .. "@<string>"
|
|---|
| 589 | end
|
|---|
| 590 | end
|
|---|
| 591 | name = name .. ":"
|
|---|
| 592 | if info.what == "C" then
|
|---|
| 593 | name = name .. "?"
|
|---|
| 594 | else
|
|---|
| 595 | name = name .. info.linedefined
|
|---|
| 596 | end
|
|---|
| 597 |
|
|---|
| 598 | return name
|
|---|
| 599 | end
|
|---|
| 600 |
|
|---|
| 601 |
|
|---|
| 602 | --
|
|---|
| 603 | -- This allows you to specify functions which you do
|
|---|
| 604 | -- not want profiled. Setting level to 1 keeps the
|
|---|
| 605 | -- function from being profiled. Setting level to 2
|
|---|
| 606 | -- keeps both the function and its children from
|
|---|
| 607 | -- being profiled.
|
|---|
| 608 | --
|
|---|
| 609 | -- BUG: 2 will probably act exactly like 1 in "time" mode.
|
|---|
| 610 | -- If anyone cares, let me (zorba) know and it can be fixed.
|
|---|
| 611 | --
|
|---|
| 612 | function _profiler.prevent(self, func, level)
|
|---|
| 613 | self.prevented_functions[func] = (level or 1)
|
|---|
| 614 | end
|
|---|
| 615 |
|
|---|
| 616 |
|
|---|
| 617 | _profiler.prevented_functions = {
|
|---|
| 618 | [_profiler.start] = 2,
|
|---|
| 619 | [_profiler.stop] = 2,
|
|---|
| 620 | [_profiler._internal_profile_by_time] = 2,
|
|---|
| 621 | [_profiler._internal_profile_by_call] = 2,
|
|---|
| 622 | [_profiler_hook_wrapper_by_time] = 2,
|
|---|
| 623 | [_profiler_hook_wrapper_by_call] = 2,
|
|---|
| 624 | [_profiler.prevent] = 2,
|
|---|
| 625 | [_profiler._get_func_rec] = 2,
|
|---|
| 626 | [_profiler.report] = 2,
|
|---|
| 627 | [_profiler.lua_report] = 2,
|
|---|
| 628 | [_profiler._pretty_name] = 2
|
|---|
| 629 | }
|
|---|