You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

213 lines
8.8 KiB

  1. # -*- coding: utf-8 -*-
  2. ##############################################################################
  3. #
  4. # Author: Alexandre Fayolle
  5. # Copyright 2014 Camptocamp SA
  6. #
  7. # This program is free software: you can redistribute it and/or modify
  8. # it under the terms of the GNU Affero General Public License as
  9. # published by the Free Software Foundation, either version 3 of the
  10. # License, or (at your option) any later version.
  11. #
  12. # This program is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU Affero General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU Affero General Public License
  18. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  19. #
  20. ##############################################################################
  21. import logging
  22. import datetime
  23. from openerp.osv import orm, fields
  24. from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
  25. _logger = logging.getLogger(__name__)
  26. class ModelRowCount(orm.Model):
  27. _name = 'server.monitor.model.row.count'
  28. _columns = {
  29. 'name': fields.text('Table name', readonly=True),
  30. 'count': fields.bigint('row count', readonly=True),
  31. 'measure_id': fields.many2one('server.monitor.database',
  32. 'Measure',
  33. ondelete='cascade',
  34. readonly=True),
  35. 'timestamp': fields.related('measure_id', 'name',
  36. string='timestamp',
  37. type='datetime',
  38. store=True),
  39. }
  40. _order = 'timestamp DESC, count DESC'
  41. class ModelTableSize(orm.Model):
  42. _name = 'server.monitor.model.table.size'
  43. _columns = {
  44. 'name': fields.text('Table name', readonly=True),
  45. 'size': fields.bigint('Size (bytes)', readonly=True),
  46. 'hsize': fields.text('Size', readonly=True),
  47. 'measure_id': fields.many2one('server.monitor.database',
  48. 'Measure',
  49. ondelete='cascade', readonly=True),
  50. 'timestamp': fields.related('measure_id', 'name',
  51. string='timestamp',
  52. type='datetime',
  53. store=True),
  54. }
  55. _order = 'timestamp DESC, size DESC'
  56. class ModelTableActivityRead(orm.Model):
  57. _name = 'server.monitor.model.table.activity.read'
  58. _columns = {
  59. 'name': fields.text('Table name'),
  60. 'disk_reads': fields.bigint('Disk reads (heap blocks)', readonly=True),
  61. 'cache_reads': fields.bigint('Cache reads', readonly=True),
  62. 'total_reads': fields.bigint('Total reads', readonly=True),
  63. 'measure_id': fields.many2one('server.monitor.database',
  64. 'Measure',
  65. ondelete='cascade', readonly=True),
  66. 'timestamp': fields.related('measure_id', 'name',
  67. string='timestamp',
  68. type='datetime',
  69. store=True),
  70. }
  71. _order = 'timestamp DESC, total_reads DESC'
  72. class ModelTableActivityUpdate(orm.Model):
  73. _name = 'server.monitor.model.table.activity.update'
  74. _columns = {
  75. 'name': fields.text('Table name', readonly=True),
  76. 'seq_scan': fields.bigint('Seq scans', readonly=True),
  77. 'idx_scan': fields.bigint('Idx scans', readonly=True),
  78. 'lines_read_total': fields.bigint('Tot lines read', readonly=True),
  79. 'num_insert': fields.bigint('Inserts', readonly=True),
  80. 'num_update': fields.bigint('Updates', readonly=True),
  81. 'num_delete': fields.bigint('Deletes', readonly=True),
  82. 'measure_id': fields.many2one('server.monitor.database',
  83. 'Measure',
  84. ondelete='cascade',
  85. readonly=True),
  86. 'timestamp': fields.related('measure_id', 'name',
  87. string='timestamp',
  88. type='datetime',
  89. store=True),
  90. }
  91. _order = 'timestamp DESC, num_update DESC'
  92. class ServerMonitorDatabase(orm.Model):
  93. _name = 'server.monitor.database'
  94. _columns = {
  95. 'name': fields.datetime('Timestamp', readonly=True),
  96. 'info': fields.text('Information'),
  97. 'table_nb_row_ids': fields.one2many('server.monitor.model.row.count',
  98. 'measure_id',
  99. 'Model row counts',
  100. readonly=True),
  101. 'table_size_ids': fields.one2many('server.monitor.model.table.size',
  102. 'measure_id',
  103. 'Model table size',
  104. readonly=True),
  105. 'table_activity_read_ids': fields.one2many(
  106. 'server.monitor.model.table.activity.read',
  107. 'measure_id',
  108. 'Model table read activity',
  109. readonly=True),
  110. 'table_activity_update_ids': fields.one2many(
  111. 'server.monitor.model.table.activity.update',
  112. 'measure_id',
  113. 'Model table update activity',
  114. readonly=True),
  115. }
  116. _order = 'name DESC'
  117. def _model_row_count(self, cr, uid, context):
  118. res = []
  119. query = ("SELECT schemaname || '.' || relname as name, "
  120. " n_live_tup as count "
  121. "FROM pg_stat_user_tables "
  122. "ORDER BY n_live_tup DESC")
  123. cr.execute(query)
  124. for val in cr.dictfetchall():
  125. res.append((0, 0, val))
  126. return res
  127. def _model_table_size(self, cr, uid, context):
  128. res = []
  129. query = (
  130. "SELECT nspname || '.' || relname AS name, "
  131. " pg_size_pretty(pg_total_relation_size(C.oid)) AS hsize, "
  132. " pg_total_relation_size(C.oid) AS size "
  133. "FROM pg_class C LEFT JOIN pg_namespace N "
  134. " ON (N.oid = C.relnamespace) "
  135. "WHERE nspname NOT IN ('pg_catalog', 'information_schema') "
  136. " AND C.relkind <> 'i' "
  137. " AND nspname !~ '^pg_toast' "
  138. "ORDER BY pg_total_relation_size(C.oid) DESC"
  139. )
  140. cr.execute(query)
  141. for val in cr.dictfetchall():
  142. res.append((0, 0, val))
  143. return res
  144. def _model_table_activity_read(self, cr, uid, context):
  145. res = []
  146. query = ("SELECT schemaname || '.' || relname as name, "
  147. " heap_blks_read as disk_reads, "
  148. " heap_blks_hit as cache_reads, "
  149. " heap_blks_read + heap_blks_hit as total_reads "
  150. "FROM pg_statio_user_tables "
  151. "ORDER BY heap_blks_read + heap_blks_hit DESC"
  152. )
  153. cr.execute(query)
  154. for val in cr.dictfetchall():
  155. res.append((0, 0, val))
  156. return res
  157. def _model_table_activity_update(self, cr, uid, context):
  158. res = []
  159. query = ("SELECT schemaname || '.' || relname as name, "
  160. " seq_scan, "
  161. " idx_scan, "
  162. " idx_tup_fetch + seq_tup_read as lines_read_total, "
  163. " n_tup_ins as num_insert, "
  164. " n_tup_upd as num_update, "
  165. " n_tup_del as num_delete "
  166. "FROM pg_stat_user_tables "
  167. "ORDER BY n_tup_upd + n_tup_ins + n_tup_del desc")
  168. cr.execute(query)
  169. for val in cr.dictfetchall():
  170. res.append((0, 0, val))
  171. return res
  172. _defaults = {
  173. 'name': fields.datetime.now,
  174. 'table_nb_row_ids': _model_row_count,
  175. 'table_size_ids': _model_table_size,
  176. 'table_activity_read_ids': _model_table_activity_read,
  177. 'table_activity_update_ids': _model_table_activity_update,
  178. }
  179. def log_measure(self, cr, uid, context=None):
  180. fields = self._defaults.keys()
  181. defaults = self.default_get(cr, uid, fields, context=context)
  182. self.create(cr, uid, defaults, context=context)
  183. return True
  184. def cleanup(self, cr, uid, age, context=None):
  185. now = datetime.datetime.now()
  186. delta = datetime.timedelta(days=age)
  187. when = (now - delta).strftime(DEFAULT_SERVER_DATETIME_FORMAT)
  188. ids = self.search(cr, uid,
  189. [('name', '<', when)],
  190. context=context)
  191. _logger.debug('Database monitor cleanup: removing %d records',
  192. len(ids))
  193. self.unlink(cr, uid, ids, context=context)