Added stocks plugin

This commit is contained in:
Tiberiu Chibici 2020-04-17 19:16:58 +03:00
parent b7c1a3f6f4
commit daa8e5316c
13 changed files with 180 additions and 64 deletions

View File

@ -1,30 +1,38 @@
import config import config
import database import database
import signal import signal
from apscheduler.schedulers.blocking import BlockingScheduler
from threading import Event from threading import Event
from plugins.system.cpu_plugin import CpuPlugin from plugins.system.cpu_plugin import CpuPlugin
from plugins.system.memory_plugin import MemoryPlugin from plugins.system.memory_plugin import MemoryPlugin
from plugins.system.disk_plugin import DiskPlugin from plugins.system.disk_plugin import DiskIOPlugin, DiskUsagePlugin
from plugins.system.network_plugin import NetworkPlugin from plugins.system.network_plugin import NetworkPlugin
from plugins.system.temperatures_plugin import TemperaturesPlugin from plugins.system.temperatures_plugin import TemperaturesPlugin
from plugins.system.ping_plugin import PingPlugin from plugins.system.ping_plugin import PingPlugin
from plugins.finance.stocks_plugin import StocksPlugin
from plugins.finance.robor_plugin import RoborPlugin from plugins.finance.robor_plugin import RoborPlugin
import logging
class Collector(object): class Collector(object):
def __init__(self): def __init__(self):
self.plugins = [ self.plugins = [
# system
CpuPlugin(), CpuPlugin(),
MemoryPlugin(), MemoryPlugin(),
DiskPlugin(), DiskUsagePlugin(),
DiskIOPlugin(),
NetworkPlugin(), NetworkPlugin(),
TemperaturesPlugin(), TemperaturesPlugin(),
PingPlugin(), PingPlugin(),
# finance # finance
StocksPlugin(),
RoborPlugin() RoborPlugin()
] ]
self.event = Event() self.event = Event()
self.scheduler = BlockingScheduler()
def collect_models(self): def collect_models(self):
models = [] models = []
@ -32,28 +40,28 @@ class Collector(object):
models.extend(plugin.models) models.extend(plugin.models)
return models return models
def initialize(self): def schedule_plugins(self):
for plugin in self.plugins:
self.scheduler.add_job(plugin.execute, 'interval', seconds=plugin.get_interval())
def run(self):
logging.basicConfig()
logging.getLogger('apscheduler').setLevel(logging.INFO)
models = self.collect_models() models = self.collect_models()
database.initialize_db() database.initialize_db()
database.DB.create_tables(models) database.DB.create_tables(models)
signal.signal(signal.SIGHUP, self.abort) self.schedule_plugins()
signal.signal(signal.SIGTERM, self.abort) # signal.signal(signal.SIGHUP, self.abort)
signal.signal(signal.SIGINT, self.abort) # signal.signal(signal.SIGTERM, self.abort)
# signal.signal(signal.SIGINT, self.abort)
def run(self):
self.initialize()
print(f'Started.') print(f'Started.')
while not self.event.is_set(): try:
for plugin in self.plugins: self.scheduler.start()
# try: except (KeyboardInterrupt, SystemExit):
plugin.execute() pass
# except BaseException as ex:
# print(ex)
self.event.wait(config.INTERVAL)
# TODO: calculate wait time based on execution time
print(f'Stopped.') print(f'Stopped.')

View File

