コード例 #1
0
    def __init__(self, configuration, name=''):
        """
        :param configuration: <dict>
        :param name: <str>
        """
        PythonDLimitedLogger.__init__(self)
        self.configuration = configuration
        self.order = list()
        self.definitions = dict()

        self.module_name = self.__module__
        self.job_name = configuration.pop('job_name')
        self.override_name = configuration.pop('override_name')
        self.fake_name = None

        self._runtime_counters = RuntimeCounters(configuration=configuration)
        self.charts = Charts(job_name=self.actual_name,
                             priority=configuration.pop('priority'),
                             cleanup=configuration.pop('chart_cleanup'),
                             get_update_every=self.get_update_every,
                             module_name=self.module_name)
コード例 #2
0
ファイル: SimpleService.py プロジェクト: firehol/netdata
    def __init__(self, configuration, name=''):
        """
        :param configuration: <dict>
        :param name: <str>
        """
        PythonDLimitedLogger.__init__(self)
        self.configuration = configuration
        self.order = list()
        self.definitions = dict()

        self.module_name = self.__module__
        self.job_name = configuration.pop('job_name')
        self.override_name = configuration.pop('override_name')
        self.fake_name = None

        self._runtime_counters = RuntimeCounters(configuration=configuration)
        self.charts = Charts(job_name=self.actual_name,
                             priority=configuration.pop('priority'),
                             cleanup=configuration.pop('chart_cleanup'),
                             get_update_every=self.get_update_every,
                             module_name=self.module_name)
コード例 #3
0
class SimpleService(Thread, PythonDLimitedLogger, OldVersionCompatibility,
                    object):
    """
    Prototype of Service class.
    Implemented basic functionality to run jobs by `python.d.plugin`
    """
    def __init__(self, configuration, name=''):
        """
        :param configuration: <dict>
        :param name: <str>
        """
        Thread.__init__(self)
        self.daemon = True
        PythonDLimitedLogger.__init__(self)
        OldVersionCompatibility.__init__(self)
        self.configuration = configuration
        self.order = list()
        self.definitions = dict()

        self.module_name = self.__module__
        self.job_name = configuration.pop('job_name')
        self.override_name = configuration.pop('override_name')
        self.fake_name = None

        self._runtime_counters = RuntimeCounters(configuration=configuration)
        self.charts = Charts(job_name=self.actual_name,
                             priority=configuration.pop('priority'),
                             cleanup=configuration.pop('chart_cleanup'),
                             get_update_every=self.get_update_every,
                             module_name=self.module_name)

    def __repr__(self):
        return '<{cls_bases}: {name}>'.format(cls_bases=', '.join(
            c.__name__ for c in self.__class__.__bases__),
                                              name=self.name)

    @property
    def name(self):
        if self.job_name:
            return '_'.join(
                [self.module_name, self.override_name or self.job_name])
        return self.module_name

    def actual_name(self):
        return self.fake_name or self.name

    @property
    def runs_counter(self):
        return self._runtime_counters.runs

    @property
    def update_every(self):
        return self._runtime_counters.update_every

    @update_every.setter
    def update_every(self, value):
        """
        :param value: <int>
        :return:
        """
        self._runtime_counters.update_every = value

    def get_update_every(self):
        return self.update_every

    def check(self):
        """
        check() prototype
        :return: boolean
        """
        self.debug(
            "job doesn't implement check() method. Using default which simply invokes get_data()."
        )
        data = self.get_data()
        if data and isinstance(data, dict):
            return True
        self.debug('returned value is wrong: {0}'.format(data))
        return False

    @create_runtime_chart
    def create(self):
        for chart_name in self.order:
            chart_config = self.definitions.get(chart_name)

            if not chart_config:
                self.debug(
                    "create() => [NOT ADDED] chart '{chart_name}' not in definitions. "
                    "Skipping it.".format(chart_name=chart_name))
                continue

            #  create chart
            chart_params = [chart_name] + chart_config['options']
            try:
                self.charts.add_chart(params=chart_params)
            except ChartError as error:
                self.error(
                    "create() => [NOT ADDED] (chart '{chart}': {error})".
                    format(chart=chart_name, error=error))
                continue

            # add dimensions to chart
            for dimension in chart_config['lines']:
                try:
                    self.charts[chart_name].add_dimension(dimension)
                except ChartError as error:
                    self.error(
                        "create() => [NOT ADDED] (dimension '{dimension}': {error})"
                        .format(dimension=dimension, error=error))
                    continue

            # add variables to chart
            if 'variables' in chart_config:
                for variable in chart_config['variables']:
                    try:
                        self.charts[chart_name].add_variable(variable)
                    except ChartError as error:
                        self.error(
                            "create() => [NOT ADDED] (variable '{var}': {error})"
                            .format(var=variable, error=error))
                        continue

        del self.order
        del self.definitions

        # True if job has at least 1 chart else False
        return bool(self.charts)

    def run(self):
        """
        Runs job in thread. Handles retries.
        Exits when job failed or timed out.
        :return: None
        """
        job = self._runtime_counters
        self.debug(
            'started, update frequency: {freq}, retries: {retries}'.format(
                freq=job.update_every,
                retries=job.max_retries - job.retries), )

        while True:
            job.sleep_until_next()

            since = 0
            if job.prev_update:
                since = int((job.start_real - job.prev_update) * 1e6)

            try:
                updated = self.update(interval=since)
            except Exception as error:
                self.error('update() unhandled exception: {error}'.format(
                    error=error))
                updated = False

            job.runs += 1

            if not updated:
                if not job.handle_retries():
                    return
            else:
                job.elapsed = int((monotonic() - job.start_mono) * 1e3)
                job.prev_update = job.start_real
                job.retries, job.penalty = 0, 0
                safe_print(
                    RUNTIME_CHART_UPDATE.format(job_name=self.name,
                                                since_last=since,
                                                elapsed=job.elapsed))
            self.debug('update => [{status}] (elapsed time: {elapsed}, '
                       'retries left: {retries})'.format(
                           status='OK' if updated else 'FAILED',
                           elapsed=job.elapsed if updated else '-',
                           retries=job.max_retries - job.retries))

    def update(self, interval):
        """
        :return:
        """
        data = self.get_data()
        if not data:
            self.debug('get_data() returned no data')
            return False
        elif not isinstance(data, dict):
            self.debug('get_data() returned incorrect type data')
            return False

        updated = False

        for chart in self.charts:
            if chart.flags.obsoleted:
                if chart.can_be_updated(data):
                    chart.refresh()
                else:
                    continue
            elif self.charts.cleanup and chart.penalty >= self.charts.cleanup:
                chart.obsolete()
                self.error(
                    "chart '{0}' was suppressed due to non updating".format(
                        chart.name))
                continue

            ok = chart.update(data, interval)
            if ok:
                updated = True

        if not updated:
            self.debug('none of the charts has been updated')

        return updated

    def get_data(self):
        return self._get_data()

    def _get_data(self):
        raise NotImplementedError
