【odoo14】第二十一章、效能優化

老韓頭的碼字生活發表於2021-03-15

通過odoo框架,我們可以開發大型且複雜的應用。良好的效能是實現這一目標的基礎。本章,我們將探討如何提高應用效能。同時,我們也會講解找出影響效能的因素。

本章包含以下內容:

  1. 記錄集的預讀取模式
  2. 將資料在記憶體中快取
  3. 生成不同尺寸的圖片
  4. 訪問組資料
  5. 一次性建立或寫多條資料
  6. 通過資料庫查詢訪問資料
  7. 優化python程式碼

記錄集的預讀取模式

當我們訪問資料時,內部其實是執行了SQL查詢。如果我們在一個多條資料的資料集讀取資料時,由於內部執行了多條SQL語句,這可能導致系統響應會比較慢。本節,我們將探討如何通過預讀取的方式優化效率。通過如下預讀取模式,我們可以減少SQL查詢的數量,進而優化系統效能。

步驟

下面的程式碼是計算函式。在這個方法中,self是包含多條資料的資料集。當我們直接在資料集進行迭代查詢的時候,預取可以完美地工作:

# Correct prefetching
def compute_method(self):
	for rec in self: 
		print(rec.name)

但是在某些場景下,預載入將變得十分複雜。比如,當通過browse方法獲取資料的時候。在下面的例子中,我們通過for迴圈一個個的獲取資料。這時將執行多次的SQL查詢,效率就比較低了:

# Incorrect prefetching
def some_action(self):
	record_ids = []
	self.env.cr.execute("some query to fetch record id") 
    for rec in self.env.cr.fetchall():
	    record = self.env['res.partner'].browse(rec[0]) 
        print(record.name)

通過將ID的列表傳給browse方法,我們可以建立一個包含多條資料的資料集。如下程式碼,預載入將工作的非常完美:

# Correct prefetching
def some_action(self):
    record_ids = []
    self.env.cr.execute("some query to fetch record id")
    record_ids = [rec[0] for rec in self.env.cr.fetchall()]
    recordset = self.env["res.partner"].browse(record_ids)
    for record_id in recordset:
        print(record.name)

這種方式,將在一個SQL查詢的情況下實現預載入。

原理

當我們操作多資料集的時候,通過預載入可以減少SQL查詢的數量。它可以通過一次性獲取所有的資料。通常,預載入是odoo內部自動實現的,但是在某些場景下將失去此特性。比如,我們像如下方式分割資料:

recs = [r for r in recordset r.id not in [1,2,4,10]]

由於我們將資料集拆分成幾部分,因此odoo內部將無法實現一次性預載入。

通過正確的預載入可以大幅提高物件-關係對映(Object-Relational Mapping,ORM)的效能。當我們通過for迴圈迭代資料集的時候,在第一次迭代訪問欄位值的時候,預載入將發揮其魔力。預載入將載入資料集所有的資料。後續,我們再迭代的時候將直接通過從快取讀取。這可以將SQL查詢的複雜的由O(n)降至O(1)。

我們假設資料集有10條資料。當我們在第一次迴圈中獲取name欄位的值,他將獲取10條資料。並不只是name欄位,而是10條資料的所有欄位。後續迭代的資料將直接從快取獲取。複雜度將由10降至1。

for record in recordset: # recordset with 10 records 
	record.name # Prefetch data of all 10 records in the first
loop
	record.email # data of email will be served from the cache.

預載入可以獲取除*2many欄位以外的欄位的值。即便某些欄位在迴圈體內並未使用。因為載入多餘的欄位所帶來的效能影響遠小於額外的查詢。

小貼士
有時,預載入會降低效能。這時,我們可以通過recordset.with_context(prefetch_fields=Flase)禁用預載入。

預載入機制使用的是環境記憶體儲存和檢索記錄值。這就意味著,一旦資料從資料庫檢索出來,後續所有的資料都將從快取查詢。我們可以通過env.cache獲取環境快取。我們可以使用invalidate_cache()函式禁用快取。

