diff --git a/.github/workflows/DockerBuildAndPush.yml b/.github/workflows/DockerBuildAndPush.yml new file mode 100644 index 0000000..a19510c --- /dev/null +++ b/.github/workflows/DockerBuildAndPush.yml @@ -0,0 +1,43 @@ +name: DockerBuildAndPush + +on: + push: + branches: main + +jobs: + multi: + runs-on: ubuntu-latest + steps: + - + name: Checkout + uses: actions/checkout@v2 + - + name: Set up QEMU + uses: docker/setup-qemu-action@v1 + - + name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + - + name: Login to GitHub Container Registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.CR_PAT }} + # - + # name: Login to DockerHub + # uses: docker/login-action@v1 + # with: + # username: ${{ secrets.DOCKERHUB_USERNAME }} + # password: ${{ secrets.DOCKERHUB_TOKEN }} + - + name: Build and push + uses: docker/build-push-action@v2 + with: + context: . + file: ./Dockerfile + platforms: linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/ppc64le,linux/s390x + push: true + tags: | + ghcr.io/ncareau/netgear-lte-exporter:latest +# ncareau/netgear-lte-exporter:latest \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0c2ad09 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.env diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..0830723 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM python:3.9 + +WORKDIR /app + +COPY requirements.txt requirements.txt + +RUN pip install -r requirements.txt + +COPY . /app + +CMD [ "python", "main.py" ] \ No newline at end of file diff --git a/README.md b/README.md index 920a1fe..f454be6 100644 --- a/README.md +++ b/README.md @@ -1 +1,12 @@ -# netgear-lte-exporter +Netgear LTE Modem Exporter +=== + + +# Tested Devices + +* Netgear LM1200 + +# Thanks + +https://github.com/amelchio/eternalegypt +https://github.com/ickymettle/netgear_cm_exporter diff --git a/main.py b/main.py new file mode 100644 index 0000000..2667d37 --- /dev/null +++ b/main.py @@ -0,0 +1,217 @@ +"""Application exporter""" + +import os +import time +from prometheus_client import start_http_server, Gauge, Enum, Info +# import requests + +import asyncio +import aiohttp +import eternalegypt + +from dotenv import load_dotenv + +load_dotenv() + +class NetgearLTEMetrics: + """ + Representation of Prometheus metrics and loop to fetch and transform + application metrics into Prometheus metrics. + """ + + def __init__(self, app_port=80, polling_interval_seconds=5 ): + self.app_port = app_port + self.polling_interval_seconds = polling_interval_seconds + + #NetgearLTE + self.rx_level = Gauge("netgear_lte_rx_level", "RX Level") + self.tx_level = Gauge("netgear_lte_tx_level", "TX Level") + self.radio_quality = Gauge("netgear_lte_radio_quality", "Radio Quality") + self.usage = Gauge("netgear_lte_usage", "Usage") + + # Prometheus metrics to collect + self.current_requests = Gauge("app_requests_current", "Current requests") + self.pending_requests = Gauge("app_requests_pending", "Pending requests") + + self.app_version = Info("app_version", "Firmware version") + self.serial = Info("serial", "Serial Number") + self.data_usage = Gauge("data_usage", "Data usage") + + self.total_uptime = Gauge("app_uptime", "Uptime") + self.health = Enum("app_health", "Health", states=["healthy", "unhealthy"]) + + def run_metrics_loop(self): + """Metrics fetching loop""" + + while True: + asyncio.get_event_loop().run_until_complete(self.fetch()) + time.sleep(self.polling_interval_seconds) + + async def fetch(self): + """ + Get metrics from application and refresh Prometheus metrics with + new values. + """ + + # Fetch raw status data from the application + # resp = requests.get(url=f"http://localhost:{self.app_port}/status") + # status_data = resp.json() + + jar = aiohttp.CookieJar(unsafe=True) + websession = aiohttp.ClientSession(cookie_jar=jar) + + try: + modem = eternalegypt.Modem(hostname=os.getenv("MODEM_HOST"), websession=websession) + await modem.login(password=os.getenv("MODEM_PASS")) + result = await modem.information() + + await websession.close() + + + self.rx_level.set(result.rx_level) + self.tx_level.set(result.tx_level) + self.radio_quality.set(result.radio_quality) + self.usage.set(result.usage) + + # Update Prometheus metrics with application metrics + self.data_usage.set(result.usage) + self.serial.info({"number":result.serial_number}) + self.current_requests.set(2) + self.pending_requests.set(3) + self.total_uptime.set(1111) + self.health.state("healthy") + + + await modem.logout() + except eternalegypt.Error: + print("Could not login") + + await websession.close() + +# async def get_information(): +# """Example of printing the current upstream.""" +# jar = aiohttp.CookieJar(unsafe=True) +# websession = aiohttp.ClientSession(cookie_jar=jar) + +# try: +# modem = eternalegypt.Modem(hostname=os.getenv("MODEM_HOST"), websession=websession) +# await modem.login(password=os.getenv("MODEM_PASS")) + +# result = await modem.information() +# # if len(sys.argv) == 3: +# print("serial_number: {}".format(result.serial_number)) +# print("usage: {}".format(result.usage)) +# print("upstream: {}".format(result.upstream)) +# print("wire_connected: {}".format(result.wire_connected)) +# print("mobile_connected: {}".format(result.mobile_connected)) +# print("connection_text: {}".format(result.connection_text)) +# print("connection_type: {}".format(result.connection_type)) +# print("current_nw_service_type: {}".format(result.current_nw_service_type)) +# print("current_ps_service_type: {}".format(result.current_ps_service_type)) +# print("register_network_display: {}".format(result.register_network_display)) +# print("roaming: {}".format(result.roaming)) +# print("radio_quality: {}".format(result.radio_quality)) +# print("rx_level: {}".format(result.rx_level)) +# print("tx_level: {}".format(result.tx_level)) +# print("current_band: {}".format(result.current_band)) +# print("cell_id: {}".format(result.cell_id)) +# # else: +# # key = sys.argv[3] +# # print("{}: {}".format(key, result.items.get(key))) + +# await modem.logout() +# except eternalegypt.Error: +# print("Could not login") + +# await websession.close() + +# async def debugStuff(): +# jar = aiohttp.CookieJar(unsafe=True) +# websession = aiohttp.ClientSession(cookie_jar=jar) + +# modem = eternalegypt.Modem(hostname="xxx", websession=websession) +# await modem.login(password="xxx") +# await modem.delete_sms(sms_id=0) +# await modem.delete_sms(sms_id=1) +# await modem.delete_sms(sms_id=2) +# await modem.sms(message="Thisisatest",phone="xxx") +# await modem.logout() + + + +# @autologin +# async def information(self): +# """Return the current information.""" +# url = self._url('model.json') +# async with self.websession.get(url) as response: +# data = json.loads(await response.text()) + +# try: +# result = self._build_information(data) +# _LOGGER.debug("Did read information: %s", data) +# except KeyError as ex: +# _LOGGER.debug("Failed to read information (%s): %s", ex, data) +# raise Error() + +# self._sms_events(result) + +# return result + +# data['sms']['unreadMsgs'] +# data['sms']['msgCount'] +# data['general']['appVersion'] + +# result.serial_number +# result.usage +# result.upstream +# result.wire_connected +# result.mobile_connected +# result.connection_text +# result.connection_type +# result.current_nw_service_type +# result.current_ps_service_type +# result.register_network_display +# result.roaming +# result.radio_quality +# result.rx_level +# result.tx_level +# result.current_band +# result.cell_id + +# async def login(): +# jar = aiohttp.CookieJar(unsafe=True) +# websession = aiohttp.ClientSession(cookie_jar=jar) + +# modem = eternalegypt.Modem(hostname="xxx", websession=websession) +# await modem.login(password="xxx") + + +# await websession.close() +# return modem + +def main(): + """Main entry point""" + + polling_interval_seconds = int(os.getenv("POLLING_INTERVAL_SECONDS", "10")) + app_port = int(os.getenv("APP_PORT", "80")) + exporter_port = int(os.getenv("EXPORTER_PORT", "9877")) + + # asyncio.get_event_loop().run_until_complete(get_information()) + + # modem = asyncio.get_event_loop().run_until_complete(login()) + + # asyncio.get_event_loop().run_until_complete(debugStuff()) + + # while(True): + # asyncio.get_event_loop().run_until_complete(get_information()) + # time.sleep(2) + + app_metrics = NetgearLTEMetrics( + app_port=app_port, + polling_interval_seconds=polling_interval_seconds + ) + start_http_server(exporter_port) + app_metrics.run_metrics_loop() + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..8a1147a --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +prometheus_client +eternalegypt +python-dotenv \ No newline at end of file