コード例 #4
0
ファイル: SimpleService.py プロジェクト: cppmx/netdata
class SimpleService(Thread, PythonDLimitedLogger, OldVersionCompatibility, object):
    """
    Prototype of Service class.
    Implemented basic functionality to run jobs by `python.d.plugin`
    """
    def __init__(self, configuration, name=''):
        """
        :param configuration: <dict>
        :param name: <str>
        """
        Thread.__init__(self)
        self.daemon = True
        PythonDLimitedLogger.__init__(self)
        OldVersionCompatibility.__init__(self)
        self.configuration = configuration
        self.order = list()
        self.definitions = dict()

        self.module_name = self.__module__
        self.job_name = configuration.pop('job_name')
        self.override_name = configuration.pop('override_name')
        self.fake_name = None

        self._runtime_counters = RuntimeCounters(configuration=configuration)
        self.charts = Charts(job_name=self.actual_name,
                             priority=configuration.pop('priority'),
                             cleanup=configuration.pop('chart_cleanup'),
                             get_update_every=self.get_update_every,
                             module_name=self.module_name)

    def __repr__(self):
        return '<{cls_bases}: {name}>'.format(cls_bases=', '.join(c.__name__ for c in self.__class__.__bases__),
                                              name=self.name)

    @property
    def name(self):
        if self.job_name:
            return '_'.join([self.module_name, self.override_name or self.job_name])
        return self.module_name

    def actual_name(self):
        return self.fake_name or self.name

    @property
    def runs_counter(self):
        return self._runtime_counters.RUNS

    @property
    def update_every(self):
        return self._runtime_counters.FREQ

    @update_every.setter
    def update_every(self, value):
        """
        :param value: <int>
        :return:
        """
        self._runtime_counters.FREQ = value

    def get_update_every(self):
        return self.update_every

    def check(self):
        """
        check() prototype
        :return: boolean
        """
        self.debug("job doesn't implement check() method. Using default which simply invokes get_data().")
        data = self.get_data()
        if data and isinstance(data, dict):
            return True
        self.debug('returned value is wrong: {0}'.format(data))
        return False

    @create_runtime_chart
    def create(self):
        for chart_name in self.order:
            chart_config = self.definitions.get(chart_name)

            if not chart_config:
                self.debug("create() => [NOT ADDED] chart '{chart_name}' not in definitions. "
                           "Skipping it.".format(chart_name=chart_name))
                continue

            #  create chart
            chart_params = [chart_name] + chart_config['options']
            try:
                self.charts.add_chart(params=chart_params)
            except ChartError as error:
                self.error("create() => [NOT ADDED] (chart '{chart}': {error})".format(chart=chart_name,
                                                                                       error=error))
                continue

            # add dimensions to chart
            for dimension in chart_config['lines']:
                try:
                    self.charts[chart_name].add_dimension(dimension)
                except ChartError as error:
                    self.error("create() => [NOT ADDED] (dimension '{dimension}': {error})".format(dimension=dimension,
                                                                                                   error=error))
                    continue

            # add variables to chart
            if 'variables' in chart_config:
                for variable in chart_config['variables']:
                    try:
                        self.charts[chart_name].add_variable(variable)
                    except ChartError as error:
                        self.error("create() => [NOT ADDED] (variable '{var}': {error})".format(var=variable,
                                                                                                error=error))
                        continue

        del self.order
        del self.definitions

        # True if job has at least 1 chart else False
        return bool(self.charts)

    def run(self):
        """
        Runs job in thread. Handles retries.
        Exits when job failed or timed out.
        :return: None
        """
        job = self._runtime_counters
        self.debug('started, update frequency: {freq}, '
                   'retries: {retries}'.format(freq=job.FREQ, retries=job.RETRIES_MAX - job.RETRIES))

        while True:
            job.START_RUN = time()

            job.NEXT_RUN = job.START_RUN - (job.START_RUN % job.FREQ) + job.FREQ + job.PENALTY

            self.sleep_until_next_run()

            if job.PREV_UPDATE:
                job.SINCE_UPDATE = int((job.START_RUN - job.PREV_UPDATE) * 1e6)

            try:
                updated = self.update(interval=job.SINCE_UPDATE)
            except Exception as error:
                self.error('update() unhandled exception: {error}'.format(error=error))
                updated = False

            job.RUNS += 1

            if not updated:
                if not self.manage_retries():
                    return
            else:
                job.ELAPSED = int((time() - job.START_RUN) * 1e3)
                job.PREV_UPDATE = job.START_RUN
                job.RETRIES, job.PENALTY = 0, 0
                safe_print(RUNTIME_CHART_UPDATE.format(job_name=self.name,
                                                       since_last=job.SINCE_UPDATE,
                                                       elapsed=job.ELAPSED))
            self.debug('update => [{status}] (elapsed time: {elapsed}, '
                       'retries left: {retries})'.format(status='OK' if updated else 'FAILED',
                                                         elapsed=job.ELAPSED if updated else '-',
                                                         retries=job.RETRIES_MAX - job.RETRIES))

    def update(self, interval):
        """
        :return:
        """
        data = self.get_data()
        if not data:
            self.debug('get_data() returned no data')
            return False
        elif not isinstance(data, dict):
            self.debug('get_data() returned incorrect type data')
            return False

        updated = False

        for chart in self.charts:
            if chart.flags.obsoleted:
                if chart.can_be_updated(data):
                    chart.refresh()
                else:
                    continue
            elif self.charts.cleanup and chart.penalty >= self.charts.cleanup:
                chart.obsolete()
                self.error("chart '{0}' was suppressed due to non updating".format(chart.name))
                continue

            ok = chart.update(data, interval)
            if ok:
                updated = True

        if not updated:
            self.debug('none of the charts has been updated')

        return updated

    def manage_retries(self):
        rc = self._runtime_counters
        rc.RETRIES += 1
        if rc.RETRIES % 5 == 0:
            rc.PENALTY = int(rc.RETRIES * self.update_every / 2)
        if rc.RETRIES >= rc.RETRIES_MAX:
            self.error('stopped after {0} data collection failures in a row'.format(rc.RETRIES_MAX))
            return False
        return True

    def sleep_until_next_run(self):
        job = self._runtime_counters

        # sleep() is interruptable
        while job.is_sleep_time():
            sleep_time = job.NEXT_RUN - job.START_RUN
            self.debug('sleeping for {sleep_time} to reach frequency of {freq} sec'.format(sleep_time=sleep_time,
                                                                                           freq=job.FREQ + job.PENALTY))
            sleep(sleep_time)
            job.START_RUN = time()

    def get_data(self):
        return self._get_data()

    def _get_data(self):
        raise NotImplementedError