更多

如果我們分隔了資料集,ORM將重新生成帶有新的預載入上下文的資料集。這時,這些資料集將僅載入其自身代表的資料的內容。如果我們打算在預載入後載入所有的資料,我們可以使用with_pretetch()函式。在下面的例子中,我們將資料集分割為兩部分。我們在兩個記錄集中都傳遞了一個通用的預取上下文,所以當你從其中一個記錄中獲取資料時,ORM會為另一個獲取資料並將資料放入快取中以備將來使用:

recordset = ... # assume recordset has 10 records. 
recordset1 = recordset[:5].with_prefetch(recordset._ids)
recordset2 = recordset[5:].with_prefetch(recordset._ids)

預取上下文不限於拆分記錄集。您也可以使用with_ prefetch()方法在多個記錄集之間擁有一個公共的預取上下文。這意味著當您從一條記錄中獲取資料時,它也會為所有其他記錄集獲取資料。

將資料在記憶體中快取

odoo框架提供了ormcache裝飾器管理記憶體快取。本節,我們將探討如何管理快取。

步驟

ORM快取類定義在/odoo/tools/cache.py中。
引入檔案:

from odoo import tools

ormcache

這是最常用的快取裝飾器。您需要傳遞方法輸出所依賴的引數名。下面是一個帶有ormcache裝飾器的示例方法:

@tools.ormcache('mode')
def fetch_mode_data(self, mode):
	# some calculations
	return result

當我們首次呼叫該函式的時候,將會返回計算值。ormcache將會儲存mode的值及result的值。如果我們再次呼叫該函式,且mode的值為之前存在的值時,將直接返回result的值。

有時,我們的函式依賴於環境屬性。比如:

@tools.ormcache('self.env.uid', 'mode')
def fetch_data(self, mode):
	# some calculations
	return result

該函式將根據當前使用者及mode的值儲存result的值。

ormcache_context

該裝飾器與ormcache類似,不同的是它依賴於引數和上下文中的值。我們需要傳入引數名稱及上線文鍵的列表。例如,我們的輸出依賴於上下文的lang及website_id,如下:

@tools.ormcache_context('mode', keys=('website_id', 'lang'))
def fetch_data(self, mode):
	# some calculations
	return result

該快取將依賴於mode及context中的值

ormcache_multi

有些方法對多個記錄或id執行操作。如果你想在這些方法上新增快取,你可以使用ormcache_multi裝飾器。您需要傳遞multi引數,在方法呼叫期間,ORM將通過迭代該引數生成快取鍵。在這個方法中,您將需要以字典格式返回結果,並以multi引數的元素作為鍵。看看下面的例子:

@tools.ormcache_multi('mode', multi='ids') 
def fetch_data(self, mode, ids):
    result = {} 
    for i in ids:
        data = ... # some calculation based on ids
        result[i] = data 
    return result

假設我們用[1,2,3]作為id呼叫前面的方法。該方法將返回一個結果{1:…2:…3:…}格式。ORM將基於這些鍵快取結果。如果你用[1,2,3,4,5]作為ID進行另一個呼叫,你的方法將接收[4,5]作為ID引數,所以方法將執行4和5個ID的操作,其餘的結果將從快取中提供。

原理

ORM快取以字典的形式儲存快取(快取查詢)。該快取的鍵將基於裝飾方法的簽名生成,結果將是值。簡單地說,當您使用x, y引數呼叫方法時,方法的結果是x+y,快取查詢將是{(x, y): x+y}。這意味著下次使用相同的引數呼叫該方法時,結果將直接從該快取中提供。這節省了計算時間,使響應更快。
ORM快取是一個記憶體快取,所以它被儲存在RAM中並佔用記憶體。不要使用ormcache來提供大型資料,例如影像或檔案。

警告
使用此裝飾器的方法永遠不應返回記錄集。如果您這樣做,它們將生成psycopg2.OperationalError,因為記錄集的基礎遊標已關閉。

