-
Notifications
You must be signed in to change notification settings - Fork 104
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
179 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
-- lulip: LuaJIT line level profiler | ||
-- | ||
-- Copyright (c) 2013 John Graham-Cumming | ||
-- | ||
-- License: http://opensource.org/licenses/MIT | ||
|
||
local io_lines = io.lines | ||
local io_open = io.open | ||
local pairs = pairs | ||
local print = print | ||
local debug = debug | ||
local tonumber = tonumber | ||
local setmetatable = setmetatable | ||
local table_sort = table.sort | ||
local table_insert = table.insert | ||
local string_find = string.find | ||
local string_sub = string.sub | ||
local string_gsub = string.gsub | ||
local string_format = string.format | ||
local util = require("ffi/util") | ||
|
||
module(...) | ||
|
||
local function gettimeofday() | ||
local tv_sec, tv_usec = util.gettime() | ||
return tv_sec * 1000000 + tv_usec | ||
end | ||
|
||
local mt = { __index = _M } | ||
|
||
-- new: create new profiler object | ||
function new(self) | ||
return setmetatable({ | ||
|
||
-- Time when start() and stop() were called in microseconds | ||
|
||
start_time = 0, | ||
stop_time = 0, | ||
|
||
-- Per line timing information | ||
|
||
lines = {}, | ||
|
||
-- The current line being processed and when it was startd | ||
|
||
current_line = nil, | ||
current_start = 0, | ||
|
||
-- List of files to ignore. Set patterns using dont() | ||
|
||
ignore = {}, | ||
|
||
-- List of short file names used as a cache | ||
|
||
short = {}, | ||
|
||
-- Maximum number of rows of output data, set using maxrows() | ||
|
||
rows = 20, | ||
}, mt) | ||
end | ||
|
||
-- event: called when a line is executed | ||
function event(self, event, line) | ||
local now = gettimeofday() | ||
|
||
local f = string_sub(debug.getinfo(3).source,2) | ||
for i=1,#self.ignore do | ||
if string_find(f, self.ignore[i], 1, true) then | ||
return | ||
end | ||
end | ||
|
||
local short = self.short[f] | ||
if not short then | ||
local start = string_find(f, "[^/]+$") | ||
self.short[f] = string_sub(f, start) | ||
short = self.short[f] | ||
end | ||
|
||
if self.current_line ~= nil then | ||
self.lines[self.current_line][1] = | ||
self.lines[self.current_line][1] + 1 | ||
self.lines[self.current_line][2] = | ||
self.lines[self.current_line][2] + (now - self.current_start) | ||
end | ||
|
||
self.current_line = short .. ':' .. line | ||
|
||
if self.lines[self.current_line] == nil then | ||
self.lines[self.current_line] = {0, 0.0, f} | ||
end | ||
|
||
self.current_start = gettimeofday() | ||
end | ||
|
||
-- dont: tell the profiler to ignore files that match these patterns | ||
function dont(self, file) | ||
table_insert(self.ignore, file) | ||
end | ||
|
||
-- maxrows: set the maximum number of rows of output | ||
function maxrows(self, max) | ||
self.rows = max | ||
end | ||
|
||
-- start: begin profiling | ||
function start(self) | ||
self:dont('ffi/lulip.lua') | ||
self:dont('ffi/util.lua') | ||
self.start_time = gettimeofday() | ||
self.current_line = nil | ||
self.current_start = 0 | ||
debug.sethook(function(e,l) self:event(e, l) end, "l") | ||
end | ||
|
||
-- stop: end profiling | ||
function stop(self) | ||
self.stop_time = gettimeofday() | ||
debug.sethook() | ||
end | ||
|
||
-- readfile: turn a file into an array for line-level access | ||
local function readfile(file) | ||
local lines = {} | ||
local ln = 1 | ||
for line in io_lines(file) do | ||
lines[ln] = string_gsub(line, "^%s*(.-)%s*$", "%1") | ||
ln = ln + 1 | ||
end | ||
return lines | ||
end | ||
|
||
-- dump: dump profile information to the named file | ||
function dump(self, file) | ||
local t = {} | ||
for l,d in pairs(self.lines) do | ||
table_insert(t, {line=l, data=d}) | ||
end | ||
table_sort(t, function(a,b) return a["data"][2] > b["data"][2] end) | ||
|
||
local files = {} | ||
|
||
local f = io_open(file, "w") | ||
if not f then | ||
print("Failed to open output file " .. file) | ||
return | ||
end | ||
f:write([[ | ||
<html> | ||
<head> | ||
<script src="https://google-code-prettify.googlecode.com/svn/loader/run_prettify.js"> | ||
</script> | ||
<style>.code { padding-left: 20px; }</style> | ||
</head> | ||
<body> | ||
<table width="100%"> | ||
<thead><tr><th align="left">file:line</th><th align="right">count</th> | ||
<th align="right">elapsed (ms)</th><th align="left" class="code">line</th> | ||
</tr></thead> | ||
<tbody> | ||
]]) | ||
|
||
for j=1,self.rows do | ||
if not t[j] then break end | ||
local l = t[j]["line"] | ||
local d = t[j]["data"] | ||
if not files[d[3]] then | ||
files[d[3]] = readfile(d[3]) | ||
end | ||
local ln = tonumber(string_sub(l, string_find(l, ":", 1, true)+1)) | ||
f:write(string_format([[ | ||
<tr><td>%s</td><td align="right">%i</td><td align="right">%.3f</td> | ||
<td class="code"><code class="prettyprint">%s</code></td></tr>]], | ||
l, d[1], d[2]/1000, files[d[3]][ln])) | ||
end | ||
f:write('</tbody></table></body></html') | ||
f:close() | ||
end |