mirror of
				https://github.com/xcat2/xcat-core.git
				synced 2025-10-31 11:22:27 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			355 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			355 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
| #!/usr/bin/env python
 | |
| # -*- encoding: utf-8 -*-
 | |
| from __future__ import print_function
 | |
| import argparse
 | |
| import json
 | |
| import os
 | |
| import sys
 | |
| import time
 | |
| 
 | |
| BUF_SIZE = 8192
 | |
| 
 | |
| 
 | |
| class Reader(object):
 | |
|     def __init__(self, file):
 | |
|         self.f_size = os.stat(file).st_size
 | |
|         self.f_pos = self.f_size
 | |
|         self.file = file
 | |
|         self.buf_list = []
 | |
|         self.remain = None
 | |
|         self.f = None
 | |
| 
 | |
|     def open(self):
 | |
|         if self.f is None:
 | |
|             self.f = open(self.file)
 | |
| 
 | |
|     def close(self):
 | |
|         if self.f is not None:
 | |
|             self.f.close()
 | |
|             self.f = None
 | |
| 
 | |
|     def __enter__(self):
 | |
|         self.open()
 | |
|         return self
 | |
| 
 | |
|     def __exit__(self, exc_type, exc_val, exc_tb):
 | |
|         self.close()
 | |
| 
 | |
|     def _read(self):
 | |
|         if self.f_pos == 0:
 | |
|             return None
 | |
| 
 | |
|         size = BUF_SIZE
 | |
|         if self.f_pos - BUF_SIZE >= 0:
 | |
|             self.f.seek(self.f_pos - BUF_SIZE)
 | |
|         else:
 | |
|             self.f.seek(0)
 | |
|             size = self.f_pos - 0
 | |
|         buf = self.f.read(size)
 | |
|         if self.remain is not None:
 | |
|             self.buf_list = (buf + self.remain).split('\n')
 | |
|         else:
 | |
|             self.buf_list = buf.split('\n')
 | |
|         if size == BUF_SIZE:
 | |
|             self.remain = self.buf_list[0]
 | |
|             self.buf_list = self.buf_list[1:]
 | |
|         else:
 | |
|             self.remain = None
 | |
|         self.f_pos -= size
 | |
| 
 | |
|         return self.buf_list
 | |
| 
 | |
|     def get_next(self):
 | |
|         ret = self._get_next()
 | |
|         if ret is not None:
 | |
|             return ret
 | |
| 
 | |
|         while self._read() is not None:
 | |
|             ret = self._get_next()
 | |
|             if ret is not None:
 | |
|                 return ret
 | |
| 
 | |
|         return None
 | |
| 
 | |
| 
 | |
| class CommandReader(Reader):
 | |
|     def __init__(self, file):
 | |
|         super(CommandReader, self).__init__(file)
 | |
|         self.cmd = {}
 | |
| 
 | |
|     def _get_next(self):
 | |
|         """ Extract data from the commands log file of xcat
 | |
|         Format::
 | |
| 
 | |
|             [Date]       2017-09-04 17:52:10
 | |
|             [ClientType] cli
 | |
|             [Request]    xdsh c910f04x35v07 'echo "nameserver 9.0.2.1" >> /etc/resolv.conf'
 | |
|             [Response]
 | |
|             [ElapsedTime] 0 s
 | |
|         :return: command dict if found, None if not found
 | |
|         """
 | |
|         for i, b in enumerate(self.buf_list[::-1]):
 | |
|             if b.find('[ElapsedTime]') == 0:
 | |
|                 self.cmd['elapsed'] = float(b.split(' ')[1])
 | |
|             elif b.find('[Request]') == 0:
 | |
|                 self.cmd['request'] = " ".join(
 | |
|                     b.rstrip().split(' ')[1:]).strip().replace(',', ' ')
 | |
|             elif b.find('[Date]') == 0:
 | |
|                 temp = b.split(' ')
 | |
|                 self.cmd['date'] = '%s %s' % (temp[-2], temp[-1])
 | |
| 
 | |
|                 if all(k in self.cmd for k in ('date', 'request', 'elapsed')):
 | |
|                     cmd = self.cmd
 | |
|                     self.cmd = {}
 | |
