aboutsummaryrefslogtreecommitdiff
path: root/main.nim
blob: 07686ed7a4d361fb46472b620dc4abb53321ded3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# 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)