你應該在純函式上使用ORM快取。純函式是指對於相同的引數總是返回相同結果的方法。這些方法的輸出僅取決於引數,因此它們返回相同的結果。如果不是這種情況,則需要在執行使快取狀態無效的操作時手動清除快取。要清除快取,呼叫clear_cache()方法:

self.env[model_name].clear_caches()

一旦清除了快取,下一個對方法的呼叫將執行該方法並將結果儲存在快取中,所有具有相同引數的後續方法呼叫都將從快取中提供服務。

更多

ORM快取是Least Recently
Used
(LRU),這意味著如果一個鍵在快取中不經常使用,它將被刪除。如果你沒有正確地使用ORM快取,它可能弊大於利。例如,如果在方法中引數總是不同的,那麼每次Odoo都會先在快取中查詢,然後呼叫方法來計算。如果你想了解你的快取是如何執行的,你可以把SIGUSR1訊號傳遞給Odoo程式:

kill -SIGUSER1 496

其中,496為程式號。執行命令後,可以在日誌中看到ORM快取的狀態。

快取中的百分比是命中率。它是在快取中找到結果的成功率。如果快取的命中率太低,你應該從方法中刪除ORM快取。

生成不同尺寸的圖片

大圖片對任何網站來說都是麻煩的。它們增加了網頁的大小,結果使網頁變慢。這就導致了不好的SEO排名和訪問者流失。本節,我們將探索如何建立不同大小的影像;通過使用正確的影像,您可以減少網頁大小和改善頁面載入時間。

步驟

您將需要繼承image.mixin。如下:

class LibraryBook(models.Model):
	_name = 'library.book'
	_description = 'Library Book'
	_inherit = ['image.mixin']

mixin模型將自動新增5個欄位,用於儲存不同大小的圖片。

步驟

image.mixin例項將自動向模型新增5個新的二進位制欄位。每個欄位儲存具有不同解析度的影像。以下是這些領域及其解決方案的列表:

  • image_1920: 1,920x1,920
  • image_1024: 1,024x1,024
  • image_512: 512x1,512
  • image_256: 256x256
  • image_128: 128x128
    在這裡給出的所有欄位中,只有image_1920是可編輯的。其他影像欄位是隻讀的,當您更改image_1920欄位時,它們會自動更新。因此,在模型的後端表單檢視中,您需要使用image_1920欄位來允許使用者上傳影像。但這樣做,我們在表單檢視中載入大image_1920影像。但是,有一種方法可以提高效能,即在form檢視中使用image_1920影像,但是顯示較小的影像。例如,我們可以使用image_1920欄位,但顯示image_128欄位。要做到這一點,你可以使用以下語法:
 <field name="image_1920" widget="image" options="{'preview_image': 'image_128'}" />

將影像儲存到欄位後,Odoo會自動調整影像的大小並將其儲存到相應的欄位中。form檢視將顯示轉換後的image_128,因為我們使用它作為preview_image。

小貼士
image.mixin模型是AbstractModel,所以它的表不在資料庫中。為了使用它,您需要在模型中繼承它。

image.mixin,您可以儲存的影像的最大解析度為1920 × 1920。如果您儲存的影像解析度高於1920 x1920, Odoo會將其降低為1920 x1920。在這樣做的同時,Odoo還將保留影像的解析度,避免任何失真。例如,如果您上傳的影像解析度為2,400x1,600,那麼image_1920欄位的解析度將為1,920x1,280。

更多

image.mixin,你可以獲得特定解析度的影像,但是如果你想使用另一種解析度的影像呢?為此,您可以使用二進位制包裝欄位影像,如下面的示例所示:

image_1500 = fields.Image("Image 1500", max_width=1500, max_ height=1500)

這將建立一個新的image_1500欄位,儲存影像將把它的解析度調整為1500 x1500。注意,這不是image.mixin的一部分。它只是將影像縮小為1,500x1,500,因此需要在form檢視中新增該欄位;編輯它不會改變image.mixin中的其他影像欄位。如果您想將其與現有的影像連結。在欄位定義中新增related="image_1920"屬性。

