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