Bug 1382440 - Fix CPUUsageWatcher on OSX and Linux r=froydnj
authorDoug Thayer <dothayer@mozilla.com>
Mon, 28 Aug 2017 14:00:22 -0700
changeset 428736 252f4499d3725ced8761b96c44cc6edd5e3b74ef
parent 428735 b84af3674b4968066d8a41e3670ea0f57bce1381
child 428737 2b2606848e3b999736c7550db2ec68cc8af25be3
push id7761
push userjlund@mozilla.com
push dateFri, 15 Sep 2017 00:19:52 +0000
treeherdermozilla-beta@c38455951db4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfroydnj
bugs1382440
milestone57.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1382440 - Fix CPUUsageWatcher on OSX and Linux r=froydnj Properly enclose all relevant details of CPUUsageWatcher in ifdefs which control whether it should be active or not. Additionally, apparently clock_gettime is not defined on OSX prior to 10.12, so this is failing to compile for OSX on the build server, but not locally. However, clock_get_time and getrusage should cover our use cases sufficiently. MozReview-Commit-ID: Ffi6yXLb9gO
xpcom/threads/CPUUsageWatcher.cpp
xpcom/threads/CPUUsageWatcher.h
--- a/xpcom/threads/CPUUsageWatcher.cpp
+++ b/xpcom/threads/CPUUsageWatcher.cpp
@@ -4,27 +4,24 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/CPUUsageWatcher.h"
 
 #include "prsystem.h"
 
 #ifdef XP_MACOSX
+#include <sys/resource.h>
+#include <mach/clock.h>
 #include <mach/mach_host.h>
 #endif
 