訪問組資料

當需要用於統計的資料時,通常需要以分組的形式提供資料,例如月度銷售報告,或者顯示每個客戶銷售額的報告。手動搜尋記錄並將它們分組是很耗時的。在這篇文章中,我們將探討如何使用read_group()方法訪問分組資料。

步驟

小貼士
read_group()方法廣泛用於統計和智慧統計按鈕。

  1. 讓我們假設您想要在合作伙伴表單上顯示銷售訂單的數量。這可以通過搜尋客戶的銷售訂單,然後計算長度來實現:
# in res.partner model
so_count = fields.Integer(compute='_compute_so_count', string='Sale order count')
def _compute_so_count(self):
    sale_orders = self.env['sale.order'].search(domain=[('partner_id', 'in', self.ids)])
     for partner in self:
        partner.so_count = len(sale_orders.filtered(lambda so: so.partner_id.id == partner.id))

前面的示例可以工作,但不是最優的。當您在樹檢視上顯示so_count欄位時,它將獲取並過濾列表中所有合作伙伴的銷售訂單。對於這樣少量的資料,read_group()方法不會產生太大的影響,但隨著資料量的增長,它可能會成為一個問題。要修復這個問題,可以使用read_ group方法。

  1. 下面的示例與前面的示例相同,但它只使用一個SQL查詢,即使是大型資料集:
# in res.partner model
so_count = fields.Integer(compute='_compute_so_count', string='Sale order count')
def _compute_so_count(self):
    sale_data = self.env['sale.order'].read_group(domain=[('partner_id', 'in', self.ids)],fields=['partner_id'], groupby=['partner_id']) 
    mapped_data = dict([(m['partner_id'][0], m['partner_id_count']) for m in sale_data])
        for partner in self:
            partner.so_count = mapped_data[partner.id]

前面的程式碼片段是優化的,因為它直接通過SQL的group BYfeature獲取銷售訂單計數。

原理

read_group()方法在內部使用SQL的GROUP BY特性。這使得read_group方法更快,即使你有大的資料集。在內部,Odoo web客戶端在圖表和分組樹檢視中使用這種方法。您可以通過使用不同的引數來調整read_group方法的行為。
讓我們來研究read_group方法:

def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True):

read_group方法可用的不同引數如下:

  • domain: 這用於過濾記錄。這將是read_group方法的搜尋條件。
  • fields: 這是要在分組中獲取的欄位列表。注意,這裡提到的欄位應該在groupby引數中,除非您使用一些聚合函式。read_group方法支援SQL聚合函式。假設您想要得到每個客戶的平均訂單量。在這種情況下,可以使用read_group,如下所示:
self.env['sale.order'].read_group([], ['partner_id', 'amount_total:avg'], ['partner_id'])

如果您想兩次訪問同一個欄位,但使用不同的聚合函式,則語法略有不同。您需要將欄位名作為別alias:agg(field_ name)。這個例子會給你每個客戶的總訂單數和平均訂單數:

self.env['sale.order'].read_group([], ['partner_id',
'total:sum(amount_total)', 'avg_total:avg(amount_ total)'], ['partner_id'])
  • groupby: 該引數將是記錄分組的欄位列表。它允許您基於多個欄位對記錄進行分組。為此,您需要傳遞一個欄位列表。例如,如果您想按客戶和訂單狀態對銷售訂單進行分組,可以在此引數中傳遞['partner_id ', 'state']。
  • offset: 此引數用於分頁。如果要跳過幾條記錄,可以使用此引數。
  • limit: 此引數用於分頁;它表示可以獲取的最大記錄數。
  • lazy: 這個引數接受布林值。預設值為True。如果該引數為真,則只根據groupby引數中的第一個欄位對結果進行分組。您將在__context中獲得剩餘的groupby引數和域,並在結果中獲得__domain鍵。如果該引數的值設定為False,它將根據groupby引數中的所有欄位對資料進行分組。

更多