|                     self.buf_list = self.buf_list[0:len(self.buf_list) - i - 1]
 | |
|                     return cmd
 | |
|         return None
 | |
| 
 | |
| 
 | |
| class DBTraceReader(Reader):
 | |
|     def __init__(self, file):
 | |
|         super(DBTraceReader, self).__init__(file)
 | |
|         self.year = str(time.localtime().tm_year)
 | |
| 
 | |
|     def _get_next(self):
 | |
|         """Extract information from syslog file of xcat
 | |
|         Format::
 | |
| 
 | |
|             Sep  4 22:16:38 c910f04x12v02 xcat[6692]: [DB Trace]: {"msg":{"table":"nodegroup","method":"xCAT::Table::getAllEntries"},"type":"end_sql","elapsed":"0.00042s"}
 | |
| 
 | |
|         :return: A dict contains the time stamp and db dict if db trace log is
 | |
|                  found. None if nothing about db trace log found.
 | |
|         """
 | |
|         for i, b in enumerate(self.buf_list[::-1]):
 | |
|             pos = b.find('[DB Trace]:')
 | |
|             if pos == -1:
 | |
|                 continue
 | |
|             date = b[0:15]
 | |
|             conv = time.strptime('%s %s' % (self.year, date),
 | |
|                                  "%Y %b %d %H:%M:%S")
 | |
|             # TODO: the year value is not included in the current syslog
 | |
|             try:
 | |
|                 ret = {'stamp': int(time.mktime(conv)),
 | |
|                        'db': json.loads(b[pos + 12:])}
 | |
|             except ValueError:
 | |
|                 if b.find('message repeated') != -1:
 | |
|                     # syslog will add [ ] around the json message.
 | |
|                     ret = {'stamp': int(time.mktime(conv)),
 | |
|                            'db': json.loads(b[pos + 12:-1])}
 | |
|                 else:
 | |
|                     raise
 | |
| 
 | |
|             self.buf_list = self.buf_list[0:len(self.buf_list) - i - 1]
 | |
|             return ret
 | |
| 
 | |
|         return None
 | |
| 
 | |
| 
 | |
| class TimeStamp(object):
 | |
|     def __init__(self, kwargs):
 | |
|         if 'date' in kwargs:
 | |
|             a = time.strptime(kwargs['date'], "%Y-%m-%d %H:%M:%S")
 | |
|             self.start = int(time.mktime(a))
 | |
|         else:
 | |
|             raise
 | |
|         if 'elapsed' in kwargs:
 | |
|             self.end = self.start + int(kwargs['elapsed']) + 1
 | |
|         else:
 | |
|             raise
 | |
| 
 | |
| 
 | |
| class Analysis(object):
 | |
|     def __init__(self, command_log_file, cluster_log_file):
 | |
|         self.command_log_file = command_log_file
 | |
|         self.cluster_log_file = cluster_log_file
 | |
| 
 | |
|     def _stat(self, record, data):
 | |
|         elapsed = 0
 | |
|         if 'elapsed' in data:
 | |
|             # elapsed = float('%.3f' % float(data['elapsed'][:-1]))
 | |
|             elapsed = float(data['elapsed'][:-1])
 | |
|         if data['type'] == 'end_sql':
 | |
|             if data['msg']['method'].startswith('xCAT::Table::get'):
 | |
|                 record['get_sql_time'] += elapsed
 | |
|             elif data['msg']['method'].startswith('xCAT::Table::set'):
 | |
|                 record['set_sql_time'] += elapsed
 | |
|             elif data['msg']['method'].startswith('xCAT::Table::del'):
 | |
|                 record['del_sql_time'] += elapsed
 | |
|         elif data['type'] == 'end':
 | |
|             if data['msg']['method'].startswith('xCAT::Table::get'):
 | |
|                 record['get_sub_time'] += elapsed
 | |
|             elif data['msg']['method'].startswith('xCAT::Table::set'):
 | |
|                 record['set_sub_time'] += elapsed
 | |
|             elif data['msg']['method'].startswith('xCAT::Table::del'):
 | |
|                 record['del_sub_time'] += elapsed
 | |
|         elif data['type'] == 'start_sql':
 | |
