New class UrlService + phpfpm python module + minor changes in python.d.conf
paulfantom committed
Jun 22, 2016 at 13:12 UTC
77563deeed3129bbe1fd5a9996633b874bbffeff
4 files changed
+217
-8
conf.d/python.d.conf
+3
-2
@@ -5,5 +5,6 @@ enabled: no
5
6
# By default python.d.plugin enables all modules stored in python.d
7
# Modules can be disabled with setting "module_name = no"
8
-example: no
9
-mysql: no
8
+example: yes
9
+mysql: yes
10
+phpfpm: yes
conf.d/python.d/phpfpm.conf
new
+4
@@ -0,0 +1,4 @@
1
+update_every: 3
2
+
3
+local:
4
+ url: "http://localhost/status"
\ No newline at end of file
python.d/phpfpm.chart.py
new
+77
@@ -0,0 +1,77 @@
1
+# -*- coding: utf-8 -*-
2
+# Description: PHP-FPM netdata python.d plugin
3
+# Author: Pawel Krupa (paulfantom)
4
+
5
+from base import UrlService
6
+
7
+# default module values (can be overridden per job in `config`)
8
+update_every = 2
9
+priority = 60000
10
+retries = 5
11
+
12
+# default job configuration (overridden by python.d.plugin)
13
+config = {'local': {
14
+ 'update_every': update_every,
15
+ 'retries': retries,
16
+ 'priority': priority,
17
+ 'url': 'http://localhost/status'
18
+}}
19
+
20
+# charts order (can be overridden if you want less charts, or different order)
21
+ORDER = ['connections', 'requests', 'performance']
22
+
23
+CHARTS = {
24
+ 'connections': {
25
+ 'options': "'' 'PHP-FPM Active Connections' 'connections' phpfpm phpfpm.connections line",
26
+ 'lines': [
27
+ {"name": "active",
28
+ "options": "'' absolute 1 1"},
29
+ {"name": "maxActive",
30
+ "options": "'max active' absolute 1 1"},
31
+ {"name": "idle",
32
+ "options": "'' absolute 1 1"}
33
+ ]},
34
+ 'requests': {
35
+ 'options': "'' 'PHP-FPM Requests' 'requests/s' phpfpm phpfpm.requests line",
36
+ 'lines': [
37
+ {"name": "requests",
38
+ "options": "'' incremental 1 1"}
39
+ ]},
40
+ 'performance': {
41
+
42
+ 'options': "'' 'PHP-FPM Performance' 'status' phpfpm phpfpm.performance line",
43
+ 'lines': [
44
+ {"name": "reached",
45
+ "options": "'max children reached' absolute 1 1"},
46
+ {"name": "slow",
47
+ "options": "'slow requests' absolute 1 1"}
48
+ ]}
49
+}
50
+
51
+
52
+class Service(UrlService):
53
+ url = "http://localhost/status"
54
+ order = ORDER
55
+ charts = CHARTS
56
+ assignment = {"active processes": 'active',
57
+ "max active processes": 'maxActive',
58
+ "idle processes": 'idle',
59
+ "accepted conn": 'requests',
60
+ "max children reached": 'reached',
61
+ "slow requests": 'slow'}
62
+
63
+ def _formatted_data(self):
64
+ """
65
+ Format data received from http request
66
+ :return: dict
67
+ """
68
+ raw = self._get_data().split('\n')
69
+ data = {}
70
+ for row in raw:
71
+ tmp = row.split(":")
72
+ if str(tmp[0]) in self.assignment:
73
+ try:
74
+ data[self.assignment[tmp[0]]] = int(tmp[1])
75
+ except (IndexError, ValueError) as a:
76
+ pass
77
+ return data
python.d/python_modules/base.py
+133
-6
@@ -1,9 +1,13 @@
1
# -*- coding: utf-8 -*-
2
-# Description: base for netdata python.d plugins
2
+# Description: prototypes for netdata python.d modules
3
# Author: Pawel Krupa (paulfantom)
4
5
from time import time
6
import sys
7
+try:
8
+ from urllib.request import urlopen
9
+except ImportError:
10
+ from urllib2 import urlopen
11
12
13
class BaseService(object):
@@ -62,7 +66,7 @@ class BaseService(object):
66
@staticmethod
67
def error(msg, exception=""):
68
if exception != "":
65
- exception = " " + str(exception).replace("\n"," ")
69
+ exception = " " + str(exception).replace("\n", " ")
70
sys.stderr.write(str(msg)+exception+"\n")
71
sys.stderr.flush()
72
@@ -71,7 +75,7 @@ class BaseService(object):
75
check() prototype
76
:return: boolean
77
"""
74
- self.error("Service " + str(self.__name__) + "doesn't implement check() function")
78
+ self.error("Service " + str(self.__module__) + "doesn't implement check() function")
79
return False
80
81
def create(self):
@@ -79,13 +83,136 @@ class BaseService(object):
83
create() prototype
84
:return: boolean
85
"""
82
- self.error("Service " + str(self.__name__) + "doesn't implement create() function?")
86
+ self.error("Service " + str(self.__module__) + "doesn't implement create() function?")
87
return False
88
85
- def update(self):
89
+ def update(self, interval):
90
"""
91
update() prototype
92
+ :param interval: int
93
:return: boolean
94
"""
90
- self.error("Service " + str(self.__name__) + "doesn't implement update() function")
95
+ self.error("Service " + str(self.__module__) + "doesn't implement update() function")
96
return False
97
+
98
+
99
+class UrlService(BaseService):
100
+ charts = {}
101
+ # charts definitions in format:
102
+ # charts = {
103
+ # 'chart_name_in_netdata': (
104
+ # "parameters defining chart (passed to CHART statement)",
105
+ # [ # dimensions (lines) definitions
106
+ # ("dimension_name", "dimension parameters (passed to DIMENSION statement)")
107
+ # ])
108
+ # }
109
+ order = []
110
+ definitions = {}
111
+ # definitions are created dynamically in create() method based on 'charts' dictionary. format:
112
+ # definitions = {
113
+ # 'chart_name_in_netdata' : (charts['chart_name_in_netdata'][1], charts['chart_name_in_netdata'][0])
114
+ # }
115
+ url = ""
116
+
117
+ def _get_data(self):
118
+ """
119
+ Get raw data from http request
120
+ :return: str
121
+ """
122
+ try:
123
+ f = urlopen(self.url)
124
+ raw = f.read().decode('utf-8')
125
+ f.close()
126
+ except Exception as e:
127
+ self.error(self.__module__, str(e))
128
+ return None
129
+ return raw
130
+
131
+ def _formatted_data(self):
132
+ """
133
+ Format data received from http request
134
+ :return: dict
135
+ """
136
+ return {}
137
+
138
+ def check(self):
139
+ """
140
+ Format configuration data and try to connect to server
141
+ :return: boolean
142
+ """
143
+ if self.name is None:
144
+ self.name = 'local'
145
+ else:
146
+ self.name = str(self.name)
147
+ try:
148
+ self.url = str(self.configuration['url'])
149
+ except (KeyError, TypeError):
150
+ pass
151
+
152
+ if self._formatted_data() is not None:
153
+ return True
154
+ else:
155
+ return False
156
+
157
+ def create(self):
158
+ """
159
+ Create charts
160
+ :return: boolean
161
+ """
162
+ for name in self.order:
163
+ if name not in self.charts:
164
+ continue
165
+ self.definitions[name] = []
166
+ for line in self.charts[name]['lines']:
167
+ self.definitions[name].append(line['name'])
168
+
169
+ idx = 0
170
+ data = self._formatted_data()
171
+ if data is None:
172
+ return False
173
+ for name in self.order:
174
+ header = "CHART " + \
175
+ self.__module__ + "_" + \
176
+ self.name + "." + \
177
+ name + " " + \
178
+ self.charts[name]['options'] + " " + \
179
+ str(self.priority + idx) + " " + \
180
+ str(self.update_every)
181
+ content = ""
182
+ # check if server has this datapoint
183
+ for line in self.charts[name]['lines']:
184
+ if line['name'] in data:
185
+ content += "DIMENSION " + line['name'] + " " + line['options'] + "\n"
186
+
187
+ if len(content) > 0:
188
+ print(header)
189
+ print(content)
190
+ idx += 1
191
+
192
+ if idx == 0:
193
+ return False
194
+ return True
195
+
196
+ def update(self, interval):
197
+ """
198
+ Update charts
199
+ :param interval: int
200
+ :return: boolean
201
+ """
202
+ data = self._formatted_data()
203
+ if data is None:
204
+ return False
205
+
206
+ for chart, dimensions in self.definitions.items():
207
+ header = "BEGIN " + self.__module__ + "_" + str(self.name) + "." + chart + " " + str(interval)
208
+ c = ""
209
+ for dim in dimensions:
210
+ try:
211
+ c += "\nSET " + dim + " = " + str(data[dim])
212
+ except KeyError:
213
+ pass
214
+ if len(c) != 0:
215
+ print(header + c)
216
+ print("END")
217
+
218
+ return True