根據日期欄位進行分組可能比較複雜,因為可以根據天、周、季度、月或年對記錄進行分組。您可以通過在groupby引數的:後面傳遞groupby_function來更改date欄位的分組行為。如果你想把每月的銷售訂單總數分組,你可以使用read_group方法:

日期分組的可能選項有日、周、月、季和年。

參考

如果您想了解更多關於PostgreSQL聚合函式的資訊,請參考文件:https://www.postgresql.org/docs/current/functions-aggregate.html

一次性建立或寫多條資料

如果您是Odoo開發新手,您可能會執行多個查詢來編寫或建立多個記錄。本節,我們將瞭解如何批量建立和寫入記錄。

步驟

建立多個記錄並在多個記錄上寫入資料對於每個記錄的工作原理是不同的。讓我們逐個看一下這些記錄。

建立多資料

Odoo支援批量建立記錄。如果要建立單個記錄,只需傳遞一個包含欄位值的字典。要在批處理中建立記錄,您只需要傳遞這些字典的列表,而不是單個字典。下面的示例在一個create呼叫中建立三條圖書記錄:

vals = [
    {
        "name": "Book1",
        "date_release": "2018/12/12",
    },
    {
        "name": "Book2",
        "date_release": "2018/12/12",
    },
    {
        "name": "Book3",
        "date_release": "2018/12/12",
    },
]
self.env["library.book"].create(vals)

寫多條資料

如果您正在處理Odoo的多個版本,那麼您應該意識到write方法在底層是如何工作的。在版本13中,Odoo寫方式有所不同。它使用一種延遲的更新方法,這意味著它不會立即將資料寫入資料庫。Odoo只在必要時或呼叫flush()時才將資料寫入資料庫。
如下:

# Example 1
data = {...}
for record in recordset:
    record.write(data)
# Example 2
data = {...} 
recordset.write(data)

如果您使用的是Odoo v13或以上版本,那麼就不會有任何效能方面的問題。但是,如果您使用的是較舊的版本,則第二個示例將比第一個示例快得多,因為第一個示例將在每次迭代中執行一個SQL查詢。

原理

為了在批處理中建立多個記錄,您需要以列表的形式傳遞值字典來建立新記錄。這將自動管理批量建立記錄。當您在批處理中建立記錄時,在內部這樣做將為每個記錄插入一個查詢。這意味著在批處理中建立記錄不是在單個查詢中完成的。然而,這並不意味著批量建立記錄不能提高效能。通過批量計算獲得效能增益。
write方法的工作方式有所不同。大多數事情都由框架自動處理。例如,如果在所有記錄上寫入相同的資料,則只需要一個UPDATE查詢就可以更新資料庫。如果你在同一個事務中一次又一次地更新相同的記錄,框架甚至會處理它,如下所示:

recordset.name= 'Admin'
recordset.email= 'admin@example.com'
recordset.name= 'Administrator'
recordset.email= 'admin-2@example.com'

在前面的程式碼片段中,對於write只執行一個查詢,其最終值為name=Administrator和email=admin-2@example.com。這不會對效能造成不好的影響,因為分配的值在快取中,稍後會在單個查詢中寫入。

如果在兩者之間使用flush()方法,情況就不一樣了,如下面的示例所示:

recordset.name= 'Admin'
recordset.email= 'admin@example.com'
recordset.flush()
recordset.name= 'Administrator'
recordset.email= 'admin-2@example.com'

flush()方法的作用是:將快取中的值更新到資料庫。因此,在前面的示例中,將執行兩個更新查詢;一個查詢在重新整理之前有資料,第二個查詢在重新整理之後有資料。

更多

延遲的更新僅適用於Odoo版本13,如果您使用的是較舊的版本,那麼寫入單個值將立即執行更新查詢。檢查以下示例來探索老版本Odoo的寫操作的正確用法:

# incorrect usage
recordset.name= 'Admin' recordset.email= 'admin@example.com'
# correct usage
recordset.write({'name': 'Admin', 'email'= 'admin@example. com'})

在第一個示例中,我們有兩個UPDATE查詢,而第二個示例只有一個更新查詢。

