Initial commit: Odoo 18.0-20251222 extra-addons
Some checks failed
pre-commit / pre-commit (push) Has been cancelled
tests / Detect unreleased dependencies (push) Has been cancelled
tests / test with OCB (push) Has been cancelled
tests / test with Odoo (push) Has been cancelled

This commit is contained in:
tocmo0nlord
2026-03-13 20:43:25 +00:00
parent 36e847a7df
commit adbe430761
9472 changed files with 1265727 additions and 0 deletions

151
database_size/README.rst Executable file
View File

@@ -0,0 +1,151 @@
.. image:: https://odoo-community.org/readme-banner-image
:target: https://odoo-community.org/get-involved?utm_source=readme
:alt: Odoo Community Association
=============
Database Size
=============
..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:57fdd3cd5e43a1434f9fe453728520f4dd445d90b738db359d34f40fa7d90328
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fserver--tools-lightgray.png?logo=github
:target: https://github.com/OCA/server-tools/tree/18.0/database_size
:alt: OCA/server-tools
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/server-tools-18-0/server-tools-18-0-database_size
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/server-tools&target_branch=18.0
:alt: Try me on Runboat
|badge1| |badge2| |badge3| |badge4| |badge5|
Monitor the size of your Odoo instance.
**Table of contents**
.. contents::
:local:
Configuration
=============
To configure this module, you can review the scheduled action called
'Take model size measurements' and check the time at which you want it
to run. It should only run once a day. If it runs more often, it just
updates the existing set of sizes for the day.
You may also review the Database Size settings in Odoo's general
settings and enable 'Purge Older Model Size Measurements'. This task
will by default delete most daily data older than a year except for the
data captured on the first day of each month. These retention periods
can be configured here as well.
Usage
=====
You can use this module to keep an eye on the development of the size of
your Odoo instance over time. Every day, a snapshot will be taken with
the full size of the database and the attachments. You can query these
daily snapshots, and you can compare the current size with a size at any
date of the past for which there is data.
Enable debug mode, then go to menu Settings -> Technical -> Database
Size.
|image1|
The data that is gathered and that is displayed are:
- Model Name - The name of the model to which the data is related
- Estimated Rows - The number of estimated rows according to the
Postgresql query planner. For performance reasons, taking the data
from the planner is preferred over doing an actual count, although the
results may be imprecise.
- Bare Table Size - The disk usage of the model table without indexes
etc.
- Index Size - The disk usage of the indexes in the model table.
- Many2many Tables Size - The disk usage of related many2many tables,
including their indexes. To prevent double counts, many2many tables
are only correlated with one of their tables (the largest of the two).
- Attachment Size - The disk usage of the attachments linked to the
model records. Because Odoo will deduplicate attachments by content,
attachments with the same content may be counted double in the
attachment size of other models, but will not be counted double when
linked to records of the same model more than once.
- Total Table Size - Bare Table Size + Index Size
- Total Database Size - Total Table Size + Many2many Tables Size
- Total Model Size - Total Database Size + Attachment Size
If you click on individual records, you can inspect the sizes of each
index and many2many table.
All sizes are in megabytes.
In the 'Compare Size per Model' report view, you can find these data
twice: once for the selected measurement date (default: today), and once
for the selected comparison date (default: one month ago).
|image2|
If you want to compare arbitrary dates, you can start typing the date in
the search box. Be sure to enter the dates in the right format for your
localization.
|image3|
.. |image1| image:: https://raw.githubusercontent.com/OCA/server-tools/18.0/database_size/static/images/model_size.png
.. |image2| image:: https://raw.githubusercontent.com/OCA/server-tools/18.0/database_size/static/images/compare_model_size.png
.. |image3| image:: https://raw.githubusercontent.com/OCA/server-tools/18.0/database_size/static/images/select_date.png
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/server-tools/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/server-tools/issues/new?body=module:%20database_size%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues.
Credits
=======
Authors
-------
* Opener B.V.
Contributors
------------
- Stefan Rijnhart <stefan@opener.amsterdam>
Maintainers
-----------
This module is maintained by the OCA.
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
This module is part of the `OCA/server-tools <https://github.com/OCA/server-tools/tree/18.0/database_size>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

2
database_size/__init__.py Executable file
View File

@@ -0,0 +1,2 @@
from . import models
from . import report

24
database_size/__manifest__.py Executable file
View File

@@ -0,0 +1,24 @@
# Copyright 2025 Opener B.V. <https://opener.amsterdam>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
{
"name": "Database Size",
"version": "18.0.1.0.2",
"author": "Opener B.V.,Odoo Community Association (OCA)",
"website": "https://github.com/OCA/server-tools",
"depends": ["base_setup"],
"license": "AGPL-3",
"category": "Tools",
"data": [
"data/ir_cron_data.xml",
"security/ir.model.access.csv",
"views/ir_model_size_views.xml",
"views/res_config_settings_views.xml",
"report/ir_model_size_report_views.xml",
],
"assets": {
"web.assets_backend": [
"database_size/static/src/scss/list_view_wrap_header.scss",
]
},
"installable": True,
}

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo noupdate="1">
<record id="ir_cron_ir_model_size_measure" model="ir.cron">
<field name="code">model._measure()</field>
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="model_id" ref="model_ir_model_size" />
<field name="name">Take model size measurements</field>
<field name="state">code</field>
</record>
</odoo>

View File

@@ -0,0 +1,397 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * database_size
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: database_size
#: model_terms:ir.ui.view,arch_db:database_size.res_config_settings_view_form
msgid "<span> days</span>"
msgstr ""
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size__attachment_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size_report__attachment_size
msgid "Attachment Size"
msgstr ""
#. module: database_size
#: model:ir.model.fields,help:database_size.field_ir_model_size__attachment_size
msgid ""
"Attachment Size in MB. Includes overlap of files that are also attached to "
"other models."
msgstr ""
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size__table_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size_report__table_size
msgid "Bare Table Size"
msgstr ""
#. module: database_size
#: model:ir.model.fields,help:database_size.field_ir_model_size__table_size
msgid "Bare Table Size in MB."
msgstr ""
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size_report__diff_total_database_size
msgid "Change in Total Database Size"
msgstr ""
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size_report__diff_total_model_size
msgid "Change in Total Model Size"
msgstr ""
#. module: database_size
#: model:ir.actions.act_window,name:database_size.ir_model_size_report_action
msgid "Compare Database Size per Model"
msgstr ""
#. module: database_size
#: model:ir.ui.menu,name:database_size.ir_model_size_report_menu
msgid "Compare Size per Model"
msgstr ""
#. module: database_size
#: model:ir.model,name:database_size.model_res_config_settings
msgid "Config Settings"
msgstr ""
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_index_size__create_uid
#: model:ir.model.fields,field_description:database_size.field_ir_model_relation_size__create_uid
#: model:ir.model.fields,field_description:database_size.field_ir_model_size__create_uid
msgid "Created by"
msgstr ""
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_index_size__create_date
#: model:ir.model.fields,field_description:database_size.field_ir_model_relation_size__create_date
#: model:ir.model.fields,field_description:database_size.field_ir_model_size__create_date
msgid "Created on"
msgstr ""
#. module: database_size
#: model:ir.ui.menu,name:database_size.database_size_menu
#: model_terms:ir.ui.view,arch_db:database_size.res_config_settings_view_form
msgid "Database Size"
msgstr ""
#. module: database_size
#: model:ir.actions.act_window,name:database_size.ir_model_size_action
msgid "Database Size per Model"
msgstr ""
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size__measurement_date
#: model:ir.model.fields,field_description:database_size.field_ir_model_size_report__measurement_date
#: model_terms:ir.ui.view,arch_db:database_size.ir_model_size_view_search
msgid "Date of Measurement"
msgstr ""
#. module: database_size
#: model_terms:ir.ui.view,arch_db:database_size.ir_model_size_report_view_tree
msgid "Details"
msgstr ""
#. module: database_size
#: model:ir.model,name:database_size.model_ir_model_index_size
msgid "Disk space usage of a single index"
msgstr ""
#. module: database_size
#: model:ir.model,name:database_size.model_ir_model_relation_size
msgid "Disk space usage of a single many2many table"
msgstr ""
#. module: database_size
#: model:ir.model,name:database_size.model_ir_model_size
msgid "Disk space usage per model"
msgstr ""
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_index_size__display_name
#: model:ir.model.fields,field_description:database_size.field_ir_model_relation_size__display_name
#: model:ir.model.fields,field_description:database_size.field_ir_model_size__display_name
#: model:ir.model.fields,field_description:database_size.field_ir_model_size_report__display_name
msgid "Display Name"
msgstr ""
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size__tuples
#: model:ir.model.fields,field_description:database_size.field_ir_model_size_report__tuples
msgid "Estimated Rows"
msgstr ""
#. module: database_size
#: model:ir.model.fields,help:database_size.field_ir_model_size__measurement_date
msgid "For the exact time, check the record's write_date."
msgstr ""
#. module: database_size
#: model_terms:ir.ui.view,arch_db:database_size.ir_model_size_view_search
msgid "Group By"
msgstr ""
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size_report__historical_attachment_size
msgid "Historical Attachment Size"
msgstr ""
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size_report__historical_table_size
msgid "Historical Bare Table Size"
msgstr ""
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size_report__historical_measurement_date
msgid "Historical Date of Measurement"
msgstr ""
#. module: database_size
#: model:ir.model,name:database_size.model_ir_model_size_report
msgid "Historical Disk space usage per model"
msgstr ""
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size_report__historical_tuples
msgid "Historical Estimated Rows"
msgstr ""
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size_report__historical_indexes_size
msgid "Historical Index Size"
msgstr ""
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size_report__historical_relations_size
msgid "Historical Many2many Tables Size"
msgstr ""
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size_report__historical_total_database_size
msgid "Historical Total Database Size"
msgstr ""
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size_report__historical_total_model_size
msgid "Historical Total Model Size"
msgstr ""
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size_report__historical_total_table_size
msgid "Historical Total Table Size"
msgstr ""
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_index_size__id
#: model:ir.model.fields,field_description:database_size.field_ir_model_relation_size__id
#: model:ir.model.fields,field_description:database_size.field_ir_model_size__id
#: model:ir.model.fields,field_description:database_size.field_ir_model_size_report__id
msgid "ID"
msgstr ""
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size__indexes_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size_report__indexes_size
msgid "Index Size"
msgstr ""
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size__ir_model_index_size_ids
#: model_terms:ir.ui.view,arch_db:database_size.ir_model_size_view_form
msgid "Indexes"
msgstr ""
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_index_size__ir_model_size_id
#: model:ir.model.fields,field_description:database_size.field_ir_model_relation_size__ir_model_size_id
msgid "Ir Model Size"
msgstr ""
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_res_config_settings__database_size_retention_daily
msgid "Keep Daily Measurements for"
msgstr ""
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_res_config_settings__database_size_retention_monthly
msgid "Keep Monthly Measurements for"
msgstr ""
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_index_size__write_uid
#: model:ir.model.fields,field_description:database_size.field_ir_model_relation_size__write_uid
#: model:ir.model.fields,field_description:database_size.field_ir_model_size__write_uid
msgid "Last Updated by"
msgstr ""
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_index_size__write_date
#: model:ir.model.fields,field_description:database_size.field_ir_model_relation_size__write_date
#: model:ir.model.fields,field_description:database_size.field_ir_model_size__write_date
msgid "Last Updated on"
msgstr ""
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size__relations_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size_report__relations_size
msgid "Many2many Tables Size"
msgstr ""
#. module: database_size
#: model_terms:ir.ui.view,arch_db:database_size.ir_model_size_view_form
msgid "Many2many tables"
msgstr ""
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size__model
#: model:ir.model.fields,field_description:database_size.field_ir_model_size_report__model
#: model_terms:ir.ui.view,arch_db:database_size.ir_model_size_view_search
msgid "Model"
msgstr ""
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size__model_name
#: model:ir.model.fields,field_description:database_size.field_ir_model_size_report__model_name
msgid "Model Name"
msgstr ""
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_index_size__name
#: model:ir.model.fields,field_description:database_size.field_ir_model_relation_size__name
msgid "Name"
msgstr ""
#. module: database_size
#: model_terms:ir.ui.view,arch_db:database_size.ir_model_size_report_view_search
msgid "Notable Change"
msgstr ""
#. module: database_size
#: model_terms:ir.ui.view,arch_db:database_size.ir_model_size_report_view_search
msgid "One Month Ago"
msgstr ""
#. module: database_size
#: model_terms:ir.ui.view,arch_db:database_size.ir_model_size_report_view_search
msgid "One Year Ago"
msgstr ""
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_res_config_settings__database_size_purge
msgid "Purge Older Model Size Measurements"
msgstr ""
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size__ir_model_relation_size_ids
msgid "Relations"
msgstr ""
#. module: database_size
#: model:ir.model.fields,help:database_size.field_ir_model_size__tuples
msgid "Rows in use, including dead tuples"
msgstr ""
#. module: database_size
#: model_terms:ir.ui.view,arch_db:database_size.res_config_settings_view_form
msgid "Set to 0 to keep monthly measurements forever."
msgstr ""
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_index_size__size
#: model:ir.model.fields,field_description:database_size.field_ir_model_relation_size__size
msgid "Size"
msgstr ""
#. module: database_size
#: model:ir.ui.menu,name:database_size.ir_model_size_menu
msgid "Size per Model"
msgstr ""
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_index_size__smart_search
#: model:ir.model.fields,field_description:database_size.field_ir_model_relation_size__smart_search
#: model:ir.model.fields,field_description:database_size.field_ir_model_size__smart_search
#: model:ir.model.fields,field_description:database_size.field_ir_model_size_report__smart_search
#: model:ir.model.fields,field_description:database_size.field_res_config_settings__smart_search
msgid "Smart Search"
msgstr ""
#. module: database_size
#: model:ir.actions.server,name:database_size.ir_cron_ir_model_size_measure_ir_actions_server
msgid "Take model size measurements"
msgstr ""
#. module: database_size
#: model:ir.model.fields,help:database_size.field_res_config_settings__database_size_retention_monthly
msgid ""
"The period of time (in days) during which database size measurmeents are "
"kept of the first day of each month. If set to 0, measurements will be kept "
"forever."
msgstr ""
#. module: database_size
#: model:ir.model.fields,help:database_size.field_res_config_settings__database_size_retention_daily
msgid ""
"The period of time (in days) during which the daily database size "
"measurements are kept. If set to 0, measurements will be kept forever."
msgstr ""
#. module: database_size
#: model:ir.model.constraint,message:database_size.constraint_ir_model_size_uniq_model_measurement_date
msgid "There is already a measurement for this model on the given date"
msgstr ""
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size__total_database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size_report__total_database_size
msgid "Total Database Size"
msgstr ""
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size__total_model_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size_report__total_model_size
msgid "Total Model Size"
msgstr ""
#. module: database_size
#: model:ir.model.fields,help:database_size.field_ir_model_size__total_database_size
msgid "Total Model Size in MB. This includes many2many tables"
msgstr ""
#. module: database_size
#: model:ir.model.fields,help:database_size.field_ir_model_size__indexes_size
msgid "Total Size of Indexes in MB"
msgstr ""
#. module: database_size
#: model:ir.model.fields,help:database_size.field_ir_model_size__relations_size
msgid "Total Size of many2many relations in MB"
msgstr ""
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size__total_table_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size_report__total_table_size
msgid "Total Table Size"
msgstr ""
#. module: database_size
#: model:ir.model.fields,help:database_size.field_ir_model_size__total_table_size
msgid "Total Table Size in MB. This includes indexes and toast tables"
msgstr ""
#. module: database_size
#: model:ir.model.fields,help:database_size.field_ir_model_size__total_model_size
msgid "Total model size in MB. This includes attachments."
msgstr ""

408
database_size/i18n/it.po Executable file
View File

@@ -0,0 +1,408 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * database_size
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-05-29 11:25+0000\n"
"Last-Translator: Sergio Zanchetta <primes2h@gmail.com>\n"
"Language-Team: none\n"
"Language: it\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.10.4\n"
#. module: database_size
#: model_terms:ir.ui.view,arch_db:database_size.res_config_settings_view_form
msgid "<span> days</span>"
msgstr "<span> giorni</span>"
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size__attachment_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size_report__attachment_size
msgid "Attachment Size"
msgstr "Dimensione allegato"
#. module: database_size
#: model:ir.model.fields,help:database_size.field_ir_model_size__attachment_size
msgid ""
"Attachment Size in MB. Includes overlap of files that are also attached to "
"other models."
msgstr ""
"Dimensione allegato in MB. Include la sovrapposizione di file che sono "
"allegati anche ad altri modelli."
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size__table_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size_report__table_size
msgid "Bare Table Size"
msgstr "Dimensione tabella vuota"
#. module: database_size
#: model:ir.model.fields,help:database_size.field_ir_model_size__table_size
msgid "Bare Table Size in MB."
msgstr "Dimensione tabella vuota in MB."
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size_report__diff_total_database_size
msgid "Change in Total Database Size"
msgstr "Modifica della dimensione totale del database"
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size_report__diff_total_model_size
msgid "Change in Total Model Size"
msgstr "Modifica nella dimensione totale del modello"
#. module: database_size
#: model:ir.actions.act_window,name:database_size.ir_model_size_report_action
msgid "Compare Database Size per Model"
msgstr "Compara dimensione database per modello"
#. module: database_size
#: model:ir.ui.menu,name:database_size.ir_model_size_report_menu
msgid "Compare Size per Model"
msgstr "Compara dimensione per modello"
#. module: database_size
#: model:ir.model,name:database_size.model_res_config_settings
msgid "Config Settings"
msgstr "Impostazioni configurazione"
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_index_size__create_uid
#: model:ir.model.fields,field_description:database_size.field_ir_model_relation_size__create_uid
#: model:ir.model.fields,field_description:database_size.field_ir_model_size__create_uid
msgid "Created by"
msgstr "Creato da"
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_index_size__create_date
#: model:ir.model.fields,field_description:database_size.field_ir_model_relation_size__create_date
#: model:ir.model.fields,field_description:database_size.field_ir_model_size__create_date
msgid "Created on"
msgstr "Creato il"
#. module: database_size
#: model:ir.ui.menu,name:database_size.database_size_menu
#: model_terms:ir.ui.view,arch_db:database_size.res_config_settings_view_form
msgid "Database Size"
msgstr "Dimensione database"
#. module: database_size
#: model:ir.actions.act_window,name:database_size.ir_model_size_action
msgid "Database Size per Model"
msgstr "Dimensione database per modello"
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size__measurement_date
#: model:ir.model.fields,field_description:database_size.field_ir_model_size_report__measurement_date
#: model_terms:ir.ui.view,arch_db:database_size.ir_model_size_view_search
msgid "Date of Measurement"
msgstr "Data della misura"
#. module: database_size
#: model_terms:ir.ui.view,arch_db:database_size.ir_model_size_report_view_tree
msgid "Details"
msgstr "Dettagli"
#. module: database_size
#: model:ir.model,name:database_size.model_ir_model_index_size
msgid "Disk space usage of a single index"
msgstr "Utilizzo spazio su disco per singolo indice"
#. module: database_size
#: model:ir.model,name:database_size.model_ir_model_relation_size
msgid "Disk space usage of a single many2many table"
msgstr "Utilizzo spazio su disco per una singola tabella many2many"
#. module: database_size
#: model:ir.model,name:database_size.model_ir_model_size
msgid "Disk space usage per model"
msgstr "Utilizzo spazio su disco per modello"
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_index_size__display_name
#: model:ir.model.fields,field_description:database_size.field_ir_model_relation_size__display_name
#: model:ir.model.fields,field_description:database_size.field_ir_model_size__display_name
#: model:ir.model.fields,field_description:database_size.field_ir_model_size_report__display_name
msgid "Display Name"
msgstr "Nome visualizzato"
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size__tuples
#: model:ir.model.fields,field_description:database_size.field_ir_model_size_report__tuples
msgid "Estimated Rows"
msgstr "Righe stimate"
#. module: database_size
#: model:ir.model.fields,help:database_size.field_ir_model_size__measurement_date
msgid "For the exact time, check the record's write_date."
msgstr "Per l'orario esatto, controllare la write_date del record."
#. module: database_size
#: model_terms:ir.ui.view,arch_db:database_size.ir_model_size_view_search
msgid "Group By"
msgstr "Raggruppa per"
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size_report__historical_attachment_size
msgid "Historical Attachment Size"
msgstr "Cronologia dimensione allegato"
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size_report__historical_table_size
msgid "Historical Bare Table Size"
msgstr "Cronologia dimensione tabella vuota"
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size_report__historical_measurement_date
msgid "Historical Date of Measurement"
msgstr "Cronologia data della misura"
#. module: database_size
#: model:ir.model,name:database_size.model_ir_model_size_report
msgid "Historical Disk space usage per model"
msgstr "Cronologia utilizzo disco per modello"
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size_report__historical_tuples
msgid "Historical Estimated Rows"
msgstr "Cronologica righe stimate"
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size_report__historical_indexes_size
msgid "Historical Index Size"
msgstr "Cronologia dimensione indice"
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size_report__historical_relations_size
msgid "Historical Many2many Tables Size"
msgstr "Cronologia dimensione tabelle many2many"
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size_report__historical_total_database_size
msgid "Historical Total Database Size"
msgstr "Cronologia dimensione totale database"
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size_report__historical_total_model_size
msgid "Historical Total Model Size"
msgstr "Cronologia dimensione totale modello"
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size_report__historical_total_table_size
msgid "Historical Total Table Size"
msgstr "Cronologia dimensione totale tabella"
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_index_size__id
#: model:ir.model.fields,field_description:database_size.field_ir_model_relation_size__id
#: model:ir.model.fields,field_description:database_size.field_ir_model_size__id
#: model:ir.model.fields,field_description:database_size.field_ir_model_size_report__id
msgid "ID"
msgstr "ID"
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size__indexes_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size_report__indexes_size
msgid "Index Size"
msgstr "Dimensione indice"
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size__ir_model_index_size_ids
#: model_terms:ir.ui.view,arch_db:database_size.ir_model_size_view_form
msgid "Indexes"
msgstr "Indici"
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_index_size__ir_model_size_id
#: model:ir.model.fields,field_description:database_size.field_ir_model_relation_size__ir_model_size_id
msgid "Ir Model Size"
msgstr "Dimensione modello ir"
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_res_config_settings__database_size_retention_daily
msgid "Keep Daily Measurements for"
msgstr "Mantieni misure giornaliere per"
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_res_config_settings__database_size_retention_monthly
msgid "Keep Monthly Measurements for"
msgstr "Mantieni misure mensili per"
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_index_size__write_uid
#: model:ir.model.fields,field_description:database_size.field_ir_model_relation_size__write_uid
#: model:ir.model.fields,field_description:database_size.field_ir_model_size__write_uid
msgid "Last Updated by"
msgstr "Ultimo aggiornamento di"
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_index_size__write_date
#: model:ir.model.fields,field_description:database_size.field_ir_model_relation_size__write_date
#: model:ir.model.fields,field_description:database_size.field_ir_model_size__write_date
msgid "Last Updated on"
msgstr "Ultimo aggiornamento il"
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size__relations_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size_report__relations_size
msgid "Many2many Tables Size"
msgstr "Dimensione tabelle many2many"
#. module: database_size
#: model_terms:ir.ui.view,arch_db:database_size.ir_model_size_view_form
msgid "Many2many tables"
msgstr "Tabelle many2many"
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size__model
#: model:ir.model.fields,field_description:database_size.field_ir_model_size_report__model
#: model_terms:ir.ui.view,arch_db:database_size.ir_model_size_view_search
msgid "Model"
msgstr "Modello"
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size__model_name
#: model:ir.model.fields,field_description:database_size.field_ir_model_size_report__model_name
msgid "Model Name"
msgstr "Nome modello"
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_index_size__name
#: model:ir.model.fields,field_description:database_size.field_ir_model_relation_size__name
msgid "Name"
msgstr "Nome"
#. module: database_size
#: model_terms:ir.ui.view,arch_db:database_size.ir_model_size_report_view_search
msgid "Notable Change"
msgstr "Modifica rilevante"
#. module: database_size
#: model_terms:ir.ui.view,arch_db:database_size.ir_model_size_report_view_search
msgid "One Month Ago"
msgstr "Un mese fa"
#. module: database_size
#: model_terms:ir.ui.view,arch_db:database_size.ir_model_size_report_view_search
msgid "One Year Ago"
msgstr "Un anno fa"
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_res_config_settings__database_size_purge
msgid "Purge Older Model Size Measurements"
msgstr "Ripulisci vecchie misure dimensione modello"
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size__ir_model_relation_size_ids
msgid "Relations"
msgstr "Relazioni"
#. module: database_size
#: model:ir.model.fields,help:database_size.field_ir_model_size__tuples
msgid "Rows in use, including dead tuples"
msgstr "Righe in uso incluse tuple inutilizzate"
#. module: database_size
#: model_terms:ir.ui.view,arch_db:database_size.res_config_settings_view_form
msgid "Set to 0 to keep monthly measurements forever."
msgstr "Impostare a 0 per mantenere le misure mensili per sempre."
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_index_size__size
#: model:ir.model.fields,field_description:database_size.field_ir_model_relation_size__size
msgid "Size"
msgstr "Dimensione"
#. module: database_size
#: model:ir.ui.menu,name:database_size.ir_model_size_menu
msgid "Size per Model"
msgstr "Dimensione per modello"
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_index_size__smart_search
#: model:ir.model.fields,field_description:database_size.field_ir_model_relation_size__smart_search
#: model:ir.model.fields,field_description:database_size.field_ir_model_size__smart_search
#: model:ir.model.fields,field_description:database_size.field_ir_model_size_report__smart_search
#: model:ir.model.fields,field_description:database_size.field_res_config_settings__smart_search
msgid "Smart Search"
msgstr "Ricerca intelligente"
#. module: database_size
#: model:ir.actions.server,name:database_size.ir_cron_ir_model_size_measure_ir_actions_server
msgid "Take model size measurements"
msgstr "Rileva le misure della dimensione del modello"
#. module: database_size
#: model:ir.model.fields,help:database_size.field_res_config_settings__database_size_retention_monthly
msgid ""
"The period of time (in days) during which database size measurmeents are "
"kept of the first day of each month. If set to 0, measurements will be kept "
"forever."
msgstr ""
"Periodo di tempo (in giorni) durante il quale vengono conservate le "
"misurazioni delle dimensioni del database, a partire dal primo giorno di "
"ogni mese. Se impostato su 0, le misurazioni verranno conservate per sempre."
#. module: database_size
#: model:ir.model.fields,help:database_size.field_res_config_settings__database_size_retention_daily
msgid ""
"The period of time (in days) during which the daily database size "
"measurements are kept. If set to 0, measurements will be kept forever."
msgstr ""
"Periodo di tempo (in giorni) durante il quale vengono conservate le "
"misurazioni giornaliere delle dimensioni del database. Se impostato su 0, le "
"misurazioni verranno conservate per sempre."
#. module: database_size
#: model:ir.model.constraint,message:database_size.constraint_ir_model_size_uniq_model_measurement_date
msgid "There is already a measurement for this model on the given date"
msgstr "Esiste già una misurazione per questo modello alla data indicata"
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size__total_database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size_report__total_database_size
msgid "Total Database Size"
msgstr "Dimensione totale database"
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size__total_model_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size_report__total_model_size
msgid "Total Model Size"
msgstr "Dimensione totale modello"
#. module: database_size
#: model:ir.model.fields,help:database_size.field_ir_model_size__total_database_size
msgid "Total Model Size in MB. This includes many2many tables"
msgstr "Dimensione totale modello in MB. Include le tabelle many2many"
#. module: database_size
#: model:ir.model.fields,help:database_size.field_ir_model_size__indexes_size
msgid "Total Size of Indexes in MB"
msgstr "Dimensione totale indici in MB"
#. module: database_size
#: model:ir.model.fields,help:database_size.field_ir_model_size__relations_size
msgid "Total Size of many2many relations in MB"
msgstr "Dimensione totale relazioni many2many in MB"
#. module: database_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size__total_table_size
#: model:ir.model.fields,field_description:database_size.field_ir_model_size_report__total_table_size
msgid "Total Table Size"
msgstr "Dimensione totale tabella"
#. module: database_size
#: model:ir.model.fields,help:database_size.field_ir_model_size__total_table_size
msgid "Total Table Size in MB. This includes indexes and toast tables"
msgstr "Dimensione totale tabella in MB. include gli indici e le tabelle toast"
#. module: database_size
#: model:ir.model.fields,help:database_size.field_ir_model_size__total_model_size
msgid "Total model size in MB. This includes attachments."
msgstr "Dimensione totale modello in MB inclusi gli allegati."

View File

@@ -0,0 +1,4 @@
from . import ir_model_size
from . import ir_model_index_size
from . import ir_model_relation_size
from . import res_config_settings

View File

@@ -0,0 +1,18 @@
# Copyright 2025 Opener B.V. <https://opener.amsterdam>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import fields, models
class IrModelIndexSize(models.Model):
_name = "ir.model.index.size"
_description = "Disk space usage of a single index"
_order = "ir_model_size_id desc, size desc"
name = fields.Char(required=True)
ir_model_size_id = fields.Many2one(
comodel_name="ir.model.size",
index=True,
ondelete="cascade",
required=True,
)
size = fields.Integer()

View File

@@ -0,0 +1,18 @@
# Copyright 2025 Opener B.V. <https://opener.amsterdam>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import fields, models
class IrModelRelationSize(models.Model):
_name = "ir.model.relation.size"
_description = "Disk space usage of a single many2many table"
_order = "ir_model_size_id desc, size desc"
name = fields.Char(required=True)
ir_model_size_id = fields.Many2one(
comodel_name="ir.model.size",
index=True,
ondelete="cascade",
required=True,
)
size = fields.Integer()

View File

@@ -0,0 +1,316 @@
# Copyright 2025 Opener B.V. <https://opener.amsterdam>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import logging
from datetime import timedelta
from odoo import api, fields, models
_logger = logging.getLogger(__name__)
class IrModelSize(models.Model):
_name = "ir.model.size"
_description = "Disk space usage per model"
_order = "measurement_date desc, total_model_size desc"
_rec_name = "model"
_sql_constraints = [
(
"uniq_model_measurement_date",
"unique(model, measurement_date)",
"There is already a measurement for this model on the given date",
),
]
model = fields.Char(index=True)
model_name = fields.Char(
compute="_compute_model_name",
store=True,
)
measurement_date = fields.Date(
"Date of Measurement",
help="For the exact time, check the record's write_date.",
required=True,
)
total_model_size = fields.Integer(
compute="_compute_total_sizes",
help="Total model size in MB. This includes attachments.",
store=True,
)
total_database_size = fields.Integer(
compute="_compute_total_sizes",
help="Total Model Size in MB. This includes many2many tables",
store=True,
)
total_table_size = fields.Integer(
help="Total Table Size in MB. This includes indexes and toast tables",
)
table_size = fields.Integer(
string="Bare Table Size",
help="Bare Table Size in MB.",
)
ir_model_index_size_ids = fields.One2many(
comodel_name="ir.model.index.size",
inverse_name="ir_model_size_id",
string="Indexes",
)
ir_model_relation_size_ids = fields.One2many(
comodel_name="ir.model.relation.size",
inverse_name="ir_model_size_id",
string="Relations",
)
indexes_size = fields.Integer(
compute="_compute_indexes_size",
help="Total Size of Indexes in MB",
store=True,
string="Index Size",
)
relations_size = fields.Integer(
compute="_compute_relations_size",
help="Total Size of many2many relations in MB",
store=True,
string="Many2many Tables Size",
)
tuples = fields.Integer(
string="Estimated Rows",
help="Rows in use, including dead tuples",
)
attachment_size = fields.Integer(
help=(
"Attachment Size in MB. Includes overlap of files that are also "
"attached to other models."
),
)
@api.depends("model")
def _compute_model_name(self):
"""Assign the model's label"""
model2name = {
model.model: model.name for model in self.env["ir.model"].sudo().search([])
}
for size in self:
size.model_name = model2name.get(size.model, "<removed>")
@api.model
def read_group(
self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True
):
"""Enforce that grouped results are ordered.
Odoo will happily use the grouping field for ordering unless groupby is a
list, and as it happens the grouping is usually passed as a list, for
example: ['measurement_date:day']
"""
if not orderby and groupby and isinstance(groupby, list | set):
field = groupby[0].split(":")[0]
orderby = f"{field} desc"
return super().read_group(
domain,
fields,
groupby,
offset=offset,
limit=limit,
orderby=orderby,
lazy=lazy,
)
@api.depends(
"total_table_size",
"relations_size",
"attachment_size",
)
def _compute_total_sizes(self):
for size in self:
size.total_database_size = size.total_table_size + size.relations_size
size.total_model_size = size.total_database_size + size.attachment_size
@api.depends("ir_model_index_size_ids", "ir_model_index_size_ids.size")
def _compute_indexes_size(self):
for size in self:
size.indexes_size = sum(size.ir_model_index_size_ids.mapped("size"))
@api.depends("ir_model_relation_size_ids", "ir_model_relation_size_ids.size")
def _compute_relations_size(self):
for size in self:
size.relations_size = sum(size.ir_model_relation_size_ids.mapped("size"))
@staticmethod
def _normalize_size(size):
"""Filter out -1s and compute as MB"""
if not size:
return 0
return int(max(0, size) / (1024 * 1024))
@api.model
def _measure(self):
"""Create the entries for today's report"""
today = fields.Date.context_today(self)
# Remove any previous report for the same day
self.search([("measurement_date", "=", today)]).unlink()
table2model = {}
for model in self.env.values():
if not model._abstract and not model._transient:
model_model = model._name
table2model[model._table] = model_model
model2vals = {
model_model: {
"model": model_model,
"measurement_date": today,
"ir_model_index_size_ids": [],
"ir_model_relation_size_ids": [],
}
for model_model in table2model.values()
}
# Some many2many relation objects are linked explicitely to both models
# involved. To prevent counting them double, we will link them to the
# largest table. Gather all the related models first.
self.env.cr.execute(
"""
select name, array_agg(model)
from ir_model_relation group by name;
"""
)
relation2model = dict(self.env.cr.fetchall())
self.env.cr.execute(
"""
SELECT relname,
reltuples,
pg_total_relation_size (C.oid),
pg_relation_size (C.oid)
FROM pg_class C
LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
WHERE nspname NOT IN (
'information_schema',
'pg_catalog',
'pg_logical',
'pg_toast'
)
AND C.relkind = 'r'
"""
)
# Gather sizes of model tables and many2many tables
rows = self.env.cr.fetchall()
for table, tuples, total_table_size, table_size in rows:
model = table2model.get(table)
if model:
model2vals[model].update(
{
"table_size": self._normalize_size(table_size),
"total_table_size": self._normalize_size(total_table_size),
"tuples": max(tuples, 0),
}
)
# Second pass to throw in the relation tables with the largest relation
for table, _tuples, total_table_size, _table_size in rows:
if table in relation2model:
models = relation2model[table]
model = sorted(
models,
key=lambda model: model2vals.get(model, {"tuples": -99})["tuples"],
reverse=True,
)[0]
vals = model2vals.get(model)
if vals:
vals["ir_model_relation_size_ids"].append(
fields.Command.create(
{
"name": table,
"size": self._normalize_size(total_table_size),
}
)
)
# Gather sizes of indexes
self.env.cr.execute(
"""
SELECT i.relname table_name,
indexrelname index_name,
pg_relation_size(indexrelid) index_size
FROM pg_stat_all_indexes i
JOIN pg_class c ON i.relid=c.oid
WHERE schemaname NOT IN (
'information_schema',
'pg_catalog',
'pg_toast',
'pg_logical'
);
"""
)
for table, index, size in self.env.cr.fetchall():
vals = model2vals.get(table2model.get(table))
if vals:
vals["ir_model_index_size_ids"].append(
fields.Command.create(
{
"name": index,
"size": self._normalize_size(size),
}
)
)
# Gather sizes of attachments. Deduplicate by checksum such that the
# attachment is attributed to the first model it was linked to.
self.env.cr.execute(
"""
with unique_attachments as (
select res_model,
file_size,
row_number() over (partition by checksum order by id) as rowno
from ir_attachment
)
select res_model, sum(file_size)
from unique_attachments
where rowno = 1
group by res_model;
"""
)
for model, size in self.env.cr.fetchall():
vals = model2vals.get(model)
if vals:
vals["attachment_size"] = self._normalize_size(size)
vals_list = [val for val in model2vals.values() if "table_size" in val]
self.create(vals_list)
_logger.info("Created %s model database size records", len(vals_list))
@api.autovacuum
def _purge(self):
"""Remove older model size records, if enabled in the General Settings."""
if (
not self.env["ir.config_parameter"]
.sudo()
.get_param("database_size.purge_enable")
):
return
retention_daily = int(
self.env["ir.config_parameter"]
.sudo()
.get_param("database_size.retention_daily", 366)
)
retention_monthly = int(
self.env["ir.config_parameter"]
.sudo()
.get_param("database_size.retention_monthly", 0)
)
if retention_daily:
cutoff_date = fields.Date.today() - timedelta(days=retention_daily)
self.env.cr.execute(
"""
delete from ir_model_size
where measurement_date < %(cutoff_date)s
and extract(day from measurement_date) != 1;
""",
{"cutoff_date": cutoff_date},
)
_logger.info(
f"Deleted {self.env.cr.rowcount} ir_model_size from before "
f"{cutoff_date} from any other day than the first day of the month."
)
if retention_monthly and retention_monthly > retention_daily:
cutoff_date = fields.Date.today() - timedelta(days=retention_monthly)
self.env.cr.execute(
"""
delete from ir_model_size
where measurement_date < %(cutoff_date)s;
""",
{"cutoff_date": cutoff_date},
)
_logger.info(
f"Deleted {self.env.cr.rowcount} ir_model_size from before "
f"{cutoff_date}."
)

View File

@@ -0,0 +1,32 @@
# Copyright 2025 Opener B.V. <https://opener.amsterdam>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import fields, models
class ResConfigSettings(models.TransientModel):
_inherit = "res.config.settings"
database_size_purge = fields.Boolean(
string="Purge Older Model Size Measurements",
config_parameter="database_size.purge_enable",
)
database_size_retention_daily = fields.Integer(
string="Keep Daily Measurements for",
config_parameter="database_size.retention_daily",
help=(
"The period of time (in days) during which the daily database size "
"measurements are kept. If set to 0, measurements will be kept "
"forever."
),
default="366",
)
database_size_retention_monthly = fields.Integer(
string="Keep Monthly Measurements for",
config_parameter="database_size.retention_monthly",
help=(
"The period of time (in days) during which database size measurmeents "
"are kept of the first day of each month. If set to 0, measurements "
"will be kept forever."
),
default="0",
)

3
database_size/pyproject.toml Executable file
View File

@@ -0,0 +1,3 @@
[build-system]
requires = ["whool"]
build-backend = "whool.buildapi"

View File

@@ -0,0 +1,9 @@
To configure this module, you can review the scheduled action called 'Take model
size measurements' and check the time at which you want it to run. It should
only run once a day. If it runs more often, it just updates the existing set of
sizes for the day.
You may also review the Database Size settings in Odoo's general settings and
enable 'Purge Older Model Size Measurements'. This task will by default delete
most daily data older than a year except for the data captured on the first day
of each month. These retention periods can be configured here as well.

View File

@@ -0,0 +1 @@
- Stefan Rijnhart \<<stefan@opener.amsterdam>\>

View File

@@ -0,0 +1 @@
Monitor the size of your Odoo instance.

33
database_size/readme/USAGE.md Executable file
View File

@@ -0,0 +1,33 @@
You can use this module to keep an eye on the development of the size of your
Odoo instance over time. Every day, a snapshot will be taken with the full size
of the database and the attachments. You can query these daily snapshots, and
you can compare the current size with a size at any date of the past for which
there is data.
Enable debug mode, then go to menu Settings -> Technical -> Database Size.
![image1](https://raw.githubusercontent.com/OCA/server-tools/18.0/database_size/static/images/model_size.png)
The data that is gathered and that is displayed are:
* Model Name - The name of the model to which the data is related
* Estimated Rows - The number of estimated rows according to the Postgresql query planner. For performance reasons, taking the data from the planner is preferred over doing an actual count, although the results may be imprecise.
* Bare Table Size - The disk usage of the model table without indexes etc.
* Index Size - The disk usage of the indexes in the model table.
* Many2many Tables Size - The disk usage of related many2many tables, including their indexes. To prevent double counts, many2many tables are only correlated with one of their tables (the largest of the two).
* Attachment Size - The disk usage of the attachments linked to the model records. Because Odoo will deduplicate attachments by content, attachments with the same content may be counted double in the attachment size of other models, but will not be counted double when linked to records of the same model more than once.
* Total Table Size - Bare Table Size + Index Size
* Total Database Size - Total Table Size + Many2many Tables Size
* Total Model Size - Total Database Size + Attachment Size
If you click on individual records, you can inspect the sizes of each index and many2many table.
All sizes are in megabytes.
In the 'Compare Size per Model' report view, you can find these data twice: once for the selected measurement date (default: today), and once for the selected comparison date (default: one month ago).
![image2](https://raw.githubusercontent.com/OCA/server-tools/18.0/database_size/static/images/compare_model_size.png)
If you want to compare arbitrary dates, you can start typing the date in the search box. Be sure to enter the dates in the right format for your localization.
![image3](https://raw.githubusercontent.com/OCA/server-tools/18.0/database_size/static/images/select_date.png)

View File

@@ -0,0 +1,3 @@
# Copyright 2025 Opener B.V. <https://opener.amsterdam>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import ir_model_size_report

View File

@@ -0,0 +1,232 @@
# Copyright 2025 Opener B.V. <https://opener.amsterdam>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from dateutil.relativedelta import relativedelta
from odoo import api, fields, models
from odoo.exceptions import UserError
class IrModelSizeReport(models.Model):
_name = "ir.model.size.report"
_description = "Historical Disk space usage per model"
_auto = False
_rec_name = "historical_measurement_date"
_order = "historical_measurement_date desc, diff_total_model_size desc"
model = fields.Char()
model_name = fields.Char()
measurement_date = fields.Date("Date of Measurement")
historical_measurement_date = fields.Date("Historical Date of Measurement")
total_model_size = fields.Integer()
historical_total_model_size = fields.Integer()
diff_total_model_size = fields.Integer("Change in Total Model Size")
total_database_size = fields.Integer()
historical_total_database_size = fields.Integer()
diff_total_database_size = fields.Integer("Change in Total Database Size")
total_table_size = fields.Integer()
historical_total_table_size = fields.Integer()
table_size = fields.Integer("Bare Table Size")
historical_table_size = fields.Integer("Historical Bare Table Size")
indexes_size = fields.Integer("Index Size")
historical_indexes_size = fields.Integer("Historical Index Size")
relations_size = fields.Integer("Many2many Tables Size")
historical_relations_size = fields.Integer("Historical Many2many Tables Size")
tuples = fields.Integer("Estimated Rows")
historical_tuples = fields.Integer("Historical Estimated Rows")
attachment_size = fields.Integer()
historical_attachment_size = fields.Integer()
def action_open_model_sizes(self):
"""Open the model_sizes from the report line.
At this point, the 'virtual' report record might not exist anymore
so we fetch the dates from the context.
"""
self.ensure_one()
domain = [
("model", "=", self.model),
(
"measurement_date",
"in",
(
self.env.context.get("measurement_date"),
self.env.context.get("historical_measurement_date"),
),
),
]
action = self.env["ir.actions.actions"]._for_xml_id(
"database_size.ir_model_size_action"
)
action["domain"] = domain
return action
@api.model
def _move_dates_to_context(self, domain):
"""Move the requested comparison date from the domain into the context.
The values in the context will be used when creating the virtual table
in `_table_query`.
"""
new_domain = []
values = {}
for clause in domain or []:
for field in ("measurement_date", "historical_measurement_date"):
if not isinstance(clause, tuple | list) or clause[0] != field:
continue
if field in values:
raise UserError(
self.env._(
f"You cannot search on more than one value for {field} "
"at the same time."
)
)
if clause[1] in ("=", "==") and clause[2]:
values[field] = clause[2]
else:
raise UserError(
self.env._(
f"Searching {field} for '{clause[1]} {clause[2]}' is "
"not supported."
)
)
new_domain.append((1, "=", 1))
else:
new_domain.append(clause)
if values:
self = self.with_context(**values)
return self, new_domain
@api.model
def _where_calc(self, domain, active_test=True):
"""Move the requested dates from the domain into the context"""
(self, new_domain) = self._move_dates_to_context(domain)
return super()._where_calc(new_domain, active_test=active_test)
@api.model
def search(self, domain, offset=0, limit=None, order=None):
"""Move the requested dates from the domain into the context"""
(self, new_domain) = self._move_dates_to_context(domain)
return super().search(new_domain, offset=offset, limit=limit, order=order)
@api.model
def search_count(self, domain, limit=None):
"""Move the requested dates from the domain into the context"""
(self, new_domain) = self._move_dates_to_context(domain)
return super().search_count(new_domain, limit=limit)
@property
def _table_query(self):
"""Report comparative database size changes between two dates.
The dates are inserted in the context in this model's `search` method.
"""
measurement_date = self.env.context.get("measurement_date")
if measurement_date:
measurement_date = fields.Date.to_date(measurement_date)
if not self.env["ir.model.size"].search(
[("measurement_date", "=", measurement_date)],
limit=1,
):
raise UserError(
self.env._(
"There is no data from "
f"{fields.Date.to_string(measurement_date)}"
)
)
else:
# Use the most recent measurement by default
measurement_date = (
self.env["ir.model.size"]
.search([], order="id desc", limit=1)
.measurement_date
)
if not measurement_date:
raise UserError(self.env._("There does not seem to be any data"))
historical_measurement_date = self.env.context.get(
"historical_measurement_date"
)
if historical_measurement_date:
historical_measurement_date = fields.Date.to_date(
historical_measurement_date
)
if not self.env["ir.model.size"].search(
[("measurement_date", "=", historical_measurement_date)],
limit=1,
):
raise UserError(
self.env._(
"There is no data from "
f"{fields.Date.to_string(historical_measurement_date)}"
)
)
else:
# Use last month by default
last_month = measurement_date - relativedelta(months=1)
historical_measurement_date = (
self.env["ir.model.size"]
.search(
[
("measurement_date", ">=", last_month),
("measurement_date", "<", measurement_date),
],
order="measurement_date asc",
limit=1,
)
.measurement_date
)
if not historical_measurement_date:
raise UserError(
self.env._("There does not seem to be enough data to compare")
)
return self.env.cr.mogrify(
"""
select %(measurement_date)s as measurement_date,
%(historical_measurement_date)s as historical_measurement_date,
cur.id as id,
cur.model,
cur.model_name,
cur.total_model_size,
coalesce(hst.total_model_size, 0) as historical_total_model_size,
coalesce(cur.total_model_size) - coalesce(hst.total_model_size, 0)
as diff_total_model_size,
cur.total_database_size,
coalesce(hst.total_database_size, 0) as historical_total_database_size,
coalesce(cur.total_database_size, 0) - coalesce(hst.total_database_size, 0)
as diff_total_database_size,
cur.table_size,
coalesce(hst.table_size, 0) as historical_table_size,
cur.total_table_size,
coalesce(hst.total_table_size, 0) as historical_total_table_size,
cur.indexes_size,
coalesce(hst.indexes_size, 0) as historical_indexes_size,
cur.relations_size,
coalesce(hst.relations_size, 0) as historical_relations_size,
cur.tuples,
coalesce(hst.tuples, 0) as historical_tuples,
cur.attachment_size,
coalesce(hst.attachment_size, 0) as historical_attachment_size
from ir_model_size cur
left join ir_model_size hst
on cur.model = hst.model
and hst.measurement_date = %(historical_measurement_date)s
where cur.measurement_date = %(measurement_date)s
""",
{
"measurement_date": measurement_date,
"historical_measurement_date": historical_measurement_date,
},
).decode("utf-8")

View File

@@ -0,0 +1,82 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="ir_model_size_report_view_search" model="ir.ui.view">
<field name="model">ir.model.size.report</field>
<field name="arch" type="xml">
<search>
<field name="measurement_date" />
<field name="historical_measurement_date" />
<field name="model" />
<separator />
<filter
string="One Year Ago"
name="filter_one_year_ago"
domain="[('historical_measurement_date', '=', (context_today() - relativedelta(years=1)).strftime('%Y-%m-%d'))]"
/>
<filter
string="One Month Ago"
name="filter_one_month_ago"
domain="[('historical_measurement_date', '=', (context_today() - relativedelta(months=1)).strftime('%Y-%m-%d'))]"
/>
<separator />
<filter
string="Notable Change"
name="filter_diff_total_model_size"
domain="[('diff_total_model_size', '>', 5)]"
/>
<separator />
</search>
</field>
</record>
<record id="ir_model_size_report_view_tree" model="ir.ui.view">
<field name="model">ir.model.size.report</field>
<field name="arch" type="xml">
<list
editable="bottom"
create="false"
delete="false"
edit="false"
class="ir_model_size_wrap_header"
>
<field name="model" optional="hide" />
<field name="model_name" />
<field name="historical_measurement_date" />
<field name="measurement_date" optional="hide" />
<button
name="action_open_model_sizes"
type="object"
icon="fa-arrow-right"
title="Details"
context="{'measurement_date': measurement_date, 'historical_measurement_date': historical_measurement_date}"
/>
<field name="historical_total_model_size" optional="hide" />
<field name="total_model_size" optional="hide" />
<field name="diff_total_model_size" />
<field name="historical_total_database_size" optional="hide" />
<field name="total_database_size" optional="hide" />
<field name="diff_total_database_size" />
<field name="historical_table_size" optional="hide" />
<field name="table_size" optional="hide" />
<field name="historical_indexes_size" optional="hide" />
<field name="indexes_size" optional="hide" />
<field name="historical_tuples" optional="hide" />
<field name="tuples" optional="hide" />
<field name="historical_attachment_size" optional="hide" />
<field name="attachment_size" optional="hide" />
</list>
</field>
</record>
<record id="ir_model_size_report_action" model="ir.actions.act_window">
<field name="name">Compare Database Size per Model</field>
<field name="res_model">ir.model.size.report</field>
<field name="view_mode">list</field>
<field name="context">{"search_default_filter_diff_total_model_size": 1}</field>
</record>
<menuitem
action="ir_model_size_report_action"
id="ir_model_size_report_menu"
name="Compare Size per Model"
parent="database_size_menu"
sequence="20"
/>
</odoo>

View File

@@ -0,0 +1,5 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_ir_model_size,access_ir_model_size,model_ir_model_size,base.group_system,1,1,0,0
access_ir_model_index_size,access_ir_model_index_size,model_ir_model_index_size,base.group_system,1,1,0,0
access_ir_model_size_report,access_ir_model_size_report,model_ir_model_size_report,base.group_system,1,0,0,0
access_ir_model_relation_size,access_ir_model_relation_size,model_ir_model_relation_size,base.group_system,1,1,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_ir_model_size access_ir_model_size model_ir_model_size base.group_system 1 1 0 0
3 access_ir_model_index_size access_ir_model_index_size model_ir_model_index_size base.group_system 1 1 0 0
4 access_ir_model_size_report access_ir_model_size_report model_ir_model_size_report base.group_system 1 0 0 0
5 access_ir_model_relation_size access_ir_model_relation_size model_ir_model_relation_size base.group_system 1 1 0 0

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -0,0 +1,487 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils: https://docutils.sourceforge.io/" />
<title>README.rst</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
Despite the name, some widely supported CSS2 features are used.
See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { color: gray; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic, pre.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document">
<a class="reference external image-reference" href="https://odoo-community.org/get-involved?utm_source=readme">
<img alt="Odoo Community Association" src="https://odoo-community.org/readme-banner-image" />
</a>
<div class="section" id="database-size">
<h1>Database Size</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:57fdd3cd5e43a1434f9fe453728520f4dd445d90b738db359d34f40fa7d90328
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/license-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/server-tools/tree/18.0/database_size"><img alt="OCA/server-tools" src="https://img.shields.io/badge/github-OCA%2Fserver--tools-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/server-tools-18-0/server-tools-18-0-database_size"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/server-tools&amp;target_branch=18.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>Monitor the size of your Odoo instance.</p>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#configuration" id="toc-entry-1">Configuration</a></li>
<li><a class="reference internal" href="#usage" id="toc-entry-2">Usage</a></li>
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-3">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="toc-entry-4">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="toc-entry-5">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="toc-entry-6">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="toc-entry-7">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="configuration">
<h2><a class="toc-backref" href="#toc-entry-1">Configuration</a></h2>
<p>To configure this module, you can review the scheduled action called
Take model size measurements and check the time at which you want it
to run. It should only run once a day. If it runs more often, it just
updates the existing set of sizes for the day.</p>
<p>You may also review the Database Size settings in Odoos general
settings and enable Purge Older Model Size Measurements. This task
will by default delete most daily data older than a year except for the
data captured on the first day of each month. These retention periods
can be configured here as well.</p>
</div>
<div class="section" id="usage">
<h2><a class="toc-backref" href="#toc-entry-2">Usage</a></h2>
<p>You can use this module to keep an eye on the development of the size of
your Odoo instance over time. Every day, a snapshot will be taken with
the full size of the database and the attachments. You can query these
daily snapshots, and you can compare the current size with a size at any
date of the past for which there is data.</p>
<p>Enable debug mode, then go to menu Settings -&gt; Technical -&gt; Database
Size.</p>
<p><img alt="image1" src="https://raw.githubusercontent.com/OCA/server-tools/18.0/database_size/static/images/model_size.png" /></p>
<p>The data that is gathered and that is displayed are:</p>
<ul class="simple">
<li>Model Name - The name of the model to which the data is related</li>
<li>Estimated Rows - The number of estimated rows according to the
Postgresql query planner. For performance reasons, taking the data
from the planner is preferred over doing an actual count, although the
results may be imprecise.</li>
<li>Bare Table Size - The disk usage of the model table without indexes
etc.</li>
<li>Index Size - The disk usage of the indexes in the model table.</li>
<li>Many2many Tables Size - The disk usage of related many2many tables,
including their indexes. To prevent double counts, many2many tables
are only correlated with one of their tables (the largest of the two).</li>
<li>Attachment Size - The disk usage of the attachments linked to the
model records. Because Odoo will deduplicate attachments by content,
attachments with the same content may be counted double in the
attachment size of other models, but will not be counted double when
linked to records of the same model more than once.</li>
<li>Total Table Size - Bare Table Size + Index Size</li>
<li>Total Database Size - Total Table Size + Many2many Tables Size</li>
<li>Total Model Size - Total Database Size + Attachment Size</li>
</ul>
<p>If you click on individual records, you can inspect the sizes of each
index and many2many table.</p>
<p>All sizes are in megabytes.</p>
<p>In the Compare Size per Model report view, you can find these data
twice: once for the selected measurement date (default: today), and once
for the selected comparison date (default: one month ago).</p>
<p><img alt="image2" src="https://raw.githubusercontent.com/OCA/server-tools/18.0/database_size/static/images/compare_model_size.png" /></p>
<p>If you want to compare arbitrary dates, you can start typing the date in
the search box. Be sure to enter the dates in the right format for your
localization.</p>
<p><img alt="image3" src="https://raw.githubusercontent.com/OCA/server-tools/18.0/database_size/static/images/select_date.png" /></p>
</div>
<div class="section" id="bug-tracker">
<h2><a class="toc-backref" href="#toc-entry-3">Bug Tracker</a></h2>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/server-tools/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/OCA/server-tools/issues/new?body=module:%20database_size%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h2><a class="toc-backref" href="#toc-entry-4">Credits</a></h2>
<div class="section" id="authors">
<h3><a class="toc-backref" href="#toc-entry-5">Authors</a></h3>
<ul class="simple">
<li>Opener B.V.</li>
</ul>
</div>
<div class="section" id="contributors">
<h3><a class="toc-backref" href="#toc-entry-6">Contributors</a></h3>
<ul class="simple">
<li>Stefan Rijnhart &lt;<a class="reference external" href="mailto:stefan&#64;opener.amsterdam">stefan&#64;opener.amsterdam</a>&gt;</li>
</ul>
</div>
<div class="section" id="maintainers">
<h3><a class="toc-backref" href="#toc-entry-7">Maintainers</a></h3>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org">
<img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" />
</a>
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.</p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/server-tools/tree/18.0/database_size">OCA/server-tools</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div>
</div>
</div>
</div>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@@ -0,0 +1,13 @@
/*
Apply word wrap to the model size list views table headers, and align the
headers of table names to the left. This clarifies the various columns on
these busy list views.
*/
.o_list_view.ir_model_size_wrap_header .o_list_renderer .o_list_table thead th {
span.o_list_number_th {
text-align: left;
}
span {
text-wrap: wrap;
}
}

View File

@@ -0,0 +1 @@
from . import test_database_size

View File

@@ -0,0 +1,217 @@
# Copyright 2025 Opener B.V. <https://opener.amsterdam>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from datetime import timedelta
from freezegun import freeze_time
from odoo import fields
from odoo.exceptions import UserError
from odoo.tests import TransactionCase, tagged
@tagged("-at_install", "post_install")
class TestDatabaseSize(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.partner_model = cls.env["ir.model"].search([("model", "=", "res.partner")])
cls.today = fields.Date.context_today(cls.env.user)
# Remove any data
cls.env.cr.execute("delete from ir_model_size")
def test_database_size(self):
"""Size table is populated and reports can be generated"""
# Remove any data
self.env.cr.execute("delete from ir_model_size")
with self.assertRaisesRegex(UserError, "not.*any data"):
self.env["ir.model.size.report"].search([])
self.env.ref(
"database_size.ir_cron_ir_model_size_measure"
).ir_actions_server_id.run()
# Backdate the data set
self.env.cr.execute(
"""
update ir_model_size
set measurement_date = measurement_date - interval '10 days'
"""
)
self.env["ir.model.size"].invalidate_model(["measurement_date"])
# Generate a new set
self.env.ref(
"database_size.ir_cron_ir_model_size_measure"
).ir_actions_server_id.run()
# Retrieve the comparison
report = self.env["ir.model.size.report"].search(
[
("model", "=", "res.partner"),
("measurement_date", "=", self.today),
("historical_measurement_date", "=", self.today - timedelta(days=10)),
]
)
self.assertTrue(report)
# Run the action to open the details
action = report.action_open_model_sizes()
partner_sizes = self.env["ir.model.size"].search(
[("model", "=", "res.partner")]
)
self.assertEqual(len(partner_sizes), 2)
self.assertEqual(
self.env[action["res_model"]].search(action["domain"]),
partner_sizes,
)
# Test default dates
report2 = self.env["ir.model.size.report"].search(
[("model", "=", "res.partner")]
)
# Default measurement date is the most recent date
self.assertEqual(report2.measurement_date, self.today)
# Default historical measurement date is the most recent date
# within the last month
self.assertEqual(
report2.historical_measurement_date,
self.today - timedelta(days=10),
)
# Test missing data for date
with self.assertRaisesRegex(UserError, "no data from"):
self.env["ir.model.size.report"].search(
[
("model", "=", "res.partner"),
(
"historical_measurement_date",
"=",
self.today - timedelta(days=11),
),
]
)
def test_database_size_report_diff(self):
"""Size report returns the expected values"""
with self.assertRaisesRegex(UserError, "not.*any data"):
self.env["ir.model.size.report"].search([])
self.env.ref(
"database_size.ir_cron_ir_model_size_measure"
).ir_actions_server_id.run()
self.env["ir.model.size"].flush_model()
# Forge some data for the partner model
self.env.cr.execute(
"""
update ir_model_size set
total_database_size = coalesce(total_database_size, 0) + 10,
total_model_size = coalesce(total_model_size, 0) + 5
where model = 'res.partner' and measurement_date = %(self.today)s
returning id;
""",
{"self.today": self.today},
)
# Backdate the data set
self.env.cr.execute(
"""
update ir_model_size
set measurement_date = measurement_date - interval '10 days'
"""
)
# Generate a new set
self.env.ref(
"database_size.ir_cron_ir_model_size_measure"
).ir_actions_server_id.run()
self.env["ir.model.size"].flush_model()
# Forge data for the partner model in the latest data set
self.env.cr.execute(
"""
update ir_model_size set
total_database_size = coalesce(total_database_size, 0) + 15,
total_model_size = coalesce(total_model_size, 0) + 11
where model = 'res.partner' and measurement_date = %(self.today)s
returning id;
""",
{"self.today": self.today},
)
# Retrieve the comparison
report = self.env["ir.model.size.report"].search(
[
("model", "=", "res.partner"),
("measurement_date", "=", self.today),
("historical_measurement_date", "=", self.today - timedelta(days=10)),
]
)
# Size growth is indicated as expected
self.assertEqual(report.diff_total_database_size, 5)
self.assertEqual(report.diff_total_model_size, 6)
def test_database_size_purge(self):
"""Records are purged according to their age"""
def purge(**args):
with freeze_time("2025-01-01"):
self.env["ir.model.size"]._purge()
self.env["ir.model.size"].flush_model()
def create(date):
return self.env["ir.model.size"].create(
{
"model": "__dummy",
"measurement_date": date,
}
)
self.env["ir.config_parameter"].set_param("database_size.purge_enable", False)
self.env["ir.config_parameter"].set_param(
"database_size.retention_daily", False
)
self.env["ir.config_parameter"].set_param(
"database_size.retention_monthly", False
)
record20231201 = create("2023-12-01")
record20231202 = create("2023-12-02")
record20240101 = create("2024-01-01")
record20240102 = create("2024-01-02")
record20241201 = create("2024-12-01")
record20241202 = create("2024-12-02")
# Purging is disabled
purge()
self.assertTrue(record20231202.exists())
# Enable purging
self.env["ir.config_parameter"].set_param("database_size.purge_enable", True)
# By default, entries not on the first date of the month
# and older than a year are purged
purge()
self.assertTrue(record20231201.exists())
self.assertFalse(record20231202.exists())
self.assertTrue(record20240101.exists())
self.assertTrue(record20240102.exists())
self.assertTrue(record20241201.exists())
self.assertTrue(record20241202.exists())
self.env["ir.config_parameter"].set_param("database_size.retention_daily", 32)
purge()
self.assertTrue(record20231201.exists())
self.assertTrue(record20240101.exists())
self.assertFalse(record20240102.exists())
self.assertTrue(record20241201.exists())
self.assertTrue(record20241202.exists())
self.env["ir.config_parameter"].set_param(
"database_size.retention_monthly", 366
)
purge()
self.assertFalse(record20231201.exists())
self.assertTrue(record20240101.exists())
self.assertTrue(record20241201.exists())
self.assertTrue(record20241202.exists())

View File

@@ -0,0 +1,121 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="ir_model_size_view_search" model="ir.ui.view">
<field name="model">ir.model.size</field>
<field name="arch" type="xml">
<search>
<field name="model" />
<group expand="0" string="Group By">
<filter
string="Date of Measurement"
name="group_measurement_date"
context="{'group_by': 'measurement_date:day'}"
/>
<filter
string="Model"
name="group_model"
context="{'group_by': 'model'}"
/>
</group>
</search>
</field>
</record>
<record id="ir_model_size_view_tree" model="ir.ui.view">
<field name="model">ir.model.size</field>
<field name="arch" type="xml">
<list
edit="false"
create="false"
delete="false"
class="ir_model_size_wrap_header"
>
<field name="measurement_date" />
<field name="model" optional="hide" />
<field name="model_name" />
<field name="total_model_size" />
<field name="total_database_size" />
<field name="total_table_size" optional="hide" />
<field name="table_size" optional="hide" />
<field name="indexes_size" optional="hide" />
<field name="relations_size" optional="hide" />
<field name="attachment_size" />
<field name="tuples" />
</list>
</field>
</record>
<record id="ir_model_size_view_form" model="ir.ui.view">
<field name="model">ir.model.size</field>
<field name="arch" type="xml">
<form edit="false" create="false" delete="false">
<sheet>
<div class="oe_title">
<h1>
<field name="measurement_date" style="margin-right: 1em;" />
<field name="model" />
</h1>
</div>
<group>
<group>
<field name="total_model_size" />
<field name="total_database_size" />
<field name="attachment_size" />
<field name="tuples" />
</group>
<group>
<field name="total_table_size" />
<field name="table_size" />
<field name="indexes_size" />
<field name="relations_size" />
</group>
</group>
<notebook>
<page
name="indexes"
string="Indexes"
invisible="not ir_model_index_size_ids"
>
<field name="ir_model_index_size_ids" mode="list">
<list editable="bottom">
<field name="name" />
<field name="size" />
</list>
</field>
</page>
<page
name="relations"
string="Many2many tables"
invisible="not ir_model_relation_size_ids"
>
<field name="ir_model_relation_size_ids" mode="list">
<list editable="bottom">
<field name="name" />
<field name="size" />
</list>
</field>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<record id="ir_model_size_action" model="ir.actions.act_window">
<field name="name">Database Size per Model</field>
<field name="res_model">ir.model.size</field>
<field name="view_mode">list,form</field>
<field name="context">{'search_default_group_measurement_date': 1}</field>
</record>
<menuitem
groups="base.group_system"
id="database_size_menu"
name="Database Size"
parent="base.menu_custom"
sequence="50"
/>
<menuitem
action="ir_model_size_action"
id="ir_model_size_menu"
name="Size per Model"
parent="database_size_menu"
sequence="10"
/>
</odoo>

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="res_config_settings_view_form" model="ir.ui.view">
<field name="name">Database Size Settings</field>
<field name="model">res.config.settings</field>
<field name="inherit_id" ref="base_setup.res_config_settings_view_form" />
<field name="arch" type="xml">
<block name="performance" position="before">
<block title="Database Size" id="database_size">
<setting name="database_size_purge">
<field name="database_size_purge" />
</setting>
<setting
name="database_size_retention_daily"
invisible="not database_size_purge"
>
<field name="database_size_retention_daily" class="oe_inline" />
<span> days</span>
</setting>
<setting
name="database_size_retention_monthly"
invisible="not database_size_purge"
help="Set to 0 to keep monthly measurements forever."
>
<field
name="database_size_retention_monthly"
class="oe_inline"
/>
<span> days</span>
</setting>
</block>
</block>
</field>
</record>
</odoo>