summary refs log tree commit diff stats
path: root/lib/system/profiler.nim
blob: eafa010ef02c6ffc38dfede16681227621e3912f (plain) (blame)
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
#
#
#            Nimrod'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. 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.

{.push profiler: off.}

const
  MaxTraceLen = 20 # tracking the last 20 calls is enough

type
  TStackTrace* = array [0..MaxTraceLen-1, cstring]
  TProfilerHook* = proc (st: TStackTrace) {.nimcall.}

proc captureStackTrace(f: PFrame, st: var TStackTrace) =
  const
    firstCalls = 5
  var
    it = f
    i = 0
    total = 0
  while it != nil and i <= high(st)-(firstCalls-1):
    # the (-1) is for the "..." entry
    st[i] = it.procname
    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[i] = "..."
    inc(i)
  while b != nil and i <= high(st):
    st[i] = b.procname
    inc(i)
    b = b.prev

const
  SamplingInterval = 50_000
    # set this to change the default sampling interval
var
  profilerHook*: TProfilerHook
    ## set this variable to provide a procedure that implements a profiler in
    ## user space. See the `nimprof` module for a reference implementation.
  gTicker {.threadvar.}: int

proc callProfilerHook(hook: TProfilerHook) {.noinline.} =
  # 'noinline' so that 'nimProfile' does not perform the stack allocation
  # in the common case.
  var st: TStackTrace
  captureStackTrace(framePtr, st)
  hook(st)

proc nimProfile() =
  ## This is invoked by the compiler in every loop and on every proc entry!
  if gTicker == 0:
    gTicker = -1
    if not isNil(profilerHook):
      # disable recursive calls: XXX should use try..finally,
      # but that's too expensive!
      let oldHook = profilerHook
      profilerHook = nil
      callProfilerHook(oldHook)
      profilerHook = oldHook
    gTicker = SamplingInterval
  dec gTicker

{.pop.}