p.lua 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. ----------------------------------------------------------------------------
  2. -- LuaJIT profiler.
  3. --
  4. -- Copyright (C) 2005-2025 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. local profile = require("jit.profile")
  44. local vmdef = require("jit.vmdef")
  45. local math = math
  46. local pairs, ipairs, tonumber, floor = pairs, ipairs, tonumber, math.floor
  47. local sort, format = table.sort, string.format
  48. local stdout = io.stdout
  49. local zone -- Load jit.zone module on demand.
  50. -- Output file handle.
  51. local out
  52. ------------------------------------------------------------------------------
  53. local prof_ud
  54. local prof_states, prof_split, prof_min, prof_raw, prof_fmt, prof_depth
  55. local prof_ann, prof_count1, prof_count2, prof_samples
  56. local map_vmmode = {
  57. N = "Compiled",
  58. I = "Interpreted",
  59. C = "C code",
  60. G = "Garbage Collector",
  61. J = "JIT Compiler",
  62. }
  63. -- Profiler callback.
  64. local function prof_cb(th, samples, vmmode)
  65. prof_samples = prof_samples + samples
  66. local key_stack, key_stack2, key_state
  67. -- Collect keys for sample.
  68. if prof_states then
  69. if prof_states == "v" then
  70. key_state = map_vmmode[vmmode] or vmmode
  71. else
  72. key_state = zone:get() or "(none)"
  73. end
  74. end
  75. if prof_fmt then
  76. key_stack = profile.dumpstack(th, prof_fmt, prof_depth)
  77. key_stack = key_stack:gsub("%[builtin#(%d+)%]", function(x)
  78. return vmdef.ffnames[tonumber(x)]
  79. end)
  80. if prof_split == 2 then
  81. local k1, k2 = key_stack:match("(.-) [<>] (.*)")
  82. if k2 then key_stack, key_stack2 = k1, k2 end
  83. elseif prof_split == 3 then
  84. key_stack2 = profile.dumpstack(th, "l", 1)
  85. end
  86. end
  87. -- Order keys.
  88. local k1, k2
  89. if prof_split == 1 then
  90. if key_state then
  91. k1 = key_state
  92. if key_stack then k2 = key_stack end
  93. end
  94. elseif key_stack then
  95. k1 = key_stack
  96. if key_stack2 then k2 = key_stack2 elseif key_state then k2 = key_state end
  97. end
  98. -- Coalesce samples in one or two levels.
  99. if k1 then
  100. local t1 = prof_count1
  101. t1[k1] = (t1[k1] or 0) + samples
  102. if k2 then
  103. local t2 = prof_count2
  104. local t3 = t2[k1]
  105. if not t3 then t3 = {}; t2[k1] = t3 end
  106. t3[k2] = (t3[k2] or 0) + samples
  107. end
  108. end
  109. end
  110. ------------------------------------------------------------------------------
  111. -- Show top N list.
  112. local function prof_top(count1, count2, samples, indent)
  113. local t, n = {}, 0
  114. for k in pairs(count1) do
  115. n = n + 1
  116. t[n] = k
  117. end
  118. sort(t, function(a, b) return count1[a] > count1[b] end)
  119. for i=1,n do
  120. local k = t[i]
  121. local v = count1[k]
  122. local pct = floor(v*100/samples + 0.5)
  123. if pct < prof_min then break end
  124. if not prof_raw then
  125. out:write(format("%s%2d%% %s\n", indent, pct, k))
  126. elseif prof_raw == "r" then
  127. out:write(format("%s%5d %s\n", indent, v, k))
  128. else
  129. out:write(format("%s %d\n", k, v))
  130. end
  131. if count2 then
  132. local r = count2[k]
  133. if r then
  134. prof_top(r, nil, v, (prof_split == 3 or prof_split == 1) and " -- " or
  135. (prof_depth < 0 and " -> " or " <- "))
  136. end
  137. end
  138. end
  139. end
  140. -- Annotate source code
  141. local function prof_annotate(count1, samples)
  142. local files = {}
  143. local ms = 0
  144. for k, v in pairs(count1) do
  145. local pct = floor(v*100/samples + 0.5)
  146. ms = math.max(ms, v)
  147. if pct >= prof_min then
  148. local file, line = k:match("^(.*):(%d+)$")
  149. if not file then file = k; line = 0 end
  150. local fl = files[file]
  151. if not fl then fl = {}; files[file] = fl; files[#files+1] = file end
  152. line = tonumber(line)
  153. fl[line] = prof_raw and v or pct
  154. end
  155. end
  156. sort(files)
  157. local fmtv, fmtn = " %3d%% | %s\n", " | %s\n"
  158. if prof_raw then
  159. local n = math.max(5, math.ceil(math.log10(ms)))
  160. fmtv = "%"..n.."d | %s\n"
  161. fmtn = (" "):rep(n).." | %s\n"
  162. end
  163. local ann = prof_ann
  164. for _, file in ipairs(files) do
  165. local f0 = file:byte()
  166. if f0 == 40 or f0 == 91 then
  167. out:write(format("\n====== %s ======\n[Cannot annotate non-file]\n", file))
  168. break
  169. end
  170. local fp, err = io.open(file)
  171. if not fp then
  172. out:write(format("====== ERROR: %s: %s\n", file, err))
  173. break
  174. end
  175. out:write(format("\n====== %s ======\n", file))
  176. local fl = files[file]
  177. local n, show = 1, false
  178. if ann ~= 0 then
  179. for i=1,ann do
  180. if fl[i] then show = true; out:write("@@ 1 @@\n"); break end
  181. end
  182. end
  183. for line in fp:lines() do
  184. if line:byte() == 27 then
  185. out:write("[Cannot annotate bytecode file]\n")
  186. break
  187. end
  188. local v = fl[n]
  189. if ann ~= 0 then
  190. local v2 = fl[n+ann]
  191. if show then
  192. if v2 then show = n+ann elseif v then show = n
  193. elseif show+ann < n then show = false end
  194. elseif v2 then
  195. show = n+ann
  196. out:write(format("@@ %d @@\n", n))
  197. end
  198. if not show then goto next end
  199. end
  200. if v then
  201. out:write(format(fmtv, v, line))
  202. else
  203. out:write(format(fmtn, line))
  204. end
  205. ::next::
  206. n = n + 1
  207. end
  208. fp:close()
  209. end
  210. end
  211. ------------------------------------------------------------------------------
  212. -- Finish profiling and dump result.
  213. local function prof_finish()
  214. if prof_ud then
  215. profile.stop()
  216. local samples = prof_samples
  217. if samples == 0 then
  218. if prof_raw ~= true then out:write("[No samples collected]\n") end
  219. elseif prof_ann then
  220. prof_annotate(prof_count1, samples)
  221. else
  222. prof_top(prof_count1, prof_count2, samples, "")
  223. end
  224. prof_count1 = nil
  225. prof_count2 = nil
  226. prof_ud = nil
  227. if out ~= stdout then out:close() end
  228. end
  229. end
  230. -- Start profiling.
  231. local function prof_start(mode)
  232. local interval = ""
  233. mode = mode:gsub("i%d*", function(s) interval = s; return "" end)
  234. prof_min = 3
  235. mode = mode:gsub("m(%d+)", function(s) prof_min = tonumber(s); return "" end)
  236. prof_depth = 1
  237. mode = mode:gsub("%-?%d+", function(s) prof_depth = tonumber(s); return "" end)
  238. local m = {}
  239. for c in mode:gmatch(".") do m[c] = c end
  240. prof_states = m.z or m.v
  241. if prof_states == "z" then zone = require("jit.zone") end
  242. local scope = m.l or m.f or m.F or (prof_states and "" or "f")
  243. local flags = (m.p or "")
  244. prof_raw = m.r
  245. if m.s then
  246. prof_split = 2
  247. if prof_depth == -1 or m["-"] then prof_depth = -2
  248. elseif prof_depth == 1 then prof_depth = 2 end
  249. elseif mode:find("[fF].*l") then
  250. scope = "l"
  251. prof_split = 3
  252. else
  253. prof_split = (scope == "" or mode:find("[zv].*[lfF]")) and 1 or 0
  254. end
  255. prof_ann = m.A and 0 or (m.a and 3)
  256. if prof_ann then
  257. scope = "l"
  258. prof_fmt = "pl"
  259. prof_split = 0
  260. prof_depth = 1
  261. elseif m.G and scope ~= "" then
  262. prof_fmt = flags..scope.."Z;"
  263. prof_depth = -100
  264. prof_raw = true
  265. prof_min = 0
  266. elseif scope == "" then
  267. prof_fmt = false
  268. else
  269. local sc = prof_split == 3 and m.f or m.F or scope
  270. prof_fmt = flags..sc..(prof_depth >= 0 and "Z < " or "Z > ")
  271. end
  272. prof_count1 = {}
  273. prof_count2 = {}
  274. prof_samples = 0
  275. profile.start(scope:lower()..interval, prof_cb)
  276. prof_ud = newproxy(true)
  277. getmetatable(prof_ud).__gc = prof_finish
  278. end
  279. ------------------------------------------------------------------------------
  280. local function start(mode, outfile)
  281. if not outfile then outfile = os.getenv("LUAJIT_PROFILEFILE") end
  282. if outfile then
  283. out = outfile == "-" and stdout or assert(io.open(outfile, "w"))
  284. else
  285. out = stdout
  286. end
  287. prof_start(mode or "f")
  288. end
  289. -- Public module functions.
  290. return {
  291. start = start, -- For -j command line option.
  292. stop = prof_finish
  293. }