Skip to content

Commit

Permalink
fix(all): fix example test scenario and some BE work
Browse files Browse the repository at this point in the history
Summary:
Use `void 0` instead of `undefined`, which can be redefined and shadowed in specific scope.

It also fixes broken OSS test scenario example (due to example site content change)

Differential Revision: D65863882

fbshipit-source-id: 8e318c28bdd776068760f3be23ac2ca5cf0d5e27
  • Loading branch information
JacksonGL authored and facebook-github-bot committed Nov 13, 2024
1 parent 369f97c commit 4f15d22
Show file tree
Hide file tree
Showing 16 changed files with 96 additions and 87 deletions.
50 changes: 28 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,18 @@ JavaScript memory leaks and optimization opportunities.
**Online Resources:** [[Website and Demo](https://facebook.github.io/memlab)] | [[Documentation](https://facebook.github.io/memlab/docs/intro)] | [[Meta Engineering Blog Post](https://engineering.fb.com/2022/09/12/open-source/memlab/)]

Features:
* **Browser memory leak detection** - Write test scenarios with the Puppeteer
API, and memlab will automatically compare JavaScript heap snapshots, filter
out memory leaks, and aggregate the results
* **Object-oriented heap traversing API** - Supports the creation of
self-defined memory leak detector, and enables programmatic analysis JS heap
snapshots taken from Chromium-based browsers, Node.js, Electron.js, and Hermes
* **Memory CLI toolbox** - Built-in toolbox and APIs for finding memory
optimization opportunities (not necessarily just memory leaks)
* **Memory assertions in Node.js** - Enables unit tests or running node.js
programs to take a heap snapshot of their own state, perform self memory
checking, or write advanced memory assertions

- **Browser memory leak detection** - Write test scenarios with the Puppeteer
API, and memlab will automatically compare JavaScript heap snapshots, filter
out memory leaks, and aggregate the results
- **Object-oriented heap traversing API** - Supports the creation of
self-defined memory leak detector, and enables programmatic analysis JS heap
snapshots taken from Chromium-based browsers, Node.js, Electron.js, and Hermes
- **Memory CLI toolbox** - Built-in toolbox and APIs for finding memory
optimization opportunities (not necessarily just memory leaks)
- **Memory assertions in Node.js** - Enables unit tests or running node.js
programs to take a heap snapshot of their own state, perform self memory
checking, or write advanced memory assertions

## CLI Usage

Expand All @@ -55,7 +56,7 @@ function url() {
// action where we want to detect memory leaks: click the Hotels button
async function action(page) {
// puppeteer page API
await page.click('button[aria-label="Hotels"]');
await page.click('text/Hotels');
}

// action where we want to go back to the step before: click clear search
Expand Down Expand Up @@ -101,6 +102,7 @@ MemLab found 46 leak(s)
--elements (internal)---> [(object elements)] (array) @182929 [8.3MB]
...
```

To get readable trace, the web site under test needs to serve non-minified code (or at least minified code
with readable variables, function name, and property names on objects).

Expand All @@ -112,6 +114,7 @@ in Chrome DevTool and search for the leaked object ID (`@182929`).
View memory issues detected by memlab based on a single JavaScript
heap snapshot taken from Chromium, Hermes, memlab, or any node.js
or Electron.js program:

```bash
memlab view-heap --snapshot <PATH TO .heapsnapshot FILE>
```
Expand All @@ -129,7 +132,7 @@ object (`node`) allocated by the target interaction.
function leakFilter(node, heap) {
// ... your leak detector logic
// return true to mark the node as a memory leak
};
}
```

`heap` is the graph representation of the final JavaScript heap snapshot.
Expand All @@ -139,6 +142,7 @@ For more details, view the
### Heap Analysis and Investigation

View which object keeps growing in size during interaction in the previous run:

```bash
memlab analyze unbound-object
```
Expand All @@ -153,6 +157,7 @@ Use `memlab analyze` to view all built-in memory analyses.
For extension, view the [doc site](https://facebook.github.io/memlab).

View retainer trace of a particular object:

```bash
memlab trace --node-id <HEAP_OBJECT_ID>
```
Expand All @@ -167,15 +172,15 @@ Use the `memlab` npm package to start a E2E run in browser and detect memory lea
const memlab = require('memlab');

const scenario = {
// initial page load url
url: () => 'https://www.google.com/maps/@37.386427,-122.0428214,11z',
// initial page load url
url: () => 'https://www.google.com/maps/@37.386427,-122.0428214,11z',

// action where we want to detect memory leaks
action: async (page) => await page.click('button[aria-label="Hotels"]'),
// action where we want to detect memory leaks
action: async page => await page.click('text/Hotels'),

// action where we want to go back to the step before
back: async (page) => await page.click('[aria-label="Close"]'),
}
// action where we want to go back to the step before
back: async page => await page.click('[aria-label="Close"]'),
};
memlab.run({scenario});
```

Expand All @@ -202,7 +207,7 @@ test('memory test with heap assertion', async () => {
let heap: IHeapSnapshot = await takeNodeMinimalHeap();

// call some function that may add references to obj
rabbitHole(obj)
rabbitHole(obj);

expect(heap.hasObjectWithClassName('TestObject')).toBe(true);
obj = null;
Expand All @@ -211,7 +216,6 @@ test('memory test with heap assertion', async () => {
// if rabbitHole does not have any side effect that
// adds new references to obj, then obj can be GCed
expect(heap.hasObjectWithClassName('TestObject')).toBe(false);

}, 30000);
```

Expand Down Expand Up @@ -240,11 +244,13 @@ NOTE: To run the memlab cli locally, make sure to prefix the memlab command with
npx from within the memlab repo e.g. `npx memlab`

Run tests:

```bash
npm run test
```

## License

memlab is MIT licensed, as found in the [LICENSE](LICENSE) file.

## Contributing
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/__tests__/parser/StringNode.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ test(
let strRepresentation = concatString?.toJSONString() ?? '{}';
let rep = JSON.parse(strRepresentation);
expect(rep.type).toBe('concatenated string');
expect(rep.stringValue).toBe(undefined);
expect(rep.stringValue).toBe(void 0);

strRepresentation = concatString?.toStringNode()?.toJSONString() ?? '{}';
rep = JSON.parse(strRepresentation);
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/lib/HeapAnalyzer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ class MemoryAnalyst {
{
strategy: config.isMLClustering
? new MLTraceSimilarityStrategy()
: undefined,
: void 0,
},
);
info.midLevel(
Expand Down Expand Up @@ -639,7 +639,7 @@ class MemoryAnalyst {
{
strategy: config.isMLClustering
? new MLTraceSimilarityStrategy()
: undefined,
: void 0,
},
);
info.midLevel(`MemLab found ${clusters.length} leak(s)`);
Expand Down Expand Up @@ -699,7 +699,7 @@ class MemoryAnalyst {
{
strategy: config.isMLClustering
? new MLTraceSimilarityStrategy()
: undefined,
: void 0,
},
);
return clusters;
Expand Down
33 changes: 15 additions & 18 deletions packages/core/src/lib/Serializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,7 @@
* @format
* @oncall web_perf_infra
*/

import fs from 'fs';
import chalk from 'chalk';
import config from './Config';
import utils from './Utils';
import info from './Console';
import PathFinder from '../paths/TraceFinder';
import {
import type {
E2EStepInfo,
HeapNodeIdSet,
IHeapEdge,
Expand All @@ -26,7 +19,15 @@ import {
LeakTracePathItem,
Nullable,
Optional,
Undefinable,
} from './Types';

import fs from 'fs';
import chalk from 'chalk';
import config from './Config';
import utils from './Utils';
import info from './Console';
import PathFinder from '../paths/TraceFinder';
import {EdgeIterationCallback} from '..';
import SerializationHelper from './SerializationHelper';

Expand Down Expand Up @@ -125,7 +126,7 @@ function JSONifyDetachedHTMLElement(
const fiberOptions = {...options};
const nextDepth = options.forceJSONifyDepth
? options.forceJSONifyDepth - 1
: undefined;
: void 0;
fiberOptions.forceJSONifyDepth = nextDepth;

// options for elem.__reactProps$xxx
Expand Down Expand Up @@ -275,7 +276,7 @@ function JSONifyFiberNode(

const propsOptions = {...options};
// for FiberNode, force expand a few more levels
if (propsOptions.forceJSONifyDepth === undefined) {
if (propsOptions.forceJSONifyDepth === void 0) {
propsOptions.forceJSONifyDepth = 1;
}
propsOptions.forceJSONifyDepth--;
Expand Down Expand Up @@ -800,11 +801,7 @@ function summarizeTabsOrder(
tabSummaryString = chalk.bold(tabSummaryString);
}
const tabSummaryStringWithSeparator = `${tabSummaryString} ${sep} `;
if (
options.color &&
options.progress !== undefined &&
i > options.progress
) {
if (options.color && options.progress !== void 0 && i > options.progress) {
res += chalk.dim(tabSummaryStringWithSeparator);
} else {
res += tabSummaryStringWithSeparator;
Expand Down Expand Up @@ -887,8 +884,8 @@ function summarizePath(
let ret = '';
let p: Optional<LeakTracePathItem> = pathArg;
let hasWeakMapEdge = false;
let weakMapKeyObjectId = undefined;
let weakMapEdgeIdx = undefined;
let weakMapKeyObjectId: Undefinable<number> = void 0;
let weakMapEdgeIdx: Undefinable<number> = void 0;

while (p) {
const node = p.node;
Expand Down Expand Up @@ -933,7 +930,7 @@ function summarizePath(
// if the shortest path contains the same WeakMap edge,
// we need to exclude the edge and re-search the shortest path
if (
weakMapEdgeIdx !== undefined &&
weakMapEdgeIdx !== void 0 &&
utils.pathHasEdgeWithIndex(keyNodePath, weakMapEdgeIdx)
) {
finder.annotateShortestPaths(snapshot, excludeKeySet);
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/lib/Types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ export type PuppeteerConfig = LaunchOptions &
* // type error here if your local puppeeter version is different
* // from the puppeteer used by MemLab
* action: async function (page: Page) {
* await page.click('button[aria-label="Hotels"]');
* await page.click('text/Hotels');
* },
* },
* };
Expand All @@ -198,7 +198,7 @@ export type PuppeteerConfig = LaunchOptions &
* },
* // no type error here
* action: async function (page: Page) {
* await page.click('button[aria-label="Hotels"]');
* await page.click('text/Hotels');
* },
* },
* };
Expand Down
12 changes: 6 additions & 6 deletions packages/core/src/lib/Utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,7 @@ function getReactFiberNode(node: Nullable<IHeapNode>, propName: string) {
return;
}
const targetNode = getToNodeByEdge(node, propName, 'property');
return isFiberNode(targetNode) ? targetNode : undefined;
return isFiberNode(targetNode) ? targetNode : void 0;
}

// check if the current node's parent has the node as a child
Expand Down Expand Up @@ -805,7 +805,7 @@ function getEdgeByNameAndType(
return node.findAnyReference(
(edge: IHeapEdge) =>
edge.name_or_index === edgeName &&
(type === undefined || edge.type === type),
(type === void 0 || edge.type === type),
);
}

Expand Down Expand Up @@ -935,14 +935,14 @@ function getNumberNodeValue(node: IHeapNode): Nullable<number> {
}

function getBooleanNodeValue(node: IHeapNode): Nullable<boolean> {
if (node === null || node === undefined) {
if (node === null || node === void 0) {
return null;
}
if (config.jsEngine === 'hermes') {
return node.name === 'true';
}
const valueNode = getToNodeByEdge(node, 'value', 'internal');
if (valueNode === null || valueNode === undefined) {
if (valueNode === null || valueNode === void 0) {
return null;
}
return valueNode.name === 'true';
Expand Down Expand Up @@ -1077,7 +1077,7 @@ function getSnapshotSequenceFilePath(): string {
}

// this should be called only after exploration
function loadTabsOrder(metaFile: Optional<string> = undefined): E2EStepInfo[] {
function loadTabsOrder(metaFile: Optional<string> = void 0): E2EStepInfo[] {
try {
const file =
metaFile != null && fs.existsSync(metaFile)
Expand Down Expand Up @@ -1203,7 +1203,7 @@ function getReadablePercent(num: number): string {

function getReadableBytes(bytes: Optional<number>): string {
let n: number, suffix: string;
if (bytes === undefined || bytes === null) {
if (bytes === void 0 || bytes === null) {
return '';
}
if (bytes >= 1e12) {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/trace-cluster/TraceBucket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ export default class NormalizedTrace {
const {allClusters} = NormalizedTrace.diffTraces(leakTraces, [], {
strategy: config.isMLClustering
? new MLTraceSimilarityStrategy()
: undefined,
: void 0,
});

return NormalizedTrace.clusteredLeakTracesToRecord(allClusters);
Expand Down
3 changes: 2 additions & 1 deletion packages/e2e/src/BaseSynthesizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import type {
CheckPageLoadCallback,
Nullable,
PageSetupCallback,
Undefinable,
} from '@memlab/core';
import type {Page} from 'puppeteer';

Expand Down Expand Up @@ -416,7 +417,7 @@ class BaseSynthesizer implements IE2EScenarioSynthesizer {
interactions: [scenario.back],
} as IE2EStepBasic;

const getRevertStep = (stepType?: string | undefined): E2EStepInfo =>
const getRevertStep = (stepType?: Undefinable<string>): E2EStepInfo =>
scenario.back
? this.getActionFromStep(revertStep, stepType)
: this.getAction(SynthesisUtils.revertStep.name, stepType);
Expand Down
2 changes: 1 addition & 1 deletion packages/e2e/src/ScriptManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ export default class ScriptManager {
this.debounce(() => {
fs.writeFileSync(
metaFile,
JSON.stringify(this.scriptInfos, undefined, 2),
JSON.stringify(this.scriptInfos, void 0, 2),
'UTF-8',
);
}, 1000);
Expand Down
2 changes: 1 addition & 1 deletion packages/e2e/src/plugins/scenarios/test-google-maps.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ function url() {

// action where we want to detect memory leaks
async function action(page) {
await page.click('button[aria-label="Hotels"]');
await page.click('text/Hotels');
}

// action where we want to go back to the step before
Expand Down
2 changes: 1 addition & 1 deletion packages/heap-analysis/src/plugins/ObjectShapeAnalysis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ class ObjectShapeAnalysis extends BaseAnalysis {
const key = serializer.summarizeNodeShape(node);
breakdown[key] = breakdown[key] || new Set();
breakdown[key].add(node.id);
if (population[key] === undefined) {
if (population[key] === void 0) {
population[key] = {examples: [], n: 0};
}
++population[key].n;
Expand Down
Loading

0 comments on commit 4f15d22

Please sign in to comment.