コード例 #5
0
ファイル: SimpleService.py プロジェクト: firehol/netdata
class SimpleService(PythonDLimitedLogger, object):
    """
    Prototype of Service class.
    Implemented basic functionality to run jobs by `python.d.plugin`
    """
    def __init__(self, configuration, name=''):
        """
        :param configuration: <dict>
        :param name: <str>
        """
        PythonDLimitedLogger.__init__(self)
        self.configuration = configuration
        self.order = list()
        self.definitions = dict()

        self.module_name = self.__module__
        self.job_name = configuration.pop('job_name')
        self.override_name = configuration.pop('override_name')
        self.fake_name = None

        self._runtime_counters = RuntimeCounters(configuration=configuration)
        self.charts = Charts(job_name=self.actual_name,
                             priority=configuration.pop('priority'),
                             cleanup=configuration.pop('chart_cleanup'),
                             get_update_every=self.get_update_every,
                             module_name=self.module_name)

    def __repr__(self):
        return '<{cls_bases}: {name}>'.format(cls_bases=', '.join(c.__name__ for c in self.__class__.__bases__),
                                              name=self.name)

    @property
    def name(self):
        if self.job_name and self.job_name != self.module_name:
            return '_'.join([self.module_name, self.override_name or self.job_name])
        return self.module_name

    def actual_name(self):
        return self.fake_name or self.name

    @property
    def runs_counter(self):
        return self._runtime_counters.runs

    @property
    def update_every(self):
        return self._runtime_counters.update_every

    @update_every.setter
    def update_every(self, value):
        """
        :param value: <int>
        :return:
        """
        self._runtime_counters.update_every = value

    def get_update_every(self):
        return self.update_every

    def check(self):
        """
        check() prototype
        :return: boolean
        """
        self.debug("job doesn't implement check() method. Using default which simply invokes get_data().")
        data = self.get_data()
        if data and isinstance(data, dict):
            return True
        self.debug('returned value is wrong: {0}'.format(data))
        return False

    @create_runtime_chart
    def create(self):
        for chart_name in self.order:
            chart_config = self.definitions.get(chart_name)

            if not chart_config:
                self.debug("create() => [NOT ADDED] chart '{chart_name}' not in definitions. "
                           "Skipping it.".format(chart_name=chart_name))
                continue

            #  create chart
            chart_params = [chart_name] + chart_config['options']
            try:
                self.charts.add_chart(params=chart_params)
            except ChartError as error:
                self.error("create() => [NOT ADDED] (chart '{chart}': {error})".format(chart=chart_name,
                                                                                       error=error))
                continue

            # add dimensions to chart
            for dimension in chart_config['lines']:
                try:
                    self.charts[chart_name].add_dimension(dimension)
                except ChartError as error:
                    self.error("create() => [NOT ADDED] (dimension '{dimension}': {error})".format(dimension=dimension,
                                                                                                   error=error))
                    continue

            # add variables to chart
            if 'variables' in chart_config:
                for variable in chart_config['variables']:
                    try:
                        self.charts[chart_name].add_variable(variable)
                    except ChartError as error:
                        self.error("create() => [NOT ADDED] (variable '{var}': {error})".format(var=variable,
                                                                                                error=error))
                        continue

        del self.order
        del self.definitions

        # True if job has at least 1 chart else False
        return bool(self.charts)

    def run(self):
        """
        Runs job in thread. Handles retries.
        Exits when job failed or timed out.
        :return: None
        """
        job = self._runtime_counters
        self.debug('started, update frequency: {freq}'.format(freq=job.update_every))

        while True:
            job.sleep_until_next()

            since = 0
            if job.prev_update:
                since = int((job.start_real - job.prev_update) * 1e6)

            try:
                updated = self.update(interval=since)
            except Exception as error:
                self.error('update() unhandled exception: {error}'.format(error=error))
                updated = False

            job.runs += 1

            if not updated:
                job.handle_retries()
            else:
                job.elapsed = int((monotonic() - job.start_mono) * 1e3)
                job.prev_update = job.start_real
                job.retries, job.penalty = 0, 0
                safe_print(RUNTIME_CHART_UPDATE.format(job_name=self.name,
                                                       since_last=since,
                                                       elapsed=job.elapsed))
            self.debug('update => [{status}] (elapsed time: {elapsed}, failed retries in a row: {retries})'.format(
                status='OK' if updated else 'FAILED',
                elapsed=job.elapsed if updated else '-',
                retries=job.retries))

    def update(self, interval):
        """
        :return:
        """
        data = self.get_data()
        if not data:
            self.debug('get_data() returned no data')
            return False
        elif not isinstance(data, dict):
            self.debug('get_data() returned incorrect type data')
            return False

        updated = False

        for chart in self.charts:
            if chart.flags.obsoleted:
                if chart.can_be_updated(data):
                    chart.refresh()
                else:
                    continue
            elif self.charts.cleanup and chart.penalty >= self.charts.cleanup:
                chart.obsolete()
                self.error("chart '{0}' was suppressed due to non updating".format(chart.name))
                continue

            ok = chart.update(data, interval)
            if ok:
                updated = True

        if not updated:
            self.debug('none of the charts has been updated')

        return updated

    def get_data(self):
        return self._get_data()

    def _get_data(self):
        raise NotImplementedError
