vanilla-wow-addons – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 office 1 
2 local mod = klhtm
3 local me = { }
4 mod.diag = me
5  
6 --[[
7 KTM_Diagnostic.lua
8  
9 This module monitors computer performance and how it is affected by the rest of the mod. It is useful to check for
10 methods that are using excessive amounts of memory or processor time.
11  
12 Whenever the KTM_Core.lua is about to call a module's .onevent() or .onupdate() methods, it instead sends the method to mod.diag.logmethodcall(module, methodtype). The time taken in milliseconds and memory used in kilobytes is recorded. For each module that has a .onevent() or .onupdate() method, a separate entry is kept, which records the total value,average rate, maximum rate over 5 seconds and rate in the last 5 seconds.
13  
14 To print out the memory data, run the command "/ktm test memory"; for timing data, "/ktm test time".
15  
16 Times are recorded in one second intervals. So whenever a method is called, the .current value of the relevant dataset is incremented. Then every second, there is a collation, where .current is added to .history, and .total and .recordinterval are updated. This is done in me.onupdate().
17  
18 ]]
19  
20  
21 me.lastcollation = GetTime() -- value of GetTime(). Happens once a second.
22 me.datalogstart = GetTime() -- when we started logging times. For the "average rate" value.
23  
24 --[[
25 timing / memory data set. Each module-method combination has a separate set. e.g. there is one for the .onevent() function of the .combat module.
26 ]]
27 me.createnewdataset = function()
28  
29 return
30 {
31 ["total"] = 0,
32 ["history"] = {0, 0, 0, 0, 0}, -- values for the last 5 seconds
33 ["historylength"] = 5,
34 ["current"] = 0, -- working value for the current second
35 ["recordinterval"] = 0, -- maximum sum of .history
36 }
37  
38 end
39  
40 --[[
41 All the data we keep is in this table. At startup, we can only be sure the "total" sets will exist - we don't know which modules have a .onupdate or .onevent method. As soon as those methods are called for the first time, we will add a data set for them.
42 ]]
43 me.data =
44 {
45 ["memory"] =
46 {
47 ["onupdate"] =
48 {
49 ["total"] = me.createnewdataset(),
50 },
51 ["onevent"] =
52 {
53 ["total"] = me.createnewdataset(),
54 },
55 ["total"] = me.createnewdataset(),
56 },
57 ["time"] =
58 {
59 ["onupdate"] =
60 {
61 ["total"] = me.createnewdataset(),
62 },
63 ["onevent"] =
64 {
65 ["total"] = me.createnewdataset(),
66 },
67 ["total"] = me.createnewdataset(),
68 }
69 }
70  
71 --[[
72 mod.diag.logmethodcall(module, calltype)
73 Runs the special Core.lua methods, and logs the timing and memory usage.
74 <module> is a string, the key for a module, e.g. "combat", "string", "netin", etc.
75 <calltype> is either "onupdate" or "onevent".
76 ]]
77 me.logmethodcall = function(module, calltype)
78  
79 local method = mod[module][calltype]
80 local memory
81 local time
82  
83 local manualstart = GetTime()
84 memory, time = me.getfunctionstats(method)
85 local manualtime = math.floor(0.5 + (GetTime() - manualstart) * 1000)
86  
87 -- Scrub out bullshit values! > 100ms = bs, > 10 ms = should be traced. < 0 kb = bs (gc).
88 -- Sometimes debugprofile gives completely spasticated values for unknown reasons. Also we might get a negative
89 -- value for memory if a garbage collection occurs.
90 if time > 100 or time < 0 or math.abs(time - manualtime) > 5 then
91 time = 0
92 elseif time > 10 then
93 if mod.out.checktrace("info", me, "timing") then
94 mod.out.printtrace(string.format("Time from %s %s is %s ms (manual says %s ms) (memory was %s).", module, calltype, me.formatdecimal(time), manualtime, memory))
95 end
96 end
97  
98 if memory < 0 then
99 memory = 0
100 end
101  
102 me.adddatapoint("time", calltype, module, time)
103 me.adddatapoint("memory", calltype, module, memory)
104  
105 end
106  
107 --[[
108 me.getfunctionstats(method)
109 Runs the the function <method>, and records how much time was taken, and memory used.
110 Returns: <memory, in whole kilobytes>, <time, in fractional milliseconds>.
111 ]]
112 me.getfunctionstats = function(method)
113  
114 local memorystart = gcinfo()
115 local timestart = debugprofilestop()
116  
117 method()
118  
119 local timetaken = (debugprofilestop() - timestart)
120 local memoryused = gcinfo() - memorystart
121  
122 return memoryused, timetaken
123 end
124  
125  
126 --[[
127 me.adddatapoint(datatype, calltype, module, value)
128 Adds <value> to the .current property of the relevant dataset. Also adds it to any parent sets, e.g. "total".
129 <datatype> is "memory" or "time"
130 <calltype> is "onupdate" or "onevent"
131 <module> is "combat", "string", "data", etc.
132 ]]
133 me.adddatapoint = function(datatype, calltype, module, value)
134  
135 if me.data[datatype][calltype][module] == nil then
136 me.data[datatype][calltype][module] = me.createnewdataset()
137 end
138  
139 me.data[datatype][calltype][module].current = me.data[datatype][calltype][module].current + value
140 me.data[datatype][calltype].total.current = me.data[datatype][calltype].total.current + value
141 me.data[datatype].total.current = me.data[datatype].total.current + value
142  
143 end
144  
145 --[[
146 Special Core.lua method. If this "second" has ended (i.e. it's been at least 1 second since the last collation),
147 then run a collation.
148 ]]
149 me.onupdate = function()
150  
151 local timenow = GetTime()
152  
153 -- update at most once a second
154 if timenow < me.lastcollation + 1.0 then
155 return
156 end
157  
158 me.collatetable(me.data, timenow - me.lastcollation)
159 me.lastcollation = timenow
160 end
161  
162 --[[
163 me.collatetable(data, period)
164 Collating a dataset is to finalise the ".current" value, add it to history, update total, etc.
165 This is a recursive method. <data> might be a data set (base case), or it might be a tree containing datasets (note the
166 structure of me.data). If it is a tree, then me.collatetable will be called on any subtrees inside it.
167 <period> = time in seconds this collation is over. It will be at least 1, and close to 1, but not exactly 1. Since we want
168 to record the rate, e.g. KB/sec, we have to divide by this value.
169 ]]
170 me.collatetable = function(data, period)
171  
172 if data.current == nil then
173 -- this guy is a table, with subtables. do the subtables.
174  
175 local value
176 for _, value in data do
177 me.collatetable(value, period)
178 end
179  
180 return
181 end
182  
183 -- this guy is an actual data set
184 local x
185 local interval = 0
186  
187 for x = data.historylength - 1, 1, -1 do
188 data.history[x + 1] = data.history[x]
189 interval = interval + data.history[x]
190 end
191  
192 -- take into account period length (we want / second)
193 data.history[1] = data.current / period
194 interval = interval + data.history[1]
195 data.total = data.total + data.current
196  
197 -- check for new record 5 second burst
198 if interval > data.recordinterval then
199 data.recordinterval = interval
200 end
201  
202 -- reset the current value, it's a new second
203 data.current = 0
204  
205 end
206  
207 --[[
208 mod.diag.printdataset(datatype, units)
209 This is called when a console command "/ktm test time" or "/ktm test memory" is run.
210 <datatype> is "time" or "memory"
211 <datatype> is a description of the units, e.g. "Milliseconds per Second".
212 ]]
213 me.printalldata = function(datatype, units)
214  
215 -- print total, then subcategories, recursively.
216 mod.out.print(string.format("|cff6666ffThis is a listing of |cffffff00%s |cff6666ffusage. Units are |cffffff00%s.|r", datatype, units))
217  
218 local data = me.data[datatype]
219 local key
220 local value
221  
222 -- grand total
223 me.printdataset("|cffff3333Total", data.total)
224  
225 -- onupdates
226 me.printdataset("|cff66ff66OnUpdates", data.onupdate.total)
227  
228 for key, value in data.onupdate do
229 if key ~= "total" then
230 me.printdataset("|cffffff00" .. key, value)
231 end
232 end
233  
234 -- onevents
235 me.printdataset("|cff66ff66OnEvents", data.onevent.total)
236  
237 for key, value in data.onevent do
238 if key ~= "total" then
239 me.printdataset("|cffffff00" .. key, value)
240 end
241 end
242  
243 end
244  
245 --[[
246 me.printdataset(name, set)
247 This prints the values of an individual data set.
248 <name> is the name of the module or category, with colouring.
249 <set> is the actual data set.
250 ]]
251 me.printdataset = function(name, set)
252  
253 local x
254 local last5 = 0
255  
256 for x = 1, 5 do
257 last5 = last5 + set.history[x]
258 end
259  
260 mod.out.print(string.format("%s:|r Total = %s, Avg = %s, Burst = %s, Recent = %s.",
261 name, me.formatdecimal(set.total), me.formatdecimal(set.total / (GetTime() - me.datalogstart)), me.formatdecimal(set.recordinterval / 5), me.formatdecimal(last5 / 5)))
262  
263 end
264  
265 --[[
266 me.formatdecimal(value)
267 Returns a string representation of <value>, including up the the first place after the decimal, if it exists.
268 ]]
269 me.formatdecimal = function(value)
270  
271 if floor(value) == value then
272 return string.format("%d", value)
273  
274 else
275 local base = string.format("%f", value)
276 local dotpoint = string.find(base, "%.")
277 return string.sub(base, 1, dotpoint + 1)
278 end
279 end