├── LICENSE ├── README.md ├── main.lua └── piefiller.lua /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 devfirefly 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Piefiller 2 | Graphical profiler for Love2D 0.10.2 3 | # Usage 4 | 1) require the file: 5 | ```lua 6 | piefiller = require("piefiller") 7 | ``` 8 | 2) make a new instance of piefiller 9 | ```lua 10 | Pie = piefiller:new() 11 | ``` 12 | 3) attach the piefiller to the part of your application that you want to monitor, it can be whatever but I suggest calling it in love.update or love.draw as this is what piefiller is all about. 13 | 14 | ```lua 15 | function love.update() 16 | Pie:attach() 17 | -- do something 18 | Pie:detach() 19 | end 20 | ``` 21 | 4) draw the output in your draw function and give event hooks for your pie. 22 | ```lua 23 | function love.draw() 24 | Pie:draw() 25 | end 26 | function love.keypressed(...) 27 | Pie:keypressed(...) 28 | end 29 | function love.mousepressed(...) 30 | Pie:mousepressed(...) 31 | end 32 | ``` 33 | 5) When you get sufficient output press the "P" key to output to file. 34 | # Keys 35 | r = resets the pie 36 | 37 | up = decreases depth 38 | 39 | down = increases depth 40 | 41 | , = decreases step size 42 | 43 | . = increases step size 44 | 45 | s = shortens the names displayed 46 | 47 | c = hides/shows hidden processes 48 | 49 | p = saves to file called "Profile" and opens directory for you 50 | ## To redefine these: 51 | Modes available: 52 | ```lua 53 | reset 54 | increase_depth 55 | decrease_depth 56 | increase_step_size 57 | decrease_step_size 58 | shorten_names 59 | show_hidden 60 | save_to_file 61 | ``` 62 | To redefine only one of the keys: 63 | 64 | ```lua 65 | piefiller:setKey(mode,key) 66 | ``` 67 | 68 | example: 69 | 70 | ```lua 71 | piefiller:setKey("increase_depth","up") 72 | ``` 73 | To redefine all of the keys: 74 | ```lua 75 | 76 | table = { 77 | "increase_depth" = "up" 78 | } 79 | piefiller:setKey(table) 80 | 81 | ``` 82 | # For your own interpretation 83 | If you wish to interpret the data on your own use piefiller:unpack(). 84 | Output is a table as such: 85 | 86 | ```lua 87 | data = { 88 | items = { 89 | { 90 | name, 91 | line_defined, 92 | current_line, 93 | source, 94 | time_taken, 95 | percentage, 96 | caller, 97 | } 98 | }, 99 | about = { 100 | depth, 101 | step, 102 | totalTime, 103 | }, 104 | } 105 | ``` 106 | 107 | # Additional notes 108 | The best depth to search for is 2 and 3. 109 | 110 | When used in large applications the output is difficult to read, however printing to file resolves this issue. 111 | # Planned features 112 | Make sure that text does not overlay. 113 | -------------------------------------------------------------------------------- /main.lua: -------------------------------------------------------------------------------- 1 | Profiler = require("piefiller") 2 | ProfOn = true 3 | drawRect = true 4 | local Prof = Profiler:new() 5 | function iterateSmall() 6 | for i=1,1000 do 7 | end 8 | end 9 | function iterateLarge() 10 | for i=1,100000 do 11 | 12 | end 13 | end 14 | function drawRectangles() 15 | for i=1,100 do 16 | love.graphics.setColor(255,0,0) 17 | love.graphics.rectangle("line",i,i,i,i) 18 | love.graphics.setColor(255,255,255) 19 | end 20 | end 21 | function love.load() 22 | end 23 | function love.draw() 24 | if drawRect then 25 | drawRectangles() 26 | Prof:detach() 27 | end 28 | if ProfOn then 29 | Prof:draw({50}) 30 | end 31 | end 32 | function love.update(dt) 33 | Prof:attach() 34 | iterateSmall() 35 | iterateLarge() 36 | if drawRect then 37 | Prof:detach(true) 38 | else 39 | Prof:detach() 40 | end 41 | local data = Prof:unpack() 42 | end 43 | function love.keypressed(key) 44 | if key == "escape" then 45 | ProfOn = not ProfOn 46 | elseif key == ";" then 47 | drawRect = not drawRect 48 | end 49 | Prof:keypressed(key) 50 | end 51 | function love.mousepressed(...) 52 | Prof:mousepressed(...) 53 | end 54 | -------------------------------------------------------------------------------- /piefiller.lua: -------------------------------------------------------------------------------- 1 | local path = ... 2 | local piefiller = {} 3 | local function hsvToRgb(h, s, v) 4 | local h,s,v = h,1,1 5 | local h,s,v = math.fmod(h,360),s,v 6 | if s==0 then return { v,v,v } end 7 | h=h/60 8 | i=math.floor(h) 9 | f=h-i 10 | p=v*(1-s) 11 | q=v*(1-s*f) 12 | t=v*(1-s*(1-f)) 13 | 14 | if i==0 then r=v g=t b=p 15 | elseif i==1 then r=q g=v b=p 16 | elseif i==2 then r=p g=v b=t 17 | elseif i==3 then r=p g=q b=v 18 | elseif i==4 then r=t g=p b=v 19 | else r=v g=p b=q 20 | end 21 | 22 | r=r*255 23 | g=g*255 24 | b=b*255 25 | return { r,g,b } 26 | end 27 | local function copy(t) 28 | local ret = {} 29 | for i,v in pairs(t) do 30 | ret[i] = v 31 | if type(v) == "table" then 32 | ret[i] = copy(v) 33 | end 34 | end 35 | return ret 36 | end 37 | function setColor(...) 38 | local args = {...} 39 | love.graphics.setColor(args[1] or 255,args[2] or 255,args[3] or 255,args[4] or 255) 40 | end 41 | local color_data = {} 42 | local colors = {} 43 | function piefiller:new() 44 | local self = {} 45 | setmetatable(self,{__index = piefiller}) 46 | self.data = {} 47 | self.parsed = {} 48 | self.last = 0 49 | self.timer = 0 50 | self:reset() 51 | self.depth = 2 52 | self.small = false 53 | self.x = 0 54 | self.y = 0 55 | self.scale = 1 56 | self.step = 1 57 | self.keys = { 58 | reset = "r", 59 | increase_depth = "down", 60 | decrease_depth = "up", 61 | increase_step_size = ",", 62 | decrease_step_size = ".", 63 | shorten_names = "s", 64 | show_hidden = "c", 65 | save_to_file = "p", 66 | } 67 | return self 68 | end 69 | function piefiller:reset() 70 | self.data = {} 71 | self.x = 0 72 | self.y = 0 73 | self.scale = 1 74 | for i=0,300 do 75 | table.insert(colors,hsvToRgb(i,100,100)) 76 | end 77 | end 78 | function piefiller:setKey(table_or_mode,key) 79 | if type(table_or_mode) == "table" then 80 | self.keys = table_or_mode 81 | for i,v in pairs(table_or_mode) do 82 | if self.keys[i] then self.keys[i] = v end 83 | end 84 | elseif type(table_or_mode) == "string" then 85 | if not self.keys[table_or_mode] then error("Invalid mode: "..tostring(table_or_mode)) end 86 | self.keys[table_or_mode] = key 87 | else 88 | error("Expected table or string got:"..type(table_or_mode)) 89 | end 90 | end 91 | 92 | function piefiller:parse(caller,parent) 93 | return { 94 | parent = parent, 95 | func = caller.func, 96 | count = 0, 97 | time = 0, 98 | child_time = 0, 99 | named_child_time = 0, 100 | children = {}, 101 | children_time = {}, 102 | info = caller, 103 | kids = {}, 104 | } 105 | end 106 | function piefiller:attach() 107 | self.last = os.clock() 108 | local function hook() 109 | local depth = self.depth 110 | local caller = debug.getinfo(depth) 111 | local taken = os.clock() - self.last 112 | if caller then 113 | local last_caller 114 | local own = string.find(caller.source,path) 115 | if caller.func ~= hook and not own then 116 | while caller do 117 | if last_caller and not self.view_children then 118 | local name = caller.func 119 | local lc = self.data[last_caller.func] 120 | if not lc.kids[name] then 121 | lc.kids[name] = self:parse(caller,last_caller) 122 | end 123 | local kid = lc.kids[name] 124 | kid.count = kid.count + 1 125 | kid.time = kid.time + taken 126 | else 127 | local name = caller.func 128 | local raw = self.data[name] 129 | if not raw then 130 | self.data[name] = self:parse(caller,last_caller) 131 | end 132 | raw = self.data[name] 133 | raw.count = raw.count + 1 134 | raw.time = raw.time + taken 135 | last_caller = caller 136 | end 137 | depth = depth + 1 138 | caller = debug.getinfo(depth) 139 | end 140 | end 141 | end 142 | end 143 | local step = 10^self.step 144 | if self.step < 0 then 145 | step = 1/-self.step 146 | end 147 | debug.sethook(hook,"",step) 148 | end 149 | function piefiller:detach(stop) 150 | local totaltime = 0 151 | local parsed = {} 152 | local no = 0 153 | for i,v in pairs(self.data) do 154 | no = no + 1 155 | totaltime = totaltime + v.time 156 | local i = no 157 | parsed[i] = {} 158 | parsed[i].name = v.info.name 159 | parsed[i].time = v.time 160 | parsed[i].src = v.info.source 161 | parsed[i].def = v.info.linedefined 162 | parsed[i].cur = v.info.currentline 163 | parsed[i].item = v 164 | parsed[i].caller = v.info 165 | if not color_data[v.func] then 166 | local i = math.random(#colors) 167 | color_data[v.func] = colors[i] 168 | table.remove(colors,i) 169 | end 170 | 171 | parsed[i].color = color_data[v.func] 172 | end 173 | local prc = totaltime/100 174 | for i,v in ipairs(parsed) do 175 | parsed[i].prc = v.time/prc 176 | end 177 | self.parsed = parsed 178 | self.totaltime = totaltime 179 | if not stop then debug.sethook() end 180 | end 181 | local largeFont = love.graphics.newFont(25) 182 | function piefiller:draw(args) 183 | local loading 184 | local oldFont = love.graphics.getFont() 185 | local args = args or {} 186 | local rad = args["rad"] or 200 187 | local mode = args["mode"] or "simple" 188 | local ret = args["return"] 189 | local pi = math.pi 190 | local arc = love.graphics.arc 191 | local w,h = love.graphics.getDimensions() 192 | 193 | love.graphics.push() 194 | 195 | love.graphics.translate(self.x,self.y) 196 | love.graphics.scale(self.scale) 197 | 198 | if self.parsed and self.totaltime > 0 then 199 | local lastangle = 0 200 | local font = love.graphics.getFont() 201 | for i,v in ipairs(self.parsed) do 202 | local color = v.color 203 | local cx,cy = w/2,h/2 204 | local angle = math.rad(3.6*v.prc) 205 | setColor(color) 206 | arc("fill",cx,cy,rad,lastangle,lastangle + angle) 207 | setColor(colors.black) 208 | if v.prc > 1 then 209 | arc("line",cx,cy,rad,lastangle,lastangle + angle) 210 | end 211 | lastangle = lastangle + angle 212 | setColor() 213 | end 214 | lastangle = 0 215 | for i,v in ipairs(self.parsed) do 216 | 217 | local color = v.color 218 | local cx,cy = w/2,h/2 219 | local angle = math.rad(3.6*v.prc) 220 | local x = cx + rad * math.cos(lastangle + angle/2) 221 | local y = cy + rad * math.sin(lastangle + angle/2) 222 | if self.small then 223 | txt = tostring(v.src).." @: "..tostring(v.name) 224 | else 225 | txt = tostring(math.ceil(v.prc)).." % "..tostring(v.name).." : "..tostring(v.src).." @: "..tostring(v.def) 226 | end 227 | local fw = font:getWidth(txt) 228 | local sx = 1 229 | if cx < x then 230 | sx = -1 231 | fw = 0 232 | end 233 | if cy + rad/2 < y then 234 | y = y + font:getHeight() 235 | elseif cy + rad/2 > y then 236 | y = y - font:getHeight() 237 | end 238 | local ofx 239 | love.graphics.print(txt,((x) + (-(fw+20))*sx),y) 240 | lastangle = lastangle + angle 241 | end 242 | else 243 | loading = true 244 | end 245 | self.timer = self.timer + love.timer.getDelta() 246 | if self.timer > 20 then 247 | self.timer = 0 248 | end 249 | love.graphics.setFont(largeFont) 250 | local t = "Depth: "..self.depth.." with step: "..self.step 251 | local fw = largeFont:getWidth(t) 252 | local fh = largeFont:getHeight() 253 | love.graphics.print(t,w/2 - fw/2,(fh+5)) 254 | if loading then 255 | t = "Loading..." 256 | fw = largeFont:getWidth(t) 257 | love.graphics.print("Loading...",w/2 - fw/2,h/2) 258 | end 259 | 260 | love.graphics.pop() 261 | 262 | love.graphics.setFont(oldFont) 263 | end 264 | function piefiller:mousepressed(x,y,b) 265 | if b == "wu" then 266 | local scale = self.scale - math.floor((0.05*self.scale)*1000)/1000 267 | if scale > 0 and scale > 0.1 then 268 | local lastzoom = self.scale 269 | local mouse_x = x - self.x 270 | local mouse_y = y - self.y 271 | self.scale = scale 272 | local newx = mouse_x * (self.scale/lastzoom) 273 | local newy = mouse_y * (self.scale/lastzoom) 274 | self.x = self.x + (mouse_x-newx) 275 | self.y = self.y + (mouse_y-newy) 276 | else 277 | self.scale = 0.1 278 | end 279 | elseif b == "wd" then 280 | local scale = self.scale + math.floor((0.05*self.scale)*1000)/1000 281 | local scalex = self.scale 282 | if scale > 0 and scale < 20 then 283 | local lastzoom = self.scale 284 | local mouse_x = x - self.x 285 | local mouse_y = y - self.y 286 | self.scale = scale 287 | local newx = mouse_x * (self.scale/lastzoom) 288 | local newy = mouse_y * (self.scale/lastzoom) 289 | self.x = self.x + (mouse_x-newx) 290 | self.y = self.y + (mouse_y-newy) 291 | else 292 | self.scale = 20 293 | end 294 | end 295 | end 296 | function piefiller:keypressed(key) 297 | local mode 298 | for i,v in pairs(self.keys) do 299 | if key == v then 300 | mode = i 301 | break 302 | end 303 | end 304 | if mode then 305 | if mode == "reset" then 306 | self:reset() 307 | elseif mode == "increase_depth" then 308 | self:reset() 309 | self.depth = self.depth + 1 310 | elseif mode == "decrease_depth" then 311 | self:reset() 312 | self.depth = self.depth - 1 313 | elseif mode == "increase_step_size" then 314 | self.step = self.step - 1 315 | elseif mode == "decrease_step_size" then 316 | self.step = self.step +1 317 | elseif mode == "shorten_names" then 318 | self.small = not self.small 319 | elseif mode == "show_hidden" then 320 | self:reset() 321 | self.view_children = not self.view_children 322 | elseif mode == "save_to_file" then 323 | local parsed = copy(self.parsed) 324 | table.sort(parsed,function(a,b) 325 | return a.prc > b.prc 326 | end) 327 | local d = {"Depth: "..self.depth.." with step: "..self.step.."\r\n".."Total time: "..self.totaltime.."\r\n"} 328 | 329 | for i,v in ipairs(parsed) do 330 | local instance = { 331 | "-----"..(v.name or "def@"..v.def).."-----", 332 | "source:"..v.src..":"..v.def, 333 | "current line: "..v.cur, 334 | "time: "..v.time, 335 | "percentage: "..math.ceil(v.prc).." %", 336 | "----------------", 337 | } 338 | for i,v in ipairs(instance) do 339 | instance[i] = v.."\r\n" 340 | end 341 | table.insert(d,table.concat(instance)) 342 | end 343 | local data = table.concat(d) 344 | love.filesystem.write("Profile",data) 345 | love.system.openURL(love.filesystem.getRealDirectory("Profile")) 346 | end 347 | end 348 | end 349 | function piefiller:unpack(fn) 350 | local data = { 351 | items = {}, 352 | about = { 353 | depth = self.depth, 354 | step = self.step, 355 | totalTime = self.totaltime, 356 | }, 357 | } 358 | for i,v in ipairs(self.parsed) do 359 | local a = { 360 | name = v.name, 361 | line_defined = v.def, 362 | current_line = v.cur, 363 | source = v.src, 364 | time_taken = v.time, 365 | percentage = v.prc, 366 | caller = v.caller, 367 | } 368 | if fn then 369 | assert(type(fn) == "function","Expected function got:"..type(fn)) 370 | fn(a) 371 | end 372 | table.insert(data.items,a) 373 | end 374 | return data 375 | end 376 | return piefiller --------------------------------------------------------------------------------