コード例 #6
0
class SimpleService(Thread, PythonDLimitedLogger, OldVersionCompatibility, object):
    """
    Prototype of Service class.
    Implemented basic functionality to run jobs by `python.d.plugin`
    """
    def __init__(self, configuration, name=''):
        """
        :param configuration: <dict>
        :param name: <str>
        """
        Thread.__init__(self)
        self.daemon = True
        PythonDLimitedLogger.__init__(self)
        OldVersionCompatibility.__init__(self)
        self.configuration = configuration
        self.order = list()
        self.definitions = dict()

        self.module_name = self.__module__
        self.job_name = configuration.pop('job_name')
        self.override_name = configuration.pop('override_name')
        self.fake_name = None

        self._runtime_counters = RuntimeCounters(configuration=configuration)
        self.charts = Charts(job_name=self.actual_name,
                             priority=configuration.pop('priority'),
                             get_update_every=self.get_update_every)

    def __repr__(self):
        return '<{cls_bases}: {name}>'.format(cls_bases=', '.join(c.__name__ for c in self.__class__.__bases__),
                                              name=self.name)

    @property
    def name(self):
        if self.job_name:
            return '_'.join([self.module_name, self.override_name or self.job_name])
        return self.module_name

    def actual_name(self):
        return self.fake_name or self.name

    @property
    def update_every(self):
        return self._runtime_counters.FREQ

    @update_every.setter
    def update_every(self, value):
        """
        :param value: <int>
        :return:
        """
        self._runtime_counters.FREQ = value

    def get_update_every(self):
        return self.update_every

    def check(self):
        """
        check() prototype
        :return: boolean
        """
        self.debug("job doesn't implement check() method. Using default which simply invokes get_data().")
        data = self.get_data()
        if data and isinstance(data, dict):
            return True
        self.debug('returned value is wrong: {0}'.format(data))
        return False

    @create_runtime_chart
    def create(self):
        for chart_name in self.order:
            chart_config = self.definitions.get(chart_name)

            if not chart_config:
                self.debug("create() => [NOT ADDED] chart '{chart_name}' not in definitions. "
                           "Skipping it.".format(chart_name=chart_name))
                continue

            #  create chart
            chart_params = [chart_name] + chart_config['options']
            try:
                self.charts.add_chart(params=chart_params)
            except ChartError as error:
                self.error("create() => [NOT ADDED] (chart '{chart}': {error})".format(chart=chart_name,
                                                                                       error=error))
                continue

            # add dimensions to chart
            for dimension in chart_config['lines']:
                try:
                    self.charts[chart_name].add_dimension(dimension)
                except ChartError as error:
                    self.error("create() => [NOT ADDED] (dimension '{dimension}': {error})".format(dimension=dimension,
                                                                                                   error=error))
                    continue

            # add variables to chart
            if 'variables' in chart_config:
                for variable in chart_config['variables']:
                    try:
                        self.charts[chart_name].add_variable(variable)
                    except ChartError as error:
                        self.error("create() => [NOT ADDED] (variable '{var}': {error})".format(var=variable,
                                                                                                error=error))
                        continue

        del self.order
        del self.definitions

        # push charts to netdata
        for chart in self.charts:
            safe_print(chart.create())

        # True if job has at least 1 chart else False
        return bool(self.charts)

    def run(self):
        """
        Runs job in thread. Handles retries.
        Exits when job failed or timed out.
        :return: None
        """
        job = self._runtime_counters
        self.debug('started, update frequency: {freq}, '
                   'retries: {retries}'.format(freq=job.FREQ, retries=job.RETRIES_MAX - job.RETRIES))

        while True:
            job.START_RUN = time()

            job.NEXT_RUN = job.START_RUN - (job.START_RUN % job.FREQ) + job.FREQ + job.PENALTY

            self.sleep_until_next_run()

            if job.PREV_UPDATE:
                job.SINCE_UPDATE = int((job.START_RUN - job.PREV_UPDATE) * 1e6)

            try:
                updated = self.update(interval=job.SINCE_UPDATE)
            except Exception as error:
                self.error('update() unhandled exception: {error}'.format(error=error))
                updated = False

            if not updated:
                if not self.manage_retries():
                    return
            else:
                job.ELAPSED = int((time() - job.START_RUN) * 1e3)
                job.PREV_UPDATE = job.START_RUN
                job.RETRIES, job.PENALTY = 0, 0
                safe_print(RUNTIME_CHART_UPDATE.format(job_name=self.name,
                                                       since_last=job.SINCE_UPDATE,
                                                       elapsed=job.ELAPSED))
            self.debug('update => [{status}] (elapsed time: {elapsed}, '
                       'retries left: {retries})'.format(status='OK' if updated else 'FAILED',
                                                         elapsed=job.ELAPSED if updated else '-',
                                                         retries=job.RETRIES_MAX - job.RETRIES))

    def update(self, interval):
        """
        :return:
        """
        data = self.get_data()
        if not data:
            self.debug('get_data() returned no data')
            return False
        elif not isinstance(data, dict):
            self.debug('get_data() returned incorrect type data')
            return False

        charts_updated = False

        for chart in self.charts.penalty_exceeded(penalty_max=CHART_OBSOLETE_PENALTY):
            safe_print(chart.obsolete())
            chart.suppress()
            self.error("chart '{0}' was removed due to non updating".format(chart.name))

        for chart in self.charts:
            if not chart.alive:
                continue
            dimension_updated, variables_updated = str(), str()

            for dimension in chart:
                try:
                    value = int(data[dimension.id])
                except (KeyError, TypeError):
                    continue
                else:
                    dimension_updated += dimension.set(value)

            for var in chart.variables:
                try:
                    value = int(data[var.id])
                except (KeyError, TypeError):
                    continue
                else:
                    variables_updated += var.set(value)

            if dimension_updated:
                charts_updated = True
                safe_print(''.join([chart.begin(since_last=interval),
                                    dimension_updated,
                                    variables_updated,
                                    'END\n']))
            else:
                chart.penalty += 1

        if not charts_updated:
            self.debug('none of the charts have been updated')

        return charts_updated

    def manage_retries(self):
        self._runtime_counters.RETRIES += 1
        if self._runtime_counters.RETRIES % 5 == 0:
            self._runtime_counters.PENALTY = int(self._runtime_counters.RETRIES * self.update_every / 2)
        if self._runtime_counters.RETRIES >= self._runtime_counters.RETRIES_MAX:
            self.error('stopped after {retries_max} data '
                       'collection failures in a row'.format(retries_max=self._runtime_counters.RETRIES_MAX))
            return False
        return True

    def sleep_until_next_run(self):
        job = self._runtime_counters

        # sleep() is interruptable
        while job.is_sleep_time():
            sleep_time = job.NEXT_RUN - job.START_RUN
            self.debug('sleeping for {sleep_time} to reach frequency of {freq} sec'.format(sleep_time=sleep_time,
                                                                                           freq=job.FREQ + job.PENALTY))
            sleep(sleep_time)
            job.START_RUN = time()

    def get_data(self):
        return self._get_data()

    def _get_data(self):
        raise NotImplementedError