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