@cryptotaxi247 / netdata-1 / commits / 62bac35dd

web_log plugin: "user defined" requests/s chart added

Ilya committed May 18, 2017 at 15:03 UTC 62bac35ddc15b51f781d1836214086922e1e8641
1 file changed +65 -54
python.d/web_log.chart.py
+65 -54
@@ -1,20 +1,19 @@
1 # -*- coding: utf-8 -*-
2 # Description: web log netdata python.d module
3 # Author: l2isbad
4 -
5 -from base import LogService
4 import re
5 import bisect
6 from os import access, R_OK
7 from os.path import getsize
8 from collections import namedtuple
9 from copy import deepcopy
10 +from base import LogService
11
12 priority = 60000
13 retries = 60
14
16 -ORDER = ['response_statuses', 'response_codes', 'bandwidth', 'response_time', 'requests_per_url', 'http_method',
17 - 'http_version', 'requests_per_ipproto', 'clients', 'clients_all']
15 +ORDER = ['response_statuses', 'response_codes', 'bandwidth', 'response_time', 'requests_per_url',
16 + 'requests_per_user_defined', 'http_method', 'http_version', 'requests_per_ipproto', 'clients', 'clients_all']
17 CHARTS = {
18 'response_codes': {
19 'options': [None, 'Response Codes', 'requests/s', 'responses', 'web_log.response_codes', 'stacked'],
@@ -80,7 +79,7 @@ CHARTS = {
79 ]}
80 }
81
83 -NAMED_URL_PATTERN = namedtuple('URL_PATTERN', ['description', 'pattern'])
82 +NAMED_PATTERN = namedtuple('PATTERN', ['description', 'pattern'])
83
84 DET_RESP_AGGR = ['', '_1xx', '_2xx', '_3xx', '_4xx', '_5xx', '_Other']
85
@@ -99,6 +98,7 @@ class Service(LogService):
98 self.log_type = self.configuration.get('type', 'web_access')
99 self.log_path = self.configuration.get('path')
100 self.url_pattern = self.configuration.get('categories') # dict
101 + self.user_pattern = self.configuration.get('user_defined') # dict
102 self.custom_log_format = self.configuration.get('custom_log_format') # dict
103 # Instance variables
104 self.regex = None # will be assigned in 'find_regex' or 'find_regex_custom' method
@@ -164,9 +164,8 @@ class Service(LogService):
164 self.error(str(error))
165 return False
166
167 - # self.url_pattern check
168 - if self.url_pattern:
169 - self.url_pattern = check_req_per_url_pattern('rpu', self.url_pattern)
167 + self.url_pattern = check_patterns('url_pattern', self.url_pattern)
168 + self.user_pattern = check_patterns('user_pattern', self.user_pattern)
169
170 self.create_access_charts(match_dict) # Create charts
171 self._get_data = self._get_access_data # _get_data assignment
@@ -174,10 +173,6 @@ class Service(LogService):
173 self.error('Not implemented')
174 return False
175
177 - # Double check
178 - if not self.regex:
179 - self.error('That can not happen, but it happened. "regex" is None')
180 -
176 self.info('Collected data: %s' % list(match_dict.keys()))
177 return True
178
@@ -247,7 +242,7 @@ class Service(LogService):
242 mandatory_values = set(mandatory_dict) - set(match_dict)
243 if mandatory_values:
244 return find_regex_return(msg='Custom log: search OK but some mandatory keys (%s) are missing'
250 - % list(mandatory_values))
245 + % list(mandatory_values))
246 else:
247 for key in mandatory_dict:
248 if not re.search(mandatory_dict[key], match_dict[key]):
@@ -339,11 +334,11 @@ class Service(LogService):
334 regex_function = zip(r_regex, r_function)
335
336 match_dict = dict()
342 - for regex, function in regex_function:
337 + for regex, func in regex_function:
338 match = regex.search(last_line)
339 if match:
340 self.regex = regex
346 - self.resp_time_func = function
341 + self.resp_time_func = func
342 match_dict = match.groupdict()
343 break
344
@@ -385,10 +380,8 @@ class Service(LogService):
380 ' "" "Requests Per HTTP Version" requests/s "http versions"' \
381 ' web_log.http_version stacked 12 %s\n' % (job_name, self.update_every)
382
388 - # Remove 'request_time' chart from ORDER if resp_time not in match_dict
383 if 'resp_time' not in match_dict:
384 self.order.remove('response_time')
391 - # Remove 'clients_all' chart from ORDER if specified in the configuration
385 if not self.all_time:
386 self.order.remove('clients_all')
387 # Add 'detailed_response_codes' chart if specified in the configuration
@@ -417,15 +410,31 @@ class Service(LogService):
410 if self.url_pattern:
411 self.definitions['requests_per_url'] = {'options': [None, 'Requests Per Url', 'requests/s',
412 'urls', 'web_log.requests_per_url', 'stacked'],
420 - 'lines': [['rpu_other', 'other', 'incremental']]}
413 + 'lines': [['url_pattern_other', 'other', 'incremental']]}
414 for elem in self.url_pattern:
422 - self.definitions['requests_per_url']['lines'].append([elem.description, elem.description[4:],
415 + self.definitions['requests_per_url']['lines'].append([elem.description,
416 + elem.description[12:],
417 'incremental'])
418 self.data.update({elem.description: 0})
425 - self.data.update({'rpu_other': 0})
419 + self.data.update({'url_pattern_other': 0})
420 else:
421 self.order.remove('requests_per_url')
422
423 + if self.user_pattern and 'user_defined' in match_dict:
424 + self.definitions['requests_per_user_defined'] = {'options': [None, 'Requests Per User Defined Pattern',
425 + 'requests/s', 'user defined',
426 + 'web_log.requests_per_user_defined',
427 + 'stacked'],
428 + 'lines': [['user_pattern_other', 'other', 'incremental']]}
429 + for elem in self.user_pattern:
430 + self.definitions['requests_per_user_defined']['lines'].append([elem.description,
431 + elem.description[13:],
432 + 'incremental'])
433 + self.data.update({elem.description: 0})
434 + self.data.update({'user_pattern_other': 0})
435 + else:
436 + self.order.remove('requests_per_user_defined')
437 +
438 def add_new_dimension(self, dimension, line_list, chart_string, key):
439 """
440 :param dimension: str: response status code. Ex.: '202', '499'
@@ -469,17 +478,22 @@ class Service(LogService):
478 self.data['0xx'] += 1
479 # detailed response code
480 if self.detailed_response_codes:
472 - self._get_data_detailed_response_codes(match_dict['code'])
481 + self._get_data_detailed_response_codes(code=match_dict['code'])
482 # response statuses
474 - self._get_data_statuses(match_dict['code'])
483 + self._get_data_statuses(code=match_dict['code'])
484 # requests per url
485 if self.url_pattern:
477 - self._get_data_per_url(match_dict['url'])
486 + self._get_data_per_pattern(field=match_dict['url'], other='url_pattern_other',
487 + pattern=self.url_pattern)
488 + # requests per user defined pattern
489 + if self.user_pattern and 'user_defined' in match_dict:
490 + self._get_data_per_pattern(field=match_dict['user_defined'], other='user_pattern_other',
491 + pattern=self.user_pattern)
492 # requests per http method
479 - self._get_data_http_method(match_dict['method'])
493 + self._get_data_http_method(method=match_dict['method'])
494 # requests per http version
495 if 'http_version' in match_dict:
482 - self._get_data_http_version(match_dict['http_version'])
496 + self._get_data_http_version(http_version=match_dict['http_version'])
497 # bandwidth sent
498 bytes_sent = match_dict['bytes_sent'] if '-' not in match_dict['bytes_sent'] else 0
499 self.data['bytes_sent'] += int(bytes_sent)
@@ -495,10 +509,11 @@ class Service(LogService):
509 proto = 'ipv4' if '.' in match_dict['address'] else 'ipv6'
510 self.data['req_' + proto] += 1
511 # unique clients ips
498 - if address_not_in_pool(self.unique_all_time, match_dict['address'],
499 - self.data['unique_tot_ipv4'] + self.data['unique_tot_ipv6']):
512 + if address_not_in_pool(pool=self.unique_all_time, address=match_dict['address'],
513 + pool_size=self.data['unique_tot_ipv4'] + self.data['unique_tot_ipv6']):
514 self.data['unique_tot_' + proto] += 1
501 - if address_not_in_pool(unique_current, match_dict['address'], ip_address_counter['unique_cur_ip']):
515 + if address_not_in_pool(pool=unique_current, address=match_dict['address'],
516 + pool_size=ip_address_counter['unique_cur_ip']):
517 self.data['unique_cur_' + proto] += 1
518 ip_address_counter['unique_cur_ip'] += 1
519 else:
@@ -506,9 +521,9 @@ class Service(LogService):
521
522 # timings
523 if request_time:
509 - self.data['resp_time_min'] += int(request_time[0])
510 - self.data['resp_time_avg'] += int(round(float(request_counter['sum']) / request_counter['count']))
511 - self.data['resp_time_max'] += int(request_time[-1])
524 + self.data['resp_time_min'] += request_time[0]
525 + self.data['resp_time_avg'] += round(float(request_counter['sum']) / request_counter['count'])
526 + self.data['resp_time_max'] += request_time[-1]
527 return self.data
528
529 def _get_data_detailed_response_codes(self, code):
@@ -556,21 +571,22 @@ class Service(LogService):
571 chart_string_copy, 'http_version')
572 self.data[http_version_dim_id] += 1
573
559 - def _get_data_per_url(self, url):
574 + def _get_data_per_pattern(self, field, other, pattern):
575 """
561 - :param url: str: URL from parsed line
576 + :param field: str:
577 + :param other: str:
578 :return:
579 Scan through string looking for the first location where patterns produce a match for all user
580 defined patterns
581 """
582 match = None
567 - for elem in self.url_pattern:
568 - if elem.pattern.search(url):
583 + for elem in pattern:
584 + if elem.pattern.search(field):
585 self.data[elem.description] += 1
586 match = True
587 break
588 if not match:
573 - self.data['rpu_other'] += 1
589 + self.data[other] += 1
590
591 def _get_data_statuses(self, code):
592 """
@@ -601,12 +617,10 @@ def address_not_in_pool(pool, address, pool_size):
617 if index < pool_size:
618 if pool[index] == address:
619 return False
604 - else:
605 - bisect.insort_left(pool, address)
606 - return True
607 - else:
620 bisect.insort_left(pool, address)
621 return True
622 + bisect.insort_left(pool, address)
623 + return True
624
625
626 def find_regex_return(match_dict=None, msg='Generic error message'):
@@ -618,36 +632,33 @@ def find_regex_return(match_dict=None, msg='Generic error message'):
632 return match_dict, msg
633
634
621 -def check_req_per_url_pattern(string, url_pattern):
635 +def check_patterns(string, dimension_regex_dict):
636 """
637 :param string: str:
624 - :param url_pattern: dict: ex. {'dim1': 'pattern1>', 'dim2': '<pattern2>'}
638 + :param dimension_regex_dict: dict: ex. {'dim1': 'pattern1>', 'dim2': '<pattern2>'}
639 :return: list of named tuples or None:
640 We need to make sure all patterns are valid regular expressions
641 """
628 - if not hasattr(url_pattern, 'keys'):
642 + if not hasattr(dimension_regex_dict, 'keys'):
643 return None
644
645 result = list()
646
633 - def is_valid_pattern(pattern):
647 + def valid_pattern(pattern):
648 """
649 :param pattern: str
650 :return: re.compile(pattern) or None
651 """
652 if not isinstance(pattern, str):
653 return False
640 - else:
641 - try:
642 - compile_pattern = re.compile(pattern)
643 - except re.error:
644 - return False
645 - else:
646 - return compile_pattern
654 + try:
655 + return re.compile(pattern)
656 + except re.error:
657 + return False
658
648 - for dimension, regex in url_pattern.items():
649 - valid_pattern = is_valid_pattern(regex)
659 + for dimension, regex in dimension_regex_dict.items():
660 + valid = valid_pattern(regex)
661 if isinstance(dimension, str) and valid_pattern:
651 - result.append(NAMED_URL_PATTERN(description='_'.join([string, dimension]), pattern=valid_pattern))
662 + result.append(NAMED_PATTERN(description='_'.join([string, dimension]), pattern=valid))
663
664 return result or None