Meh I'll figure out submodules later
This commit is contained in:
parent
4ca9d44a90
commit
8cb281f436
352 changed files with 66107 additions and 0 deletions
|
|
@ -0,0 +1,252 @@
|
|||
--[[ Copyright (c) 2018-2020, Charles Mallah ]]
|
||||
-- Released with MIT License
|
||||
--
|
||||
-- Originally link:
|
||||
-- https://github.com/charlesmallah/lua-profiler
|
||||
--
|
||||
-- Hopefully will add some better neovim stuff in the future.
|
||||
-- Shoutout to @clason for finding this.
|
||||
|
||||
|
||||
---------------------------------------|
|
||||
--- Configuration
|
||||
--
|
||||
---------------------------------------|
|
||||
|
||||
local PROFILER_FILENAME = "lua/telescope/profile/lua_profiler.lua" -- Location and name of profiler (to remove itself from reports);
|
||||
-- e.g. if this is in a 'tool' folder, rename this as: "tool/profiler.lua"
|
||||
|
||||
local EMPTY_TIME = "0.0000" -- Detect empty time, replace with tag below
|
||||
local emptyToThis = "~"
|
||||
|
||||
local fileWidth = 75
|
||||
local funcWidth = 22
|
||||
local lineWidth = 6
|
||||
local timeWidth = 7
|
||||
local relaWidth = 6
|
||||
local callWidth = 4
|
||||
|
||||
local reportSaved = " > Report saved to"
|
||||
local formatOutputHeader = "| %-"..fileWidth.."s: %-"..funcWidth.."s: %-"..lineWidth.."s: %-"..timeWidth.."s: %-"..relaWidth.."s: %-"..callWidth.."s|\n"
|
||||
local formatOutputTitle = "%-"..fileWidth.."."..fileWidth.."s: %-"..funcWidth.."."..funcWidth.."s: %-"..lineWidth.."s" -- File / Function / Line count
|
||||
local formatOutput = "| %s: %-"..timeWidth.."s: %-"..relaWidth.."s: %-"..callWidth.."s|\n" -- Time / Relative / Called
|
||||
local formatTotalTime = "TOTAL TIME = %f s\n"
|
||||
local formatFunLine = "%"..(lineWidth - 2).."i"
|
||||
local formatFunTime = "%04.4f"
|
||||
local formatFunRelative = "%03.1f"
|
||||
local formatFunCount = "%"..(callWidth - 1).."i"
|
||||
local formatHeader = string.format(formatOutputHeader, "FILE", "FUNCTION", "LINE", "TIME", "%", "#")
|
||||
|
||||
|
||||
---------------------------------------|
|
||||
--- Locals
|
||||
--
|
||||
---------------------------------------|
|
||||
|
||||
local module = {}
|
||||
|
||||
local getTime = os.clock
|
||||
local string = string
|
||||
local debug = debug
|
||||
local table = table
|
||||
|
||||
local TABL_REPORT_CACHE = {}
|
||||
local TABL_REPORTS = {}
|
||||
local reportCount = 0
|
||||
local startTime = 0
|
||||
local stopTime = 0
|
||||
|
||||
local printFun = nil
|
||||
local verbosePrint = false
|
||||
|
||||
local function functionReport(information)
|
||||
local src = information.short_src
|
||||
if src == nil then
|
||||
src = "<C>"
|
||||
elseif string.sub(src, #src - 3, #src) == ".lua" then
|
||||
src = string.sub(src, 1, #src - 4)
|
||||
end
|
||||
|
||||
local name = information.name
|
||||
if name == nil then
|
||||
name = "Anon"
|
||||
elseif string.sub(name, #name - 1, #name) == "_l" then
|
||||
name = string.sub(name, 1, #name - 2)
|
||||
end
|
||||
|
||||
local title = string.format(formatOutputTitle,
|
||||
src, name,
|
||||
string.format(formatFunLine, information.linedefined or 0))
|
||||
|
||||
local funcReport = TABL_REPORT_CACHE[title]
|
||||
if not funcReport then
|
||||
funcReport = {
|
||||
title = string.format(formatOutputTitle,
|
||||
src, name,
|
||||
string.format(formatFunLine, information.linedefined or 0)),
|
||||
count = 0,
|
||||
timer = 0,
|
||||
}
|
||||
TABL_REPORT_CACHE[title] = funcReport
|
||||
reportCount = reportCount + 1
|
||||
TABL_REPORTS[reportCount] = funcReport
|
||||
end
|
||||
|
||||
return funcReport
|
||||
end
|
||||
|
||||
local onDebugHook = function(hookType)
|
||||
local information = debug.getinfo(2, "nS")
|
||||
if hookType == "call" then
|
||||
local funcReport = functionReport(information)
|
||||
funcReport.callTime = getTime()
|
||||
funcReport.count = funcReport.count + 1
|
||||
elseif hookType == "return" then
|
||||
local funcReport = functionReport(information)
|
||||
if funcReport.callTime and funcReport.count > 0 then
|
||||
funcReport.timer = funcReport.timer + (getTime() - funcReport.callTime)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function charRepetition(n, character)
|
||||
local s = ""
|
||||
character = character or " "
|
||||
for _ = 1, n do
|
||||
s = s..character
|
||||
end
|
||||
return s
|
||||
end
|
||||
|
||||
local function singleSearchReturn(str, search)
|
||||
for _ in string.gmatch(str, search) do
|
||||
do return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local divider = charRepetition(#formatHeader - 1, "-").."\n"
|
||||
|
||||
|
||||
---------------------------------------|
|
||||
--- Functions
|
||||
--
|
||||
---------------------------------------|
|
||||
|
||||
--- Attach a print function to the profiler, to receive a single string parameter
|
||||
--
|
||||
function module.attachPrintFunction(fn, verbose)
|
||||
printFun = fn
|
||||
if verbose ~= nil then
|
||||
verbosePrint = verbose
|
||||
end
|
||||
end
|
||||
|
||||
---
|
||||
--
|
||||
function module.start()
|
||||
TABL_REPORT_CACHE = {}
|
||||
TABL_REPORTS = {}
|
||||
reportCount = 0
|
||||
startTime = getTime()
|
||||
stopTime = nil
|
||||
debug.sethook(onDebugHook, "cr", 0)
|
||||
end
|
||||
|
||||
---
|
||||
--
|
||||
function module.stop()
|
||||
stopTime = getTime()
|
||||
debug.sethook()
|
||||
end
|
||||
|
||||
--- Writes the profile report to file
|
||||
--
|
||||
function module.report(filename)
|
||||
if stopTime == nil then
|
||||
module.stop()
|
||||
end
|
||||
|
||||
if reportCount > 0 then
|
||||
filename = filename or "profiler.log"
|
||||
table.sort(TABL_REPORTS, function(a, b) return a.timer > b.timer end)
|
||||
local file = io.open(filename, "w+")
|
||||
|
||||
if reportCount > 0 then
|
||||
local divide = false
|
||||
local totalTime = stopTime - startTime
|
||||
local totalTimeOutput = " > "..string.format(formatTotalTime, totalTime)
|
||||
|
||||
file:write(totalTimeOutput)
|
||||
if printFun ~= nil then
|
||||
printFun(totalTimeOutput)
|
||||
end
|
||||
|
||||
file:write("\n"..divider)
|
||||
file:write(formatHeader)
|
||||
file:write(divider)
|
||||
|
||||
for i = 1, reportCount do
|
||||
local funcReport = TABL_REPORTS[i]
|
||||
|
||||
if funcReport.count > 0 and funcReport.timer <= totalTime then
|
||||
local printThis = true
|
||||
|
||||
if PROFILER_FILENAME ~= "" then
|
||||
if singleSearchReturn(funcReport.title, PROFILER_FILENAME) then
|
||||
printThis = false
|
||||
end
|
||||
end
|
||||
|
||||
-- Remove line if not needed
|
||||
if printThis == true then
|
||||
if singleSearchReturn(funcReport.title, "[[C]]") then
|
||||
printThis = false
|
||||
end
|
||||
end
|
||||
|
||||
if printThis == true then
|
||||
local count = string.format(formatFunCount, funcReport.count)
|
||||
local timer = string.format(formatFunTime, funcReport.timer)
|
||||
local relTime = string.format(formatFunRelative, (funcReport.timer / totalTime) * 100)
|
||||
if divide == false and timer == EMPTY_TIME then
|
||||
file:write(divider)
|
||||
divide = true
|
||||
end
|
||||
|
||||
-- Replace
|
||||
if timer == EMPTY_TIME then
|
||||
timer = emptyToThis
|
||||
relTime = emptyToThis
|
||||
end
|
||||
|
||||
-- Build final line
|
||||
local outputLine = string.format(formatOutput, funcReport.title, timer, relTime, count)
|
||||
file:write(outputLine)
|
||||
|
||||
-- This is a verbose print to the printFun, however maybe make this smaller for on screen debug?
|
||||
if printFun ~= nil and verbosePrint == true then
|
||||
printFun(outputLine)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
file:write(divider)
|
||||
|
||||
end
|
||||
|
||||
file:close()
|
||||
|
||||
if printFun ~= nil then
|
||||
printFun(reportSaved.."'"..filename.."'")
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- End
|
||||
--
|
||||
return module
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,312 @@
|
|||
----------------------------------------------------------------------------
|
||||
-- LuaJIT profiler.
|
||||
--
|
||||
-- Copyright (C) 2005-2021 Mike Pall. All rights reserved.
|
||||
-- Released under the MIT license. See Copyright Notice in luajit.h
|
||||
----------------------------------------------------------------------------
|
||||
--
|
||||
-- This module is a simple command line interface to the built-in
|
||||
-- low-overhead profiler of LuaJIT.
|
||||
--
|
||||
-- The lower-level API of the profiler is accessible via the "jit.profile"
|
||||
-- module or the luaJIT_profile_* C API.
|
||||
--
|
||||
-- Example usage:
|
||||
--
|
||||
-- luajit -jp myapp.lua
|
||||
-- luajit -jp=s myapp.lua
|
||||
-- luajit -jp=-s myapp.lua
|
||||
-- luajit -jp=vl myapp.lua
|
||||
-- luajit -jp=G,profile.txt myapp.lua
|
||||
--
|
||||
-- The following dump features are available:
|
||||
--
|
||||
-- f Stack dump: function name, otherwise module:line. Default mode.
|
||||
-- F Stack dump: ditto, but always prepend module.
|
||||
-- l Stack dump: module:line.
|
||||
-- <number> stack dump depth (callee < caller). Default: 1.
|
||||
-- -<number> Inverse stack dump depth (caller > callee).
|
||||
-- s Split stack dump after first stack level. Implies abs(depth) >= 2.
|
||||
-- p Show full path for module names.
|
||||
-- v Show VM states. Can be combined with stack dumps, e.g. vf or fv.
|
||||
-- z Show zones. Can be combined with stack dumps, e.g. zf or fz.
|
||||
-- r Show raw sample counts. Default: show percentages.
|
||||
-- a Annotate excerpts from source code files.
|
||||
-- A Annotate complete source code files.
|
||||
-- G Produce raw output suitable for graphical tools (e.g. flame graphs).
|
||||
-- m<number> Minimum sample percentage to be shown. Default: 3.
|
||||
-- i<number> Sampling interval in milliseconds. Default: 10.
|
||||
--
|
||||
----------------------------------------------------------------------------
|
||||
|
||||
-- Cache some library functions and objects.
|
||||
local jit = require("jit")
|
||||
assert(20100 <= jit.version_num and jit.version_num <= 20199, "LuaJIT core/library version mismatch: " .. jit.version)
|
||||
local profile = require("jit.profile")
|
||||
local vmdef = require("jit.vmdef")
|
||||
local math = math
|
||||
local pairs, ipairs, tonumber, floor = pairs, ipairs, tonumber, math.floor
|
||||
local sort, format = table.sort, string.format
|
||||
local stdout = io.stdout
|
||||
local zone -- Load jit.zone module on demand.
|
||||
|
||||
-- Output file handle.
|
||||
local out
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
local prof_ud
|
||||
local prof_states, prof_split, prof_min, prof_raw, prof_fmt, prof_depth
|
||||
local prof_ann, prof_count1, prof_count2, prof_samples
|
||||
|
||||
local map_vmmode = {
|
||||
N = "Compiled",
|
||||
I = "Interpreted",
|
||||
C = "C code",
|
||||
G = "Garbage Collector",
|
||||
J = "JIT Compiler",
|
||||
}
|
||||
|
||||
-- Profiler callback.
|
||||
local function prof_cb(th, samples, vmmode)
|
||||
prof_samples = prof_samples + samples
|
||||
local key_stack, key_stack2, key_state
|
||||
-- Collect keys for sample.
|
||||
if prof_states then
|
||||
if prof_states == "v" then
|
||||
key_state = map_vmmode[vmmode] or vmmode
|
||||
else
|
||||
key_state = zone:get() or "(none)"
|
||||
end
|
||||
end
|
||||
if prof_fmt then
|
||||
key_stack = profile.dumpstack(th, prof_fmt, prof_depth)
|
||||
key_stack = key_stack:gsub("%[builtin#(%d+)%]", function(x)
|
||||
return vmdef.ffnames[tonumber(x)]
|
||||
end)
|
||||
if prof_split == 2 then
|
||||
local k1, k2 = key_stack:match("(.-) [<>] (.*)")
|
||||
if k2 then key_stack, key_stack2 = k1, k2 end
|
||||
elseif prof_split == 3 then
|
||||
key_stack2 = profile.dumpstack(th, "l", 1)
|
||||
end
|
||||
end
|
||||
-- Order keys.
|
||||
local k1, k2
|
||||
if prof_split == 1 then
|
||||
if key_state then
|
||||
k1 = key_state
|
||||
if key_stack then k2 = key_stack end
|
||||
end
|
||||
elseif key_stack then
|
||||
k1 = key_stack
|
||||
if key_stack2 then k2 = key_stack2 elseif key_state then k2 = key_state end
|
||||
end
|
||||
-- Coalesce samples in one or two levels.
|
||||
if k1 then
|
||||
local t1 = prof_count1
|
||||
t1[k1] = (t1[k1] or 0) + samples
|
||||
if k2 then
|
||||
local t2 = prof_count2
|
||||
local t3 = t2[k1]
|
||||
if not t3 then t3 = {}; t2[k1] = t3 end
|
||||
t3[k2] = (t3[k2] or 0) + samples
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
-- Show top N list.
|
||||
local function prof_top(count1, count2, samples, indent)
|
||||
local t, n = {}, 0
|
||||
for k in pairs(count1) do
|
||||
n = n + 1
|
||||
t[n] = k
|
||||
end
|
||||
sort(t, function(a, b) return count1[a] > count1[b] end)
|
||||
for i=1,n do
|
||||
local k = t[i]
|
||||
local v = count1[k]
|
||||
local pct = floor(v*100/samples + 0.5)
|
||||
if pct < prof_min then break end
|
||||
if not prof_raw then
|
||||
out:write(format("%s%2d%% %s\n", indent, pct, k))
|
||||
elseif prof_raw == "r" then
|
||||
out:write(format("%s%5d %s\n", indent, v, k))
|
||||
else
|
||||
out:write(format("%s %d\n", k, v))
|
||||
end
|
||||
if count2 then
|
||||
local r = count2[k]
|
||||
if r then
|
||||
prof_top(r, nil, v, (prof_split == 3 or prof_split == 1) and " -- " or
|
||||
(prof_depth < 0 and " -> " or " <- "))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Annotate source code
|
||||
local function prof_annotate(count1, samples)
|
||||
local files = {}
|
||||
local ms = 0
|
||||
for k, v in pairs(count1) do
|
||||
local pct = floor(v*100/samples + 0.5)
|
||||
ms = math.max(ms, v)
|
||||
if pct >= prof_min then
|
||||
local file, line = k:match("^(.*):(%d+)$")
|
||||
if not file then file = k; line = 0 end
|
||||
local fl = files[file]
|
||||
if not fl then fl = {}; files[file] = fl; files[#files+1] = file end
|
||||
line = tonumber(line)
|
||||
fl[line] = prof_raw and v or pct
|
||||
end
|
||||
end
|
||||
sort(files)
|
||||
local fmtv, fmtn = " %3d%% | %s\n", " | %s\n"
|
||||
if prof_raw then
|
||||
local n = math.max(5, math.ceil(math.log10(ms)))
|
||||
fmtv = "%"..n.."d | %s\n"
|
||||
fmtn = (" "):rep(n).." | %s\n"
|
||||
end
|
||||
local ann = prof_ann
|
||||
for _, file in ipairs(files) do
|
||||
local f0 = file:byte()
|
||||
if f0 == 40 or f0 == 91 then
|
||||
out:write(format("\n====== %s ======\n[Cannot annotate non-file]\n", file))
|
||||
break
|
||||
end
|
||||
local fp, err = io.open(file)
|
||||
if not fp then
|
||||
out:write(format("====== ERROR: %s: %s\n", file, err))
|
||||
break
|
||||
end
|
||||
out:write(format("\n====== %s ======\n", file))
|
||||
local fl = files[file]
|
||||
local n, show = 1, false
|
||||
if ann ~= 0 then
|
||||
for i=1,ann do
|
||||
if fl[i] then show = true; out:write("@@ 1 @@\n"); break end
|
||||
end
|
||||
end
|
||||
for line in fp:lines() do
|
||||
if line:byte() == 27 then
|
||||
out:write("[Cannot annotate bytecode file]\n")
|
||||
break
|
||||
end
|
||||
local v = fl[n]
|
||||
if ann ~= 0 then
|
||||
local v2 = fl[n+ann]
|
||||
if show then
|
||||
if v2 then show = n+ann elseif v then show = n
|
||||
elseif show+ann < n then show = false end
|
||||
elseif v2 then
|
||||
show = n+ann
|
||||
out:write(format("@@ %d @@\n", n))
|
||||
end
|
||||
if not show then goto next end
|
||||
end
|
||||
if v then
|
||||
out:write(format(fmtv, v, line))
|
||||
else
|
||||
out:write(format(fmtn, line))
|
||||
end
|
||||
::next::
|
||||
n = n + 1
|
||||
end
|
||||
fp:close()
|
||||
end
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
-- Finish profiling and dump result.
|
||||
local function prof_finish()
|
||||
if prof_ud then
|
||||
profile.stop()
|
||||
local samples = prof_samples
|
||||
if samples == 0 then
|
||||
if prof_raw ~= true then out:write("[No samples collected]\n") end
|
||||
return
|
||||
end
|
||||
if prof_ann then
|
||||
prof_annotate(prof_count1, samples)
|
||||
else
|
||||
prof_top(prof_count1, prof_count2, samples, "")
|
||||
end
|
||||
prof_count1 = nil
|
||||
prof_count2 = nil
|
||||
prof_ud = nil
|
||||
if out ~= stdout then out:close() end
|
||||
end
|
||||
end
|
||||
|
||||
-- Start profiling.
|
||||
local function prof_start(mode)
|
||||
local interval = ""
|
||||
mode = mode:gsub("i%d*", function(s) interval = s; return "" end)
|
||||
prof_min = 3
|
||||
mode = mode:gsub("m(%d+)", function(s) prof_min = tonumber(s); return "" end)
|
||||
prof_depth = 1
|
||||
mode = mode:gsub("%-?%d+", function(s) prof_depth = tonumber(s); return "" end)
|
||||
local m = {}
|
||||
for c in mode:gmatch(".") do m[c] = c end
|
||||
prof_states = m.z or m.v
|
||||
if prof_states == "z" then zone = require("jit.zone") end
|
||||
local scope = m.l or m.f or m.F or (prof_states and "" or "f")
|
||||
local flags = (m.p or "")
|
||||
prof_raw = m.r
|
||||
if m.s then
|
||||
prof_split = 2
|
||||
if prof_depth == -1 or m["-"] then prof_depth = -2
|
||||
elseif prof_depth == 1 then prof_depth = 2 end
|
||||
elseif mode:find("[fF].*l") then
|
||||
scope = "l"
|
||||
prof_split = 3
|
||||
else
|
||||
prof_split = (scope == "" or mode:find("[zv].*[lfF]")) and 1 or 0
|
||||
end
|
||||
prof_ann = m.A and 0 or (m.a and 3)
|
||||
if prof_ann then
|
||||
scope = "l"
|
||||
prof_fmt = "pl"
|
||||
prof_split = 0
|
||||
prof_depth = 1
|
||||
elseif m.G and scope ~= "" then
|
||||
prof_fmt = flags..scope.."Z;"
|
||||
prof_depth = -100
|
||||
prof_raw = true
|
||||
prof_min = 0
|
||||
elseif scope == "" then
|
||||
prof_fmt = false
|
||||
else
|
||||
local sc = prof_split == 3 and m.f or m.F or scope
|
||||
prof_fmt = flags..sc..(prof_depth >= 0 and "Z < " or "Z > ")
|
||||
end
|
||||
prof_count1 = {}
|
||||
prof_count2 = {}
|
||||
prof_samples = 0
|
||||
profile.start(scope:lower()..interval, prof_cb)
|
||||
prof_ud = newproxy(true)
|
||||
getmetatable(prof_ud).__gc = prof_finish
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
local function start(mode, outfile)
|
||||
if not outfile then outfile = os.getenv("LUAJIT_PROFILEFILE") end
|
||||
if outfile then
|
||||
out = outfile == "-" and stdout or assert(io.open(outfile, "w"))
|
||||
else
|
||||
out = stdout
|
||||
end
|
||||
prof_start(mode or "f")
|
||||
end
|
||||
|
||||
-- Public module functions.
|
||||
return {
|
||||
start = start, -- For -j command line option.
|
||||
stop = prof_finish
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue