Add support for cpu speed events on Mac.

PiperOrigin-RevId: 421140063
diff --git a/src/main/java/com/google/devtools/build/lib/platform/BUILD b/src/main/java/com/google/devtools/build/lib/platform/BUILD
index 9f5620b..c8aa482 100644
--- a/src/main/java/com/google/devtools/build/lib/platform/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/platform/BUILD
@@ -106,3 +106,19 @@
         "//third_party:jsr305",
     ],
 )
+
+java_library(
+    name = "system_cpu_speed_module",
+    srcs = [
+        "SystemCPUSpeedEvent.java",
+        "SystemCPUSpeedModule.java",
+    ],
+    deps = [
+        "//src/main/java/com/google/devtools/build/lib:runtime",
+        "//src/main/java/com/google/devtools/build/lib/events",
+        "//src/main/java/com/google/devtools/build/lib/jni",
+        "//third_party:flogger",
+        "//third_party:guava",
+        "//third_party:jsr305",
+    ],
+)
diff --git a/src/main/java/com/google/devtools/build/lib/platform/SystemCPUSpeedEvent.java b/src/main/java/com/google/devtools/build/lib/platform/SystemCPUSpeedEvent.java
new file mode 100644
index 0000000..f0268db
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/platform/SystemCPUSpeedEvent.java
@@ -0,0 +1,46 @@
+// Copyright 2022 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.build.lib.platform;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.devtools.build.lib.events.ExtendedEventHandler;
+
+/**
+ * This event is fired from {@link
+ * com.google.devtools.build.lib.platform.SystemCPUSpeedModule#cpuSpeedCallback} to indicate that a
+ * cpu speed event has occurred.
+ */
+public class SystemCPUSpeedEvent implements ExtendedEventHandler.Postable {
+  private final int speed;
+
+  /**
+   * SystemCPUSpeedEvent Constructor.
+   *
+   * @param speed The actual speed (1-100).
+   */
+  public SystemCPUSpeedEvent(int speed) {
+    checkArgument(speed > 0 && speed <= 100);
+    this.speed = speed;
+  }
+
+  public int speed() {
+    return speed;
+  }
+
+  public String logString() {
+    return String.format("SystemCPUSpeedEvent: %d", speed);
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/platform/SystemCPUSpeedModule.java b/src/main/java/com/google/devtools/build/lib/platform/SystemCPUSpeedModule.java
new file mode 100644
index 0000000..524ff13
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/platform/SystemCPUSpeedModule.java
@@ -0,0 +1,73 @@
+// Copyright 2022 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.build.lib.platform;
+
+import com.google.common.flogger.GoogleLogger;
+import com.google.devtools.build.lib.events.Reporter;
+import com.google.devtools.build.lib.jni.JniLoader;
+import com.google.devtools.build.lib.runtime.BlazeModule;
+import com.google.devtools.build.lib.runtime.CommandEnvironment;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.GuardedBy;
+
+/** Detects cpu speed events. */
+public final class SystemCPUSpeedModule extends BlazeModule {
+  private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();
+
+  static {
+    JniLoader.loadJni();
+  }
+
+  @GuardedBy("this")
+  @Nullable
+  private Reporter reporter;
+
+  private native void registerJNI();
+
+  private static native int cpuSpeed();
+
+  public SystemCPUSpeedModule() {
+    if (JniLoader.isJniAvailable()) {
+      registerJNI();
+    }
+  }
+
+  @Override
+  public synchronized void beforeCommand(CommandEnvironment env) {
+    this.reporter = env.getReporter();
+    int startingSpeed = cpuSpeed();
+    if (startingSpeed < 100) {
+      cpuSpeedCallback(startingSpeed);
+    }
+  }
+
+  @Override
+  public synchronized void afterCommand() {
+    this.reporter = null;
+  }
+
+  private synchronized void cpuSpeedCallback(int speed) {
+    if (speed == -1) {
+      // Speeds of -1 imply an error occurred in our speed gathering code.
+      // It is expected that lower level code has logged the error, so we are just going to ignore.
+      return;
+    }
+    SystemCPUSpeedEvent event = new SystemCPUSpeedEvent(speed);
+    if (reporter != null) {
+      reporter.post(event);
+    }
+    logger.atInfo().log(event.logString());
+  }
+}
diff --git a/src/main/native/BUILD b/src/main/native/BUILD
index 1e02c9c..7b5533a 100644
--- a/src/main/native/BUILD
+++ b/src/main/native/BUILD
@@ -27,6 +27,7 @@
             "darwin/fsevents.cc",
             "darwin/file_jni.cc",
             "darwin/sleep_prevention_jni.cc",
+            "darwin/system_cpu_speed_monitor_jni.cc",
             "darwin/system_disk_space_monitor_jni.cc",
             "darwin/system_load_advisory_monitor_jni.cc",
             "darwin/system_memory_pressure_jni.cc",
diff --git a/src/main/native/darwin/system_cpu_speed_monitor_jni.cc b/src/main/native/darwin/system_cpu_speed_monitor_jni.cc
new file mode 100644
index 0000000..2e9dd19
--- /dev/null
+++ b/src/main/native/darwin/system_cpu_speed_monitor_jni.cc
@@ -0,0 +1,84 @@
+// Copyright 2022 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <IOKit/pwr_mgt/IOPM.h>
+#include <IOKit/pwr_mgt/IOPMLib.h>
+#include <mach/mach_error.h>
+#include <notify.h>
+
+#include "src/main/cpp/util/logging.h"
+#include "src/main/native/darwin/util.h"
+#include "src/main/native/unix_jni.h"
+
+namespace blaze_jni {
+
+void portable_start_cpu_speed_monitoring() {
+  // We install a test notification as well that can be used for testing.
+  static dispatch_once_t once_token;
+  dispatch_once(&once_token, ^{
+    dispatch_queue_t queue = bazel::darwin::JniDispatchQueue();
+    int token;
+    int status = notify_register_dispatch(kIOPMCPUPowerNotificationKey, &token,
+                                          queue, ^(int state) {
+                                            int value = portable_cpu_speed();
+                                            cpu_speed_callback(value);
+                                          });
+    BAZEL_CHECK_EQ(status, NOTIFY_STATUS_OK);
+
+    // This is registered solely so we can test the system from end-to-end.
+    // Using the Apple notification requires admin access.
+    status = notify_register_dispatch(
+        "com.google.bazel.test.cpuspeed", &token, queue, ^(int t) {
+          uint64_t state;
+          uint32_t status = notify_get_state(t, &state);
+          if (status != NOTIFY_STATUS_OK) {
+            BAZEL_LOG(FATAL) << "notify_get_state failed: " << status;
+          }
+          cpu_speed_callback(state);
+        });
+    BAZEL_CHECK_EQ(status, NOTIFY_STATUS_OK);
+  });
+}
+
+int portable_cpu_speed() {
+  CFDictionaryRef cfCpuPowerStatus = NULL;
+  IOReturn ret = IOPMCopyCPUPowerStatus(&cfCpuPowerStatus);
+  if (ret == kIOReturnNotFound) {
+    // This is a common occurrence when starting up so don't bother logging.
+    return -1;
+  } else if (ret != kIOReturnSuccess) {
+    BAZEL_LOG(ERROR) << "IOPMCopyCPUPowerStatus failed: "
+                     << mach_error_string(ret) << "(" << ret << ")";
+    return -1;
+  }
+  CFNumberRef cfSpeed = static_cast<CFNumberRef>(CFDictionaryGetValue(
+      cfCpuPowerStatus, CFSTR(kIOPMCPUPowerLimitProcessorSpeedKey)));
+
+  if (!cfSpeed) {
+    BAZEL_LOG(ERROR)
+        << "IOPMCopyCPUPowerStatus missing kIOPMCPUPowerLimitProcessorSpeedKey";
+    CFRelease(cfCpuPowerStatus);
+    return -1;
+  }
+  int speed;
+  CFNumberGetValue(cfSpeed, kCFNumberIntType, &speed);
+  CFRelease(cfCpuPowerStatus);
+
+  BAZEL_LOG(USER) << "cpu speed anomaly: " << speed;
+
+  return speed;
+}
+
+}  // namespace blaze_jni
diff --git a/src/main/native/unix_jni.cc b/src/main/native/unix_jni.cc
index 09cc59a..6264b99 100644
--- a/src/main/native/unix_jni.cc
+++ b/src/main/native/unix_jni.cc
@@ -1498,4 +1498,56 @@
   }
 }
 
