├── .github └── workflows │ └── gh-pages.yml ├── .gitignore ├── .nvmrc ├── .watchmanconfig ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── docs ├── jest.config.js ├── package.json ├── packages ├── README.md ├── api │ ├── LICENSE │ ├── README.md │ ├── jest.config.js │ ├── package.json │ ├── src │ │ ├── API.ts │ │ ├── __tests__ │ │ │ ├── API │ │ │ │ ├── E2EBasicAnalysis.test.ts │ │ │ │ ├── E2EDetachedDOMAnalysis.test.ts │ │ │ │ ├── E2EDuplicateObjectAnalysis.test.ts │ │ │ │ ├── E2EFindLeaks.example.ts │ │ │ │ ├── E2EFindMemoryLeaks.test.ts │ │ │ │ ├── E2EFindMemoryLeaksWithSnapshotFiles.test.ts │ │ │ │ ├── E2EFindTaggedMemoryLeaks.test.ts │ │ │ │ ├── E2EInjectLeaksWithSetup.test.ts │ │ │ │ ├── E2EMemoryLeakFilter.test.ts │ │ │ │ ├── E2EReloadInSetup.test.ts │ │ │ │ ├── E2EResultReader.test.ts │ │ │ │ ├── E2ERunMultipleSnapshots.example.ts │ │ │ │ ├── E2ERunSingleSnapshot.example.ts │ │ │ │ ├── E2EShapeUnboundGrowthAnalysis.test.ts │ │ │ │ ├── E2EStringAnalysis.test.ts │ │ │ │ ├── E2ETestScenarioCallback.test.ts │ │ │ │ └── lib │ │ │ │ │ └── E2ETestSettings.ts │ │ │ ├── heap │ │ │ │ ├── E2EHeapParser.test.ts │ │ │ │ ├── examples │ │ │ │ │ ├── example-1.ts │ │ │ │ │ ├── example-2.ts │ │ │ │ │ ├── example-3.ts │ │ │ │ │ ├── example-4.ts │ │ │ │ │ ├── example-5.test.ts │ │ │ │ │ ├── example-6.ts │ │ │ │ │ └── example-7.test.ts │ │ │ │ └── lib │ │ │ │ │ └── HeapParserTestUtils.ts │ │ │ └── packages │ │ │ │ └── heap-analysis.test.ts │ │ ├── index.ts │ │ ├── lib │ │ │ └── APIUtils.ts │ │ ├── result-reader │ │ │ ├── BaseResultReader.ts │ │ │ ├── BrowserInteractionResultReader.ts │ │ │ └── SnapshotResultReader.ts │ │ └── state │ │ │ ├── APIStateManager.ts │ │ │ ├── ConsoleModeManager.ts │ │ │ └── PuppeteerConfigManager.ts │ └── tsconfig.json ├── cli │ ├── LICENSE │ ├── README.md │ ├── bin │ │ ├── memlab.js │ │ └── preinstall │ ├── package.json │ ├── src │ │ ├── BaseCommand.ts │ │ ├── CommandLoader.ts │ │ ├── Dispatcher.ts │ │ ├── commands │ │ │ ├── CleanLoggerDataCommand.ts │ │ │ ├── CleanRunDataCommand.ts │ │ │ ├── GetVersionCommand.ts │ │ │ ├── InitDirectoryCommand.ts │ │ │ ├── ListScenariosCommand.ts │ │ │ ├── MemLabRunCommand.ts │ │ │ ├── PrintSummaryCommand.ts │ │ │ ├── ResetDirectoryCommand.ts │ │ │ ├── RunMeasureCommand.ts │ │ │ ├── WarmupAppCommand.ts │ │ │ ├── heap │ │ │ │ ├── CheckLeakCommand.ts │ │ │ │ ├── DiffLeakCommand.ts │ │ │ │ ├── GetRetainerTraceCommand.ts │ │ │ │ ├── HeapAnalysisCommand.ts │ │ │ │ ├── HeapAnalysisSubCommandWrapper.ts │ │ │ │ └── interactive │ │ │ │ │ ├── InteractiveCommandLoader.ts │ │ │ │ │ ├── InteractiveHeapCommand.ts │ │ │ │ │ ├── InteractiveHeapExploreCommand.ts │ │ │ │ │ ├── ui-components │ │ │ │ │ ├── CliScreen.ts │ │ │ │ │ ├── HeapViewController.ts │ │ │ │ │ ├── HeapViewUtils.ts │ │ │ │ │ └── ListComponent.ts │ │ │ │ │ └── worker │ │ │ │ │ └── LocateClosureSourceWorker.ts │ │ │ ├── helper │ │ │ │ ├── GenerateCLIDocCommand.ts │ │ │ │ ├── HelperCommand.ts │ │ │ │ └── lib │ │ │ │ │ ├── CommandOrder.ts │ │ │ │ │ ├── DocUtils.ts │ │ │ │ │ └── Types.ts │ │ │ ├── query │ │ │ │ └── QueryDefaultWorkDirCommand.ts │ │ │ └── snapshot │ │ │ │ ├── CheckXvfbSupportCommand.ts │ │ │ │ ├── Snapshot.ts │ │ │ │ ├── TakeSnapshotCommand.ts │ │ │ │ └── WarmupAndSnapshotCommand.ts │ │ ├── index.ts │ │ ├── lib │ │ │ └── CLIUtils.ts │ │ ├── options │ │ │ ├── DebugOption.ts │ │ │ ├── HelperOption.ts │ │ │ ├── MLClusteringLinkageMaxDistanceOption.ts │ │ │ ├── MLClusteringMaxDFOption.ts │ │ │ ├── MLClusteringOption.ts │ │ │ ├── NumberOfRunsOption.ts │ │ │ ├── SetContinuousTestOption.ts │ │ │ ├── SetMaxClusterSampleSizeOption.ts │ │ │ ├── SetWorkingDirectoryOption.ts │ │ │ ├── SilentOption.ts │ │ │ ├── VerboseOption.ts │ │ │ ├── constant.ts │ │ │ ├── e2e │ │ │ │ ├── AppOption.ts │ │ │ │ ├── DisableWebSecurityOption.ts │ │ │ │ ├── DisableXvfbOption.ts │ │ │ │ ├── DisplayLeakOutlinesOptions.ts │ │ │ │ ├── EnableJSInterceptOption.ts │ │ │ │ ├── EnableJSRewriteOption.ts │ │ │ │ ├── FullExecutionOption.ts │ │ │ │ ├── HeadfulBrowserOption.ts │ │ │ │ ├── InteractionOption.ts │ │ │ │ ├── RemoteBrowserDebugOption.ts │ │ │ │ ├── RunningModeOption.ts │ │ │ │ ├── ScenarioFileOption.ts │ │ │ │ ├── SetChromiumBinaryOption.ts │ │ │ │ ├── SetChromiumProtocolTimeoutOption.ts │ │ │ │ ├── SetDeviceOption.ts │ │ │ │ ├── SetUserAgentOption.ts │ │ │ │ ├── SkipExtraOperationOption.ts │ │ │ │ ├── SkipGCOption.ts │ │ │ │ ├── SkipScreenshotOption.ts │ │ │ │ ├── SkipScrollOption.ts │ │ │ │ ├── SkipSnapshotOption.ts │ │ │ │ ├── SkipWarmupOption.ts │ │ │ │ └── TargetWorkerOption.ts │ │ │ ├── experiment │ │ │ │ ├── ExperimentOptionUtils.ts │ │ │ │ ├── SetControlSnapshotOption.ts │ │ │ │ ├── SetControlWorkDirOption.ts │ │ │ │ ├── SetTreatmentSnapshotOption.ts │ │ │ │ ├── SetTreatmentWorkDirOption.ts │ │ │ │ └── utils │ │ │ │ │ └── ExperimentOptionsUtils.ts │ │ │ ├── heap │ │ │ │ ├── BaselineFileOption.ts │ │ │ │ ├── CleanupSnapshotOption.ts │ │ │ │ ├── FinalFileOption.ts │ │ │ │ ├── HeapAnalysisPluginOption.ts │ │ │ │ ├── HeapNodeIdOption.ts │ │ │ │ ├── HeapParserDictFastStoreSizeOption.ts │ │ │ │ ├── JSEngineOption.ts │ │ │ │ ├── LeakClusterSizeThresholdOption.ts │ │ │ │ ├── LogTraceAsClusterOption.ts │ │ │ │ ├── OversizeThresholdOption.ts │ │ │ │ ├── SetTraceContainsFilterOption.ts │ │ │ │ ├── SnapshotDirectoryOption.ts │ │ │ │ ├── SnapshotFileOption.ts │ │ │ │ ├── TargetFileOption.ts │ │ │ │ ├── TraceAllObjectsOption.ts │ │ │ │ └── leak-filter │ │ │ │ │ ├── LeakFilterFileOption.ts │ │ │ │ │ └── examples │ │ │ │ │ ├── FilterLib.ts │ │ │ │ │ ├── dup-string-as-leak.example-1.ts │ │ │ │ │ ├── dup-string-as-leak.example-2.ts │ │ │ │ │ └── large-object-as-leak.example.js │ │ │ └── lib │ │ │ │ ├── OptionConstant.ts │ │ │ │ └── UniversalOptions.ts │ │ └── runner.ts │ └── tsconfig.json ├── core │ ├── LICENSE │ ├── README.md │ ├── jest.config.js │ ├── package.json │ ├── src │ │ ├── __tests__ │ │ │ ├── lib │ │ │ │ └── TestUtils.ts │ │ │ ├── parser │ │ │ │ ├── HeapParser.test.ts │ │ │ │ ├── NodeHeap.test.ts │ │ │ │ ├── StringNode.test.ts │ │ │ │ └── traverse │ │ │ │ │ └── HeapNodeTraverse.test.ts │ │ │ └── utils │ │ │ │ ├── TagParser.test.ts │ │ │ │ └── Utils.test.ts │ │ ├── index.ts │ │ ├── lib │ │ │ ├── BaseOption.ts │ │ │ ├── BrowserInfo.ts │ │ │ ├── Config.ts │ │ │ ├── Console.ts │ │ │ ├── Constant.ts │ │ │ ├── FileManager.ts │ │ │ ├── HeapAnalyzer.ts │ │ │ ├── HeapParser.ts │ │ │ ├── InternalValueSetter.ts │ │ │ ├── NodeHeap.ts │ │ │ ├── PackageInfoLoader.ts │ │ │ ├── ProcessManager.ts │ │ │ ├── RunInfoUtils.ts │ │ │ ├── SerializationHelper.ts │ │ │ ├── Serializer.ts │ │ │ ├── StringLoader.ts │ │ │ ├── TraceSampler.ts │ │ │ ├── Types.ts │ │ │ ├── TypesThirdParty.d.ts │ │ │ ├── Utils.ts │ │ │ ├── charts │ │ │ │ └── MemoryBarChart.ts │ │ │ ├── heap-data │ │ │ │ ├── HeapEdge.ts │ │ │ │ ├── HeapLocation.ts │ │ │ │ ├── HeapNode.ts │ │ │ │ ├── HeapSnapshot.ts │ │ │ │ ├── HeapStringNode.ts │ │ │ │ ├── HeapUtils.ts │ │ │ │ ├── MemLabTagStore.ts │ │ │ │ └── utils │ │ │ │ │ └── NumericDictionary.ts │ │ │ ├── leak-filters │ │ │ │ ├── BaseLeakFilter.rule.ts │ │ │ │ ├── LeakFilterRuleList.ts │ │ │ │ ├── LeakObjectFilter.ts │ │ │ │ └── rules │ │ │ │ │ ├── FilterByExternalFilter.rule.ts │ │ │ │ │ ├── FilterDetachedDOMElement.rule.ts │ │ │ │ │ ├── FilterHermesNode.rule.ts │ │ │ │ │ ├── FilterOverSizedNodeAsLeak.rule.ts │ │ │ │ │ ├── FilterStackTraceFrame.rule.ts │ │ │ │ │ ├── FilterTrivialNode.rule.ts │ │ │ │ │ ├── FilterUnmountedFiberNode.rule.ts │ │ │ │ │ ├── FilterUserTaggedLeaks.rule.ts │ │ │ │ │ └── FilterXMLHTTPRequest.rule.ts │ │ │ └── trace-filters │ │ │ │ ├── BaseTraceFilter.rule.ts │ │ │ │ ├── LeakTraceFilter.ts │ │ │ │ ├── TraceFilterRuleList.ts │ │ │ │ └── rules │ │ │ │ ├── FilterAttachedDOMToDetachedDOMTrace.rule.ts │ │ │ │ ├── FilterCppRootsToDetachedDOMTrace.rule.ts │ │ │ │ ├── FilterDOMNodeChainTrace.rule.ts │ │ │ │ ├── FilterHermesTrace.rule.ts │ │ │ │ ├── FilterInternalNodeTrace.rule.ts │ │ │ │ ├── FilterPendingActivitiesTrace.rule.ts │ │ │ │ ├── FilterShadowRootTrace.rule.ts │ │ │ │ └── FilterStyleEngineTrace.rule.ts │ │ ├── logger │ │ │ ├── LeakClusterLogger.ts │ │ │ └── LeakTraceDetailsLogger.ts │ │ ├── modes │ │ │ ├── BaseMode.ts │ │ │ ├── InteractionTestMode.ts │ │ │ ├── MeasureMode.ts │ │ │ └── RunningModes.ts │ │ ├── paths │ │ │ └── TraceFinder.ts │ │ └── trace-cluster │ │ │ ├── ClusterUtils.ts │ │ │ ├── ClusterUtilsHelper.ts │ │ │ ├── ClusteringHeuristics.ts │ │ │ ├── EvalutationMetric.ts │ │ │ ├── MultiIterationSeqClustering.ts │ │ │ ├── SequentialClustering.ts │ │ │ ├── TraceBucket.ts │ │ │ ├── TraceElement.ts │ │ │ └── strategies │ │ │ ├── MLTraceSimilarityStrategy.ts │ │ │ ├── TraceAsClusterStrategy.ts │ │ │ ├── TraceSimilarityStrategy.ts │ │ │ └── machine-learning │ │ │ ├── DistanceMatrix.ts │ │ │ ├── HAC.ts │ │ │ ├── Ngram.ts │ │ │ └── TfidfVectorizer.ts │ ├── static │ │ ├── run-meta.json │ │ ├── visit-order-single-snapshot.json │ │ └── visit-order.json │ └── tsconfig.json ├── e2e │ ├── LICENSE │ ├── README.md │ ├── jest.config.js │ ├── package.json │ ├── src │ │ ├── BaseSynthesizer.ts │ │ ├── E2EInteractionManager.ts │ │ ├── NetworkManager.ts │ │ ├── ScriptManager.ts │ │ ├── TestRunnerLoader.ts │ │ ├── TypesThirdParty.ts │ │ ├── __tests__ │ │ │ └── scope │ │ │ │ ├── lib.ts │ │ │ │ └── scope-analysis.test.ts │ │ ├── code-analysis │ │ │ └── Script.ts │ │ ├── index.ts │ │ ├── instrumentation │ │ │ ├── BaseAstTransform.ts │ │ │ ├── ScriptRewriteManager.ts │ │ │ ├── TransformLoader.ts │ │ │ └── transforms │ │ │ │ ├── InjectSourceInfoTranform.ts │ │ │ │ └── InjectSourceInfoTransform.ts │ │ ├── lib │ │ │ ├── E2EUtils.ts │ │ │ ├── SynthesisUtils.ts │ │ │ └── operations │ │ │ │ ├── BackOperation.ts │ │ │ │ ├── BackspaceOperation.ts │ │ │ │ ├── BaseOperation.ts │ │ │ │ ├── CachePageContent.ts │ │ │ │ ├── ClickOperation.ts │ │ │ │ ├── CompoundOperation.ts │ │ │ │ ├── EnterOperation.ts │ │ │ │ ├── EscOperation.ts │ │ │ │ ├── HoverOperation.ts │ │ │ │ ├── InteractionUtils.ts │ │ │ │ ├── Operations.ts │ │ │ │ ├── RevertOperation.ts │ │ │ │ ├── RunJSCode.ts │ │ │ │ ├── ScrollOperation.ts │ │ │ │ ├── TargetExtraOperation.ts │ │ │ │ ├── TestPlanner.ts │ │ │ │ ├── TypeOperation.ts │ │ │ │ ├── UploadOperation.ts │ │ │ │ ├── WaitForElementOperation.ts │ │ │ │ ├── WaitOperation.ts │ │ │ │ ├── WithCachedPageContent.ts │ │ │ │ └── XVirtualFrameBuffer.ts │ │ └── plugins │ │ │ ├── DefaultScenarioSynthesizer.ts │ │ │ ├── TestSPAVisitSynthesizer.ts │ │ │ └── scenarios │ │ │ ├── test-airbnb.js │ │ │ ├── test-databricks.js │ │ │ ├── test-ebay.js │ │ │ ├── test-google-maps.js │ │ │ ├── test-shopify.js │ │ │ └── test-youtube.js │ ├── static │ │ ├── example │ │ │ ├── .eslintrc.json │ │ │ ├── .gitignore │ │ │ ├── README.md │ │ │ ├── components │ │ │ │ └── layout.jsx │ │ │ ├── leak-filters │ │ │ │ └── filter-by-retained-size.js │ │ │ ├── next.config.js │ │ │ ├── package.json │ │ │ ├── pages │ │ │ │ ├── _app.js │ │ │ │ ├── _document.js │ │ │ │ ├── examples │ │ │ │ │ ├── detached-dom.jsx │ │ │ │ │ └── oversized-object.jsx │ │ │ │ └── index.js │ │ │ ├── public │ │ │ │ └── favicon.ico │ │ │ └── scenarios │ │ │ │ ├── detached-dom.js │ │ │ │ ├── oversized-leak-filter.js │ │ │ │ ├── oversized-object-with-filter.js │ │ │ │ └── oversized-object.js │ │ └── links │ │ │ ├── index.html │ │ │ ├── min.js │ │ │ ├── package.json │ │ │ └── src │ │ │ ├── App.js │ │ │ └── index.js │ └── tsconfig.json ├── heap-analysis │ ├── LICENSE │ ├── README.md │ ├── jest.config.js │ ├── package.json │ ├── src │ │ ├── BaseAnalysis.ts │ │ ├── HeapAnalysisLoader.ts │ │ ├── HeapConfig.ts │ │ ├── PluginUtils.ts │ │ ├── __tests__ │ │ │ ├── HeapAnalysis.test.ts │ │ │ └── package.test.ts │ │ ├── index.ts │ │ ├── options │ │ │ ├── HeapAnalysisNodeIdOption.ts │ │ │ ├── HeapAnalysisOutputOption.ts │ │ │ ├── HeapAnalysisSnapshotDirectoryOption.ts │ │ │ └── HeapAnalysisSnapshotFileOption.ts │ │ └── plugins │ │ │ ├── CollectionUnboundGrowthAnalysis.ts │ │ │ ├── CollectionsHoldingStaleAnalysis.ts │ │ │ ├── DetachedDOMElementAnalysis.ts │ │ │ ├── GlobalVariableAnalysis │ │ │ ├── BuiltInGlobalVariables.ts │ │ │ └── GlobalVariableAnalysis.ts │ │ │ ├── ObjectContentAnalysis.ts │ │ │ ├── ObjectFanoutAnalysis.ts │ │ │ ├── ObjectShallowAnalysis.ts │ │ │ ├── ObjectShapeAnalysis.ts │ │ │ ├── ObjectSizeAnalysis.ts │ │ │ ├── ObjectUnboundGrowthAnalysis.ts │ │ │ ├── ReactComponentHookAnalysis.ts │ │ │ ├── ShapeUnboundGrowthAnalysis.ts │ │ │ ├── StringAnalysis.ts │ │ │ └── UnmountedReactFiberNodesAnalysis.ts │ └── tsconfig.json ├── lens │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── explainer.md │ ├── package.json │ ├── playwright.config.ts │ ├── src │ │ ├── config │ │ │ └── config.ts │ │ ├── core │ │ │ ├── dom-observer.ts │ │ │ ├── event-listener-tracker.ts │ │ │ ├── react-fiber-analysis.ts │ │ │ ├── react-memory-scan.ts │ │ │ ├── types.ts │ │ │ └── valid-component-name.ts │ │ ├── extensions │ │ │ ├── basic-extension.ts │ │ │ └── dom-visualization-extension.ts │ │ ├── index.ts │ │ ├── memlens.lib.js.flow │ │ ├── memlens.lib.ts │ │ ├── memlens.run.ts │ │ ├── tests │ │ │ ├── bundle │ │ │ │ ├── lib.bundle.test.ts │ │ │ │ ├── run.bundle.start.head.test.ts │ │ │ │ ├── run.bundle.start.test.ts │ │ │ │ └── run.bundle.test.ts │ │ │ ├── fiber │ │ │ │ ├── dev.fiber.complex.dev.test.ts │ │ │ │ ├── dev.fiber.complex.list.dev.test.ts │ │ │ │ ├── dev.fiber.complex.prod.test.ts │ │ │ │ ├── dev.fiber.name.dev.test.ts │ │ │ │ └── dev.fiber.name.prod.test.ts │ │ │ ├── lib │ │ │ │ ├── babel.prod.js │ │ │ │ ├── react-dom-v18.dev.js │ │ │ │ ├── react-dom-v18.prod.js │ │ │ │ ├── react-v18.dev.js │ │ │ │ └── react-v18.prod.js │ │ │ ├── manual │ │ │ │ ├── playwright-open-manual.js │ │ │ │ └── todo-list │ │ │ │ │ └── todo-with-run.bundle.html │ │ │ └── utils │ │ │ │ └── test-utils.ts │ │ ├── utils │ │ │ ├── intersection-observer.ts │ │ │ ├── react-fiber-utils.ts │ │ │ ├── utils.ts │ │ │ └── weak-ref-utils.ts │ │ └── visual │ │ │ ├── components │ │ │ ├── component-stack-panel.ts │ │ │ ├── control-widget.ts │ │ │ ├── overlay-rectangle.ts │ │ │ ├── status-text.ts │ │ │ ├── toggle-button.ts │ │ │ └── visual-overlay.ts │ │ │ ├── dom-element-visualizer-interactive.ts │ │ │ ├── dom-element-visualizer.ts │ │ │ └── visual-utils.ts │ ├── tsconfig.json │ └── webpack.config.js └── memlab │ ├── LICENSE │ ├── README.md │ ├── bin │ ├── memlab │ └── preinstall │ ├── package.json │ ├── src │ └── index.ts │ └── tsconfig.json ├── tsconfig.base.json ├── tsconfig.json └── website ├── .env ├── .gitignore ├── .prettierignore ├── .prettierrc ├── README.md ├── babel.config.js ├── docs ├── api │ ├── _category_.yml │ ├── classes │ │ ├── _category_.yml │ │ ├── api_src.BrowserInteractionResultReader.md │ │ ├── api_src.SnapshotResultReader.md │ │ ├── heap_analysis_src.BaseAnalysis.md │ │ ├── heap_analysis_src.CollectionsHoldingStaleAnalysis.md │ │ ├── heap_analysis_src.DetachedDOMElementAnalysis.md │ │ ├── heap_analysis_src.GlobalVariableAnalysis.md │ │ ├── heap_analysis_src.ObjectFanoutAnalysis.md │ │ ├── heap_analysis_src.ObjectShallowAnalysis.md │ │ ├── heap_analysis_src.ObjectShapeAnalysis.md │ │ ├── heap_analysis_src.ObjectSizeAnalysis.md │ │ ├── heap_analysis_src.ObjectUnboundGrowthAnalysis.md │ │ ├── heap_analysis_src.ShapeUnboundGrowthAnalysis.md │ │ └── heap_analysis_src.StringAnalysis.md │ ├── enums │ │ ├── _category_.yml │ │ └── api_src.ConsoleMode.md │ ├── index.md │ ├── interfaces │ │ ├── _category_.yml │ │ ├── core_src.IBrowserInfo.md │ │ ├── core_src.IHeapEdge.md │ │ ├── core_src.IHeapEdges.md │ │ ├── core_src.IHeapLocation.md │ │ ├── core_src.IHeapNode.md │ │ ├── core_src.IHeapNodes.md │ │ ├── core_src.IHeapSnapshot.md │ │ ├── core_src.IHeapStringNode.md │ │ ├── core_src.ILeakFilter.md │ │ └── core_src.IScenario.md │ └── modules │ │ ├── _category_.yml │ │ ├── api_src.md │ │ ├── core_src.md │ │ └── heap_analysis_src.md ├── cli │ └── CLI-commands.md ├── getting-started.md ├── guides │ ├── 01-detached-dom.mdx │ ├── 02-detect-oversized-object.mdx │ ├── 03-find-leak.md │ ├── 04-continuous-test.md │ ├── 05-integrate-with-E2E-frameworks.md │ ├── _category_.yml │ ├── example-app-1.png │ ├── memlab-result-2.png │ ├── memlab-result.png │ └── oversized-object.png ├── how-memlab-works.md ├── installation.md └── intro.md ├── docusaurus.config.js ├── package.json ├── search-index-config.json ├── sidebars.js ├── src ├── components │ ├── CodeBlock.js │ ├── Logo.js │ ├── Showcase.js │ ├── TerminalReplay │ │ ├── css │ │ │ └── styles.css │ │ ├── index.js │ │ └── lib │ │ │ ├── Term.js │ │ │ ├── Timeline.js │ │ │ └── TimelineFactory.js │ └── TerminalStatic.js ├── css │ └── custom.css ├── data │ └── HomePageMainTerminal.js ├── lib │ ├── ContainerAnimation.js │ ├── TypeSpeedNormalization.js │ └── useWindowSize.js └── pages │ ├── index.tsx │ ├── styles.module.css │ └── under-construction.md ├── static ├── .nojekyll └── img │ ├── docusaurus.png │ ├── favicon.ico │ ├── heap-diff.gif │ ├── heap-view.png │ ├── logo.png │ ├── meta-favicon.png │ ├── oss_logo.png │ ├── undraw_analyze.svg │ ├── undraw_data_extraction.svg │ ├── undraw_online_test.svg │ ├── undraw_split_testing.svg │ └── users │ ├── fb.png │ ├── instagram.svg │ ├── messenger.svg │ └── workplace.svg └── tsconfig.json /.github/workflows/gh-pages.yml: -------------------------------------------------------------------------------- 1 | name: GitHub Pages 2 | on: 3 | push: 4 | branches: 5 | - main 6 | jobs: 7 | deploy: 8 | runs-on: ubuntu-22.04 9 | steps: 10 | - uses: actions/checkout@v3 11 | 12 | - name: Setup Node 13 | uses: actions/setup-node@v3 14 | with: 15 | node-version: "20" 16 | 17 | - name: Get yarn cache 18 | id: yarn-cache 19 | run: echo "dir=$(yarn cache dir)" >> "$GITHUB_OUTPUT" 20 | 21 | - name: Cache dependencies 22 | uses: actions/cache@v4 23 | with: 24 | path: ${{ steps.yarn-cache.outputs.dir }} 25 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 26 | restore-keys: | 27 | ${{ runner.os }}-yarn- 28 | 29 | - name: Build packages 30 | run: | 31 | yarn install 32 | yarn build 33 | working-directory: "." 34 | 35 | - name: Build website 36 | run: | 37 | yarn install 38 | yarn build 39 | working-directory: ./website 40 | 41 | - name: Deploy 42 | uses: peaceiris/actions-gh-pages@v3 43 | if: ${{ github.ref == 'refs/heads/main' }} 44 | with: 45 | github_token: ${{ secrets.GITHUB_TOKEN }} 46 | publish_dir: ./website/build 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **.js.map 2 | **.ts.map 3 | **/tsconfig.tsbuildinfo 4 | .DS_Store 5 | .yarnrc 6 | .idea 7 | /chrome 8 | /data 9 | /dist/ 10 | /dist/packages 11 | /tmp 12 | lerna-debug.log 13 | build.log 14 | node_modules 15 | package-lock.json 16 | package-oss.json 17 | packages/**/dist 18 | yarn-error.log 19 | yarn.lock 20 | 21 | # But do not ignore these files 22 | !packages/lens/dist/*.bundle.*.(js|ts) 23 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v16.14.2 2 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to memlab 2 | We want to make contributing to this project as easy and transparent as 3 | possible. 4 | 5 | ## Pull Requests 6 | We actively welcome your pull requests. 7 | 8 | 1. Fork the repo and create your branch from `main`. 9 | 2. If you've added code that should be tested, add tests. 10 | 3. If you've changed APIs, update the documentation. 11 | 4. Ensure the test suite passes. 12 | 5. Make sure your code lints. 13 | 6. If you haven't already, complete the Contributor License Agreement ("CLA"). 14 | 15 | ## Contributor License Agreement ("CLA") 16 | In order to accept your pull request, we need you to submit a CLA. You only need 17 | to do this once to work on any of Facebook's open source projects. 18 | 19 | Complete your CLA here: 20 | 21 | ## Issues 22 | We use GitHub issues to track public bugs. Please ensure your description is 23 | clear and has sufficient instructions to be able to reproduce the issue. 24 | 25 | Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe 26 | disclosure of security bugs. In those cases, please go through the process 27 | outlined on that page and do not file a public issue. 28 | 29 | ## License 30 | By contributing to memlab, you agree that your contributions will be licensed 31 | under the LICENSE file in the root directory of this source tree. 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Meta Platforms, Inc. and affiliates. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs: -------------------------------------------------------------------------------- 1 | website/docs -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | const config = { 12 | verbose: true, 13 | testRegex: '((.*/)?__tests__/.*)(\\.test|\\.spec)\\.(js|jsx)?$', 14 | maxConcurrency: 1, 15 | testEnvironment: 'node', 16 | }; 17 | 18 | module.exports = config; 19 | -------------------------------------------------------------------------------- /packages/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## npm packages 3 | 4 | [![Licensed under the MIT License](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/facebook/memlab/blob/master/LICENSE) 5 | [![PR's Welcome](https://img.shields.io/badge/PRs%20-welcome-brightgreen.svg)](https://github.com/facebook/memlab/blob/main/CONTRIBUTING.md) 6 | 7 | This GitHub repo hosts the code for the following npm packages: 8 | * [memlab](https://www.npmjs.com/package/memlab?activeTab=readme) - All memlab 9 | features packed in one package. This enables using all memlab 10 | features through CLI and should be installed globally. 11 | * [@memlab/core](https://www.npmjs.com/package/@memlab/core?activeTab=readme) 12 | – Heap parser, leak trace clustering, config, and other core libraries 13 | * [@memlab/e2e](https://www.npmjs.com/package/@memlab/e2e?activeTab=readme) 14 | – All logic for interacting with web browsers 15 | * [@memlab/cli](https://www.npmjs.com/package/@memlab/cli?activeTab=readme) 16 | – Command line interfaces and extension API 17 | * [@memlab/api](https://www.npmjs.com/package/@memlab/api?activeTab=readme) 18 | – API for continuous test integration 19 | * [@memlab/heap-analysis](https://www.npmjs.com/package/@memlab/heap-analysis?activeTab=readme) 20 | – Memory analysis and extension API 21 | -------------------------------------------------------------------------------- /packages/api/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Meta Platforms, Inc. and affiliates. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/api/README.md: -------------------------------------------------------------------------------- 1 | ## memlab API 2 | 3 | This is the memlab API library. Add this dependency to your project 4 | if you want to integrate memlab with your continuous testing system. 5 | 6 | ## Online Resources 7 | * [Official Website and Demo](https://facebook.github.io/memlab) 8 | * [Documentation](https://facebook.github.io/memlab/docs/intro) 9 | -------------------------------------------------------------------------------- /packages/api/jest.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | const config = { 12 | verbose: true, 13 | testRegex: '(/dist/(.*/)?__tests__/.*)(\\.test|\\.spec)\\.jsx?$', 14 | maxConcurrency: 1, 15 | }; 16 | 17 | module.exports = config; 18 | -------------------------------------------------------------------------------- /packages/api/src/__tests__/API/E2EFindLeaks.example.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | /* eslint-disable @typescript-eslint/ban-ts-comment */ 12 | 13 | import {info} from '@memlab/core'; 14 | import {Page} from 'puppeteer'; 15 | import {run} from '../../index'; 16 | 17 | const scenario = { 18 | app: () => 'test-spa', 19 | url: () => '', 20 | action: async (page: Page): Promise => 21 | await page.click('[data-testid="link-4"]'), 22 | }; 23 | 24 | function inject() { 25 | // @ts-ignore 26 | window.injectHookForLink4 = () => { 27 | const arr = []; 28 | for (let i = 0; i < 23; ++i) { 29 | arr.push(document.createElement('div')); 30 | } 31 | // @ts-ignore 32 | window.__injectedValue = arr; 33 | // @ts-ignore 34 | window._path_1 = {x: {y: document.createElement('div')}}; 35 | // @ts-ignore 36 | window._path_2 = new Set([document.createElement('div')]); 37 | }; 38 | } 39 | 40 | async function test() { 41 | const {leaks} = await run({scenario, evalInBrowserAfterInitLoad: inject}); 42 | info.lowLevel(`${leaks.length}`); 43 | } 44 | 45 | test(); 46 | -------------------------------------------------------------------------------- /packages/api/src/__tests__/API/E2EReloadInSetup.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | /* eslint-disable @typescript-eslint/ban-ts-comment */ 12 | 13 | import type {Page} from 'puppeteer'; 14 | 15 | import {run} from '../../index'; 16 | import {scenario, testSetup, testTimeout} from './lib/E2ETestSettings'; 17 | 18 | beforeEach(testSetup); 19 | 20 | // clear the page reload check in setup 21 | // to check that the page reload check should 22 | // be injected after the setup callback 23 | const setup = async (page: Page) => { 24 | await page.evaluate(() => { 25 | // @ts-ignore 26 | delete window.__memlab_check_reload; 27 | }); 28 | }; 29 | 30 | test( 31 | 'reload in setup is fine', 32 | async () => { 33 | const {leaks} = await run({ 34 | scenario: { 35 | ...scenario, 36 | setup, 37 | }, 38 | }); 39 | // no memory leaks are detected 40 | expect(leaks.length).toBe(0); 41 | }, 42 | testTimeout, 43 | ); 44 | -------------------------------------------------------------------------------- /packages/api/src/__tests__/API/E2ERunSingleSnapshot.example.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import {Page} from 'puppeteer'; 12 | import {StringAnalysis, warmupAndTakeSnapshots} from '../../index'; 13 | 14 | const scenario = { 15 | app: () => 'test-spa', 16 | url: () => '', 17 | action: async (page: Page): Promise => 18 | await page.click('[data-testid="link-4"]'), 19 | }; 20 | 21 | async function test() { 22 | const result = await warmupAndTakeSnapshots({ 23 | scenario, 24 | }); 25 | const analysis = new StringAnalysis(); 26 | const snapshotFile = result.getSnapshotFiles().pop() as string; 27 | analysis.analyzeSnapshotFromFile(snapshotFile); 28 | } 29 | 30 | test(); 31 | -------------------------------------------------------------------------------- /packages/api/src/__tests__/API/lib/E2ETestSettings.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {Page} from 'puppeteer'; 12 | import {config, ErrorHandling} from '@memlab/core'; 13 | 14 | export const testTimeout = 5 * 60 * 1000; 15 | 16 | export const defaultAnalysisArgs = {args: {_: []}}; 17 | 18 | export const scenario = { 19 | app: (): string => 'test-spa', 20 | url: (): string => '', 21 | action: async (page: Page): Promise => 22 | await page.click('[data-testid="link-4"]'), 23 | }; 24 | 25 | export const testSetup = (): void => { 26 | config.isTest = true; 27 | config.useXVFB = false; 28 | config.skipExtraOps = true; 29 | config.errorHandling = ErrorHandling.Throw; 30 | }; 31 | 32 | let uindex = 1; 33 | export function getUniqueID(): string { 34 | return `${process.pid}-${Date.now()}-${uindex++}`; 35 | } 36 | -------------------------------------------------------------------------------- /packages/api/src/__tests__/heap/examples/example-1.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {IHeapSnapshot} from '@memlab/core'; 12 | import {takeNodeMinimalHeap} from '@memlab/core'; 13 | 14 | class TestObject { 15 | public arr1 = [1, 2, 3]; 16 | public arr2 = ['1', '2', '3']; 17 | } 18 | 19 | (async function () { 20 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 21 | const obj = new TestObject(); 22 | // get a heap snapshot of the current program state 23 | const heap: IHeapSnapshot = await takeNodeMinimalHeap(); 24 | 25 | const node = heap.getAnyObjectWithClassName('TestObject'); 26 | console.log(node?.name); 27 | })(); 28 | -------------------------------------------------------------------------------- /packages/api/src/__tests__/heap/examples/example-2.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {IHeapSnapshot, IHeapNode} from '@memlab/core'; 12 | import {dumpNodeHeapSnapshot} from '@memlab/core'; 13 | import {getFullHeapFromFile} from '@memlab/heap-analysis'; 14 | 15 | (async function () { 16 | const heapFile = dumpNodeHeapSnapshot(); 17 | const heap: IHeapSnapshot = await getFullHeapFromFile(heapFile); 18 | 19 | // get the total number of heap objects 20 | heap.nodes.length; 21 | 22 | heap.nodes.forEach((node: IHeapNode) => { 23 | console.log(node.name); 24 | }); 25 | })(); 26 | -------------------------------------------------------------------------------- /packages/api/src/__tests__/heap/examples/example-3.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {IHeapSnapshot, IHeapEdge} from '@memlab/core'; 12 | import {dumpNodeHeapSnapshot} from '@memlab/core'; 13 | import {getFullHeapFromFile} from '@memlab/heap-analysis'; 14 | 15 | (async function () { 16 | const heapFile = dumpNodeHeapSnapshot(); 17 | const heap: IHeapSnapshot = await getFullHeapFromFile(heapFile); 18 | 19 | // get the total number of heap references 20 | heap.edges.length; 21 | 22 | heap.edges.forEach((edge: IHeapEdge) => { 23 | console.log(edge.name_or_index); 24 | }); 25 | })(); 26 | -------------------------------------------------------------------------------- /packages/api/src/__tests__/heap/examples/example-4.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {IHeapSnapshot} from '@memlab/core'; 12 | import {dumpNodeHeapSnapshot} from '@memlab/core'; 13 | import {getFullHeapFromFile} from '@memlab/heap-analysis'; 14 | 15 | (async function () { 16 | const heapFile = dumpNodeHeapSnapshot(); 17 | const heap: IHeapSnapshot = await getFullHeapFromFile(heapFile); 18 | 19 | const node = heap.getNodeById(1); 20 | if (node) { 21 | console.log(node.id); 22 | } 23 | })(); 24 | -------------------------------------------------------------------------------- /packages/api/src/__tests__/heap/examples/example-5.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {IHeapSnapshot, Nullable} from '@memlab/core'; 12 | import {config, takeNodeMinimalHeap} from '@memlab/core'; 13 | 14 | class TestObject { 15 | public arr1 = [1, 2, 3]; 16 | public arr2 = ['1', '2', '3']; 17 | } 18 | 19 | test('memory test', async () => { 20 | config.muteConsole = true; 21 | let obj: Nullable = new TestObject(); 22 | // get a heap snapshot of the current program state 23 | let heap: IHeapSnapshot = await takeNodeMinimalHeap(); 24 | 25 | // call some function that may add references to obj 26 | // rabbitHole() 27 | 28 | expect(heap.hasObjectWithClassName('TestObject')).toBe(true); 29 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 30 | obj = null; 31 | 32 | heap = await takeNodeMinimalHeap(); 33 | // if rabbitHole does not add new references, the obj can be GCed 34 | expect(heap.hasObjectWithClassName('TestObject')).toBe(false); 35 | }, 60000); 36 | -------------------------------------------------------------------------------- /packages/api/src/__tests__/heap/examples/example-6.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {IHeapSnapshot} from '@memlab/core'; 12 | import {dumpNodeHeapSnapshot} from '@memlab/core'; 13 | import {getFullHeapFromFile} from '@memlab/heap-analysis'; 14 | 15 | (async function () { 16 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 17 | const object = {'memlab-test-heap-property': 'memlab-test-heap-value'}; 18 | 19 | const heapFile = dumpNodeHeapSnapshot(); 20 | const heap: IHeapSnapshot = await getFullHeapFromFile(heapFile); 21 | 22 | // should be true 23 | console.log(heap.hasObjectWithPropertyName('memlab-test-heap-property')); 24 | })(); 25 | -------------------------------------------------------------------------------- /packages/api/src/__tests__/heap/examples/example-7.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {IHeapSnapshot, AnyValue} from '@memlab/core'; 12 | import {config, takeNodeMinimalHeap, tagObject} from '@memlab/core'; 13 | 14 | test('memory test', async () => { 15 | config.muteConsole = true; 16 | const o1: AnyValue = {}; 17 | let o2: AnyValue = {}; 18 | // tag o1 with marker: "memlab-mark-1" 19 | tagObject(o1, 'memlab-mark-1'); 20 | // tag o2 with marker: "memlab-mark-2" 21 | tagObject(o2, 'memlab-mark-2'); 22 | o2 = null; 23 | const heap: IHeapSnapshot = await takeNodeMinimalHeap(); 24 | 25 | // expect object with marker "memlab-mark-1" exists 26 | expect(heap.hasObjectWithTag('memlab-mark-1')).toBe(true); 27 | 28 | // expect object with marker "memlab-mark-2" can be GCed 29 | expect(heap.hasObjectWithTag('memlab-mark-2')).toBe(false); 30 | }, 60000); 31 | -------------------------------------------------------------------------------- /packages/api/src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import path from 'path'; 12 | import {PackageInfoLoader} from '@memlab/core'; 13 | /** @internal */ 14 | export async function registerPackage(): Promise { 15 | return PackageInfoLoader.registerPackage(path.join(__dirname, '..')); 16 | } 17 | 18 | export * from './API'; 19 | export * from '@memlab/heap-analysis'; 20 | export * from './state/ConsoleModeManager'; 21 | export {default as BrowserInteractionResultReader} from './result-reader/BrowserInteractionResultReader'; 22 | export {default as SnapshotResultReader} from './result-reader/SnapshotResultReader'; 23 | export { 24 | dumpNodeHeapSnapshot, 25 | getNodeInnocentHeap, 26 | takeNodeMinimalHeap, 27 | } from '@memlab/core'; 28 | /** @internal */ 29 | export {config} from '@memlab/core'; 30 | -------------------------------------------------------------------------------- /packages/api/src/state/PuppeteerConfigManager.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {MemLabConfig, PuppeteerConfig} from '@memlab/core'; 12 | import type {RunOptions} from '../API'; 13 | 14 | import {utils} from '@memlab/core'; 15 | 16 | /** 17 | * Manage, save, and restore the current state of the PuppeteerConfig. 18 | */ 19 | class PuppeteerStateManager { 20 | getAndUpdateState(config: MemLabConfig, options: RunOptions = {}) { 21 | const existing = config.puppeteerConfig; 22 | config.puppeteerConfig = {...config.puppeteerConfig}; 23 | config.externalCookiesFile = options.cookiesFile; 24 | config.scenario = options.scenario; 25 | if (options.chromiumBinary != null) { 26 | utils.setChromiumBinary(config, options.chromiumBinary); 27 | } 28 | return existing; 29 | } 30 | 31 | restoreState(config: MemLabConfig, puppeteerConfig: PuppeteerConfig) { 32 | config.puppeteerConfig = puppeteerConfig; 33 | } 34 | } 35 | 36 | export default new PuppeteerStateManager(); 37 | -------------------------------------------------------------------------------- /packages/api/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./dist" 6 | }, 7 | "include": ["src/**/*"], 8 | "references": [ 9 | {"path": "../core"}, 10 | {"path": "../e2e"}, 11 | {"path": "../heap-analysis"} 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /packages/cli/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Meta Platforms, Inc. and affiliates. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/cli/README.md: -------------------------------------------------------------------------------- 1 | ## memlab CLI 2 | 3 | This is the memlab CLI library. It contains all memlab commands, command-line options, and command-line interfaces. 4 | The library supports adding new commands. 5 | 6 | ## Online Resources 7 | * [Official Website and Demo](https://facebook.github.io/memlab) 8 | * [Documentation](https://facebook.github.io/memlab/docs/intro) 9 | -------------------------------------------------------------------------------- /packages/cli/bin/memlab.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node --expose-gc --max-old-space-size=4096 2 | 3 | /** 4 | * Copyright (c) Meta Platforms, Inc. and affiliates. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE file in the root directory of this source tree. 8 | * 9 | * @format 10 | * @oncall memory_lab 11 | */ 12 | 13 | // eslint-disable-next-line no-var 14 | var cli = require('../dist/index'); 15 | 16 | // register the `@memlab/cli` package info 17 | // so that `memlab version` get use the info 18 | // eslint-disable-next-line fb-www/promise-termination 19 | cli.registerPackage().then(() => { 20 | cli.run(); 21 | }); 22 | -------------------------------------------------------------------------------- /packages/cli/bin/preinstall: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Copyright (c) Meta Platforms, Inc. and affiliates. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE file in the root directory of this source tree. 8 | * 9 | * @format 10 | * @oncall memory_lab 11 | */ 12 | 13 | const path = require("path"); 14 | const fs = require("fs"); 15 | if (process.platform !== "win32") { 16 | const memlabFile = path.join(__dirname, "../bin/memlab.js"); 17 | const content = fs.readFileSync(memlabFile, "UTF-8"); 18 | fs.writeFileSync( 19 | memlabFile, 20 | content.replace("#!/usr/bin/env node", "#!/usr/bin/env -S node"), 21 | "UTF-8" 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /packages/cli/src/commands/CleanLoggerDataCommand.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {CLIOptions, Optional} from '@memlab/core'; 12 | 13 | import BaseCommand from '../BaseCommand'; 14 | import {fileManager, BaseOption} from '@memlab/core'; 15 | import SetWorkingDirectoryOption from '../options/SetWorkingDirectoryOption'; 16 | import InitDirectoryCommand from './InitDirectoryCommand'; 17 | 18 | export default class CleanTraceDataCommand extends BaseCommand { 19 | getCommandName(): string { 20 | return 'clear-trace-data'; 21 | } 22 | 23 | getDescription(): string { 24 | return 'remove all retainer trace data generated from memlab runs'; 25 | } 26 | 27 | isInternalCommand(): boolean { 28 | return true; 29 | } 30 | 31 | getOptions(): BaseOption[] { 32 | return [new SetWorkingDirectoryOption()]; 33 | } 34 | 35 | async run(options: CLIOptions): Promise { 36 | const workDir = options.configFromOptions?.workDir as Optional; 37 | fileManager.emptyTraceLogDataDir({workDir}); 38 | await new InitDirectoryCommand().run(options); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/cli/src/commands/CleanRunDataCommand.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {CLIOptions, Optional} from '@memlab/core'; 12 | 13 | import BaseCommand from '../BaseCommand'; 14 | import {fileManager, BaseOption} from '@memlab/core'; 15 | import SetWorkingDirectoryOption from '../options/SetWorkingDirectoryOption'; 16 | 17 | export default class CleanRunDataCommand extends BaseCommand { 18 | getCommandName(): string { 19 | return 'clear-run-data'; 20 | } 21 | 22 | getDescription(): string { 23 | return 'remove all web page E2E data generated from memlab runs'; 24 | } 25 | 26 | isInternalCommand(): boolean { 27 | return true; 28 | } 29 | 30 | getOptions(): BaseOption[] { 31 | return [new SetWorkingDirectoryOption()]; 32 | } 33 | 34 | async run(options: CLIOptions): Promise { 35 | const workDir = options.configFromOptions?.workDir as Optional; 36 | fileManager.clearDataDirs({workDir}); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/cli/src/commands/InitDirectoryCommand.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {CLIOptions, Optional} from '@memlab/core'; 12 | 13 | import BaseCommand from '../BaseCommand'; 14 | import {config, fileManager, BaseOption} from '@memlab/core'; 15 | import SetWorkingDirectoryOption from '../options/SetWorkingDirectoryOption'; 16 | 17 | export default class InitDirectoryCommand extends BaseCommand { 18 | getCommandName(): string { 19 | return 'init-dir'; 20 | } 21 | 22 | getDescription(): string { 23 | return 'initialize all directories'; 24 | } 25 | 26 | isInternalCommand(): boolean { 27 | return true; 28 | } 29 | 30 | getOptions(): BaseOption[] { 31 | return [new SetWorkingDirectoryOption()]; 32 | } 33 | 34 | async run(options: CLIOptions): Promise { 35 | const workDir = options.configFromOptions?.workDir as Optional; 36 | fileManager.initDirs(config, {workDir}); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/cli/src/commands/ListScenariosCommand.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {CLIOptions} from '@memlab/core'; 12 | 13 | import chalk from 'chalk'; 14 | import BaseCommand from '../BaseCommand'; 15 | import {defaultTestPlanner} from '@memlab/e2e'; 16 | import {info} from '@memlab/core'; 17 | 18 | export default class ListScenariosCommand extends BaseCommand { 19 | getCommandName(): string { 20 | return 'list'; 21 | } 22 | 23 | getDescription(): string { 24 | return 'List all test scenarios'; 25 | } 26 | 27 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 28 | async run(_options: CLIOptions): Promise { 29 | const names = defaultTestPlanner.getAppNames(); 30 | info.topLevel( 31 | `All available ${chalk.yellow('apps')} and ${chalk.green( 32 | 'interactions', 33 | )}:`, 34 | ); 35 | for (const name of names) { 36 | const targets = defaultTestPlanner 37 | .getTargetNames(name) 38 | .map(name => chalk.green(name)) 39 | .join(chalk.grey(', ')); 40 | info.topLevel(`\n${chalk.yellow(name)}: ${targets}`); 41 | } 42 | info.topLevel(''); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/cli/src/commands/ResetDirectoryCommand.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {CLIOptions, Optional} from '@memlab/core'; 12 | 13 | import BaseCommand from '../BaseCommand'; 14 | import {fileManager, config, BaseOption} from '@memlab/core'; 15 | import SetWorkingDirectoryOption from '../options/SetWorkingDirectoryOption'; 16 | 17 | export default class ResetDirectoryCommand extends BaseCommand { 18 | getCommandName(): string { 19 | return 'reset'; 20 | } 21 | 22 | getDescription(): string { 23 | return 'reset and initialize all directories'; 24 | } 25 | 26 | getOptions(): BaseOption[] { 27 | return [new SetWorkingDirectoryOption()]; 28 | } 29 | 30 | async run(options: CLIOptions): Promise { 31 | const workDir = options.configFromOptions?.workDir as Optional; 32 | fileManager.rmWorkDir({workDir}); 33 | fileManager.initDirs(config, {workDir}); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/cli/src/commands/heap/HeapAnalysisSubCommandWrapper.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {CLIOptions} from '@memlab/core'; 12 | 13 | import {BaseOption} from '@memlab/core'; 14 | import BaseCommand, {CommandCategory} from '../../BaseCommand'; 15 | import {BaseAnalysis} from '@memlab/heap-analysis'; 16 | 17 | export default class HeapAnalysisSubCommandWrapper extends BaseCommand { 18 | private heapAnalysis: BaseAnalysis; 19 | 20 | constructor(analysis: BaseAnalysis) { 21 | super(); 22 | this.heapAnalysis = analysis; 23 | } 24 | 25 | getCommandName(): string { 26 | return this.heapAnalysis.getCommandName(); 27 | } 28 | 29 | getDescription(): string { 30 | return this.heapAnalysis.getDescription(); 31 | } 32 | 33 | getCategory(): CommandCategory { 34 | return CommandCategory.COMMON; 35 | } 36 | 37 | getOptions(): BaseOption[] { 38 | return this.heapAnalysis.getOptions(); 39 | } 40 | 41 | async run(options: CLIOptions): Promise { 42 | await this.heapAnalysis.run({args: options.cliArgs}); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/cli/src/commands/heap/interactive/InteractiveCommandLoader.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import BaseCommand from '../../../BaseCommand'; 12 | import CommandLoader from '../../../CommandLoader'; 13 | import HelperCommand from '../../helper/HelperCommand'; 14 | import GetRetainerTraceCommand from '../GetRetainerTraceCommand'; 15 | 16 | import RunHeapAnalysisCommand from '../HeapAnalysisCommand'; 17 | 18 | const commandToInclude = new Set([ 19 | RunHeapAnalysisCommand, 20 | GetRetainerTraceCommand, 21 | HelperCommand, 22 | ]); 23 | 24 | export default class InteractiveCommandLoader extends CommandLoader { 25 | protected shouldRegisterCommand(command: BaseCommand) { 26 | const constructor = command.constructor as typeof BaseCommand; 27 | return commandToInclude.has(constructor); 28 | } 29 | 30 | protected postRegistration(): void { 31 | // do nothing 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/cli/src/commands/helper/lib/CommandOrder.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {CommandOrder} from './Types'; 12 | import {constant, setInternalValue} from '@memlab/core'; 13 | import {CommandCategory} from '../../../BaseCommand'; 14 | import MemLabRunCommand from '../../MemLabRunCommand'; 15 | import ListScenariosCommand from '../../ListScenariosCommand'; 16 | import CheckLeakCommand from '../../heap/CheckLeakCommand'; 17 | import GetRetainerTraceCommand from '../../heap/GetRetainerTraceCommand'; 18 | import HeapAnalysisCommand from '../../heap/HeapAnalysisCommand'; 19 | 20 | const commandOrder: CommandOrder = [ 21 | { 22 | category: CommandCategory.COMMON, 23 | commands: [ 24 | new MemLabRunCommand(), 25 | new ListScenariosCommand(), 26 | new GetRetainerTraceCommand(), 27 | new CheckLeakCommand(), 28 | new HeapAnalysisCommand(), 29 | ], 30 | }, 31 | { 32 | category: CommandCategory.DEV, 33 | commands: [], 34 | }, 35 | { 36 | category: CommandCategory.MISC, 37 | commands: [], 38 | }, 39 | ]; 40 | 41 | export default setInternalValue(commandOrder, __filename, constant.internalDir); 42 | -------------------------------------------------------------------------------- /packages/cli/src/commands/helper/lib/Types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import BaseCommand, {CommandCategory} from '../../../BaseCommand'; 12 | 13 | export type CommandOrderItem = { 14 | category: CommandCategory; 15 | commands: BaseCommand[]; 16 | }; 17 | 18 | export type CommandOrder = Array; 19 | -------------------------------------------------------------------------------- /packages/cli/src/commands/query/QueryDefaultWorkDirCommand.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import {CLIOptions, info} from '@memlab/core'; 12 | 13 | import BaseCommand, {CommandCategory} from '../../BaseCommand'; 14 | import {fileManager, BaseOption} from '@memlab/core'; 15 | 16 | export default class QueryDefaultWorkDirCommand extends BaseCommand { 17 | getCommandName(): string { 18 | return 'get-default-work-dir'; 19 | } 20 | 21 | getDescription(): string { 22 | return 'query the default working directory'; 23 | } 24 | 25 | getCategory(): CommandCategory { 26 | return CommandCategory.MISC; 27 | } 28 | 29 | getOptions(): BaseOption[] { 30 | return []; 31 | } 32 | 33 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 34 | async run(_options: CLIOptions): Promise { 35 | info.topLevel(fileManager.getWorkDir()); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/cli/src/commands/snapshot/CheckXvfbSupportCommand.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {CLIOptions} from '@memlab/core'; 12 | 13 | import cp from 'child_process'; 14 | import BaseCommand from '../../BaseCommand'; 15 | import {config, info} from '@memlab/core'; 16 | 17 | export default class CheckXvfbSupportCommand extends BaseCommand { 18 | getCommandName(): string { 19 | return 'check-xvfb'; 20 | } 21 | 22 | getDescription(): string { 23 | return 'if Xvfb is installed on the machine, enable it'; 24 | } 25 | 26 | isInternalCommand(): boolean { 27 | return true; 28 | } 29 | 30 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 31 | async run(_options: CLIOptions): Promise { 32 | try { 33 | const ret = cp.execSync('command -v xvfb-run').toString(); 34 | if (ret) { 35 | config.machineSupportsXVFB = true; 36 | } 37 | } catch { 38 | // the env doesn't support XVFB, no need to do anything; 39 | } 40 | if (config.verbose) { 41 | info.lowLevel(`Xvfb supports: ${config.machineSupportsXVFB}`); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/cli/src/commands/snapshot/Snapshot.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import {config, info, utils} from '@memlab/core'; 12 | import {testInBrowser} from '@memlab/api'; 13 | 14 | export async function runPageInteractionFromCLI(): Promise { 15 | const start = Date.now(); 16 | try { 17 | await testInBrowser(); 18 | } catch (e) { 19 | const error = utils.getError(e); 20 | if (error.message.indexOf('cannot open display') < 0) { 21 | // fail due to other errors 22 | utils.haltOrThrow(error); 23 | } 24 | if (config.verbose) { 25 | info.lowLevel('failed to start Chrome with display, disabling xvfb...'); 26 | } 27 | config.machineSupportsXVFB = false; 28 | config.disableXvfb(); 29 | await testInBrowser(); 30 | } 31 | 32 | const end = Date.now(); 33 | info.topLevel(`total time: ${utils.getReadableTime(end - start)}`); 34 | } 35 | -------------------------------------------------------------------------------- /packages/cli/src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import path from 'path'; 12 | import {PackageInfoLoader} from '@memlab/core'; 13 | /** @internal */ 14 | export async function registerPackage(): Promise { 15 | return PackageInfoLoader.registerPackage(path.join(__dirname, '..')); 16 | } 17 | 18 | export * from './runner'; 19 | -------------------------------------------------------------------------------- /packages/cli/src/options/DebugOption.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {ParsedArgs} from 'minimist'; 12 | import type {MemLabConfig} from '@memlab/core'; 13 | import {BaseOption} from '@memlab/core'; 14 | import optionConstants from './lib/OptionConstant'; 15 | 16 | export default class DebugOption extends BaseOption { 17 | getOptionName(): string { 18 | return optionConstants.optionNames.DEBUG; 19 | } 20 | 21 | getDescription(): string { 22 | return 'enable manual debugging'; 23 | } 24 | 25 | async parse(config: MemLabConfig, args: ParsedArgs): Promise { 26 | if (args[this.getOptionName()]) { 27 | config.isManualDebug = true; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/cli/src/options/HelperOption.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {ParsedArgs} from 'minimist'; 12 | import type {MemLabConfig} from '@memlab/core'; 13 | import {BaseOption} from '@memlab/core'; 14 | import optionConstants from './lib/OptionConstant'; 15 | 16 | export default class HelperOption extends BaseOption { 17 | getOptionName(): string { 18 | return optionConstants.optionNames.HELP; 19 | } 20 | 21 | getOptionShortcut(): string { 22 | return optionConstants.optionShortcuts.H; 23 | } 24 | 25 | getDescription(): string { 26 | return 'print helper text'; 27 | } 28 | 29 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 30 | async parse(_config: MemLabConfig, _args: ParsedArgs): Promise { 31 | // the logic is done in dispatcher 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/cli/src/options/MLClusteringOption.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {ParsedArgs} from 'minimist'; 12 | import type {MemLabConfig} from '@memlab/core'; 13 | import {BaseOption} from '@memlab/core'; 14 | import optionConstants from './lib/OptionConstant'; 15 | 16 | export default class MLClusteringOption extends BaseOption { 17 | getOptionName(): string { 18 | return optionConstants.optionNames.ML_CLUSTERING; 19 | } 20 | 21 | getDescription(): string { 22 | return 'use machine learning algorithms for clustering leak traces (by default, traces are clustered by heuristics)'; 23 | } 24 | 25 | static hasOptionSet(args: ParsedArgs): boolean { 26 | const name = optionConstants.optionNames.ML_CLUSTERING; 27 | return args[name] != null; 28 | } 29 | 30 | async parse(config: MemLabConfig, args: ParsedArgs): Promise { 31 | const name = this.getOptionName(); 32 | if (args[name]) { 33 | config.isMLClustering = true; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/cli/src/options/SetContinuousTestOption.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {ParsedArgs} from 'minimist'; 12 | import type {MemLabConfig} from '@memlab/core'; 13 | import {BaseOption} from '@memlab/core'; 14 | import optionConstants from './lib/OptionConstant'; 15 | 16 | export default class SetContinuousTestOption extends BaseOption { 17 | getOptionName(): string { 18 | return optionConstants.optionNames.SC; 19 | } 20 | 21 | getDescription(): string { 22 | return 'set to continuous test mode'; 23 | } 24 | 25 | async parse(config: MemLabConfig, args: ParsedArgs): Promise { 26 | if ( 27 | args[optionConstants.optionNames.CONTINUS_TEST] || 28 | args[this.getOptionName()] 29 | ) { 30 | config.isContinuousTest = true; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/cli/src/options/SetMaxClusterSampleSizeOption.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {ParsedArgs} from 'minimist'; 12 | import type {MemLabConfig} from '@memlab/core'; 13 | import {BaseOption} from '@memlab/core'; 14 | import optionConstants from './lib/OptionConstant'; 15 | 16 | export default class SetMaxClusterSampleSizeOption extends BaseOption { 17 | getOptionName(): string { 18 | return optionConstants.optionNames.MAX_CLUSTER_SAMPLE_SIZE; 19 | } 20 | 21 | getDescription(): string { 22 | return ( 23 | 'specify the max number of leak traces as input to leak trace ' + 24 | 'clustering algorithm. Big sample size will preserve more complete ' + 25 | 'inforrmation, but may risk out-of-memory crash.' 26 | ); 27 | } 28 | 29 | getExampleValues(): string[] { 30 | return ['5000', '10000']; 31 | } 32 | 33 | async parse(config: MemLabConfig, args: ParsedArgs): Promise { 34 | if (args[this.getOptionName()]) { 35 | const sampleSize = parseInt(args[this.getOptionName()], 10); 36 | if (!isNaN(sampleSize)) { 37 | config.maxSamplesForClustering = sampleSize; 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/cli/src/options/SetWorkingDirectoryOption.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {ParsedArgs} from 'minimist'; 12 | import {AnyRecord, MemLabConfig} from '@memlab/core'; 13 | import {BaseOption} from '@memlab/core'; 14 | import optionConstants from './lib/OptionConstant'; 15 | 16 | export default class SetWorkingDirectoryOption extends BaseOption { 17 | getOptionName(): string { 18 | return optionConstants.optionNames.WORK_DIR; 19 | } 20 | 21 | getDescription(): string { 22 | return 'set the working directory of the current run'; 23 | } 24 | 25 | async parse( 26 | config: MemLabConfig, 27 | args: ParsedArgs, 28 | ): Promise<{workDir?: string}> { 29 | const name = this.getOptionName(); 30 | const ret: AnyRecord = {}; 31 | const workDir = args[name]; 32 | if (workDir) { 33 | ret.workDir = workDir; 34 | } 35 | return ret; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/cli/src/options/SilentOption.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {ParsedArgs} from 'minimist'; 12 | import type {MemLabConfig} from '@memlab/core'; 13 | import {BaseOption} from '@memlab/core'; 14 | import optionConstants from './lib/OptionConstant'; 15 | 16 | export default class SilentOption extends BaseOption { 17 | getOptionName(): string { 18 | return optionConstants.optionNames.SILENT; 19 | } 20 | 21 | getOptionShortcut(): string { 22 | return optionConstants.optionShortcuts.S; 23 | } 24 | 25 | getDescription(): string { 26 | return 'mute all terminal output'; 27 | } 28 | 29 | async parse(config: MemLabConfig, args: ParsedArgs): Promise { 30 | if (args[this.getOptionName()] || args[this.getOptionShortcut()]) { 31 | config.muteConsole = true; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/cli/src/options/VerboseOption.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {ParsedArgs} from 'minimist'; 12 | import type {MemLabConfig} from '@memlab/core'; 13 | import {BaseOption} from '@memlab/core'; 14 | import optionConstants from './lib/OptionConstant'; 15 | 16 | export default class VerboseOption extends BaseOption { 17 | getOptionName(): string { 18 | return optionConstants.optionNames.VERBOSE; 19 | } 20 | 21 | getOptionShortcut(): string | null { 22 | return optionConstants.optionShortcuts.V; 23 | } 24 | 25 | getDescription(): string { 26 | return 'show more details'; 27 | } 28 | 29 | async parse(config: MemLabConfig, args: ParsedArgs): Promise { 30 | if (args.verbose || args.v) { 31 | config.verbose = true; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/cli/src/options/e2e/AppOption.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {ParsedArgs} from 'minimist'; 12 | import type {MemLabConfig} from '@memlab/core'; 13 | import {BaseOption} from '@memlab/core'; 14 | import optionConstants from '../lib/OptionConstant'; 15 | 16 | export default class AppOption extends BaseOption { 17 | getOptionName(): string { 18 | return optionConstants.optionNames.APP; 19 | } 20 | 21 | getDescription(): string { 22 | return 'set name for onboarded web application'; 23 | } 24 | 25 | getExampleValues(): string[] { 26 | return ['comet', 'ads-manager']; 27 | } 28 | 29 | async parse(config: MemLabConfig, args: ParsedArgs): Promise { 30 | const name = this.getOptionName(); 31 | const arg = args[name]; 32 | if (arg) { 33 | config.targetApp = Array.isArray(arg) ? arg[arg.length - 1] : arg; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/cli/src/options/e2e/DisableWebSecurityOption.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {ParsedArgs} from 'minimist'; 12 | import type {MemLabConfig} from '@memlab/core'; 13 | import {BaseOption} from '@memlab/core'; 14 | import optionConstants from '../lib/OptionConstant'; 15 | 16 | export default class DisableWebSecurityOption extends BaseOption { 17 | getOptionName(): string { 18 | return optionConstants.optionNames.DISABLE_WEB_SECURITY; 19 | } 20 | 21 | getDescription(): string { 22 | return ( 23 | 'disable web security in Chromium to enable cross domain requests; ' + 24 | 'web security is enabled by default' 25 | ); 26 | } 27 | 28 | async parse(config: MemLabConfig, args: ParsedArgs): Promise { 29 | if (args[this.getOptionName()]) { 30 | config.disableWebSecurity = true; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/cli/src/options/e2e/DisableXvfbOption.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {ParsedArgs} from 'minimist'; 12 | import type {MemLabConfig} from '@memlab/core'; 13 | import {BaseOption} from '@memlab/core'; 14 | import optionConstants from '../lib/OptionConstant'; 15 | 16 | export default class DisableXvfbOption extends BaseOption { 17 | getOptionName(): string { 18 | return optionConstants.optionNames.DISABLE_XVFB; 19 | } 20 | 21 | getDescription(): string { 22 | return 'disable Xvfb (X virtual framebuffer) for simulating headful browser rendering'; 23 | } 24 | 25 | async parse(config: MemLabConfig, args: ParsedArgs): Promise { 26 | if (args[this.getOptionName()]) { 27 | config.useXVFB = false; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/cli/src/options/e2e/DisplayLeakOutlinesOptions.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {ParsedArgs} from 'minimist'; 12 | import type {MemLabConfig} from '@memlab/core'; 13 | import {BaseOption} from '@memlab/core'; 14 | import optionConstants from '../lib/OptionConstant'; 15 | 16 | export default class DisplayLeakOutlinesOptions extends BaseOption { 17 | getOptionName(): string { 18 | return optionConstants.optionNames.DISPLAY_LEAK_OUTLINES; 19 | } 20 | 21 | getDescription(): string { 22 | return ( 23 | 'display leaked component outlines in headful browser; ' + 24 | `use this with the --${optionConstants.optionNames.HEADFUL} option` 25 | ); 26 | } 27 | 28 | async parse(config: MemLabConfig, args: ParsedArgs): Promise { 29 | if (args[this.getOptionName()]) { 30 | config.displayLeakOutlines = true; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/cli/src/options/e2e/EnableJSInterceptOption.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {ParsedArgs} from 'minimist'; 12 | import type {MemLabConfig} from '@memlab/core'; 13 | import {BaseOption} from '@memlab/core'; 14 | import optionConstants from '../lib/OptionConstant'; 15 | 16 | export default class EnableJSInterceptOption extends BaseOption { 17 | getOptionName(): string { 18 | return optionConstants.optionNames.LOG_SCRIPT; 19 | } 20 | 21 | getDescription(): string { 22 | return 'enable intercepting and logging JavaScript code in browser'; 23 | } 24 | 25 | async parse(config: MemLabConfig, args: ParsedArgs): Promise { 26 | if (args[this.getOptionName()]) { 27 | // intercept script 28 | config.interceptScript = true; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/cli/src/options/e2e/EnableJSRewriteOption.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {ParsedArgs} from 'minimist'; 12 | import type {MemLabConfig} from '@memlab/core'; 13 | import {BaseOption} from '@memlab/core'; 14 | import optionConstants from '../lib/OptionConstant'; 15 | 16 | export default class EnableJSRewriteOption extends BaseOption { 17 | getOptionName(): string { 18 | return optionConstants.optionNames.REWRITE_JS; 19 | } 20 | 21 | getDescription(): string { 22 | return 'enable instrument JavaScript code in browser'; 23 | } 24 | 25 | async parse(config: MemLabConfig, args: ParsedArgs): Promise { 26 | if (args[this.getOptionName()]) { 27 | // intercept script 28 | config.interceptScript = true; 29 | // rewrite script 30 | config.instrumentJS = true; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/cli/src/options/e2e/FullExecutionOption.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {ParsedArgs} from 'minimist'; 12 | import type {MemLabConfig} from '@memlab/core'; 13 | import {BaseOption} from '@memlab/core'; 14 | import optionConstants from '../lib/OptionConstant'; 15 | 16 | export default class FullExecutionOption extends BaseOption { 17 | getOptionName(): string { 18 | return optionConstants.optionNames.FULL; 19 | } 20 | 21 | getDescription(): string { 22 | return 'take heap snapshot for every step in E2E interaction'; 23 | } 24 | 25 | async parse(config: MemLabConfig, args: ParsedArgs): Promise { 26 | if (args[this.getOptionName()]) { 27 | config.isFullRun = true; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/cli/src/options/e2e/HeadfulBrowserOption.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {ParsedArgs} from 'minimist'; 12 | import type {MemLabConfig} from '@memlab/core'; 13 | import {BaseOption} from '@memlab/core'; 14 | import optionConstants from '../lib/OptionConstant'; 15 | 16 | export default class HeadfulBrowserOption extends BaseOption { 17 | getOptionName(): string { 18 | return optionConstants.optionNames.HEADFUL; 19 | } 20 | 21 | getDescription(): string { 22 | return 'start the browser in headful mode, by default it is headless'; 23 | } 24 | 25 | async parse(config: MemLabConfig, args: ParsedArgs): Promise { 26 | if (args[this.getOptionName()]) { 27 | config.isHeadfulBrowser = true; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/cli/src/options/e2e/InteractionOption.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {ParsedArgs} from 'minimist'; 12 | import type {MemLabConfig} from '@memlab/core'; 13 | import {BaseOption} from '@memlab/core'; 14 | import optionConstants from '../lib/OptionConstant'; 15 | 16 | export default class InteractionOption extends BaseOption { 17 | getOptionName(): string { 18 | return optionConstants.optionNames.INTERACTION; 19 | } 20 | 21 | getDescription(): string { 22 | return 'set name for onboarded interaction'; 23 | } 24 | 25 | getExampleValues(): string[] { 26 | return ['watch', 'campaign-tab']; 27 | } 28 | 29 | async parse(config: MemLabConfig, args: ParsedArgs): Promise { 30 | const name = this.getOptionName(); 31 | const arg = args[name]; 32 | if (arg) { 33 | config.targetTab = Array.isArray(arg) ? arg[arg.length - 1] : arg; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/cli/src/options/e2e/RemoteBrowserDebugOption.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {ParsedArgs} from 'minimist'; 12 | import type {MemLabConfig} from '@memlab/core'; 13 | import {BaseOption} from '@memlab/core'; 14 | import optionConstants from '../lib/OptionConstant'; 15 | 16 | export default class RemoteBrowserDebugOption extends BaseOption { 17 | getOptionName(): string { 18 | return optionConstants.optionNames.LOCAL_PUPPETEER; 19 | } 20 | 21 | getDescription(): string { 22 | return 'enable remote browser instance debugging via local puppeteer'; 23 | } 24 | 25 | async parse(config: MemLabConfig, args: ParsedArgs): Promise { 26 | if (args[this.getOptionName()]) { 27 | config.isLocalPuppeteer = true; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/cli/src/options/e2e/RunningModeOption.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {ParsedArgs} from 'minimist'; 12 | import type {MemLabConfig} from '@memlab/core'; 13 | import {BaseOption, modes} from '@memlab/core'; 14 | import optionConstants from '../lib/OptionConstant'; 15 | 16 | export default class RunningModeOption extends BaseOption { 17 | getOptionName(): string { 18 | return optionConstants.optionNames.RUN_MODE; 19 | } 20 | 21 | getDescription(): string { 22 | return 'set running mode'; 23 | } 24 | 25 | getExampleValues(): string[] { 26 | return ['regular', 'measure', 'interaction-test']; 27 | } 28 | 29 | async parse(config: MemLabConfig, args: ParsedArgs): Promise { 30 | if (args[this.getOptionName()]) { 31 | config.runningMode = modes.get(args['run-mode'], config); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/cli/src/options/e2e/ScenarioFileOption.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {ParsedArgs} from 'minimist'; 12 | import type {MemLabConfig} from '@memlab/core'; 13 | import {constant, utils, BaseOption} from '@memlab/core'; 14 | import {E2EUtils} from '@memlab/e2e'; 15 | import optionConstants from '../lib/OptionConstant'; 16 | 17 | export default class ScenarioFileOption extends BaseOption { 18 | getOptionName(): string { 19 | return optionConstants.optionNames.SCENARIO; 20 | } 21 | 22 | getDescription(): string { 23 | return 'set file path loading test scenario'; 24 | } 25 | 26 | getExampleValues(): string[] { 27 | return ['/tmp/scenario.js']; 28 | } 29 | 30 | async parse(config: MemLabConfig, args: ParsedArgs): Promise { 31 | const name = this.getOptionName(); 32 | if (args[name]) { 33 | const scenarioFile = args[name] as string; 34 | // load scenario file and get target app name 35 | config.scenario = utils.loadScenario(scenarioFile); 36 | config.targetApp = E2EUtils.getScenarioAppName(config.scenario); 37 | config.targetTab = constant.unset; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/cli/src/options/e2e/SetChromiumBinaryOption.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | import type {ParsedArgs} from 'minimist'; 11 | import {MemLabConfig} from '@memlab/core'; 12 | 13 | import {utils, BaseOption} from '@memlab/core'; 14 | import optionConstants from '../lib/OptionConstant'; 15 | 16 | export default class SetChromiumBinaryOption extends BaseOption { 17 | getOptionName(): string { 18 | return optionConstants.optionNames.CHROMIUM_BINARY; 19 | } 20 | 21 | getDescription(): string { 22 | return 'set the chromium binary for E2E run'; 23 | } 24 | 25 | async parse(config: MemLabConfig, args: ParsedArgs): Promise { 26 | const name = this.getOptionName(); 27 | const arg = args[name]; 28 | if (arg) { 29 | utils.setChromiumBinary(config, arg); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/cli/src/options/e2e/SetDeviceOption.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {ParsedArgs} from 'minimist'; 12 | import type {MemLabConfig} from '@memlab/core'; 13 | import {BaseOption, constant} from '@memlab/core'; 14 | import optionConstants from '../lib/OptionConstant'; 15 | 16 | const devices = constant.isFRL 17 | ? {} 18 | : constant.isFB 19 | ? require('puppeteer-core/DeviceDescriptors') 20 | : // eslint-disable-next-line @typescript-eslint/no-var-requires 21 | require('puppeteer').KnownDevices; 22 | 23 | export default class SetDeviceOption extends BaseOption { 24 | getOptionName(): string { 25 | return optionConstants.optionNames.DEVICE; 26 | } 27 | 28 | getDescription(): string { 29 | return 'set the device type to emulate'; 30 | } 31 | 32 | getExampleValues(): string[] { 33 | return Object.keys(devices); 34 | } 35 | 36 | async parse(config: MemLabConfig, args: ParsedArgs): Promise { 37 | const name = this.getOptionName(); 38 | const arg = args[name]; 39 | if (arg) { 40 | config.setDevice(arg, {manualOverride: true}); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/cli/src/options/e2e/SetUserAgentOption.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {ParsedArgs} from 'minimist'; 12 | import type {MemLabConfig} from '@memlab/core'; 13 | import {BaseOption} from '@memlab/core'; 14 | import optionConstants from '../lib/OptionConstant'; 15 | 16 | export default class SetUserAgentOption extends BaseOption { 17 | getOptionName(): string { 18 | return optionConstants.optionNames.USER_AGENT; 19 | } 20 | 21 | getDescription(): string { 22 | return ( 23 | 'set the UserAgent string in browser (for E2E interaction), ' + 24 | 'otherwise it uses the default UserAgent from Chromium' 25 | ); 26 | } 27 | 28 | getExampleValues(): string[] { 29 | return ['"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2)"']; 30 | } 31 | 32 | async parse(config: MemLabConfig, args: ParsedArgs): Promise { 33 | const optionName = this.getOptionName(); 34 | if (args[optionName]) { 35 | config.defaultUserAgent = args[optionName]; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/cli/src/options/e2e/SkipExtraOperationOption.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {ParsedArgs} from 'minimist'; 12 | import type {MemLabConfig} from '@memlab/core'; 13 | import {BaseOption} from '@memlab/core'; 14 | import optionConstants from '../lib/OptionConstant'; 15 | 16 | export default class SkipExtraOperationOption extends BaseOption { 17 | getOptionName(): string { 18 | return optionConstants.optionNames.SKIP_EXTRA_OPS; 19 | } 20 | 21 | getDescription(): string { 22 | return 'skip doing extra interactions (e.g., scrolling and waiting) on target and final page'; 23 | } 24 | 25 | async parse(config: MemLabConfig, args: ParsedArgs): Promise { 26 | if ( 27 | args[this.getOptionName()] || 28 | args[optionConstants.optionNames.SKIP_EXTRA_OP] 29 | ) { 30 | config.skipExtraOps = true; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/cli/src/options/e2e/SkipGCOption.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {ParsedArgs} from 'minimist'; 12 | import type {MemLabConfig} from '@memlab/core'; 13 | import {BaseOption} from '@memlab/core'; 14 | import optionConstants from '../lib/OptionConstant'; 15 | 16 | export default class SkipGCOption extends BaseOption { 17 | getOptionName(): string { 18 | return optionConstants.optionNames.SKIP_GC; 19 | } 20 | 21 | getDescription(): string { 22 | return 'skip doing garbage collection in browser'; 23 | } 24 | 25 | async parse(config: MemLabConfig, args: ParsedArgs): Promise { 26 | if (args[this.getOptionName()]) { 27 | config.skipGC = true; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/cli/src/options/e2e/SkipScreenshotOption.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {ParsedArgs} from 'minimist'; 12 | import type {MemLabConfig} from '@memlab/core'; 13 | import {BaseOption} from '@memlab/core'; 14 | import optionConstants from '../lib/OptionConstant'; 15 | 16 | export default class SkipScreenshotOption extends BaseOption { 17 | getOptionName(): string { 18 | return optionConstants.optionNames.SKIP_SCREENSHOT; 19 | } 20 | 21 | getDescription(): string { 22 | return 'skip taking screenshots'; 23 | } 24 | 25 | async parse(config: MemLabConfig, args: ParsedArgs): Promise { 26 | if (args[this.getOptionName()]) { 27 | config.skipScreenshot = true; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/cli/src/options/e2e/SkipScrollOption.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {ParsedArgs} from 'minimist'; 12 | import type {MemLabConfig} from '@memlab/core'; 13 | import {BaseOption} from '@memlab/core'; 14 | import optionConstants from '../lib/OptionConstant'; 15 | 16 | export default class SkipScrollOption extends BaseOption { 17 | getOptionName(): string { 18 | return optionConstants.optionNames.SKIP_SCROLL; 19 | } 20 | 21 | getDescription(): string { 22 | return 'skip scrolling target page in browser'; 23 | } 24 | 25 | async parse(config: MemLabConfig, args: ParsedArgs): Promise { 26 | if (args[this.getOptionName()]) { 27 | config.skipScroll = true; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/cli/src/options/e2e/SkipSnapshotOption.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {ParsedArgs} from 'minimist'; 12 | import type {MemLabConfig} from '@memlab/core'; 13 | import {BaseOption} from '@memlab/core'; 14 | import optionConstants from '../lib/OptionConstant'; 15 | 16 | export default class SkipSnapshotOption extends BaseOption { 17 | getOptionName(): string { 18 | return optionConstants.optionNames.SKIP_SNAPSHOT; 19 | } 20 | 21 | getDescription(): string { 22 | return 'skip taking heap snapshots'; 23 | } 24 | 25 | async parse(config: MemLabConfig, args: ParsedArgs): Promise { 26 | if (args[this.getOptionName()]) { 27 | config.skipSnapshot = true; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/cli/src/options/e2e/SkipWarmupOption.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {ParsedArgs} from 'minimist'; 12 | import type {MemLabConfig} from '@memlab/core'; 13 | import {BaseOption} from '@memlab/core'; 14 | import optionConstants from '../lib/OptionConstant'; 15 | 16 | export default class SkipWarmupOption extends BaseOption { 17 | getOptionName(): string { 18 | return optionConstants.optionNames.SKIP_WARMUP; 19 | } 20 | 21 | getDescription(): string { 22 | return 'skip warming up web server'; 23 | } 24 | 25 | async parse(config: MemLabConfig, args: ParsedArgs): Promise { 26 | if (args[this.getOptionName()]) { 27 | config.skipWarmup = true; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/cli/src/options/e2e/TargetWorkerOption.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {ParsedArgs} from 'minimist'; 12 | import type {MemLabConfig} from '@memlab/core'; 13 | import {BaseOption} from '@memlab/core'; 14 | import optionConstants from '../lib/OptionConstant'; 15 | 16 | export default class TargetWorkerOption extends BaseOption { 17 | getOptionName(): string { 18 | return optionConstants.optionNames.WORKER_TITLE; 19 | } 20 | 21 | getDescription(): string { 22 | return ( 23 | 'set title of the target (worker) that ' + 24 | 'needs to be selected and analyzed' 25 | ); 26 | } 27 | 28 | getExampleValues(): string[] { 29 | return ['WorkerTitle']; 30 | } 31 | 32 | async parse(config: MemLabConfig, args: ParsedArgs): Promise { 33 | const name = this.getOptionName(); 34 | const arg = args[name]; 35 | if (arg) { 36 | config.isAnalyzingMainThread = false; 37 | const value = Array.isArray(arg) ? arg[arg.length - 1] : arg; 38 | if (typeof value === 'string') { 39 | config.targetWorkerTitle = value; 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/cli/src/options/experiment/ExperimentOptionUtils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {ParsedArgs} from 'minimist'; 12 | import type {Nullable} from '@memlab/core'; 13 | 14 | import fs from 'fs'; 15 | import {fileManager} from '@memlab/core'; 16 | 17 | export function extractAndCheckWorkDirs( 18 | optionName: string, 19 | args: ParsedArgs, 20 | ): Nullable { 21 | let dirs: string[] = []; 22 | const flagValue = args[optionName]; 23 | if (!flagValue) { 24 | return null; 25 | } 26 | if (Array.isArray(flagValue)) { 27 | dirs = flagValue as string[]; 28 | } else { 29 | dirs = [flagValue] as string[]; 30 | } 31 | for (const dir of dirs) { 32 | if (fs.existsSync(dir)) { 33 | fileManager.createDefaultVisitOrderMetaFile({ 34 | workDir: dir, 35 | }); 36 | } 37 | } 38 | return dirs; 39 | } 40 | -------------------------------------------------------------------------------- /packages/cli/src/options/experiment/SetControlWorkDirOption.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {ParsedArgs} from 'minimist'; 12 | import type {MemLabConfig, Nullable} from '@memlab/core'; 13 | 14 | import {BaseOption} from '@memlab/core'; 15 | import optionConstants from '../lib/OptionConstant'; 16 | import {extractAndCheckWorkDirs} from './ExperimentOptionUtils'; 17 | 18 | export default class SetControlWorkDirOption extends BaseOption { 19 | getOptionName(): string { 20 | return optionConstants.optionNames.CONTROL_WORK_DIR; 21 | } 22 | 23 | getDescription(): string { 24 | return 'set the working directory of the control run'; 25 | } 26 | 27 | async parse( 28 | _config: MemLabConfig, 29 | args: ParsedArgs, 30 | ): Promise<{controlWorkDirs?: Nullable}> { 31 | const dirs = extractAndCheckWorkDirs(this.getOptionName(), args); 32 | return {controlWorkDirs: dirs}; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/cli/src/options/experiment/SetTreatmentWorkDirOption.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {ParsedArgs} from 'minimist'; 12 | import type {MemLabConfig, Nullable} from '@memlab/core'; 13 | 14 | import {BaseOption} from '@memlab/core'; 15 | import optionConstants from '../lib/OptionConstant'; 16 | import {extractAndCheckWorkDirs} from './ExperimentOptionUtils'; 17 | 18 | export default class SetTreatmentWorkDirOption extends BaseOption { 19 | getOptionName(): string { 20 | return optionConstants.optionNames.TREATMENT_WORK_DIR; 21 | } 22 | 23 | getDescription(): string { 24 | return 'set the working directory of the treatment run'; 25 | } 26 | 27 | async parse( 28 | config: MemLabConfig, 29 | args: ParsedArgs, 30 | ): Promise<{treatmentWorkDirs?: Nullable}> { 31 | const dirs = extractAndCheckWorkDirs(this.getOptionName(), args); 32 | return {treatmentWorkDirs: dirs}; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/cli/src/options/experiment/utils/ExperimentOptionsUtils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | import {AnyValue, MemLabConfig, fileManager, utils} from '@memlab/core'; 11 | import {existsSync} from 'fs-extra'; 12 | 13 | export function validateHeapSnapshotFileOrThrow(file: AnyValue): string { 14 | if (typeof file !== 'string') { 15 | throw utils.haltOrThrow( 16 | `Heap snapshot file must be a string, but got ${typeof file}`, 17 | ); 18 | } 19 | if (!file.endsWith('.heapsnapshot')) { 20 | throw utils.haltOrThrow( 21 | `Heap snapshot file must end with .heapsnapshot, but got ${file}`, 22 | ); 23 | } 24 | if (!existsSync(file)) { 25 | throw utils.haltOrThrow(`Heap snapshot file ${file} does not exist`); 26 | } 27 | return file; 28 | } 29 | 30 | export function createTransientWorkDirFromSingleHeapSnapshot( 31 | file: string, 32 | ): string { 33 | const config = MemLabConfig.resetConfigWithTransientDir(); 34 | fileManager.createOrOverrideVisitOrderMetaFileForExternalSnapshot(file); 35 | return config.workDir; 36 | } 37 | -------------------------------------------------------------------------------- /packages/cli/src/options/heap/CleanupSnapshotOption.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {ParsedArgs} from 'minimist'; 12 | 13 | import {BaseOption, MemLabConfig} from '@memlab/core'; 14 | import optionConstants from '../lib/OptionConstant'; 15 | 16 | export default class CleanupSnapshotOption extends BaseOption { 17 | getOptionName(): string { 18 | return optionConstants.optionNames.CLEAN_UP_SNAPSHOT; 19 | } 20 | 21 | getDescription(): string { 22 | return 'clean up heap snapshots after running'; 23 | } 24 | 25 | async parse( 26 | config: MemLabConfig, 27 | args: ParsedArgs, 28 | ): Promise<{cleanUpSnapshot: boolean}> { 29 | const name = this.getOptionName(); 30 | return { 31 | cleanUpSnapshot: !!args[name], 32 | }; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/cli/src/options/heap/HeapAnalysisPluginOption.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {ParsedArgs} from 'minimist'; 12 | import {AnyRecord, MemLabConfig} from '@memlab/core'; 13 | import {BaseOption} from '@memlab/core'; 14 | import optionConstants from '../lib/OptionConstant'; 15 | 16 | export default class HeapAnalysisPluginOption extends BaseOption { 17 | getOptionName(): string { 18 | return optionConstants.optionNames.HEAP_ANALYSIS_PLUGIN_FILE; 19 | } 20 | 21 | getDescription(): string { 22 | return ( 23 | 'specify the external heap analysis plugin file ' + 24 | '(must be a vanilla JS file ended with `Analysis.js` suffix)' 25 | ); 26 | } 27 | 28 | async parse( 29 | config: MemLabConfig, 30 | args: ParsedArgs, 31 | ): Promise<{workDir?: string}> { 32 | const name = this.getOptionName(); 33 | const ret: AnyRecord = {}; 34 | const heapAnalysisPlugin = args[name]; 35 | if (heapAnalysisPlugin) { 36 | ret.heapAnalysisPlugin = heapAnalysisPlugin; 37 | } 38 | return ret; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/cli/src/options/heap/HeapNodeIdOption.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {ParsedArgs} from 'minimist'; 12 | import type {MemLabConfig} from '@memlab/core'; 13 | import {BaseOption} from '@memlab/core'; 14 | import optionConstants from '../lib/OptionConstant'; 15 | 16 | export default class HeapNodeIdOption extends BaseOption { 17 | getOptionName(): string { 18 | return optionConstants.optionNames.NODE_ID; 19 | } 20 | 21 | getDescription(): string { 22 | return 'set heap node ID'; 23 | } 24 | 25 | getExampleValues(): string[] { 26 | return ['94435', '@94435']; 27 | } 28 | 29 | async parse(config: MemLabConfig, args: ParsedArgs): Promise { 30 | const optionName = this.getOptionName(); 31 | const optionValue = args[optionName]; 32 | if (optionValue) { 33 | if (typeof optionValue === 'string' && optionValue.startsWith('@')) { 34 | args[optionName] = optionValue.slice(1); 35 | } 36 | config.focusFiberNodeId = Number(args[optionName]); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/cli/src/options/heap/JSEngineOption.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {ParsedArgs} from 'minimist'; 12 | 13 | import {BaseOption, MemLabConfig, constant, utils} from '@memlab/core'; 14 | import optionConstants from '../lib/OptionConstant'; 15 | 16 | export default class JSEngineOption extends BaseOption { 17 | getOptionName(): string { 18 | return optionConstants.optionNames.ENGINE; 19 | } 20 | 21 | getDescription(): string { 22 | return 'set the JavaScript engine (default to V8)'; 23 | } 24 | 25 | getExampleValues(): string[] { 26 | return ['V8', 'hermes']; 27 | } 28 | 29 | async parse(config: MemLabConfig, args: ParsedArgs): Promise { 30 | const name = this.getOptionName(); 31 | if (!args[name]) { 32 | return; 33 | } 34 | config.jsEngine = args[name]; 35 | config.specifiedEngine = true; 36 | if (constant.supportedEngines.indexOf(config.jsEngine) < 0) { 37 | utils.haltOrThrow( 38 | `Invalid engine: ${config.jsEngine} ` + 39 | `(supported engines: ${constant.supportedEngines.join(', ')})`, 40 | ); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/cli/src/options/heap/LeakClusterSizeThresholdOption.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {ParsedArgs} from 'minimist'; 12 | import type {MemLabConfig} from '@memlab/core'; 13 | import {BaseOption} from '@memlab/core'; 14 | import optionConstants from '../lib/OptionConstant'; 15 | 16 | export default class LeakClusterSizeThresholdOption extends BaseOption { 17 | getOptionName(): string { 18 | return optionConstants.optionNames.IGNORE_LEAK_CLUSTER_SIZE_BELOW; 19 | } 20 | 21 | getDescription(): string { 22 | return ( 23 | 'ignore memory leaks with aggregated retained size ' + 24 | 'smaller than the threshold (in bytes)' 25 | ); 26 | } 27 | 28 | getExampleValues(): string[] { 29 | return ['1000', '1000000']; 30 | } 31 | 32 | async parse(config: MemLabConfig, args: ParsedArgs): Promise { 33 | if (args[this.getOptionName()]) { 34 | const sizeThreshold = parseInt(args[this.getOptionName()], 10); 35 | if (!isNaN(sizeThreshold)) { 36 | config.clusterRetainedSizeThreshold = sizeThreshold; 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/cli/src/options/heap/LogTraceAsClusterOption.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {ParsedArgs} from 'minimist'; 12 | import type {MemLabConfig} from '@memlab/core'; 13 | import {BaseOption} from '@memlab/core'; 14 | import optionConstants from '../lib/OptionConstant'; 15 | 16 | export default class LogTraceAsClusterOption extends BaseOption { 17 | getOptionName(): string { 18 | return optionConstants.optionNames.SAVE_TRACE_AS_UNCLASSIFIED_CLUSTER; 19 | } 20 | 21 | getDescription(): string { 22 | return 'dump each retainer trace as an unclassified trace cluster'; 23 | } 24 | 25 | async parse(config: MemLabConfig, args: ParsedArgs): Promise { 26 | if (args[this.getOptionName()]) { 27 | config.logUnclassifiedClusters = true; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/cli/src/options/heap/OversizeThresholdOption.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {ParsedArgs} from 'minimist'; 12 | import type {MemLabConfig} from '@memlab/core'; 13 | import {BaseOption} from '@memlab/core'; 14 | import optionConstants from '../lib/OptionConstant'; 15 | 16 | export default class OversizeThresholdOption extends BaseOption { 17 | getOptionName(): string { 18 | return optionConstants.optionNames.TRACE_OBJECT_SIZE_ABOVE; 19 | } 20 | 21 | getDescription(): string { 22 | return ( 23 | 'objects with retained size (bytes) ' + 24 | 'bigger than the threshold will be considered as leaks' 25 | ); 26 | } 27 | 28 | getExampleValues(): string[] { 29 | return ['1000', '1000000']; 30 | } 31 | 32 | async parse(config: MemLabConfig, args: ParsedArgs): Promise { 33 | if (args[this.getOptionName()]) { 34 | const sizeThreshold = parseInt(args[this.getOptionName()], 10); 35 | if (!isNaN(sizeThreshold)) { 36 | config.oversizeObjectAsLeak = true; 37 | config.oversizeThreshold = sizeThreshold; 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/cli/src/options/heap/SetTraceContainsFilterOption.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {ParsedArgs} from 'minimist'; 12 | import type {MemLabConfig} from '@memlab/core'; 13 | import {BaseOption} from '@memlab/core'; 14 | import optionConstants from '../lib/OptionConstant'; 15 | 16 | export default class SetTraceContainsFilterOption extends BaseOption { 17 | getOptionName(): string { 18 | return optionConstants.optionNames.TRACE_CONTAINS; 19 | } 20 | 21 | getDescription(): string { 22 | return 'set the node name or edge name to filter leak traces that contain the name'; 23 | } 24 | 25 | async parse(config: MemLabConfig, args: ParsedArgs): Promise { 26 | const filterName = args[this.getOptionName()]; 27 | if (filterName != null) { 28 | config.filterTraceByName = filterName; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/cli/src/options/heap/SnapshotDirectoryOption.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {ParsedArgs} from 'minimist'; 12 | 13 | import fs from 'fs'; 14 | import {BaseOption, MemLabConfig, utils} from '@memlab/core'; 15 | import optionConstants from '../lib/OptionConstant'; 16 | 17 | export default class SnapshotDirectoryOption extends BaseOption { 18 | getOptionName(): string { 19 | return optionConstants.optionNames.SNAPSHOT_DIR; 20 | } 21 | 22 | getDescription(): string { 23 | return 'set directory path containing all heap snapshots under analysis'; 24 | } 25 | 26 | getExampleValues(): string[] { 27 | return ['/tmp/snapshots/']; 28 | } 29 | 30 | async parse(config: MemLabConfig, args: ParsedArgs): Promise { 31 | const name = this.getOptionName(); 32 | if (!args[name]) { 33 | return; 34 | } 35 | const dir = args[name]; 36 | if (!fs.existsSync(dir)) { 37 | utils.haltOrThrow(`Invalid directory: ${dir}`); 38 | } 39 | config.externalSnapshotDir = dir; 40 | config.useExternalSnapshot = true; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/cli/src/options/heap/SnapshotFileOption.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {ParsedArgs} from 'minimist'; 12 | 13 | import fs from 'fs'; 14 | import {BaseOption, MemLabConfig, utils} from '@memlab/core'; 15 | import optionConstants from '../lib/OptionConstant'; 16 | 17 | export default class SnapshotFileOption extends BaseOption { 18 | getOptionName(): string { 19 | return optionConstants.optionNames.SNAPSHOT; 20 | } 21 | 22 | getDescription(): string { 23 | return 'set file path of the heap snapshot under analysis'; 24 | } 25 | 26 | getExampleValues(): string[] { 27 | return ['/tmp/file.heapsnapshot']; 28 | } 29 | 30 | async parse(config: MemLabConfig, args: ParsedArgs): Promise { 31 | const name = this.getOptionName(); 32 | if (!args[name]) { 33 | return; 34 | } 35 | const file = args[name]; 36 | if (!fs.existsSync(file)) { 37 | utils.haltOrThrow(`Invalid snapshot file: ${file}`); 38 | } 39 | config.useExternalSnapshot = true; 40 | config.externalSnapshotFilePaths[0] = file; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/cli/src/options/heap/leak-filter/LeakFilterFileOption.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {ParsedArgs} from 'minimist'; 12 | import type {MemLabConfig} from '@memlab/core'; 13 | import {BaseOption, utils} from '@memlab/core'; 14 | import optionConstants from '../../lib/OptionConstant'; 15 | 16 | export default class LeakFilterFileOption extends BaseOption { 17 | getOptionName(): string { 18 | return optionConstants.optionNames.LEAK_FILTER; 19 | } 20 | 21 | getDescription(): string { 22 | return 'specify a definition JS file for leak filter'; 23 | } 24 | 25 | getExampleValues(): string[] { 26 | return ['/tmp/leak-filter.js']; 27 | } 28 | 29 | async parse(config: MemLabConfig, args: ParsedArgs): Promise { 30 | if (args[this.getOptionName()]) { 31 | const file = args[this.getOptionName()]; 32 | config.externalLeakFilter = utils.loadLeakFilter(file); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/cli/src/options/heap/leak-filter/examples/FilterLib.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import {IHeapSnapshot, utils} from '@memlab/core'; 12 | 13 | export function initMap(snapshot: IHeapSnapshot): Record { 14 | const map = Object.create(null); 15 | snapshot.nodes.forEach(node => { 16 | if (node.type !== 'string') { 17 | return; 18 | } 19 | const str = utils.getStringNodeValue(node); 20 | if (str in map) { 21 | ++map[str]; 22 | } else { 23 | map[str] = 1; 24 | } 25 | }); 26 | return map; 27 | } 28 | -------------------------------------------------------------------------------- /packages/cli/src/options/heap/leak-filter/examples/dup-string-as-leak.example-1.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import {IHeapNode, IHeapSnapshot, HeapNodeIdSet, utils} from '@memlab/core'; 12 | import {initMap} from './FilterLib'; 13 | 14 | let map = Object.create(null); 15 | 16 | export function beforeLeakFilter( 17 | snapshot: IHeapSnapshot, 18 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 19 | _leakedNodeIds: HeapNodeIdSet, 20 | ): void { 21 | map = initMap(snapshot); 22 | } 23 | 24 | // duplicated string with size > 1KB as memory leak 25 | export function leakFilter(node: IHeapNode): boolean { 26 | if (node.type !== 'string' || node.retainedSize < 1000) { 27 | return false; 28 | } 29 | const str = utils.getStringNodeValue(node); 30 | return map[str] > 1; 31 | } 32 | -------------------------------------------------------------------------------- /packages/cli/src/options/heap/leak-filter/examples/dup-string-as-leak.example-2.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import {IHeapNode, IHeapSnapshot, HeapNodeIdSet, utils} from '@memlab/core'; 12 | import {initMap} from './FilterLib'; 13 | 14 | let map = Object.create(null); 15 | 16 | const beforeLeakFilter = ( 17 | snapshot: IHeapSnapshot, 18 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 19 | _leakedNodeIds: HeapNodeIdSet, 20 | ): void => { 21 | map = initMap(snapshot); 22 | }; 23 | 24 | // duplicated string with size > 1KB as memory leak 25 | const leakFilter = (node: IHeapNode): boolean => { 26 | if (node.type !== 'string' || node.retainedSize < 1000) { 27 | return false; 28 | } 29 | const str = utils.getStringNodeValue(node); 30 | return map[str] > 1; 31 | }; 32 | 33 | export default {beforeLeakFilter, leakFilter}; 34 | -------------------------------------------------------------------------------- /packages/cli/src/options/heap/leak-filter/examples/large-object-as-leak.example.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | module.exports = function leakFilter(node, _snapshot, _leakedNodeIds) { 12 | return node.retainedSize > 1000000; 13 | }; 14 | -------------------------------------------------------------------------------- /packages/cli/src/options/lib/UniversalOptions.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import DebugOption from '../DebugOption'; 12 | import HelperOption from '../HelperOption'; 13 | import SetContinuousTestOption from '../SetContinuousTestOption'; 14 | import SilentOption from '../SilentOption'; 15 | import VerboseOption from '../VerboseOption'; 16 | 17 | const universalOptions = [ 18 | new HelperOption(), 19 | new VerboseOption(), 20 | new SetContinuousTestOption(), 21 | new DebugOption(), 22 | new SilentOption(), 23 | ]; 24 | 25 | export default universalOptions; 26 | -------------------------------------------------------------------------------- /packages/cli/src/runner.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import minimist, {ParsedArgs} from 'minimist'; 12 | import {utils} from '@memlab/core'; 13 | import commandDispatcher from './Dispatcher'; 14 | 15 | export async function run(): Promise { 16 | const argv: ParsedArgs = minimist(process.argv.slice(2)); 17 | commandDispatcher.dispatch(argv); 18 | } 19 | 20 | if (require.main === module) { 21 | // called from command line 22 | utils.callAsync(run); 23 | } 24 | -------------------------------------------------------------------------------- /packages/cli/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./dist" 6 | }, 7 | "include": ["src/**/*"], 8 | "references": [ 9 | {"path": "../core"}, 10 | {"path": "../e2e"}, 11 | {"path": "../api"}, 12 | {"path": "../heap-analysis"} 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /packages/core/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Meta Platforms, Inc. and affiliates. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/core/README.md: -------------------------------------------------------------------------------- 1 | ## memlab Core Library 2 | 3 | This is the memlab core library. It contains V8/Hermes heap snapshot parser, core algorithms, leak trace clustering, utilities, and config. 4 | 5 | ## Online Resources 6 | * [Official Website and Demo](https://facebook.github.io/memlab) 7 | * [Documentation](https://facebook.github.io/memlab/docs/intro) 8 | -------------------------------------------------------------------------------- /packages/core/jest.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | const config = { 12 | verbose: true, 13 | testRegex: '(/dist/(.*/)?__tests__/.*)(\\.test|\\.spec)\\.jsx?$', 14 | maxConcurrency: 1, 15 | }; 16 | 17 | module.exports = config; 18 | -------------------------------------------------------------------------------- /packages/core/src/__tests__/lib/TestUtils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import {analysis, info, utils} from '../..'; 12 | import {AnyOptions, IHeapSnapshot, Optional} from '../../lib/Types'; 13 | 14 | /** @internal */ 15 | export async function getFullHeapFromFile( 16 | file: string, 17 | ): Promise { 18 | return await loadProcessedSnapshot({file}); 19 | } 20 | 21 | async function loadProcessedSnapshot( 22 | options: AnyOptions & {file?: Optional} = {}, 23 | ): Promise { 24 | const opt = {buildNodeIdIndex: true, verbose: true}; 25 | const file = options.file || utils.getSnapshotFilePathWithTabType(/.*/); 26 | const snapshot = await utils.getSnapshotFromFile(file as string, opt); 27 | analysis.preparePathFinder(snapshot); 28 | info.flush(); 29 | return snapshot; 30 | } 31 | -------------------------------------------------------------------------------- /packages/core/src/lib/SerializationHelper.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type { 12 | IHeapNode, 13 | IHeapSnapshot, 14 | ISerializationHelper, 15 | ISerializedInfo, 16 | JSONifyArgs, 17 | JSONifyOptions, 18 | Nullable, 19 | } from './Types'; 20 | 21 | import {setInternalValue} from './InternalValueSetter'; 22 | import constants from './Constant'; 23 | 24 | export class SerializationHelper implements ISerializationHelper { 25 | protected snapshot: Nullable = null; 26 | 27 | setSnapshot(snapshot: IHeapSnapshot): void { 28 | this.snapshot = snapshot; 29 | } 30 | 31 | createOrMergeWrapper( 32 | info: ISerializedInfo, 33 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 34 | _node: IHeapNode, 35 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 36 | _args: JSONifyArgs, 37 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 38 | _options: JSONifyOptions, 39 | ): ISerializedInfo { 40 | return info; 41 | } 42 | } 43 | 44 | export default setInternalValue( 45 | SerializationHelper, 46 | __filename, 47 | constants.internalDir, 48 | ); 49 | -------------------------------------------------------------------------------- /packages/core/src/lib/TypesThirdParty.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | declare module 'babar' { 12 | interface BabarOption { 13 | color?: string; 14 | width?: number; 15 | height?: number; 16 | xFractions?: number; 17 | yFractions?: number; 18 | minY?: number; 19 | maxY?: number; 20 | } 21 | export default function babar( 22 | plotData: number[][], 23 | params: BabarOption, 24 | ): string; 25 | } 26 | -------------------------------------------------------------------------------- /packages/core/src/lib/heap-data/HeapUtils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @lightSyntaxTransform 9 | * @oncall memory_lab 10 | */ 11 | 12 | 'use strict'; 13 | 14 | export const NodeDetachState = { 15 | Unknown: 0, 16 | Attached: 1, 17 | Detached: 2, 18 | }; 19 | 20 | export function throwError(error: Error): Error { 21 | if (error) { 22 | error.stack; 23 | } 24 | throw error; 25 | } 26 | -------------------------------------------------------------------------------- /packages/core/src/lib/leak-filters/rules/FilterByExternalFilter.rule.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {MemLabConfig} from '../../Config'; 12 | import type {HeapNodeIdSet, IHeapNode, IHeapSnapshot} from '../../Types'; 13 | 14 | import {LeakDecision, LeakObjectFilterRuleBase} from '../BaseLeakFilter.rule'; 15 | 16 | /** 17 | * filter memory leaks defined by external leak filter 18 | */ 19 | export class FilterByExternalFilterRule extends LeakObjectFilterRuleBase { 20 | filter( 21 | config: MemLabConfig, 22 | node: IHeapNode, 23 | snapshot: IHeapSnapshot, 24 | leakedNodeIds: HeapNodeIdSet, 25 | ): LeakDecision { 26 | if (config.externalLeakFilter?.leakFilter == null) { 27 | return LeakDecision.MAYBE_LEAK; 28 | } 29 | return config.externalLeakFilter.leakFilter(node, snapshot, leakedNodeIds) 30 | ? LeakDecision.LEAK 31 | : LeakDecision.NOT_LEAK; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/core/src/lib/leak-filters/rules/FilterHermesNode.rule.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {MemLabConfig} from '../../Config'; 12 | import type {IHeapNode} from '../../Types'; 13 | import {LeakDecision, LeakObjectFilterRuleBase} from '../BaseLeakFilter.rule'; 14 | import utils from '../../Utils'; 15 | 16 | export class FilterHermesNodeRule extends LeakObjectFilterRuleBase { 17 | public filter(config: MemLabConfig, node: IHeapNode): LeakDecision { 18 | // when analyzing hermes heap snapshots, filter Hermes internal objects 19 | if (config.jsEngine === 'hermes' && utils.isHermesInternalObject(node)) { 20 | return LeakDecision.NOT_LEAK; 21 | } 22 | return LeakDecision.MAYBE_LEAK; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/core/src/lib/leak-filters/rules/FilterStackTraceFrame.rule.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {MemLabConfig} from '../../Config'; 12 | import type {IHeapNode} from '../../Types'; 13 | 14 | import {LeakDecision, LeakObjectFilterRuleBase} from '../BaseLeakFilter.rule'; 15 | import utils from '../../Utils'; 16 | 17 | /** 18 | * stack trace frames as memory leaks 19 | */ 20 | export class FilterStackTraceFrameRule extends LeakObjectFilterRuleBase { 21 | filter(_config: MemLabConfig, node: IHeapNode): LeakDecision { 22 | return utils.isStackTraceFrame(node) 23 | ? LeakDecision.LEAK 24 | : LeakDecision.MAYBE_LEAK; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/core/src/lib/leak-filters/rules/FilterTrivialNode.rule.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {MemLabConfig} from '../../Config'; 12 | import type {IHeapNode} from '../../Types'; 13 | import {LeakDecision, LeakObjectFilterRuleBase} from '../BaseLeakFilter.rule'; 14 | import utils from '../../Utils'; 15 | 16 | /** 17 | * trivial nodes are not reported as memory leaks 18 | */ 19 | export class FilterTrivialNodeRule extends LeakObjectFilterRuleBase { 20 | filter(config: MemLabConfig, node: IHeapNode): LeakDecision { 21 | return this.isTrivialNode(node) 22 | ? LeakDecision.NOT_LEAK 23 | : LeakDecision.MAYBE_LEAK; 24 | } 25 | 26 | protected isTrivialNode(node: IHeapNode): boolean { 27 | return ( 28 | node.type === 'number' || 29 | utils.isStringNode(node) || 30 | node.type === 'hidden' 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/core/src/lib/leak-filters/rules/FilterUnmountedFiberNode.rule.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {MemLabConfig} from '../../Config'; 12 | import type {IHeapNode} from '../../Types'; 13 | import {LeakDecision, LeakObjectFilterRuleBase} from '../BaseLeakFilter.rule'; 14 | import utils from '../../Utils'; 15 | 16 | /** 17 | * mark React FiberNodes without a React Fiber Root as memory leaks 18 | */ 19 | export class FilterUnmountedFiberNodeRule extends LeakObjectFilterRuleBase { 20 | filter(config: MemLabConfig, node: IHeapNode): LeakDecision { 21 | if (this.checkDetachedFiberNode(config, node)) { 22 | return LeakDecision.LEAK; 23 | } 24 | return LeakDecision.MAYBE_LEAK; 25 | } 26 | 27 | protected checkDetachedFiberNode( 28 | config: MemLabConfig, 29 | node: IHeapNode, 30 | ): boolean { 31 | if (!config.detectFiberNodeLeak || !utils.isFiberNode(node)) { 32 | return false; 33 | } 34 | if (!utils.isDetachedFiberNode(node)) { 35 | return false; 36 | } 37 | return !utils.isNodeDominatedByDeletionsArray(node); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/core/src/lib/leak-filters/rules/FilterXMLHTTPRequest.rule.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {MemLabConfig} from '../../Config'; 12 | import type {IHeapEdge, IHeapNode} from '../../Types'; 13 | import {LeakDecision, LeakObjectFilterRuleBase} from '../BaseLeakFilter.rule'; 14 | 15 | /** 16 | * mark XMLHTTPRequest with status ok as memory leaks 17 | */ 18 | export class FilterXMLHTTPRequestRule extends LeakObjectFilterRuleBase { 19 | filter(_config: MemLabConfig, node: IHeapNode): LeakDecision { 20 | return this.checkFinishedXMLHTTPRequest(node) 21 | ? LeakDecision.LEAK 22 | : LeakDecision.MAYBE_LEAK; 23 | } 24 | 25 | protected checkFinishedXMLHTTPRequest(node: IHeapNode): boolean { 26 | if (node.name !== 'XMLHttpRequest' || node.type !== 'native') { 27 | return false; 28 | } 29 | return ( 30 | node.findAnyReference( 31 | (edge: IHeapEdge) => edge.toNode.name === '{"status":"ok"}', 32 | ) != null 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/core/src/lib/trace-filters/BaseTraceFilter.rule.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {MemLabConfig} from '../Config'; 12 | import type {HeapNodeIdSet, IHeapSnapshot, LeakTracePathItem} from '../Types'; 13 | 14 | /** 15 | * Every leak trace filter rule needs to give a label 16 | * to each leak trace passed to the filter 17 | */ 18 | export enum TraceDecision { 19 | INSIGHTFUL = 'insightful', 20 | MAYBE_INSIGHTFUL = 'maybe-insightful', 21 | NOT_INSIGHTFUL = 'not-insightful', 22 | } 23 | 24 | export type LeakTraceFilterOptions = { 25 | config?: MemLabConfig; 26 | snapshot?: IHeapSnapshot; 27 | leakedNodeIds?: HeapNodeIdSet; 28 | }; 29 | 30 | export interface ILeakTraceFilterRule { 31 | filter(p: LeakTracePathItem, options: LeakTraceFilterOptions): TraceDecision; 32 | } 33 | -------------------------------------------------------------------------------- /packages/core/src/lib/trace-filters/LeakTraceFilter.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {LeakTracePathItem} from '../Types'; 12 | import type {LeakTraceFilterOptions} from './BaseTraceFilter.rule'; 13 | import {TraceDecision} from './BaseTraceFilter.rule'; 14 | import rules from './TraceFilterRuleList'; 15 | 16 | /** 17 | * apply the leak trace filter rules chain and decide 18 | * if a leak trace is useful for memory debugging, 19 | * by default all leak traces are considered useful 20 | */ 21 | export class LeakTraceFilter { 22 | public filter( 23 | p: LeakTracePathItem, 24 | options: LeakTraceFilterOptions, 25 | ): boolean { 26 | for (const rule of rules) { 27 | const decision = rule.filter(p, options); 28 | if (decision === TraceDecision.INSIGHTFUL) { 29 | return true; 30 | } 31 | if (decision === TraceDecision.NOT_INSIGHTFUL) { 32 | return false; 33 | } 34 | } 35 | return true; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/core/src/lib/trace-filters/rules/FilterHermesTrace.rule.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {LeakTracePathItem} from '../../Types'; 12 | 13 | import config from '../../Config'; 14 | 15 | import { 16 | ILeakTraceFilterRule, 17 | LeakTraceFilterOptions, 18 | TraceDecision, 19 | } from '../BaseTraceFilter.rule'; 20 | 21 | export class FilterHermesTraceRule implements ILeakTraceFilterRule { 22 | filter( 23 | _p: LeakTracePathItem, 24 | options: LeakTraceFilterOptions = {}, 25 | ): TraceDecision { 26 | const curConfig = options.config ?? config; 27 | // do not filter out paths when analyzing Hermes snapshots 28 | if (curConfig.jsEngine === 'hermes') { 29 | return TraceDecision.INSIGHTFUL; 30 | } 31 | return TraceDecision.MAYBE_INSIGHTFUL; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/core/src/modes/InteractionTestMode.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import BaseMode from './BaseMode'; 12 | 13 | // mode for running quick interaction test 14 | class InteractionTestMode extends BaseMode { 15 | shouldGC(): boolean { 16 | return false; 17 | } 18 | 19 | shouldScroll(): boolean { 20 | return false; 21 | } 22 | 23 | shouldGetMetrics(): boolean { 24 | return false; 25 | } 26 | 27 | shouldUseConciseConsole(): boolean { 28 | return true; 29 | } 30 | 31 | shouldTakeScreenShot(): boolean { 32 | return false; 33 | } 34 | 35 | shouldTakeHeapSnapshot(): boolean { 36 | return false; 37 | } 38 | 39 | shouldExtraWaitForTarget(): boolean { 40 | return false; 41 | } 42 | 43 | shouldExtraWaitForFinal(): boolean { 44 | return false; 45 | } 46 | 47 | shouldRunExtraTargetOperations(): boolean { 48 | return false; 49 | } 50 | } 51 | 52 | export default InteractionTestMode; 53 | -------------------------------------------------------------------------------- /packages/core/src/modes/RunningModes.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {Config, IRunningMode} from '../lib/Types'; 12 | 13 | import utils from '../lib/Utils'; 14 | import BaseMode from './BaseMode'; 15 | import InteractionTestMode from './InteractionTestMode'; 16 | import MeasureMode from './MeasureMode'; 17 | 18 | export default { 19 | get(name: string, config?: Config): IRunningMode { 20 | let ret: IRunningMode; 21 | switch (name) { 22 | case 'regular': 23 | ret = new BaseMode(); 24 | break; 25 | case 'interaction-test': 26 | ret = new InteractionTestMode(); 27 | break; 28 | case 'measure': 29 | ret = new MeasureMode(); 30 | break; 31 | default: 32 | throw utils.haltOrThrow(`unknown running mode: ${name}`); 33 | } 34 | if (config) { 35 | ret.setConfig(config); 36 | } 37 | return ret; 38 | }, 39 | }; 40 | -------------------------------------------------------------------------------- /packages/core/src/trace-cluster/ClusteringHeuristics.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import {setInternalValue} from '../lib/InternalValueSetter'; 12 | import Constant from '../lib/Constant'; 13 | 14 | export default setInternalValue( 15 | { 16 | edgeNameStopWords: new Map(), 17 | nodeNameStopWords: new Map(), 18 | similarWordRegExps: new Map(), 19 | decendentDecayFactors: [] as {kind: string; name: string; decay: number}[], 20 | startingModuleForTraceMatching: [] as (string | RegExp)[], 21 | }, 22 | __filename, 23 | Constant.internalDir, 24 | ); 25 | -------------------------------------------------------------------------------- /packages/core/src/trace-cluster/strategies/machine-learning/Ngram.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | export function nGram(n: number, terms: string[]) { 12 | const nGrams: string[] = []; 13 | let index = 0; 14 | while (index <= terms.length - n) { 15 | nGrams[index] = terms.slice(index, index + n).join(' '); 16 | ++index; 17 | } 18 | 19 | return nGrams; 20 | } 21 | -------------------------------------------------------------------------------- /packages/core/static/run-meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "external", 3 | "interaction": "manual", 4 | "type": "standard", 5 | "browserInfo": { 6 | "_browserVersion": "Hermes/", 7 | "_puppeteerConfig": {}, 8 | "_consoleMessages": [] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/core/static/visit-order-single-snapshot.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "target", 4 | "url": "", 5 | "snapshot": true, 6 | "screenshot": false, 7 | "type": "target", 8 | "idx": 1 9 | }, 10 | { 11 | "name": "final", 12 | "url": "", 13 | "delay": 10000, 14 | "snapshot": true, 15 | "screenshot": false, 16 | "type": "final", 17 | "idx": 2 18 | } 19 | ] 20 | -------------------------------------------------------------------------------- /packages/core/static/visit-order.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "baseline", 4 | "url": "", 5 | "snapshot": true, 6 | "screenshot": false, 7 | "type": "baseline", 8 | "idx": 1 9 | }, 10 | { 11 | "name": "target", 12 | "url": "", 13 | "snapshot": true, 14 | "screenshot": false, 15 | "type": "target", 16 | "idx": 2 17 | }, 18 | { 19 | "name": "final", 20 | "url": "", 21 | "delay": 10000, 22 | "snapshot": true, 23 | "screenshot": false, 24 | "type": "final", 25 | "idx": 3 26 | } 27 | ] 28 | -------------------------------------------------------------------------------- /packages/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./dist" 6 | }, 7 | "include": ["src/**/*"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/e2e/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Meta Platforms, Inc. and affiliates. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/e2e/README.md: -------------------------------------------------------------------------------- 1 | ## memlab E2E 2 | 3 | This is the memlab E2E testing framework for headless/headful Chromium. It is mainly responsible 4 | for controling/interacting with browser, recording heap snapshots, and logging other execution meta data. 5 | 6 | ## Online Resources 7 | * [Official Website and Demo](https://facebook.github.io/memlab) 8 | * [Documentation](https://facebook.github.io/memlab/docs/intro) 9 | -------------------------------------------------------------------------------- /packages/e2e/jest.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | const config = { 12 | verbose: true, 13 | testRegex: '(/dist/(.*/)?__tests__/.*)(\\.test|\\.spec)\\.jsx?$', 14 | maxConcurrency: 1, 15 | }; 16 | 17 | module.exports = config; 18 | -------------------------------------------------------------------------------- /packages/e2e/src/TestRunnerLoader.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import path from 'path'; 12 | import type {E2EScenarioSynthesizerConstructor} from '@memlab/core'; 13 | import {fileManager} from '@memlab/core'; 14 | import BaseSynthesizer from './BaseSynthesizer'; 15 | 16 | class TestRunnerLoader { 17 | load(): E2EScenarioSynthesizerConstructor[] { 18 | const ret: E2EScenarioSynthesizerConstructor[] = []; 19 | const dir = path.join(__dirname, 'plugins'); 20 | fileManager.iterateAllFiles(dir, (file: string) => { 21 | if (!file.endsWith('Synthesizer.js')) { 22 | return; 23 | } 24 | // eslint-disable-next-line @typescript-eslint/no-var-requires 25 | const importedEntity = require(file); 26 | if (!importedEntity.default) { 27 | return; 28 | } 29 | const constructor = importedEntity.default; 30 | if ( 31 | typeof constructor !== 'function' || 32 | !(constructor.prototype instanceof BaseSynthesizer) 33 | ) { 34 | return; 35 | } 36 | ret.push(constructor); 37 | }); 38 | return ret; 39 | } 40 | } 41 | 42 | export default new TestRunnerLoader(); 43 | -------------------------------------------------------------------------------- /packages/e2e/src/TypesThirdParty.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | declare module 'xvfb' { 12 | interface XvfbParams { 13 | silent: boolean; 14 | xvfb_args: string[]; 15 | timeout: number; 16 | } 17 | export default class Xvfb { 18 | constructor(params: XvfbParams); 19 | start: (callback: (error: Error) => unknown | null) => void; 20 | stop: (callback: (error: Error) => unknown | null) => void; 21 | startSync: () => void; 22 | stopSync: () => void; 23 | display: () => string; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/e2e/src/__tests__/scope/lib.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | import type {AnyValue} from '@memlab/core'; 11 | import Script from '../../code-analysis/Script'; 12 | 13 | export function testScopeAnalysis(code: string, expectedScope: AnyValue): void { 14 | const script = new Script(code); 15 | const scope = script.getClosureScopeTree(); 16 | expect(removeLoc(scope)).toEqual(removeLoc(expectedScope)); 17 | } 18 | 19 | export function removeLoc(entity: AnyValue): AnyValue { 20 | const visited = new Set(); 21 | function remove(e: AnyValue) { 22 | if (!e || typeof e !== 'object' || visited.has(e)) { 23 | return; 24 | } 25 | visited.add(e); 26 | for (const k of Object.keys(e)) { 27 | if (k === 'loc') { 28 | delete e.loc; 29 | } else { 30 | remove(e[k]); 31 | } 32 | } 33 | } 34 | remove(entity); 35 | return entity; 36 | } 37 | -------------------------------------------------------------------------------- /packages/e2e/src/__tests__/scope/scope-analysis.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import {testScopeAnalysis} from './lib'; 12 | 13 | const code = ` 14 | const v0 = 'abc'; 15 | function f() { 16 | const v1 = 'test'; 17 | function f2() { 18 | let v2 = 123; 19 | console.log(v0, v1, v2); 20 | } 21 | } 22 | `; 23 | 24 | const closureScope = { 25 | functionName: null, 26 | functionType: 'Program', 27 | nestedClosures: [ 28 | { 29 | functionName: 'f', 30 | functionType: 'FunctionDeclaration', 31 | nestedClosures: [ 32 | { 33 | functionName: 'f2', 34 | functionType: 'FunctionDeclaration', 35 | nestedClosures: [], 36 | usedVariablesFromParentScope: ['v1'], 37 | variablesDefined: ['v2'], 38 | }, 39 | ], 40 | usedVariablesFromParentScope: ['v0'], 41 | variablesDefined: ['v1', 'f2'], 42 | }, 43 | ], 44 | usedVariablesFromParentScope: [], 45 | variablesDefined: ['v0', 'f'], 46 | }; 47 | 48 | test('simple scope analysis works as expected', async () => { 49 | testScopeAnalysis(code, closureScope); 50 | }); 51 | -------------------------------------------------------------------------------- /packages/e2e/src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import path from 'path'; 12 | import {PackageInfoLoader} from '@memlab/core'; 13 | /** @internal */ 14 | export async function registerPackage(): Promise { 15 | return PackageInfoLoader.registerPackage(path.join(__dirname, '..')); 16 | } 17 | 18 | export {default as defaultTestPlanner} from './lib/operations/TestPlanner'; 19 | export * from './lib/operations/TestPlanner'; 20 | export {default as Xvfb} from './lib/operations/XVirtualFrameBuffer'; 21 | export {default as E2EInteractionManager} from './E2EInteractionManager'; 22 | export {default as BaseSynthesizer} from './BaseSynthesizer'; 23 | export {default as E2EUtils} from './lib/E2EUtils'; 24 | /** @internal */ 25 | export {default as ScriptManager} from './ScriptManager'; 26 | /** @internal */ 27 | export type {ClosureScope} from './code-analysis/Script'; 28 | -------------------------------------------------------------------------------- /packages/e2e/src/instrumentation/BaseAstTransform.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {ParseResult} from '@babel/core'; 12 | import type {RewriteScriptOption} from './ScriptRewriteManager'; 13 | 14 | import {utils} from '@memlab/core'; 15 | 16 | export default abstract class BaseAstTransform { 17 | public async transform( 18 | ast: ParseResult, 19 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 20 | options: RewriteScriptOption = {}, 21 | ): Promise { 22 | throw utils.haltOrThrow('BaseAstTransform.transform is not implemented'); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/e2e/src/instrumentation/ScriptRewriteManager.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {ParseResult} from '@babel/core'; 12 | import type BaseAstTransform from './BaseAstTransform'; 13 | 14 | import {parse} from '@babel/parser'; 15 | import generate from '@babel/generator'; 16 | import TransformLoader from './TransformLoader'; 17 | 18 | export type RewriteScriptOption = { 19 | url?: string; 20 | resourceType?: string; 21 | }; 22 | 23 | export default class ScriptRewriteManager { 24 | public async rewriteScript( 25 | code: string, 26 | options: RewriteScriptOption = {}, 27 | ): Promise { 28 | // parse code 29 | const ast: ParseResult = parse(code, { 30 | sourceType: 'script', 31 | }); 32 | // transform ast 33 | const transforms: BaseAstTransform[] = TransformLoader.loadAllTransforms(); 34 | for (const transform of transforms) { 35 | await transform.transform(ast, options); 36 | } 37 | // generate code 38 | const generateResult = generate(ast); 39 | return generateResult.code; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/e2e/src/instrumentation/TransformLoader.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type BaseAstTransform from './BaseAstTransform'; 12 | 13 | import InjectSourceInfoTransform from './transforms/InjectSourceInfoTransform'; 14 | 15 | export default class TransformLoader { 16 | public static loadAllTransforms(): BaseAstTransform[] { 17 | return [new InjectSourceInfoTransform()]; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/e2e/src/lib/SynthesisUtils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import BackOperation from './operations/BackOperation'; 12 | import RevertOperation from './operations/RevertOperation'; 13 | 14 | const backStep = { 15 | name: 'back', 16 | url: '', 17 | interactions: [new BackOperation()], 18 | }; 19 | 20 | const revertStep = { 21 | name: 'revert', 22 | url: '', 23 | interactions: [new RevertOperation()], 24 | }; 25 | 26 | export default { 27 | backStep, 28 | revertStep, 29 | }; 30 | -------------------------------------------------------------------------------- /packages/e2e/src/lib/operations/BackOperation.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import BaseOperation from './BaseOperation'; 12 | import type {Page} from 'puppeteer'; 13 | import {config} from '@memlab/core'; 14 | 15 | export default class BackOperation extends BaseOperation { 16 | kind: string; 17 | 18 | constructor() { 19 | super(); 20 | this.kind = 'back'; 21 | } 22 | 23 | async act(page: Page): Promise { 24 | page.goBack({timeout: config.presenceCheckTimeout || 120000}); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/e2e/src/lib/operations/BackspaceOperation.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import BaseOperation from './BaseOperation'; 12 | import type {Page} from 'puppeteer'; 13 | 14 | class BackspaceOperation extends BaseOperation { 15 | kind: string; 16 | 17 | constructor() { 18 | super(); 19 | this.kind = 'backspace'; 20 | } 21 | 22 | async act(page: Page): Promise { 23 | await page.keyboard.press('Backspace'); 24 | } 25 | } 26 | 27 | export default BackspaceOperation; 28 | -------------------------------------------------------------------------------- /packages/e2e/src/lib/operations/CompoundOperation.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import {Page} from 'puppeteer'; 12 | import type {E2EOperation} from '@memlab/core'; 13 | import BaseOperation from './BaseOperation'; 14 | 15 | class CompoundOperation extends BaseOperation { 16 | kind: string; 17 | protected operations: E2EOperation[]; 18 | constructor(operations = []) { 19 | super(); 20 | this.kind = 'compound'; 21 | this.operations = operations; 22 | } 23 | 24 | async act(page: Page): Promise { 25 | for (const op of this.operations) { 26 | await op.act(page); 27 | } 28 | } 29 | } 30 | 31 | export default CompoundOperation; 32 | -------------------------------------------------------------------------------- /packages/e2e/src/lib/operations/EnterOperation.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import {Page} from 'puppeteer'; 12 | import BaseOperation from './BaseOperation'; 13 | 14 | class EnterOperation extends BaseOperation { 15 | kind: string; 16 | constructor() { 17 | super(); 18 | this.kind = 'enter'; 19 | } 20 | 21 | async act(page: Page): Promise { 22 | await page.keyboard.press('Enter'); 23 | } 24 | } 25 | 26 | export default EnterOperation; 27 | -------------------------------------------------------------------------------- /packages/e2e/src/lib/operations/EscOperation.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import BaseOperation from './BaseOperation'; 12 | import type {Page} from 'puppeteer'; 13 | 14 | export default class EscOperation extends BaseOperation { 15 | kind: string; 16 | 17 | constructor() { 18 | super(); 19 | this.kind = 'escape'; 20 | } 21 | 22 | async act(page: Page): Promise { 23 | await page.keyboard.press('Escape'); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/e2e/src/lib/operations/Operations.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | export {default as CachePageContent} from './CachePageContent'; 12 | export {default as ClickOperation} from './ClickOperation'; 13 | export {default as CompoundOperation} from './CompoundOperation'; 14 | export {default as EnterOperation} from './EnterOperation'; 15 | export {default as EscOperation} from './EscOperation'; 16 | export {default as BackOperation} from './BackOperation'; 17 | export {default as BackspaceOperation} from './BackspaceOperation'; 18 | export {default as HoverOperation} from './HoverOperation'; 19 | export {default as RunJSCode} from './RunJSCode'; 20 | export {default as ScrollOperation} from './ScrollOperation'; 21 | export {default as TypeOperation} from './TypeOperation'; 22 | export {default as UploadOperation} from './UploadOperation'; 23 | export {default as WaitForElementOperation} from './WaitForElementOperation'; 24 | export {default as WaitOperation} from './WaitOperation'; 25 | export {default as WithCachedPageContent} from './WithCachedPageContent'; 26 | -------------------------------------------------------------------------------- /packages/e2e/src/lib/operations/TargetExtraOperation.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import {config} from '@memlab/core'; 12 | import BaseOperation from './BaseOperation'; 13 | import CompoundOperation from './CompoundOperation'; 14 | import ScrollOperation from './ScrollOperation'; 15 | 16 | class TargetExtraOperation extends CompoundOperation { 17 | kind: string; 18 | operations: BaseOperation[]; 19 | constructor(operations = []) { 20 | super(operations); 21 | this.kind = 'target-extra'; 22 | 23 | if (config.runningMode.shouldRunExtraTargetOperations()) { 24 | // scroll the window by certain amount of pixels 25 | this.operations.push( 26 | new ScrollOperation(config.windowHeight, config.scrollRepeat), 27 | ); 28 | } 29 | } 30 | } 31 | 32 | export default TargetExtraOperation; 33 | -------------------------------------------------------------------------------- /packages/e2e/src/lib/operations/WaitOperation.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import {Page} from 'puppeteer'; 12 | import {info} from '@memlab/core'; 13 | import BaseOperation from './BaseOperation'; 14 | import interactUtils from './InteractionUtils'; 15 | 16 | class WaitOperation extends BaseOperation { 17 | kind: string; 18 | timeout: number; 19 | 20 | constructor(timeout: number) { 21 | super(); 22 | this.kind = 'wait'; 23 | this.timeout = timeout; 24 | } 25 | 26 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 27 | async act(_page: Page): Promise { 28 | info.lowLevel(`wait for ${this.timeout} ms ...`); 29 | await interactUtils.waitFor(this.timeout); 30 | return; 31 | } 32 | } 33 | 34 | export default WaitOperation; 35 | -------------------------------------------------------------------------------- /packages/e2e/src/lib/operations/XVirtualFrameBuffer.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import Xvfb from 'xvfb'; 12 | import type {Nullable, XvfbType} from '@memlab/core'; 13 | import {info, config} from '@memlab/core'; 14 | 15 | export default { 16 | startIfEnabled(): Nullable { 17 | const failRet = null; 18 | if (!config.useXVFB || !config.machineSupportsXVFB) { 19 | return failRet; 20 | } 21 | if (config.verbose) { 22 | info.lowLevel('starting xvfb...'); 23 | } 24 | const xvfb = new Xvfb({ 25 | silent: true, 26 | xvfb_args: ['-screen', '0', '1280x720x24', '-ac'], 27 | timeout: 10000, // 10 seconds timeout for Xvfb start 28 | }); 29 | try { 30 | xvfb.startSync(); 31 | } catch (_e) { 32 | if (config.verbose) { 33 | info.lowLevel('fail to start xvfb...'); 34 | } 35 | config.disableXvfb(); 36 | return failRet; 37 | } 38 | config.enableXvfb(xvfb.display()); 39 | return xvfb; 40 | }, 41 | }; 42 | -------------------------------------------------------------------------------- /packages/e2e/src/plugins/scenarios/test-airbnb.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | // initial page load's url 12 | function url() { 13 | return 'https://www.airbnb.com/?tab_id=home_tab'; 14 | } 15 | 16 | // action where we want to detect memory leaks 17 | async function action(page) { 18 | const arr = await page.$x("//button[contains(., 'Show map')]"); 19 | if (arr[0]) { 20 | await arr[0].click(); 21 | } 22 | arr.forEach(elem => elem.dispose()); 23 | } 24 | 25 | // action where we want to go back to the step before 26 | async function back(page) { 27 | const arr = await page.$x("//button[contains(., 'Show list')]"); 28 | if (arr[0]) { 29 | await arr[0].click(); 30 | } 31 | arr.forEach(elem => elem.dispose()); 32 | } 33 | 34 | // specify the number of repeat for the action 35 | function repeat() { 36 | return 15; 37 | } 38 | 39 | module.exports = {action, back, repeat, url}; 40 | -------------------------------------------------------------------------------- /packages/e2e/src/plugins/scenarios/test-databricks.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | // initial page load's url 12 | function url() { 13 | return 'https://databricks.com/'; 14 | } 15 | 16 | // action where we want to detect memory leaks 17 | async function action(page) { 18 | await page.hover('a[href="/product/data-lakehouse"]'); 19 | } 20 | 21 | // action where we want to go back to the step before 22 | async function back(page) { 23 | await page.hover('a[href="/solutions"]'); 24 | } 25 | 26 | // specify the number of repeat for the action 27 | function repeat() { 28 | return 1; 29 | } 30 | 31 | module.exports = {action, back, repeat, url}; 32 | -------------------------------------------------------------------------------- /packages/e2e/src/plugins/scenarios/test-ebay.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | // initial page load's url 12 | function url() { 13 | return 'https://www.ebay.com/'; 14 | } 15 | 16 | // action where we want to detect memory leaks 17 | async function action(page) { 18 | const arr = await page.$x("//button[contains(., 'Shop by category')]"); 19 | if (arr[0]) { 20 | await arr[0].click(); 21 | } 22 | arr.forEach(elem => elem.dispose()); 23 | } 24 | 25 | // action where we want to go back to the step before 26 | async function back(page) { 27 | const arr = await page.$x("//button[contains(., 'Shop by category')]"); 28 | if (arr[0]) { 29 | await arr[0].click(); 30 | } 31 | arr.forEach(elem => elem.dispose()); 32 | } 33 | 34 | // specify the number of repeat for the action 35 | function repeat() { 36 | return 3; 37 | } 38 | 39 | module.exports = {action, back, repeat, url}; 40 | -------------------------------------------------------------------------------- /packages/e2e/src/plugins/scenarios/test-google-maps.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | // initial page load's url 12 | function url() { 13 | // Meta Headquarter 14 | return 'https://www.google.com/maps/@37.386427,-122.0428214,11z'; 15 | } 16 | 17 | // action where we want to detect memory leaks 18 | async function action(page) { 19 | await page.click('text/Hotels'); 20 | } 21 | 22 | // action where we want to go back to the step before 23 | async function back(page) { 24 | await page.click('[aria-label="Close"]'); 25 | } 26 | 27 | // specify the number of repeat for the action 28 | function repeat() { 29 | return 5; 30 | } 31 | 32 | module.exports = {action, back, repeat, url}; 33 | -------------------------------------------------------------------------------- /packages/e2e/src/plugins/scenarios/test-shopify.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | // initial page load's url 12 | function url() { 13 | return 'https://www.shopify.com/'; 14 | } 15 | 16 | // action where we want to detect memory leaks 17 | async function action(page) { 18 | await page.click('input[data-trekkie-id="Main Nav Get Started"]'); 19 | } 20 | 21 | // action where we want to go back to the step before 22 | async function back(page) { 23 | await page.click('button[id="CloseModal"]'); 24 | } 25 | 26 | // specify the number of repeat for the action 27 | function repeat() { 28 | return 3; 29 | } 30 | 31 | module.exports = {action, back, repeat, url}; 32 | -------------------------------------------------------------------------------- /packages/e2e/src/plugins/scenarios/test-youtube.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | // initial page load's url 12 | function url() { 13 | return 'https://www.youtube.com'; 14 | } 15 | 16 | // action where we want to detect memory leaks 17 | async function action(page) { 18 | await page.click('[id="video-title-link"]'); 19 | } 20 | 21 | // action where we want to go back to the step before 22 | async function back(page) { 23 | await page.click('[id="logo-icon"]'); 24 | } 25 | 26 | // specify the number of repeat for the action 27 | function repeat() { 28 | return 5; 29 | } 30 | 31 | module.exports = {action, back, repeat, url}; 32 | -------------------------------------------------------------------------------- /packages/e2e/static/example/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals", 3 | "overrides": [ 4 | { 5 | "excludedFiles": ["leak-filters/**", "scenarios/**"] 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /packages/e2e/static/example/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | -------------------------------------------------------------------------------- /packages/e2e/static/example/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | Install dependencies via `npm install` or `yarn install`. 6 | 7 | Run the development server via `npm run dev` or `yarn dev`. 8 | 9 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 10 | -------------------------------------------------------------------------------- /packages/e2e/static/example/components/layout.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import Head from 'next/head'; 12 | import React from 'react'; 13 | 14 | export default function Layout({children}) { 15 | return ( 16 |
17 | 18 | memlab examples 19 | 23 | 24 |
25 |