|             if data['msg']['method'].startswith('xCAT::Table::get'):
 | |
|                 if data['msg']['table'] not in record['get_tables']:
 | |
|                     record['get_tables'][data['msg']['table']] = 1
 | |
|                 else:
 | |
|                     record['get_tables'][data['msg']['table']] += 1
 | |
|             elif data['msg']['method'].startswith('xCAT::Table::set'):
 | |
|                 if data['msg']['table'] not in record['set_tables']:
 | |
|                     record['set_tables'][data['msg']['table']] = 1
 | |
|                 else:
 | |
|                     record['set_tables'][data['msg']['table']] += 1
 | |
|             elif data['msg']['method'].startswith('xCAT::Table::del'):
 | |
|                 if data['msg']['table'] not in record['del_tables']:
 | |
|                     record['del_tables'][data['msg']['table']] = 1
 | |
|                 else:
 | |
|                     record['del_tables'][data['msg']['table']] += 1
 | |
|         elif data['type'] == 'build_cache':
 | |
|             if data['msg']['table'] not in record['build_cache']:
 | |
|                 record['build_cache'][data['msg']['table']] = 1
 | |
|             else:
 | |
|                 record['build_cache'][data['msg']['table']] += 1
 | |
|         elif data['type'] == 'cache_hit':
 | |
|             if data['msg']['table'] not in record['cache_hit']:
 | |
|                 record['cache_hit'][data['msg']['table']] = 1
 | |
|             else:
 | |
|                 record['cache_hit'][data['msg']['table']] += 1
 | |
| 
 | |
|     def process(self, latest, specific_cmd):
 | |
|         if specific_cmd is not None:
 | |
|             latest = None
 | |
| 
 | |
|         self._print_header()
 | |
|         with CommandReader(self.command_log_file) as cmd_reader:
 | |
|             cmd = cmd_reader.get_next()
 | |
|             trace = None
 | |
|             index = 0
 | |
|             db_reader = DBTraceReader(self.cluster_log_file)
 | |
|             db_reader.open()
 | |
|             while cmd is not None:
 | |
|                 if specific_cmd is not None and not cmd['request'].startswith(
 | |
|                         specific_cmd):
 | |
|                     cmd = cmd_reader.get_next()
 | |
|                     continue
 | |
| 
 | |
|                 stamp = TimeStamp(cmd)
 | |
|                 if trace is None:
 | |
|                     trace = db_reader.get_next()
 | |
| 
 | |
|                 while trace and trace['stamp'] > stamp.end:
 | |
|                     trace = db_reader.get_next()
 | |
|                 key = '%s(%s)' % (cmd['request'], cmd['date'])
 | |
| 
 | |
|                 record = {'command': key,
 | |
|                           'elapsed': cmd['elapsed'],
 | |
|                           'get_sql_time': float(0),
 | |
|                           'set_sql_time': float(0),
 | |
|                           'del_sql_time': float(0),
 | |
|                           'set_sub_time': float(0),
 | |
|                           'get_sub_time': float(0),
 | |
|                           'del_sub_time': float(0),
 | |
|                           'get_tables': {},
 | |
|                           'set_tables': {},
 | |
|                           'del_tables': {},
 | |
|                           'build_cache': {},
 | |
|                           'cache_hit': {}}
 | |
| 
 | |
|                 while trace and trace['stamp'] >= stamp.start:
 | |
|                     self._stat(record, trace['db'])
 | |
|                     trace = db_reader.get_next()
 | |
| 
 | |
|                 self._print_csv(record)
 | |
|                 index += 1
 | |
|                 if latest is not None and index == latest:
 | |
|                     break
 | |
|                 cmd = cmd_reader.get_next()
 | |
|             db_reader.close()
 | |
| 
 | |
|     def _print_header(self):
 | |
|         print("command,elapsed(precision: 1s),"
 | |
|               "get_sub_time(s),set_sub_time(s),del_sub_time(s),"
 | |
|               "get_sql_time(s),set_sql_time(s),del_sql_time(s),"
 | |
|               "build_cache(times),cache_hit(times),"
 | |
|               "get_tables(times),set_tables(times),del_tables(times)")
 | |
| 
 | |
