内存分配测试¶
该 allocations 文件夹包含特殊的 mochitests,旨在记录有关 DevTools 内存使用情况的数据。这使用了 Spidermonkey 的内存 API,该 API 在调试器 API 旁边实现。有关更多信息,请参阅以下文档: https://searchfox.org/mozilla-central/source/js/src/doc/Debugger/Debugger.Memory.md
测试示例¶
add_task(async function() {
// Execute preliminary setup in order to be able to run your scenario
// You would typicaly load modules, open a tab, a toolbox, ...
...
// Run the test scenario first before recording in order to load all the
// modules. Otherwise they get reported as "still allocated" objects,
// whereas we do expect them to be kept in memory as they are loaded via
// the main DevTools loader, which keeps the module loaded until the
// shutdown of Firefox
await testScript();
// Pass alsoRecordContentProcess if you want to record the content process
// of the current tab. Otherwise it will only record parent process objects.
await startRecordingAllocations({ alsoRecordContentProcess: true });
// Now, run the test script. This time, we record this run.
await testScript(toolbox);
// This will stop the record and also publish the results to Talos database
// Second argument will be the name of the test displayed in Talos.
// Many tests will be recorded, but all of them will be prefixed with this string.
await stopRecordingAllocations("reload", { alsoRecordContentProcess: true });
// Then, here you can execute cleanup.
// You would typically close the tab, toolbox, ...
});
如何在本地运行测试¶
$ ./mach mochitest --headless devtools/client/framework/test/allocations/
以及仅查看结果
$ ./mach mochitest --headless devtools/client/framework/test/allocations/ | grep " test leaked "
调试内存泄漏¶
如果您看到回归或改进,仅仅查看泄漏的对象数量并没有多大帮助。测试包含一些特殊的调试模式,这些模式会打印大量数据以找出泄漏的内容以及原因。
您可以使用以下环境变量来启用调试模式
DEBUG_DEVTOOLS_ALLOCATIONS=leak|allocations $ ./mach mochitest --headless devtools/client/framework/test/allocations/the-fault-test.js
**DEBUG_DEVTOOLS_ALLOCATIONS** 可以启用两种不同的调试输出。(一次只能启用一个)
**DEBUG_DEVTOOLS_ALLOCATIONS=allocations** 将报告在运行测试期间进行的所有分配站点。这将包括已释放的分配。如果您希望减少分配以减少 GC 负载,则此视图特别有用。
**DEBUG_DEVTOOLS_ALLOCATIONS=leak** 将仅报告在测试结束时仍分配的对象。有时它只会报告缺少堆栈跟踪的分配。从而使预览视图变得有用。
示例¶
假设我们有以下代码
1: exports.MyModule = {
2: globalArray: [],
3: test() {
3: // The following object will be allocated but not leaked,
5: // as we keep no reference to it anywhere
6: const transientObject = {};
7:
8: // The following object will be allocated on this line,
9: // but leaked on the following one. By storing a reference
10: // to it in the global array which is never cleared.
11: const leakedObject = {};
12: this.globalArray.push(leakedObject);
13: },
14: };
并且,我们有一个执行此操作的内存测试
const { MyModule } = require("devtools/my-module");
await startRecordingAllocations();
MyModule.test();
await stopRecordingAllocations("target");
我们可以首先通过运行以下命令来查看所有分配
DEBUG_DEVTOOLS_ALLOCATIONS=allocations $ ./mach mochitest --headless devtools/client/framework/test/allocations/browser_allocation_myTest.js
这将在最后打印
DEVTOOLS ALLOCATION: all allocations (which may be freed or are still allocated):
[
{
"src": "UNKNOWN",
"count": 80,
"lines": [
"?: 80"
]
},
{
"src": "resource://devtools/my-module.js",
"count": 2,
"lines": [
"11: 1"
"6: 1"
]
}
]
第一部分,带有 UNKNOWN
可以忽略。这是关于缺少分配站点的对象。此日志的第二部分告诉我们,在运行测试时从 my-module.js 分配了 2 个对象。一个是在第 6 行分配的,它是 transcientObject
。另一个是在第 11 行分配的,它是 leakedObject
。
现在,我们可以使用第二个视图来专注于保留分配的对象
DEBUG_DEVTOOLS_ALLOCATIONS=leaks $ ./mach mochitest --headless devtools/client/framework/test/allocations/browser_allocation_myTest.js
这将在最后打印
DEVTOOLS ALLOCATION: allocations which leaked:
[
{
"src": "UNKNOWN",
"count": 80,
"lines": [
"?: 80"
]
},
{
"src": "resource://devtools/shared/commands/commands-factory.js",
"count": 1,
"lines": [
"11: 1"
]
}
]
类似地,我们可以只关注第二部分,它告诉我们只有一个对象正在泄漏,并且此对象最初是从第 11 行创建的,它是 leakedObject
。这并没有告诉我们为什么对象被保留分配,但至少我们知道哪个对象被保留在内存中。
通过支配者调试内存泄漏¶
此最后一个功能可能是最强大的,并且不受 DEBUG_DEVTOOLS_ALLOCATIONS 的限制。它始终启用。此外,它需要知道哪个特定对象正在泄漏,还需要修改代码库以将可疑对象的引用传递给测试助手。
您可以通过执行以下操作来指示测试助手跟踪给定对象
1: // Let's say it is some code running from "my-module.js"
2:
3: // From a DevTools CommonJS module:
4: const { track } = require("devtools/shared/test-helpers/tracked-objects.sys.mjs");
5: // From anything else, JSM, XPCOM module,...:
6: const { track } = ChromeUtils.importESModule("resource://devtools/shared/test-helpers/tracked-objects.sys.mjs");
7:
8: const g = [];
9: function someFunctionInDevToolsCalledBySomething() {
10: const myLeakedObject = {};
11: track(myLeakedObject);
12:
13: // Simulate a leak by holding a reference to the object in a global `g` array
14: g.push({ seeMyCustomAttributeHere: myLeakedObject });
15: }
然后,在运行测试时,您将获得如下输出
0:41.26 GECKO(644653) # Tracing: Object@my-module:10
0:40.65 GECKO(644653) ### Path(s) from root:
0:41.26 GECKO(644653) - other@no-stack:undefined.WeakMap entry value
0:41.26 GECKO(644653) \--> [email protected]:160.**UNKNOWN SLOT 1**
0:41.26 GECKO(644653) \--> [email protected]:155.g
0:41.26 GECKO(644653) \--> [email protected]:8.objectElements[0]
0:41.26 GECKO(644653) \--> [email protected]:14.seeMyCustomAttributeHere
0:41.26 GECKO(644653) \--> [email protected]:10
此输出表示 myLeakedObject
最初是从 my-module.js 的第 10 行分配的。并且被保留分配是因为它被保存在从 my-module.js 的第 14 行分配的对象中。这是我们存储在 g
全局数组中的自定义对象。此自定义对象由第 8 行 my-module.js 分配的数组持有。并且此数组由一个对象持有,该对象本身由 base-loader.sys.mjs 的第 155 行分配。这是 my-module.js 模块的全局变量,由 DevTools 加载器创建。然后我们看到一些更低级别的对象一直到另一个全局对象,该对象缺少其分配站点。
如何轻松获取 try 运行的数据¶
$ ./mach try fuzzy devtools/client/framework/test/allocations/ --query "'linux 'chrome-e10s 'opt '64-qr/opt"
如果测试结果存在一些噪声并且您希望进行更多测试运行,您也可以传递 --rebuild 3
。
跟踪这些测试的趋势¶
您可以尝试查看: https://firefox-dev.tools/performance-dashboard/tools/memory.html
从以下链接获取: https://treeherder.mozilla.org/perfherder/graphs 通过查看过去一年的“DevTools”(第一个下拉列表)数据,然后双击“测试”菜单列表中的相关行。
重大的改进和回归将通过 以下仪表板 通知。