1
0
mirror of https://github.com/SpaceVim/SpaceVim.git synced 2025-02-03 15:00:06 +08:00
SpaceVim/bundle/plenary.nvim/lua/plenary/profile/lua_profiler.lua
2022-05-16 22:20:10 +08:00

253 lines
6.5 KiB
Lua

--[[ 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