PageRenderTime 165ms CodeModel.GetById 60ms app.highlight 74ms RepoModel.GetById 27ms app.codeStats 0ms

/mysql_watcher/dblibs/dbbrowser.py

https://bitbucket.org/lindenlab/apiary/
Python | 315 lines | 264 code | 16 blank | 35 comment | 24 complexity | e7682e829b7c2c9d57ee26cc16e61241 MD5 | raw file
  1#!/usr/bin/env python
  2
  3#
  4# $LicenseInfo:firstyear=2007&license=mit$
  5# 
  6# Copyright (c) 2007-2010, Linden Research, Inc.
  7# 
  8# Permission is hereby granted, free of charge, to any person obtaining a copy
  9# of this software and associated documentation files (the "Software"), to deal
 10# in the Software without restriction, including without limitation the rights
 11# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 12# copies of the Software, and to permit persons to whom the Software is
 13# furnished to do so, subject to the following conditions:
 14# 
 15# The above copyright notice and this permission notice shall be included in
 16# all copies or substantial portions of the Software.
 17# 
 18# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 19# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 20# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 21# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 22# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 23# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 24# THE SOFTWARE.
 25# $/LicenseInfo$
 26#
 27
 28import curses
 29import os
 30
 31from llbase import llsd
 32from dbmonitor import LLQueryStatMap, asciify
 33
 34def edit_list(win, l, text):
 35    # Pop up an edit box to edit a list, and return the resulting list
 36    # Go forth and edit tables
 37    curses.textpad.rectangle(win, 2, 2, 13, 63)
 38    win.addstr(2,3,"Ctrl-G to exit - %s" % text)
 39    win.refresh()
 40    editwin = win.derwin(10, 60, 3, 3)
 41    editwin.clear()
 42    editwin.refresh()
 43
 44    clean_list = []
 45    for line in l:
 46        res = line.strip()
 47        if res:
 48            clean_list.append(res)
 49    l_str = "\n".join(clean_list)
 50    editwin.addstr(l_str)
 51    textbox = curses.textpad.Textbox(editwin)
 52    res = textbox.edit()
 53    res = res.strip("\n")
 54    res_l = []
 55    for line in res.split("\n"):
 56        res = line.strip()
 57        if res:
 58            res_l.append(res)
 59    return res_l
 60
 61
 62class LLQueryMetadata:
 63    "Stores metadata for all known queries"
 64    def __init__(self, fn):
 65        # Keyed on host_clean:query_clean
 66        self.mMap = {}
 67
 68        if not os.path.exists(fn):
 69            # Skip if no metadata file
 70            return
 71        # Read in metadata
 72        query_info_file = open(fn)
 73        query_info_string = query_info_file.read()
 74        query_info_file.close()
 75        query_list = llsd.LLSD.parse(query_info_string)
 76        if not query_list:
 77            return
 78        for query in query_list:
 79            key = query['host_clean'] + ":" + query['query_clean']
 80            self.mMap[key] = query
 81        
 82    def save(self, fn):
 83        out = []
 84        # Sort the output by the key, and format it to allow merging
 85
 86        sorted_keys = self.mMap.keys()
 87        sorted_keys.sort()
 88        for key in sorted_keys:
 89            out.append(self.mMap[key])
 90        tmp_fn = "/tmp/query_info.tmp"
 91        out_file = open(tmp_fn, "w")
 92        out_file.write(str(llsd.LLSD(out)))
 93        out_file.close()
 94        os.system('xmllint --format %s > %s' % (tmp_fn, fn))
 95
 96    def lookupQuery(self, query):
 97        key = query.getKey()
 98        if key in self.mMap:
 99            return self.mMap[key]
