aboutsummaryrefslogtreecommitdiff
path: root/nim
diff options
context:
space:
mode:
Diffstat (limited to 'nim')
-rw-r--r--nim/file.nim35
-rw-r--r--nim/logger.nim92
-rw-r--r--nim/main.nim133
-rw-r--r--nim/ping.nim320
4 files changed, 580 insertions, 0 deletions
diff --git a/nim/file.nim b/nim/file.nim
new file mode 100644
index 0000000..83c2560
--- /dev/null
+++ b/nim/file.nim
@@ -0,0 +1,35 @@
1# tickwatch
2# Author: Euxane TRAN-GIRARD
3# Licence: EUPL-1.2
4
5import std/paths
6import std/strutils
7
8
9type
10 FileValueMonitor = ref object
11 path: Path
12
13 FileChangeMonitor = ref object
14 path: Path
15 lastValue: int
16
17proc readValue(path: Path): int =
18 readFile(path.string).strip.parseInt
19
20proc initFileValueMonitor*(path: Path): FileValueMonitor =
21 FileValueMonitor(path: path)
22
23proc readValue*(mon: FileValueMonitor): int =
24 mon.path.readValue()
25
26proc initFileChangeMonitor*(path: Path): FileChangeMonitor =
27 FileChangeMonitor(
28 path: path,
29 lastValue: path.readValue(),
30 )
31
32proc readValue*(mon: FileChangeMonitor): int =
33 let newValue = mon.path.readValue()
34 result = abs(mon.lastValue - newValue)
35 mon.lastValue = newValue
diff --git a/nim/logger.nim b/nim/logger.nim
new file mode 100644
index 0000000..48aedf2
--- /dev/null
+++ b/nim/logger.nim
@@ -0,0 +1,92 @@
1# tickwatch
2# Author: Euxane TRAN-GIRARD
3# Licence: EUPL-1.2
4
5import std/sugar
6import std/math
7import std/unicode
8import std/times
9import std/os
10
11
12const
13 TIMESTAMP_FORMAT = "yyyy-MM-dd HH:mmZZZ "
14 SKIPPED_SYMBOL = "."
15 UNKNOWN_SYMBOL = "!"
16 NUMERIC_SYMBOLS* = "123456789".toRunes
17 UNICODE_SYMBOLS* = "_▁▂▃▄▅▆▇█".toRunes
18
19
20type Scale* = proc(val: float): float {.noSideEffect.}
21
22proc indicator(symbols: seq[Rune], min, max, val: float, scale: Scale): Rune =
23 let division = scale(max - min) / float(symbols.len)
24 let bucket = int(scale(val - min) / division)
25 symbols[bucket.clamp(0, symbols.len - 1)]
26
27proc indicator*(symbols: seq[Rune], min, max, val: int, scale: Scale): Rune =
28 symbols.indicator(min.float, max.float, val.float, scale)
29
30func millisecond(t: DateTime): int =
31 t.nanosecond div (1_000_000)
32
33func msUntilNextSec(t: DateTime): int =
34 1000 - t.millisecond + 1
35
36proc puts(f: File, s: string) =
37 f.write s
38 f.flushFile
39
40proc puts(f: File, r: Rune) =
41 f.puts r.toUTF8
42
43func formatTimestampDateTime*(dt: DateTime): string =
44 dt.format(TIMESTAMP_FORMAT)
45
46func formatTimestampUnix*(dt: DateTime): string =
47 ($dt.toTime.toUnix) & " "
48
49func formatTimestampNone*(dt: DateTime): string =
50 ""
51
52proc loop*(
53 probe: (timeout: Duration) -> int,
54 getSymbol: (int) -> Rune,
55 timestampHeader: (DateTime) -> string,
56) =
57 while true:
58 stdout.puts timestampHeader(now())
59
60 for tick in 0..<60:
61 let t = now()
62 if tick < t.second:
63 stdout.puts SKIPPED_SYMBOL
64 continue
65
66 let timeout = initDuration(milliseconds = t.msUntilNextSec)
67 try:
68 let val = probe(timeout)
69 stdout.puts getSymbol(val)
70 except CatchableError:
71 stdout.puts UNKNOWN_SYMBOL
72
73 let tAfter = now()
74 if tAfter < t + timeout:
75 sleep(tAfter.msUntilNextSec)
76
77 stdout.puts "\n"
78
79proc flushAndQuit*() {.noconv.} =
80 stdout.puts "\n"
81 quit(0)
82
83
84when defined(test):
85 import std/unittest
86 import std/sequtils
87
88 suite "logger":
89 test "indicator value":
90 proc id(val: float): float = val
91 let indics = (0..30).mapIt(NUMERIC_SYMBOLS.indicator(5, 20, it, id))
92 check indics.deduplicate(isSorted = true) == NUMERIC_SYMBOLS
diff --git a/nim/main.nim b/nim/main.nim
new file mode 100644
index 0000000..0ea96f4
--- /dev/null
+++ b/nim/main.nim
@@ -0,0 +1,133 @@
1# tickwatch
2# Author: Euxane TRAN-GIRARD
3# Licence: EUPL-1.2
4
5import std/sugar
6import std/math
7import std/times
8import std/net
9import std/posix
10import std/paths
11import std/strutils
12import std/sequtils
13import std/parseopt
14
15import file
16import ping
17import logger
18
19
20const
21 NAME = "tickwatch"
22 VERSION = staticExec("""
23 command -v git >/dev/null && git describe --tags --always || echo $VERSION
24 """).strip()
25 HELP_TEXT =
26 staticRead("../readme.md")
27 .split("```helptext", 1)[1]
28 .split("```", 1)[0]
29 .strip()
30
31proc registerTerminationHandlers() =
32 proc terminationHandler(sig: cint) {.noconv.} = flushAndQuit()
33 var action = SigAction(sa_handler: terminationHandler)
34 for signal in [SIGTERM, SIGHUP, SIGQUIT, SIGINT]:
35 discard sigaction(signal, action)
36
37func getArg(
38 args: seq[tuple[kind: CmdLineKind, key, val: string]],
39 index: int,
40 label: string
41): string =
42 try: args[index].key
43 except: raise newException(ValueError, "Missing " & label & " argument")
44
45proc main() =
46 var
47 scale: Scale = log2
48 symbols = UNICODE_SYMBOLS
49 timestampHeader = formatTimestampDateTime
50 (min, max) = (0, 1000)
51
52 for optKind, key, val in getopt():
53 if optKind notin {cmdLongOption, cmdShortOption}:
54 continue
55
56 case key:
57
58 of "help", "h":
59 echo HELP_TEXT
60 quit(0)
61
62 of "version", "v":
63 echo NAME & " " & VERSION
64 quit(0)
65
66 of "scale":
67 case val:
68 of "logarithmic", "log", "log2": scale = log2
69 of "log10": scale = log10
70 of "ln": scale = ln
71 of "linear", "lin": scale = (val: float) => val
72 else: raise newException(ValueError, "Unrecognised scale choice")
73
74 of "symbols":
75 case val:
76 of "unicode", "utf8", "utf-8": symbols = UNICODE_SYMBOLS
77 of "numeric", "ascii": symbols = NUMERIC_SYMBOLS
78 else: raise newException(ValueError, "Unrecognised symbols choice")
79
80 of "timestamp":
81 case val:
82 of "datetime", "date-time": timestampHeader = formatTimestampDateTime
83 of "unix", "epoch": timestampHeader = formatTimestampUnix
84 of "none", "false": timestampHeader = formatTimestampNone
85 else: raise newException(ValueError, "Unrecognised timestamp choice")
86
87 of "range":
88 let parts = val.split(':', 1)
89 if parts.len != 2: raise newException(ValueError, "Invalid range")
90 (min, max) = (parseInt(parts[0]), parseInt(parts[1]))
91
92 else:
93 raise newException(ValueError, "Unrecognised option")
94
95 let
96 args = getopt().toSeq.filterIt(it.kind == cmdArgument)
97 monitor = args.getArg(0, "monitor")
98 target = args.getArg(1, "target")
99
100 if args.len > 2:
101 raise newException(ValueError, "Invalid number of arguments")
102
103 let probe = case monitor:
104 of "ping":
105 let targetIp = resolve(target)
106 let mon = initPingMonitor(targetIp, procIdent())
107 (timeout: Duration) => mon.ping(timeout).inMilliseconds.int
108
109 of "value":
110 let mon = initFileValueMonitor(Path target)
111 (timeout: Duration) => mon.readValue()
112
113 of "change":
114 let mon = initFileChangeMonitor(Path target)
115 (timeout: Duration) => mon.readValue()
116
117 else:
118 raise newException(ValueError, "Unrecognised monitor argument")
119
120 loop(
121 probe,
122 (val: int) => symbols.indicator(min, max, val, scale),
123 timestampHeader,
124 )
125
126
127when not defined(test):
128 try:
129 registerTerminationHandlers()
130 main()
131 except CatchableError as err:
132 stderr.writeLine err.msg