-// We only support OSX and Windows, because on Linux we're forced to read
-// from /proc/stat in order to get global CPU values. We would prefer to not
-// eat that cost for this.
-#if defined(NIGHTLY_BUILD) && (defined(XP_WIN) || defined(XP_MACOSX))
-#define CPU_USAGE_WATCHER_ACTIVE
-#endif
+namespace mozilla {
 
-namespace mozilla {
+#ifdef CPU_USAGE_WATCHER_ACTIVE
 
 // Even if the machine only has one processor, tolerate up to 50%
 // external CPU usage.
 static const float kTolerableExternalCPUUsageFloor = 0.5f;
 
 struct CPUStats {
   // The average CPU usage time, which can be summed across all cores in the
   // system, or averaged between them. Whichever it is, it needs to be in the
@@ -32,36 +29,54 @@ struct CPUStats {
   uint64_t usageTime;
   // A monotonically increasing value in the same units as usageTime, which can
   // be used to determine the percentage of active vs idle time
   uint64_t updateTime;
 };
 
 #ifdef XP_MACOSX
 
-static const uint64_t kNanosecondsPerSecond = 1000000000LL;
-static const uint64_t kCPUCheckInterval = kNanosecondsPerSecond / 2LL;
+static const uint64_t kMicrosecondsPerSecond = 1000000LL;
+static const uint64_t kNanosecondsPerMicrosecond = 1000LL;
+static const uint64_t kCPUCheckInterval = kMicrosecondsPerSecond / 2LL;
 
-Result<uint64_t, CPUUsageWatcherError>
-GetClockTime(clockid_t clockId) {
-  timespec clockResult;
-  bool success = !clock_gettime(clockId, &clockResult);
-  if (!success) {
-    return Err(ClockGetTimeError);
-  }
-  return ((uint64_t)clockResult.tv_sec) * kNanosecondsPerSecond +
-         (uint64_t)clockResult.tv_nsec;
+uint64_t GetMicroseconds(timeval time) {
+    return ((uint64_t)time.tv_sec) * kMicrosecondsPerSecond +
+           (uint64_t)time.tv_usec;
+}
+
+uint64_t GetMicroseconds(mach_timespec_t time) {
+    return ((uint64_t)time.tv_sec) * kMicrosecondsPerSecond +
+           ((uint64_t)time.tv_nsec) / kNanosecondsPerMicrosecond;
 }
 
 Result<CPUStats, CPUUsageWatcherError>
 GetProcessCPUStats(int32_t numCPUs) {
   CPUStats result = {};
-  MOZ_TRY_VAR(result.usageTime, GetClockTime(CLOCK_PROCESS_CPUTIME_ID));
-  MOZ_TRY_VAR(result.updateTime, GetClockTime(CLOCK_MONOTONIC));
-  // CLOCK_PROCESS_CPUTIME_ID will give us the sum of the values across all
+  rusage usage;
+  int32_t rusageResult = getrusage(RUSAGE_SELF, &usage);
+  if (rusageResult == -1) {
+    return Err(GetProcessTimesError);
+  }
+  result.usageTime = GetMicroseconds(usage.ru_utime) + GetMicroseconds(usage.ru_stime);
+
+  clock_serv_t realtimeClock;
+  kern_return_t errorResult =
+      host_get_clock_service(mach_host_self(), REALTIME_CLOCK, &realtimeClock);
+  if (errorResult != KERN_SUCCESS) {
+    return Err(GetProcessTimesError);
+  }
+  mach_timespec_t time;
+  errorResult = clock_get_time(realtimeClock, &time);
+  if (errorResult != KERN_SUCCESS) {
+    return Err(GetProcessTimesError);
+  }
+  result.updateTime = GetMicroseconds(time);
+
+  // getrusage will give us the sum of the values across all
   // of our cores. Divide by the number of CPUs to get an average.
   result.usageTime /= numCPUs;
   return result;
 }
 
 Result<CPUStats, CPUUsageWatcherError>
 GetGlobalCPUStats() {
   CPUStats result = {};
@@ -152,18 +167,16 @@ CPUUsageWatcher::Init()
   mNumCPUs = PR_GetNumberOfProcessors();
   if (mNumCPUs <= 0) {
     mExternalUsageThreshold = 1.0f;
     return Err(GetNumberOfProcessorsError);
   }
   mExternalUsageThreshold = std::max(1.0f - 1.0f / (float)mNumCPUs,
                                      kTolerableExternalCPUUsageFloor);
 
-#ifdef CPU_USAGE_WATCHER_ACTIVE
-
   CPUStats processTimes;
   MOZ_TRY_VAR(processTimes, GetProcessCPUStats(mNumCPUs));
   mProcessUpdateTime = processTimes.updateTime;
   mProcessUsageTime = processTimes.usageTime;
 
   CPUStats globalTimes;
   MOZ_TRY_VAR(globalTimes, GetGlobalCPUStats());
   mGlobalUpdateTime = globalTimes.updateTime;
@@ -171,38 +184,35 @@ CPUUsageWatcher::Init()
 
   mInitialized = true;
 
   CPUUsageWatcher* self = this;
   NS_DispatchToMainThread(
     NS_NewRunnableFunction("CPUUsageWatcher::Init",
                            [=]() { HangMonitor::RegisterAnnotator(*self); }));
 
-#endif // CPU_USAGE_WATCHER_ACTIVE
   return Ok();
 }
 
 void
 CPUUsageWatcher::Uninit()
 {
+  if (mInitialized) {
+    HangMonitor::UnregisterAnnotator(*this);
+  }
   mInitialized = false;
-
-#ifdef CPU_USAGE_WATCHER_ACTIVE
-  HangMonitor::UnregisterAnnotator(*this);
-#endif // CPU_USAGE_WATCHER_ACTIVE
 }
 
 Result<Ok, CPUUsageWatcherError>
 CPUUsageWatcher::CollectCPUUsage()
 {
   if (!mInitialized) {
     return Ok();
   }
 
-#ifdef CPU_USAGE_WATCHER_ACTIVE
   mExternalUsageRatio = 0.0f;
 
   CPUStats processTimes;
   MOZ_TRY_VAR(processTimes, GetProcessCPUStats(mNumCPUs));
   CPUStats globalTimes;
   MOZ_TRY_VAR(globalTimes, GetGlobalCPUStats());
 
   uint64_t processUsageDelta = processTimes.usageTime - mProcessUsageTime;
@@ -219,25 +229,44 @@ CPUUsageWatcher::CollectCPUUsage()
 
   mProcessUsageTime = processTimes.usageTime;
   mProcessUpdateTime = processTimes.updateTime;
   mGlobalUsageTime = globalTimes.usageTime;
   mGlobalUpdateTime = globalTimes.updateTime;
 
   mExternalUsageRatio = std::max(0.0f,
                                  globalUsageNormalized - processUsageNormalized);
-#endif // CPU_USAGE_WATCHER_ACTIVE
 
   return Ok();
 }
 
 void
 CPUUsageWatcher::AnnotateHang(HangMonitor::HangAnnotations& aAnnotations) {
   if (!mInitialized) {
     return;
   }
 
   if (mExternalUsageRatio > mExternalUsageThreshold) {
     aAnnotations.AddAnnotation(NS_LITERAL_STRING("ExternalCPUHigh"), true);
   }
 }
 
+#else // !CPU_USAGE_WATCHER_ACTIVE
+
+Result<Ok, CPUUsageWatcherError>
+CPUUsageWatcher::Init()
+{
+  return Ok();
+}
+
+void CPUUsageWatcher::Uninit() {}
+
+Result<Ok, CPUUsageWatcherError>
+CPUUsageWatcher::CollectCPUUsage()
+{
+  return Ok();
+}
+
+void CPUUsageWatcher::AnnotateHang(HangMonitor::HangAnnotations& aAnnotations) {}
+
+#endif // CPU_USAGE_WATCHER_ACTIVE
+
 } // namespace mozilla
--- a/xpcom/threads/CPUUsageWatcher.h
+++ b/xpcom/threads/CPUUsageWatcher.h
@@ -6,16 +6,23 @@
 
 #ifndef mozilla_CPUUsageWatcher_h
 #define mozilla_CPUUsageWatcher_h
 
 #include <stdint.h>
 
 #include "mozilla/HangAnnotations.h"
 
+// We only support OSX and Windows, because on Linux we're forced to read
+// from /proc/stat in order to get global CPU values. We would prefer to not
+// eat that cost for this.
+#if defined(NIGHTLY_BUILD) && (defined(XP_WIN) || defined(XP_MACOSX))
+#define CPU_USAGE_WATCHER_ACTIVE
+#endif
+
 namespace mozilla {
 
 enum CPUUsageWatcherError : uint8_t
 {
   ClockGetTimeError,
   GetNumberOfProcessorsError,
   GetProcessTimesError,
   GetSystemTimesError,
@@ -28,37 +35,40 @@ class CPUUsageHangAnnotator
 {
 public:
 };
 
 class CPUUsageWatcher
   : public HangMonitor::Annotator
 {
 public:
+#ifdef CPU_USAGE_WATCHER_ACTIVE
   CPUUsageWatcher()
     : mInitialized(false)
     , mExternalUsageThreshold(0)
     , mExternalUsageRatio(0)
     , mProcessUsageTime(0)
     , mProcessUpdateTime(0)
     , mGlobalUsageTime(0)
     , mGlobalUpdateTime(0)
   {}
+#endif
 
   Result<Ok, CPUUsageWatcherError> Init();
 
   void Uninit();
 
   // Updates necessary values to allow AnnotateHang to function. This must be
   // called on some semi-regular basis, as it will calculate the mean CPU
   // usage values between now and the last time it was called.
   Result<Ok, CPUUsageWatcherError> CollectCPUUsage();
 
   void AnnotateHang(HangMonitor::HangAnnotations& aAnnotations) final;
 private:
+#ifdef CPU_USAGE_WATCHER_ACTIVE
   bool mInitialized;
   // The threshold above which we will mark a hang as occurring under high
   // external CPU usage conditions
   float mExternalUsageThreshold;
   // The CPU usage (0-1) external to our process, averaged between the two
   // most recent monitor thread runs
   float mExternalUsageRatio;
   // The total cumulative CPU usage time by our process as of the last
@@ -70,13 +80,14 @@ private:
   // The total cumulative CPU usage time by all processes as of the last
   // CollectCPUUsage or Startup
   uint64_t mGlobalUsageTime;
   // A time value in the same units as mGlobalUsageTime used to
   // determine the ratio of CPU usage time to idle time
   uint64_t mGlobalUpdateTime;
   // The number of virtual cores on our machine
   uint64_t mNumCPUs;
+#endif
 };
 
 } // namespace mozilla
 
 #endif // mozilla_CPUUsageWatcher_h