Repository: facebook/react
Pull request: [DevTools] Add utils for perfomance marks

## Summary

When rebasing my profiling PR (#22167) I encountered a lot of merge conflicts, so I figured it might make sense to provide similar utils for performance marks, both to make it easier to manage my merge conflicts, but also a lot of the branching introduced into the code. 

Just a proposal, so let me know if you think this makes sense, but also totally fine to abandon this refactor.

## Test Plan

- yarn flow
- yarn test
- yarn test-build-devtools
- manual test of browser extension



Changed files:
filename: packages/react-devtools-extensions/src/astUtils.js
patch: @@ -7,7 +7,7 @@
  * @flow
  */
 
-import {__PERFORMANCE_PROFILE__} from 'react-devtools-shared/src/constants';
+import {withSyncPerformanceMark} from 'react-devtools-shared/src/PerformanceMarks';
 import traverse, {NodePath, Node} from '@babel/traverse';
 import {File} from '@babel/types';
 
@@ -28,15 +28,6 @@ export type SourceFileASTWithHookDetails = {
 
 export const NO_HOOK_NAME = '<no-hook>';
 
-function mark(markName: string): void {
-  performance.mark(markName + '-start');
-}
-
-function measure(markName: string): void {
-  performance.mark(markName + '-end');
-  performance.measure(markName, markName + '-start', markName + '-end');
-}
-
 const AST_NODE_TYPES = Object.freeze({
   PROGRAM: 'Program',
   CALL_EXPRESSION: 'CallExpression',
@@ -141,13 +132,10 @@ export function getHookName(
   originalSourceLineNumber: number,
   originalSourceColumnNumber: number,
 ): string | null {
-  if (__PERFORMANCE_PROFILE__) {
-    mark('getPotentialHookDeclarationsFromAST(originalSourceAST)');
-  }
-  const hooksFromAST = getPotentialHookDeclarationsFromAST(originalSourceAST);
-  if (__PERFORMANCE_PROFILE__) {
-    measure('getPotentialHookDeclarationsFromAST(originalSourceAST)');
-  }
+  const hooksFromAST = withSyncPerformanceMark(
+    'getPotentialHookDeclarationsFromAST(originalSourceAST)',
+    () => getPotentialHookDeclarationsFromAST(originalSourceAST),
+  );
 
   let potentialReactHookASTNode = null;
   if (originalSourceColumnNumber === 0) {
@@ -188,29 +176,23 @@ export function getHookName(
   // nodesAssociatedWithReactHookASTNode could directly be used to obtain the hook variable name
   // depending on the type of potentialReactHookASTNode
   try {
-    if (__PERFORMANCE_PROFILE__) {
-      mark('getFilteredHookASTNodes()');
-    }
-    const nodesAssociatedWithReactHookASTNode = getFilteredHookASTNodes(
-      potentialReactHookASTNode,
-      hooksFromAST,
-      originalSourceCode,
+    const nodesAssociatedWithReactHookASTNode = withSyncPerformanceMark(
+      'getFilteredHookASTNodes()',
+      () =>
+        getFilteredHookASTNodes(
+          potentialReactHookASTNode,
+          hooksFromAST,
+          originalSourceCode,
+        ),
     );
-    if (__PERFORMANCE_PROFILE__) {
-      measure('getFilteredHookASTNodes()');
-    }
 
-    if (__PERFORMANCE_PROFILE__) {
-      mark('getHookNameFromNode()');
-    }
-    const name = getHookNameFromNode(
-      hook,
-      nodesAssociatedWithReactHookASTNode,
-      potentialReactHookASTNode,
+    const name = withSyncPerformanceMark('getHookNameFromNode()', () =>
+      getHookNameFromNode(
+        hook,
+        nodesAssociatedWithReactHookASTNode,
+        potentialReactHookASTNode,
+      ),
     );
-    if (__PERFORMANCE_PROFILE__) {
-      measure('getHookNameFromNode()');
-    }
 
     return name;
   } catch (error) {
@@ -315,19 +297,15 @@ function getHookVariableName(
 
 function getPotentialHookDeclarationsFromAST(sourceAST: File): NodePath[] {
   const potentialHooksFound: NodePath[] = [];
-  if (__PERFORMANCE_PROFILE__) {
-    mark('traverse(sourceAST)');
-  }
-  traverse(sourceAST, {
-    enter(path) {
-      if (path.isVariableDeclarator() && isPotentialHookDeclaration(path)) {
-        potentialHooksFound.push(path);
-      }
-    },
-  });
-  if (__PERFORMANCE_PROFILE__) {
-    measure('traverse(sourceAST)');
-  }
+  withSyncPerformanceMark('traverse(sourceAST)', () =>
+    traverse(sourceAST, {
+      enter(path) {
+        if (path.isVariableDeclarator() && isPotentialHookDeclaration(path)) {
+          potentialHooksFound.push(path);
+        }
+      },
+    }),
+  );
   return potentialHooksFound;
 }
 

filename: packages/react-devtools-extensions/src/parseHookNames/parseHookNames.js
patch: @@ -12,32 +12,24 @@ import LRU from 'lru-cache';
 import {SourceMapConsumer} from 'source-map-js';
 import {getHookName} from '../astUtils';
 import {areSourceMapsAppliedToErrors} from '../ErrorTester';
-import {
-  __DEBUG__,
-  __PERFORMANCE_PROFILE__,
-} from 'react-devtools-shared/src/constants';
+import {__DEBUG__} from 'react-devtools-shared/src/constants';
 import {getHookSourceLocationKey} from 'react-devtools-shared/src/hookNamesCache';
 import {sourceMapIncludesSource} from '../SourceMapUtils';
 import {SourceMapMetadataConsumer} from '../SourceMapMetadataConsumer';
+import {
+  withAsyncPerformanceMark,
+  withCallbackPerformanceMark,
+  withSyncPerformanceMark,
+} from 'react-devtools-shared/src/PerformanceMarks';
 
 import type {
   HooksNode,
   HookSource,
   HooksTree,
 } from 'react-debug-tools/src/ReactDebugHooks';
 import type {HookNames, LRUCache} from 'react-devtools-shared/src/types';
-import type {Thenable} from 'shared/ReactTypes';
 import type {SourceConsumer} from '../astUtils';
 
-function mark(markName: string): void {
-  performance.mark(markName + '-start');
-}
-
-function measure(markName: string): void {
-  performance.mark(markName + '-end');
-  performance.measure(markName, markName + '-start', markName + '-end');
-}
-
 const MAX_SOURCE_LENGTH = 100_000_000;
 
 type AST = mixed;
@@ -118,17 +110,20 @@ const originalURLToMetadataCache: LRUCache<
 
 export async function parseHookNames(
   hooksTree: HooksTree,
-): Thenable<HookNames | null> {
-  if (__PERFORMANCE_PROFILE__) {
-    mark('parseHookNames()');
-    mark('flattenHooksList()');
-  }
+): Promise<HookNames | null> {
   const hooksList: Array<HooksNode> = [];
-  flattenHooksList(hooksTree, hooksList);
-  if (__PERFORMANCE_PROFILE__) {
-    measure('flattenHooksList()');
-  }
+  withSyncPerformanceMark('flattenHooksList()', () => {
+    flattenHooksList(hooksTree, hooksList);
+  });
+
+  return withAsyncPerformanceMark('parseHookNames()', () =>
+    parseHookNamesImpl(hooksList),
+  );
+}
 
+async function parseHookNamesImpl(
+  hooksList: HooksNode[],
+): Promise<HookNames | null> {
   if (__DEBUG__) {
     console.log('parseHookNames() hooksList:', hooksList);
   }
@@ -183,56 +178,25 @@ export async function parseHookNames(
     }
   }
 
-  if (__PERFORMANCE_PROFILE__) {
-    mark('loadSourceFiles()');
-  }
+  await withAsyncPerformanceMark('loadSourceFiles()', () =>
+    loadSourceFiles(locationKeyToHookSourceData),
+  );
 
-  let promise = loadSourceFiles(locationKeyToHookSourceData);
-  if (__PERFORMANCE_PROFILE__) {
-    promise = promise.then(data => {
-      mark('extractAndLoadSourceMaps()');
-      measure('loadSourceFiles()');
-      return data;
-    });
-  }
-  promise = promise.then(() =>
+  await withAsyncPerformanceMark('extractAndLoadSourceMaps()', () =>
     extractAndLoadSourceMaps(locationKeyToHookSourceData),
   );
-  if (__PERFORMANCE_PROFILE__) {
-    promise = promise.then(data => {
-      mark('parseSourceAST()');
-      measure('extractAndLoadSourceMaps()');
-      return data;
-    });
-  }
-  promise = promise.then(() => parseSourceAST(locationKeyToHookSourceData));
-  if (__PERFORMANCE_PROFILE__) {
-    promise = promise.then(data => {
-      mark('updateLruCache()');
-      measure('parseSourceAST()');
-      return data;
-    });
-  }
-  promise = promise.then(() => updateLruCache(locationKeyToHookSourceData));
-  if (__PERFORMANCE_PROFILE__) {
-    promise = promise.then(data => {
-      mark('findHookNames()');
-      measure('updateLruCache()');
-      return data;
-    });
-  }
-  promise = promise.then(() =>
-    findHookNames(hooksList, locationKeyToHookSourceData),
+
+  withSyncPerformanceMark('parseSourceAST()', () =>
+    parseSourceAST(locationKeyToHookSourceData),
   );
-  if (__PERFORMANCE_PROFILE__) {
-    promise = promise.then(data => {
-      measure('findHookNames()');
-      measure('parseHookNames()');
-      return data;
-    });
-  }
 
-  return promise;
+  withSyncPerformanceMark('updateLruCache()', () =>
+    updateLruCache(locationKeyToHookSourceData),
+  );
+
+  return withSyncPerformanceMark('findHookNames()', () =>
+    findHookNames(hooksList, locationKeyToHookSourceData),
+  );
 }
 
 function decodeBase64String(encoded: string): Object {
@@ -279,13 +243,10 @@ function extractAndLoadSourceMaps(
     const sourceMapRegex = / ?sourceMappingURL=([^\s'"]+)/gm;
     const runtimeSourceCode = ((hookSourceData.runtimeSourceCode: any): string);
 
-    if (__PERFORMANCE_PROFILE__) {
-      mark('sourceMapRegex.exec(runtimeSourceCode)');
-    }
-    let sourceMappingURLMatch = sourceMapRegex.exec(runtimeSourceCode);
-    if (__PERFORMANCE_PROFILE__) {
-      measure('sourceMapRegex.exec(runtimeSourceCode)');
-    }
+    let sourceMappingURLMatch = withSyncPerformanceMark(
+      'sourceMapRegex.exec(runtimeSourceCode)',
+      () => sourceMapRegex.exec(runtimeSourceCode),
+    );
 
     if (sourceMappingURLMatch == null) {
       // Maybe file has not been transformed; we'll try to parse it as-is in parseSourceAST().
@@ -309,21 +270,13 @@ function extractAndLoadSourceMaps(
           const trimmed = ((sourceMappingURL.match(
             /base64,([a-zA-Z0-9+\/=]+)/,
           ): any): Array<string>)[1];
-          if (__PERFORMANCE_PROFILE__) {
-            mark('decodeBase64String()');
-          }
-          const decoded = decodeBase64String(trimmed);
-          if (__PERFORMANCE_PROFILE__) {
-            measure('decodeBase64String()');
-          }
+          const decoded = withSyncPerformanceMark('decodeBase64String()', () =>
+            decodeBase64String(trimmed),
+          );
 
-          if (__PERFORMANCE_PROFILE__) {
-            mark('JSON.parse(decoded)');
-          }
-          const parsed = JSON.parse(decoded);
-          if (__PERFORMANCE_PROFILE__) {
-            measure('JSON.parse(decoded)');
-          }
+          const parsed = withSyncPerformanceMark('JSON.parse(decoded)', () =>
+            JSON.parse(decoded),
+          );
 
           if (__DEBUG__) {
             console.groupCollapsed(
@@ -336,27 +289,24 @@ function extractAndLoadSourceMaps(
           // Hook source might be a URL like "https://4syus.csb.app/src/App.js"
           // Parsed source map might be a partial path like "src/App.js"
           if (sourceMapIncludesSource(parsed, runtimeSourceURL)) {
-            if (__PERFORMANCE_PROFILE__) {
-              mark('new SourceMapMetadataConsumer(parsed)');
-            }
-            hookSourceData.metadataConsumer = new SourceMapMetadataConsumer(
-              parsed,
+            hookSourceData.metadataConsumer = withSyncPerformanceMark(
+              'new SourceMapMetadataConsumer(parsed)',
+              () => new SourceMapMetadataConsumer(parsed),
+            );
+            hookSourceData.sourceConsumer = withSyncPerformanceMark(
+              'new SourceMapConsumer(parsed)',
+              () => new SourceMapConsumer(parsed),
             );
-            if (__PERFORMANCE_PROFILE__) {
-              measure('new SourceMapMetadataConsumer(parsed)');
-              mark('new SourceMapConsumer(parsed)');
-            }
-            hookSourceData.sourceConsumer = new SourceMapConsumer(parsed);
-            if (__PERFORMANCE_PROFILE__) {
-              measure('new SourceMapConsumer(parsed)');
-            }
             break;
           }
         } else {
           externalSourceMapURLs.push(sourceMappingURL);
         }
 
-        sourceMappingURLMatch = sourceMapRegex.exec(runtimeSourceCode);
+        sourceMappingURLMatch = withSyncPerformanceMark(
+          'sourceMapRegex.exec(runtimeSourceCode)',
+          () => sourceMapRegex.exec(runtimeSourceCode),
+        );
       }
 
       const foundInlineSourceMap =
@@ -395,29 +345,20 @@ function extractAndLoadSourceMaps(
             fetchPromises.get(url) ||
             fetchFile(url).then(
               sourceMapContents => {
-                if (__PERFORMANCE_PROFILE__) {
-                  mark('JSON.parse(sourceMapContents)');
-                }
-                const parsed = JSON.parse(sourceMapContents);
-                if (__PERFORMANCE_PROFILE__) {
-                  measure('JSON.parse(sourceMapContents)');
-                }
+                const parsed = withSyncPerformanceMark(
+                  'JSON.parse(sourceMapContents)',
+                  () => JSON.parse(sourceMapContents),
+                );
 
-                if (__PERFORMANCE_PROFILE__) {
-                  mark('SourceMapConsumer(parsed)');
-                }
-                const sourceConsumer = new SourceMapConsumer(parsed);
-                if (__PERFORMANCE_PROFILE__) {
-                  measure('SourceMapConsumer(parsed)');
-                }
+                const sourceConsumer = withSyncPerformanceMark(
+                  'new SourceMapConsumer(parsed)',
+                  () => new SourceMapConsumer(parsed),
+                );
 
-                if (__PERFORMANCE_PROFILE__) {
-                  mark('SourceMapMetadataConsumer(parsed)');
-                }
-                const metadataConsumer = new SourceMapMetadataConsumer(parsed);
-                if (__PERFORMANCE_PROFILE__) {
-                  measure('SourceMapMetadataConsumer(parsed)');
-                }
+                const metadataConsumer = withSyncPerformanceMark(
+                  'new SourceMapMetadataConsumer(parsed)',
+                  () => new SourceMapMetadataConsumer(parsed),
+                );
 
                 return {sourceConsumer, metadataConsumer};
               },
@@ -451,51 +392,43 @@ function extractAndLoadSourceMaps(
 }
 
 function fetchFile(url: string): Promise<string> {
-  if (__PERFORMANCE_PROFILE__) {
-    mark('fetchFile("' + url + '")');
-  }
-
-  return new Promise((resolve, reject) => {
-    fetch(url).then(
-      response => {
-        if (response.ok) {
-          response
-            .text()
-            .then(text => {
-              if (__PERFORMANCE_PROFILE__) {
-                measure('fetchFile("' + url + '")');
-              }
-              resolve(text);
-            })
-            .catch(error => {
-              if (__DEBUG__) {
-                console.log(`fetchFile() Could not read text for url "${url}"`);
-              }
-              if (__PERFORMANCE_PROFILE__) {
-                measure('fetchFile("' + url + '")');
-              }
-              reject(null);
-            });
-        } else {
-          if (__DEBUG__) {
-            console.log(`fetchFile() Got bad response for url "${url}"`);
+  return withCallbackPerformanceMark('fetchFile("' + url + '")', done => {
+    return new Promise((resolve, reject) => {
+      fetch(url).then(
+        response => {
+          if (response.ok) {
+            response
+              .text()
+              .then(text => {
+                done();
+                resolve(text);
+              })
+              .catch(error => {
+                if (__DEBUG__) {
+                  console.log(
+                    `fetchFile() Could not read text for url "${url}"`,
+                  );
+                }
+                done();
+                reject(null);
+              });
+          } else {
+            if (__DEBUG__) {
+              console.log(`fetchFile() Got bad response for url "${url}"`);
+            }
+            done();
+            reject(null);
           }
-          if (__PERFORMANCE_PROFILE__) {
-            measure('fetchFile("' + url + '")');
+        },
+        error => {
+          if (__DEBUG__) {
+            console.log(`fetchFile() Could not fetch file: ${error.message}`);
           }
+          done();
           reject(null);
-        }
-      },
-      error => {
-        if (__DEBUG__) {
-          console.log(`fetchFile() Could not fetch file: ${error.message}`);
-        }
-        if (__PERFORMANCE_PROFILE__) {
-          measure('fetchFile("' + url + '")');
-        }
-        reject(null);
-      },
-    );
+        },
+      );
+    });
   });
 }
 
@@ -535,20 +468,18 @@ function findHookNames(
       originalSourceColumnNumber = columnNumber;
       originalSourceLineNumber = lineNumber;
     } else {
-      if (__PERFORMANCE_PROFILE__) {
-        mark('sourceConsumer.originalPositionFor()');
-      }
-      const position = sourceConsumer.originalPositionFor({
-        line: lineNumber,
-
-        // Column numbers are represented differently between tools/engines.
-        // Error.prototype.stack columns are 1-based (like most IDEs) but ASTs are 0-based.
-        // For more info see https://github.com/facebook/react/issues/21792#issuecomment-873171991
-        column: columnNumber - 1,
-      });
-      if (__PERFORMANCE_PROFILE__) {
-        measure('sourceConsumer.originalPositionFor()');
-      }
+      const position = withSyncPerformanceMark(
+        'sourceConsumer.originalPositionFor()',
+        () =>
+          sourceConsumer.originalPositionFor({
+            line: lineNumber,
+
+            // Column numbers are represented differently between tools/engines.
+            // Error.prototype.stack columns are 1-based (like most IDEs) but ASTs are 0-based.
+            // For more info see https://github.com/facebook/react/issues/21792#issuecomment-873171991
+            column: columnNumber - 1,
+          }),
+      );
 
       originalSourceColumnNumber = position.column;
       originalSourceLineNumber = position.line;
@@ -571,33 +502,25 @@ function findHookNames(
     let name;
     const {metadataConsumer} = hookSourceData;
     if (metadataConsumer != null) {
-      if (__PERFORMANCE_PROFILE__) {
-        mark('metadataConsumer.hookNameFor()');
-      }
-      name = metadataConsumer.hookNameFor({
-        line: originalSourceLineNumber,
-        column: originalSourceColumnNumber,
-        source: originalSourceURL,
-      });
-      if (__PERFORMANCE_PROFILE__) {
-        measure('metadataConsumer.hookNameFor()');
-      }
+      name = withSyncPerformanceMark('metadataConsumer.hookNameFor()', () =>
+        metadataConsumer.hookNameFor({
+          line: originalSourceLineNumber,
+          column: originalSourceColumnNumber,
+          source: originalSourceURL,
+        }),
+      );
     }
 
     if (name == null) {
-      if (__PERFORMANCE_PROFILE__) {
-        mark('getHookName()');
-      }
-      name = getHookName(
-        hook,
-        hookSourceData.originalSourceAST,
-        ((hookSourceData.originalSourceCode: any): string),
-        ((originalSourceLineNumber: any): number),
-        originalSourceColumnNumber,
+      name = withSyncPerformanceMark('getHookName()', () =>
+        getHookName(
+          hook,
+          hookSourceData.originalSourceAST,
+          ((hookSourceData.originalSourceCode: any): string),
+          ((originalSourceLineNumber: any): number),
+          originalSourceColumnNumber,
+        ),
       );
-      if (__PERFORMANCE_PROFILE__) {
-        measure('getHookName()');
-      }
     }
 
     if (__DEBUG__) {
@@ -645,9 +568,9 @@ function loadSourceFiles(
   return Promise.all(setPromises);
 }
 
-async function parseSourceAST(
+function parseSourceAST(
   locationKeyToHookSourceData: Map<string, HookSourceData>,
-): Promise<*> {
+): void {
   locationKeyToHookSourceData.forEach(hookSourceData => {
     if (hookSourceData.originalSourceAST !== null) {
       // Use cached metadata.
@@ -665,22 +588,20 @@ async function parseSourceAST(
       if (lineNumber == null || columnNumber == null) {
         throw Error('Hook source code location not found.');
       }
-      if (__PERFORMANCE_PROFILE__) {
-        mark('sourceConsumer.originalPositionFor()');
-      }
       // Now that the source map has been loaded,
       // extract the original source for later.
-      const {source} = sourceConsumer.originalPositionFor({
-        line: lineNumber,
-
-        // Column numbers are represented differently between tools/engines.
-        // Error.prototype.stack columns are 1-based (like most IDEs) but ASTs are 0-based.
-        // For more info see https://github.com/facebook/react/issues/21792#issuecomment-873171991
-        column: columnNumber - 1,
-      });
-      if (__PERFORMANCE_PROFILE__) {
-        measure('sourceConsumer.originalPositionFor()');
-      }
+      const {source} = withSyncPerformanceMark(
+        'sourceConsumer.originalPositionFor()',
+        () =>
+          sourceConsumer.originalPositionFor({
+            line: lineNumber,
+
+            // Column numbers are represented differently between tools/engines.
+            // Error.prototype.stack columns are 1-based (like most IDEs) but ASTs are 0-based.
+            // For more info see https://github.com/facebook/react/issues/21792#issuecomment-873171991
+            column: columnNumber - 1,
+          }),
+      );
 
       if (source == null) {
         // TODO (named hooks) maybe fall back to the runtime source instead of throwing?
@@ -693,16 +614,10 @@ async function parseSourceAST(
       // It can be relative if the source map specifies it that way,
       // but we use it as a cache key across different source maps and there can be collisions.
       originalSourceURL = (source: string);
-      if (__PERFORMANCE_PROFILE__) {
-        mark('sourceConsumer.sourceContentFor()');
-      }
-      originalSourceCode = (sourceConsumer.sourceContentFor(
-        source,
-        true,
-      ): string);
-      if (__PERFORMANCE_PROFILE__) {
-        measure('sourceConsumer.sourceContentFor()');
-      }
+      originalSourceCode = withSyncPerformanceMark(
+        'sourceConsumer.sourceContentFor()',
+        () => (sourceConsumer.sourceContentFor(source, true): string),
+      );
 
       if (__DEBUG__) {
         console.groupCollapsed(
@@ -756,16 +671,14 @@ async function parseSourceAST(
         originalSourceCode.indexOf('@flow') > 0 ? 'flow' : 'typescript';
 
       // TODO (named hooks) Parsing should ideally be done off of the main thread.
-      if (__PERFORMANCE_PROFILE__) {
-        mark('[@babel/parser] parse(originalSourceCode)');
-      }
-      const originalSourceAST = parse(originalSourceCode, {
-        sourceType: 'unambiguous',
-        plugins: ['jsx', plugin],
-      });
-      if (__PERFORMANCE_PROFILE__) {
-        measure('[@babel/parser] parse(originalSourceCode)');
-      }
+      const originalSourceAST = withSyncPerformanceMark(
+        '[@babel/parser] parse(originalSourceCode)',
+        () =>
+          parse(originalSourceCode, {
+            sourceType: 'unambiguous',
+            plugins: ['jsx', plugin],
+          }),
+      );
       hookSourceData.originalSourceAST = originalSourceAST;
       if (__DEBUG__) {
         console.log(
@@ -778,7 +691,6 @@ async function parseSourceAST(
       });
     }
   });
-  return Promise.resolve();
 }
 
 function flattenHooksList(
@@ -812,7 +724,7 @@ function isUnnamedBuiltInHook(hook: HooksNode) {
 
 function updateLruCache(
   locationKeyToHookSourceData: Map<string, HookSourceData>,
-): Promise<*> {
+): void {
   locationKeyToHookSourceData.forEach(
     ({metadataConsumer, sourceConsumer, runtimeSourceURL}) => {
       // Only set once to avoid triggering eviction/cleanup code.
@@ -830,7 +742,6 @@ function updateLruCache(
       }
     },
   );
-  return Promise.resolve();
 }
 
 export function purgeCachedMetadata(): void {

filename: packages/react-devtools-shared/src/PerformanceMarks.js
patch: @@ -0,0 +1,59 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow strict-local
+ */
+
+import {__PERFORMANCE_PROFILE__} from './constants';
+
+function mark(markName: string): void {
+  performance.mark(markName + '-start');
+}
+
+function measure(markName: string): void {
+  performance.mark(markName + '-end');
+  performance.measure(markName, markName + '-start', markName + '-end');
+}
+
+export async function withAsyncPerformanceMark<TReturn>(
+  markName: string,
+  callback: () => Promise<TReturn>,
+): Promise<TReturn> {
+  if (__PERFORMANCE_PROFILE__) {
+    mark(markName);
+    const result = await callback();
+    measure(markName);
+    return result;
+  }
+  return callback();
+}
+
+export function withSyncPerformanceMark<TReturn>(
+  markName: string,
+  callback: () => TReturn,
+): TReturn {
+  if (__PERFORMANCE_PROFILE__) {
+    mark(markName);
+    const result = callback();
+    measure(markName);
+    return result;
+  }
+  return callback();
+}
+
+export function withCallbackPerformanceMark<TReturn>(
+  markName: string,
+  callback: (done: () => void) => TReturn,
+): TReturn {
+  if (__PERFORMANCE_PROFILE__) {
+    mark(markName);
+    const done = () => {
+      measure(markName);
+    };
+    return callback(done);
+  }
+  return callback(() => {});
+}