.
675 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PHP OPcache exporter
2 |
3 | ## Overview
4 |
5 | prometheus exporter for PHP OPcache stats, written in python.
6 |
7 |
8 |
9 | integration for discovery a send prometheus metrics to zabbix
10 |
11 | ## Description
12 |
13 | Data is fetched from [opcache_get_status()](http://php.net/manual/en/function.opcache-get-status.php) function.
14 |
15 | FastCGi variant was tested on CentOS 7 with php-fpm 7.1.
16 |
17 | scprape-uri varianta tested with docker and owncloud docker stack.
18 |
19 | ## Usage
20 |
21 | You can just use "--scrape_uri" for scraping from URL/URI or use fast-cgi client
22 |
23 | Help on flags:
24 |
25 |
26 | usage: php_opcache_exporter.py [-h] [-p port] [--scrape_uri SCRAPE_URI]
27 | [--fhost FHOST] [--phpfile phpfile]
28 | [--phpcontent phpcontent] [--phpcode phpcode]
29 | [--fport FPORT]
30 |
31 | php_opcache_exporter args
32 |
33 | optional arguments:
34 | -h, --help show this help message and exit
35 | -p port, --port port Listen to this port
36 | --scrape_uri SCRAPE_URI
37 | URL for scraping, such as http://127.0.0.1/opcache-
38 | status.php
39 | --fhost FHOST Target FastCGI host, such as 127.0.0.1
40 | --phpfile phpfile A php file absolute path, such as
41 | /usr/local/lib/php/System.php
42 | --phpcontent phpcontent
43 | http get params, such as name=john&address=beijing
44 | --phpcode phpcode code for execution over fastcgi client
45 | --fport FPORT FastCGI port
46 |
47 |
48 | ## Author(s)
49 |
50 | * Patrik Majer (@czhujer)
51 |
52 | ## Docs
53 |
54 | * https://prometheus.io/docs/concepts/metric_types/
55 |
56 | * https://prometheus.io/docs/instrumenting/writing_exporters/
57 |
58 | * https://github.com/prometheus/client_python
59 |
60 | * https://github.com/RobustPerception/python_examples/tree/master/jenkins_exporter
61 |
62 | * https://github.com/lovoo/jenkins_exporter
63 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 | php:
4 | build:
5 | context: ./docker/php
6 | volumes:
7 | - ./docker/php/docker-php-ext-opcache.ini:/usr/local/etc/php/conf.d/docker-php-ext-opcache.ini
8 | - tmp:/tmp
9 | ports:
10 | - 9000:9000
11 |
12 | python:
13 | build:
14 | context: .
15 | dockerfile: Dockerfile
16 | image: czhujer/php-opcache-exporter:v0.4.1
17 | ports:
18 | - 9462:9462
19 | command: python php_opcache_exporter.py --fhost php
20 | volumes:
21 | - ./:/app
22 | - tmp:/tmp
23 |
24 | volumes:
25 | tmp:
26 |
--------------------------------------------------------------------------------
/docker/php/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM php:7.2-fpm
2 | LABEL maintainer="Narate Ketram "
3 | RUN docker-php-ext-install opcache && docker-php-ext-enable opcache
4 |
--------------------------------------------------------------------------------
/docker/php/docker-php-ext-opcache.ini:
--------------------------------------------------------------------------------
1 | zend_extension=/usr/local/lib/php/extensions/no-debug-non-zts-20170718/opcache.so
2 | ; Sets how much memory to use
3 | opcache.memory_consumption=128
4 |
5 | ;Sets how much memory should be used by OPcache for storing internal strings
6 | ;(e.g. classnames and the files they are contained in)
7 | opcache.interned_strings_buffer=8
8 |
9 | ; The maximum number of files OPcache will cache
10 | opcache.max_accelerated_files=4000
11 |
12 | ;How often (in seconds) to check file timestamps for changes to the shared
13 | ;memory storage allocation.
14 | opcache.revalidate_freq=60
15 |
16 | ;If enabled, a fast shutdown sequence is used for the accelerated code
17 | ;The fast shutdown sequence doesn't free each allocated block, but lets
18 | ;the Zend Engine Memory Manager do the work.
19 | opcache.fast_shutdown=1
20 |
21 | ;Enables the OPcache for the CLI version of PHP.
22 | opcache.enable_cli=1
23 |
--------------------------------------------------------------------------------
/docker/python/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:2-alpine
2 | LABEL maintainer="Narate Ketram "
3 | WORKDIR /app
4 | ADD ./docker/python/requirements.txt .
5 | RUN pip install -r requirements.txt
6 | ADD ./php_opcache_exporter.py .
7 | EXPOSE 9462
8 | CMD ["python", "php_opcache_exporter.py"]
9 |
--------------------------------------------------------------------------------
/docker/python/requirements.txt:
--------------------------------------------------------------------------------
1 | prometheus_client
2 | requests
3 | codecov
4 |
5 |
--------------------------------------------------------------------------------
/php_opcache_exporter.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | import re
4 | import time
5 | import requests
6 | import argparse
7 | from pprint import pprint
8 |
9 | import os
10 | from sys import exit
11 | from prometheus_client import start_http_server, Summary
12 | from prometheus_client.core import GaugeMetricFamily, REGISTRY
13 |
14 | import socket
15 | import random
16 | import sys
17 | from io import BytesIO
18 | import tempfile
19 |
20 | import json
21 |
22 | DEBUG = int(os.environ.get('DEBUG', '0'))
23 | DEBUG2 = int(os.environ.get('DEBUG2', '0'))
24 |
25 | COLLECTION_TIME = Summary('php_opcache_collector_collect_seconds', 'Time spent to collect metrics from PHP OPcache')
26 |
27 | PY2 = True if sys.version_info.major == 2 else False
28 |
29 | def bchr(i):
30 | if PY2:
31 | return force_bytes(chr(i))
32 | else:
33 | return bytes([i])
34 |
35 | def bord(c):
36 | if isinstance(c, int):
37 | return c
38 | else:
39 | return ord(c)
40 |
41 | def force_bytes(s):
42 | if isinstance(s, bytes):
43 | return s
44 | else:
45 | return s.encode('utf-8', 'strict')
46 |
47 | def force_text(s):
48 | if issubclass(type(s), str):
49 | return s
50 | if isinstance(s, bytes):
51 | s = str(s, 'utf-8', 'strict')
52 | else:
53 | s = str(s)
54 | return s
55 |
56 | def UmaskNamedTemporaryFile(*args, **kargs):
57 | fdesc = tempfile.NamedTemporaryFile(*args, **kargs)
58 | umask = os.umask(0)
59 | os.umask(umask)
60 | os.chmod(fdesc.name, 0o666 & ~umask)
61 | return fdesc
62 |
63 | class OpcacheCollector(object):
64 |
65 | def __init__(self, scrape_uri, phpcode, phpcontent, fhost, fport):
66 | self._scrape_uri = scrape_uri
67 | self._phpcode = phpcode
68 | self._phpcontent = phpcontent
69 | self._fhost = fhost
70 | self._fport = fport
71 |
72 | def collect(self):
73 | start = time.time()
74 |
75 | # The metrics we want to export about.
76 | items = ["opcache_enabled", "cache_full", "restart_in_progress", "restart_pending",
77 | "interned_strings_usage", "memory_usage", "opcache_statistics",
78 | ]
79 |
80 | items2 = ["used_memory", "buffer_size", "number_of_strings", "free_memory"]
81 |
82 | items3 = ["used_memory", "wasted_memory", "current_wasted_percentage", "free_memory"]
83 |
84 | items4 = ["hits", "blacklist_miss_ratio", "max_cached_keys", "manual_restarts",
85 | "num_cached_keys", "opcache_hit_rate", "last_restart_time", "start_time",
86 | "misses", "oom_restarts", "num_cached_scripts", "blacklist_misses", "hash_restarts"]
87 |
88 | # The metrics we want to export.
89 | metrics = {
90 | 'opcache_enabled':
91 | GaugeMetricFamily('php_opcache_opcache_enabled', 'PHP OPcache opcache_enabled'),
92 | 'cache_full':
93 | GaugeMetricFamily('php_opcache_cache_full', 'PHP OPcache cache_full'),
94 | 'restart_in_progress':
95 | GaugeMetricFamily('php_opcache_restart_in_progress', 'PHP OPcache restart_in_progress'),
96 | 'restart_pending':
97 | GaugeMetricFamily('php_opcache_restart_pending', 'PHP OPcache restart_pending'),
98 | 'interned_strings_usage_used_memory':
99 | GaugeMetricFamily('php_opcache_interned_strings_usage_used_memory', 'PHP OPcache interned_strings_usage used_memory'),
100 | 'interned_strings_usage_buffer_size':
101 | GaugeMetricFamily('php_opcache_interned_strings_usage_buffer_size', 'PHP OPcache interned_strings_usage buffer_size'),
102 | 'interned_strings_usage_number_of_strings':
103 | GaugeMetricFamily('php_opcache_interned_strings_usage_number_of_strings', 'PHP OPcache interned_strings_usage number_of_strings'),
104 | 'interned_strings_usage_free_memory':
105 | GaugeMetricFamily('php_opcache_interned_strings_usage_free_memory', 'PHP OPcache interned_strings_usage free_memory'),
106 | 'memory_usage_used_memory':
107 | GaugeMetricFamily('php_opcache_memory_usage_used_memory', 'PHP OPcache memory_usage used_memory'),
108 | 'memory_usage_wasted_memory':
109 | GaugeMetricFamily('php_opcache_memory_usage_wasted_memory', 'PHP OPcache memory_usage wasted_memory'),
110 | 'memory_usage_current_wasted_percentage':
111 | GaugeMetricFamily('php_opcache_memory_usage_current_wasted_percentage', 'PHP OPcache memory_usage current_wasted_percentage'),
112 | 'memory_usage_free_memory':
113 | GaugeMetricFamily('php_opcache_memory_usage_free_memory', 'PHP OPcache memory_usage free_memory'),
114 | 'opcache_statistics_hits':
115 | GaugeMetricFamily('opcache_statistics_hits', 'PHP OPcache opcache_statistics hits'),
116 | 'opcache_statistics_blacklist_miss_ratio':
117 | GaugeMetricFamily('opcache_statistics_blacklist_miss_ratio', 'PHP OPcache opcache_statistics blacklist_miss_ratio'),
118 | 'opcache_statistics_max_cached_keys':
119 | GaugeMetricFamily('opcache_statistics_max_cached_keys', 'PHP OPcache opcache_statistics max_cached_keys'),
120 | 'opcache_statistics_manual_restarts':
121 | GaugeMetricFamily('opcache_statistics_manual_restarts', 'PHP OPcache opcache_statistics manual_restarts'),
122 | 'opcache_statistics_num_cached_keys':
123 | GaugeMetricFamily('opcache_statistics_num_cached_keys', 'PHP OPcache opcache_statistics num_cached_keys'),
124 | 'opcache_statistics_opcache_hit_rate':
125 | GaugeMetricFamily('opcache_statistics_opcache_hit_rate', 'PHP OPcache opcache_statistics opcache_hit_rate'),
126 | 'opcache_statistics_last_restart_time':
127 | GaugeMetricFamily('opcache_statistics_last_restart_time', 'PHP OPcache opcache_statistics last_restart_time'),
128 | 'opcache_statistics_start_time':
129 | GaugeMetricFamily('opcache_statistics_start_time', 'PHP OPcache opcache_statistics start_time'),
130 | 'opcache_statistics_misses':
131 | GaugeMetricFamily('opcache_statistics_misses', 'PHP OPcache opcache_statistics misses'),
132 | 'opcache_statistics_oom_restarts':
133 | GaugeMetricFamily('opcache_statistics_oom_restarts', 'PHP OPcache opcache_statistics oom_restarts'),
134 | 'opcache_statistics_num_cached_scripts':
135 | GaugeMetricFamily('opcache_statistics_num_cached_scripts', 'PHP OPcache opcache_statistics num_cached_scripts'),
136 | 'opcache_statistics_blacklist_misses':
137 | GaugeMetricFamily('opcache_statistics_blacklist_misses', 'PHP OPcache opcache_statistics blacklist_misses'),
138 | 'opcache_statistics_hash_restarts':
139 | GaugeMetricFamily('opcache_statistics_hash_restarts', 'PHP OPcache opcache_statistics hash_restarts'),
140 | }
141 |
142 | # Request data from PHP Opcache
143 | if self._scrape_uri:
144 | values = self._request_data_over_url()
145 | else:
146 | values = self._request_data()
147 |
148 | values_json = json.loads(values)
149 |
150 | # filter metrics and transform into array
151 | for key in values_json:
152 | value = values_json[key]
153 | if key != "scripts":
154 | if DEBUG2:
155 | print("The key and value are ({}) = ({})".format(key, value))
156 | if key in items:
157 | if value == True:
158 | metrics[key].add_metric('',1)
159 | elif value == False:
160 | metrics[key].add_metric('',0)
161 | elif type(value) is dict:
162 | if re.match('^interned_strings.*', key) is not None:
163 | for key2 in value:
164 | if key2 in items2:
165 | #print("The key and value are ({}) = ({})".format(key2, value[key2]))
166 | key2_c = key.encode('ascii') + "_" + key2.encode('ascii')
167 | metrics[key2_c].add_metric('',value[key2])
168 | elif re.match('^memory_usage.*', key) is not None:
169 | for key3 in value:
170 | if key3 in items3:
171 | #print("The key and value are ({}) = ({})".format(key3, value[key3]))
172 | key3_c = key.encode('ascii') + "_" + key3.encode('ascii')
173 | metrics[key3_c].add_metric('',value[key3])
174 | elif re.match('^opcache_statistics.*', key) is not None:
175 | for key4 in value:
176 | if key4 in items4:
177 | #print("The key and value are ({}) = ({})".format(key4, value[key4]))
178 | key4_c = key.encode('ascii') + "_" + key4.encode('ascii')
179 | metrics[key4_c].add_metric('',value[key4])
180 | else:
181 | metrics[key].add_metric('',value)
182 |
183 | for i in metrics:
184 | yield metrics[i]
185 |
186 | duration = time.time() - start
187 | COLLECTION_TIME.observe(duration)
188 |
189 | def _request_data_over_url(self):
190 | # Request exactly the information we need from Opcache
191 |
192 | r = requests.get(self._scrape_uri)
193 |
194 | if r.status_code != 200:
195 | print "ERROR: status code from scrape-url is wrong (" + str(r.status_code) + ")"
196 | exit(14)
197 |
198 | text = r.text
199 | if len(text) > 0:
200 | return text
201 | else:
202 | print "ERROR: response for scrape-url is empty"
203 | exit(13)
204 |
205 | def _request_data(self):
206 | # Request exactly the information we need from Opcache
207 |
208 | #make tmpfile with php code
209 | tmpfile = UmaskNamedTemporaryFile(suffix='.php')
210 | with open(tmpfile.name, 'w') as f:
211 | f.write(self._phpcode)
212 |
213 | #get php content
214 | client = FastCGIClient(self._fhost, self._fport, 3, 0)
215 | params = dict()
216 | documentRoot = "/tmp"
217 | uri = tmpfile.name
218 | scriptname = uri.replace('/tmp','',1)
219 | content = self._phpcontent
220 | params = {
221 | 'GATEWAY_INTERFACE': 'FastCGI/1.0',
222 | 'REQUEST_METHOD': 'POST',
223 | 'SCRIPT_FILENAME': uri,
224 | 'SCRIPT_NAME': scriptname,
225 | 'QUERY_STRING': '',
226 | 'REQUEST_URI': scriptname,
227 | 'DOCUMENT_ROOT': documentRoot,
228 | 'SERVER_SOFTWARE': 'php/fcgiclient',
229 | 'REMOTE_ADDR': '127.0.0.1',
230 | 'REMOTE_PORT': '9985',
231 | 'SERVER_ADDR': '127.0.0.1',
232 | 'SERVER_PORT': '80',
233 | 'SERVER_NAME': "localhost",
234 | 'SERVER_PROTOCOL': 'HTTP/1.1',
235 | 'CONTENT_TYPE': 'application/text',
236 | 'CONTENT_LENGTH': "%d" % len(content),
237 | 'PHP_VALUE': 'auto_prepend_file = php://input',
238 | 'PHP_ADMIN_VALUE': 'allow_url_include = On'
239 | }
240 | response = client.request(params, content)
241 | if DEBUG:
242 | print "params: "
243 | print params
244 | print "response:"
245 | print(force_text(response))
246 |
247 | if not response:
248 | print "ERROR: response for fastcgi call is empty"
249 | exit(2)
250 |
251 | response_body = "\n".join(response.split("\n")[3:])
252 | response_force_text = force_text(response_body)
253 |
254 | if DEBUG:
255 | print "converted response:"
256 | print(response_force_text)
257 |
258 | return response_body
259 |
260 | class FastCGIClient:
261 | # Referrer: https://github.com/wuyunfeng/Python-FastCGI-Client
262 | # Referrer: https://gist.github.com/phith0n/9615e2420f31048f7e30f3937356cf75
263 | """A Fast-CGI Client for Python"""
264 |
265 | # private
266 | __FCGI_VERSION = 1
267 |
268 | __FCGI_ROLE_RESPONDER = 1
269 | __FCGI_ROLE_AUTHORIZER = 2
270 | __FCGI_ROLE_FILTER = 3
271 |
272 | __FCGI_TYPE_BEGIN = 1
273 | __FCGI_TYPE_ABORT = 2
274 | __FCGI_TYPE_END = 3
275 | __FCGI_TYPE_PARAMS = 4
276 | __FCGI_TYPE_STDIN = 5
277 | __FCGI_TYPE_STDOUT = 6
278 | __FCGI_TYPE_STDERR = 7
279 | __FCGI_TYPE_DATA = 8
280 | __FCGI_TYPE_GETVALUES = 9
281 | __FCGI_TYPE_GETVALUES_RESULT = 10
282 | __FCGI_TYPE_UNKOWNTYPE = 11
283 |
284 | __FCGI_HEADER_SIZE = 8
285 |
286 | # request state
287 | FCGI_STATE_SEND = 1
288 | FCGI_STATE_ERROR = 2
289 | FCGI_STATE_SUCCESS = 3
290 |
291 | def __init__(self, host, port, timeout, keepalive):
292 | self.host = host
293 | self.port = port
294 | self.timeout = timeout
295 | if keepalive:
296 | self.keepalive = 1
297 | else:
298 | self.keepalive = 0
299 | self.sock = None
300 | self.requests = dict()
301 |
302 | def __connect(self):
303 | self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
304 | self.sock.settimeout(self.timeout)
305 | self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
306 | # if self.keepalive:
307 | # self.sock.setsockopt(socket.SOL_SOCKET, socket.SOL_KEEPALIVE, 1)
308 | # else:
309 | # self.sock.setsockopt(socket.SOL_SOCKET, socket.SOL_KEEPALIVE, 0)
310 | try:
311 | self.sock.connect((self.host, int(self.port)))
312 | except socket.error as msg:
313 | self.sock.close()
314 | self.sock = None
315 | print(repr(msg))
316 | return False
317 | return True
318 |
319 | def __encodeFastCGIRecord(self, fcgi_type, content, requestid):
320 | length = len(content)
321 | buf = bchr(FastCGIClient.__FCGI_VERSION) \
322 | + bchr(fcgi_type) \
323 | + bchr((requestid >> 8) & 0xFF) \
324 | + bchr(requestid & 0xFF) \
325 | + bchr((length >> 8) & 0xFF) \
326 | + bchr(length & 0xFF) \
327 | + bchr(0) \
328 | + bchr(0) \
329 | + content
330 | return buf
331 |
332 | def __encodeNameValueParams(self, name, value):
333 | nLen = len(name)
334 | vLen = len(value)
335 | record = b''
336 | if nLen < 128:
337 | record += bchr(nLen)
338 | else:
339 | record += bchr((nLen >> 24) | 0x80) \
340 | + bchr((nLen >> 16) & 0xFF) \
341 | + bchr((nLen >> 8) & 0xFF) \
342 | + bchr(nLen & 0xFF)
343 | if vLen < 128:
344 | record += bchr(vLen)
345 | else:
346 | record += bchr((vLen >> 24) | 0x80) \
347 | + bchr((vLen >> 16) & 0xFF) \
348 | + bchr((vLen >> 8) & 0xFF) \
349 | + bchr(vLen & 0xFF)
350 | return record + name + value
351 |
352 | def __decodeFastCGIHeader(self, stream):
353 | header = dict()
354 | header['version'] = bord(stream[0])
355 | header['type'] = bord(stream[1])
356 | header['requestId'] = (bord(stream[2]) << 8) + bord(stream[3])
357 | header['contentLength'] = (bord(stream[4]) << 8) + bord(stream[5])
358 | header['paddingLength'] = bord(stream[6])
359 | header['reserved'] = bord(stream[7])
360 | return header
361 |
362 | def __decodeFastCGIRecord(self, buffer):
363 | header = buffer.read(int(self.__FCGI_HEADER_SIZE))
364 |
365 | if not header:
366 | return False
367 | else:
368 | record = self.__decodeFastCGIHeader(header)
369 | record['content'] = b''
370 |
371 | if 'contentLength' in record.keys():
372 | contentLength = int(record['contentLength'])
373 | record['content'] += buffer.read(contentLength)
374 | if 'paddingLength' in record.keys():
375 | skiped = buffer.read(int(record['paddingLength']))
376 | return record
377 |
378 | def request(self, nameValuePairs={}, post=''):
379 | if not self.__connect():
380 | print('connect failure! please check your fasctcgi-server !!')
381 | return
382 |
383 | requestId = random.randint(1, (1 << 16) - 1)
384 | self.requests[requestId] = dict()
385 | request = b""
386 | beginFCGIRecordContent = bchr(0) \
387 | + bchr(FastCGIClient.__FCGI_ROLE_RESPONDER) \
388 | + bchr(self.keepalive) \
389 | + bchr(0) * 5
390 | request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_BEGIN,
391 | beginFCGIRecordContent, requestId)
392 | paramsRecord = b''
393 | if nameValuePairs:
394 | for (name, value) in nameValuePairs.items():
395 | name = force_bytes(name)
396 | value = force_bytes(value)
397 | paramsRecord += self.__encodeNameValueParams(name, value)
398 |
399 | if paramsRecord:
400 | request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_PARAMS, paramsRecord, requestId)
401 | request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_PARAMS, b'', requestId)
402 |
403 | if post:
404 | request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_STDIN, force_bytes(post), requestId)
405 | request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_STDIN, b'', requestId)
406 |
407 | self.sock.send(request)
408 | self.requests[requestId]['state'] = FastCGIClient.FCGI_STATE_SEND
409 | self.requests[requestId]['response'] = b''
410 | return self.__waitForResponse(requestId)
411 |
412 | def __waitForResponse(self, requestId):
413 | data = b''
414 | while True:
415 | buf = self.sock.recv(512)
416 | if not len(buf):
417 | break
418 | data += buf
419 |
420 | data = BytesIO(data)
421 | while True:
422 | response = self.__decodeFastCGIRecord(data)
423 | if not response:
424 | break
425 | if response['type'] == FastCGIClient.__FCGI_TYPE_STDOUT \
426 | or response['type'] == FastCGIClient.__FCGI_TYPE_STDERR:
427 | if response['type'] == FastCGIClient.__FCGI_TYPE_STDERR:
428 | self.requests['state'] = FastCGIClient.FCGI_STATE_ERROR
429 | if requestId == int(response['requestId']):
430 | self.requests[requestId]['response'] += response['content']
431 | if response['type'] == FastCGIClient.FCGI_STATE_SUCCESS:
432 | self.requests[requestId]
433 | return self.requests[requestId]['response']
434 |
435 | def __repr__(self):
436 | return "fastcgi connect host:{} port:{}".format(self.host, self.port)
437 |
438 |
439 | def parse_args():
440 | parser = argparse.ArgumentParser(
441 | description='php_opcache_exporter args'
442 | )
443 | parser.add_argument(
444 | '-p', '--port',
445 | metavar='port',
446 | required=False,
447 | type=int,
448 | help='Listen to this port',
449 | default=int(os.environ.get('VIRTUAL_PORT', '9462'))
450 | )
451 | parser.add_argument(
452 | '--scrape_uri',
453 | help='URL for scraping, such as http://127.0.0.1/opcache-status.php',
454 | )
455 | parser.add_argument(
456 | '--fhost',
457 | help='Target FastCGI host, such as 127.0.0.1',
458 | default='127.0.0.1'
459 | )
460 | parser.add_argument(
461 | '--phpfile',
462 | metavar='phpfile',
463 | help='A php file absolute path, such as /usr/local/lib/php/System.php',
464 | default=''
465 | )
466 | parser.add_argument(
467 | '--phpcontent',
468 | metavar='phpcontent',
469 | help='http get params, such as name=john&address=beijing',
470 | default=''
471 | )
472 | parser.add_argument(
473 | '--phpcode',
474 | metavar='phpcode',
475 | help='code for execution over fastcgi client',
476 | default=''
477 | )
478 | parser.add_argument(
479 | '--fport',
480 | help='FastCGI port',
481 | default=9000,
482 | type=int
483 | )
484 |
485 | return parser.parse_args()
486 |
487 |
488 | def main():
489 | try:
490 | args = parse_args()
491 | port = int(args.port)
492 | REGISTRY.register(OpcacheCollector(args.scrape_uri, args.phpcode, args.phpcontent, args.fhost, args.fport))
493 | start_http_server(port)
494 | print("Polling... Serving at port: {}".format(args.port))
495 | while True:
496 | time.sleep(1)
497 | except KeyboardInterrupt:
498 | print(" Interrupted")
499 | exit(0)
500 |
501 | if __name__ == "__main__":
502 | main()
503 |
--------------------------------------------------------------------------------