Skip to content

Commit

Permalink
Rewrite code snippet handling
Browse files Browse the repository at this point in the history
Each former code snippet has been transformed into a full-fledged python module.
Responses generated by modules are cached in a single ttl-based cache reducing
required CPU-time for subsequent queries drastically.
  • Loading branch information
TobleMiner authored and rubo77 committed Jul 26, 2018
1 parent 9aee93f commit 55ce8b5
Show file tree
Hide file tree
Showing 50 changed files with 399 additions and 178 deletions.
13 changes: 8 additions & 5 deletions announce.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import json
import argparse

from gather import gather_data
from providers import Provider

parser = argparse.ArgumentParser()

Expand All @@ -15,7 +15,10 @@

args = parser.parse_args()

print(json.dumps(gather_data(
args.directory,
{'batadv_dev': args.batman}
)))
provider = Provider.from_directory('providers',
args.directory)

print(json.dumps(provider.call(
{'batadv_dev': args.batman}
)
))
4 changes: 2 additions & 2 deletions announce.sh
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,5 @@ done

export GZIP="--best"

"${DIR}"/announce.py -d "${DIR}"/nodeinfo.d/ ${BATADV} | gzip | alfred $INTERFACE $SOCKET -s 158
"${DIR}"/announce.py -d "${DIR}"/statistics.d/ ${BATADV} | gzip | alfred $INTERFACE $SOCKET -s 159
"${DIR}"/announce.py -d nodeinfo ${BATADV} | gzip | alfred $INTERFACE $SOCKET -s 158
"${DIR}"/announce.py -d statistics ${BATADV} | gzip | alfred $INTERFACE $SOCKET -s 159
68 changes: 0 additions & 68 deletions gather.py

This file was deleted.

14 changes: 0 additions & 14 deletions neighbours.d/batadv

This file was deleted.

1 change: 0 additions & 1 deletion neighbours.d/node_id

This file was deleted.

1 change: 0 additions & 1 deletion nodeinfo.d/hardware/nproc

This file was deleted.

1 change: 0 additions & 1 deletion nodeinfo.d/hostname

This file was deleted.

17 changes: 0 additions & 17 deletions nodeinfo.d/network/addresses

This file was deleted.

1 change: 0 additions & 1 deletion nodeinfo.d/network/mac

This file was deleted.

13 changes: 0 additions & 13 deletions nodeinfo.d/network/mesh

This file was deleted.

1 change: 0 additions & 1 deletion nodeinfo.d/node_id

This file was deleted.

1 change: 0 additions & 1 deletion nodeinfo.d/software/batman-adv/version

This file was deleted.

1 change: 0 additions & 1 deletion nodeinfo.d/software/fastd/enabled

This file was deleted.

1 change: 0 additions & 1 deletion nodeinfo.d/software/fastd/version

This file was deleted.

1 change: 0 additions & 1 deletion nodeinfo.d/software/firmware/base

This file was deleted.

1 change: 0 additions & 1 deletion nodeinfo.d/software/firmware/release

This file was deleted.

1 change: 0 additions & 1 deletion nodeinfo.d/vpn

This file was deleted.

157 changes: 157 additions & 0 deletions providers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import os
import time
import traceback
from util import source_dirs, find_modules
from importlib import import_module

class DataSource():
''' Base data source, inherited by sources in provider directories
'''
def cache_ttl(self):
''' Default cache ttl, set to 0 to disable cache
'''
return 30

def required_args(self):
''' Returns a list of required argument names
'''
return []

def call(self, *args):
''' Override with actual implementation, args contains arguments listed
in required_args in order
'''
raise NotImplementedError()



def _set_value(node, path, value):
''' Sets a value inside a complex data dictionary.
The path Array must have at least one element.
'''
key = path[0]
if len(path) == 1:
node[key] = value
elif key in node:
_set_value(node[key], path[1:], value)
else:
node[path[0]] = {}
_set_value(node[key], path[1:], value)

class SourceCache():
''' Simple singleton-based cache
'''
class CacheEntry():
def __init__(self, key, value, ttl):
self.key = key
self.value = value
self.expiry = time.monotonic() + ttl

def isvalid(self):
return time.time() < self.expiry

instance = None

@classmethod
def getinstance(cls):
if not cls.instance:
cls.instance = cls()

return cls.instance

