Skip to content

Commit

Permalink
Merge branch 'default'
Browse files Browse the repository at this point in the history
  • Loading branch information
stefantalpalaru committed Aug 7, 2019
2 parents a6f281d + 0edbe59 commit b77efb8
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 13 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ nimble install https://github.com/status-im/nim-metrics@#master

## Usage

To enable metrics, compile your code with `-d:metrics`. You also need `--threads:on` for some atomic operations to work.
To enable metrics, compile your code with `-d:metrics`. You also need `--threads:on` for atomic operations to work.

## Contributing

Expand Down
132 changes: 129 additions & 3 deletions metrics.nim
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
# at your option. This file may not be copied, modified, or distributed except according to those terms.

import algorithm, hashes, locks, re, sequtils, sets, strutils, tables, times
import algorithm, hashes, locks, os, re, sequtils, sets, strutils, tables, times

type
Labels* = seq[string]
Expand Down Expand Up @@ -180,22 +180,26 @@ proc newRegistry*(): Registry =
# needs to be {.global.} because of the alternative API's usage of {.global.} collector vars
var defaultRegistry* {.global.} = newRegistry()

proc register*(collector: Collector, registry = defaultRegistry) =
# We use a generic type here in order to avoid the hidden type casting of
# Collector child types to the parent type.
proc register* [T] (collector: T, registry = defaultRegistry) =
when defined(metrics):
withLock registry.lock:
if collector in registry.collectors:
raise newException(RegistrationError, "Collector already registered.")

registry.collectors.incl(collector)

proc unregister*(collector: Collector | type IgnoredCollector, registry = defaultRegistry) =
proc unregister* [T] (collector: T, registry = defaultRegistry) =
when defined(metrics) and collector is not IgnoredCollector:
withLock registry.lock:
if collector notin registry.collectors:
raise newException(RegistrationError, "Collector not registered.")

registry.collectors.excl(collector)

proc unregister* (collector: type IgnoredCollector, registry = defaultRegistry) = discard

proc collect*(registry: Registry): OrderedTable[Collector, Metrics] =
when defined(metrics):
result = initOrderedTable[Collector, Metrics]()
Expand Down Expand Up @@ -683,3 +687,125 @@ proc startHttpServer*(address = "127.0.0.1", port = Port(9093)) =
when defined(metrics):
httpServerThread.createThread(httpServer, (address, port))

################
# process info #
################

when defined(metrics) and defined(linux):
import posix

type ProcessInfo = ref object of Gauge

proc newProcessInfo*(name: string, help: string, registry = defaultRegistry): ProcessInfo =
validateName(name)
result = ProcessInfo(name: name,
help: help,
typ: "gauge", # Prometheus won't allow fantasy types in here
creationThreadId: getThreadId())
result.register(registry)

var
processInfo* {.global.} = newProcessInfo("process_info", "CPU and memory usage")
btime {.global.}: float64 = 0
ticks {.global.}: float64 # clock ticks per second
pagesize {.global.}: float64 # page size in bytes
whitespaceRegex {.global.} = re(r"\s+")

if btime == 0:
try:
for line in lines("/proc/stat"):
if line.startsWith("btime"):
btime = line.split(' ')[1].parseFloat()
except IOError:
# /proc not mounted?
discard
ticks = sysconf(SC_CLK_TCK).float64
pagesize = sysconf(SC_PAGE_SIZE).float64

method collect*(collector: ProcessInfo): Metrics =
result = initOrderedTable[Labels, seq[Metric]]()
result[@[]] = @[]
if btime == 0:
# we couldn't access /proc
return

var timestamp = getTime().toMilliseconds()
let selfStat = readFile("/proc/self/stat").split(' ')[2..^1]
result[@[]] = @[
Metric(
name: "process_virtual_memory_bytes", # Virtual memory size in bytes.
value: selfStat[20].parseFloat(),
timestamp: timestamp,
),
Metric(
name: "process_resident_memory_bytes", # Resident memory size in bytes.
value: selfStat[21].parseFloat() * pagesize,
timestamp: timestamp,
),
Metric(
name: "process_start_time_seconds", # Start time of the process since unix epoch in seconds.
value: selfStat[19].parseFloat() / ticks + btime,
timestamp: timestamp,
),
Metric(
name: "process_cpu_seconds_total", # Total user and system CPU time spent in seconds.
value: (selfStat[11].parseFloat() + selfStat[12].parseFloat()) / ticks,
timestamp: timestamp,
),
]

