PageRenderTime 20ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/mysql_watcher/mysql_watcher

https://bitbucket.org/lindenlab/apiary/
Python | 188 lines | 113 code | 19 blank | 56 comment | 20 complexity | 85fa8b44e4521e8ff388952d56c4a56d MD5 | raw file
  1. #!/usr/bin/env python
  2. #
  3. # $LicenseInfo:firstyear=2010&license=mit$
  4. #
  5. # Copyright (c) 2010, Linden Research, Inc.
  6. #
  7. # Permission is hereby granted, free of charge, to any person obtaining a copy
  8. # of this software and associated documentation files (the "Software"), to deal
  9. # in the Software without restriction, including without limitation the rights
  10. # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  11. # copies of the Software, and to permit persons to whom the Software is
  12. # furnished to do so, subject to the following conditions:
  13. #
  14. # The above copyright notice and this permission notice shall be included in
  15. # all copies or substantial portions of the Software.
  16. #
  17. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18. # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  19. # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  20. # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  21. # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  22. # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  23. # THE SOFTWARE.
  24. # $/LicenseInfo$
  25. #
  26. """
  27. Provides real-time query profiling of mysql by using tcpdump and netcat.
  28. You need to have the root ssh key to use it.
  29. """
  30. try:
  31. # To improve performance on older Pythons that support psyco
  32. import psyco
  33. psyco.full()
  34. except:
  35. pass
  36. import curses
  37. import curses.wrapper
  38. import getopt
  39. import os
  40. import re
  41. import socket
  42. import sys
  43. import time
  44. from dblibs.dbutil import LLQueryStream, LLQueryStatMap, LLBinnedQueryStats, remote_mysql_stream
  45. from dblibs.dbbrowser import *
  46. DUMP_INTERVAL = 5.0
  47. def process_query_stream(stdscr, query_stream, query_metadata, host):
  48. "Processes mysql query events coming from the stream"
  49. #
  50. # Options/parameters
  51. #
  52. output_path = "./%s" % host
  53. # Make output path
  54. os.system("mkdir -p %s" % output_path)
  55. # Curses configuration stuff
  56. size = stdscr.getmaxyx()
  57. stdscr.nodelay(True) # Non-blocking keyboard input
  58. sort_by = "total_time"
  59. count = 0
  60. cur_time = 0
  61. last_time = time.time()
  62. last_count = 0
  63. total_stats = LLQueryStatMap("Total", time.time())
  64. cur_stats = LLQueryStatMap("5 seconds", time.time())
  65. prev_stats = LLQueryStatMap("5 seconds", time.time())
  66. display_stats = prev_stats
  67. # Initialize the curses query data browser
  68. query_browser = LLQueryBrowser(stdscr, cur_stats, query_metadata)
  69. last_hour = 0
  70. done = False
  71. try:
  72. while not done:
  73. # The real work happens inside here - this is where we get the next event coming off of the mysql stream
  74. (event_type, query) = query_stream.getNextEvent()
  75. # Use the start time to determine which hour bin to put the query into
  76. start_time = query.mStartTime
  77. start_hour = time.localtime(start_time)[3]
  78. if event_type == "QueryStart":
  79. total_stats.queryStart(query)
  80. cur_stats.queryStart(query)
  81. elif (event_type == "QueryResponse"):
  82. total_stats.queryResponse(query)
  83. cur_stats.queryResponse(query)
  84. count += 1
  85. elif event_type == "Quit":
  86. # Quit is an "instantaneous" query, both start and response
  87. total_stats.queryStart(query)
  88. total_stats.queryResponse(query)
  89. cur_stats.queryStart(query)
  90. cur_stats.queryResponse(query)
  91. continue
  92. if 0 == (count % 20):
  93. # Handle keyboard input every 20th event (slow in the case of a not-very-loaded database
  94. c = 0
  95. done = False
  96. while -1 != c:
  97. # Fetch the key from curses
  98. c = stdscr.getch()
  99. if -1 == c:
  100. # Skip if no keyboard input
  101. pass
  102. # See if the generic query browser class handles it
  103. elif query_browser.handleKey(c):
  104. # Yep, just redraw
  105. query_browser.redraw()
  106. pass
  107. #
  108. # Check other keyboard events
  109. #
  110. elif c == ord('q'):
  111. # Quit
  112. done = True
  113. break
  114. elif c == ord(' '):
  115. # Switch which stats we're displaying between the previous interval (last 5 secs) and total interval
  116. if display_stats == prev_stats:
  117. display_stats = total_stats
  118. elif display_stats:
  119. display_stats = prev_stats
  120. query_browser.setQueryMap(display_stats)
  121. query_browser.redraw()
  122. elif c == ord('d'):
  123. # Dump output to files
  124. total_stats.dumpTiming("%s/query_timing.txt" % output_path)
  125. total_stats.dumpLLSD("%s/query_dump.llsd" % output_path)
  126. else:
  127. print "Pressed key %s" % c
  128. # Switch the current stat accumulator bin every DUMP_INTERVAL
  129. cur_time = time.time()
  130. if (cur_time - last_time > DUMP_INTERVAL):
  131. last_time = cur_time
  132. last_count = count
  133. if display_stats == prev_stats:
  134. display_stats = cur_stats
  135. prev_stats = cur_stats
  136. prev_stats.mElapsedTime = 5.0
  137. cur_stats = LLQueryStatMap("5 seconds", time.time())
  138. query_browser.setQueryMap(display_stats)
  139. query_browser.redraw()
  140. except KeyboardInterrupt:
  141. pass
  142. # Dump total stats on exit
  143. total_stats.dumpTiming("%s/query_timing.txt" % output_path)
  144. total_stats.dumpLLSD("%s/query_dump.llsd" % output_path)
  145. #
  146. # Start the main application, parse parameters
  147. #
  148. if __name__ == "__main__":
  149. opts, args = getopt.getopt(sys.argv[1:], "", ["host="])
  150. host = None
  151. for o, a in opts:
  152. if o in ("--host"):
  153. host = a
  154. if not host:
  155. print "Specify a host using --host="
  156. sys.exit(1)
  157. # Load existing metadata
  158. query_metadata = LLQueryMetadata("./query_info.llsd")
  159. # Start up the stream from the target host and create a file
  160. # that we can hand to LLQueryStream.
  161. query_stream_file = remote_mysql_stream(host)
  162. query_stream = LLQueryStream(query_stream_file)
  163. # start up the curses interface and start processing the stream data
  164. curses.wrapper(process_query_stream, query_stream, query_metadata, host)