+jobject g_cpu_speed_module;
+
+/*
+ * Class:     Java_com_google_devtools_build_lib_platform_SystemCPUSpeedModule
+ * Method:    registerJNI
+ * Signature: ()I
+ */
+extern "C" JNIEXPORT void JNICALL
+Java_com_google_devtools_build_lib_platform_SystemCPUSpeedModule_registerJNI(
+    JNIEnv *env, jobject local_object) {
+
+  if (g_cpu_speed_module != nullptr) {
+    PostAssertionError(
+        env, "Singleton SystemCPUSpeedModule already registered");
+    return;
+  }
+
+  JavaVM *java_vm = GetJavaVM(env);
+  if (java_vm == nullptr) {
+    PostAssertionError(
+        env, "Unable to get javaVM registering SystemCPUSpeedModule");
+    return;
+  }
+
+  g_cpu_speed_module = env->NewGlobalRef(local_object);
+  if (g_cpu_speed_module == nullptr) {
+    PostAssertionError(
+        env, "Unable to create global ref for SystemCPUSpeedModule");
+    return;
+  }
+  portable_start_cpu_speed_monitoring();
+}
+
+void cpu_speed_callback(int speed) {
+  if (g_cpu_speed_module != nullptr) {
+    PerformIntegerValueCallback(g_cpu_speed_module, "cpuSpeedCallback", speed);
+  }
+}
+
+/*
+ * Class:     Java_com_google_devtools_build_lib_platform_SystemCPUSpeedModule
+ * Method:    cpuSpeed
+ * Signature: ()I
+ *
+ * Returns 1-100 to represent CPU speed. Returns -1 in case of error.
+ */
+extern "C" JNIEXPORT jint JNICALL
+Java_com_google_devtools_build_lib_platform_SystemCPUSpeedModule_cpuSpeed(
+    JNIEnv *env, jclass) {
+  return portable_cpu_speed();
+}
+
 }  // namespace blaze_jni
