Lua is a great programming language and I use it as embedded scripting language in
What impresses me most it's flexibility. What ever I come up with is only a few lines away ... and I have crazy ideas. Like profiling lua scripts with KCacheGrind.
For profiling lua-scripts you can use
From my development in C I'm a big fan valgrind which has a plugins for
- memcheck: memleaks
- cachegrind: cache usage
- massif: memory allocation patterns and
- callgrind: calltrees and profiling with KCacheGrind
After taking the callgrind file-spec and playing with
debug.getinfo() I can now generate useful performance graphs for lua scripts.
To use the lua-callgrind you only have to use it as a call-wrapper around your script that you want to test:
$ lua lua-callgrind.lua <lua-script> [output file]
outputfile isn't specified the output is written to
$ kcachegrind lua-callgrind.txt
Now you can get the calltrees, the callmap (as show) and dive through the source-code with annotated costs.
I had to jump through some hoops to get to this stage.
What you see in the profiling data is the instruction code of the lua-vm. C-functions appear as no cost for now. In the future I may add "os.clocks()" to get CPU-time into the math and that way at least move the C-functions into the picture.
Tracking the stack
The first version could run very well for simple scripts but failed for my unit-test framework. It makes use of pcall() and error(). Assuming that each
call event is mirrored with a
return event led to a funny looking graph as the cost got assigned to the wrong functions.
i = 0 function f() i = i + 1 if i > 4 then error("out") end f() end pcall(f)
The stack tracking helped to see where we went wrong. Some
assert()ions later the issue was fixed.
>>function: 0x8071960 (main) >>function: 0x806b108 (pcall) >>function: 0x806df98 (nil) >>function: 0x806df98 (f) >>function: 0x806df98 (f) >>function: 0x806df98 (f) >>function: 0x806df98 (f) >>function: 0x806b028 (error) <<function: 0x806b108 function: 0x806b108 =? true
Just to open the path to the second issue: runtime errors in a
pcall(function() asd() end)
Refactoring the code and we are safe again:
>>function: 0x8071960 (main) >>function: 0x806b108 (pcall) >>function: 0x8075ee8 (nil) <<function: 0x806b108 function: 0x806b108 =? true <<function: 0x8071960 function: 0x8071960 =? true >>function: 0x806ed38 (sethook)
Resolving function names
In the screen-shot you see the nice functions names like
proxy.test.Suite.run. To get this names we have to do our own reserve-mapping for function-addresses (
tostring(func)) into method names as the
debug.*-API only provides the name of the function, which is
run. We have to traverse the global symbol-table and build the "path-name" to the function to get this nice name.
If we can't do it (like for local functions in other files) we use filename we see at call-time (like the
boom which is local to test.lua).
In case we can't resolve it at all be cause a closure is called we just use the
function 0x.... name we always get.