通過資料庫查詢訪問資料

Odoo ORM的方法有限,有時很難從ORM中獲取某些資料。在這種情況下,您可以按照需要的格式獲取資料,並且需要對資料進行操作才能得到特定的結果。因此,它會變慢。為了處理這些特殊情況,您可以在資料庫中執行SQL查詢。在這個食譜中,我們將探索如何從Odoo執行SQL查詢。

步驟

使用self._cr.execute方法

  1. 新增程式碼
self.flush()
self._cr.execute("SELECT id, name, date_release FROM library_book WHERE name ilike %s", ('%odoo%',))
data = self._cr.fetchall()
print(data)
Output:
[(7, 'Odoo basics', datetime.date(2018, 2, 15)), (8, 'Odoo 11 Development Cookbook', datetime.date(2018, 2, 15)), (1, 'Odoo 12 Development Cookbook', datetime. date(2019, 2, 13))]
  1. 查詢的結果將以元組列表的形式出現。元組中的資料與查詢中的欄位的順序相同。如果你想獲取字典格式的資料,你可以使用dictfetchall()方法。看看下面的例子:
self.flush()
self._cr.execute("SELECT id, name, date_release FROM library_book WHERE name ilike %s", ('%odoo%',))
data = self._cr.dictfetchall()
print(data)
Output:
[{'id': 7, 'name': 'Odoo basics', 'date_release': datetime.date(2018, 2, 15)}, {'id': 8, 'name': 'Odoo 11 Development Cookbook', 'date_release': datetime. date(2018, 2, 15)}, {'id': 1, 'name': 'Odoo 12 Development Cookbook', 'date_release': datetime. date(2019, 2, 13)}]

如果你只想獲取一條記錄,你可以使用fetchone()和dictfetchone()方法。這些方法的工作原理類似於fetchall()和dictfetchall(),但它們只返回一條記錄,如果您想獲取多條記錄,則需要多次呼叫fetchone()和dictfetchone()方法。

原理

有兩種方法可以從記錄集訪問資料庫遊標:一種是從記錄集本身,如self._cr,另一個來自環境,特別是self.env.cr。此遊標用於執行資料庫查詢。在前面的示例中,我們看到了如何通過原始查詢獲取資料。表名是替換後的型號名稱。用_表示庫。圖書模型變成了library_book。
如果你注意到了,我們在執行查詢之前使用了self.flush()。這背後的原因是Odoo過度使用快取,資料庫可能沒有正確的值。self.flush()會將所有延遲的更新推送到資料庫,並執行所有相關的計算,這樣你就會從資料庫中獲得正確的值。flush()方法還支援一些引數,這些引數可以幫助您控制資料庫中正在重新整理的內容。引數說明如下:

  • fname: 要重新整理到資料庫的欄位列表。
  • records: 要重新整理到資料庫的資料集。

如果您正在執行INSERT或UPDATE查詢,您還需要在執行查詢之後執行flush(),因為ORM可能不知道您所做的更改,而且它可能已經快取了記錄。
在執行原始查詢之前,需要考慮一些事情。只有在別無選擇時才使用原始查詢。通過執行原始查詢,您繞過了ORM層。因此,您也繞過了安全規則和ORM的效能優勢。有時,錯誤構建的查詢可能會引入SQL隱碼攻擊漏洞。考慮以下示例,其中查詢可能允許攻擊者執行SQL隱碼攻擊:

