summary refs log tree commit diff stats
path: root/lib/pure/nimprof.nim
blob: 7a907d2ca93caae0ea4f604ba3871f7f17407020 (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
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
#
#
#            Nimrod's Runtime Library
#        (c) Copyright 2012 Andreas Rumpf
#
#    See the file "copying.txt", included in this
#    distribution, for details about the copyright.
#

## Profiling support for Nimrod. This is an embedded profiler that requires
## ``--profiler:on``. You only need to import this module to get a profiling
## report at program exit.

when not defined(profiler):
  {.warning: "Profiling support is turned off!".}

# We don't want to profile the profiling code ...
{.push profiler: off.}

import hashes, algorithm, strutils, tables, sets
include "system/timers"

const
  withThreads = compileOption("threads")
  tickCountCorrection = 50_000

when not defined(system.TStackTrace):
  type TStackTrace = array [0..20, cstring]

# We use a simple hash table of bounded size to keep track of the stack traces:
type
  TProfileEntry = object
    total: int
    st: TStackTrace
  TProfileData = array [0..64*1024-1, ptr TProfileEntry]

proc `==`(a, b: TStackTrace): bool =
  for i in 0 .. high(a):
    if a[i] != b[i]: return false
  result = true

# XXX extract this data structure; it is generally useful ;-)
# However a chain length of over 3000 is suspicious...
var
  profileData: TProfileData
  emptySlots = profileData.len * 3 div 2
  maxChainLen = 0
  totalCalls = 0

var
  interval: TNanos = 5_000_000 - tickCountCorrection # 5ms

proc setSamplingFrequency*(intervalInUs: int) =
  ## set this to change the sampling frequency. Default value is 5ms.
  ## Set it to 0 to disable time based profiling; it uses an imprecise
  ## instruction count measure instead then.
  if intervalInUs <= 0: interval = 0
  else: interval = intervalInUs * 1000 - tickCountCorrection
  
when withThreads:
  var
    profilingLock: TLock

  InitLock profilingLock

proc hookAux(st: TStackTrace) =
  # this is quite performance sensitive!
  when withThreads: Acquire profilingLock
  inc totalCalls
  var last = high(st)
  while last > 0 and isNil(st[last]): dec last
  var h = hash(pointer(st[last])) and high(profileData)
  
  # we use probing for maxChainLen entries and replace the encountered entry
  # with the minimal 'total' value:
  if emptySlots == 0:
    var minIdx = h
    var probes = maxChainLen
    while probes >= 0:
      if profileData[h].st == st:
        # wow, same entry found:
        inc profileData[h].total
        return
      if profileData[minIdx].total < profileData[h].total:
        minIdx = h
      h = ((5 * h) + 1) and high(profileData)
      dec probes
    profileData[minIdx].total = 1
    profileData[minIdx].st = st
  else:
    var chain = 0
    while true:
      if profileData[h] == nil:
        profileData[h] = cast[ptr TProfileEntry](
                             allocShared0(sizeof(TProfileEntry)))
        profileData[h].total = 1
        profileData[h].st = st
        dec emptySlots
        break
      if profileData[h].st == st:
        # wow, same entry found:
        inc profileData[h].total
        break
      h = ((5 * h) + 1) and high(profileData)
      inc chain
    maxChainLen = max(maxChainLen, chain)
  when withThreads: Release profilingLock

var
  t0 {.threadvar.}: TTicks

proc hook(st: TStackTrace) {.nimcall.} =
  if interval == 0:
    hookAux(st)
  elif getticks() - t0 > interval:
    hookAux(st)
    t0 = getticks()  

proc getTotal(x: ptr TProfileEntry): int =
  result = if isNil(x): 0 else: x.total

proc cmpEntries(a, b: ptr TProfileEntry): int =
  result = b.getTotal - a.getTotal

proc `//`(a, b: int): string =
  result = format("$1/$2 = $3%", a, b, formatFloat(a / b * 100.0, ffDefault, 2))

proc writeProfile() {.noconv.} =
  when defined(system.TStackTrace): 
    system.profilerHook = nil
  const filename = "profile_results.txt"
  echo "writing " & filename & "..."
  var f: TFile
  if open(f, filename, fmWrite):
    sort(profileData, cmpEntries)
    writeln(f, "total executions of each stack trace:")
    var entries = 0
    for i in 0..high(profileData):
      if profileData[i] != nil: inc entries
    
    var perProc = initCountTable[string]()
    for i in 0..entries-1:
      var dups = initSet[string]()
      for ii in 0..high(TStackTrace):
        let procname = profileData[i].st[ii]
        if isNil(procname): break
        let p = $procname
        if not containsOrIncl(dups, p):
          perProc.inc(p, profileData[i].total)
    
    var sum = 0
    # only write the first 100 entries:
    for i in 0..min(100, entries-1):
      if profileData[i].total > 1:
        inc sum, profileData[i].total
        writeln(f, "Entry: ", i+1, "/", entries, " Calls: ",
          profileData[i].total // totalCalls, " [sum: ", sum, "; ",
          sum // totalCalls, "]")
        for ii in 0..high(TStackTrace):
          let procname = profileData[i].st[ii]
          if isNil(procname): break
          writeln(f, "  ", procname, " ", perProc[$procname] // totalCalls)
    close(f)
    echo "... done"
  else:
    echo "... failed"

var
  disabled: int

proc disableProfiling*() =
  atomicDec disabled
  system.profilerHook = nil

proc enableProfiling*() =
  if atomicInc(disabled) >= 0:
    system.profilerHook = hook

when defined(system.TStackTrace):
  system.profilerHook = hook
  addQuitProc(writeProfile)

{.pop.}
s="w"> inc(c.bufpos) xi = 0 handleHexChar(c, xi) handleHexChar(c, xi) add(tok.literal, Chr(xi)) of '0'..'9': xi = 0 handleDecChars(c, xi) if (xi <= 255): add(tok.literal, Chr(xi)) else: tok.kind = tkInvalid else: tok.kind = tkInvalid proc HandleCRLF(c: var TCfgParser, pos: int): int = case c.buf[pos] of CR: result = lexbase.HandleCR(c, pos) of LF: result = lexbase.HandleLF(c, pos) else: result = pos proc getString(c: var TCfgParser, tok: var TToken, rawMode: bool) = var pos: int ch: Char buf: cstring pos = c.bufPos + 1 # skip " buf = c.buf # put `buf` in a register tok.kind = tkSymbol if (buf[pos] == '\"') and (buf[pos + 1] == '\"'): # long string literal: inc(pos, 2) # skip "" # skip leading newline: pos = HandleCRLF(c, pos) buf = c.buf while true: case buf[pos] of '\"': if (buf[pos + 1] == '\"') and (buf[pos + 2] == '\"'): break add(tok.literal, '\"') Inc(pos) of CR, LF: pos = HandleCRLF(c, pos) buf = c.buf tok.literal = tok.literal & "\n" of lexbase.EndOfFile: tok.kind = tkInvalid break else: add(tok.literal, buf[pos]) Inc(pos) c.bufpos = pos + 3 # skip the three """ else: # ordinary string literal while true: ch = buf[pos] if ch == '\"': inc(pos) # skip '"' break if ch in {CR, LF, lexbase.EndOfFile}: tok.kind = tkInvalid break if (ch == '\\') and not rawMode: c.bufPos = pos getEscapedChar(c, tok) pos = c.bufPos else: add(tok.literal, ch) Inc(pos) c.bufpos = pos proc getSymbol(c: var TCfgParser, tok: var TToken) = var pos: int buf: cstring pos = c.bufpos buf = c.buf while true: add(tok.literal, buf[pos]) Inc(pos) if not (buf[pos] in SymChars): break c.bufpos = pos tok.kind = tkSymbol proc skip(c: var TCfgParser) = var buf: cstring pos: int pos = c.bufpos buf = c.buf while true: case buf[pos] of ' ': Inc(pos) of Tabulator: inc(pos) of '#', ';': while not (buf[pos] in {CR, LF, lexbase.EndOfFile}): inc(pos) of CR, LF: pos = HandleCRLF(c, pos) buf = c.buf else: break # EndOfFile also leaves the loop c.bufpos = pos proc rawGetTok(c: var TCfgParser, tok: var TToken) = tok.kind = tkInvalid setlen(tok.literal, 0) skip(c) case c.buf[c.bufpos] of '=': tok.kind = tkEquals inc(c.bufpos) tok.literal = "=" of '-': inc(c.bufPos) if c.buf[c.bufPos] == '-': inc(c.bufPos) tok.kind = tkDashDash tok.literal = "--" of ':': tok.kind = tkColon inc(c.bufpos) tok.literal = ":" of 'r', 'R': if c.buf[c.bufPos + 1] == '\"': Inc(c.bufPos) getString(c, tok, true) else: getSymbol(c, tok) of '[': tok.kind = tkBracketLe inc(c.bufpos) tok.literal = "[" of ']': tok.kind = tkBracketRi Inc(c.bufpos) tok.literal = "]" of '\"': getString(c, tok, false) of lexbase.EndOfFile: tok.kind = tkEof else: getSymbol(c, tok) proc errorStr(c: TCfgParser, msg: string): string = result = `%`("$1($2, $3) Error: $4", [c.filename, $(getLine(c)), $(getColumn(c)), msg]) proc getKeyValPair(c: var TCfgParser, kind: TCfgEventKind): TCfgEvent = if c.tok.kind == tkSymbol: result.kind = kind result.key = c.tok.literal result.value = "" rawGetTok(c, c.tok) while c.tok.literal == ".": add(result.key, '.') rawGetTok(c, c.tok) if c.tok.kind == tkSymbol: add(result.key, c.tok.literal) rawGetTok(c, c.tok) else: result.kind = cfgError result.msg = errorStr(c, "symbol expected, but found: " & c.tok.literal) break if c.tok.kind in {tkEquals, tkColon}: rawGetTok(c, c.tok) if c.tok.kind == tkSymbol: result.value = c.tok.literal else: result.kind = cfgError result.msg = errorStr(c, "symbol expected, but found: " & c.tok.literal) rawGetTok(c, c.tok) else: result.kind = cfgError result.msg = errorStr(c, "symbol expected, but found: " & c.tok.literal) rawGetTok(c, c.tok) proc next(c: var TCfgParser): TCfgEvent = case c.tok.kind of tkEof: result.kind = cfgEof of tkDashDash: rawGetTok(c, c.tok) result = getKeyValPair(c, cfgOption) of tkSymbol: result = getKeyValPair(c, cfgKeyValuePair) of tkBracketLe: rawGetTok(c, c.tok) if c.tok.kind == tkSymbol: result.kind = cfgSectionStart result.section = c.tok.literal else: result.kind = cfgError result.msg = errorStr(c, "symbol expected, but found: " & c.tok.literal) rawGetTok(c, c.tok) if c.tok.kind == tkBracketRi: rawGetTok(c, c.tok) else: result.kind = cfgError result.msg = errorStr(c, "\']\' expected, but found: " & c.tok.literal) of tkInvalid, tkBracketRi, tkEquals, tkColon: result.kind = cfgError result.msg = errorStr(c, "invalid token: " & c.tok.literal) rawGetTok(c, c.tok)