Let's fix memory leaks

26 | {children} 27 |
28 |
29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /packages/e2e/static/example/leak-filters/filter-by-retained-size.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * @nolint 5 | * @oncall memory_lab 6 | */ 7 | 8 | module.exports = function leakFilter(node, _snapshot, _leakedNodeIds) { 9 | return node.retainedSize > 1000 * 1000; 10 | }; 11 | -------------------------------------------------------------------------------- /packages/e2e/static/example/next.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * @nolint 5 | * @oncall memory_lab 6 | */ 7 | 8 | /** @type {import('next').NextConfig} */ 9 | const nextConfig = { 10 | reactStrictMode: true, 11 | }; 12 | 13 | module.exports = nextConfig; 14 | -------------------------------------------------------------------------------- /packages/e2e/static/example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "memlab-examples", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "next": ">=14.2.25", 13 | "react": "18.2.0", 14 | "react-dom": "18.2.0" 15 | }, 16 | "devDependencies": { 17 | "eslint": "8.18.0", 18 | "eslint-config-next": "12.1.6", 19 | "prettier": "^2.7.1" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/e2e/static/example/pages/_app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import Layout from '../components/layout.jsx'; 12 | import React from 'react'; 13 | 14 | function MyApp({Component, pageProps}) { 15 | return ( 16 | 17 | 18 | 19 | ); 20 | } 21 | 22 | export default MyApp; 23 | -------------------------------------------------------------------------------- /packages/e2e/static/example/pages/_document.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import {Head, Html, Main, NextScript} from 'next/document'; 12 | import React from 'react'; 13 | 14 | export default function Document() { 15 | return ( 16 | 17 | 18 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /packages/e2e/static/example/pages/examples/detached-dom.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * @nolint 5 | * @oncall memory_lab 6 | */ 7 | 8 | import Link from 'next/link'; 9 | import React from 'react'; 10 | 11 | export default function DetachedDom() { 12 | const addNewItem = () => { 13 | if (!window.leakedObjects) { 14 | window.leakedObjects = []; 15 | } 16 | for (let i = 0; i < 1024; i++) { 17 | window.leakedObjects.push(document.createElement('div')); 18 | } 19 | console.log( 20 | 'Detached DOMs are created. Please check Memory tab in Chrome DevTools', 21 | ); 22 | }; 23 | 24 | return ( 25 |
26 |
27 | Go back 28 |
29 |
30 |
31 | 34 |
35 |
36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /packages/e2e/static/example/pages/examples/oversized-object.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * @nolint 5 | * @oncall memory_lab 6 | */ 7 | 8 | import Link from 'next/link'; 9 | import React, {useEffect} from 'react'; 10 | 11 | export default function OversizedObject() { 12 | const bigArray = Array(1024 * 1024 * 2).fill(0); 13 | 14 | const eventHandler = () => { 15 | // the eventHandler closure keeps a reference 16 | // to the bigArray in the outter scope 17 | console.log('Using hugeObject', bigArray); 18 | }; 19 | 20 | useEffect(() => { 21 | // eventHandler is never unregistered 22 | window.addEventListener('custom-click', eventHandler); 23 | }, []); 24 | 25 | return ( 26 |
27 |
28 | Go back 29 |
30 |
31 |
32 | ObjectbigArrayis leaked. Please check Memory{' '} 33 | tab in DevTools 34 |
35 |
36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /packages/e2e/static/example/pages/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import Link from 'next/link'; 12 | import React from 'react'; 13 | 14 | export default function Home() { 15 | return ( 16 | <> 17 |
18 |
19 | 20 |

Example 1 →

21 | 22 |

Detached DOM element

23 |
24 |
25 | 26 |

Example 2 →

27 | 28 |

Leaked event listener

29 |
30 |
31 |
32 | Make sure to open your Console tab in DevTools. 33 |
34 | 35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /packages/e2e/static/example/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facebook/memlab/45c00767e0475b74cb75f08ed3cc92e137fddf57/packages/e2e/static/example/public/favicon.ico -------------------------------------------------------------------------------- /packages/e2e/static/example/scenarios/detached-dom.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * @nolint 5 | * @oncall memory_lab 6 | */ 7 | 8 | // memlab/packages/e2e/static/example/scenario/detached-dom.js 9 | /** 10 | * The initial `url` of the scenario we would like to run. 11 | */ 12 | function url() { 13 | return 'http://localhost:3000/examples/detached-dom'; 14 | } 15 | 16 | /** 17 | * Specify how memlab should perform action that you want 18 | * to test whether the action is causing memory leak. 19 | * 20 | * @param page - Puppeteer's page object: 21 | * https://pptr.dev/api/puppeteer.page/ 22 | */ 23 | async function action(page) { 24 | const elements = await page.$x( 25 | "//button[contains(., 'Create detached DOMs')]", 26 | ); 27 | const [button] = elements; 28 | if (button) { 29 | await button.click(); 30 | } 31 | // clean up external references from memlab 32 | await Promise.all(elements.map(e => e.dispose())); 33 | } 34 | 35 | /** 36 | * Specify how memlab should perform action that would 37 | * reset the action you performed above. 38 | * 39 | * @param page - Puppeteer's page object: 40 | * https://pptr.dev/api/puppeteer.page/ 41 | */ 42 | async function back(page) { 43 | await page.click('a[href="/"]'); 44 | } 45 | 46 | module.exports = {action, back, url}; 47 | -------------------------------------------------------------------------------- /packages/e2e/static/example/scenarios/oversized-leak-filter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * @nolint 5 | * @oncall memory_lab 6 | */ 7 | 8 | // leakFilter is called with each object (node) in browser 9 | // allocated by `action` but not released after the `back` call 10 | function leakFilter(node, _snapshot, _leakedNodeIds) { 11 | return node.retainedSize > 1000 * 1000; 12 | } 13 | 14 | module.exports = {leakFilter}; 15 | -------------------------------------------------------------------------------- /packages/e2e/static/example/scenarios/oversized-object-with-filter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * @nolint 5 | * @oncall memory_lab 6 | */ 7 | 8 | function url() { 9 | return 'http://localhost:3000/'; 10 | } 11 | 12 | // action where you suspect the memory leak might be happening 13 | async function action(page) { 14 | await page.click('a[href="/examples/oversized-object"]'); 15 | } 16 | 17 | // how to go back to the state before actionw 18 | async function back(page) { 19 | await page.click('a[href="/"]'); 20 | } 21 | 22 | // leakFilter is called with each object (node) in browser 23 | // allocated by `action` but not released after the `back` call 24 | function leakFilter(node, _snapshot, _leakedNodeIds) { 25 | return node.retainedSize > 1000 * 1000; 26 | } 27 | 28 | module.exports = {action, back, leakFilter, url}; 29 | -------------------------------------------------------------------------------- /packages/e2e/static/example/scenarios/oversized-object.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * @nolint 5 | * @oncall memory_lab 6 | */ 7 | 8 | function url() { 9 | return 'http://localhost:3000/'; 10 | } 11 | 12 | // action where you suspect the memory leak might be happening 13 | async function action(page) { 14 | await page.click('a[href="/examples/oversized-object"]'); 15 | } 16 | 17 | // how to go back to the state before actionw 18 | async function back(page) { 19 | await page.click('a[href="/"]'); 20 | } 21 | 22 | module.exports = {action, back, url}; 23 | -------------------------------------------------------------------------------- /packages/e2e/static/links/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | React App 11 | 34 | 35 | 36 | 37 | 38 |
39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /packages/e2e/static/links/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spa-test-links", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "eslint": "^8.8.0", 7 | "react": "^17.0.2", 8 | "react-dom": "^17.0.2", 9 | "react-scripts": "5.0.0", 10 | "web-vitals": "^2.1.4" 11 | }, 12 | "scripts": { 13 | "start": "react-scripts start", 14 | "build": "react-scripts build", 15 | "test": "react-scripts test", 16 | "eject": "react-scripts eject" 17 | }, 18 | "eslintConfig": { 19 | "extends": [ 20 | "react-app", 21 | "react-app/jest" 22 | ] 23 | }, 24 | "browserslist": { 25 | "production": [ 26 | ">0.2%", 27 | "not dead", 28 | "not op_mini all" 29 | ], 30 | "development": [ 31 | "last 1 chrome version", 32 | "last 1 firefox version", 33 | "last 1 safari version" 34 | ] 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/e2e/static/links/src/App.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import React from 'react'; 12 | 13 | // Use the window.injectHookForLink{n} hook to inject objects 14 | // to memory when link-{n} is triggered, for example: 15 | // 16 | // window.injectHookForLink4 = () => { 17 | // console.log('inject link 4'); 18 | // } 19 | 20 | function getClosure(i) { 21 | return () => { 22 | const name = `page-${i}`; 23 | console.log(name + ' clicked'); 24 | const hook = window[`injectHookForLink${i}`]; 25 | if (typeof hook === 'function') { 26 | hook(); 27 | } 28 | }; 29 | } 30 | 31 | function App() { 32 | return ( 33 |
34 | {[1, 2, 3, 4, 5, 6, 7, 8].map(i => { 35 | const testid = `link-${i}`; 36 | const callback = getClosure(i); 37 | return ( 38 | // eslint-disable-next-line jsx-a11y/no-static-element-interactions 39 |
40 | link-{i} 41 |
42 | ); 43 | })} 44 |
45 | ); 46 | } 47 | 48 | export default App; 49 | -------------------------------------------------------------------------------- /packages/e2e/static/links/src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import App from './App'; 12 | import React from 'react'; 13 | import ReactDOM from 'react-dom'; 14 | 15 | ReactDOM.render( 16 | 17 | 18 | , 19 | document.getElementById('root'), 20 | ); 21 | -------------------------------------------------------------------------------- /packages/e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./dist" 6 | }, 7 | "include": ["src/**/*"], 8 | "references": [{"path": "../lens"}, {"path": "../core"}] 9 | } 10 | -------------------------------------------------------------------------------- /packages/heap-analysis/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Meta Platforms, Inc. and affiliates. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/heap-analysis/README.md: -------------------------------------------------------------------------------- 1 | ## memlab Heap Analysis 2 | 3 | This is the memlab heap analysis library. It contains all memlab built-in heap analysis and 4 | provides a plugin interface for adding new heap analysis that can be easily added to memlab API and memlab CLI. 5 | 6 | ## Online Resources 7 | * [Official Website and Demo](https://facebook.github.io/memlab) 8 | * [Documentation](https://facebook.github.io/memlab/docs/intro) 9 | -------------------------------------------------------------------------------- /packages/heap-analysis/jest.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | const config = { 12 | verbose: true, 13 | testRegex: '(/dist/(.*/)?__tests__/.*)(\\.test|\\.spec)\\.jsx?$', 14 | maxConcurrency: 1, 15 | }; 16 | 17 | module.exports = config; 18 | -------------------------------------------------------------------------------- /packages/heap-analysis/src/HeapConfig.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {IHeapSnapshot, Optional, IHeapConfig} from '@memlab/core'; 12 | import {config} from '@memlab/core'; 13 | 14 | class HeapConfig implements IHeapConfig { 15 | public isCliInteractiveMode = false; 16 | public currentHeapFile: Optional; 17 | public currentHeap: Optional; 18 | 19 | private constructor() { 20 | this.currentHeap = null; 21 | this.currentHeapFile = null; 22 | } 23 | 24 | private static instance: Optional = null; 25 | public static getInstance(): HeapConfig { 26 | if (!HeapConfig.instance) { 27 | HeapConfig.instance = new HeapConfig(); 28 | } 29 | return HeapConfig.instance; 30 | } 31 | } 32 | 33 | const heapConfig = HeapConfig.getInstance(); 34 | config.heapConfig = heapConfig; 35 | export default heapConfig; 36 | -------------------------------------------------------------------------------- /packages/heap-analysis/src/options/HeapAnalysisNodeIdOption.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {ParsedArgs} from 'minimist'; 12 | import type {MemLabConfig} from '@memlab/core'; 13 | import {BaseOption} from '@memlab/core'; 14 | 15 | export default class HeapAnalysisNodeIdOption extends BaseOption { 16 | getOptionName(): string { 17 | return 'node-id'; 18 | } 19 | 20 | getDescription(): string { 21 | return 'set heap node ID'; 22 | } 23 | 24 | getExampleValues(): string[] { 25 | return ['94435', '@94435']; 26 | } 27 | 28 | async parse(config: MemLabConfig, args: ParsedArgs): Promise { 29 | const optionName = this.getOptionName(); 30 | const optionValue = args[optionName]; 31 | if (optionValue) { 32 | if (typeof optionValue === 'string' && optionValue.startsWith('@')) { 33 | args[optionName] = optionValue.slice(1); 34 | } 35 | config.focusFiberNodeId = Number(args[optionName]); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/heap-analysis/src/options/HeapAnalysisSnapshotDirectoryOption.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {ParsedArgs} from 'minimist'; 12 | 13 | import fs from 'fs'; 14 | import {BaseOption, MemLabConfig, utils} from '@memlab/core'; 15 | 16 | export default class HeapAnalysisSnapshotDirectoryOption extends BaseOption { 17 | getOptionName(): string { 18 | return 'snapshot-dir'; 19 | } 20 | 21 | getDescription(): string { 22 | return 'set directory path containing all heap snapshots under analysis'; 23 | } 24 | 25 | getExampleValues(): string[] { 26 | return ['/tmp/snapshots/']; 27 | } 28 | 29 | async parse(config: MemLabConfig, args: ParsedArgs): Promise { 30 | if (!args[this.getOptionName()]) { 31 | return; 32 | } 33 | const dir = args[this.getOptionName()]; 34 | if (!fs.existsSync(dir)) { 35 | utils.haltOrThrow(`Invalid directory: ${dir}`); 36 | } 37 | config.externalSnapshotDir = dir; 38 | config.useExternalSnapshot = true; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/heap-analysis/src/options/HeapAnalysisSnapshotFileOption.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import type {ParsedArgs} from 'minimist'; 12 | 13 | import fs from 'fs'; 14 | import {BaseOption, MemLabConfig, utils} from '@memlab/core'; 15 | 16 | export default class HeapAnalysisSnapshotFileOption extends BaseOption { 17 | getOptionName(): string { 18 | return 'snapshot'; 19 | } 20 | 21 | getDescription(): string { 22 | return 'set file path of the heap snapshot under analysis'; 23 | } 24 | 25 | getExampleValues(): string[] { 26 | return ['/tmp/file.heapsnapshot']; 27 | } 28 | 29 | async parse(config: MemLabConfig, args: ParsedArgs): Promise { 30 | if (!args.snapshot) { 31 | return; 32 | } 33 | const file = args.snapshot; 34 | if (!fs.existsSync(file)) { 35 | utils.haltOrThrow(`Invalid snapshot file: ${file}`); 36 | } 37 | config.useExternalSnapshot = true; 38 | config.externalSnapshotFilePaths[0] = file; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/heap-analysis/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./dist" 6 | }, 7 | "include": ["src/**/*"], 8 | "references": [{"path": "../core"}, {"path": "../e2e"}] 9 | } 10 | -------------------------------------------------------------------------------- /packages/lens/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | package-lock.json 3 | node_modules 4 | out 5 | dist/* 6 | 7 | # But do not ignore these files 8 | !dist/*.bundle.*.(js|ts) 9 | -------------------------------------------------------------------------------- /packages/lens/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Meta Platforms, Inc. and affiliates. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/lens/playwright.config.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | import process from 'process'; 11 | import {PlaywrightTestConfig} from '@playwright/test'; 12 | 13 | const config: PlaywrightTestConfig = { 14 | testDir: './src/tests', 15 | use: { 16 | // Serve files from the root directory 17 | baseURL: `file://${process.cwd()}`, 18 | }, 19 | }; 20 | 21 | export default config; 22 | -------------------------------------------------------------------------------- /packages/lens/src/config/config.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import {AnyValue, Config} from '../core/types'; 12 | 13 | // Performance Configuration 14 | export const performanceConfig = { 15 | scanIntervalMs: 1000, 16 | maxComponentStackDepth: 100, 17 | memoryMeasurementIntervalMs: 5000, 18 | }; 19 | 20 | // Feature Flags 21 | export const featureFlags = { 22 | enableMutationObserver: true, 23 | enableMemoryTracking: true, 24 | enableComponentStack: true, 25 | enableConsoleLogs: (window as AnyValue)?.TEST_MEMORY_SCAN, 26 | }; 27 | 28 | // overall Config 29 | export const config: Config = { 30 | performance: performanceConfig, 31 | features: featureFlags, 32 | }; 33 | -------------------------------------------------------------------------------- /packages/lens/src/core/valid-component-name.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import {Optional} from './types'; 12 | 13 | const displayNameBlockList = new Set(); 14 | 15 | export function isValidComponentName(name: Optional): boolean { 16 | return name != null && !displayNameBlockList.has(name); 17 | } 18 | -------------------------------------------------------------------------------- /packages/lens/src/extensions/basic-extension.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | import type ReactMemoryScan from '../core/react-memory-scan'; 11 | import type {AnalysisResult} from '../core/types'; 12 | 13 | /** 14 | * Base class for React Memory Scanner extensions. 15 | * Extensions can hook into the scanning process before and after analysis. 16 | */ 17 | export abstract class BasicExtension { 18 | protected readonly scanner: ReactMemoryScan; 19 | 20 | constructor(scanner: ReactMemoryScan) { 21 | this.scanner = scanner; 22 | } 23 | 24 | /** 25 | * Hook that runs before the memory scan starts. 26 | * Override this method to perform any setup or pre-scan operations. 27 | */ 28 | beforeScan(): void { 29 | // to be overridden 30 | } 31 | 32 | /** 33 | * Hook that runs after the memory scan completes. 34 | * Override this method to process or modify the analysis results. 35 | * @param analysisResult - The results from the memory scan 36 | */ 37 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 38 | afterScan(_analysisResult: AnalysisResult): void { 39 | // to be overridden 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/lens/src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | import * as fs from 'fs'; 11 | import * as path from 'path'; 12 | 13 | export function getBundleContent(): string { 14 | const bundlePath = path.join(__dirname, 'memlens.run.bundle.min.js'); 15 | return fs.readFileSync(bundlePath, 'utf-8'); 16 | } 17 | -------------------------------------------------------------------------------- /packages/lens/src/memlens.lib.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | import ReactMemoryScan from './core/react-memory-scan'; 11 | import {CreateOptions} from './core/types'; 12 | // import { DOMVisualizationExtension } from './extensions/dom-visualization-extension'; 13 | 14 | export function createReactMemoryScan( 15 | options: CreateOptions = {}, 16 | ): ReactMemoryScan { 17 | return new ReactMemoryScan(options); 18 | // const memoryScan = new ReactMemoryScan(options); 19 | // const domVisualizer = new DOMVisualizationExtension(memoryScan); 20 | // memoryScan.registerExtension(domVisualizer); 21 | // return memoryScan; 22 | } 23 | -------------------------------------------------------------------------------- /packages/lens/src/memlens.run.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | import ReactMemoryScan from './core/react-memory-scan'; 11 | import {DOMVisualizationExtension} from './extensions/dom-visualization-extension'; 12 | import {hasRunInSession, setRunInSession} from './utils/utils'; 13 | 14 | if (!hasRunInSession()) { 15 | const memoryScan = new ReactMemoryScan({isDevMode: true}); 16 | const domVisualizer = new DOMVisualizationExtension(memoryScan); 17 | memoryScan.registerExtension(domVisualizer); 18 | 19 | memoryScan.start(); 20 | setRunInSession(); 21 | } 22 | -------------------------------------------------------------------------------- /packages/lens/src/tests/bundle/lib.bundle.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | import type {AnyValue} from '../../core/types'; 11 | import {test, expect} from '@playwright/test'; 12 | import {libBundleFilePath} from '../utils/test-utils'; 13 | 14 | test('test library in browser via addScriptTag', async ({page}) => { 15 | // Navigate to an empty page (or a test page) 16 | await page.goto('about:blank'); 17 | 18 | // Inject the lib bundle file (UMD bundle of the library) into the page 19 | await page.addScriptTag({path: libBundleFilePath}); 20 | 21 | // Now the global `MemLens` should be available in the page 22 | const libraryLoaded = await page.evaluate(() => { 23 | const createReactMemoryScan = (window as AnyValue).MemLens 24 | .createReactMemoryScan; 25 | const instance = createReactMemoryScan(); 26 | const analysisResult = instance.scan(); 27 | return typeof analysisResult.totalElements === 'number'; 28 | }); 29 | 30 | expect(libraryLoaded).toBe(true); 31 | }); 32 | -------------------------------------------------------------------------------- /packages/lens/src/tests/manual/playwright-open-manual.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | const path = require('path'); 11 | const {chromium, devices} = require('playwright'); 12 | 13 | // eslint-disable-next-line fb-www/async-iife-termination 14 | (async () => { 15 | const args = process.argv.slice(2); 16 | if (args.length === 0) { 17 | const scriptName = path.basename(__filename); 18 | console.error(`Usage: node ${scriptName} `); 19 | process.exit(1); 20 | } 21 | 22 | let filePath = args[0]; 23 | if (!path.isAbsolute(filePath)) { 24 | filePath = path.resolve(__dirname, filePath); 25 | } 26 | 27 | const browser = await chromium.launch({ 28 | headless: false, 29 | args: ['--auto-open-devtools-for-tabs'], 30 | }); 31 | 32 | const context = await browser.newContext({ 33 | ...devices['Desktop Chrome'], 34 | }); 35 | 36 | const page = await context.newPage(); 37 | await page.goto('file://' + filePath); 38 | 39 | // Don’t close the browser so you can inspect 40 | })(); 41 | -------------------------------------------------------------------------------- /packages/lens/src/tests/utils/test-utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | import path from 'path'; 11 | 12 | // export a function that returns the absolute path to the dist folder 13 | 14 | export const distPath = path.join(__dirname, '..', '..', '..', 'dist'); 15 | export const srcPath = path.join(__dirname, '..', '..', '..', 'src'); 16 | 17 | export async function wait(timeoutInMs: number) { 18 | await new Promise(resolve => setTimeout(resolve, timeoutInMs)); 19 | } 20 | 21 | export const libBundleFile = 'memlens.lib.bundle.js'; 22 | export const libBundleFilePath = path.join(distPath, libBundleFile); 23 | export const libBundleMinFile = 'memlens.lib.bundle.min.js'; 24 | export const libBundleMinFilePath = path.join(distPath, libBundleMinFile); 25 | export const runBundleFile = 'memlens.run.bundle.js'; 26 | export const runBundleFilePath = path.join(distPath, runBundleFile); 27 | export const runBundleMinFile = 'memlens.run.bundle.min.js'; 28 | export const runBundleMinFilePath = path.join(distPath, runBundleMinFile); 29 | -------------------------------------------------------------------------------- /packages/lens/src/visual/components/visual-overlay.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | import {createVisualizerElement} from '../visual-utils'; 11 | 12 | export function createOverlayDiv(): HTMLDivElement { 13 | const overlayDiv = createVisualizerElement('div') as HTMLDivElement; 14 | overlayDiv.style.position = 'absolute'; 15 | overlayDiv.style.top = '0px'; 16 | overlayDiv.style.left = '0px'; 17 | overlayDiv.id = 'memory-visualization-overlay'; 18 | return overlayDiv; 19 | } 20 | -------------------------------------------------------------------------------- /packages/lens/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES6", // Choose ES5 or ES6 for broad browser support 4 | "module": "NodeNext", // Use ESNext for modern module systems; CommonJS for Node.js 5 | "moduleResolution": "nodenext", 6 | "lib": ["DOM", "ES2021"], // Include DOM for browser types 7 | "outDir": "./dist", // Output directory for compiled files 8 | "rootDir": "./src", // Source directory for TypeScript files 9 | "strict": true, // Enable all strict type-checking options 10 | "esModuleInterop": true, // Enable interoperability between CommonJS and ES Modules 11 | "skipLibCheck": true, // Skip type-checking of declaration files for performance 12 | "forceConsistentCasingInFileNames": true, // Ensure consistent casing in imports 13 | "declaration": true, // generate .d.ts files 14 | "composite": true 15 | }, 16 | "include": ["src/**/*"], // Include all files in the src directory 17 | "exclude": ["node_modules", "dist", "src/lib-index.js.flow"] // Exclude unnecessary directories 18 | } 19 | -------------------------------------------------------------------------------- /packages/memlab/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Meta Platforms, Inc. and affiliates. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/memlab/bin/memlab: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node --expose-gc --max-old-space-size=4096 2 | 3 | /** 4 | * Copyright (c) Meta Platforms, Inc. and affiliates. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE file in the root directory of this source tree. 8 | * 9 | * @format 10 | * @oncall memory_lab 11 | */ 12 | 13 | var path = require('path'); 14 | var core = require('@memlab/core'); 15 | var cli = require("@memlab/cli"); 16 | 17 | // register `memlab` and `@memlab/cli` package info 18 | // so that `memlab version` get use the info 19 | Promise.all([ 20 | // register the current `memlab` package 21 | core.PackageInfoLoader.registerPackage(path.join(__dirname, '..')), 22 | cli.registerPackage(), 23 | ]).then(() => { 24 | cli.run(); 25 | }); 26 | -------------------------------------------------------------------------------- /packages/memlab/bin/preinstall: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Copyright (c) Meta Platforms, Inc. and affiliates. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE file in the root directory of this source tree. 8 | * 9 | * @format 10 | * @oncall memory_lab 11 | */ 12 | 13 | const path = require("path"); 14 | const fs = require("fs"); 15 | if (process.platform !== "win32") { 16 | const memlabFile = path.join(__dirname, "../bin/memlab"); 17 | const content = fs.readFileSync(memlabFile, "UTF-8"); 18 | fs.writeFileSync( 19 | memlabFile, 20 | content.replace("#!/usr/bin/env node", "#!/usr/bin/env -S node"), 21 | "UTF-8" 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /packages/memlab/src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | import path from 'path'; 11 | import {PackageInfoLoader} from '@memlab/core'; 12 | /** @internal */ 13 | export async function registerPackage(): Promise { 14 | return PackageInfoLoader.registerPackage(path.join(__dirname, '..')); 15 | } 16 | 17 | export * from '@memlab/api'; 18 | -------------------------------------------------------------------------------- /packages/memlab/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./dist" 6 | }, 7 | "include": ["src/**/*"], 8 | "references": [{"path": "../api"}, {"path": "../cli"}] 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist", 4 | "allowJs": true, 5 | "module": "commonjs", 6 | "esModuleInterop": true, 7 | "target": "es6", 8 | "composite": true, 9 | "declaration": true, 10 | "declarationMap": true, 11 | "incremental": true, 12 | "strict": true, 13 | "strictPropertyInitialization": false, 14 | "noEmitOnError": true, 15 | "types": ["node", "jest"], 16 | "paths": { 17 | "@memlab/core": ["./packages/core/src/"], 18 | "@memlab/lens": ["./packages/lens/src/"], 19 | "@memlab/e2e": ["./packages/e2e/src/"], 20 | "@memlab/api": ["./packages/api/src/"], 21 | "@memlab/cli": ["./packages/cli/src/"], 22 | "@memlab/heap-analysis": ["./packages/heap-analysis/src/"], 23 | "@memlab/memlab": ["./packages/memlab/src/"] 24 | } 25 | }, 26 | "include": ["./src", "./packages/**/src", "./node_modules/@types/puppeteer/index.d.ts"], 27 | "exclude": ["./node_modules", "./packages/**/dist"] 28 | } 29 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { "path": "./packages/core" }, 5 | { "path": "./packages/lens" }, 6 | { "path": "./packages/e2e" }, 7 | { "path": "./packages/heap-analysis" }, 8 | { "path": "./packages/api" }, 9 | { "path": "./packages/cli" }, 10 | { "path": "./packages/memlab" } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /website/.env: -------------------------------------------------------------------------------- 1 | APPLICATION_ID=OLRNUAX6MR 2 | API_KEY= 3 | -------------------------------------------------------------------------------- /website/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | build.log* 22 | -------------------------------------------------------------------------------- /website/.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | .docusaurus 4 | -------------------------------------------------------------------------------- /website/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "bracketSameLine": true, 4 | "bracketSpacing": false, 5 | "printWidth": 80, 6 | "proseWrap": "never", 7 | "singleQuote": true, 8 | "trailingComma": "all" 9 | } 10 | -------------------------------------------------------------------------------- /website/README.md: -------------------------------------------------------------------------------- 1 | # Website 2 | 3 | This website is built using [Docusaurus 2](https://docusaurus.io/), 4 | a modern static website generator. 5 | 6 | ### Installation 7 | 8 | ```bash 9 | $ yarn 10 | ``` 11 | 12 | ### Local Development 13 | 14 | ```bash 15 | $ yarn start 16 | ``` 17 | 18 | This command starts a local development server and opens up a browser window. 19 | Most changes are reflected live without having to restart the server. 20 | 21 | ### Build 22 | 23 | ```bash 24 | $ yarn build 25 | ``` 26 | 27 | This command generates static content into the `build` directory and 28 | can be served using any static contents hosting service. 29 | 30 | ### Deployment 31 | 32 | Using SSH: 33 | 34 | ```bash 35 | $ USE_SSH=true yarn deploy 36 | ``` 37 | 38 | Not using SSH: 39 | 40 | ```bash 41 | $ GIT_USER= yarn deploy 42 | ``` 43 | 44 | If you are using GitHub pages for hosting, this command is a convenient way 45 | to build the website and push to the `gh-pages` branch. 46 | 47 | ### Build Search Index 48 | 49 | First set the application id and key in the `.env` file, 50 | then `cd` into this `website` folder 51 | ```bash 52 | docker run -it --env-file=.env -e "CONFIG=$(cat /path-to/search-index-config.json | jq -r tostring)" algolia/docsearch-scraper 53 | ``` 54 | -------------------------------------------------------------------------------- /website/babel.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @nolint 9 | * @oncall memory_lab 10 | */ 11 | 12 | module.exports = { 13 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 14 | }; 15 | -------------------------------------------------------------------------------- /website/docs/api/_category_.yml: -------------------------------------------------------------------------------- 1 | label: "API" -------------------------------------------------------------------------------- /website/docs/api/classes/_category_.yml: -------------------------------------------------------------------------------- 1 | label: "Classes" 2 | position: 3 -------------------------------------------------------------------------------- /website/docs/api/enums/_category_.yml: -------------------------------------------------------------------------------- 1 | label: "Enumerations" 2 | position: 2 -------------------------------------------------------------------------------- /website/docs/api/enums/api_src.ConsoleMode.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: "api_src.ConsoleMode" 3 | title: "Enumeration: ConsoleMode" 4 | sidebar_label: "ConsoleMode" 5 | custom_edit_url: null 6 | --- 7 | 8 | enum of all console mode options 9 | 10 | ## Enumeration Members 11 | 12 | ### **CONTINUOUS\_TEST** 13 | 14 | continuous test mode, no terminal output overwrite or animation, 15 | equivalent to using `--sc` 16 | 17 | * **Source**: 18 | * api/src/state/ConsoleModeManager.ts:26 19 | 20 | ___ 21 | 22 | ### **DEFAULT** 23 | 24 | the default mode, there could be terminal output overwrite and animation, 25 | 26 | * **Source**: 27 | * api/src/state/ConsoleModeManager.ts:30 28 | 29 | ___ 30 | 31 | ### **SILENT** 32 | 33 | mute all terminal output, equivalent to using `--silent` 34 | 35 | * **Source**: 36 | * api/src/state/ConsoleModeManager.ts:21 37 | 38 | ___ 39 | 40 | ### **VERBOSE** 41 | 42 | verbose mode, there could be terminal output overwrite and animation 43 | 44 | * **Source**: 45 | * api/src/state/ConsoleModeManager.ts:34 46 | -------------------------------------------------------------------------------- /website/docs/api/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: "index" 3 | title: "memlab" 4 | sidebar_label: "Table of contents" 5 | sidebar_position: 0.5 6 | hide_table_of_contents: true 7 | custom_edit_url: null 8 | --- 9 | 10 | ## Modules 11 | 12 | - [api/src](modules/api_src.md) 13 | - [core/src](modules/core_src.md) 14 | - [heap-analysis/src](modules/heap_analysis_src.md) 15 | -------------------------------------------------------------------------------- /website/docs/api/interfaces/_category_.yml: -------------------------------------------------------------------------------- 1 | label: "Interfaces" 2 | position: 4 -------------------------------------------------------------------------------- /website/docs/api/interfaces/core_src.IBrowserInfo.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: "core_src.IBrowserInfo" 3 | title: "Interface: IBrowserInfo" 4 | sidebar_label: "IBrowserInfo" 5 | custom_edit_url: null 6 | --- 7 | 8 | This data structure contains the input configuration for the browser and 9 | output data from the browser. You can retrieve the instance of this type 10 | through [RunMetaInfo](../modules/core_src.md#runmetainfo). 11 | 12 | ## Properties 13 | 14 | ### **\_browserVersion**: `string` 15 | 16 | browser version 17 | 18 | * **Source**: 19 | * core/src/lib/Types.ts:1234 20 | 21 | ___ 22 | 23 | ### **\_consoleMessages**: `string`[] 24 | 25 | all web console output 26 | 27 | * **Source**: 28 | * core/src/lib/Types.ts:1242 29 | 30 | ___ 31 | 32 | ### **\_puppeteerConfig**: `LaunchOptions` 33 | 34 | configuration for puppeteer 35 | 36 | * **Source**: 37 | * core/src/lib/Types.ts:1238 38 | -------------------------------------------------------------------------------- /website/docs/api/modules/_category_.yml: -------------------------------------------------------------------------------- 1 | label: "Modules" 2 | position: 1 -------------------------------------------------------------------------------- /website/docs/guides/_category_.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facebook/memlab/45c00767e0475b74cb75f08ed3cc92e137fddf57/website/docs/guides/_category_.yml -------------------------------------------------------------------------------- /website/docs/guides/example-app-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facebook/memlab/45c00767e0475b74cb75f08ed3cc92e137fddf57/website/docs/guides/example-app-1.png -------------------------------------------------------------------------------- /website/docs/guides/memlab-result-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facebook/memlab/45c00767e0475b74cb75f08ed3cc92e137fddf57/website/docs/guides/memlab-result-2.png -------------------------------------------------------------------------------- /website/docs/guides/memlab-result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facebook/memlab/45c00767e0475b74cb75f08ed3cc92e137fddf57/website/docs/guides/memlab-result.png -------------------------------------------------------------------------------- /website/docs/guides/oversized-object.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facebook/memlab/45c00767e0475b74cb75f08ed3cc92e137fddf57/website/docs/guides/oversized-object.png -------------------------------------------------------------------------------- /website/search-index-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "index_name": "memlab doc site", 3 | "start_urls": [ 4 | "https://facebook.github.io/memlab/docs/intro", 5 | "https://facebook.github.io/memlab/docs/installation", 6 | "https://facebook.github.io/memlab/docs/getting-started", 7 | "https://facebook.github.io/memlab/docs/cli/CLI-commands", 8 | "https://facebook.github.io/memlab/docs/guides/guides-detached-dom", 9 | "https://facebook.github.io/memlab/docs/guides/guides-detect-oversized-object", 10 | "https://facebook.github.io/memlab/docs/guides/guides-find-leaks", 11 | "https://facebook.github.io/memlab/docs/guides/integration-and-file-structure", 12 | "https://facebook.github.io/memlab/docs/how-memlab-works", 13 | "https://facebook.github.io/memlab/docs/api/" 14 | ], 15 | "stop_urls": [], 16 | "selectors": { 17 | "lvl0": "article h1", 18 | "lvl1": "article h2", 19 | "lvl2": "article h3", 20 | "lvl3": "article h4", 21 | "lvl4": "article h5", 22 | "lvl5": "article h6", 23 | "text": "article p, article li" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /website/src/components/CodeBlock.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import {useColorMode} from '@docusaurus/theme-common'; 12 | import Highlight, {defaultProps} from 'prism-react-renderer'; 13 | import palenight from 'prism-react-renderer/themes/palenight'; 14 | import vsLight from 'prism-react-renderer/themes/vsLight'; 15 | import React from 'react'; 16 | 17 | const CodeBlock = ({code, language}) => { 18 | const {colorMode} = useColorMode(); 19 | return ( 20 | 25 | {({className, getLineProps, getTokenProps, style, tokens}) => ( 26 |
27 |           {tokens.map((line, i) => (
28 |             
29 | {line.map((token, key) => ( 30 | 31 | ))} 32 |
33 | ))} 34 |
35 | )} 36 |
37 | ); 38 | }; 39 | 40 | export default CodeBlock; 41 | -------------------------------------------------------------------------------- /website/src/components/Logo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import styles from '../pages/styles.module.css'; 12 | import useBaseUrl from '@docusaurus/useBaseUrl'; 13 | import React from 'react'; 14 | 15 | const Logo = ({i, infoUrl, imageUrl, caption}) => { 16 | return ( 17 | 18 | 19 | 20 | ); 21 | }; 22 | 23 | export default Logo; 24 | -------------------------------------------------------------------------------- /website/src/components/TerminalReplay/css/styles.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @oncall memory_lab 8 | * @format 9 | */ 10 | 11 | /* @nolint */ 12 | 13 | .terminal { 14 | background: transparent; 15 | background-color: transparent !important; 16 | border: 0; 17 | color: #f0f0f0; 18 | display: inline-block; 19 | /* Monaco is from Macbook */ 20 | font-family: 'DejaVu Sans Mono', 'Liberation Mono', monospace, 'Monaco'; 21 | font-size: 13px; 22 | text-align: left; 23 | width: 100%; 24 | -webkit-font-smoothing: auto; 25 | word-wrap: normal; 26 | } 27 | 28 | span.terminal-cursor { 29 | background-color: #f0f0f0; 30 | width: 10px; 31 | } 32 | 33 | .terminal-inner { 34 | background: rgb(23, 25, 35); 35 | border-radius: 6px; 36 | min-height: 400px; 37 | padding: 10px 10px 6px 10px; 38 | width: 780px; 39 | } 40 | 41 | .terminal-content { 42 | line-height: 16px; 43 | margin: 20px auto; 44 | width: 780px; 45 | } 46 | 47 | @media only screen and (max-width: 780px) { 48 | .terminal-content { 49 | display: none; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /website/src/components/TerminalReplay/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import _ from './css/styles.css'; 12 | import Terminal from './lib/Term'; 13 | import createTimeline from './lib/TimelineFactory'; 14 | import React, {useEffect, useState} from 'react'; 15 | 16 | function initializeTerminal(id) { 17 | const terminal = new Terminal({ 18 | cols: 90, 19 | rows: 28, 20 | screenKeys: true, 21 | }); 22 | 23 | terminal.open(document.querySelector(`#${id} .content`)); 24 | return terminal; 25 | } 26 | 27 | let gid = 0; 28 | 29 | const TerminalDemo = ({stdouts}) => { 30 | const [id] = useState(`terminal-${gid++}`); 31 | useEffect(() => { 32 | const terminal = initializeTerminal(id); 33 | const timeline = createTimeline(terminal, stdouts); 34 | timeline.play(); 35 | return () => { 36 | timeline.pause(); 37 | }; 38 | }, [id, stdouts]); 39 | 40 | return ( 41 |
42 |
43 |
44 |
45 |
46 | ); 47 | }; 48 | 49 | export default TerminalDemo; 50 | -------------------------------------------------------------------------------- /website/src/components/TerminalReplay/lib/TimelineFactory.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import Timeline from './Timeline'; 12 | 13 | export default function createTimeline(term, stdouts) { 14 | const totalTime = stdouts[stdouts.length - 1].time; 15 | const timeline = new Timeline({length: totalTime, frequency: 10}); 16 | 17 | class Marker { 18 | constructor(time, content) { 19 | this.time = time; 20 | this.content = content; 21 | } 22 | forward() { 23 | term.write(this.content); 24 | } 25 | } 26 | 27 | for (let i = 0; i < stdouts.length; i++) { 28 | const {time, content} = stdouts[i]; 29 | timeline.markers.push(new Marker(time, content)); 30 | } 31 | return timeline; 32 | } 33 | -------------------------------------------------------------------------------- /website/src/components/TerminalStatic.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import Highlight, {defaultProps} from 'prism-react-renderer'; 12 | import theme from 'prism-react-renderer/themes/palenight'; 13 | import React from 'react'; 14 | 15 | const TerminalStatic = ({code, language}) => ( 16 | 17 | {({className, getLineProps, getTokenProps, style, tokens}) => ( 18 |
19 |         {tokens.map((line, i) => (
20 |           
21 | {line.map((token, key) => ( 22 | 23 | ))} 24 |
25 | ))} 26 |
27 | )} 28 |
29 | ); 30 | 31 | export default TerminalStatic; 32 | -------------------------------------------------------------------------------- /website/src/lib/useWindowSize.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | * @format 8 | * @oncall memory_lab 9 | */ 10 | 11 | import {useEffect, useState} from 'react'; 12 | 13 | // deal with SSR 14 | const _window = 15 | typeof window !== 'undefined' 16 | ? window 17 | : { 18 | addEventListener: () => {}, 19 | removeEventListener: () => {}, 20 | innerWidth: 0, 21 | innerHeight: 0, 22 | }; 23 | 24 | function getWindowSize() { 25 | const {innerWidth: width, innerHeight: height} = _window; 26 | return { 27 | width, 28 | height, 29 | }; 30 | } 31 | 32 | export default function useWindowSize() { 33 | const [windowSize, setWindowSize] = useState(getWindowSize()); 34 | 35 | useEffect(() => { 36 | function handleResize() { 37 | setWindowSize(getWindowSize()); 38 | } 39 | 40 | _window.addEventListener('resize', handleResize); 41 | return () => _window.removeEventListener('resize', handleResize); 42 | }, []); 43 | 44 | return windowSize; 45 | } 46 | -------------------------------------------------------------------------------- /website/src/pages/under-construction.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 🚧 This Page is under construction 3 | --- 4 | 5 | # This Page is Under Construction 🚧 6 | 7 | Use this page for anything you want to use as placeholder link. Lets make sure we don't publish this page to public 8 | -------------------------------------------------------------------------------- /website/static/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facebook/memlab/45c00767e0475b74cb75f08ed3cc92e137fddf57/website/static/.nojekyll -------------------------------------------------------------------------------- /website/static/img/docusaurus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facebook/memlab/45c00767e0475b74cb75f08ed3cc92e137fddf57/website/static/img/docusaurus.png -------------------------------------------------------------------------------- /website/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facebook/memlab/45c00767e0475b74cb75f08ed3cc92e137fddf57/website/static/img/favicon.ico -------------------------------------------------------------------------------- /website/static/img/heap-diff.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facebook/memlab/45c00767e0475b74cb75f08ed3cc92e137fddf57/website/static/img/heap-diff.gif -------------------------------------------------------------------------------- /website/static/img/heap-view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facebook/memlab/45c00767e0475b74cb75f08ed3cc92e137fddf57/website/static/img/heap-view.png -------------------------------------------------------------------------------- /website/static/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facebook/memlab/45c00767e0475b74cb75f08ed3cc92e137fddf57/website/static/img/logo.png -------------------------------------------------------------------------------- /website/static/img/meta-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facebook/memlab/45c00767e0475b74cb75f08ed3cc92e137fddf57/website/static/img/meta-favicon.png -------------------------------------------------------------------------------- /website/static/img/oss_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facebook/memlab/45c00767e0475b74cb75f08ed3cc92e137fddf57/website/static/img/oss_logo.png -------------------------------------------------------------------------------- /website/static/img/users/fb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facebook/memlab/45c00767e0475b74cb75f08ed3cc92e137fddf57/website/static/img/users/fb.png -------------------------------------------------------------------------------- /website/static/img/users/workplace.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /website/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/docusaurus/tsconfig.json", 3 | "compilerOptions": { 4 | "jsx": "react", 5 | "baseUrl": "." 6 | } 7 | } 8 | --------------------------------------------------------------------------------