util-deb.lmt /size: 11 Kb    last modification: 2025-02-21 11:03
1if not modules then modules = { } end modules ['util-deb'] = {
2    version   = 1.001,
3    comment   = "companion to luat-lib.mkiv",
4    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5    copyright = "PRAGMA ADE / ConTeXt Development Team",
6    license   = "see context related readme files"
7}
8
9-- the <anonymous> tag is kind of generic and used for functions that are not
10-- bound to a variable, like node.new, node.copy etc (contrary to for instance
11-- node.hasattribute which is bound to a hasattribute local variable in mkiv)
12
13local type, next, tostring, tonumber, xpcall, print = type, next, tostring, tonumber, xpcall, print
14local format, find, sub, gsub = string.format, string.find, string.sub, string.gsub
15local insert, remove, sort = table.insert, table.remove, table.sort
16local setmetatableindex = table.setmetatableindex
17
18utilities          = utilities or { }
19local debugger     = utilities.debugger or { }
20utilities.debugger = debugger
21
22local report       = logs.reporter("debugger")
23
24local ticks        = os.gettimeofday or os.clock
25local seconds      = function(n) return n or 0 end
26local overhead     = 0
27local dummycalls   = 10*1000
28local nesting      = 0
29local names        = { }
30
31local function initialize()
32    ticks      = lua.getpreciseticks
33    seconds    = lua.getpreciseseconds
34    initialize = false
35end
36
37setmetatableindex(names,function(t,name)
38    local v = setmetatableindex(function(t,source)
39        local v = setmetatableindex(function(t,line)
40         -- local v = { total = 0, count = 0, nesting = 0, ticks = 0 }
41            local v = { 0, 0, 0, 0 }
42            t[line] = v
43            return v
44        end)
45        t[source] = v
46        return v
47    end)
48    t[name] = v
49    return v
50end)
51
52local getinfo = nil
53local sethook = nil
54
55-- local function hook(where)
56--    local f = getinfo(2,"nS")
57--     if f then
58--         local source = f.short_src
59--         if not source then
60--             return
61--         end
62--         local line = f.linedefined or 0
63--         local name = f.name
64--         if not name then
65--             local what = f.what
66--             if what == "C" then
67--                 name = "<anonymous>"
68--             else
69--                 name = f.namewhat or what or "<unknown>"
70--             end
71--         end
72--         local data = names[name][source][line]
73--         if where == "call" then
74--             local nesting = data.nesting
75--             if nesting == 0 then
76--                 data.count = data.count + 1
77--                 insert(data,ticks())
78--                 data.nesting = 1
79--             else
80--                 data.nesting = nesting + 1
81--             end
82--         elseif where == "return" then
83--             local nesting = data.nesting
84--             if nesting == 1 then
85--                 local t = remove(data)
86--                 if t then
87--                     data.total = data.total + ticks() - t
88--                 end
89--                 data.nesting = 0
90--             else
91--                 data.nesting = nesting - 1
92--             end
93--         end
94--     end
95-- end
96
97local getdebuginfo = lua.getdebuginfo
98
99-- local function hook(where) -- make two hooks
100--     local name, source, line = getdebuginfo()
101--     if name then
102--         local data = names[name][source][line]
103--         if where == "call" then
104--             local nesting = data.nesting
105--             if nesting == 0 then
106--                 data.count = data.count + 1
107--              -- insert(data,ticks())
108--                 data.nesting = 1
109--                 data.ticks = ticks()
110--             else
111--                 data.nesting = nesting + 1
112--             end
113--         elseif where == "return" then
114--             local nesting = data.nesting
115--             if nesting == 1 then
116--              -- local t = remove(data)
117--                 local t = data.ticks
118--                 if t then
119--                     data.total = data.total + ticks() - t
120--                 end
121--                 data.nesting = 0
122--             elseif nesting > 0 then
123--                 data.nesting = nesting - 1
124--             end
125--         elseif where == "tail call" then
126--             local nesting = data.nesting
127--             if nesting == 1 then
128--              -- local t = remove(data)
129--                 local t = data.ticks
130--                 if t then
131--                     data.total = data.total + ticks() - t
132--                 end
133--                 data.nesting = 0
134--             elseif nesting > 0 then
135--                 data.nesting = nesting - 1
136--             end
137--         end
138--     end
139-- end
140
141local function hook(where) -- make two hooks
142    local name, source, line = getdebuginfo()
143    if name then
144        local data = names[name][source][line]
145        if where == "call" then
146            local nesting = data[3]
147            if nesting == 0 then
148                data[2] = data[2] + 1
149                data[3] = 1
150                data[4] = ticks()
151            else
152                data[3] = nesting + 1
153            end
154     -- elseif where == "return" then
155        else
156            local nesting = data[3]
157            if nesting == 1 then
158                local t = data[4]
159                if t then
160                    data[1] = data[1] + ticks() - t
161                end
162                data[3] = 0
163            elseif nesting > 0 then
164                data[3] = nesting - 1
165            end
166     -- elseif where == "tail call" then
167     --     local nesting = data[3]
168     --     if nesting == 1 then
169     --         local t = data[4]
170     --         if t then
171     --             data[4] = data[4] + ticks() - t
172     --         end
173     --         data[3] = 0
174     --     elseif nesting > 0 then
175     --         data[3] = nesting - 1
176     --     end
177        end
178    end
179end
180
181function debugger.showstats(printer,threshold)
182    local printer   = printer or report
183    local calls     = 0
184    local functions = 0
185    local dataset   = { }
186    local length    = 0
187    local realtime  = 0
188    local totaltime = 0
189    local threshold = threshold or 0
190    for name, sources in next, names do
191        for source, lines in next, sources do
192            for line, data in next, lines do
193             -- local count = data.count
194                local count = data[2]
195                if count > threshold then
196                    if #name > length then
197                        length = #name
198                    end
199                 -- local total = data.total
200                    local total = data[1]
201                    local real  = total
202                    if real > 0 then
203                        real = total - (count * overhead / dummycalls)
204                        if real < 0 then
205                            real = 0
206                        end
207                        realtime = realtime + real
208                    end
209                    totaltime = totaltime + total
210                    dataset[#dataset+1] = { real, total, count, name, source, line < 0 and 0 or line }
211                end
212            end
213        end
214    end
215    sort(dataset,function(a,b)
216        if a[1] == b[1] then
217            if a[2] == b[2] then
218                if a[3] == b[3] then
219                    if a[4] == b[4] then
220                        if a[5] == b[5] then
221                            return a[6] < b[6]
222                        else
223                            return a[5] < b[5]
224                        end
225                    else
226                        return a[4] < b[4]
227                    end
228                else
229                    return b[3] < a[3]
230                end
231            else
232                return b[2] < a[2]
233            end
234        else
235            return b[1] < a[1]
236        end
237    end)
238    if length > 50 then
239        length = 50
240    end
241    local fmt = string.formatters["%4.9k s  %3.3k %%  %4.9k s  %3.3k %%  %8i #  %-" .. length .. "s  %4i  %s"]
242    for i=1,#dataset do
243        local data   = dataset[i]
244        local real   = data[1]
245        local total  = data[2]
246        local count  = data[3]
247        local name   = data[4]
248        local source = data[5]
249        local line   = data[6]
250        calls     = calls + count
251        functions = functions + 1
252        name = gsub(name,"%s+"," ")
253        if #name > length then
254            name = sub(name,1,length)
255        end
256        printer(fmt(seconds(total),100*total/totaltime,seconds(real),100*real/realtime,count,name,line,source))
257    end
258    printer("")
259    printer(format("functions : %i", functions))
260    printer(format("calls     : %i", calls))
261    printer(format("overhead  : %f", seconds(overhead/1000)))
262
263 -- table.save("luatex-profile.lua",names)
264end
265
266local function getdebug()
267    if sethook and getinfo then
268        return
269    end
270    if not debug then
271        local okay
272        okay, debug = pcall(require,"debug")
273    end
274    if type(debug) ~= "table" then
275        return
276    end
277    getinfo = debug.getinfo
278    sethook = debug.sethook
279    if type(getinfo) ~= "function" then
280        getinfo = nil
281    end
282    if type(sethook) ~= "function" then
283        sethook = nil
284    end
285end
286
287function debugger.savestats(filename,threshold)
288    local f = io.open(filename,'w')
289    if f then
290        debugger.showstats(function(str) f:write(str,"\n") end,threshold)
291        f:close()
292    end
293end
294
295function debugger.enable()
296    getdebug()
297    if sethook and getinfo and nesting == 0 then
298        running = true
299        if initialize then
300            initialize()
301        end
302        sethook(hook,"cr")
303--         sethook(hook_c,"c")
304--         sethook(hook_r,"r")
305        local function dummy() end
306        local t = ticks()
307        for i=1,dummycalls do
308            dummy()
309        end
310        overhead = ticks() - t
311    end
312    if nesting > 0 then
313        nesting = nesting + 1
314    end
315end
316
317function debugger.disable()
318    if nesting > 0 then
319        nesting = nesting - 1
320    end
321    if sethook and getinfo and nesting == 0 then
322        sethook()
323    end
324end
325
326-- debugger.enable()
327--
328-- print(math.sin(1*.5))
329-- print(math.sin(1*.5))
330-- print(math.sin(1*.5))
331-- print(math.sin(1*.5))
332-- print(math.sin(1*.5))
333--
334-- debugger.disable()
335--
336-- print("")
337-- debugger.showstats()
338-- print("")
339-- debugger.showstats(print,3)
340--
341-- from the lua book:
342
343local function showtraceback(rep) -- from lua site / adapted
344    getdebug()
345    if getinfo then
346        local level = 2 -- we don't want this function to be reported
347        local reporter = rep or report
348        while true do
349            local info = getinfo(level, "Sl")
350            if not info then
351                break
352            elseif info.what == "C" then
353                reporter("%2i : %s",level-1,"C function")
354            else
355                reporter("%2i : %s : %s",level-1,info.short_src,info.currentline)
356            end
357            level = level + 1
358        end
359    end
360end
361
362debugger.showtraceback = showtraceback
363-- debug.showtraceback = showtraceback
364
365-- showtraceback()
366
367if luac then
368
369    local show, dump = luac.print, string.dump
370
371    function luac.inspect(v)
372        if type(v) == "function" then
373            local ok, str = xpcall(dump,function() end,v)
374            if ok then
375                v = str
376            end
377        end
378        if type(v) == "string" then
379            show(v,true)
380        else
381            print(v)
382        end
383    end
384
385end
386