|     def _print_csv(self, record):
 | |
|         print("%(command)s,%(elapsed)f,"
 | |
|               "%(get_sub_time)f,%(set_sub_time)f,%(del_sub_time)f,"
 | |
|               "%(get_sql_time)f,%(set_sql_time)f,%(del_sql_time)f,"
 | |
|               "%(build_cache)s,%(cache_hit)s,"
 | |
|               "%(get_tables)s,%(set_tables)s,%(del_tables)s\n" %
 | |
|               {'command': record['command'],
 | |
|                'elapsed': record['elapsed'],
 | |
|                'get_sub_time': float('%.3f' % record['get_sub_time']),
 | |
|                'set_sub_time': float('%.3f' % record['set_sub_time']),
 | |
|                'del_sub_time': float('%.3f' % record['del_sub_time']),
 | |
|                'get_sql_time': float('%.3f' % record['get_sql_time']),
 | |
|                'set_sql_time': float('%.3f' % record['set_sql_time']),
 | |
|                'del_sql_time': float('%.3f' % record['del_sql_time']),
 | |
|                'build_cache': self._format_dict(record['build_cache']),
 | |
|                'cache_hit': self._format_dict(record['cache_hit']),
 | |
|                'get_tables': self._format_dict(record['get_tables']),
 | |
|                'set_tables': self._format_dict(record['set_tables']),
 | |
|                'del_tables': self._format_dict(record['del_tables'])})
 | |
| 
 | |
|     def _format_list(self, a):
 | |
|         return " ".join(a)
 | |
| 
 | |
|     def _format_dict(self, d):
 | |
|         ret = []
 | |
|         for k, v in d.items():
 | |
|             if type(v) is list:
 | |
|                 ret.append('%s:[%s]', k, " ".join(v))
 | |
|             elif type(v) is str or type(v) is int:
 | |
|                 ret.append('%s:%s' % (k, str(v)))
 | |
|         return " ".join(ret)
 | |
| 
 | |
| 
 | |
| class StatsShell(object):
 | |
|     def get_base_parser(self):
 | |
|         parser = argparse.ArgumentParser(
 | |
|             prog='dbstats',
 | |
|             add_help=False,
 | |
|             formatter_class=HelpFormatter,
 | |
|         )
 | |
|         parser.add_argument('-h', '--help',
 | |
|                             action='store_true',
 | |
|                             help=argparse.SUPPRESS,
 | |
|                             )
 | |
|         parser.add_argument('--clusterlog',
 | |
|                             help="The syslog file of xcat.",
 | |
|                             default='/var/log/xcat/cluster.log',
 | |
|                             type=str)
 | |
|         parser.add_argument('--commandlog',
 | |
|                             help="The command log file of xcat.",
 | |
|                             default='/var/log/xcat/commands.log',
 | |
|                             type=str)
 | |
|         parser.add_argument('-n', '--num',
 | |
|                             help="The recent count of commands to analyze",
 | |
|                             type=int,
 | |
|                             default=10)
 | |
|         parser.add_argument('-c', '--command',
 | |
|                             help="The specified command to track",
 | |
|                             type=str,
 | |
|                             default=None)
 | |
|         return parser
 | |
| 
 | |
|     def do_help(self, args):
 | |
|         self.parser.print_help()
 | |
| 
 | |
|     def main(self, argv):
 | |
|         self.parser = self.get_base_parser()
 | |
|         (options, args) = self.parser.parse_known_args(argv)
 | |
| 
 | |
|         if options.help:
 | |
|             self.do_help(options)
 | |
|             return 0
 | |
| 
 | |
|         a = Analysis(options.commandlog, options.clusterlog)
 | |
|         a.process(options.num, options.command)
 | |
| 
 | |
| 
 | |
| class HelpFormatter(argparse.HelpFormatter):
 | |
|     def start_section(self, heading):
 | |
|         # Title-case the headings
 | |
|         heading = '%s%s' % (heading[0].upper(), heading[1:])
 | |
|         super(HelpFormatter, self).start_section(heading)
 | |
| 
 | |
| 
 | |
| if __name__ == "__main__":
 | |
|     StatsShell().main(sys.argv[1:])
 |