@cryptotaxi247 / netdata-1 / commits / 53d42b709

charts classes added

lgz committed Oct 13, 2017 at 00:06 UTC 53d42b709d19a7642551a4df93e9ed090462d1a0
1 file changed +234
python.d/python_modules/bases/charts.py new
+234
@@ -0,0 +1,234 @@
1 +# -*- coding: utf-8 -*-
2 +# Description:
3 +# Author: Ilya Mashchenko (l2isbad)
4 +
5 +from bases.collection import safe_print
6 +
7 +CHART_PARAMS = ['type', 'id', 'name', 'title', 'units', 'family', 'context', 'chart_type']
8 +DIMENSION_PARAMS = ['id', 'name', 'algorithm', 'multiplier', 'divisor']
9 +CHART_TYPES = ['line', 'area', 'stacked']
10 +DIMENSION_ALGORITHMS = ['absolute', 'incremental', 'percentage-of-absolute-row', 'percentage-of-incremental-row']
11 +
12 +CHART_BEGIN = 'BEGIN {type}.{id} {since_last}\n'
13 +CHART_CREATE = "CHART {type}.{id} '{name}' '{title}' '{units}' '{family}' '{context}' " \
14 + "{chart_type} {priority} {update_every}\n"
15 +CHART_OBSOLETE = "CHART {type}.{id} '{name}' '{title}' '{units}' '{family}' '{context}' " \
16 + "{chart_type} {priority} {update_every} 'obsolete'\n"
17 +
18 +
19 +DIMENSION_CREATE = "DIMENSION '{id}' '{name}' {algorithm} {multiplier} {divisor} '{hidden}'\n"
20 +DIMENSION_SET = "SET '{id}' = {value}\n"
21 +
22 +RUNTIME_CHART_CREATE = "CHART netdata.runtime_{job_name} '' 'Execution time for {job_name}' 'ms' 'python.d' " \
23 + "netdata.pythond_runtime line 145000 {update_every}\n" \
24 + "DIMENSION run_time 'run time' absolute 1 1\n"
25 +
26 +
27 +def create_runtime_chart(func):
28 + """
29 + :param func: class method
30 + :return:
31 + """
32 + def wrapper(*args, **kwargs):
33 + self = args[0]
34 + ok = func(*args, **kwargs)
35 + if ok:
36 + self.safe_print(RUNTIME_CHART_CREATE.format(job_name=self.name,
37 + update_every=self._runtime_counters.FREQ))
38 + return ok
39 + return wrapper
40 +
41 +
42 +class ReAddingError(Exception):
43 + pass
44 +
45 +
46 +class Checks:
47 + @staticmethod
48 + def chart_params(params, chart_name):
49 + """
50 + :param params: <list>
51 + :param chart_name: <str>
52 + :return:
53 + """
54 + for param in CHART_PARAMS:
55 + try:
56 + params[param]
57 + except KeyError:
58 + raise KeyError('{param} is missing ({chart})'.format(param=param, chart=chart_name))
59 + value = params[param]
60 + if any([param == 'id', param == 'units', param == 'context', param == 'chart_type']):
61 + if not value:
62 + raise ValueError('Wrong chart value ({chart}). {param} must not be empty'.format(chart=chart_name,
63 + param=param))
64 + if param == 'chart_type':
65 + if value not in CHART_TYPES:
66 + raise ValueError('Wrong chart type ({chart}). Must be on of {types}'.format(chart=chart_name,
67 + types=CHART_TYPES))
68 +
69 + @staticmethod
70 + def dimension_params(params, chart_name):
71 + """
72 + :param params: <list>
73 + :param chart_name: <str>
74 + :return:
75 + """
76 + if not params.get('id'):
77 + raise ValueError('Wrong dimension id ({chart}). Must not be empty'.format(chart=chart_name))
78 + if params['algorithm'] not in DIMENSION_ALGORITHMS:
79 + raise ValueError('Wrong dimension algorithm ({chart}). '
80 + 'Must be one of {algorithms}'.format(chart=chart_name,
81 + algorithms=DIMENSION_ALGORITHMS))
82 + if not (str(params['multiplier']).isdigit() and str(params['divisor']).isdigit()):
83 + raise ValueError('Wrong multiplier or divisor ({chart}). Must be a digit'.format(chart=chart_name))
84 +
85 +
86 +class Charts:
87 + def __init__(self, job_name, priority, update_every):
88 + """
89 + :param job_name: <str>
90 + :param priority: <int>
91 + :param update_every: <int>
92 + """
93 + self.job_name = job_name
94 + self.priority = priority
95 + self.update_every = update_every
96 + self.charts = dict()
97 +
98 + def __len__(self):
99 + return len(self.charts)
100 +
101 + def __iter__(self):
102 + return iter(self.charts.values())
103 +
104 + def __repr__(self):
105 + return str([chart for chart in self.charts])
106 +
107 + def __contains__(self, item):
108 + return item in self.charts
109 +
110 + def __getitem__(self, item):
111 + return self.charts[item]
112 +
113 + def __delitem__(self, key):
114 + del self.charts[key]
115 +
116 + def empty(self):
117 + return not bool(self.charts)
118 +
119 + def penalty_exceeded(self, penalty_max):
120 + """
121 + :param penalty_max: <int>
122 + :return:
123 + """
124 + return (chart for chart in self if chart.penalty > penalty_max)
125 +
126 + def add_chart(self, params):
127 + """
128 + :param params: <list>
129 + :return:
130 + """
131 + params = [self.job_name] + params
132 + chart_id = params[1]
133 + if chart_id in self.charts:
134 + raise ReAddingError('{chart} already in charts'.format(chart=chart_id))
135 + else:
136 + new_chart = Chart(params)
137 + new_chart.params['update_every'] = self.update_every
138 + new_chart.params['priority'] = self.priority
139 + self.priority += 1
140 + self.charts[new_chart.params['id']] = new_chart
141 + return new_chart
142 +
143 +
144 +class Chart:
145 + def __init__(self, params):
146 + """
147 + :param params: <list>
148 + """
149 + self.params = dict(zip(CHART_PARAMS, (p or str() for p in params)))
150 + self.name = '.'.join([self.params['type'], self.params['id']])
151 + Checks.chart_params(self.params, self.name)
152 + self.dimensions = list()
153 + self.penalty = 0
154 +
155 + def __repr__(self):
156 + return str(self.params)
157 +
158 + def __str__(self):
159 + return self.params['id']
160 +
161 + def add_dimension(self, dimension):
162 + """
163 + :param dimension: <list>
164 + :return:
165 + """
166 + if dimension[0] in self.dimensions:
167 + raise ReAddingError('{dimension} already in {chart} dimensions'.format(dimension=dimension[0],
168 + chart=self.name))
169 + self.dimensions.append(Dimension(dimension, self.name))
170 +
171 + def add_dimension_and_push_chart(self, dimension):
172 + """
173 + :param dimension: <list>
174 + :return:
175 + """
176 + if dimension[0] in self.dimensions:
177 + raise ReAddingError('{dimension} already in {chart} dimensions'.format(dimension=dimension[0],
178 + chart=self.name))
179 + dimension = Dimension(dimension, self.name)
180 + self.dimensions.append(dimension)
181 + safe_print(self.create(dimension))
182 +
183 + def create(self, dimension=None):
184 + """
185 + :param dimension: <list>
186 + :return:
187 + """
188 + chart = CHART_CREATE.format(**self.params)
189 + if not dimension:
190 + dimensions = ''.join([dimension.create() for dimension in self.dimensions])
191 + else:
192 + dimensions = dimension.create()
193 + return chart + dimensions
194 +
195 + def begin(self, since_last):
196 + """
197 + :param since_last: <int>: microseconds
198 + :return:
199 + """
200 + return CHART_BEGIN.format(type=self.params['type'],
201 + id=self.params['id'],
202 + since_last=since_last)
203 +
204 + def obsolete(self):
205 + return CHART_OBSOLETE.format(**self.params)
206 +
207 +
208 +class Dimension:
209 + def __init__(self, params, chart_name):
210 + """
211 + :param params: <list>
212 + :param chart_name: <str>
213 + """
214 + self.params = dict(zip(DIMENSION_PARAMS, (p or str() for p in params)))
215 + self.params['name'] = self.params.get('name') or self.params.get('id')
216 + self.params.setdefault('algorithm', 'incremental')
217 + self.params.setdefault('multiplier', 1)
218 + self.params.setdefault('divisor', 1)
219 + self.params.setdefault('hidden', '')
220 + Checks.dimension_params(self.params, chart_name)
221 +
222 + def __repr__(self):
223 + return self.params['id']
224 +
225 + def create(self):
226 + return DIMENSION_CREATE.format(**self.params)
227 +
228 + def set(self, value):
229 + """
230 + :param value: <str>: must be a digit
231 + :return:
232 + """
233 + return DIMENSION_SET.format(id=self.params['id'],
234 + value=value)