mirror of https://github.com/craigerl/aprsd.git
Added the fetch-stats command
You can now fetch and view the stats of a live running aprsd server if it has enabled the rpc server in the config file's rpc_settings block. You just have to match the magic word as specified in the config file to authorize against the rpc server. aprsd fetch-stats --ip-address <ip of aprsd> --port <port> --magic-word <magic word>
This commit is contained in:
parent
fe0d71de4d
commit
b2e621da4b
|
@ -0,0 +1,131 @@
|
||||||
|
# Fetch active stats from a remote running instance of aprsd server
|
||||||
|
# This uses the RPC server to fetch the stats from the remote server.
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import click
|
||||||
|
from click_params import IP_ADDRESS
|
||||||
|
from oslo_config import cfg
|
||||||
|
from rich.console import Console
|
||||||
|
from rich.table import Table
|
||||||
|
|
||||||
|
# local imports here
|
||||||
|
import aprsd
|
||||||
|
from aprsd import cli_helper
|
||||||
|
from aprsd.main import cli
|
||||||
|
from aprsd.rpc import client as rpc_client
|
||||||
|
|
||||||
|
|
||||||
|
# setup the global logger
|
||||||
|
# logging.basicConfig(level=logging.DEBUG) # level=10
|
||||||
|
LOG = logging.getLogger("APRSD")
|
||||||
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command()
|
||||||
|
@cli_helper.add_options(cli_helper.common_options)
|
||||||
|
@click.option(
|
||||||
|
"--ip-address", type=IP_ADDRESS,
|
||||||
|
default=CONF.rpc_settings.ip,
|
||||||
|
help="IP address of the remote aprsd server to fetch stats from.",
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--port", type=int,
|
||||||
|
default=CONF.rpc_settings.port,
|
||||||
|
help="Port of the remote aprsd server rpc port to fetch stats from.",
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--magic-word", type=str,
|
||||||
|
default=CONF.rpc_settings.magic_word,
|
||||||
|
help="Magic word of the remote aprsd server rpc port to fetch stats from.",
|
||||||
|
)
|
||||||
|
@click.pass_context
|
||||||
|
@cli_helper.process_standard_options
|
||||||
|
def fetch_stats(ctx, ip_address, port, magic_word):
|
||||||
|
"""Fetch stats from a remote running instance of aprsd server."""
|
||||||
|
LOG.info(f"APRSD Fetch-Stats started version: {aprsd.__version__}")
|
||||||
|
|
||||||
|
CONF.log_opt_values(LOG, logging.DEBUG)
|
||||||
|
|
||||||
|
msg = f"Fetching stats from {ip_address}:{port} with magic word '{magic_word}'"
|
||||||
|
|
||||||
|
console = Console()
|
||||||
|
with console.status(msg) as status:
|
||||||
|
client = rpc_client.RPCClient(ip_address, port, magic_word)
|
||||||
|
stats = client.get_stats_dict()
|
||||||
|
console.print_json(data=stats)
|
||||||
|
aprsd_title = (
|
||||||
|
"APRSD "
|
||||||
|
f"[bold cyan]v{stats['aprsd']['version']}[/] "
|
||||||
|
f"Callsign [bold green]{stats['aprsd']['callsign']}[/] "
|
||||||
|
f"Uptime [bold yellow]{stats['aprsd']['uptime']}[/]"
|
||||||
|
)
|
||||||
|
|
||||||
|
console.rule(f"Stats from {ip_address}:{port} with magic word '{magic_word}'")
|
||||||
|
console.print("\n\n")
|
||||||
|
console.rule(aprsd_title)
|
||||||
|
|
||||||
|
# Show the connection to APRS
|
||||||
|
# It can be a connection to an APRS-IS server or a local TNC via KISS or KISSTCP
|
||||||
|
if "aprs-is" in stats:
|
||||||
|
title = f"APRS-IS Connection {stats['aprs-is']['server']}"
|
||||||
|
table = Table(title=title)
|
||||||
|
table.add_column("Key")
|
||||||
|
table.add_column("Value")
|
||||||
|
for key, value in stats["aprs-is"].items():
|
||||||
|
table.add_row(key, value)
|
||||||
|
console.print(table)
|
||||||
|
|
||||||
|
|
||||||
|
msgs_table = Table(title="Messages")
|
||||||
|
msgs_table.add_column("Key")
|
||||||
|
msgs_table.add_column("Value")
|
||||||
|
for key, value in stats["messages"].items():
|
||||||
|
msgs_table.add_row(key, str(value))
|
||||||
|
|
||||||
|
console.print(msgs_table)
|
||||||
|
|
||||||
|
packets_table = Table(title="Packets")
|
||||||
|
packets_table.add_column("Key")
|
||||||
|
packets_table.add_column("Value")
|
||||||
|
for key, value in stats["packets"].items():
|
||||||
|
packets_table.add_row(key, str(value))
|
||||||
|
|
||||||
|
console.print(packets_table)
|
||||||
|
|
||||||
|
if "plugins" in stats:
|
||||||
|
plugins_table = Table(title="Plugins")
|
||||||
|
plugins_table.add_column("Plugin")
|
||||||
|
plugins_table.add_column("Enabled")
|
||||||
|
plugins_table.add_column("Version")
|
||||||
|
plugins_table.add_column("TX")
|
||||||
|
plugins_table.add_column("RX")
|
||||||
|
for key, value in stats["plugins"].items():
|
||||||
|
plugins_table.add_row(
|
||||||
|
key,
|
||||||
|
str(stats["plugins"][key]["enabled"]),
|
||||||
|
stats["plugins"][key]["version"],
|
||||||
|
str(stats["plugins"][key]["tx"]),
|
||||||
|
str(stats["plugins"][key]["rx"]),
|
||||||
|
)
|
||||||
|
|
||||||
|
console.print(plugins_table)
|
||||||
|
|
||||||
|
if "seen_list" in stats["aprsd"]:
|
||||||
|
seen_table = Table(title="Seen List")
|
||||||
|
seen_table.add_column("Callsign")
|
||||||
|
seen_table.add_column("Message Count")
|
||||||
|
seen_table.add_column("Last Heard")
|
||||||
|
for key, value in stats["aprsd"]["seen_list"].items():
|
||||||
|
seen_table.add_row(key, str(value["count"]), value["last"])
|
||||||
|
|
||||||
|
console.print(seen_table)
|
||||||
|
|
||||||
|
if "watch_list" in stats["aprsd"]:
|
||||||
|
watch_table = Table(title="Watch List")
|
||||||
|
watch_table.add_column("Callsign")
|
||||||
|
watch_table.add_column("Last Heard")
|
||||||
|
for key, value in stats["aprsd"]["watch_list"].items():
|
||||||
|
watch_table.add_row(key, value["last"])
|
||||||
|
|
||||||
|
console.print(watch_table)
|
|
@ -70,8 +70,8 @@ def main():
|
||||||
# First import all the possible commands for the CLI
|
# First import all the possible commands for the CLI
|
||||||
# The commands themselves live in the cmds directory
|
# The commands themselves live in the cmds directory
|
||||||
from .cmds import ( # noqa
|
from .cmds import ( # noqa
|
||||||
completion, dev, healthcheck, list_plugins, listen, send_message,
|
completion, dev, fetch_stats, healthcheck, list_plugins, listen,
|
||||||
server, webchat,
|
send_message, server, webchat,
|
||||||
)
|
)
|
||||||
cli(auto_envvar_prefix="APRSD")
|
cli(auto_envvar_prefix="APRSD")
|
||||||
|
|
||||||
|
|
|
@ -16,23 +16,32 @@ class RPCClient:
|
||||||
_instance = None
|
_instance = None
|
||||||
_rpc_client = None
|
_rpc_client = None
|
||||||
|
|
||||||
|
ip = None
|
||||||
|
port = None
|
||||||
|
magic_word = None
|
||||||
|
|
||||||
def __new__(cls, *args, **kwargs):
|
def __new__(cls, *args, **kwargs):
|
||||||
if cls._instance is None:
|
if cls._instance is None:
|
||||||
cls._instance = super().__new__(cls)
|
cls._instance = super().__new__(cls)
|
||||||
return cls._instance
|
return cls._instance
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, ip=None, port=None, magic_word=None):
|
||||||
|
self.ip = str(ip) or CONF.rpc_settings.ip
|
||||||
|
self.port = int(port) or CONF.rpc_settings.port
|
||||||
|
self.magic_word = magic_word or CONF.rpc_settings.magic_word
|
||||||
self._check_settings()
|
self._check_settings()
|
||||||
self.get_rpc_client()
|
self.get_rpc_client()
|
||||||
|
|
||||||
def _check_settings(self):
|
def _check_settings(self):
|
||||||
if not CONF.rpc_settings.enabled:
|
if not CONF.rpc_settings.enabled:
|
||||||
LOG.error("RPC is not enabled, no way to get stats!!")
|
LOG.warning("RPC is not enabled, no way to get stats!!")
|
||||||
|
|
||||||
if CONF.rpc_settings.magic_word == conf.common.APRSD_DEFAULT_MAGIC_WORD:
|
if self.magic_word == conf.common.APRSD_DEFAULT_MAGIC_WORD:
|
||||||
LOG.warning("You are using the default RPC magic word!!!")
|
LOG.warning("You are using the default RPC magic word!!!")
|
||||||
LOG.warning("edit aprsd.conf and change rpc_settings.magic_word")
|
LOG.warning("edit aprsd.conf and change rpc_settings.magic_word")
|
||||||
|
|
||||||
|
LOG.debug(f"RPC Client: {self.ip}:{self.port} {self.magic_word}")
|
||||||
|
|
||||||
def _rpyc_connect(
|
def _rpyc_connect(
|
||||||
self, host, port,
|
self, host, port,
|
||||||
service=rpyc.VoidService,
|
service=rpyc.VoidService,
|
||||||
|
@ -53,11 +62,11 @@ class RPCClient:
|
||||||
|
|
||||||
def get_rpc_client(self):
|
def get_rpc_client(self):
|
||||||
if not self._rpc_client:
|
if not self._rpc_client:
|
||||||
magic = CONF.rpc_settings.magic_word
|
CONF.rpc_settings.magic_word
|
||||||
self._rpc_client = self._rpyc_connect(
|
self._rpc_client = self._rpyc_connect(
|
||||||
CONF.rpc_settings.ip,
|
self.ip,
|
||||||
CONF.rpc_settings.port,
|
self.port,
|
||||||
authorizer=lambda sock: sock.send(magic.encode()),
|
authorizer=lambda sock: sock.send(self.magic_word.encode()),
|
||||||
)
|
)
|
||||||
return self._rpc_client
|
return self._rpc_client
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
aprslib>=0.7.0
|
aprslib>=0.7.0
|
||||||
click
|
click
|
||||||
|
click-params
|
||||||
click-completion
|
click-completion
|
||||||
flask==2.1.2
|
flask==2.1.2
|
||||||
werkzeug==2.1.2
|
werkzeug==2.1.2
|
||||||
|
|
|
@ -13,13 +13,15 @@ bitarray==2.7.6 # via ax253, kiss3
|
||||||
certifi==2023.5.7 # via requests
|
certifi==2023.5.7 # via requests
|
||||||
cffi==1.15.1 # via cryptography
|
cffi==1.15.1 # via cryptography
|
||||||
charset-normalizer==3.2.0 # via requests
|
charset-normalizer==3.2.0 # via requests
|
||||||
click==8.1.4 # via -r requirements.in, click-completion, flask
|
click==8.1.4 # via -r requirements.in, click-completion, click-params, flask
|
||||||
click-completion==0.5.2 # via -r requirements.in
|
click-completion==0.5.2 # via -r requirements.in
|
||||||
|
click-params==0.4.1 # via -r requirements.in
|
||||||
commonmark==0.9.1 # via rich
|
commonmark==0.9.1 # via rich
|
||||||
cryptography==38.0.1 # via -r requirements.in, pyopenssl
|
cryptography==38.0.1 # via -r requirements.in, pyopenssl
|
||||||
dacite2==2.0.0 # via -r requirements.in
|
dacite2==2.0.0 # via -r requirements.in
|
||||||
dataclasses==0.6 # via -r requirements.in
|
dataclasses==0.6 # via -r requirements.in
|
||||||
debtcollector==2.5.0 # via oslo-config
|
debtcollector==2.5.0 # via oslo-config
|
||||||
|
decorator==5.1.1 # via validators
|
||||||
dnspython==2.3.0 # via eventlet
|
dnspython==2.3.0 # via eventlet
|
||||||
eventlet==0.33.3 # via -r requirements.in
|
eventlet==0.33.3 # via -r requirements.in
|
||||||
flask==2.1.2 # via -r requirements.in, flask-classful, flask-httpauth, flask-socketio
|
flask==2.1.2 # via -r requirements.in, flask-classful, flask-httpauth, flask-socketio
|
||||||
|
@ -66,6 +68,7 @@ ua-parser==0.18.0 # via user-agents
|
||||||
update-checker==0.18.0 # via -r requirements.in
|
update-checker==0.18.0 # via -r requirements.in
|
||||||
urllib3==2.0.3 # via requests
|
urllib3==2.0.3 # via requests
|
||||||
user-agents==2.2.0 # via -r requirements.in
|
user-agents==2.2.0 # via -r requirements.in
|
||||||
|
validators==0.20.0 # via click-params
|
||||||
werkzeug==2.1.2 # via -r requirements.in, flask
|
werkzeug==2.1.2 # via -r requirements.in, flask
|
||||||
wrapt==1.15.0 # via -r requirements.in, debtcollector
|
wrapt==1.15.0 # via -r requirements.in, debtcollector
|
||||||
zipp==3.15.0 # via importlib-metadata
|
zipp==3.16.0 # via importlib-metadata
|
||||||
|
|
Loading…
Reference in New Issue