1if not modules then modules = { } end modules ['mtx-profile'] = {
2 version = 1.000,
3 comment = "companion to mtxrun.lua",
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
10
11
12
13local match, format, find, gsub = string.match, string.format, string.find, string.gsub
14
15local helpinfo = [[
16<?xml version="1.0"?>
17<application>
18 <metadata>
19 <entry name="name">mtx-profile</entry>
20 <entry name="detail">ConTeXt MkIV LuaTeX Profiler</entry>
21 <entry name="version">1.00</entry>
22 </metadata>
23 <flags>
24 <category name="basic">
25 <subcategory>
26 <flag name="analyze"><short>analyze lua calls</short></flag>
27 <flag name="trace"><short>analyze tex calls</short></flag>
28 </subcategory>
29 </category>
30 </flags>
31</application>
32]]
33
34local application = logs.application {
35 name = "mtx-profile",
36 banner = "ConTeXt MkIV LuaTeX Profiler 1.00",
37 helpinfo = helpinfo,
38}
39
40local report = application.report
41
42scripts = scripts or { }
43scripts.profiler = scripts.profiler or { }
44
45local timethreshold = 0
46local callthreshold = 2500
47local countthreshold = 2500
48
49local functiontemplate = "%12s %03.4f %9i %s"
50local calltemplate = "%9i %s"
51local totaltemplate = "%i internal calls, %i function calls taking %3.4f seconds"
52local thresholdtemplate = "thresholds: %i internal calls, %i function calls, %i seconds"
53
54function scripts.profiler.analyze(filename)
55 local f = io.open(filename)
56 if f then
57 local times, counts, calls = { }, { }, { }
58 local totalruntime, totalcount, totalcalls = 0, 0, 0
59 for line in f:lines() do
60 if not find(line,"__index") and not find(line,"__newindex") then
61 local stacklevel, filename, functionname, linenumber, currentline, localtime, totaltime = match(line,"^(%d+)\t(.-)\t(.-)\t(.-)\t(.-)\t(.-)\t(.-)")
62 if not filename then
63
64 elseif filename == "=[C]" then
65 if not functionname:find("^%(") then
66 calls[functionname] = (calls[functionname] or 0) + 1
67 end
68 else
69 local filename = match(filename,"^@(.*)$")
70 if filename then
71 local fi = times[filename]
72 if not fi then fi = { } times[filename] = fi end
73 fi[functionname] = (fi[functionname] or 0) + tonumber(localtime)
74 counts[functionname] = (counts[functionname] or 0) + 1
75 end
76 end
77 end
78 end
79 f:close()
80 print("")
81 local loaded = { }
82 local sorted = table.sortedkeys(times)
83 for i=1,#sorted do
84 local filename = sorted[i]
85 local functions = times[filename]
86 local sorted = table.sortedkeys(functions)
87 for i=1,#sorted do
88 local functionname = sorted[i]
89 local totaltime = functions[functionname]
90 local count = counts[functionname]
91 totalcount = totalcount + count
92 if totaltime > timethreshold or count > countthreshold then
93 totalruntime = totalruntime + totaltime
94 local functionfile, somenumber = match(functionname,"^@(.+):(.-)$")
95 if functionfile then
96 local number = tonumber(somenumber)
97 if number then
98 if not loaded[functionfile] then
99 loaded[functionfile] = string.splitlines(io.loaddata(functionfile) or "")
100 end
101 functionname = loaded[functionfile][number] or functionname
102 functionname = gsub(functionname,"^%s*","")
103 functionname = gsub(functionname,"%s*%-%-.*$","")
104 functionname = number .. ": " .. functionname
105 end
106 end
107 filename = file.basename(filename)
108 print(format(functiontemplate,filename,totaltime,count,functionname))
109 end
110 end
111 end
112 print("")
113 local sorted = table.sortedkeys(calls)
114 for i=1,#sorted do
115 local call = sorted[i]
116 local n = calls[call]
117 totalcalls = totalcalls + n
118 if n > callthreshold then
119 print(calltemplate:format(n,call))
120 end
121 end
122 print("")
123 print(totaltemplate:format(totalcalls,totalcount,totalruntime))
124 print("")
125 print(thresholdtemplate:format(callthreshold,countthreshold,timethreshold))
126 end
127end
128
129function scripts.profiler.x_analyze(filename)
130 local f = io.open(filename)
131 local calls = { }
132 local lines = 0
133 if f then
134 while true do
135 local line = f:read()
136 if line then
137 lines = lines + 1
138 local c = match(line,"\\([a-zA-Z%!%?@]+) *%->")
139 if c then
140 local cc = calls[c]
141 if not cc then
142 calls[c] = 1
143 else
144 calls[c] = cc + 1
145 end
146 end
147 else
148 break
149 end
150 end
151 f:close()
152 local noc = 0
153 local criterium = 100
154 for name, n in next, calls do
155 if n > criterium then
156 if find(name,"^@@[a-z][a-z]") then
157
158 elseif find(name,"^[cvserft]%!") then
159
160 elseif find(name,"^%?%?[a-z][a-z]$") then
161
162 elseif find(name,"^%!%!") then
163
164 elseif find(name,"^@.+@$") then
165
166 else
167 noc = noc + n
168 print(format("%6i: %s",n,name))
169 end
170 end
171 end
172 print("")
173 print(format("number of lines: %s",lines))
174 print(format("number of calls: %s",noc))
175 print(format("criterium calls: %s",criterium))
176 end
177end
178
179
180
181
182if environment.argument("analyze") then
183 scripts.profiler.analyze(environment.files[1] or "luatex-profile.log")
184elseif environment.argument("trace") then
185 scripts.profiler.analyze(environment.files[1] or "temp.log")
186elseif environment.argument("exporthelp") then
187 application.export(environment.argument("exporthelp"),environment.files[1])
188else
189 application.help()
190end
191 |