commit 028155f33c38eda55a2239868bd75121136af5af
parent bd3a22c9eaec3d27df7f9790b2a851726050c086
Author: ctucx <c@ctu.cx>
Date: Tue, 18 Aug 2020 20:29:53 +0200
parent bd3a22c9eaec3d27df7f9790b2a851726050c086
Author: ctucx <c@ctu.cx>
Date: Tue, 18 Aug 2020 20:29:53 +0200
foo
6 files changed, 407 insertions(+), 90 deletions(-)
M
|
110
+++++++++++++++----------------------------------------------------------------
A
|
266
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
diff --git a/default.nix b/default.nix @@ -0,0 +1,46 @@ +let + crossPkgs = import <nixpkgs> { + crossSystem = { + config = "mips-unknown-linux-musl"; + platform = { + name = "fritzbox"; + kernelMajor = "4.4"; + kernelArch = "mips"; + gcc = { + arch = "mips32"; + float = "soft"; + }; + }; + }; + + crossOverlays = [ + (import <nixpkgs/pkgs/top-level/static.nix>) + ]; + + overlays = [ + (self: super: rec { + }) + ]; + + config.allowUnsupportedSystem = true; + }; + + pkgs = import <nixpkgs> {}; + +in crossPkgs.stdenv.mkDerivation { + name = "fritzbox-exporter"; + + nativeBuildInputs = [ pkgs.nim ]; + src = ./.; + + buildPhase = '' + runHook preBuild + nim c --cpu:mips -d:release --nimcache:$PWD src/fb_exporter_client.nim + runHook postBuild + ''; + installPhase = '' + runHook preInstall + install -Dm755 src/fb_exporter_client $out/bin/fb_exporter_client + runHook postInstall + ''; +}
diff --git a/nim.cfg b/nim.cfg @@ -0,0 +1,2 @@ +mips.linux.gcc.exe = "mips-unknown-linux-musl-gcc" +mips.linux.gcc.linkerexe = "mips-unknown-linux-musl-gcc"
diff --git a/result b/result @@ -0,0 +1 @@ +/nix/store/jvgwrz1hhvd622x0wkgmdy15r072fffz-fritzbox-exporter-mips-unknown-linux-musl+ \ No newline at end of file
diff --git a/src/fb_exporter.nim b/src/fb_exporter.nim @@ -1,75 +1,6 @@ -import times, posix +import times, posix, utils import asyncHttpServer -proc CtrlCHook() {.noconv.} = - echo "Ctrl+C fired! \nStopping Server now!" - quit() - -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 parseCtlMgr* (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 getTxTraffic (data: JsonNode, mode: string): int = - if data[mode].hasKey("BytesSentHigh") and data[mode].hasKey("BytesSentLow"): - return data[mode]["BytesSentHigh"].getInt shl 32 + data[mode]["BytesSentLow"].getInt - -proc getRxTraffic (data: JsonNode, mode: string): int = - if data[mode].hasKey("BytesReceivedHigh") and data[mode].hasKey("BytesReceivedLow"): - return data[mode]["BytesReceivedHigh"].getInt shl 32 + data[mode]["BytesReceivedLow"].getInt - proc prometheusResponse* (request: Request, state: JsonNode) {.async.} = var res = "" @@ -120,31 +51,30 @@ proc prometheusResponse* (request: Request, state: JsonNode) {.async.} = if cells[num] != "" and data.hasKey("cell"&cells[num]): let cell = "cell"&cells[num] - if cell != "": - if data[cell].hasKey("cell_type"): - let cell_technology = data[cell]["cell_type"].getStr - let tech_id = %* { - "umts": 3, - "lte": 4 - } + if data[cell].hasKey("cell_type"): + let cell_technology = data[cell]["cell_type"].getStr + let tech_id = %* { + "umts": 3, + "lte": 4 + } - res &= "fritzbox_cell_techology{cell=\"" & $num & "\"} " & $(tech_id[cell_technology].getInt) & " " & $(lastUpdated * 1000) & "\n" + res &= "fritzbox_cell_techology{cell=\"" & $num & "\"} " & $(tech_id[cell_technology].getInt) & " " & $(lastUpdated * 1000) & "\n" - if data[cell].hasKey("quality"): - let cell_quality = data[cell]["quality"].getInt - res &= "fritzbox_network_quality{cell=\"" & $num & "\"} " & $(cell_quality) & " " & $(lastUpdated * 1000) & "\n" + if data[cell].hasKey("quality"): + let cell_quality = data[cell]["quality"].getInt + res &= "fritzbox_network_quality{cell=\"" & $num & "\"} " & $(cell_quality) & " " & $(lastUpdated * 1000) & "\n" - if data[cell].hasKey("band"): - let cell_band = data[cell]["band"].getInt - res &= "fritzbox_network_band{cell=\"" & $num & "\"} " & $(cell_band) & " " & $(lastUpdated * 1000) & "\n" + if data[cell].hasKey("band"): + let cell_band = data[cell]["band"].getInt + res &= "fritzbox_network_band{cell=\"" & $num & "\"} " & $(cell_band) & " " & $(lastUpdated * 1000) & "\n" - if data[cell].hasKey("distance"): - let cell_distance = data[cell]["distance"].getInt - res &= "fritzbox_network_distance{cell=\"" & $num & "\"} " & $(cell_distance/1000) & " " & $(lastUpdated * 1000) & "\n" + if data[cell].hasKey("distance"): + let cell_distance = data[cell]["distance"].getInt + res &= "fritzbox_network_distance{cell=\"" & $num & "\"} " & $(cell_distance/1000) & " " & $(lastUpdated * 1000) & "\n" - if data[cell].hasKey("usage"): - let cell_usage = data[cell]["usage"].getInt - res &= "fritzbox_network_usage{cell=\"" & $num & "\"} " & $(cell_usage) & " " & $(lastUpdated * 1000) & "\n" + if data[cell].hasKey("usage"): + let cell_usage = data[cell]["usage"].getInt + res &= "fritzbox_network_usage{cell=\"" & $num & "\"} " & $(cell_usage) & " " & $(lastUpdated * 1000) & "\n" data.parseCell(0) data.parseCell(1)
diff --git a/src/fb_exporter_client.nim b/src/fb_exporter_client.nim @@ -0,0 +1,266 @@ +import times, posix, osproc, utils +import asyncHttpServer + +proc prometheusResponse* (request: Request, state: JsonNode) {.async.} = + var res = "" + + if state["inetstat"]["lastUpdated"].getInt == 0 and state["mobiled"]["lastUpdated"].getInt == 0: + await request.respond(Http500, "500 No data yet", newHttpHeaders([("Content-Type","text/plain")])) + + + if state["inetstat"]["lastUpdated"].getInt != 0: + let data = state["inetstat"]["data"] + let lastUpdated = state["inetstat"]["lastUpdated"].getInt + + if data.hasKey("Total0"): + #total + res &= "fritzbox_network_transmit_bytes_total " & $(data.getTxTraffic("Total0")) & " " & $(lastUpdated * 1000) & "\n" + res &= "fritzbox_network_receive_bytes_total " & $(data.getRxTraffic("Total0")) & " " & $(lastUpdated * 1000) & "\n" + + #month + res &= "fritzbox_network_transmit_bytes_month " & $(data.getTxTraffic("ThisMonth0")) & " " & $(lastUpdated * 1000) & "\n" + res &= "fritzbox_network_receive_bytes_month " & $(data.getRxTraffic("ThisMonth0")) & " " & $(lastUpdated * 1000) & "\n" + + #today + res &= "fritzbox_network_transmit_bytes_today " & $(data.getTxTraffic("Today0")) & " " & $(lastUpdated * 1000) & "\n" + res &= "fritzbox_network_receive_bytes_today " & $(data.getRxTraffic("Today0")) & " " & $(lastUpdated * 1000) & "\n" + + + if state["mobiled"]["lastUpdated"].getInt != 0: + let data = state["mobiled"]["data"] + let lastUpdated = state["mobiled"]["lastUpdated"].getInt() + + if data.hasKey("ue0"): + if data["ue0"].hasKey("temperature") and data["ue0"]["temperature"].getInt != 0: + res &= "fritzbox_temperature " & $(data["ue0"]["temperature"].getInt/1000) & " " & $(lastUpdated * 1000) & "\n" + + if data["ue0"].hasKey("conn_rate_rx"): + let downstream = data["ue0"]["conn_rate_rx"].getInt() + res &= "fritzbox_network_downstram " & $(downstream) & " " & $(lastUpdated * 1000) & "\n" + + + if data["ue0"].hasKey("conn_rate_tx"): + let upstream = data["ue0"]["conn_rate_tx"].getInt() + res &= "fritzbox_network_upstream " & $(upstream) & " " & $(lastUpdated * 1000) & "\n" + + + if data["ue0"].hasKey("conn_cell"): + template parseCell(data: JsonNode, num: int) = + let cells = data["ue0"]["conn_cell"].getStr.split(",") + + if cells[num] != "" and data.hasKey("cell"&cells[num]): + let cell = "cell"&cells[num] + + if data[cell].hasKey("cell_type"): + let cell_technology = data[cell]["cell_type"].getStr + let tech_id = %* { + "umts": 3, + "lte": 4 + } + + res &= "fritzbox_cell_techology{cell=\"" & $num & "\"} " & $(tech_id[cell_technology].getInt) & " " & $(lastUpdated * 1000) & "\n" + + if data[cell].hasKey("quality"): + let cell_quality = data[cell]["quality"].getInt + res &= "fritzbox_network_quality{cell=\"" & $num & "\"} " & $(cell_quality) & " " & $(lastUpdated * 1000) & "\n" + + if data[cell].hasKey("band"): + let cell_band = data[cell]["band"].getInt + res &= "fritzbox_network_band{cell=\"" & $num & "\"} " & $(cell_band) & " " & $(lastUpdated * 1000) & "\n" + + if data[cell].hasKey("distance"): + let cell_distance = data[cell]["distance"].getInt + res &= "fritzbox_network_distance{cell=\"" & $num & "\"} " & $(cell_distance/1000) & " " & $(lastUpdated * 1000) & "\n" + + if data[cell].hasKey("usage"): + let cell_usage = data[cell]["usage"].getInt + res &= "fritzbox_network_usage{cell=\"" & $num & "\"} " & $(cell_usage) & " " & $(lastUpdated * 1000) & "\n" + + data.parseCell(0) + data.parseCell(1) + + await request.respond(Http200, res, newHttpHeaders([("Content-Type","text/plain")])) + + +proc statusResponse* (request: Request, state: JsonNode) {.async.} = + var res = "" + + var responseJson = %* { + "traffic": { + "lastUpdated": 0, + "total": { + "received": 0, + "transmited": 0, + }, + "month": { + "received": 0, + "transmited": 0, + }, + "today": { + "received": 0, + "transmited": 0, + } + }, + + "modem": { + "lastUpdated": 0, + "connectedCells": [] + } + } + + if state["inetstat"]["lastUpdated"].getInt == 0 and state["mobiled"]["lastUpdated"].getInt == 0: + await request.respond(Http500, "500 No data yet", newHttpHeaders([("Content-Type","text/plain")])) + + + if state["inetstat"]["lastUpdated"].getInt != 0: + let data = state["inetstat"]["data"] + responseJson["traffic"]["lastUpdated"] = state["inetstat"]["lastUpdated"] + + if data.hasKey("Total0"): + #total + responseJson["traffic"]["total"]["transmited"] = %data.getTxTraffic("Total0") + responseJson["traffic"]["total"]["received"] = %data.getRxTraffic("Total0") + + #month + responseJson["traffic"]["month"]["transmited"] = %data.getTxTraffic("ThisMonth0") + responseJson["traffic"]["month"]["received"] = %data.getRxTraffic("ThisMonth0") + + #today + responseJson["traffic"]["today"]["transmited"] = %data.getTxTraffic("Today0") + responseJson["traffic"]["today"]["received"] = %data.getRxTraffic("Today0") + + + if state["mobiled"]["lastUpdated"].getInt != 0: + let data = state["mobiled"]["data"] + responseJson["modem"]["lastUpdated"] = state["mobiled"]["lastUpdated"] + + if data.hasKey("ue0"): + if data["ue0"].hasKey("temperature") and data["ue0"]["temperature"].getInt != 0: + responseJson["modem"]["temperature"] = %(data["ue0"]["temperature"].getInt/1000) + + if data["ue0"].hasKey("spn"): + responseJson["modem"]["vendorText"] = data["ue0"]["spn"] + + if data["ue0"].hasKey("conn_rate_rx"): + responseJson["modem"]["linkSpeedRx"] = data["ue0"]["conn_rate_rx"] + + if data["ue0"].hasKey("conn_rate_tx"): + responseJson["modem"]["linkSpeedTx"] = data["ue0"]["conn_rate_tx"] + + + if data["ue0"].hasKey("conn_cell"): + proc parseCell(data: JsonNode, num: int):JsonNode = + let cells = data["ue0"]["conn_cell"].getStr.split(",") + result = %* {} + + let cell = "cell"&cells[num] + if cells[num] != "" and data.hasKey(cell): + + if data[cell].hasKey("cell_type"): + result["technology"] = data[cell]["cell_type"] + + if data[cell].hasKey("provider"): + result["provider"] = data[cell]["provider"] + + if data[cell].hasKey("plmn"): + result["plmn"] = data[cell]["plmn"] + + if data[cell].hasKey("plmn"): + result["plmn"] = data[cell]["plmn"] + + if data[cell].hasKey("cell_id"): + result["cellId"] = data[cell]["cell_id"] + + if data[cell].hasKey("rssi"): + result["rssi"] = %data[cell]["rssi"] + + if data[cell].hasKey("rscp"): + result["rscp"] = %data[cell]["rscp"] + + if data[cell].hasKey("rsrp"): + result["rsrp"] = %data[cell]["rsrp"].getStr.split(",")[0] + + if data[cell].hasKey("rsrq"): + result["rsrq"] = %data[cell]["rsrq"].getStr.split(",")[0] + + if data[cell].hasKey("sinr"): + result["sinr"] = %data[cell]["sinr"].getStr.split(",")[0] + + if data[cell].hasKey("quality"): + result["quality"] = data[cell]["quality"] + + if data[cell].hasKey("band"): + result["band"] = data[cell]["band"] + + if data[cell].hasKey("usage"): + result["usage"] = data[cell]["usage"] + + if data[cell].hasKey("distance"): + result["distance"] = data[cell]["distance"] + + responseJson["modem"]["connectedCells"].add(data.parseCell(0)) + responseJson["modem"]["connectedCells"].add(data.parseCell(1)) + + await request.respondJson(Http200, "success", "", responseJson) + +proc main = # for gcsafe + setControlCHook(CtrlCHook) + + onSignal(SIGTERM): + echo "Got SIGTERM! \nStopping Server now!" + quit() + + let authToken = "penis123" + var server = newServer("0.0.0.0", 1234) # launch on http://localhost:5000 + var state = %* { + "inetstat": { + "data": {}, + "lastUpdated": 0, + }, + "mobiled": { + "data": {}, + "lastUpdated": 0, + } + } + + proc timerProc() = + let inetstat_data = execProcess("/usr/bin/ctlmgr_ctl u inetstat") + let mobiled_data = execProcess("/usr/bin/ctlmgr_ctl u mobiled") + + state["inetstat"]["data"] = parseCtlMgr(inetstat_data) + state["inetstat"]["lastUpdated"] = %toUnix(getTime()) + + state["mobiled"]["data"] = parseCtlMgr(mobiled_data) + state["mobiled"]["lastUpdated"] = %toUnix(getTime()) + + addTimer(10000, false, proc(fd: AsyncFD): bool = + timerProc() + ) + + timerProc() + + server.pages: + equals("/"): + await request.respond(Http200, "Hello, nothing to see here", newHttpHeaders([("Content-Type","text/plain")])) + + equals("/metrics"): + await request.prometheusResponse(state) + + equals("/status.json"): + await request.statusResponse(state) + + equals("/inetstat.json"): + if state["inetstat"]["lastUpdated"].getInt != 0: + await request.respondJson(Http200, "success", "", state["inetstat"]["data"]) + else: + await request.respondJson(Http404, "error", "no data yet", newJObject()) + + equals("/mobiled.json"): + if state["mobiled"]["lastUpdated"].getInt != 0: + await request.respondJson(Http200, "success", "", state["mobiled"]["data"]) + else: + await request.respondJson(Http404, "error", "no data yet", newJObject()) + + server.start() + +main()
diff --git a/src/utils.nim b/src/utils.nim @@ -0,0 +1,70 @@ +import times, posix, strutils, json + +proc CtrlCHook* () {.noconv.} = + echo "Ctrl+C fired! \nStopping Server now!" + quit() + +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 parseCtlMgr* (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 getTxTraffic* (data: JsonNode, mode: string): int = + if data[mode].hasKey("BytesSentHigh") and data[mode].hasKey("BytesSentLow"): + return data[mode]["BytesSentHigh"].getInt shl 32 + data[mode]["BytesSentLow"].getInt + +proc getRxTraffic* (data: JsonNode, mode: string): int = + if data[mode].hasKey("BytesReceivedHigh") and data[mode].hasKey("BytesReceivedLow"): + return data[mode]["BytesReceivedHigh"].getInt shl 32 + data[mode]["BytesReceivedLow"].getInt+ \ No newline at end of file