summary refs log blame commit diff stats
path: root/lib/pure/nimprof.nim
blob: 99af71f9091a225780848b3d1dab19b5da86c1a0 (plain) (tree)




























































































































                                                                               
#
#
#            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):
  {.error: "Profiling support is turned off!".}

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

import hashes, algorithm, strutils

# We use a simple hash table of bounded size to keep track of the stack traces:
type
  TProfileEntry = object
    total: int
    st: system.TStackTrace
  TProfileData = array [0..64*1024-1, ref 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 ;-)
var
  profileData: TProfileData
  emptySlots = profileData.len * 3 div 2
  maxChainLen = 0
  totalCalls = 0

proc hook(st: system.TStackTrace) {.nimcall.} =
  # this is quite performance sensitive!
  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:
        GC_disable()
        new profileData[h]
        GC_enable()
        profileData[h].total = 1
        profileData[h].st = st
        dec emptySlots
        maxChainLen = max(maxChainLen, chain)
        return
      if profileData[h].st == st:
        # wow, same entry found:
        inc profileData[h].total
        maxChainLen = max(maxChainLen, chain)
        return
      h = ((5 * h) + 1) and high(profileData)
      inc chain

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

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

proc writeProfile() {.noconv.} =
  stopProfiling()
  const filename = "profile_results"
  var f: TFile
  var j = 1
  while open(f, filename & $j & ".txt"):
    close(f)
    inc(j)
  let filen = filename & $j & ".txt"
  echo "writing ", filen, "..."
  if open(f, filen, 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 sum = 0
    # only write the first 100 entries:
    for i in 0..min(100, high(profileData)):
      if profileData[i] != nil and profileData[i].total > 1:
        inc sum, profileData[i].total
        writeln(f, "Entry: ", i, "/", entries, " Calls: ",
          profileData[i].total, "/", totalCalls, " [sum: ", sum, "; ",
          formatFloat(sum / totalCalls * 100.0, ffDefault, 2), "%]")
        for ii in 0..high(TStackTrace):
          let procname = profileData[i].st[ii]
          if isNil(procname): break
          writeln(f, "  ", procname)
    close(f)
  echo "... done"

system.profilerHook = hook
addQuitProc(writeProfile)

{.pop.}