p.lua 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. ----------------------------------------------------------------------------
  2. -- LuaJIT profiler.
  3. --
  4. -- Copyright (C) 2005-2022 Mike Pall. All rights reserved.
  5. -- Released under the MIT license. See Copyright Notice in luajit.h
  6. ----------------------------------------------------------------------------
  7. --
  8. -- This module is a simple command line interface to the built-in
  9. -- low-overhead profiler of LuaJIT.
  10. --
  11. -- The lower-level API of the profiler is accessible via the "jit.profile"
  12. -- module or the luaJIT_profile_* C API.
  13. --
  14. -- Example usage:
  15. --
  16. -- luajit -jp myapp.lua
  17. -- luajit -jp=s myapp.lua
  18. -- luajit -jp=-s myapp.lua
  19. -- luajit -jp=vl myapp.lua
  20. -- luajit -jp=G,profile.txt myapp.lua
  21. --
  22. -- The following dump features are available:
  23. --
  24. -- f Stack dump: function name, otherwise module:line. Default mode.
  25. -- F Stack dump: ditto, but always prepend module.
  26. -- l Stack dump: module:line.
  27. -- <number> stack dump depth (callee < caller). Default: 1.
  28. -- -<number> Inverse stack dump depth (caller > callee).
  29. -- s Split stack dump after first stack level. Implies abs(depth) >= 2.
  30. -- p Show full path for module names.
  31. -- v Show VM states. Can be combined with stack dumps, e.g. vf or fv.
  32. -- z Show zones. Can be combined with stack dumps, e.g. zf or fz.
  33. -- r Show raw sample counts. Default: show percentages.
  34. -- a Annotate excerpts from source code files.
  35. -- A Annotate complete source code files.
  36. -- G Produce raw output suitable for graphical tools (e.g. flame graphs).
  37. -- m<number> Minimum sample percentage to be shown. Default: 3.
  38. -- i<number> Sampling interval in milliseconds. Default: 10.
  39. --
  40. ----------------------------------------------------------------------------
  41. -- Cache some library functions and objects.
  42. local jit = require("jit")
  43. assert(jit.version_num == 20100, "LuaJIT core/library version mismatch")
  44. local profile = require("jit.profile")
  45. local vmdef = require("jit.vmdef")
  46. local math = math
  47. local pairs, ipairs, tonumber, floor = pairs, ipairs, tonumber, math.floor
  48. local sort, format = table.sort, string.format
  49. local stdout = io.stdout
  50. local zone -- Load jit.zone module on demand.
  51. -- Output file handle.
  52. local out
  53. ------------------------------------------------------------------------------
  54. local prof_ud
  55. local prof_states, prof_split, prof_min, prof_raw, prof_fmt, prof_depth
  56. local prof_ann, prof_count1, prof_count2, prof_samples
  57. local map_vmmode = {
  58. N = "Compiled",
  59. I = "Interpreted",
  60. C = "C code",
  61. G = "Garbage Collector",
  62. J = "JIT Compiler",
  63. }
  64. -- Profiler callback.
  65. local function prof_cb(th, samples, vmmode)
  66. prof_samples = prof_samples + samples
  67. local key_stack, key_stack2, key_state
  68. -- Collect keys for sample.
  69. if prof_states then
  70. if prof_states == "v" then
  71. key_state = map_vmmode[vmmode] or vmmode
  72. else
  73. key_state = zone:get() or "(none)"
  74. end
  75. end
  76. if prof_fmt then
  77. key_stack = profile.dumpstack(th, prof_fmt, prof_depth)
  78. key_stack = key_stack:gsub("%[builtin#(%d+)%]", function(x)
  79. return vmdef.ffnames[tonumber(x)]
  80. end)
  81. if prof_split == 2 then
  82. local k1, k2 = key_stack:match("(.-) [<>] (.*)")
  83. if k2 then key_stack, key_stack2 = k1, k2 end
  84. elseif prof_split == 3 then
  85. key_stack2 = profile.dumpstack(th, "l", 1)
  86. end
  87. end
  88. -- Order keys.
  89. local k1, k2
  90. if prof_split == 1 then
  91. if key_state then
  92. k1 = key_state
  93. if key_stack then k2 = key_stack end
  94. end
  95. elseif key_stack then
  96. k1 = key_stack
  97. if key_stack2 then k2 = key_stack2 elseif key_state then k2 = key_state end
  98. end
  99. -- Coalesce samples in one or two levels.
  100. if k1 then
  101. local t1 = prof_count1
  102. t1[k1] = (t1[k1] or 0) + samples
  103. if k2 then
  104. local t2 = prof_count2
  105. local t3 = t2[k1]
  106. if not t3 then t3 = {}; t2[k1] = t3 end
  107. t3[k2] = (t3[k2] or 0) + samples
  108. end
  109. end
  110. end
  111. ------------------------------------------------------------------------------
  112. -- Show top N list.
  113. local function prof_top(count1, count2, samples, indent)
  114. local t, n = {}, 0
  115. for k in pairs(count1) do
  116. n = n + 1
  117. t[n] = k
  118. end
  119. sort(t, function(a, b) return count1[a] > count1[b] end)
  120. for i=1,n do
  121. local k = t[i]
  122. local v = count1[k]
  123. local pct = floor(v*100/samples + 0.5)
  124. if pct < prof_min then break end
  125. if not prof_raw then
  126. out:write(format("%s%2d%% %s\n", indent, pct, k))
  127. elseif prof_raw == "r" then
  128. out:write(format("%s%5d %s\n", indent, v, k))
  129. else
  130. out:write(format("%s %d\n", k, v))
  131. end
  132. if count2 then
  133. local r = count2[k]
  134. if r then
  135. prof_top(r, nil, v, (prof_split == 3 or prof_split == 1) and " -- " or
  136. (prof_depth < 0 and " -> " or " <- "))
  137. end
  138. end
  139. end
  140. end
  141. -- Annotate source code
  142. local function prof_annotate(count1, samples)
  143. local files = {}
  144. local ms = 0
  145. for k, v in pairs(count1) do
  146. local pct = floor(v*100/samples + 0.5)
  147. ms = math.max(ms, v)
  148. if pct >= prof_min then
  149. local file, line = k:match("^(.*):(%d+)$")
  150. if not file then file = k; line = 0 end
  151. local fl = files[file]
  152. if not fl then fl = {}; files[file] = fl; files[#files+1] = file end
  153. line = tonumber(line)
  154. fl[line] = prof_raw and v or pct
  155. end
  156. end
  157. sort(files)
  158. local fmtv, fmtn = " %3d%% | %s\n", " | %s\n"
  159. if prof_raw then
  160. local n = math.max(5, math.ceil(math.log10(ms)))
  161. fmtv = "%"..n.."d | %s\n"
  162. fmtn = (" "):rep(n).." | %s\n"
  163. end
  164. local ann = prof_ann
  165. for _, file in ipairs(files) do
  166. local f0 = file:byte()
  167. if f0 == 40 or f0 == 91 then
  168. out:write(format("\n====== %s ======\n[Cannot annotate non-file]\n", file))
  169. break
  170. end
  171. local fp, err = io.open(file)
  172. if not fp then
  173. out:write(format("====== ERROR: %s: %s\n", file, err))
  174. break
  175. end
  176. out:write(format("\n====== %s ======\n", file))
  177. local fl = files[file]
  178. local n, show = 1, false
  179. if ann ~= 0 then
  180. for i=1,ann do
  181. if fl[i] then show = true; out:write("@@ 1 @@\n"); break end
  182. end
  183. end
  184. for line in fp:lines() do
  185. if line:byte() == 27 then
  186. out:write("[Cannot annotate bytecode file]\n")
  187. break
  188. end
  189. local v = fl[n]
  190. if ann ~= 0 then
  191. local v2 = fl[n+ann]
  192. if show then
  193. if v2 then show = n+ann elseif v then show = n
  194. elseif show+ann < n then show = false end
  195. elseif v2 then
  196. show = n+ann
  197. out:write(format("@@ %d @@\n", n))
  198. end
  199. if not show then goto next end
  200. end
  201. if v then
  202. out:write(format(fmtv, v, line))
  203. else
  204. out:write(format(fmtn, line))
  205. end
  206. ::next::
  207. n = n + 1
  208. end
  209. fp:close()
  210. end
  211. end
  212. ------------------------------------------------------------------------------
  213. -- Finish profiling and dump result.
  214. local function prof_finish()
  215. if prof_ud then
  216. profile.stop()
  217. local samples = prof_samples
  218. if samples == 0 then
  219. if prof_raw ~= true then out:write("[No samples collected]\n") end
  220. return
  221. end
  222. if prof_ann then
  223. prof_annotate(prof_count1, samples)
  224. else
  225. prof_top(prof_count1, prof_count2, samples, "")
  226. end
  227. prof_count1 = nil
  228. prof_count2 = nil
  229. prof_ud = nil
  230. if out ~= stdout then out:close() end
  231. end
  232. end
  233. -- Start profiling.
  234. local function prof_start(mode)
  235. local interval = ""
  236. mode = mode:gsub("i%d*", function(s) interval = s; return "" end)
  237. prof_min = 3
  238. mode = mode:gsub("m(%d+)", function(s) prof_min = tonumber(s); return "" end)
  239. prof_depth = 1
  240. mode = mode:gsub("%-?%d+", function(s) prof_depth = tonumber(s); return "" end)
  241. local m = {}
  242. for c in mode:gmatch(".") do m[c] = c end
  243. prof_states = m.z or m.v
  244. if prof_states == "z" then zone = require("jit.zone") end
  245. local scope = m.l or m.f or m.F or (prof_states and "" or "f")
  246. local flags = (m.p or "")
  247. prof_raw = m.r
  248. if m.s then
  249. prof_split = 2
  250. if prof_depth == -1 or m["-"] then prof_depth = -2
  251. elseif prof_depth == 1 then prof_depth = 2 end
  252. elseif mode:find("[fF].*l") then
  253. scope = "l"
  254. prof_split = 3
  255. else
  256. prof_split = (scope == "" or mode:find("[zv].*[lfF]")) and 1 or 0
  257. end
  258. prof_ann = m.A and 0 or (m.a and 3)
  259. if prof_ann then
  260. scope = "l"
  261. prof_fmt = "pl"
  262. prof_split = 0
  263. prof_depth = 1
  264. elseif m.G and scope ~= "" then
  265. prof_fmt = flags..scope.."Z;"
  266. prof_depth = -100
  267. prof_raw = true
  268. prof_min = 0
  269. elseif scope == "" then
  270. prof_fmt = false
  271. else
  272. local sc = prof_split == 3 and m.f or m.F or scope
  273. prof_fmt = flags..sc..(prof_depth >= 0 and "Z < " or "Z > ")
  274. end
  275. prof_count1 = {}
  276. prof_count2 = {}
  277. prof_samples = 0
  278. profile.start(scope:lower()..interval, prof_cb)
  279. prof_ud = newproxy(true)
  280. getmetatable(prof_ud).__gc = prof_finish
  281. end
  282. ------------------------------------------------------------------------------
  283. local function start(mode, outfile)
  284. if not outfile then outfile = os.getenv("LUAJIT_PROFILEFILE") end
  285. if outfile then
  286. out = outfile == "-" and stdout or assert(io.open(outfile, "w"))
  287. else
  288. out = stdout
  289. end
  290. prof_start(mode or "f")
  291. end
  292. -- Public module functions.
  293. return {
  294. start = start, -- For -j command line option.
  295. stop = prof_finish
  296. }