123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234 |
- #
- #
- # Nim's Runtime Library
- # (c) Copyright 2015 Andreas Rumpf
- #
- # See the file "copying.txt", included in this
- # distribution, for details about the copyright.
- #
- ## Profiling support for Nim. This is an embedded profiler that requires
- ## `--profiler:on`. You only need to import this module to get a profiling
- ## report at program exit. See `Embedded Stack Trace Profiler <estp.html>`_
- ## for usage.
- when not defined(profiler) and not defined(memProfiler):
- {.error: "Profiling support is turned off! Enable profiling by passing `--profiler:on --stackTrace:on` to the compiler (see the Nim Compiler User Guide for more options).".}
- {.used.}
- # We don't want to profile the profiling code ...
- {.push profiler: off.}
- import hashes, algorithm, strutils, tables, sets
- when defined(nimPreviewSlimSystem):
- import std/[syncio, sysatomics]
- when not defined(memProfiler):
- include "system/timers"
- const
- withThreads = compileOption("threads")
- tickCountCorrection = 50_000
- when not declared(system.StackTrace):
- type StackTrace = object
- lines: array[0..20, cstring]
- files: array[0..20, cstring]
- proc `[]`*(st: StackTrace, i: int): cstring = st.lines[i]
- # We use a simple hash table of bounded size to keep track of the stack traces:
- type
- ProfileEntry = object
- total: int
- st: StackTrace
- ProfileData = array[0..64*1024-1, ptr ProfileEntry]
- proc `==`(a, b: StackTrace): bool =
- for i in 0 .. high(a.lines):
- 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: ProfileData
- emptySlots = profileData.len * 3 div 2
- maxChainLen = 0
- totalCalls = 0
- when not defined(memProfiler):
- var interval: Nanos = 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:
- import locks
- var
- profilingLock: Lock
- initLock profilingLock
- proc hookAux(st: StackTrace, costs: int) =
- # this is quite performance sensitive!
- when withThreads: acquire profilingLock
- inc totalCalls
- var last = high(st.lines)
- 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, costs
- return
- if profileData[minIdx].total < profileData[h].total:
- minIdx = h
- h = ((5 * h) + 1) and high(profileData)
- dec probes
- profileData[minIdx].total = costs
- profileData[minIdx].st = st
- else:
- var chain = 0
- while true:
- if profileData[h] == nil:
- profileData[h] = cast[ptr ProfileEntry](
- allocShared0(sizeof(ProfileEntry)))
- profileData[h].total = costs
- profileData[h].st = st
- dec emptySlots
- break
- if profileData[h].st == st:
- # wow, same entry found:
- inc profileData[h].total, costs
- break
- h = ((5 * h) + 1) and high(profileData)
- inc chain
- maxChainLen = max(maxChainLen, chain)
- when withThreads: release profilingLock
- when defined(memProfiler):
- const
- SamplingInterval = 50_000
- var
- gTicker {.threadvar.}: int
- proc requestedHook(): bool {.nimcall.} =
- if gTicker == 0:
- gTicker = SamplingInterval
- result = true
- dec gTicker
- proc hook(st: StackTrace, size: int) {.nimcall.} =
- when defined(ignoreAllocationSize):
- hookAux(st, 1)
- else:
- hookAux(st, size)
- else:
- var
- t0 {.threadvar.}: Ticks
- gTicker: int # we use an additional counter to
- # avoid calling 'getTicks' too frequently
- proc requestedHook(): bool {.nimcall.} =
- if interval == 0: result = true
- elif gTicker == 0:
- gTicker = 500
- if getTicks() - t0 > interval:
- result = true
- else:
- dec gTicker
- proc hook(st: StackTrace) {.nimcall.} =
- #echo "profiling! ", interval
- if interval == 0:
- hookAux(st, 1)
- else:
- hookAux(st, 1)
- t0 = getTicks()
- proc getTotal(x: ptr ProfileEntry): int =
- result = if isNil(x): 0 else: x.total
- proc cmpEntries(a, b: ptr ProfileEntry): int =
- result = b.getTotal - a.getTotal
- proc `//`(a, b: int): string =
- result = format("$1/$2 = $3%", a, b, formatFloat(a / b * 100.0, ffDecimal, 2))
- proc writeProfile() {.noconv.} =
- system.profilingRequestedHook = nil
- when declared(system.StackTrace):
- system.profilerHook = nil
- const filename = "profile_results.txt"
- echo "writing " & filename & "..."
- var f: File
- if open(f, filename, fmWrite):
- sort(profileData, cmpEntries)
- writeLine(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 = initHashSet[string]()
- for ii in 0..high(StackTrace.lines):
- 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
- writeLine(f, "Entry: ", i+1, "/", entries, " Calls: ",
- profileData[i].total // totalCalls, " [sum: ", sum, "; ",
- sum // totalCalls, "]")
- for ii in 0..high(StackTrace.lines):
- let procname = profileData[i].st[ii]
- let filename = profileData[i].st.files[ii]
- if isNil(procname): break
- writeLine(f, " ", $filename & ": " & $procname, " ",
- perProc[$procname] // totalCalls)
- close(f)
- echo "... done"
- else:
- echo "... failed"
- var
- disabled: int
- proc disableProfiling*() =
- when declared(system.StackTrace):
- atomicDec disabled
- system.profilingRequestedHook = nil
- proc enableProfiling*() =
- when declared(system.StackTrace):
- if atomicInc(disabled) >= 0:
- system.profilingRequestedHook = requestedHook
- when declared(system.StackTrace):
- import std/exitprocs
- system.profilingRequestedHook = requestedHook
- system.profilerHook = hook
- addExitProc(writeProfile)
- {.pop.}
|