class BaseInfoItem(object): "Base class for all database monitoring objects." #: Weak reference to parent :class:`Monitor` instance. monitor = None def __init__(self,monitor,attributes): self.monitor = (type(monitor) == weakref.ProxyType) and monitor or weakref.proxy(monitor) self._attributes = dict(attributes) #--- protected def _strip_attribute(self,attr): if self._attributes.get(attr): self._attributes[attr] = self._attributes[attr].strip() #--- Protected def _get_stat_id(self): return self._attributes.get('MON$STAT_ID') #--- properties stat_id = LateBindingProperty(_get_stat_id,None,None,"Internal ID.")
class TransactionInfo(BaseInfoItem): "Information about transaction." def __init__(self, monitor, attributes): super(TransactionInfo, self).__init__(monitor, attributes) #--- Protected def __get_id(self): return self._attributes['MON$TRANSACTION_ID'] def __get_attachment(self): return self.monitor.get_attachment( self._attributes['MON$ATTACHMENT_ID']) def __get_state(self): return self._attributes['MON$STATE'] def __get_timestamp(self): return self._attributes['MON$TIMESTAMP'] def __get_top(self): return self._attributes['MON$TOP_TRANSACTION'] def __get_oldest(self): return self._attributes['MON$OLDEST_TRANSACTION'] def __get_oldest_active(self): return self._attributes['MON$OLDEST_ACTIVE'] def __get_isolation_mode(self): return self._attributes['MON$ISOLATION_MODE'] def __get_lock_timeout(self): return self._attributes['MON$LOCK_TIMEOUT'] def _get_statements(self): return [ s for s in self.monitor.statements if s._attributes['MON$TRANSACTION_ID'] == self.id ] def _get_variables(self): return [ s for s in self.monitor.variables if s._attributes['MON$TRANSACTION_ID'] == self.id ] def __get_iostats(self): for io in self.monitor.iostats: if (io.stat_id == self.stat_id) and (io.group == STAT_TRANSACTION): return io return None def __get_tablestats(self): res = {} for io in self.monitor.tablestats: if (io.stat_id == self.stat_id) and (io.group == STAT_TRANSACTION): res[io.table_name] = io return res #--- properties id = property(__get_id, None, None, "Transaction ID.") attachment = property( __get_attachment, None, None, ":class:`AttachmentInfo` instance to which this transaction belongs.") state = property(__get_state, None, None, "Transaction state (idle/active).") timestamp = property(__get_timestamp, None, None, "Transaction start date/time.") top = property(__get_top, None, None, "Top transaction.") oldest = property(__get_oldest, None, None, "Oldest transaction (local OIT).") oldest_active = property(__get_oldest_active, None, None, "Oldest active transaction (local OAT).") isolation_mode = property(__get_isolation_mode, None, None, "Transaction isolation mode code.") lock_timeout = property(__get_lock_timeout, None, None, "Lock timeout.") statements = LateBindingProperty( _get_statements, None, None, "List of statements associated with transaction.\nItems are :class:`StatementInfo` objects." ) variables = LateBindingProperty( _get_variables, None, None, "List of variables associated with transaction.\nItems are :class:`ContextVariableInfo` objects." ) iostats = property(__get_iostats, None, None, ":class:`IOStatsInfo` for this object.") # FB 3.0 tablestats = property( __get_tablestats, None, None, "Dictionary of :class:`TableStatsInfo` instances for this object.") #--- Public def isactive(self): "Returns True if transaction is active." return self.state == STATE_ACTIVE def isidle(self): "Returns True if transaction is idle." return self.state == STATE_IDLE def isreadonly(self): "Returns True if transaction is Read Only." return self._attributes['MON$READ_ONLY'] == FLAG_SET #return bool(self._attributes['MON$READ_ONLY']) def isautocommit(self): "Returns True for autocommited transaction." return self._attributes['MON$AUTO_COMMIT'] == FLAG_SET #return bool(self._attributes['MON$AUTO_COMMIT']) def isautoundo(self): "Returns True for transaction with automatic undo." return self._attributes['MON$AUTO_UNDO'] == FLAG_SET
class Monitor(object): """Class for access to Firebird monitoring tables. """ def __init__(self): self._con = None self._ic = None self.__internal = False def __del__(self): if not self.closed: self._close() def __get_closed(self): return self._con is None def __fail_if_closed(self): if self.closed: raise fdb.ProgrammingError("Monitor is not binded to connection.") def _close(self): self._ic.close() self._con = None self._ic = None def _set_as_internal(self): """Mark this instance as `internal` (embedded). This blocks calls to :meth:`bind` and :meth:`close`.""" self.__internal = True self._con = weakref.proxy(self._con) #--- protected def _get_database(self): if self.__database is None: self.__fail_if_closed() if self._con.ods >= fdb.ODS_FB_21: self._ic.execute("select * from mon$database") self.__database = DatabaseInfo(self, self._ic.fetchonemap()) else: self.__database = [] return self.__database def _get_attachments(self): if self.__attachments is None: self.__fail_if_closed() if self._con.ods >= fdb.ODS_FB_21: self._ic.execute("select * from mon$attachments") self.__attachments = [ AttachmentInfo(self, row) for row in self._ic.itermap() ] else: self.__attachments = [] return self.__attachments def _get_this_attachment(self): return self.get_attachment( self._con.db_info(fdb.isc_info_attachment_id)) def _get_transactions(self): if self.__transactions is None: self.__fail_if_closed() if self._con.ods >= fdb.ODS_FB_21: self._ic.execute("select * from mon$transactions") self.__transactions = [ TransactionInfo(self, row) for row in self._ic.itermap() ] else: self.__transactions = [] return self.__transactions def _get_statements(self): if self.__statements is None: self.__fail_if_closed() if self._con.ods >= fdb.ODS_FB_21: self._ic.execute("select * from mon$statements") self.__statements = [ StatementInfo(self, row) for row in self._ic.itermap() ] else: self.__statements = [] return self.__statements def _get_callstack(self): if self.__callstack is None: self.__fail_if_closed() if self._con.ods >= fdb.ODS_FB_21: self._ic.execute("select * from mon$call_stack") self.__callstack = [ CallStackInfo(self, row) for row in self._ic.itermap() ] else: self.__callstack = [] return self.__callstack def _get_iostats(self): if self.__iostats is None: self.__fail_if_closed() if self._con.ods >= fdb.ODS_FB_30: self._ic.execute("""SELECT r.MON$STAT_ID, r.MON$STAT_GROUP, r.MON$RECORD_SEQ_READS, r.MON$RECORD_IDX_READS, r.MON$RECORD_INSERTS, r.MON$RECORD_UPDATES, r.MON$RECORD_DELETES, r.MON$RECORD_BACKOUTS, r.MON$RECORD_PURGES, r.MON$RECORD_EXPUNGES, r.MON$RECORD_LOCKS, r.MON$RECORD_WAITS, r.MON$RECORD_CONFLICTS, r.MON$BACKVERSION_READS, r.MON$FRAGMENT_READS, r.MON$RECORD_RPT_READS, io.MON$PAGE_FETCHES, io.MON$PAGE_MARKS, io.MON$PAGE_READS, io.MON$PAGE_WRITES, m.MON$MEMORY_ALLOCATED, m.MON$MEMORY_USED, m.MON$MAX_MEMORY_ALLOCATED, m.MON$MAX_MEMORY_USED FROM MON$RECORD_STATS r join MON$IO_STATS io on r.MON$STAT_ID = io.MON$STAT_ID and r.MON$STAT_GROUP = io.MON$STAT_GROUP join MON$MEMORY_USAGE m on r.MON$STAT_ID = m.MON$STAT_ID and r.MON$STAT_GROUP = m.MON$STAT_GROUP""") elif self._con.ods >= fdb.ODS_FB_25: self._ic.execute("""SELECT r.MON$STAT_ID, r.MON$STAT_GROUP, r.MON$RECORD_SEQ_READS, r.MON$RECORD_IDX_READS, r.MON$RECORD_INSERTS, r.MON$RECORD_UPDATES, r.MON$RECORD_DELETES, r.MON$RECORD_BACKOUTS, r.MON$RECORD_PURGES, r.MON$RECORD_EXPUNGES, io.MON$PAGE_FETCHES, io.MON$PAGE_MARKS, io.MON$PAGE_READS, io.MON$PAGE_WRITES, m.MON$MEMORY_ALLOCATED, m.MON$MEMORY_USED, m.MON$MAX_MEMORY_ALLOCATED, m.MON$MAX_MEMORY_USED FROM MON$RECORD_STATS r join MON$IO_STATS io on r.MON$STAT_ID = io.MON$STAT_ID and r.MON$STAT_GROUP = io.MON$STAT_GROUP join MON$MEMORY_USAGE m on r.MON$STAT_ID = m.MON$STAT_ID and r.MON$STAT_GROUP = m.MON$STAT_GROUP""") elif self._con.ods >= fdb.ODS_FB_21: self._ic.execute("""SELECT r.MON$STAT_ID, r.MON$STAT_GROUP, r.MON$RECORD_SEQ_READS, r.MON$RECORD_IDX_READS, r.MON$RECORD_INSERTS, r.MON$RECORD_UPDATES, r.MON$RECORD_DELETES, r.MON$RECORD_BACKOUTS, r.MON$RECORD_PURGES, r.MON$RECORD_EXPUNGES, io.MON$PAGE_FETCHES, io.MON$PAGE_MARKS, io.MON$PAGE_READS, io.MON$PAGE_WRITES FROM MON$RECORD_STATS r join MON$IO_STATS io on r.MON$STAT_ID = io.MON$STAT_ID and r.MON$STAT_GROUP = io.MON$STAT_GROUP""") if self._con.ods >= fdb.ODS_FB_21: self.__iostats = [ IOStatsInfo(self, row) for row in self._ic.itermap() ] else: self.__iostats = [] return self.__iostats def _get_variables(self): if self.__variables is None: self.__fail_if_closed() if self._con.ods >= fdb.ODS_FB_25: self._ic.execute("select * from mon$context_variables") self.__variables = [ ContextVariableInfo(self, row) for row in self._ic.itermap() ] else: self.__variables = [] return self.__variables def _get_tablestats(self): if self.__tablestats is None: self.__fail_if_closed() if self._con.ods >= fdb.ODS_FB_30: self._ic.execute( """SELECT ts.MON$STAT_ID, ts.MON$STAT_GROUP, ts.MON$TABLE_NAME, ts.MON$RECORD_STAT_ID, r.MON$RECORD_SEQ_READS, r.MON$RECORD_IDX_READS, r.MON$RECORD_INSERTS, r.MON$RECORD_UPDATES, r.MON$RECORD_DELETES, r.MON$RECORD_BACKOUTS, r.MON$RECORD_PURGES, r.MON$RECORD_EXPUNGES, r.MON$RECORD_LOCKS, r.MON$RECORD_WAITS, r.MON$RECORD_CONFLICTS, r.MON$BACKVERSION_READS, r.MON$FRAGMENT_READS, r.MON$RECORD_RPT_READS FROM MON$TABLE_STATS ts join MON$RECORD_STATS r on ts.MON$RECORD_STAT_ID = r.MON$STAT_ID""") self.__tablestats = [ TableStatsInfo(self, row) for row in self._ic.itermap() ] else: self.__tablestats = [] return self.__tablestats #--- Properties #: True if link to :class:`~fdb.Connection` is closed. closed = property(__get_closed) db = LateBindingProperty( _get_database, None, None, ":class:`DatabaseInfo` object for attached database.") attachments = LateBindingProperty( _get_attachments, None, None, "List of all attachments.\nItems are :class:`AttachmentInfo` objects.") this_attachment = LateBindingProperty( _get_this_attachment, None, None, ":class:`AttachmentInfo` object for current connection.") transactions = LateBindingProperty( _get_transactions, None, None, "List of all transactions.\nItems are :class:`TransactionInfo` objects." ) statements = LateBindingProperty( _get_statements, None, None, "List of all statements.\nItems are :class:`StatementInfo` objects.") callstack = LateBindingProperty( _get_callstack, None, None, "List with complete call stack.\nItems are :class:`CallStackInfo` objects." ) iostats = LateBindingProperty( _get_iostats, None, None, "List of all I/O statistics.\nItems are :class:`IOStatsInfo` objects.") variables = LateBindingProperty( _get_variables, None, None, "List of all context variables.\nItems are :class:`ContextVariableInfo` objects." ) # FB 3.0 tablestats = LateBindingProperty( _get_tablestats, None, None, "List of all table record I/O statistics.\nItems are :class:`TableStatsInfo` objects." ) #--- Public def bind(self, connection): """Bind this instance to specified :class:`~fdb.Connection`. :param connection: :class:`~fdb.Connection` instance. :raises ProgrammingError: If Monitor object was set as internal (via :meth:`_set_as_internal`) or database has ODS lower than 11.1. """ if self.__internal: raise fdb.ProgrammingError( "Call to 'bind' not allowed for embedded Monitor.") if self._con: self.close() if connection.ods < fdb.ODS_FB_21: raise fdb.ProgrammingError("Monitoring tables are available only " \ "for databases with ODS 11.1 and higher.") self._con = connection self._ic = self._con.trans( fdb.ISOLATION_LEVEL_READ_COMMITED_RO).cursor() self.clear() def close(self): """Sever link to :class:`~fdb.Connection`. :raises ProgrammingError: If Monitor object was set as internal (via :meth:`_set_as_internal`). """ if self.__internal: raise fdb.ProgrammingError( "Call to 'close' not allowed for embedded Monitor.") self._close() self.clear() def clear(self): """Drop all cached information objects. Force reload of fresh monitoring information on next reference.""" self.__database = None self.__attachments = None self.__transactions = None self.__statements = None self.__callstack = None self.__iostats = None self.__variables = None self.__tablestats = None if not self.closed: self._ic.transaction.commit() def refresh(self): "Reloads fresh monitoring information." self.__fail_if_closed() self._ic.transaction.commit() self.clear() self._get_database() def get_attachment(self, id): """Get :class:`AttachmentInfo` by ID. :param int id: Attachment ID. :returns: :class:`AttachmentInfo` with specified ID or `None`. """ for attachment in self.attachments: if attachment.id == id: return attachment else: return None def get_transaction(self, id): """Get :class:`TransactionInfo` by ID. :param int id: Transaction ID. :returns: :class:`TransactionInfo` with specified ID or `None`. """ for transaction in self.transactions: if transaction.id == id: return transaction else: return None def get_statement(self, id): """Get :class:`StatementInfo` by ID. :param int id: Statement ID. :returns: :class:`StatementInfo` with specified ID or `None`. """ for statement in self.statements: if statement.id == id: return statement else: return None def get_call(self, id): """Get :class:`CallStackInfo` by ID. :param int id: Callstack ID. :returns: :class:`CallStackInfo` with specified ID or `None`. """ for call in self.callstack: if call.id == id: return call else: return None
class AttachmentInfo(BaseInfoItem): "Information about attachment (connection) to database." def __init__(self, monitor, attributes): super(AttachmentInfo, self).__init__(monitor, attributes) self._strip_attribute('MON$ATTACHMENT_NAME') self._strip_attribute('MON$USER') self._strip_attribute('MON$ROLE') self._strip_attribute('MON$REMOTE_PROTOCOL') self._strip_attribute('MON$REMOTE_ADDRESS') self._strip_attribute('MON$REMOTE_PROCESS') self._strip_attribute('MON$CLIENT_VERSION') self._strip_attribute('MON$REMOTE_VERSION') self._strip_attribute('MON$REMOTE_HOST') self._strip_attribute('MON$REMOTE_OS_USER') self._strip_attribute('MON$AUTH_METHOD') #--- Protected def __get_id(self): return self._attributes['MON$ATTACHMENT_ID'] def __get_server_pid(self): return self._attributes['MON$SERVER_PID'] def __get_state(self): return self._attributes['MON$STATE'] def __get_name(self): return self._attributes['MON$ATTACHMENT_NAME'] def __get_user(self): return self._attributes['MON$USER'] def __get_role(self): return self._attributes['MON$ROLE'] def __get_remote_protocol(self): return self._attributes['MON$REMOTE_PROTOCOL'] def __get_remote_address(self): return self._attributes['MON$REMOTE_ADDRESS'] def __get_remote_pid(self): return self._attributes['MON$REMOTE_PID'] def __get_remote_process(self): return self._attributes['MON$REMOTE_PROCESS'] def __get_character_set(self): return self.monitor._con.schema.get_character_set_by_id( self._attributes['MON$CHARACTER_SET_ID']) def __get_timestamp(self): return self._attributes['MON$TIMESTAMP'] def _get_transactions(self): return [ t for t in self.monitor.transactions if t._attributes['MON$ATTACHMENT_ID'] == self.id ] def _get_statements(self): return [ s for s in self.monitor.statements if s._attributes['MON$ATTACHMENT_ID'] == self.id ] def _get_variables(self): return [ s for s in self.monitor.variables if s._attributes['MON$ATTACHMENT_ID'] == self.id ] def __get_iostats(self): for io in self.monitor.iostats: if (io.stat_id == self.stat_id) and (io.group == STAT_ATTACHMENT): return io return None def __get_auth_method(self): return self._attributes.get('MON$AUTH_METHOD') def __get_client_version(self): return self._attributes.get('MON$CLIENT_VERSION') def __get_remote_version(self): return self._attributes.get('MON$REMOTE_VERSION') def __get_remote_os_user(self): return self._attributes.get('MON$REMOTE_OS_USER') def __get_remote_host(self): return self._attributes.get('MON$REMOTE_HOST') def __get_tablestats(self): res = {} for io in self.monitor.tablestats: if (io.stat_id == self.stat_id) and (io.group == STAT_ATTACHMENT): res[io.table_name] = io return res #--- properties id = property(__get_id, None, None, "Attachment ID.") server_pid = property(__get_server_pid, None, None, "Server process ID.") state = property(__get_state, None, None, "Attachment state (idle/active).") name = property(__get_name, None, None, "Database pathname or alias.") user = property(__get_user, None, None, "User name.") role = property(__get_role, None, None, "Role name.") remote_protocol = property(__get_remote_protocol, None, None, "Remote protocol name.") remote_address = property(__get_remote_address, None, None, "Remote address.") remote_pid = property(__get_remote_pid, None, None, "Remote client process ID.") remote_process = property(__get_remote_process, None, None, "Remote client process pathname.") character_set = property( __get_character_set, None, None, ":class:`~fdb.schema.CharacterSet` for this attachment.") timestamp = property(__get_timestamp, None, None, "Attachment date/time.") transactions = LateBindingProperty( _get_transactions, None, None, "List of transactions associated with attachment.\nItems are :class:`TransactionInfo` objects." ) statements = LateBindingProperty( _get_statements, None, None, "List of statements associated with attachment.\nItems are :class:`StatementInfo` objects." ) variables = LateBindingProperty( _get_variables, None, None, "List of variables associated with attachment.\nItems are :class:`ContextVariableInfo` objects." ) iostats = property(__get_iostats, None, None, ":class:`IOStatsInfo` for this object.") # FB 3.0 auth_method = property(__get_auth_method, None, None, "Authentication method.") client_version = property(__get_client_version, None, None, "Client library version.") remote_version = property(__get_remote_version, None, None, "Remote protocol version.") remote_os_user = property(__get_remote_os_user, None, None, "OS user name of client process.") remote_host = property(__get_remote_host, None, None, "Name of remote host.") tablestats = property( __get_tablestats, None, None, "Dictionary of :class:`TableStatsInfo` instances for this object.") #--- Public def isactive(self): "Returns True if attachment is active." return self.state == STATE_ACTIVE def isidle(self): "Returns True if attachment is idle." return self.state == STATE_IDLE def isgcallowed(self): "Returns True if Garbage Collection is enabled for this attachment." return bool(self._attributes['MON$GARBAGE_COLLECTION']) def isinternal(self): "Returns True if attachment is internal system attachment." return bool(self._attributes.get('MON$SYSTEM_FLAG')) def terminate(self): """Terminates client session associated with this attachment. :raises ProgrammingError: If database has ODS lower than 11.2 or this attachement is current session. """ if self.monitor._con.ods < fdb.ODS_FB_25: raise fdb.ProgrammingError("Attachments could be terminated only " \ "for databases with ODS 11.2 and higher.") elif self is self.monitor.this_attachment: raise fdb.ProgrammingError("Can't terminate current session.") else: self.monitor._ic.execute( 'delete from mon$attachments where mon$attachment_id = ?', (self.id, ))