diff --git a/src/main/native/unix_jni.h b/src/main/native/unix_jni.h
index 8060e2d..3e231fc 100644
--- a/src/main/native/unix_jni.h
+++ b/src/main/native/unix_jni.h
@@ -169,6 +169,17 @@
 // monitoring when a disk space alert happens.
 extern void disk_space_callback(DiskSpaceLevel level);
 
+// Starts up any infrastructure needed to do cpu speed monitoring.
+// May be called more than once.
+void portable_start_cpu_speed_monitoring();
+
+// Returns the current CPU speed. Return -1 in case of error.
+int portable_cpu_speed();
+
+// Declaration for callback function that is called by cpu speed
+// monitoring when a cpu speed alert happens.
+extern void cpu_speed_callback(int speed);
+
 }  // namespace blaze_jni
 
 #endif  // BAZEL_SRC_MAIN_NATIVE_UNIX_JNI_H__
diff --git a/src/main/native/unix_jni_bsd.cc b/src/main/native/unix_jni_bsd.cc
index 9f65854..6dd5068 100644
--- a/src/main/native/unix_jni_bsd.cc
+++ b/src/main/native/unix_jni_bsd.cc
@@ -161,4 +161,13 @@
   // Currently not implemented.
 }
 
+void portable_start_cpu_speed_monitoring() {
+  // Currently not implemented.
+}
+
+int portable_cpu_speed() {
+  // Currently not implemented.
+  return -1;
+}
+
 }  // namespace blaze_jni
diff --git a/src/main/native/unix_jni_linux.cc b/src/main/native/unix_jni_linux.cc
index 42221bc..63a204f 100644
--- a/src/main/native/unix_jni_linux.cc
+++ b/src/main/native/unix_jni_linux.cc
@@ -136,4 +136,13 @@
   // Currently not implemented.
 }
 
+void portable_start_cpu_speed_monitoring() {
+  // Currently not implemented.
+}
+
+int portable_cpu_speed() {
+  // Currently not implemented.
+  return -1;
+}
+
 }  // namespace blaze_jni
diff --git a/src/main/native/windows/BUILD b/src/main/native/windows/BUILD
index 8588762..934c235 100644
--- a/src/main/native/windows/BUILD
+++ b/src/main/native/windows/BUILD
@@ -59,6 +59,7 @@
         "jni-util.h",
         "processes-jni.cc",
         "sleep_prevention_jni.cc",
+        "system_cpu_speed_monitor_jni.cc",
         "system_disk_space_monitor_jni.cc",
         "system_load_advisory_monitor_jni.cc",
         "system_memory_pressure_jni.cc",
diff --git a/src/main/native/windows/system_cpu_speed_monitor_jni.cc b/src/main/native/windows/system_cpu_speed_monitor_jni.cc
new file mode 100644
index 0000000..f2eb055
--- /dev/null
+++ b/src/main/native/windows/system_cpu_speed_monitor_jni.cc
@@ -0,0 +1,46 @@
+// Copyright 2022 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+
+#include <windows.h>
+
+#include "src/main/native/jni.h"
+
+/*
+ * Class:     Java_com_google_devtools_build_lib_platform_SystemCPUSpeedModule
+ * Method:    registerJNI
+ * Signature: ()V
+ */
+extern "C" JNIEXPORT void JNICALL
+Java_com_google_devtools_build_lib_platform_SystemCPUSpeedModule_registerJNI(
+    JNIEnv *env, jobject local_object) {
+  // Currently not implemented.
+}
+
+/*
+ * Class:     Java_com_google_devtools_build_lib_platform_SystemCPUSpeedModule
+ * Method:    cpuSpeed
+ * Signature: ()I
+ *
+ * Returns 1-100 to represent CPU speed. Returns -1 in case of error.
+ */
+extern "C" JNIEXPORT jint JNICALL
+Java_com_google_devtools_build_lib_platform_SystemCPUSpeedModule_cpuSpeed(
+    JNIEnv *env, jclass) {
+  // Currently not implemented.
+  return -1;
+}
diff --git a/src/test/java/com/google/devtools/build/lib/platform/BUILD b/src/test/java/com/google/devtools/build/lib/platform/BUILD
index d0cb0c7..aad8713 100644
--- a/src/test/java/com/google/devtools/build/lib/platform/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/platform/BUILD
@@ -108,3 +108,26 @@
         "@bazel_tools//tools/java/runfiles",
     ],
 )
