diff options
Diffstat (limited to 'lib/pure/nimprof.nim')
-rw-r--r-- | lib/pure/nimprof.nim | 120 |
1 files changed, 74 insertions, 46 deletions
diff --git a/lib/pure/nimprof.nim b/lib/pure/nimprof.nim index cce2a20ae..bf8367d1d 100644 --- a/lib/pure/nimprof.nim +++ b/lib/pure/nimprof.nim @@ -1,23 +1,29 @@ # # # Nim's Runtime Library -# (c) Copyright 2012 Andreas Rumpf +# (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. +## `--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): - {.warning: "Profiling support is turned off!".} + {.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 +import std/[hashes, algorithm, strutils, tables, sets] + +when defined(nimPreviewSlimSystem): + import std/[syncio, sysatomics] when not defined(memProfiler): include "system/timers" @@ -26,31 +32,34 @@ const withThreads = compileOption("threads") tickCountCorrection = 50_000 -when not declared(system.TStackTrace): - type TStackTrace = array [0..20, cstring] +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 - TProfileEntry = object + ProfileEntry = object total: int - st: TStackTrace - TProfileData = array [0..64*1024-1, ptr TProfileEntry] + st: StackTrace + ProfileData = array[0..64*1024-1, ptr ProfileEntry] -proc `==`(a, b: TStackTrace): bool = - for i in 0 .. high(a): +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: TProfileData + profileData: ProfileData emptySlots = profileData.len * 3 div 2 maxChainLen = 0 totalCalls = 0 when not defined(memProfiler): - var interval: TNanos = 5_000_000 - tickCountCorrection # 5ms + var interval: Nanos = 5_000_000 - tickCountCorrection # 5ms proc setSamplingFrequency*(intervalInUs: int) = ## set this to change the sampling frequency. Default value is 5ms. @@ -60,17 +69,17 @@ when not defined(memProfiler): else: interval = intervalInUs * 1000 - tickCountCorrection when withThreads: - import locks + import std/locks var - profilingLock: TLock + profilingLock: Lock initLock profilingLock -proc hookAux(st: TStackTrace, costs: int) = +proc hookAux(st: StackTrace, costs: int) = # this is quite performance sensitive! when withThreads: acquire profilingLock inc totalCalls - var last = high(st) + var last = high(st.lines) while last > 0 and isNil(st[last]): dec last var h = hash(pointer(st[last])) and high(profileData) @@ -94,8 +103,8 @@ proc hookAux(st: TStackTrace, costs: int) = var chain = 0 while true: if profileData[h] == nil: - profileData[h] = cast[ptr TProfileEntry]( - allocShared0(sizeof(TProfileEntry))) + profileData[h] = cast[ptr ProfileEntry]( + allocShared0(sizeof(ProfileEntry))) profileData[h].total = costs profileData[h].st = st dec emptySlots @@ -115,53 +124,68 @@ when defined(memProfiler): var gTicker {.threadvar.}: int - proc hook(st: TStackTrace, size: int) {.nimcall.} = + proc requestedHook(): bool {.nimcall.} = if gTicker == 0: - gTicker = -1 - when defined(ignoreAllocationSize): - hookAux(st, 1) - else: - hookAux(st, size) 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.}: TTicks - - proc hook(st: TStackTrace) {.nimcall.} = + 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) - elif int64(t0) == 0 or getTicks() - t0 > interval: + else: hookAux(st, 1) t0 = getTicks() -proc getTotal(x: ptr TProfileEntry): int = +proc getTotal(x: ptr ProfileEntry): int = result = if isNil(x): 0 else: x.total -proc cmpEntries(a, b: ptr TProfileEntry): int = +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, ffDefault, 2)) + result = format("$1/$2 = $3%", a, b, formatFloat(a / b * 100.0, ffDecimal, 2)) proc writeProfile() {.noconv.} = - when declared(system.TStackTrace): + 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) - writeln(f, "total executions of each stack trace:") + 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 = initSet[string]() - for ii in 0..high(TStackTrace): + var dups = initHashSet[string]() + for ii in 0..high(StackTrace.lines): let procname = profileData[i].st[ii] if isNil(procname): break let p = $procname @@ -173,13 +197,15 @@ proc writeProfile() {.noconv.} = 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: ", + writeLine(f, "Entry: ", i+1, "/", entries, " Calls: ", profileData[i].total // totalCalls, " [sum: ", sum, "; ", sum // totalCalls, "]") - for ii in 0..high(TStackTrace): + for ii in 0..high(StackTrace.lines): let procname = profileData[i].st[ii] + let filename = profileData[i].st.files[ii] if isNil(procname): break - writeln(f, " ", procname, " ", perProc[$procname] // totalCalls) + writeLine(f, " ", $filename & ": " & $procname, " ", + perProc[$procname] // totalCalls) close(f) echo "... done" else: @@ -189,17 +215,19 @@ var disabled: int proc disableProfiling*() = - when declared(system.TStackTrace): + when declared(system.StackTrace): atomicDec disabled - system.profilerHook = nil + system.profilingRequestedHook = nil proc enableProfiling*() = - when declared(system.TStackTrace): + when declared(system.StackTrace): if atomicInc(disabled) >= 0: - system.profilerHook = hook + system.profilingRequestedHook = requestedHook -when declared(system.TStackTrace): +when declared(system.StackTrace): + import std/exitprocs + system.profilingRequestedHook = requestedHook system.profilerHook = hook - addQuitProc(writeProfile) + addExitProc(writeProfile) {.pop.} |