# very bad, SQL injection possible
self.env.cr.execute('SELECT id, name FROM library_book WHERE name ilike + search_keyword + ';')
# good
self.env.cr.execute('SELECT id, name FROM library_book WHERE name ilike %s ';', (search_keyword,))

也不要使用字串格式函式;它還允許攻擊者執行SQL隱碼攻擊。使用SQL查詢會使其他開發人員難以閱讀和理解您的程式碼,所以儘可能避免使用它們。

資訊
許多Odoo開發人員認為,執行SQL查詢可以使操作更快,因為它繞過了ORM層。然而,這並不完全正確;這要看具體情況。在大多數操作中,ORM比原始查詢執行得更好更快,因為資料是從記錄集快取中提供的。

更多

在一個事務中完成的操作只在事務結束時提交。如果ORM中發生錯誤,事務將回滾。如果你已經做了一個插入或更新查詢,並且你想讓它永久存在,你可以使用self._cr.commit()來提交更改。

小貼士
注意,使用commit()可能是危險的,因為它可能將記錄置於不一致的狀態。ORM中的錯誤可能會導致不完全回滾,所以只有在你完全確定自己在做什麼時才使用commit()。

如果使用了commit()方法,那麼之後就不需要使用flush()。commit()方法在內部重新整理環境。

優化python程式碼

有時,你將無法查明問題的原因。在效能問題上尤其如此。Odoo提供了一些內建的分析工具,可以幫助您找到問題的真正原因。

步驟

  1. odoo的分析器可以在Odoo /tools/profiler.py上找到。為了在你的程式碼中使用profiler,請將它匯入檔案:
from odoo.tools.profiler import profiler
  1. 匯入之後,您可以在這些方法上使用概要檔案裝飾器。要對一個特定的方法進行概要分析,您需要向它新增概要分析裝飾器。看一下下面的例子。我們把概要檔案裝飾器放在make_available方法中:
@profile
def make_available(self):
    if self.state != 'lost': 
        self.write({'state': 'available'})
    return True
  1. 當這個方法被呼叫時,它將在日誌中列印完整的統計資訊:

原理

在方法上新增profile裝飾器之後,當您呼叫該方法時,Odoo將在日誌中列印完整的統計資訊,如前面的示例所示。它將以三列的形式列印統計資料。第一列將包含呼叫的次數或執行一行的次數。(當該行處於for迴圈中或方法是遞迴的時候,這個數字會增加。)第二列表示用給定行觸發的查詢的數量。最後一列是給定行所花費的時間,以毫秒為單位。注意,此列中顯示的時間是相對的;當分析器關閉時,它會更快。
分析器裝飾器接受一些可選引數,這些引數可以幫助您獲得該方法的詳細統計資訊。下面是profiler裝飾器的定義:

def profile(method=None, whitelist=None, blacklist=(None,), files=None, minimum_time=0, minimum_queries=0):

下面是profile()方法支援的引數列表:

  • whiltelist: 在日誌中顯示的模型名稱列表。
  • files: 要顯示的檔名列表。
  • blacklist: 不希望在日誌中顯示的模型名稱列表。
  • minimum_time: 這將接受一個整數值(以毫秒為單位)。它將隱藏總時間小於給定時間的日誌。
  • minimum_queries: 這將接受查詢數的整數值。它將隱藏查詢總數小於給定數量的日誌。

更多

Odoo中還有一種型別的分析器可以為執行的方法生成圖表。這個分析器可以在misc包中找到,所以您需要從那裡匯入它。它將生成一個帶有統計資料的檔案,該資料將生成一個圖形檔案。要使用這個分析器,您需要將檔案路徑作為引數傳遞。當這個函式被呼叫時,它將在給定的位置生成一個檔案。看一下下面的示例,它生成make_available.prof檔案:

from odoo.tools.misc import profile
... 
@profile('/Users/parth/Desktop/make_available.profile') 
def make_available(self):
    if self.state != 'lost':
        self.write({'state': 'available'})
        self.env['res.partner'].create({'name': 'test', 'email': 'test@ada.asd'})
    return True

當呼叫make_available方法時,它將在桌面上生成一個檔案。要將此資料轉換為圖形資料,您需要安裝gprof2dot工具,然後執行以下命令生成圖形:

gprof2dot -f pstats -o /Users/parth/Desktop/prof.xdot /Users/ parth/Desktop/make_available.profile

這個命令將在桌面上生成prof.xdot檔案。然後,您可以使用以下命令用xdot顯示圖形:

xdot /Users/parth/Desktop/prof.xdot

使用上述xdot命令將生成如下圖所示的圖形:

相關文章