100        else:
101            return None
102        
103    def addQuery(self, query):
104        key = query.getKey()
105        if not key in self.mMap:
106            self.mMap[key] = {'host_clean':query.mData['host_clean'], 'query_clean':query.mData['query_clean'],'notes':''}
107        return self.mMap[key]
108
109class LLQueryBrowser:
110    "Browse through the map of supplied queries."
111    def __init__(self, win, query_map = None, metadata = None):
112        self.mWin = win
113        self.mQueryMap = query_map
114        self.mMetadata = metadata
115        self.mSortBy = "total_time"
116        self.mSortedKeys = query_map.getSortedKeys(self.mSortBy)
117        self.mSelectedIndex = 0
118        self.mSelectedKey = None
119        self.mOffset = 0
120        self.mListHeight = 20
121        self.mAllowEdit = False
122        
123    def setQueryMap(self, query_map):
124        self.mQueryMap = query_map
125        self.mSortedKeys = query_map.getSortedKeys(self.mSortBy)
126
127    def editSelectedText(self, column):
128        # Go forth and edit notes
129        curses.textpad.rectangle(self.mWin, 2, 2, 13, 63)
130        self.mWin.addstr(2,3,"Ctrl-G to exit - Editing %s" % column)
131        self.mWin.refresh()
132        editwin = self.mWin.derwin(10, 60, 3, 3)
133        editwin.clear()
134        editwin.refresh()
135        query = self.mQueryMap.mQueryMap[self.mSelectedKey]
136        query_metadata = self.mMetadata.lookupQuery(query)
137        if not query_metadata:
138            query_metadata = self.mMetadata.addQuery(query)
139        if not query_metadata:
140            raise "No query metadata"
141        editwin.addstr(query_metadata[column])
142        textbox = curses.textpad.Textbox(editwin)
143        res = textbox.edit()
144        query_metadata[column] = res
145
146    def editSelectedList(self, column):
147        query = self.mQueryMap.mQueryMap[self.mSelectedKey]
148        query_metadata = self.mMetadata.lookupQuery(query)
149        tables = query_metadata[column]
150        tables = edit_list(self.mWin, tables, "Editing %s" % column)
151        query_metadata[column] = tables
152
153    def handleKey(self, key):
154        "Returns True if the key was handled, otherwise false"
155        if key == curses.KEY_DOWN:
156            self.mSelectedIndex += 1
157            self.mSelectedIndex = min(len(self.mSortedKeys)-1, self.mSelectedIndex)
158            self.mSelectedKey = self.mSortedKeys[self.mSelectedIndex]
159
160            if self.mSelectedIndex >= self.mOffset + self.mListHeight:
161                self.mOffset += 1
162            self.mOffset = min(len(self.mSortedKeys)-1, self.mOffset)
163        elif key == curses.KEY_NPAGE:
164            self.mSelectedIndex += self.mListHeight
165            self.mSelectedIndex = min(len(self.mSortedKeys)-1, self.mSelectedIndex)
166            self.mSelectedKey = self.mSortedKeys[self.mSelectedIndex]
167
168            self.mOffset += self.mListHeight
169            self.mOffset = min(len(self.mSortedKeys)-1, self.mOffset)
170        elif key == curses.KEY_UP:
171            self.mSelectedIndex -= 1
172            self.mSelectedIndex = max(0, self.mSelectedIndex)
173            self.mSelectedKey = self.mSortedKeys[self.mSelectedIndex]
174
175            if self.mSelectedIndex < self.mOffset:
176                self.mOffset -= 1
177            self.mOffset = max(0, self.mOffset)
178        elif key == curses.KEY_PPAGE:
179            self.mSelectedIndex -= self.mListHeight
180            self.mSelectedIndex = max(0, self.mSelectedIndex)
181            self.mSelectedIndex = max(0, self.mSelectedIndex)
182            self.mSelectedKey = self.mSortedKeys[self.mSelectedIndex]
183
184            self.mOffset -= self.mListHeight
185            self.mOffset = max(0, self.mOffset)
186        elif key == ord('s'):
187            self.toggleSort()
188        elif not self.mAllowEdit:
189            return False
190        elif c == ord('n'):
191            # Go forth and edit notes
192            self.editSelectedText('notes')
193        else:
194            return False
195
196        self.redraw()
197        return True
198
199    def drawHeader(self, y):
200        self.mWin.addstr(y, 0, self.mQueryMap.mDescription + " Query %d of %d" % (self.mSelectedIndex, len(self.mSortedKeys)))
201        y += 1
202        self.mWin.addstr(y, 0, 'QPS: %.2f\tElapsed: %.2f' % (self.mQueryMap.getQPS(), self.mQueryMap.getElapsedTime()))
203        y += 1
204        x = 0
205        self.mWin.addnstr(y, x, ' TotalSec', 9)
206        x += 10
207        self.mWin.addnstr(y, x, ' AvgSec', 7)
208        x += 8
209        self.mWin.addnstr(y, x, '  Count', 7)
210        x += 8
211        self.mWin.addnstr(y, x, '  QPS', 5)
212        x += 6
213        self.mWin.addnstr(y, x, 'Host', 9)
214        x += 10
215        self.mWin.addnstr(y, x, 'Query', 59)
216        x += 60
217        self.mWin.addnstr(y, x, 'Notes', 10)
218        y += 1
219        self.mWin.addstr(y, 0, "-"*(x+10))
220        y += 1
221        return y
222
223    def drawLine(self, y, query, attr, elapsed = 1.0):
224        x = 0
225        self.mWin.addnstr(y, x, "%9.2f" % (query.mTotalTimeCorrected), 9, attr)
226        x += 10
227        self.mWin.addnstr(y, x, "%7.4f" % (query.getAvgTimeCorrected()), 7, attr)
228        x += 8
229        self.mWin.addnstr(y, x, "%7d" % (query.mNumQueriesCorrected), 7, attr)
230        x += 8
231        self.mWin.addnstr(y, x, "%5.1f" % (query.mNumQueriesCorrected/elapsed), 5, attr)
232        x += 6
233        self.mWin.addnstr(y, x, query.mData['host_clean'], 9, attr)
234        x += 10
235        self.mWin.addnstr(y, x, query.mData['query_clean'], 59, attr)
236        x += 60
237        query_metadata = self.mMetadata.lookupQuery(query)
238        if query_metadata:
239            self.mWin.addnstr(y, x, query_metadata['notes'], 19, attr)
240        
241    def drawDetail(self, y, query):
242        query.analyze()
243        self.mWin.addstr(y, 0, "Tables:")
244        self.mWin.addnstr(y, 10, str(query.mData['tables']), 80)
245        y += 1
246        self.mWin.addstr(y, 0, "Clean Query: " + query.mData['host_clean'])
247        y += 1
248        self.mWin.addstr(y, 0, asciify(str(query.mData['query_clean'])))
249        y += 8
250        self.mWin.addstr(y, 0, "Sample Query: " + query.mData['host_clean'])
251        y += 1
252        try:
253            self.mWin.addstr(y, 0, asciify(str(query.mData['query'])))
254        except curses.error:
255            pass
256        y += 8
257        query_metadata = self.mMetadata.lookupQuery(query)
258        if not query_metadata:
259            return
260        self.mWin.addstr(y, 0, "Notes:")
261        y += 1
262        self.mWin.addnstr(y, 0, str(query_metadata['notes']), 80)
263        self.mWin.refresh()
264
265    def redraw(self):
266        # Find the location of the selected key
267        if not self.mSelectedKey:
268            self.Selected = 0
269            self.mSelectedKey = self.mSortedKeys[0]
270            self.mOffset = 0
271        else:
272            # Find the selected key by brute force for now
273            self.mSelectedIndex = -1
274            for i in range(0, len(self.mSortedKeys)):
275                if self.mSortedKeys[i] == self.mSelectedKey:
276                    self.mSelectedIndex = i
277                    break
278            if -1 == self.mSelectedIndex:
279                self.mSelectedIndex = 0
280                self.mSelectedKey = self.mSortedKeys[0]
281
282            # Reset the offset to make sure it's on screen
283            if self.mSelectedIndex < self.mOffset:
284                self.mOffset = self.mSelectedIndex
285            elif self.mSelectedIndex >= (self.mOffset + self.mListHeight):
286                self.mOffset = (self.mSelectedIndex - self.mListHeight) + 1
287        
288        # Redraw the display
289        self.mWin.clear()
290        y = 0
291        y = self.drawHeader(y)
292
293        for i in range(self.mOffset, min(len(self.mSortedKeys), self.mOffset + self.mListHeight)):
294            attr = curses.A_NORMAL
295            if i == self.mSelectedIndex:
296                attr = curses.A_BOLD
297            self.drawLine(y, self.mQueryMap.mQueryMap[self.mSortedKeys[i]], attr, self.mQueryMap.getElapsedTime())
298            y += 1
299
300        # Write detailed information about the selected query
301        y += 1
302        self.drawDetail(y, self.mQueryMap.mQueryMap[self.mSortedKeys[self.mSelectedIndex]])
303
304    def toggleSort(self):
305        "Toggle to the next sort by column"
306        if self.mSortBy == "total_time":
307            self.mSortBy = "avg_time"
308        elif self.mSortBy == "avg_time":
309            self.mSortBy = "count"
310        elif self.mSortBy == "count":
311            self.mSortBy = "total_time"
312        self.mSortedKeys = self.mQueryMap.getSortedKeys(self.mSortBy)
313        #self.mSelectedIndex = 0
314        #self.mSelectedKey = None
315        self.mOffset = 0