util-deb.lmt /size: 11 Kb    last modification: 2023-12-21 09:44
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                    if line < 0 then
211                        line = 0
212                    end
213                 -- if name = "a" then
214                 --     -- weird name
215                 -- end
216                    dataset[#dataset+1] = { real, total, count, name, source, line }
217                end
218            end
219        end
220    end
221    sort(dataset,function(a,b)
222        if a[1] == b[1] then
223            if a[2] == b[2] then
224                if a[3] == b[3] then
225                    if a[4] == b[4] then
226                        if a[5] == b[5] then
227                            return a[6] < b[6]
228                        else
229                            return a[5] < b[5]
230                        end
231                    else
232                        return a[4] < b[4]
233                    end
234                else
235                    return b[3] < a[3]
236                end
237            else
238                return b[2] < a[2]
239            end
240        else
241            return b[1] < a[1]
242        end
243    end)
244    if length > 50 then
245        length = 50
246    end
247    local fmt = string.formatters["%4.9k s  %3.3k %%  %4.9k s  %3.3k %%  %8i #  %-" .. length .. "s  %4i  %s"]
248    for i=1,#dataset do
249        local data   = dataset[i]
250        local real   = data[1]
251        local total  = data[2]
252        local count  = data[3]
253        local name   = data[4]
254        local source = data[5]
255        local line   = data[6]
256        calls     = calls + count
257        functions = functions + 1
258        name = gsub(name,"%s+"," ")
259        if #name > length then
260            name = sub(name,1,length)
261        end
262        printer(fmt(seconds(total),100*total/totaltime,seconds(real),100*real/realtime,count,name,line,source))
263    end
264    printer("")
265    printer(format("functions : %i", functions))
266    printer(format("calls     : %i", calls))
267    printer(format("overhead  : %f", seconds(overhead/1000)))
268
269 -- table.save("luatex-profile.lua",names)
270end
271
272local function getdebug()
273    if sethook and getinfo then
274        return
275    end
276    if not debug then
277        local okay
278        okay, debug = pcall(require,"debug")
279    end
280    if type(debug) ~= "table" then
281        return
282    end
283    getinfo = debug.getinfo
284    sethook = debug.sethook
285    if type(getinfo) ~= "function" then
286        getinfo = nil
287    end
288    if type(sethook) ~= "function" then
289        sethook = nil
290    end
291end
292
293function debugger.savestats(filename,threshold)
294    local f = io.open(filename,'w')
295    if f then
296        debugger.showstats(function(str) f:write(str,"\n") end,threshold)
297        f:close()
298    end
299end
300
301function debugger.enable()
302    getdebug()
303    if sethook and getinfo and nesting == 0 then
304        running = true
305        if initialize then
306            initialize()
307        end
308        sethook(hook,"cr")
309--         sethook(hook_c,"c")
310--         sethook(hook_r,"r")
311        local function dummy() end
312        local t = ticks()
313        for i=1,dummycalls do
314            dummy()
315        end
316        overhead = ticks() - t
317    end
318    if nesting > 0 then
319        nesting = nesting + 1
320    end
321end
322
323function debugger.disable()
324    if nesting > 0 then
325        nesting = nesting - 1
326    end
327    if sethook and getinfo and nesting == 0 then
328        sethook()
329    end
330end
331
332-- debugger.enable()
333--
334-- print(math.sin(1*.5))
335-- print(math.sin(1*.5))
336-- print(math.sin(1*.5))
337-- print(math.sin(1*.5))
338-- print(math.sin(1*.5))
339--
340-- debugger.disable()
341--
342-- print("")
343-- debugger.showstats()
344-- print("")
345-- debugger.showstats(print,3)
346--
347-- from the lua book:
348
349local function showtraceback(rep) -- from lua site / adapted
350    getdebug()
351    if getinfo then
352        local level = 2 -- we don't want this function to be reported
353        local reporter = rep or report
354        while true do
355            local info = getinfo(level, "Sl")
356            if not info then
357                break
358            elseif info.what == "C" then
359                reporter("%2i : %s",level-1,"C function")
360            else
361                reporter("%2i : %s : %s",level-1,info.short_src,info.currentline)
362            end
363            level = level + 1
364        end
365    end
366end
367
368debugger.showtraceback = showtraceback
369-- debug.showtraceback = showtraceback
370
371-- showtraceback()
372
373if luac then
374
375    local show, dump = luac.print, string.dump
376
377    function luac.inspect(v)
378        if type(v) == "function" then
379            local ok, str = xpcall(dump,function() end,v)
380            if ok then
381                v = str
382            end
383        end
384        if type(v) == "string" then
385            show(v,true)
386        else
387            print(v)
388        end
389    end
390
391end
392