PageRenderTime 28ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/mysql_watcher/dblibs/dbbrowser.py

https://bitbucket.org/lindenlab/apiary/
Python | 315 lines | 264 code | 16 blank | 35 comment | 18 complexity | e7682e829b7c2c9d57ee26cc16e61241 MD5 | raw file
  1. #!/usr/bin/env python
  2. #
  3. # $LicenseInfo:firstyear=2007&license=mit$
  4. #
  5. # Copyright (c) 2007-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. import curses
  27. import os
  28. from llbase import llsd
  29. from dbmonitor import LLQueryStatMap, asciify
  30. def edit_list(win, l, text):
  31. # Pop up an edit box to edit a list, and return the resulting list
  32. # Go forth and edit tables
  33. curses.textpad.rectangle(win, 2, 2, 13, 63)
  34. win.addstr(2,3,"Ctrl-G to exit - %s" % text)
  35. win.refresh()
  36. editwin = win.derwin(10, 60, 3, 3)
  37. editwin.clear()
  38. editwin.refresh()
  39. clean_list = []
  40. for line in l:
  41. res = line.strip()
  42. if res:
  43. clean_list.append(res)
  44. l_str = "\n".join(clean_list)
  45. editwin.addstr(l_str)
  46. textbox = curses.textpad.Textbox(editwin)
  47. res = textbox.edit()
  48. res = res.strip("\n")
  49. res_l = []
  50. for line in res.split("\n"):
  51. res = line.strip()
  52. if res:
  53. res_l.append(res)
  54. return res_l
  55. class LLQueryMetadata:
  56. "Stores metadata for all known queries"
  57. def __init__(self, fn):
  58. # Keyed on host_clean:query_clean
  59. self.mMap = {}
  60. if not os.path.exists(fn):
  61. # Skip if no metadata file
  62. return
  63. # Read in metadata
  64. query_info_file = open(fn)
  65. query_info_string = query_info_file.read()
  66. query_info_file.close()
  67. query_list = llsd.LLSD.parse(query_info_string)
  68. if not query_list:
  69. return
  70. for query in query_list:
  71. key = query['host_clean'] + ":" + query['query_clean']
  72. self.mMap[key] = query
  73. def save(self, fn):
  74. out = []
  75. # Sort the output by the key, and format it to allow merging
  76. sorted_keys = self.mMap.keys()
  77. sorted_keys.sort()
  78. for key in sorted_keys:
  79. out.append(self.mMap[key])
  80. tmp_fn = "/tmp/query_info.tmp"
  81. out_file = open(tmp_fn, "w")
  82. out_file.write(str(llsd.LLSD(out)))
  83. out_file.close()
  84. os.system('xmllint --format %s > %s' % (tmp_fn, fn))
  85. def lookupQuery(self, query):
  86. key = query.getKey()
  87. if key in self.mMap:
  88. return self.mMap[key]
  89. else:
  90. return None
  91. def addQuery(self, query):
  92. key = query.getKey()
  93. if not key in self.mMap:
  94. self.mMap[key] = {'host_clean':query.mData['host_clean'], 'query_clean':query.mData['query_clean'],'notes':''}
  95. return self.mMap[key]
  96. class LLQueryBrowser:
  97. "Browse through the map of supplied queries."
  98. def __init__(self, win, query_map = None, metadata = None):
  99. self.mWin = win
  100. self.mQueryMap = query_map
  101. self.mMetadata = metadata
  102. self.mSortBy = "total_time"
  103. self.mSortedKeys = query_map.getSortedKeys(self.mSortBy)
  104. self.mSelectedIndex = 0
  105. self.mSelectedKey = None
  106. self.mOffset = 0
  107. self.mListHeight = 20
  108. self.mAllowEdit = False
  109. def setQueryMap(self, query_map):
  110. self.mQueryMap = query_map
  111. self.mSortedKeys = query_map.getSortedKeys(self.mSortBy)
  112. def editSelectedText(self, column):
  113. # Go forth and edit notes
  114. curses.textpad.rectangle(self.mWin, 2, 2, 13, 63)
  115. self.mWin.addstr(2,3,"Ctrl-G to exit - Editing %s" % column)
  116. self.mWin.refresh()
  117. editwin = self.mWin.derwin(10, 60, 3, 3)
  118. editwin.clear()
  119. editwin.refresh()
  120. query = self.mQueryMap.mQueryMap[self.mSelectedKey]
  121. query_metadata = self.mMetadata.lookupQuery(query)
  122. if not query_metadata:
  123. query_metadata = self.mMetadata.addQuery(query)
  124. if not query_metadata:
  125. raise "No query metadata"
  126. editwin.addstr(query_metadata[column])
  127. textbox = curses.textpad.Textbox(editwin)
  128. res = textbox.edit()
  129. query_metadata[column] = res
  130. def editSelectedList(self, column):
  131. query = self.mQueryMap.mQueryMap[self.mSelectedKey]
  132. query_metadata = self.mMetadata.lookupQuery(query)
  133. tables = query_metadata[column]
  134. tables = edit_list(self.mWin, tables, "Editing %s" % column)
  135. query_metadata[column] = tables
  136. def handleKey(self, key):
  137. "Returns True if the key was handled, otherwise false"
  138. if key == curses.KEY_DOWN:
  139. self.mSelectedIndex += 1
  140. self.mSelectedIndex = min(len(self.mSortedKeys)-1, self.mSelectedIndex)
  141. self.mSelectedKey = self.mSortedKeys[self.mSelectedIndex]
  142. if self.mSelectedIndex >= self.mOffset + self.mListHeight:
  143. self.mOffset += 1
  144. self.mOffset = min(len(self.mSortedKeys)-1, self.mOffset)
  145. elif key == curses.KEY_NPAGE:
  146. self.mSelectedIndex += self.mListHeight
  147. self.mSelectedIndex = min(len(self.mSortedKeys)-1, self.mSelectedIndex)
  148. self.mSelectedKey = self.mSortedKeys[self.mSelectedIndex]
  149. self.mOffset += self.mListHeight
  150. self.mOffset = min(len(self.mSortedKeys)-1, self.mOffset)
  151. elif key == curses.KEY_UP:
  152. self.mSelectedIndex -= 1
  153. self.mSelectedIndex = max(0, self.mSelectedIndex)
  154. self.mSelectedKey = self.mSortedKeys[self.mSelectedIndex]
  155. if self.mSelectedIndex < self.mOffset:
  156. self.mOffset -= 1
  157. self.mOffset = max(0, self.mOffset)
  158. elif key == curses.KEY_PPAGE:
  159. self.mSelectedIndex -= self.mListHeight
  160. self.mSelectedIndex = max(0, self.mSelectedIndex)
  161. self.mSelectedIndex = max(0, self.mSelectedIndex)
  162. self.mSelectedKey = self.mSortedKeys[self.mSelectedIndex]
  163. self.mOffset -= self.mListHeight
  164. self.mOffset = max(0, self.mOffset)
  165. elif key == ord('s'):
  166. self.toggleSort()
  167. elif not self.mAllowEdit:
  168. return False
  169. elif c == ord('n'):
  170. # Go forth and edit notes
  171. self.editSelectedText('notes')
  172. else:
  173. return False
  174. self.redraw()
  175. return True
  176. def drawHeader(self, y):
  177. self.mWin.addstr(y, 0, self.mQueryMap.mDescription + " Query %d of %d" % (self.mSelectedIndex, len(self.mSortedKeys)))
  178. y += 1
  179. self.mWin.addstr(y, 0, 'QPS: %.2f\tElapsed: %.2f' % (self.mQueryMap.getQPS(), self.mQueryMap.getElapsedTime()))
  180. y += 1
  181. x = 0
  182. self.mWin.addnstr(y, x, ' TotalSec', 9)
  183. x += 10
  184. self.mWin.addnstr(y, x, ' AvgSec', 7)
  185. x += 8
  186. self.mWin.addnstr(y, x, ' Count', 7)
  187. x += 8
  188. self.mWin.addnstr(y, x, ' QPS', 5)
  189. x += 6
  190. self.mWin.addnstr(y, x, 'Host', 9)
  191. x += 10
  192. self.mWin.addnstr(y, x, 'Query', 59)
  193. x += 60
  194. self.mWin.addnstr(y, x, 'Notes', 10)
  195. y += 1
  196. self.mWin.addstr(y, 0, "-"*(x+10))
  197. y += 1
  198. return y
  199. def drawLine(self, y, query, attr, elapsed = 1.0):
  200. x = 0
  201. self.mWin.addnstr(y, x, "%9.2f" % (query.mTotalTimeCorrected), 9, attr)
  202. x += 10
  203. self.mWin.addnstr(y, x, "%7.4f" % (query.getAvgTimeCorrected()), 7, attr)
  204. x += 8
  205. self.mWin.addnstr(y, x, "%7d" % (query.mNumQueriesCorrected), 7, attr)
  206. x += 8
  207. self.mWin.addnstr(y, x, "%5.1f" % (query.mNumQueriesCorrected/elapsed), 5, attr)
  208. x += 6
  209. self.mWin.addnstr(y, x, query.mData['host_clean'], 9, attr)
  210. x += 10
  211. self.mWin.addnstr(y, x, query.mData['query_clean'], 59, attr)
  212. x += 60
  213. query_metadata = self.mMetadata.lookupQuery(query)
  214. if query_metadata:
  215. self.mWin.addnstr(y, x, query_metadata['notes'], 19, attr)
  216. def drawDetail(self, y, query):
  217. query.analyze()
  218. self.mWin.addstr(y, 0, "Tables:")
  219. self.mWin.addnstr(y, 10, str(query.mData['tables']), 80)
  220. y += 1
  221. self.mWin.addstr(y, 0, "Clean Query: " + query.mData['host_clean'])
  222. y += 1
  223. self.mWin.addstr(y, 0, asciify(str(query.mData['query_clean'])))
  224. y += 8
  225. self.mWin.addstr(y, 0, "Sample Query: " + query.mData['host_clean'])
  226. y += 1
  227. try:
  228. self.mWin.addstr(y, 0, asciify(str(query.mData['query'])))
  229. except curses.error:
  230. pass
  231. y += 8
  232. query_metadata = self.mMetadata.lookupQuery(query)
  233. if not query_metadata:
  234. return
  235. self.mWin.addstr(y, 0, "Notes:")
  236. y += 1
  237. self.mWin.addnstr(y, 0, str(query_metadata['notes']), 80)
  238. self.mWin.refresh()
  239. def redraw(self):
  240. # Find the location of the selected key
  241. if not self.mSelectedKey:
  242. self.Selected = 0
  243. self.mSelectedKey = self.mSortedKeys[0]
  244. self.mOffset = 0
  245. else:
  246. # Find the selected key by brute force for now
  247. self.mSelectedIndex = -1
  248. for i in range(0, len(self.mSortedKeys)):
  249. if self.mSortedKeys[i] == self.mSelectedKey:
  250. self.mSelectedIndex = i
  251. break
  252. if -1 == self.mSelectedIndex:
  253. self.mSelectedIndex = 0
  254. self.mSelectedKey = self.mSortedKeys[0]
  255. # Reset the offset to make sure it's on screen
  256. if self.mSelectedIndex < self.mOffset:
  257. self.mOffset = self.mSelectedIndex
  258. elif self.mSelectedIndex >= (self.mOffset + self.mListHeight):
  259. self.mOffset = (self.mSelectedIndex - self.mListHeight) + 1
  260. # Redraw the display
  261. self.mWin.clear()
  262. y = 0
  263. y = self.drawHeader(y)
  264. for i in range(self.mOffset, min(len(self.mSortedKeys), self.mOffset + self.mListHeight)):
  265. attr = curses.A_NORMAL
  266. if i == self.mSelectedIndex:
  267. attr = curses.A_BOLD
  268. self.drawLine(y, self.mQueryMap.mQueryMap[self.mSortedKeys[i]], attr, self.mQueryMap.getElapsedTime())
  269. y += 1
  270. # Write detailed information about the selected query
  271. y += 1
  272. self.drawDetail(y, self.mQueryMap.mQueryMap[self.mSortedKeys[self.mSelectedIndex]])
  273. def toggleSort(self):
  274. "Toggle to the next sort by column"
  275. if self.mSortBy == "total_time":
  276. self.mSortBy = "avg_time"
  277. elif self.mSortBy == "avg_time":
  278. self.mSortBy = "count"
  279. elif self.mSortBy == "count":
  280. self.mSortBy = "total_time"
  281. self.mSortedKeys = self.mQueryMap.getSortedKeys(self.mSortBy)
  282. #self.mSelectedIndex = 0
  283. #self.mSelectedKey = None
  284. self.mOffset = 0