ctucx.git: fritzbox-exporter

[nimlang] prometheus exporter for lte fritzboxes

commit d68d700d68740491a21304c6115bb9f13001a3f9
Author: ctucx <c@ctu.cx>
Date: Sun, 19 Jan 2020 19:54:48 +0100

init
6 files changed, 241 insertions(+), 0 deletions(-)
A
.gitignore
|
1
+
A
README.md
|
4
++++
A
client.sh
|
11
+++++++++++
A
fb_exporter.nimble
|
14
++++++++++++++
A
nim.cfg
|
1
+
A
src/fb_exporter.nim
|
210
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1 @@
+fb_exporter
diff --git a/README.md b/README.md
@@ -0,0 +1,3 @@
+# fb_exporter
+
+fixme+
\ No newline at end of file
diff --git a/client.sh b/client.sh
@@ -0,0 +1,10 @@
+#!/bin/sh
+export BB=/wrapper/ctucx/busybox
+
+while true; do
+        /usr/bin/ctlmgr_ctl u inetstat > /tmp/inetstat
+        /usr/bin/ctlmgr_ctl u mobiled > /tmp/mobiled
+        echo -ne "POST /update/inetstat?penis123 HTTP/1.1\r\nConnection: Close\r\nContent-Length: $($BB wc -c /tmp/inetstat | $BB cut -d' ' -f1)\r\n\r\n$(cat /tmp/inetstat)\r\n" | $BB nc taurus.ctu.cx 1234
+        echo -ne "POST /update/mobiled?penis123 HTTP/1.1\r\nConnection: Close\r\nContent-Length: $($BB wc -c /tmp/mobiled | $BB cut -d' ' -f1)\r\n\r\n$(cat /tmp/mobiled)\r\n" | $BB nc taurus.ctu.cx 1234
+        sleep 20
+done+
\ No newline at end of file
diff --git a/fb_exporter.nimble b/fb_exporter.nimble
@@ -0,0 +1,14 @@
+# Package
+
+version       = "0.1.0"
+author        = "ctucx"
+description   = "A new awesome nimble package"
+license       = "GPL-2.0"
+srcDir        = "src"
+bin           = @["fb_exporter"]
+
+
+
+# Dependencies
+
+requires "nim >= 1.0.4"
diff --git a/nim.cfg b/nim.cfg
@@ -0,0 +1 @@
+--passL:"-static -no-pie"
diff --git a/src/fb_exporter.nim b/src/fb_exporter.nim
@@ -0,0 +1,210 @@
+import asynchttpserver
+import asyncdispatch
+import json
+import strutils
+import times
+
+# config
+var httpPort {.threadvar.}: uint16
+var authToken {.threadvar.}: string
+
+# state
+var mobiled_lastUpdated:int64 = 0
+var mobiled_data {.threadvar.}: JsonNode
+
+var inetstat_lastUpdated:int64 = 0
+var inetstat_data {.threadvar.}: JsonNode
+
+
+# main
+proc isInt*(s: string): bool =
+  try:
+    discard s.parseInt()
+    result = true
+  except:
+    discard
+
+proc isFloat*(s: string): bool =
+  try:
+    discard s.parseFloat()
+    result = true
+  except:
+    discard
+
+
+proc parse_ctlmgr(n: string): JsonNode =
+  var input = n.split("\n")
+
+  input.del(0)
+
+  var last_group = ""
+
+  var data = newJObject()
+
+  for i in items(input):
+    let line = strip(i)
+    if line == "": continue
+
+    if endsWith(line, "/"):
+      last_group = replace(line, "/", "")
+      continue
+
+    let entry = line.split("=")
+
+    var value = ""
+
+    if entry.len > 1:
+        value = entry[1]
+
+    if last_group == "":
+      if isInt(value):
+        data.add(entry[0], newJInt(parseInt(value)))
+      elif isFloat(value):
+        data.add(entry[0], newJFloat(parseFloat(value)))
+      else:
+        data.add(entry[0], %(value))
+    else:
+      if not data.hasKey(last_group):
+        data.add(last_group, newJObject())
+
+      if isInt(value):
+        data[last_group].add(entry[0], newJInt(parseInt(value)))
+      elif isFloat(value):
+        data[last_group].add(entry[0], newJFloat(parseFloat(value)))
+      else:
+        data[last_group].add(entry[0], %(value))
+  return data
+
+proc processHttpClient(req: Request) {.async.} =
+  if req.reqMethod == HttpGet:
+
+    if inetstat_data != nil or mobiled_data != nil:
+      if req.url.path == "/metrics":
+        var res = ""
+
+        var total_transmit   = 0
+        var total_receive    = 0
+        var month_transmit   = 0
+        var month_receive    = 0
+        var today_transmit   = 0
+        var today_receive    = 0
+        
+        var upstream         = 0
+        var downstream       = 0
+        
+        var cell0_technology = ""
+        var cell0_quality    = 0
+        var cell0_band       = 0
+        var cell0_distance   = 0
+
+        var cell1_technology = ""
+        var cell1_quality    = 0
+        var cell1_band       = 0
+        var cell1_distance   = 0
+
+        if inetstat_lastUpdated != 0:
+          if inetstat_data.hasKey("Total0"):
+            #total
+            if inetstat_data["Total0"].hasKey("BytesSentHigh") and inetstat_data["Total0"].hasKey("BytesSentLow"):
+              total_transmit = inetstat_data["Total0"]["BytesSentHigh"].getInt() shl 32 + inetstat_data["Total0"]["BytesSentLow"].getInt()
+            if inetstat_data["Total0"].hasKey("BytesReceivedHigh") and inetstat_data["Total0"].hasKey("BytesReceivedLow"):
+              total_receive = inetstat_data["Total0"]["BytesReceivedHigh"].getInt() shl 32 + inetstat_data["Total0"]["BytesReceivedLow"].getInt()
+
+            #month
+            if inetstat_data["ThisMonth0"].hasKey("BytesSentHigh") and inetstat_data["ThisMonth0"].hasKey("BytesSentLow"):
+              month_transmit = inetstat_data["ThisMonth0"]["BytesSentHigh"].getInt() shl 32 + inetstat_data["ThisMonth0"]["BytesSentLow"].getInt()
+            if inetstat_data["ThisMonth0"].hasKey("BytesReceivedHigh") and inetstat_data["ThisMonth0"].hasKey("BytesReceivedLow"):
+              month_receive = inetstat_data["Total0"]["BytesReceivedHigh"].getInt() shl 32 + inetstat_data["Total0"]["BytesReceivedLow"].getInt()
+                         
+            #today
+            if inetstat_data["Today0"].hasKey("BytesSentHigh") and inetstat_data["Today0"].hasKey("BytesSentLow"):
+              today_transmit = inetstat_data["Today0"]["BytesSentHigh"].getInt() shl 32 + inetstat_data["Today0"]["BytesSentLow"].getInt()
+            if inetstat_data["Today0"].hasKey("BytesReceivedHigh") and inetstat_data["Today0"].hasKey("BytesReceivedLow"):
+              today_receive = inetstat_data["Today0"]["BytesReceivedHigh"].getInt() shl 32 + inetstat_data["Today0"]["BytesReceivedLow"].getInt()
+
+            #total     
+            res &= "fritzbox_network_transmit_bytes_total " & $(total_transmit) & " " & $(inetstat_lastUpdated * 1000) & "\n"
+            res &= "fritzbox_network_receive_bytes_total " & $(total_receive) & " " & $(inetstat_lastUpdated * 1000) & "\n"
+
+            #month
+            res &= "fritzbox_network_transmit_bytes_month " & $(month_transmit) & " " & $(inetstat_lastUpdated * 1000) & "\n"
+            res &= "fritzbox_network_receive_bytes_month " & $(month_receive) & " " & $(inetstat_lastUpdated * 1000) & "\n"
+
+            #today
+            res &= "fritzbox_network_transmit_bytes_today " & $(today_transmit) & " " & $(inetstat_lastUpdated * 1000) & "\n"
+            res &= "fritzbox_network_receive_bytes_today " & $(today_receive) & " " & $(inetstat_lastUpdated * 1000) & "\n"
+
+        if mobiled_lastUpdated != 0:
+          if mobiled_data.hasKey("ue0"):
+            if mobiled_data["ue0"].hasKey("conn_rate_rx"): downstream = mobiled_data["ue0"]["conn_rate_rx"].getInt()
+            if mobiled_data["ue0"].hasKey("conn_rate_tx"): upstream = mobiled_data["ue0"]["conn_rate_tx"].getInt()
+             
+            if mobiled_data["ue0"].hasKey("conn_cell"):
+              let cells = mobiled_data["ue0"]["conn_cell"].getStr.split(",")
+
+              var cell0 = ""
+              var cell1 = ""
+              
+              if cells[0] != "" and mobiled_data.hasKey("cell"&cells[0]): cell0 = "cell"&cells[0]
+              if cells[1] != "" and mobiled_data.hasKey("cell"&cells[1]): cell1 = "cell"&cells[1]
+                
+              if cell0 != "":
+                if mobiled_data[cell0].hasKey("technology"): cell0_technology = mobiled_data[cell0]["technology"].getStr()
+                if mobiled_data[cell0].hasKey("quality"): cell0_quality = mobiled_data[cell0]["quality"].getInt()
+                if mobiled_data[cell0].hasKey("band"): cell0_band = mobiled_data[cell0]["band"].getInt()
+                if mobiled_data[cell0].hasKey("distance"): cell0_distance = mobiled_data[cell0]["distance"].getInt()
+
+              if cell1 != "":
+                if mobiled_data[cell1].hasKey("technology"): cell1_technology = mobiled_data[cell1]["technology"].getStr()
+                if mobiled_data[cell1].hasKey("quality"): cell1_quality = mobiled_data[cell1]["quality"].getInt()
+                if mobiled_data[cell1].hasKey("band"): cell1_band = mobiled_data[cell1]["band"].getInt()
+                if mobiled_data[cell1].hasKey("distance"): cell1_distance = mobiled_data[cell1]["distance"].getInt()
+
+                
+
+          res &= "fritzbox_network_downstram " & $(downstream) & " " & $(mobiled_lastUpdated * 1000) & "\n"
+          res &= "fritzbox_network_upstream " & $(upstream) & " " & $(mobiled_lastUpdated * 1000) & "\n"
+
+          if cell0_band != 0: res &= "fritzbox_network_band{cell=\"0\"} " & $(cell0_band) & " " & $(mobiled_lastUpdated * 1000) & "\n"
+          if cell0_quality != 0: res &= "fritzbox_network_quality{cell=\"0\"} " & $(cell0_quality) & " " & $(mobiled_lastUpdated * 1000) & "\n"
+          if cell0_distance != 0: res &= "fritzbox_network_distance{cell=\"0\"} " & $(cell0_distance/1000) & " " & $(mobiled_lastUpdated * 1000) & "\n"
+
+          if cell1_band != 0: res &= "fritzbox_network_band{cell=\"1\"} " & $(cell1_band) & " " & $(mobiled_lastUpdated * 1000) & "\n"
+          if cell1_quality != 0: res &= "fritzbox_network_quality{cell=\"1\"} " & $(cell1_quality) & " " & $(mobiled_lastUpdated * 1000) & "\n"
+          if cell1_distance != 0: res &= "fritzbox_network_distance{cell=\"1\"} " & $(cell1_distance/1000) & " " & $(mobiled_lastUpdated * 1000) & "\n"
+
+          await req.respond(Http200, res)
+      elif req.url.path == "/inetstat.json":
+        await req.respond(Http200, $(%* inetstat_data))
+      elif req.url.path == "/mobiled.json":
+        await req.respond(Http200, $(%* mobiled_data))
+      else:
+        await req.respond(Http404, "404 Not found")
+    else:
+      await req.respond(Http500, "500 No data yet")
+
+  elif req.reqMethod == HttpPost:
+    if req.url.query == authToken:
+      if req.url.path == "/update/inetstat":
+        inetstat_data = parse_ctlmgr(req.body)
+        inetstat_lastUpdated = toUnix(getTime())
+        await req.respond(Http200, "Noted, thanks")
+      elif req.url.path == "/update/mobiled":
+        mobiled_data = parse_ctlmgr(req.body)
+        mobiled_lastUpdated = toUnix(getTime())
+        await req.respond(Http200, "Noted, thanks")
+      else:
+        await req.respond(Http404, "404 Not found")
+    else:
+      await req.respond(Http401, "401 Unauthorized")
+
+  else:
+    await req.respond(Http405, "405 Method Not Allowed")
+
+proc serveHttp*() {.async.} =
+  httpPort = 1234
+  authToken = "penis123"
+  var httpServer = newAsyncHttpServer()
+  await httpServer.serve(Port(httpPort), processHttpClient)
+
+waitFor serveHttp()