PageRenderTime 27ms CodeModel.GetById 1ms RepoModel.GetById 0ms app.codeStats 0ms

/mysql_analyzer.py

https://github.com/m00dawg/Dolphin-Hunter
Python | 470 lines | 452 code | 10 blank | 8 comment | 7 complexity | f46eef6a50aa536441b972747c83c19d MD5 | raw file
  1. #!/usr/bin/env python
  2. """
  3. Dolphin Hunter
  4. MySQL Runtime Analyzer
  5. Author: Tim "Sweetums" Soderstrom
  6. With contributions from BJ Dierkes and Andrew Garner
  7. """
  8. import sys
  9. if sys.version_info < (2, 4):
  10. print "Python 2.4 or later required. Sorry"
  11. sys.exit(1)
  12. import platform
  13. import os
  14. import string
  15. from optparse import OptionParser, OptionGroup
  16. import warnings
  17. warnings.simplefilter("ignore")
  18. import MySQLdb
  19. # Local Imports
  20. from mysqlinfo import MySQL, MySQLError
  21. from functions import format_interval, format_bytes, format_percent
  22. from functions import print_header, print_stat
  23. from functions import AttributeAdapter
  24. from colorize import color_print
  25. ##################
  26. # Global Variables
  27. ##################
  28. version = '0.7.3'
  29. # How many items to list from list output
  30. # (Such as information_schema reuslts)
  31. limit = 10
  32. # Constant for 2 days
  33. TWO_DAYS_IN_SECONDS = 60 * 60 * 24 * 2
  34. ####################
  35. # Display Functions
  36. ###################
  37. def display_system_info(mysql):
  38. system_info = dict()
  39. print "Gathering system information...",
  40. system_info['architecture'] = platform.machine()
  41. if platform.system() == 'Linux':
  42. system_info['totalMemory'] = format_bytes(round(os.sysconf('SC_PHYS_PAGES') * \
  43. os.sysconf('SC_PAGE_SIZE')), 0)
  44. system_info['freeMemory'] = format_bytes(os.sysconf('SC_AVPHYS_PAGES') * \
  45. os.sysconf('SC_PAGE_SIZE'))
  46. system_info['cpuCores'] = os.sysconf('SC_NPROCESSORS_CONF')
  47. else:
  48. system_info['totalMemory'] = 'Unknown'
  49. system_info['freeMemory'] = 'Unknown'
  50. system_info['cpuCores'] = 'Unknown'
  51. print "done!\n"
  52. print_header('Local System Information', 1)
  53. print_stat('CPU Cores', system_info['cpuCores'], 1)
  54. print_stat('Total Memory', system_info['totalMemory'], 1)
  55. print_stat('System Architecture', system_info['architecture'], 1)
  56. def display_mysql_global_results(mysql_info):
  57. print_header('Global Information', 2)
  58. print_stat('Server ID', int(mysql_info.vars.server_id))
  59. print_stat('MySQL Architecture',
  60. mysql_info.vars.version_compile_machine, 1)
  61. print_stat('MySQL Version', mysql_info.vars.version, 1)
  62. print_stat('Data Directory', mysql_info.vars.datadir)
  63. print_stat("Uptime", format_interval(mysql_info.status.uptime))
  64. print_stat('Max Allowed Packet',
  65. format_bytes(mysql_info.vars.max_allowed_packet))
  66. print_stat("Connections", "%s of %s" % \
  67. (int(mysql_info.status.max_used_connections),
  68. int(mysql_info.vars.max_connections)))
  69. print_stat("Disk Based Temp Tables", "%s of %s (%s)" % \
  70. (int(mysql_info.status.created_tmp_disk_tables),
  71. int(mysql_info.status.created_tmp_tables),
  72. format_percent(mysql_info.tmp_tables_disk_pct)))
  73. print_stat("Sort Merge Passes",
  74. int(mysql_info.status.sort_merge_passes))
  75. print_stat("Non-Indexed Joins",
  76. int(mysql_info.status.select_full_join))
  77. print_stat("Open Files", '%s (limit %s)' % \
  78. (int(mysql_info.status.open_files),
  79. int(mysql_info.vars.open_files_limit)))
  80. print_stat("Open Tables",
  81. int(mysql_info.status.open_tables))
  82. if mysql_info.vars.query_cache_size == 0:
  83. print_stat("Query Cache", "Disabled")
  84. else:
  85. print_stat("Query Cache"),
  86. print_stat("Size",
  87. format_bytes(mysql_info.vars.query_cache_size), 2)
  88. print_stat("Hit Rate",
  89. format_percent(mysql_info.query_cache_hitrate), 2)
  90. print_stat("Table Lock Waits", "%s of %s (%s)" % \
  91. (int(mysql_info.status.table_locks_waited),
  92. int(mysql_info.table_locks_total),
  93. format_percent(mysql_info.table_lock_wait_pct)))
  94. print_stat("Estimated Table Scans",
  95. format_percent(mysql_info.table_scans_pct))
  96. print_stat("Slow Queries")
  97. print_stat("Queries", "%s of %s (%s)" % \
  98. (int(mysql_info.status.slow_queries),
  99. int(mysql_info.status.com_select),
  100. format_percent(mysql_info.slow_query_pct)), 2)
  101. print_stat("Long Query Time",
  102. format_interval(mysql_info.vars.long_query_time), 2)
  103. print_stat("Log Non-Indexed Queries",
  104. mysql_info.vars.log_queries_not_using_indexes, 2)
  105. print_stat('Binary Log', '')
  106. print_stat('Binary Logging',
  107. mysql_info.vars.log_bin, 2)
  108. try:
  109. print_stat('Binlog Format',
  110. mysql_info.vars.binlog_format, 2)
  111. except KeyError:
  112. print_stat('Binlog Format', 'Not-Detected / Pre 5.1', 2)
  113. print_stat("Read Frequency", format_percent(mysql_info.read_pct))
  114. def display_mysql_myisam_results(mysql_info):
  115. print_header("MyISAM", 2)
  116. print_stat("Key Buffer")
  117. print_stat("Size", format_bytes(mysql_info.vars.key_buffer_size), 2)
  118. print_stat("Used", "%s (%s)" % \
  119. (format_bytes(mysql_info.key_buffer_used),
  120. format_percent(mysql_info.key_buffer_used_pct)), 2)
  121. def display_mysql_innodb_results(mysql_info):
  122. print_header("InnoDB", 2)
  123. if mysql_info.vars.innodb_version == ('DISABLED' or False):
  124. print "Disabled"
  125. else:
  126. try:
  127. print_stat('Version', mysql_info.vars.innodb_version)
  128. except KeyError:
  129. print_stat('Version', 'Default')
  130. print_stat('Paths','')
  131. print_stat('InnoDB Home Directory',
  132. mysql_info.vars.innodb_data_home_dir, 2)
  133. print_stat("InnoDB Log Directory",
  134. mysql_info.vars.innodb_log_group_home_dir, 2)
  135. print_stat("InnoDB Data File Path",
  136. mysql_info.vars.innodb_data_file_path, 2)
  137. print_stat('Buffer Pool', '')
  138. print_stat("Usage", "%s of %s (%s)" % \
  139. (format_bytes(mysql_info.innodb_buffer_pool_used),
  140. format_bytes(mysql_info.vars.innodb_buffer_pool_size),
  141. format_percent(mysql_info.innodb_buffer_pool_used_pct)), 2)
  142. print_stat("Hit Rate",
  143. format_percent(mysql_info.innodb_buffer_pool_hit_rate), 2)
  144. print_stat('History List', mysql_info.ibstatus.history_list_length)
  145. print_stat("File Per Table",
  146. mysql_info.vars.innodb_file_per_table)
  147. if mysql_info.vars.innodb_file_per_table:
  148. print_stat("InnoDB Open Files",
  149. int(mysql_info.vars.innodb_open_files), 2)
  150. print_stat("Flush Log At Commit",
  151. int(mysql_info.vars.innodb_flush_log_at_trx_commit))
  152. print_stat("Flush Method",
  153. mysql_info.innodb_flush_method)
  154. print_stat("Thread Concurrency",
  155. int(mysql_info.vars.innodb_thread_concurrency))
  156. print_stat("Log File Size", "%s x %s logs (%s total)" % \
  157. (format_bytes(mysql_info.vars.innodb_log_file_size),
  158. int(mysql_info.vars.innodb_log_files_in_group),
  159. format_bytes(mysql_info.innodb_log_file_size_total)))
  160. def display_mysql_thread_results(mysql_info):
  161. print_header("Threads", 2)
  162. print_stat("Buffers")
  163. print ' %-9s : %-9s : %-9s : %-9s' % \
  164. ('Read', 'Read RND', 'Sort', 'Join')
  165. print ' %-9s : %-9s : %-9s : %-9s' % \
  166. (format_bytes(mysql_info.vars.read_buffer_size),
  167. format_bytes(mysql_info.vars.read_rnd_buffer_size),
  168. format_bytes(mysql_info.vars.sort_buffer_size),
  169. format_bytes(mysql_info.vars.join_buffer_size))
  170. print_stat("Threads")
  171. print ' %-9s : %-9s : %-9s : %-9s' % \
  172. ('Size', 'Cached', 'Running', 'Created')
  173. print ' %-9s : %-9s : %-9s : %-9s' % \
  174. (int(mysql_info.vars.thread_cache_size),
  175. int(mysql_info.status.threads_cached),
  176. int(mysql_info.status.threads_running),
  177. int(mysql_info.status.threads_created))
  178. def display_slave_info(mysql_info):
  179. print_header('Replication', 2)
  180. if mysql_info.slave_status is None:
  181. print "Not Enabled"
  182. return
  183. print_stat('Master',
  184. mysql_info.slave_status.master_host)
  185. print_stat('Logs', '')
  186. print_stat('Spooled Master Log File',
  187. '%s (pos: %s)' % \
  188. (mysql_info.slave_status.master_log_file,
  189. mysql_info.slave_status.read_master_log_pos), 2)
  190. print_stat('Executed Master Log File',
  191. '%s (pos: %s)' % \
  192. (mysql_info.slave_status.relay_master_log_file,
  193. mysql_info.slave_status.exec_master_log_pos), 2)
  194. print_stat('Relay Log File',
  195. '%s (pos: %s)' % \
  196. (mysql_info.slave_status.relay_log_file,
  197. mysql_info.slave_status.relay_log_pos), 2)
  198. # Using a long-style if for Python 2.4 compatibility
  199. #print_stat('Relay Log Space Limit',
  200. #(format_bytes(mysql_info.vars.relay_log_space_limit))
  201. # if mysql_info.vars.relay_log_space_limit != 0 else 'Unlimited')
  202. if mysql_info.vars.relay_log_space_limit != 0:
  203. print_stat('Relay Log Space Limit',
  204. format_bytes(mysql_info.vars.relay_log_space_limit))
  205. else:
  206. print_stat('Relay Log Space Limit', 'Unlimited')
  207. print_stat('IO Thread Running',
  208. mysql_info.slave_status.slave_io_running)
  209. print_stat('SQL Thread Running',
  210. mysql_info.slave_status.slave_sql_running)
  211. print_stat('Seconds Behind Master',
  212. mysql_info.slave_status.seconds_behind_master)
  213. print_stat('Last Error',
  214. mysql_info.slave_status.last_error)
  215. def display_mysql_results(mysql):
  216. print ""
  217. mysql_info = mysql.mysql_info
  218. print ""
  219. print_header('MySQL Information', 1)
  220. print ""
  221. display_mysql_global_results(mysql_info)
  222. print ""
  223. display_mysql_thread_results(mysql_info)
  224. print ""
  225. display_mysql_myisam_results(mysql_info)
  226. print ""
  227. display_mysql_innodb_results(mysql_info)
  228. print ""
  229. display_slave_info(mysql_info)
  230. def display_innodb_transactions(mysql):
  231. print ""
  232. print_header("InnoDB Transactions")
  233. try:
  234. for i, txn in enumerate(mysql.mysql_info.ibstatus.transactions):
  235. print "TRANSACTION(%d)" % i
  236. print txn
  237. except ValueError:
  238. print "Unable To Parse SHOW ENGINE INNODB STATUS"
  239. def display_schema_info(mysql):
  240. print ""
  241. print_header("Schema Information")
  242. print ""
  243. print_header("Engine Breakdown", 2)
  244. print '%-8s : %8s : %12s : %12s' % \
  245. ('Engine', '# Tables', 'Data Length', 'Index Length')
  246. for row in mysql.schema_engine_summary:
  247. print '%-8s : %8s : %12s : %12s' % \
  248. (row['Engine'],
  249. row['Count'],
  250. format_bytes(row['Data Length']),
  251. format_bytes(row['Index Length']))
  252. print ""
  253. print_header('%s Largest Databases' % limit, 2)
  254. print '%-32s : %12s : %12s' % \
  255. ('Database', 'Data Length', 'Index Length')
  256. for row in mysql.schema_largest_dbs(limit):
  257. print '%-32s : %12s : %12s' % \
  258. (row['Database'],
  259. format_bytes(row['Data Length']),
  260. format_bytes(row['Index Length']))
  261. print ""
  262. print_header('%s Largest Tables' % limit, 2)
  263. print '%-32s : %12s : %12s' % \
  264. ('Table', 'Data Length', 'Index Length')
  265. for row in mysql.schema_largest_tables(limit):
  266. print '%-32s : %12s : %12s' % \
  267. (row['Table'],
  268. format_bytes(row['Data Length']),
  269. format_bytes(row['Index Length']))
  270. def check_health(mysql):
  271. global opts
  272. if opts.max_replication_delay:
  273. max_replication_delay = opts.max_replication_delay
  274. else:
  275. max_replication_delay = 300
  276. errors = ""
  277. print_header("Health Checks")
  278. mysql_info = mysql.mysql_info
  279. print ""
  280. if mysql_info.queries['long_running_queries'] > 0:
  281. errors += "One or more long running queries detected\n"
  282. if mysql_info.slave_status:
  283. if mysql_info.slave_status.slave_io_running != 'Yes':
  284. errors += "Slave IO Thread Not Running\n"
  285. if mysql_info.slave_status.slave_sql_running != 'Yes':
  286. errors += "Slave SQL Thread Not Running\n"
  287. if mysql_info.slave_status.seconds_behind_master > max_replication_delay:
  288. errors += "Slave Lagging Too Far Behind\n"
  289. if mysql_info.slave_status.last_error:
  290. errors += "Slave Error Reported\n"
  291. # If wsrep variable does not exist, we can assume it is not
  292. # a Galera server, so we skip the checks
  293. try:
  294. if mysql_info.vars.wsrep_provider:
  295. if mysql_info.status.wsrep_cluster_size < 3:
  296. errors += "Galera Node Missing\n"
  297. if not mysql_info.status.wsrep_ready:
  298. errors += "Galera Cluster Not Ready\n"
  299. except:
  300. pass
  301. # Same thing as above - if, somehow InnoDB is not enabled
  302. # we don't need to check parameters for it. There could
  303. # be cases where you'd want to error if InnoDB is unavailable
  304. # but using 'innodb = FORCE' within MySQL is a better solution.
  305. try:
  306. if mysql_info.vars.innodb_version:
  307. if mysql_info.innodb_buffer_pool_hit_rate < 95:
  308. errors += "InnoDB Buffer Pool Hit Rate Under 95%\n"
  309. if (mysql_info.status.threads_connected /
  310. mysql_info.vars.max_connections * 100) > 75:
  311. errors += "Open Connections Above 75% of Max\n"
  312. except:
  313. pass
  314. if errors != "":
  315. print errors
  316. sys.exit(1)
  317. else:
  318. print "Everything is all good!"
  319. sys.exit(0)
  320. #################
  321. # Meat & Potatoes
  322. #################
  323. # Preamble
  324. print("""
  325. |
  326. |
  327. |
  328. | __
  329. | _.-~ )
  330. _..-|~~~~,' ,-/ _
  331. .-'. . | .' ,-',' ,' )
  332. ,'. . . _| ,--~,-'__..-' ,'
  333. ,'. . . (@|' ---~~~~ ,'
  334. ------------------------+------------------------
  335. /. . . . . | ,-'
  336. ; . . . . - .| ,'
  337. : . . . . |_ /
  338. . . . . . | `-.:
  339. . . . ./ - . | )
  340. . . . | _____..-|-.._/ _____
  341. ~---~~~~----~~~~ | ~~
  342. |
  343. |
  344. Dolphin Hunter v%s
  345. MySQL Runtime Analyzer
  346. Author: Tim "Sweetums" Soderstrom
  347. With contributions from BJ Dierkes and Andrew Garner
  348. """ % version)
  349. # Callback for OptionParser when -a is used
  350. def set_all(option, opt, value, parser):
  351. parser.values.actions.extend([
  352. (10, display_system_info),
  353. (20, display_mysql_results),
  354. (30, display_innodb_transactions),
  355. (40, display_schema_info),
  356. ])
  357. # For Python 2.4 compatibility
  358. def append_const_callback(priority, const):
  359. def callback( option, opt_str, value, parser ):
  360. getattr(parser.values, option.dest).append((priority, const))
  361. return callback
  362. # Main
  363. def main():
  364. global opts
  365. # The numbers before the function in const denote priority to make sure
  366. # the output is always in the same order. It also helps avoid priting
  367. # information more than once.
  368. parser = OptionParser()
  369. parser.add_option('-a', '--all', action='callback', type=None,
  370. callback=set_all,
  371. help="Gather all possible information")
  372. parser.add_option('-y', '--system', action='callback',
  373. dest="actions", callback=append_const_callback(10,display_system_info),
  374. help="Print System Information")
  375. parser.add_option('-i', '--info', action='callback',
  376. dest="actions", callback=append_const_callback(20,display_mysql_results),
  377. help="MySQL Information")
  378. parser.add_option('-t', '--transactions', action='callback',
  379. dest="actions", callback=append_const_callback(30, display_innodb_transactions),
  380. help='Display InnoDB transactions')
  381. parser.add_option('-s', '--schema', action='callback',
  382. dest="actions", callback=append_const_callback(40, display_schema_info),
  383. help="Print Schema Statistics (Avoid For Large #'s of Tables/DBs)")
  384. parser.add_option('-m', '--monitor', action='callback',
  385. dest="actions", callback=append_const_callback(50, check_health),
  386. help="Look at various metrics in MySQL and bomb if there is a problem. Useful for things like Monit")
  387. mysql_login_group = OptionGroup(parser, "MySQL Login Options")
  388. mysql_login_group.add_option('-u', '--username', dest="user",
  389. help="MySQL User")
  390. mysql_login_group.add_option('-p', '--password', dest="passwd",
  391. help="MySQL Password")
  392. mysql_login_group.add_option('-H', '--hostname', dest="host",
  393. help="MySQL host to connect to")
  394. mysql_login_group.add_option('-P', '--port', dest='port',
  395. type='int', help="MySQL port to connect to")
  396. mysql_login_group.add_option('-S', '--socket', dest="unix_socket",
  397. help="Path to MySQL unix socket")
  398. parser.add_option_group(mysql_login_group)
  399. monitor_group = OptionGroup(parser, "Monitor Options")
  400. monitor_group.add_option('-d', '--delay', dest="max_replication_delay",
  401. type="int",
  402. help="Max replication delay allowed before Dolphin Hunter complains")
  403. parser.add_option_group(monitor_group)
  404. parser.set_defaults(actions=[])
  405. opts, args = parser.parse_args()
  406. # Pull out MySQL login information passed from command-line
  407. connection_args = {}
  408. for key in ('user', 'passwd', 'host', 'port', 'unix_socket'):
  409. value = getattr(opts, key)
  410. if value is not None:
  411. connection_args[key] = value
  412. # Display help by default
  413. if not opts.actions:
  414. parser.print_help()
  415. return 0
  416. # Iterate through actions and call functions magically
  417. mysql = MySQL(read_default_group='client', charset='utf8', **connection_args)
  418. for priority, action in sorted(set(opts.actions)):
  419. action(mysql)
  420. return 0
  421. if __name__ == "__main__":
  422. sys.exit(main())