+
+java_test(
+    name = "SystemCPUSpeedEventTest",
+    timeout = "short",
+    srcs = ["SystemCPUSpeedEventTest.java"],
+    data = select({
+        "//src/conditions:darwin": [
+            "//src/test/java/com/google/devtools/build/lib/platform/darwin:notifier",
+        ],
+        "//conditions:default": [
+        ],
+    }),
+    deps = [
+        "//src/main/java/com/google/devtools/build/lib:runtime",
+        "//src/main/java/com/google/devtools/build/lib/platform:system_cpu_speed_module",
+        "//src/main/java/com/google/devtools/build/lib/util:os",
+        "//src/test/java/com/google/devtools/build/lib/buildtool/util",
+        "//third_party:guava",
+        "//third_party:junit4",
+        "//third_party:truth",
+        "@bazel_tools//tools/java/runfiles",
+    ],
+)
diff --git a/src/test/java/com/google/devtools/build/lib/platform/SystemCPUSpeedEventTest.java b/src/test/java/com/google/devtools/build/lib/platform/SystemCPUSpeedEventTest.java
new file mode 100644
index 0000000..c51ac8d
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/platform/SystemCPUSpeedEventTest.java
@@ -0,0 +1,78 @@
+// Copyright 2022 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.build.lib.platform;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.eventbus.Subscribe;
+import com.google.devtools.build.lib.buildtool.util.BuildIntegrationTestCase;
+import com.google.devtools.build.lib.runtime.BlazeModule;
+import com.google.devtools.build.lib.runtime.BlazeRuntime;
+import com.google.devtools.build.lib.runtime.CommandEnvironment;
+import com.google.devtools.build.lib.util.OS;
+import com.google.devtools.build.runfiles.Runfiles;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests {@link SystemCPUSpeedEvent} by sending fake notifications. */
+@RunWith(JUnit4.class)
+public final class SystemCPUSpeedEventTest extends BuildIntegrationTestCase {
+  static class SystemCPUSpeedEventListener extends BlazeModule {
+    public int cpuSpeedEventCount = 0;
+
+    @Override
+    public void beforeCommand(CommandEnvironment env) {
+      env.getEventBus().register(this);
+    }
+
+    @Subscribe
+    public void cpuSpeedEvent(SystemCPUSpeedEvent event) {
+      ++cpuSpeedEventCount;
+    }
+  }
+
+  private final SystemCPUSpeedEventListener eventListener = new SystemCPUSpeedEventListener();
+
+  @Override
+  protected BlazeRuntime.Builder getRuntimeBuilder() throws Exception {
+    return super.getRuntimeBuilder()
+        .addBlazeModule(eventListener)
+        .addBlazeModule(new SystemCPUSpeedModule());
+  }
+
+  @Test
+  public void testCPUSpeed() throws Exception {
+    Assume.assumeTrue(OS.getCurrent() == OS.DARWIN);
+    Runfiles runfiles = Runfiles.create();
+    String notifierFilePath =
+        runfiles.rlocation(
+            "io_bazel/src/test/java/com/google/devtools/build/lib/platform/darwin/notifier");
+    write(
+        "system_cpuspeed_event/BUILD",
+        "genrule(",
+        "  name = 'fire_cpuspeed_notifications',",
+        "  outs = ['fire_cpuspeed_notifications.out'],",
+        "  cmd = '" + notifierFilePath + " com.google.bazel.test.cpuspeed 80 > $@ && ' + ",
+        "        '" + notifierFilePath + " com.google.bazel.test.cpuspeed 60 >> $@ && ' + ",
+        "        '" + notifierFilePath + " com.google.bazel.test.cpuspeed 70 >> $@ && ' + ",
+        "        '" + notifierFilePath + " com.google.bazel.test.cpuspeed 40 >> $@ && ' + ",
+        "        '" + notifierFilePath + " com.google.bazel.test.cpuspeed 50 >> $@',",
+        ")");
+    buildTarget("//system_cpuspeed_event:fire_cpuspeed_notifications");
+    assertThat(eventListener.cpuSpeedEventCount).isAtLeast(5);
+  }
+}