summary refs log tree commit diff stats
path: root/lib/system/profiler.nim
diff options
context:
space:
mode:
Diffstat (limited to 'lib/system/profiler.nim')
-rw-r--r--[-rwxr-xr-x]lib/system/profiler.nim142
1 files changed, 92 insertions, 50 deletions
diff --git a/lib/system/profiler.nim b/lib/system/profiler.nim
index 97c7dcfeb..e7eb6ac82 100755..100644
--- a/lib/system/profiler.nim
+++ b/lib/system/profiler.nim
@@ -1,61 +1,103 @@
 #
 #
-#            Nimrod's Runtime Library
-#        (c) Copyright 2008 Andreas Rumpf
+#            Nim's Runtime Library
+#        (c) Copyright 2012 Andreas Rumpf
 #
 #    See the file "copying.txt", included in this
 #    distribution, for details about the copyright.
 #
 
-# This file implements the Nimrod profiler. The profiler needs support by the
-# code generator. 
+# This file implements the Nim profiler. The profiler needs support by the
+# code generator. The idea is to inject the instruction stream
+# with 'nimProfile()' calls. These calls are injected at every loop end
+# (except perhaps loops that have no side-effects). At every Nth call a
+# stack trace is taken. A stack tace is a list of cstrings.
+
+when defined(profiler) and defined(memProfiler):
+  {.error: "profiler and memProfiler cannot be defined at the same time (See Embedded Stack Trace Profiler (ESTP) User Guide) for more details".}
+
+{.push profiler: off.}
+
+const
+  MaxTraceLen = 20 # tracking the last 20 calls is enough
 
 type
-  TProfileData {.compilerproc, final.} = object
-    procname: cstring
-    total: float
+  StackTrace* = object
+    lines*: array[0..MaxTraceLen-1, cstring]
+    files*: array[0..MaxTraceLen-1, cstring]
+  ProfilerHook* = proc (st: StackTrace) {.nimcall.}
+
+proc `[]`*(st: StackTrace, i: int): cstring = st.lines[i]
+
+proc captureStackTrace(f: PFrame, st: var StackTrace) =
+  const
+    firstCalls = 5
+  var
+    it = f
+    i = 0
+    total = 0
+  while it != nil and i <= high(st.lines)-(firstCalls-1):
+    # the (-1) is for the "..." entry
+    st.lines[i] = it.procname
+    st.files[i] = it.filename
+    inc(i)
+    inc(total)
+    it = it.prev
+  var b = it
+  while it != nil:
+    inc(total)
+    it = it.prev
+  for j in 1..total-i-(firstCalls-1):
+    if b != nil: b = b.prev
+  if total != i:
+    st.lines[i] = "..."
+    st.files[i] = "..."
+    inc(i)
+  while b != nil and i <= high(st.lines):
+    st.lines[i] = b.procname
+    st.files[i] = b.filename
+    inc(i)
+    b = b.prev
 
 var
-  profileData {.compilerproc.}: array [0..64*1024-1, TProfileData]
-
-proc sortProfile(a: var array[0..64*1024-1, TProfileData], N: int) = 
-  # we use shellsort here; fast enough and simple
-  var h = 1
-  while true: 
-    h = 3 * h + 1
-    if h > N: break 
-  while true: 
-    h = h div 3
-    for i in countup(h, N - 1): 
-      var v = a[i]
-      var j = i
-      while a[j-h].total <= v.total: 
-        a[j] = a[j-h]
-        j = j-h
-        if j < h: break 
-      a[j] = v
-    if h == 1: break
-
-proc writeProfile() {.noconv.} =
-  const filename = "profile_results"
-  var i = 0
-  var f: TFile
-  var j = 1
-  while openFile(f, filename & $j & ".txt"):
-    closeFile(f)
-    inc(j)
-  if openFile(f, filename & $j & ".txt", fmWrite):
-    var N = 0
-    # we have to compute the actual length of the array:
-    while profileData[N].procname != nil: inc(N)
-    sortProfile(profileData, N)
-    writeln(f, "total running time of each proc" &
-               " (interpret these numbers relatively)")
-    while profileData[i].procname != nil:
-      write(f, profileData[i].procname)
-      write(f, ": ")
-      writeln(f, profileData[i].total)
-      inc(i)
-    closeFile(f)
-
-addQuitProc(writeProfile)
+  profilingRequestedHook*: proc (): bool {.nimcall, gcsafe.}
+    ## set this variable to provide a procedure that implements a profiler in
+    ## user space. See the `nimprof` module for a reference implementation.
+
+when defined(memProfiler):
+  type
+    MemProfilerHook* = proc (st: StackTrace, requestedSize: int) {.nimcall, gcsafe.}
+
+  var
+    profilerHook*: MemProfilerHook
+      ## set this variable to provide a procedure that implements a profiler in
+      ## user space. See the `nimprof` module for a reference implementation.
+
+  proc callProfilerHook(hook: MemProfilerHook, requestedSize: int) =
+    var st: StackTrace
+    captureStackTrace(framePtr, st)
+    hook(st, requestedSize)
+
+  proc nimProfile(requestedSize: int) =
+    if not isNil(profilingRequestedHook) and profilingRequestedHook():
+      callProfilerHook(profilerHook, requestedSize)
+else:
+  var
+    profilerHook*: ProfilerHook
+      ## set this variable to provide a procedure that implements a profiler in
+      ## user space. See the `nimprof` module for a reference implementation.
+
+  proc callProfilerHook(hook: ProfilerHook) {.noinline.} =
+    # 'noinline' so that 'nimProfile' does not perform the stack allocation
+    # in the common case.
+    when not defined(nimdoc):
+      var st: StackTrace
+      captureStackTrace(framePtr, st)
+      hook(st)
+
+  proc nimProfile() =
+    ## This is invoked by the compiler in every loop and on every proc entry!
+    if not isNil(profilingRequestedHook) and profilingRequestedHook():
+      callProfilerHook(profilerHook)
+
+{.pop.}