Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions pm_dashboard/data_logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@

class DataLogger:

DEFAULT_VIRTUAL_INTERFACE_PREFIXES = ['veth', 'br-', 'docker', 'virbr', 'vmnet']

@log_error
def __init__(self, database='pm_dashboard', interval=1, spc_enabled=False, get_logger=None):
if get_logger is None:
Expand All @@ -45,6 +47,7 @@ def __init__(self, database='pm_dashboard', interval=1, spc_enabled=False, get_l

self.db = Database(database, get_logger=get_logger)
self.interval = interval
self.virtual_interface_prefixes = list(self.DEFAULT_VIRTUAL_INTERFACE_PREFIXES)
if spc_enabled:
self.log.info("SPC peripheral enabled")
from spc.spc import SPC
Expand All @@ -68,11 +71,21 @@ def update_status(self, status):
def set_interval(self, interval):
self.interval = interval

@log_error
def set_virtual_interface_prefixes(self, prefixes):
if isinstance(prefixes, list) and all(isinstance(prefix, str) for prefix in prefixes):
self.virtual_interface_prefixes = prefixes

def _get_exclude_prefixes(self):
prefixes = tuple(self.virtual_interface_prefixes)
return prefixes or None

@log_error
def get_data(self):
exclude_prefixes = self._get_exclude_prefixes()
boot_time = get_boot_time()
ips = get_ips()
macs = get_macs()
ips = get_ips(exclude_prefixes=exclude_prefixes)
macs = get_macs(exclude_prefixes=exclude_prefixes)
network_connection_type = get_network_connection_type()
network_speed = get_network_speed()

Expand Down Expand Up @@ -111,11 +124,9 @@ def get_data(self):

data['boot_time'] = float(boot_time)

ips = get_ips()
for name in ips:
data[f'ip_{name}'] = ips[name]

macs = get_macs()
for name in macs:
data[f'mac_{name}'] = macs[name]

Expand Down
49 changes: 48 additions & 1 deletion pm_dashboard/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,46 @@ def __init__(self, database, get_logger=None):
self.log = get_logger(__name__)
self.database = database
self.influx_manually_started = False
self._field_keys_cache = {}
self._virtual_prefixes = self._build_virtual_field_prefixes(['veth', 'br-', 'docker', 'virbr', 'vmnet'])

self.client = InfluxDBClient(host='localhost', port=8086)

@staticmethod
def _build_virtual_field_prefixes(interface_prefixes):
return tuple(
f'{field_type}_{iface}'
for iface in interface_prefixes
for field_type in ('mac', 'ip')
)

@staticmethod
def _quote_identifier(identifier):
return '"' + identifier.replace('"', '\\"') + '"'

def set_virtual_prefixes(self, interface_prefixes):
if not isinstance(interface_prefixes, list) or not all(isinstance(prefix, str) for prefix in interface_prefixes):
return
self._virtual_prefixes = self._build_virtual_field_prefixes(interface_prefixes)
self._field_keys_cache.clear()

def get_stable_field_keys(self, measurement, max_age=60):
cached = self._field_keys_cache.get(measurement)
if cached and (time.time() - cached[0]) < max_age:
return cached[1]

try:
result = self.client.query(f'SHOW FIELD KEYS FROM "{measurement}"')
all_keys = [row['fieldKey'] for row in result.get_points()]
stable_keys = [
key for key in all_keys
if not key.startswith(self._virtual_prefixes)
]
self._field_keys_cache[measurement] = (time.time(), stable_keys)
return stable_keys
except Exception as e:
self.log.warning(f"get_stable_field_keys failed for {measurement}: {e}")
return []

def set_debug_level(self, level):
self.log.info(f"Setting debug level to {level}")
Expand Down Expand Up @@ -113,7 +151,11 @@ def get_data_by_time_range(self, measurement, start_time, end_time, keys="*", fu
if function not in ["mean", "sum", "min", "max", "count"]:
self.log.error(f"Invalid function: {function}")
return []
if keys != "*":
if keys == "*":
stable_keys = self.get_stable_field_keys(measurement)
if stable_keys:
keys = ",".join(self._quote_identifier(k) for k in stable_keys)
else:
newKeys = []
for k in keys.split(","):
newKeys.append(f'{function}("{k}") as "{k}"')
Expand Down Expand Up @@ -142,6 +184,11 @@ def get(self, measurement, key="*", n=1):
if not self.is_ready():
self.log.error('Database is not ready')
return []
if key == "*":
stable_keys = self.get_stable_field_keys(measurement)
if stable_keys:
key = ",".join(self._quote_identifier(k) for k in stable_keys)

for _ in range(3):
query = f"SELECT {key} FROM {measurement} ORDER BY time DESC LIMIT {n}"
result = self.client.query(query)
Expand Down
23 changes: 22 additions & 1 deletion pm_dashboard/pm_dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
__device_info__ = {}
__mqtt_connected__ = False
__enable_history__ = False
DEFAULT_VIRTUAL_INTERFACE_PREFIXES = ['veth', 'br-', 'docker', 'virbr', 'vmnet']

__on_outside_config_changed__ = lambda config: None
__on_inside_config_changed__ = lambda config: None
Expand Down Expand Up @@ -283,9 +284,19 @@ def get_disk_list():
@__app__.route(f'{__api_prefix__}/get-network-interface-list')
@cross_origin()
def get_network_interface_list():
interfaces = list(get_ips().keys())
prefixes = tuple(__config__.get('system', {}).get('virtual_interface_prefixes', DEFAULT_VIRTUAL_INTERFACE_PREFIXES))
interfaces = list(get_ips(exclude_prefixes=prefixes or None).keys())
return {"status": True, "data": interfaces}

@__app__.route(f'{__api_prefix__}/set-virtual-interface-prefixes', methods=['POST'])
@cross_origin()
def set_virtual_interface_prefixes():
prefixes = request.json.get("prefixes")
if not isinstance(prefixes, list) or not all(isinstance(prefix, str) for prefix in prefixes):
return {"status": False, "error": "[ERROR] prefixes must be a list of strings"}
__on_config_changed__({'system': {'virtual_interface_prefixes': prefixes}})
return {"status": True, "data": "OK"}

@__app__.route(f'{__api_prefix__}/set-temperature-unit', methods=['POST'])
@cross_origin()
def set_temperature_unit():
Expand Down Expand Up @@ -479,16 +490,20 @@ def __init__(self, device_info=None, database='pm_dashboard', spc_enabled=False,
__config__ = config
if 'enable_history' not in __config__['system']:
__config__['system']['enable_history'] = False
if 'virtual_interface_prefixes' not in __config__['system']:
__config__['system']['virtual_interface_prefixes'] = list(DEFAULT_VIRTUAL_INTERFACE_PREFIXES)
__enable_history__ = config['system']['enable_history']

self.data_logger = DataLogger(
database=database,
spc_enabled=spc_enabled,
interval=__config__['system']['data_interval'],
get_logger=get_logger)
self.data_logger.set_virtual_interface_prefixes(__config__['system']['virtual_interface_prefixes'])
__data_logger__ = self.data_logger
if __enable_history__:
__db__ = Database(database, get_logger=get_logger)
__db__.set_virtual_prefixes(__config__['system']['virtual_interface_prefixes'])

self.started = False
__on_inside_config_changed__ = self.on_config_changed
Expand Down Expand Up @@ -516,8 +531,14 @@ def start(self):

@log_error
def on_config_changed(self, config):
global __enable_history__, __db__
if 'data_interval' in config['system']:
self.data_logger.set_interval(config['system']['data_interval'])
if 'virtual_interface_prefixes' in config['system']:
prefixes = config['system']['virtual_interface_prefixes']
self.data_logger.set_virtual_interface_prefixes(prefixes)
if __db__ is not None:
__db__.set_virtual_prefixes(prefixes)
if 'enable_history' in config['system']:
if config['system']['enable_history'] == True:
if __enable_history__ == False:
Expand Down