GitHub Viewer
# -*- coding: utf-8 -*-
# Description: netdata python modules framework
# Author: Pawel Krupa (paulfantom)
# Remember:
# ALL CODE NEEDS TO BE COMPATIBLE WITH Python > 2.7 and Python > 3.1
# Follow PEP8 as much as it is possible
# "check" and "create" CANNOT be blocking.
# "update" CAN be blocking
# "update" function needs to be fast, so follow:
# https://wiki.python.org/moin/PythonSpeed/PerformanceTips
# basically:
# - use local variables wherever it is possible
# - avoid dots in expressions that are executed many times
# - use "join()" instead of "+"
# - use "import" only at the beginning
#
# using ".encode()" in one thread can block other threads as well (only in python2)
import time
# import sys
import os
import socket
import select
try:
import urllib.request as urllib2
except ImportError:
import urllib2
from subprocess import Popen, PIPE
import threading
import msg
# class BaseService(threading.Thread):
class SimpleService(threading.Thread):
"""
Prototype of Service class.
Implemented basic functionality to run jobs by `python.d.plugin`
"""
def __init__(self, configuration=None, name=None):
"""
This needs to be initialized in child classes
:param configuration: dict
:param name: str
"""
threading.Thread.__init__(self)
self._data_stream = ""
self.daemon = True
self.retries = 0
self.retries_left = 0
self.priority = 140000
self.update_every = 1
self.name = name
self.override_name = None
self.chart_name = ""
self._dimensions = []
self._charts = []
self.__chart_set = False
self.__first_run = True
self.order = []
self.definitions = {}
if configuration is None:
self.error("BaseService: no configuration parameters supplied. Cannot create Service.")
raise RuntimeError
else:
self._extract_base_config(configuration)
self.timetable = {}
self.create_timetable()
# --- BASIC SERVICE CONFIGURATION ---
def _extract_base_config(self, config):
"""
Get basic parameters to run service
Minimum config:
config = {'update_every':1,
'priority':100000,
'retries':0}
:param config: dict
"""
pop = config.pop
try:
self.override_name = pop('name')
except KeyError:
pass
self.update_every = int(pop('update_every'))
self.priority = int(pop('priority'))
self.retries = int(pop('retries'))
self.retries_left = self.retries
self.configuration = config
def create_timetable(self, freq=None):
"""
Create service timetable.
`freq` is optional
Example:
timetable = {'last': 1466370091.3767564,
'next': 1466370092,
'freq': 1}
:param freq: int
"""
if freq is None:
freq = self.update_every
now = time.time()
self.timetable = {'last': now,
'next': now - (now % freq) + freq,
'freq': freq}
# --- THREAD CONFIGURATION ---
def _run_once(self):
"""
Executes self.update(interval) and draws run time chart.
Return value presents exit status of update()
:return: boolean
"""
t_start = time.time()
timetable = self.timetable
chart_name = self.chart_name
# check if it is time to execute job update() function
if timetable['next'] > t_start:
self.debug(chart_name, "will be run in", str(int((timetable['next'] - t_start) * 1000)), "ms")
return True
since_last = int((t_start - timetable['last']) * 1000000)
self.debug(chart_name,
"ready to run, after", str(int((t_start - timetable['last']) * 1000)),
"ms (update_every:", str(timetable['freq'] * 1000),
"ms, latency:", str(int((t_start - timetable['next']) * 1000)), "ms")
if self.__first_run:
since_last = 0
if not self.update(since_last):
self.error("update function failed.")
return False
t_end = time.time()
self.timetable['next'] = t_end - (t_end % timetable['freq']) + timetable['freq']
# draw performance graph
run_time = str(int((t_end - t_start) * 1000))
# noinspection SqlNoDataSourceInspection
print("BEGIN netdata.plugin_pythond_%s %s\nSET run_time = %s\nEND\n" %
(self.chart_name, str(since_last), run_time))
# sys.stdout.write("BEGIN netdata.plugin_pythond_%s %s\nSET run_time = %s\nEND\n" %
# (self.chart_name, str(since_last), run_time))
self.debug(chart_name, "updated in", str(run_time), "ms")
self.timetable['last'] = t_start
self.__first_run = False
return True
def run(self):
"""
Runs job in thread. Handles retries.
Exits when job failed or timed out.
:return: None
"""
self.timetable['last'] = time.time()
while True: # run forever, unless something is wrong
try:
status = self._run_once()
except Exception as e:
self.error("Something wrong: ", str(e))
return
if status: # handle retries if update failed
time.sleep(self.timetable['next'] - time.time())
self.retries_left = self.retries
else:
self.retries_left -= 1
if self.retries_left