Systrace.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. /**
  2. * Copyright (c) Facebook, Inc. and its 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. * @flow
  8. * @format
  9. */
  10. 'use strict';
  11. const invariant = require('invariant');
  12. const TRACE_TAG_REACT_APPS = 1 << 17; // eslint-disable-line no-bitwise
  13. const TRACE_TAG_JS_VM_CALLS = 1 << 27; // eslint-disable-line no-bitwise
  14. let _enabled = false;
  15. let _asyncCookie = 0;
  16. const _markStack = [];
  17. let _markStackIndex = -1;
  18. let _canInstallReactHook = false;
  19. // Implements a subset of User Timing API necessary for React measurements.
  20. // https://developer.mozilla.org/en-US/docs/Web/API/User_Timing_API
  21. const REACT_MARKER = '\u269B';
  22. const userTimingPolyfill = __DEV__
  23. ? {
  24. mark(markName: string) {
  25. if (_enabled) {
  26. _markStackIndex++;
  27. _markStack[_markStackIndex] = markName;
  28. let systraceLabel = markName;
  29. // Since perf measurements are a shared namespace in User Timing API,
  30. // we prefix all React results with a React emoji.
  31. if (markName[0] === REACT_MARKER) {
  32. // This is coming from React.
  33. // Removing component IDs keeps trace colors stable.
  34. const indexOfId = markName.lastIndexOf(' (#');
  35. const cutoffIndex = indexOfId !== -1 ? indexOfId : markName.length;
  36. // Also cut off the emoji because it breaks Systrace
  37. systraceLabel = markName.slice(2, cutoffIndex);
  38. }
  39. Systrace.beginEvent(systraceLabel);
  40. }
  41. },
  42. measure(measureName: string, startMark: ?string, endMark: ?string) {
  43. if (_enabled) {
  44. invariant(
  45. typeof measureName === 'string' &&
  46. typeof startMark === 'string' &&
  47. typeof endMark === 'undefined',
  48. 'Only performance.measure(string, string) overload is supported.',
  49. );
  50. const topMark = _markStack[_markStackIndex];
  51. invariant(
  52. startMark === topMark,
  53. 'There was a mismatching performance.measure() call. ' +
  54. 'Expected "%s" but got "%s."',
  55. topMark,
  56. startMark,
  57. );
  58. _markStackIndex--;
  59. // We can't use more descriptive measureName because Systrace doesn't
  60. // let us edit labels post factum.
  61. Systrace.endEvent();
  62. }
  63. },
  64. clearMarks(markName: string) {
  65. if (_enabled) {
  66. if (_markStackIndex === -1) {
  67. return;
  68. }
  69. if (markName === _markStack[_markStackIndex]) {
  70. // React uses this for "cancelling" started measurements.
  71. // Systrace doesn't support deleting measurements, so we just stop them.
  72. if (userTimingPolyfill != null) {
  73. userTimingPolyfill.measure(markName, markName);
  74. }
  75. }
  76. }
  77. },
  78. clearMeasures() {
  79. // React calls this to avoid memory leaks in browsers, but we don't keep
  80. // measurements anyway.
  81. },
  82. }
  83. : null;
  84. function installPerformanceHooks(polyfill) {
  85. if (polyfill) {
  86. if (global.performance === undefined) {
  87. global.performance = {};
  88. }
  89. Object.keys(polyfill).forEach(methodName => {
  90. if (typeof global.performance[methodName] !== 'function') {
  91. global.performance[methodName] = polyfill[methodName];
  92. }
  93. });
  94. }
  95. }
  96. const Systrace = {
  97. installReactHook() {
  98. if (_enabled) {
  99. if (__DEV__) {
  100. installPerformanceHooks(userTimingPolyfill);
  101. }
  102. }
  103. _canInstallReactHook = true;
  104. },
  105. setEnabled(enabled: boolean) {
  106. if (_enabled !== enabled) {
  107. if (__DEV__) {
  108. if (enabled) {
  109. global.nativeTraceBeginLegacy &&
  110. global.nativeTraceBeginLegacy(TRACE_TAG_JS_VM_CALLS);
  111. } else {
  112. global.nativeTraceEndLegacy &&
  113. global.nativeTraceEndLegacy(TRACE_TAG_JS_VM_CALLS);
  114. }
  115. if (_canInstallReactHook) {
  116. if (enabled) {
  117. installPerformanceHooks(userTimingPolyfill);
  118. }
  119. }
  120. }
  121. _enabled = enabled;
  122. }
  123. },
  124. isEnabled(): boolean {
  125. return _enabled;
  126. },
  127. /**
  128. * beginEvent/endEvent for starting and then ending a profile within the same call stack frame
  129. **/
  130. beginEvent(profileName?: any, args?: any) {
  131. if (_enabled) {
  132. profileName =
  133. typeof profileName === 'function' ? profileName() : profileName;
  134. global.nativeTraceBeginSection(TRACE_TAG_REACT_APPS, profileName, args);
  135. }
  136. },
  137. endEvent() {
  138. if (_enabled) {
  139. global.nativeTraceEndSection(TRACE_TAG_REACT_APPS);
  140. }
  141. },
  142. /**
  143. * beginAsyncEvent/endAsyncEvent for starting and then ending a profile where the end can either
  144. * occur on another thread or out of the current stack frame, eg await
  145. * the returned cookie variable should be used as input into the endAsyncEvent call to end the profile
  146. **/
  147. beginAsyncEvent(profileName?: any): any {
  148. const cookie = _asyncCookie;
  149. if (_enabled) {
  150. _asyncCookie++;
  151. profileName =
  152. typeof profileName === 'function' ? profileName() : profileName;
  153. global.nativeTraceBeginAsyncSection(
  154. TRACE_TAG_REACT_APPS,
  155. profileName,
  156. cookie,
  157. );
  158. }
  159. return cookie;
  160. },
  161. endAsyncEvent(profileName?: any, cookie?: any) {
  162. if (_enabled) {
  163. profileName =
  164. typeof profileName === 'function' ? profileName() : profileName;
  165. global.nativeTraceEndAsyncSection(
  166. TRACE_TAG_REACT_APPS,
  167. profileName,
  168. cookie,
  169. );
  170. }
  171. },
  172. /**
  173. * counterEvent registers the value to the profileName on the systrace timeline
  174. **/
  175. counterEvent(profileName?: any, value?: any) {
  176. if (_enabled) {
  177. profileName =
  178. typeof profileName === 'function' ? profileName() : profileName;
  179. global.nativeTraceCounter &&
  180. global.nativeTraceCounter(TRACE_TAG_REACT_APPS, profileName, value);
  181. }
  182. },
  183. };
  184. if (__DEV__) {
  185. // This is needed, because require callis in polyfills are not processed as
  186. // other files. Therefore, calls to `require('moduleId')` are not replaced
  187. // with numeric IDs
  188. // TODO(davidaurelio) Scan polyfills for dependencies, too (t9759686)
  189. (require: any).Systrace = Systrace;
  190. }
  191. module.exports = Systrace;