Skip to content

Commit

Permalink
split out profiler, add serializer, and refactor collapse stack
Browse files Browse the repository at this point in the history
  • Loading branch information
jackbackrack committed Jan 9, 2025
1 parent 33085a6 commit 88b579c
Show file tree
Hide file tree
Showing 3 changed files with 334 additions and 248 deletions.
251 changes: 3 additions & 248 deletions core/core.stanza
Original file line number Diff line number Diff line change
Expand Up @@ -188,11 +188,11 @@ protected lostanza deftype StackTraceTableEntry :
lbl: ptr<?>
record: StackTraceRecord

lostanza deftype FunctionInfoTable :
protected lostanza deftype FunctionInfoTable :
length: long
entries: FunctionInfoEntry ...

lostanza deftype FunctionInfoEntry :
protected lostanza deftype FunctionInfoEntry :
package: ptr<byte>
name: ptr<byte>
file: ptr<byte>
Expand Down Expand Up @@ -11219,50 +11219,7 @@ public defmulti name (t:TypeObject) -> String
;====================== Profiling ===========================
;============================================================

public defstruct ProfileInfo :
id : Int
package : String
name : String
info : FileInfo
count : Long

public defstruct ProfileResult :
info : Tuple<ProfileInfo>
id-traces : Tuple<Tuple<Int>>

defmethod print (o:OutputStream, c:ProfileInfo) :
print(o, "ProfileInfo(%_, %_, %_, %_)" % [id(c), package(c), name(c), info(c), count(c)])

defn namestring (p:ProfileInfo) -> String :
to-string("%_/%_ %_ (%_)" % [package(p), name(p), info(p), id(p)])

;Fast stack trace record lookup
lostanza defn stack-trace-record (ret:long, trace-table:ptr<StackTraceTable>, trace-table-table:ref<HashTable<Long,Int>>) -> ptr<StackTraceRecord> :
val idx = get?(trace-table-table, new Long{ret}, new Int{-1})
if idx.value == -1 :
return null
else :
val entry = addr(trace-table.entries[idx.value])
return addr(entry.record)

;Fast stack trace record table construction
lostanza defn build-stack-trace-record-table (trace-table:ptr<StackTraceTable>) -> ref<HashTable<Long,Int>> :
val tab = HashTable<Long,Int>()
for (var i:int = 0, i < trace-table.length, i = i + 1) :
val entry = addr(trace-table.entries[i])
set(tab, new Long{entry.lbl as long}, new Int{i})
return tab

#if-defined(BOOTSTRAP) :

defn collect-profiling () -> ProfileResult : ProfileResult([], [])
defn collect-coverage () -> Tuple<ProfileInfo> : []
public defn clear-profiling () : false
public defn clear-coverage () : false
defn do-start-sample-profiling (num-usecs:Int) : false
defn do-stop-sample-profiling () : false

#else :
#if-not-defined(BOOTSTRAP) :

;Called from app to record stack trace as return addresses in buffer
;Demarcate end of each coroutine stack with -2 and end trace with -1
Expand Down Expand Up @@ -11293,208 +11250,6 @@ lostanza defn build-stack-trace-record-table (trace-table:ptr<StackTraceTable>)
[vms.profile-flag] = 0L
return false

;Remove empty stack traces
defn prune-traces (traces:Vector<List<Int>>) -> List<List<Int>> :
to-list $ seq(unique, filter({ not empty?(_) }, traces))

;Count all functions references in stack traces where traces have function ids in them
defn count-traces (len:Int, traces:Vector<Tuple<Int>>) -> Array<Long> :
val counts = Array<Long>(len, 0L)
for trace in traces do :
for id in trace do :
counts[id] = counts[id] + 1L
counts

;Split stack trace buffer using delimiter
defn split-buffer (buffer:List<Long>, delimiter:Long) -> List<List<Long>> :
let loop (i:Int = 0, buffers:List<List<Long>> = List(), ids:List<Long> = List()) :
if i < length(buffer) :
val id = buffer[i]
if id == delimiter :
loop(i + 1, cons(reverse(ids), buffers), List())
else :
loop(i + 1, buffers, cons(id, ids))
else :
reverse(buffers)

;Split stack trace buffer using delimiter
defn split-buffer (buffer:Tuple<Long>, delimiter:Long) -> Vector<Tuple<Long>> :
val res = Vector<Tuple<Long>>()
val chunk = Vector<Long>()
for id in buffer do :
if id == delimiter :
add(res, to-tuple(chunk))
clear(chunk)
else :
add(chunk, id)
res

;Reconsititue full stack traces by splitting up chunks and ordering them properly
defn build-stack-traces (buffer:Tuple<Long>) -> Tuple<Tuple<Long>> :
val buffers = split-buffer(buffer, -1L)
;same as:
; to-tuple $ for trace in buffers seq :
; to-tuple $ reverse $ to-list $ cat-all $ seq(reverse, split-buffer(trace, -2L))
to-tuple $ for (trace in buffers, k in 0 to false) seq :
val res = Vector<Long>()
val chunks = split-buffer(trace, -2L) ;split full stack trace into coroutine stack traces
for i in (length(chunks) - 1) through 0 by -1 do :
val chunk = chunks[i]
for j in 0 to length(chunk) do :
add(res, chunk[j])
to-tuple(res)

;Create function id traces and function info results after profiling
lostanza defn collect-profiling () -> ref<ProfileResult> :
val vms:ptr<VMState> = call-prim flush-vm()
val info:ptr<FunctionInfoTable> = vms.function-info
val counters:ptr<long> = vms.function-counters
val buffer:ptr<LSLongVector> = vms.profile-buffer
val tab = build-stack-trace-record-table(vms.stack-trace-table) ;For fast lookup of return addresses
val buffer* = Vector<Long>()

