Download:
child 11:251971044ff2
parent 9:ea1f77aa6c79
10:c104b23c85f0
Anton Shestakov <engored@ya.ru>, Sat, 11 Jan 2014 23:41:32 +0900
qmonitor.

2 файлов изменено, 187 вставок(+), 0 удалений(-) [+]
qmonitor/README.md file | annotate | diff | comparison | revisions
qmonitor/example.py file | annotate | diff | comparison | revisions
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/qmonitor/README.md Sat Jan 11 23:41:32 2014 +0900
@@ -0,0 +1,68 @@
+One of the new features in SQLAlchemy 0.9 is event removal API. Basically, if
+you've added an event listener, this change makes you able to remove it.
+Although, to be honest, I suspect there was a way to remove event listeners
+before 0.9, but it was probably complicated or undocumented.
+
+With this change you can now do a context manager that can watch queries
+executed for an orbitrary (small) block of code without leaving any event
+listeners behind or catching unrelated queries by accident.
+
+Anyway, here's some code, and if you need a complete example, see example.py.
+
+ :::python
+ class QMonitor(object):
+ def __init__(self, max_queries):
+ self.queries = []
+ self.max_queries = max_queries
+
+ def assist_user(self):
+ """ User requires assistance in query minimization.
+ """
+ qc = Counter(zip(*self.queries)[0])
+ if qc.values().count(1) < len(qc):
+ duplicates = [
+ '** Repeated {0} times: {1}'.format(c, q)
+ for q, c in qc.items()
+ if c > 1
+ ]
+ print (u'Too many queries, try eliminating duplicates:\n' +
+ u'\n'.join(duplicates))
+ else:
+ print u'Too many queries:\n' + u'\n'.join(zip(*self.queries)[0])
+
+ def check(self):
+ """ Check saved database queries.
+ """
+ if len(self.queries) > self.max_queries:
+ self.assist_user()
+
+
+ @contextmanager
+ def qmonitor(max_queries=15):
+ qm = QMonitor(max_queries)
+
+ def before_cursor_execute(conn, cursor, statement, parameters, context, executemany):
+ context._query_start_time = time.time()
+
+ def after_cursor_execute(conn, cursor, statement, parameters, context, executemany):
+ total = time.time() - context._query_start_time
+ qm.queries.append((statement, total))
+
+ event.listen(engine, 'before_cursor_execute', before_cursor_execute)
+ event.listen(engine, 'after_cursor_execute', after_cursor_execute)
+ yield qm
+ event.remove(engine, 'before_cursor_execute', before_cursor_execute)
+ event.remove(engine, 'after_cursor_execute', after_cursor_execute)
+
+
+ print '== Naive query'
+ q = session.query(Address)
+ with qmonitor(max_queries=1) as qm:
+ for address in q:
+ print address.email_address + ' - ' + address.user.fullname
+
+ qm.check()
+
+This will check performace (so far just count number of DB queries) and assist
+in making the "managed" block of code as fast as possible. It is also quite
+easy to use in integration tests for finding database-intensive code.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/qmonitor/example.py Sat Jan 11 23:41:32 2014 +0900
@@ -0,0 +1,119 @@
+#!/usr/bin/env python
+#-*- coding:utf-8 -*-
+import time
+from collections import Counter
+from contextlib import contextmanager
+
+from sqlalchemy import create_engine
+from sqlalchemy import event
+from sqlalchemy import Column, ForeignKey, Integer, String
+from sqlalchemy.orm import relationship, backref, joinedload
+from sqlalchemy.orm import sessionmaker
+from sqlalchemy.ext.declarative import declarative_base
+
+
+engine = create_engine('sqlite:///:memory:')
+Session = sessionmaker(bind=engine)
+Base = declarative_base()
+session = Session()
+
+
+class User(Base):
+ __tablename__ = 'users'
+
+ id = Column(Integer, primary_key=True)
+ name = Column(String)
+ fullname = Column(String)
+ password = Column(String)
+
+
+class Address(Base):
+ __tablename__ = 'addresses'
+ id = Column(Integer, primary_key=True)
+ email_address = Column(String, nullable=False)
+ user_id = Column(Integer, ForeignKey('users.id'))
+
+ user = relationship("User", backref=backref('addresses', order_by=id))
+
+
+Base.metadata.create_all(engine)
+
+
+users = [
+ User(name='wendy', fullname='Wendy Williams', password='foobar'),
+ User(name='mary', fullname='Mary Contrary', password='xxg527'),
+ User(name='fred', fullname='Fred Flinstone', password='blah')
+]
+
+for user in users:
+ user.addresses = [
+ Address(email_address=user.name + '@example.com'),
+ Address(email_address=user.name + '@work.corp.com')
+ ]
+
+session.add_all(users)
+session.commit()
+
+
+class QMonitor(object):
+ def __init__(self, max_queries):
+ self.queries = []
+ self.max_queries = max_queries
+
+ def assist_user(self):
+ """ User requires assistance in query minimization.
+ """
+ qc = Counter(zip(*self.queries)[0])
+ if qc.values().count(1) < len(qc):
+ duplicates = [
+ '** Repeated {0} times: {1}'.format(c, q)
+ for q, c in qc.items()
+ if c > 1
+ ]
+ print (u'Too many queries, try eliminating duplicates:\n' +
+ u'\n'.join(duplicates))
+ else:
+ print u'Too many queries:\n' + u'\n'.join(zip(*self.queries)[0])
+
+ def check(self):
+ """ Check saved database queries.
+ """
+ if len(self.queries) > self.max_queries:
+ self.assist_user()
+
+
+@contextmanager
+def qmonitor(max_queries=15):
+ qm = QMonitor(max_queries)
+
+ def before_cursor_execute(conn, cursor, statement, parameters, context, executemany):
+ context._query_start_time = time.time()
+
+ def after_cursor_execute(conn, cursor, statement, parameters, context, executemany):
+ total = time.time() - context._query_start_time
+ qm.queries.append((statement, total))
+
+ event.listen(engine, 'before_cursor_execute', before_cursor_execute)
+ event.listen(engine, 'after_cursor_execute', after_cursor_execute)
+ yield qm
+ event.remove(engine, 'before_cursor_execute', before_cursor_execute)
+ event.remove(engine, 'after_cursor_execute', after_cursor_execute)
+
+
+print '== Naive query'
+q = session.query(Address)
+with qmonitor(max_queries=1) as qm:
+ for address in q:
+ print address.email_address + ' - ' + address.user.fullname
+
+qm.check()
+
+
+print
+print '== Optimized query'
+q = session.query(Address).options(joinedload('user'))
+with qmonitor(max_queries=1) as qm:
+ for address in q:
+ print address.email_address + ' - ' + address.user.fullname
+
+qm.check()