# tickwatch # Author: Euxane TRAN-GIRARD # Licence: EUPL-1.2 import std/sugar import std/math import std/times import std/net import std/posix import std/paths import std/strutils import std/sequtils import std/parseopt import file import ping import logger const NAME = "tickwatch" VERSION = staticExec(""" command -v git >/dev/null && git describe --tags --always || echo $VERSION """).strip() HELP_TEXT = staticRead("readme.md") .split("```helptext", 1)[1] .split("```", 1)[0] .strip() proc registerTerminationHandlers() = proc terminationHandler(sig: cint) {.noconv.} = flushAndQuit() var action = SigAction(sa_handler: terminationHandler) for signal in [SIGTERM, SIGHUP, SIGQUIT, SIGINT]: discard sigaction(signal, action) func getArg( args: seq[tuple[kind: CmdLineKind, key, val: string]], index: int, label: string ): string = try: args[index].key except: raise newException(ValueError, "Missing " & label & " argument") proc main() = var scale: Scale = log2 symbols = UNICODE_SYMBOLS timestampHeader = formatTimestampDateTime (min, max) = (0, 1000) for optKind, key, val in getopt(): if optKind notin {cmdLongOption, cmdShortOption}: continue case key: of "help", "h": echo HELP_TEXT quit(0) of "version", "v": echo NAME & " " & VERSION quit(0) of "scale": case val: of "logarithmic", "log", "log2": scale = log2 of "log10": scale = log10 of "ln": scale = ln of "linear", "lin": scale = (val: float) => val else: raise newException(ValueError, "Unrecognised scale choice") of "symbols": case val: of "unicode", "utf8", "utf-8": symbols = UNICODE_SYMBOLS of "numeric", "ascii": symbols = NUMERIC_SYMBOLS else: raise newException(ValueError, "Unrecognised symbols choice") of "timestamp": case val: of "datetime", "date-time": timestampHeader = formatTimestampDateTime of "unix", "epoch": timestampHeader = formatTimestampUnix of "none", "false": timestampHeader = formatTimestampNone else: raise newException(ValueError, "Unrecognised timestamp choice") of "range": let parts = val.split(':', 1) if parts.len != 2: raise newException(ValueError, "Invalid range") (min, max) = (parseInt(parts[0]), parseInt(parts[1])) else: raise newException(ValueError, "Unrecognised option") let args = getopt().toSeq.filterIt(it.kind == cmdArgument) monitor = args.getArg(0, "monitor") target = args.getArg(1, "target") if args.len > 2: raise newException(ValueError, "Invalid number of arguments") let probe = case monitor: of "ping": let targetIp = resolve(target) let mon = initPingMonitor(targetIp, procIdent()) (timeout: Duration) => mon.ping(timeout).inMilliseconds.int of "value": let mon = initFileValueMonitor(Path target) (timeout: Duration) => mon.readValue() of "change": let mon = initFileChangeMonitor(Path target) (timeout: Duration) => mon.readValue() else: raise newException(ValueError, "Unrecognised monitor argument") loop( probe, (val: int) => symbols.indicator(min, max, val, scale), timestampHeader, ) when not defined(test): try: registerTerminationHandlers() main() except CatchableError as err: stderr.writeLine err.msg quit(1)