for (var i:long = 0, i < buffer.length, i = i + 1) :
add(buffer*, new Long{buffer.items[i]})

val traces = build-stack-traces(to-tuple(buffer*)) ;Order and concatenate stack trace chunks

;Lookup stack trace records and then translate into function ids
val id-traces = Vector<Tuple<Int>>()
for (var i:long = 0, i < length(traces).value, i = i + 1) :
val trace = get(traces, new Int{i as int})
val trace* = Vector<Int>()
for (var j:long = 0, j < length(trace).value, j = j + 1) :
val ret = get(trace, new Int{j as int})
val entry = stack-trace-record(ret.value, vms.stack-trace-table, tab)
if entry != null :
val id = entry.function
if id != -1L :
add(trace*, new Int{id as int})
add(id-traces, to-tuple(trace*))

val len:long = info.length
val counts = count-traces(new Int{len as int}, id-traces)
val infos = Vector<ProfileInfo>()

;Collect all FunctionInfos corresponding to ids that occurred on stack at least once
for (var i:long = 0L, i < len, i = i + 1) :
val entry = info.entries[i]
val fi = FileInfo(String(entry.file), new Int{entry.line}, new Int{entry.column})
val count = get(counts, new Int{i as int})
val package = String(entry.package)
val name = String(entry.name)
val ci = ProfileInfo(new Int{i as int}, package, name, fi, count)
add(infos, ci)

return ProfileResult(to-tuple(infos), to-tuple(id-traces))

;Collect all coverage information from all functions
lostanza defn collect-coverage () -> ref<Vector<ProfileInfo>> :
val vms:ptr<VMState> = call-prim flush-vm()
val info:ptr<FunctionInfoTable> = vms.function-info
val counters:ptr<long> = vms.function-counters
val len:long = info.length
val res = Vector<ProfileInfo>()
for (var i:long = 0L, i < len, i = i + 1) :
val entry = info.entries[i]
val fi = FileInfo(String(entry.file), new Int{entry.line}, new Int{entry.column})
val count = new Long{counters[i]}
val package = String(entry.package)
val name = String(entry.name)
val ci = ProfileInfo(new Int{i as int}, package, name, fi, count)
add(res, ci)
return res

;Clear profiling state ready for next run
public lostanza defn clear-profiling () -> ref<False> :
val vms:ptr<VMState> = call-prim flush-vm()
[vms.profile-flag] = 0L
vms.profile-buffer.length = 0
return false

;Clear coverage state ready for next run
public lostanza defn clear-coverage () -> ref<False> :
val vms:ptr<VMState> = call-prim flush-vm()
val info:ptr<FunctionInfoTable> = vms.function-info
val counters:ptr<long> = vms.function-counters
val len:long = info.length
call-c clib/memset(counters, 0, len << 3L)
return false

var PROFILING-MSECS:Int = 0

;Start profiling by initializing state and starting profile thread
extern start_sample_profiling: (int, long, ptr<long>, ptr<long>) -> int
lostanza defn do-start-sample-profiling (msecs:ref<Int>) -> ref<True|False> :
val vms:ptr<VMState> = call-prim flush-vm()
[vms.profile-flag] = 0L
vms.profile-buffer.length = 0
PROFILING-MSECS = msecs
val res = call-c start_sample_profiling(msecs.value, vms.function-info.length, vms.profile-flag, vms.function-counters)
if res == 0 :
return false
else :
return true

;Stop profiling by clearing state and stopping profile thread
extern stop_sample_profiling: () -> int
lostanza defn do-stop-sample-profiling () -> ref<Int> :
val vms:ptr<VMState> = call-prim flush-vm()
[vms.profile-flag] = 0L
call-c stop_sample_profiling()
return PROFILING-MSECS

;dumps coverage results to given file
; must be stanza compiled with -coverage flag as well
public defn dump-coverage (filename:String) :
val file = FileOutputStream(filename)
try :
val coverage = collect-coverage()
for elt in reverse(to-list(lazy-qsort(count, coverage))) do :
if count(elt) > 0L :
println(file, "%_: %_/%_ in %_ (%_)" % [count(elt), package(elt), name(elt), info(elt), id(elt)])
finally: close(file)

;Coverage wrapper to be called with within which
; runs thunk with coverage counting on and then dumps coverage report to file
public defn coverage (f:() -> ?, filename:String) :
clear-coverage()
f()
dump-coverage(filename)

; starts profiling sampling at msecs period
public defn start-profiling (msecs:Int) :
clear-profiling()
do-start-sample-profiling(msecs)
false

; dumps profiling results to given file
; must be stanza compiled with -profile flag as well
public defn stop-profiling (filename:String) :
val msecs = do-stop-sample-profiling()
val profiling = collect-profiling()
if not empty?(info(profiling)) :
; within (file) = with-output-file(filename) :
val file = FileOutputStream(filename)
try :
println(file, msecs)
println(file, "(")
do(println{file, _}, id-traces(profiling))
println(file, ")")
println(file, "(")
for (elt in info(profiling), i in 0 to false) do :
if count(elt) > 0L :
println(file, "(%_ %~)" % [id(elt), string-join $ [package(elt) "/" name(elt) ":" line(info(elt))]])
println(file, ")")
finally: close(file)
false

;Profiling wrapper to be called with within
public defn profiling (f:() -> ?, msecs:Int, filename:String) :
start-profiling(msecs)
f()
stop-profiling(filename)

;############################################################
;################## Math Package ############################
;############################################################
Expand Down
Loading

0 comments on commit 88b579c

Please sign in to comment.