for line in lines("/proc/self/limits"):
if line.startsWith("Max open files"):
result[@[]].add(
Metric(
name: "process_max_fds", # Maximum number of open file descriptors.
value: line.split(whitespaceRegex)[3].parseFloat(), # a simple `split()` does not combine adjacent whitespace
timestamp: timestamp,
)
)
break

result[@[]].add(
Metric(
name: "process_open_fds", # Number of open file descriptors.
value: toSeq(walkDir("/proc/self/fd")).len.float64,
timestamp: timestamp,
)
)

####################
# Nim runtime info #
####################

when defined(metrics):
type RuntimeInfo = ref object of Gauge

proc newRuntimeInfo*(name: string, help: string, registry = defaultRegistry): RuntimeInfo =
validateName(name)
result = RuntimeInfo(name: name,
help: help,
typ: "gauge",
creationThreadId: getThreadId())
result.register(registry)

var
runtimeInfo* {.global.} = newRuntimeInfo("nim_runtime_info", "Nim runtime info")

method collect*(collector: RuntimeInfo): Metrics =
result = initOrderedTable[Labels, seq[Metric]]()
var timestamp = getTime().toMilliseconds()

result[@[]] = @[
Metric(
name: "nim_gc_total_mem_bytes", # the number of bytes that are owned by the process
value: getTotalMem().float64,
timestamp: timestamp,
),
Metric(
name: "nim_gc_occupied_mem_bytes", # the number of bytes that are owned by the process and hold data
value: getOccupiedMem().float64,
timestamp: timestamp,
),
]
# TODO: parse the output of `GC_getStatistics()` for more stats

15 changes: 8 additions & 7 deletions metrics.nimble
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,25 @@ proc buildBinary(name: string, srcDir = "./", params = "", lang = "c") =
var extra_params = params
for i in 2..<paramCount():
extra_params &= " " & paramStr(i)
exec "nim " & lang & " --out:./build/" & name & " " & extra_params & " " & srcDir & name & ".nim"
exec "nim " & lang & " --out:./build/" & name & " -f --skipParentCfg " & extra_params & " " & srcDir & name & ".nim"

proc test(name: string) =
buildBinary name, "tests/", "-f -r -d:metrics"
buildBinary name, "tests/", "-r -d:metrics --threads:on"

proc bench(name: string) =
buildBinary name, "benchmarks/", "-f -r -d:metrics -d:release"
buildBinary name, "benchmarks/", "-r -d:metrics --threads:on -d:release"

### tasks
task test, "Main tests":
# build it with metrics disabled, first
buildBinary "main_tests", "tests/", "-f"
buildBinary "main_tests", "tests/"
buildBinary "main_tests", "tests/", "--threads:on"
test "main_tests"
buildBinary "bench_collectors", "benchmarks/", "-f"
buildBinary "bench_collectors", "benchmarks/", "-f -d:metrics"
buildBinary "bench_collectors", "benchmarks/"
buildBinary "bench_collectors", "benchmarks/", "-d:metrics --threads:on"

task test_chronicles, "Chronicles tests":
buildBinary "chronicles_tests", "tests/", "-f"
buildBinary "chronicles_tests", "tests/"
test "chronicles_tests"

task benchmark, "Run benchmarks":
Expand Down
2 changes: 0 additions & 2 deletions nim.cfg

This file was deleted.

1 change: 1 addition & 0 deletions tests/chronicles_tests.nim
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,5 @@ suite "logging":
for metric in metrics:
info "metric", metric
info "registry", registry
info "default registry", defaultRegistry

0 comments on commit b77efb8

Please sign in to comment.