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

/qmonitor/README.md

https://bitbucket.org/engored/experiments.md
Markdown | 80 lines | 64 code | 16 blank | 0 comment | 0 complexity | b72434954c3679fc40253f3a51a66601 MD5 | raw file
  1. One of the new features in [SQLAlchemy] 0.9 is event removal API. Basically, if
  2. you've added an event listener, this change makes you able to remove it.
  3. Although, to be honest, I suspect there was a way to remove event listeners
  4. before 0.9, but it was probably complicated or undocumented.
  5. With this change you can now do a context manager that can watch queries
  6. executed for an orbitrary (small) block of code without leaving any event
  7. listeners behind or catching unrelated queries by accident.
  8. Anyway, here's some code, and if you need a complete example, see example.py.
  9. :::python
  10. class QMonitor(object):
  11. def __init__(self, max_queries):
  12. self.queries = []
  13. self.max_queries = max_queries
  14. def assist_user(self):
  15. """ User requires assistance in query minimization.
  16. """
  17. qc = Counter(zip(*self.queries)[0])
  18. if qc.values().count(1) < len(qc):
  19. duplicates = [
  20. '** Repeated {0} times: {1}'.format(c, q)
  21. for q, c in qc.items()
  22. if c > 1
  23. ]
  24. print (u'Too many queries, try eliminating duplicates:\n' +
  25. u'\n'.join(duplicates))
  26. else:
  27. print u'Too many queries:\n' + u'\n'.join(zip(*self.queries)[0])
  28. def check(self):
  29. """ Check saved database queries.
  30. """
  31. if len(self.queries) > self.max_queries:
  32. self.assist_user()
  33. @contextmanager
  34. def qmonitor(max_queries=15):
  35. qm = QMonitor(max_queries)
  36. def before_cursor_execute(conn, cursor, statement, parameters, context, executemany):
  37. context._query_start_time = time.time()
  38. def after_cursor_execute(conn, cursor, statement, parameters, context, executemany):
  39. total = time.time() - context._query_start_time
  40. qm.queries.append((statement, total))
  41. event.listen(engine, 'before_cursor_execute', before_cursor_execute)
  42. event.listen(engine, 'after_cursor_execute', after_cursor_execute)
  43. yield qm
  44. event.remove(engine, 'before_cursor_execute', before_cursor_execute)
  45. event.remove(engine, 'after_cursor_execute', after_cursor_execute)
  46. print '== Naive query'
  47. q = session.query(Address)
  48. with qmonitor(max_queries=1) as qm:
  49. for address in q:
  50. print address.email_address + ' - ' + address.user.fullname
  51. qm.check()
  52. QMonitor class will hold all related queries and, in general, will be a user
  53. interface, since it is returned by the qmonitor context manager. The last thing
  54. in the snippet is a basic query that will generate 4 database queries, which is
  55. far from optimal. This is an easy case for QMonitor, since it will easily find
  56. that the block of code generated 3 identical queries.
  57. Such context manager can help checking performace (at least just count number
  58. of DB queries) and assist in making the "managed" block of code as fast as
  59. possible. I also found it to be really easy to use in integration tests for
  60. [Tornado] without any need to modify actual handlers or monkey-patching
  61. anything. (It's easier to try yourself, but in case you're wondering it simply
  62. works by wrapping `self.fetch(url)` in the context manager.)
  63. [SQLAlchemy]: http://www.sqlalchemy.org/
  64. [Tornado]: http://www.tornadoweb.org/