EventTracer.cpp 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. /* This Source Code Form is subject to the terms of the Mozilla Public
  2. * License, v. 2.0. If a copy of the MPL was not distributed with this
  3. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  4. /*
  5. * Event loop instrumentation. This code attempts to measure the
  6. * latency of the UI-thread event loop by firing native events at it from
  7. * a background thread, and measuring how long it takes for them
  8. * to be serviced. The measurement interval (kMeasureInterval, below)
  9. * is also used as the upper bound of acceptable response time.
  10. * When an event takes longer than that interval to be serviced,
  11. * a sample will be written to the log.
  12. *
  13. * Usage:
  14. *
  15. * Set MOZ_INSTRUMENT_EVENT_LOOP=1 in the environment to enable
  16. * this instrumentation. Currently only the UI process is instrumented.
  17. *
  18. * Set MOZ_INSTRUMENT_EVENT_LOOP_OUTPUT in the environment to a
  19. * file path to contain the log output, the default is to log to stdout.
  20. *
  21. * Set MOZ_INSTRUMENT_EVENT_LOOP_THRESHOLD in the environment to an
  22. * integer number of milliseconds to change the threshold for reporting.
  23. * The default is 20 milliseconds. Unresponsive periods shorter than this
  24. * threshold will not be reported.
  25. *
  26. * Set MOZ_INSTRUMENT_EVENT_LOOP_INTERVAL in the environment to an
  27. * integer number of milliseconds to change the maximum sampling frequency.
  28. * This variable controls how often events will be sent to the main
  29. * thread's event loop to sample responsiveness. The sampler will not
  30. * send events twice within LOOP_INTERVAL milliseconds.
  31. * The default is 10 milliseconds.
  32. *
  33. * All logged output lines start with MOZ_EVENT_TRACE. All timestamps
  34. * output are milliseconds since the epoch (PRTime / 1000).
  35. *
  36. * On startup, a line of the form:
  37. * MOZ_EVENT_TRACE start <timestamp>
  38. * will be output.
  39. *
  40. * On shutdown, a line of the form:
  41. * MOZ_EVENT_TRACE stop <timestamp>
  42. * will be output.
  43. *
  44. * When an event servicing time exceeds the threshold, a line of the form:
  45. * MOZ_EVENT_TRACE sample <timestamp> <duration>
  46. * will be output, where <duration> is the number of milliseconds that
  47. * it took for the event to be serviced. Duration may contain a fractional
  48. * component.
  49. */
  50. #include "GeckoProfiler.h"
  51. #include "EventTracer.h"
  52. #include <stdio.h>
  53. #include "mozilla/Preferences.h"
  54. #include "mozilla/TimeStamp.h"
  55. #include "mozilla/WidgetTraceEvent.h"
  56. #include "nsDebug.h"
  57. #include <limits.h>
  58. #include <prenv.h>
  59. #include <prinrval.h>
  60. #include <prthread.h>
  61. #include <prtime.h>
  62. using mozilla::TimeDuration;
  63. using mozilla::TimeStamp;
  64. using mozilla::FireAndWaitForTracerEvent;
  65. namespace {
  66. PRThread* sTracerThread = nullptr;
  67. bool sExit = false;
  68. struct TracerStartClosure {
  69. bool mLogTracing;
  70. int32_t mThresholdInterval;
  71. };
  72. /*
  73. * The tracer thread fires events at the native event loop roughly
  74. * every kMeasureInterval. It will sleep to attempt not to send them
  75. * more quickly, but if the response time is longer than kMeasureInterval
  76. * it will not send another event until the previous response is received.
  77. *
  78. * The output defaults to stdout, but can be redirected to a file by
  79. * settting the environment variable MOZ_INSTRUMENT_EVENT_LOOP_OUTPUT
  80. * to the name of a file to use.
  81. */
  82. void TracerThread(void *arg)
  83. {
  84. PR_SetCurrentThreadName("Event Tracer");
  85. TracerStartClosure* threadArgs = static_cast<TracerStartClosure*>(arg);
  86. // These are the defaults. They can be overridden by environment vars.
  87. // This should be set to the maximum latency we'd like to allow
  88. // for responsiveness.
  89. int32_t thresholdInterval = threadArgs->mThresholdInterval;
  90. PRIntervalTime threshold = PR_MillisecondsToInterval(thresholdInterval);
  91. // This is the sampling interval.
  92. PRIntervalTime interval = PR_MillisecondsToInterval(thresholdInterval / 2);
  93. sExit = false;
  94. FILE* log = nullptr;
  95. char* envfile = PR_GetEnv("MOZ_INSTRUMENT_EVENT_LOOP_OUTPUT");
  96. if (envfile) {
  97. log = fopen(envfile, "w");
  98. }
  99. if (log == nullptr)
  100. log = stdout;
  101. char* thresholdenv = PR_GetEnv("MOZ_INSTRUMENT_EVENT_LOOP_THRESHOLD");
  102. if (thresholdenv && *thresholdenv) {
  103. int val = atoi(thresholdenv);
  104. if (val != 0 && val != INT_MAX && val != INT_MIN) {
  105. threshold = PR_MillisecondsToInterval(val);
  106. }
  107. }
  108. char* intervalenv = PR_GetEnv("MOZ_INSTRUMENT_EVENT_LOOP_INTERVAL");
  109. if (intervalenv && *intervalenv) {
  110. int val = atoi(intervalenv);
  111. if (val != 0 && val != INT_MAX && val != INT_MIN) {
  112. interval = PR_MillisecondsToInterval(val);
  113. }
  114. }
  115. if (threadArgs->mLogTracing) {
  116. long long now = PR_Now() / PR_USEC_PER_MSEC;
  117. fprintf(log, "MOZ_EVENT_TRACE start %llu\n", now);
  118. }
  119. while (!sExit) {
  120. TimeStamp start(TimeStamp::Now());
  121. profiler_responsiveness(start);
  122. PRIntervalTime next_sleep = interval;
  123. //TODO: only wait up to a maximum of interval; return
  124. // early if that threshold is exceeded and dump a stack trace
  125. // or do something else useful.
  126. if (FireAndWaitForTracerEvent()) {
  127. TimeDuration duration = TimeStamp::Now() - start;
  128. // Only report samples that exceed our measurement threshold.
  129. long long now = PR_Now() / PR_USEC_PER_MSEC;
  130. if (threadArgs->mLogTracing && duration.ToMilliseconds() > threshold) {
  131. fprintf(log, "MOZ_EVENT_TRACE sample %llu %lf\n",
  132. now,
  133. duration.ToMilliseconds());
  134. }
  135. if (next_sleep > duration.ToMilliseconds()) {
  136. next_sleep -= int(duration.ToMilliseconds());
  137. }
  138. else {
  139. // Don't sleep at all if this event took longer than the measure
  140. // interval to deliver.
  141. next_sleep = 0;
  142. }
  143. }
  144. if (next_sleep != 0 && !sExit) {
  145. PR_Sleep(next_sleep);
  146. }
  147. }
  148. if (threadArgs->mLogTracing) {
  149. long long now = PR_Now() / PR_USEC_PER_MSEC;
  150. fprintf(log, "MOZ_EVENT_TRACE stop %llu\n", now);
  151. }
  152. if (log != stdout)
  153. fclose(log);
  154. delete threadArgs;
  155. }
  156. } // namespace
  157. namespace mozilla {
  158. bool InitEventTracing(bool aLog)
  159. {
  160. if (sTracerThread)
  161. return true;
  162. // Initialize the widget backend.
  163. if (!InitWidgetTracing())
  164. return false;
  165. // The tracer thread owns the object and will delete it.
  166. TracerStartClosure* args = new TracerStartClosure();
  167. args->mLogTracing = aLog;
  168. // Pass the default threshold interval.
  169. int32_t thresholdInterval = 20;
  170. Preferences::GetInt("devtools.eventlooplag.threshold", &thresholdInterval);
  171. args->mThresholdInterval = thresholdInterval;
  172. // Create a thread that will fire events back at the
  173. // main thread to measure responsiveness.
  174. MOZ_ASSERT(!sTracerThread, "Event tracing already initialized!");
  175. sTracerThread = PR_CreateThread(PR_USER_THREAD,
  176. TracerThread,
  177. args,
  178. PR_PRIORITY_NORMAL,
  179. PR_GLOBAL_THREAD,
  180. PR_JOINABLE_THREAD,
  181. 0);
  182. return sTracerThread != nullptr;
  183. }
  184. void ShutdownEventTracing()
  185. {
  186. if (!sTracerThread)
  187. return;
  188. sExit = true;
  189. // Ensure that the tracer thread doesn't hang.
  190. SignalTracerThread();
  191. if (sTracerThread)
  192. PR_JoinThread(sTracerThread);
  193. sTracerThread = nullptr;
  194. // Allow the widget backend to clean up.
  195. CleanUpWidgetTracing();
  196. }
  197. } // namespace mozilla