Skip to content

Commit 05d7271

Browse files
JacksonGLmeta-codesync[bot]
authored andcommitted
feat(heap-analysis): support analyzing js source code strings in the heap string analysis
Summary: This diff adds logic in `memlab analyze string` command so that it can extract and analyze JS code strings in the heap snapshots. Reviewed By: twobassdrum Differential Revision: D89912914 fbshipit-source-id: 57a89778690e5b943aa931574723226ac0115502
1 parent 3c2a27e commit 05d7271

File tree

3 files changed

+69
-20
lines changed

3 files changed

+69
-20
lines changed

packages/core/src/lib/Utils.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -837,6 +837,29 @@ function isSlicedStringNode(node: IHeapNode): boolean {
837837
return node.type === 'sliced string';
838838
}
839839

840+
function isCompiledCodeNode(node: IHeapNode): boolean {
841+
return node.type === 'code';
842+
}
843+
844+
function isJSSourceStringNode(node: IHeapNode): boolean {
845+
if (!utils.isStringNode(node) || utils.isSlicedStringNode(node)) {
846+
return false;
847+
}
848+
849+
let isSourceString = false;
850+
851+
node.forEachReferrer((referrerEdge: IHeapEdge) => {
852+
if (
853+
referrerEdge.name_or_index === 'source' &&
854+
isCompiledCodeNode(referrerEdge.fromNode)
855+
) {
856+
isSourceString = true;
857+
return {stop: true};
858+
}
859+
});
860+
return isSourceString;
861+
}
862+
840863
function getStringNodeValue(node: Optional<IHeapNode>): string {
841864
if (!node) {
842865
return '';
@@ -2426,6 +2449,7 @@ export default {
24262449
hasReactEdges,
24272450
isAlternateNode,
24282451
isBlinkRootNode,
2452+
isCompiledCodeNode,
24292453
isCppRootsNode,
24302454
isDOMInternalNode,
24312455
isDOMNodeIncomplete,
@@ -2442,6 +2466,7 @@ export default {
24422466
isHTMLDocumentNode,
24432467
isHermesInternalObject,
24442468
isHostRoot,
2469+
isJSSourceStringNode,
24452470
isMeaningfulEdge,
24462471
isMeaningfulNode,
24472472
isNodeDominatedByDeletionsArray,

packages/heap-analysis/src/plugins/StringAnalysis.ts

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
* @oncall memory_lab
99
*/
1010

11-
import type {IHeapSnapshot, IHeapNode} from '@memlab/core';
11+
import type {IHeapSnapshot, IHeapNode, Optional} from '@memlab/core';
1212
import type {AnalyzeSnapshotResult, HeapAnalysisOptions} from '../PluginUtils';
1313

1414
import chalk from 'chalk';
@@ -36,6 +36,8 @@ type StringPatternRecord = {
3636
dupSize: number;
3737
};
3838

39+
type HeapNodeFilter = (node: IHeapNode) => boolean;
40+
3941
/**
4042
* duplicated string pattern information
4143
*/
@@ -162,13 +164,20 @@ export default class StringAnalysis extends BaseAnalysis {
162164
async process(options: HeapAnalysisOptions): Promise<void> {
163165
const snapshot = await pluginUtils.loadHeapSnapshot(options);
164166
const stringMap = this.getPreprocessedStringMap(snapshot);
165-
this.calculateStringPatternsStatistics(stringMap);
167+
const sourceCodeStringMap = this.getPreprocessedStringMap(
168+
snapshot,
169+
utils.isJSSourceStringNode,
170+
);
171+
this.calculateStringPatternsStatistics(stringMap, sourceCodeStringMap);
166172
this.calculateTopDuplicatedStringsInCount(stringMap);
167173
this.calculateTopDuplicatedStringsInSize(stringMap);
168174
this.print();
169175
}
170176

171-
private getPreprocessedStringMap(snapshot: IHeapSnapshot): StringMap {
177+
private getPreprocessedStringMap(
178+
snapshot: IHeapSnapshot,
179+
filter: Optional<HeapNodeFilter> = null,
180+
): StringMap {
172181
info.overwrite('building string map...');
173182
const stringMap = Object.create(null);
174183

@@ -177,6 +186,9 @@ export default class StringAnalysis extends BaseAnalysis {
177186
if (StringAnalysis.shouldIgnoreNode(node)) {
178187
return;
179188
}
189+
if (filter && filter(node) === false) {
190+
return;
191+
}
180192

181193
const strValue = utils.getStringNodeValue(node);
182194
stringMap[strValue] = stringMap[strValue] || {
@@ -236,32 +248,44 @@ export default class StringAnalysis extends BaseAnalysis {
236248
);
237249
}
238250

239-
private calculateStringPatternsStatistics(stringMap: StringMap): void {
251+
private calculateStringPatternsStatistics(
252+
stringMap: StringMap,
253+
sourceStringMap: StringMap,
254+
): void {
240255
info.overwrite('calculating statistics for specified string patterns...');
241256

242257
const strPatternStat = Object.create(null);
258+
function aggregatePatternStat(patternName: string, record: StringRecord) {
259+
strPatternStat[patternName] = strPatternStat[patternName] || {
260+
n: 0,
261+
dupN: 0,
262+
size: 0,
263+
dupSize: 0,
264+
};
265+
const item = strPatternStat[patternName];
266+
item.n += record.n;
267+
item.size += record.size;
268+
if (record.n > 1) {
269+
item.dupN += record.n - 1;
270+
item.dupSize += (record.size * (record.n - 1)) / record.n;
271+
}
272+
}
273+
// calculate pattern stats for normal strings
243274
for (const [str, record] of Object.entries(stringMap)) {
244275
patternLoop: for (const [patternName, patternCheck] of Object.entries(
245276
StringAnalysis.stringPatternsToObserve,
246277
)) {
247278
if (!patternCheck(str)) {
248279
continue patternLoop;
249280
}
250-
strPatternStat[patternName] = strPatternStat[patternName] || {
251-
n: 0,
252-
dupN: 0,
253-
size: 0,
254-
dupSize: 0,
255-
};
256-
const item = strPatternStat[patternName];
257-
item.n += record.n;
258-
item.size += record.size;
259-
if (record.n > 1) {
260-
item.dupN += record.n - 1;
261-
item.dupSize += (record.size * (record.n - 1)) / record.n;
262-
}
281+
aggregatePatternStat(patternName, record);
263282
}
264283
}
284+
// calculate pattern stats for source code strings
285+
for (const [, record] of Object.entries(sourceStringMap)) {
286+
const patternName = 'JS code string';
287+
aggregatePatternStat(patternName, record);
288+
}
265289
this.stringPatternsStat = strPatternStat;
266290
}
267291

website/docs/api/heap-analysis/src/classes/StringAnalysis.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Class: StringAnalysis
22

3-
Defined in: heap-analysis/src/plugins/StringAnalysis.ts:57
3+
Defined in: heap-analysis/src/plugins/StringAnalysis.ts:59
44

55
This analysis finds duplicated string instance in JavaScript heap
66
and rank them based on the duplicated string size and count.
@@ -86,7 +86,7 @@ const result = await analysis.analyzeSnapshotFromFile(snapshotFile, {
8686

8787
> **getCommandName**(): `string`
8888
89-
Defined in: heap-analysis/src/plugins/StringAnalysis.ts:118
89+
Defined in: heap-analysis/src/plugins/StringAnalysis.ts:120
9090

9191
get CLI command name for this memory analysis;
9292
use it with `memlab analyze <ANALYSIS_NAME>` in CLI
@@ -107,7 +107,7 @@ command name
107107

108108
> **getTopDuplicatedStringsInCount**(): `StringRecord`[]
109109
110-
Defined in: heap-analysis/src/plugins/StringAnalysis.ts:68
110+
Defined in: heap-analysis/src/plugins/StringAnalysis.ts:70
111111

112112
get the top duplicated string in terms of duplicated string count
113113

0 commit comments

Comments
 (0)