-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathAppleCake.lua
404 lines (353 loc) · 12.1 KB
/
AppleCake.lua
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
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
local PATH = (...):match("(.-)[^%.]+$")
local dirPATH = PATH:gsub("%.","/") -- for thread.lua to be read as a file
--[[
AppleCake Profiling for Love2D
https://github.com/EngineerSmith/AppleCake
Docs can be found in the README.md
License is MIT, details can be found in the LICENSE file
Written by https://github.com/EngineerSmith or
engineersmith_4628 on Discord
You can view the profiling data visually by going to
chrome:\\tracing and dropping in the json created.
Check README.md#Viewing-AppleCake for more details
]]
local lt = love.thread or require("love.thread")
local threadConfig = require(PATH.."threadConfig")
local info = lt.getChannel(threadConfig.infoID)
local _err= error
local error = function(msg)
_err("Error thrown by AppleCake: "..tostring(msg))
end
local isActive -- Used to return the same table as first requested
local threadStartIndex -- Used for sorting the thread; sorts by thread started
local function setActiveMode(active)
if isActive == nil then
info:performAtomic(function()
local i = info:pop()
if i then
isActive = i.active
threadStartIndex = i.threadIndex
else
if active == nil then
active = true
end
isActive = active
threadStartIndex = 0
end
info:push({ active = isActive, threadIndex = threadStartIndex + 1})
end)
end
end
local AppleCakeEnableLevels = {
["none"] = 0,
["profiles"] = 1,
["mark"] = 2,
["counter"] = 4,
["all"] = 7,
}
local emptyFunc = function() end
local emptyProfile = {stop=emptyFunc, args={}}
local emptyCounter = { }
local AppleCakeRelease = {
isDebug = false, -- Deprecated
isActive = false, -- Replaced isDebug
enableLevels = AppleCakeEnableLevels,
beginSession = emptyFunc,
endSession = emptyFunc,
enabled = emptyFunc,
profile = function() return emptyProfile end,
stopProfile = emptyFunc,
profileFunc = function() return emptyProfile end,
mark = emptyFunc,
counter = function() return emptyCounter end,
countMemory = function() return emptyCounter end,
setName = emptyFunc,
setThreadName = emptyFunc,
setThreadSortIndex = emptyFunc,
-- Added for those who want to convert from jprof
jprof = {
push = emptyFunc,
pop = emptyFunc,
popAll = emptyFunc,
write = emptyFunc,
enabled = emptyFunc,
connect = emptyFunc,
netFlush = emptyFunc,
},
-- Deprecated
markMemory = emptyFunc,
_stopProfile = emptyFunc,
}
local AppleCake
return function(active)
setActiveMode(active)
if not isActive then
return AppleCakeRelease
end
if AppleCake then -- return appleCake if it's already been made
return AppleCake
end
AppleCake = {
isDebug = true, -- Deprecated
isActive = true, -- Replaced isDebug
enableLevels = AppleCakeEnableLevels,
}
local threadID = threadStartIndex
local commandTbl = { threadID }
if not love.timer then
require("love.timer")
end
local _getTime = love.timer.getTime
local getTime = function() -- Return time in microseconds
return _getTime() * 1e+6
end
local thread
local outStream = love.thread.getChannel(threadConfig.outStreamID)
local useBuffer, buffer = pcall(require, "string.buffer") -- Added in love11.4, jit2.1
local buf_enc, _options
if useBuffer then
_options = { dict = threadConfig.dict }
buf_enc = buffer.new(_options)
buffer = nil
end
local bufferMode = false -- set with AppleCake.setBuffer
local commandBuffer, commandBufferIndex = { buffer = true }, 1
local pushCommand = function(command, arg, force)
commandTbl.command = command
commandTbl[2] = arg
if not bufferMode or force then
if useBuffer then
outStream:push(buf_enc:reset():encode(commandTbl):get())
else
outStream:push(commandTbl)
end
else -- useBuffer must be true
commandBuffer[commandBufferIndex] = buf_enc:reset():encode(commandTbl):get()
commandBufferIndex = commandBufferIndex + 1
end
commandTbl[2] = nil
end
AppleCake.beginSession = function(filepath, name)
if thread then
AppleCake.endSession()
else
thread = lt.newThread(dirPATH.."thread.lua")
end
pushCommand("open", filepath, true)
thread:start(PATH, threadID)
if not name then
name = love.filesystem.getIdentity()
end
AppleCake.setName(name)
AppleCake.setThreadSortIndex(threadStartIndex)
end
AppleCake.endSession = function()
if thread and thread:isRunning() then
AppleCake.flush()
pushCommand("close", nil, true)
thread:wait()
elseif not thread then
error("The session can only be closed within the thread that started it.")
end
end
AppleCake.setBuffer = function(enabled)
AppleCake.flush()
bufferMode = enabled == true
if bufferMode and not useBuffer then
error("You can only use the buffer in Love11.4+, jit2.1. If you're using a package manager, sometimes it doesn't include the correct version of jit and you should use the appImage.")
end
end
AppleCake.flush = function()
if commandBufferIndex ~= 1 then
outStream:push(buf_enc:reset():encode(commandBuffer):get())
commandBuffer, commandBufferIndex = { buffer = true }, 1
end
end
local profileEnabled = true
local markEnabled = true
local counterEnabled = true
--[[ Disable logging in an area of code e.g.
AppleCake.enable(AppleCake.enableLevels["none"])
AppleCake.enable(AppleCake.enableLevels["all"])
-- only allow profiling and marks, disable counters
AppleCake.enable(AppleCake.enableLevels["profile"] + AppleCake.enableLevels["mark"])
AppleCake.enable(AppleCake.enableLevels["all"] - AppleCake.enableLevels["counters"])
-- Following also works:
AppleCake.enable("none")
AppleCake.enable("all")
-- Note, it's better to use require("libs.appleCake")(false) to stop logging all together.
This is useful for temp disabling in a section of code
]]
AppleCake.enable = function(level)
if type(level) ~= "number" then
level = level and level:lower() or "all"
level = AppleCakeEnableLevels[level] or AppleCakeEnableLevels["all"]
end
profileEnabled = level/AppleCakeEnableLevels["profile"] % 2 == 1
markEnabled = level/AppleCakeEnableLevels["mark"] % 2 == 1
counterEnabled = level/AppleCakeEnableLevels["counter"] % 2 == 1
end
-- Profile a section of code
AppleCake.profile = function(name, args, profile)
if not profileEnabled then return emptyProfile end
if profile then
profile.name = name
profile.args = args
profile._stopped = false
profile.start = getTime()
return profile
else
return {
stop = AppleCake.stopProfile,
name = name,
args = args,
start = getTime(),
}
end
end
AppleCake.stopProfile = function(profile)
profile.finish = getTime()
if not profileEnabled then return end
if profile._stopped then
error("Attempted to stop and write profile more than once. If attempting to reuse profile, ensure it is passed back into the function which created it to reset it's use")
end
profile.stop = nil -- Can't push functions
profile._stopped = nil -- decrease number of fields
pushCommand("writeProfile", profile)
profile.stop = AppleCake.stopProfile
profile._stopped = true
end
-- Profile time taken within a function
AppleCake.profileFunc = function(args, profile)
if not profileEnabled then return emptyProfile end
if profile then
return AppleCake.profile(profile.name, args, profile)
end
local info = debug.getinfo(2, "fnS")
if info then
local name
if info.name then
name = info.name
elseif info.func then -- Attempt to create a name from memory address
name = tostring(info.func):sub(10)
else
error("Could not generate name for this function")
end
if info.short_src then
name = name.."@"..info.short_src..(info.linedefined and "#"..info.linedefined or "")
end
return AppleCake.profile(name, args)
end
end
-- Mark an event at a point in time
-- Scope: "p": process, make a line across the entire process
-- "t": thread, make a line across the current thread
AppleCake.mark = function(name, scope, args)
if not markEnabled then return end
if scope == nil or (scope ~= "p" and scope ~= "t") then
scope = "p"
end
pushCommand("writeMark", {
name = name,
args = args,
scope = scope,
start = getTime(),
})
end
-- Track variable over time
AppleCake.counter = function(name, args, counter)
if not counterEnabled then return end
if counter then
counter.name = name
counter.args = args
counter.start = getTime()
else
counter = {
name = name,
args = args,
start = getTime(),
}
end
pushCommand("writeCounter", counter)
return counter
end
local memArg, mem = { }, nil
AppleCake.countMemory = function(option)
if not counterEnabled then return end
if option == "megabyte" then
memArg.megabytes = collectgarbage("count")/1024
elseif option == "byte" then
memArg.bytes = collectgarbage("count")*1024
else --kilobyte
memArg.kilobytes = collectgarbage("count")
end
mem = AppleCake.counter("Memory usage", memArg, mem)
end
-- Set the name of the process, usually your projects name, or identity
AppleCake.setName = function(name)
if type(name) == "string" then
pushCommand("writeMetadata", {
process_name = name,
thread_name = name,
}, true)
end
end
AppleCake.setThreadName = function(name)
if type(name) == "string" then
pushCommand("writeMetadata", {
thread_name = name,
}, true)
end
end
-- By default this is the order the threads are created
AppleCake.setThreadSortIndex = function(index)
if type(index) == "number" then
assert(index >= 0, "Given index must be greater than or equal to 0")
pushCommand("writeMetadata", {
thread_sort_index = index,
}, true)
end
end
-- jprof functions to allow others to easily intergate profiling into thier already jprof filled code
AppleCake.jprof = { }
local jprof = AppleCake.jprof
-- Custom functions
-- NOTE: You must at least call appleCake.beginSession() or jprof.START() otherwise it will not be able to write out using these functions
jprof.START = AppleCake.beginSession
jprof.COUNTMEMORY = AppleCake.countMemory -- function to count memory, as we don't do it in push
-- classic jprof functions
local stack, anno = { }, { }
jprof.push = function(name, annotation)
anno[1] = annotation -- to avoid creating a new table for each annotation
table.insert(stack, AppleCake.profile(name, anno, stack[name]))
end
jprof.pop = function(name)
local head = stack[#stack]
if name then
assert(name == head.name, ("(appleCake.jprof) Top of zone stack, does not match the zone passed to appleCake.jprof.pop ('%s', on top: '%s')!"):format(name, stack[#stack])) -- Error message taken from jprof
end
head:stop()
stack[#stack] = nil
end
jprof.popAll = function()
for i=#stack, 1, -1 do
if not stack[i]._stopped then
stack[i]:stop()
end
end
stack = { }
end
jprof.write = AppleCake.beginSession -- will wait for previous session to close, before reopening
jprof.enabled = function(enabled)
AppleCake.enable(enabled and AppleCakeEnableLevels["all"] or AppleCakeEnableLevels["none"])
end
local notSupported = function() error("Sorry this function is not supported in AppleCake right now") end
jprof.connect = notSupported
jprof.netFlush = notSupported
--[[ Deprecated functions ]]
-- _stopProfile deprecated with stopProfile
AppleCake._stopProfile = AppleCake.stopProfile
-- markMemory deprecated with countMemory
AppleCake.markMemory = AppleCake.countMemory
return AppleCake
end