def __init__(self):
self.cache = {}

def put(self, key, value, ttl):
self.cache[key] = self.CacheEntry(key, value, ttl)

def has(self, key):
return (key in self.cache) and self.cache[key].isvalid()

def get(self, key):
if not self.has(key):
return None

return self.cache[key].value

class InvalidSourceModuleExeption(Exception):
def __init__(self, path):
super().__init__(self, 'Invalid data source,' +
' module at "{}" does not spcify required member "Source"')

class Source():
@classmethod
def from_file(cls, modulepath, jsonpath):
# construct a relative path
relpath = '.'.join([''] + modulepath)
# import modules relative to this package
module = import_module(relpath, __package__)
if not module.Source:
raise InvalidSourceModuleExeption(relpath)

return Source(jsonpath, module.Source())

def __init__(self, path, source):
self.path = path
self.source = source

def call(self, env):
cache = SourceCache.getinstance()
cache_result = cache.get(self)
if cache_result:
return cache_result

args = []
for argname in self.source.required_args():
args.append(env[argname])

result = self.source.call(*args)
cache.put(self, result, self.source.cache_ttl())

return result

class Provider():
@classmethod
def from_directory(cls, basepath, dirname):
provider = Provider(dirname)

sourcepaths = find_modules(os.path.join(basepath, dirname))
for path in sourcepaths:
for fname in path[1]:
src = Source.from_file([dirname] + path[0] + [fname], path[0] + [fname])
provider.add_source(src)

return provider


def __init__(self, name):
self.name = name
self.sources = []

def add_source(self, source):
self.sources.append(source)

def call(self, env):
ret = {}

for source in self.sources:
try:
_set_value(ret, source.path, source.call(env))
except:
traceback.print_exc()

return ret


def get_providers(directory):
providers = {}

if not os.path.isdir(directory):
raise FileNotFoundError("The path '{}' is not a valid directory".format(directory))

for dir in source_dirs(directory):
provider = Provider.from_directory(directory, dir)

providers[provider.name] = provider

return providers
22 changes: 22 additions & 0 deletions providers/neighbours/batadv.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import providers
from glob import glob

class Source(providers.DataSource):
def required_args(self):
return ['batadv_dev']

def call(self, batadv_dev):
return (lambda neighbours: {
open("{}/address".format(path)).read().strip(): {"neighbours": {
neigh[0]: {"lastseen": neigh[1], "tq": neigh[2]}
for neigh in neighbours
if neigh[3] == path[len('/sys/class/net/{}/lower_'.format(batadv_dev)):]
}}
for path in
glob('/sys/class/net/{}/lower_*'.format(batadv_dev))
})([
(line[0], float(line[1].strip('s')), int(line[2].strip(')')), line[4].strip('[]:'))
for line in map(lambda l: l.replace('(', '').replace('[', '').split(),
open('/sys/kernel/debug/batman_adv/{}/originators'.format(batadv_dev)))
if line[0] == line[3]
])
1 change: 1 addition & 0 deletions providers/neighbours/node_id.py
6 changes: 6 additions & 0 deletions providers/nodeinfo/hardware/nproc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import providers
import multiprocessing

class Source(providers.DataSource):
def call(self):
return multiprocessing.cpu_count()
6 changes: 6 additions & 0 deletions providers/nodeinfo/hostname.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import providers
import socket

class Source(providers.DataSource):
def call(self):
return socket.gethostname()
26 changes: 26 additions & 0 deletions providers/nodeinfo/network/addresses.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import providers
from ipaddress import IPv6Address
from glob import glob

class Source(providers.DataSource):
def required_args(self):
return ['batadv_dev']

def call(self, batadv_dev):
# filter addresses from /proc/net/if_inet6 belonging to given ifaces
return (lambda ifaces:
[
str(IPv6Address(int(line[0], 16)))
for line in map(str.split, open('/proc/net/if_inet6'))
if line[5] in ifaces
and not int(line[4], 16) & (0x40 | 0x20)
# address is neither tentative nor deprecated
]
)(
# generate the list of ifaces
[ batadv_dev ] +
list(map(
lambda s: s[len('/sys/class/net/{}/upper_'.format(batadv_dev)):],
glob('/sys/class/net/{}/upper_*'.format(batadv_dev))
))
)
Loading

0 comments on commit 55ce8b5

Please sign in to comment.