diff options
author | Tom Worster <fsb@thefsb.org> | 2017-11-07 13:23:43 -0500 |
---|---|---|
committer | Tom Worster <fsb@thefsb.org> | 2017-11-07 13:23:43 -0500 |
commit | 2e28f0e11bf9fe5697caf4faf6f3732fd8e13a44 (patch) | |
tree | a383d7139b653daf325397cf5ddc085afe954b78 /python.d | |
parent | 48d4cbf54d4fcf112295ddd35b31cad67ca47626 (diff) | |
parent | 5609e6dc1b13d2ddd0f26315cdf1a6867a652555 (diff) |
Merge branch 'master' of github.com:firehol/netdata
Diffstat (limited to 'python.d')
-rw-r--r-- | python.d/Makefile.am | 1 | ||||
-rw-r--r-- | python.d/README.md | 107 | ||||
-rw-r--r-- | python.d/beanstalk.chart.py | 250 | ||||
-rw-r--r-- | python.d/bind_rndc.chart.py | 2 | ||||
-rw-r--r-- | python.d/cpufreq.chart.py | 2 | ||||
-rw-r--r-- | python.d/cpuidle.chart.py | 2 | ||||
-rw-r--r-- | python.d/example.chart.py | 18 | ||||
-rw-r--r-- | python.d/freeradius.chart.py | 2 | ||||
-rw-r--r-- | python.d/memcached.chart.py | 1 | ||||
-rw-r--r-- | python.d/mysql.chart.py | 2 | ||||
-rw-r--r-- | python.d/postgres.chart.py | 4 | ||||
-rw-r--r-- | python.d/python_modules/bases/FrameworkServices/SimpleService.py | 69 | ||||
-rw-r--r-- | python.d/python_modules/bases/charts.py | 136 | ||||
-rw-r--r-- | python.d/python_modules/bases/collection.py | 6 | ||||
-rw-r--r-- | python.d/redis.chart.py | 1 | ||||
-rw-r--r-- | python.d/varnish.chart.py | 199 | ||||
-rw-r--r-- | python.d/web_log.chart.py | 26 |
17 files changed, 603 insertions, 225 deletions
diff --git a/python.d/Makefile.am b/python.d/Makefile.am index 2275643366..6eb35c7aee 100644 --- a/python.d/Makefile.am +++ b/python.d/Makefile.am @@ -14,6 +14,7 @@ dist_python_SCRIPTS = \ dist_python_DATA = \ README.md \ apache.chart.py \ + beanstalk.chart.py \ bind_rndc.chart.py \ chrony.chart.py \ couchdb.chart.py \ diff --git a/python.d/README.md b/python.d/README.md index fe85ec4299..45fede537b 100644 --- a/python.d/README.md +++ b/python.d/README.md @@ -125,13 +125,118 @@ If no configuration is given, module will attempt to read log file at `/var/log/ --- +# beanstalk + +Module provides server and tube level statistics: + +**Requirements:** + * `python-beanstalkc` + * `python-yaml` + +**Server statistics:** + +1. **Cpu usage** in cpu time + * user + * system + +2. **Jobs rate** in jobs/s + * total + * timeouts + +3. **Connections rate** in connections/s + * connections + +4. **Commands rate** in commands/s + * put + * peek + * peek-ready + * peek-delayed + * peek-buried + * reserve + * use + * watch + * ignore + * delete + * release + * bury + * kick + * stats + * stats-job + * stats-tube + * list-tubes + * list-tube-used + * list-tubes-watched + * pause-tube + +5. **Current tubes** in tubes + * tubes + +6. **Current jobs** in jobs + * urgent + * ready + * reserved + * delayed + * buried + +7. **Current connections** in connections + * written + * producers + * workers + * waiting + +8. **Binlog** in records/s + * written + * migrated + +9. **Uptime** in seconds + * uptime + +**Per tube statistics:** + +1. **Jobs rate** in jobs/s + * jobs + +2. **Jobs** in jobs + * using + * ready + * reserved + * delayed + * buried + +3. **Connections** in connections + * using + * waiting + * watching + +4. **Commands** in commands/s + * deletes + * pauses + +5. **Pause** in seconds + * since + * left + + +### configuration + +Sample: + +```yaml +host : '127.0.0.1' +port : 11300 +``` + +If no configuration is given, module will attempt to connect to beanstalkd on `127.0.0.1:11300` address + +--- + # bind_rndc Module parses bind dump file to collect real-time performance metrics **Requirements:** * Version of bind must be 9.6 + - * Netdata must have permissions to run `rndc status` + * Netdata must have permissions to run `rndc stats` It produces: diff --git a/python.d/beanstalk.chart.py b/python.d/beanstalk.chart.py new file mode 100644 index 0000000000..8880afdd98 --- /dev/null +++ b/python.d/beanstalk.chart.py @@ -0,0 +1,250 @@ +# -*- coding: utf-8 -*- +# Description: beanstalk netdata python.d module +# Author: l2isbad + +try: + import beanstalkc + BEANSTALKC = True +except ImportError: + BEANSTALKC = False + +try: + import yaml + YAML = True +except ImportError: + YAML = False + +from bases.FrameworkServices.SimpleService import SimpleService + +# default module values (can be overridden per job in `config`) +# update_every = 2 +priority = 60000 +retries = 60 + +ORDER = ['cpu_usage', 'jobs_rate', 'connections_rate', 'commands_rate', 'current_tubes', 'current_jobs', + 'current_connections', 'binlog', 'uptime'] + +CHARTS = { + 'cpu_usage': { + 'options': [None, 'Cpu Usage', 'cpu time', 'server statistics', 'beanstalk.cpu_usage', 'area'], + 'lines': [ + ['rusage-utime', 'user', 'incremental'], + ['rusage-stime', 'system', 'incremental'] + ] + }, + 'jobs_rate': { + 'options': [None, 'Jobs Rate', 'jobs/s', 'server statistics', 'beanstalk.jobs_rate', 'line'], + 'lines': [ + ['total-jobs', 'total', 'incremental'], + ['job-timeouts', 'timeouts', 'incremental'] + ] + }, + 'connections_rate': { + 'options': [None, 'Connections Rate', 'connections/s', 'server statistics', 'beanstalk.connections_rate', + 'area'], + 'lines': [ + ['total-connections', 'connections', 'incremental'] + ] + }, + 'commands_rate': { + 'options': [None, 'Commands Rate', 'commands/s', 'server statistics', 'beanstalk.commands_rate', 'stacked'], + 'lines': [ + ['cmd-put', 'put', 'incremental'], + ['cmd-peek', 'peek', 'incremental'], + ['cmd-peek-ready', 'peek-ready', 'incremental'], + ['cmd-peek-delayed', 'peek-delayed', 'incremental'], + ['cmd-peek-buried', 'peek-buried', 'incremental'], + ['cmd-reserve', 'reserve', 'incremental'], + ['cmd-use', 'use', 'incremental'], + ['cmd-watch', 'watch', 'incremental'], + ['cmd-ignore', 'ignore', 'incremental'], + ['cmd-delete', 'delete', 'incremental'], + ['cmd-release', 'release', 'incremental'], + ['cmd-bury', 'bury', 'incremental'], + ['cmd-kick', 'kick', 'incremental'], + ['cmd-stats', 'stats', 'incremental'], + ['cmd-stats-job', 'stats-job', 'incremental'], + ['cmd-stats-tube', 'stats-tube', 'incremental'], + ['cmd-list-tubes', 'list-tubes', 'incremental'], + ['cmd-list-tube-used', 'list-tube-used', 'incremental'], + ['cmd-list-tubes-watched', 'list-tubes-watched', 'incremental'], + ['cmd-pause-tube', 'pause-tube', 'incremental'] + ] + }, + 'current_tubes': { + 'options': [None, 'Current Tubes', 'tubes', 'server statistics', 'beanstalk.current_tubes', 'area'], + 'lines': [ + ['current-tubes', 'tubes'] + ] + }, + 'current_jobs': { + 'options': [None, 'Current Jobs', 'jobs', 'server statistics', 'beanstalk.current_jobs', 'stacked'], + 'lines': [ + ['current-jobs-urgent', 'urgent'], + ['current-jobs-ready', 'ready'], + ['current-jobs-reserved', 'reserved'], + ['current-jobs-delayed', 'delayed'], + ['current-jobs-buried', 'buried'] + ] + }, + 'current_connections': { + 'options': [None, 'Current Connections', 'connections', 'server statistics', + 'beanstalk.current_connections', 'line'], + 'lines': [ + ['current-connections', 'written'], + ['current-producers', 'producers'], + ['current-workers', 'workers'], + ['current-waiting', 'waiting'] + ] + }, + 'binlog': { + 'options': [None, 'Binlog', 'records/s', 'server statistics', 'beanstalk.binlog', 'line'], + 'lines': [ + ['binlog-records-written', 'written', 'incremental'], + ['binlog-records-migrated', 'migrated', 'incremental'] + ] + }, + 'uptime': { + 'options': [None, 'Uptime', 'seconds', 'server statistics', 'beanstalk.uptime', 'line'], + 'lines': [ + ['uptime'], + ] + } +} + + +def tube_chart_template(name): + order = ['{0}_jobs_rate'.format(name), + '{0}_jobs'.format(name), + '{0}_connections'.format(name), + '{0}_commands'.format(name), + '{0}_pause'.format(name) + ] + family = 'tube {0}'.format(name) + + charts = { + order[0]: { + 'options': [None, 'Job Rate', 'jobs/s', family, 'beanstalk.jobs_rate', 'area'], + 'lines': [ + ['_'.join([name, 'total-jobs']), 'jobs', 'incremental'] + ]}, + order[1]: { + 'options': [None, 'Jobs', 'jobs', family, 'beanstalk.jobs', 'stacked'], + 'lines': [ + ['_'.join([name, 'current-jobs-urgent']), 'urgent'], + ['_'.join([name, 'current-jobs-ready']), 'ready'], + ['_'.join([name, 'current-jobs-reserved']), 'reserved'], + ['_'.join([name, 'current-jobs-delayed']), 'delayed'], + ['_'.join([name, 'current-jobs-buried']), 'buried'] + ]}, + order[2]: { + 'options': [None, 'Connections', 'connections', family, 'beanstalk.connections', 'stacked'], + 'lines': [ + ['_'.join([name, 'current-using']), 'using'], + ['_'.join([name, 'current-waiting']), 'waiting'], + ['_'.join([name, 'current-watching']), 'watching'] + ]}, + order[3]: { + 'options': [None, 'Commands', 'commands/s', family, 'beanstalk.commands', 'stacked'], + 'lines': [ + ['_'.join([name, 'cmd-delete']), 'deletes', 'incremental'], + ['_'.join([name, 'cmd-pause-tube']), 'pauses', 'incremental'] + ]}, + order[4]: { + 'options': [None, 'Pause', 'seconds', family, 'beanstalk.pause', 'stacked'], + 'lines': [ + ['_'.join([name, 'pause']), 'since'], + ['_'.join([name, 'pause-time-left']), 'left'] + ]} + + } + + return order, charts + + +class Service(SimpleService): + def __init__(self, configuration=None, name=None): + SimpleService.__init__(self, configuration=configuration, name=name) + self.configuration = configuration + self.order = list(ORDER) + self.definitions = dict(CHARTS) + self.conn = None + self.alive = True + + def check(self): + if not BEANSTALKC: + self.error("'beanstalkc' module is needed to use beanstalk.chart.py") + return False + + if not YAML: + self.error("'yaml' module is needed to use beanstalk.chart.py") + return False + + self.conn = self.connect() + + return True if self.conn else False + + def get_data(self): + """ + :return: dict + """ + if not self.is_alive(): + return None + + active_charts = self.charts.active_charts() + data = dict() + + try: + data.update(self.conn.stats()) + + for tube in self.conn.tubes(): + stats = self.conn.stats_tube(tube) + + if tube + '_jobs_rate' not in active_charts: + self.create_new_tube_charts(tube) + + for stat in stats: + data['_'.join([tube, stat])] = stats[stat] + + except beanstalkc.SocketError: + self.alive = False + return None + + return data or None + + def create_new_tube_charts(self, tube): + order, charts = tube_chart_template(tube) + + for chart_name in order: + params = [chart_name] + charts[chart_name]['options'] + dimensions = charts[chart_name]['lines'] + + new_chart = self.charts.add_chart(params) + for dimension in dimensions: + new_chart.add_dimension(dimension) + + def connect(self): + host = self.configuration.get('host', '127.0.0.1') + port = self.configuration.get('port', 11300) + timeout = self.configuration.get('timeout', 1) + try: + return beanstalkc.Connection(host=host, + port=port, + connect_timeout=timeout, + parse_yaml=yaml.load) + except beanstalkc.SocketError as error: + self.error('Connection to {0}:{1} failed: {2}'.format(host, port, error)) + return None + + def reconnect(self): + try: + self.conn.reconnect() + self.alive = True + return True + except beanstalkc.SocketError: + return False + + def is_alive(self): + if not self.alive: + return self.reconnect() + return True diff --git a/python.d/bind_rndc.chart.py b/python.d/bind_rndc.chart.py index 97a33fc891..cc96659b21 100644 --- a/python.d/bind_rndc.chart.py +++ b/python.d/bind_rndc.chart.py @@ -165,7 +165,7 @@ class Service(SimpleService): if dimension_id not in self.data: dimension = dimension_id.replace(chart_name[:9], '') if dimension_id not in self.charts[chart_name]: - self.charts[chart_name].add_dimension_and_push_chart([dimension_id, dimension, 'incremental']) + self.charts[chart_name].add_dimension([dimension_id, dimension, 'incremental']) self.data[dimension_id] = value diff --git a/python.d/cpufreq.chart.py b/python.d/cpufreq.chart.py index 9c5355ad62..3abde736ca 100644 --- a/python.d/cpufreq.chart.py +++ b/python.d/cpufreq.chart.py @@ -14,7 +14,7 @@ ORDER = ['cpufreq'] CHARTS = { 'cpufreq': { - 'options': [None, 'CPU Clock', 'MHz', 'cpufreq', 'cpufreq', 'line'], + 'options': [None, 'CPU Clock', 'MHz', 'cpufreq', 'cpufreq.cpufreq', 'line'], 'lines': [ # lines are created dynamically in `check()` method ]} diff --git a/python.d/cpuidle.chart.py b/python.d/cpuidle.chart.py index 4f732e6eb1..d14c6aaf3d 100644 --- a/python.d/cpuidle.chart.py +++ b/python.d/cpuidle.chart.py @@ -122,7 +122,7 @@ class Service(SimpleService): self.order.append(orderid) active_name = '%s_active_time' % (cpu,) self.definitions[orderid] = { - 'options': [None, 'C-state residency', 'time%', 'cpuidle', 'cpuidle', 'stacked'], + 'options': [None, 'C-state residency', 'time%', 'cpuidle', 'cpuidle.cpuidle', 'stacked'], 'lines': [ [active_name, 'C0 (active)', 'percentage-of-incremental-row', 1, 1], ], diff --git a/python.d/example.chart.py b/python.d/example.chart.py index f7e4329cb2..ee7ff62fc6 100644 --- a/python.d/example.chart.py +++ b/python.d/example.chart.py @@ -2,7 +2,7 @@ # Description: example netdata python.d module # Author: Pawel Krupa (paulfantom) -import random +from random import SystemRandom from bases.FrameworkServices.SimpleService import SimpleService @@ -24,22 +24,24 @@ CHARTS = { class Service(SimpleService): def __init__(self, configuration=None, name=None): - super(self.__class__, self).__init__(configuration=configuration, name=name) + SimpleService.__init__(self, configuration=configuration, name=name) self.order = ORDER self.definitions = CHARTS - self.data = dict() + self.random = SystemRandom() @staticmethod def check(): return True def get_data(self): - dimension = ''.join(['random', str(random.randint(1, 3))]) + data = dict() - if dimension not in self.charts['random']: - self.charts['random'].add_dimension_and_push_chart([dimension]) + for i in range(1, 4): + dimension_id = ''.join(['random', str(i)]) - self.data[dimension] = random.randint(0, 100) + if dimension_id not in self.charts['random']: + self.charts['random'].add_dimension([dimension_id]) - return self.data + data[dimension_id] = self.random.randint(0, 100) + return data diff --git a/python.d/freeradius.chart.py b/python.d/freeradius.chart.py index 92818e818f..251582d4f2 100644 --- a/python.d/freeradius.chart.py +++ b/python.d/freeradius.chart.py @@ -84,7 +84,7 @@ class Service(SimpleService): self.sub_echo = [self.echo, RADIUS_MSG] self.sub_radclient = [self.radclient, '-r', '1', '-t', '1', ':'.join([self.host, self.port]), 'status', self.secret] - + def check(self): if not all([self.echo, self.radclient]): self.error('Can\'t locate "radclient" binary or binary is not executable by netdata') diff --git a/python.d/memcached.chart.py b/python.d/memcached.chart.py index 59ff154075..4f7adfa233 100644 --- a/python.d/memcached.chart.py +++ b/python.d/memcached.chart.py @@ -149,7 +149,6 @@ class Service(SocketService): data[t[0]] = t[1] except (IndexError, ValueError): self.debug("invalid line received: " + str(line)) - pass if not data: self.error("received data doesn't have any records") diff --git a/python.d/mysql.chart.py b/python.d/mysql.chart.py index 1f35b4f579..9f323d0cdd 100644 --- a/python.d/mysql.chart.py +++ b/python.d/mysql.chart.py @@ -6,7 +6,7 @@ from bases.FrameworkServices.MySQLService import MySQLService # default module values (can be overridden per job in `config`) # update_every = 3 -priority = 90000 +priority = 60000 retries = 60 # query executed on MySQL server diff --git a/python.d/postgres.chart.py b/python.d/postgres.chart.py index 3241e6e81e..ef69a9c77e 100644 --- a/python.d/postgres.chart.py +++ b/python.d/postgres.chart.py @@ -17,7 +17,7 @@ from bases.FrameworkServices.SimpleService import SimpleService # default module values update_every = 1 -priority = 90000 +priority = 60000 retries = 60 METRICS = dict( @@ -225,7 +225,7 @@ CHARTS = { class Service(SimpleService): def __init__(self, configuration=None, name=None): - super(self.__class__, self).__init__(configuration=configuration, name=name) + SimpleService.__init__(self, configuration=configuration, name=name) self.order = ORDER[:] self.definitions = deepcopy(CHARTS) self.table_stats = configuration.pop('table_stats', False) diff --git a/python.d/python_modules/bases/FrameworkServices/SimpleService.py b/python.d/python_modules/bases/FrameworkServices/SimpleService.py index 65c791f981..14c8391013 100644 --- a/python.d/python_modules/bases/FrameworkServices/SimpleService.py +++ b/python.d/python_modules/bases/FrameworkServices/SimpleService.py @@ -14,8 +14,6 @@ from bases.charts import Charts, ChartError, create_runtime_chart from bases.collection import OldVersionCompatibility, safe_print from bases.loggers import PythonDLimitedLogger -CHART_OBSOLETE_PENALTY = 10 - RUNTIME_CHART_UPDATE = 'BEGIN netdata.runtime_{job_name} {since_last}\n' \ 'SET run_time = {elapsed}\n' \ 'END\n' @@ -66,7 +64,9 @@ class SimpleService(Thread, PythonDLimitedLogger, OldVersionCompatibility, objec 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) + 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__), @@ -149,10 +149,6 @@ class SimpleService(Thread, PythonDLimitedLogger, OldVersionCompatibility, objec 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) @@ -209,55 +205,32 @@ class SimpleService(Thread, PythonDLimitedLogger, OldVersionCompatibility, objec 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)) + updated = False for chart in self.charts: - if not chart.alive: + if chart.flags.obsoleted: + 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 - 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 + ok = chart.update(data, interval) + if ok: + updated = True - if not charts_updated: - self.debug('none of the charts have been updated') + if not updated: + self.debug('none of the charts has been updated') - return charts_updated + return 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)) + 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 diff --git a/python.d/python_modules/bases/charts.py b/python.d/python_modules/bases/charts.py index 731cebdfaf..f6296adc52 100644 --- a/python.d/python_modules/bases/charts.py +++ b/python.d/python_modules/bases/charts.py @@ -13,7 +13,7 @@ DIMENSION_ALGORITHMS = ['absolute', 'incremental', 'percentage-of-absolute-row', CHART_BEGIN = 'BEGIN {type}.{id} {since_last}\n' CHART_CREATE = "CHART {type}.{id} '{name}' '{title}' '{units}' '{family}' '{context}' " \ - "{chart_type} {priority} {update_every}\n" + "{chart_type} {priority} {update_every} '' 'python.d.plugin' '{module_name}'\n" CHART_OBSOLETE = "CHART {type}.{id} '{name}' '{title}' '{units}' '{family}' '{context}' " \ "{chart_type} {priority} {update_every} 'obsolete'\n" @@ -71,7 +71,7 @@ class Charts: All charts stored in a dict. Chart is a instance of Chart class. Charts adding must be done using Charts.add_chart() method only""" - def __init__(self, job_name, priority, get_update_every): + def __init__(self, job_name, priority, cleanup, get_update_every, module_name): """ :param job_name: <bound method> :param priority: <int> @@ -79,7 +79,9 @@ class Charts: """ self.job_name = job_name self.priority = priority + self.cleanup = cleanup self.get_update_every = get_update_every + self.module_name = module_name self.charts = dict() def __len__(self): @@ -109,13 +111,6 @@ class Charts: def __nonzero__(self): return self.__bool__() - def penalty_exceeded(self, penalty_max): - """ - :param penalty_max: <int> - :return: - """ - return (chart for chart in self if chart.penalty > penalty_max and chart.alive) - def add_chart(self, params): """ Create Chart instance and add it to the dict @@ -125,16 +120,19 @@ class Charts: :return: """ params = [self.job_name()] + params - chart_id = params[1] - if chart_id in self.charts: - raise DuplicateItemError("'{chart}' already in charts".format(chart=chart_id)) - else: - new_chart = Chart(params) - new_chart.params['update_every'] = self.get_update_every() - new_chart.params['priority'] = self.priority - self.priority += 1 - self.charts[new_chart.id] = new_chart - return new_chart + new_chart = Chart(params) + + new_chart.params['update_every'] = self.get_update_every() + new_chart.params['priority'] = self.priority + new_chart.params['module_name'] = self.module_name + + self.priority += 1 + self.charts[new_chart.id] = new_chart + + return new_chart + + def active_charts(self): + return [chart.id for chart in self if not chart.flags.obsoleted] class Chart: @@ -156,7 +154,7 @@ class Chart: self.dimensions = list() self.variables = set() - self.alive = True + self.flags = ChartFlags() self.penalty = 0 def __getattr__(self, item): @@ -178,13 +176,6 @@ class Chart: def __contains__(self, item): return item in [dimension.id for dimension in self.dimensions] - def suppress(self): - self.alive = False - - def unsuppress(self): - self.penalty = 0 - self.alive = True - def add_variable(self, variable): """ :param variable: <list> @@ -202,43 +193,62 @@ class Chart: if dim.id in self: raise DuplicateItemError("'{dimension}' already in '{chart}' dimensions".format(dimension=dim.id, chart=self.name)) + self.refresh() self.dimensions.append(dim) return dim - def add_dimension_and_push_chart(self, dimension): - """ - :param dimension: <list> - :return: - """ - dim = self.add_dimension(dimension) - self.unsuppress() - safe_print(self.create(dim)) - - def create(self, dimension=None): + def create(self): """ - :param dimension: Dimension :return: """ chart = CHART_CREATE.format(**self.params) - if not dimension: - dimensions = ''.join([dimension.create() for dimension in self.dimensions]) - variables = ''.join([var.set(var.value) for var in self.variables if var]) - return chart + dimensions + variables + dimensions = ''.join([dimension.create() for dimension in self.dimensions]) + variables = ''.join([var.set(var.value) for var in self.variables if var]) + + self.flags.push = False + self.flags.created = True + + safe_print(chart + dimensions + variables) + + def update(self, data, interval): + updated_dimensions, updated_variables = str(), str() + + for dim in self.dimensions: + value = dim.get_value(data) + if value is not None: + updated_dimensions += dim.set(value) + + for var in self.variables: + value = var.get_value(data) + if value is not None: + updated_variables += var.set(value) + + if updated_dimensions: + since_last = interval if self.flags.updated else 0 + + if self.flags.push: + self.create() + + chart_begin = CHART_BEGIN.format(type=self.type, id=self.id, since_last=since_last) + safe_print(chart_begin, updated_dimensions, updated_variables, 'END\n') + + self.flags.updated = True + self.penalty = 0 else: - dimensions = dimension.create() - return chart + dimensions + self.penalty += 1 + self.flags.updated = False - def begin(self, since_last): - """ - :param since_last: <int>: microseconds - :return: - """ - return CHART_BEGIN.format(type=self.type, - id=self.id, - since_last=since_last) + return bool(updated_dimensions) |