@ -1,5 +1,5 @@
# Collect interval in seconds # Collect interval in seconds
INTERVAL=30 DEFAULT_INTERVAL = 30
# Database URL, which defines connection settings. # Database URL, which defines connection settings.
# #
@ -26,19 +26,30 @@ DATABASE_URL = 'sqlite:///data.db'
### CPU ### CPU
CPU_INTERVAL = DEFAULT_INTERVAL
# Store statistics per CPU, not only combined # Store statistics per CPU, not only combined
CPU_PER_CPU = True CPU_PER_CPU = True
### Disk ### Disk
# How often to poll space information, as a number of INTERVALs DISK_USAGE_INTERVAL = DEFAULT_INTERVAL * 10
DISK_SPACE_FREQUENCY = 10 DISK_IO_INTERVAL = DEFAULT_INTERVAL
### Memory
MEMORY_INTERVAL = DEFAULT_INTERVAL
### Network
NETWORK_INTERVAL = DEFAULT_INTERVAL
### Temperatures ### Temperatures
TEMPERATURE_INTERVAL = DEFAULT_INTERVAL
# If true, fahrenheit is used, otherwise celsius # If true, fahrenheit is used, otherwise celsius
TEMPERATURE_USE_FAHRENHEIT = False TEMPERATURE_USE_FAHRENHEIT = False
### Ping ### Ping
# Ping hosts # Ping hosts
PING_INTERVAL = 5 * 60 # every 5 min
PING_HOSTS = [ PING_HOSTS = [
'10.0.0.1', '10.0.0.1',
'192.168.0.1', '192.168.0.1',
@ -47,11 +58,23 @@ PING_HOSTS = [
'bing.com' 'bing.com'
] ]
# How often to send pings, as a number of INTERVALs ### Stocks
PING_FREQUENCY = 10 STOCKS_INTERVAL = 12 * 60 * 60 # updates daily
STOCKS_TICKERS = {
'MCRO.L' : 'Micro Focus International PLC',
'^GSPC' : 'S&P 500',
'^SPEUP' : 'S&P 350 Europe',
'^SPG1200' : 'S&P 1200 Global',
'^IXIC' : 'NASDAQ Composite',
'BTC-USD' : 'Bitcoin USD',
'ETH-USD' : 'Ethereum USD',
}
### ROBOR ### ROBOR
# Romanian Interbank Offer Rate # Romanian Interbank Offer Rate
ROBOR_INTERVAL = 12 * 60 * 60 # updates daily, every 12 hours should be fine
ROBOR_FIELDS = [ ROBOR_FIELDS = [
'ROBOR 6M' 'ROBOR 6M'
] ]

View File

@ -22,6 +22,9 @@ class RoborPlugin(Plugin):
def __init__(self): def __init__(self):
self.__table = None self.__table = None
def get_interval(self):
return config.ROBOR_INTERVAL
def get_column_index(self, table, column_name): def get_column_index(self, table, column_name):
header_row = table.find('tr') header_row = table.find('tr')
for elem in header_row.iter('th'): for elem in header_row.iter('th'):
@ -74,7 +77,6 @@ class RoborPlugin(Plugin):
entry.field = field entry.field = field
entry.value = value entry.value = value
entry.save() entry.save()
print(model_to_dict(entry))

View File

@ -0,0 +1,56 @@
import datetime
from peewee import *
from playhouse.shortcuts import model_to_dict
import config
from database import BaseModel
from plugins.plugin import Plugin
import yfinance as yf
class Stocks(BaseModel):
date = DateTimeField(index=True, default=datetime.datetime.now(), null=False)
ticker = TextField(null=False)
label = TextField(null=False)
value_open = FloatField(null=False)
value_close = FloatField(null=False)
value_high = FloatField(null=False)
value_low = FloatField(null=False)
class StocksPlugin(Plugin):
models = [Stocks]
def get_interval(self):
return config.STOCKS_INTERVAL
def execute(self):
for ticker, label in config.STOCKS_TICKERS.items():
# Get last existing date
latest_date = Stocks.select(Stocks.date) \
.order_by(Stocks.date.desc()) \
.limit(1) \
.scalar()
try:
yfticker = yf.Ticker(ticker)
if latest_date is None:
data = yfticker.history(period='max')
else:
data = yfticker.history(start=latest_date + datetime.timedelta(seconds=1))
for row in data.itertuples():
entry = Stocks()
entry.date = row.Index.to_pydatetime()
entry.ticker = ticker
entry.label = label
entry.value_open = row.Open
entry.value_close = row.Close
entry.value_high = row.High
entry.value_low = row.Low
entry.save()
print(model_to_dict(entry))
except BaseException as e:
print(e)

View File

@ -5,6 +5,10 @@ from typing import Tuple
class Plugin(ABC): class Plugin(ABC):
models = [] models = []
@abstractmethod
def get_interval(self) -> int:
pass
@abstractmethod @abstractmethod
def execute(self) -> None: def execute(self) -> None:
pass pass

View File

@ -27,6 +27,9 @@ class Cpu(BaseModel):
class CpuPlugin(Plugin): class CpuPlugin(Plugin):
models = [Cpu] models = [Cpu]
def get_interval(self):
return config.CPU_INTERVAL
def store(self, cpu, times, freq): def store(self, cpu, times, freq):
entry = Cpu() entry = Cpu()
entry.cpu = cpu entry.cpu = cpu

View File

@ -28,30 +28,13 @@ class DiskIO(BaseModel):
write_speed = FloatField(null=False) write_speed = FloatField(null=False)
class DiskPlugin(Plugin): class DiskUsagePlugin(Plugin):
models = [DiskUsage, DiskIO] models = [DiskUsage]
def __init__(self): def get_interval(self):
self.__i = 0 return config.DISK_USAGE_INTERVAL
self.__previous_io = {}
def store_io(self, disk, current):
previous = self.__previous_io.get(disk, current)
entry = DiskIO()
entry.disk = disk
entry.read_count = (current.read_count - previous.read_count) / config.INTERVAL
entry.write_count = (current.write_count - previous.write_count) / config.INTERVAL
entry.read_speed = (current.read_bytes - previous.read_bytes) / config.INTERVAL
entry.write_speed = (current.write_bytes - previous.write_bytes) / config.INTERVAL
entry.save()
self.__previous_io[disk] = current
def execute(self): def execute(self):
# Collect disk usage
if (self.__i % config.DISK_SPACE_FREQUENCY) == 0:
for partition in psutil.disk_partitions(): for partition in psutil.disk_partitions():
usage = psutil.disk_usage(partition.mountpoint) usage = psutil.disk_usage(partition.mountpoint)
@ -63,11 +46,34 @@ class DiskPlugin(Plugin):
entry.free = usage.free entry.free = usage.free
entry.save() entry.save()
# Collect IO
class DiskIOPlugin(Plugin):
models = [DiskIO]
def __init__(self):
self.__previous_io = {}
def get_interval(self):
return config.DISK_IO_INTERVAL
def store_io(self, disk, current):
previous = self.__previous_io.get(disk, current)
entry = DiskIO()
entry.disk = disk
entry.read_count = (current.read_count - previous.read_count) / self.get_interval()
entry.write_count = (current.write_count - previous.write_count) / self.get_interval()
entry.read_speed = (current.read_bytes - previous.read_bytes) / self.get_interval()
entry.write_speed = (current.write_bytes - previous.write_bytes) / self.get_interval()
entry.save()
self.__previous_io[disk] = current
def execute(self):
self.store_io(None, psutil.disk_io_counters(perdisk=False)) self.store_io(None, psutil.disk_io_counters(perdisk=False))
io_reads = psutil.disk_io_counters(perdisk=True) io_reads = psutil.disk_io_counters(perdisk=True)
for disk, current in io_reads.items(): for disk, current in io_reads.items():
self.store_io(disk, current) self.store_io(disk, current)
self.__i += 1

View File

@ -26,6 +26,9 @@ class Memory(BaseModel):
class MemoryPlugin(Plugin): class MemoryPlugin(Plugin):
models = [Memory] models = [Memory]
def get_interval(self):
return config.MEMORY_INTERVAL
def execute(self): def execute(self):
vmem = psutil.virtual_memory() vmem = psutil.virtual_memory()
swap = psutil.swap_memory() swap = psutil.swap_memory()

View File

@ -25,15 +25,18 @@ class NetworkPlugin(Plugin):
def __init__(self): def __init__(self):
self.__previous_io = {} self.__previous_io = {}
def get_interval(self):
return config.NETWORK_INTERVAL
def store_io(self, nic, current): def store_io(self, nic, current):
previous = self.__previous_io.get(nic, current) previous = self.__previous_io.get(nic, current)
entry = NetworkIO() entry = NetworkIO()
entry.nic = nic entry.nic = nic
entry.packets_sent = (current.packets_sent - previous.packets_sent) / config.INTERVAL entry.packets_sent = (current.packets_sent - previous.packets_sent) / self.get_interval()
entry.packets_recv = (current.packets_recv - previous.packets_recv) / config.INTERVAL entry.packets_recv = (current.packets_recv - previous.packets_recv) / self.get_interval()
entry.bytes_sent = (current.bytes_sent - previous.bytes_sent) / config.INTERVAL entry.bytes_sent = (current.bytes_sent - previous.bytes_sent) / self.get_interval()
entry.bytes_recv = (current.bytes_recv - previous.bytes_recv) / config.INTERVAL entry.bytes_recv = (current.bytes_recv - previous.bytes_recv) / self.get_interval()
entry.save() entry.save()
self.__previous_io[nic] = current self.__previous_io[nic] = current

View File

@ -22,8 +22,10 @@ class PingPlugin(Plugin):
models = [Ping] models = [Ping]
def __init__(self): def __init__(self):
self.__timeout = config.INTERVAL // 3 self.__timeout = config.PING_INTERVAL // 3
self.__i = 0
def get_interval(self):
return config.PING_INTERVAL
async def do_ping(self, host): async def do_ping(self, host):
command = ['ping', '-c', '1', '-W', str(self.__timeout), host] command = ['ping', '-c', '1', '-W', str(self.__timeout), host]
@ -47,7 +49,6 @@ class PingPlugin(Plugin):
await asyncio.gather(*[self.do_ping(host) for host in config.PING_HOSTS]) await asyncio.gather(*[self.do_ping(host) for host in config.PING_HOSTS])
def execute(self): def execute(self):
if (self.__i % config.PING_FREQUENCY) == 0:
if getattr(asyncio, 'run', None) is not None: if getattr(asyncio, 'run', None) is not None:
# Python 3.7+ # Python 3.7+
asyncio.run(self.execute_internal()) asyncio.run(self.execute_internal())
@ -56,4 +57,4 @@ class PingPlugin(Plugin):
loop.run_until_complete(self.execute_internal()) loop.run_until_complete(self.execute_internal())
loop.close() loop.close()
self.__i += 1

View File

@ -22,6 +22,9 @@ class Temperatures(BaseModel):
class TemperaturesPlugin(Plugin): class TemperaturesPlugin(Plugin):
models = [Temperatures] models = [Temperatures]
def get_interval(self):
return config.TEMPERATURE_INTERVAL
def execute(self): def execute(self):
for sensor, temps in psutil.sensors_temperatures(config.TEMPERATURE_USE_FAHRENHEIT).items(): for sensor, temps in psutil.sensors_temperatures(config.TEMPERATURE_USE_FAHRENHEIT).items():
for temp in temps: for temp in temps:

View File

@ -1,8 +1,12 @@
apscheduler
peewee peewee
# Used in most system plugins # Used in most system plugins
psutil psutil
# Finance plugins # robor plugin
requests requests
lxml lxml
# stocks plugin
yfinance